Repository navigation
Commit 7e8575d
authored
feat(deploy): the two-plane boot — the train stages the approved snapshot; ADR 0043 (the crossroads) (#781)
* feat(everything-dev): boot-snapshot + composition-lockfile wire schemas (ADR 0041/0042)
Ticket 01 of both follow-on sprints (boot-snapshot, composition-lockfile):
the two wire interfaces land before any wiring, so the host (coordinator
write on adopt, degraded boot load), the CLI producers (bos lock, the
deploy train), and the verification tools (smoke, sentinel) import one
schema per artifact.
- everything-dev/snapshot (ADR 0041): the approved boot snapshot — the
persisted, verified serving state. fingerprint = pointerFingerprint of
the stored config; the loader recomputes it (the file's own integrity
check, independent of the registry).
- everything-dev/lockfile (ADR 0042): bos.lock.json — the frozen exact
composition. The remotes record is written in walk order so the
fingerprint recomputes from the stored record alone
(lockfileFingerprint == pointerFingerprint), pinned by golden tests.
Both schemas are strict (unknown keys refuse on load); exports, golden
tests, and changesets included.
* feat(cli): bos lock — freeze the resolved composition; the runtime map carries lockfile provenance (ADR 0042 ticket 02)
bos lock resolves the authored config through the same resolution session
the deploy train uses and freezes the result into bos.lock.json
(atomically — temp + rename). bos lock --check re-resolves and exits 1 on
drift: the CI gate that keeps the committed lockfile honest. The writer is
one seam (writeLockfile) shared with the deploy train's later producer leg.
The runtime map derives its provenance from the lockfile when one exists:
the generated inventory carries the frozen fingerprint, release, and
resolved-at, and names every source/backend whose serving digest differs
from the frozen one (lockfile.drift) with a loud 'run bos lock' warning —
the explainer can no longer drift silently from the frozen composition.
Serving digests stay truthful (never replaced by frozen values); a
missing or corrupt lockfile degrades the provenance, not the build.
everything-dev/lockfile gains readLockfileIfExists and writeLockfile;
the producer decision table (write/unchanged/--check missing, matching,
drifting) is pinned in the lockfile suite, and the runtime-map suite
covers provenance presence, drift naming, digest truthfulness, and the
no-lockfile omission.
* test(everything-dev): the handler-structure guard counts registerLock — bos lock is a contract route like the rest
* feat(start): boot consumes bos.lock.json — the frozen composition is the authority for remote coordinates (ADR 0042 ticket 03)
A registry start with a bos.lock.json present for its runtime ref applies
the frozen composition after resolution: missing pins are stamped from the
frozen descriptors, drifted coordinates are overridden, and every stamp
and disagreement rides the start summary as a loud warning. The boot
never fails because of the lockfile — enforcement lives in CI (bos lock
--check), not in the restart path; a stale lockfile degrades loudly, it
does not brick.
applyCompositionLockfile is pure and seam-free: runtime-ref guard (a
foreign lockfile is not applied, one drift note), pluginRef and
descriptor agreement short-circuits, missing-pin stamping, drift-on-
mismatch stamping, and a remove-detected drift entry when the resolved
composition no longer carries a remote the lockfile freezes. Clone-along-
the-path keeps untouched branches identity-preserved.
Deliberate follow-up: bos dev session parity (the dev session's
resolution is local-first where the chain is cheap; the production start
is where frozen coordinates matter) — noted for the crossroads ticket
with the boot-snapshot stack, where boot-time resolution elimination
lands as one design.
* feat(deploy): the train freezes what it publishes — DeployRecord names the lockfile digest (ADR 0042 ticket 04)
The publish train composes the lockfile from the exact publish payload
before submitting: the per-deploy audit record (deploys/<ts>.json) gains
lockfile: { digest, fingerprint } — a deployment record answers 'what
exact composition was live' — and after the publish state is confirmed
(or an already-published idempotent run) the train writes bos.lock.json
atomically (healing a deleted or stale file). The leg is non-fatal: the
lockfile is provenance, never a gate; a composition that cannot be
frozen still publishes, with a dim skip note.
The registry's fingerprint module consolidates both identity hashes:
pointerFingerprint (THE composition identity — lockfile, deploy records,
watch change-detection) and deploymentFingerprint (moved here from the
host: the serving-generation payload hash stamped into the client config
and used by adopt comparison), with their identity contract documented
and their semantic merge recorded as a seam — both sides of every
existing comparison must move together, and nothing user-visible calls
for that move today.
everything-dev/lockfile gains digestOfLockfile + writeLockfileSync (the
train's publish path is sync-fs).
* feat(cli): bos promote <block-height> — activate a recorded deployment without rebuilding (ADR 0042 ticket 05)
The same verified activation path as bos rollback --version — publish
history targeting, SRI pin verification, boot-payload check, pointer
restore, and the train's lockfile leg freezing the promoted composition
into bos.lock.json (the deployment record named its digest all along) —
framed as promotion: the payload republishes verbatim with NO
rolledBackFrom brand (a promotion is not a rollback), an already-live
target is an idempotent no-op rather than a refusal (the train's
already-published path handles it), and the version is required —
promotion targets a record, not 'previous'.
The Railway image leg stays in CI's deploy workflow by design: the CLI
holds no Railway credential, and the image plane's restore is the
deploy workflow's digest handover. The config/pointer/lockfile plane is
complete from here.
* feat(deploy): the two-plane boot — the train stages the approved snapshot; ADR 0043 (the crossroads)
The control plane produces and activates; the application runtime starts
from a digest-verified approved artifact. Both sprints shipped the halves
(the snapshot gives restarts a verified serving state; the lockfile gives
CI and the boot a composition identity) — this ADR names the contract
they converge on and ships its implementation slice:
- The deploy train stages the approved snapshot post-confirm:
bootSnapshotOf over the exact publish payload, stamped with the release
thin tag (when the release leg ran) and the lockfile digest, written
under .bos/snapshot/<account>/<gateway>.json — so restarts of the
published runtime boot from the staged state instead of resolving.
Non-fatal, like every provenance leg.
- BootSnapshotSchema gains the optional lockfileDigest — the staging
stamp tying an approved generation to the deployment record that named
it. Enforcement (requiring the match when a lockfile exists) is
deferred; the strict-reader downgrade window is accepted per the ADR
0040 posture.
- Fresh activations stay fail-loud; remote-tier staging rides the hosting
adapter (recorded seam).
* fix(deploy): await the staging leg — a floating write settles after the caller's teardown; unicorn no-useless-spread cleanups
stageBootSnapshot was sync and fired the async writeBootSnapshot without
awaiting: the promise settled after the aborted-train test's teardown had
removed the temp dir, and the escaped ENOENT rejection killed the suite
as an unhandled rejection. The leg is now async and awaited at both call
sites (already-published + post-confirm).
Also clears the unicorn(no-useless-spread) warnings on the reviewed
lines — the conditional registry spreads become plain conditional
arguments (BootCheckOptions defaults to {}).
* feat(start): boot consumes bos.lock.json — the frozen composition is the authority for remote coordinates (ADR 0042 ticket 03)
A registry start with a bos.lock.json present for its runtime ref applies
the frozen composition after resolution: missing pins are stamped from the
frozen descriptors, drifted coordinates are overridden, and every stamp
and disagreement rides the start summary as a loud warning. The boot
never fails because of the lockfile — enforcement lives in CI (bos lock
--check), not in the restart path; a stale lockfile degrades loudly, it
does not brick.
applyCompositionLockfile is pure and seam-free: runtime-ref guard (a
foreign lockfile is not applied, one drift note), pluginRef and
descriptor agreement short-circuits, missing-pin stamping, drift-on-
mismatch stamping, and a remove-detected drift entry when the resolved
composition no longer carries a remote the lockfile freezes. Clone-along-
the-path keeps untouched branches identity-preserved.
Deliberate follow-up: bos dev session parity (the dev session's
resolution is local-first where the chain is cheap; the production start
is where frozen coordinates matter) — noted for the crossroads ticket
with the boot-snapshot stack, where boot-time resolution elimination
lands as one design.
* feat(deploy): the train freezes what it publishes — DeployRecord names the lockfile digest (ADR 0042 ticket 04)
The publish train composes the lockfile from the exact publish payload
before submitting: the per-deploy audit record (deploys/<ts>.json) gains
lockfile: { digest, fingerprint } — a deployment record answers 'what
exact composition was live' — and after the publish state is confirmed
(or an already-published idempotent run) the train writes bos.lock.json
atomically (healing a deleted or stale file). The leg is non-fatal: the
lockfile is provenance, never a gate; a composition that cannot be
frozen still publishes, with a dim skip note.
The registry's fingerprint module consolidates both identity hashes:
pointerFingerprint (THE composition identity — lockfile, deploy records,
watch change-detection) and deploymentFingerprint (moved here from the
host: the serving-generation payload hash stamped into the client config
and used by adopt comparison), with their identity contract documented
and their semantic merge recorded as a seam — both sides of every
existing comparison must move together, and nothing user-visible calls
for that move today.
everything-dev/lockfile gains digestOfLockfile + writeLockfileSync (the
train's publish path is sync-fs).
* feat(cli): bos promote <block-height> — activate a recorded deployment without rebuilding (ADR 0042 ticket 05)
The same verified activation path as bos rollback --version — publish
history targeting, SRI pin verification, boot-payload check, pointer
restore, and the train's lockfile leg freezing the promoted composition
into bos.lock.json (the deployment record named its digest all along) —
framed as promotion: the payload republishes verbatim with NO
rolledBackFrom brand (a promotion is not a rollback), an already-live
target is an idempotent no-op rather than a refusal (the train's
already-published path handles it), and the version is required —
promotion targets a record, not 'previous'.
The Railway image leg stays in CI's deploy workflow by design: the CLI
holds no Railway credential, and the image plane's restore is the
deploy workflow's digest handover. The config/pointer/lockfile plane is
complete from here.1 parent c797040 commit 7e8575d
8 files changed
Lines changed: 143 additions & 9 deletions
File tree
- .changeset
- docs/adr
- packages/everything-dev
- src/services
- deploy
- snapshot
- tests/unit
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
45 | 45 | | |
46 | 46 | | |
47 | 47 | | |
| 48 | + | |
48 | 49 | | |
49 | 50 | | |
50 | 51 | | |
| |||
56 | 57 | | |
57 | 58 | | |
58 | 59 | | |
59 | | - | |
| 60 | + | |
60 | 61 | | |
61 | 62 | | |
62 | 63 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
109 | 109 | | |
110 | 110 | | |
111 | 111 | | |
112 | | - | |
| 112 | + | |
113 | 113 | | |
114 | 114 | | |
115 | 115 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
40 | 40 | | |
41 | 41 | | |
42 | 42 | | |
| 43 | + | |
43 | 44 | | |
44 | 45 | | |
45 | 46 | | |
| |||
727 | 728 | | |
728 | 729 | | |
729 | 730 | | |
730 | | - | |
731 | | - | |
732 | | - | |
| 731 | + | |
733 | 732 | | |
734 | 733 | | |
735 | 734 | | |
| |||
739 | 738 | | |
740 | 739 | | |
741 | 740 | | |
742 | | - | |
743 | | - | |
744 | | - | |
| 741 | + | |
745 | 742 | | |
746 | 743 | | |
747 | 744 | | |
| |||
836 | 833 | | |
837 | 834 | | |
838 | 835 | | |
| 836 | + | |
839 | 837 | | |
840 | 838 | | |
841 | 839 | | |
842 | 840 | | |
843 | 841 | | |
844 | 842 | | |
845 | 843 | | |
846 | | - | |
| 844 | + | |
| 845 | + | |
847 | 846 | | |
848 | 847 | | |
849 | 848 | | |
| |||
1076 | 1075 | | |
1077 | 1076 | | |
1078 | 1077 | | |
| 1078 | + | |
| 1079 | + | |
| 1080 | + | |
| 1081 | + | |
| 1082 | + | |
| 1083 | + | |
| 1084 | + | |
| 1085 | + | |
| 1086 | + | |
| 1087 | + | |
| 1088 | + | |
| 1089 | + | |
| 1090 | + | |
| 1091 | + | |
| 1092 | + | |
| 1093 | + | |
| 1094 | + | |
| 1095 | + | |
| 1096 | + | |
| 1097 | + | |
| 1098 | + | |
| 1099 | + | |
| 1100 | + | |
| 1101 | + | |
| 1102 | + | |
| 1103 | + | |
| 1104 | + | |
| 1105 | + | |
| 1106 | + | |
| 1107 | + | |
| 1108 | + | |
| 1109 | + | |
| 1110 | + | |
1079 | 1111 | | |
1080 | 1112 | | |
1081 | 1113 | | |
| |||
1093 | 1125 | | |
1094 | 1126 | | |
1095 | 1127 | | |
| 1128 | + | |
1096 | 1129 | | |
1097 | 1130 | | |
1098 | 1131 | | |
| |||
1267 | 1300 | | |
1268 | 1301 | | |
1269 | 1302 | | |
| 1303 | + | |
1270 | 1304 | | |
1271 | 1305 | | |
1272 | 1306 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
55 | 55 | | |
56 | 56 | | |
57 | 57 | | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
58 | 65 | | |
59 | 66 | | |
60 | 67 | | |
| |||
94 | 101 | | |
95 | 102 | | |
96 | 103 | | |
| 104 | + | |
97 | 105 | | |
98 | 106 | | |
99 | 107 | | |
| |||
105 | 113 | | |
106 | 114 | | |
107 | 115 | | |
| 116 | + | |
108 | 117 | | |
109 | 118 | | |
110 | 119 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
207 | 207 | | |
208 | 208 | | |
209 | 209 | | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
0 commit comments