Skip to content

feat(gates): check:self-test-wired audits every root alias that declares a --self-test (#21351) - #21366

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-21351-self-test-wired-alias-population
Oct 2, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-21351-self-test-wired-alias-population

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #21351
Clause-②: no

What changes

scripts/check-self-test-wired.mjs gains the population source triage directed (comment 5947041331): every root package.json alias whose expansion carries --self-test. The existing gate is widened to its stated purpose. No gate is added and no workflow is edited.

  • Membership (collectAliasDeclarations). A root alias is a member when its expansion self-tests a script. The expansion goes through the same expandAlias the workflow half already uses, so pnpm ALIAS chains count. A root alias is also a member when its own text has a right-bounded --self-test that the anchor cannot attribute to a script path. The roster is computed inside collectPopulation from the one parse of package.json that the workflow extraction already does. package.json is not read a second time.

  • Verdict (auditAliasPopulation). For each member, every script its expansion self-tests must be run with --self-test by some workflow step or composite action. That can happen by path, through this alias, or through any alias a step names. An existing SELF_TEST_RUN_OTHERWISE row also counts. When neither holds, the gate reports [alias-self-test-not-run] pnpm ALIAS, which names the alias and the unrun script(s). A literal the anchor cannot attribute is reported as [alias-self-test-unattributed].

  • Allowlist (SELF_TEST_ALIASES_OUTSIDE_CI, rows of { alias, why }, in the gate's own file). The list is empty: no alias qualifies, because all 170 are wired. auditAliasAllowlist reports a row as red in four cases:

    • it has no reason;
    • its alias no longer exists;
    • its alias's expansion no longer carries --self-test;
    • a workflow now runs every self-test the alias declares.

    The remedy text refuses the allowlist as a way to clear a finding. Judging whether a row is legitimate is marked ⛔ MAINTAINER-ONLY, following the existing ledger's wording.

  • Floor. refusalFor refuses a reading whose alias roster is empty or absent ("the alias reader is broken"). This is the last arm, so the sibling gate's refusal pins still hit the arms they hit before.

  • ⛔ population is unchanged. That export means "the self-tests CI runs", and check-self-test-workflow-commands.mjs spawns every member of it. An alias that CI does not run has no place in that sweep. The sibling's scope line still reads 232.

Measured: objectstack main 23365ea (base) against this head 98d23b3

Roster. 170 root aliases carry --self-test, and for every one of them each self-tested script is run with the flag by a workflow step:

  • 153 aliases are named by a workflow directly.
  • For the other 17, the script's self-test is run by path or through another alias. For example, check:adr-links is not named, but check-links.yml runs node scripts/check-adr-links.mjs --self-test.
  • No alias is unwired and no literal is unattributable, so the allowlist is empty.
  • No current alias turned red, so no workflow edit is needed.

Probes. These are production runs of the gate on scratch copies of scripts/, .github/workflows, .github/actions and package.json. Only the gate file differs between the two columns: base blob c36931a5fe against head blob 89cf1f5914.

tree base gate this gate
unmodified exit 0 exit 0
plus alias check:pm-probe-unwired = node scripts/pm/measurement-claim-triage.mjs --self-test, named by no step exit 0 exit 1, [alias-self-test-not-run] pnpm check:pm-probe-unwired
lint.yml step Issue-transfer door self-test (pnpm check:pm-issue-transfer) deleted exit 0 exit 1, [alias-self-test-not-run] pnpm check:pm-issue-transfer

Pins

The new battery declared alias population has 22 cases. The battery registry floor goes from 11 to 12.

  • Deleting the only step that runs an alias goes red and names the alias. This is pinned two ways:
    • on pure inputs;
    • end to end through collectPopulation on a fixture tree. That case also asserts that the old named-population audit stays silent on the same tree, so the hole and the repair are in one case.
  • An allowlisted alias stays green. The row excuses only its own alias: a second alias that declares the same script still goes red.
  • The current roster passes. This is the production run on this tree, which exits 0. lint.yml runs it on every PR. A self-test cannot hold a tree verdict.
  • Stale allowlist rows go red: a row for an alias that is gone, one whose expansion lost --self-test, one whose self-tests a workflow now runs, and one with no reason.
  • Crediting routes stay green: an alias a step names, a chain alias a step names, a script self-tested by path, and a script with a SELF_TEST_RUN_OTHERWISE row.
  • Partial wiring still goes red. A step running the script without the flag goes red. A two-script alias with one script unrun names exactly that one.
  • Membership: chain aliases are members; --self-test-extra is not the flag.
  • Unattributable literals go red: node scripts/l.mjs --json --self-test reports the alias as unattributed, while the attributed spelling beside it does not.
  • the exported population battery: it gains the empty and absent roster refusal, and its floor goes from 8 to 9.
  • Composite-action fixture: its package.json now carries one self-test alias, because the shared reading refuses a tree that has none. No step names that alias, so both of the battery's controls are unchanged.

Ablation

Both legs ran on the committed state 98d23b3 through scripts/ablation-replace.mjs. For each leg:

  • the anchor went from 1 hit to 0;
  • the blob changed;
  • the restore was proven: the blob equals the HEAD blob 89cf1f5914 and git diff HEAD is empty.
leg mutation --self-test production, this tree production, deleted-step tree
verdict const unrun = scripts.filter(...) becomes const unrun = [] exit 1 (5 cases, including the deleted-step pin) exit 0 exit 0
membership the roster's membership condition becomes false exit 1 (11 cases) exit 1, refused: alias reader is broken exit 1, refused

The first leg is the reason this matters. With the verdict ablated, production on this tree and on the deleted-step tree are both green. --self-test is therefore the only instrument for this rule, and lint.yml runs it with the flag.

Gates (head 98d23b3; patch round 2 at 03d397c714: 57 derived, 57 run, all exit 0, reported in 5948256806)

Derivation: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands, with no paths. It gives 29 commands, set-equal to the seat's lead. All 29 exit 0:

  • check-ci-filter-parity (run only, not edited)
  • check-closing-keyword-parity, production and --self-test
  • check-comment-mask-corpus
  • check-declaration-mirrors, both modes
  • check-scripts-symbol-anchors, both modes
  • check-self-test-wired, both modes
  • check-self-test-workflow-commands, both modes, population 232
  • check-whole-set-label-write, both modes
  • pm/bare-root-worklist --self-test
  • pnpm check: agent-test-spelling, bash32-floor, cli-command-ids, cross-package-test-inputs, driver-memory-census, entry-guard, gitlink-declared, nul-bytes, parse-guard, pm-dispatch-gates, pnpm-filter-targets, ratchet-remedy-authority, refd-timer-probe, watch-hint-literal

check:ratchet-remedy-authority still classifies this file as refused, with no live offer. The counts are the same as on base: 15 marked, 5 refused.

No package is touched, so no build closure, package tests or typecheck are owed. The changeset is skipped because the change is under the repo-root scripts/: the root package is private and nothing is published.

Acceptance notes

  • Four passages this change made false are corrected in this PR (patch round 2, 03d397c714, comments only; the seat's ruling on the dev's open question, per 5948256806). They are three lint.yml comment blocks (the Issue-transfer door self-test step, the stale-finding sweep step, and the import-prerequisite module step) and the scripts/check-system-context-census.mjs header paragraph. Each now states the declared-alias population. A probe backs each sentence: deleting the stale-finding step, or both census invocations, now reds. No step, run: line or code moved: yaml.parse of the two lint.yml versions is deep-equal, and the census script's comment-masked code is identical. (Seat edit.)
  • Scope is the root package.json, as the card set it. An alias in a package's own package.json is outside this population.

Generated by Claude Code

claude added 2 commits October 2, 2026 07:17
…res a --self-test

The gate's population started from what a workflow names, so a root
package.json alias whose expansion carries --self-test and that no step
runs sat outside every audit: deleting the only step that ran one stayed
green. Every such alias is now a member, read from the same package.json
parse and through the same expandAlias the workflow half uses. Each must
have its self-test run by some workflow step (directly, through the
alias, or through any alias a step names), or carry a
SELF_TEST_ALIASES_OUTSIDE_CI row with its reason. The allowlist is empty:
all 170 declaring aliases are wired today.

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

Iterate the roster by sorted key, reflow the remedy line, and anchor the
"170 aliases" reading to the tree it was taken on.

Claude-Session: https://claude.ai/code/session_01UtnxvdiN376GF3sgXwAw4d
Co-authored-by: Claude <noreply@anthropic.com>
…on falsified

Three lint.yml comment blocks and one check-system-context-census docblock
described the gate's population as only the scripts a workflow names. Since
the declared-alias source, the gate holds the issue-transfer and
stale-finding steps through their aliases, and it reds when both
system-context-census invocations are deleted. Comment-only; no step, run
line or code moves.

Claude-Session: https://claude.ai/code/session_01UtnxvdiN376GF3sgXwAw4d
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added the ci/cd label Oct 2, 2026
@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 2, 2026 08:53
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 2, 2026 08:53
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 2, 2026
Merged via the queue into main with commit 43e928d Oct 2, 2026
40 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-21351-self-test-wired-alias-population branch October 2, 2026 09:32
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…its that decided them (stage 5 of objectstack-ai#20595) (objectstack-ai#21375)

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

## What changed

Stage 5 of the `domain:engine` lane of the dead-citation sweep:
`packages/drivers/driver-memory/**`, comment and docblock prose only,
per the claim (`5947712781`). Stages 1 to 4 landed as `a7d9768ec`,
`d150c3039`, `4bf4e7e70` and `13a24ece2`; objectstack-ai#20595 stays open for the next
stage.

Every comment or docblock site in the package that cited a tracker
number answering 404 is rewritten in ruling C+D's form C (record
`5749154545` on objectstack-ai#19123): the ADR when one records the decision,
otherwise the commit in this repository's history that made it. That is
**104 sites on 103 lines in 27 files, covering 13 numbers**:

- **59 census sites** (59 lines, 5 files under `src/`): the whole
`allocated-but-absent` population of the gate's own census in this
package at the base;
- **4 sites on 3 lines outside the census glob, inside the claimed
surface**: `objectstack.config.ts:19` (objectstack-ai#11332) and `:26` (objectstack-ai#10724), and
`tsconfig.typecheck.json:11` (objectstack-ai#11332 and objectstack-ai#10724 on one `//` line);
- **41 test-comment sites** (41 lines, 20 test files), which the census
defers. They carry 11 numbers: 6 the census itself reads as dead in this
package's `src`, 2 it reads as dead elsewhere in the repository (objectstack-ai#6075,
objectstack-ai#17590), and 3 it never judges because they stand only in test files
(objectstack-ai#14434, objectstack-ai#16838, objectstack-ai#17348), which the board and a single read each settle.

No comment-id citation in the package is dead (see Census).

**Anchors: 13 numbers by commit, 0 by ADR, 0 by words alone.** 11
numbers reuse the anchor another lane or stage already measured for
them; 2 were measured here (objectstack-ai#14435, objectstack-ai#16838). 13 distinct shas. No ADR or
ruling record names any of the 13 for this subject: `git grep` over
`docs/adr` and `scripts/adr-anchors` finds only ADR-0088's correction
row for objectstack-ai#10724, which is about `contributes.functions`, not the
`contributes.drivers` retirement cited here.

Only comments changed. Every file keeps its line count (103 lines out,
103 in, plus the changeset), so no line citation into any of them moves.
No code token moves (the guard below). **No citation number is added**:
on every changed line, the numbers on the new text are a subset of those
on the old, and the diff-scoped gate judged the 7 citations left on
changed lines: all 7 resolve.

**A `patch` changeset**: 20 of the 62 rewritten non-test lines are in
the published `dist` (the `.d.ts` keeps JSDoc on exported members, and
esbuild keeps some comments in the JS), and `dist` is not byte-identical
with the base text (see Changeset).

## H0: the largest remaining `domain:engine` package

The gate's own `node scripts/check-issue-citations.mjs --census --json`
at base `5fd4855a9` (the before run below), `allocated-but-absent` per
package over every `domain:engine` family in
`.claude/skills/pm-dispatch/SKILL.md`'s lane table:

| package | sites |
|---|---|
| `drivers/driver-memory` | **59** |
| `metadata` | 41 |
| `core` | 40 |
| `platform-objects` | 27 |
| `metadata-core` | 19 |
| `drivers/driver-turso` | 14 |
| `drivers/driver-mongodb` | 9 |
| `formula` | 4 |
| `metadata-fs` | 2 |
| `drivers/driver-sqlite-wasm`, `plugins/plugin-pinyin-search`,
`objectql`, `metadata-protocol`, `drivers/driver-sql` | 0 each |

`driver-memory` is still the largest (lane total 215), so this stage
went ahead.

## Census: `driver-memory`, before and after

**Instrument (A1).** 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
`packages/drivers/driver-memory/`.

| reading | tree | board | whole-repo `allocated-but-absent` | sites |
lines | files | numbers |
|---|---|---|---|---|---|---|---|
| before | base `5fd4855a9`, run 07:54:54Z to 07:58:32Z | enumerated,
192 pages, frontier objectstack-ai#21366, 19,187 records (newest number read before
and after the run: objectstack-ai#21366) | 330 | **59** | 59 | 5 | 6 |
| after | `17f667f86`, run 08:13:08Z to 08:17:18Z | enumerated, 192
pages, frontier objectstack-ai#21370, 19,191 records (newest before and after: objectstack-ai#21370)
| 271 | **0** | 0 | 0 | 0 |

The whole-repo drop is 59, and the two finding sets differ by exactly
the 59 rows of this package, removed; none was added. `resolves`
(34,880), `resolves-as-pull-request` (2,378) and `cross-repo-unjudged`
(1,165) did not move. The card's 62 was taken at `f11b5f20a2` with the
older extractor; the base here reads 59. The head's later commits are
the changeset (outside the census surface) and one merge of `main` that
touches no file under `packages/drivers/driver-memory`.

**Supplementary instrument, the whole package.** The census reads
neither test files nor strings nor files outside `src`. A second reading
runs the gate's own exported `extractCitations` (whole-file and
comment-prose projections) over every tracked file in the package (89:
82 `.ts`, `package.json`, `tsconfig.json`, `tsconfig.typecheck.json`,
`README.md`, `CHANGELOG.md`, `LICENSE`, `objectstack.config.ts` among
the `.ts`), and classifies each citation with the gate's
`classifyCitation` against one board enumerated by the gate's
`enumerateBoard` (192 pages, frontier objectstack-ai#21369, 19,190 records, 07:59:39Z
to 08:03:13Z). Every one of the 13 numbers in the population was then
read on its own over the issues endpoint (08:04Z): **all 13 answer
404**, and the lit controls `objectstack-ai#5286` and `objectstack-ai#12624` answer 200.

| reading | citations | dead | src comment | test comment | other
(`tsconfig.typecheck.json`) | test string | changelog |
|---|---|---|---|---|---|---|---|
| before, `5fd4855a9` | 1,720 | **143** | 61 | 41 | 2 | 21 | 18 |
| after, `17f667f86` | 1,616 | **39** | 0 | 0 | 0 | 21 | 18 |

`src comment` here is the 59 census sites plus the 2 in
`objectstack.config.ts`. The drop of 104 citations is exactly the
rewritten sites, and the live counts did not move (src comment: 494
resolve, 13 as pull requests, 1 cross-repo; test comment: 504, 20 and
4). A third, raw reading (every `#` followed by 2 to 6 digits, whatever
surrounds it) counts 1,736 before and 1,632 after: a drop of 104.

**Comment ids.** Three distinct comment-id citations stand on 6 sites in
this package: `5234868334` (1 site), `5861435168` (2) and `5865693155`
(3). Each was read over the issue-comments endpoint and answers 200
(control `5947712781`, 200). So no comment id is in the population.

## Per-number table

`src` counts the census sites plus the four outside the census glob;
`test` counts test-comment sites. Every sha below matches exactly one
commit (`git rev-parse --disambiguate`, count 1) and is an ancestor of
the base `5fd4855a9` (`git merge-base --is-ancestor`, exit 0 for all 13
shas; the clone is not shallow). The `+` lines introduce exactly these
13 nine-hex spans and no other. The message or the diff of each one
names the number it replaces: 6 in the message and the diff
(`56c093c4d`, `0f38ab084`, `9dac1ae01`, `be21955ba`, `e04a0aff2`,
`d367f03d6`), 6 in the diff alone (`555a89cbd` 28 added lines,
`20950404c` 7, `dce5cd4f0` 26, `51efbf116` 4, `20032594f` 8, `b90aff81f`
9), and 1 only in the subject's squash suffix (`93940d492`, where objectstack-ai#14434
was that pull request's own number). Each anchor touches
`packages/drivers/driver-memory` itself, except `e04a0aff2`, whose two
sites name the `driver-sql` fixture it created. `git blame` at the base
puts 67 of the 104 sites on their anchor. The other 37 were written by a
different commit that cites the number as another change: for objectstack-ai#13197,
the declared-index commit `b7f645a8a` and the atomicity commits
`34e80993f` and `4642f4c64`; for objectstack-ai#13195, the clobber generalisation
`df1812050`, the reference-matcher retirement `8fec76a2b`, and two
commits from while that card was open (`178325bcb`, `9268aec56`, see
Wordings to check). In each case the anchor is the commit that made the
change the sentence credits to the number. `source` says whether another
lane or stage already used this anchor for this number (`reused`) or it
was measured here (`measured`).

| number | src | test | anchor | kind | source | what it decided |
|---|---|---|---|---|---|---|
| `objectstack-ai#6075` | 0 | 1 | `d367f03d6` | commit | reused (stage 4) | five
drivers' query parameters follow `DriverQuery` |
| `objectstack-ai#10724` | 2 | 0 | `be21955ba` | commit | reused (cli lane, the
eleven-package sweep `aaad682dbc`, spec) | retire the nine dead
`contributes` members, `drivers` among them, with driver-memory's
manifest corrected in the same change |
| `objectstack-ai#11065` | 2 | 3 | `20950404c` | commit | reused (stage 4) | count a
boolean aggregand as 1/0 in avg and sum, on driver-memory's data face
and analytics face |
| `objectstack-ai#11332` | 2 | 0 | `dce5cd4f0` | commit | reused (cli lane, the
eleven-package sweep, spec) | retire the plugin manifest's dead
top-level containers (`capabilities`, `configuration`, `extensions`),
driver-memory's manifest included |
| `objectstack-ai#13195` | 8 | 11 | `9dac1ae01` | commit | reused (spec stage 4) |
`$exists` means HAS A VALUE on the live mingo path, the analytics face
and `translateFilter`, with the lowered `$exists` promoted to its own
`$and` branch when its key is taken; its changeset records the
maintainer's 2026-08-30 ruling |
| `objectstack-ai#13197` | 16 | 11 | `56c093c4d` | commit | reused (stage 3, the
types, rest and spec lanes) | driver-memory enforces field-level
`unique`, refusing a colliding write with the ADR-0112 envelope;
`updateMany` checks every row before mutating any |
| `objectstack-ai#14434` | 0 | 1 | `93940d492` | commit | reused (stage 4) | un-mask
driver-memory's published `update` / `upsert` types (that pull request's
squash) |
| `objectstack-ai#14435` | 3 | 3 | `20032594f` | commit | measured | publish the
declared return types on `find()`, `findOne()` and `create()` |
| `objectstack-ai#16589` | 23 | 3 | `555a89cbd` | commit | reused (the services lane's
pointer `5904005321` on this card) | refuse a tenant-scoped call instead
of returning cross-organization rows: seam 3,
`assertCallNotTenantScoped` |
| `objectstack-ai#16729` | 7 | 1 | `0f38ab084` | commit | reused (stage 4) | an
explicit tenancy opt-out survives a partial `syncSchema`
re-registration, on driver-memory and driver-sql |
| `objectstack-ai#16838` | 0 | 4 | `b90aff81f` | commit | measured | read a stored
ARRAY as its elements in the equality arm, so both filter faces answer
one filter one way |
| `objectstack-ai#17348` | 0 | 1 | `51efbf116` | commit | reused (spec stage 3) |
type-gate a text operator over a declared temporal column; it pinned
driver-memory's divergence by name and recorded the follow-up as filed |
| `objectstack-ai#17590` | 0 | 2 | `e04a0aff2` | commit | reused (stage 4) | compile
`$contains` on a JSON column as a per-dialect MEMBERSHIP test; it
created the `driver-sql` fixture these two sites copy |

## Wordings to check

Most rewrites swap a tag in place (`[#N]` to `[commit SHA]`, `(#N)` to
`(commit SHA)`, `#N's X` to `commit SHA's X`, `since #N` to `since
commit SHA`), the form the landed stages use. These say more than the
tag:

- **`objectstack-ai#17348`** (`memory-driver-filter-logic-conformance.test.ts:598`):
「the repair is in the driver, and it is objectstack-ai#17348's, filed」 became 「the
repair is in the driver, named as owed in commit 51efbf1 and filed」,
with `:599` (「against objectstack-ai#5499's investment freeze.」) untouched.
`51efbf116` wrote this note, and its changeset records 「That divergence
is filed as objectstack-ai#17348 and pinned by name」.
- **`objectstack-ai#13195` where it was written while that card was open**
(`memory-matcher-no-value-negated-operators.test.ts:32`): the sentence
says `$exists` 「is the neighbouring cell」 and 「still read[s] it as
key-presence」, and `:47` already calls that caution history. A bare swap
would credit `9dac1ae01` with a still-open divergence, so it reads 「(the
card commit 9dac1ae closed)」. The other site written while that card
was open, `memory-exists-has-value-faces.test.ts:4`, is the file's title
from the pin `9268aec56` took before the fix; the header under it says
the file 「is no longer a pin of a divergence」 but was inverted in place,
and `9dac1ae01` is the commit that inverted it, so a tag swap reads true
there.
- **A quotation of this note's own earlier text**
(`memory-driver-document-not.test.ts:239`-`:241`, 「It used to say "… the
direction is now objectstack-ai#13166's and objectstack-ai#13195's to settle."」): the dead number
inside the quote became a bracketed editorial substitution, 「[commit
9dac1ae]'s」, so the quote stays recognisable as a quote and says where
the edit is. It is the note quoting itself, not a ruling.
- **Where the number named the defect, not the change**: 「(this is the
objectstack-ai#16589 defect)」 became 「(this is the defect commit 555a89c fixed)」
(`memory-tenancy-guard.ts:271`); 「This is the objectstack-ai#16589 answer」 became
「This is the defect's answer (fixed in commit 555a89c)」
(`memory-tenant-scope-refusal.test.ts:139`); and in that file's reading
table (`:29`) the cell 「the objectstack-ai#16589 defect」 became 「what commit 555a89c
fixed」, which fits the column's 28-character width, so the table stays
aligned and no row is added.
- **Slash and list pairs**: 「objectstack-ai#13197/objectstack-ai#13239」 became 「commit 56c093c and
objectstack-ai#13239」 (two sites); 「objectstack-ai#13197 / objectstack-ai#13239」 became 「commit 56c093c /
objectstack-ai#13239」; 「[objectstack-ai#13197, objectstack-ai#13239]」 became 「[commit 56c093c, objectstack-ai#13239]」 (four
sites); 「(objectstack-ai#11332 / objectstack-ai#10724 / objectstack-ai#4914)」 became 「(commit dce5cd4 / commit
be21955 / objectstack-ai#4914)」, the exact form the eleven-package sweep
(`aaad682dbc`) gave the same sentence in `plugin-hono-server`.
- **`driver-sql`'s fixture**
(`memory-20874-contains-membership.test.ts:63`, `:110`): 「`driver-sql`'s
objectstack-ai#17590 fixture」 / 「assertions」 became 「`driver-sql`'s commit e04a0af
fixture」 / 「assertions」; `e04a0aff2` created
`sql-driver-17590-json-column-membership.test.ts`.
- **Dated rulings keep their date**: the `objectstack-ai#13195` sites that say 「ruled
… 2026-08-30」 keep it (`9dac1ae01`'s changeset and docblocks carry 「the
maintainer ruled on 2026-08-30」).
- **Capitalisation**: where the number opened a sentence, the new text
opens with 「Commit」 (five sites).
- No line was reflowed, so some are longer than their block's wrap
(`eslint.config.mjs` declares no line-length rule, and a reflow would
move neighbouring lines and every line citation into the file).

## Sites left

- **In comments (src, test, the two config files): none.**
- **String literals: 21 test-string sites, 7 numbers, 11 files**
(`describe` and `it` titles, assertion arguments): `objectstack-ai#13195` 7, `objectstack-ai#13197`
7, `objectstack-ai#11065` 2, `objectstack-ai#16838` 2, `objectstack-ai#14435` 1, `objectstack-ai#16589` 1, `objectstack-ai#16729` 1. Every one
of the 7 is in this stage's population, so the table above holds an
anchor for each. Non-test strings carry none. Strings are outside this
stage's surface.
- **Outside `src`:** the release-owned `CHANGELOG.md` names dead numbers
on 18 sites (11 numbers); left. `package.json`, `tsconfig.json`,
`README.md` and `LICENSE` cite no dead number.

## Mechanical guard: no code token moves

The guard (stages 2 to 4's) compares base `5fd4855a9` against the
working tree over all 27 touched files, with TypeScript 6.0.3
(`tsconfig.typecheck.json` through `ts.parseJsonText`, the same two
walks):

- **Reading 1**: the parser's leaf nodes, from a `forEachChild` walk.
Comments are trivia there, and JSDoc is never visited. A leaf that is
not itself a token is re-scanned with trivia skipped.
- **Reading 2**: the full token stream in parser context, from a
`getChildren` walk, JSDoc nodes skipped. String, template and numeric
literals are compared in full on both readings.

Results:

- Real run at the head: 61,808 base tokens, **0 files with a token
change** (exit 0).
- Comment control (「The declaration dies with the table.」 to 「DIES」,
`memory-driver.ts`): 0 files changed (exit 0).
- Positive control, an identifier (`tenantFieldOf` to `tenantFieldOfX`,
`memory-unique-constraint.ts`): DIFFER on both readings (exit 1).
- Positive control, a string literal
(`'MEMORY_MULTI_TENANT_UNSUPPORTED'` to `…X`,
`memory-tenancy-guard.ts`): DIFFER on both readings (exit 1).
- Positive control, a numeric literal (`UNIQUE_VIOLATION_STATUS = 409`
to `410`, `memory-unique-constraint.ts`): DIFFER on both readings (exit
1).
- Positive control, a JSON value (`"rootDir": "."` to `"./"`,
`tsconfig.typecheck.json`): DIFFER on both readings (exit 1).

Each mutation went through `scripts/ablation-replace.mjs` (wrap mode,
anchor hit 1 to 0) under a shell trap that restores by absolute path
from `HEAD`. Each restore was proven equal to its `HEAD` blob
(`ac823bf8fa9b`, `eb0eb11358ea`, `7b008c1f12b7`, `eb0eb11358ea`,
`88035b359542`), with `git diff HEAD` empty and a clean tree afterwards.

## Changeset: `patch` (`dist` measured)

`files[]` is `dist`, `README.md` and `CHANGELOG.md`, and the package is
not private. The dependency closure and the package were built first
(`turbo run build --filter='@objectstack/driver-memory^...'
--filter=@objectstack/driver-memory`, 5 of 5 tasks, VERDICT command-exit
0). Then the package's own `build` (tsup plus `check-dts-emitted`) ran
three times in one script under the shared verify lock (VERDICT
command-exit 0), at `17f667f86`:

- **Leg 1**, the head text: 6 `dist` files hashed. Of the 62 rewritten
non-test lines, 20 appear verbatim in `dist`: 11 from
`memory-driver.ts`, 4 each from `memory-tenancy-guard.ts` and
`memory-unique-constraint.ts`, 1 from `memory-analytics.ts`; in
`index.d.ts` / `index.d.mts` (16) and `index.js` / `index.mjs` (10).
- **Leg 2**, the base text put back in the 7 non-test touched files (7
of 7 proven equal to their base blob): 4 of the 6 files differ from leg
1 (`index.d.ts`, `index.d.mts`, `index.js`, `index.mjs`), and
`scripts/ablation-dist-preflight.mjs` finds the base marker 「[objectstack-ai#16729]
One driver's record」 in 2 built files (exit 0).
- **Leg 3**, after the proven restore (7 of 7 equal to their `HEAD`
blob, `git diff HEAD` empty, porcelain empty): all 6 files are
byte-identical to leg 1, and the preflight's `--absent` reading exits 0,
so the build is deterministic and the difference is the rewrite.

So the rewrite ships, and
`.changeset/20595-driver-memory-provenance-anchors.md` declares a
`patch` for `@objectstack/driver-memory`, comment text only, with the
claim's `Clause-②: no` line. The later changeset commit and the merge of
`main` touch no file under `packages/drivers/driver-memory`.

## Gates (head `381dddc9c`)

- **Derived gates:** `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` at `381dddc9c` (28 paths against
merge base `fbe2debb7`, 220 changed lines) derived 62 commands. All 62
ran, each exit code captured before any pipe: 62 exit 0. `--ran` reports
「62 derived, 62 run, 0 NOT-MEASURED, 0 UNRUN」 (a derived zero) and exits
0.
- **Roster families the derivation flags under a touched directory**,
also run, each exit 0: `node scripts/check-changeset-fixed.mjs`, `pnpm
check:authz-resolver`, `pnpm check:error-code-casing`, `pnpm
check:filter-alias-parity`, `pnpm check:object-def-param-keys`, `pnpm
check:tenant-chokepoint`.
- **Named readings:** `node scripts/check-issue-citations.mjs` exits 0
(「every citation this change adds resolves」, 7 judged across 5 files);
`pnpm check:issue-citations` exits 0 (self-test, 173 cases, 9
batteries); `pnpm check:doc-authoring` exits 0 (the sibling-package
prose-id baseline holds, no growth); `pnpm check:nul-bytes` exits 0
(9,669 files, no raw control bytes), and a control-byte grep over the 28
changed files finds none (exit 1).
- **Build before the gates:** `turbo run build --filter='./packages/*'
--filter='./packages/*/*' --concurrency=2` at `381dddc9c`, 71 of 71
tasks, VERDICT command-exit 0.
- **Tests and typecheck, under the verify lock, at `381dddc9c`:** `pnpm
--filter @objectstack/driver-memory test`: 70 test files pass (70),
1,718 tests pass (1,718), exit 0; `pnpm --filter
@objectstack/driver-memory typecheck` (both programs, `tsconfig.json`
and `tsconfig.typecheck.json`) exits 0. `tsc --listFiles` puts all 70
tracked test files and the 25 changed `src` files in the `tsconfig.json`
program, and `objectstack.config.ts` in the `tsconfig.typecheck.json`
one.
- **Lint, as a proven narrowing:** eslint with inline config disabled,
over the 26 touched `.ts` files plus `dist/index.js` as the control: 27
results, 0 errors and 1 warning, the control's ignore notice; none of
the 26 is reported ignored. `eslint.config.mjs` never enables type-aware
linting (its lines 327-328 say so), so a comment edit cannot move the
verdict on an untouched file. `tsconfig.typecheck.json` is not an eslint
target (run alone, eslint answers 「File ignored because no matching
configuration was supplied」); the token guard covers it. The repo-wide
`pnpm lint` is CI's run.

## Acceptance notes

- **Base.** The branch was cut at `5fd4855a9` and merged with `main`
once, at `fbe2debb7`, before the gates (merge `381dddc9c`, no conflict,
no file under `packages/drivers/driver-memory`, and neither
`check-issue-citations.mjs` nor `dispatch-gates.mjs`). `45ce12a48` (the
last `driver-memory` change before this stage) is in the base
(`merge-base --is-ancestor` exit 0; control `13a24ece2`, exit 0). The
net diff against `main` is the 27 rewritten files (+103/−103) and the
changeset.
- **One sentence keeps a deictic whose antecedent was the number.**
`memory-exists-has-value-faces.test.ts:264` says 「not in the card」 two
lines under the tag that named that card, now `[commit 9dac1ae]`. It
has no number of its own, so it was not touched.
- **Wording only:** no line without a number was changed.

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

---------

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

ci/cd size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants