Skip to content

docs(changelog): correct the two published Seed.locale claims PR #17013 falsified, with erratum lines - #17896

Merged
os-bill merged 1 commit into
mainfrom
claude/issue-17026-seed-locale-changelog-erratum
Sep 12, 2026
Merged

os-bill merged 1 commit into
mainfrom
claude/issue-17026-seed-locale-changelog-erratum

Conversation

@os-bill

@os-bill os-bill commented Sep 12, 2026

Copy link
Copy Markdown
Collaborator

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-feat(runtime, spec): AppPlugin supplies SeedLoaderConfig.locale, so the Seed.locale axis takes effect on the default boot path #17013 truth, each followed by one erratum line naming PR feat(runtime, spec): AppPlugin supplies SeedLoaderConfig.locale, so the Seed.locale axis takes effect on the default boot path #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 SeedLoaderRequests 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

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

#17013 falsified

`packages/spec/CHANGELOG.md` and `packages/metadata-protocol/CHANGELOG.md`
carry the same compiled 17.4.0 entry for the `Seed.locale` axis. Two of its
present-tense claims were true when written and false by the time the entry
was published: "no first-party call site supplies one" and "the liveness
ledger records `seed.locale` as `experimental`".

Both are corrected to the post-#17013 truth, each followed by one erratum
line naming PR #17013 and the correction date, per the maintainer ruling
recorded on #17026 (director decision batch #119 item 1, 2026-09-12).

The clause "on the default boot path the axis is inert" is falsified by the
same landing and is corrected in the same sentence; the residual bound is
stated rather than dropped — three request builders outside `AppPlugin`
still pass no locale.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
@os-bill os-bill added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 12, 2026 — with Claude
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️ 2 changed file(s) yielded no anchor (packages/metadata-protocol/CHANGELOG.md, packages/spec/CHANGELOG.md), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 2 changed package(s)).

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/metadata-protocol/CHANGELOG.md, packages/spec/CHANGELOG.md) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 137 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json a61ae59f93302c1256330be8a6282f7be7f4a120 → packageMentionDocs.

@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Sep 12, 2026
@os-bill
os-bill marked this pull request as ready for review September 12, 2026 20:03
@os-bill
os-bill added this pull request to the merge queue Sep 12, 2026
Merged via the queue into main with commit ef47459 Sep 12, 2026
41 checks passed
@os-bill
os-bill deleted the claude/issue-17026-seed-locale-changelog-erratum branch September 12, 2026 20:33
zhuangjianguo pushed a commit that referenced this pull request Sep 13, 2026
The published packages/spec/CHANGELOG.md 17.4.0 entry, under "Keys
deliberately left alone", said the burn-rate `window` and the
OpenTelemetry exporter `timeout` name no unit anywhere in their prose
and are outside check:duration-unit-keys's population, with pin tests
holding the bare spellings. All three claims are false now: #17783
renamed the burn-rate window to `durationSeconds` and #17785 renamed
the exporter timeout to `timeoutMs` (with its `exportTimeout` sibling
to `exportTimeoutMs`), both tombstoned with `retiredKey()`, and the
pin tests that held the bare names were rewritten into rename-refusal
pins. This corrects the sentence to the post-#17783/#17785 truth and
appends one erratum line, matching the ef47459 (#17896) precedent's
shape.

Clause-②: no

skip-changeset: no package publishes anything from this change — the
edit is prose inside an already-compiled CHANGELOG.md section plus an
erratum line.
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 17, 2026
…ectstack-ai#17783/objectstack-ai#17785 (objectstack-ai#18077)

Fixes objectstack-ai#18076

Clause-②: no — the landing surface is prose inside one already-compiled
`packages/spec/CHANGELOG.md` entry plus an erratum line. No schema key,
no accepted-set change, no new exported symbol, no new key on a
published payload, and no path under `packages/**/src/**` moves.

`skip-changeset`: no package publishes anything from this change.

## The ruling this PR implements

Director seat, decision batch objectstack-ai#119 item 1, 2026-09-12T04:15Z,
maintainer's own word `1A·2A·3A·4D·5A`: 「同意」, implemented once already
by `ef47459474` (objectstack-ai#17896). This card applies the same remedy shape to a
second falsified instance:

> **A: correct the two published CHANGELOG entries in the repository,
with a post-release erratum note.** … the … sentences … are corrected to
the post-objectstack-ai#17013 truth, each followed by **one erratum line naming PR
objectstack-ai#17013 and the correction date**.
>
> **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** … **if it does not survive, stop
and report**.
>
> `skip-changeset`: no package publishes anything from this change.

## Step 2 first — the survival gate, run before any edit

On a local scratch branch off `484484041a`
(`scratch/issue-18076-survival-gate`), discarded afterward:

| reading | value |
|:--|:--|
| marker inserted into the already-compiled `## 17.4.0` "Keys
deliberately left alone" paragraph | `SURVIVALGATEMARKER18076XQ7` |
| marker count before `changeset version` | **1** |
| dark control (`ZZQV18076NOPE`, invented at read time) before | **0** |
| `pnpm changeset version` | exit 0, "All files have been updated" |
| marker count **after** `changeset version` | **1** — the manual edit
**survives** |
| where the marker ended up | line 2869 → line 7316 — pushed down,
content intact |
| dark control after | **0** — unchanged, confirming the grep
methodology (not a broken instrument reading a false zero) |
| lit control — did the tool actually rewrite the files? | yes: a new
`## 17.5.0` section is prepended, `packages/spec/package.json` moves
`17.4.0` → `17.5.0`, and 452 of 453 pending `.changeset/*.md` files are
consumed |
| restore | `git checkout -f` back to the real branch; `diff <(git show
origin/main:packages/spec/CHANGELOG.md) packages/spec/CHANGELOG.md` →
**identical**; scratch branch deleted |

⇒ `changeset version` prepends a new section and never rewrites an
already-compiled one. Re-measured on this tree rather than inherited
from `ef47459474`'s reading, per the ruling's explicit instruction.

## What changed, and why each corrected claim is true of today's tree

`packages/spec/CHANGELOG.md`, inside the already-released `## 17.4.0`
entry (commit `e9fcd6b`, "Keys deliberately left alone"), said:

> `ServiceLevelObjective.errorBudget`'s burn-rate `window` and the
OpenTelemetry exporter `timeout` name no unit anywhere in their prose,
so both are outside the gate's population entirely. Pin tests assert
each of these, so a later sweep cannot read this card as "every
duration-shaped number on these files".

All three claims are false, verified on this tree independently of the
card:

- **Half 1** — `packages/spec/src/system/metrics.zod.ts:368-375`
(`SLO_BURN_RATE_WINDOW_RETIRED`):
`ServiceLevelObjective.errorBudget.burnRateWindows[].window` was renamed
to `durationSeconds` in `@objectstack/spec` 17 (objectstack-ai#17783), tombstoned with
`retiredKey()` at `metrics.zod.ts:402`/`:498`. The pin that held the
bare spelling is now `metrics.test.ts:612`, `it('REFUSES the burn-rate
\`window\` with a rename naming \`durationSeconds\`', …)`.
- **Half 2** — `packages/spec/src/system/tracing.zod.ts:546-550`:
`OpenTelemetryCompatibility.exporter.timeout` was renamed to `timeoutMs`
(objectstack-ai#17785), tombstoned with `retiredKey()`; its `batch.exportTimeout`
sibling moved to `exportTimeoutMs` the same way (`:586`/`:601`). The pin
is now `tracing.test.ts:594-611`, `it.each([['exporter.timeout',
'timeoutMs'], …])('REFUSES the retired \`%s\` with the rename in the
message', …)`.

The corrected paragraph quotes the falsified sentence, states the truth
with both PR numbers and both new key names, and closes with one erratum
line naming this PR and the correction date, matching `ef47459474`'s
shape.

**The paragraph's other four claims were checked and are still true**
(not touched, per the ruling's scope boundary):

- `FailoverConfig.dns.ttl` — still bare, `disaster-recovery.zod.ts:148`,
under the same `externalVocabulary` exemption comment.
- `CacheAvalanchePrevention.lockout.lockTimeoutMs` — still milliseconds,
`cache.zod.ts:162`.
- `MetricExportConfig.batch.size` — still a bare record count,
`metrics.zod.ts:702`.
- `QueueConfig.rateLimit.max` — still a bare task count,
`worker.zod.ts:~276`.

## The erratum line's PR number — how it went in, and the fix for a
review finding

`objectstack-ai#18077` (the PR number for this correction) is not knowable before the
PR exists, so the first pushed commit (`bfc744c98c`) carried a
placeholder, `#PRPLACEHOLDER18076`, in the erratum line's parenthetical,
meaning to fill it in once the PR was open. **That fill-in commit
(`ac68eff364`) was made locally but not pushed before the round's first
status check ran against `bfc744c98c`, so a reviewer correctly caught a
shipped placeholder.** It is now pushed; **HEAD is `ac68eff364`**,
verified directly against the remote rather than assumed:

```
$ git show origin/claude/issue-18076-changelog-duration-keys-erratum:packages/spec/CHANGELOG.md | grep -c PRPLACEHOLDER
0
$ git show origin/claude/issue-18076-changelog-duration-keys-erratum:packages/spec/CHANGELOG.md | grep -n 'Corrected after publication, objectstack-ai#18077'
7331:  population as of `@objectstack/spec` 17. (Corrected after publication,
```

**Placeholder sweep of the whole diff, with controls** (`git diff
origin/main..HEAD -- packages/spec/CHANGELOG.md`, 35 lines):

- Pattern
`PLACEHOLDER|\bTODO\b|\bTBD\b|\bXXX\b|\bFIXME\b|<[A-Z_]+>|\{\{.*\}\}`
(case-insensitive) over the diff: **0 matches**.
- Lit control, drawn a different way: appended a synthetic
`PRPLACEHOLDER99999` line to the same scanned text and re-ran the
identical pattern — **1 match**, confirming the regex class actually
fires and the 0 above is a real zero, not a typo'd pattern reading a
false clean.
- Positive control: `18077` appears **exactly once** in the diff — the
one erratum reference, present and correctly substituted.

## Verification

`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived **54** local gate families for this
diff (all matched via broad `packages/**` / `packages/spec` globs — this
path carries no gate of its own). Ran all 54 **twice** — once on
`bfc744c98c`, again on the final `ac68eff364` after the placeholder fix
landed — both times reconciled with `--ran` against the recorded exit
codes, same result both runs:

```
Run reconciliation — 54 derived, 50 run, 4 NOT-MEASURED, 0 UNRUN.
✓ dispatch-gates --ran: 54 derived famil(ies) accounted for — 50 run, 4 NOT-MEASURED (4 DERIVED from a recorded exit 3).
```

- **50/50 that ran, passed** (`pnpm check:nul-bytes`,
`check:release-notes`, `check:release-page-status`,
`check:release-section-coverage[--strict]`,
`check:changeset-gate-self-tests`, `check:doc-authoring`,
`check:page-declaration-shape`, `check:published-files`,
`check:slot-lookup`, `check:test-source-alias`,
`check:type-source-resolution`, and 39 more — full list in this branch's
derivation).
- **4 NOT-MEASURED, exit 3 (`PREREQUISITE NOT MET`), declared rather
than swallowed**: `check:dts-closure`, `check:dual-build-cjs-loads`,
`check:lean-entry-closure`, `check:sourcemap-no-sources-content` — each
refuses on a missing `dist/` in this fresh worktree ("Run `pnpm build`
first … this is NOT a pass: nothing was measured"). These are whole-repo
build-completeness gates unrelated to this diff's content (no code, no
export, no `dist` path moves); running a ~80-package `pnpm build` to
satisfy them is disproportionate to a one-line CHANGELOG correction, and
CI builds fresh before running them.
- No changeset added; `check:changeset-gate-self-tests` (the gate's own
self-test) is green. `skip-changeset` now shows as an applied label on
this PR (not applied by this session — labels were left untouched per
this card's operating constraints).
- `pnpm check:nul-bytes` and a direct `grep -naP
'[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'` both clean on the diff.
- `node scripts/check-commit-card-trailers.mjs --range
origin/main..HEAD`: clean on both commits — no card-relation trailer,
model-free trailer pair.

No `packages/spec` test/typecheck run beyond the above: this diff
touches no code, no export and no public surface byte, so per this
repo's verification-scope convention there is nothing for `pnpm --filter
@objectstack/spec test`/`typecheck` to newly exercise.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_015c5G6TmpMKgnusmTpD7Ntt

---
_Generated by [Claude
Code](https://claude.ai/code/session_015c5G6TmpMKgnusmTpD7Ntt)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 17, 2026
…e — drop the M9.1 / M9.2 phase language (objectstack-ai#18557)

Part of objectstack-ai#17849

Clause-②: no

The spec-seat half of objectstack-ai#17323, ruled **B** by the director seat (comment
`5644710461`, decision batch objectstack-ai#123 item 1, 2026-09-12; maintainer
verbatim 「同意」). The M9.1 / M9.2 phase language is dropped from the
citation sites in `packages/spec` and `packages/formula`, and the
contract each of them describes is restated in the present tense. ⛔ No
accept-set change: no key is added, renamed, re-typed or retired, and
every envelope that parsed before parses byte-identically.

The docs half (PR objectstack-ai#17851, `Part of objectstack-ai#17323`) merged on
2026-09-12T13:17:02Z, so `content/docs/data-modeling/formulas.mdx` and
the two pending changesets already carry the new wording. This PR is the
other half the ruling's item 4 assigns to this seat — `packages/formula`
included, which the domain table calls `domain:engine` and the ruling
puts here verbatim.

## What the text says now

- `source` is the canonical persisted form — it is what the engine
evaluates.
- `ast` is accepted beside it as an optional opaque structured value
(each engine validates its own shape), and it carries no promise of
becoming required.
- A slot whose value the engine RUNS requires `source`;
`EvaluatedExpressionSchema` is where that is spelled out.

| file | `M9\.[12]` hits at `79a046f` | after |
|:--|--:|--:|
| `packages/spec/src/shared/expression.zod.ts` | 7 | 0 |
| `packages/formula/src/cel-engine.ts` | 1 | 0 |
| `packages/formula/src/normalize.ts` | 1 | 0 |

Lit control for the same probe: `ExpressionSchema` in
`expression.zod.ts` reads 19, so the zero is a reading and not a broken
path.

`cel-engine.ts`'s AST-only arm also carried an `M9.7` promise ("revisit
AST-only execution in M9.7 when we cut the spec persistence over")
**inside the same sentence** as its `M9.1` hit. It goes with the
sentence rather than being left as a phase promise standing beside a
de-phased one; it is named here rather than smuggled.

## The verbatim-quote constraint, and the proof it holds

Coordination note `5645945565`: the two pending changesets quote the
exported constant `EVALUATED_EXPRESSION_SOURCE_REQUIRED` VERBATIM inside
a blockquote, and the docs half already edited them to read 「(the
canonical persisted form)」. The constant loses exactly the four words
`of phase M9.1` and nothing else, so the quote stays a quote. Proved
mechanically rather than by eye — the constant's runtime value was
reassembled from the source and compared with each changeset's
blockquote, joined:

```
.changeset/flow-edge-condition-evaluated-slot.md              ==> BYTE-IDENTICAL to the constant
.changeset/blank-node-condition-refused-at-registration.md    ==> BYTE-IDENTICAL to the constant
```

No consumer parses that sentence: every call site imports the constant
by name, and the message assertions in `packages/lint`,
`packages/services/service-automation` and `packages/spec` read
substrings that never contained the phase id — the non-blank-source
clause and the cannot-evaluate-ast clause.

## ⚠️ Declared deviation — the `packages/spec/CHANGELOG.md` erratum is
NOT in this PR

The card's work items and the ruling's item 2 also name an erratum line
under the already-published `## 17.4.0` entry that promised M9.2 (3 hits
at `:1096`, `:1103`, `:1119`). It is **deliberately not carried here**,
because AGENTS.md's Documentation Guardrails row for
`packages/*/CHANGELOG.md` is categorical about the packaging:

> ❌ Never edit in a code PR … 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 a code PR: it moves a published string and owes a `patch`
changeset. The ruling names 「the objectstack-ai#17026 shape」 for the erratum, and that
shape — read off the card objectstack-ai#17026 and PR objectstack-ai#17896 that executed it — **is**
a dedicated docs-only PR with `skip-changeset`, whose executing seat
quoted this same AGENTS.md row verbatim as its authorization chain. So
the ruling and the guardrail agree; only the card's file-surface
bundling puts them in one PR.

⇒ The erratum is owed as a **separate docs-only PR**, and objectstack-ai#17849 stays
open for it — which is why this body reads `Part of objectstack-ai#17849` and not the
closing form the dispatch asked for. The residual after this PR,
measured on this branch:

```
$ git grep -lE 'M9\.[12]' -- packages
packages/spec/CHANGELOG.md          # 3 hits — the erratum's target, owed as a docs-only PR
```

Nothing under `content/` remains; the docs half cleared it.

## Verification

Everything below is a real local run, exit codes landed to disk before
reading (⛔ never through a pipe).

| run | verdict |
|:--|:--|
| `pnpm --filter @objectstack/spec --filter @objectstack/formula build`
| `VERDICT command-exit 0` (shared verify lock) |
| `pnpm --filter @objectstack/spec check:generated` | exit 0 — ✓ all 15
generated artifacts up to date (nothing to regenerate: no `.describe()`
moved) |
| `pnpm --filter @objectstack/spec --filter @objectstack/formula
typecheck` | exit 0 |
| `pnpm --filter @objectstack/spec --filter @objectstack/formula test` |
exit 0 — spec 483 files / 13773 tests, formula 29 files / 856 tests |
| `pnpm lint` (`eslint . --no-inline-config`, whole repo) | exit 0 |
| derived gate families (`dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands`) | 78 derived · 76 run green · 2
NOT MEASURED |

`--ran` reconciliation with exit codes recorded per family: `78 derived
famil(ies) accounted for — 76 run, 2 NOT-MEASURED (2 DERIVED from a
recorded exit 3)`. The two are `check:dual-build-cjs-loads` and
`check:lean-entry-closure`, both exit **3 = PREREQUISITE NOT MET** (they
read the `dist/` of ~80 packages this tree never built) — ⛔ neither a
pass nor a failure; CI's `Build Core` is where they run.

Two families needed a prerequisite before they could answer, and both
are green once given it: `check:doc-formula-expressions` (exit 3 until
`@objectstack/lint` was built, then exit 0) and
`check-plugin-teardown-shape --self-test` (exit 1 on this shallow clone
until its pinned fixture commit was fetched, then exit 0 / 47 cases).

## Acceptance notes

⚠️ **One gate reds on this tree and it is NOT this diff** — `pnpm
check:cross-package-test-inputs` (exit 1), flagging `@objectstack/cli
descends a directory tree from packages/spec/dist/`, rooted in
`packages/cli/test/init-created-files-summary.e2e.test.ts`. Ablated to
the cause on a **pristine `origin/main` worktree with zero diff**:

```
node scripts/check-cross-package-test-inputs.mjs          -> exit 0  (OK: 29 package(s) … 13 walked root(s) judged)
mkdir -p packages/spec/dist  &&  (same command)           -> exit 1  (the identical failure text)
rm -rf packages/spec/dist    &&  (same command)           -> exit 0  (restored; tree clean)
```

⇒ the verdict turns on whether the gitignored `packages/spec/dist`
exists, not on any source change — and building `packages/spec` is
exactly what AGENTS.md requires before the spec artifact gates run. CI
cannot see it: `Lint & Repo Gates` installs and never builds, so the
gate is green there. Filed as a finding for the dispatching seat rather
than fixed here — `packages/cli` and
`scripts/check-cross-package-test-inputs.mjs` are both outside this
card's file surface.

Noted, not filed: nothing else. The three source files were read whole
and carry no other stale phase promise; the remaining `M9.x` mentions in
the tree (`M9.9b` in `default-value-shape.ts`, `action.zod.ts`,
`objectql/engine.ts`; `M9.5` / `M9.7` in `objectql` and the docs page)
are ROADMAP section references outside the ruling's seven citation
sites, and the ruling names only the M9.1 / M9.2 phase promise.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3)_

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 17, 2026
…red M9.2 promise (objectstack-ai#18569)

Part of objectstack-ai#17849

Clause-②: no

The second half of objectstack-ai#17849, split out of PR objectstack-ai#18557 because AGENTS.md's
Documentation Guardrails row for `packages/*/CHANGELOG.md` requires it:
an already-published entry is amended **in a dedicated docs-only PR**, ⛔
never as a rider on code changes. This diff is exactly one file and adds
no source, no schema and no export.

objectstack-ai#17323's ruling item 2 orders it: 「`packages/spec/CHANGELOG.md`: an
erratum line under the entry that promised M9.2 (the objectstack-ai#17026 shape —
correct the published text, note the date), ⛔ not a rewrite of history.」

## What was corrected, and where

One entry: `ef3a138` (`feat(spec)!: an evaluated expression slot
requires a non-blank source`) under the already-published `## 17.4.0`.
Line numbers re-derived on this tree at `79a046f8c`, ⛔ not carried over
from the card or from the earlier round:

| line | published text | disposition |
|--:|:--|:--|
| 1096 | the blockquote quoting `EVALUATED_EXPRESSION_SOURCE_REQUIRED`,
parenthetical `(the canonical persisted form of phase M9.1)` | **left
exactly as shipped** — it is a faithful quote of what 17.4.0 published.
A paragraph under it says what the constant reads now. |
| 1103 | 「its docblock declares that `ast` becomes required in build
output at phase M9.2」 | corrected in place, old words kept as a marked
quotation |
| 1119 | 「has no evaluable form under M9.1」 | corrected in place, old
words kept as a marked quotation |

One adjacent clause falsified by the same ruling is corrected in the
same stroke and **named here rather than smuggled**: the bullet also
read 「when AST-only evaluation lands」, which presupposes the retired
promise. It now reads 「if AST-only evaluation is ever chartered」 — the
wording the two pending changesets already carry. Leaving it would have
left a 「when it lands」 sitting beside 「no promise of becoming required」
in one bullet.

One dated erratum line closes the entry, carrying the in-repo tail this
repository already uses in five places (`packages/spec/CHANGELOG.md` ×3
at `:2457`, `:2882`, `:5277`, `packages/lint/CHANGELOG.md:1214`,
`packages/metadata-protocol/CHANGELOG.md:134`):

```
*Erratum, 2026-09-17 — the M9.1 / M9.2 phase promise this entry restated was retired by the
ruling on objectstack-ai#17323 (2026-09-12) … (Corrected after publication, objectstack-ai#17849.)*
```

⛔ No new entry at the top, ⛔ no version heading added (`git diff -U0 |
grep -c '^+## '` → 0), ⛔ nothing this release published is changed.

## ⚠️ The card's grep criterion cannot be met, and the ruling is why

The card sets `git grep -l 'M9\.[12]' origin/main -- packages` → 0.
After this PR that file still carries **four** hits, and every one of
them is required by the ruling's own 「⛔ not a rewrite of history」:

```
:1096  the as-shipped blockquote                       (deliberately untouched)
:1109  "…becomes required in build output at phase M9.2"   inside `As published, that sentence continued "…"`
:1130  "no evaluable form under M9.1"                      inside `As published that clause read "…"`
:1142  the erratum line itself, naming the retired promise
```

Not one of them is a live assertion of the promise — they are the
quotation marks the objectstack-ai#17026 shape puts around it, plus the erratum that
retires it. A zero would require deleting the published words, which is
precisely the rewrite the ruling forbids. ⇒ the criterion and the ruling
are not jointly satisfiable, and the ruling governs. Reported rather
than forced.

## ⚠️ `skip-changeset` — checked against objectstack-ai#18375 before relying on it

The label is applied. Before applying it I re-read the refusal objectstack-ai#18375 is
about, `scripts/check-empty-changeset.mjs`, and it does **not** reach
this PR:

- that gate has two rules and **both take the `.changeset/` diff and
nothing else** — rule 1 fires on a newly ADDED empty-frontmatter
changeset, rule 2 (`scanForeign()`) on a MODIFIED or DELETED changeset
that exists on the merge base;
- the DELIBERATE-CORRECTION text 「no label and no diff shape makes that
safe」 is scoped, in its own words, to 「the note you rewrote describes
behaviour THIS PR changed」 — a **pending release note**, i.e. a
`.changeset/*.md`;
- **this diff contains no `.changeset/` path at all** (`git diff
--name-only origin/main...HEAD` → one line,
`packages/spec/CHANGELOG.md`). So the label suppresses no refusal that
could have fired here, and the finding's hazard is absent rather than
accepted.

What the label IS doing is the documented job: `Check Changeset`
requires an added changeset from **every** PR, with no path filter, so
`skip-changeset` is the only instrument for a diff that releases nothing
of its own — the same instrument the objectstack-ai#17026 ruling named (item 3) and PR
objectstack-ai#17896 spent for the same shape.

## Verification

Exit codes landed to disk before reading, ⛔ never through a pipe.

| run | verdict |
|:--|:--|
| derived gate families for this one path | 55 derived · **51 run
green** · 4 NOT MEASURED |
| `--ran` reconciliation with per-family exit codes | `55 derived
famil(ies) accounted for — 51 run, 4 NOT-MEASURED (4 DERIVED from a
recorded exit 3)` |
| `check-release-section-coverage` (plain · `--self-test` · `--strict`)
| exit 0 ×3 — 7 published minors across 2 GA majors, every one still
covered |
| `pnpm check:release-notes`, `pnpm check:release-page-status` | exit 0
|
| `pnpm lint` (`eslint . --no-inline-config`, whole repo) | exit 0 |
| `pnpm check:nul-bytes` + a direct control-character sweep of the file
| exit 0 / no hits |

The 4 NOT MEASURED are `check:dts-closure`,
`check:dual-build-cjs-loads`, `check:lean-entry-closure` and
`check:sourcemap-no-sources-content` — all exit **3 = PREREQUISITE NOT
MET** (they read the `dist/` of packages this tree never built). ⛔
Neither a pass nor a failure; CI's `Build Core` runs them.

**No test is owed and that is measured, not assumed.** Four test files
name a `CHANGELOG.md` path and **every one of them excludes it**:
`compliance-families-retirement.test.ts` skips `CHANGELOG.md`, both
`action-owner-key-single-source.test.ts` files list it under `covers` as
「a published CHANGELOG is the record of the removal itself」, and
`template-consistency.test.ts` passes `:(exclude)**/CHANGELOG.md`. No
test reads the bytes this PR moves.

**The `changeset version` survival question is already answered and is
not re-run here.** objectstack-ai#17026's round measured it with a lit control: a
manual edit inside an already-compiled section SURVIVES `changeset
version` (marker present before and after, while the tool provably
re-wrote both files — a new version section prepended, the package
version advanced, 250 pending changesets consumed). That reading stands;
re-running it would buy nothing.

## Acceptance notes

Confirming reading for the finding recorded on objectstack-ai#17849: `pnpm
check:cross-package-test-inputs` is **green (exit 0)** on this tree,
which has no `packages/spec/dist` because a markdown-only diff needs no
build. Same gate, same commit base, opposite verdict from the built tree
— a third independent leg for the same cause. ⛔ Not filed again; it is
already objectstack-ai#18353 and objectstack-ai#18440.

Noted, not filed: nothing else. Successor for the `M9.9b` / `M9.5` /
`M9.7` ROADMAP citations elsewhere in the tree: none — no PR or person
is routed to those files by this work, and the ruling names only the
M9.1 / M9.2 phase promise.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3)_

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

1 participant