@@ -358,6 +358,17 @@ in N+1 — but never deleted": a retired entry is skipped by the loader
358358fixture CI. Live-window entries (currently the protocol-15 ADR-0089 visibility
359359aliases) stay load-active until they graduate.
360360
361+ > ** Superseded for metadata at rest** (2026-07-31, #3903 ; 2026-09-13, #12772 ).
362+ > The paragraph above states the rule for the ** authoring** load path and for
363+ > nothing else, and a reader who stops here carries it to seams where it is
364+ > false. Retirement is an authoring-surface event: the loader passes a retired
365+ > entry over so a live author is taught the canonical spelling. Metadata at rest
366+ > has no author to teach, so it replays the full chain, retired entries
367+ > included — ` sys_metadata ` rows unconditionally (the 2026-07-31 addendum
368+ > below), and a compiled artifact inside the versioned window its own manifest
369+ > opens (the 2026-09-13 addendum below). Kept in place rather than rewritten,
370+ > because a superseded decision is still a record (Prime Directive #13 ).
371+
361372### Ratified: the pre-launch launch-window exemption (majors 12–15)
362373
363374Majors 12–14 shipped breaks as ** pre-launch one-step changes with no alias
@@ -825,3 +836,93 @@ their PR touched.
825836lands, per the ruling, and one of them (` declare-search-clone-response-contracts ` )
826837fails predicates 2 and 3 outright — it bumps ` @objectstack/spec ` and adds
827838` packages/spec/src/api/protocol.zod.ts ` , so it is not a member of this class.
839+
840+ ## Addendum (2026-09-13) — the artifact-ingestion door opens a versioned window (#12772 )
841+
842+ The addenda above reach authored source and metadata at rest in ` sys_metadata ` .
843+ A third seam was reached by neither: a ** compiled artifact**
844+ (` objectstack build ` → ` dist/objectstack.json ` ) is data at rest * with a version
845+ stamp* , and nothing replayed the chain before its strict parse. Measured on
846+ #12772 : an app built by its pinned released 17.1.0 toolchain carried ** 75**
847+ occurrences of ` allowPurge ` / ` allowRestore ` that its own source never wrote —
848+ the released builder injected the then-legal permission bits — and booting that
849+ artifact on a 17.2.0 runtime was refused at the door by the #12497 tombstone.
850+ ` os migrate meta ` could not help: it targets sources, not built artifacts, so
851+ the only operator remedy found was hand-editing the artifact JSON. The policy
852+ that closed it shipped inside the v17 line; it lived in the issue and in no
853+ addendum here, which is the omission this entry corrects (#17894 ).
854+
855+ ** The ruling** (#12772 triage, comment ` 5443380108 ` , 2026-08-27), verbatim:
856+
857+ > So the conversion must be a * versioned forward conversion keyed off the
858+ > artifact's authored ` specVersion ` * , ⛔ not an unconditional strip — an
859+ > unconditional strip becomes wrong again the moment M2 lands and the keys are
860+ > legal once more.
861+
862+ The M2 half is the load-bearing reason and not a stylistic one: #12497 retired
863+ these keys with the M2 anchor left open (#1883 , ` pm:on-hold ` ), so an
864+ unconditional strip would start deleting legal metadata the day the keys return.
865+
866+ - ** The window, and what keys it.** The policy is one function —
867+ ` packages/metadata-core/src/artifact-forward-conversion.ts#applyArtifactForwardConversions `
868+ — and it compares two versions: the ** floor** of the range the artifact's
869+ manifest declares (` engines.protocol ` , ADR-0025) and the ` @objectstack/spec `
870+ version the process actually runs. ` floor < runtime ` replays the FULL chain,
871+ retired entries included, before the strict parse — the artifact is the
872+ "consumer arriving late" D3 keeps every conversion forever for. `floor >=
873+ runtime` replays nothing: the artifact claims the current or a newer surface,
874+ and the strict parse, tombstones included, stays the authority. That branch is
875+ what makes the window * versioned rather than a blanket amnesty* , and it is the
876+ branch the M2 return needs. No declared range replays (an artifact of unknown
877+ age is old data at rest, and conversions only rewrite shapes they positively
878+ recognize); an unresolvable runtime version replays nothing, because amnesty
879+ rests on positive version evidence.
880+ - ** ⚠️ The shipped key is the DECLARED FLOOR, not the authored version.** The
881+ ruling says "authored ` specVersion ` "; what an artifact manifest actually
882+ carries is a protocol * range* , so the implementation keys off that range's
883+ floor. The difference is not cosmetic — ` ^17.0.0 ` is the range
884+ ` create-objectstack ` stamps, so an app authored today lands inside the window.
885+ Recorded here as the as-built reading, because the next two bullets are its
886+ consequences.
887+ - ** One policy, not per-door copies** (#12772 , the card's own shape). Two
888+ in-tree doors consume that one function: the metadata plugin's artifact
889+ ingestion (` packages/metadata/src/plugin.ts ` ) and the runtime app plugin's
890+ security bundle (` packages/runtime/src/app-plugin.ts ` ). ⛔ A door that
891+ re-implements the comparison is the drift this bullet exists to forbid.
892+ - ** ⛔ The window does NOT admit default flips** (narrowed later, on #16693 and
893+ #17885 ). A retirement whose old shape has no live meaning left is safe to
894+ replay here. A DEFAULT FLIP is not: its old shape still parses, still means
895+ something on today's authoring surface, and rewriting it is a
896+ reinterpretation — sound only where "this input predates the flip" is a fact,
897+ and at this door it is a guess, because the key is a declared floor and not an
898+ age. The exclusion list is
899+ ` packages/metadata-core/src/artifact-forward-conversion.ts#DEFAULT_FLIPS_NOT_REPLAYED_HERE ` ;
900+ an entry on it keeps firing at the stored-row seams and under
901+ ` os migrate meta ` , where the old meaning is the only meaning.
902+ - ** Where the window starts, relative to the schema.** A retired key carrying
903+ only its * emitted default* parses as inert residue and is stripped silently at
904+ the SCHEMA layer (the ` acceptRetiredDefaultResidue ` stage, ruled on #12845 and
905+ recorded in this card's review, comments ` 5448522858 ` / ` 5448958115 ` ); the
906+ door's window is not what rescues that class. What the window admits is the
907+ class the schema still refuses: a retired key carrying a NON-default value,
908+ which keeps the #12497 tombstone at the current version and is convertible
909+ only because the artifact says it was authored before the retirement.
910+ - ** The flag's live inventory** , measured for this entry: three
911+ ` includeRetired: true ` literals in runtime source — the artifact policy above,
912+ the automation engine's stored-flow canonicalization seam
913+ (` packages/services/service-automation/src/engine.ts#canonicalizeStoredFlow ` ,
914+ which ` registerFlow ` calls), and
915+ ` packages/spec/src/conversions/stored.ts#applyConversionsToStoredItem ` — and
916+ ** four** runtime callers, because the artifact policy is called by two doors.
917+ ` stored.ts ` ** pins** the flag rather than offering it
918+ (` packages/spec/src/conversions/stored.ts#StoredConversionOptions ` is an
919+ ` Omit ` over ` includeRetired ` ), so no caller can turn the stored-row replay
920+ off.
921+
922+ ** What this does not decide.** The cloud composed-artifact door
923+ (` OS_COMPOSED_ARTIFACT_URL ` ) is a second door in another repository and was
924+ recorded, not filed, by the #12772 triage: it needs the same treatment and
925+ should CONSUME this policy rather than reimplement it. Nothing here changes the
926+ protocol handshake's jurisdiction either — a cross-major gap refuses at
927+ ` checkProtocolCompat ` before conversion could matter, and this window is for
928+ within-line retirements (17.1 → 17.2), which are the case that created it.
0 commit comments