Skip to content

chore: version packages - #20639

Merged
os-zhuang merged 1 commit into
mainfrom
changeset-release/main
Oct 2, 2026
Merged

os-zhuang merged 1 commit into
mainfrom
changeset-release/main

Conversation

@github-actions

@github-actions github-actions Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and publish to npm yourself or setup this action to publish automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.

Releases

The changelog information of each package has been omitted from this message, as the content exceeds the size limit.

@objectstack/cli@17.6.0

@objectstack/client@17.6.0

@objectstack/console@17.6.0

@objectstack/core@17.6.0

@objectstack/driver-memory@17.6.0

@objectstack/driver-mongodb@17.6.0

@objectstack/driver-sql@17.6.0

@objectstack/driver-turso@17.6.0

@objectstack/lint@17.6.0

@objectstack/mcp@17.6.0

@objectstack/metadata-protocol@17.6.0

@objectstack/objectql@17.6.0

@objectstack/platform-objects@17.6.0

@objectstack/plugin-approvals@17.6.0

@objectstack/plugin-audit@17.6.0

@objectstack/plugin-auth@17.6.0

@objectstack/plugin-security@17.6.0

@objectstack/rest@17.6.0

@objectstack/runtime@17.6.0

@objectstack/sdui-parser@17.6.0

@objectstack/service-analytics@17.6.0

@objectstack/service-automation@17.6.0

@objectstack/spec@17.6.0

@objectstack/types@17.6.0

@objectstack/hono@17.6.0

@objectstack/account@17.6.0

@objectstack/setup@17.6.0

@objectstack/studio@17.6.0

@objectstack/client-react@17.6.0

@objectstack/cloud-connection@17.6.0

@objectstack/connector-mcp@17.6.0

@objectstack/connector-openapi@17.6.0

@objectstack/connector-rest@17.6.0

@objectstack/connector-slack@17.6.0

create-objectstack@17.6.0

@objectstack/driver-sqlite-wasm@17.6.0

@objectstack/formula@17.6.0

@objectstack/metadata@17.6.0

@objectstack/metadata-core@17.6.0

@objectstack/metadata-fs@17.6.0

@objectstack/observability@17.6.0

@objectstack/embedder-openai@17.6.0

@objectstack/knowledge-memory@17.6.0

@objectstack/knowledge-ragflow@17.6.0

@objectstack/organizations@17.6.0

@objectstack/plugin-dev@17.6.0

@objectstack/plugin-email@17.6.0

@objectstack/plugin-hono-server@17.6.0

@objectstack/plugin-pinyin-search@17.6.0

@objectstack/plugin-sharing@17.6.0

@objectstack/plugin-webhooks@17.6.0

@objectstack/service-cache@17.6.0

@objectstack/service-cluster@17.6.0

@objectstack/service-cluster-redis@17.6.0

@objectstack/service-datasource@17.6.0

@objectstack/service-i18n@17.6.0

@objectstack/service-job@17.6.0

@objectstack/service-knowledge@17.6.0

@objectstack/service-messaging@17.6.0

@objectstack/service-package@17.6.0

@objectstack/service-queue@17.6.0

@objectstack/service-realtime@17.6.0

@objectstack/service-settings@17.6.0

@objectstack/service-sms@17.6.0

@objectstack/service-storage@17.6.0

@objectstack/trigger-api@17.6.0

@objectstack/trigger-record-change@17.6.0

@objectstack/trigger-schedule@17.6.0

@objectstack/verify@17.6.0

@objectstack/example-crm@4.0.98

@objectstack/example-multi-package@0.0.5

@objectstack/example-showcase@0.3.20

@objectstack/example-todo@4.0.98

@objectstack/example-embed-objectql@0.0.38

@objectstack/dogfood@0.0.46

@objectstack/downstream-contract@0.0.44

@objectstack/http-conformance@0.1.6

