Skip to content

test(create-objectstack,metadata): make two version-sensitive tests hold across the 17.5.0 version pass - #20565

Merged
hotlong merged 2 commits into
mainfrom
claude/objectstack-release-steps-ynhz3j
Sep 29, 2026
Merged

hotlong merged 2 commits into
mainfrom
claude/objectstack-release-steps-ynhz3j

Conversation

@hotlong

@hotlong hotlong commented Sep 29, 2026

Copy link
Copy Markdown
Contributor

Fixes #20560

Unblocks the 17.5.0 Version Packages PR #17076 (changeset-release/main). Its required Test Core (1/6) is red on head de35e567 (CI run 36510180528):

AssertionError: scaffolded Dockerfile pins :17.4.2 but the scaffolded package.json asks for "^17.5.0" — the generated app would run a runtime image that is not the CLI building its artifact (#9017): expected false to be true

Both changes are test-only. No runtime code moves, and neither file ships: both packages publish only dist, README.md and CHANGELOG.md, and neither file is a tsup entry. So there is no changeset and the PR carries skip-changeset.

1. create-objectstack: the runtime-image fixture (the reported failure)

runtime-image.test.ts planted the npm-resolved @objectstack/cli as the literal ${major}.4.2. The scaffolder writes a caret range on its own version, so that literal is inside the range only while the minor is 4 or lower. At 17.5.0 it falls below the floor, and the agreement test blamed the scaffolder for what was a fixture defect.

  • resolved is now derived from the package's own version: same major, own minor + 1, patch 2 (17.4.0 gives 17.5.2, 17.5.0 gives 17.6.2). It is always inside the caret range and always above its floor, which is what the fixture's own comment says it is for.
  • A new precondition test, plants a resolved version inside the scaffolded range and above its floor, makes future fixture drift point at the fixture rather than at the scaffolder.

I checked the other create-objectstack tests for a current-minor assumption. They key on the major only (template-consistency), use fixed literals unrelated to the package version (the readResolvedCliVersion cases, banner-version), or carry a fixture version of their own (template-version-stamps). The whole suite passes on the versioned tree (table below).

2. metadata: artifact-door notice wording (found by running the unreached shard-1 packages on the versioned tree)

Turbo stopped shard 1 at the failure above, so ten scheduled packages never ran against 17.5.0. Running them found one more failure of the same class:

FAIL src/plugin-artifact-forward-conversion-retired-after.test.ts > [#20390] artifact door — a 17.4.0-built artifact boots on unreleased main > logs one conversion summary per retired entry it replayed, naming the site count
AssertionError: expected '[MetadataPlugin] artifact \'forward-p…' not to contain 'predates this runtime\'s spec'

The fixture is a 17.4.0-built artifact (engines.protocol: ^17.4.0). While packages/spec carries the 17.4.0 label, the floor is not below the label. The per-entry half of the window opens (converted-retired-after), and its clause deliberately avoids "predates". The version pass moves the label to 17.5.0 and the floor falls below it. The label half opens instead (converted-forward), and the door correctly prints predates this runtime's spec (authored engines.protocol floor 17.4.0, runtime spec 17.5.0). The runtime behaves as designed; the assertion had the pre-release label built in.

The assertion now reads the running spec label the same way the door does, with resolveInstalledSpecVersion(). plugin-artifact-forward-conversion.test.ts already uses that pattern. The test then pins the exact clause of whichever half that label selects. Both halves were exercised: the per-entry clause on main (label 17.4.0), and the predates clause with the exact floor and runtime version on the versioned tree (label 17.5.0).

Verification on the versioned tree

I made a throwaway worktree at origin/changeset-release/main = de35e567, the #17076 head that the failing run tested, and copied both fixes in. Then:

  1. pnpm install --frozen-lockfile.
  2. pnpm turbo run build over each package's dependency closure (--filter='PKG^...'; 61 tasks, 0 cached).
  3. Each package's own script via pnpm --filter PKG run SCRIPT, with the env Test Core sets (OS_TEST_TIERS=queue, CI=true).
Package Script Result on de35e56 + this PR
create-objectstack test pass: 16 files / 234 tests. Without the fix: 1 failed, the assertion quoted above
@objectstack/spec test pass: 573 / 16846, 1 todo
@objectstack/spec test:repo pass: 40 / 716
@objectstack/verify test pass: 15 / 116
@objectstack/metadata test pass: 55 / 826. Without the fix: 1 failed, the assertion quoted in section 2
@objectstack/plugin-audit test pass: 25 / 363
@objectstack/example-todo test pass: 7 / 238
@objectstack/trigger-record-change test pass: 10 / 101
@objectstack/formula test pass: 42 / 1227
@objectstack/downstream-contract test pass: 3 / 31
@objectstack/service-cluster-redis test pass: 3 / 37
@objectstack/knowledge-ragflow test pass: 1 / 10

Of shard 1's other scheduled packages, @objectstack/types had already passed in that run, and shards 2–6 were green. So every package that run scheduled has now passed against the de35e567 tree.

Checks on this branch (base b0574343)

  • pnpm --filter create-objectstack test: 16 files / 234 tests pass; typecheck clean.
  • pnpm --filter @objectstack/metadata test: 55 / 826 pass (the per-entry half); typecheck clean.
  • eslint on both changed files: clean.

main has since moved to f6ceddc3, six commits on. None of them touches packages/create-objectstack, packages/metadata or packages/metadata-core.

A wider gap is tracked separately in #20562 and not addressed here: a version-sensitive test is only exercised after pnpm run version.


Generated by Claude Code

… CLI version from the scaffolder's own

The "with dependencies installed" fixture planted `<major>.4.2` as the
npm-resolved @objectstack/cli. The scaffolder writes `^<ownVersion>`, so
that literal sits inside the range only while create-objectstack's minor
is at most 4: the 17.5.0 version pass put it below the floor and the
agreement test failed with "scaffolded Dockerfile pins :17.4.2 but the
scaffolded package.json asks for ^17.5.0" — a fixture defect reported as
a scaffolder one.

The resolved version is now same major, next minor, patch 2 — always
inside `^<ownVersion>` and always above its floor, which is what the
fixture's comment says it is for. A new precondition test asserts exactly
that, so a future fixture drift names the fixture instead of the
scaffolder.

Claude-Session: https://claude.ai/code/session_014VGCS11YUtYAiinRcdqQwL
Co-authored-by: Claude <noreply@anthropic.com>
…not as the pre-release literal

"logs one conversion summary per retired entry it replayed" asserted that
no conversion notice says "predates this runtime's spec". That holds only
while packages/spec still carries the 17.4.0 label: the 17.4.0-floored
fixture then sits in the per-entry half of the window. The version pass
moves the label to 17.5.0, the floor falls below it, the label half opens
the window (verdict converted-forward), and the door correctly prints
"predates this runtime's spec (authored engines.protocol floor 17.4.0,
runtime spec 17.5.0)" — so the test went red on the versioned tree while
the runtime behaved exactly as designed.

The assertion now reads the running spec's label the way the door does
(resolveInstalledSpecVersion) and pins the clause of whichever half that
label selects: the per-entry "was built on a surface that still accepted
shapes this runtime has since retired" clause below the floor, the
"predates" clause with the exact floor and runtime version above it.

Claude-Session: https://claude.ai/code/session_014VGCS11YUtYAiinRcdqQwL
Co-authored-by: Claude <noreply@anthropic.com>
@hotlong hotlong added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 29, 2026 — with Claude
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see

Coarse fallback — 0 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 f6ceddc3c4deb9d56025fff9fae03d65b62e95a7 → packageMentionDocs.

@hotlong
hotlong marked this pull request as ready for review September 29, 2026 03:29
@hotlong
hotlong enabled auto-merge September 29, 2026 03:29
@hotlong
hotlong added this pull request to the merge queue Sep 29, 2026
Merged via the queue into main with commit 1c761c0 Sep 29, 2026
42 checks passed
@hotlong
hotlong deleted the claude/objectstack-release-steps-ynhz3j branch September 29, 2026 03:48
veigajoao pushed a commit to veigajoao/objectstack that referenced this pull request Sep 29, 2026
… sites to the commits that decided them (stage 5) (objectstack-ai#20576)

Part of objectstack-ai#20234
Clause-②: no

## What changed

This is stage 5 of the staged sweep: the `ui/` area
(`packages/spec/src/ui/**`, 133 files), plus two sites freed since stage
4: `data/filter-subtree-provenance.ts` (PR objectstack-ai#20460 landed without
touching it) and `meta-spelling/manifest-collection-spelling.ts` (the
census hand-over, comment 5882628946 on objectstack-ai#20234).
`ui/view-grouping-query.ts` is excluded because objectstack-ai#20446's claim holds it;
it carries 4 citations and none of them is dead, so the exclusion
removes nothing. Later stages cover the other areas, so this PR says
`Part of`.

Every comment and docblock site in that population that cites a tracker
number answering 404 has been rewritten in ruling C+D's form C (comment
5749154545 on objectstack-ai#19123). That is **87 comment sites**: 84 re-anchored and
3 respelled.
- **84 re-anchored, over 25 numbers.** Each rewritten line now cites the
commit in `origin/main` history that decided what the line describes,
and says in its own words what that commit decided. One line
(`ui/dashboard.zod.ts:686`) quotes ADR-0087 itself; it keeps ADR-0087 as
its citation and paraphrases the amendment heading instead of quoting
its number.
- **3 respelled**, so each number of a sibling pair carries its own
qualifier: `ui/view.zod.ts:3634` now reads `objectui#6110 +
objectui#6111`, and `ui/component.zod.ts:3479` and `:4140` now read
`objectui#8221's PR objectui#8758`. Each second number answers 404 here,
and the sentence attributes it to objectui (objectui REST: `issues/6111`
200, `pulls/8758` 200, merged 2026-09-09).

Only comments changed, plus the one generated reference page they
project into and a patch changeset. Every source file keeps its line
count (90 lines out, 90 in, over 25 files). Three of the 90 lines held
no dead number; each is the other half of a rewritten sentence:
`ui/action.zod.ts:400`, `ui/action-param-carryover.test.ts:13` and
`ui/expression-bindable-text-keys.test.ts:119`. No code token moves (see
the guard below).

**No tracker number is added.** Every tracker number on an added line
was already on the lines it replaces, and no `PR #N` is added.

## Census: before and after

**Instrument.** This is the instrument of stages 1 to 4, rebuilt for
this stage. It sends REST `GET
/repos/objectstack-ai/objectstack/issues/N` without following redirects,
for every distinct number cited in the population. The population is:
- the citation gate's own exported `CITATION_RE` and
`NON_CITATION_HEADS` at the base, kept when the qualifier is none,
`objectstack`, `objectstack-ai/objectstack`, `framework`, `pre-` or
`post-`;
- matched case-insensitively (`Pre-`, `POST-`, `Framework`);
- N of 100 or more, excluding `summon` heads.

A qualifier covers only the number it is joined to. Each site is
classified by the TypeScript parser as a line comment, a docblock, a
block comment or a string.

**Controls.** The lit controls were `objectstack-ai#16862`, `objectstack-ai#16847` and `objectstack-ai#17698`. The
dead controls were `objectstack-ai#16714`, `objectstack-ai#16715` and `objectstack-ai#16697`. They were probed at
the start, after every 100 numbers and at the end: 18 of 18 lit (200)
and 18 of 18 dead (404) over 6 checkpoints in the base run, and 15 of 15
and 15 of 15 over 5 checkpoints in the head run.

| reading | tree | numbers probed | 200 | 404 | 301 or other | dead
sites | lines | files | of which comments | of which strings |
|---|---|---|---|---|---|---|---|---|---|---|
| before | base `487a7846df`, probed 2026-09-29T02:57:01Z to 02:59:36Z |
400 | 372 | 28 | 0 | **111** | 110 | 26 | 90 | 21 |
| after | head `83e39641d0`, probed 2026-09-29T03:15:00Z to 03:18:06Z |
383 | 372 | 11 | 0 | **24** | 23 | 8 | 3 | 21 |

The head probe found no number newly dead since the base probe: the same
372 numbers answer 200. The head was probed at `83e39641d0`; every
census file is byte-identical at the final head.

**Cross-check under the grammar that landed during this stage.** PR
objectstack-ai#20554 (`199002b3e4`) landed the closed qualifier set while this stage
ran, and it reads `objectui PR objectstack-ai#8758` as objectui's number. Re-run with
that gate's own `extractCitations` and `namesThisRepository`, the same
population reads **108** dead sites before and **21** after, all 21 test
strings. The difference is exactly the three `objectui PR objectstack-ai#8758` prose
sites below, which that PR's own header measured as "census deaths here
that are not deaths at all".

**Per file.** Cited sites are every in-repo citation the population
reads, live or dead.

| file | cited sites (base) | dead before | by class | dead after |
|---|---|---|---|---|
| `data/filter-subtree-provenance.ts` | 9 | 3 | 3 docblock | 0 |
| `meta-spelling/manifest-collection-spelling.ts` | 8 | 2 | 2 line
comment | 0 |
| `ui/action-param-carryover.test.ts` | 5 | 3 | 2 line comment, 1 string
| 1 |
| `ui/action.test.ts` | 39 | 3 | 3 line comment | 0 |
| `ui/action.zod.ts` | 90 | 7 | 5 docblock, 2 line comment | 0 |
| `ui/bulk-action.test.ts` | 9 | 4 | 2 line comment, 2 string | 2 |
| `ui/bulk-action.zod.ts` | 9 | 3 | 2 docblock, 1 line comment | 0 |
| `ui/component-element-navigation-17987.test.ts` | 8 | 5 | 1 docblock,
4 string | 4 |
| `ui/component-type-vocabulary.test.ts` | 8 | 2 | 2 docblock | 0 |
| `ui/component-type-vocabulary.ts` | 2 | 1 | 1 docblock | 0 |
| `ui/component.test.ts` | 190 | 22 | 11 line comment, 11 string | 11 |
| `ui/component.zod.ts` | 265 | 20 | 16 docblock, 4 line comment | 0 |
| `ui/dashboard.zod.ts` | 52 | 1 | 1 docblock | 0 |
| `ui/expression-bindable-text-keys.test.ts` | 3 | 2 | 2 line comment |
0 |
| `ui/expression-bindable-text-keys.zod.ts` | 4 | 2 | 2 docblock | 0 |
| `ui/form-select-option.test.ts` | 3 | 1 | 1 docblock | 0 |
| `ui/index.ts` | 15 | 2 | 2 line comment | 0 |
| `ui/interaction-config-retirement.test.ts` | 19 | 1 | 1 docblock | 0 |
| `ui/react-blocks.test.ts` | 9 | 2 | 1 docblock, 1 string | 1 |
| `ui/react-blocks.ts` | 17 | 4 | 2 docblock, 2 line comment | 0 |
| `ui/view-form-features-root.test.ts` | 4 | 1 | 1 line comment | 0 |
| `ui/view-metadata-schema.test.ts` | 31 | 3 | 2 line comment, 1 string
| 1 |
| `ui/view-submit-redirect-url.test.ts` | 8 | 1 | 1 line comment | 0 |
| `ui/view.test.ts` | 136 | 2 | 1 docblock, 1 string | 2 |
| `ui/view.zod.ts` | 331 | 13 | 10 docblock, 3 line comment | 2 |
| `ui/widget-i18n-retirement.test.ts` | 17 | 1 | 1 line comment | 0 |

`ui/` alone went from 106 dead sites in 24 files to 24. The other 107
`ui/` files carry no dead site.

## Per-number table

Anchors are 9-hex commit abbreviations. "Wrote" means the commit's own
diff added the line being rewritten.

| number | comment sites / files | anchor: what it decided |
|---|---|---|
| `objectstack-ai#5970` | 4 / 2, `action.zod.ts:819`, `action.test.ts:268`, `:304`,
`:354` | `97e7e3caa`: `ActionSchema.visible` / `disabled` speak one
condition shape; `visible` gains its boolean arm. Stage 1's anchor for
the same number |
| `objectstack-ai#6276` | 7 / 1, `component.zod.ts:34`, `:165`, `:568`, `:2455`,
`:2546`, `:2554`, `component.test.ts:2126` | `78f0be872`: declares
`element:record_picker`'s flat `sort` / `limit` on the objectstack-ai#5611 rule
(maintainer ruling 2026-08-08, direction A). It wrote `:34`, `:2455` and
the "enumerate by the renderer's read pattern" lesson |
| `objectstack-ai#8794`, `objectstack-ai#8836` | 3 / 1, `filter-subtree-provenance.ts:130`, `:131`,
`:156` | `1850ebbb0`: corrects the reuse-safety claim from the survey
and pins the invariant. It wrote `:131` itself. Stages 1 and 3 gave both
numbers this anchor |
| `objectstack-ai#9933` | 7 / 2, `view.zod.ts:2517`, `:5060`, `:5086`, `:5118`,
`:5513`, `view-metadata-schema.test.ts:398`, `:410` | `d5552ca13`:
admits `columnState` as an explicitly runtime-only view-overlay key,
rejected by name at every authoring door. It wrote "explicitly out of
objectstack-ai#9933's scope" |
| `objectstack-ai#9972` | 3 / 2 (+1 unread), `component.zod.ts:2213`,
`component.test.ts:382`, `:3565`; also `:3612`'s `objectstack-ai#9881/objectstack-ai#9972`, a
slash-joined spelling the grammar does not read | `60e0f900a`: records
the live read point of `page:tabs` `items[].icon` and its accept-pin. It
wrote the `:382` header |
| `objectstack-ai#10194` | 1 / 1, `manifest-collection-spelling.ts:71` (`pre-objectstack-ai#10194`)
| `2306a765c`: `/meta/theme` and `/meta/analytics_cube` stop storing any
JSON as success and validate at the write door. The line now says "the
store-anything branch from before commit 2306a76". Stage 1's anchor |
| `objectstack-ai#10274` | 6 / 2, `component.zod.ts:2304`, `component.test.ts:308`,
`:405`, `:3591`, `:3603`, `:3612` | `d1ba685ec`: re-measures the four
pin citations and gates the class. Its gate header records that the
re-measure found two anchors wrong since they were written, which is why
a refresh re-reads. Stage 3's anchor |
| `objectstack-ai#10485` | 4 / 4, `index.ts:51`,
`interaction-config-retirement.test.ts:116`,
`widget-i18n-retirement.test.ts:112`,
`manifest-collection-spelling.ts:67` | `35ad101bc`: retires the `themes`
carrier and `ThemeSchema` whole (ruled B, 2026-08-21; ADR-0049 stays
cited). Stage 1's anchor |
| `objectstack-ai#11284` | 5 / 2, `react-blocks.ts:39`, `:94`, `:109`, `:295`,
`react-blocks.test.ts:139` | `5383fa670`: the react tier converges on
the metadata-tier vocabulary, deprecate-first. Its changeset heads
"(objectstack-ai#11284, maintainer ruling 2026-08-23)" |
| `objectstack-ai#11350` | 1 / 1, `index.ts:105` | `ece4dad31`: records the maintainer
ruling of 2026-08-23 that a type in an entry's public declarations must
be nameable from that entry. Same wording as stage 1's
`kernel/index.ts:53` |
| `objectstack-ai#11507` | 2 / 2, `component.zod.ts:1465`, `component.test.ts:2726` |
`88b9d749a`: declares `sys_activity.type` an open, author-extensible
vocabulary (maintainer ruling 2026-08-24, direction 4). Stage 3's anchor
|
| `objectstack-ai#11658` | 2 / 2, `component.zod.ts:1464`, `component.test.ts:2725` |
`1a6a19c31`: opens `RecordActivityProps.types` to author-contributed
kinds, executing that ruling. Stage 3's anchor |
| `objectstack-ai#11703` | 3 / 2, `action.zod.ts:399` to `:400`, `:460`,
`action-param-carryover.test.ts:12` to `:13` | `5cb62d88b`:
`clone_permission_set` carries all five copied facets; its params list
had silently dropped three. The lines now name "the silent-drop shape
commit 5cb62d8 fixed" |
| `objectstack-ai#11753` | 5 / 2, `action.zod.ts:66`, `:390`, `:398`, `:409`,
`action-param-carryover.test.ts:1` | `0e4e51b0a`:
`ActionParamSchema.carryOver`, the spec half of the 2026-08-25
maintainer ruling (recommendation A). It wrote every one of these lines,
and its changeset records the `visible: false` measurement `:398` names
|
| `objectstack-ai#12194` | 1 / 1, `view.zod.ts:4838` | `311433f6b`: declares the
metadata item-name grammar (`QUALIFIED_ITEM_NAME_PATTERN` among it) and
refuses it at the publish door. Stage 2's anchor |
| `objectstack-ai#12868` | 5 / 2, `view.zod.ts:2938`, `:2966`, `:3179`, `:7087`,
`form-select-option.test.ts:4` | `c459da6bc`: narrows the per-option
`default` key out of the form-view options vocabulary. Its changeset
records the ruled census `:2966` cites ("measured ZERO occurrences").
Stages 3 and 4's anchor |
| `objectstack-ai#12950` | 3 / 2, `component-type-vocabulary.ts:4`,
`component-type-vocabulary.test.ts:4`, `:101` | `225e7690f`: created
`component-type-vocabulary.ts`; its message records the readiness read
`:101` pins (`global:search` and `global:notifications` stay declared) |
| `objectstack-ai#13156` | 2 / 2, `view-form-features-root.test.ts:70`,
`view-submit-redirect-url.test.ts:110` | `fd289be45`: strips tracker ids
from function-declaration-built refusal prose. It wrote both lines.
Stage 3's wording ("commit fd289be's strip") |
| `objectstack-ai#13670` | 1 / 1, `expression-bindable-text-keys.zod.ts:72` |
`8c6a7fc0b`: records `text.value` as deliberately omitted; its message
states the ruling that `text`'s evaluation channel is `content` alone |
| `objectstack-ai#13672` | 3 / 2, `expression-bindable-text-keys.zod.ts:89`,
`.test.ts:65`, `:118` | `e854a531a`: narrows the `button` row to the
spelling its key reaches, and records `action:button` and `ui:button` as
deliberately out |
| `objectstack-ai#16626` | 1 / 1, `component.test.ts:2336` | `30b099078`: the objectui
pin bump to `53ded82bf7a4` that ships objectui#7754's array-analytics
lowering, the door the family waited on. The association is PR objectstack-ai#16788's
body (it names objectstack-ai#16626 as the card it lands), and `30b099078` is that
PR's merge commit; neither its message nor its diff names objectstack-ai#16626 (the
stage-3 objectstack-ai#11065 precedent) |
| `objectstack-ai#17987` | 9 / 2, `component.zod.ts:10`, `:4014`, `:4062`, `:4153`,
`:4193`, `:5068`, `:5226`, `:5457`,
`component-element-navigation-17987.test.ts:4` | `e233db9db`: declares
element-level `navigation` on `object-kanban` / `object-calendar` and
gives `object-timeline` its `ComponentPropsMap` row, executing the
objectui#8652 ruling (verbatim `B`) |
| `objectstack-ai#18003` | 1 / 1, `dashboard.zod.ts:686` | **ADR-0087**, the rung
above a commit. The line quoted the ADR's own amendment heading, number
included. It now reads "(ADR-0087, its 2026-09-13 amendment, 「the level
half」)": the ADR stays the citation and the fragment it quotes is
verbatim |
| `objectstack-ai#18177` | 5 / 2, `bulk-action.zod.ts:51`, `:169`, `:262`,
`bulk-action.test.ts:61`, `:307` | `adabccf5f`: `BulkActionParamSchema`
is strict and declares `dependsOn`, executing decision batch objectstack-ai#146 item
4, letter A |
| `objectstack-ai#6111` | 1 / 1, `view.zod.ts:3634` | respelled `objectui#6111` (not
re-anchored): it is objectui's number |
| `objectstack-ai#8758` | 2 / 1, `component.zod.ts:3479`, `:4140` | respelled `PR
objectui#8758` (not re-anchored): objectui's PR objectstack-ai#8758, merged 2026-09-09
|

No ADR or ruling-record file in `docs/adr/` or `scripts/adr-anchors/`
records the decision behind any of the 25 re-anchored numbers except
objectstack-ai#18003. ADR-0126 mentions objectstack-ai#11703 and objectstack-ai#11753 only as references
("permission-set precedent"), not as the record of either decision.

**Anchor checks.** Every sha on an added line is one of 23, and none is
on a removed line. At the base `487a7846df`:
- each matches exactly one object (`git rev-parse --disambiguate`, count
1, 23 of 23);
- each is an ancestor (`git merge-base --is-ancestor`, exit 0, 23 of
23); the control leg `e9584681a4` also exits 0, and the repository is
not shallow;
- for 22 of the 23, a grep of the commit's own message or diff finds the
number it replaces (the message for 16; the diff for `0e4e51b0a`,
`5383fa670`, `c459da6bc`, `225e7690f`, `e854a531a`, and for objectstack-ai#8836 in
`1850ebbb0`). `30b099078` is the exception explained in the table.
- Each commit was read for the rule its line states, not only for the
number. In most cases the commit wrote the very line it now anchors.

Wordings to check, each true of its commit:
- `filter-subtree-provenance.ts:130` and `:156` read 「survey commit
1850ebb records」: the survey was the card's, and the commit's message
records its measurement. It is stage 3's wording for the same relation
(「from the survey it records」).
- `component.zod.ts:1465` and `component.test.ts:2726` read 「maintainer
ruling commit 88b9d74 declared」: that commit landed the ruling
(direction 4) as the `sys_activity.type` declaration.
- `manifest-collection-spelling.ts:71` reads 「the store-anything branch
from before commit 2306a76」: before that commit, `PUT
/meta/theme/:name` stored any JSON as success.

## Mechanical guard: no code token moves

The check compares leaf tokens with comments stripped, base `487a7846df`
against the head. It uses the TypeScript parser's leaf tokens from the
head's lockfile, so template literals are scanned in context, and it
excludes JSDoc nodes. It ran over all 25 touched `.ts` files, and every
control mutates the head text in memory only.

- Real run: 101,836 base tokens, **0 files with a token change** (exit
0).
- Comment-insertion control (`ui/index.ts`): 0 files changed (exit 0).
- Positive control (a declaration inserted into `ui/view.zod.ts`): 1
file reads DIFFER at token 19222 (exit 1).
- Positive control (one digit changed in a `component.test.ts` test
title): 1 file reads DIFFER at token 14972 (exit 1).

Line balance holds in every file, 90 out and 90 in over the 25, and
every line count is equal at base and head. Tracker numbers:
added-not-removed is empty in every file. The net-removed numbers are
the 25 in the table, 85 sites: the census's 84 comment sites, plus the
slash-joined `objectstack-ai#9972` at `component.test.ts:3612`.

## Generated page

`check:generated` proved one artifact stale:
`content/docs/references/ui/expression-bindable-text-keys.mdx`, the
projection of `expression-bindable-text-keys.zod.ts`'s module docblock.
`check:generated --fix` regenerated only that page, and a re-run read
`All 15 generated artifacts are up to date`. Its two changed lines are
the `:72` and `:89` substitutions verbatim. No other docblock here
projects into a reference page, and nothing under `skills/**` moved.

## Changeset

This change ships bytes, so a `patch` changeset for `@objectstack/spec`
is included. It says only that the provenance comments were re-anchored.
`Clause-②: no`: no export, key, value or type moves (the guard above).

Measured on the head's built package: 6 touched sources are
`src/**/*.zod.ts`, which `files[]` ships verbatim. The rewritten
comments also reach `dist`:
- `c459da6bc` appears in 32 bundled `.js` files and 2 `.d.ts`;
- `adabccf5f` in 24 `.js` and 2 `.d.ts`; `d5552ca13` and `0e4e51b0a` in
24 `.js` each; `e233db9db` and `78f0be872` in 2 `.js` and 2 `.d.ts`
each;
- the positive control, the pre-existing sentence 「the object-field face
enforces」, appears in 32 files.

## Gates (head `1b885d3c27`)

- **Citation judging pass, run as CI runs it:** `pnpm
check:issue-citations && node scripts/check-issue-citations.mjs`, both
under the grammar PR objectstack-ai#20554 landed, exit 0. The self-test passes 114
cases in 8 batteries. The live, diff-scoped run judged 13 citations
across 11 files: 3 resolve and 10 are declared cross-repo references. It
reads "every citation this change adds resolves".
- **Doc authoring:** `pnpm check:doc-authoring` exits 0. Its 16,759
customer-facing strings across 1,174 spec sources carry no internal
issue id, and the sibling-package prose-id baseline holds with no
growth.
- **Derived gates:** `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` at this head derived 112
families, and all 112 exit 0. `--ran` reports 112 run, 0 NOT MEASURED, 0
unrun. A full `turbo run build` of `./packages/*` at this head ran
first, under the shared verify lock: 71 of 71 tasks, VERDICT
command-exit 0. So no gate met an unbuilt prerequisite.
- **Five roster gates the derivation flags for this diff** (their
rosters sit in `.changeset/` or `packages/`, so their silence proves
nothing): `node scripts/check-changeset-fixed.mjs`, `pnpm --filter
@objectstack/spec run check:spec-changes`, `pnpm check:authz-resolver`,
`pnpm check:error-code-casing` and `pnpm check:filter-alias-parity`. All
exit 0.
- `pnpm --filter @objectstack/spec run check:generated` (derived) reads
`All 15 generated artifacts are up to date`, and `check:docs` reads `226
generated files in sync`.
- **Tests and typecheck, under the lock:**
- `pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/ui
src/meta-spelling`: Test Files 98 passed (98), Tests 3452 passed (3452),
VERDICT command-exit 0 (the chain held the lock 142s on a shared box).
- The 16 spec suites outside `src/ui` that read the touched files'
source text or pin their lines: Test Files 16 passed (16), Tests 489
passed (489). They are
`scripts/{export-origins,file-description,root-index,schema-closure,skill-map-guards,strictness-ledger}.test.ts`,
`src/ai/tool-confirmation-prescription-tense.pin.test.ts`,
`src/api/api-entry-graph.pin.test.ts`,
`src/contracts/scoped-context.test.ts`,
`src/data/filter-subtree-provenance.test.ts`,
`src/shared/{alias-integrity,evaluated-slot-population,retired-key-migrate-sentence,union-author-message-pins}.test.ts`,
`src/system/constants/platform-object-names.test.ts` and
`src/type-alias-convention.pin.test.ts`. Three more suites matched the
reader scan and are not run here:
`scripts/build-schemas-check-mode.test.ts` only imports `ViewItemSchema`
(code the guard proves unchanged) and rebuilds schemas in a temp tree;
`scripts/def-key-collisions.test.ts` names `ui/view.zod.ts` only in a
comment; `scripts/published-projection-choke-point.test.ts` matched on
`build-react-blocks-contract.ts`, not a touched file. They are left to
CI.
- `pnpm --filter @objectstack/spec typecheck`: exit 0, including
`check:test-typecheck` (53 files, 251 errors, 138 pinned signatures
held). The same three runs also passed, with the same counts, on the
pre-merge tree.
- **Lint, as a proven narrowing:** `eslint --no-inline-config --format
json` over the 25 touched `.ts` files gives 25 files, 0 errors and 0
warnings. All 25 are in eslint's own population (`isPathIgnored` is
false for each, read through eslint's API). `eslint.config.mjs` never
enables type-aware linting (no `parserOptions.project`, which its own
lines 327 to 328 state), so a comment edit here cannot move the verdict
on any untouched file. The repo-wide `pnpm lint` is CI's run.
- **Merge probe:** a `merge-tree` of the head onto `origin/main`
`f572a7eb3c`, from a bare shared clone with no merge driver registered,
exits 0 (2026-09-29T04:15Z).

## Acceptance notes

- **Base and merge.** The branch forked from `487a7846df`, one commit
past the claim's stamp `6154165484` (PR objectstack-ai#20551, outside the surface).
`origin/main` then moved three commits, and `199002b3e4` (PR objectstack-ai#20554)
changed `scripts/check-issue-citations.mjs`, so the gate derivation read
STALE TREE. `origin/main` `dee9b26f6c` was merged in (`1b885d3c27`): a
clean merge with no driver-routed path and no lockfile change, touching
none of this diff's files. The PR's delta against `dee9b26f6c` is
exactly its 27 files. `origin/main` has since moved one more commit,
`1c761c0d71` (PR objectstack-ai#20565, tests in two other packages), which touches
none of them.
- **One site beyond the hand-over's list.** The hand-over named
`manifest-collection-spelling.ts:71` (`pre-objectstack-ai#10194`). The same comment's
first line, `:67`, cites `objectstack-ai#10485`, which also answers 404, and it is
rewritten too. The claim names this file; the fix is the same defect
class, mechanical in the form stages 1 to 4 fixed, in a file no other
claim holds, under the same gates. Reverting it would be one line.
- **Open PRs, re-read at 2026-09-29T04:22Z:** 8 open PRs, and none
touches any file in this diff. The one that touches `ui/` is objectstack-ai#20570
(objectstack-ai#20446's), on the excluded `view-grouping-query.ts`. The in-flight
`Claim:` comments on the 12 `pm:dispatched` cards were read too: only
objectstack-ai#20446's names a `ui/` file (`view-grouping-query.ts`, excluded above).
- **Hypothesis 2, measured.** In `ui/`, 14 sibling-qualified pairs leave
the second number bare: 13 on one line (`+`, `and`, `,`, `/` or `'s PR`
between them) and `component.zod.ts:3478` to `:3479`, split across a
line break. In 3 of them the second number answers 404 here and the
sentence attributes it to objectui (`objectstack-ai#6111` once, `objectstack-ai#8758` twice); they
are respelled above. In the other 11 the second number answers 200 here,
so it is judged as this repository's and left: `action.zod.ts:1603`,
`component.test.ts:2052`, `:2199`, `:2315`, `component.zod.ts:3276`,
`:3793`, `:4812`, `expression-bindable-text-keys.zod.ts:33`,
`page.test.ts:696`, `react-blocks.ts:256` and `widget.zod.ts:34`. The PR
objectstack-ai#20554 header measured `,` and `and` pairs as naming this repository's
number and `/` pairs as mostly, but not always, the qualifier's. Whether
any `/` pair here names objectui's number is not measured; a 200 here
cannot tell.
- **What stays in this population: 24 dead sites** (21 under the landed
grammar).
- **21 test strings**, left as tokens (vitest `it` / `describe` titles
in 7 test files): `objectstack-ai#6276` ×6, `objectstack-ai#11658` ×3 and `objectstack-ai#11507` ×1 in
`component.test.ts`; `objectstack-ai#9972` in `component.test.ts:411`; `objectstack-ai#17987` ×4 in
`component-element-navigation-17987.test.ts`; `objectstack-ai#18177` ×2 in
`bulk-action.test.ts`; `objectstack-ai#9933` in `view-metadata-schema.test.ts:406`;
`objectstack-ai#11284` in `react-blocks.test.ts:147`; `objectstack-ai#11753` in
`action-param-carryover.test.ts:17`; `objectstack#11195` in
`view.test.ts:3396`. None is a Zod `.describe()` text, an exported
string or a migration-entry field, so no form D site arises here.
- **3 comments that name objectui's live PR objectstack-ai#8758 in prose** (`objectui
PR objectstack-ai#8758`): `view.zod.ts:2354`, `:2579` and `view.test.ts:426`. They are
not pairs, and the landed grammar reads them as objectui's.
- **Left for later stages of objectstack-ai#20234** (the stage-4 landing comment
5882686893's list, unchanged): the migrations area, the `liveness/**`
notes, the `why` strings and the `PROVENANCE_WAIVERS` reason,
`rest-server.zod.ts`, the held `analytics*` and `driver/turso.*` files,
the 2 `AGGREGATION_CASES` note strings, and `data/`'s test strings and
deliberate markers.
- **The same rot outside `packages/spec/src`** is objectstack-ai#20556's, not this
card's. Examples met here: ADR-0087's own amendment heading
(`docs/adr/0087-metadata-protocol-upgrade-contract.md:390`, `:397`)
cites the dead `objectstack-ai#18003`, and ADR-0126 cites `objectstack-ai#11703` and `objectstack-ai#11753`; both
are governed. `packages/spec/scripts/strictness-ledger.test.ts:375`,
`:380` cite `objectstack-ai#9933`, and `check-objectui-pin-citations.ts` cites
`objectstack-ai#10274` and `objectstack-ai#9972`.
- **Unchanged wording.** `react-blocks.test.ts:140` says the 2026-08-23
ruling was "recorded on-card". The card is gone, and the changeset of
`5383fa670` (now cited on `:139`) records the ruling. The line holds no
number, so it is left.
- **The citation gate's reach.** It defers `packages/**/*.test.ts`. The
11 touched non-test files are in its judging population.

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

---------

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

size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

2 participants