Repository navigation
fix(pm): split check-widening-tells' file count and let its CLI be told the board - #17278
Merged
Merged
Conversation
…ld the board The success line counted every changed file as "read" and set that count beside a qualified negative, so exit 0 read as coverage for surfaces the instrument cannot see: six of the seven declared rows sit in packages/spec, and a published symbol anywhere else is invisible by construction. The count is now split — `judged` is the only number exit 0 is evidence about, and every other file is named under the reason it could not be examined. The scoping is untouched, the #16349 honesty caveat is still printed, and no verdict or exit code moves. The second half is the same output line: main() never passed a repo, so the declared objectui mirror row was unreachable through the documented CLI and an objectui diff could only come back clean — including one that really adds a key to packages/types/src/zod/**. The board is now resolved by resolveSweepRepo (PM_SWEEP_REPO, else GITHUB_REPOSITORY, else this repo), imported from the sibling rather than reinvented, stated in the verdict, and `--repo` is refused with the variable named so two entry points cannot disagree. Fixing the count alone would have filed the mirror under "no declared surface covers it", which is false about it — hence one pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
This was referenced Sep 10, 2026
os-bill
marked this pull request as ready for review
September 10, 2026 02:43
os-bill
deleted the
claude/issue-17112-widening-tells-coverage-honesty
branch
September 10, 2026 03:07
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 28, 2026
… list with a counterfactual pin (objectstack-ai#18700) Fixes objectstack-ai#18560 **Clause-②: no** `scripts/pm/check-widening-tells.mjs` is the TELL half of the clause-② gate (the DECLARATION half, `check-clause2-carriers.mjs`, is a different file and PR objectstack-ai#18681's subject). Its T1 reader decides a line declares a schema member by what the property's VALUE opens with, and that vocabulary was five alternatives inside one 130-character regex literal. A form missing from it is not a line judged leniently — it is a line that is not a key line at all: `memberTellKind` answers `null`, the row neither fires nor spends the objectstack-ai#16943 replacement budget nor earns it on the removed side, and nothing in the output says so. The silence is indistinguishable from a correct `no`. ## The counterfactual, before and after Re-derived here rather than inherited from the card's reading (objectui#9647 comment 5707064702, an at-tier reviewer's measurement). Run as `PM_SWEEP_REPO=objectstack-ai/objectui node scripts/pm/check-widening-tells.mjs --declaration no --diff …`, the BEFORE leg against the file as it stands at `6dfa3ea77` (this branch's merge base), the AFTER leg at `7f1a81419`: | diff | declaration | BEFORE (6dfa3ea) | AFTER (7f1a814) | |---|---|---|---| | objectui#9647, unmodified | `no` | exit 0, 1 file judged, **0 tells** | exit 0, 1 file judged, 0 tells | | objectui#9647 plus one key added through `stripImportedDefaults(` | `no` | **exit 0, 0 tells** | **exit 4, T1 at `packages/types/src/zod/data-display.zod.ts:584`** | | the same widened diff | `yes` | exit 0 | exit 0 (a `yes` is never blocked) | Row 2 is the red this PR turns. Row 1 does NOT change verdict, and that is the correction the re-derivation forced. ## What the re-derivation corrects The two helpers the card names are objectui's REFUSAL family (`packages/types/src/zod/tombstone.zod.ts`), read off the source: - `retirementTombstone(guidance)` returns `z.never({ error: guidance }).optional().describe(guidance)` — the same primitive as this repo's `retiredKey()`; - `handlerKeyRefusal(key, disposition, label)` returns a `z.custom` predicate typed `never` that returns `false`, and its own docblock records that "The predicate refuses EVERYTHING, a live function included". A key declared through either is a key an author may NO LONGER write. Making those two fire a tell would re-mint, on 290 objectui key lines, the exact false positive objectstack-ai#17955 removed on 255 objectstack ones — and a false tell does not cost a word in a comment, it costs the false `yes` this file's own header refuses to ask an author for. The form that DOES carry a widening, and that no seat had named, is `stripImportedDefaults()` (`packages/types/src/zod/imported-defaults.ts`), whose docblock states its contract as "the same TypeScript type, the same keys, the same checks, the same registry metadata and the same accept set". It returns a LIVE schema and is spelled at 45 key positions on the judged objectui surface. ## The named list `SCHEMA_PROPERTY_FORMS` — exported, frozen, and the constant `SCHEMA_PROPERTY` is BUILT from it. Two fields carry two questions: `pattern` (what makes the line a KEY LINE) and `writable` (whether the key it declares is one an author may write). Counts are key-POSITION counts, each with the tree it was taken against. | form | writable | measured | |---|---|---| | `z.` | yes | 7,784 at objectstack `6dfa3ea77` · 1,482 at objectui `15f01223d` | | `lazySchema(` | yes | 0 at objectstack `6dfa3ea77` (live at DECLARATION positions) | | `strictObject(` | yes | 47 at objectstack `6dfa3ea77` | | `*Schema` | yes | 995 at objectstack `6dfa3ea77` · 56 at objectui `15f01223d` | | `stripImportedDefaults(` | yes | **45 at objectui `15f01223d`** (added) | | `retiredKey(` | no | 255 at objectstack `6dfa3ea77` | | `retirementTombstone(` | no | **187 at objectui `15f01223d`** (added) | | `handlerKeyRefusal(` | no | **90 at objectui `15f01223d`** (added) | | `aliasKeyRefusal(` | no | **13 at objectui `15f01223d`** (added) | The `writable: false` arm is objectstack-ai#17955's decline generalised from one helper name to the family, on the SAME positive, line-local evidence: the value must BE the call and nothing after it. `declaresRetiredKeyTombstone` is renamed `declaresUnwritableKey` accordingly; it is not imported by any other file. **The `no` criterion is not loosened anywhere, and the direction is provable rather than argued.** An unrecognised line reports NOTHING, so no row that fires today can stop firing when the list grows. A self-test case keeps the literal this replaced as the reference and asserts it: every legacy verdict is byte-identical, and the only cells that move are the four added forms moving from "not a key line" to "a key line" — one direction, zero losses. ## The pins New battery, registered on the roster and pinned: `'objectstack-ai#18560 — the declaring vocabulary is a NAMED list, every form pinned by a counterfactual fixture': 30` (39 cases run against a floor of 30). Its unit is the FORM, not the assertion: - a frozen fixture roster is asserted EQUAL to the form set, in both directions and naming the offenders — a form added to the list without a fixture reds, a form silently dropped from the list reds. The fixtures are deliberately NOT generated from the list, which would make every future form pass by construction; - every form is asserted RECOGNISED (`memberTellKind` answers T1) — writable or not, because an unrecognised line is invisible to both sides of the budget, which is the defect itself; - every form's fixture is then driven through `tellsInFile` and asserted against its OWN register: a `writable` form must FIRE with its file:line, an unwritable one must be recognised and DECLINE; - every unwritable form carries the chained-arm control that FIRES, so the decline is bound to the evidence the line carries and never to the helper's name; - the objectui#9647 shape is carried as a case in all three readings, plus the `stripImportedDefaults(` widening with its refusal, its file:line, the `yes` control and the default-board control. `check:entry-guard` is the reason the vocabulary's structural guard is a const initializer's `map` step rather than a top-level `for`: this file exports bindings the sibling gate imports, so a load-time throw would run inside the importer. ## The census — report-only, no re-grade, no state write Has the silence already been relied on? **Zero confirmed `Clause-②: no` landings through these forms, over 11 of 18 rows read.** - **Horizon.** objectui's full history (`git rev-parse --is-shallow-repository` = false, 10,282 commits, initial commit 2026-01-13) up to the checkout tip `15f01223d` (2026-09-16). PR objectui#9647 itself is OUTSIDE it: `git merge-base --is-ancestor 604476d HEAD` exits 1, with the initial commit as the control leg at exit 0 on the same non-shallow checkout. The judgeable part of that window starts 2026-09-10, when objectstack-ai#17278 first let this CLI be told which board it judges. - **Population.** 46 commits add a key through one of the four added forms on `packages/types/src/zod/**`; 18 land inside the judgeable window. - **Coverage — read (11 of 18):** 10 carry `Clause-②: yes` in the PR body (objectui#8884, objectstack-ai#8895, objectstack-ai#8930, objectstack-ai#8967, objectstack-ai#9051, objectstack-ai#9338, objectstack-ai#9495, objectstack-ai#9539, objectstack-ai#9541, objectstack-ai#9565); 1 (objectui#9443) carries a "Clause-② carriers" section attaching `needs:contract-review` with no `yes`/`no` token. - **Coverage — NOT ATTEMPTED (7 of 18):** objectui#8984, objectstack-ai#9254, objectstack-ai#9261, objectstack-ai#9343, objectstack-ai#9544, objectstack-ai#9589, objectstack-ai#9621 carry no declaration in the PR body; the remaining carrier is each card's claim comment, which was not read. **This is NOT a zero** — it is seven rows unread and named. - The commit-local changeset is a weak carrier in objectui: only 2 commits in the whole history carry a `Clause-②` line in a changeset, which is why the census reads PR bodies rather than the tree. ## The ablation From the committed fix, `HEAD` blob `1b741ee6c22db6523475b698d392a8ecd41686d6`, under a `trap '…' EXIT INT TERM` restoring `git checkout HEAD -- scripts/pm/check-widening-tells.mjs` at an absolute path. No build or `dist/` is involved — the gate runs from source, so there is no rebuild leg to prove. - **mutation:** the `retirementTombstone(` and `handlerKeyRefusal(` rows deleted from `SCHEMA_PROPERTY_FORMS`; - **mutation proved on disk, not from an exit code:** anchor counts 1 → 0 for each form, file 257,365 → 256,697 bytes, blob `9fb65edf6f68b1501a0aec29d0ddb4c95bcf44a2` which is not the HEAD blob; - **predicted direction:** RED. **Observed:** RED — `--self-test` exits 1, "6 of 377 case(s) failed" plus the verdict-handshake report. The failures name the mechanism: the orphaned-fixture case prints both dropped forms, both RECOGNITION cases fail, both objectui#9647 cases fail, and the one-direction case fails; - **restore verified by hash, not by a return code:** `git hash-object` back to `1b741ee6c22db6523475b698d392a8ecd41686d6`, `git diff HEAD` empty, `git status --porcelain` clean, and the suite back to 381 cases pass. ## Self-test `pnpm check:pm-widening-tells` — **381 cases pass** (342 before this PR; +39). Exit 0. The two gates that IMPORT this module were run too: `check:pm-clause2-carriers` exit 0, `check:pm-prior-rulings` exit 0. ## Derived gates `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` from the worktree, no hand-fed path list — 29 commands, each run with `$?` captured BEFORE any pipe. Reconciled with `--ran`: "29 derived famil(ies) accounted for — 29 run, 0 NOT-MEASURED (a DERIVED zero — all 29 recorded an exit code and none of them is 3)". ``` exit 0 node scripts/check-ci-filter-parity.mjs exit 0 node scripts/check-closing-keyword-parity.mjs exit 0 node scripts/check-closing-keyword-parity.mjs --self-test exit 0 node scripts/check-comment-mask-corpus.mjs exit 0 node scripts/check-declaration-mirrors.mjs exit 0 node scripts/check-declaration-mirrors.mjs --self-test exit 0 node scripts/check-scripts-symbol-anchors.mjs exit 0 node scripts/check-scripts-symbol-anchors.mjs --self-test exit 0 node scripts/check-self-test-wired.mjs exit 0 node scripts/check-self-test-wired.mjs --self-test exit 0 node scripts/check-self-test-workflow-commands.mjs exit 0 node scripts/check-self-test-workflow-commands.mjs --self-test exit 0 node scripts/check-whole-set-label-write.mjs exit 0 node scripts/check-whole-set-label-write.mjs --self-test exit 0 node scripts/pm/bare-root-worklist.mjs --self-test exit 0 pnpm check:agent-test-spelling exit 0 pnpm check:bash32-floor exit 0 pnpm check:cli-command-ids exit 0 pnpm check:cross-package-test-inputs exit 0 pnpm check:driver-memory-census exit 0 pnpm check:entry-guard exit 0 pnpm check:nul-bytes exit 0 pnpm check:parse-guard exit 0 pnpm check:pm-dispatch-gates exit 0 pnpm check:pm-widening-tells exit 0 pnpm check:pnpm-filter-targets exit 0 pnpm check:ratchet-remedy-authority exit 0 pnpm check:refd-timer-probe exit 0 pnpm check:watch-hint-literal ``` Repo-wide `pnpm lint` (`eslint . --no-inline-config`) at `7f1a81419`: **exit 0**, no findings. Control-byte scan over the edited file: no hits. `skip-changeset`: `scripts/pm/**` is not published by any package's `files[]`, and this diff touches nothing else. ## One boundary this does NOT close, recorded rather than left to be found A FILE-LOCAL declaring factory. Both trees mint them — `placeholderFree(` (23 key lines), `strictIdent(` (12), `emptyProps(` (9) at objectstack `6dfa3ea77`; `chatbotRequestBodyArm(` (2), `retiredDeclarativeKanbanKey(` (1) at objectui `15f01223d` — and a list of shared, exported helpers cannot name a factory private to one file. A name-shaped heuristic is refused in the header, with the overturn condition written down. Filed as its own finding in the report on the card, not fixed here. --- _Generated by [Claude Code](https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #17112
Fixes #17217
Clause-②: no
Both cards are one defect wearing two faces, on one file and one output line —
scripts/pm/check-widening-tells.mjs, the sentence it prints when it finds nothing. They landtogether because #17112's remedy applied alone would have mislabelled #17217's population.
Premise re-verification first — both cards measured on current
origin/mainBoth were filed before
0e45ee8cf(#17216) landed; #17112 was measured onf87fdf359. Re-measuredon
origin/main0780e88481(0e45ee8cfconfirmed an ancestor, exit 0; controlc3ead5524alsoexit 0). Every leg still holds — nothing about either card is stale.
0780e88481packages/spec/**surfaceCovers(ALL, 'packages/services/service-analytics/src/index.ts')1 changed file(s) read, no widening tell…EXIT 0✗ T4 … EXIT=4✗ T4 …:143EXIT 4grep -c -- '--repo'grep -c -- '--declaration'(control: the grep speaks)PM_SWEEP_REPO/GITHUB_REPOSITORYhitsmain()'s only judge callwideningRefusal({ declaration, files })reposet to objectuireposetrepodefaulted (what the CLI does)One correction to the record, in this PR's favour and not the cards': my first attempt at #17112's
positive control did not fire, because I shaped the added ledger row as an object literal
(
{ code: 'X', status: 418 }) rather than the bare elementREGISTRATION_ROWactually matches. Thatwas my fixture, not the tool — with the shape the registry really uses (
'WORKFLOW_STEP_FAILED',)the control fires at exit 4, exactly as filed.
Before / after — the success line
Judged against a diff adding a published type export to
packages/services/service-analytics/src/index.ts:Before
After
The count is split, never deleted —
judgedis the only number exit 0 is evidence about, and everyother file is named under the reason it could not be examined. Five states, each a different fact:
judged,deleted(a deletion adds nothing — a measurement, not a gap),not-contract-source(a globcovers it, the file kind declares no contract),
other-repo,unmatched. The listing is capped at tenper bucket and says how many it did not list, so a 300-file diff cannot drown the verdict.
Before / after — the CLI surface
Before: no invocation could reach the declared
objectstack-ai/objectui packages/types/src/zod/**row. After, the board is a resolved input, stated in the verdict:
and the same diff on the default board no longer reads as a confident clean:
That distinction is precisely why the two cards could not land apart: under #17112's remedy alone this
file would have printed under "no declared surface covers it", which is false about it.
Three deliberate choices:
resolveSweepRepois imported fromcheck-half-states.mjs—PM_SWEEP_REPO, elseGITHUB_REPOSITORY, else this repo. The precedence is the one thing that could drift into a secondconvention, so it is not retyped.
THIS_REPOis nowDEFAULT_SWEEP_REPO(byte-identical value,equality pinned in
--self-test).--repoflag.check-clause2-carriers.mjsdeliberately refuses a positional board andpoints at the variable; two entry points in one directory disagreeing about this would be its own
trap, which [finding] check-widening-tells' CLI cannot be told the repo, so its declared objectui mirror row is unreachable — every objectui diff reads clean, including one that DOES add a key to packages/types/src/zod/** #17217 names in its own filing.
--repois refused withPM_SWEEP_REPOnamed, so aseat reaching for it is answered rather than ignored. A malformed board is refused too, never
silently replaced by the default.
THIS_REPO;check-clause2-carrierspasses its ownrepoand is untouched. No existing caller changes meaning.⛔ What this deliberately does NOT do
row is still inert for a run whose board is this repo.
its absence is not one either — false negatives are the cost the [Decision] Clause-② — is it directional? Two seats read the same sentence oppositely, and the enqueue gate hangs on the answer #16349 ruling accepted" — is still
printed on every clean verdict, and
--self-testpins that it is.pass is a new refusal class on the enqueue path — [Decision] Clause-② — is it directional? Two seats read the same sentence oppositely, and the enqueue gate hangs on the answer #16349's chain to rule on, and both filings put it
outside themselves. [finding] check-widening-tells' CLI cannot be told the repo, so its declared objectui mirror row is unreachable — every objectui diff reads clean, including one that DOES add a key to packages/types/src/zod/** #17217 measured the enqueue gate itself as sound (
pairWidening(pair, repo)threads the board correctly); that path is untouched.
Verification
--self-test: 218 cases pass (173 before; +45 in two new batteries, floors pinned at 23 and 22, theroster floor raised 14 → 16). Sibling
check:pm-clause2-carriers: 319 cases pass.Ablation — three legs, each mutation proven on disk, each restore proven by state. Anchor and
injected occurrence counts were
grep -c-ed before and after every mutation (⛔ never an editor's exitcode), and every leg restored with
git checkout HEAD -- PATHthen verified bygit diff HEADemptyAND
git hash-objectequal to the HEAD blob949e94fd— ⛔ never by an exit code. Each leg carried atrap … EXIT INT TERMwith absolute paths.--self-testN changed file(s) readsentence backrepo: board.repofrom the judge callresolveSweepRepo(env)→resolveSweepRepo({})inmain()Leg C exists because leg B alone would not have proved that
mainthreads its environment — and a casethat tested the judge instead of the CLI is exactly the shape that let #17217 sit green through 150
passing self-test cases.
Gates. Derived with
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(the tool takes the change set from the merge base itself): 32 commands, all EXIT 0, reconciled with
--ran→ "32 derived, 32 run, 0 NOT-MEASURED, 0 UNRUN." Pluspnpm check:pm-clause2-carriers(theimporting sibling, owed under the tool-script rule and outside the derived families) — EXIT 0.
Governed?
node scripts/pm/check-governed-merges.mjs --test scripts/pm/check-widening-tells.mjs→EXIT 0, "NOT governed — ordinary queue landing applies to a PR with exactly this file list."
.claude/skills/pm-dispatch/references/contract-review.mdwas checked and is not edited: it namesthis script as the judge of widening tells but documents no invocation of it (the invocation it
documents is
check-clause2-carriers.mjs --pair, unchanged, and the default board is unchanged), sonothing in it becomes false. Editing it would have made the whole PR governed for no gain.
Lint — a declared narrowing, with its three readings. Population: this repo runs
⚠️
pnpm lint=eslint . --no-inline-configover the whole tree from oneeslint.config.mjs(CI's run, not this PR'sdebt). Count:
--format jsonreports 1 file linted, 0 errors, 0 warnings. Invariance: that confignever enables type-aware linting for any file — its own header says so, and
grep -cE 'project\s*:'returns 0 against a control of 8
parserOptionshits — so this diff cannot move the verdict onany file it does not touch.
turbo ls --affectedis no evidence here in either direction: the changesits in the private repo-root package, which is not in the turbo graph at all.
No package
testortypecheckis owed: the diff touches no package source, only a private root-packagescript, and
git grepfinds no*.test.tsnaming this file.Changeset — derived, not inherited
skip-changeset. Measured over all 82package.jsonfiles in the tree: 12 private, 70 published,all 70 declaring
files[], and zerofiles[]entry mentioningscripts— against a positivecontrol of 70/70 mentioning
dist, so the scan speaks. The changed file lives in the repo-rootpackage, which is
private: true. Nothing in this diff is shipped by any released package.⭐ This PR is its own demonstration
The same diff, judged by the gate's
origin/maincopy and by this branch's:So my own
Clause-②measurement agrees with the declaredno— but on the grounds the claimcomment actually gives (
scripts/pm/**is on no declared surface; it sits in no published package'sfiles[]; the gate judges more and claims less), and ⛔ not on that exit 0, which this very PRshows is NOT MEASURED rather than clean. The old line would have read as corroboration. That is the
finding, printed about itself.
验收备注
Noted while reading, ⛔ not filed and ⛔ not fixed here:
refusedandincompletepaths. Only the boardprovenance line was added there; the file listing was kept to the clean path, where the count-reads-as-
coverage failure actually lives. Carrier: this file's next author — no PR or person is otherwise
waiting on it.
pass. That is a new refusal class on the enqueue path, i.e. a ruling on [Decision] Clause-② — is it directional? Two seats read the same sentence oppositely, and the enqueue gate hangs on the answer #16349's chain, and the triage
seat marked it ⛔ 非裁定. Left to that chain's owner; the loud NOT-MEASURED line is the non-ruling half
of it. Carrier: whoever owns [Decision] Clause-② — is it directional? Two seats read the same sentence oppositely, and the enqueue gate hangs on the answer #16349's chain.
Generated by Claude Code