fix(pm): teach check-widening-tells the declaring helpers, as a named list with a counterfactual pin - #18700
Merged
os-justin merged 3 commits intoSep 17, 2026
Conversation
Claude-Session: https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu Co-authored-by: Claude <noreply@anthropic.com>
… list with a counterfactual pin `SCHEMA_PROPERTY` was five alternatives inside one regex literal, and a form missing from it is not a line judged leniently — it is a line that is not a key line at all, invisible to both sides of the #16943 budget, in silence. The vocabulary is now `SCHEMA_PROPERTY_FORMS`: a named, enumerable list the regex is built from, with two registers (`pattern` = recognition, `writable` = whether the key it declares is one an author may write). objectui's declaring helpers join it; the refusal family declines on #17955's own line-local evidence, and `stripImportedDefaults(` — the live one, which no seat had named — now fires. Claude-Session: https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu Co-authored-by: Claude <noreply@anthropic.com>
`check:entry-guard` reads a top-level `for` that throws as load-time dispatch, and this file exports bindings the sibling gate imports. The same assertion is now the const initializer's own map step — a declaration, which is what the rest of this module's vocabulary already is. Claude-Session: https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu Co-authored-by: Claude <noreply@anthropic.com>
This was referenced Sep 17, 2026
os-justin
marked this pull request as ready for review
September 17, 2026 16:02
This was referenced Sep 17, 2026
os-justin
deleted the
claude/issue-18560-widening-tells-declaring-helpers
branch
September 17, 2026 16:25
This was referenced Sep 17, 2026
This was referenced Sep 17, 2026
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 #18560
Clause-②: no
scripts/pm/check-widening-tells.mjsis the TELL half of the clause-② gate (the DECLARATION half,check-clause2-carriers.mjs, is a different file and PR #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:memberTellKindanswersnull, the row neither fires nor spends the #16943 replacement budget nor earns it on the removed side, and nothing in the output says so. The silence is indistinguishable from a correctno.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 at6dfa3ea77(this branch's merge base), the AFTER leg at7f1a81419:nostripImportedDefaults(nopackages/types/src/zod/data-display.zod.ts:584yesyesis 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)returnsz.never({ error: guidance }).optional().describe(guidance)— the same primitive as this repo'sretiredKey();handlerKeyRefusal(key, disposition, label)returns az.custompredicate typedneverthat returnsfalse, 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 #17955 removed on 255 objectstack ones — and a false tell does not cost a word in a comment, it costs the false
yesthis 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 constantSCHEMA_PROPERTYis BUILT from it. Two fields carry two questions:pattern(what makes the line a KEY LINE) andwritable(whether the key it declares is one an author may write). Counts are key-POSITION counts, each with the tree it was taken against.z.6dfa3ea77· 1,482 at objectui15f01223dlazySchema(6dfa3ea77(live at DECLARATION positions)strictObject(6dfa3ea77*Schema6dfa3ea77· 56 at objectui15f01223dstripImportedDefaults(15f01223d(added)retiredKey(6dfa3ea77retirementTombstone(15f01223d(added)handlerKeyRefusal(15f01223d(added)aliasKeyRefusal(15f01223d(added)The
writable: falsearm is #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.declaresRetiredKeyTombstoneis renameddeclaresUnwritableKeyaccordingly; it is not imported by any other file.The
nocriterion 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:
'#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:memberTellKindanswers T1) — writable or not, because an unrecognised line is invisible to both sides of the budget, which is the defect itself;tellsInFileand asserted against its OWN register: awritableform must FIRE with its file:line, an unwritable one must be recognised and DECLINE;stripImportedDefaults(widening with its refusal, its file:line, theyescontrol and the default-board control.check:entry-guardis the reason the vocabulary's structural guard is a const initializer'smapstep rather than a top-levelfor: 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-②: nolandings through these forms, over 11 of 18 rows read.git rev-parse --is-shallow-repository= false, 10,282 commits, initial commit 2026-01-13) up to the checkout tip15f01223d(2026-09-16). PR objectui#9647 itself is OUTSIDE it:git merge-base --is-ancestor 604476d HEADexits 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 fix(pm): split check-widening-tells' file count and let its CLI be told the board #17278 first let this CLI be told which board it judges.packages/types/src/zod/**; 18 land inside the judgeable window.Clause-②: yesin the PR body (objectui#8884, ObjectQL.cascadeDeleteRelations fails OPEN: a failed dependents probe skips therestrictguard entirely, so a delete that should be refused succeeds silently #8895, fix(engine-core): a system-context insert resolves the install's organization, or is refused — the runtime producer of the autonumber fork (#8844) #8930, gate: nothing reconciles the documented HTTP status of an error code against the status the runtime actually emits — measured on MISSING_REQUIRED_FIELD (400 documented, 422 served) #8967, docs: drop stale renderer-status notes now that type:'form' is shipped #9051, QA run · integration-system (6/14 items consulted) · e4e5c6e3 · 2026-08-17 · 2 PASS / 4 PARTIAL / 0 FAIL / 8 NOT-RUN #9338, governance: human merge IS the review record for governed surfaces — retire the per-PR ADR approval check, add a report-only post-merge audit #9495,record_viewslists anip_addresscolumn that no read-path writer ever stamps — a declared-but-unwritten column on a shipped compliance view #9539, docs(plugin-audit): the published README documents the record-view audit surface that shipped #9541, fix(driver-sql): bound the metadata-lock wait on boot schema-sync's MySQL widening ALTER too, keeping boot's swallow (#9542) #9565); 1 (objectui#9443) carries a "Clause-② carriers" section attachingneeds:contract-reviewwith noyes/notoken.optionsfield description overstates the refusal: says "credential material is refused", true only forauth.password#9254, probeInstallOrganizations answers a failed sys_organization read with a memoised empty list, so system-insert organization resolution fails open #9261, publishPackageDrafts still discards the runtime authoring gate's per-draft advisories — Studio's "publish whole app" reports none of them #9343, Four more published READMEs document symbols that do not exist — driver-sql, mcp (×2), objectql, and spec's own front page #9544, Restore plugin-audit's README "See Also" link toruntime-services/audit-service, dropped in PR #9531 because the page was wrong #9589, A negative-lookbehind literal cannot be carried in an issue/PR body — the!is deleted from(?+ less-than +!+[, and AGENTS.md's sanitizer clause names only less-than plus a letter #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.Clause-②line in a changeset, which is why the census reads PR bodies rather than the tree.The ablation
From the committed fix,
HEADblob1b741ee6c22db6523475b698d392a8ecd41686d6, under atrap '…' EXIT INT TERMrestoringgit checkout HEAD -- scripts/pm/check-widening-tells.mjsat an absolute path. No build ordist/is involved — the gate runs from source, so there is no rebuild leg to prove.retirementTombstone(andhandlerKeyRefusal(rows deleted fromSCHEMA_PROPERTY_FORMS;9fb65edf6f68b1501a0aec29d0ddb4c95bcf44a2which is not the HEAD blob;--self-testexits 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;git hash-objectback to1b741ee6c22db6523475b698d392a8ecd41686d6,git diff HEADempty,git status --porcelainclean, 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-carriersexit 0,check:pm-prior-rulingsexit 0.Derived gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackfrom 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)".Repo-wide
pnpm lint(eslint . --no-inline-config) at7f1a81419: exit 0, no findings. Control-byte scan over the edited file: no hits.skip-changeset:scripts/pm/**is not published by any package'sfiles[], 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 objectstack6dfa3ea77;chatbotRequestBodyArm((2),retiredDeclarativeKanbanKey((1) at objectui15f01223d— 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