Skip to content

docs(releases): finalize the 17.5.0 notes after publish - #20623

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

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

Conversation

@hotlong

@hotlong hotlong commented Sep 29, 2026

Copy link
Copy Markdown
Contributor

What this is

The release-time half of the 17.5.0 release notes. The page content/docs/releases/v17/17-5.mdx landed before the cut (#20396) with a RELEASE-TIME TODO comment listing four edits to make once 17.5.0 was on npm. 17.5.0 was published on 2026-09-29 (@objectstack/cli@17.5.0 at 07:58Z, the last package, @objectstack/spec, at 08:09Z). This PR makes those edits, deletes both TODO comments, and updates content/docs/releases/v17/index.mdx.

Docs-only: two files under content/docs/releases/, which is release-owned, so this is the dedicated docs-only PR AGENTS.md sanctions for that tree. It publishes nothing from any package, hence skip-changeset.

What changed

17-5.mdx

  • Publish date. "What's new" now opens: 17.5.0 was published to the latest tag on 2026-09-29, 20 days after 17.4.0.
  • Count. The draft said it was compiled from "868 changesets pending on main at ab6fb027". The version commit 8c87d26a (chore: version packages #17076) actually consumed 958 changesets (the .changeset/*.md files it deletes, README excluded). The page now states 958 as its measure and cross-checks it against the CHANGELOGs: the 69 package CHANGELOG.md files that carry a 17.5.0 section at 8c87d26a list 1,372 per-package entries (703 minor, 669 patch, 0 major) in 56 of those files, and those entries de-duplicate to exactly the same 958.
  • The 90 changesets the draft never read, the ones consumed by 8c87d26a but not pending at ab6fb027, were each read in full and folded in:
    • Breaking changes & migration: 45. Two new subsections: Written values are held to the field's declared type (date and datetime ISO spellings on a real day, the year range 0001–9999, the numeric string grammar, precision, progress bounds, /import thousands commas, with a Migration table) and An edge-branched decision takes its first matching branch (feat(automation)!: edge-branched decision is exclusive; mode: 'inclusive' takes every branch (#15429) #20344, with the stored-row caveat). The rest joined existing subsections: RLS cross-class comparisons; org-less grants; cube public; number comparands, having placeholders and double accumulation; flow node config, connector_action, api flow secrets and the connector resilience keys; list-view tabs, action aria and view round-trip keys; /diff /history /audit as authoring doors and OpenAPI info; remote Turso and unbuildable indexes; the one stack authoring shape and new lint positions; QA requires, narrowed published types and retiredAfter.
    • New capabilities: 11. Studio form rows for 27 structured keys, the staged $empty operator, the new ComponentPropsMap rows, and email verification under open.
    • Notable fixes: 15. Dispatcher-only hosts, /diff default range, plain-text email faces, auth-settings sibling isolation, SQLite reclaimSpace(), zh-CN/ja-JP/es-ES object labels, aggregate search, and the OSV sweep.
    • New in Console: 2. The fourth objectui pin move and the trash-2 → trash icon.
    • Judged too minor to surface: 17. Each is text only, with no behaviour change an app or operator can reach: describe, docblock and comment rewrites, os migrate meta guidance text, liveness-ledger data and layout, a form row's declared language, a test-only import change in plugin-dev, and the successor Link header of the deprecated ?layers=true flag on the environment-scoped mount.
    • Highlights gain three bullets drawn from the above (decision first-match, written values, the stack authoring shape). The "running deployment" warning list gains five lines.
    • Every breaking entry that needs an operator action has an upgrade-checklist line, marked Not exercised unless the HotCRM upgrade below exercised it.
  • Console. Four pin moves now, not three: f8a9d0fb0596 → dd3f7e1be356 (3cf6449, chore(objectui): bump the console pin to dd3f7e1be356 (carries the injected-client boot fix) with the showcase div→box and trash-icon follow-through #20436) carries 325 releasing objectui changesets, 41 of them declared breaking upstream. The Highlights, "What's new" and Console sections all say four.
  • Dependencies. nodemailer is ^10.0.2, not ^9.1.1. That is a major bump for GHSA-6vj9-mwq6-2f5v, which has no 9.x fix. The line also carries the operator-visible note from fix(deps): take the fix for the seven OSV advisories turning Validate Package Dependencies red #20564's changeset: from nodemailer 10.0.12, requireTLS wins over ignoreTLS, so a transportOptions: { ignoreTLS: true } override on a port other than 465 now upgrades to STARTTLS or fails the send, and secure: false is the way to connect in the clear.
  • New subsection "Also shipped in 17.5.0 — not in its CHANGELOG". The publish ran from main at 0f6dcac5 (Release run 36536081716), 8 first-parent commits after the version commit, so the npm packages also contain 6e3aa75e a093ce3e 92fe0814 3a89d459 7001918e c96beb27 ba4648da 0f6dcac5. Their changesets are still unconsumed in .changeset/. The subsection gives one line per commit and says they will be listed again in 17.6.0's CHANGELOG and that the cause is tracked in [finding] release.yml: every main landing between the version-PR merge and the approval queues a new publish deployment, evicts the waiting one, and ships main's head instead of the version commit — ADR-0125 D1's premise does not hold #20613. The breaking 92fe0814 (feat(spec)!: retire the inner name on cube measures and dimensions — the record key is the member's name (#20300) #20458, cube member inner name retired) gets a Migration note taken from its own changeset and a checklist entry, and the checklist preface says where that note lives.

v17/index.mdx (following the 17.4.0 curation precedent b11bfb9a)

  • frontmatter description: "17.0.0 through 17.5.0";
  • status blockquote: 17.5.0 is released and current, published 2026-09-29, taking over from 17.4.0; a plain install resolves 17.5.0; the minors warning names 17.5.0;
  • a "17.5.0 stays in that register" paragraph drawn from the page's Highlights, linking #breaking-changes--migration-in-1750 and #upgrade-checklist;
  • the per-release list marks 17.5.0 current and 17.4.0 no longer current;
  • the checklist callout records that 17.4.0 → 17.5.0 has been exercised only in part (seven lines, on HotCRM), and the per-release checklist links lead with 17.5.0.

Findings from a HotCRM 17.4.0 → 17.5.0 upgrade

These were folded in at the coordinator's request; the parent session verified them.

  • Decision-mode flip (feat(automation)!: edge-branched decision is exclusive; mode: 'inclusive' takes every branch (#15429) #20344): now a 17.4.0 → 17.5.0 table, a standing warning that flows stored in sys_metadata take the new meaning without being rewritten, and a checklist line. The line says to review each mode: 'inclusive' that os migrate meta --from 17 offers, deleting it where the conditions partition, because applied blindly it draws flow-decision-inclusive-overlap. It then says to review the --stored list.
  • specVersion / engines.protocol: the checklist now says what an app does after a 17.x minor, from the code. PROTOCOL_VERSION is still 17.0.0, and the handshake compares only the major, so engines.protocol: '^17' stays, a ^17.0.0 specVersion admits 17.5.0, and a ^18 range is refused OS_PROTOCOL_INCOMPATIBLE. "Protocol 18" is the migration registry's next major; the 17.5.0 schemas already refuse its shapes, which is why os migrate meta --from 17 runs to 18. The Breaking-changes intro carries the same sentence.
  • Seven checklist lines are marked Exercised on HotCRM (a 17.4.0 app with a 17.4.0-created SQLite DB), 2026-09-29 with the observed result: os doctor scheduled-work reading, the account-issuer pre-flight, os migrate meta --from 17 (41 refusals in 874 lines, 240 of them generic protocol-18 notices, so filter the output), the decision review with --stored (0 rows), page.assignedProfiles, lookup screen field reference, and chartConfig (34 sites). Every other line stays Not exercised, and the preface and the v17 index callout say the hop was exercised only in part.

Citations

Every added #N was resolved on the board: 144 candidate numbers from the 90 commits and the 8 post-version commits, all resolving, and #20613 is open. SHAs are 7-character short SHAs, and each was verified to resolve unambiguously.

Gates run (workspace installed)

The full sweep ran on 2b3b323b. The head 664854a4 changes one phrase in one checklist line, and on it the MDX parse, check:doc-anchors, check:role-word, check:issue-citations --base origin/main, the audit-scope gate and the release-page gates were re-run, all green.

Named in the task, all exit 0:

  • pnpm check:doc-anchors: 391 internal fragment links, all resolve.
  • node scripts/check-issue-citations.mjs --base origin/main: 119 citations judged (104 resolve as pull requests, 1 as an issue, 14 cross-repo objectui#N unjudged); every added citation resolves.
  • pnpm check:role-word: no new occurrences.
  • node scripts/docs-audit/check-audit-scope.mjs: in sync, and release-owned pages are review-only.
  • check-release-page-status, check-release-section-coverage (plain and --strict) and check-release-notes: all OK.
  • MDX parse: both pages compile with @mdx-js/mdx 3 + remark-gfm, and all 7 tables on 17-5.mdx parse with no ragged rows.

Derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands: 47 commands, all 47 exit 0. The first sweep hit 5 prerequisite refusals (exit 3, or check:docs on the missing gitignored json-schema tree) from unbuilt @objectstack/spec, @objectstack/formula, @objectstack/lint and @objectstack/client-react. None was a finding. Those packages were built and the whole list was re-run. Among the 47: check:doc-authoring, check:docs-single-h1, check:docs-redirects, check:corpus-claim-drift, check:docs-transcript-drift, @objectstack/spec check:docs / check:skill-examples / check:liveness, @objectstack/lint check:doc-formula-expressions / check:doc-security-posture, check-doc-frontmatter, check-docs-section-name, check-section-landing-index and check:nul-bytes.

The diff was also re-read by hand; the fixes from that pass are the second commit (da443bdb).

Not in this PR

content/docs/upgrading.mdx's per-release table still reads "v17.4.0 — ⛔ checklist not written; machine-draft notes only" and has no 17.5.0 row. It is a hand-written tree outside content/docs/releases/, so it is left for a separate change.


Generated by Claude Code

17.5.0 was published to the `latest` tag on 2026-09-29. Resolve the
release-time TODOs the pre-cut draft carried:

- the publish date, 20 days after 17.4.0;
- the count: the version commit 8c87d26 consumed 958 changesets, not the
  868 pending at ab6fb02; the 17.5.0 CHANGELOG sections list them as 1,372
  per-package entries in 56 of 69 files, de-duplicating to the same 958;
- the 90 changesets consumed that the draft never read, folded into
  Highlights, Breaking changes & migration, New capabilities, Notable fixes
  and New in Console, with checklist lines marked not exercised;
- the fourth objectui pin move, f8a9d0fb0596 -> dd3f7e1be356;
- nodemailer ^10.0.2 (GHSA-6vj9-mwq6-2f5v) and its requireTLS note;
- a subsection for the eight commits the publish carried past the version
  commit, with a migration note for the breaking 92fe081 (#20613);
- v17/index.mdx: 17.5.0 released and current, its register paragraph,
  the per-release list and the checklist links.

Claude-Session: https://claude.ai/code/session_014VGCS11YUtYAiinRcdqQwL
Co-authored-by: Claude <noreply@anthropic.com>
Attribute the /import entry to the import door's cell reader rather than
the record validator, state the 0.1 + 0.2 result only for the faces the
changeset measured, note that remote Turso's count answered 500 rather
than [] before, keep the index-skip warning the lint changeset describes,
point the checklist preface at the Also-shipped migration note, and reflow
the lines the additions left long.

Claude-Session: https://claude.ai/code/session_014VGCS11YUtYAiinRcdqQwL
Co-authored-by: Claude <noreply@anthropic.com>
….5.0 notes

- The edge-branched decision flip gets a 17.4.0 -> 17.5.0 table and a
  standing warning that flows stored in sys_metadata take the new meaning
  unrewritten; its checklist line says to review each offered
  mode: 'inclusive' (deleting it where the conditions partition, or
  flow-decision-inclusive-overlap follows) and then the --stored list.
- The protocol declarations: 17.5.0 still implements protocol 17 and the
  handshake compares only the major, so engines.protocol stays '^17' and a
  ^17.0.0 specVersion admits 17.5.0; "protocol 18" names the migration
  registry's next major, whose shapes the 17.5.0 schemas already refuse.
- Seven checklist lines carry what an upgrade of HotCRM (a 17.4.0 app with
  a 17.4.0-created SQLite DB) observed on 2026-09-29; every other line stays
  not exercised, and the v17 index callout says the hop was exercised only
  in part.

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 github-actions Bot added size/l documentation Improvements or additions to documentation labels Sep 29, 2026
@hotlong
hotlong marked this pull request as ready for review September 29, 2026 09:27
@hotlong
hotlong enabled auto-merge September 29, 2026 09:27
@hotlong
hotlong added this pull request to the merge queue Sep 29, 2026
Merged via the queue into main with commit 1322cc7 Sep 29, 2026
38 checks passed
@hotlong
hotlong deleted the claude/objectstack-release-steps-ynhz3j branch September 29, 2026 09:42
veigajoao pushed a commit to veigajoao/objectstack that referenced this pull request Sep 29, 2026
…me/src to the commits that decided them (objectstack-ai#20624)

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

## What changed

This is stage 1 of the `domain:cli` lane of the dead-citation sweep:
`packages/runtime/src/**`, the lane's largest package. Every comment or
docblock site in scope that cited a tracker number answering 404 now
cites, in ruling C+D's form C (comment 5749154545 on objectstack-ai#19123), the commit
in this repository's history that decided what the line describes, and
says in its own words what that commit decided. PR objectstack-ai#20533 is the method
and PR objectstack-ai#20609 the closest sibling. Later stages cover `rest`, `cli`,
`types` and the rest of the lane, so this PR says `Part of` and the card
stays open.

That is **513 comment sites on 508 lines in 118 files, covering 96
numbers**: 194 of the census's 217 sites, and 319 more in test comments,
which the census defers. Three more sites carried a slash-joined dead
number the citation grammar does not read (`objectstack-ai#10629/objectstack-ai#10630`,
`objectstack-ai#5811/objectstack-ai#12281`, `objectstack-ai#8421/objectstack-ai#12194`), and they are rewritten too. Each
rewritten line cites one of **95 distinct commits**.

No ADR or ruling-record file in `docs/adr/` or `scripts/adr-anchors/`
records the decision behind any of these numbers. ADR-0126 and ADR-0131
name objectstack-ai#10243 only as the incident, ADR-0126 names objectstack-ai#11513 only for the
flow-clone half, and ADR-0112 names objectstack-ai#12281 only as another card. So
every anchor is a commit. The anchors the landed stages already gave the
same numbers are reused (24 numbers, for example `f19475c0a` for objectstack-ai#14143,
`e2798fab7` for objectstack-ai#6345 and `79c46da90` for objectstack-ai#9934), so each number carries
one anchor across the tree.

Only comments changed. Every touched file keeps its line count (508
lines out, 508 in, over 118 files), so no line citation into these files
moves. Seven of the 508 lines held no census site. Five are the other
half of a sentence that had to change:
`action-governance-scope-divergence.test.ts:6` (「the card names」 to
「that diverged」, because line 4 no longer names the card),
`action-record-load-denied.test.ts:560`,
`dispatcher-5xx-demoted-code-withhold.test.ts:45` (「that card's change」
to 「that commit's change」),
`hook-input-writeback-readonly-provenance.integration.test.ts:380`
(「that card」 to 「that commit」) and
`standalone-stack-seeder-declaration-copy.test.ts:88` (a trailing 「PR」
whose number wrapped onto line 89). Two carry only a slash-joined
number: `dispatcher-plugin.ts:688` and
`meta-compound-arity-mint-door.test.ts:4`. No code token moves (see the
guard below).

**No citation number is added.** Every tracker number on an added line
was already on the line it replaces. No PR number stands on an added
line, and none of the 95 shas is on a removed line.

Twenty-eight dead comment sites are left on purpose:
- **20 in `domains/meta.ts`.** PR objectstack-ai#20615 (objectstack-ai#20590's) opened at
2026-09-29T08:12:40Z, after this stage's claim and first read, and edits
that file. So the file went back to its base blob (`b4ddb362cc`) in
`a5cdfd8a46`, as PR objectstack-ai#20612 did with `authoring-rules.ts`. The anchors
are verified and listed below for the follow-up.
- **8 with no deciding commit, or with a literal reader.** See "The
sites left" below.

One more file: a `patch` changeset for `@objectstack/runtime`, because
the rewritten docblocks ship (see Changeset below).

## Census: `packages/runtime`, before and after

**Instrument.** The gate's own `node scripts/check-issue-citations.mjs
--census --json`, read-only and unchanged, run with the fleet token. Its
surface is comment prose in `packages/**/src/**/*.ts` with string
literals blanked, and it defers `*.test.ts`. The count is its
`allocated-but-absent` findings under `packages/runtime/`. Both runs
enumerated the whole board (185 pages), so neither read a truncated
board.

| reading | tree | board | whole-repo `allocated-but-absent` | runtime
sites | lines | files | numbers |
|---|---|---|---|---|---|---|---|
| before | base `eb4b17c346`, run 2026-09-29T07:55:51Z to 08:05:47Z |
enumerated, 185 pages, frontier objectstack-ai#20614, 18,441 numbers | 2,397 | **217**
| 216 | 29 | 59 |
| after | head `a5cdfd8a46`, run 09:08:23Z to 09:14:11Z | enumerated,
185 pages, frontier objectstack-ai#20623, 18,450 numbers | 2,027 | **23** | 23 | 4 |
12 |

The before count equals the card's 217 at `f11b5f20a2`. The 23 left are
the 20 held `domains/meta.ts` sites and 3 deliberate ones
(`api-exposure.ts:108`, `domains/mcp.ts:360`, `route-ledger.ts:300`).
The whole-repo drop is 370: this diff's 194, plus the 97 and 79 of PR
objectstack-ai#20609 and PR objectstack-ai#20612, which landed on `main` in between and came in with
the merge.

**Supplementary instrument, the whole scope.** The census does not read
test files or strings, and this stage's scope includes test comments. So
a second reading runs the gate's own exported `extractCitations`
(whole-file and comment-prose projections) and `classifyCitation` over
every `.ts` file under `packages/runtime/src` (373 files), against a
board probed by REST for every number cited there. The lit controls
objectstack-ai#16862, objectstack-ai#16847 and objectstack-ai#17698 answered 200 and the dead controls objectstack-ai#16714,
objectstack-ai#16715 and objectstack-ai#16697 answered 404 in both runs.

| reading | tree | citations | dead | src comment | test comment | src
string | test string |
|---|---|---|---|---|---|---|---|
| before, 08:17:55Z | `eb4b17c346` | 5,364 | **641** | 217 | 324 | 5 |
95 |
| after, 09:24:24Z | `a5cdfd8a46` | 4,851 | **128** | 23 | 5 | 5 | 95 |

Its src-comment column equals the census's 217 and 23, which is the
control on the second instrument. The 4,499 resolving citations, the 195
that resolve as pull requests and the 29 cross-repo ones are the same in
both readings. The drop is 513, exactly this diff's grammar-read sites.

## Per-number table

Sites and files are the dead comment sites in scope at the base, tests
included. `held` is `domains/meta.ts` (see above) and `left` is a site
with no deciding commit or with a literal reader. `strings kept` counts
string-literal sites, which are tokens and stay as they were. Every
anchor was read in its message or its diff, not only in its subject: it
is the commit that made the change the line describes, and its own
message or diff names the number it replaces.

| number | comment sites / files | rewritten | held | left | strings
kept | anchor |
|---|---|---|---|---|---|---|
| `objectstack-ai#6065` | 1/1 | 1 | 0 | 0 | 0 | `026101660` |
| `objectstack-ai#6123` | 1/1 | 1 | 0 | 0 | 0 | `59d1933f9` |
| `objectstack-ai#6206` | 5/2 | 5 | 0 | 0 | 0 | `8e13ca876` |
| `objectstack-ai#6216` | 2/1 | 2 | 0 | 0 | 1 | `f586f1a89` |
| `objectstack-ai#6220` | 1/1 | 1 | 0 | 0 | 0 | `83df2fd73` |
| `objectstack-ai#6238` | 2/2 | 2 | 0 | 0 | 2 | `c8d6f6e08` |
| `objectstack-ai#6259` | 4/2 | 3 | 0 | 1 | 1 | `6968885ef` |
| `objectstack-ai#6265` | 12/2 | 12 | 0 | 0 | 4 | `cfb549db8` |
| `objectstack-ai#6268` | 9/3 | 9 | 0 | 0 | 0 | `68f5eccb1` |
| `objectstack-ai#6287` | 1/1 | 1 | 0 | 0 | 0 | `84c86fb45` |
| `objectstack-ai#6307` | 1/1 | 1 | 0 | 0 | 0 | `293476148` |
| `objectstack-ai#6316` | 6/3 | 6 | 0 | 0 | 0 | `448ac9565` |
| `objectstack-ai#6345` | 10/3 | 10 | 0 | 0 | 0 | `e2798fab7` |
| `objectstack-ai#6361` | 4/2 | 4 | 0 | 0 | 6 | `90bbf2510` |
| `objectstack-ai#6363` | 6/2 | 6 | 0 | 0 | 2 | `17d095413` |
| `objectstack-ai#6483` | 3/2 | 3 | 0 | 0 | 0 | `ee58392e1` |
| `objectstack-ai#8722` | 1/1 | 0 | 0 | 1 | 0 | — |
| `objectstack-ai#8724` | 1/1 | 1 | 0 | 0 | 0 | `ff4ba6a06` |
| `objectstack-ai#8726` | 8/4 | 7 | 1 | 0 | 1 | `e783e163d` |
| `objectstack-ai#8796` | 13/3 | 13 | 0 | 0 | 4 | `a4331227b` |
| `objectstack-ai#8848` | 3/2 | 1 | 2 | 0 | 1 | `4fc4a3c0b` |
| `objectstack-ai#8919` | 1/1 | 0 | 1 | 0 | 0 | `b5378550e` (held file) |
| `objectstack-ai#9934` | 17/7 | 17 | 0 | 0 | 4 | `79c46da90` |
| `objectstack-ai#9967` | 1/1 | 1 | 0 | 0 | 0 | `8f266f1cd` |
| `objectstack-ai#10179` | 1/1 | 0 | 0 | 1 | 2 | — |
| `objectstack-ai#10243` | 40/13 | 40 | 0 | 0 | 9 | `266436a7f`, `02b41232d` |
| `objectstack-ai#10293` | 3/3 | 3 | 0 | 0 | 0 | `92a69d813` |
| `objectstack-ai#10338` | 1/1 | 1 | 0 | 0 | 0 | `d2619fd0c` |
| `objectstack-ai#10340` | 3/2 | 2 | 1 | 0 | 1 | `26f3588fb` |
| `objectstack-ai#10380` | 12/2 | 12 | 0 | 0 | 0 | `dd8172ee2` |
| `objectstack-ai#10485` | 3/3 | 3 | 0 | 0 | 0 | `35ad101bc` |
| `objectstack-ai#10503` | 8/2 | 3 | 5 | 0 | 1 | `67ceb9aef` |
| `objectstack-ai#10537` | 2/1 | 2 | 0 | 0 | 0 | `e634ecf6a` |
| `objectstack-ai#10554` | 1/1 | 1 | 0 | 0 | 0 | `6abc4df03` |
| `objectstack-ai#10629` | 75/23 | 75 | 0 | 0 | 0 | `13a6cb4ad` |
| `objectstack-ai#10630` | 4/1 | 4 | 0 | 0 | 0 | `dd8172ee2` |
| `objectstack-ai#10789` | 2/1 | 2 | 0 | 0 | 1 | `38bc74ed1` |
| `objectstack-ai#10886` | 1/1 | 1 | 0 | 0 | 1 | `809e61221` |
| `objectstack-ai#10888` | 3/3 | 2 | 1 | 0 | 1 | `d806081dd` |
| `objectstack-ai#10961` | 5/3 | 5 | 0 | 0 | 3 | `222d06fc1` |
| `objectstack-ai#10965` | 2/1 | 2 | 0 | 0 | 1 | `ab47f6974` |
| `objectstack-ai#10978` | 1/1 | 1 | 0 | 0 | 0 | `4c9780c7a` |
| `objectstack-ai#10983` | 3/2 | 3 | 0 | 0 | 0 | `6a4e929f5` |
| `objectstack-ai#11006` | 4/4 | 3 | 1 | 0 | 0 | `cccbe51bf` |
| `objectstack-ai#11015` | 3/1 | 3 | 0 | 0 | 0 | `82cb6e849` |
| `objectstack-ai#11166` | 8/3 | 8 | 0 | 0 | 4 | `735f5c709` |
| `objectstack-ai#11333` | 1/1 | 1 | 0 | 0 | 0 | `ea4d16420` |
| `objectstack-ai#11504` | 3/2 | 3 | 0 | 0 | 0 | `f90e82024` |
| `objectstack-ai#11513` | 2/2 | 2 | 0 | 0 | 0 | `e170b0ae5` |
| `objectstack-ai#11703` | 8/3 | 8 | 0 | 0 | 1 | `5cb62d88b` |
| `objectstack-ai#12010` | 1/1 | 1 | 0 | 0 | 0 | `77b91bdb4` |
| `objectstack-ai#12176` | 5/5 | 5 | 0 | 0 | 0 | `7986d973f` |
| `objectstack-ai#12194` | 11/4 | 8 | 3 | 0 | 0 | `311433f6b` |
| `objectstack-ai#12195` | 9/4 | 4 | 5 | 0 | 10 | `7986d973f` |
| `objectstack-ai#12281` | 20/5 | 20 | 0 | 0 | 5 | `0783d7b80` |
| `objectstack-ai#12943` | 7/3 | 7 | 0 | 0 | 0 | `090f2302e` |
| `objectstack-ai#13037` | 8/2 | 8 | 0 | 0 | 5 | `e7dfb1d69` |
| `objectstack-ai#13233` | 5/1 | 5 | 0 | 0 | 0 | `3800e4293` |
| `objectstack-ai#13241` | 5/4 | 5 | 0 | 0 | 1 | `a21d2a9cf` |
| `objectstack-ai#13273` | 11/3 | 11 | 0 | 0 | 0 | `3a86a65e7` |
| `objectstack-ai#13279` | 3/2 | 3 | 0 | 0 | 0 | `6a180e42d` |
| `objectstack-ai#13325` | 3/1 | 3 | 0 | 0 | 0 | `2e0b7b18f` |
| `objectstack-ai#13644` | 5/4 | 5 | 0 | 0 | 1 | `34ce8e7db` |
| `objectstack-ai#13657` | 13/1 | 13 | 0 | 0 | 1 | `b003cf2e8` |
| `objectstack-ai#14143` | 26/8 | 26 | 0 | 0 | 4 | `f19475c0a` |
| `objectstack-ai#14390` | 1/1 | 1 | 0 | 0 | 0 | `9d7f7259f` |
| `objectstack-ai#14398` | 3/1 | 3 | 0 | 0 | 0 | `317132495` |
| `objectstack-ai#14403` | 6/1 | 6 | 0 | 0 | 0 | `93d2d679b` |
| `objectstack-ai#14421` | 2/1 | 2 | 0 | 0 | 0 | `bd8795ea1` |
| `objectstack-ai#14422` | 4/2 | 4 | 0 | 0 | 4 | `dc7c226b9` |
| `objectstack-ai#14423` | 3/1 | 3 | 0 | 0 | 1 | `a56baa2bd` |
| `objectstack-ai#14474` | 1/1 | 1 | 0 | 0 | 0 | `df657d9df` |
| `objectstack-ai#14667` | 2/1 | 2 | 0 | 0 | 0 | `dc7c226b9` |
| `objectstack-ai#14678` | 2/1 | 2 | 0 | 0 | 2 | `73ad0bba7` |
| `objectstack-ai#14683` | 2/2 | 2 | 0 | 0 | 0 | `96326040f` |
| `objectstack-ai#14723` | 1/1 | 1 | 0 | 0 | 0 | `65846bc46` |
| `objectstack-ai#14745` | 1/1 | 0 | 0 | 1 | 0 | — |
| `objectstack-ai#14748` | 1/1 | 1 | 0 | 0 | 1 | `92b5d7f00` |
| `objectstack-ai#14758` | 15/5 | 15 | 0 | 0 | 1 | `84199cb87` |
| `objectstack-ai#14760` | 6/2 | 6 | 0 | 0 | 2 | `ee32e1cb8` |
| `objectstack-ai#14864` | 3/3 | 3 | 0 | 0 | 3 | `066dd3bd0` |
| `objectstack-ai#14878` | 2/1 | 2 | 0 | 0 | 1 | `29db3cd2a` |
| `objectstack-ai#14908` | 3/2 | 3 | 0 | 0 | 0 | `d5cbb44f3` |
| `objectstack-ai#14921` | 2/1 | 2 | 0 | 0 | 0 | `c1d274de7` |
| `objectstack-ai#15063` | 2/1 | 2 | 0 | 0 | 0 | `ad35745e8` |
| `objectstack-ai#15068` | 2/2 | 2 | 0 | 0 | 4 | `8744de9e9` |
| `objectstack-ai#15071` | 5/2 | 5 | 0 | 0 | 0 | `cf6e0a193` |
| `objectstack-ai#16610` | 3/1 | 3 | 0 | 0 | 0 | `316a20fc5` |
| `objectstack-ai#16649` | 4/1 | 4 | 0 | 0 | 0 | `44c917a47`, `613bfbd3d` |
| `objectstack-ai#16755` | 1/1 | 1 | 0 | 0 | 0 | `44c849c7d` |
| `objectstack-ai#16758` | 1/1 | 1 | 0 | 0 | 0 | `6e9bee640` |
| `objectstack-ai#16783` | 1/1 | 1 | 0 | 0 | 0 | `854639b31` |
| `objectstack-ai#16919` | 1/1 | 1 | 0 | 0 | 0 | `2cd4c548e` |
| `objectstack-ai#17038` | 1/1 | 0 | 0 | 1 | 0 | — |
| `objectstack-ai#17039` | 1/1 | 1 | 0 | 0 | 0 | `edf59e359` |
| `objectstack-ai#17041` | 2/2 | 0 | 0 | 2 | 0 | — |
| `objectstack-ai#17114` | 2/2 | 2 | 0 | 0 | 2 | `4af758d47` |
| `objectstack-ai#17147` | 1/1 | 1 | 0 | 0 | 0 | `aaacf1d5c` |
| `objectstack-ai#17148` | 1/1 | 0 | 0 | 1 | 0 | — |
| `objectstack-ai#17195` | 1/1 | 1 | 0 | 0 | 0 | `d2c1d1980` |
| `objectstack-ai#17219` | 1/1 | 1 | 0 | 0 | 0 | `706ad0fcc` |
| `objectstack-ai#19364` | 2/2 | 2 | 0 | 0 | 0 | `ada701220` |
| `objectstack-ai#19394` | 5/2 | 5 | 0 | 0 | 0 | `0862063ba` |

Every cited sha matches exactly one object (`git rev-parse
--disambiguate`, count 1 for each of the 95), is a commit, has one
parent, and is an ancestor of the base (`merge-base --is-ancestor`, exit
0 for all 95). The checkout is not shallow (`--is-shallow-repository`
false), and the control leg `13a6cb4ad` exits 0 too.

**Numbers with more than one anchor, by site:**
- `objectstack-ai#10243` (40 sites): `266436a7f` for the 26 sites that describe the
2026-08-23 ruling it implements (the enablement door joins the
`manage_metadata` write set, with the `trigger` exclusion), and
`02b41232d` for the 14 that name the leak itself (「the leak commit
02b4123 measured」). That commit recorded the measurement over HTTP and
says it is part of that card.
- `objectstack-ai#16649` (4 sites): `613bfbd3d` for the first half (the fourteen
remaining `boot-refusal` rows registered) and `44c917a47` for the second
(the face refusal widened to every published package, and `boot-refusal`
retired).
- `objectstack-ai#12176`, `objectstack-ai#12194`, `objectstack-ai#12195`: the stages of one ruled retirement.
`311433f6b` is stage 1 (the item-name grammar refused at the publish
door) and `7986d973f` is stage 3 (the compound arities un-mounted).
These are the anchors the spec stages gave.

**Wordings to check, each true of its commit:**
- `objectstack-ai#10293` (3 sites) cited the p1 flake whose signature had the
expected-noise lines lifted into it. They now read 「(a vitest teardown
race, fixed by commit 92a69d8)」. `92a69d813` names that number in its
subject and fixed the flake by disarming vitest's console-forwarding
teardown race, which is why the noise pointed the dispatch at the wrong
mechanism.
- `objectstack-ai#16755` and `objectstack-ai#16783` each cited an open PR that held a file at the
time. They now read 「the change that landed as commit 44c849c held
that file」 and 「then held by the change that landed as commit
854639b」. Each commit's diff edits the named file
(`domains/automation.ts`, `seed-loader.test.ts`).
- Quoted rulings keep their words.
`dispatcher-plugin.declared-5xx-prose-withhold.test.ts:13` and
`dispatcher-plugin.declared-user-message.test.ts:35` quote the
2026-08-27 ruling, and
`dispatcher-5xx-demoted-code-withhold.test.ts:281` quotes an older note.
There the commit stands in an editorial bracket (`[commit 79c46da]`,
`[commit 0783d7b]`) in place of the number.
- `objectstack-ai#9934`'s 「second constraint」 and 「third constraint」 now read 「the
ruling's second constraint, commit 79c46da」. That commit's own diff
calls status-agnosticism 「the ruling's second constraint」.
- `domains/packages.ts:841` read 「declares, since objectstack-ai#19364:」 above the
`enabled` line, but that line predates `ada701220` (objectstack-ai#19364's commit). It
now reads 「declares — a key commit ada7012 kept rather than retired:」.
- `route-ledger.ts:288`: 「objectstack-ai#16758 filed the second kind」 now reads
「Commit 6e9bee6 gated the second kind」, because that commit added the
row census after the index-slice incident the sentence goes on to
describe.
- `flow-clone.ts:7` and `domains/automation.ts:2403` cite `e170b0ae5`
for objectstack-ai#11513: the commit that landed 「lock package-declared permission
sets at the save door; clone to customize」, whose changeset names the
number.

## The sites left

**No deciding commit, or a literal reader (8 sites):**
- `api-exposure.ts:108` (objectstack-ai#6259): `api-exposure.test.ts:152` splits this
`@param` block on the literal `'objectstack-ai#6259'`, so rewriting the comment would
change what the test measures. Its deciding commit is `6968885ef`, which
the three test-comment sites of the same number now cite.
- `domains/mcp.ts:360` (objectstack-ai#8722): a wider contract change 「archived
unscheduled」. It never landed, so no commit decided it.
- `domains/meta-state-plural-tolerance.test.ts:130` (objectstack-ai#10179): an untaken
option on a tracking card. The only commit naming the card, `53a48c93f`,
recorded the opposite state.
- `package-door-namespace-conflict-code.test.ts:30` (objectstack-ai#14745): a residue
item on a review card. The only commit carrying the token is the one
that added this file.
- `route-ledger.conformance.test.ts:33` (objectstack-ai#17038): an ablation measured
on a PR whose squash commit, `6a7910abb`, neither records nor performs
it.
- `route-ledger.conformance.test.ts:38` and `route-ledger.ts:300`
(objectstack-ai#17041): a maintainer decision the lines call open.
- `security/artifact-granted-permissions.test.ts:291` (objectstack-ai#17148): a
question the line itself says is unsettled.

**Held with `domains/meta.ts` (20 sites), anchors verified for the
follow-up:** `objectstack-ai#8726` `:116` to `e783e163d`; `objectstack-ai#8848` `:200`, `:1398` to
`4fc4a3c0b`; `objectstack-ai#8919` `:1247` to `b5378550e`; `objectstack-ai#10340` `:1309` to
`26f3588fb`; `objectstack-ai#10503` `:14`, `:1143`, `:1159`, `:1250`, `:1308` to
`67ceb9aef`; `objectstack-ai#10888` `:1337` to `d806081dd`; `objectstack-ai#11006` `:103` to
`cccbe51bf`; `objectstack-ai#12194` `:831`, `:1050`, `:1173` to `311433f6b`; `objectstack-ai#12195`
`:819`, `:827`, `:1046`, `:1167`, `:1960` to `7986d973f`. PR objectstack-ai#20615's
one hunk there is at `:1874`, disjoint from these lines, but the rule is
file-level.

**String sites kept as tokens (100).** 95 are test titles and test-code
strings in 43 files. Five are non-test strings: the `route-ledger.ts`
`note` fields at `:435`, `:441` and `:505`, a string at
`dispatcher-error-vocabulary.ts:349`, and the enablement door's refusal
text at `domains/activation-gate.ts:279`, which ends 「(objectstack-ai#10243).」 (see
Acceptance notes).

## Mechanical guard: no code token moves

The check compares the TypeScript parser's leaf tokens (TypeScript
6.0.3, JSDoc nodes excluded, so template literals are read in context)
of each touched file at base `eb4b17c346` against the working tree at
`a5cdfd8a46`, over all 118 touched `.ts` files. Controls mutate the head
text in memory only, so nothing on disk moved for them.

- Real run: 301,081 base tokens, **0 files with a token change** (exit
0).
- Comment-insertion control (`domains/activation-gate.ts`): 0 files
changed (exit 0).
- Code-insertion positive control (a declaration in the same file):
DIFFER at token 34 (exit 1).
- String positive control (`(objectstack-ai#10243)` to `(objectstack-ai#10244)` inside the kept
refusal string): DIFFER at token 339 (exit 1).

Line balance: every touched file is +N/−N (508/508), and every line
count is equal at base and head. A raw scan of the 119 changed files for
control bytes finds none.

## Changeset

This change ships bytes, so a `patch` changeset for
`@objectstack/runtime` is included, in PR objectstack-ai#20609's form and level. It
says only that the provenance comments were re-anchored.

Measured on the built package: `files[]` is `dist`, `README.md` and
`CHANGELOG.md`. After `pnpm --filter @objectstack/runtime build`, the
rewritten docblocks reach `dist`: for example `e2798fab7` appears 3
times and `68f5eccb1` 6 times in `dist/index.d.ts`, and `f19475c0a` 4
times in `dist/index.js`. The positive control, the unchanged sentence
「drags `@libsql/client` (native bindings included)」 of the same
`turso-driver-factory.ts` docblock, is in `dist/index.d.ts`, and a
negative control phrase appears nowhere. The only dead number left in
`dist` is the kept refusal string's `objectstack-ai#10243`.

## Gates (head `a5cdfd8a46`)

This host has no `flock`, so `os-verify-lock.sh` ran in its declared
unlocked mode. Its disclosure, verbatim, from each locked run at this
head:

```text
os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 5s · declare it in the PR body · pnpm --filter @objectstack/runtime build
os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 99s (1m39s) · declare it in the PR body · pnpm --filter @objectstack/runtime exec vitest run --project local --maxWorkers=2
os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 6s · declare it in the PR body · pnpm --filter @objectstack/runtime exec vitest run --project repo --maxWorkers=2
os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 8s · declare it in the PR body · pnpm --filter @objectstack/runtime typecheck
```

The dependency closure and the whole workspace were built first, at the
merge head `ca6d13d6ab`, the same way: `turbo run build
--filter='@objectstack/runtime...'` (30 tasks, exit 0) and `turbo run
build --filter='./packages/*' --filter='./packages/*/*'` (71 tasks, exit
0). `a5cdfd8a46` differs from that head only in `domains/meta.ts`, which
went back to base bytes, and `@objectstack/runtime` was rebuilt at
`a5cdfd8a46`.

- **Tests:** `vitest run --project local`: 288 files, 4,190 tests
passed, 1 skipped. `--project repo` (which holds the touched
`action-owner-key-single-source.test.ts`): 3 files, 727 tests passed.
Together they cover every touched test file.
- **Typecheck:** `pnpm --filter @objectstack/runtime typecheck` exits 0.
`tsc --listFiles` counts 82 `src` files (no tests) under `tsconfig.json`
and all 291 test files under `tsconfig.test.json`, which
`check:test-typecheck` judges: 27 files, 190 errors, 68 pinned
signatures held.
- **Lint:** the repo-wide `pnpm lint` (`eslint . --no-inline-config`)
exits 0 at `a5cdfd8a46` (2026-09-29T09:23:06Z to 09:23:36Z). A narrowed
run over the 118 touched `.ts` files through eslint's API agrees: 118
linted, 0 ignored, 0 errors, 0 warnings.
- **Citation judging:** `node scripts/check-issue-citations.mjs --base
origin/main` exits 0. The diff-scoped run judged 23 citations across 28
files, and all 23 resolve. These are the live numbers that stay on
rewritten lines. It defers `*.test.ts`, so the added-minus-removed count
over the whole diff covers the rest: 0 numbers added.
- **Derived gates:** `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` at `a5cdfd8a46` derived 67
families, the same set as at the merge head. All 67 exit 0. `--ran`
reads 「67 derived, 67 run, 0 NOT-MEASURED, 0 UNRUN」.
- At the merge head, `check:dual-build-cjs-loads` and
`check:type-check-debt` first exited 3 (PREREQUISITE NOT MET) on a
partly built workspace. After the whole-workspace build both exited 0,
and both exit 0 at the final head.
- Among them: `check:doc-authoring` (the sibling prose-id baseline
holds, 810 pinned sites, no growth), `check:nul-bytes` (9,250 files, no
raw control bytes), `check:route-ledger-census`,
`check:dispatcher-error-vocabulary` and `check:issue-citations`
(self-test, 114 cases in 8 batteries).
- **Artifact rosters:** 38 of the 41 non-self-test roster rows exit 0 at
the merge head. The other three, `check-closing-target-claim`,
`check-partof-closing-keyword` and `check-single-claim-paths`, answer
「NOT WIRED」 (exit 2) without a pull request's context, and are run
against this PR and reported on the card.

## Hypotheses (measured first)

- **H0 holds.** The filtered census answers 217 dead sites at
`eb4b17c346` (29 files, 59 numbers), equal to the card's count at
`f11b5f20a2`: no drift.
- **H1 holds, with the listed exceptions.** After the rewrite the
filtered census answers 23: the 20 sites held with `domains/meta.ts` for
an open PR, and 3 deliberate ones (a literal reader, a card never
landed, an open decision). The supplementary reading adds 5 test-comment
sites of the same two kinds.
- **H2 holds, by the token guard.** A comment-stripped comparison of
every touched file (the parser's leaf tokens, JSDoc excluded) is empty,
and its controls fire. The emitted `dist` is not byte-identical, because
the docblocks ship, which is why the changeset is `patch`.

## Acceptance notes

- **The held file.** The claim's read (07:51Z) and this stage's first
read of the open PRs' file lists (08:06:32Z, 8 open PRs) found none
touching `packages/runtime/src`. PR objectstack-ai#20615 opened at 08:12:40Z and edits
`domains/meta.ts`. The re-read at 09:07:08Z (7 open PRs) found it, and
it is the only open PR touching the package. The file went back to its
base blob in `a5cdfd8a46`, and `git hash-object` equals `b4ddb362cc`,
the blob at the base and at `origin/main`. The 20 anchors above are
ready for the follow-up once that PR lands.
- **Form D, not touched here.** `domains/activation-gate.ts:279` is part
of the enablement door's refusal message and ends 「(objectstack-ai#10243).」. An author
sees it, so it is ruling D's (no number, the lesson in words), a string
change outside this comment-only scope. It needs a form-D carrier. The
other four non-test string sites are ledger `note` data and a gate's own
string.
- **The grammar does not read a slash-joined number.** `CITATION_RE`
refuses a `#` preceded by `/`, so the second number of `#A/#B` is never
judged. In `packages/runtime/src`, 3 such dead numbers exist (`objectstack-ai#10630`,
`objectstack-ai#12281`, `objectstack-ai#12194`), and all 3 are rewritten here. The other 36 distinct
slash-joined numbers there were probed by REST and answer 200. One more
dead one, `objectstack-ai#17219`, stands slash-joined inside a test title, a string,
and is kept. This is the same shape as PR objectstack-ai#20612's slash-joined
`objectstack-ai#5775/objectstack-ai#6629`. It is noted, not filed.
- **Outside the scope and the census surface.**
`packages/runtime/vitest.config.ts:54` cites `objectstack-ai#17853`, which answers
404. The file is outside `src/**`, so it is left for whoever owns the
package's config. The other numbers there, and those in `tsup.config.ts`
and `README.md`, answer 200.
- **Base.** The branch merged `origin/main` once (`ca6d13d6ab`, merging
`c1d8051e0a`) before the `--base origin/main` run, as the dispatch
orders. That merge brought PR objectstack-ai#20609's and PR objectstack-ai#20612's landed stages and
touched none of this diff's files. `origin/main` has since moved to
`ed6f7348f9`, one commit that touches only `packages/cli`, so there was
no second merge.
- **Anchors shared with the landed stages.** 24 numbers keep the anchor
the spec, lint or service-messaging stages already gave them, for
example `f19475c0a` (objectstack-ai#14143), `b003cf2e8` (objectstack-ai#13657), `311433f6b`
(objectstack-ai#12194), `8e13ca876` (objectstack-ai#6206) and `17d095413` (objectstack-ai#6363).

## Deviations

- Three changed lines hold only a slash-joined dead number, beyond the
census's sites (see Acceptance notes). Five more are the other half of a
rewritten sentence (listed under What changed).
- Commit trailers are AGENTS.md's model-free pair (`Claude-Session` plus
`Co-authored-by: Claude`), and the pre-push trailer check passed on
every push. The merge commit carries git's default message.

---
_Generated by [Claude
Code](https://claude.ai/code/session_local_1d2a197c-c20e-4e90-9be8-413d4d432289)_

---------

Co-authored-by: Jack Zhuang <50353452+hotlong@users.noreply.github.com>
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/l skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants