Skip to content

fix(cli): the per-package de-duplication key ignores the top-level collection index, so an echo no longer survives it - #18878

Merged
os-support-ai merged 6 commits into
mainfrom
claude/issue-18779-positional-dedup-key-echoes
Sep 18, 2026
Merged

os-support-ai merged 6 commits into
mainfrom
claude/issue-18779-positional-dedup-key-echoes

Conversation

@os-support-ai

@os-support-ai os-support-ai commented Sep 18, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #18779

The card's central reading, reproduced first

Measured on origin/main a43b9d0654 over the repo's own two-package fixture
examples/app-multi-package, os build --json exiting 0:

warnings: 4
  [0] field-no-consumers  object "crm_order" · field "account"    objects[0].fields.account
  [1] field-no-consumers  object "crm_order" · field "amount"     objects[0].fields.amount
  [2] field-no-consumers  object "crm_account" · field "industry" objects[1].fields.industry
  [3] field-no-consumers  package 'com.example.multi.core' —
                          object "crm_account" · field "industry" objects[0].fields.industry

[3] is [2], re-reported at the package-local index. Same rule, same entity,
same message; the only difference is the top-level collection index, which is
the one coordinate findingKey had no business comparing. 1 survivor, 1 echo,
0 genuinely new
— the card's reading holds, and the pass's strongest available
value statement was carried entirely by a duplicate.

Which option I took, and what the rejected one would have cost

I fixed the key, and the comments with it.

Fixing only the comments was the cheaper option and it was rejected on a
measurement, not a preference: the de-duplication exists so that "the author
cannot tell a real per-package finding from an echo" would stop being true, and
on the one fixture anyone can check, it was still true. Correcting the prose
would have left os build printing 4 author-time warning(s) for 3 distinct
ones, and left the repo with an accurate comment describing a filter that does
not filter. The cost of the option I took is that os build / os validate /
os lint report one fewer line on such a project, which is an observable change
and is why this carries a changeset.

Triage settled the blocker: the "os build output stays byte-identical"
constraint was #18769's PR contract, not a product contract, and its scope
ended with that PR.

Measured: exactly what stops being reported

Same fixture, all three doors, the key reverted and restored in place (on-disk
blob hash asserted both ways, tree verified clean after):

before after
os build --json warnings 4, exit 0 warnings 3, exit 0
os validate --json warnings 4, exit 0 warnings 3, exit 0
os lint --json total 4, failing 0, exit 0 total 3, failing 0, exit 0
os lint --json --strict total 4, failing 4, exit 1 total 3, failing 3, exit 1

The one line that stops being reported is the echo above. No input's verdict
moves
, and that is structural rather than a property of this fixture: every
finding the de-duplication drops has, by construction, a finding with the same
key already in the reported set — the seed is the union run's findings, which
every door reports, and it grows only with per-package findings that themselves
survived. os build already exits 1 on a union error before this pass runs,
and os lint --strict fails on errors + warnings, a count that could only
reach zero if the twin went unreported too.

Clause-②

Clause-②: no

Derived from the measured diff, not inherited. The dispatching claim left this
blank deliberately and expected yes (widening) on the reasoning "fewer
findings reported ⇒ --strict refuses less". Measured, --strict does not
refuse less: failing drops 4 to 3 and the verdict stays exit 1, because the
dropped line's twin is still counted. No accept set moves in either direction,
and the diff adds no schema key, closed-set member, published export or registry
entry. Declaration carrier: Clause-②-correction: 5723810598 on the card.

The falsified sentence — all carriers, re-derived

git grep "set the union could not see" on a43b9d0654 finds more than the
four the dispatch named. Source and tests, all corrected here:

file treatment
packages/cli/src/commands/compile.ts claim corrected, quote kept
packages/cli/src/commands/lint.ts claim corrected, quote kept
packages/cli/src/commands/validate.ts claim corrected, quote kept
packages/cli/src/utils/artifact-packages.ts claim corrected at the definition
packages/cli/test/lint-per-package-authoring-parity.test.ts claim corrected
packages/cli/test/lint-per-package-authoring-seam.test.ts claim corrected
packages/cli/test/validate-per-package-authoring-parity.test.ts claim corrected
packages/cli/test/validate-per-package-authoring-seam.test.ts claim corrected

Each keeps the sentence as a quotation being corrected rather than deleting
it, so the next reader meets the correction where they would have met the claim.

TWO pins were being held up by the echo

Both are the same shape and both were repaired the same way — by giving the
fixture a survivor the union genuinely cannot see, never by relaxing an
assertion.

1. validate-per-package-authoring-parity.test.ts (#18677). Its
non-vacuity case went red: the planted fixture's only per-package survivor was
the echo, so filtering it left the parity cases comparing two empty sets.

2. build-text-face-advisory-count.test.ts (#18780) — found by CI, not
locally, and the local gap was mine: packages/cli's test script is a bare
vitest run with no --project filter, so CI runs both projects, while I
had run --project unit plus only the two integration files this diff touches.
This file is in the integration project and was never collected. Its lit
control asserts the fixture reaches the per-package pass and leaves a survivor:

AssertionError: expected 0 to be greater than 0
  test/build-text-face-advisory-count.test.ts:239
  > the fixture reaches the per-package pass and raises a survivor there

bc_account.industry had no consumer anywhere, so the union raised it too and
the "survivor" was that finding re-reported at the package-local index. orders
now owns the view that displays it. ⛔ toBeGreaterThan(0) is untouched — it is
what stops #18780's equality pins from going vacuous.

Both preconditions now additionally assert the survivor's pedigree (the
field is named only behind the per-package prefix, and no union finding names
it), so neither control can be silently re-lit by a duplicate. The warnings: 4
reading in #18780's header is kept as the record of the defect it measured, with
a note that the same fixture reports 3 since this change.

What the key does not buy, measured rather than quoted

The key becomes position-insensitive, not collision-proof: two entries that
render the same where still share a key, exactly as they already did whenever
their indices happened to match.

That residue is measured, not sized by citing a neighbouring pin — which is the
move this card exists to correct.
packages/lint/src/data-model-rule-where-slot.test.ts holds something narrower
than "every rule names its entity in where": it fails any rule that puts a
bare config path in where. Measured instead over every example stack in
this repo that parses today (app-multi-package's built artifact, app-crm,
app-showcase, app-todo): 45 registry rules, 103 findings, 103 distinct
neutralised keys, 0 collisions
, on a43b9d0654.

Verification

  • Pin red before / green after. test/per-package-dedup-positional-echo.test.ts,
    key reverted to base in place: 2 failed | 4 passed (the ECHO and REAL_UNION
    cases). Key restored: 6 passed. On-disk mutation proved by blob hash both
    ways; the test imports the mutated module through a relative source specifier,
    so no build sits between mutation and assertion.
  • The control can fail. An ablation widening the rewrite from the top-level
    index to every index turns exactly one case red — the NESTED control. The
    first draft of that control was built on a field-no-consumers twin and
    stayed green under the same ablation, so the fixture now carries a bare
    unique: true index to give the control a finding whose path really has a
    nested index. A control that cannot fail is decoration.
  • Tier measured in both directions, from the predicate rather than the
    filename: the new pin fires no integration signal and is absent from the
    derived integration population — UNIT tier, asserted by the file's own last
    case.
  • Changeset decided by measuring the built dist, with controls both ways:
    the rewritten key is present in packages/cli/dist/utils/artifact-packages.js
    and files[] ships dist; positive control runPerPackageAuthoringRules
    present; negative control (a test-only symbol) 0 hits. Published ⇒ changeset,
    graded patch.
  • dispatch-gates.mjs: 61 derived families, 61 run, 0 NOT-MEASURED, 0 UNRUN
    (exit codes recorded, none is 3).
  • pnpm lint (eslint . --no-inline-config, whole repo, not narrowed):
    exit 0 at 6a0a4df1b4.
  • pnpm --filter @objectstack/cli test run WHOLE — no --project, no
    file list, exactly what CI runs: 267 files / 3485 tests passed, exit 0.
    The same command at 15cc0db8d4 reproduced CI's red first:
    3 failed | 264 passed (267), of which the one real assertion was [finding] os build's text face COUNTS per-package advisories it never prints — the closing line reads "4 author-time warning(s) — see above" above a list of 3 #18780's
    lit control (the other two were this worktree lacking packages/cli/dist,
    which that pin refuses by name; cleared by building the package).
  • pnpm --filter @objectstack/cli typecheck: exit 0.

check:dual-build-cjs-loads first returned exit 3 — its own text says "This is
NOT a pass: nothing was measured", 8 packages had no dist in this worktree. I
built them and re-ran it: exit 0.

Acceptance notes

  • Two pending changesets still assert the falsified sentence and are not
    touched here: .changeset/18677-validate-per-package-authoring-pass.md and
    .changeset/18778-lint-per-package-authoring-pass.md. check:empty-changeset
    refuses a PR that modifies a changeset present on the merge base, and names
    this exact situation as its DELIBERATE CORRECTION class, whose remedy is "do
    NOT restore it; get it confirmed on the PR". That confirmation is the
    reviewing seat's to give, so the call is surfaced here rather than taken. The
    correction itself is published in this PR's own changeset, which lands in the
    same release. Note also that [finding] os build runs a per-package authoring-rule walk os validate does not — the residue #17069 left, in the same false-clean direction #18677's changeset says "After: both report 4",
    which this change makes read 3.
  • Derivation ran against a tree behind origin/main (the derivation's own
    staleness warning named scripts/gen-sdui-manifest-node.mjs among others);
    CI judges the merge.
  • Noted, not filed: bin/run-dev.js needs TSX_TSCONFIG_PATH pinned when
    invoked from an example directory, and says so itself in a good refusal — a
    working command, not a defect.

Authored by Claude Code in session session_01DvvamiacK328idtBYJBxV3 (durable attribution kept in prose: the body-edit channel appends its own footer block, so a footer sent here would be stored twice).


Generated by Claude Code

…llection index, so an echo no longer survives it (#18779)

Claude-Session: https://claude.ai/code/session_01DvvamiacK328idtBYJBxV3
Co-authored-by: Claude <noreply@anthropic.com>
…ries a nested index, and the key's bound is measured rather than quoted (#18779)

Claude-Session: https://claude.ai/code/session_01DvvamiacK328idtBYJBxV3
Co-authored-by: Claude <noreply@anthropic.com>
…ple corpus, not sized by quoting a neighbouring pin (#18779)

Claude-Session: https://claude.ai/code/session_01DvvamiacK328idtBYJBxV3
Co-authored-by: Claude <noreply@anthropic.com>
…enuinely cannot see, not an echo (#18779)

The per-package survivor this pin relied on was an echo of a union finding.
Once the de-duplication key stopped comparing the top-level index it was
correctly filtered, the fixture's per-package set went empty and the
non-vacuity case went red. The fixture now uses the falsifier shape its lint
sibling already used, and the non-vacuity case asserts the survivor's pedigree
rather than only its count.

Claude-Session: https://claude.ai/code/session_01DvvamiacK328idtBYJBxV3
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Sep 18, 2026
@github-actions

github-actions Bot commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/cli, touching 5 documentable anchor(s).

32 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 600b1e2ab129008e9d43e7101be35d22befe2142.

⛔ 8 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 1 anchor(s) matched too much of the corpus to be a work list: os validate (command, 49 pages)
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 60 of 215 client-bound route-ledger rows — the other 155 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 155: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 100 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 24 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 600b1e2ab129008e9d43e7101be35d22befe2142 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 1664c593ba7ce4cc55ff6bdb9755600cf4521776 — the merge of head 6a0a4df1b4e9e2f83578c2278f5985fc0053e74c into base 600b1e2ab129008e9d43e7101be35d22befe2142, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 1664c593ba7ce4cc55ff6bdb9755600cf4521776 && git checkout 1664c593ba7ce4cc55ff6bdb9755600cf4521776
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 600b1e2ab129008e9d43e7101be35d22befe2142 6a0a4df1b4e9e2f83578c2278f5985fc0053e74c && git checkout -B drift-repro 600b1e2ab129008e9d43e7101be35d22befe2142 && git merge --no-ff 6a0a4df1b4e9e2f83578c2278f5985fc0053e74c

node scripts/docs-audit/affected-docs.mjs --json 600b1e2ab129008e9d43e7101be35d22befe2142

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 600b1e2ab129008e9d43e7101be35d22befe2142 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

Copy link
Copy Markdown
Collaborator Author

The failing assertion, recovered locally — CI could not hand it over

domain:cli execution PM seat · reading 2026-09-18T04:25Z · reproduced at this PR's head 15cc0db8d4

Test Core (4/6) is red at step 11. ⛔ The job log is not readable from this container — it redirects off api.github.com to productionresultssa*.blob.core.windows.net, which this session's egress refuses (http=000, no bytes). That is an absence of a reading, not an absence of evidence, so the assertion below was recovered by running the shard rather than by guessing from the annotations.

What CI did publish, as annotations:

exit-status      Process completed with exit code 1.
package-pointer  command (…/packages/cli) pnpm run test --shard=2/2 exited (1)

Re-run here, whole, with no --project filter — which is what packages/cli's "test" script (vitest run) does on CI:

Test Files  2 failed | 131 passed (133)
Tests       1 failed | 1611 passed | 15 skipped (1627)

FAIL |integration| test/build-text-face-advisory-count.test.ts
  > #18780 — `os build`'s text face prints every advisory its summary line counts
  > the fixture reaches the per-package pass and raises a survivor there

  test/build-text-face-advisory-count.test.ts:239
    expect(perPackage.length).toBeGreaterThan(0);
  AssertionError: expected 0 to be greater than 0

What it is. That is a precondition test, not a count pin: it asserts #18780's fixture really reaches the per-package pass and leaves a survivor, so the equality assertions below it are not vacuous. On that fixture the only per-package survivor was the echo — so once this PR's key stops letting echoes through, the count is 0 and the precondition fails.

⇒ Same shape as the #18677 validate-parity fixture this PR already repaired, in a third file. The remedy is the same one and it is not relaxing the assertion: give the fixture a survivor the union genuinely cannot see — a field whose only consumer lives in a sibling package. ⛔ Weakening toBeGreaterThan(0) would re-open the very defect #18780 exists to close.

Why a green local run and a red CI coexisted. The failing test is |integration| tier. The delivering run covered --project unit (216 files) plus only the two integration parity files this diff touches, so this file was never collected locally. ⇒ validation before the next push is pnpm --filter @objectstack/cli test whole, not a file list.

One reading in that run is NOT this PR's, recorded so nobody chases it. test/published-subpath-hook-body.pin.test.ts failed at load with the file's own deliberate refusal — "packages/cli is not built (./dist/index.js is absent) … Run: pnpm --filter @objectstack/cli build". This worktree built @objectstack/cli^..., the dependency closure, and not the package itself. ⛔ A gap in my environment, not a defect, and not in this PR's scope.

⇒ One real failure, one file, one fixture to repair. The fix is in progress; this comment exists so the assertion is on the record, since the job log that carries it cannot be fetched.


Generated by Claude Code

… echo (#18779)

The multi-package fixture gave bc_account.industry no consumer anywhere, so
the union run raised it too and the per-package 'survivor' was that same
finding re-reported at the package-local index. Once the de-duplication key
stopped comparing the top-level collection index the survivor count went to 0
and the precondition failed — correctly. `orders` now owns the view that
displays the field, the same falsifier shape the two parity pins carry, and the
precondition additionally asserts the survivor's pedigree so the control cannot
be re-lit by a duplicate.

Claude-Session: https://claude.ai/code/session_01DvvamiacK328idtBYJBxV3
Co-authored-by: Claude <noreply@anthropic.com>
@os-support-ai
os-support-ai marked this pull request as ready for review September 18, 2026 05:06
@os-support-ai
os-support-ai added this pull request to the merge queue Sep 18, 2026
Merged via the queue into main with commit 031e5fb Sep 18, 2026
50 checks passed
@os-support-ai
os-support-ai deleted the claude/issue-18779-positional-dedup-key-echoes branch September 18, 2026 05:34
os-project-manager pushed a commit that referenced this pull request Sep 20, 2026
…s the source states it

`content/docs/deployment/validating-metadata.mdx` said of the per-package walk
that "what it reports is exactly the set the union could not see". That sentence
was removed from eight code carriers by #18878 because it is false, and the two
notes that replaced it forbid restating it: `packages/cli/src/commands/compile.ts`
carries "Do not re-inflate that to" it, and
`packages/cli/src/utils/artifact-packages.ts` records that the claim the pass is
entitled to make "is narrower than" it. The page was the last place in the tree
still asserting it as a claim.

The page now states the bound in the source's own settled words — the set of
per-package findings no union finding already carried under the same rule,
`where`, message and non-top-level position — and carries the narrowness note
the source wrote down so the next reader does not re-inflate it: the key is
position-insensitive, not collision-proof.

Claude-Session: https://claude.ai/code/session_01QCdUBjM47SxioST9z5Zwdf
Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…s own source, and derive the pins that bound a spelling (objectstack-ai#18924)

Fixes objectstack-ai#18520

Clause-②: no

The nightly-tier test files under `packages/cli/test/` that read this
package's own source
TEXT now read it through the shared mask
(`scripts/js-comment-mask.mjs`), and the pins that
bound a byte-exact SPELLING now bind a property derived from the source
instead. Every file
was judged on its own; the table below gives the rung and the reason per
file.

## The population, re-derived — no number inherited

**Predicate, stated:** a file under `packages/cli/test/` whose NAME puts
it in a nightly tier
(`scripts/nightly-tiers.mjs` — `*.e2e.test.*` / `*.live.test.*`, and
nothing else selects a
tier), which READS this package's own source text at test time, directly
or through a helper
under `test/helpers/` it imports. Evaluated over each file's local
import closure, on MASKED
text, so a read one module away still counts and a path named only in
prose does not.

| predicate | count | measured at |
|:--|--:|:--|
| card's: `test/*.e2e.test.ts` containing `readFileSync` | **26** |
reproduces exactly |
| card's: of those, importing the shared mask | **3** | reproduces
exactly — `serve-port-readback`, `published-entry-stderr-nonblocking`,
`run-dev-stderr-nonblocking` |
| claim's: `grep -rl readFileSync test/*.e2e.test.ts \| xargs grep -l
'src/'` | **20** | reproduces exactly |
| **mine** (above) — nightly-tier readers of this package's own source |
**15** | 12 raw, 3 already masked |

⭐ **20 and 15 are not a contradiction — they are two predicates, and the
delta is readable
both ways.** Eight files are in the claim's 20 and not in mine, three
are in mine and not in
its 20:

- **prose only** — `lint-eval-generator-load-envelope` matches `src/` in
a docblock sentence
and reads `/definitely/not/here.json`. This is the false positive the
dispatch predicted.
- **a temp fixture's `src/`, not ours** — `build-docs-step-count`,
`build-multi-package-artifact`,
`generate-skill`, `serve-publishes-bound-port` create `src/docs` or
`src/skills` inside a
  scratch project and read `dist/objectstack.json` back.
- **another package's source** — `scaffold-emission-policy` reads
`packages/create-objectstack`'s
  template `package.json`. JSON carries no comments, so no rung applies.
- **own source, but `bin/` not `src/`** —
`published-entry-stderr-nonblocking` (already masked)
and `run-dev-unbuilt-workspace` (raw). Both read a hand-written
published entry; the second is
  in this PR, the first was already correct.
- **missed by the claim's grep, found by mine** —
`config-miss-stdout-purity` reads
`src/commands` through `test/helpers/config-miss-family.ts` and never
spells `src/` itself;
`invocation-loudness` and `serve-host-fallback-base` DO spell
`../src/...` but never read it
as text — they `symlink`/`execFile` it, so they are readers of a module,
not of prose.

## Rung per file

| file | reads | rung | what changed, and why that rung |
|:--|:--|:--|:--|
| `build-json-failure-conversions.e2e.test.ts` |
`src/commands/compile.ts` | **3 only** | Masked. Its `indexOf` ORDER
block (`declAt` / `tryAt` / `loadAt` / `normalizeAt`) is the objectstack-ai#17633
shape verbatim — raw positions over a file whose docblocks name `await
loadConfig(`. ⛔ The six frozen integers are card objectstack-ai#18894's and are
untouched here. |
| `build-json-failure-warnings.e2e.test.ts` | `src/commands/compile.ts`
| **3 only** | Masked, same fence — integers untouched. |
| `cloud-login-json-ndjson.e2e.test.ts` | `src/commands/cloud/login.ts`
| **3 + 2** | Masked; and the pin that subtracted ONE byte-exact line
from the `emitJson(` hits now partitions the call sites against
`emitRecord`'s own brace-matched body: outside must be empty, inside
must not be. |
| `login-json-ndjson.e2e.test.ts` | `src/commands/login.ts` | **3 + 2**
| Same conversion, same reason. |
| `login-json-noninteractive.e2e.test.ts` | `src/commands/login.ts` |
**3** | Masked only. Its `rl.question(` filter already binds a SHAPE
(does the line carry the abort signal), not a spelling — rung 2 has
nothing to convert. |
| `json-stdout-purity.e2e.test.ts` | `src/commands/**` | **3 + 2** |
Masked; and `toHaveLength(10)` demoted to a floor. The line above it
already binds the SET against the map a sibling nightly file drives, so
the integer was a second frozen copy of one fact. It stays as a floor
because it is the only guard on the vacuum both sides share. |
| `config-miss-stdout-purity.e2e.test.ts` | `src/commands/**` via helper
| **3** | ⭐ No edit in this file — its reader lives in
`test/helpers/config-miss-family.ts`, which this PR masks once for both
consumers. |
| `test/helpers/config-miss-family.ts` | `src/commands/**` | **3** |
Masked. Both discovery halves are regexes over command source and both
are satisfiable by prose: a docblock naming `json: Flags.boolean(`
beside a `utils/config.js` import invents a direct member; a
commented-out `export default class X extends Y` invents an alias. |
| `diff-usage-error-stream.e2e.test.ts` | `src/commands/**` | **3** |
Masked. This scan decides by LINE POSITION — writers above the first
`flags.json` read — which is the print-ORDER failure that opened this
card. Its `toBeGreaterThan(10)` population control was already a floor.
|
| `run-dev-unbuilt-workspace.e2e.test.ts` | `bin/run-dev.js` | **3** |
Masked. `exec` takes the FIRST match, so a docblock recording the old
`STDERR_DRAIN_STALL_MS` would be read as the shim's bound. Its sibling
over the other published entry already masks; this makes the pair
consistent. |
| `serve-app-anchored-optional-import.e2e.test.ts` |
`src/commands/serve.ts` | **1 + 2 + 3** | See below — five byte-exact
statement pins, rewritten. |
| `validate-json-failure-conversions.e2e.test.ts` |
`src/commands/validate.ts` | **3** | Masked, and the paragraph that
recorded the opposite decision is corrected rather than left false. |
| `validate-json-failure-warnings.e2e.test.ts` |
`src/commands/validate.ts` | **3** | Same. |
| `serve-port-readback` · `published-entry-stderr-nonblocking` ·
`run-dev-stderr-nonblocking` | own source | — | Already masked.
Untouched. |

### Rung 1 — where it applied, and where it did NOT

Rung 1 permits DELETING a nightly assertion when a per-PR sibling
already binds the same
thing. I searched for a sibling for every byte-exact subject in this
population and **opened**
the one I found:

- ✅ **Applied once.** `serve-app-anchored-optional-import` bound
`function importFromHost(specifier: string, hostRoot: string =
servedAppRootOrCwd())` — an
argument LIST, the objectstack-ai#17725 shape exactly.
`src/commands/serve-cluster-host-resolution.test.ts`
is queue-tier (its name carries no `.e2e`, so `nightly-tiers.mjs` leaves
it in the per-PR run)
and binds that function's EXISTENCE, its module scope and its
uniqueness. So this PR does not
re-pin any of that here; what it keeps, because the sibling does not
bind it, is the DEFAULT —
  read off the paren-matched parameter list.
- ⛔ **Did not apply anywhere else, and this is a measurement.**
`anchorServedApp` and
`servedAppRootOrCwd` appear in no other test in the package. The login
emitter contract
(`emitRecord`) appears in no queue-tier test at all. For
`warningsSoFar`, the queue-tier
`test/truncation-remainder-notices.test.ts` binds `'warnings:
warningsSoFar(),'` in
`compile.ts` — the CALL SITE, not the declaration and not the spread
ORDER these files pin,
and for `validate.ts` it binds different payload keys entirely. Not the
same thing, so
  nothing was deleted on its account.

### `serve-app-anchored-optional-import` in detail

Five `toContain`/`toMatch` pins over whole statements of `serve.ts`, in
a file only the nightly
tier collects. Each now binds what it was written for:

| was | is |
|:--|:--|
| `toContain('const { configPath: absolutePath, configExists } =
anchorServedApp(args.config!);')` | exactly ONE `anchorServedApp(` call
site (the declaration excluded by the same `function` lookbehind the
per-PR sibling uses), and its ARGUMENT is `args.config!`. The
destructured local names are deliberately no longer bound. |
| `not.toMatch(/const absolutePath = path\.resolve\(process\.cwd\(\),
args\.config!\)/)` | the same negative as a SHAPE:
`path.resolve(process.cwd(), args.config` with whitespace tolerated. A
negative pin on one exact spelling passes for every respelling of the
defect. |
| `toContain('function importFromHost(specifier: string, hostRoot:
string = servedAppRootOrCwd())')` | the `hostRoot` parameter's DEFAULT,
read off the paren-matched parameter list. A third parameter or a
renamed `specifier` no longer reddens it. |
| `toContain('const hostRoot = servedAppRootOrCwd();')` and
`toContain('const root = hostRoot ?? servedAppRootOrCwd();')` | a
partition over every `const`/`let` host-root binding: at least two
exist, and none may be bound without resolving through
`servedAppRootOrCwd()`. |
| `toMatch(/^function servedAppRootOrCwd\(\): string \{$/m)` | exactly
one MODULE-SCOPE `function servedAppRootOrCwd(`, never indented, never a
`const`/`let`/`var`. The return-type annotation and the brace are not
the defect. |

## What the mask changed TODAY: nothing — and that is the measurement

Every converted reader was evaluated raw and masked over the same tree,
and every verdict is
identical. Masking is therefore behaviour-preserving now and protective
later — it did not
launder a stale pin green:

```
SAME  json-stdout-purity/discoverFamily        (the same command ids)
SAME  config-miss-family/discover              (the same 10 ids)
SAME  diff-usage/offenders                   = []
SAME  diff-usage/withJson count              = 28
SAME  emitJson call sites — login.ts         = 1      cloud/login.ts = 1
SAME  rl.question without signal — login.ts  = []
SAME  payloadLiterals — validate.ts          = 7      compile.ts = 11
SAME  order indexes — validate.ts / compile.ts       (declAt/tryAt/loadAt/normalizeAt)
SAME  serve.ts anchorServedApp / servedAppRootOrCwd sites
SAME  run-dev STDERR_DRAIN_STALL_MS          = "15_000"
```

⛔ `scripts/check-comment-mask-adoption.mjs` is green before and after
and its ledger is
unchanged at 14 rows — because none of these twelve files ever carried a
private stripper.
They carried NO masking at all, which is the blind spot the card names
and the gate documents.
This PR adds no private stripper; every file imports the shared module.

## Ablations

Each is a script with `trap` restore on `EXIT INT TERM`, absolute paths,
an on-disk occurrence
count proving the mutation landed, exit codes captured BEFORE any pipe,
and a restore proven by
`git hash-object` against the HEAD blob plus a clean `git status` for
both paths.

⚠️ The first attempt of all three read exit 1 everywhere in 17 seconds.
That was not a result:
without `OS_TEST_TIERS=nightly` the package collects none of these
files, and it says so
loudly. Recorded here because a run that measured nothing is the failure
mode this card is
about. The numbers below are from the re-run with the switch set.

**ABL-1 — the objectstack-ai#17633 shape, reproduced and then shown fixed.** A COMMENT
is injected into
`src/commands/info.ts` between `async run(` and its first `flags.json`
read, naming
`printHeader(`. Behaviour is untouched; only prose moved.

| leg | pin | exit | reading |
|:--|:--|--:|:--|
| masked (this PR) | `diff-usage-error-stream` | **0** | prose is
invisible |
| raw (pre-conversion, restored from the merge base) | same file | **1**
| `AssertionError: expected [ 'info.ts' ] to deeply equal []` |

⭐ That failure line IS the card's first row: a docblock reporting an
order change that never
happened.

**ABL-2 — `serve.ts`, both directions.**

| leg | mutation | pin | exit | reading |
|:--|:--|:--|--:|:--|
| 1 | `const hostRoot = servedAppRootOrCwd();` becomes `process.cwd()` |
converted | **1** | `a host root is bound without resolving through
servedAppRootOrCwd(): expected [ Array(1) ] to deeply equal []` — the
new partition is not vacuous |
| 2 | a THIRD parameter added to `importFromHost`, same arguments, same
behaviour | converted | **0** | the argument LIST is no longer the
contract |
| 2 | same mutation | pre-conversion | **1** | `expected '…' to contain
'function importFromHost(specifier: st…'` — objectstack-ai#17725 verbatim, on a pin no
pull request can see |

**ABL-3 — the login emitter partition.**

| leg | mutation | pin | exit | reading |
|:--|:--|:--|--:|:--|
| 1 | a real second `await emitJson(` outside `emitRecord` | converted |
**1** | `every --json write in login.ts must go through emitRecord():
expected [ Array(1) ] to deeply equal []` |
| 2 | a COMMENT quoting `emitJson(payload, 0)` | converted | **0** |
prose is invisible |
| 2 | same comment | pre-conversion | **1** | same assertion — a comment
breaking a pin whose code never moved, the card's second row |

All three restored cleanly: `git hash-object` matches the HEAD blob for
every mutated path and
`git status --porcelain` is empty for both paths in each script.

## Verification

- ⚠️ **Tier.** `OS_TEST_TIERS` unset collects none of these files. Every
red/green below is
under `OS_TEST_TIERS=nightly`. Independently confirmed by `pnpm
check:tier-file-adoption`:
  68 nightly-tier files on disk, owned by one package.
- **`packages/cli` built** (`pnpm --filter '@objectstack/cli...' build`)
so the pins that refuse
  at load on an absent `dist/index.js` actually run.
- **Both whole-package runs, no `--project` filter — what CI runs:**

```
pnpm --filter @objectstack/cli test                       Test Files  267 passed (267)   Tests  3485 passed (3485)   exit 0
OS_TEST_TIERS=nightly pnpm --filter @objectstack/cli test  Test Files   68 passed  (68)   Tests   695 passed  (695)   exit 0
```

The 12 converted files were also run on their own under
`OS_TEST_TIERS=nightly` before the
  whole runs: 12 files / 336 tests / exit 0.
- **Gate families** derived with `node scripts/pm/dispatch-gates.mjs
--commands --repo
objectstack-ai/objectstack` and reconciled with `--ran`: 47 derived, 46
run green, **1 NOT
MEASURED** — `pnpm check:dual-build-cjs-loads` exits 3 (PREREQUISITE NOT
MET: it reads built
output for packages outside this closure). ⛔ Recorded as NOT MEASURED,
not as a pass and not
  as a red.
- **`pnpm lint`** over the whole repo, unnarrowed. `node
--stack-size=4000 eslint . --no-inline-config` over the whole
repository, **exit 0**.
  This is the unnarrowed run, so no narrowing has to be justified.

## `skip-changeset`, by measurement with controls both ways

This PR's own diff (`git diff --name-only origin/main...HEAD`) is 12
files, all under
`packages/cli/test/`. `packages/cli`'s `files[]` is
`["dist","README.md","CHANGELOG.md"]` and
`tsconfig.build.json` has `include: ["src"]`, so `test/` reaches
neither. Measured against the
built tree rather than argued:

- **negative** — `bodySpan`, `splitParams`, `paramsOf` (the three
symbols this PR introduces):
  0 files in `dist`, 0 in `README.md`.
- **positive control** — `anchorServedApp` (2 files),
`servedAppRootOrCwd` (2), `emitRecord` (3)
in `dist`, so the grep over the published tree finds things and the zero
above is a reading,
  not a dead search.
- ⛔ No `scripts/**` path is touched, so the published surface did not
have to be re-derived.

## Fences

- ⛔ The six frozen integers in
`build-json-failure-{warnings,conversions}` are untouched; card
  objectstack-ai#18894 owns that axis. Only rung 3 was applied in those two files.
- PR objectstack-ai#18878 (card objectstack-ai#18779) was re-measured at the start: zero
intersection with this population,
and it has since merged as `031e5fbfa3`. Its `compile.ts` /
`validate.ts` edits moved no count
these pins read (compile.ts still 11 payloads, validate.ts still 7),
re-measured after the
  merge. This PR touches none of its six test files.
- ⛔ No test is skipped, quarantined or weakened; no assertion was
loosened to make a run pass.
- ⛔ `content/docs/releases/` and every `packages/*/CHANGELOG.md`
untouched.
- ⛔ No command was refused by the permission classifier.

## Acceptance notes

- `src/commands/serve-cluster-host-resolution.test.ts` carries a private
`stripComments` at
line 151. It is a DECLARED row in `check-comment-mask-adoption.mjs`'s
shrink-only ledger,
measured there as agreeing with the shared mask byte for byte over 212
files, so it is
recorded debt rather than a finding, and converting it is that ledger's
per-row work.
  Noted, not filed.
- `validate-json-failure-{warnings,conversions}` still pin a multi-line
spread ORDER in
`validate.ts` byte-exactly (`...ruleAdvisories, ...docWarnings, ...`),
and the two
`build-json-failure-*` files pin the same shape over `compile.ts`. That
is a rung-1/rung-2
question on an axis this PR did not open, and for the `build-*` pair it
sits in the file the
fence closes. Noted, not filed — the carrier is card objectstack-ai#18894, which is
already open over those
  two files.
- `invocation-loudness.e2e.test.ts` and
`serve-host-fallback-base.e2e.test.ts` reach
`../src/...` and are NOT source-text readers (they symlink and execute
it). No rung applies;
recorded so the next re-derivation does not re-open them. Noted, not
filed.

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

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…the source states it (objectstack-ai#19241)

Fixes objectstack-ai#18893

`content/docs/deployment/validating-metadata.mdx` was the last place in
the tree still asserting, as a claim, the sentence the CLI source
explicitly forbids restating — of the per-package walk: *"what it
reports is exactly the set the union could not see"*.

PR objectstack-ai#18878 (card objectstack-ai#18779) removed that sentence from eight code carriers
because it is false, and the two notes that replaced it are this page's
acceptance baseline:

- `packages/cli/src/utils/artifact-packages.ts` — the `findingKey`
docblock records that the claim the pass is entitled to make *"is
narrower than"* that sentence, and that the key is
*"position-insensitive, ⛔ not collision-proof"*.
- `packages/cli/src/commands/compile.ts` — *"⛔ Do not re-inflate that
to"* it, beside the settled statement of what does survive.

## What changed

One sentence, one file. The page now states the bound in the source's
own settled words — the set of per-package findings no union finding
already carried under the same rule, `where`, message and non-top-level
position — says why the leading collection index is neutralised (a
package body re-bases its collections from 0, so one finding would
otherwise get two keys), and carries the narrowness note the source
wrote down so the next reader does not re-inflate it. The surrounding
paragraph's teaching is untouched.

⛔ No source file was changed. The source is the authority here; the page
is what was wrong.

## Measurement

Whitespace-normalised, because the target sentence **wraps across two
lines** and a line-oriented `grep -F` returns `0` on it — a zero triage
and two seats each paid for once on this very card:

| needle | base `e233db9` | after |
|:--|--:|--:|
| `exactly the set the union could not see` | 1 | **0** |
| lit control `one gate, four doors` | 2 | 2 |
| dark control `zzzNotARealToken` | 0 | 0 |

The lit control still fires after the edit, so that `0` is a reading and
⛔ not an instrument artefact. The naive line-oriented `grep -F` reads
`0` both before and after — recorded here so nobody re-derives a
clearance from it.

## Gates

39 families derived from the **actual diff** (`node
scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack`, change set taken by the script itself from
the merge base), every one run, every one exit `0`, reconciled `39
derived / 39 run / 0 UNRUN`. Plus the full `pnpm lint` union, which
`dispatch-gates` does not name.

Four of the 39 first exited `3`/`1` carrying PREREQUISITE-NOT-MET text —
*"Nothing was measured: this gate exited before running a single check"*
— because workspace packages were unbuilt. They were re-run to a real
verdict after building `@objectstack/spec`, `@objectstack/formula`,
`@objectstack/lint` and `@objectstack/client-react`. ⛔ Those refusals
are recorded as not-measured-then-measured, never as a failed
measurement.

## Changeset

`skip-changeset`, **derived rather than assumed**: the one changed path
lives under no package directory except the private monorepo root
(`@objectstack/spec-monorepo`, `private: true`), so no published
package's tarball can contain it whatever decides its contents; and no
published package names `content/docs` in its `files[]`. ⛔ No label was
written — this dispatch forbids label writes, so the label is the seat's
to apply.

## Acceptance notes

- **Only one carrier on this page.** The card asked for a grep rather
than an assumption, since PR objectstack-ai#18872 introduced the whole section in one
landing: probed whitespace-normalised for `de-duplicat`, `union could
not`, `could not see`, `only what`, `echo` and `duplicate` across the
page — the corrected sentence was the single restatement. No second one
exists.
- **The "only place in the repo" premise is true of source code and ⛔
not of the tree.** Re-derived at the branch base, whitespace-normalised:
13 non-doc occurrences across 13 files. Eleven are the settled shapes —
one bound declaration, one prohibition, and nine quoted corrections in
`compile.ts`, `lint.ts`, `validate.ts` and five CLI pin tests. **Two are
still assertions**:
`.changeset/18677-validate-per-package-authoring-pass.md` states *"By
`compile.ts`' own description the survivors of that second pass are …"*
and `.changeset/18778-lint-per-package-authoring-pass.md` states *"every
finding that pass produces — … — in the build command's own words"*.
Both attribute the sentence to source text that no longer says it, both
are unreleased, and a changeset body ships verbatim into `CHANGELOG.md`
as the text an upgrading agent greps. ⛔ Not fixed here — out of this
card's declared one-file surface, and `.changeset/18677-…` is the
claimed surface of in-flight card objectstack-ai#18823 (PR objectstack-ai#18867) for a different
defect. Reported for filing.

Clause-②: no

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

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…xit code (objectstack-ai#19369)

Fixes objectstack-ai#18897

Clause-②: no

## The sweep, and what it found

The card's discriminant: **a control is satisfied by an impostor
whenever some mechanism other than the one under test can supply the
asserted value in that fixture.** The method is ablation — delete the
mechanism a control claims to pin, re-run, and read the colour. A
control that stays green was never pinning it.

21 `packages/cli` fixtures were enumerated by the property (*a control
that asserts a survivor or a resolution exists*), not by the three files
the card body tabulates, and every one of them was ablated. Baseline
first: 21 files / 225 tests, all green at the dispatch base
`13d52947d8`.

**One control family was impostor-satisfied, and its impostor is a THIRD
mechanism — neither the echo nor the id tail.**

`test/union-fold-command-parity.test.ts` claimed to pin
`authoringRuleUnionStack`, the fold that makes the author-time rule
table's INPUT non-empty on an option-B (`packages[]`-only) stack. It
could not. Since objectstack-ai#18677 (`os validate`) and objectstack-ai#18778 (`os lint`) all three
doors ALSO run `runPerPackageAuthoringRules`, which judges each package
body as its own stack — and on this fixture that pass raises the SAME
rule at the SAME path and refuses with the same exit code.

| ablation | `union-fold-command-parity.test.ts` |
|:--|:--|
| `authoringRuleUnionStack` never folds | **6/6 GREEN** |
| `runPerPackageAuthoringRules` yields no findings | **6/6 GREEN** |
| BOTH of the above | **3 RED** (the three refusal cases) |

Either mechanism alone kept every case green. Only deleting both turned
the refusal cases red. The control was correct when it was written
(objectstack-ai#17069) and became impostor-satisfied when a sibling pass was added to
the same doors a year of cards later — which is the generalisation this
PR contributes back to the card.

## The repair

The two mechanisms are told apart by the finding's **pedigree**.
`runPerPackageAuthoringRules` prefixes the `where` of every finding it
raises with `package 'ID' — `; the union run renders `where` bare.
Measured on that fixture, with only the fold's presence moving:

```text
fold intact    • object "ob_order" · field "ghost": …
fold ablated   • package 'com.example.ob' — object "ob_order" · field "ghost": …
```

So each refusal case now asserts that prefix is ABSENT, and a new
**positive pedigree control** asserts the same suite can make it PRESENT
— on a fixture whose only finding really is per-package-only (`core`
declares `ob_account.industry`, `orders` owns the view that displays it:
the falsifier shape PR objectstack-ai#18878 landed). Without that pair the negative
assertion would be satisfied by a prefix this suite never produces at
all.

Verified in both directions, from the committed state:

| run | result |
|:--|:--|
| repaired file at HEAD | 9/9 GREEN |
| union fold ablated | **3 RED** — the three refusal cases (was 6/6
green before this change) |
| per-package pass ablated | **3 RED** — the three pedigree controls;
refusal cases correctly stay green |

### One half of the prescribed remedy is not available here, and the
header says so

The card's remedy shape is *a real falsifier AND a pedigree assertion*.
The falsifier half cannot be built for this rule class, and that was
measured rather than assumed: the per-package pass is a SUPERSET of the
union run for reference rules (`utils/artifact-packages.ts`: *"the
per-package run is STRICTER"*), because each body is judged with the
artifact's own `packages[]` handed in as resolution context. A name no
package provides therefore dangles in BOTH views, under every
arrangement of packages. Two candidate union-only fixtures were built
and probed; neither produced a finding the per-package pass could not
also raise. The pedigree is the whole discriminant here, and the
positive control is what makes it falsifiable.

## The full ablation table

Every fixture is reported, including the ones that needed no change — a
sweep that lists only what it changed cannot be checked for coverage.

| fixture | mechanism deleted | red? | verdict |
|:--|:--|:--|:--|
| `test/union-fold-command-parity.test.ts` | `authoringRuleUnionStack`
fold | **NO — 6/6 green** | **IMPOSTOR (third family)** — repaired here
|
| `test/union-fold-command-parity.test.ts` | per-package pass findings |
**NO — 6/6 green** | same control, other mechanism |
| `test/union-fold-command-parity.test.ts` | both of the above | YES — 3
| confirms neither alone is load-bearing |
| `test/validate-per-package-authoring-parity.test.ts` | per-package
pass findings | YES — 1/4 | correct (only the lit control; the parity
pins go vacuous, as the card predicts) |
| `test/validate-per-package-authoring-parity.test.ts` | the fixture's
falsifier view | YES — 1/4 | correct; `expected 0 to be greater than 0`
|
| `test/build-text-face-advisory-count.test.ts` | per-package pass
findings | YES — 3/5 | correct |
| `test/build-text-face-advisory-count.test.ts` | the fixture's
falsifier view | YES — 1/5 | correct |
| `test/lint-per-package-authoring-parity.test.ts` | per-package pass
findings | YES — 2/5 | correct (documented already-correct; verified,
not inherited) |
| `test/lint-per-package-authoring-parity.test.ts` | the fixture's
falsifier view | YES — 1/5 | correct |
| `test/lint-per-package-authoring-seam.test.ts` | per-package pass
findings | YES — 2/6 | correct |
| `test/lint-per-package-authoring-seam.test.ts` | the fixture's
falsifier view | YES — 1/6 | correct |
| `test/validate-per-package-authoring-seam.test.ts` | per-package pass
findings | YES — 2/6 | correct for its narrower claim (it de-duplicates
against an EMPTY union by construction, and its header says so) |
| `test/per-package-dedup-positional-echo.test.ts` | per-package pass
findings | YES — 5/6 | correct by construction |
| `src/utils/collect-docs.package-docs.test.ts` | `docsPackageRefs`
id-TAIL branch | YES — 12/32 | correct |
| `src/utils/collect-docs.package-docs.test.ts` | `docsPackageRefs`
FULL-ID branch | YES — 2/32 | correct — and the 30 staying green is the
objectstack-ai#18962 reading reproduced |
| `src/utils/artifact-packages.test.ts` | `packageBodyAsStack`
resolution context | YES — 2/4 | correct; it already carries its own
contextless CONTROL leg |
| `test/lint-handwritten-checks-package-fold.test.ts` |
`authoringRuleUnionStack` fold | YES — 5/7 | correct |
| `src/utils/format.metadata-stats-package-fold.test.ts` |
`authoringRuleUnionStack` fold | YES — 3/8 | correct (the top-level-only
and empty-stack rows rightly stay green) |
| `src/utils/stack-collections.test.ts` | `authoringRuleUnionStack` fold
| YES — 3/16 | correct |
| `src/utils/stack-collections.test.ts` | `resolveStackCollection`
packages leg | YES — 2/16 | correct |
| `test/info-detail-package-fold.test.ts` | `resolveStackCollection`
packages leg | YES — 7/7 | correct |
| `src/utils/nav-contribution-groups.test.ts` | `artifactPackages`
reports no packages | YES — 6/9 | correct |
| `src/utils/nav-contribution-groups.package-id.test.ts` |
`artifactPackages` reports no packages | YES — 4/4 | correct |
| `src/utils/permission-set-name-collisions.test.ts` |
`artifactPackages` reports no packages | YES — 5/10 | correct |
| `test/build-multi-package-artifact.e2e.test.ts` | `packageBodyAsStack`
resolution context | YES — 1/7 | correct |
| `test/build-multi-package-artifact.e2e.test.ts` | compile drops
`packages` from the artifact | YES — 2/7 | correct |
| `test/build-package-docs-attachment.e2e.test.ts` | `docsPackageRefs`
id-TAIL branch | YES — 2/3 | correct |
| `test/build-package-docs-attachment.e2e.test.ts` | `docsPackageRefs`
FULL-ID branch | **NO — 3/3 green** | correct, NOT an impostor: its
fixture directory is an id tail by construction and its header says so.
It never claims the full-id spelling. Recorded as a coverage boundary,
not a defect |
| `test/compile-artifact-packages.e2e.test.ts` | `artifactPackages`
reports no packages | **NO — 4/4 green** | correct, NOT an impostor: it
reads the written artifact, which is not produced through
`artifactPackages` |
| `test/compile-artifact-packages.e2e.test.ts` | compile drops
`packages` from the artifact | YES — 1/4 | correct; this IS its
mechanism |
| `test/validate-build-gate-parity.test.ts` | `authoringRuleUnionStack`
fold body | **NO — green** | correct, NOT an impostor: it is a
source-text WIRING pin with its own positive control (the export must
exist) and fabricated negative controls. Ablating the body leaves the
wiring, which is what it asserts |
| `test/init.test.ts` | not ablated | n/a | EXCLUDED, with evidence: its
`packages:` occurrences are the pnpm-workspace key, and no control in it
asserts a per-package survivor or resolution. It appears in the card's
keyword tally as a false positive |

Every ablation was performed through `scripts/ablation-replace.mjs`, so
each mutation carries its own on-disk proof (anchor count moved, blob
hash changed) and each restore is proven by blob equality against HEAD
plus an empty `git diff HEAD`. No ablation is left on disk.

## Fences observed

- `packages/cli/src/utils/collect-docs.ts` is **not** edited by this PR
— objectstack-ai#19248 holds that file. Two ablation legs mutated it transiently
inside this worktree only, each restored with a proven blob match; the
diff touches one file,
`packages/cli/test/union-fold-command-parity.test.ts`.
- The de-duplication key (objectstack-ai#18893) and the docs scan depth (objectstack-ai#18965) are
out of scope. The key was reverted to its positional form for ONE
measurement — the historical-impostor reconstruction described in the
report comment — and restored.

## Verification

At `e2b7d13d0f`:

- `pnpm --filter @objectstack/cli exec vitest run --project unit` — 220
files / 3114 tests, all pass
- `pnpm --filter @objectstack/cli exec vitest run --project integration`
— 51 files / 430 tests, all pass, run in two halves for the foreground
cap
- `pnpm --filter @objectstack/cli typecheck` — pass
- `node scripts/pm/dispatch-gates.mjs --commands` derived 46 gate
families; all 46 run, all exit 0, reconciled with `--ran` carrying each
exit code: *"46 derived families accounted for — 46 run, 0 NOT-MEASURED
(a DERIVED zero)"*
- `pnpm lint` (repo-wide, `eslint . --no-inline-config`) — exit 0, run
in full rather than narrowed
- `pnpm --filter '@objectstack/cli^...' build` and the full `turbo run
build` over the package farm — both exit 0

`check:dual-build-cjs-loads` and `check:type-check-debt` first answered
`PREREQUISITE NOT MET` (exit 3, which both scripts state is neither a
pass nor a finding) because only the CLI closure had been built. Both
were re-run after the prescribed full build and both exit 0.

## Acceptance notes

Noted, not filed — observations from the sweep that are not reproducible
defects, contract violations or authoring traps:

- `test/build-package-docs-attachment.e2e.test.ts` cannot distinguish
`docsPackageRefs`' full-id branch from its id-tail branch. That is
deliberate in its fixture and stated in its own comments, so it is a
coverage boundary rather than a defect. Next toucher: whoever changes
`docsPackageRefs`' spelling set — the unit pins next door already cover
both branches, and they go red.
- `test/validate-per-package-authoring-seam.test.ts` and the non-vacuity
case of `test/lint-per-package-authoring-seam.test.ts` de-duplicate
against an EMPTY union by construction, so their survivors are not
asserted to be union-invisible. Both headers say so explicitly; the
stronger claim lives in the parity files. Next toucher: none currently
in flight.
- The card's keyword tally lists `test/init.test.ts` as an unexamined
multi-package fixture. It is not one; the match is the pnpm-workspace
`packages:` key.

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

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation domain:cli priority:p2 Medium: important, M3 size/l tests tooling

Projects

None yet

2 participants