Skip to content

fly-deployment — A deployment is a projection of the repository model, and a reachable URL is its evidence

One canonical deployment object under .ai/repo/deployments/ describes the deployment; a typed Rust model parses and validates it; the container image, the Fly configuration, the smoke suite, the doctor checks, the documentation, the cockpit view and the CI jobs are all projections of that object and of the capability registry, and every one of them is checked for drift. The service runs on Fly.io on a single shared-CPU Machine that stops when idle, its health and readiness are verified from outside, the smoke suite is derived from the route registry rather than written twice, and the deployed URL answers.

PLANNED 0 of 32 issues done

ready
1
blocked
31
active
0
verify
0
done
0
cancelled
0

Problem

Majordomus has no deployment at all: no container, no hosting provider, no way for anyone who is not standing in front of this checkout to see the layer it serves. The obvious repair is the wrong one. A Dockerfile and a fly.toml written by hand would be a second description of facts the repository already holds — the port the server binds, the routes it registers, the assets the site build produces, the binary the workspace names — and every one of those pairs would drift the first time one half changed. The repository already refuses that shape everywhere else: capabilities are declared once and projected into CLI, HTTP, MCP, OpenAPI, docs and the site, and generate --check fails when a projection is stale. A deployment must arrive the same way or not at all.

Outcome

One canonical deployment object under .ai/repo/deployments/ describes the deployment; a typed Rust model parses and validates it; the container image, the Fly configuration, the smoke suite, the doctor checks, the documentation, the cockpit view and the CI jobs are all projections of that object and of the capability registry, and every one of them is checked for drift. The service runs on Fly.io on a single shared-CPU Machine that stops when idle, its health and readiness are verified from outside, the smoke suite is derived from the route registry rather than written twice, and the deployed URL answers.

Current state

No Dockerfile, no fly.toml, no deployment kind, no deploy capability module and no container test exist anywhere in the repository. The HTTP server binds loopback by default and warns when it does not. system.health exists as a capability; /healthz and /readyz do not. The site is built by one pipeline already and deployed to GitHub Pages by scripts/site-deploy.

Desired state

majordomus deploy validates the canonical object, verifies that every generated projection matches it, builds the pinned multi-stage Alpine image, deploys one Machine to the chosen region, waits for Fly's health checks, asserts the Machine inventory against the declared cost profile, runs the route-derived smoke suite against the public URL and writes a machine-readable report. Changing the region, the memory, the port or the Machine count means changing one file and regenerating.

Scope

  • .ai/repo/deployments
  • .ai/repo/adrs
  • .ai/repo/rules/project
  • .ai/repo/policy.yaml
  • apps/majordomus-cli/src/deploy
  • apps/majordomus-cli/src/capability/builtin
  • apps/majordomus-cli/src/http
  • deploy
  • fly.toml
  • share/kinds.yaml
  • share/allow
  • share/schemas
  • docs
  • site
  • test/cases
  • .github/workflows

Out of scope

  • Any dependency, in code, script, documentation, CI or default, on ~/dev/prismatic-platform
  • A provider abstraction covering hosts other than the one this repository deploys to
  • Kubernetes, Nomad, buildpacks, Nixpacks or a second container implementation
  • A persistent volume, a database or any stateful service, unless a measured requirement forces one
  • Mutating deployment operations exposed to unauthenticated HTTP or to the browser
  • Profile-guided optimisation

Acceptance criteria

  • One canonical deployment object is the only place the port, the region, the resources, the Machine count, the health routes, the build inputs and the budgets are stated
  • Every provider artifact — the Dockerfile, its ignore file and fly.toml — is generated, marked as generated with its source and its regeneration command, and fails generate --check when edited by hand
  • The deployment reads are declared once in the capability registry and appear in the CLI, the HTTP API, MCP, OpenAPI and the generated documentation without a second registration
  • The smoke suite is derived from the route registry; no second list of endpoints exists anywhere
  • The production image runs as a non-root user, contains no build toolchain, no repository history, no secret and no absolute developer path
  • The deployed service answers on its public HTTPS URL, its health and readiness checks pass from outside, and the Machine inventory matches the declared cost profile
  • Image size, binary size, build context size, cold start, resident memory and endpoint latency are measured, recorded as baselines and gated against unexplained regression
  • No Fly credential is written to the repository, to a Docker layer, to a generated file, to the site or to a log, and CI deploys with an app-scoped token rather than an account-wide one
  • Every rule this milestone states is enforced by a check that CI executes

Validation

  • bash test/run.sh
  • bin/majordomus doctor
  • cargo test --workspace --all-features
  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • majordomus generate --check
  • majordomus deploy doctor
  • fly config validate