This was referenced Sep 29, 2026
veigajoao pushed a commit to veigajoao/objectstack that referenced this pull request Sep 29, 2026
…ough @objectstack/console/package.json, so a project without its own manifest gets full component checking (objectstack-ai#20589)

Fixes objectstack-ai#19922
Clause-②: no

## What this changes

`resolveSduiManifest()` (`packages/cli/src/utils/sdui-manifest.ts`) is
the one resolver `os validate`, `os compile` / `os build` and `os lint`
use to arm the JSX page gate. `os dev` and `os start` run `compile`
before they boot when `dist/objectstack.json` is missing or `--compile`
is passed, and `dev`'s default watch mode reruns it when a watched file
changes. The resolver's second place to look, the copy
`@objectstack/console` ships as `dist/sdui.manifest.json`, asked Node
for that file by its own subpath. The console's `exports` map publishes
`./package.json` alone, so the resolve threw
`ERR_PACKAGE_PATH_NOT_EXPORTED`, a `catch` swallowed it, and every
project with no `sdui.manifest.json` of its own had its `kind: 'html'`
pages checked at parse level only.

The fallback now resolves `@objectstack/console/package.json` from the
CLI's own location (`import.meta.url`, the CLI's declared dependency in
the same fixed release group) and joins `dist/sdui.manifest.json` to its
directory, through a new `consoleSduiManifestPath(origin)`. The
console's `exports` stays closed. `resolveSduiManifest(cwd,
consoleOrigin)` gains an optional origin, used only by the pins.

The old module header handed one decision to whoever made this leg
reachable: what a broken shipped copy should do. It now gets the project
leg's rule. A shipped copy that is present but cannot be read or parsed
is `unusable` (new `source: 'console'`), and the command is refused with
exit 1, naming the file, with the remedy "reinstall
@objectstack/console". It is never read as "not found". With no page to
check it is read by nothing and not refused, the same as the project
leg.

## This round (the seat's unlock record `5890591366` on objectstack-ai#19922)

The ledger entry `ui-html-page-div-refused` landed on `main` (objectstack-ai#20592, PR
objectstack-ai#20610), and Version Packages objectstack-ai#17076 consumed
`.changeset/sdui-manifest-one-producer.md`. This round:

1. **Merged `origin/main` at `f1e921ab8e`** (a merge, not a rebase;
merge commit `f9cb969f44`). One conflict:
`.changeset/sdui-manifest-one-producer.md`, modify/delete, resolved in
favour of `main`'s deletion.
`build-json-failure-conversions.e2e.test.ts` and
`validate-json-failure-conversions.e2e.test.ts` auto-merged: `main`
changed other regions of both, and the `box` fixture line and its
docblock sentence survived. The branch's delta against `main` is exactly
the 7 intended files. `main` has since gained one commit (`cd901d7a5f`),
which touches none of them.
2. **The correction moved into this PR's changeset.** The released note
is `@objectstack/console` 17.5.0, patch entry `28ce612`. A new paragraph
goes through its closing paragraph one sentence at a time:
- two sentences stop being true with this release: the file is no longer
"only present in the tarball", and the CLI fallback no longer "keeps
parse-level validation";
   - two still hold: `exports` is unchanged.

No `CHANGELOG.md`, no `content/docs/releases/` and nothing under
`packages/spec/` is edited.
3. **The ADR-0087 marker** now reads `not-required (already-registered
ui-html-page-div-refused)` with its reason. The gate's verdict:
"check-adr-0087-registration: 1 declared-breaking changeset(s), each
carrying an ADR-0087 disposition … not-required (already-registered)",
exit 0.
4. **Two sentences re-measured and corrected:**
- **"the html-tier renderer still renders `div`" was false.** At the
pinned objectui `dd3f7e1be3`, read with `git show` from the sibling
checkout (nothing checked out, nothing edited):
- `packages/components/src/renderers/layout/page.tsx:487-488` builds the
html compile's whitelist from `getKnownTypes()` minus `deprecationFor(t,
'html')`;
- `packages/components/src/renderers/basic/div.tsx` registers `div` with
`deprecated.surfaces: ['json', 'html']`;
- `nameHtmlTierReplacement` turns the resulting `forbidden-tag` into a
refusal naming the replacement.

That pin shipped in `@objectstack/console` 17.5.0: its CHANGELOG entry
`3cf6449` says a `kind:'html'` page that authors a `div` "is refused at
compile time, and the error names `box`". The changeset now says the
console has refused `div` since 17.5.0, and that what is new is every
other tag the manifest does not declare. The console's html compile
accepts every non-deprecated registered component, while the manifest
declares the public contract plus the html intrinsics. Measured below
with `avatar`.
- **"`objectstack compile` (which `dev` and `start` run first)" was
inexact.** It now says exactly when they run it: `dev.ts:319` compiles
on `flags.compile` or a missing artifact, and `dev.ts:383` re-runs it in
watch mode; `start.ts:228-232` has the same condition.

## Premise and hypotheses, measured

Round-1 readings (on `f11b5f20a2`) are kept where they still hold.
Round-2 readings are on `77338a7186`: Node v26.7.0, macOS.

- **H0 (premise holds).** On unmodified `f11b5f20a2`, a real `os init`
project with a `kind: 'html'` page rooted in `div` passes `os validate`,
`os compile` and `os lint` at exit 0. Each prints only the parse-level
notice, and it does so even with a `cmp`-identical copy of the tracked
manifest at `packages/console/dist/sdui.manifest.json`. From
`packages/cli/dist`, the old subpath throws
`ERR_PACKAGE_PATH_NOT_EXPORTED`.
- **H1 (the route finds the file in both layouts).**
- Workspace: `consoleSduiManifestPath()` answers
`packages/console/dist/sdui.manifest.json`.
- Installed package: `npm pack` of `packages/console` with a stand-in
dist lists `dist/sdui.manifest.json`. Extracted under a scratch
`node_modules`, the old subpath throws `ERR_PACKAGE_PATH_NOT_EXPORTED`
from a sibling CLI origin, while `resolveSduiManifest` answers
`resolved`.
- **H2 (Clause-② arm: narrowing).** Round 2, merged tree, with the
console copy present (`cmp`-identical stand-in):
- a `div` page gives exit 1 (`jsx-forbidden-tag`,
`jsx-unknown-component`);
  - a `box` page gives exit 0, with no findings;
- an `avatar` page gives exit 1 (`jsx-forbidden-tag`,
`jsx-unknown-component`).

With no copy, all three exit 0 with the notice only. `avatar` is
registered at the pin (`renderers/data-display/avatar.tsx:17`) and not
deprecated, so the console's html compile renders it and nothing refused
it before this change. That is the narrowing the `(narrowing)` arm and
BREAKING rest on.
- ⚠️ **Round 1 misread this half.** It took "the renderer still renders
`div`" from ruling A's reading, which predates objectui#10757, instead
of reading the pin. The pin had landed on `main` (objectstack-ai#20436) before round 1
ran. For `div`, this change moves a refusal the 17.5.0 console already
gives at render time to author time. The arm still holds because of the
undeclared tags.
- **CLI fixtures the live fallback newly refuses** (round 1, with the
console copy present): 23 tests went red across
`build-json-failure-conversions.e2e` (5),
`validate-json-failure-conversions.e2e` (4) and
`jsx-gate-manifest-notice.e2e` (14). `lint-conversion-notices.e2e`
stayed green, but its page is refused too. The three conversion fixtures
moved from `div` to `box`. The notice file's 14 manifest-less cases are
skipped by name where the CLI's own console copy exists. They run in the
CI job, which builds no console, and their rules are pinned hermetically
in `src/utils/sdui-manifest.test.ts`.
- **Examples:** only `examples/app-showcase` carries html pages (three).
- **H3 (shipped pages stay clean)**, round 2, merged tree. `main`
brought a regenerated `sdui.manifest.json` carrying `tier: 'html'` marks
(objectstack-ai#20582).
  - `validate-jsx-pages.production-witness.test.ts`: 5/5 pass.
- `examples/app-showcase` with the console copy present: exit 0 on `os
validate` / `os compile` / `os lint`, with zero `jsx-*` / `sdui/*`
findings.
- **H4 (ablation, round 1)**, through `node
scripts/ablation-replace.mjs` in WRAP mode:
- the anchor `resolve(CONSOLE_PACKAGE_JSON)` went 1 → 0, and
`resolve(CONSOLE_SDUI_MANIFEST)` (the old subpath) 0 → 1;
  - 4 console-leg pins went red ("expected undefined to be defined");
- restore: the blob is back at the HEAD blob `be1f8d4ad33e`, and `git
diff HEAD` is empty.

The pins import the subject from `src/`, so no `dist/` sits on that
path. This round changed no source or test file.

## Tests, at `77338a7186`

- The whole CLI `unit` project (`--project unit --maxWorkers=2`) with a
real-path `TMPDIR`: 234/234 files, 3347/3347 tests. With the default
macOS `TMPDIR`: 232/234. The 2 files are `published-subpath-console.pin`
and `published-subpath-hook-body.pin`, 5 cases comparing `/var` against
`/private/var`. They are host-only and untouched here.
- `pnpm --filter @objectstack/cli typecheck` (`tsc --noEmit` plus
`check:test-typecheck`): exit 0.
- The four touched nightly `*.e2e` files (`OS_TEST_TIERS=nightly
--project integration`):
  - with the console copy present: 53 passed, 14 skipped;
  - without it (the CI state): 67/67 passed.

  The rest of the integration layer is declared to CI.

## Gates, at `77338a7186`

- `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
--commands`: 63 derived, the same 63 as round 1. All 63 exit 0, and
`--ran` reconciled "63 run, 0 NOT-MEASURED (a DERIVED zero — all 63
recorded an exit code and none of them is 3)".
`check:dual-build-cjs-loads` first answered PREREQUISITE NOT MET, then
exit 0 after building its eight missing packages.
- `check-adr-0087-registration --base origin/main`,
`check-empty-changeset --base origin/main` (it now reads "No changeset
from the merge base modified or deleted by this diff") and
`check-changeset-no-major --base origin/main`: all exit 0.
- Roster rows that could apply, all exit 0: `check-changeset-fixed`,
`check-sdui-manifest` (plus `--self-test`), `check:authz-resolver`,
`check:error-code-casing`, `check:filter-alias-parity`,
`check:cli-examples-parity`, `check:published-readme-exports`,
`check:scaffold-emission-policy`, `check:console-injection`.
- `pnpm lint` (full repo, not narrowed): exit 0, no output.
- `node scripts/check-issue-citations.mjs --base origin/main`: exit 0.
- `check:nul-bytes`: exit 0, plus a control-byte scan of the 7 changed
files: 0.

**Declared narrowing — verification ran UNLOCKED.**
`scripts/pm/os-verify-lock.sh`
could not take the shared verify lock on this host: no usable `flock`.
The shared
verify lock is declared Linux-only (`flock` is util-linux, and a stock
macOS does
not ship it), so the command below was run directly, without the lock —
a declared narrowing, not a silent one. No serialization guarantee held
for this
run, nor for any sibling agent in this container while it ran.

every build, test, typecheck, ablation and `pnpm lint` command named
above

## Acceptance notes

- **Where the `div` → `box` prescription reaches an upgrader.** The
CLI's refusal text does not carry it: the gate answers "is not an
allowed component" / "is not a known component", from
`@objectstack/sdui-parser` (`parse.ts`). The upgrade guide does not
carry it either: `packages/spec/scripts/build-upgrade-guide.ts:78` loops
majors up to `PROTOCOL_MAJOR`, `PROTOCOL_VERSION` is `17.0.0`, and
`docs/protocol-upgrade-guide.md` does not name
`ui-html-page-div-refused`. What does carry it:
  - this changeset's FROM → TO table;
  - the console's own render-time refusal, which names `box`;
- `objectstack migrate meta --from 17`. Measured on a stack with a `div`
page, it lists the entry as one of 242 "manual change(s) require your
judgment", headed "⚠ [protocol 18] kind:'html' page source …", with
`box` as the replacement, and exits 0.
- The ledger entry's own `why` text
(`packages/spec/src/migrations/entries/semantic/18.ui-html-page-div-refused.ts`,
the spec seat's file) still says "`objectstack compile` (which `dev` and
`start` run first)", the phrasing corrected here. Noted, not edited.
- Release order, flagged by the seat in `5890591366`: Version Packages
PR objectstack-ai#20639 carries the ledger entry's changeset. If it merges before this
PR, the ledger row ships one release ahead of the CLI refusal it
describes.
- `packages/cli/src/utils/scaffold-validate.ts` (the note at :128-:133)
was re-read. It is true now, so it is not edited. A pre-existing
imprecision stays as it was: `os init` reads the invoker's directory,
which may carry its own `sdui.manifest.json` (this repository's root
does).
- Comment drift outside this claim, noted only:
- `.github/workflows/lint.yml` (:899) and
`scripts/check-sdui-manifest.mjs` (:28-30, :240) still say
`resolveSduiManifest()` degrades to parse-only silently;
- the header of `packages/lint/src/validate-jsx-pages.ts` still calls
manifest validation "not wired";
- `docs/qa/platform-checklist/areas/studio-authoring.json` describes the
showcase tree as flex/div/a.
- The five macOS-only `published-subpath-*` failures come from a
`tmpdir()` path compared with the real path that module resolution
returns. They are host-specific.
- Measurement scaffolding was all in scratch, or in this worktree's
gitignored `packages/console/dist/`, with each stand-in trap-removed.
`git status --porcelain` printed 0 lines after every run.

---
_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>
veigajoao pushed a commit to veigajoao/objectstack that referenced this pull request Sep 29, 2026
… notes (objectstack-ai#20667)

Fixes objectstack-ai#20622

Clause-②: no

One new `patch` changeset (`@objectstack/cli`, in the fixed release
group) and nothing else. No code, no docs pages, no edit to any existing
changeset.

## What it carries

1. An upgrade line: the raised dependency floors cover what objectstack
loads; a lockfile-preserving upgrade can keep an older `hono` under
`@modelcontextprotocol/sdk` (via `@objectstack/cli` to
`@objectstack/mcp`), which objectstack never loads. `pnpm update hono`
clears a scanner. It states no version number.
2. A section naming, by PR number and title only, the 16 changesets (15
PRs) that shipped inside 17.5.0 without being consumed. Breaking entries
first: objectstack-ai#20458, objectstack-ai#20504, objectstack-ai#20567 (two changesets).

## Measurement

The brief's range command (`git log --diff-filter=A --name-only
8c87d26..0f6dcac -- .changeset/`) yields only 8 files. The other 8
were added BEFORE the version commit and were already left unconsumed by
it (the tree at `8c87d26a5d` still holds them). The set that shipped in
17.5.0 and is still pending is the changeset directory at `0f6dcac5e9`
intersected with `origin/main`: 16 files, all still pending, matching
the triage's 16 and its breaking set (3 PRs, 4 files). Breaking was
decided by each file's text (`BREAKING` banner / narrowing arm).

Code anchors for the upgrade line: `packages/mcp/package.json` depends
on `@modelcontextprotocol/sdk ^1.30.0`; `packages/cli/package.json`
depends on `@objectstack/mcp`;
`packages/plugins/plugin-hono-server/package.json` carries `hono
^4.13.5`; `packages/mcp/src` imports only `server/mcp`, `server/stdio`,
`server/webStandardStreamableHttp` and `types` from the SDK (no
`server/streamableHttp`).

## Gates

19 derived by `dispatch-gates.mjs --commands`, all 19 run and exit 0
(adr-0087-registration, changeset-no-major, closing-keyword-parity,
comment-mask-corpus, empty-changeset, gate self-tests, nul-bytes,
published-files and the rest); `--ran` reconciliation: 19 derived, 19
run, 0 NOT-MEASURED, 0 UNRUN. `check-changeset-fixed` green. Ordering:
must land before objectstack-ai#20639 (Version Packages, open at the time of writing).

---

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

https://claude.ai/code/session_01VDtqoecgES7ScQYGbFVDRv

---------

Co-authored-by: Claude <noreply@anthropic.com>
@github-actions
github-actions Bot force-pushed the changeset-release/main branch from 5ce847c to 14f155a Compare September 29, 2026 18:14
@github-actions
github-actions Bot force-pushed the changeset-release/main branch from 14f155a to c477a7d Compare September 30, 2026 00:29
@github-actions
github-actions Bot force-pushed the changeset-release/main branch from f671889 to 43369f6 Compare October 2, 2026 01:23
@os-zhuang
os-zhuang enabled auto-merge October 2, 2026 01:24
@os-zhuang
os-zhuang added this pull request to the merge queue Oct 2, 2026
Merged via the queue into main with commit 617f25f Oct 2, 2026
40 checks passed
@os-zhuang
os-zhuang deleted the changeset-release/main branch October 2, 2026 02:29
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…/src to the commits that decided them (objectstack-ai#20703)

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

## What changed

This is stage 6 of the `domain:cli` lane of the dead-citation sweep:
`packages/client/src`. Every comment site there 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 keeps saying in its own words what
that commit decided. PR objectstack-ai#20533 is the method and stages 1 to 5 of this
card (PR objectstack-ai#20624, PR objectstack-ai#20632, PR objectstack-ai#20656, PR objectstack-ai#20673, PR objectstack-ai#20689) are the
precedents. The card stays open for the lane's remaining packages, so
this PR says `Part of`.

That is **43 sites on 43 lines in 7 files, covering 13 numbers**,
rewritten to **12 distinct commits**:
- the census's **19 sites**, all in `src/index.ts` (8 numbers);
- **24 test-file comment sites** in 6 test files (the census defers
`*.test.ts`; stages 1 to 5 took test comments too).

One more line changed: `client.test.ts:2198`, the second half of the
`:2197` sentence ("byte-identical to the pre-(number) behavior" now
reads "byte-identical to the behavior before commit cf74a11").

Only comments changed: **44 lines out, 44 in**, and every touched file
keeps its line count, so no line citation into these files moves. **No
citation number is added**: over the 44 line pairs, added-minus-removed
numbers is empty, and no PR number stands on an added line. No ADR or
ruling-record file in `docs/adr/` or `scripts/adr-anchors/` records any
of these 13 decisions, so every anchor is a commit.

A **`patch` changeset** for `@objectstack/client` rides along, because
the rewritten docblocks reach `dist` (measured below).

## Census: `packages/client`, 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. The
count is its `allocated-but-absent` findings under `packages/client/`.
Both runs enumerated the whole board.

| reading | tree | board | whole-repo `allocated-but-absent` |
`packages/client` sites | lines | numbers | files |
|---|---|---|---|---|---|---|---|
| before | base `3b47a693c7`, run 2026-09-29T18:31:09Z to 18:35:42Z |
enumerated, 186 pages, frontier objectstack-ai#20701, 18,528 numbers | 1,298 | **19**
| 19 | 8 | 1 (`src/index.ts`) |
| after | `221a3f5e63`, run 18:39:47Z to 18:44:31Z | enumerated, 186
pages, frontier objectstack-ai#20702, 18,529 numbers | 1,279 | **0** | 0 | 0 | 0 |

The whole-repo drop of 19 is exactly these sites: a site-by-site diff of
the two JSON outputs has 19 findings gone, all in
`packages/client/src/index.ts`, and none added. `packages/client/src` is
byte-identical at `221a3f5e63` and at the head.

**Supplementary scan (test files included).** The gate's exported
`extractCitations` and `classifyCitation` over all 53 `.ts` files under
`src/`, with the board from the gate's own `probeBoard`: 963 citations
and 61 dead before (src comments 19, test comments 24, src strings 0,
test strings 18), 920 and 18 after (0, 0, 0, 18). Its before list of src
comment sites is identical to the census's. The 18 left are test titles,
the form-D stage (see Acceptance notes).

## Per-site table

`git blame` at the base ties each line to the commit that wrote it, and
each anchor was read in its message, changeset or diff, not only its
subject.

| number | sites (base line) | anchor: what it decided |
|---|---|---|
| `objectstack-ai#12195` | `index.ts:684`, `:728`, `:1834`, `:2021`;
`client.test.ts:2869`; `meta-automation-descriptors.test.ts:29` |
`7986d973f`, stage 3 of the compound-name retirement: un-mounts the
three `:section` arities and unifies the SDK's URL spelling on
`encodeURIComponent`. All six lines blame to it. |
| `objectstack-ai#12176` | `client.test.ts:357`;
`meta-automation-descriptors.test.ts:31` | `7986d973f` as well: the
lines say the card "retired compound-name addressing", and that commit
completes the retirement. Both lines blame to it, and the landed stages
give this number the same anchor. |
| `objectstack-ai#12194` | `index.ts:738`, `:1838`, `:2026`; `client.test.ts:360`;
`meta-automation-descriptors.test.ts:43` | `311433f6b`, stage 1: the
item-name grammar, refused at the publish door. |
| `objectstack-ai#12181` | `index.ts:799`, `:820`, `:911`, `:1866`;
`meta-delete-item-carriers.test.ts:4`, `:265` | `cf71d73f8`:
`meta.deleteItem` sends the reset door's `If-Match` pin and
`?state=draft` on both declarations. Its changeset also records the
withholding of `?dropStorage` that `:820` and the test's `:265`
describe. All six lines blame to it; stage 3 gave the number the same
anchor. |
| `objectstack-ai#14879` | `index.ts:3479`, `:3552`, `:3635`, `:7536`;
`client.data-prefix.test.ts:4`; `client.metadata-prefix.test.ts:7`;
`client.test.ts:2165`, `:2197` | `cf74a1128`: the SDK reads the CRUD
data prefix from discovery instead of restating `/data`
(`_dataPrefix()`). Six lines blame to it; `index.ts:3552` and
`client.metadata-prefix.test.ts:7` blame to `032452a54`, the
metadata-prefix sibling, and name the data-prefix change as their
precedent. |
| `objectstack-ai#14313` | `index.ts:4911`; `return-type-precision.test.ts:1031` |
`b1b978c8d`: binds the `auth.*` family to the wire shapes better-auth
sends, and deliberately leaves `auth.deleteUser` unbound (the member
`:4911` describes). Both lines blame to it. |
| `objectstack-ai#14312` | `return-type-precision.test.ts:891`, `:973`, `:981`,
`:1130` | `e944fdb24`: binds four `oauth.*` methods and deliberately
leaves `oauth.applications.delete` unbound, the "open decision" `:973`
and `:981` name. `:891` blames to it; `:973` and `:981` blame to the
later delete binding (`7beaaa32c`) and `:1130` to `b1b978c8d`, and each
refers back to what the `oauth.*` card did. |
| `objectstack-ai#14314` | `return-type-precision.test.ts:1138` | `7092d63e4`: binds
the `organizations.*` family. The line blames to it. |
| `objectstack-ai#6361` | `index.ts:6379`; `client.test.ts:878`, `:1390` |
`90bbf2510`: retires the notification-list `cursor` on both halves.
`:1390` blames to `0b4022b41`, which applies the same retirement one
door over and names this one as its precedent. Stages 1 and 2 and the
spec lane gave the number this anchor. |
| `objectstack-ai#9934` | `index.ts:7406`; `client.test.ts:27` | `79c46da90`: the
producer-side `userMessage` marking. Both lines blame to it; the anchor
the landed stages give this number. |
| `objectstack-ai#6239` | `index.ts:8122` | `f549a0d4a`: the ADR-0049 retirement sweep
that deleted `ViewProtocol` and its schemas. The line blames to it; the
spec lane's stage 2 gave the number this anchor. |
| `objectstack-ai#8480` | `client.test.ts:512` | `caaae2cca`: the typed
`security.explain()` request gains the `recordIds` batch spelling. The
line blames to it, and its changeset names the card. |
| `objectstack-ai#13208` | `return-type-precision.test.ts:1394`, `:1415` |
`74049254d`: `DeleteMetaItemResponseSchema` declares `seq` and
`projectionApplied`. `objectstack-ai#13208` was the pull request that landed as this
commit (its subject carries the number), and it answers 404 too.
`objectstack-ai#13155`, the issue beside it, answers 200 and stays. |

**Anchor checks.** Every cited sha matches exactly one object (`git
rev-parse --disambiguate`, count 1 for each of the 12), is a commit, has
one parent, and is an ancestor of `main` (`merge-base --is-ancestor`
against `31ed067639`, exit 0 for all 12). The checkout is not shallow.
The control leg `6f657f4f5` (2026-08-07, the parent of the oldest anchor
`f549a0d4a` of 2026-08-08) exits 0, and the negative control, this
branch's own `221a3f5e63`, exits 1. Six anchors reuse the landed stages'
(`7986d973f`, `311433f6b`, `cf71d73f8`, `90bbf2510`, `79c46da90`,
`f549a0d4a`), so each number carries one anchor across the tree; six are
new (`cf74a1128`, `b1b978c8d`, `e944fdb24`, `7092d63e4`, `caaae2cca`,
`74049254d`).

**Numbers.** All 13 dropped numbers answer 404 by REST (re-probed
2026-09-29T18:59Z). The three numbers kept on changed lines (`objectstack-ai#8326`,
`objectstack-ai#12104`, `objectstack-ai#13155`) answer 200. Five slash-joined numbers stand in
`packages/client/src`, which the citation grammar does not read
(`objectstack-ai#11925/objectstack-ai#12036`, `objectstack-ai#3431/objectstack-ai#3455`, `objectstack-ai#2567/objectstack-ai#3963`, `objectstack-ai#5449/objectstack-ai#5546`,
`objectstack-ai#5674/objectstack-ai#5787`); their second halves all answer 200, so none is dead.

**Wordings to check, each true of its commit:**
- `index.ts:820` "Maintainer-seat ruling, landed by commit cf71d73":
that commit's changeset states the withholding in the same terms ("no
caller was measured needing it from this client").
- `index.ts:3552` now reads "The defect commit cf74a11 fixed, one key
over"; `client.metadata-prefix.test.ts:7` reads "the `crud.dataPrefix`
defect commit cf74a11 fixed".
- `index.ts:738` keeps "Stage 1" beside the commit, and its "this stage"
is the docblock's own commit, tagged `7986d973f` at `:728`.
- `return-type-precision.test.ts:891`, `:1031`, `:1138` keep "card N of
3 of objectstack-ai#12104" (that number answers 200).

## Mechanical guard: no code token moves

**H2 holds on the comment-stripped reading; the emitted `dist` is NOT
byte-identical, because the docblocks ship.**

**Token guard.** It compares the TypeScript parser's leaf tokens
(TypeScript 6.0.3, JSDoc nodes excluded) of the 7 touched files at base
`3b47a693c7` and at `221a3f5e63`. Controls mutate the head text in
memory only.
- Real run: 68,102 base tokens, 0 differing (exit 0).
- Comment-insertion control: 0 differing (exit 0).
- Code-insertion control: all 7 files differ at token 0 (exit 1).
- String control (`'token'` to `'tokeN'` in the code literal at
`index.ts:4895`): exactly 1 differing `StringLiteral`, at token 14,882
of `index.ts` (exit 1).

**Emitted `dist`.** `pnpm --filter @objectstack/client build` at base
(the base tree of `packages/client/src` restored in place with a
trap-armed restore; blob-equal to HEAD and `git diff HEAD` empty
afterwards) and at the head, with the same dependency builds:
- `index.d.ts`, `index.d.mts`, `index.js` and `index.mjs` differ;
`index.js.map` and `index.mjs.map` are equal.
- The same parser comparison over the four differing `dist` files reads
0 differing tokens (12,637 / 12,637 / 25,487 / 25,184), so the whole
`dist` delta is comment text. Its code control (a code line appended to
`index.mjs`) reads COUNT DIFFERS. A first try at that control appended
the line onto the file's last line, which is the `sourceMappingURL`
comment with no trailing newline, so it landed inside a comment, read 0,
and was void; it was redone after a newline.
- The new wording is in `dist`: for example "commit cf74a11" appears 4
times in `index.d.ts` and `index.js`.
- Code-mutation control (`scripts/ablation-replace.mjs`, anchor
`searchParams.set('token'` hit 1 to 0, blob restored to HEAD
`19305adddb`, `git diff HEAD` empty): changes `index.js` and
`index.mjs`. `dist` was rebuilt to the head bytes and
`scripts/ablation-dist-preflight.mjs` reads the marker absent from all 6
files with a clean tree.

A raw scan of the 8 changed files for control bytes finds none (a
positive probe on a scratch file matched).

## Changeset

**`patch` for `@objectstack/client`**
(`.changeset/client-provenance-anchors.md`), in PR objectstack-ai#20632's form.
`@objectstack/client`'s `files[]` is `dist`, `README.md` and
`CHANGELOG.md`, and the build above emits different `index.d.ts` /
`index.d.mts` / `index.js` / `index.mjs` at base and head, so this diff
publishes. `check-changeset-no-major`, `check-empty-changeset`,
`check-adr-0087-registration` and `check-changeset-fixed` all exit 0.

## Gates (head `afa654081f`)

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

```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 59s · declare it in the PR body · pnpm --workspace-concurrency=2 --filter '@objectstack/client...' 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 121s (2m01s) · declare it in the PR body · pnpm turbo run build --filter='./packages/*' --filter='./packages/*/*' --concurrency=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 21s · declare it in the PR body · pnpm --filter @objectstack/client exec vitest run --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/client typecheck
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 2s · declare it in the PR body · pnpm --filter @objectstack/client build
```

- **Build:** `@objectstack/client`'s closure (35 of 81 workspace
projects), then the whole workspace, `turbo run build
--filter='./packages/*' --filter='./packages/*/*'`, 71 of 71 tasks,
after the merge.
- **Tests:** `vitest run`: 50 files, 641 tests passed (every `*.test.ts`
under `src/`; `tests/integration/**` needs a running server and is
excluded by the package's own config).
- **Typecheck:** `pnpm --filter @objectstack/client typecheck` exits 0.
`tsc --listFiles`: `tsconfig.json` compiles the 3 non-test `src` files,
`tsconfig.test.json` all 53 including the 50 test files.
`check:test-typecheck`: 0 files, 0 errors, 0 pinned signatures.
- **Lint:** the repo-wide `pnpm lint` (`eslint . --no-inline-config`)
exits 0 (2026-09-29T18:57:45Z to 18:58:13Z).
- **Citation judging:** after merging `origin/main` (`31ed067639`),
`node scripts/check-issue-citations.mjs --base origin/main` reports "no
issue citations added against 31ed067 (1 file(s) read)" (exit 0);
pinned `--base 31ed067` reads the same.
- **Derived gates:** `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` derived 61 families. All 61 exit
0, and `--ran` with the exit-coded record reads "61 derived, 61 run, 0
NOT-MEASURED, 0 UNRUN" (a derived zero). Among them:
`check:issue-citations`, `check:doc-authoring` (808 pinned sites, no
growth), `check:nul-bytes` (9,315 files, no raw control bytes),
`check:published-files`, `check:type-check-debt`.
- **Artifact rosters:** 36 of the 39 non-self-test roster rows exit 0,
including the four the derivation marks as keeping their roster under
one of this diff's paths (`check-changeset-fixed`,
`check:authz-resolver`, `check:error-code-casing`,
`check:filter-alias-parity`). The other three need a pull request's
context; they are run against this PR once it exists and reported on the
card. The 18 self-test-only rows grade their checkers' fixtures and
cannot judge this diff.

## Hypotheses (measured first)

- **H0 holds.** At base `3b47a693c7` the filtered census answers 19
sites on 19 lines, 8 numbers, all in `src/index.ts`. The whole-repo
count is 1,298, as on `0be898499f`.
- **H1 holds.** After the rewrite, the filtered census answers 0 for
`packages/client`. No site was left for an open PR (the file lists of
all open PRs were read at 18:36:50Z, 11 PRs, and at 19:01:46Z, 8 PRs:
only the Version Packages PR objectstack-ai#20639 touches `packages/client`, in
`CHANGELOG.md` and `package.json`) or for an unfound anchor.
- **H2 holds on the token reading, not on the `dist` reading.** The
parser leaf-token diff is empty with its controls firing. The emitted
`dist` differs, and the difference is comment text only (token-identical
`dist` with a code control). That is why the changeset ships.

## Acceptance notes

- **Test titles, the form-D stage.** 18 dead numbers remain in test
string literals in `packages/client/src` (`describe` and `it` titles, no
assertion text): `objectstack-ai#12195` 6, `objectstack-ai#12181` 5, `objectstack-ai#14879` 4, `objectstack-ai#9934` 1, `objectstack-ai#8480`
1, `objectstack-ai#6361` 1. They stay on the card for its form-D stage; no string
moved here.
- **Outside `src/**`, a later stage of the card:**
`packages/client/tsconfig.json:8` and
`packages/client/vitest.config.ts:33` cite `objectstack-ai#12181` (404), and
`packages/client/test-typecheck-debt.json:3` cites `objectstack-ai#6083` (404). The
other citations in `packages/client` outside `src/**` (`CHANGELOG.md`
excluded) answer 200.
- **The moving `origin/main`.** The branch merged `origin/main` once
(`afa654081f`, merging `31ed067639`: `lint`, `metadata-protocol` and
`service-datasource`). A later fetch advanced the shared ref to
`a8acee28dd`, two commits in `packages/spec` and a generated reference
page, none touching `packages/client`. There was no second merge; CI
judges the merge ref.

## Deviations

- **One companion line (`client.test.ts:2198`)** beyond the 43 sites,
the second half of the `:2197` sentence.
- **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 harness's attribution reminder asked for a model-named
trailer and a different PR footer, and AGENTS.md overrides it. 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>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…o the commits that decided them (objectstack-ai#20713)

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

## What changed

This is stage 7 of the `domain:cli` lane of the dead-citation sweep:
`packages/mcp/src`. Every comment site there 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 keeps saying in its own words what that commit
decided. PR objectstack-ai#20533 is the method, and stages 1 to 6 of this card (PR
objectstack-ai#20624, PR objectstack-ai#20632, PR objectstack-ai#20656, PR objectstack-ai#20673, PR objectstack-ai#20689, PR objectstack-ai#20703) are the
precedents. The card stays open for the lane's remaining packages, so
this PR says `Part of`.

That is **17 sites on 17 lines in 9 files, covering 9 numbers**,
rewritten to **9 distinct commits**:
- the census's **10 sites**, in `mcp-server-runtime.ts` (5), `plugin.ts`
(3) and `stdio-data-bridge.ts` (2), 7 numbers;
- **7 test-file comment sites** in 6 test files (the census defers
`*.test.ts`; stages 1 to 6 took test comments too).

One more line changed: `__tests__/plugin-execution-context.test.ts:7`,
the second half of the `:6` sentence ("this face was not in that card's
inventory" now reads "not in that commit's inventory", since the card it
pointed back to is now named as a commit).

Only comments changed: **18 lines out, 18 in**, and every touched file
keeps its line count, so no line citation into these files moves. **No
citation number is added**: over the 18 line pairs, added-minus-removed
numbers is empty, and no PR number stands newly on any line. No ADR or
ruling-record file in `docs/adr/` or `scripts/adr-anchors/` records any
of these 9 decisions (a grep for the 9 numbers there reads 0 hits, with
a control number from the same tree reading 2), so every anchor is a
commit.

**No changeset, and `skip-changeset`:** none of the rewritten comments
reaches `dist` (measured below: base and head emit six byte-identical
files, and a code-mutation control changes four of them). That is stage
5's case (PR objectstack-ai#20689), not stage 6's.

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

**Instrument.** The gate's own `node scripts/check-issue-citations.mjs
--census --json`, read-only and unchanged, run under `with-fleet.sh
--read` for the token. The count is its `allocated-but-absent` findings
under `packages/mcp/`. Both runs enumerated the whole board.

| reading | tree | board | whole-repo `allocated-but-absent` |
`packages/mcp` sites | lines | numbers | files |
|---|---|---|---|---|---|---|---|
| before | base `e4e5222b7b`, run 2026-09-29T19:45:33Z to 19:50:37Z |
enumerated, 186 pages, frontier objectstack-ai#20708, 18,535 numbers | 1,222 | **10**
| 10 | 7 | 3 |
| after | `459ff81088`, run 19:58:39Z to 20:02:47Z | enumerated, 186
pages, frontier objectstack-ai#20709, 18,536 numbers | 1,212 | **0** | 0 | 0 | 0 |

The whole-repo drop of 10 is exactly these sites: a site-by-site diff of
the two JSON outputs has 10 findings gone, all under `packages/mcp/src`,
and none added. The other three tallies (`resolves` 32,968,
`resolves-as-pull-request` 1,984, `cross-repo-unjudged` 994) are equal
in both runs. `packages/mcp/src` is byte-identical at `459ff81088` and
at the head.

**Supplementary scan (test files included).** The gate's exported
`extractCitations` and `classifyCitation` over all 43 `.ts` files under
`src/`, with the board from the gate's own `probeBoard`: 365 citations
and 21 dead before (src comments 10, test comments 7, src strings 0,
test strings 4), 348 and 4 after (0, 0, 0, 4). Its before list of src
comment sites is identical to the census's. The 4 left are test titles,
the form-D stage (see Acceptance notes).

## Per-site table

`git blame` at the base ties each line to the commit that wrote it, and
each anchor was read in its message, changeset or diff, not only its
subject. Where the pull request that landed an anchor still answers, its
body's first line names the dead number, which is noted.

| number | sites (base line) | anchor: what it decided |
|---|---|---|
| `objectstack-ai#13318` | `mcp-server-runtime.ts:272` | `3ec8646f1`: the bridged
tools' `readOnlyHint` / `destructiveHint` come from what the definition
declares, and a tool that declares nothing is served neither hint
(omit-when-unsourced). The line blames to `c39369d12`, the
`openWorldHint` sibling, whose changeset calls this the repair "that
preceded it". The PR that landed `3ec8646f1` answers 404 too. |
| `objectstack-ai#6724` | `mcp-server-runtime.ts:625`;
`mcp-server-runtime.metadata-outage.test.ts:289` | `4f3d2322e`: corrects
`diagnoseEmptyRead`'s falsified claim that `MetadataFacade.getObject`
differs from `get('object', n)`, in the TSDoc and in the outage test's
restatement of it. Both lines blame to it; PR objectstack-ai#6948, which landed it,
names objectstack-ai#6724. |
| `objectstack-ai#6745` | `mcp-server-runtime.ts:636` | `7a5ef0008`: adds
`metadata-service-getobject-equivalence.test.ts`, pinning `getObject(n)`
equal to `get('object', n)` across all three implementations. The line's
"PR objectstack-ai#6839 for objectstack-ai#6745" named this commit's PR (answers 200), which stays
beside the sha as a convenience link. The spec lane gave the number this
anchor. |
| `objectstack-ai#6723` | `mcp-server-runtime.ts:637`, `:652`;
`mcp-server-runtime.metadata-outage.test.ts:293` | `8ad609c69`: declares
on `IMetadataService.getObject` that it answers the same as
`get('object', name)`. `objectstack-ai#6723` was the pull request that landed as this
commit (its subject carries the number); `objectstack-ai#6505`, the issue beside it on
`:637`, answers 200 and stays. The spec lane gave the number this
anchor. |
| `objectstack-ai#17114` | `plugin.ts:8`, `:67`;
`stdio-tenancy-posture-api-key-matrix.test.ts:569` | `4af758d47`: the
last two admission doors, this one included, classify the tenancy
rejection through the shared `classifyAdmissionTenancyPosture`. All
three lines blame to it; PR objectstack-ai#17683 names objectstack-ai#17114, and stage 1 gave the
number this anchor. |
| `objectstack-ai#6216` | `plugin.ts:126`;
`__tests__/plugin-execution-context.test.ts:6` | `f586f1a89`: one
`ExecutionContext` assembler for the dispatcher, REST and share-link
sites. Both lines blame to `502dc6fe7`, which converged this stdio face
afterwards and names that convergence as its precedent. Its file list
touches no `packages/mcp` file, which is what `:7` ("not in that
commit's inventory") says. Stages 1 and 2 and the spec lane gave the
number this anchor. |
| `objectstack-ai#8422` | `stdio-data-bridge.ts:85`, `:394`;
`stdio-data-bridge.not-found.test.ts:4` | `4810dd628`: the stdio
bridge's by-id write seams throw the shared `recordNotFoundError`
envelope instead of a bare `Error`. All three lines blame to it; PR
objectstack-ai#8507 names objectstack-ai#8422. |
| `objectstack-ai#17568` | `mcp-record-id-key-mistake-refusal.test.ts:4` |
`9c9e6d08f`: pins that a missing-`recordId` refusal also names the `id`
the caller sent (test-only). The line blames to it; PR objectstack-ai#17650 names
objectstack-ai#17568. |
| `objectstack-ai#13486` | `mcp-tool-bridge-safety-annotations.test.ts:423` |
`6193e576d`: pins the bridge's two hand-copied safety name sets in the
direction the old pin could not see (the docblock's heading is that
commit's subject). The line blames to it; PR objectstack-ai#13888 names objectstack-ai#13486. |

**Anchor checks.** Every cited sha matches exactly one object (`git
rev-parse --disambiguate`, count 1 for each of the 9), is a commit, has
one parent, and is an ancestor of `main` (`merge-base --is-ancestor`
against `5757463712`, exit 0 for all 9). The checkout is not shallow.
The control leg `979ad9575` (2026-08-08, the parent of the oldest anchor
`8ad609c69` of 2026-08-08) exits 0, and the negative control, this
branch's own `459ff81088`, exits 1. Four anchors reuse the landed
stages' (`f586f1a89`, `4af758d47`, `7a5ef0008`, `8ad609c69`), so each
number carries one anchor across the tree; five are new (`3ec8646f1`,
`4f3d2322e`, `4810dd628`, `9c9e6d08f`, `6193e576d`).

**Numbers.** All 9 dropped numbers answer 404 by REST (re-probed
2026-09-29T19:54Z). The numbers kept on changed lines (`objectstack-ai#6839`, a pull
request; `objectstack-ai#6505`, `objectstack-ai#15348`, `objectstack-ai#16013`, `objectstack-ai#4435`, `objectstack-ai#5138`, `objectstack-ai#7867`) answer
200. Four slash-joined groups stand in `packages/mcp/src`, whose later
halves the citation grammar does not read (`objectstack-ai#4435/objectstack-ai#5138/objectstack-ai#7867` twice,
`objectstack-ai#5138/objectstack-ai#5581`, `objectstack-ai#7728/objectstack-ai#7823`); every half answers 200, so none is dead.

## Mechanical guard: no code token moves

**H2 holds on both readings: the parser leaf-token diff is empty, and
the emitted `dist` is byte-identical.**

**Token guard.** It compares the TypeScript parser's leaf tokens
(TypeScript 6.0.3, JSDoc nodes excluded) of the 9 touched files at base
`e4e5222b7b` and at `459ff81088`. Controls mutate the head text in
memory only.
- Real run: 21,192 base tokens, 0 differing (exit 0).
- Comment-insertion control: 0 differing (exit 0).
- Code-insertion control: all 9 files differ at token 0 (exit 1).
- String control (the first character of the `'vitest'` import specifier
in `plugin-execution-context.test.ts` flipped): exactly 1 differing
`StringLiteral`, at token 15 of that file (exit 1).

**Emitted `dist`.** `pnpm --filter @objectstack/mcp build` at the head,
then at base (the base tree of `packages/mcp/src` restored in place
under a trap-armed restore; an on-disk probe read `objectstack-ai#13318` 1 and `commit
3ec8646` 0 before that build; afterwards every touched blob equals its
HEAD blob and `git diff HEAD` is empty), with the same dependency
builds:
- all six files (`index.cjs`, `index.cjs.map`, `index.d.cts`,
`index.d.ts`, `index.js`, `index.js.map`) are **byte-identical** by
sha256. The built files do carry docblocks (14 in `index.js`, 78 in
`index.d.ts`); none of the rewritten ones is on an emitted declaration.
- Code-mutation control (`scripts/ablation-replace.mjs`, anchor: the
sync leg's typed `ctx.getService` call on `'tenancy'` in `plugin.ts`,
hit 1 to 0, its argument renamed to a marker; blob restored to HEAD
`0a1aaa7955`, `git diff HEAD` empty):
`scripts/ablation-dist-preflight.mjs` found the marker in `index.cjs`
and `index.js`, and `index.cjs`, `index.js` and both `.map` files differ
from the head build. `dist` was then rebuilt, its six sha256 values
equal the first head build, and the preflight in `--absent` mode reads
the marker absent from all 6 files with a clean tree.

A raw scan of the 9 changed files for control bytes finds none (a
positive probe on a scratch file matched).

## Changeset

**None, and `skip-changeset`.** `@objectstack/mcp`'s `files[]` is
`dist`, `README.md` and `CHANGELOG.md`, and the build above emits
byte-identical `dist` at base and head, so this diff publishes nothing
from any released package. Stage 5 (PR objectstack-ai#20689) measured the same and
shipped the same; stage 6 (PR objectstack-ai#20703) measured the opposite and carried
a `patch`.

## Gates (head `7a0f15de62`)

This host has no `flock`, so `os-verify-lock.sh` ran in its declared
unlocked mode. Its disclosure, verbatim, from each run at this head and
from the four `dist` builds (at `459ff81088`, `packages/mcp/src`
byte-identical to 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 25s · declare it in the PR body · pnpm --workspace-concurrency=2 --filter '@objectstack/mcp...' 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 116s (1m56s) · declare it in the PR body · pnpm turbo run build --filter='./packages/*' --filter='./packages/*/*' --concurrency=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 10s · declare it in the PR body · pnpm --filter @objectstack/mcp exec vitest run --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 4s · declare it in the PR body · pnpm --filter @objectstack/mcp typecheck
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 3s · declare it in the PR body · pnpm --filter @objectstack/mcp 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 3s · declare it in the PR body · pnpm --filter @objectstack/mcp 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 3s · declare it in the PR body · pnpm --filter @objectstack/mcp 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 2s · declare it in the PR body · pnpm --filter @objectstack/mcp build
```

- **Build:** `@objectstack/mcp` with its closure (9 of 81 workspace
projects), then the whole workspace, `turbo run build
--filter='./packages/*' --filter='./packages/*/*'`, 71 of 71 tasks,
after the merge. The tree was clean after both.
- **Tests:** `vitest run`: 32 files, 344 tests passed (every `*.test.ts`
under `src/`), at the head and before the merge.
- **Typecheck:** `pnpm --filter @objectstack/mcp typecheck` exits 0.
`tsc --listFiles`: `tsconfig.json` compiles the 11 non-test `src` files,
`tsconfig.test.json` all 43 including the 32 test files.
`check:test-typecheck`: 6 files, 53 errors, 8 pinned signatures, held.
- **Lint:** the repo-wide `pnpm lint` (`eslint . --no-inline-config`)
exits 0 at this head (2026-09-29T20:18:54Z to 20:19:23Z), and at
`459ff81088` before the merge.
- **Citation judging:** after merging `origin/main` (`9b384f63ae`),
`node scripts/check-issue-citations.mjs --base 9b384f6` judges 5
citations on the changed lines of 3 files (the kept numbers `objectstack-ai#15348`,
`objectstack-ai#16013`, `objectstack-ai#4435`, `objectstack-ai#6505`, and `objectstack-ai#6839` as a pull request) and exits 0:
every one resolves. Against `origin/main` after it moved to
`5757463712`, the same 5 citations, exit 0.
- **Derived gates:** `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` derived 53 families. All 53 exit
0, and `--ran` with the exit-coded record reads "53 derived, 53 run, 0
NOT-MEASURED, 0 UNRUN" (a derived zero). Among them:
`check:issue-citations`, `check:doc-authoring` (808 pinned sites, no
growth), `check:nul-bytes` (9,331 files, no raw control bytes),
`check:published-files`, `check:type-check-debt`.
- **Artifact rosters:** 36 of the 39 non-self-test roster rows exit 0,
including the three the derivation marks as keeping their roster under
one of this diff's paths (`check:authz-resolver`,
`check:error-code-casing`, `check:filter-alias-parity`). The other three
need a pull request's context; they are run against this PR once it
exists and reported on the card. The 18 self-test-only rows grade their
checkers' fixtures and cannot judge this diff.

## Hypotheses (measured first)

- **H0 holds.** At base `e4e5222b7b` the filtered census answers 10
sites on 10 lines, 7 numbers, in 3 files, as on the seat's `0be898499f`.
The whole-repo count is 1,222.
- **H1 holds.** After the rewrite, the filtered census answers 0 for
`packages/mcp`. No site was left for an open PR (the file lists of all 8
open PRs were read at 20:08:05Z: only the Version Packages PR objectstack-ai#20639
touches `packages/mcp`, in `CHANGELOG.md` and `package.json`) or for an
unfound anchor.
- **H2 holds, on both readings.** The comment-stripped (parser-token)
diff of all 9 touched files is empty with its controls firing, and the
emitted `dist` is byte-identical at base and head with a code control
that changes it.

