Skip to content

fix(pm): split check-widening-tells' file count and let its CLI be told the board - #17278

Merged
os-bill merged 1 commit into
mainfrom
claude/issue-17112-widening-tells-coverage-honesty
Sep 10, 2026
Merged

os-bill merged 1 commit into
mainfrom
claude/issue-17112-widening-tells-coverage-honesty

Conversation

@os-bill

@os-bill os-bill commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

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 land
together because #17112's remedy applied alone would have mislabelled #17217's population.

Premise re-verification first — both cards measured on current origin/main

Both were filed before 0e45ee8cf (#17216) landed; #17112 was measured on f87fdf359. Re-measured
on origin/main 0780e88481 (0e45ee8cf confirmed an ancestor, exit 0; control c3ead5524 also
exit 0). Every leg still holds — nothing about either card is stale.

reading card re-measured on 0780e88481
declared surface rows 7, six inside packages/spec/** 7, same rows, same repos
surfaceCovers(ALL, 'packages/services/service-analytics/src/index.ts') false false
controls: error-code-ledger / dispatcher-vocabulary true / true true / true
the exit-0 line on an unexaminable file 1 changed file(s) read, no widening tell… EXIT 0 reproduced verbatim, EXIT 0
positive control (a row on a declared registry surface) ✗ T4 … EXIT=4 ✗ T4 …:143 EXIT 4
grep -c -- '--repo' 0 0
grep -c -- '--declaration' (control: the grep speaks) 6 6
PM_SWEEP_REPO / GITHUB_REPOSITORY hits 0 / 0 0 / 0
main()'s only judge call wideningRefusal({ declaration, files }) same, line 1153
leg 1 — control diff, repo set to objectui refused refused (T1 …objectql.zod.ts:23)
leg 2 — real (removal-only) diff, repo set clean clean
leg 3 — control diff, repo defaulted (what the CLI does) clean clean

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 element REGISTRATION_ROW actually matches. That
was 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

✓ check-widening-tells: 1 changed file(s) read, no widening tell on any declared surface.
  ⚠️ A tell is not a proof and its absence is not one either — …
EXIT=0

After

✓ check-widening-tells: 1 changed file(s) — 0 judged against a declared surface (no widening tell),
  1 NOT MEASURED. ⛔ NOTHING on this diff was examined for widening tells, so this exit 0 is
  evidence about no surface at all.
  board: this run judges objectstack-ai/objectstack (source: default — set PM_SWEEP_REPO to judge another repo).
  ⛔ NOT MEASURED is not a clean reading — no tell could have fired on these files whatever they contain:
    no declared surface covers it (1):
      packages/services/service-analytics/src/index.ts
  ⚠️ A tell is not a proof and its absence is not one either — false negatives are the cost the #16349 ruling accepted.
EXIT=0

The count is split, never deleted — judged is the only number exit 0 is evidence about, and every
other 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 glob
covers it, the file kind declares no contract), other-repo, unmatched. The listing is capped at ten
per 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:

$ PM_SWEEP_REPO=objectstack-ai/objectui node scripts/pm/check-widening-tells.mjs \
    --declaration no --diff /tmp/objectui-adds-a-key.diff
✗ T1 packages/types/src/zod/objectql.zod.ts:23 — a new key on a Zod object schema — …
check-widening-tells: a widening tell with `Clause-②: no` — … 1 tell(s): …objectql.zod.ts:23
  board: this run judges objectstack-ai/objectui (source: PM_SWEEP_REPO).
EXIT=4

and the same diff on the default board no longer reads as a confident clean:

    a declared surface covers it, but for objectstack-ai/objectui —
    re-run with PM_SWEEP_REPO=objectstack-ai/objectui (1):
      packages/types/src/zod/objectql.zod.ts

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:

  • resolveSweepRepo is imported from check-half-states.mjs — PM_SWEEP_REPO, else
    GITHUB_REPOSITORY, else this repo. The precedence is the one thing that could drift into a second
    convention, so it is not retyped. THIS_REPO is now DEFAULT_SWEEP_REPO (byte-identical value,
    equality pinned in --self-test).
  • ⛔ No --repo flag. check-clause2-carriers.mjs deliberately refuses a positional board and
    points 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. --repo is refused with PM_SWEEP_REPO named, so a
    seat reaching for it is answered rather than ignored. A malformed board is refused too, never
    silently replaced by the default.
  • ⛔ The default is unchanged. Every exported function still defaults to THIS_REPO;
    check-clause2-carriers passes its own repo and is untouched. No existing caller changes meaning.

⛔ What this deliberately does NOT do

Verification

--self-test: 218 cases pass (173 before; +45 in two new batteries, floors pinned at 23 and 22, the
roster 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 exit
code), and every leg restored with git checkout HEAD -- PATH then verified by git diff HEAD empty
AND git hash-object equal to the HEAD blob 949e94fd
— ⛔ never by an exit code. Each leg carried a
trap … EXIT INT TERM with absolute paths.

leg mutation --self-test assertions that fired
A · #17112 put the conflating N changed file(s) read sentence back EXIT 1 (3 of 218 failed) the word that carried the defect is gone; what WAS judged is stated; that clean reading says the file WAS judged
B · #17217 drop repo: board.repo from the judge call EXIT 1 (3 of 218 failed) THE FINDING: a key added to the objectui mirror is REFUSED through the CLI; …navigable; GITHUB_REPOSITORY is honoured
C · #17217 resolveSweepRepo(env) → resolveSweepRepo({}) in main() EXIT 1 (5 of 218 failed) the four above plus a malformed board is refused, never replaced by the default

Leg C exists because leg B alone would not have proved that main threads its environment — and a case
that 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." Plus pnpm check:pm-clause2-carriers (the
importing 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.md was checked and is not edited: it names
this 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), so
nothing 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-config over the whole tree from one eslint.config.mjs (CI's run, not this PR's
debt). Count: --format json reports 1 file linted, 0 errors, 0 warnings. Invariance: that config
never enables type-aware linting for any file — its own header says so, and grep -cE 'project\s*:'
returns 0 against a control of 8 parserOptions hits — so this diff cannot move the verdict on
any file it does not touch. ⚠️ turbo ls --affected is no evidence here in either direction: the change
sits in the private repo-root package, which is not in the turbo graph at all.

No package test or typecheck is owed: the diff touches no package source, only a private root-package
script, and git grep finds no *.test.ts naming this file.

Changeset — derived, not inherited

skip-changeset. Measured over all 82 package.json files in the tree: 12 private, 70 published,
all 70 declaring files[], and zero files[] entry mentioning scripts — against a positive
control of 70/70 mentioning dist, so the scan speaks. The changed file lives in the repo-root
package, 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/main copy and by this branch's:

old  ✓ check-widening-tells: 1 changed file(s) read, no widening tell on any declared surface.   EXIT=0
new  ✓ check-widening-tells: 1 changed file(s) — 0 judged against a declared surface,
       1 NOT MEASURED. ⛔ NOTHING on this diff was examined for widening tells …               EXIT=0

So my own Clause-② measurement agrees with the declared no — but on the grounds the claim
comment actually gives (scripts/pm/** is on no declared surface; it sits in no published package's
files[]; the gate judges more and claims less), and ⛔ not on that exit 0, which this very PR
shows 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:


Generated by Claude Code

…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
@os-bill os-bill added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 10, 2026 — with Claude
@os-bill
os-bill marked this pull request as ready for review September 10, 2026 02:43
@os-bill
os-bill added this pull request to the merge queue Sep 10, 2026
Merged via the queue into main with commit 7804f29 Sep 10, 2026
36 checks passed
@os-bill
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

2 participants