Skip to content

Commit ef47459

Browse files
os-billclaude
andauthored
docs(changelog): correct the two published Seed.locale claims PR #17013 falsified, with erratum lines (#17896)
Fixes #17026 Clause-②: no — the landing surface is prose inside two already-compiled `packages/*/CHANGELOG.md` entries plus erratum lines. No schema key, no accepted-set change, no new exported symbol, no new key on a published payload. Diff is markdown only; no path under `packages/**/src/**` moves. ## The ruling this PR implements, verbatim Director seat, decision batch #119 item 1, 2026-09-12T04:15Z. Maintainer's own word to the batch presented as `1A·2A·3A·4D·5A`: 「同意」. > **A: correct the two published CHANGELOG entries in the repository, with a post-release erratum note.** > > 1. `packages/spec/CHANGELOG.md` and `packages/metadata-protocol/CHANGELOG.md`: the two `Seed.locale` sentences ("no first-party call site supplies one", "the liveness ledger records `seed.locale` as `experimental`") are corrected to the post-#17013 truth, each followed by **one erratum line naming PR #17013 and the correction date**. > 2. **Before editing**, run the release tooling's dry-run (`changeset version` on a scratch branch) and record that a manual edit inside an already-compiled section **survives** — the triage seat's open confidence gap; **if it does not survive, stop and report**. > 3. `skip-changeset`: no package publishes anything from this change. And the boundary that makes A legitimate, also verbatim: > Immutability belongs to the **shipped npm tarball**, which this ruling does not and cannot touch; it does not belong to the repository's markdown. ## Step 2 first — the gate, run before any edit Run on a local scratch branch off the same base, then discarded. Marker inserted into the already-compiled `## 17.4.0` section of both files (into the very paragraph this PR edits), then `pnpm changeset version`. | reading | value | |:--|:--| | marker count before edit | spec **0**, metadata-protocol **0** | | marker count after edit, on disk | spec **1**, metadata-protocol **1** (`git diff --stat` = 2 files, 2 insertions / 2 deletions) | | `pnpm changeset version` | `VERDICT changeset-version-exit 0` — "All files have been updated" | | **marker count AFTER `changeset version`** | spec **1**, metadata-protocol **1** — **the manual edit SURVIVES** | | where the marker ended up | spec `2451` → `5654`; metadata-protocol `128` → `898` — pushed down, content intact | | **lit control** — did the tool actually rewrite the files? | yes: a new `## 17.5.0` section is prepended at the top, `packages/spec/package.json` moves `17.4.0` → `17.5.0`, and the 250 pending changesets drop to **0** | | restore | `git reset --hard` + `git clean -fd`; `git diff HEAD` empty, 0 marker residue, 250 changesets back, scratch branch deleted | ⇒ `changeset version` **prepends** and never rewrites an already-compiled section. The triage seat's own stated blind spot — "我**没有验证过手工编辑 CHANGELOG 会不会被下一次发版覆盖或冲突** …… 但我**没有测**" — is now measured, and it does not block A. ## Step 1 — what changed, and why each corrected sentence is true of today's tree Both files carry the same compiled `## 17.4.0` entry for commit `c1d8f98`, byte-identical before and after this change. The single paragraph at `packages/spec/CHANGELOG.md:2451` / `packages/metadata-protocol/CHANGELOG.md:128` becomes four: two corrected paragraphs, each followed by one erratum line naming PR #17013 and the correction date 2026-09-12. **Claim 1 — "no first-party call site supplies one".** Verified false on this tree, not taken from the card: - `packages/runtime/src/app-plugin.ts:1717` declares `private resolveSeedLocale()`, reading `i18n.defaultLocale` off the bundle. - It is threaded into all three `SeedLoaderRequest`s that plugin builds: lines `1349`, `1434`, `1791` (`...(seedLocale ? { locale: seedLocale } : {})`). ⇒ the corrected sentence says a first-party call site **does** supply `config.locale` and names the producer and the three sites. **Claim 2 — "the liveness ledger records `seed.locale` as `experimental`".** Verified false: `packages/spec/liveness/seed.json` → `props.locale.status` reads **`live`**, with `producer` naming `packages/runtime/src/app-plugin.ts#resolveSeedLocale`. **One adjacent clause corrected in the same stroke, named rather than smuggled.** "on the default boot path the axis is inert" sits in the same paragraph and is falsified by the same landing. Leaving it would have left a known-false clause beside a corrected one, which defeats the ruling's own purpose; it is corrected and called out here. **The corrected text does not overclaim.** The ledger row's `live` is explicitly bounded, and the correction carries that bound rather than dropping it. Measured on this tree: three request builders outside `AppPlugin` still build a `config` with no `locale` key — - `packages/runtime/src/domains/packages.ts:1776` `applyPublishedSeeds` (package apply) — `config: { defaultMode, multiPass, organizationId? }`, zero occurrences of `locale` in the whole file; - `packages/metadata-protocol/src/protocol.ts:17490` `applySeedBodies` (draft publish) — same three keys; - `packages/cloud-connection/src/marketplace-install-local-plugin.ts:1510` `runInlineSeed` (marketplace install) — same three keys. And `SeedLoaderService#warnOnUnresolvedLocaleScope` still exists (`packages/metadata-protocol/src/seed-loader.ts:2592`, called at `:554`), so the corrected paragraph keeps the sentence about the warning instead of deleting it. ## House convention followed rather than invented The dispatch pointed at #16859 as the precedent. ⚠️ Read to the end, **#16859 did not in fact edit a published CHANGELOG** — its own report states `packages/spec/CHANGELOG.md` "was not opened for writing" and that correction 1 was "confirmed foreclosed and left alone". So it establishes no erratum wording. The convention that does exist was found by measurement — three entries already corrected in place in this repo: - `packages/spec/CHANGELOG.md:5257` — "As published, this sentence justified that by … that was false when written and is false now …" closing `(Corrected after publication, #15058.)` - `packages/spec/CHANGELOG.md:7401` and `packages/lint/CHANGELOG.md:1214` — same shape, closing `(Corrected after publication, #15026.)` ⇒ this PR follows that: the published sentence is quoted in place and marked false, the correction follows in the same entry, and the last erratum line closes `(Corrected after publication, #17026.)` naming the card, as those three name theirs. It is also exactly what the `packages/*/CHANGELOG.md` guardrail row now added to AGENTS.md by #16849 prescribes: "Factual error in a released entry → **amend that entry in a dedicated docs-only PR**, ⛔ never an erratum in a later entry and never a rider on code changes". This PR is docs-only, amends the entry itself, and adds nothing to a later entry. ## `skip-changeset` — measured, not asserted | reading | value | |:--|:--| | diff file list vs merge base `c744c0af332` | exactly two paths, both `CHANGELOG.md` | | any non-markdown path | **none** | | `.changeset/*.md` added by this PR | **0** | | `packages/spec` `files[]` | `dist, json-schema, liveness, prompts, llms.txt, README.md, src/**/*.zod.ts, CHANGELOG.md, api-surface, spec-changes.json` | | `packages/metadata-protocol` `files[]` | `dist, README.md, CHANGELOG.md` | ⚠️ Stated precisely so neither half misleads: `CHANGELOG.md` **is** in both packages' `files[]`, so the corrected bytes do ship inside the next tarball either package happens to publish. What does **not** move is any package **version**, any **API**, any `dist/`, any source file, or any generated artifact — and because this PR adds no changeset, it schedules no version bump of its own. That is the sense in which "no package publishes anything from this change" holds. ## Gates Derived with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` against the real change set, then reconciled with `--ran`. - **54 derived families — 50 run green, 4 NOT MEASURED, 0 unrun.** Reconciliation verdict, verbatim: `✓ dispatch-gates --ran: 54 derived famil(ies) accounted for — 50 run, 4 NOT-MEASURED (4 DERIVED from a recorded exit 3).` Every exit code captured before any pipe. - The 4 NOT MEASURED are `check:dts-closure`, `check:dual-build-cjs-loads`, `check:lean-entry-closure`, `check:sourcemap-no-sources-content`; all exit **3** = PREREQUISITE NOT MET, each refusing because there is no `dist/` in this worktree ("Run `pnpm build` first. ⛔ This is NOT a pass: nothing was measured."). They read build artifacts only, which a markdown-only diff cannot move. **Declared to CI**, which builds first. - Repo-wide `pnpm lint` (`eslint . --no-inline-config`) over the **full** population: exit **0** in 1m26s. No narrowing declared because none was needed. A targeted run over just these two files reports `File ignored because no matching configuration was supplied.` for both — markdown is outside eslint's configured population here. - `pnpm check:nul-bytes` green, plus a manual control-character sweep of both touched files (`grep -naP` over the non-tab, non-newline C0 range plus DEL): **no matches**, with a printable lit control on the same files firing. - Package test suites are **not owed and were not run**, stated rather than silently skipped: the diff compiles into nothing and is imported by nothing. The only test that mentions `CHANGELOG.md` as a file class is the `action-owner-key-single-source` pair, whose scanner is fixed to `.ts` sources. ## Acceptance notes — out of scope, noted, not filed - `packages/core/src/plugin-contract.ts:69` carries a code comment asserting the opposite posture for a different entry: "⚠️ `CHANGELOG.md` is NOT a live document and is deliberately not corrected … supersedes them BY VERSION rather than by rewriting them." That predates the `packages/*/CHANGELOG.md` guardrail row #16849 added to AGENTS.md, which now prescribes amending the entry. ⛔ Not touched by this PR and not filed — it belongs to whoever next revisits that entry, and the governing text is already the AGENTS.md row. ## Not in play ⛔ `content/docs/releases/` is untouched by this PR and by all of the ruling's options. This is `packages/*/CHANGELOG.md`, a different surface. Named here only so the red line is not read into a task it does not cover. Authored by the `domain:spec` execution lane, session `session_01MkQhmuuJAVDjmeWNixwDDH`, on branch `claude/issue-17026-seed-locale-changelog-erratum`. --- _Generated by [Claude Code](https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 7e74af3 commit ef47459

