Relationships
↔ sibling #3200#3194 stagecraft: upgrade stored factory definitions on read, so a format change does not break work in progress
Opened by skunk-ape · 10/8/2026· Shipped 10/8/2026
Problem
Every change to the factory definition format is a breaking change today, and it breaks work already in progress.
- A factory definition carries
schemaVersion: 1(DEFINITION_SCHEMA_VERSION,engine/definition_schema.ts), and a run record carriesschemaVersion: 1(RUN_SCHEMA_VERSION,engine/run_record.ts). Anything with another version is refused. Nothing can read an older version. - A work item pins a copy of its factory definition when it is claimed. Every later step re-parses that copy with the current schema and checks it against the digest the run recorded (
readPinned/checkPinned,engine/work_item_ops.ts). If the schema stops accepting a field, every work item in progress fails with "the pinned definition is invalid". Rewriting the pinned copy changes its digest, so that fails too. - DESIGN.md says swamp never upgrades stored data, only
globalArguments. Stagecraft does not use swamp's modelupgradeshook (upgradeAttributes), so a definition someone wrote is not upgraded either.
So stagecraft cannot change its definition format without stranding in-progress work items and making everyone hand-edit their definitions. #3190 is the first change that needs this: it replaces bindings with let plus passAsInputs.
Request
Upgrade stored definitions as they are read, through a chain of versioned steps that each keep the meaning exactly, so that a format change upgrades users instead of breaking them.
- Upgrade steps. One pure JSON-to-JSON function per version step (v1 to v2, v2 to v3, and so on). Each keeps the definition's meaning exactly.
- One read path for every stored definition (pinned copies, the factory's definition, the summary report): read
schemaVersion, verify the digest against the stored form, apply the upgrade steps in memory, then parse with the current schema. The rest of the engine sees only the current form. - Pinned copies are never rewritten. A work item claimed on v1 keeps its v1 copy and its digest, and keeps running after an upgrade.
- A definition someone wrote is upgraded by swamp: the factory model type declares
upgradeswhoseupgradeAttributesrun the same upgrade steps, so nobody hand-edits it. - A version newer than the runtime is refused with a clear message, for example "this definition needs a newer stagecraft".
- Fixtures at each old version are tested on every release: each must read, upgrade to the expected current form, and give the same dispatch as before. Every future format change adds its step and a fixture.
The same pattern should be available for run records (RUN_SCHEMA_VERSION) when they first need to change. Building that is not required here.
Open questions for planning
- The digest is computed over the parsed value at pin time (
pindigests the parsed definition and storesjsonSafeof it). Confirm with a test that the digest of the stored copy as-is equals the recorded digest for existing records, so the digest check can run before the upgrade. - Anything that compares a factory's current definition digest with a work item's pinned digest (re-pin, a changed-definition notice) would see an upgraded definition as changed. Decide whether the comparison should be between upgraded forms.
- Confirm when swamp runs
upgradeAttributesand whether it writes the result back to the model's file.
Done
- The definition can move to
schemaVersion: 2with an upgrade step, and a work item pinned at v1 before the change still dispatches, records and advances afterwards. - A factory definition at v1 is upgraded by swamp without anyone editing it.
- Old-version fixtures are part of the test suite.
- DESIGN.md documents the rule: a format change ships an upgrade step that keeps the meaning, plus a fixture.
#3190 (let + passAsInputs, {{binding}} in a stage's target) builds on this as the v1 to v2 step.
Shipped
Click a lifecycle step above to view its details.