Skip to content

Commit c44c8ac

Browse files
docs(agents): stored rows replay only what the installed release still carries (#29011)
<!-- claude-projects-attribution: {"github_login":"huangyiirene"} --> _Requested by **Irene** · [project thread](https://claude.ai/code/project/chan_01TJXtQ1tVD26ecLDZxFS67x?thread=cmsg_01TJXtQ1tVD26ecLDZxFS67xJhE8WhApnrf9rNZxcXpcQx)_ Fixes #28924 Clause-②: no Metadata-protocol: none ## 维护者速读 - **改了什么**:三处文字(AGENTS.md 第 12 条首要原则的最后三行,以及 `stored.ts`、`stored-migration.ts` 两个文件头注释)不再说"存储的转换永远重放";代码和行为零改动。 - **为什么改**:18.3 起 ≤16.x 的退役转换已被删除,旧说法会让 AI 告诉用户 16 时代的存储行仍然安全,与 `.changeset/25767-retire-16x-retirement-kits.md` 的 Stored rows 条目矛盾。 - **风险与回滚**:纯文字,无运行时风险;回滚即 revert 这一个 commit。 - **先看**:① `AGENTS.md` 第 253-255 行(Tier H,行数保持 3 行不变);② `packages/metadata-protocol/src/stored-migration.ts` 头注释(含对 "Not load-bearing" 段的收窄);③ `packages/spec/src/conversions/stored.ts` 头注释。 - **两分钟验证**:`git diff origin/main -- AGENTS.md`,预期只见 3 行删、3 行增,第 12 条总行数不变。 - **需要你做的**:批准(APPROVE)本 PR —— 它触及 AGENTS.md,属 Tier H,批准后由席位落地。 ## What changes Three texts that still stated the pre-18.3 policy, that every stored conversion replays forever, now say what ADR-0087 as amended (ruling 6434038284) says: rehydration replays the chain the **installed release** carries, retirement kits age out one major after their retirement, and the stored pass (`os migrate meta --stored --apply`) must run on a release that still carries a row's conversions, before the deployment moves to the release that drops them. 1. **`AGENTS.md` Prime Directive #12, lines 253-255.** Governed Tier H text. The rewrite keeps the directive at exactly its line count (3 lines removed, 3 added; the directive still spans lines 245-255), every line stays at or under 116 characters, and the sentence keeps its shape: "Stored rows: [mechanism]; [limit], so [action]. Strengthens #5." The old consequence clause ("so a consumer never needs its own accommodation for a legacy stored shape either") was dropped because it is no longer unconditionally true, and the three facts the card requires could not fit beside it in three lines. 2. **`packages/spec/src/conversions/stored.ts` header, the first policy bullet.** "D3 keeps every conversion forever precisely so any past major can replay forward" is replaced by the kit age-out rule and the stored-pass instruction; the bullet's heading now reads "The chain the installed release carries replays". 3. **`packages/metadata-protocol/src/stored-migration.ts` header.** "a row written under any past protocol is *served* canonical forever" is replaced. The same header's **Not load-bearing** paragraph, two paragraphs below, said "the read path stays the guarantee and nothing gates on this having run"; left alone it would have contradicted the corrected sentence, so it is narrowed to the window in which the release still carries a row's conversions, matching the ADR's 2026-10-26 amendment ("for those rows it is the required one, not an optional one"). This stays inside the claimed file surface (header comment only). The wording was cross-checked against `skills/objectstack-upgrade/SKILL.md` § *Stored rows: rehydration replays what the release carries* and the **Stored rows** bullet of `.changeset/25767-retire-16x-retirement-kits.md`. ## Why Agents read AGENTS.md as binding. After 18.3 a stored row still in a ≤16.x shape is not rewritten by any seam, so a rule saying every seam replays the full chain forever would lead an agent to tell a user their 16-era rows are safe. ## Tests No test was added or changed: the diff is comment and rules prose only, with no behaviour to pin and no ablation owed. - `pnpm --filter @objectstack/spec build`, exit 0 (os-verify-lock VERDICT command-exit 0). - `pnpm --filter @objectstack/spec typecheck`, exit 0 (os-verify-lock VERDICT command-exit 0). - `@objectstack/metadata-protocol` typecheck and tests were not run locally: the edit is inside a block comment and cannot change a type or a test result. CI's `TypeScript Type Check` and `Test Core` carry them. ## Acceptance notes - Other comments still say the full chain replays, retired entries included, for example `packages/metadata-core/src/artifact-forward-conversion.ts`, `packages/core/src/fallbacks/authored-translation-sync.ts` and `packages/metadata-protocol/src/protocol.ts`. Those describe what replays (every entry the release carries, retired ones too), which is still true, and none says "forever". They were left alone as outside this card. Carrier: none. - No changeset: the diff changes only comments in published packages plus `AGENTS.md`, so nothing that ships changes in behaviour or API. The PR carries `skip-changeset` for that reason. ## Verification Gate derivation `node scripts/pm/dispatch-gates.mjs --commands` on head `6bb390a5f` (objectstack-ai/objectstack): 73 commands. The reconciliation `--ran` reports 73 accounted for: 68 run with exit 0, 5 NOT MEASURED (exit 3, prerequisite not met), 0 unrun. <details><summary>Local gate readings (head 6bb390a)</summary> - 68 derived commands exit 0, including `check:pm-governed-prose`, `check:pm-skill-id-lint` (106 files clean), `check:pm-skill-ratchet`, `check:doc-authoring`, `check:issue-citations`, `check:nul-bytes`, `check:published-files`, `check-adr-0087-registration --base origin/main`, and the spec gates `check:api-surface` (unchanged) and `check:docs` (235 generated files in sync). - `check-plugin-teardown-shape --self-test` first exited 3 on a shallow fixture commit; it passed (48 cases) after fetching that commit. - NOT MEASURED, reason: each needs a full workspace build, which a comment-only diff does not justify locally, and CI builds before running them: `check:dts-closure`, `check:dual-build-cjs-loads`, `check:lean-entry-closure`, `check:sourcemap-no-sources-content`, and `@objectstack/lint check:doc-formula-expressions`. </details> ## Human Minimum - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. (No schema touched.) - [x] 2. Every name I added uses the casing for its kind. (No names added.) - [ ] 3. No formatter ran; `pnpm lint` passes. (No formatter ran; `pnpm lint` was not run locally, CI owns that repo-wide run.) - [x] 4. One issue, one branch off an up-to-date `main`, one pull request. - [x] 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. (Nothing publishes; `skip-changeset`.) - [x] 6. `packages/spec` touched: its generated artifacts are regenerated and committed. (`check:docs` and `check:api-surface` green; nothing to regenerate.) - [x] 7. No hand edits to `content/docs/references/`, `packages/*/CHANGELOG.md` or `content/docs/releases/`. - [x] 8. Title, body and comments are in English. --- _Generated by [Claude Code](https://claude.ai/code/session_01J8VVAmLfc58Ea3cCpbSx7C)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 9a0c622 commit c44c8ac

4 files changed

Lines changed: 31 additions & 15 deletions

File tree

‎AGENTS.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -250,9 +250,9 @@ See `ui/AGENTS.md` for the app-id / localStorage / auth gotchas.
250250
"be liberal in what you accept" does **not** apply. Change the **spec** only when the spec itself is genuinely
251251
wrong, and then deliberately (edit the Zod schema + migrate). When an alias must be tolerated at all, declare it
252252
as an **ADR-0087 conversion-layer entry** (never a bare `??`, no executor shims) so it is declared, loud, tested,
253-
and *removable on a schedule*. Stored `sys_metadata` rows are covered from the other side: every rehydration seam
254-
replays the **full** conversion chain — retired entries included — via `applyConversionsToStoredItem` (ADR-0087
255-
addendum), so a consumer never needs its own accommodation for a legacy stored shape either. Strengthens #5.
253+
and *removable on a schedule*. Stored rows: `applyConversionsToStoredItem` replays the chain the installed release
254+
carries, retired entries included; kits age out one major after retiring (ADR-0087 as amended, ruling 6434038284),
255+
so run `os migrate meta --stored --apply` before leaving a release that still converts the row. Strengthens #5.
256256
13. **An accepted ADR binds until a superseding ADR says otherwise.** Reversing a recorded decision is itself a
257257
decision: it needs a **new ADR** (or an amended status line on the old one), not a changeset that quietly does the
258258
opposite. Before changing behaviour in `docs/adr/`-governed territory, **grep the ADRs for the surface you are

‎packages/metadata-protocol/src/stored-migration.ts‎

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,15 @@
55
* (#4327), the follow-up to #3903's read-path guarantee.
66
*
77
* #4317 closed the correctness gap from the read side: every stored-row
8-
* rehydration seam replays the full ADR-0087 conversion chain
9-
* (`applyConversionsToStoredItem`, retired entries included), so a row written
10-
* under any past protocol is *served* canonical forever. What it deliberately
8+
* rehydration seam replays the ADR-0087 conversion chain the installed release
9+
* carries (`applyConversionsToStoredItem`, retired entries included), so a row
10+
* is *served* canonical while that release still carries its conversions. A
11+
* retirement kit ages out one major after it retires (ADR-0087 as amended,
12+
* ruling 6434038284), so this pass must run on a release that still carries a
13+
* row's conversions, before the deployment moves to the release that drops
14+
* them. Past that, the seams still read and serve the row's legacy bytes, but
15+
* no conversion recognizes its shape, so this pass counts it `canonical`: a
16+
* false clean, not evidence. What it deliberately
1117
* did not do is make the rows themselves canonical — a pre-17 row keeps its
1218
* legacy bytes, the chain re-lowers it on every load, and each affected row
1319
* emits one conversion notice per process.
@@ -19,10 +25,13 @@
1925
* the types rather than in the CLI so an admin route renders the same run the
2026
* same way.
2127
*
22-
* **Not load-bearing.** #3855's conclusion still holds — an operator-run
23-
* migration cannot be relied on, so the read path stays the guarantee and
24-
* nothing gates on this having run. No `sys_migration` flag is recorded for
25-
* exactly that reason: a flag row would advertise a gate that does not exist.
28+
* **Not load-bearing while the release carries a row's conversions.** #3855's
29+
* conclusion holds within that window — an operator-run migration cannot be
30+
* relied on, so the read path stays the guarantee there and nothing gates on
31+
* this having run; for a row whose conversions the next release drops, this
32+
* pass is the required step above, not an optional one. No `sys_migration`
33+
* flag is recorded for exactly that reason: a flag row would advertise a gate
34+
* that does not exist.
2635
* The verifiable statement operators wanted is the *re-run* — a second pass
2736
* that reports every row canonical is the evidence, and it costs one command.
2837
*/

‎packages/spec/src/conversions/apply.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ export interface ApplyConversionsOptions {
5252
* have no author to teach, so each replays the FULL chain, retired entries
5353
* included — ADR-0087's `## Addendum (2026-07-31)` for the first two, the
5454
* #12772 ruling for the third. The fixture CI sets it as well, so graduated
55-
* transforms stay covered forever (ADR-0087 D3).
55+
* transforms stay covered while the installed release carries them; a
56+
* retirement kit ages out one major after it retires (ADR-0087 as amended,
57+
* ruling 6434038284), and its transform leaves coverage with it.
5658
*
5759
* `objectstack migrate meta` is NOT one of these callers: `applyMetaMigrations`
5860
* (`../migrations/chain.js`) looks each step's conversion up in

‎packages/spec/src/conversions/stored.ts‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,18 @@
1414
* {@link applyConversionsToStoredItem} is the one primitive every stored-row
1515
* rehydration seam calls, and it encodes the policy for data at rest:
1616
*
17-
* - **The full chain replays, including `retiredFromLoadPath` entries.**
17+
* - **The chain the installed release carries replays, including
18+
* `retiredFromLoadPath` entries.**
1819
* Retirement is an *authoring-surface* event — the schema tombstones the
1920
* key so a live author is taught the canonical spelling. A stored row has
2021
* no author; refusing its historical shape would not teach anyone, it
21-
* would only break data that once worked. D3 keeps every conversion
22-
* forever precisely so any past major can replay forward — a row at rest
23-
* is the perpetual "consumer arriving late".
22+
* would only break data that once worked. But a retirement kit ages out
23+
* one major after it retires (ADR-0087 as amended, ruling 6434038284), and
24+
* its conversion leaves with it: a row still in that shape is no longer
25+
* recognized here, so it must be canonicalized by the stored pass
26+
* (`os migrate meta --stored --apply`) on a release that still carries
27+
* the row's conversions, before the deployment moves to the release that
28+
* drops them.
2429
* - **Idempotent by construction.** Conversions only rewrite shapes they
2530
* positively recognize, so re-converting an already-canonical item is a
2631
* no-op — seams may safely stack (e.g. a row converted here and again at

0 commit comments

Comments
 (0)