2 files changed

Lines changed: 14 additions & 2 deletions

File tree

packages/metadata-protocol/CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,13 @@
125125

126126
The platform still translates nothing and merges nothing. The app authors both record sets; this adds only the axis that selects between them.
127127

128-
**What is not wired yet, stated plainly.** The locale axis is evaluated against `config.locale`, and no first-party call site supplies one — the runtime wiring that would resolve it from the stack's configured locale is a separate change in `packages/runtime`. An embedding host that passes `config.locale` itself gets the full behaviour today; on the default boot path the axis is inert. That is the shape `Seed.env` was in before framework#4704, so it is not left silent: a load carrying locale-scoped datasets and no `config.locale` warns naming each dataset it let through and the config key that would make the scope take effect. The liveness ledger records `seed.locale` as `experimental` for exactly this reason, with the consumer side cited and the producer gap spelled out, rather than claiming `live` on a correct-but-insufficient consumer pointer.
128+
**What is not wired yet, stated plainly.** The locale axis is evaluated against `config.locale`. As published, this entry continued "and no first-party call site supplies one — the runtime wiring that would resolve it from the stack's configured locale is a separate change in `packages/runtime`", and said that on the default boot path the axis is inert. Both clauses are false as of PR #17013: `AppPlugin#resolveSeedLocale` resolves the load-time locale from the app's declared `i18n.defaultLocale` and threads it into all three `SeedLoaderRequest`s that plugin builds — the inline boot seed, the per-org replayer registered for tenant provisioning, and the dev hot-reload seeder — so a first-party call site does supply `config.locale`, and the axis takes effect on the default boot path. An embedding host that passes `config.locale` itself gets the full behaviour, as it always did. What is still unwired is narrower, and named: three request builders outside that plugin pass no locale — `applyPublishedSeeds` (package apply), `applySeedBodies` (draft publish) and `runInlineSeed` (marketplace install).
129+
130+
*Erratum, 2026-09-12 — PR #17013 supplied the producer this paragraph described as missing. The sentence was true when written: the release compiled this entry 15 minutes before that PR landed.*
131+
132+
That is the shape `Seed.env` was in before framework#4704, so it is not left silent: a load carrying locale-scoped datasets and no `config.locale` warns naming each dataset it let through and the config key that would make the scope take effect. That warning stays after #17013, because the three builders above, any embedding host that builds its own request, and a stack that declares no `i18n` block all still reach the loader without a locale. As published, this entry closed "The liveness ledger records `seed.locale` as `experimental` for exactly this reason, with the consumer side cited and the producer gap spelled out, rather than claiming `live` on a correct-but-insufficient consumer pointer"; the ledger records `seed.locale` as `live`, naming `AppPlugin#resolveSeedLocale` as the producer and writing the remaining bound — three of six request builders — into the row itself.
133+
134+
*Erratum, 2026-09-12 — PR #17013 flipped `packages/spec/liveness/seed.json` → `seed.locale` from `experimental` to `live` in the same landing. (Corrected after publication, #17026.)*
129135
- 615fac3: A publish now refuses an object whose `highlightFields` names a field that does not exist on it — the same gate that refuses a code-authored stack.
130136

131137
`list-view-field-unknown` inspects `view.columns`, and Studio's app builder mints no `view` items at all, so the reference-integrity family had nothing to inspect on the only artifacts the click path authors. What it authors is the **object**, and an object-level field-name list was covered by nothing that could refuse: measured on `origin/main`, `runtimeAuthoringRulesFor('object')` dispatched seven rules with no reference-integrity rule among them, while the object-level existence check that did exist (`semantic-role-field-unknown`) is `warning`, advisory-tier and CLI-only. So `os validate` exited 0 on a dangling reference and the runtime publish door — the only door a Studio, REST `/meta` or MCP author has — said nothing at all.

packages/spec/CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2448,7 +2448,13 @@
24482448

24492449
The platform still translates nothing and merges nothing. The app authors both record sets; this adds only the axis that selects between them.
24502450

2451-
**What is not wired yet, stated plainly.** The locale axis is evaluated against `config.locale`, and no first-party call site supplies one — the runtime wiring that would resolve it from the stack's configured locale is a separate change in `packages/runtime`. An embedding host that passes `config.locale` itself gets the full behaviour today; on the default boot path the axis is inert. That is the shape `Seed.env` was in before framework#4704, so it is not left silent: a load carrying locale-scoped datasets and no `config.locale` warns naming each dataset it let through and the config key that would make the scope take effect. The liveness ledger records `seed.locale` as `experimental` for exactly this reason, with the consumer side cited and the producer gap spelled out, rather than claiming `live` on a correct-but-insufficient consumer pointer.
2451+
**What is not wired yet, stated plainly.** The locale axis is evaluated against `config.locale`. As published, this entry continued "and no first-party call site supplies one — the runtime wiring that would resolve it from the stack's configured locale is a separate change in `packages/runtime`", and said that on the default boot path the axis is inert. Both clauses are false as of PR #17013: `AppPlugin#resolveSeedLocale` resolves the load-time locale from the app's declared `i18n.defaultLocale` and threads it into all three `SeedLoaderRequest`s that plugin builds — the inline boot seed, the per-org replayer registered for tenant provisioning, and the dev hot-reload seeder — so a first-party call site does supply `config.locale`, and the axis takes effect on the default boot path. An embedding host that passes `config.locale` itself gets the full behaviour, as it always did. What is still unwired is narrower, and named: three request builders outside that plugin pass no locale — `applyPublishedSeeds` (package apply), `applySeedBodies` (draft publish) and `runInlineSeed` (marketplace install).
2452+
2453+
*Erratum, 2026-09-12 — PR #17013 supplied the producer this paragraph described as missing. The sentence was true when written: the release compiled this entry 15 minutes before that PR landed.*
2454+
2455+
That is the shape `Seed.env` was in before framework#4704, so it is not left silent: a load carrying locale-scoped datasets and no `config.locale` warns naming each dataset it let through and the config key that would make the scope take effect. That warning stays after #17013, because the three builders above, any embedding host that builds its own request, and a stack that declares no `i18n` block all still reach the loader without a locale. As published, this entry closed "The liveness ledger records `seed.locale` as `experimental` for exactly this reason, with the consumer side cited and the producer gap spelled out, rather than claiming `live` on a correct-but-insufficient consumer pointer"; the ledger records `seed.locale` as `live`, naming `AppPlugin#resolveSeedLocale` as the producer and writing the remaining bound — three of six request builders — into the row itself.
2456+
2457+
*Erratum, 2026-09-12 — PR #17013 flipped `packages/spec/liveness/seed.json` → `seed.locale` from `experimental` to `live` in the same landing. (Corrected after publication, #17026.)*
24522458
- 8e0b297: fix(plugin-auth)!: `positions[]` on the session payload is the SECURITY axis, not the better-auth role scalar (#15136)
24532459

24542460
<!-- adr-0087: registered session-payload-positions-security-axis -->

0 commit comments

Comments
 (0)