docs(spec): re-anchor the dead tracker citations in stack.zod.ts and data/analytics.zod.ts to the commits that decided them (stage 7) - #20616
Conversation
…data/analytics.zod.ts to the commits that decided them Twelve comment and docblock sites cited six tracker numbers that answer 404. Each now names the commit on main that decided the rule its line states: 35ad101 (themes carrier retired), c8d6f6e (functions array accepts the lowered handler), 4d0d944 (ManifestSchema closed), 279431e (defineStack same-key action refusal), 35dffea (composeStacks cross-stack action key refusal) and 2306a76 (analytics_cube bound at the /meta write door). Comment lines only, one for one. Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx Co-authored-by: Claude <noreply@anthropic.com>
…tics.zod.ts provenance re-anchoring Both files ship verbatim through the package's files[] (src/**/*.zod.ts), so the rewritten comments publish. Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 137 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin ff317850daac372fb18ddc2d4e6fea1a206a6d43 && git checkout ff317850daac372fb18ddc2d4e6fea1a206a6d43
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 7510663c878f13467198525570b87df5235578a5 cc0580d404917d546c2a10ae745f95b77f3239be && git checkout -B drift-repro 7510663c878f13467198525570b87df5235578a5 && git merge --no-ff cc0580d404917d546c2a10ae745f95b77f3239be
node scripts/docs-audit/affected-docs.mjs --json 7510663c878f13467198525570b87df5235578a5
|
Contract reviewServed-tier: Read (this act, 2026-09-29T08:39Z): card #20234 (body; every comment: triage ① Derived judgments(a) Scope and file surface: right. 3 files: the claim's two, (b) Comment-only, no code token, string, test title, exported string or (c) Numbers: right. Net-removed is exactly 6 numbers on 12 sites: #10485 x2 ( (d) Anchor truth: right, 6 of 6. Six distinct 9-hex shas stand on added lines and none on removed lines.
(e) Form C over the whole diff: right. Each rewrite leads with (f) Readers: right. The source-text readers of the two files read code, not these comments: ② Semver level
③ Boundary flagsBlocking: none. Dev deviations, each answered:
Out-of-scope findings, each judged:
Non-blocking observations: (1) Escalated: none. CI at this head, the judging read at 2026-09-29T08:32Z: 33 check-runs, 24 success, 3 skipped ( Implemented-by: VERDICT: PASS Generated by Claude Code |
…mits that decided them (objectstack-ai#20626) Part of objectstack-ai#20596 Clause-②: no ## What changed This is the second stage of the `domain:services` lane of the dead-citation sweep. It covers `packages/plugins/plugin-sharing/src/**` and nothing else. By census, it is the largest package in the lane that no open PR or in-flight claim holds (the claim, `5886159115`, gives the order). Later stages cover the other packages, so this PR says `Part of` and the card stays open. Every comment or docblock site in scope that cited a tracker number answering 404 has been rewritten in ruling C+D's form C (comment 5749154545 on objectstack-ai#19123), by stage 1's method (PR objectstack-ai#20609, landed as `422db788a`). That is **87 sites on 86 lines in 23 files, covering 13 numbers**: the 60 census sites outside the generated headers, and 27 sites in test comments, which the census defers. Each rewritten line now cites the commit in `origin/main` history that decided what the line describes, and it says in its own words what that commit decided. No ADR or ruling-record file records the decision behind any of the 13 numbers (ADR-0131 names objectstack-ai#14484 only as evidence, not as the record of its ruling), so every anchor is a commit: **13 distinct shas**. No number was dropped. Only comments changed. Every touched source file keeps its line count (87 lines out, 87 in, over 23 files), so no line citation into these files moves. One of those 87 lines held no dead citation: `backfill-sys-record-share-organizations.ts:14`, where 「the cliff the card names」 lost its referent once line 10 named a commit instead of a card. It now reads 「the cliff that commit pins」, and `3f64fe6c6`'s backfill test is the one titled 「the cliff」. No code token moves (see the guard below). **No citation number is added.** Every tracker number on an added line was already on the line it replaces. Over the whole diff, added minus removed is 0 or negative for every number, and no number is new to the diff. No PR number stands on an added line. The one PR spelling in scope (`PR objectstack-ai#5973`, dead) became its squash commit. Nineteen dead sites are left on purpose: 1 string literal, 14 test titles, 1 verbatim ruling quotation and 3 generated file headers (see the list below). One more file: a `patch` changeset for `@objectstack/plugin-sharing`, because the rewritten docblocks ship (see Changeset below). ## Census: `plugin-sharing`, before and after **Instrument (A1).** The gate's own `node scripts/check-issue-citations.mjs --census --json`, read-only and unchanged. The count below is its `allocated-but-absent` findings under `packages/plugins/plugin-sharing/`. Each run counts as a reading only because its board frontier equals the newest issue number, read by a separate request just before and just after the run. | reading | tree | board | whole-repo `allocated-but-absent` | plugin-sharing sites | lines | files | numbers | |---|---|---|---|---|---|---|---| | before | base `422db788a`, run 2026-09-29T08:04:49Z to 08:08:19Z | enumerated, 185 pages, frontier objectstack-ai#20614 (newest objectstack-ai#20614), 18,441 numbers | 2,300 | **63** | 62 | 14 | 13 | | after | head `a6d231713`, run 08:27:44Z to 08:31:07Z | enumerated, 185 pages, frontier objectstack-ai#20616 (newest objectstack-ai#20616), 18,443 numbers | 2,240 | **3** | 3 | 3 | 1 | The before count matches the 63 that census `5884031174` read at `f11b5f20`. The whole-repo drop is 60, exactly this diff's census sites. The `resolves` tally is 32,803 in both runs, and `resolves-as-pull-request` (1,891) and `cross-repo-unjudged` (983) did not move either. The 3 left are the generated headers below. No run was truncated or discarded: all three enumerations in this stage (two census runs and the supplementary board below) read 185 pages at the newest frontier. **Supplementary instrument, the whole scope.** The census does not read test files or strings, and this stage's scope includes both. So a second reading runs the gate's own exported `extractCitations` (whole-file and comment-prose projections) and `classifyCitation` over every `.ts` file under `plugin-sharing/src` (74 files). It uses one board, enumerated by the gate's own `enumerateBoard` at 08:12:18Z (185 pages, frontier objectstack-ai#20614, equal to the newest). | reading | citations | dead | src comment | test comment | src string | test string | |---|---|---|---|---|---|---| | before, `422db788a` | 1,187 | **106** | 63 | 28 | 1 | 14 | | after, `a6d231713` | 1,100 | **19** | 3 | 1 | 1 | 14 | Its src-comment column equals the census's 63, which is the control on the second instrument. The 989 resolving, 87 pull-request and 5 cross-repo citations are the same in both readings. ## Per-number table Sites and files count all dead sites in scope at the base (comments and strings, tests included). `rewritten / left` counts the sites rewritten and the sites left. Each anchor was read in its message and diff, not only its subject. It is the commit that decided what the line describes: its own message or diff names the number it replaces, or, for a squash-merged PR, it is the merge of that PR. | number | sites / files | rewritten / left | anchor: what it decided | |---|---|---|---| | `objectstack-ai#5973` | 4/2 | 3/1 | `abeb3751f`: `HierarchyScopeContext.organizationId` is the tenancy authority, and it is required. `objectstack-ai#5973` was the PR itself; this is its squash commit | | `objectstack-ai#6206` | 12/7 | 11/1 | `8e13ca876`: the share-link routes hand enforcement the whole authz envelope, per maintainer ruling A of 2026-08-07. It is the plugin-sharing half; the spec stages anchor the contract half at `d7e0b4212`. Three sites name the ruling in words, 「the full-envelope ruling」, beside `aa4b90d9a`, which applied it | | `objectstack-ai#6523` | 3/3 | 3/0 | `aa4b90d9a`: 36 contract signatures converge on the full `ExecutionContext`. The same anchor the spec stages gave this number | | `objectstack-ai#8710` | 10/3 | 9/1 | `04d03c3a0`: a deactivated `sys_position` confers no sharing-rule shares. Its message quotes the 2026-08-15 ruling: access-conferring paths filter, addressing paths do not | | `objectstack-ai#8792` | 2/1 | 2/0 | `83c661d97`: the bulk-write merge's missing provenance mark is recorded as ruled (2026-08-15), not oversight | | `objectstack-ai#8836` | 1/1 | 1/0 | `1850ebbb0`: it pins 「no filter object that can be vouched 'author' may outlive the request that vouched it」, the invariant the line names. The same anchor the spec stages gave this number | | `objectstack-ai#11671` | 5/5 | 2/3 | `09b4f4e4e`: `os i18n extract --source-hashes` writes the provenance companion (maintainer ruling objectstack-ai#12069 Option A, which stays cited). The same anchor stage 1 gave it | | `objectstack-ai#11674` | 4/2 | 4/0 | `1cba33f16`: the seed loader warns at load time when a required column is deferred, and the ordering constraint is written at the four pointer-pair sites, these two among them | | `objectstack-ai#12493` | 2/2 | 2/0 | `aa5994e17`: the Operation Message Catalog gains `record_write_denied` ahead of its emitters. The same anchor the spec stages gave this number | | `objectstack-ai#13279` | 3/2 | 3/0 | `6a180e42d`: a permission-store read that throws raises `AuthzStoreUnavailableError` (503), and each transport re-raises it rather than laundering it into a 401 | | `objectstack-ai#13398` | 1/1 | 1/0 | `953a81f4a`: the class ruling on published logger sinks, applied at this site. `error` is reachable only because the sink already declares it; growing `error?` onto a published sink is forbidden. No record of the ruling exists in the repo, and this commit, which wrote this heading, is its earliest application in history | | `objectstack-ai#13608` | 23/3 | 21/2 | `fc9ba76a5`: `publicSharing.eligibility` is held at redemption, not only at mint. The same anchor the spec stages gave this number | | `objectstack-ai#14484` | 36/8 | 25/11 | `3f64fe6c6`: `organization_id` is stamped on every `sys_record_share` write, the stranded rows are backfilled, and the object is admitted to the tenancy ledger (the 2026-09-02 ruling, decision batch objectstack-ai#11 item 3) | Every cited sha matches exactly one commit (`git rev-parse --disambiguate`, count 1 for each), and every one is an ancestor of the base (`merge-base --is-ancestor`, exit 0 for all 13). The history is complete (`--is-shallow-repository` false, 15,073 commits). A line-origin pickaxe (`git log -S` on each dead line's exact text) found each line entering either in its anchor commit or in a later commit that cites that commit's decision. For example, `65759baca` is the consumer half that cites `aa5994e17`'s key, and `b70a55d62` cites `3f64fe6c6`'s ledger admission. Wordings to check, each true of its commit: - `backfill-sys-record-share-organizations.ts:5`: 「rows that `SharingService.grant`, before commit 3f64fe6, stranded」. `3f64fe6c6` is the writer fix, and this module is its backfill. - `backfill-sys-record-share-organizations.ts:36` and `:124`: 「the 2026-09-02 ruling commit 3f64fe6 applies (decision batch objectstack-ai#11 item 3, …)」. The verbatim maintainer quotation on line 37 is untouched. - `share-link-service.ts:841`: 「(published-sink level ruling, commit 953a81f)」. The heading's body already states the ruling (option C allowed, option B forbidden). - `exec-context-annotation.pin.ts:7-8`, `sharing-rule-service.ts:12-13` and `sharing-service.ts:20`: 「since commit aa4b90d (the full-envelope ruling: no per-site subset contracts)」. `aa4b90d9a`'s message: 「Apply the … ruling default (converge on the full envelope, keep no per-site subset contracts)」. - `share-link-routes.ts:81`: 「[commit 8e13ca8, full-envelope ruling]」, so that 「the whole point of the ruling」 five lines down still has a referent. ## The 19 sites left - **Non-test string (1 site).** `sharing-service.ts:1674` sits inside the operator-facing `warn` text for a hierarchy scope that was not widened (「… resolveOwnerIds, objectstack-ai#5973); …」). It is a runtime string, so it is form D, not form C, and the shrink-only `doc-authoring-prose-id` baseline already holds it (`sharing-service.ts` → `objectstack-ai#5973: 1`). Left and listed, as stage 1 left its refusal strings. - **Test titles (14 sites).** `describe` titles in `backfill-sys-record-share-organizations.test.ts:185`, `:274`, `:324`, `:367`, `record-share-organization-stamp.test.ts:194`, `:239`, `:278`, `:315`, `:353`, `:436`, `sharing-service.test.ts:1798` (the `objectstack-ai#14484` titles), `share-link-eligibility.test.ts:607` (`objectstack-ai#13608`), `share-link-enforcement-context.test.ts:226` (`objectstack-ai#6206`) and `sharing-rule.test.ts:1898` (`objectstack-ai#8710`). Tokens, left as they were. - **A verbatim ruling quotation (1 site).** `share-link-service.test.ts:478` is point 2 of the maintainer's 2026-09-01 ruling, quoted verbatim and untranslated. It carries 「沿 objectstack-ai#13608 先例」. AGENTS.md keeps a quoted Chinese ruling in its original words, and rewriting the quote would rewrite the ruling. Left. - **Generated headers (3 sites).** Line 8 of the `es-ES`, `ja-JP` and `zh-CN` `.source-hashes.generated.ts` files carries 「(objectstack-ai#11671, maintainer ruling objectstack-ai#12069 Option A, extending objectstack-ai#8765 Option B)」. `os i18n extract` writes that line from `packages/cli/src/utils/i18n-extract.ts`, so the fix belongs at the producer, the carrier stage 1 named. The hand-written `translations/index.ts:26` is rewritten here, with the same wording stage 1 used. ## Mechanical guard: no code token moves The guard compares the TypeScript parser's leaf nodes, with comments as trivia and JSDoc nodes excluded, base `422db788a` against head. Template literals are therefore read in context. It ran over all 23 touched `.ts` files. - Real run: 96,665 base tokens, **0 files with a token change** (exit 0). - Comment-insertion control in `share-link-service.ts`: 0 files changed, as expected (exit 0). The first attempt was a no-op: its replacement still contained the anchor, so `scripts/ablation-replace.mjs` refused it before the guard ran. It was redone with an anchor the replacement does not contain. - Positive control, a code token changed in `share-link-service.ts` (`Boolean(eligibility),` to `Boolean(eligibility) && true,`): DIFFER (exit 1). - Positive control, one digit changed inside the kept `sharing-service.ts:1674` warn string: DIFFER (exit 1). Every mutation went through `scripts/ablation-replace.mjs`. Each restore was proven byte-identical to the HEAD blob (`ba7fba2e8199`, `2833b9a1616d`), with `git diff HEAD` empty and a clean tree afterwards. ## Changeset This change ships bytes, so a `patch` changeset for `@objectstack/plugin-sharing` is included. It says only that the provenance comments were re-anchored. Measured on the built package (A3): `files[]` is `dist`, `README.md` and `CHANGELOG.md`. After the build, the rewritten comments reach both halves of `dist`: `3f64fe6c6` appears 6 times in `dist/index.d.ts` and 8 in `dist/index.js`, `fc9ba76a5` 3 and 3, `04d03c3a0` 2 and 2, `8e13ca876` twice in `index.d.ts`, and `1cba33f16` 4 times in `index.js`. esbuild keeps only some comments, so the positive controls are unchanged lines beside rewritten ones that shipped. `share-link-service.ts:891` is found once in each half, and `sharing-service.ts:1269` once in `index.js`. A never-written negative phrase appears nowhere. The only dead number left in `dist` is the kept `objectstack-ai#5973` warn string. ## Gates (head `a6d231713`) - **Citation judging, as CI runs it:** `pnpm check:issue-citations` (self-test) exits 0. `node scripts/check-issue-citations.mjs` exits 0: the diff-scoped run judged 9 citations across 11 files, and all 9 resolve. - **Doc authoring:** `pnpm check:doc-authoring` exits 0, with the sibling-package prose ids at their baseline and no growth. - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at `a6d231713` (re-derived after a fresh `git fetch` at 09:58Z: the same 65, and none of the 8 new `main` commits touch anything it derives from) derived 65 commands. They include all 50 derived at dispatch, plus 15 more. All 65 exit 0. `--ran` reports 65 run, 0 NOT MEASURED, 0 unrun, and exits 0. - Three gates first exited 3 (PREREQUISITE NOT MET) because the workspace was only partly built: `check:dual-build-cjs-loads`, `check:i18n` and `check:type-check-debt`. A full `turbo run build` of `./packages/*` and `./packages/*/*` then ran under the shared verify lock (71 tasks, exit 0), and all three exited 0 on their rerun. `check:dts-closure`, `check:sourcemap-no-sources-content` and `check:lean-entry-closure` were rerun too, over 71, 68 and 15 built packages, and exited 0. - **Tests and typecheck, under the verify lock:** - `pnpm --filter @objectstack/plugin-sharing test`: 37 files and 913 tests pass. That is every test file in the package, the 12 touched ones included. - `pnpm --filter @objectstack/plugin-sharing typecheck` exits 0. Its main `tsc` program reads the 37 non-test files, and its `check:test-typecheck` program (`tsconfig.test.json`) reads all 74 files under `src/`, the 37 test files included (`--listFiles`). - **Lint, as a proven narrowing:** `eslint --no-inline-config --format json` over the 23 touched `.ts` files gives 23 files, 0 errors and 0 warnings. All 23 are in eslint's own population (`isPathIgnored` is false for each). `eslint.config.mjs` never enables type-aware linting (no `parserOptions.project`, as its own line 328 states), so a comment edit here cannot move the verdict on any untouched file. The repo-wide `pnpm lint` is CI's run. - **Control bytes:** `pnpm check:nul-bytes` exits 0, and a raw scan of the 24 changed files for control bytes finds none. ## Acceptance notes - **The census instrument did not truncate in this stage.** Three enumerations read 185 pages each at the newest frontier. The truncation stage 1 saw (1 run in 5) is carried on objectstack-ai#20556, and this stage changes no instrument. - **What stays for later stages.** - The 3 generated `objectstack-ai#11671` headers, whose producer is `packages/cli/src/utils/i18n-extract.ts`. - The `objectstack-ai#5973` warn string (form D, held by the `doc-authoring-prose-id` baseline), the 14 test titles and the verbatim ruling quotation. - **Anchors the next stages can reuse.** The same numbers stand elsewhere in `packages/**/src`: `objectstack-ai#6206` at 53 sites and `objectstack-ai#11674` at 46 (the ordering-constraint note has two sibling copies outside this package, in `sys-approval-request.object.ts` and `sys-audit-log.object.ts`). `8e13ca876` / `d7e0b4212` / `aa4b90d9a` and `1cba33f16` are the anchors used here. - **Base.** The branch is 8 commits behind `origin/main` (`1322cc72c`, read at 09:58Z). None of them touches `plugin-sharing` or this changeset, and none re-anchors any of these 13 numbers, so there was no merge. --- _Generated by [Claude Code](https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
…anularities take effect on the query doors (objectstack-ai#20282, stage 2) (objectstack-ai#20635) Part of objectstack-ai#20282 Clause-②: yes (narrowing) Stage 2 of objectstack-ai#20282, under claim `5886559774` (`session_014EJ1ED8X4MMrT18BhVx4tx`, `domain:spec` seat 2). An AUTHORED analytics cube's `measures.format` and `dimensions.granularities` now reach the readers a compiled dataset already reaches. `refreshKey` is measured only, and nothing is built for it. The card stays open for stage 3 (descriptions) and for the `refreshKey` decision. ## What changes One Cube shape has three producers: authored cubes (`AnalyticsServiceConfig.cubes`, which the CLI threads from `analyticsCubes`), compiled datasets, and ad-hoc inference. Until now, both keys were read only on the compiled-dataset path. - **`measures.format`** - `analytics-service.ts#withDeclaredMeasureFormats` runs in `queryIn`, beside the SQL-echo gate. - Every measure column a query names now carries its cube measure's declared `format` as `fields[].format`. This holds for every strategy (NativeSQL, ObjectQL, the delegated fallback) and for both member spellings. - A measure that declares no format gets no key. A value already on the column is never replaced. - On the dataset door, the value read is the compiler's copy of the dataset measure's `format`, the same value `enrichResultColumns` writes anyway. - `GET /analytics/meta` is unchanged. See the premise checks below. - **`dimensions.granularities`** - `analytics-service.ts#withDeclaredGranularityDefaults` runs on `query()` and on the `generateSql()` dry run, before the source-field gates and strategy selection. - It reads `dataset-executor.ts#declaredDefaultGranularity`, a new function extracted from `granularityOf`, which now calls it too. Both producers are therefore read by one rule. - The rule, exactly the compiled-dataset path's: - a single-entry list is the default bucket for a time dimension the query groups by without stating a granularity; - a stated granularity always wins; - a granularity outside the list is not refused; - a list of two or more states no default; - a `timeDimensions` entry that carries only a `dateRange`, for a dimension the query does not group by, stays a filter. - **Spec** (`packages/spec/src/data/analytics.zod.ts`, after PR objectstack-ai#20616 merged; `origin/main` merged first through `scripts/pm/os-regen-merge.sh` as `d963f30e33`) - `MetricSchema.format` and `DimensionSchema.granularities` gain describes that state the enforcement. - The metric's example values move from the names "currency" and "percent" to numeral patterns, the vocabulary the `fields[].format` slot documents. - `content/docs/references/data/analytics.mdx` is regenerated with `gen:docs`. - **Ledger** - Both rows in `packages/spec/liveness/analytics_cube.json` go `dead` → `live`. Each cites its readers as `file#symbol` and the CLI threading producer. - The file note's two sentences that named both keys as dead are rewritten. - `state-counts/analytics_cube.md` is regenerated with `gen:liveness-counts`: live/dead goes from 18/9 to 20/7. - The `analytics_cube` Notes cell in `liveness/README.md`, which listed both keys among the dead, is rewritten. - **Changeset**: `@objectstack/spec` minor and `@objectstack/service-analytics` minor, with a `**BREAKING**` sentence. ## Clause-② (measured arm): yes (narrowing) - **Widening:** two authored keys take effect, and `fields[].format` is populated for authored cubes. The contract already declares that member. - **Narrowing, measured:** - Method: a throwaway service-seam probe, run once on head and once with the fill ablated through `scripts/ablation-replace.mjs` (round 1 at `958251b6ac`; round 3 at `e1383be04d`, blob `9d77adcd798f` → `94b1bc63165a`, restored to HEAD). The probe is not committed. - Cube: an authored cube whose `placed_at` declares `granularities: ['month']`, whose `shipped_at` declares two intervals, and (round 3) which declares `joins: { account: { name: 'crm_account' } }`. | request (authored cube; `placed_at` declares `['month']`, `shipped_at` two intervals; `joins: { account }` where named) | fill ablated (= base behaviour) | head | |:--|:--|:--| | custom-SQL measure grouped by `placed_at` | 200 (raw SQL) | **400 `INVALID_FIELD`**, custom-SQL measure (`resolveMeasureAggregation`) | | cross-object measure (`sum` of `account.balance`) grouped by `placed_at` | 200 | **400 `INVALID_FIELD`**, cross-object measure (`planCrossObject`) | | `count` by `placed_at` with a `where` field over `account` | 200 | **400 `INVALID_FIELD`**, cross-object filter, `param: where` | | `count` grouped by a one-interval time dimension over `account` (`account.created_at`) | 200 | **400 `INVALID_FIELD`**, cannot bucket a cross-object time dimension | | `count` by `placed_at` beside a multi-hop dimension (`account.owner.region`) | 200 | **400 `INVALID_FIELD`**, single-hop only | | `avg` by `placed_at` beside a cross-object dimension (`account.industry`) | 200 | **400 `INVALID_FIELD`**, non-recombinable measure | | `count_distinct` by `placed_at` beside a cross-object dimension | 200 | **400 `INVALID_FIELD`**, non-recombinable measure | | host whose `queryCapabilities` offers raw SQL only (a hand override; `AnalyticsServicePlugin` wires both): plain `count` grouped by `placed_at` | 200 | **"No strategy can handle query"** (every newly bucketed query) | | control: `count` by `placed_at` beside a cross-object dimension (recombinable) | 200 | 200 | | control: `avg` by `placed_at`, no cross-object member | 200 | 200 | | control: custom-SQL measure grouped by `shipped_at` (two intervals) | 200 | 200 | - Every 400 is byte-identical (code, status, message, member, param, cube) to what the same request with `granularity: 'month'` stated by hand already got: the fill hands the engine path the very query a hand-stated granularity does. The class is the engine aggregate path's whole refusal set as the new bucketing reaches it, read from `ObjectQLStrategy.planCrossObject` and `resolveMeasureAggregation`: a custom-SQL measure; and, on a cube whose members resolve through `joins`, a measure or `where` field over a joined object, a `timeDimensions` entry over a joined object, a multi-hop dimension, and an `avg` / `count_distinct` measure beside a dimension over a joined object. `planCrossObject`'s two dataset-definition arms read only compiled-dataset scope, so they cannot fire on an authored cube. Two pins: `DECLARED NARROWING` (custom-SQL) and `DECLARED NARROWING, joined cube` (cross-object measure). - The changeset carries the `**BREAKING**` sentence (the class and its remedy) and the disposition `registered analytics-cube-single-granularity-default-enforced`, a new ADR-0087 D3 semantic entry (round 2, seat note `5889752648` Q4 = B). - `check-adr-0087-registration`: `✓ 1 declared-breaking changeset(s), each carrying an ADR-0087 disposition`. - Round 2 registered it: `18.analytics-cube-single-granularity-default-enforced.ts` tells authors that a one-interval list is now a default bucket, including one the protocol-18 conversion `cube-sub-day-granularities-removed` minted, and (round 3) which queries grouped by it the engine path refuses: the whole class above, plus every newly bucketed query on a raw-SQL-only host. `registry.ts` is regenerated; `spec-changes.json` and the upgrade guide were regenerated with no change, because neither projects major-18 entries yet. ## Premise checks, against `origin/main` `7510663c87` - **`format` on `CubeMeta`: not done, because the premise does not hold.** - The dataset path surfaces `format` only through `fields[]`. A compiled dataset's `getMeta` projection is `{ name, type, title }` too. - `AnalyticsMetadataResponseSchema` records the narrowing for this (`objectstack-ai#6442`). - `content/docs/api/data-api.mdx` already sends clients to `fields[]` for `format`. - The spec contract files (`contracts/analytics-service.ts`, `api/analytics.zod.ts`) are outside this claim's surface. - **`granularities` refusal: none invented.** - The dataset path never compares a requested granularity against the list, so there is no refusal to mirror. - What a multi-entry list should mean ("Supported Granularities") is an open fork in the report. - **`refreshKey` census** (tree `958251b6ac`; `packages/services`, `packages/drivers`, `packages/rest`, non-test): - `refreshKey`: 0 hits. The repo-wide control finds 9 files. - Pre-aggregation, materialized-view and rollup terms: 7 hits, all unrelated (automation subflow rollups, and a driver-sql built-in column flag). - `ICacheService` consumers: plugin-auth rate-limit and secondary storage, runtime inbound rate limit, dispatcher counter store, sms. None is in analytics. - service-analytics reads no job, cache or scheduler service. Its one cache is the request-scoped label map in `dimension-labels.ts#withLabelFetchCache` (the lit control, 1 hit). - A scheduler exists (`service-job`'s `IJobService`: cron, interval and db adapters), and nothing in analytics uses it. - Nothing exists that could key on `refreshKey`, so building a cache is a separate card. ## Verification Final head `162e6c0f01` (round 3; merge base `f4ce10c89d`), unless a line says otherwise. Builds and test runs went through `os-verify-lock.sh`. The `check:*` gates and eslint ran outside it, as the lock's scope prescribes. So did `gen:docs`, after two lock acquisitions for `check:generated --fix` timed out in the queue (exit 99). - **Round 3 (`162e6c0f01`):** service-analytics 136/3185 (includes the new joined-cube pin), typecheck clean; runtime REST pin 1/3; spec `src/migrations` 3/161; `migrate-meta-engine-guidance.test.ts` 3/3; spec `--project repo` 43/761. Lit/dark: with the fill ablated, the pin file goes 7 red (the 6 prior bucketing and narrowing cases plus the joined pin), and the probe's E1 to E7 and R1 each answer 200; on head each is refused. `dispatch-gates --ran`: 115 derived / 115 run / 0 NOT MEASURED, 114 exit 0, 1 exit 1 (`check:platform-checklist`, not a PR gate). Regenerated artefacts vs `origin/main`: `registry.ts` +62/-0 (the entry block only), `spec-changes.json` and the upgrade guide identical. Driver-free merge-tree probe against `2473e26875`: clean, and the migration and liveness checks are green on the merge tree. - **Round 2 (`257ab1bc92`):** `migrate-meta-engine-guidance.test.ts` 3/3; spec `--project repo` 43/761; spec `src/migrations` 3/161; `check:migration-registry`, `check:spec-changes`, `check:upgrade-guide`, `check-adr-0087-registration` green; `dispatch-gates --ran` 115 derived / 115 run / 0 NOT MEASURED, 114 exit 0, 1 exit 1 (`check:platform-checklist`, inputs equal to merge base `3f45b6cc13`). The lines below are round 1's, at `d665865d5b`. - **Builds (①):** - `pnpm --filter '@objectstack/service-analytics...' build`: exit 0. - `turbo run build --filter='@objectstack/runtime^...'`: 29/29. - `pnpm --filter @objectstack/spec build`, after the describe edit: exit 0. - **Tests (②):** | suite | files | tests | result | |:--|--:|--:|:--| | service-analytics, whole package | 135 | 3178 | pass | | new service-door file | — | 13 | included above | | spec `--project local` | 575 | 16917 (+1 todo) | pass | | spec `--project repo` | 42 | 745 | pass | | runtime: the new REST pin plus the 2 sibling harness files that consume service-analytics | 3 | 24 | pass | | rest: the 7 files that import service-analytics | 7 | 86 | pass (at `958251b6ac`; service-analytics src is unchanged since) | - Typecheck is clean for spec, service-analytics and runtime. Runtime's includes `check:test-typecheck`, and the new file adds no debt. - `tsc --listFiles` puts the new service test in service-analytics' program. - Two consumers were not run, and both are unaffected by construction because their cubes declare no `format` and no time dimension: `packages/client` `analytics-automation-json-erasure.test.ts`, and the dogfood analytics files, which declare neither key. - **Ablations (predicted before each run; every leg restored to the HEAD blob with `git diff HEAD` empty):** | mutation | suite | predicted | observed | at | |:--|:--|:--|:--|:--| | format early-return (`9d77adcd798f` → `0d48cfde9469`) | service door | 4 red | 4 red, 8 green | `9bf3b3b0b4` | | format early-return | REST, through `dist/` | 1 red | 1 red, 2 green | `9bf3b3b0b4` | | granularity early-return (`9d77adcd798f` → `94b1bc63165a`) | service door | 6 red | 6 red, 7 green | `958251b6ac` | | granularity early-return | REST, through `dist/` | 2 red | 2 red, 1 green | `9bf3b3b0b4` | - Both REST legs were rebuilt, then checked with `ablation-dist-preflight` (marker present in 2 built files). Each restore leg was rebuilt again and checked `--absent`, with the tree clean. - **Gates:** - `dispatch-gates --commands`: 111 derived. `--ran` with exit codes: 111 run, 0 NOT MEASURED. 109 exit 0. - Two exit 1, both pre-existing. Their inputs are byte-identical to the merge base `1322cc72c`: - `check:platform-checklist`: `areas/identity-auth.json` cites `auth-plugin.ts#twoFactor`, which is absent; - `check:docs-transcript-drift`: 4 CLI transcripts print "author-time rules (47)", and the count derives to 46. - `check:liveness`: `analytics_cube 27 classified (live 20, dead 7)`, with the state-counts current. - `check:generated`: all 15 artifacts up to date. - **Lint, narrowed:** - `eslint --no-inline-config --format json` on the 4 changed `.ts` files: 4 files, 0 errors, 0 warnings. - The other 4 changed paths (`.md` / `.json`) are reported by eslint itself as "File ignored because no matching configuration". - Invariance: `eslint.config.mjs` never enables type-aware linting (no `parserOptions.project`), so this diff cannot move a verdict on an untouched file. ## Acceptance notes - **File-surface amendments to claim `5886559774`,** each forced by the claim's own items: - the `analytics_cube` Notes cell in `packages/spec/liveness/README.md`. It is the prose half of the state table whose shard the claim names, and it named both keys as dead. - `packages/runtime/src/analytics-authored-cube-format-granularity.test.ts`, the REST pin the claim asks for "where the analytics harness reaches". It is a new file, and the runtime harness drives the real dispatcher route. - the generated `content/docs/references/data/analytics.mdx`, which the describes regenerate. - the D3 entry, the regenerated `migrations/registry.ts` and the changeset marker, per seat note `5889752648`. - `packages/services/service-analytics/src/preview-evaluator.ts` still open-codes the single-entry rule (`dim.granularities?.length === 1`) on the draft-preview path. That makes it a third spelling beside `declaredDefaultGranularity`. It is outside this surface; `carrier:` 承接者:无. - `examples/app-showcase/src/data/analytics/showcase.cube.ts` authors `done_rate: { format: 'percent' }`. That named style now reaches `fields[].format` verbatim, and a numeral-pattern renderer does not read it as a percentage. The spec describe now teaches the pattern vocabulary. The example's value is outside this surface and is reported to the seat. - Carried from stage 1 and unchanged here: the open-core `os serve` artifact-fallback boot threads no `analyticsCubes`. --- _Generated by [Claude Code](https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Part of #20234
Clause-②: no
Stage 7 of the staged sweep:
packages/spec/src/stack.zod.tsandpackages/spec/src/data/analytics.zod.ts, both freed by landings (PR #20579 and PR #20458). Its claim is5885635758. Every comment or docblock line in those two files that cited a tracker number answering 404 now cites the commit onmainthat decided its rule, in ruling C+D's form C, and says in its own words what was decided. Comments only: 12 lines out, 12 in, across 2 files. No code token, string literal ordescribe()text moves. No dead site stays: none of the 12 is read by literal.The census is the gate's own
node scripts/check-issue-citations.mjs --census --json, filtered to the two paths. Before: base0f6dcac5e9, board enumerated (185 pages, frontier #20611). After: headcc0580d404, board enumerated (185 pages, frontier #20615).Measurement
packages/spec/src/)stack.zod.ts:415,:1023) to35ad101bc; #6238 (:633) toc8d6f6e08; #14192 (:1233) to4d0d9445a; #14686 ×2 (:3037,:3194) to279431e7a; #14662 ×3 (:4510,:5070,:5293) to35dffeacedata/analytics.zod.ts:404,:407,:485) to2306a765cPer-file counts at base equal the claim's (9 and 3, from stage 6's census). A second instrument agrees site for site: every
#Nin the two files, classified by the TypeScript parser, and each of the 84 distinct numbers of 100 or more probed by RESTissues/Nwithout following redirects (the other 3 are the ordinalsPrime Directive #12,batch #23,batch #57).kernel/manifest-unknown-keys.test.tsandpackages/cli/src/utils/lower-callables.test.ts.filters+recordIdtogether, which spec refuses #16714, check-react-blocks-declaration-parity.ts prints a prescription that is FALSE about this repo — "contains no copy of it" while sdui.manifest.json is checked in at the root; it made a dev declare a runnable gate NOT MEASURED #16715 and feat(spec): export the object-level refinement checks the mirrored UI schemas run (#16489) #16697 answered 404 at every checkpoint.Why each anchor decides its line
Each sha resolves uniquely, is an ancestor of
origin/main(and of the base), and has one parent. No file underdocs/adr/**,docs/NORTH-STAR.mdorscripts/adr-anchors/names any of the six numbers or records these rules, so each takes the commit rung, as stages 1–6 did.defineStack({ themes })is parsed, ingested and never applied — the last hop from a storedthemeitem to the theme engine does not exist #10485 to35ad101bc(:415,:1023): retires thethemescarrier key andThemeSchemaunder ADR-0049. Its message records the ruling, "Ruled B (退役授权面, 2026-08-21)", and its ownstack.zod.tsdiff wrote both lines.:415keeps ADR-0049 and the ruling in its words; the D3 entrystack-themes-carrier-retiredit names on:423is unchanged. This is the anchor stages 1, 5 and 6 used for the same retirement.functions: [{ name, handler }](the array form) cannot surviveobjectstack buildeither — #4343/#4976 both fixed only the map form #6238 toc8d6f6e08(:633): widens the array member offunctionsso itshandleralso takes the lowered string ref, which is the fix forobjectstack buildrefusing its own array output. Its message namesfunctions: [{ name, handler }](the array form) cannot surviveobjectstack buildeither — #4343/#4976 both fixed only the map form #6238, and its own diff wrote the line.script的 config 契约要接入 #4277 的执行期 parse,先得有判别式(actionType)形态 #4343 andfunctions: { fn: { handler, effect: 'writes' } }cannot surviveobjectstack build— lowering emits a shape FlowFunctionEntrySchema rejects #4976 on the same line stay (both 200).ManifestSchemastrips unknown keys silently — anamespacetypo insidemanifest:parses green and the namespace comes outundefined#14192 to4d0d9445a(:1233): turnsManifestSchemaand its nested blocks intostrictObjectand flips the assembled-body strip pin to a refusal pin; each of its sub-commits namesManifestSchemastrips unknown keys silently — anamespacetypo insidemanifest:parses green and the namespace comes outundefined#14192. The line itself was written later byc78c9180de, whose own message says "ManifestSchemastrips unknown keys silently — anamespacetypo insidemanifest:parses green and the namespace comes outundefined#14192 closed ManifestSchema with strictObject", so the commit that closed it is the anchor.279431e7a(:3037,:3194): "defineStack refuses two actions that resolve to one scope-qualified runtime key". Its subject names feat(spec): defineStack refuses two actions that resolve to one scope-qualified runtime key #14686, and its diff addscollectDuplicateActionKeyErrorsand the changeset for that refusal. Both lines were written later by773a99960a(PR fix(spec): composeStacks carries each bound standalone action once in the composed object #15022), whose message describes the same "same-key rule, which runs before the merge".composeStacksconcatenatesactionsacross packages with no same-scope duplicate-key check — two packages each declaring oneglobal:NAMEcompose into one collapsed handler key #14662 to35dffeace(:4510,:5070,:5293): "composeStacks refuses two stacks whose actions resolve to one scope-qualified runtime key". It checks the composed set with the ruledefineStackapplies within one stack, with noactionConflictoption (maintainer ruling 2026-09-03). Its message does not namecomposeStacksconcatenatesactionsacross packages with no same-scope duplicate-key check — two packages each declaring oneglobal:NAMEcompose into one collapsed handler key #14662; its ownstack.zod.tsdiff wrote all three(#14662)lines.themeandanalytics_cubehave strict stack schemas but noUNREGISTERED_KIND_SCHEMASbinding —PUT /meta/theme/:namestill accepts any JSON (the two doors #6245 left open) #10194 to2306a765c(analytics.zod.ts:404,:407,:485): bindsanalytics_cube(andtheme) inUNREGISTERED_KIND_SCHEMAS, soPUT /meta/analytics_cube/:nameparses throughCubeSchema, and givesCubeSchemathe...MetadataProtectionFieldsspread. Its message namesthemeandanalytics_cubehave strict stack schemas but noUNREGISTERED_KIND_SCHEMASbinding —PUT /meta/theme/:namestill accepts any JSON (the two doors #6245 left open) #10194, and its own diff wrote all three lines. The[#10194]markers become[commit 2306a765c], the spelling stages 1 and 5 already use inkernel/metadata-type-schemas.ts.Mechanical proof
tokcmp.mjs: TypeScript 6.0.3 leaf tokens, JSDoc kinds excluded, controls mutate the head text in memory only). Base0f6dcac5e9against the head, 2 files, 17,249 base tokens:data/analytics.zod.ts): 0 (exit 0).stack.zod.ts, a declaration appended): DIFFER at token 15388 (exit 1).StringLiteralthe parser locates in each file): DIFFER at token 5 (exit 1), once per file.describe()positive control (the first.describe()string argument the parser locates:stack.zod.ts:133,analytics.zod.ts:244): DIFFER at tokens 507 and 442 (exit 1).stack.zod.ts+9/−9,data/analytics.zod.ts+3/−3; line counts equal at base and head (5344 and 853).PR #Nis on an added line. Net-removed: 12 sites, 6 numbers. The only numbers on added lines arescript的 config 契约要接入 #4277 的执行期 parse,先得有判别式(actionType)形态 #4343 andfunctions: { fn: { handler, effect: 'writes' } }cannot surviveobjectstack build— lowering emits a shape FlowFunctionEntrySchema rejects #4976, which stay on:633.rev-parse --disambiguateanswers 1 object for each.merge-base --is-ancestorexits 0 for each, againstorigin/main7510663c87and against the base; each is single-parent; the repository is not shallow.compose-stacks-refusal-envelopes.test.tscountsthrow new Error(, andcheck-stack-collection-maps.mjsandcheck-skill-top-level-keys.mjsread the declared collections and keys.Tests and gates (at head
cc0580d404)pnpm exec turbo run build --concurrency=2 --filter=./packages/* --filter=./packages/*/*underos-verify-lock: Tasks 71 successful, 71 total, VERDICT command-exit 0.pnpm --filter @objectstack/spec check:generatedunder the lock: all 15 generated artifacts up to date,check:docsovercontent/docs/references/**included; VERDICT command-exit 0. No reference page projects any of the 12 lines, so none is regenerated.vitest run --maxWorkers=2under the lock over the two files' own suites (src/stack*,src/compose-stacks*,src/define-stack*,src/assembled-package-body,src/data/analytics*,src/data/cube*): Test Files 35 passed (35), Tests 976 passed (976).src/, or carry one of these numbers, under the lock: Test Files 37 passed (37), Tests 759 passed (759).scripts/{category-title,dist-freshness,dist-freshness-adoption,file-description,strictness-ledger,strictness-ledger-doc,root-index,skill-map-guards,export-origins,split-entries,root-entry-type-nameability.pin},scripts/liveness/{evidence,tombstoned-row-status};src/type-alias-convention.pin,src/eager-entry-import,src/api/{api-entry-graph.pin,auth,export-job-family-retirement},src/ai/tool-confirmation-prescription-tense.pin,src/data/{currency-mode-family-closure.pin,external-lookup-retirement},src/identity/position-delegatable-enforcer.pin,src/integration/{connector-connection-timeout-retirement,connector-resilience-keys-retirement},src/security/rls-tags-retirement,src/shared/{alias-integrity,retired-key-migrate-sentence},src/system/{compliance-families-retirement,constants/platform-object-names,email-template-floor-locale-parity.pin,message-queue-retirement},src/ui/{action-requires-confirmation-docblock.pin,i18n,interaction-config-retirement,strictness-batch14},src/kernel/{manifest-unknown-keys,metadata-type-schemas}.scripts/{build-schemas-check-mode,def-key-collisions,openapi-self-consistency}(each rebuilds artifacts in a temp tree) andscripts/{check-generated-ledger,check-generated-fix-rebuild.pin}(read the ledger anddist). None reads comment text.pnpm --filter @objectstack/spec typecheckunder the lock: exit 0;check:test-typecheckOK (53 files / 251 errors / 138 pinned signatures held).eslint --no-inline-config --format jsonover the 2 files gives 2 files, 0 errors, 0 warnings.isPathIgnoredis false for both, read through eslint's API.eslint.config.mjs:327-328says type-aware linting is never enabled, so a comment edit cannot move an untouched file's verdict.pnpm lintis CI's.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands: 79 families derived and run, every one exit 0.--ranreads "79 derived, 79 run, 0 NOT-MEASURED, 0 UNRUN". Among them:pnpm check:issue-citations(self-test, 114 cases in 8 batteries) and the live diff-scopednode scripts/check-issue-citations.mjs: it judged the 2 citations on added lines,script的 config 契约要接入 #4277 的执行期 parse,先得有判别式(actionType)形态 #4343 andfunctions: { fn: { handler, effect: 'writes' } }cannot surviveobjectstack build— lowering emits a shape FlowFunctionEntrySchema rejects #4976, and both are live issues.pnpm check:doc-authoring: 16,804 customer-facing strings across 1,179 spec sources clean; the sibling baseline holds.pnpm check:stack-collection-maps: 8 enumerations reconciled against 31 declared collections.patchfor@objectstack/spec. Both files aresrc/**/*.zod.ts, whichfiles[]ships verbatim, and the rewritten docblocks reachdist: "posture: commit 4d0d944 closed" and "[commit 2306a76] This docblock used to say" are each in 2.d.ts, their old spellings in 0. Positive control: the unchanged neighbouring sentence "BY INHERITANCE — an undeclared key on one is REFUSED" is in the same 2.d.ts.merge-treeof the head ontoorigin/main7510663c87, from a bare shared clone, exits 0. The 3 commitsmaingained since the base touch neither file nor the citation or derivation scripts, and a re-derivation prints the same 79 commands. No merge was made.Hypotheses (measured first)
stack.zod.tsand 3 indata/analytics.zod.ts, equal per file to stage 6's census.Claim:on all 11pm:dispatchedcards. None names either file, except this card's own claim.describe()text carries one, and no test or script reads any of them by literal.check:docsis green with no regeneration.Deviations
Claude-SessionplusCo-authored-by: Claude); the pre-push trailer check passed on every push.Acceptance notes
What stays for later stages. The gate's census at this PR's head (base
0f6dcac5e9plus this PR) reads 248 dead sites (29 numbers) inpackages/spec/src. The onlypackages/spec/srcchangemainhas made since the base (#20610's migrations entry and registry) adds four live numbers and removes none, so 248 also stands at the tip7510663c87plus this PR:migrations/233: os migrate meta prints tracker numbers to the author: ADR-0087 migration entries' reason / replacement / acceptanceCriteria text carries ~2,060 of them, 178 dead, which AGENTS.md's runtime-string rule forbids #20233 edits the same entry files (PR fix(spec): os migrate meta guidance for twenty-four more migration-entry families states each lesson in words, not tracker numbers (stage 8) #20607 holdsmigrations/registry.ts).conversions/registry.ts12: PRs feat(spec)!: $empty joins FILTER_OPERATORS, and is_empty / is_not_empty lower to it (#20446) #20570 and feat(spec)!: retire the connector triggers array — the ConnectorTrigger shape nothing registered, polled or received (#20287) #20587 hold it.integration/connector.zod.ts1: PR feat(spec)!: retire the connector triggers array — the ConnectorTrigger shape nothing registered, polled or received (#20287) #20587 (automation: connector triggers start flows, and a connector action'sdescription/outputSchemareach the flow designer (7 keys) #20287).data/api-derivation.ts:163(DATA_ACTION_TO_API_OPERATION的batch: 'bulk'行在 #5856 之后没有生产者了 —— spec 注释仍称其为「runtime callData action」 #6259) andidentity/identity.zod.ts:230([finding] TheApiKeyreference table documents better-auth's apiKey-plugin schema — a plugin this platform does not load and a shapesys_api_keydoes not have #8715), 1 each: kept because tests read them by literal, so removing them is form D.Outside the gate's census: test files. The gate defers
*.test.ts. The same six dead numbers still stand at 15 comment sites and 10 test-title strings inpackages/spec/srctest files:data/analytics-strictness-batchd.test.ts:96(comment,themeandanalytics_cubehave strict stack schemas but noUNREGISTERED_KIND_SCHEMASbinding —PUT /meta/theme/:namestill accepts any JSON (the two doors #6245 left open) #10194) and its title:93. This file is in theanalytics*set stages 3 and 4 excluded while PR feat(spec)!: retire the inner name on cube measures and dimensions — the record key is the member's name (#20300) #20458 held it;analytics-date-range-two-bound-window.test.tsandcube-member-inner-name-retirement.test.tswere in that set too and are not re-measured here.compose-stacks-action-echo.test.ts:20,:34,:200(feat(spec): defineStack refuses two actions that resolve to one scope-qualified runtime key #14686) and titles:176,:224;compose-stacks-action-key-collision.test.ts:3(composeStacksconcatenatesactionsacross packages with no same-scope duplicate-key check — two packages each declaring oneglobal:NAMEcompose into one collapsed handler key #14662);stack-top-level-strict.test.ts:103(defineStack({ themes })is parsed, ingested and never applied — the last hop from a storedthemeitem to the theme engine does not exist #10485) and title:128;type-alias-convention.pin.test.ts:257,:1572,:1937(defineStack({ themes })is parsed, ingested and never applied — the last hop from a storedthemeitem to the theme engine does not exist #10485).shared/:metadata-collection.test.ts:250,metadata-url-spelling.test.ts:51,:72,:168(defineStack({ themes })is parsed, ingested and never applied — the last hop from a storedthemeitem to the theme engine does not exist #10485),:257(themeandanalytics_cubehave strict stack schemas but noUNREGISTERED_KIND_SCHEMASbinding —PUT /meta/theme/:namestill accepts any JSON (the two doors #6245 left open) #10194), title:254.automation/sync-retirement.test.ts:207(defineStack({ themes })is parsed, ingested and never applied — the last hop from a storedthemeitem to the theme engine does not exist #10485).kernel/:manifest-unknown-keys.test.ts, four titles (ManifestSchemastrips unknown keys silently — anamespacetypo insidemanifest:parses green and the namespace comes outundefined#14192);metadata-type-schemas.test.ts:422, a title (themeandanalytics_cubehave strict stack schemas but noUNREGISTERED_KIND_SCHEMASbinding —PUT /meta/theme/:namestill accepts any JSON (the two doors #6245 left open) #10194).shared/andautomation/through the gate's census, which never lists a test file, so test-file comment lines there may carry other dead numbers as well. That wider population is not measured here.Outside
packages/spec/src. The same six numbers stand at 44 more sites (packages/{metadata-protocol,objectql,rest,runtime,cli,core,metadata,qa},examples/,scripts/,packages/spec/scripts/), and at 19 sites inmigrations/(the #20233 area).Rung. The #10485 retirement also has the ADR-0087 D3 entry
stack-themes-carrier-retired, which:423already names. This PR takes the commit rung, as stages 1–6 did.Wording, each true of its commit.
:3037and:3194now read "commit 279431e's same-key refusal": the refusal that commit added, in lines773a99960awrote.:1233reads "commit 4d0d944 closedManifestSchema", in a linec78c9180dewrote.Generated by Claude Code