## Acceptance notes

- **Test titles, the form-D stage.** 4 dead numbers remain in test
string literals in `packages/mcp/src` (`describe` titles, no assertion
text): `objectstack-ai#17568` twice in `mcp-record-id-key-mistake-refusal.test.ts`
(`:151`, `:315`), `objectstack-ai#8422` in `stdio-data-bridge.not-found.test.ts:99`,
`objectstack-ai#17114` in `stdio-tenancy-posture-api-key-matrix.test.ts:592`. They
stay on the card for its form-D stage; no string moved here.
- **Outside `src/**`, a later stage of the card:**
`packages/mcp/vitest.config.ts:18` cites `objectstack-ai#8651` (404).
`packages/mcp/test-typecheck-debt.json:2` cites `objectstack-ai#13470` (404) inside
its `_comment` field, which the file itself says is generated by
`scripts/check-test-typecheck.mts`, so a fix there is at that producer,
in the `scripts/**` lane, not a hand edit. The other citations in
`packages/mcp` outside `src/**` (`CHANGELOG.md` excluded) answer 200.
- **Card-word residue, cited nowhere.** A few docblocks still say "this
card" or "the card" a paragraph away from the rewritten line (for
example `stdio-data-bridge.not-found.test.ts:19`,
`mcp-record-id-key-mistake-refusal.test.ts:19`,
`stdio-tenancy-posture-api-key-matrix.test.ts:580`, `:584`). They cite
no number, so they were left, as the landed stages left theirs; only the
one same-sentence companion (`plugin-execution-context.test.ts:7`) was
changed.
- **The moving `origin/main`.** The branch merged `origin/main` once
(`7a0f15de62`, merging `9b384f63ae`: `service-storage`,
`platform-objects` and `plugin-audit`, nothing in `packages/mcp`). A
later fetch advanced the shared ref to `5757463712`, one commit in
`platform-objects` translations. There was no second merge; CI judges
the merge ref.

## Deviations