Evidence required

  • canonical_object
  • typed_model
  • generated_projections
  • registry_derived_smoke
  • container_minimal
  • deployed_url
  • budgets_measured
  • secrets_isolated
  • rules_enforced

Risks

  • Generation can hide the Dockerfile from the people who must read it. The generated file has to stay idiomatic and reviewable; the source describes intent, the output describes mechanics, and a contributor who edits the output is told which file to change instead.
  • The route topology and the served surfaces are being rewritten on other branches under ADR 0013. A smoke suite pinned to today's paths would be stale on arrival, so it must read the route registry rather than a path list.
  • 256 MB is a hypothesis. It is held only as long as a measured run under that limit passes; the evidence, not the preference, decides the final number.
  • A deployment touches money. The first deploy must not leave two Machines running, and the Machine inventory is asserted rather than assumed.
  • The bootstrap credential comes from outside this repository. It may be used to obtain an authenticated session and nowhere else; anything else would make Majordomus depend on Prismatic Platform, which is forbidden.

Issues

Each one is an execution contract with its own acceptance criteria, validation command and required evidence. The wave column is the layer of the dependency graph it sits in.

issuestatuswavedepends ontitle
I0901 READY 0 The deployment description is a kind of the layer, not a provider file
I0902 BLOCKED 1 I0901 The deployment object becomes typed Rust that refuses nonsense
I0903 BLOCKED 2 I0902 Deployment reads are declared once and projected everywhere
I0904 BLOCKED 2 I0902 The listen address is one fact with two projections
I0905 BLOCKED 2 I0902 Liveness and readiness are registered capabilities, not router hacks
I0906 BLOCKED 3 I0904 Termination is graceful and proved against the real container
I0907 BLOCKED 2 I0902 The running process says what it is, from an allowlist
I0908 BLOCKED 2 I0902 A hosted process has no repository, and says so instead of failing
I0909 BLOCKED 3 I0902, I0904, I0905 The production image is a generated projection of the build specification
I0910 BLOCKED 4 I0909 The release profile is chosen by measurement, not by folklore
I0911 BLOCKED 4 I0909 A source change does not rebuild the world, and the context stays small
I0912 BLOCKED 4 I0909 The image serves the site the canonical pipeline produced
I0913 BLOCKED 3 I0902, I0903 The provider configuration is generated, and the cheap profile is declared
I0914 BLOCKED 4 I0903, I0909, I0913 One deployment core, one command, and a dry run that mutates nothing
I0915 BLOCKED 5 I0914 Deployment joins the diagnostics that already exist
I0916 BLOCKED 5 I0905, I0914 The smoke suite is derived from the route registry, never written twice
I0917 BLOCKED 5 I0914 A bad deployment can be undone deterministically
I0918 BLOCKED 2 I0902 The bootstrap credential is used once and belongs to nothing
I0919 BLOCKED 4 I0909 Nothing secret and nothing local leaves in the image
I0920 BLOCKED 4 I0909 The image's dependencies are known and its advisories are policy
I0921 BLOCKED 5 I0910, I0911 Size is measured first and budgeted second
I0922 BLOCKED 6 I0930 The memory hypothesis is tested, not asserted
I0923 BLOCKED 6 I0930 Cold start and endpoint cost are benchmark targets like everything else
I0924 BLOCKED 1 I0901 The decision is recorded where architecture decisions are recorded
I0925 BLOCKED 6 I0915 Every deployment rule is a check that runs
I0926 BLOCKED 4 I0903, I0913, I0924 The deployment documentation is generated and reachable
I0927 BLOCKED 3 I0903 The cockpit shows the deployment from the same data
I0928 BLOCKED 6 I0919, I0921, I0930 A pull request cannot merge a deployment that would not work
I0929 BLOCKED 8 I0918, I0931 Production deploys from CI with a token scoped to one application
I0930 BLOCKED 5 I0906, I0909, I0912 The real image runs under the real limits before anything is deployed
I0931 BLOCKED 7 I0913, I0914, I0916, I0917, I0918, I0922, I0930 The application exists, one machine runs it, and the URL answers
I0932 BLOCKED 9 I0920, I0923, I0925, I0926, I0927, I0928, I0929, I0931 The milestone is accepted on its evidence

The graph

fly-deployment dependency graphfrom .ai/repo/project
flowchart LR
    I0901["I0901<br/>The deployment description is a kind of the layer, not a provider file"]:::ready
    I0902["I0902<br/>The deployment object becomes typed Rust that refuses nonsense"]:::blocked
    I0903["I0903<br/>Deployment reads are declared once and projected everywhere"]:::blocked
    I0904["I0904<br/>The listen address is one fact with two projections"]:::blocked
    I0905["I0905<br/>Liveness and readiness are registered capabilities, not router hacks"]:::blocked
    I0906["I0906<br/>Termination is graceful and proved against the real container"]:::blocked
    I0907["I0907<br/>The running process says what it is, from an allowlist"]:::blocked
    I0908["I0908<br/>A hosted process has no repository, and says so instead of failing"]:::blocked
    I0909["I0909<br/>The production image is a generated projection of the build specification"]:::blocked
    I0910["I0910<br/>The release profile is chosen by measurement, not by folklore"]:::blocked
    I0911["I0911<br/>A source change does not rebuild the world, and the context stays small"]:::blocked
    I0912["I0912<br/>The image serves the site the canonical pipeline produced"]:::blocked
    I0913["I0913<br/>The provider configuration is generated, and the cheap profile is declared"]:::blocked
    I0914["I0914<br/>One deployment core, one command, and a dry run that mutates nothing"]:::blocked
    I0915["I0915<br/>Deployment joins the diagnostics that already exist"]:::blocked
    I0916["I0916<br/>The smoke suite is derived from the route registry, never written twice"]:::blocked
    I0917["I0917<br/>A bad deployment can be undone deterministically"]:::blocked
    I0918["I0918<br/>The bootstrap credential is used once and belongs to nothing"]:::blocked
    I0919["I0919<br/>Nothing secret and nothing local leaves in the image"]:::blocked
    I0920["I0920<br/>The image's dependencies are known and its advisories are policy"]:::blocked
    I0921["I0921<br/>Size is measured first and budgeted second"]:::blocked
    I0922["I0922<br/>The memory hypothesis is tested, not asserted"]:::blocked
    I0923["I0923<br/>Cold start and endpoint cost are benchmark targets like everything else"]:::blocked
    I0924["I0924<br/>The decision is recorded where architecture decisions are recorded"]:::blocked
    I0925["I0925<br/>Every deployment rule is a check that runs"]:::blocked
    I0926["I0926<br/>The deployment documentation is generated and reachable"]:::blocked
    I0927["I0927<br/>The cockpit shows the deployment from the same data"]:::blocked
    I0928["I0928<br/>A pull request cannot merge a deployment that would not work"]:::blocked
    I0929["I0929<br/>Production deploys from CI with a token scoped to one application"]:::blocked
    I0930["I0930<br/>The real image runs under the real limits before anything is deployed"]:::blocked
    I0931["I0931<br/>The application exists, one machine runs it, and the URL answers"]:::blocked
    I0932["I0932<br/>The milestone is accepted on its evidence"]:::blocked
    I0901 --> I0902
    I0901 --> I0924
    I0902 --> I0903
    I0902 --> I0904
    I0902 --> I0905
    I0902 --> I0907
    I0902 --> I0908
    I0902 --> I0909
    I0902 --> I0913
    I0902 --> I0918
    I0903 --> I0913
    I0903 --> I0914
    I0903 --> I0926
    I0903 --> I0927
    I0904 --> I0906
    I0904 --> I0909
    I0905 --> I0909
    I0905 --> I0916
    I0906 --> I0930
    I0909 --> I0910
    I0909 --> I0911
    I0909 --> I0912
    I0909 --> I0914
    I0909 --> I0919
    I0909 --> I0920
    I0909 --> I0930
    I0910 --> I0921
    I0911 --> I0921
    I0912 --> I0930
    I0913 --> I0914
    I0913 --> I0926
    I0913 --> I0931
    I0914 --> I0915
    I0914 --> I0916
    I0914 --> I0917
    I0914 --> I0931
    I0915 --> I0925
    I0916 --> I0931
    I0917 --> I0931
    I0918 --> I0929
    I0918 --> I0931
    I0919 --> I0928
    I0920 --> I0932
    I0921 --> I0928
    I0922 --> I0931
    I0923 --> I0932
    I0924 --> I0926
    I0925 --> I0932
    I0926 --> I0932
    I0927 --> I0932
    I0928 --> I0932
    I0929 --> I0932
    I0930 --> I0922
    I0930 --> I0923
    I0930 --> I0928
    I0930 --> I0931
    I0931 --> I0929
    I0931 --> I0932
    classDef done stroke:#16a34a,fill:#052e16,stroke-width:2px
    classDef active stroke:#2563eb,fill:#eff6ff,stroke-width:2px
    classDef verify stroke:#7c3aed,fill:#f5f3ff,stroke-width:2px
    classDef ready stroke:#0891b2,fill:#ecfeff,stroke-width:2px
    classDef blocked stroke:#b45309,fill:#fffbeb,stroke-width:2px
    classDef cancelled stroke:#6b7280,fill:#f9fafb,stroke-width:2px

Canonical record: .ai/repo/project/milestones/fly-deployment.yaml. Read it back with majordomus plan show fly-deployment.