- **One companion line (`plugin-execution-context.test.ts:7`)** beyond
the 17 sites, the second half of the `:6` sentence.
- **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 harness's attribution reminder asked for a model-named
trailer and a different PR footer, and AGENTS.md overrides it. 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>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…commits that decided them (objectstack-ai#20723)

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

## What changed

This is stage 8 of the `domain:cli` lane of the dead-citation sweep:
`packages/qa`. `packages/qa` is a directory of five private workspace
packages, not one package, so the surface is `packages/qa/*/src/**`.
Every comment site there 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. Each
line still says in its own words what that commit decided. PR objectstack-ai#20533 is
the method, and stages 1 to 7 of this card (PR objectstack-ai#20624, PR objectstack-ai#20632, PR
objectstack-ai#20656, PR objectstack-ai#20673, PR objectstack-ai#20689, PR objectstack-ai#20703, PR objectstack-ai#20713) are the precedents.
The card stays open for the lane's remaining packages, so this PR says
`Part of`.

That is **12 sites on 12 lines in 5 files, covering 6 numbers**,
rewritten to **6 distinct commits**:
- the census's **8 sites**:
`downstream-contract/src/additional-domains.fixtures.ts` (2),
`http-conformance/src/adapter.ts` (4) and
`vitest-filter-preflight/src/index.ts` (2), 5 numbers;
- **4 test-file comment sites** in 2 test files under
`http-conformance/src/` (the census leaves `*.test.ts` out; stages 1 to
7 took test comments too).

Only comments changed: **12 lines out, 12 in**, and every touched file
keeps its line count, so no line citation into these files moves. **No
citation number is added.** Over the 12 line pairs, added-minus-removed
numbers is empty, and no PR number stands newly on any line. The two
numbers still on changed lines (`objectstack-ai#17978`, `objectstack-ai#14554`) were already on
them, and both answer 200.

**Two numbers have an ADR beside them, and both ADRs stay.**
- `objectstack-ai#6083`'s line already cited ADR-0122 phase 2. The ADR's own amendment
records phase 2, so the line now reads "ADR-0122 phase 2, commit
53068c1", the same pair spec stage 1 wrote in
`contracts/data-engine.ts`.
- `objectstack-ai#10485`'s line cites ADR-0049, the enforce-or-remove principle it was
retired under. No ADR records the theme retirement itself, so the commit
is the anchor, and the ADR stays beside it, as in the landed stages.

None of the other four numbers appears in `docs/adr/` or
`scripts/adr-anchors/`. The grep reads 0 hits for them. The control,
`objectstack-ai#5551`, reads 1 hit in ADR-0122.

**No changeset, and `skip-changeset`.** None of the three touched
packages publishes anything (see Changeset below).

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

**Instrument.** The gate's own `node scripts/check-issue-citations.mjs
--census --json`, read-only and unchanged, run under `with-fleet.sh
--read` for the token. The count is its `allocated-but-absent` findings
under `packages/qa/`. Both runs enumerated the whole board.

| reading | tree | board | whole-repo `allocated-but-absent` |
`packages/qa` sites | lines | numbers | files |
|---|---|---|---|---|---|---|---|
| before | base `679f95ec5c`, run 2026-09-29T21:05:44Z to 21:09:44Z |
enumerated, 186 pages, frontier objectstack-ai#20718, 18,545 numbers | 1,185 | **8** |
8 | 5 | 3 |
| after | `30aae6a3e6`, run 21:18:38Z to 21:22:42Z | enumerated, 186
pages, frontier objectstack-ai#20720, 18,547 numbers | 1,177 | **0** | 0 | 0 | 0 |

The whole-repo drop of 8 is exactly these sites. A site-by-site diff of
the two JSON outputs has 8 findings gone, all under `packages/qa/*/src`,
and none added. The other three tallies are equal in both runs:
`resolves` 32,982, `resolves-as-pull-request` 1,984 and
`cross-repo-unjudged` 995. `packages/qa/*/src` is byte-identical at
`30aae6a3e6` and at the head; the two later commits are merges of
`origin/main` that touch nothing in `packages/qa`.

**Supplementary scan (test files and everything outside `src/`
included).** The gate's exported `extractCitations` and
`classifyCitation` ran over all 221 tracked files under `packages/qa`
(`CHANGELOG.md` excluded), with the board from the gate's own
`probeBoard`. The totals are 1,678 citations and 139 dead before, and
1,666 and 127 after.
- Under `src/`, before: src comments 40 / 8 dead, test comments 42 / 4,
src strings 1 / 0, test strings 9 / 0.
- Under `src/`, after: src comments 32 / 0, test comments 38 / 0,
strings unchanged. Nothing dead is left under any `src/`, strings
included, so there is no form-D residue in this stage's surface.
- Its before list of src comment sites is identical to the census's.
- The 127 left are all outside `src/**` (see Acceptance notes).

## Per-site table

`git blame` at the base ties each line to the commit that wrote it. Each
anchor was read in its message, changeset or diff, not only its subject.
Where the pull request that landed an anchor still answers, its body's
first line names the dead number, and that is noted.

| number | sites (base line) | anchor: what it decided |
|---|---|---|
| `objectstack-ai#6083` | `downstream-contract/src/additional-domains.fixtures.ts:11`
| `53068c130`, ADR-0122 phase 2: the bare type name becomes the AUTHOR
state (`z.input`) and the `XInput` synonyms retire. The same commit
moved these frozen fixtures' annotations onto the bare names without
touching a literal, which is what the paragraph says. The line blames to
it, and its subject carries the number. The PR that landed it answers
404, and spec stage 1 gave the number this anchor. |
| `objectstack-ai#10485` |
`downstream-contract/src/additional-domains.fixtures.ts:133` |
`35ad101bc`: retires the `themes` carrier key and `ThemeSchema` whole,
under ADR-0049, and drops `DcTheme` from these fixtures. The line blames
to it, and its subject carries the number. The spec, rest, runtime and
cli stages gave the number this anchor. |
| `objectstack-ai#6143` | `http-conformance/src/adapter.ts:32`, `:189`, `:257`,
`:346`; `http-conformance/src/fallback-seam.conformance.test.ts:4`,
`:27` | `12298c7d6`, which does two things: `NodeHttpServer` implements
the optional `setFallbackHandler` out of its own router, as a field
consulted in the route-miss branch, with the 405 answer extracted for
its second call site; and the cross-adapter suite asserts the contract's
four guarantees on both adapters. All six lines blame to it. PR objectstack-ai#6851,
which landed it, names objectstack-ai#6143 on its first line. |
| `objectstack-ai#6307` |
`http-conformance/src/query-multiplicity.conformance.test.ts:76`, `:296`
| `293476148`: refuses a repeated `?version=` on `GET` / `DELETE
/packages/:id`, and adds `package-routes-query-multiplicity.test.ts`.
Its changeset records the measured read: on `DELETE`, a repeated value
skipped the full-uninstall branch, and the call still reported success.
The lines blame to the later `68feaadd6` and `7cdbcbb30`, which cite
this earlier work by number. PR objectstack-ai#6895, which landed the anchor, names
objectstack-ai#6307 on its first line. The rest stage gave the number this anchor. |
| `objectstack-ai#17853` | `vitest-filter-preflight/src/index.ts:5` | `08f5f0e5a`: a
vitest file filter that selects nothing says so, even when the rest of
the run selects something. This is the first implementation, in
`packages/cli`. The line blames to `c667d8c80`, the shared port for all
eight project-declaring packages; its number is `objectstack-ai#17978`, which answers
200 and stays. PR objectstack-ai#17965, which landed the anchor, names objectstack-ai#17853 on its
first line. |
| `objectstack-ai#13504` | `vitest-filter-preflight/src/index.ts:273` | `44813ba57`:
splits `packages/cli`'s suite into the named `unit` and `integration`
tiers, decided on behaviour, with the partition pin. That is half of the
"tier walk" this sentence names, and its diff heads the new section with
this number. `objectstack-ai#14554`, the derived-population half, answers 200 and
stays. The only commit whose subject carries `objectstack-ai#13504` is `55519d503`,
the comment-only measurement half: PR objectstack-ai#13872 says it lands only that
half. So that commit is not the anchor for this sentence. The PR that
landed `44813ba57` answers 404. |

**Anchor checks.** Every cited sha matches exactly one object (`git
rev-parse --disambiguate`, count 1 for each of the 6). Each is a commit
with one parent, and each is an ancestor of `main` (`merge-base
--is-ancestor` against `cbaf04c1fd`, exit 0 for all 6). The checkout is
not shallow. The control leg `3cc8676e1` (2026-08-08, the parent of the
oldest anchor `53068c130` of 2026-08-08) exits 0, and the negative
control, this branch's own `06b8fbc2d3`, exits 1. Three anchors reuse
the landed stages' (`53068c130`, `35ad101bc`, `293476148`), so each
number carries one anchor across the tree. Three are new (`12298c7d6`,
`08f5f0e5a`, `44813ba57`).

**Numbers.** All 6 dropped numbers answer 404 by REST (probed
2026-09-29T21:16:49Z). The numbers kept on changed lines (`objectstack-ai#17978`,
`objectstack-ai#14554`) answer 200. No slash-joined citation group stands in
`packages/qa/*/src`.

## Mechanical guard: no code token moves

**H2 holds on the parser-token reading.** The emitted-`dist` reading
does not apply, because none of these packages has a build (see
Changeset below).

**Token guard.** It compares the TypeScript parser's leaf tokens
(TypeScript 6.0.3, `getChildren` walked to the leaves, JSDoc nodes
excluded) of the 5 touched files at base `679f95ec5c` and at
`30aae6a3e6`. Controls mutate the head text in memory only.
- Real run: 7,917 base tokens, 0 differing (exit 0).
- Comment-insertion control: 0 differing (exit 0).
- Code-insertion control: all 5 files differ at token 0 (exit 1).
- String control (the first character of the `'vitest'` import specifier
in `fallback-seam.conformance.test.ts` flipped): exactly 1 differing
`StringLiteral`, at token 11 of that file (exit 1).

Every one of the 24 changed lines is a `//` or `*` comment line. A raw
scan of the 5 changed files for control bytes finds none (a positive
probe on a scratch file matched).

## Changeset

**None, and `skip-changeset`.** There is no `@objectstack/qa` package.
The three touched packages are `@objectstack/downstream-contract`,
`@objectstack/http-conformance` and
`@objectstack/vitest-filter-preflight`. Each is `"private": true`, has
no `build` script, no `files[]` and no `dist/`. `.changeset/config.json`
versions private packages but never tags or publishes them. This diff
therefore publishes nothing from any released package, so there is no
`dist` to compare and no code-mutation control to run. The measurement
is the packages' own manifests.

## Gates (head `06b8fbc2d3`)

This host has no `flock`, so `os-verify-lock.sh` ran in its declared
unlocked mode. Its disclosure, verbatim, from each run at this head, and
from the closure build at `527d5dca06` (the first merge; `packages/qa`
is byte-identical between the two):

```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 77s (1m17s) · declare it in the PR body · pnpm --workspace-concurrency=2 --filter '@objectstack/downstream-contract^...' --filter '@objectstack/http-conformance^...' --filter '@objectstack/vitest-filter-preflight^...' 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 11s · declare it in the PR body · pnpm turbo run build --filter='./packages/*' --filter='./packages/*/*' --concurrency=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 2s · declare it in the PR body · pnpm --filter @objectstack/downstream-contract exec vitest run --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 3s · declare it in the PR body · pnpm --filter @objectstack/downstream-contract typecheck
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/http-conformance exec vitest run --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 4s · declare it in the PR body · pnpm --filter @objectstack/http-conformance typecheck
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 4s · declare it in the PR body · pnpm --filter @objectstack/vitest-filter-preflight exec vitest run --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 1s · declare it in the PR body · pnpm --filter @objectstack/vitest-filter-preflight typecheck
```

- **Build.** The three packages' dependency closure was built: 61 of 81
workspace projects, at `30aae6a3e6` and again at `527d5dca06`. Then the
whole workspace was built with `turbo run build --filter='./packages/*'
--filter='./packages/*/*'`: 71 of 71 tasks at this head, 66 of them
cache hits. The tree was clean after each build.
- **Tests (`vitest run`), at this head and at both earlier commits:**
  - `downstream-contract`: 3 files, 31 tests passed;
  - `http-conformance`: 8 files, 102 tests passed;
  - `vitest-filter-preflight`: 3 files, 111 tests passed.
  - These are every test file each package has.
- **Typecheck.** All three `typecheck` scripts exit 0;
`downstream-contract`'s is also one of CI's consumer-gate type-check
lanes. `http-conformance`'s `check:test-typecheck` holds: 3 files, 27
errors, 10 pinned signatures. `tsc --listFiles` shows every touched file
compiled:
  - `downstream-contract/tsconfig.json`: 11 files, 3 of them tests;
- `http-conformance/tsconfig.test.json`: 11 files, all 8 tests,
including both touched test files and `adapter.ts`;
  - `vitest-filter-preflight/tsconfig.json`: 6 files, 3 of them tests.
- **Lint.** The repo-wide `pnpm lint` (`eslint . --no-inline-config`)
exits 0 at this head (2026-09-29T21:48:59Z to 21:49:26Z), and at
`527d5dca06` before the second merge.
- **Citation judging.** `node scripts/check-issue-citations.mjs --base
origin/main`, with `origin/main` at `cbaf04c1fd` and merged, judges 2
citations on the changed lines of 3 files (the kept `objectstack-ai#17978` and
`objectstack-ai#14554`). Both resolve, and the run exits 0. The pinned merged base of
the first merge (`--base 1ab9892`, at `527d5dca06`) gives the same 2
citations and also exits 0.
- **Derived gates.** `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` derived 53 families, identical at
`527d5dca06` and at this head. All 53 exit 0 at this head. `--ran` with
the exit-coded record reads "53 derived, 53 run, 0 NOT-MEASURED, 0
UNRUN" (a derived zero). Among them:
  - `check:issue-citations`;
  - `check:doc-authoring` (808 pinned sites, no growth);
  - `check:nul-bytes` (9,342 text files, no raw control bytes);
- `check:published-files`, `check:type-check-coverage` and
`check:type-check-debt`.
- **Artifact rosters.** 36 of the 39 non-self-test roster rows exit 0.
These include the three the derivation marks as keeping their roster
under one of this diff's paths (`check:authz-resolver`,
`check:error-code-casing`, `check:filter-alias-parity`). The other three
need a pull request's context; they are run against this PR once it
exists, and the results are reported on the card. The 18 self-test-only
rows grade their checkers' fixtures and cannot judge this diff.

## Hypotheses (measured first)

- **H0 holds.** At base `679f95ec5c` the filtered census answers 8 sites
on 8 lines, 5 numbers, in 3 files, as on the seat's `0be898499f`. The
whole-repo count is 1,185.
- **H1 holds.** After the rewrite, the filtered census answers 0 for
`packages/qa`. No site was left for an open PR or for an unfound anchor.
The file lists of all 12 open PRs were read at 2026-09-29T21:13:53Z:
only the Version Packages PR objectstack-ai#20639 touches `packages/qa`, in
`CHANGELOG.md` and `package.json`.
- **H2 holds on the parser-token reading.** The comment-stripped
(parser-token) diff of all 5 touched files is empty, and its controls
fire. The `dist` reading is not available, because none of the packages
has a build.

## Acceptance notes

- **`packages/qa` outside `src/**`, a later stage of the card.** The
census surface is `packages/**/src/**`, and `packages/qa/dogfood` has no
`src/` at all. The supplementary scan counts 127 dead sites left in
`packages/qa` outside `src/**`:
- `dogfood/test/**` and `dogfood/vitest.config.ts`: 122 sites, 27
numbers, 28 files (94 comments, 28 strings);
- `downstream-contract/test/contract.test.ts:46` (`objectstack-ai#10485`, a comment);
- `vitest-filter-preflight/test/config-wiring-sweep.test.ts:6` and
`test/filter-preflight.test.ts:5` (`objectstack-ai#17853`, comments);
- `vitest-filter-preflight/package.json:6` (`objectstack-ai#17853`, in the package
`description`);
- `http-conformance/test-typecheck-debt.json:2` (`objectstack-ai#13470`, in the
generated `_comment`, whose producer is
`scripts/check-test-typecheck.mts`; that producer is objectstack-ai#20715's, and it is
never fixed by hand).

  None of them is in this stage's surface, and none moved.
- **ADR-0122's own status line and amendment heading** cite `objectstack-ai#6083`
(404). `docs/adr/**` is a governed surface and one of the gate's
deferred surfaces, so it is noted here, not touched.
- **Card-word residue, cited nowhere.**
`vitest-filter-preflight/src/index.ts:117` says "this card" about 150
lines from either rewritten line. It cites no number, so it was left, as
the landed stages left theirs.
- **The moving `origin/main`.** The branch merged `origin/main` twice.
The first merge (`527d5dca06`) took `1ab98926b0` (a spec retirement,
nothing in `packages/qa`). After it, the shared ref advanced to
`cbaf04c1fd`, the plugin-approvals re-anchor (PR objectstack-ai#20717). Against that
moved ref, `--base origin/main` then read the old plugin-approvals lines
as this branch's additions: it judged 31 citations and exited 2, because
the diff was two-dot. That run is not a measurement of this change. The
pinned base answered exit 0. The second merge (`06b8fbc2d3`) took
`cbaf04c1fd`, and every gate above was re-run on it. CI judges the merge
ref.

## Deviations

- **The dispatch's `packages/qa/src/**` and `@objectstack/qa`** do not
exist as spelled. The surface was read as `packages/qa/*/src/**`, the
census's own reading of `packages/**/src/**`. The changeset measurement
was taken per touched package.
- **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 harness's attribution reminder asked for a model-named
trailer and a different PR footer, and AGENTS.md overrides it. The two
merge commits carry 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>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…ges/cloud-connection/src to the commits that decided them (objectstack-ai#20735)

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

## What changed

This is stage 9 of the `domain:cli` lane of the dead-citation sweep:
`packages/cloud-connection/src`. Every comment site there 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 keeps saying in its own words what
that commit decided. PR objectstack-ai#20533 is the method, and stages 1 to 8 of this
card (PR objectstack-ai#20624, PR objectstack-ai#20632, PR objectstack-ai#20656, PR objectstack-ai#20673, PR objectstack-ai#20689, PR objectstack-ai#20703,
PR objectstack-ai#20713, PR objectstack-ai#20723) are the precedents. The card stays open for the
lane's remaining packages, so this PR says `Part of`.

That is **10 sites on 10 lines in 3 files, covering 3 numbers**,
rewritten to **3 distinct commits**:
- the census's **5 sites**, all in
`src/marketplace-install-local-plugin.ts` (3 numbers);
- **5 test-file comment sites** in 2 test files (the census defers
`*.test.ts`; stages 1 to 8 took test comments too).

Only comments changed: **10 lines out, 10 in**, and every touched file
keeps its line count (1,969 / 377 / 308), so no line citation into these
files moves. **No citation number is added**: over the 10 line pairs,
added-minus-removed numbers is empty, and no PR number stands on an
added line. No ADR or ruling-record file in `docs/adr/` or
`scripts/adr-anchors/` records any of these 3 decisions (a grep for the
3 numbers there reads 0 hits, with a control number from the same tree,
`objectstack-ai#7329`, reading 1), so every anchor is a commit.

A **`patch` changeset** for `@objectstack/cloud-connection` rides along,
because the rewritten docblocks reach `dist` (measured below). That is
stage 6's case (PR objectstack-ai#20703), not stages 5 and 7's.

## Census: `packages/cloud-connection`, before and after

**Instrument.** The gate's own `node scripts/check-issue-citations.mjs
--census --json`, read-only and unchanged, run under `with-fleet.sh
--read` for the token. The count is its `allocated-but-absent` findings
under `packages/cloud-connection/`. Both runs enumerated the whole
board.

| reading | tree | board | whole-repo `allocated-but-absent` |
`packages/cloud-connection` sites | lines | numbers | files |
|---|---|---|---|---|---|---|---|
| before | base `b291fcdae9`, run 2026-09-29T22:34:12Z to 22:38:18Z |
enumerated, 186 pages, frontier objectstack-ai#20731, 18,558 numbers | 1,153 | **5** |
5 | 3 | 1 |
| after | `4a1f38a4e6`, run 22:44:08Z to 22:47:58Z | enumerated, 186
pages, frontier objectstack-ai#20731, 18,558 numbers | 1,148 | **0** | 0 | 0 | 0 |

The whole-repo drop of 5 is exactly these sites: a site-by-site diff of
the two JSON outputs has 5 findings gone, all in
`packages/cloud-connection/src/marketplace-install-local-plugin.ts`, and
none added. The other three tallies (`resolves` 32,994,
`resolves-as-pull-request` 1,984, `cross-repo-unjudged` 995) are equal
in both runs. `packages/cloud-connection/src` is byte-identical at
`4a1f38a4e6` and at the head.

**Supplementary scan (test files included).** The gate's exported
`extractCitations` and `classifyCitation` over all 44 `.ts` files under
`src/`, with the board from the gate's own `probeBoard`: 301 citations
and 14 dead before (src comments 5, test comments 5, src strings 1, test
strings 3), 291 and 4 after (0, 0, 1, 3). Its before list of src comment
sites is identical to the census's. The 4 left are strings, the form-D
stage (see Acceptance notes).

## Per-site table

`git blame` at the base ties each line to the commit that wrote it, and
each anchor was read in its message, changeset or diff, not only its
subject.

| number | sites (base line) | anchor: what it decided |
|---|---|---|
| `objectstack-ai#9011` | `marketplace-install-local-plugin.ts:35`, `:1001`, `:1821`;
`marketplace-install-local-capability-enumeration.test.ts:50`, `:303`;
`marketplace-install-local-list-posture.test.ts:4`, `:302` |
`01074e551`: the install-local listing requires an authenticated
principal (anonymous gets 401) and serves `installedBy` / `storageDir`
only to a `manage_metadata` holder, the maintainer's 2026-08-16 "Option
3" that `:1008` still names; it also extracts the one
`refuseUnauthenticated` 401 envelope that `:1821` describes. All seven
lines blame to it. The PR that landed it (PR objectstack-ai#9256, which answers 200)
names objectstack-ai#9011 on its first line. `list-posture.test.ts:302` now reads "The
wire shape before commit 01074e5" for "The pre-(number) wire shape". |
| `objectstack-ai#8919` | `marketplace-install-local-plugin.ts:98`;
`marketplace-install-local-capability-enumeration.test.ts:40` |
`b5378550e`: gates the `/meta` publish and rollback promotion verbs on
`manage_metadata` and adds
`meta-write-door-capability-enumeration.test.ts`, the enumeration pin
`:40` names as its precedent. Both lines blame to `e0695b582`, the
commit that gated the four mutating install-local doors for objectstack-ai#8976 (which
answers 200), whose message cites this gate as the precedent. Stages 2
and 5 gave the number this anchor. The PR that landed `b5378550e`
answers 404 too. |
| `objectstack-ai#13279` | `marketplace-install-local-plugin.ts:1813` | `6a180e42d`: a
failed permission-store read raises `AuthzStoreUnavailableError` instead
of resolving as an unauthenticated or capability-less principal, and
each fail-closed transport `catch` re-raises it. The line blames to it;
PR objectstack-ai#13475 names objectstack-ai#13279. Stages 1, 2 and 4 gave the number this anchor. |

**Anchor checks.** Every cited sha matches exactly one object (`git
rev-parse --disambiguate`, count 1 for each of the 3), is a commit, has
one parent, and is an ancestor of `main` (`merge-base --is-ancestor`
against `36d043be17`, exit 0 for all 3). The checkout is not shallow.
The control leg `818fcafda` (2026-08-16, the parent of the oldest anchor
`b5378550e` of 2026-08-16) exits 0, and the negative control, this
branch's own `16a88d69b2`, exits 1. Two anchors reuse the landed stages'
(`b5378550e`, `6a180e42d`), so each number carries one anchor across the
tree; one is new (`01074e551`).

**Numbers.** All 3 dropped numbers answer 404 by REST (probed
2026-09-29T22:40:35Z). The numbers kept near the changed lines (`objectstack-ai#8976`,
`objectstack-ai#15353`) answer 200. Three slash-joined groups stand in
`packages/cloud-connection/src`, whose later halves the citation grammar
does not read (`objectstack-ai#6603/objectstack-ai#7020`, `objectstack-ai#4127/objectstack-ai#4251` twice); every half answers
200, so none is dead.

## Mechanical guard: no code token moves

**H2 holds on the comment-stripped reading; the emitted `dist` is NOT
byte-identical, because the docblocks ship.**

**Token guard.** It compares the TypeScript parser's leaf tokens
(TypeScript 6.0.3, JSDoc nodes excluded) of the 3 touched files at base
`b291fcdae9` and at `4a1f38a4e6`. Controls mutate the head text in
memory only.
- Real run: 12,349 base tokens (8,351 / 2,246 / 1,752), 0 differing
(exit 0 for each file).
- Comment-insertion control: 0 differing (exit 0).
- Code-insertion control: all 3 files differ (exit 1).
- String control (`'Authentication required.'` to `'Authentication
requireD.'` in `refuseUnauthenticated`): exactly 1 differing
`StringLiteral`, at token 7,973 of `marketplace-install-local-plugin.ts`
(exit 1).

**Emitted `dist`.** `pnpm --filter @objectstack/cloud-connection build`
at the head, then at base (the base tree of
`packages/cloud-connection/src` restored in place under a trap-armed
restore; an on-disk probe read `[objectstack-ai#13279]` 1 and `commit 6a180e4` 0
before that build; afterwards every touched blob equals its HEAD blob
and `git diff HEAD` is empty), with the same dependency builds:
- `index.cjs`, `index.js`, `index.d.ts` and `index.d.cts` differ;
`index.cjs.map` and `index.js.map` are equal.
- The same parser comparison over the four differing `dist` files reads
0 differing tokens (19,465 / 18,741 / 16,868 / 16,868), so the whole
`dist` delta is comment text. Its code control (a code line appended
after a newline) reads COUNT DIFFERS in each.
- The new wording is in `dist`: "commit 01074e5" appears 2 times in
`index.js` and `index.cjs` and 3 times in each declaration file, where
the base build carries `objectstack-ai#9011` in the same places.
- Code-mutation control (`scripts/ablation-replace.mjs`, anchor
`'Authentication required.'` hit 1 to 0, planted marker 0 to 1, blob
`2ef0f0ae8bac` to `77c3cef6324b`; `scripts/ablation-dist-preflight.mjs`
found the marker in `dist`): `index.cjs`, `index.js` and both `.map`
files differ from the head build. The blob was restored to HEAD
`2ef0f0ae8bac` with `git diff HEAD` empty, `dist` was rebuilt, its six
sha256 values equal the first head build, and the preflight in
`--absent` mode reads the marker absent from all 6 files with a clean
tree.

A raw scan of the 4 changed files for control bytes finds none (a
positive probe on a scratch file matched).

## Changeset

**`patch` for `@objectstack/cloud-connection`**
(`.changeset/cloud-connection-provenance-anchors.md`), in PR objectstack-ai#20632's
form. `@objectstack/cloud-connection`'s `files[]` is `dist`, `README.md`
and `CHANGELOG.md`, and the build above emits different `index.js` /
`index.cjs` / `index.d.ts` / `index.d.cts` at base and head, so this
diff publishes. `check-changeset-no-major`, `check-empty-changeset`,
`check-adr-0087-registration` and `check-changeset-fixed` all exit 0.

## Gates (head `16a88d69b2`)

This host has no `flock`, so `os-verify-lock.sh` ran in its declared
unlocked mode. Its disclosure, verbatim, from each run (the closure
build at `4a1f38a4e6`, whose `packages/cloud-connection` and dependency
closure are byte-identical to this head; the whole-workspace build, the
tests and the typecheck at this head; the three `dist` builds at
`4a1f38a4e6`, whose `packages/cloud-connection/src` is byte-identical to
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 59s · declare it in the PR body · pnpm --workspace-concurrency=2 --filter '@objectstack/cloud-connection...' 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 118s (1m58s) · declare it in the PR body · pnpm turbo run build --filter='./packages/*' --filter='./packages/*/*' --concurrency=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 11s · declare it in the PR body · pnpm --filter @objectstack/cloud-connection exec vitest run --maxWorkers=2
os-verify-lock: VERDICT command-exit 2 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 2s · declare it in the PR body · pnpm exec tsc --noEmit -p tsconfig.json --listFiles
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 3s · declare it in the PR body · pnpm --filter @objectstack/cloud-connection 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 3s · declare it in the PR body · pnpm --filter @objectstack/cloud-connection 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 3s · declare it in the PR body · pnpm --filter @objectstack/cloud-connection build
```

- **Build:** `@objectstack/cloud-connection` with its closure (33 of 81
workspace projects), then the whole workspace, `turbo run build
--filter='./packages/*' --filter='./packages/*/*'`, 71 of 71 tasks,
after the merge. The tree was clean after both, and the package's six
`dist` files after the whole build equal the first head build by sha256.
- **Tests:** `vitest run`: 30 files, 397 tests passed (every `*.test.ts`
under `src/`), at this head and before the merge.
- **Typecheck:** `@objectstack/cloud-connection` has no `typecheck`
script; it is a `DEBT` entry in `scripts/check-type-check-coverage.mjs`
(13 errors: 11 TS2493, 2 config-tier). `tsc --noEmit -p tsconfig.json`
exits 2 with exactly those 13 (11 TS2493, 2 TS2550), all in three test
files this PR does not touch (`cloud-connection-plugin.test.ts` 4,
`connection-credential-store.test.ts` 7,
`marketplace-install-local-bundle.test.ts` 2). `--listFiles` compiles
all three touched files and all 30 test files. `check:type-check-debt`
and `check:type-check-coverage` exit 0, and the `dist` build's DTS step,
this package's type gate, succeeds.
- **Lint:** the repo-wide `pnpm lint` (`eslint . --no-inline-config`)
exits 0 at this head (2026-09-29T23:01:12Z to 23:01:39Z).
- **Citation judging:** after merging `origin/main` (`36d043be17`),
`node scripts/check-issue-citations.mjs --base origin/main` reports "no
issue citations added against 36d043b (1 file(s) read)" (exit 0);
pinned `--base 36d043b` reads the same.
- **Derived gates:** `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` derived 61 families. All 61 exit
0, and `--ran` with the exit-coded record reads "61 derived, 61 run, 0
NOT-MEASURED, 0 UNRUN" (a derived zero). Among them:
`check:issue-citations`, `check:doc-authoring`, `check:nul-bytes`,
`check:published-files`, `check:type-check-debt`,
`check-adr-0087-registration`, `check-empty-changeset`.
- **Artifact rosters:** 36 of the 39 non-self-test roster rows exit 0,
including the four the derivation marks as keeping their roster under
one of this diff's paths (`check-changeset-fixed`,
`check:authz-resolver`, `check:error-code-casing`,
`check:filter-alias-parity`). The other three need a pull request's
context; they are run against this PR once it exists and reported on the
card. The 18 self-test-only rows grade their checkers' fixtures and
cannot judge this diff.

## Hypotheses (measured first)

- **H0 holds.** At base `b291fcdae9` the filtered census answers 5 sites
on 5 lines, 3 numbers, all in `src/marketplace-install-local-plugin.ts`,
as on the seat's `0be898499f`. The whole-repo count is 1,153.
- **H1 holds.** After the rewrite, the filtered census answers 0 for
`packages/cloud-connection`. No site was left for an open PR (the file
lists of all 10 open PRs were read at 2026-09-29T22:41:18Z: only the
Version Packages PR objectstack-ai#20639 touches `packages/cloud-connection`, in
`CHANGELOG.md` and `package.json`) or for an unfound anchor.
- **H2 holds on the token reading, not on the `dist` reading.** The
parser leaf-token diff of all 3 touched files is empty with its controls
firing. The emitted `dist` differs, and the difference is comment text
only (token-identical `dist` with a code control). That is why the
changeset ships.

## Acceptance notes

- **Strings, the form-D stage.** 4 dead numbers remain in string
literals in `packages/cloud-connection/src`: `objectstack-ai#9011` in the three
`describe` titles of `marketplace-install-local-list-posture.test.ts`
(`:206`, `:247`, `:287`, no assertion text), and `objectstack-ai#9011` in the `note`
string of the `GET /api/v1/marketplace/install-local` row of
`cloud-connection-route-ledger.ts` (`:215`), a runtime string already
recorded in `scripts/doc-authoring-prose-id.baseline.json`. They stay on
the card for its form-D stage; no string moved here.
- **Outside `src/**`, a later stage of the card:**
`packages/cloud-connection/vitest.config.ts:64` cites `objectstack-ai#16917` (404).
The other citations in `packages/cloud-connection` outside `src/**`
(`CHANGELOG.md` excluded) answer 200: `README.md:108` (`objectstack-ai#10805`,
`objectstack-ai#12681`) and `vitest.config.ts` (`objectstack-ai#10374`, `objectstack-ai#11480`, `objectstack-ai#7668/objectstack-ai#7778`,
`objectstack-ai#7955`, `objectstack-ai#10374/objectstack-ai#13522`).
- **Card-word residue, cited nowhere.** A few docblocks still say "this
card's ruling" or "That ruling has since landed" a paragraph away from a
rewritten line
(`marketplace-install-local-capability-enumeration.test.ts:48`,
`marketplace-install-local-list-posture.test.ts:12`). They cite no
number, so they were left, as the landed stages left theirs.
- **The moving `origin/main`.** The branch merged `origin/main` once
(`16a88d69b2`, merging `36d043be17`: `service-automation` and two
changesets, nothing in `packages/cloud-connection` or its dependency
closure).

## Deviations

- **The first token-guard run was void.** It looped over the touched
files in a zsh shell, which does not word-split an unquoted variable, so
each invocation received all three paths as one argument and read
nothing; it was rerun under bash before any reading was used.
- **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 harness's attribution reminder asked for a model-named
trailer and a different PR footer, and AGENTS.md overrides it. 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>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…kages/plugins/plugin-hono-server/src to the commits that decided them (objectstack-ai#20741)

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

## What changed

This is stage 10 of the `domain:cli` lane of the dead-citation sweep:
`packages/plugins/plugin-hono-server/src`. Every comment site there 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 keeps saying in its own words
what that commit decided. PR objectstack-ai#20533 is the method, and stages 1 to 9 of
this card (PR objectstack-ai#20624, PR objectstack-ai#20632, PR objectstack-ai#20656, PR objectstack-ai#20673, PR objectstack-ai#20689, PR
objectstack-ai#20703, PR objectstack-ai#20713, PR objectstack-ai#20723, PR objectstack-ai#20735) are the precedents. The card
stays open for the lane's remaining packages, so this PR says `Part of`.

That is **24 sites on 24 lines in 5 files, covering 5 numbers**,
rewritten to **5 distinct commits**:
- the census's **5 sites**: `src/adapter.ts` 4,
`src/current-user-endpoints.ts` 1 (4 numbers);
- **19 test-file comment sites** in 3 test files (the census defers
`*.test.ts`; stages 1 to 9 took test comments too):
`ui-plugin-auto-discovery.pin.test.ts` 16,
`handler-throw-declared-envelope.test.ts` 2,
`current-user-endpoints-localization.test.ts` 1.

Only comments changed: **24 lines out, 24 in**, every one of them a site
(no companion line), and every touched file keeps its line count (1,660
/ 1,020 / 335 / 403 / 697), so no line citation into these files moves.
**No citation number is added**: over the 24 line pairs, the added
numbers are a subset of the removed ones (`objectstack-ai#16599` x2, `objectstack-ai#9864`,
`objectstack-ai#16334`, all answering 200, stay where they stood), and no PR number
stands on an added line. No ADR or ruling-record file in `docs/adr/` or
`scripts/adr-anchors/` records any of these 5 decisions (a grep for the
5 numbers there reads 0 hits; the control number `objectstack-ai#7329` reads 1 in the
same tree), so every anchor is a commit.

A **`patch` changeset** for `@objectstack/plugin-hono-server` rides
along, because one rewritten comment reaches `dist` (measured below).
That is stage 6's and stage 9's case (PR objectstack-ai#20703, PR objectstack-ai#20735), not stages
5 and 7's.

## Census: `packages/plugins/plugin-hono-server`, before and after

**Instrument.** The gate's own `node scripts/check-issue-citations.mjs
--census --json`, read-only and unchanged, run under `with-fleet.sh
--read` for the token. The count is its `allocated-but-absent` findings
under `packages/plugins/plugin-hono-server/`. Both runs enumerated the
whole board.

| reading | tree | board | whole-repo `allocated-but-absent` | package
sites | lines | numbers | files |
|---|---|---|---|---|---|---|---|
| before | base `f927864ea0`, run 2026-09-29T23:42:29Z to 23:46:56Z |
enumerated, 186 pages, frontier objectstack-ai#20735, 18,562 numbers | 1,105 | **5** |
5 | 4 | 2 |
| after | `91ce7e5e8f`, run 23:58:22Z to 2026-09-30T00:02:19Z |
enumerated, 186 pages, frontier objectstack-ai#20737, 18,564 numbers | 1,100 | **0** |
0 | 0 | 0 |

The whole-repo drop of 5 is exactly these sites: a site-by-site diff of
the two JSON outputs has 5 findings gone (`adapter.ts:225`, `:227`,
`:308`, `:349`; `current-user-endpoints.ts:448`) and none added. The
other three tallies (`resolves` 33,003, `resolves-as-pull-request`
1,984, `cross-repo-unjudged` 995) are equal in both runs.
`packages/plugins/plugin-hono-server` is byte-identical at `91ce7e5e8f`
and at the head.

**Supplementary scan (test files, strings and files outside `src/`
included).** The gate's exported `extractCitations` and
`classifyCitation` over all 41 tracked files of the package
(`CHANGELOG.md` excluded), comment-prose and whole-file projections,
with the board from the gate's own `probeBoard`: 347 citations and 32
dead before, 323 and 8 after. Under `src/`: comments 5 dead to 0, test
comments 19 to 0, strings 0 and test strings 2 unchanged. Its before
list of `src/` comment sites equals the census's. The 8 left are 2 test
strings and 6 sites outside `src/` (see Acceptance notes).

## Per-site table

`git blame` at the base ties each line to the commit that wrote it, and
each anchor was read in its message, changeset or diff, not only its
subject.

| number | sites (base line) | anchor: what it decided |
|---|---|---|
| `objectstack-ai#13279` | `adapter.ts:225`, `:227`;
`handler-throw-declared-envelope.test.ts:84`;
`current-user-endpoints-localization.test.ts:90` | `6a180e42d`:
`tryFind` in `resolveAuthzContext` raises `AuthzStoreUnavailableError`
(503 `SERVICE_UNAVAILABLE`) when a permission-store read is issued and
throws, instead of answering it as an empty read, and each fail-closed
transport `catch` (`requireDatasourceAdmin` in `service-datasource`
among them) re-raises it: an unreadable store licenses no verdict, the
maintainer's 2026-08-30 ruling recorded in its message. The first three
lines blame to `cefe068702` (the declared-envelope rendering, PR objectstack-ai#17412)
and the fourth to `5f7fa1de0`; both wrote them citing this ruling. PR
objectstack-ai#13475 (200) names objectstack-ai#13279. Stages 1, 2, 4 and 9 gave the number this
anchor. |
| `objectstack-ai#9934` | `adapter.ts:308`;
`handler-throw-declared-envelope.test.ts:152` | `79c46da90`: the
producer-side user-facing marking for hook refusals, the `userMessage`
channel, a text field a producer sets at throw time and every classified
envelope carries. Both lines blame to `cefe068702`. The PR that landed
it (PR objectstack-ai#9992) answers 404 too. Stages 1, 2, 4 and 6 gave the number this
anchor. |
| `objectstack-ai#6307` | `adapter.ts:349` | `293476148`: refuse a repeated
`?version=` on `GET`/`DELETE /packages/:id` rather than pick one value,
through `readSingleQueryValue`, which it introduces. The line blames to
`7cdbcbb306` (surface repeated query parameters as arrays, PR objectstack-ai#7396),
which says it follows that direction. Stages 2 and 8 gave the number
this anchor. |
| `objectstack-ai#6216` | `current-user-endpoints.ts:448` | `f586f1a89`: one
`ExecutionContext` assembler with two named anonymous entries,
`assembleExecutionContext` the default, fail-closed one and
`assembleExecutionContextOrGuest` the explicit guest one, the
maintainer's 2026-08-08 Option A recorded in its docblock. The line
blames to `6615a024c3` (the current-user faces adopt the shared
assembler). Stages 1, 2 and 7 gave the number this anchor. |
| `objectstack-ai#16721` | `ui-plugin-auto-discovery.pin.test.ts:27`, `:37`, `:42`,
`:180`, `:211`, `:217`, `:360`, `:363`, `:495`, `:498`, `:594`, `:596`,
`:604`, `:608`, `:631`, `:646` | `51ae73123`: `LiteKernel.use()` runs
the same `assertPluginContract` as `ObjectKernel.use()` and refuses the
same plugin objects with the same envelope, the maintainer's 2026-09-08
option A (the kernels converge) recorded in its changeset. 15 lines
blame to it; `:180` blames to `3c48234b3`, which re-wrapped that
sentence and keeps its fact. Its `lite-kernel.ts` docblock records the
measurement taken before converging, which `:604` describes ("before
commit 51ae731"). `:363`, `:498` and `:631` said the `hono-plugin.ts`
question was "noted on" the dead number; that note is the text this
commit wrote into this file, so they now say "raised with" / "recorded
with" it. New anchor; no other package has re-anchored this number yet.
|

**Anchor checks.** Every cited sha matches exactly one object (`git
rev-parse --disambiguate`, count 1 for each of the 5), is a commit, has
one parent, and is an ancestor of `main` (`merge-base --is-ancestor`
against `f927864ea0`, exit 0 for all 5). The checkout is not shallow.
The control leg `2672f855fa` (2026-08-09, the parent of the oldest
anchor `293476148`) exits 0 against `origin/main`, and the negative
control, this branch's own head, exits 1. Four anchors reuse the landed
stages' (`6a180e42d`, `79c46da90`, `293476148`, `f586f1a89`), so each
number carries one anchor across the tree; one is new (`51ae73123`).

**Numbers.** All 5 dropped numbers answer 404 by REST (probed
2026-09-30T00:14:17Z). The numbers kept on or beside the changed lines
answer 200: `objectstack-ai#16599`, `objectstack-ai#9864`, `objectstack-ai#16334`, `objectstack-ai#16363`, `objectstack-ai#16049`, `objectstack-ai#6878`,
`objectstack-ai#3867`, `objectstack-ai#8086`, `objectstack-ai#16545`, `objectstack-ai#15999`, `objectstack-ai#5090`.
`packages/plugins/plugin-hono-server/src` has no slash-joined `#A/#B`
without spaces; the spaced pairs (`objectstack-ai#3867 / objectstack-ai#8086`, `objectstack-ai#2408 / objectstack-ai#3361`) are
read by the grammar and every half answers 200.

## Mechanical guard: no code token moves

**H2 holds on the parser-token reading; the emitted `dist` is NOT
byte-identical, because one rewritten `//` comment sits inside a
returned object literal and the bundler keeps it.**

**Token guard.** It compares the TypeScript parser's leaf tokens
(TypeScript 6.0.3, `getChildren` walk, JSDoc nodes excluded) of the 5
touched files at base `f927864ea0` and at the head. Controls mutate the
head text in memory only.
- Real run: 15,054 base tokens (4,831 / 2,134 / 3,559 / 2,363 / 2,167),
0 differing, exit 0.
- Comment-insertion control: 0 differing, exit 0.
- Code-insertion control: all 5 files differ, exit 1.
- String control (the first character of the first import specifier
flipped in each file): exactly 1 differing `StringLiteral` per file,
exit 1.

All 48 changed lines (24 out, 24 in) are `//` or `*` comment lines.

**Emitted `dist`.** `pnpm --filter @objectstack/plugin-hono-server
build` at the head, then at base (the base blobs of the 5 touched files
restored in place under a trap-armed restore; an on-disk probe read
`objectstack-ai#9934` 1 and `commit 79c46da` 0 in `adapter.ts` before that build;
afterwards every touched blob equals its HEAD blob, `git diff HEAD` is
empty and the status is clean), with the same dependency builds:
- `index.js` and `index.mjs` differ, in one line each: the
`adapter.ts:308` comment, `refusal text (objectstack-ai#9934)` at base and `refusal
text (commit 79c46da)` at head. `index.d.ts`, `index.d.mts` and both
`.map` files are equal. No docblock of this diff reaches the declaration
files.
- The same parser comparison over the two differing files reads
identical tokens (10,818 in `index.js`, 10,521 in `index.mjs`), so the
whole `dist` delta is comment text. Its code control (a code line
appended) reads COUNT/TOKENS DIFFER in each.
- Code-mutation control (`scripts/ablation-replace.mjs`, wrap mode,
anchor `message: 'No response from handler' }` hit 1 to 0, planted
marker 0 to 1, blob `6a0c10f76282` to `d5223196afeb`;
`scripts/ablation-dist-preflight.mjs` found the marker in `index.js` and
`index.mjs`): `index.js`, `index.mjs` and both `.map` files differ from
the head build. The blob was restored to HEAD `6a0c10f76282` with `git
diff HEAD` empty, `dist` was rebuilt, its six sha256 values equal the
first head build, and the preflight in `--absent` mode reads the marker
absent from all 6 files with a clean tree.

A raw scan of the 6 changed files for ASCII control bytes finds none,
and `check:nul-bytes` exits 0.

## Changeset

**`patch` for `@objectstack/plugin-hono-server`**
(`.changeset/plugin-hono-server-provenance-anchors.md`), in PR objectstack-ai#20632's
form. The package's `files[]` is `dist`, `README.md` and `CHANGELOG.md`,
and the build above emits different `index.js` / `index.mjs` at base and
head, so this diff publishes. `check-changeset-no-major`,
`check-empty-changeset`, `check-adr-0087-registration` and
`check-changeset-fixed` all exit 0.

## Gates (head `03e5f4c0fd`)

This host has no `flock`, so `os-verify-lock.sh` ran in its declared
unlocked mode. Its official wording, verbatim (printed by every run; the
command line differs per run and is listed in the verdicts below):

> **Declared narrowing — verification ran UNLOCKED.**
`scripts/pm/os-verify-lock.sh`
> could not take the shared verify lock on this host: no usable `flock`.
The shared
> verify lock is declared Linux-only (`flock` is util-linux, and a stock
macOS does
> not ship it), so the command below was run directly, without the lock
—
> a declared narrowing, not a silent one. No serialization guarantee
held for this
> run, nor for any sibling agent in this container while it ran.

Its verdict line from each run (the closure build and the three `dist`
builds at `feaf0c9b73`, whose `packages/plugins/plugin-hono-server` is
byte-identical to this head; the first whole-workspace build, tests and
typecheck at `91ce7e5e8f`; the second whole-workspace build, tests and
typecheck at this head after the merge; the scratch-script paths
shortened to `SCRATCH`):

```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 27s · declare it in the PR body · pnpm --workspace-concurrency=2 --filter '@objectstack/plugin-hono-server...' 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 3s · declare it in the PR body · bash SCRATCH/base-build.sh SCRATCH
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 3s · declare it in the PR body · node scripts/ablation-replace.mjs --file packages/plugins/plugin-hono-server/src/adapter.ts --anchor "message: 'No response from handler' }" --replacement "message: 'No response from handler ABLMARK20594S10' }" -- bash SCRATCH/mut-inner.sh SCRATCH
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 3s · declare it in the PR body · pnpm --filter @objectstack/plugin-hono-server 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 12s · declare it in the PR body · pnpm --filter @objectstack/plugin-hono-server exec vitest run --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 17s · declare it in the PR body · pnpm --filter @objectstack/plugin-hono-server typecheck
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 118s (1m58s) · declare it in the PR body · pnpm exec turbo run build --filter='./packages/*' --filter='./packages/*/*' --concurrency=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 118s (1m58s) · declare it in the PR body · pnpm exec turbo run build --filter='./packages/*' --filter='./packages/*/*' --concurrency=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 11s · declare it in the PR body · pnpm --filter @objectstack/plugin-hono-server exec vitest run --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 16s · declare it in the PR body · pnpm --filter @objectstack/plugin-hono-server typecheck
```

- **Build:** `@objectstack/plugin-hono-server` with its closure (7 of 81
workspace projects), then the whole workspace, `turbo run build
--filter='./packages/*' --filter='./packages/*/*' --concurrency=2`, 71
of 71 tasks, before and again after the merge. The tree was clean after
each, and the package's six `dist` files after each whole build equal
the first head build by sha256.
- **Tests:** `vitest run --maxWorkers=2`: 27 files, 324 tests passed
(every `*.test.ts` under `src/`), at this head and at `91ce7e5e8f`.
- **Typecheck:** `pnpm --filter @objectstack/plugin-hono-server
typecheck` exits 0 at this head and at `91ce7e5e8f` (`tsc --noEmit`,
`tsc --noEmit -p tsconfig.typecheck.json`, and `check:test-typecheck` OK
with 0 files / 0 errors / 0 pinned signatures). `--listFiles`:
`tsconfig.json` and `tsconfig.test.json` each compile 33 `src/` files
including all 27 tests and all 5 touched files.
- **Spec artifacts:** `origin/main` brought a `packages/spec` change, so
`pnpm --filter @objectstack/spec check:generated` ran after the rebuild:
"All 15 generated artifacts are up to date" (exit 0).
- **Lint:** the repo-wide `pnpm lint` (`eslint . --no-inline-config`)
exits 0 at this head (2026-09-30T00:25:09Z to 00:25:36Z), and at
`91ce7e5e8f`.
- **Citation judging:** after merging `origin/main` (`fbec216e2d`),
`node scripts/check-issue-citations.mjs --base origin/main` reports "no
issue citations added against fbec216 (2 file(s) read)" (exit 0).
- **Derived gates:** `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` derived 63 families, the same
list at `91ce7e5e8f` and at this head. All 63 exit 0 at this head in one
pass, and `--ran` with the exit-coded record reads "63 derived, 63 run,
0 NOT-MEASURED, 0 UNRUN" (a derived zero). Among them:
`check:issue-citations`, `check:doc-authoring`, `check:nul-bytes`,
`check:published-files`, `check:dts-closure`,
`check:dual-build-cjs-loads`, `check:type-check-debt`,
`check-adr-0087-registration`, `check-empty-changeset`.
- **Artifact rosters:** 36 of the 39 non-self-test roster rows exit 0 at
this head, including the four the derivation marks as keeping their
roster under one of this diff's paths (`check-changeset-fixed`,
`check:authz-resolver`, `check:error-code-casing`,
`check:filter-alias-parity`). The other three need a pull request's
context; they are run against this PR once it exists and reported on the
card. The 18 self-test-only rows grade their checkers' fixtures and
cannot judge this diff.

## Hypotheses (measured first)

- **H0 holds.** At base `f927864ea0` the filtered census answers 5 sites
on 5 lines, 4 numbers, 2 files, as on the seat's `0be898499f`. The
whole-repo count is 1,105.
- **H1 holds.** After the rewrite, the filtered census answers 0 for
`packages/plugins/plugin-hono-server`. No site was left for an open PR
(the file lists of all open PRs were read at 2026-09-29T23:48:36Z, 6
PRs, and again at 2026-09-30T00:15:14Z, 9 PRs: only the Version Packages
PR objectstack-ai#20639 touches the package, in `CHANGELOG.md` and `package.json`) or
for an unfound anchor.
- **H2 holds on the token reading, not on the `dist` reading.** The
parser leaf-token diff of all 5 touched files is empty with its controls
firing. The emitted `dist` differs in one comment line of `index.js` and
of `index.mjs`, token-identical with a code control. That is why the
changeset ships.

## Acceptance notes

- **Strings, the form-D stage.** One dead number remains in a string
literal in `packages/plugins/plugin-hono-server/src`: `objectstack-ai#16721` at the
end of the group-F `describe` title of
`ui-plugin-auto-discovery.pin.test.ts` (`:635`, a test title, no
assertion text). It stays on the card for its form-D stage; no string
moved here. The supplementary scan's second test-string hit, `:111`
(`'.os-pin{color:#123456}'`), is a CSS hex colour in a fixture, not a
citation: the whole-file projection reads it as a six-digit number,
while the census blanks strings and defers test files.
- **Outside `src/**`, a later stage of the card:**
`objectstack.config.ts:19` (`objectstack-ai#11332`) and `:26` (`objectstack-ai#10724`),
`tsconfig.test.json:3` and `:61` (`objectstack-ai#13176`),
`tsconfig.typecheck.json:12` (`objectstack-ai#11332` and `objectstack-ai#10724`; `objectstack-ai#4914` in the same
group answers 200). The other citations in the package outside `src/**`
(`CHANGELOG.md` excluded) answer 200: `tsconfig.test.json` (`objectstack-ai#14062`,
`objectstack-ai#5286`, `objectstack-ai#5449`, `objectstack-ai#12542`), `tsconfig.typecheck.json` (`objectstack-ai#13284`,
`objectstack-ai#5475`, `objectstack-ai#10756`), `vitest.config.ts` (`objectstack-ai#10374`, `objectstack-ai#9457`, `objectstack-ai#7378`;
`objectstack-ai#8129` resolves as a pull request). `README.md` carries none.
- **An open question now lives only in this file.** `hono-plugin.ts:521`
and `:523` still carry the `&& plugin.staticPath` conjunct and the
`plugin.slug || plugin.name.split('/').pop()` derivation that, since
`51ae73123`, neither published kernel's `use()` lets an input reach. The
pin file says so and leaves the call to `hono-plugin.ts`; the tracker
note it pointed at is gone, so this file's text (and commit
`3c48234b3`'s message) are the record. Unreachable defensive code, not a
defect: noted, not filed.
- **Card-word residue, cited nowhere.**
`handler-throw-declared-envelope.test.ts` still says "before this card"
(`:85`) and "the card" (`:19`, `:32`) around its rewritten lines. They
cite no dead number, so they were left, as the landed stages left
theirs.
- **The moving `origin/main`.** The branch merged `origin/main` once
(`03e5f4c0fd`, merging `fbec216e2d`: the ADR-0087 migration chain moves
to `@objectstack/spec/migrations`). `packages/spec` is in this package's
dependency closure, so the workspace was rebuilt and the package's
tests, typecheck and every gate above were rerun at the merge head;
nothing in `packages/plugins/plugin-hono-server` changed.

## Deviations

- **Three derived gates first read NOT MEASURED.**
`check:dual-build-cjs-loads`, `check:lean-entry-closure` and
`check:type-check-debt` exited 3 (PREREQUISITE NOT MET: built output
absent) in the first pass, before the whole-workspace build. Rerun after
it, each exits 0, and all 63 exit 0 in the single pass at this head.
- **The first `check:generated` run was void.** This host's global
`pnpm` is a v11 front end that rejects the `-s` each sub-gate passes, so
all 15 rows read "unexpected argument '-s'" (exit 1, nothing measured).
Rerun with the real pnpm 10.31 binary first on `PATH`, it reads all 15
up to date (exit 0).
- **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 harness's attribution reminder asked for a model-named
trailer and a different PR footer, and AGENTS.md overrides it. 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>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…kages/create-objectstack/src to the commits that decided them (objectstack-ai#20748)

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

## What changed

This is stage 11 of the `domain:cli` lane of the dead-citation sweep:
`packages/create-objectstack/src`. Every comment site there 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 keeps saying in its own words what
that commit decided. PR objectstack-ai#20533 is the method, and stages 1 to 10 of this
card (PR objectstack-ai#20624, PR objectstack-ai#20632, PR objectstack-ai#20656, PR objectstack-ai#20673, PR objectstack-ai#20689, PR objectstack-ai#20703,
PR objectstack-ai#20713, PR objectstack-ai#20723, PR objectstack-ai#20735, PR objectstack-ai#20741) are the precedents. The card
stays open for the lane's remaining packages, so this PR says `Part of`.

That is **25 sites on 25 lines in 10 files, covering 9 numbers**,
rewritten to **8 distinct commits**:
- the census's **3 sites**: `src/banner.ts` 2, `src/index.ts` 1 (2
numbers);
- **22 test-file comment sites** in 8 test files (the census defers
`*.test.ts`; stages 1 to 10 took test comments too):
`starter-comments-self-contained.test.ts` 9,
`scaffold-e2e-boot-probe.test.ts` 3, `banner-version.test.ts` 2,
`blank-readme-validate-disclosure.test.ts` 2,
`scaffold-next-steps-pm.test.ts` 2, `template-consistency.test.ts` 2,
`scaffold-skills-single-copy.test.ts` 1, `template-ci-workflow.test.ts`
1.

Only comments changed: **25 lines out, 25 in**, every one of them a site
(no companion line), and every touched file keeps its line count (147 /
67 / 58 / 617 / 910 / 261 / 357 / 328 / 221 / 745), so no line citation
into these files moves. **No citation number is added**: the added lines
carry no tracker number at all, and no PR number stands on an added
line. No ADR or ruling-record file in `docs/adr/` or
`scripts/adr-anchors/` records any of these 9 decisions (a grep for the
9 numbers there reads 0 hits; the control number `objectstack-ai#7329` reads 2 in the
same tree), so every anchor is a commit.

**No changeset; `skip-changeset`.** The rewritten comments do not reach
the published `dist` (measured below), as in stages 5 and 7 (PR objectstack-ai#20689,
PR objectstack-ai#20713).

**Scaffold output is untouched.** No site sits inside a template literal
or in a file the scaffolder copies: `src/templates/**` carries zero
tracker citations in either projection, and all 25 sites are `//` or
JSDoc comment prose outside any string. A real scaffold run at base and
at head emits a byte-identical project (below).

## Census: `packages/create-objectstack`, before and after

**Instrument.** The gate's own `node scripts/check-issue-citations.mjs
--census --json`, read-only and unchanged, run under `with-fleet.sh
--read` for the token. The count is its `allocated-but-absent` findings
under `packages/create-objectstack/`. Both runs enumerated the whole
board.

| reading | tree | board | whole-repo `allocated-but-absent` | package
sites | lines | numbers | files |
|---|---|---|---|---|---|---|---|
| before | base `01e78dceef`, run 2026-09-30T01:14:08Z to 01:19:48Z |
enumerated, 186 pages, frontier objectstack-ai#20742, 18,569 numbers | 1,077 | **3** |
3 | 2 | 2 |
| after | `4ed638093d`, run 01:30:22Z to 01:35:31Z | enumerated, 186
pages, frontier objectstack-ai#20745, 18,572 numbers | 1,074 | **0** | 0 | 0 | 0 |

The whole-repo drop of 3 is exactly these sites: a site-by-site diff of
the two JSON outputs has 3 findings gone (`banner.ts:10`,
`banner.ts:17`, `index.ts:441`) and none added. The other three tallies
(`resolves` 33,014, `resolves-as-pull-request` 1,984,
`cross-repo-unjudged` 995) are equal in both runs.
`packages/create-objectstack` is byte-identical at `4ed638093d` and at
the head (the one merge brought no file under it).

**Supplementary scan (test files, strings and files outside `src/`
included).** The gate's exported `extractCitations` and
`classifyCitation` over all 53 tracked files of the package
(`CHANGELOG.md` excluded), comment-prose and whole-file projections,
with the board from the gate's own `probeBoard`: 77 citations and 33
dead before, 52 and 8 after. Under `src/`: comments 3 dead to 0, test
comments 23 to 1, test strings 7 unchanged; `src/templates/**` 0
citations of any kind. Outside `src/`, one citation
(`vitest.config.ts:24`, `objectstack-ai#10374`) answers 200. Its before list of `src/`
comment sites equals the census's. The 8 left are 7 test strings and 1
test comment with no deciding commit (see "The site left" and Acceptance
notes).

## Per-number table

`git blame` at the base ties each line to the commit that wrote it, and
each anchor was read in its message, changeset or diff, not only its
subject.

| number | sites (base line) | anchor: what it decided |
|---|---|---|
| `objectstack-ai#10325` | `banner.ts:10`; `banner-version.test.ts:3` | `cec9d239d`:
the startup banner reads the real version from `package.json` through
the new `renderVersionBanner()`, and sizes the box from the version's
plain length, widening and never truncating, instead of the hardcoded
`v6.x`. Both lines blame to it; its message carries the closing trailer
for this number. New anchor. |
| `objectstack-ai#10322` | `banner.ts:17`; `index.ts:441`;
`banner-version.test.ts:17`;
`blank-readme-validate-disclosure.test.ts:3`, `:50`;
`scaffold-next-steps-pm.test.ts:3`, `:7` | `8d21f7a76`: detect the
package manager once, up front, and name it in the install line, the
install-failure remedy and every "Next steps" line (labels padded to the
longer of the two instead of hand-kerned for `npm`), and name `validate`
in the blank README's "Getting started". Its message carries the closing
trailer for this number. `index.ts:441` and the two test headers blame
to it; `banner.ts:17` and `banner-version.test.ts:17` blame to
`cec9d239d`, whose message calls this "the sibling bug fixed one
function away in the same file"; `scaffold-next-steps-pm.test.ts:7`
blames to `c6c7feccd`, a re-wrap that keeps the sentence. New anchor. |
| `objectstack-ai#19424` | `scaffold-e2e-boot-probe.test.ts:397`, `:679`, `:816` |
`c27e16059`: the boot-probe neighbour announces its own listener (or its
bind error), asks the kernel for its port with `listen(0)`, and the
harness names five distinct outcomes instead of one "never came up"; the
controls block pins each. All three lines blame to it; its message
carries the closing trailer for this number. New anchor. |
| `objectstack-ai#16331` | `scaffold-skills-single-copy.test.ts:3` | `fd75728bc`:
install the skills bundle for one agent (`--skill '*' --agent
claude-code -y`) so a scaffolded project's first commit stages it once,
with no symlinks. The line blames to it, and its diff is what added the
number; its message names none. New anchor. |
| `objectstack-ai#10990` | `starter-comments-self-contained.test.ts:41`, `:283` |
`21756b325`: converge the shipped template files on the ruled canonical
docs origin and pin that convergence as assertion 4 over
`shippedFiles()`. Both lines blame to it; its message carries the
closing trailer for this number. New anchor for this number. |
| `objectstack-ai#11022` | `starter-comments-self-contained.test.ts:50`, `:91`,
`:122`, `:221` | `21756b325`: rewrite the blank README's two
monorepo-only references, add the fifth `MONOREPO_ONLY` pattern (the
framework's own name next to a "repo" word), retire the self-retiring
`EXCLUDED` entry and add the README's two RATIONALE facts. All four
lines blame to it. Stage 3 (PR objectstack-ai#20656) gave this number the same anchor.
|
| `objectstack-ai#15150` | `starter-comments-self-contained.test.ts:72`, `:133`,
`:141` | `cc986c913`: the sixth `MONOREPO_ONLY` pattern, for a reference
written as a relative path that climbs out of the project, anchored on
bare `../` rather than on a depth judgement. All three lines blame to
it; its diff is what added the number (8 times, across both scaffolders'
pins), its message names none. New anchor. |
| `objectstack-ai#16330` | `template-ci-workflow.test.ts:3`;
`template-consistency.test.ts:376` | `4998efa71`: ship
`.github/workflows/ci.yml` in the blank template (the template's first
dot-directory) so a scaffolded project has gates from its first push.
Both lines blame to it; its diff added the number, its message names
none. New anchor. |
| `objectstack-ai#10326` | `template-consistency.test.ts:498` | `675ab574e`: declare
the two benign peer skews a clean first install reported as scoped pnpm
`allowedVersions` inside the scaffold. The line blames to it. Stage 3
(PR objectstack-ai#20656) gave this number the same anchor. |

**Anchor checks.** Every cited sha matches exactly one object (`git
rev-parse --disambiguate`, count 1 for each of the 8), is a commit, has
one parent, and is an ancestor of `main` (`merge-base --is-ancestor`
against `01e78dceef`, exit 0 for all 8). The checkout is not shallow.
The control leg `2aca1bc4c0` (the parent of the oldest anchor
`675ab574e`, 2026-08-20) exits 0 against the base, and the negative
control (the base as an ancestor of `675ab574e`) exits 1. Two anchors
reuse the landed stages' (`21756b325`, `675ab574e`); six are new.

**Numbers.** All 9 dropped numbers answer 404 by REST (probed
2026-09-30T01:11:14Z and again at 01:50:21Z). The one number kept on a
line beside the changed ones, `objectstack-ai#9779`
(`scaffold-e2e-boot-probe.test.ts:673`), answers 200. The anchor
commits' own PR numbers are not cited: three of them (objectstack-ai#11030, objectstack-ai#11013,
objectstack-ai#11191) answer 404 as well, which is the reason the ruling cites
commits.

## The site left

**No deciding commit (1 site, a test comment, so not in the census):**
`template-consistency.test.ts:153` (`objectstack-ai#11048`): "admitting them is a
support decision (objectstack-ai#11048), not a value to drift here". The number names
an open support decision (whether to admit pnpm 10.0 to 10.4). The only
commit naming it, `568de194e`, files it unassigned; no later commit
decides it, and the floor is still pnpm 10.15 or later at the base.
Stage 3 (PR objectstack-ai#20656) left the sibling site
`packages/cli/src/commands/init.ts:267` for the same reason.

## Mechanical guard: no code token moves, and nothing emitted moves

**H2 holds on both readings: the parser-token diff is empty, and the
emitted `dist` and the scaffolded project are byte-identical.**

**Token guard.** It compares the TypeScript parser's leaf tokens
(TypeScript 6.0.3, `getChildren` walk, JSDoc nodes excluded) of the 10
touched files at base `01e78dceef` and at `4ed638093d`. Controls mutate
the head text in memory only.
- Real run: 16,198 base tokens, 0 files differing, exit 0.
- Comment-insertion control: 0 differing, exit 0.
- Code-insertion control: all 10 files differ, exit 1.
- String control (the first character of the first import specifier
flipped in each file): all 10 files differ, first differing kind
`StringLiteral`, exit 1.

All 50 changed lines (25 out, 25 in) are `//` or `*` comment lines.

**Emitted `dist`.** `pnpm --filter create-objectstack build` at base
(before any edit) and at `4ed638093d`, after the same dependency build.
All 24 `dist` files (`index.js`, `chunk-ZIUW7UEA.js`,
`created-summary.js`, `created-summary.d.ts` and the 20 copied template
files) have equal sha256 at base and head, and `diff -r` is empty. None
of the dead numbers appears in the base `dist` at all: tsup drops these
comments.
- Code-mutation control (`scripts/ablation-replace.mjs`, wrap mode,
anchor `Dependency installation failed.` hit 1 to 0, planted marker 0 to
1, blob `b68538942c96` to `860de8778f10`;
`scripts/ablation-dist-preflight.mjs` found the marker in
`dist/index.js`): `index.js` differs from the head build. The blob was
restored to HEAD `b68538942c96` with `git diff HEAD` empty, `dist` was
rebuilt, the preflight in `--absent` mode reads the marker absent from
all 24 files with a clean tree, and the 24 sha256 values equal the first
head build.
- The whole-workspace builds (below) left `create-objectstack`'s `dist`
equal to the same 24 values.

**Scaffold output.** `node
packages/create-objectstack/bin/create-objectstack.js demo-app
--skip-install --skip-skills`, run in an empty directory from the base
build and again from the head build: both emit the same 21 files with
equal sha256, `diff -r` is empty, and the printed output differs only in
the absolute target directory line.

A raw scan of the 10 changed files for ASCII control bytes finds none (a
positive probe on a scratch file with one such byte reads 1), and
`check:nul-bytes` exits 0.

## Changeset

**None; `skip-changeset`.** The package's `files[]` is `dist`,
`README.md` and `CHANGELOG.md`; the build above emits a byte-identical
`dist` at base and head, and the code-mutation control proves that build
does move when code moves. The two other shipped files are untouched, so
this diff publishes nothing.

## Gates (head `a84b73af13`)

This host has no `flock`, so `os-verify-lock.sh` ran in its declared
unlocked mode. Its official wording, verbatim (printed by every run; the
command line differs per run and is listed in the verdicts below):

> **Declared narrowing — verification ran UNLOCKED.**
`scripts/pm/os-verify-lock.sh`
> could not take the shared verify lock on this host: no usable `flock`.
The shared
> verify lock is declared Linux-only (`flock` is util-linux, and a stock
macOS does
> not ship it), so the command below was run directly, without the lock
—
> a declared narrowing, not a silent one. No serialization guarantee
held for this
> run, nor for any sibling agent in this container while it ran.

Its verdict line from each run (the closure build and the base build at
`01e78dceef`; the head build, the first whole-workspace build, the
tests, the boot-probe file and the typecheck at `4ed638093d`; the second
whole-workspace build, tests and typecheck at this head after the
merge):

```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 22s · declare it in the PR body · pnpm --workspace-concurrency=2 --filter 'create-objectstack^...' 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 2s · declare it in the PR body · pnpm --filter create-objectstack 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 1s · declare it in the PR body · pnpm --filter create-objectstack 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 134s (2m14s) · declare it in the PR body · pnpm exec turbo run build --filter=./packages/* --filter=./packages/*/* --concurrency=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 create-objectstack exec vitest run --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 2s · declare it in the PR body · pnpm --filter create-objectstack exec vitest run --maxWorkers=2 src/scaffold-e2e-boot-probe.test.ts
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 2s · declare it in the PR body · pnpm --filter create-objectstack typecheck
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 35s · declare it in the PR body · pnpm exec turbo run build --filter=./packages/* --filter=./packages/*/* --concurrency=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 7s · declare it in the PR body · pnpm --filter create-objectstack exec vitest run --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 2s · declare it in the PR body · pnpm --filter create-objectstack typecheck
```

- **Build:** `create-objectstack`'s dependency closure
(`@objectstack/spec`, its only workspace dependency), then the package,
then the whole workspace, `turbo run build --filter=./packages/*
--filter=./packages/*/* --concurrency=2`, 71 of 71 tasks, before and
again after the merge. The tree was clean after each.
- **Tests:** `vitest run --maxWorkers=2`: 16 files, 247 tests: 233
passed and 14 skipped, at this head and at `4ed638093d`. The 14 skipped
are the whole of `scaffold-e2e-boot-probe.test.ts` (run alone: 1 file
skipped, 14 tests skipped), which its own `RUNNABLE` gate
(`process.platform === 'linux'`, plus `bash`, `curl`, `openssl`) skips
on this macOS host. **NOT MEASURED locally:
`scaffold-e2e-boot-probe.test.ts`, reason: Linux-only by its own gate;
CI runs it.** Its diff is 3 comment lines with identical parser tokens.
- **Typecheck:** `pnpm --filter create-objectstack typecheck` (`tsc
--noEmit`) exits 0 at this head and at `4ed638093d`. `--listFiles`
reaches 26 `src/` files outside `src/templates/`, including all 16 tests
and all 10 touched files.
- **Spec artifacts:** not run. Neither `origin/main`'s one incoming
commit nor this diff touches `packages/spec`.
- **Lint:** the repo-wide `pnpm lint` (`eslint . --no-inline-config`)
exits 0 at this head (2026-09-30T02:00:09Z to 02:00:43Z), and at
`4ed638093d` (01:49:31Z to 01:50:04Z).
- **Citation judging:** after merging `origin/main` (`697845d19f`),
`node scripts/check-issue-citations.mjs --base origin/main` reports "no
issue citations added against 697845d (2 file(s) read)" (exit 0).
- **Derived gates:** `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` derived 52 families, the same
list at `4ed638093d` and at this head. All 52 exit 0 at this head in one
pass, and `--ran` with the exit-coded record reads "52 derived, 52 run,
0 NOT-MEASURED, 0 UNRUN" (a derived zero). Among them:
`check:issue-citations`, `check:doc-authoring`, `check:nul-bytes`,
`check:published-files`, `check:cross-package-test-inputs`,
`check:dts-closure`, `check:dual-build-cjs-loads`,
`check:type-check-debt`, `check-changeset-no-major`.
- **Artifact rosters:** 36 of the 39 non-self-test roster rows exit 0 at
this head, among them `check:scaffold-emission-policy` and the three the
derivation marks as keeping their roster under one of this diff's paths
(`check:authz-resolver`, `check:error-code-casing`,
`check:filter-alias-parity`). The other three need a pull request's
context; they are run against this PR once it exists and reported on the
card. The 18 self-test-only rows grade their checkers' fixtures and
cannot judge this diff.

## Hypotheses (measured first)

- **H0 holds.** At base `01e78dceef` the filtered census answers 3 sites
on 3 lines, 2 numbers, 2 files, as on the seat's `0be898499f`. The
whole-repo count is 1,077.
- **H1 holds.** After the rewrite, the filtered census answers 0 for
`packages/create-objectstack`. No census site was left for an open PR
(the file lists of all open PRs were read at 2026-09-30T01:21:44Z and
again at 01:52:53Z, 8 PRs each time: only the Version Packages PR objectstack-ai#20639
touches the package, in `CHANGELOG.md` and `package.json`) or for an
unfound anchor. The one site left for an unfound anchor is a test
comment, outside the census.
- **H2 holds.** The parser leaf-token diff of all 10 touched files is
empty with its controls firing, and, independently, the emitted `dist`
and the scaffolded project are byte-identical at base and head, with a
code-mutation control that changes `dist`.

## Acceptance notes

- **Strings, the form-D stage.** Seven dead numbers remain in string
literals, all test titles in `src/`: `banner-version.test.ts:66` and
`:96` (`objectstack-ai#10325`), `blank-readme-validate-disclosure.test.ts:25`
(`objectstack-ai#10322`), `scaffold-e2e-boot-probe.test.ts:829` (`objectstack-ai#19424`),
`scaffold-next-steps-pm.test.ts:173` and `:197` (`objectstack-ai#10322`),
`template-consistency.test.ts:503` (`objectstack-ai#10326`). They stay on the card for
its form-D stage; no string moved here. None is an assertion text or
scaffold output.
- **Outside `src/**`:** nothing dead. The one citation there,
`vitest.config.ts:24` (`objectstack-ai#10374`), answers 200; `README.md` and `bin/`
carry none.
- **Live but misdirected numbers, a different class.** Two numbers in
this package answer 200, but as unrelated pull requests. `objectstack-ai#4902`
(`index.ts:165`, `:239`; `rewrite-identity.ts:36`;
`runtime-image.ts:140`; `rewrite-identity.test.ts:3`, and the test title
at `:123`) was written by `8d41998b0`, whose own message names `objectstack-ai#4926`
(the remote-template object-name rewrite being silently skipped), and
`f2f09e4e3` repeated it at `runtime-image.ts:140`; `objectstack-ai#4902` itself is an
unrelated `init-service` guard PR. `objectstack-ai#3120` (`template-copy.ts:20`;
`template-consistency.test.ts:259`) was written by `3b6ef8a32` (the
scaffolded `.gitignore`), and `objectstack-ai#3120` is an unrelated approvals-docs PR.
The census reads both as `resolves-as-pull-request`, a reading and not a
finding, and this card is about 404s, so neither moved here. Noted, not
filed.
- **Card-word residue, cited nowhere.** Some rewritten test headers
still say "the card" or "per triage" nearby
(`banner-version.test.ts:13`,
`blank-readme-validate-disclosure.test.ts:3`). They cite no dead number,
so they were left, as the landed stages left theirs.
- **The moving `origin/main`.** The branch merged `origin/main` once
(`a84b73af13`, merging `697845d19f`: PR objectstack-ai#20742, the `service-package`
citation re-anchoring). Nothing under `packages/create-objectstack` or
`packages/spec` changed, so the package's tests, typecheck, every
derived gate, the roster rows and lint were rerun at the merge head and
all read as before.

## Deviations

- **Three derived gates first read NOT MEASURED.**
`check:dual-build-cjs-loads`, `check:lean-entry-closure` and
`check:type-check-debt` exited 3 (PREREQUISITE NOT MET: built output
absent) in the first pass, before the whole-workspace build. Rerun after
it, each exits 0, and all 52 exit 0 in the single pass at this head.
- **The first code-mutation attempt was void.** Its replacement text
contained the anchor, so the anchor count could not fall;
`ablation-replace.mjs` refused it (anchor 1 to 1, exit 1) and restored
the blob to HEAD before anything was built. The second attempt, with a
replacement that does not contain the anchor, is the one reported above.
- **The two builds inside the code-mutation control** (the mutate leg
and the restore leg) ran directly, not through `os-verify-lock.sh`. On
this host that wrapper runs unlocked anyway, so nothing was serialized
either way.
- **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 harness's attribution reminder asked for a model-named
trailer and a different PR footer, and AGENTS.md overrides it. 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>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…ugins/plugin-dev/src to the commits that decided them (objectstack-ai#20767)

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

## What changed

This is stage 12 of the `domain:cli` lane of the dead-citation sweep:
`packages/plugins/plugin-dev/src`. Every comment site there 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 keeps saying in its own words what
that commit decided. PR objectstack-ai#20533 is the method, and stages 1 to 11 of this
card (PR objectstack-ai#20624, PR objectstack-ai#20632, PR objectstack-ai#20656, PR objectstack-ai#20673, PR objectstack-ai#20689, PR objectstack-ai#20703,
PR objectstack-ai#20713, PR objectstack-ai#20723, PR objectstack-ai#20735, PR objectstack-ai#20741, PR objectstack-ai#20748) are the
precedents. The card stays open for the lane's remaining packages, so
this PR says `Part of`.

That is **6 sites on 6 lines in 3 files, covering 2 numbers**, rewritten
to **2 distinct commits**:
- the census's **3 sites**, all in `src/dev-plugin.ts` (`:1063`,
`:1078`, `:1097`);
- **3 test-file comment sites** (the census defers `*.test.ts`; stages 1
to 11 took test comments too): `dev-plugin.test.ts:90` and `:127`,
`dev-plugin-security-enforcement-warning.test.ts:53`.

Only comments changed: **6 lines out, 6 in**, every one of them a site
(no companion line), and every touched file keeps its line count (1159 /
317 / 199), so no line citation into these files moves. **No citation
number is added**: the only tracker number on an added line is `objectstack-ai#3900`
at `dev-plugin.ts:1063`, which the removed line already carried and
which answers 200; no PR number stands on an added line. No ADR or
ruling-record file in `docs/adr/` or `scripts/adr-anchors/` records
either decision (a grep there for the 2 numbers, their PR number objectstack-ai#10092
and the 2 shas reads 0 hits; the control number `7329` reads 1 file in
the same tree), so both anchors are commits. ADR-0115 records the older
decision the warning comes from (an empty security slot gets one loud
boot-log line), not the move these lines describe.

**A `patch` changeset** for `@objectstack/plugin-dev` rides along
(`.changeset/plugin-dev-provenance-anchors.md`, in PR objectstack-ai#20632's form),
because the two rewritten docblock lines reach the published `dist`
(measured below), as stage 6 (PR objectstack-ai#20703) measured for its package.

## Census: `packages/plugins/plugin-dev`, before and after

**Instrument.** The gate's own `node scripts/check-issue-citations.mjs
--census --json`, read-only and unchanged, run under `with-fleet.sh
--read` for the token. The count is its `allocated-but-absent` findings
under `packages/plugins/plugin-dev/`. Both runs enumerated the whole
board.

| reading | tree | board | whole-repo `allocated-but-absent` | package
sites | lines | numbers | files |
|---|---|---|---|---|---|---|---|
| before | base `33e4a5609c`, run 2026-09-30T02:45:30Z to 02:51:51Z |
enumerated, 186 pages, frontier objectstack-ai#20757, 18,584 numbers | 1,061 | **3** |
3 | 2 | 1 |
| after | head `a237b10ee7`, run 03:07:39Z to 03:14:14Z | enumerated,
186 pages, frontier objectstack-ai#20765, 18,592 numbers | 1,058 | **0** | 0 | 0 | 0 |

The whole-repo drop of 3 is exactly these sites: a site-by-site diff of
the two JSON outputs has 3 findings gone (`dev-plugin.ts:1063`, `:1078`,
`:1097`) and none added. The other three tallies (`resolves` 33,038,
`resolves-as-pull-request` 1,984, `cross-repo-unjudged` 995) are equal
in both runs.

**Supplementary scan (test files, strings and files outside `src/`
included).** Every `#N` token (two to six digits) in the package's 19
tracked files, `CHANGELOG.md` excluded, was probed by REST: 39 distinct
numbers at base, of which 2 answer 404 in `src/` (`objectstack-ai#10035`, `objectstack-ai#10036`)
and 1 outside it (`objectstack-ai#13176`, in `tsconfig.test.json`); `objectstack-ai#1020` is
`cloud#1020`, cross-repo. Dead occurrences at base: 6 in `src/` comments
(3 source, 3 test), 1 in a test string, 2 in `tsconfig.test.json`.
After: 0 in comments, the test string and the two `tsconfig.test.json`
lines unchanged (see Acceptance notes). A grep for the two numbers with
no word-boundary operator, beside a control of the same shape (`objectstack-ai#3900`
reads 6 lines of `dev-plugin.ts`), finds only those three lines left.

## Per-number table

`git blame` at the base ties every one of the 6 lines to `7552e0337`,
the commit that wrote them, and each anchor was read in its message and
its diff, not only its subject.

| number | sites (base line) | anchor: what it decided |
|---|---|---|
| `objectstack-ai#10036` | `dev-plugin.ts:1063`, `:1078`; `dev-plugin.test.ts:90`,
`:127`; `dev-plugin-security-enforcement-warning.test.ts:53` |
`7552e0337`: the "RBAC/RLS/masking are NOT enforced" warning stops
probing the three `SecurityPlugin.init()` internals
(`security.permissions`, `security.rls`, `security.fieldMasker`, which
the spec contract names implementation internals) and asks the published
`security` service instead, and asks it from `DevPlugin.start()`, after
the child-start loop and beside the boot banner, since asking from
`init()` would find it absent on every stack; the internal handles keep
one use, telling "never loaded" apart from "loaded, then failed to
start". Both halves of its squash message carry this number. Its own PR
number (objectstack-ai#10092) answers 404 as well. |
| `objectstack-ai#10035` | `dev-plugin.ts:1097` | `c1731d023`: `plugin-hono-server`'s
`/auth/me/permissions` and `/me/apps` delegate permission-set resolution
to the `security` service, and their degraded branches key on the
published `security` service instead of `security.permissions` (its
docblock "What absent now means, precisely"). The site's sentence says
the same presence signal misled that endpoint and was cured "by this
same move"; `objectstack-ai#10035` is that commit's own PR number, carried in its
subject. |

**How the lines read now.** `:1063` keeps `objectstack-ai#3900` and says `commit
7552e03 moved this check here from init()`; the `:1078` heading and
the test-comment brackets name `commit 7552e03` where the number
stood, with the decision spelled out in the surrounding prose they
already carried; `:127` reads `(the two told apart since commit
7552e03)`; `:1097` reads `commit c1731d0 by this same move`.

**Anchor checks.** Both cited shas match exactly one object (`git
rev-parse --disambiguate`, count 1 each), are commits, have one parent,
and are ancestors of `main` (`merge-base --is-ancestor` against
`33e4a5609c`, exit 0 for both). The checkout is not shallow. Control
legs: `44738f7af6` (the parent of `c1731d023`) exits 0 against the base;
the negative control (the base as an ancestor of `7552e0337`) exits 1.

**Numbers.** `objectstack-ai#10035`, `objectstack-ai#10036` and `objectstack-ai#10092` answer 404 by REST (probed
2026-09-30T02:43:04Z and again at 03:14:40Z). `objectstack-ai#3900`, kept on `:1063`,
answers 200.

## Mechanical guard: no code token moves

**H2 holds on the token reading; the emitted `dist` is NOT
byte-identical, and the difference is exactly the two docblock lines.**

**Token guard.** It compares the TypeScript parser's leaf tokens
(TypeScript 6.0.3, `getChildren` walk, JSDoc nodes excluded) of the 3
touched files at base `33e4a5609c` and at `37eaf1647f` (the comment
commit). Controls mutate the head text in memory only.
- Real run: 6,653 base tokens, 0 files differing.
- Comment-insertion control: 0 differing.
- Code-insertion control: all 3 files differ.
- String control (the first character of the first import specifier
flipped in each file): all 3 files differ, first differing kind
`StringLiteral`.
- The script's own verdict: exit 0 (real 0 and every control as
expected).

All 12 changed lines in `src/` (6 out, 6 in) are `//` or `*` comment
lines.

**Emitted `dist`.** `pnpm --filter @objectstack/plugin-dev build` at
base (before any edit, after its dependency closure) and at
`37eaf1647f`. Of the 6 `dist` files, `index.js.map` and `index.mjs.map`
have equal sha256; `index.js`, `index.mjs`, `index.d.ts` and
`index.d.mts` differ, and `diff -r` shows exactly two changed lines in
each: the `:1078` heading and the `:1097` line of the
`warnIfNothingIsEnforcingSecurity` docblock. The `//` comment at `:1063`
does not ship. So the published tarball carried both dead numbers, and
now carries the commits.
- Code-mutation control (`scripts/ablation-replace.mjs`, wrap mode,
anchor `ctx.logger.info(' Discovery: /.well-known/objectstack');` hit 1
to 0, planted marker 0 to 1, blob `708af69f9b2a` to `b0b387f53d6a`;
`scripts/ablation-dist-preflight.mjs` found the marker in
`dist/index.js` and `dist/index.mjs`): `index.js`, `index.mjs` and both
source maps differ from the head build. The blob was restored to HEAD
`708af69f9b2a` with `git diff HEAD` empty, `dist` was rebuilt, the
preflight in `--absent` mode reads the marker absent from all 6 files
with a clean tree, and the 6 sha256 values equal the head build.
- The whole-workspace build (below) left `plugin-dev`'s `dist` equal to
the same 6 values.

A raw scan of the 4 changed files for ASCII control bytes finds none (a
positive probe on a scratch file with one such byte reads 1), and
`check:nul-bytes` exits 0.

## Changeset

**`patch` for `@objectstack/plugin-dev`.** The package publishes
(`files` is `dist`, `README.md`, `CHANGELOG.md`), and the measurement
above shows the rewritten docblock reaching four `dist` files. The
changeset states comments only, with no behaviour change.
`check-empty-changeset`, `check-changeset-no-major`,
`check-adr-0087-registration` (1 non-breaking changeset seen) and
`check-changeset-fixed` all exit 0.

## Gates (head `a237b10ee7`)

This host has no `flock`, so `os-verify-lock.sh` ran in its declared
unlocked mode. Its official wording, verbatim (printed by every run; the
command line differs per run and is listed in the verdicts below):

> **Declared narrowing — verification ran UNLOCKED.**
`scripts/pm/os-verify-lock.sh`
> could not take the shared verify lock on this host: no usable `flock`.
The shared
> verify lock is declared Linux-only (`flock` is util-linux, and a stock
macOS does
> not ship it), so the command below was run directly, without the lock
—
> a declared narrowing, not a silent one. No serialization guarantee
held for this
> run, nor for any sibling agent in this container while it ran.

Its verdict line from each run (the closure build at base `33e4a5609c`;
the head build at `37eaf1647f`; the whole-workspace build, the tests and
the typecheck 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 65s (1m05s) · declare it in the PR body · pnpm --workspace-concurrency=2 --filter '@objectstack/plugin-dev...' 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 3s · declare it in the PR body · pnpm --filter @objectstack/plugin-dev 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 97s (1m37s) · declare it in the PR body · pnpm exec turbo run build --filter='./packages/*' --filter='./packages/*/*' --concurrency=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 7s · declare it in the PR body · pnpm --filter @objectstack/plugin-dev exec vitest run --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/plugin-dev typecheck
```

- **Build:** `plugin-dev` with its dependency closure (36 packages, the
filter spelled with the package included), then the package, then the
whole workspace, `turbo run build --filter=./packages/*
--filter=./packages/*/* --concurrency=2`, 71 of 71 tasks. The tree was
clean after each.
- **Tests:** `vitest run --maxWorkers=2`: 9 files, 86 tests, all passed.
- **Typecheck:** `pnpm --filter @objectstack/plugin-dev typecheck` (`tsc
--noEmit`, then `check:test-typecheck` over `tsconfig.test.json`) exits
0. `--listFiles` under both configs reaches all 12 `src/` files,
including the 9 tests and the 3 touched files.
- **Spec artifacts:** not run. `origin/main` did not move while this
branch was open (still `33e4a5609c`; the merge was a no-op), and this
diff does not touch `packages/spec`.
- **Lint:** the repo-wide `pnpm lint` (`eslint . --no-inline-config`)
exits 0 at this head (2026-09-30T03:07:02Z to 03:07:32Z).
- **Citation judging:** after merging `origin/main` (already up to date
at `33e4a5609c`), `node scripts/check-issue-citations.mjs --base
origin/main` judges 1 added citation (`objectstack-ai#3900`), which resolves (exit 0).
- **Derived gates:** `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` derived 62 families from the 4
changed paths. All 62 exit 0 in one pass at this head, and `--ran` with
the exit-coded record reads "62 derived, 62 run, 0 NOT-MEASURED, 0
UNRUN" (a derived zero). Among them: `check:issue-citations`,
`check:doc-authoring`, `check:nul-bytes`, `check:published-files`,
`check:cross-package-test-inputs`, `check:dts-closure`,
`check:dual-build-cjs-loads`, `check:type-check-debt`,
`check-empty-changeset`, `check-adr-0087-registration`.
- **Artifact rosters:** 36 of the 39 non-self-test roster rows exit 0 at
this head, among them `check-changeset-fixed` and the three others the
derivation marks as keeping their roster under one of this diff's paths
(`check:authz-resolver`, `check:error-code-casing`,
`check:filter-alias-parity`). The other three need a pull request's
context; they are run against this PR once it exists and reported on the
card. The 18 self-test-only rows grade their checkers' fixtures and
cannot judge this diff.

## Hypotheses (measured first)

- **H0 holds.** At base `33e4a5609c` the filtered census answers 3 sites
on 3 lines, 2 numbers, 1 file, as on the seat's `0be898499f`. The
whole-repo count is 1,061.
- **H1 holds.** After the rewrite, the filtered census answers 0 for
`packages/plugins/plugin-dev`. No site was left for an open PR (the file
lists of all 8 open PRs were read at 2026-09-30T02:45:10Z: only the
Version Packages PR objectstack-ai#20639 touches the package, in `CHANGELOG.md` and
`package.json`) or for an unfound anchor.
- **H2 holds, by the token reading, not the `dist` reading.** The parser
leaf-token diff of all 3 touched files is empty with its controls
firing. The emitted `dist` is not byte-identical, and it is not meant to
be: its only difference is the two docblock lines, which is why the
changeset ships.

## Acceptance notes

- **Strings, the form-D stage.** One dead number remains in a string
literal: the `describe` title at
`dev-plugin-security-enforcement-warning.test.ts:121` (`objectstack-ai#10036`). It
stays on the card for its form-D stage; no string moved here. It is not
assertion text. The same title is quoted in three recorded CI-log
fixtures under `scripts/fixtures/merge-queue-triage/`; those are
captured logs read by `check-merge-queue-triage-outcome.mjs`, so a later
rename of the title does not need them edited.
- **Outside `src/**`:** `tsconfig.test.json:3` and `:56` cite `objectstack-ai#13176`,
which answers 404. The same number sits in the `tsconfig.test.json` of
13 `packages/plugins/*` packages (17 `tsconfig*.json` files under
`packages/` in all), outside the census's declared surface; stage 10 (PR
objectstack-ai#20741) recorded its own copy for a later stage of this card. Every
other citation in the package outside `src/` answers 200
(`vitest.config.ts`, `README.md`, `tsconfig.json`, `package.json`);
`CHANGELOG.md` is release-owned and was not read as a site.
- **`origin/main` did not move.** It read `33e4a5609c` at worktree
creation and at every later fetch, so every run above is against the
same base and nothing needed rerunning after the merge.

## Deviations

- **The two builds inside the code-mutation control** (the mutate leg
and the restore leg) ran directly, not through `os-verify-lock.sh`. On
this host that wrapper runs unlocked anyway, so nothing was serialized
either way.
- **The dependency-closure build** used the filter
`'@objectstack/plugin-dev...'` (package plus its dependencies) rather
than the closure-only `^...` spelling; it built the same closure and the
package in one run.
- **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 harness's attribution reminder asked for a model-named
trailer and a different PR footer, and AGENTS.md overrides it.

---
_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>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…ackages/observability/src and packages/verify/src to the commits that decided them (objectstack-ai#20842)

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

## What changed

This is stage 13 of the `domain:cli` lane of the dead-citation sweep:
`packages/observability/src` and `packages/verify/src` in **one PR**.
The maintainer ruled that in the handover `5903477632` on the card
(「合成一个 PR (Recommended)」). The ruling changes the one-package-per-PR
direction for these two packages only, so each package's before and
after census counts are listed separately below.

Every comment site in these two trees that cited a tracker number
answering 404 now cites the commit in this repository's history that
made the decision the line describes. The form is ruling C+D's form C
(comment `5749154545` on objectstack-ai#19123), as stages 1 to 12 of this card applied
it. The last stage was `plugin-dev`, landed as `f7c6d65f5`. The card
stays open for its later stages, so this PR says `Part of`.

In total, **4 sites on 4 lines in 3 files, covering 3 numbers**, now
cite **3 distinct commits**:

- `observability`: **1 site**, `src/semconv.ts:49` (a census site);
- `verify`: **3 sites**:
  - `src/harness.ts:580` (a census site);
- `src/erasure-transaction-authorization.test.ts:163` and `:167`. These
are test-file comments, which the census defers. Stages 1 to 12 took
test comments too.

Only comments changed: **4 lines out, 4 in**. Every one of them is a
site, with no reflow and no companion line. Every touched file keeps its
line count (178 / 955 / 185 at base and head), so no line citation into
these files moves.

**No citation number is added.** The only tracker numbers on added lines
are `objectstack-ai#9650` and `objectstack-ai#9835` at `semconv.ts:49`. The removed line already
carried both, and both answer 200. No PR number stands on an added line.

No ADR or ruling-record file records any of the three decisions. A grep
of `docs/adr/` and `scripts/adr-anchors/` for the 3 numbers, the 3 shas,
`afterResponse` and `http_request_duration_ms` reads 0 hits; the control
number `7329` reads 1 file in the same tree. So all three anchors are
commits.

**One `patch` changeset**, for `@objectstack/observability` only
(`.changeset/20594-observability-provenance-anchors.md`). Its rewritten
`//` line reaches the published `dist`. The `verify` rewrites leave
`verify`'s `dist` byte-identical, so `verify` takes no changeset. Both
results are measured below.

## Census, before and after, one package at a time

**Instrument:** the gate's own `node scripts/check-issue-citations.mjs
--census --json`, read-only and unchanged. The count is its
`allocated-but-absent` findings under each package's path. Both runs
enumerated the whole board (187 pages).

| package | before (base `22e584c9db`) | after (head `62e556317c`) |
|---|---|---|
| `packages/observability` | **1** (`src/semconv.ts:49`, `objectstack-ai#10004`) |
**0** |
| `packages/verify` | **1** (`src/harness.ts:580`, `objectstack-ai#10943`) | **0** |
| whole repository | 752 | 750 |

| run | tree | when (UTC) | board |
|---|---|---|---|
| before | base `22e584c9db` | 2026-09-30 10:33:12 to 10:37:06 |
frontier objectstack-ai#20838, 18,665 numbers |
| after | head `62e556317c` | 2026-09-30 10:49:43 to 10:53:16 | frontier
objectstack-ai#20839, 18,666 numbers |

The whole-repo drop of 2 is exactly these two sites. A site-by-site diff
of the two JSON outputs has 2 findings gone (`semconv.ts:49`,
`harness.ts:580`) and 0 added. The other three tallies are equal in both
runs: 33,155 citations that answer 200, 1,985 that answer as pull
requests, and 1,018 cross-repo.

**Supplementary scan (census-invisible spellings, test files and files
outside `src/`).** The scan covers every `#N` token (two to six digits)
in the two packages' 53 tracked files, `CHANGELOG.md` excluded, and
probes each by REST (2026-09-30 10:34Z, re-probed 10:58Z).

- It finds **90 distinct numbers**: 12 in `observability` and 79 in
`verify`, with 1 shared. 86 answer 200 and 4 answer 404: `objectstack-ai#10004`,
`objectstack-ai#10943`, `objectstack-ai#11477` and `objectstack-ai#15145`.
- Dead occurrences in `src/`: 1 in `observability` and 3 in `verify` at
base, 0 and 0 at head. None of them was in a string literal. At head all
232 `#N` tokens under the two `src/` trees answer 200.
- Spellings the census cannot see:
  - `#N-word`: 0.
  - `#A/#B`: 4 sites. Only `semconv.ts:49` held a dead member.
  - `option #N` or `clause #N`: 0.
  - `word-#N`: 1, the `pre-objectstack-ai#11477` at `:167`, rewritten.
- URL forms (`issues/N`, `pull/N`): 0. The only `github.com` strings are
the two `package.json` `repository`/`bugs` URLs.
- A re-grep of the four numbers with no word-boundary operator finds
only `verify/tsconfig.test.json:1` (see Acceptance notes). A control of
the same shape, `objectstack-ai#9835`, reads 2 lines of `semconv.ts`.

## Per-number table

`git blame` at the base (on a full, unshallowed history) ties each line
to the commit that wrote it. That commit's message and diff were read to
decide the anchor.

| number | sites (file:line) | anchor | what that commit decided |
|---|---|---|---|
| `objectstack-ai#10004` | `observability/src/semconv.ts:49` | `1e050a5b1` |
`http_request_duration_ms` is emitted from the
`IHttpServer.afterResponse` transport seam, so p95 latency sees every
inbound surface. It is the squash of the pull request numbered `objectstack-ai#10004`,
and the line cites it beside the two live numbers for the same seam
move. The line was written by `914c41302` (the
`http_request_errors_total` retirement), whose changeset, now the
released entry at `observability/CHANGELOG.md:985`, cites `objectstack-ai#10004` for
this same seam move. |
| `objectstack-ai#10943` | `verify/src/harness.ts:580` | `46d34ab7c` | The host
importer's undeclared fallback resolves from the caller's base, not from
`@objectstack/types`, and `bootStack` hands in `(s) => import(s)`. The
line blames to this commit. Stages 3 and 4 used the same anchor for the
same number in `cli` and `types`. |
| `objectstack-ai#11477` | `verify/src/erasure-transaction-authorization.test.ts:163`,
`:167` | `6dd3e6968` | `/admin/remove-user` gets the raw-mount shading
whose `gateAdmin` runs before the break-glass guard (ruled option A, as
its message records), so a plain member hears `PERMISSION_DENIED`. Both
lines blame to this commit. Its squashed message includes the
`test(verify)` step that rewrote this pin. The `plugin-auth` stage used
the same anchor for the same number. |

All three shas were checked the same way:

- Each has exactly 1 match under `rev-parse --disambiguate`.
- Each is a commit with one parent.
- `merge-base --is-ancestor` of each against base `22e584c9db` exits 0,
on a history that is not shallow (`--is-shallow-repository` false).
- The control legs: `1e050a5b1^` against the base exits 0, and
base-as-ancestor-of-`1e050a5b1` exits 1.

### Wording per site

- `semconv.ts:49`: `(objectstack-ai#9650 / objectstack-ai#9835 / objectstack-ai#10004)` becomes `(objectstack-ai#9650 / objectstack-ai#9835 /
commit 1e050a5)`. Precedents for a mixed list:
`cli/src/commands/lint.ts:915`, `mcp/src/plugin.ts:8`.
- `harness.ts:580`: `objectstack-ai#10943:` becomes `Commit 46d34ab:`. The
neighbouring `objectstack-ai#4700:`, `objectstack-ai#4719:` and `objectstack-ai#17911:` labels answer 200 and
stay.
- `erasure-transaction-authorization.test.ts:163`: `objectstack-ai#11477
(maintainer-ruled option A):` becomes `Commit 6dd3e69
(maintainer-ruled option A):`.
- `erasure-transaction-authorization.test.ts:167`: `the pre-objectstack-ai#11477
route` becomes `the route before that commit`, which refers back to
`:163` four lines up.

## Does the rewrite reach `dist`? Measured per package

Both packages were built at base `22e584c9db` (closure plus package,
`pnpm --workspace-concurrency=2 --filter '@objectstack/verify...'
--filter '@objectstack/observability...' build`, VERDICT 0) and again at
head `5a144efc39` (VERDICT 0). All 6 `dist` files of each package were
compared byte for byte.

- **`observability`: reaches `dist`.**
- `index.js` and `index.cjs` differ in exactly 1 line each: the
`semconv.ts:49` comment, which esbuild keeps inside the `SEMCONV` object
literal.
  - `index.d.ts`, `index.d.cts` and both maps are equal.
  - The base `dist` carried `objectstack-ai#10004` in `index.js` and `index.cjs`.
  - ⇒ a `patch` changeset.
- **`verify`: does not reach `dist`.** All 6 files are byte-equal at
base and head, and the base `dist` carried none of the three numbers. ⇒
no changeset.
- **Code-mutation control for `verify`,** which proves that "equal" was
a measurement and not a stale build.
- The mutation went through `scripts/ablation-replace.mjs` in wrap mode.
The anchor `const organizationsPkg = opts.organizationsPackage` went 1
to 0, and the marker `ABLMARK20594S13` went 0 to 1 in the source. The
blob moved `47a921ad0b` to `3f54cdebfe`.
- After a rebuild, `ablation-dist-preflight` found the marker in
`dist/index.js` and `dist/index.cjs`. `index.js`, `index.cjs` and both
maps differ from the head build, and both `.d.ts` files are equal.
- The restore leg: the restored blob equals HEAD `47a921ad0b` and `git
diff HEAD` is empty. After a rebuild, `preflight --absent` reads the
marker absent from all 6 files with a clean tree, and all 6 files are
byte-equal to the head build.
- The first control attempt used an anchor that was a prefix of its own
replacement. The tool refused it (anchor count 1 to 1) and restored the
file, so no build ran on it.
- The whole workspace build, `pnpm exec turbo run build
--filter='./packages/*' --filter='./packages/*/*' --concurrency=2`,
finished 71/71 (all cache hits). The `dist` of both packages is
byte-equal to the head build.

## Token guard

The guard compared TypeScript 6.0.3 parser leaf tokens (`getChildren`,
JSDoc nodes excluded) of the 3 source files at base `22e584c9db` and at
`5a144efc39`, over 3,523 base tokens:

- **the real diff: 0 files differ**;
- comment-insertion control: 0 differ;
- code-insertion control: 3 of 3 differ;
- string control (first character of the first string literal): 3 of 3
differ, first differing kind `StringLiteral`.

The script exited 0, and its controls ran in memory only. A first
attempt with a bare scanner was discarded: it misaligns inside template
literals and reported a false difference.

## Tests, typecheck, lint and gates (head `62e556317c`)

- **`observability` tests:** `pnpm --filter @objectstack/observability
exec vitest run --maxWorkers=2` → Test Files 7 passed (7), Tests 85
passed (85).
- **`verify` tests:** `pnpm --filter @objectstack/verify exec vitest run
--maxWorkers=2` → Test Files 16 passed (16), Tests 120 passed (120). The
package has 16 test files, `erasure-transaction-authorization.test.ts`
included.
- **`verify` typecheck:** `pnpm --filter @objectstack/verify typecheck`
passed, with VERDICT command-exit 0 (`tsc --noEmit`, then
`check:test-typecheck` over `tsconfig.test.json`: 0 files / 0 errors).
`tsc --listFiles` confirms that `tsconfig.json` reaches `harness.ts` and
`tsconfig.test.json` reaches both edited `verify` files.
- **`observability` typecheck:** the package has no `typecheck` script;
it is a DEBT entry in `check-type-check-coverage.mjs` at 11 errors. `tsc
-p packages/observability/tsconfig.json --noEmit` reads 11 errors, all
in `src/__tests__/`, none in `semconv.ts`, and the program includes
`semconv.ts`. `pnpm check:type-check-coverage` and `pnpm
check:type-check-debt` pass (exit 0).
- **`pnpm lint`:** the repo-wide `eslint . --no-inline-config` exits 0
(2026-09-30 10:54:08 to 10:57:25 UTC, at `62e556317c`).
- **Gates:** `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` (run with no path) derived **60**
commands, and all 60 were run with their exit codes recorded before any
pipe.
  - 59 exited 0 on the first pass.
- `pnpm check:dual-build-cjs-loads` first exited 3 (PREREQUISITE NOT
MET: no `dist` for 33 packages). After the whole-workspace build it was
run again and exited 0.
- `--ran` reconciliation: 「60 derived, 60 run, 0 NOT-MEASURED, 0 UNRUN」,
exit 0.
- The diff-scoped `node scripts/check-issue-citations.mjs` judged the 2
citations this change adds (`objectstack-ai#9650`, `objectstack-ai#9835`); both answer 200, exit 0.
- `pnpm check:nul-bytes` passed, and a control-byte scan of the 4
changed files reads 0.
- **Main moved during the work:** `origin/main` moved 8 commits past the
base (to `1741c5dcb6`). `git diff --name-only` over the two packages and
the changeset path reads 0 files, so this branch was not merged forward.
The merge ref CI builds covers the joint tree.

## Acceptance notes

- **Left outside `src/**`, listed and not changed (a later stage of the
card):** `packages/verify/tsconfig.test.json:1` cites `objectstack-ai#15145`, which
answers 404. The other 14 citations outside `src/**` in the two packages
answer 200: `observability/vitest.config.ts:11`,
`verify/tsconfig.json:5` and `:13`, `verify/tsconfig.test.json:1`, `:2`,
`:32`, `:35`, `:43` and `:60`, and `verify/vitest.config.ts:8`, `:14`,
`:32`, `:63` and `:70`. `README.md` in either package carries no `#N`.
- **Release-owned, not a site:**
`packages/observability/CHANGELOG.md:985` carries `objectstack-ai#10004` (with
`objectstack-ai#9834`) in a released entry. This PR does not edit it.
- **Open-PR overlap** (read 2026-09-30 10:58Z): 13 open PRs. Only the
Version Packages PR objectstack-ai#20639 touches either package, and only in
`CHANGELOG.md` and `package.json`.
- **Not governed:** no path is on the governed-surface register. The
diff changes 19 lines, far under 5,000.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
… to the SQL echo on either strategy, and anchor the nested-relation note to its measured base (objectstack-ai#21012)

Part of objectstack-ai#20917
Clause-②: no
Part of objectstack-ai#20933 · Part of objectstack-ai#20887

A follow-up on three landed cards. It corrects one sentence in each of
three release notes of `@objectstack/service-analytics` that are still
pending on `main`, before release PR objectstack-ai#20639 consumes them. The order is
this lane's dispatch note on objectstack-ai#20917. The reasons are the `domain:spec`
seat's pointer `5922364600` on objectstack-ai#6021 (two banners) and the named
follow-up in section ② of the delta review `5922352898` on PR objectstack-ai#20916
(one paragraph). PR objectstack-ai#20991 is the precedent: it makes the same banner
correction to objectstack-ai#20935's note, and that note is not touched here. No code,
test or other file changes.

## The three sentences

| Note | Before | After |
|:---|:---|:---|
| `.changeset/20917-analytics-field-permission-gate.md`, the BREAKING
banner | "BREAKING for analytics queries on a SQL deployment that read a
field the caller may not read." | "BREAKING for analytics queries that
read a field the caller may not read: on a SQL deployment, and on `POST
/api/v1/analytics/sql` whichever strategy serves the cube." |
| `.changeset/20933-analytics-relationship-path-admission.md`, the
BREAKING banner | "BREAKING for analytics queries on a SQL deployment
that read a related object through a relationship path the cube does not
declare." | "BREAKING for analytics queries that read a related object
through a relationship path the cube does not declare: on a SQL
deployment, and on `POST /api/v1/analytics/sql` whichever strategy
serves the cube." |
| `.changeset/20887-analytics-nested-relation-engine-answer.md`, the
first sentence of **Why** | "Measured on the base over one fixture with
the real security layer (…)." | "Measured on the base before the
field-level gate (objectstack-ai#20917) and the relationship-path admission (objectstack-ai#20933)
landed, over one fixture with the real security layer (…)." |

The parenthesis in the third row is unchanged and elided here. Both
banners keep their bold. Everything else in the three files is
byte-identical: front matter, summary line, `Clause-②` line, ADR-0087
disposition marker and every other paragraph. Each file's diff is one
line (+1/−1).

## The readings behind each sentence

### Banner of the objectstack-ai#20917 note

**Before, by source, at `95555e71` (the parent of objectstack-ai#20931's landing
`1571aedc`):**

-
`packages/services/service-analytics/src/analytics-service.ts:2305-2333`:
`generateSql()` runs `callCtx` (2329), resolves a strategy (2330) and
returns that strategy's `generateSql` (2333).
- `analytics-service.ts:1283-1327`: `callCtx` runs the object-level
admission (1308) and the read-scope pre-pass. It has no field-level
gate. No non-test source in the package names `getReadableFields` at
that commit (`git grep` exit 1). The control is the landing `1571aedc`,
where it is named in three files.
- `strategies/objectql-strategy.ts:372-638`: the ObjectQL strategy's
`generateSql` renders the statement from the cube definition and returns
it (638). It makes no engine call, so it never reaches the engine's
field guards. That strategy's `execute()` reaches `engine.aggregate`,
which is why the query door already refused on it.
- So for a field the caller may not read, the echo printed a statement
on the ObjectQL strategy as well.

**After, on `main` at `a5bce40888`:**

- `analytics-service.ts:1453-1515`: `callCtx` runs the field-level gate
(1487) after the object admission (1481).
- `generateSql()` calls `callCtx` before it resolves a strategy (2580).

### Banner of the objectstack-ai#20933 note

**Before, by source, at `83480c6a` (the parent of objectstack-ai#20962's landing
`5f6b63a6`):**

- `analytics-service.ts:1452-1507`: `callCtx` admits objects over
`queryObjects` (1477). That set is `cubeObjects` (1584-1588, 1596-1608):
the base object and the declared joins only. An object reached through
an undeclared relationship path is not in it.
- The field-level gate (1483) asks the security service's
`getReadableFields`. That answer is field-level only and never asks
about object-level read
(`packages/plugins/plugin-security/src/security-plugin.ts:5410-5412`,
`computeReadableFields` 5440-5461 over `resolveProjectionFieldMask` 5517
ff.). Object-level read is the separate `canReadObject` (5641). So the
field gate does not refuse a readable field on an unreadable related
object.
- `objectql-strategy.ts` is byte-identical at `95555e71`, `83480c6a` and
`5f6b63a6` (`git diff --quiet`, exit 0 for both ranges).
- Its `generateSql` passes a one-hop cross-object dimension through
`planCrossObject` (895 ff.). That function throws only for the
out-of-envelope shapes: a cross-object time dimension, measure or
filter, a multi-hop dimension, a non-recombinable measure. The one-hop
dimension renders a LEFT JOIN (461-466), and the statement is returned
(638).
- So on the ObjectQL strategy the echo printed a statement for a one-hop
dimension through a related object the caller may not read.

**After, on `main` at `a5bce40888`:**

- `queryObjects` (1607-1616) adds every object a named member reads.
- The admission at 1481 covers that set before `generateSql()` resolves
a strategy (2580).

### Measured after, on both strategies

The measurement was a scratch probe at `a5bce40888`, copied into
`packages/rest/src` for one run and removed by an EXIT trap. It was
never committed; `git status --porcelain` was empty afterwards.

- **Build:** the probe's dependency closure was built under
`os-verify-lock.sh` first: `turbo run build --concurrency=1` over
`@objectstack/service-analytics...`, `@objectstack/plugin-security...`,
`@objectstack/objectql...` and `@objectstack/driver-sql...`. That is 19
tasks, `VERDICT command-exit 0`.
- **Run:** `pnpm --filter @objectstack/rest exec vitest run
--maxWorkers=2` on the one file gave 1 file / 2 tests passed, `VERDICT
command-exit 0`.
- **Fixture:** the composition is the shipped one, with the real
security layer: `SecurityPlugin` over `ObjectQL` on `SqlDriver`
(SQLite), and `AnalyticsServicePlugin` over the same engine. There are
two compositions: `native` (the plugin's own capabilities) and
`objectql` (narrowed to the engine-aggregate path).
- **What was asked:** each query body first passed the door's own body
schema (`AnalyticsQueryRequestSchema`). It then went to `generateSql`,
the call `POST /api/v1/analytics/sql` makes
(`packages/runtime/src/domains/analytics.ts:141-159`). The thrown
`status` is the HTTP status
(`packages/runtime/src/dispatcher-plugin.ts:646-649`).

| Question, inferred cube | Member caller, `native` | Member caller,
`objectql` | System caller, either |
|:---|:---|:---|:---|
| A field the member may not read, grouped | `403 PERMISSION_DENIED` |
`403 PERMISSION_DENIED` | statement printed, by the composition's
strategy |
| The same field, filtered | `403 PERMISSION_DENIED` | `403
PERMISSION_DENIED` | statement printed |
| A one-hop dimension through a related object the member may not read,
path not declared | `403 PERMISSION_DENIED` | `403 PERMISSION_DENIED` |
statement printed |
| Control: a readable field | statement printed (`NativeSQLStrategy`) |
statement printed (`ObjectQLStrategy`) | statement printed |
| Control: a readable related object, path not declared | statement
printed (`NativeSQLStrategy`) | statement printed (`ObjectQLStrategy`) |
statement printed |

Before and after both hold for both banners. So each banner is scoped
the way PR objectstack-ai#20991 scoped objectstack-ai#20935's. The note's own pins in
`packages/rest/src/analytics-field-permission-gate.test.ts` and
`analytics-relationship-path-admission.test.ts` assert the same echo
refusals on both compositions. The probe adds the system-caller and
readable controls, and it ran the bodies through the door's schema.

### The objectstack-ai#20887 **Why** paragraph

The dev measured on the branch point of PR objectstack-ai#20916, `00a92e18da`. That is
the parent of its first commit, `5687276296`.

**The base predates both gates.** `git merge-base --is-ancestor
00a92e1 X` exits 0 for X = `95555e71`, `1571aedc`, `83480c6a` and
`5f6b63a6`, so both landings descend from it. The reverse, `1571aedc`
against `00a92e18da`, exits 1. Its control leg, `00a92e18da~200` against
`00a92e18da`, exits 0, and the repository is not shallow.

**Clause one, "answered rows for a condition on a field the caller
cannot read", held on that base:**

- No non-test source in the package names `getReadableFields` (`git
grep` exit 1; control: six hits in `analytics-service.ts` on `main`).
- `callCtx` (`analytics-service.ts:1283` ff. at `00a92e18da`) runs only
the object admission (1308) and the read-scope pre-pass.
- The base filter normalizer flattened the form to a dotted member
(`strategies/filter-normalizer.ts:1296-1299`). The native strategy
joined the declared include.
- The dev's first report (`5916988260` on objectstack-ai#20887) records the measured
rows.

**Clause two, "without the declared join it named a table that does not
exist (500)", held on that base:**

- `strategies/native-sql-strategy.ts:771-783` falls back to the
relationship name as the joined table when no join is declared.
- The join allowlist applies to a compiled dataset only. An inferred
cube has none (`analytics-service.ts:1266-1268`;
`native-sql-strategy.ts:575-576`).
- The object admission covered the base object and the declared joins
only (`analytics-service.ts:1404`, `1416` ff.). So nothing refused
before the statement ran.

The paragraph is anchored to that base in the delta review's own words;
the clauses are kept.

### The ADR-0087 gate's reading

`breakingDeclaration` and `readDisposition` were re-run on each note at
`a5bce40888` and at this head. All three notes read the same on both:

- `breaking: true`;
- the signals `BREAKING`, `bang` and `clause-②-narrowing`;
- the disposition `not-required (no-migration-prescription)`.

The gate itself counts the three as inherited, not introduced: it skips
a changeset already breaking at the branch point.

## Changeset gate: no `skip-changeset`, and `Check Changeset` stays red

This PR edits three pending changesets and adds none, so `Check
Changeset` goes red by design. `check-empty-changeset.mjs --base
origin/main` exits 1 and refuses all three files as the DELIBERATE
CORRECTION class. Ruling D on objectstack-ai#18375 says `skip-changeset` is never
applied to a PR that edits an existing changeset, so no label is
applied. `Check Changeset` is not a required context.

**Confirmation:** this lane's at-tier contract review record on this
PR's head judges each rewritten sentence above. The sentences are:

1. the objectstack-ai#20917 note's banner, now scoped to include `POST
/api/v1/analytics/sql` on either strategy;
2. the objectstack-ai#20933 note's banner, scoped the same way;
3. the first sentence of the objectstack-ai#20887 note's **Why**, now anchored to the
base before the field-level gate and the relationship-path admission
landed.

`Clause-②: no` was checked against `scripts/pm/clause2-line.mjs`. A
wording edit to pending notes widens no accept set and adds no public
surface, so the value is `no`. With no arm, the line declares no
direction.

## Verification at `56fc0e77e3`

`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` (exit 0) derived 19 commands from the three
changed paths. Each ran on this head, with its exit code captured before
any pipe.

- **18 exit 0:**
- `check-adr-0087-registration.mjs --base origin/main` and `--self-test`
  - `check-changeset-no-major.mjs --base origin/main` and `--self-test`
  - `check-closing-keyword-parity.mjs` and `--self-test`
  - `check-comment-mask-corpus.mjs`
  - `check-empty-changeset.mjs --self-test`
  - `pm/release-rehearsal-clone.mjs --self-test`
- `check:changeset-gate-self-tests`, `check:driver-memory-census`,
`check:gitlink-declared`, `check:nul-bytes`, `check:objectui-changeset`,
`check:pm-changeset-deadline-census`, `check:published-files`,
`check:refd-timer-probe`, `check:watch-hint-literal`
- **1 exit 1, by design:** `check-empty-changeset.mjs --base
origin/main`, the DELIBERATE CORRECTION class above.
- **Roster family:** `check-changeset-fixed.mjs` (its roster lives under
`.changeset/`) exited 0.
- **Reconciliation:** `dispatch-gates.mjs --ran` with the recorded exit
codes exited 0. It read "19 derived, 19 run, 0 NOT-MEASURED, 0 UNRUN".
- **Main moved:** `origin/main` gained `f8178ffece` after the branch
point. It touches none of the three notes, and all three are still
present there.
- **NOT MEASURED, by design:** the type-check lanes and the package test
suites. The diff touches no TypeScript and no package source.

## Acceptance notes

- **Wording noted, not changed (corrections only, no new claims).**
- The objectstack-ai#20917 note's **What changed** ends: "the ObjectQL strategy and
the data API already refused them". Its ADR-0087 marker says the engine
"already refuses" such queries "on the ObjectQL strategy". Both hold for
the query and dataset doors, not for the SQL echo, which printed on that
strategy (reading above). The corrected banner carries the echo's scope.
- The objectstack-ai#20933 note's **Refusals that change form** says: "On the ObjectQL
strategy a related object the caller may not read was already refused".
That holds for the query door. On the echo, a one-hop dimension through
such an object printed (reading above). The corrected banner carries the
echo's scope.
- **Where the probe measured.** It measured the service call the door
relays, not the mounted HTTP route: identity resolution needs an auth
composition the probe did not boot. The relay and the status mapping are
cited at file:line above.
- **Not in this PR:** objectstack-ai#20935's note. PR objectstack-ai#20991 holds it.
- **Disclosure discipline holds.** No request recipe, field spelling or
returned value appears here.

## Patch rounds (the seat's append from the dev's report on objectstack-ai#20917; the
dev writes a body only once)

### Patch round 1

At `db64c37690`, on the seat's note `5923016038` on objectstack-ai#20917, which takes
the dev's class (a) finding into this PR. Two sentences beside the
corrected banners are qualified to the doors they hold for. Nothing else
changes: each is still one sentence, rewrapped in place, and both
ADR-0087 disposition markers are byte-identical.

| Note | Before | After |
|:---|:---|:---|
| `.changeset/20917-analytics-field-permission-gate.md`, **What
changed**, last sentence | "The native-SQL strategy, the one a SQL
driver serves first, answered such queries; the ObjectQL strategy and
the data API already refused them." | "The native-SQL strategy, the one
a SQL driver serves first, answered such queries; the ObjectQL strategy
already refused them on `POST /api/v1/analytics/query` and `POST
/api/v1/analytics/dataset/query`, as the data API did, but printed the
statement on `POST /api/v1/analytics/sql`." |
| `.changeset/20933-analytics-relationship-path-admission.md`,
**Refusals that change form**, first sentence | "On the ObjectQL
strategy a related object the caller may not read was already refused;
it now answers the analytics door's refusal rather than the engine's,
the same one a declared join gets." | "On the ObjectQL strategy a
related object the caller may not read was already refused on `POST
/api/v1/analytics/query` and `POST /api/v1/analytics/dataset/query`,
though `POST /api/v1/analytics/sql` printed the statement; on those two
doors it now answers the analytics door's refusal rather than the
engine's, the same one a declared join gets." |

**Readings:**

- **objectstack-ai#20917's sentence, at `95555e71`.** On the query door, the ObjectQL
strategy's `execute()` reaches the engine (`objectql-strategy.ts:301`)
before it renders its own echo (350-354). The dataset door runs the same
`execute()`, so both refused through the engine. The SQL echo's
`generateSql` (372-638) renders with no engine call, and `callCtx` had
no field gate (`analytics-service.ts:1283-1327`). So `POST
/api/v1/analytics/sql` printed the statement.
- **objectstack-ai#20933's sentence, at `83480c6a`.** On the query and dataset doors,
a one-hop dimension through a related object goes through
`executeCrossObject`. Its `resolveFkAttr` (1176 ff.) reads that object
through the engine as the caller (1211-1215). The echo renders the join
(461-466) and returns the statement (638), with no engine call, and the
object was outside the admitted set (`analytics-service.ts:1584-1608`).
After the change, on `main`, the echo answers `403 PERMISSION_DENIED` on
both strategies (round 0's probe).

**ADR-0087 reading at `db64c37690`:** for both notes,
`breakingDeclaration` reads `breaking: true` with the signals
`BREAKING`, `bang` and `clause-②-narrowing`, and `readDisposition` reads
`not-required (no-migration-prescription)`. Both are unchanged from
`a5bce40888`, and the objectstack-ai#20887 note reads the same.

**Gates at `db64c37690`:**
- `dispatch-gates.mjs --commands` derived the same 19 commands. 18
exited 0. `check-empty-changeset.mjs --base origin/main` exited 1 by
design: the DELIBERATE CORRECTION class, with all three notes named.
- The roster gate `check-changeset-fixed.mjs` exited 0.
- `--ran`: 19 derived, 19 run, 0 not measured.
- The derivation was repeated at `origin/main` `8055ff2279`, which had
changed one roster file, and gave the same 19 commands. None of the
three notes changed on `main` over that range.

**Acceptance note, wording kept:** both ADR-0087 disposition markers are
HTML comments, and they stay byte-identical. The objectstack-ai#20917 marker still
says the engine "already refuses" such queries "on the ObjectQL
strategy". Like the two sentences above, that wording holds for the
query and dataset doors, not for the SQL echo.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…the error-code ledger lists its two refuse() codes (objectstack-ai#21106) (objectstack-ai#21150)

Fixes objectstack-ai#21106
Clause-②: yes

Three release-text follow-ups from the at-tier contract review of PR
objectstack-ai#21084 (comment `5926057594`), all due before Version Packages objectstack-ai#20639
next picks up PR objectstack-ai#21084. Each sentence was checked against the code at
PR objectstack-ai#21084's merge commit `8368f1c0`. The target files are byte-identical
between `8368f1c0` and this branch's base (`git diff --stat` printed
nothing). No package source changes.

1. **A deliberate correction of the stage-① pending note.**
`.changeset/20281-connector-sync-moved-to-mapping.md` said
`connectorSource` was "declared, not yet executed" and that "Nothing
executes it in this release". The stage-② note
`.changeset/20919-spec-connector-source-live.md` says the executor reads
it. Both compile into the same `@objectstack/spec` CHANGELOG version.
2. **The "provenance, not identity" ledger sentence** for this release's
`ERROR_CODE_LEDGER` face changes, in a new spec note,
`.changeset/21106-error-code-ledger-provenance-rows.md`.
3. **The two `@objectstack/service-automation` provenance rows**,
`MAPPING_NOT_FOUND` and `UNSUPPORTED_TRANSFORM`, in
`packages/spec/src/api/error-code-ledger.zod.ts`. A hand pin in the
provenance gate's own test,
`packages/spec/scripts/check-error-code-provenance.test.ts`, guards
them.

## 1 · The stage-① note correction

| | Before | After |
|:---|:---|:---|
| Lead label | "**Added (declared, not yet executed):**" | "**Added:**"
|
| Last sentence of that paragraph | "Nothing executes it in this
release, and `os validate` / `os build` warn when it is authored." |
"The connector sync executor, `@objectstack/service-automation`'s
`pullConnectorSource` (objectstack-ai#20919), reads it; nothing schedules a pull until
the `job` stage lands." |

Everything else in the file is byte-identical: the front matter, the
summary line, the BREAKING banner, the FROM → TO table and the
retirement kit. The diff is +4 / −3 lines in one paragraph.

**Readings at `8368f1c0`:**

- **"reads it".**
`packages/services/service-automation/src/connector-pull.ts`
`pullConnectorSource` (:209) reads the mapping through `getMetaItem` and
its `connectorSource`. It makes one action call and writes through
`runImport`. The liveness rows say the same:
`packages/spec/liveness/mapping.json`, `connectorSource` `live`,
evidence `connector-pull.ts#pullConnectorSource`.
- **"nothing schedules a pull until the `job` stage lands".** Outside
tests, `pullConnectorSource` has three places in its own package: the
definition, the index re-export and the plugin method (`plugin.ts:621`).
No package source calls the plugin method.
- **Why the review's suggested clause "`os validate` / `os build` warn
when it is authored" is NOT kept.** It is false at `8368f1c0`, measured:
- The ledger row is `live` with `authorWarn: true`.
`lintLivenessProperties`'s `describe()`
(`packages/lint/src/lint-liveness-properties.ts`) has no `live` branch.
It throws its sentinel for that pair, by design: its own header calls
the pair "a ledger authoring mistake".
- Through lint's source, against the built spec:
`runAuthoringRules('validate', …)`, the call `os validate` makes
(`packages/cli/src/commands/validate.ts:522`), THREW
`lintLivenessProperties: ledger entry has unrecognised status "live"`
for a stack whose `mappings[]` carries `connectorSource`. The control
without `connectorSource` gave 0 findings.
- The runtime metadata-write door (`runRuntimeAuthoringRules`) answered
one `authoring-rule-threw` advisory in place of the liveness warning.
- Read at `99398542b` and again at this head.
`lint-liveness-properties.ts`, `authoring-rules.ts`, `runtime-gate.ts`
and `liveness/mapping.json` have no diff between `8368f1c0` and
`99398542b`. The CLI process itself was not run: its closure was not
built here.
- So the corrected note claims no warning. The defect is reported to the
seat as a separate finding below and not changed here.

## 2 · The ledger sentence, and the counts behind it

Measured on `ERROR_CODE_LEDGER` at `8368f1c0` against its parent
`2742e537`, comments stripped:

| Key | Parent | `8368f1c0` | Change |
|:---|---:|---:|:---|
| `@objectstack/rest` | 83 | 76 | −7: `AMBIGUOUS_MATCH`,
`BLANK_MATCH_KEY`, `CONCURRENT_UPDATE`, `ERR_DATASOURCE_UNAVAILABLE`,
`NO_MATCH`, `SUMMARY_RECOMPUTE_FAILED`, `UNIQUE_VIOLATION` |
| `@objectstack/core` | 12 | 17 | +5: `AMBIGUOUS_MATCH`,
`BLANK_MATCH_KEY`, `NO_MATCH`, `SUMMARY_RECOMPUTE_FAILED`,
`UNSUPPORTED_TRANSFORM` |
| `@objectstack/types` | absent | 3 | new key: `CONCURRENT_UPDATE`,
`ERR_DATASOURCE_UNAVAILABLE`, `UNIQUE_VIOLATION` |
| owner keys | 30 | 31 | |
| union (`ErrorCode`) | 280 | 280 | none added, none removed |

**The card and the review say rest lost −8. The measurement is −7.**
Eight codes moved (5 to core and 3 to types), but rest kept
`UNSUPPORTED_TRANSFORM`, which `resolveNamedMapping` still stamps.
Literal counts in `packages/rest/src` non-test source at `8368f1c0`: 0
for each of the seven, and 1 for `UNSUPPORTED_TRANSFORM`. The note says
seven.

The sentence follows the objectstack-ai#20206 (`5f9d7d78`) and objectstack-ai#19441 (`3f9e2eaa`)
paragraphs. It names both steps of this release's face change, the
objectstack-ai#20919 move and this PR's two rows, and says the union, the wire and the
HTTP answers are unchanged.

## 3 · The two rows, and why a pin rather than a wider gate

- **Stamp sites at `8368f1c0`** (unchanged since).
`connector-pull.ts:234` `refuse('MAPPING_NOT_FOUND', 404,
'mapping_not_found', …)` and `:303-304` `refuse('UNSUPPORTED_TRANSFORM',
400, 'unsupported_transform', …)`. Both go onto
`ConnectorPullError.code`, and the class is exported from the package
index.
- **Both codes are registered extension codes.** They are listed under
`@objectstack/rest`, and `UNSUPPORTED_TRANSFORM` under
`@objectstack/core` too. Neither is in `errors.zod.ts`. The executor's
other five codes are standard-catalog members and owe no row:
`VALIDATION_ERROR`, `EXTERNAL_SERVICE_ERROR`, `INTEGRATION_ERROR`,
`SERVICE_UNAVAILABLE`, `INVALID_FIELD`.
- **Placement.** The rows go in the key's existing ASCII order:
`MAPPING_NOT_FOUND` after `INVALID_SIGNAL`, and `UNSUPPORTED_TRANSFORM`
last. One comment records the stamp sites, the statuses and the
reachability reading: no HTTP door on this tree, so the thrown value is
the boundary. No other package's rows were touched.
- **Not widened.** `check:error-code-provenance`'s header declares it
blind to a helper indirection (a `makeError(code, …)` call site). It
also says "Widening is a gate-population change with an unmeasured blast
radius — its own card, never a rider". So this PR keeps the gate's
patterns and pins the two rows by hand instead.
- **Measured for the seat, read-only.** The `refuse('CODE', …)`
call-site form stamps a registered code at 7 sites in 3 packages: core
`artifact-packages.ts` ×4, runtime `artifact-collections.ts` ×1, and
these two. Before this PR, these two were the only unlisted ones; after
it, none is unlisted.
- **The pin.** A new block in the gate's test file has two halves. One
pins the blind spot itself: `scanSourceText` finds no site in a
`refuse('X', …)` call. The other asserts that
`ERROR_CODE_LEDGER['@objectstack/service-automation']` lists each code.
It reads the ledger module inside `packages/spec`, so the suite still
reads nothing outside its package.
- **Ablation, two legs** through `scripts/ablation-replace.mjs` in WRAP
mode, run from the committed state at `c227eb366`:
- Removing the `MAPPING_NOT_FOUND` row gave `1 failed | 16 passed (17)`:
"expected [ …(9) ] to include 'MAPPING_NOT_FOUND'".
- Removing the `UNSUPPORTED_TRANSFORM` row gave `1 failed | 16 passed
(17)`, on that code's case.
- Each leg restored the file (blob `229345964e13` = HEAD, `git diff
HEAD` empty).
- A first attempt at leg one was refused by the tool before any test
ran: its replacement text already existed in the file. The anchor was
changed and the leg re-run. That first attempt is not counted as a run.

## Changeset gate: no `skip-changeset`, and `Check Changeset` stays red
by design

This PR edits a pending changeset that it did not add. `node
scripts/check-empty-changeset.mjs --base origin/main` exits 1 and
refuses `.changeset/20281-connector-sync-moved-to-mapping.md` as the
DELIBERATE CORRECTION class. The precedents are PR objectstack-ai#20991 and PR objectstack-ai#21012.
Ruling D on objectstack-ai#18375 says `skip-changeset` is never applied to a PR that
edits an existing changeset, so no label is applied. `Check Changeset`
is not a required context.

**Confirmation requested in writing on this PR:** the stage-① note's
`connectorSource` paragraph now says the executor reads the binding and
nothing schedules a pull yet. It no longer says nothing executes it or
that `os validate` / `os build` warn.

## `Clause-②: yes`, not the claim's `no`

The claim (`5926939917`) and the dispatch say `patch` with `Clause-②:
no`. The dispatch also says not to keep `no` silently if the diff widens
a public surface, and it does:

- Two literal members are added to
`ERROR_CODE_LEDGER['@objectstack/service-automation']`, a published `as
const` face.
- The ledger header (the objectstack-ai#16404 ruling) names this ledger, together with
`StandardErrorCode`, as the published contract face for error codes.
- The two precedents this card names, objectstack-ai#20206 and objectstack-ai#19441, each declared a
provenance-row addition as `@objectstack/spec` `minor` with `Clause-②:
yes`. Both say "What widens is the per-package face".

So the new note is `minor` with `Clause-②: yes`, and this body's second
line matches it. No accept set changes: the `ErrorCode` union is
unchanged. Every package is in the one `fixed` group, and
`@objectstack/spec` already has a pending `minor`, so the released
version is the same either way. The seat can flip it back if it reads
the precedent differently.

## Verification at `5748c8ddd` (base `99398542b`, merged with
`origin/main` `39ab2940e`)

- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` (exit 0) derived 87 commands from the four
changed paths. Each ran on this head, with its exit code captured before
any pipe:
- **84 exit 0.** Among them: `check:error-code-provenance` ("scanned
2636 files; 335 registered-code stamp site(s): 316 listed, 19 waived" /
"OK"), `check-adr-0087-registration.mjs --base origin/main` ("this PR
adds no declared-breaking changeset (2 non-breaking changeset(s)
seen)"), `check-changeset-no-major.mjs --base origin/main` ("This diff
introduces no `major` bump"), `check:api-surface`,
`check:authorable-surface`, `check:docs`, `check:liveness`,
`check:error-code-casing`, `check:dispatcher-error-vocabulary`,
`check:cross-package-test-inputs`, `check:nul-bytes`,
`check:query-options-erasure` and `check:type-check-debt`.
- **1 exit 1, by design:** `check-empty-changeset.mjs --base
origin/main`, the DELIBERATE CORRECTION class above.
- **2 NOT MEASURED** (exit 3, `PREREQUISITE NOT MET`):
`check:dual-build-cjs-loads` (needs a full `pnpm build`) and
`check:lean-entry-closure` (needs `@objectstack/objectql` built). CI
builds the tree. Reason: a built tree was not produced here.
- Reconciled: `dispatch-gates.mjs --ran` exit 0, "87 derived famil(ies)
accounted for — 85 run, 2 NOT-MEASURED".
- `pnpm --filter @objectstack/spec build`: `VERDICT command-exit 0`.
Then `pnpm --filter @objectstack/spec check:generated`: "All 15
generated artifacts are up to date".
- `vitest run --project local` on `src/api` plus
`scripts/check-error-code-provenance.test.ts`: 47 files / 1546 tests
passed. The 12 other spec test files that read the ledger: 182 tests
passed.
- `pnpm --filter @objectstack/spec typecheck`: exit 0. That covers
`tsc`, `check:scripts-typecheck`, and `check:test-typecheck` ("52
file(s) / 246 error(s) / 135 pinned signature(s) held", unchanged).
- Main moved during the run: `origin/main` gained 13 commits after the
branch point. None touches the four files. The delta against `39ab2940e`
is exactly them (+70 / −4).

## Acceptance notes

- **Read and kept in the stage-① note:**
- "Runtime behaviour is deliberately **unchanged**: no connector sync
ever ran." This is about the retired connector-attached keys
(`syncConfig` / `fieldMappings`), and it stays true of them.
- "It carries no cadence (a `job` sets that)." This describes the
design; the corrected clause says nothing schedules a pull until the
`job` stage lands.
- **Reported to the seat, not changed here:**
- **The liveness lint throws on `connectorSource`.** The `live` +
`authorWarn` row reaches `describe()`'s sentinel throw, so `os validate`
/ `os build` stop with an internal lint error, and the runtime door
answers `authoring-rule-threw`, instead of warning on an authored
`connectorSource`. The comments in `authoring-rules.ts` and
`runtime-gate.inert-type-writes.test.ts` still say it warns. The fix is
a choice for its own card: teach `describe()` a caveat branch for
`live`, or change the row.
- **`turbo` 2.11.5 edits `AGENTS.md`.** It arrived with the
development-dependencies bump (`840ec9dab`, now on `main`). On every
turbo invocation it sees as an AI agent's, it appends a managed
`turborepo-agent-rules` block to `AGENTS.md`, a Tier H governed surface,
and `turbo.json` declares no `agentGuidance: false`. Measured here:
`pnpm exec turbo run build --filter='@objectstack/lint...'` and `pnpm
check:type-check-debt` each left `M AGENTS.md`, +11 lines. Each time it
was restored with `git checkout HEAD -- AGENTS.md` (blob `e9e211fc` =
HEAD) and never committed. An agent that commits with `-a` would carry
it into its PR.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
… starter barrel, and a Picklists count in the metadata summary (objectstack-ai#21167)

Part of objectstack-ai#21018
Clause-②: yes (widening)

This PR is the code half of objectstack-ai#21018. It covers scope items 1, 2, 3 and 5
of the card body. Item 4, the "seven generator barrels" line in
`skills/objectstack-platform/SKILL.md`, is on a Tier H governed path. It
follows in its own PR under a second claim, so objectstack-ai#21018 remains open for
it.

Dispatched by the `domain:cli` seat's PM under claim 5929373213. Dev
session `session_01VvcEokUG1tvVxkceYfR5XB`.

## Premise, checked on `origin/main`

The card waited on the runtime reader. That reader is on `main` as
88b484e (objectstack-ai#21047), and these are the parts this PR relies on:

- `picklists` and `picklistExtensions` are in `METADATA_ARRAY_KEYS`
(`packages/objectql/src/engine.ts`).
- The registry fold resolves a field's `picklist` into served `options`
(`picklist-resolution.ts`, `resolvePicklistFieldsOnto`).
- The write door judges a write against the resolved options.
- The boot refuses an unresolved name
(`packages/objectql/src/plugin.ts`).

The repro below measures all of this end to end on a scaffolded project.

## What changed

**Item 1: `os generate picklist NAME`**
(`packages/cli/src/commands/generate.ts`)
- This is the row written and reverted on objectstack-ai#20825's branch (`6ef78d3f29`,
reverted in `e16359a9fa`), re-read against the landed reader.
- It writes `src/picklists/NAME.picklist.ts` through `definePicklist`.
The file name comes from `metadataFileName`, with no override.
- The list is collected under `picklists` (`singularToPlural`).
`namesObject: false`, and `requires` is empty.
- The emitted header states the next rule an author meets: a field that
names the list declares no `options` of its own.
- The row's comment now names the reader: the authoring doors, the boot
refusal, the served options and the write judgement.
- Two existing comments counted the roster ("an app or a skill", "14
emission sites across all 7 generators"). Both are reworded so the new
row does not make them false.

**Item 2: scaffold wiring**
- `os init` derives its wiring from the roster
(`SCAFFOLD_WIRED_BARRELS`), so the `app` and `plugin` templates wire
`src/picklists` with no edit to `init.ts`.
- The blank starter in `create-objectstack` gains
`src/picklists/index.ts`, byte-identical to the empty barrel `os init`
writes. Its `objectstack.config.ts` gains `import * as picklists from
'./src/picklists';` and `picklists: exportsOf(picklists),`.
- `create-objectstack-wiring-parity.test.ts` holds the two scaffolders
equal.

**Item 3: docs**
- `content/docs/deployment/cli.mdx` gets the example line, the table row
(`picklist`, `src/picklists/`, `NAME.picklist.ts`, `picklists`) and the
"What it does" clause.
- `packages/cli/README.md` gets the type list and a short paragraph.

**Item 5: the metadata summary** (`packages/cli/src/utils/format.ts`)
- `MetadataStats` gains `picklists`, and `collectMetadataStats` counts
it through the existing `authoringRuleUnionStack` fold, so both ADR-0130
D4 shapes are covered.
- `printMetadataStats` renders it in the `Data:` row, the kind's own
domain (`domain: 'data'` in the registry).
- A stack with no picklists prints the row it printed before. A
`picklistExtensions` entry is not counted as a list.
- The two existing test fixtures that spell out every `MetadataStats`
member gain `picklists: 0`: `format.metadata-stats-package-fold.test.ts`
and `print-metadata-stats-zero-row.test.ts`.

**Changesets**
- `@objectstack/cli`: `minor`. A new generator kind, and
`stats.picklists` is added to the `--json` output of `os validate`, `os
build` and `os info`.
- `create-objectstack`: `minor`. The starter gains a wired barrel.
- Both carry `Clause-②: yes (widening)`. Nothing is narrowed and nothing
is renamed, so there is no ADR-0087 marker.

## Repro, before and after

Before, on `origin/main` 58a77db:

```text
os generate picklist industry      exit 1   ✗ Unknown type: picklist   (roster lists 7 types)
npm create objectstack (blank)     src/ = actions apps dashboards flows objects skills views
hand-wired picklist + select field
  os validate                      exit 0   Data: 1 Objects  3 Fields
```

After, on this branch:

```text
npm create objectstack (blank)     src/ = actions apps dashboards flows objects picklists skills views
os generate picklist industry      exit 0   ✓ Reaches the stack: objectstack.config.ts carries it in `picklists` as 'industry'
note.object.ts gains  industry: Field.select({ picklist: 'industry', label: 'Industry' })
  os validate                      exit 0   Data: 1 Objects  3 Fields  1 Picklists
  os build                         exit 0   Data: 1 Objects  3 Fields  1 Picklists   (artifact carries `picklists` and the field's `picklist`)
  os info                          exit 0   Data: 1 Objects  3 Fields  1 Picklists
  os info --json                   stats.picklists = 1
os dev --fresh -p RANDOM
  GET  /api/v1/meta/object/my_app_note   200   fields.industry = { picklist: 'industry', options: [option_a, option_b], … }
  GET  /api/v1/meta/picklist/industry    200
  POST /api/v1/data/my_app_note { industry: 'option_a' }   201
  POST /api/v1/data/my_app_note { industry: 'option_z' }   400 VALIDATION_FAILED · invalid_option · names picklist "industry"
```

On a project scaffolded before this change (its config wires no
`src/picklists`), `os g picklist region` exits 0. It reports `Not wired`
and prints the import line and the `defineStack` key to add.

## Tests

The pins are:

- `packages/cli/src/commands/generate-picklist.pin.test.ts`:
- the roster row (`src/picklists`, stack key `picklists`, `namesObject:
false`, no `requires`) and the `NAME.picklist.ts` file name;
- the scaffold loaded through the loader `os validate` uses
(`bundle-require`, `BUNDLE_REQUIRE_EXTERNALS`), parsed by
`PicklistSchema`, under the item name `os g` reports;
- that scaffold registered through `ObjectQL.registerApp` under
`picklists`, serving its options on a `Field.select({ picklist })`
field, and passing `PicklistServedFieldSchema`;
  - a control: the same field with no list serves no options.

  It constructs `ObjectQL`, so it lands in the `integration` tier.
- `packages/cli/src/utils/format.metadata-stats-picklists.test.ts`:
  - the count, top level and option-B;
  - that an extension is not a list;
  - the zero case;
  - the rendered row `Data: 1 Objects  2 Fields  1 Picklists`;
- a control: a stack with no picklists prints `Data: 1 Objects 2
Fields`.

The runs, all on HEAD 5a53515 except where noted:

| Run | Files | Tests | Result |
|:---|---:|---:|:---|
| `pnpm --filter @objectstack/cli exec vitest run --project unit
--maxWorkers=2` | 241 + 2 | 3427 + 29 | All passed, 29 skipped. The 2
files first failed with `packages/cli is not built` and passed after
`pnpm --filter @objectstack/cli build`. |
| `--project integration`, the 8 integration files among the related
tests | 8 | 66 | Passed: `generate-picklist.pin`,
`create-objectstack-stack-reach`, `generate-stack-reach`,
`info-detail-package-fold` and 4 more |
| `OS_TEST_TIERS=nightly`, the related `*.e2e` files | 6 | 42 | Passed.
Includes `generate-scaffolds-reach-stack.e2e`: "`os g picklist` exits 0
and reports no wiring to add" |
| `pnpm --filter create-objectstack exec vitest run` | 16 | 249 | Passed
|
| `pnpm --filter @objectstack/cli typecheck` (`tsc --noEmit &&
check:test-typecheck`) | | | Exit 0. The test-typecheck ledger is
unchanged (3 files / 28 errors) |
| `pnpm --filter create-objectstack typecheck` | | | Exit 0 |

## Ablation

The ablations ran on the committed fix (e63b1db). Each mutation went
through `scripts/ablation-replace.mjs` with the anchor counted on disk.
Each restore was proven: the blob equals HEAD and `git diff HEAD` is
empty.

| Leg | Mutation | Red | Control (green) |
|:---|:---|:---|:---|
| generator | The `picklist` row deleted from `GENERATORS` (anchor x1 →
x0, 53 lines) | `generate-picklist.pin` 3 of 5. `wiring-parity`: imports
and stack keys (2) | `generate-scaffold-validates` 19/19; the pin's
file-name case and its no-list control |
| template | `picklists: exportsOf(picklists),` deleted from the blank
config | `wiring-parity`: "hands every wired barrel to its stack key" (1
of 22) | the other 21 |
| collect | `picklists: count(stack.picklists),` deleted |
`metadata-stats-picklists` 5 and `package-fold` 4 (its `ZEROES`
comparisons) | the pin's no-picklists row;
`print-metadata-stats-zero-row` |
| print | `['Picklists', stats.picklists],` deleted |
`metadata-stats-picklists` "Data: row", and
`print-metadata-stats-zero-row` "every metric collectMetadataStats
counts is rendered" | the other 27 |

## Gates

- `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
--commands`, run with no paths, derived 96 families. All 96 were run,
each with its exit code recorded before any pipe.
- Five of them first refused with `PREREQUISITE NOT MET` (exit 3)
because their build inputs were missing: `check:skill-examples`,
`check:dual-build-cjs-loads`, `check:i18n`, `check:i18n-coverage` and
`check:i18n-walk-parity`. All five passed once those inputs were built.
- `--ran` reconciliation: `✓ dispatch-gates --ran: 96 derived famil(ies)
accounted for — 96 run, 0 NOT-MEASURED`.

**`pnpm lint`, as a proven narrowing** (on HEAD 5a53515):

1. **Population, read from eslint's own config** (`isPathIgnored` /
`calculateConfigForFile`): all 8 touched `.ts` files are linted. The 4
touched `.md` / `.mdx` files are outside its population.
2. **Count, from `--format json`**: 8 files, 0 errors, 0 warnings, with
`--no-inline-config`.
3. **Invariance**: no resolved config for these files is type-aware (no
`parserOptions.project`, no `projectService`, as `eslint.config.mjs`
states). This diff touches no lint config or baseline. So it cannot move
the verdict on any file it does not touch.

## Not in this PR

- **Item 4** (Tier H). On `db48028f1a`, lines 194–195 of
`skills/objectstack-platform/SKILL.md` read:

  ```text
generic connector executors in `plugins:`; and the seven generator
barrels
(`objects`, `views`, `actions`, `flows`, `dashboards`, `apps`,
`skills`),
  ```

  The proposed text, net zero lines:

  ```text
generic connector executors in `plugins:`; and the eight generator
barrels
(`objects`, `views`, `actions`, `flows`, `dashboards`, `apps`, `skills`,
`picklists`),
  ```

- Not carried here, as the card says: the
`picklist-reference-unverified` trigger, and a dangling
`picklistExtensions[].extend` (folded into objectstack-ai#20825).

## Acceptance notes

- **The two starter README listings now name `src/picklists`** (commit
`90d55b1e13`, added on contract review `5930680827`):
- the `Layout` bullet in
`packages/create-objectstack/src/templates/blank/README.md`, which ships
into every new project;
  - the tree in `packages/create-objectstack/README.md`.

They ride this PR, not the Tier H item-4 PR, so two non-governed lines
do not wait on a human approval.
- **`Scaffold with repo dist` is red on this PR, and the PR is held
until the next release publishes.** This note was added by the seat.
- **Cause:** the job scaffolds with the repo-built `create-objectstack`,
then installs the project's framework from the npm registry. The
template's `^17.0.0` range resolves to the published `@objectstack/spec`
17.5.0, which predates the `picklists` stack key (`addbbf0`,
`.changeset/19518-picklist-kind.md`, still unreleased). So the generated
project's `defineStack` refuses the key as unrecognized at the validate
step.
- **Scope of the measurement:** the repro table's `npm create
objectstack` rows and the end-to-end chain were measured on the repo
build, not against the registry.
- **It is not this PR's code to fix.** Its gate checks the template
against the real registry, and the template now runs one release ahead
of it.
- The check is not required. It runs only on `pull_request` under its
path filter (`packages/create-objectstack/**`, `docker/**` and its own
workflow file), and it is not red on `main`.
- Landing now would leave it red on every later `create-objectstack` PR
until that release.
- **The hold:** the release that carries the key is the open Version
Packages PR objectstack-ai#20639; its `@objectstack/spec` changelog already lists
`addbbf0`. Once it merges and publishes, a re-run of this job reads the
new spec and the template is coherent with it.
- Until then, the PR stays draft and its card is `pm:blocked` on objectstack-ai#20639.
- On a project scaffolded by an earlier release, the not-wired hint
prints `picklists: Object.values(picklists),`, not the starter's
`exportsOf(...)` spelling. Both type-check once the barrel exports a
list, which is the only state the hint is printed in.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…ai#21362)

## What this is

The release-time half of the 17.6.0 release notes, following objectstack-ai#20623 for
17.5.0.

The page `content/docs/releases/v17/17-6.mdx` landed before publish
(objectstack-ai#21290) with a RELEASE-TIME TODO. 17.6.0 was published to `latest` on
2026-10-02 from the version commit `617f25f8` (objectstack-ai#20639):
`@objectstack/cli` at 02:53Z, `@objectstack/spec` at 03:03Z. This PR
makes the TODO's edits, deletes both TODO comments, and updates
`content/docs/releases/v17/index.mdx`.

Docs-only, two files under the release-owned `content/docs/releases/`.
Nothing is published, hence `skip-changeset`.

Clause-②: no

## What changed

**`17-6.mdx`**

- **Publish date.** 2026-10-02, 3 days after 17.5.0.
- **Count.** The version commit consumed **337** changesets (the
`.changeset/*.md` files it deletes, README excluded). The draft read
338.
- The 69 `CHANGELOG.md` files that carry a 17.6.0 section at `617f25f8`
list 496 per-package entries (198 minor, 298 patch) in 48 of those
files.
  - Those entries de-duplicate to the same 337.
- Every consumed changeset was already in the draft, so nothing new
needed folding in.
  - New in 17.6.0: 337 − 16 shipped in 17.5.0 = **321**.
- **New section "Also shipped in 17.6.0 — not in its CHANGELOG".**
- `748b240` (objectstack-ai#21270, `hostDisabledReason`) is an ancestor of the version
commit, but the version commit did not consume its changeset.
- The npm packages therefore carry it, and no 17.6.0 CHANGELOG names it.
HotCRM's upgrade observed the same (objectstack-ai/hotcrm#1982).
- This is the same window that produced 17.5.0's seven stragglers. I
filed it as objectstack-ai#21361.
- **Console.**
  - The pin stayed `31971ff1e28f`.
- The known-issues list gains the zh-CN defect: console strings render
in English (objectui#11326, fixed upstream in `d0fba91aa`). objectstack-ai#21330
confirmed it on 17.6.0.
- The release verification reproduced the dataset and datasource saves
on 17.6.0.
- **New section "Known issues found after publish".** It draws on the
three 17.6.0 verification lanes:
  - the HotCRM upgrade, objectstack-ai/hotcrm#1982;
  - the North Star path on the published packages, objectstack-ai#21318;
  - the P0 + 17.6-risk checklist run, objectstack-ai#21330.

  The issues it lists:
- objectstack-ai#21349: the `--stored` and `audit-metadata-bodies` previews write to
the database;
- objectstack-ai#21321 / objectstack-ai#21322: local package install loses script action bodies, and
a hot install leaves flows unbound;
- objectstack-ai#21323 / objectstack-ai#21324: `verify` passes a refused stack, and its `--json`
output is not clean;
- objectstack-ai#21350: "My Pending" misses position-routed approvals (pre-existing);
  - objectstack-ai#21332: a cloned flow reaches no Studio surface;
  - objectstack-ai#21158: anonymous endpoints;
  - the console pin issues.

The authorization-class finding in objectstack-ai#21330 (R2) is deliberately not
described here.
- **Upgrade checklist.** 19 of 30 lines are now marked *Exercised on
HotCRM (a 17.5.0 app with a 17.5.0-created SQLite DB), 2026-10-02*, each
with what was observed, taken from hotcrm#1982's per-line table.
  - Lines that do not apply to HotCRM stay *Not exercised*.
- Two lines gain a ⚠️ for objectstack-ai#21349, telling operators to run the previews
on a copy.
- One line is new: `os validate --strict` now fails on dead keys. HotCRM
got +88 warnings.
- **Unchanged.** objectstack-ai#21158 is still open, so its "no supported channel"
lines stay.

**`v17/index.mdx`** (following objectstack-ai#20623)

- The frontmatter description runs through 17.6.0.
- The status blockquote says 17.6.0 is released and current, published
2026-10-02, taking over from 17.5.0. A plain install resolves 17.6.0,
and the minors warning names 17.6.0.
- A new "17.6.0 stays in that register" paragraph links the breaking
changes, the known issues and the checklist.
- The per-release list marks 17.6.0 as current. The checklist callout
records that 17.5.0 → 17.6.0 was exercised in part (19 lines, on
HotCRM), and the checklist links lead with 17.6.0.
- The top-level `content/docs/releases/index.mdx` already reads "current
series: 17.6.0", stamped at version time.

## Verification

Run locally on the head commit. All of these pass:

- `check-issue-citations --base origin/main`
- `check-doc-anchors` (406 links)
- `check-role-word`
- `check-release-page-status`
- `check-release-section-coverage`, plain and `--strict`
- `check:release-index-currency-sync`
- `check-docs-single-h1`
- `check-release-notes`
- `check-doc-frontmatter`

Both pages compile as MDX with `@mdx-js/mdx` 3 + `remark-gfm`.

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

https://claude.ai/code/session_014VGCS11YUtYAiinRcdqQwL

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

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

dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation size/xl tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant