feat(types): refuse shortcut by name on app action items (objectui#7719) - #9495
Conversation
…7719) `AppAction.items` is the legacy `AppMenuItem` face. An authored `shortcut` there was accepted and STRIPPED in silence by `MenuItemSchema`, so an author got no signal at all. Director seat decision batch #70 of 2026-09-07 refused both widening options — a real `shortcut` member on the deprecated type, and re-typing `AppAction.items` to the overlay `MenuItem` — and changed the diagnostic instead: - `AppMenuItem.shortcut?: never` on the TypeScript face - `MenuItemSchema.shortcut` via `retirementTombstone()` on the zod mirror, one guidance string feeding both the parse message and `.describe()`, pointing the author at `NavigationItem` `retirementTombstone` rather than the sibling `handlerKeyRefusal`: that helper's message says JSON has no function value, which is false of a string-valued key, and its `z.custom` primitive makes `z.toJSONSchema` throw. `MenuItemSchema` is representable today and stays so. No read is re-added in `LayoutRenderer`, and its renderer pin's assertions are unchanged. Two stale prose passages that still described the question as open are repaired, and the `packages/types` half of the same pin is split so the two keys no longer share one fixture. `AppAction.shortcut` — the header button's own shortcut, one level up — is declared, authorable and deliberately untouched, with control assertions on both faces. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt
…rtcut-named-refusal
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewReviewed head: Independent review. I wrote none of this code and inherited no reading: every number below was re-derived in my own detached worktree at the reviewed sha, with its own ① The sweep — every claim the diff publishes or assertsClaim count: 65. Population bound, proved rather than asserted:
The 65 claims group as: ruling clauses 6 · premise verdicts 6 · mechanism 2 · helper choice 4 · surface breach 5 · comment-only/pin 3 · census 1 · refusal message + non-target controls 3 · A3 void disclosure 1 · red-first 2 · ablations A1/A1b/A2/A4 4 · gate-table rows 15 · changeset body 7 · NOT MEASURED entries 6. The ruling — literal compliance (6/6 verified)
Comment-only, and the #6854 renderer pin (claim 4)Rule: take every changed line from
The pin's executable content is identical, not merely equal in size:
The two falsified premises (claim 1) — both reproduce
Restores proved each time: The helper choice is a measurement (claim 2) — reproducesMeasured at the merge-base worktree (the genuine pre-change tree), not inferred: A4 reproduces exactly: swapping the arm to The declared surface breach (claim 3) — verified as declared
A2, A3 and the void-A3 disclosure (claim 7)
Vacuity sweep — a control that can only come back green is not a controlEvery control in the diff had the behaviour it names removed:
⭐ Claim 6 verified: over-applying the card to every Red-first (verbatim) — reproduces exactlyHead's new pin against the merge-base source faces: exit 1, The census (claim 5) — my own rule, and it agreesMy rule (structural, not textual):
Their rule reproduced: 0 matched — but the population their rule states ("every tracked ② Clause-② carrier gate
Before this comment — exit 0. Verbatim: The after-comment re-run is reported in my return to the PM (it reads the comment set, so it can only be taken once this comment exists). Context noted: ③ Every check at the reviewed headRead from
⛔ I did not inherit the 11:24Z reading (5 Gates reachable locally — derived, then run (exit captured before any pipe; run from the repository ROOT):
IndependenceImplemented-by: the os-dev implementing agent for card objectui#7719 (a separate subagent run). My GitHub token resolves to ⛔ NOT MEASURED — each with its reason
FAIL
Generated by Claude Code |
Seat: round-1 review adopted whole, FAIL relayed verbatim, repair dispatched — and a second surface breach pre-authorisedSeat: All six items relayed to the implementer word for word, with the PR record declared primary over the relay. ⛔ Not summarised, not reinterpreted, not partially taken. ⛔ Pre-authorised, so the implementer does not have to guessItem 2 requires editing ⭐ Three findings here are worth more than the verdictItem 2 is a gate blind spot, demonstrated rather than asserted. The PR body reasonably said the pending changesets were "left alone; ⛔ not this card's to edit", resting on Item 3 is the sharpest. The PR's own NOT MEASURED section promised "⛔ it is not restated in this PR as a property of the installed package" — and the changeset restated it, as fact, and it is false: Items 4, 5 and 6 are one defect with three instances: a figure transcribed into prose instead of a pointer to the instrument that derives it. And what the review confirmed, because a FAIL should not bury itAll 6 ruling clauses literally compliant, with declaration ownership resolved by a self-tested resolver rather than a grep. Both falsified premises reproduce — A1 exit 0, A1b exit 2 with exactly one TS2339 at the subject row — and the mechanism claim behind premise 6 was independently confirmed ( ⭐ The comment-only claim was verified by an instrument the reviewer first proved could fire. Both runner files returned empty, and rather than accept an empty reading it ran the identical rule on three other changed files — which returned non-comment lines immediately — plus a synthetic leg carrying one known code line. And the #6854 pin's executable content is byte-identical, not merely equal in size: ⭐ The surface breach was verified as declared, four ways: nothing skipped or disabled at either revision (census with a firing positive control); the breach genuinely mandated (restoring the merge-base file onto the head source tree reds 2 rows); the One reading this seat is keeping, and it is about the implementer's instrument rather than its conclusionThe JSON census conclusion is right and the reviewer established it independently under a structural rule — parse every tracked Generated by Claude Code |
…rtcut-named-refusal
Round-1 contract review returned FAIL on six defects, all false published prose or figures rather than substance. The ruling implementation itself was verified compliant and is unchanged by this commit. 1. `app-action-onclick-refusal-6854.test.ts` carried a comment saying "the assertions below are unchanged" and "both undeclared keys are gone" — this branch had removed one of the two assertions directly beneath it, and `shortcut` is no longer an undeclared key at all. Rewritten to describe the single surviving assertion and the two-contract split, citing the sibling row by NAME rather than by position so it cannot go stale on insertion. The deleted assertion is deliberately NOT restored: the parse now fails, so there is no parsed value left to read the key out of. 2. `.changeset/6854-layout-renderer-retired-onclick.md` publishes verbatim to users and carried three sentences this branch falsifies (an "eight-member" mirror that now has nine, "neither declares", "strips in silence", and an open contract question that is now answered). Corrected in place, prose only, frontmatter untouched. The changeset-claims gate cannot see this file because it matches backticked file names and this one names only symbols. 3. The card's own changeset claimed `@objectstack/spec` removed `action.shortcut` together with its tombstone. Measured against the RESOLVED installed package: the tombstone is live and refuses the key by name. Corrected to the measurement, with the positive control that separates a key-specific refusal from generic strictness, and with the difference in route stated rather than flattened — nothing was removed as authorable here, because the key was never declared on this face. 4-6. Three figures frozen into prose that nothing re-derives. Replaced with pointers to the instruments that produce them, rather than corrected in place: a corrected number re-stales on the next push, which is exactly what happened to the stale test line citation when this branch's own docblock moved the test down ten lines. Also replaces the migration census in the card's changeset with a re-runnable structural rule. The textual rule stated there was handed an empty population, so its zero was vacuous even though its conclusion was right. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewReviewed head: Store depth, published with its timestamp — Population bound (proved)File set from Claim count89 distinct falsifiable claims judged, enumerated by this rule — one claim = one distinct falsifiable proposition asserted by the diff's 525 added lines or published about it in the PR body — in four classes: (A) 22 changeset propositions published verbatim to users; (B) 16 source-comment propositions; (C) 33 executable assertions added by the diff ( ① Own measurementThe objectui#3378 console guard — taken with the PROJECT-TAG rule and self-tested before being trusted. Counting rule stated: count result lines whose first Self-test leg, one real console file collected from the repository root ( Reading on this PR's own run ( Ruling re-verified as not regressed: option B refused (an authored Round-1 items 1–6 and the addendum, re-measured independently: item 2 frontmatter is genuinely untouched — 61 bytes, sha256 Addendum, re-derived with my own rule. Population 1564 real changesets (1565 tracked The deliberate non-repair of changeset 6692 is right, verified at the merge-base before concluding. At Of the four defects the implementer reports catching in its own repair diff, (a), (b), (c) and (d) are verified true in the current text: the citation is by name and the named row exists; "no authorability was removed" is independently confirmed — the only two commits that ever touched The sweep did not stop there, and found three claims that are false at this head. All three are one failure mode — prose describing a prior state, published at a head that falsifies it, which is the class round 1 failed on. ② Clause-2 carrier gate
Before this comment — exit After-run exit code is reported to the dispatcher with this comment's id. ③ Checks at the reviewed headRead from
NOT MEASURED — each with its reason
Implemented-by: os-dev implementing agent for card objectui#7719 (separate subagent run) FAIL
Generated by Claude Code |
All three are the same failure mode: prose describing a prior state, published at a head that falsifies it. 1. `app-action-onclick-refusal-6854.test.ts` — the comment added last round said `shortcut` "makes this very document FAIL to parse" and that `'shortcut' in first` is "unreachable now, because there is no `first` to read". Both are false of the code six lines below: the fixture no longer carries `shortcut`, so the document parses green and `first` is destructured and read. Rewritten to say what is actually true — restored alone the assertion would still PASS and pin nothing, and making it mean anything would require putting the key back into the fixture, which is what makes the parse fail and the early return fire. 2. `app.ts` — the new docblock claimed a `grep` for `shortcut` over the file "finds that member first", meaning `AppAction`'s. This PR's own 28-line insertion reversed that: `AppMenuItem`'s refused member now comes first. A claim born false, true at the merge base and falsified by the same diff that wrote it. The ordering clause is DROPPED rather than inverted — an ordering claim is falsified by the next insertion, so the durable statement is the one that survives motion: the file declares the key twice, on two interfaces, and a position is not an owner. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt
…rtcut-named-refusal
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewRound 3, reviewed head The three round-2 items are genuinely repaired, and I re-derived them rather than accepting the self-report. Item 1's two ablation legs reproduce exactly (leg A: 7 passed / exit 0; leg B: exit 1 / 2 failed, and the two reds are precisely "a whole action carrying such an item parses green" plus the sibling row sharing It still FAILs, on the same failure mode as round 2 — prose describing a prior state, published at a head that falsifies it — at six sites across four claims, including one that is born false from this PR's own diff and one in the very sentence that argues against transcribing counts. ① Sweep — claim count and PROVED population boundCorpus (closed, and every line read):
How the bound is proved. Every claim lives on a line of A or B, so bounding the line sets bounds the claims. I partitioned each corpus into mutually exclusive, exhaustive classes and required the partition to sum to the total — a counter that fails that self-test is not used:
Two independent rules reconciled: my own diff partition yields added=537 / removed=28, and the GitHub API's I read A in three contiguous ranges covering lines 1–250 with no gap, and B hunk-by-hunk across all 13 hunks. 72 distinct checkable claims adjudicated; 68 reproduce, 4 are false (at 6 sites). Classes were derived from what round 3 changes the truth of, not from the three item numbers: the rewritten item-1 comment, the count-to-rule conversion, the dropped ordering clause, the re-derived changeset census — plus internal consistency as its own class, which is where items 2 and 3 below were caught. Reproduced (a sample, each with its own measurement): the mirror is The objectui#3378 guard, with the discriminator self-tested before it was trusted. From the repo root, ② Carrier gate
The pair formed on the PR number. I did not pass the card number, which would have made the pair unformable and exited 2 — not a clearance and not a red. ③ Every check greenRead from
VerdictFAIL
NOT MEASURED — each with its reason
Disclosed errors and near-misses of my own
Hard-rule observationsNo GitHub review was created — this is a plain issue comment. No label added or removed; no approve, merge, enqueue or draft flip; no CI job re-run (I polled Implemented-by: Claude Code session Generated by Claude Code |
…rtcut-named-refusal
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewRound 4, reviewed head Merge-base derived here, not adopted. The substance reproduces, and round 4's central structural claim is true by blob hash. All 8 files in ① Sweep — claim count and PROVED population boundCorpus. A — the PR body at this head, fetched from the API: 283 lines / 39,706 bytes. B — the diff Bound, and how it was proved. Every line of A is structural or content. Structural = blank, a table separator row matching Two independent selectors, reconciled. Rule A (a line carrying an assertion token — a backtick span, a bold span, an integer, or one of Self-tests, run BEFORE any counter was read. The splitter returned 283 on a non-empty corpus; the structural filter was asserted non-degenerate (it removed 125 and kept 158 — a filter that removed everything, or nothing, would have thrown). Residue test. Every one of the 132 rule-A lines maps to a 45 distinct checkable claims adjudicated; 45 reproduce; 0 false. Classes derived from what round 4 changes the truth of, not from the four item numbers: Class 1 — the four repairs (8 claims). Item 1: the trap paragraph is tensed to the merge-base with the head stated. I re-derived ownership by my own brace-walk (comments and string literals stripped so Class 2 — outside the body (6 claims). This is where I expected to find the round's defect, and did not. Round 4's repairs are body-only, so I defaulted the population to the whole tracked tree at this head — 7,764 files (self-tested: 179 files contain ⭐ The one that could have bitten. Class 3 — citation class (7 claims). 7 resolved, 7 resolve correctly, 2 declared stale, 0 stale and undeclared. At the merge-base, Class 4 — figure class, both questions (9 claims). Not only is it true now but was it ever true, against what. 39 workflow files: 39 at this head and at every one of the 7 shas in this PR's history including the merge-base ( Class 5 — source claims (6). Both runner files are 100% comment-only: 0 of their changed lines begin with anything but a comment marker, by a filter I inverted to print any offender. The renderer pin's code body is byte-identical merge-base→head by md5 (instrument self-tested — it differs on Class 6 — internal consistency (4), run as its own class on the document alone. 20 + 2 = 22 ✓; 4300 + 8 = 4308 ✓; 39 − 6 = 33 ✓; +16 / −6 = +10 ✓. The seventh site round 4 found itself is genuinely closed: the counting rule no longer says "for each of the 1565 pending changesets", and the three surviving Class 7 — the authority (5). The ruling blockquote is byte-identical to comment
② Carrier gateFrom
I verified in the script's own source that ⛔ The after-run is taken after this comment lands. I am not writing its value here, because I have not taken it yet. ③ Every check green
At my first reading (13:50Z) three test shards were
Candidates I measured and dropped⭐ Dropping a measured candidate is a result too.
NOT MEASURED — each with its reason
Disclosed errors and near-misses of my own
Hard-rule observations
VerdictThree consecutive record-accuracy rounds do not make a fourth obligatory. Round 4 repaired all four items, found a seventh site itself, closed the PASS Landable on my reading, as a contract review. The ruling is implemented literally, nothing is regressed, ③ is met at this head, and I found no false claim in the body or in the tree. ⛔ That is the limit of what this comment settles. Landing itself was never mine — ⛔ I have not approved, enqueued, merged, flipped the draft, or touched a label, and this PASS is ⛔ not an instruction to any gate to treat itself as satisfied. Implemented-by: claude/issue-7719-shortcut-named-refusal — a Generated by Claude Code |
Carrier discharged —
|
| verdict | round 4, 5665297214, 2026-09-14T14:08:39Z |
| head it declares reviewed | 4a1a7217e1a4b6d682bcebb24dc4155b43dabb31 |
| PR head at discharge (2026-09-14T14:40Z) | 4a1a7217e1a4b6d682bcebb24dc4155b43dabb31 |
| comparison | ⭐ character-identical, compared as strings — ⛔ not by prefix, ⛔ not by short sha |
| verdict token | **PASS** present in the comment body |
The operative rule is the dispatch protocol's, quoted by the guard verbatim: 「双肢命中任一 ⇒ 无席内条款②复核 PASS 在案 ⛔ 禁止入队」. ⇒ with a PASS on record for this exact head, the prohibition does not apply and the carrier is discharged rather than stripped.
The other two gates, measured at this same head
- ② carrier gate —
PM_SWEEP_REPO=objectstack-ai/objectui node scripts/pm/check-clause2-carriers.mjs --pair 9495, run from/home/user/objectstack, exit captured before any pipe: exit 0. ⛔ That 0 is not evidence about the clause-② independence limb — C4 printed no row, on this pair or on any of the four in this series, and this seat did not establish why. Independence rests on the maintainer's ruling 「新起的子代理算干净」, judged by independent measurement — the reviewer used its own worktree, its own corpora and its own instruments, and disclosed its own errors. - ③ every check green —
commits/4a1a7217e1a4b6d682bcebb24dc4155b43dabb31/check-runs?per_page=100:total_count37 == 37 returned, 34success+ 3skipped, 0 not-green, 0 still running; legacy combined statussuccess.⚠️ The count moved 36 → 37 when the PR left draft:Governed Surface Queue Guardstarts on thepull_requestevent, and it is green (started 14:39:20Z). - Governed surface —
pulls/9495/filesread in full: zero paths underdocs/adr/**,.claude/**,skills/**,AGENTS.md,CLAUDE.md. ⇒ the guard's governed leg has nothing to refuse; only its carrier leg was ever in play.
What the review verified
45 distinct checkable claims, 45 reproduce, 0 false, over a proved 158-line body corpus plus the whole 7,764-file tracked tree, residue 0.
Identity pair, in the fixed spelling of check-clause2-carriers.mjs:2234-2244 — Implemented-by: claude/… is the branch, because a mode:subagent dev has no session of its own:
Implemented-by: claude/issue-7719-shortcut-named-refusal
Reviewed-by: session_01L5xpA5q533BgTTNADibEFt
Next action
⛔ Not a merge. The PR goes to the merge queue via auto-merge; the queue rebuilds on current main at landing. ⛔ No main was merged in by hand: the branch reads mergeable: true / clean, and a manual sync would move the head and falsify the ① reading proved above — as well as re-rolling Test (shard 1/4), measured this shift at 1199.0 s against its 1200 s ceiling.
Generated by Claude Code
Fixes objectui#7719
Round 2 — the six round-1 FAIL items, each repaired
Round-1 contract review returned FAIL at
76a2ce6881on six defects, all false published prose or figures; the ruling implementation itself was verified compliant and is unchanged. The list was adopted whole — ⛔ nothing reinterpreted, nothing left unimplemented.app-action-onclick-refusal-6854.test.tssaying "the assertions below are unchanged" and "both undeclared keys are gone", directly above theit()block this branch took from 3expect()calls to 2.changeset/6854-layout-renderer-retired-onclick.md— three sentences this branch falsifies, publishing verbatim to users@objectstack/specremovedaction.shortcuttogether with its tombstone4 source file(s); the gate says6scanned 7667 tracked text files; the gate said7668:116cited a differentit()— this PR's own +10-line docblock had moved the named test to:126⭐ Items 4, 5 and 6 are one defect with three instances, and the fix is the pointer rather than the corrected number. That is not a preference — it is measured: the review's own correction of item 5 to
7668had already staled by the next push, and the value it staled to staled again. ⛔ No count is quoted here, because quoting one is the defect this sentence describes; the gate prints its own.The two sweeps run on top of the list
Every pending changeset, not only the one the review surfaced.
check-changeset-claims.mjsmatches backticked file names, so it is blind to a changeset naming only symbols — which is how defect 2 survived. Counting rule: for every pending.changeset/*.md(populationls .changeset/*.md | wc -l, dated per sha in the round-3 notes below — ⛔ not quoted here, it moves with every merge), countgrep -oFmatches ofMenuItemSchema,AppMenuItem,AppAction.items,shortcut,LayoutRenderer; list every file with a non-zero total; then read every hit in context and resolve which declaration it names.grep -lF 'changeset', selects 110 of the same population — ⭐ the predicate is published here because round 3's body named that figure without one, and a count without its rule is not a reading). 20 are adjudicated not falsified, and the dominant reason is the ownership trap this card is about:MenuItemSchema/MenuItem/shortcutin6132,6521,6523,6931,7004,7072,7073all resolve to the overlay union ({ separator: true }divider,MenuCommandItem), which this diff does not touch.6301,6490,8348,marketplace-disabled-state-5504,olive-clouds-repeatuse "shortcut" as an ordinary English word.6143andrename-command-palette-shortcuts-demoareCommandItem, a different type.6349,6692,7344,7721,7760,7918make statements that remain true. The 2 that were falsified are the two repaired above.origin/mainmerge, and each newly arrived changeset is checked against the same five terms; the result is stated at the head being pushed, never carried over from the pre-merge tree. ⛔ The arrival count is not quoted — it is a property of whichever merge just happened, and quoting round 2's value at round 3's head is exactly how it went wrong before.6692citesapp.ts:728andapp.ts:461, and this diff inserts intoapp.tsabove the first of those (the hunk header ingit diffnames the offset; ⛔ the line count is not transcribed — it moved again this round, from 28 to 33, which is the point). Re-measured at the merge-base this PR actually points at:app.ts:728istitle: string;and:461is a comment fragment — both were already stale before this branch existed. This diff shifted an already-wrong pointer rather than breaking a correct one, which is exactly whatcheck-new-cross-file-line-citations.mjsrefuses to sweep, and it is not repaired here.My own repair diff, re-read adversarially — the habit, not the gate, since nothing in CI catches a newly introduced false claim. It caught two of my own:
shortcutwas never authorable onAppMenuItem, so nothing was removed. Corrected to state the shared end state and the different route. TheonClick-still-dropped half of the defect-2 repair was measured before being written (parse succeeds, key absent from output, and theshortcutcontrol on the same parse fails).Round 3 — the three round-2 FAIL items, each repaired
Round-2 contract review returned FAIL at
da2e719101on three items. It confirmed round 1's six items and both addendum items as genuinely repaired, re-measured rather than taken on my word, and confirmed the ruling is not regressed. ⭐ All three new failures were prose defects the repair itself introduced, and they are one failure mode: prose describing a prior state, published at a head that falsifies it. Adopted verbatim.shortcut"makes this very document FAIL to parse" and that'shortcut' in firstis unreachable "because there is nofirstto read" — both false of the code six lines below, since the fixture no longer carriesshortcut.changeset/6854-layout-renderer-retired-onclick.md— the file that publishes verbatim to userscheck-governed-queue-guardrow, which carried the same arithmeticAppMenuItem.shortcutdocblock asserted that agrepfindsAppAction's member first — true at the merge-base, and reversed by this PR's own insertionAppMenuItem's;AppAction's comes second⭐ Item 3 is the sharpest form of the mode: a claim born false — written against the merge base while describing the head, falsified by the same diff that wrote it. The review notes
check-changeset-claims.mjsstates in its own output that this is precisely the class it does not cover, so ⛔ no gate catches it. The counter-discipline is now applied to this whole body: every state-dependent sentence re-read against the head about to be pushed.Item 2's population rule, stated rather than counted
⛔ Not "eight". A count re-stales every time the diff grows, and this one had already staled once between rounds. The scan is re-run over the rule's full output at each push, and the scanner is self-tested each time — seeding a
\x07into a scratch file and confirming the same invocation exits 0 on it. At this head: population 8, scan no hits (grep exit 1), self-test fires (exit 0). Thecheck-governed-queue-guardrow now takes the same rule as its input and quotes the gate's own echoed count rather than a transcribed one.Item 1, verified by my own ablation rather than by accepting the review's
From the committed state, under
trap … EXIT INT TERM, absolute paths, mutation proved on disk by blob-hash comparison before any result was read, restore proved bygit diff HEADnaming no file:expect('shortcut' in first).toBe(false);onlyshortcutin theauthoredfixture⇒ the repaired comment now states leg A and leg B as what they are, where the round-2 text asserted leg B's outcome about leg A's code.
Three more instances of the same mode, caught by my own adversarial re-read before pushing
⛔ Not found by any gate, and not on the review's list — found by re-reading my own repair against the head:
app.ts:578" — my own item-3 repair moved it to 33. Restated as the rule (git diff's hunk header names the offset) with the number deliberately not transcribed.ls .changeset/*.md | wc -l): 1565 atda2e719101, 1567 at35cc5de208, 1567 at this head. ⛔ Arrival counts are not quoted — see the rule above.Round 4 — the four round-3 FAIL items, across six sites
Round-3 contract review returned FAIL at
35cc5de208on four items over six sites, all in this body, none in the source. It confirmed round 2's three items as genuinely repaired (re-derived, not accepted on my self-report), the ruling implemented literally and not regressed, and 68 of 72 claims reproducing. Adopted verbatim.shortcuthit inapp.ts/app.zod.tsbelongs toAppAction— true at the merge-base, falsified by this PR's own refusal. It also contradicted the docblock this same diff adds above it and its own "What changed" row7674published twice as "this head's" control-byte count; it was the round-2 head's value+2its own sentence reported⭐ Item 4 is different in kind and is the most useful thing in this review. Items 1–3 went, or were born, false against a moving head. Item 4 was simply wrong when written, and then survived two full review rounds — because every round checked figures against the current head, and a figure that was never true at any head passes that check trivially. ⇒ the second question, now applied to every figure in this body: not only "is this true now" but "was it ever true, and against what?" I re-ran item 4's count at
76a2ce6881,da2e719101,d0a7b11336,35cc5de208, the merge-base and this head: 39 at every one.Internal consistency — a cheap instrument I was not running, now run
Three of round 3's six sites were this document disagreeing with itself, which needs no tree access at all. Running it caught one more the review had not listed: the sweep's counting rule said "for each of the 1565 pending changesets" while the round-3 notes twelve lines below reported the population at 1567 for this head. Restated as the rule, with the population dated per sha instead.
Figures kept, and why
⛔ Two candidates the review measured and explicitly declined to score are left alone rather than tidied: the
1565→1567population move (both correct at their respective shas, and this body discloses it — now dated to each sha with its counting rule, because an undated total is the defect this section is about), and the39 packagesfixed-group figure (40 entries of which 39 are@object-ui/*-scoped — a defensible reading, untouched). ⛔ I did not "fix" either.One trip-wire closed while editing figures
The review could not score "the 110-file positive control" because the body named the figure but published no predicate for it anywhere — "a count without its rule is not a reading". Its rule is now published beside it: the same per-file rule applied to a term known to be present,
grep -lF 'changeset', which selects 110 of the same population.The rule I am taking from the reviewer's own disclosed near-miss
⛔ Never write a reading in the past tense before taking it. Recorded here because it is the same class as items 1 and 3, committed by the reviewer diagnosing that class, and caught only on re-read.
The authority
Director seat decision batch #70,
5565626122, 2026-09-07, maintainer verbatim 「同意」. Quoted rather than paraphrased:All three clauses are implemented literally. Option B and option C are refused,
LayoutRenderergains no read, and the renderer pin's four assertions are byte-identical.What changed
AppMenuItem(packages/types/src/app.ts)shortcutmembershortcut?: neverMenuItemSchema(packages/types/src/zod/app.zod.ts)retirementTombstone()named refusal pointing atNavigationItemLayoutRenderer.tsxLayoutRenderer.appActionItems-6854.test.tsxshortcutmust stay unrendered for ever"Which refusal helper, and why it is a measurement
retirementTombstone(). The two rejected candidates, each rejected on a reading rather than a preference:handlerKeyRefusal(), despite the in-file precedent on the siblingAppActionSchema.onClick. Its message says JSON has no function value — false of a string-valued key an author can perfectly well write — and itsz.customprimitive makesz.toJSONSchemathrow. Measured before the change:z.toJSONSchema(MenuItemSchema)succeeds in both io directions, whilez.toJSONSchema(AppActionSchema)already throws ("Custom types cannot be represented in JSON Schema") because of that veryonClickarm. Choosing it here would have takenMenuItemSchemafrom representable to throwing. Ablation A4 below turns that into a number.aliasKeyRefusal()composes "Did you meanshortcut→canonical?", and no member ofMenuItemSchemameans a keyboard shortcut. The remedy is a different TYPE, not a sibling spelling, so the helper could only be made to say something untrue.retirementTombstone()is the helper whose own documented contract is this exact situation — "a key that stays declared but is unwritable, so an authored value is REFUSED loudly instead of being silently stripped the way an undeclared key would be". Declaration history does not separate it from its siblings:aliasKeyRefusal's own docblock records that the overlayMenuItemSchema.type(objectui#6523) is aretirementTombstonefor a spelling that was never declared either.@objectstack/spec'sretiredKey, whose describe contract prefixes[REMOVED]; these describe strings are this package's published metadata.Premise verdicts — each re-measured, two falsified
MenuItemSchemacarries its own TS declaration as BOTHz.ZodTypetype argumentsshortcutdid not occur inMenuItemSchema's object literalAppMenuItemstill has noshortcutmemberneverand the mirror refusal "typecheck against each other"⛔ Falsified premise 5 — the two halves do not typecheck against each other
The 2026-09-07 deferral parked this card behind objectui#8354 on the argument that the retype makes a half-landing a compile error. Ablation A1 measures that it does not. With the mirror refusal in place and
shortcut?: neverdeleted fromAppMenuItem,tsc --noEmit -p packages/types/tsconfig.jsonexits 0.z.ZodTypecompares the resolved output and input types its two type arguments name, and an extra optionalundefined-valued member is mutually assignable with an interface that omits it.⇒ The "declaration and refusal in ONE PR" constraint is normative, not mechanical. Both halves are in this PR, but ⛔ nobody should believe the type system will catch a future half-landing.
What does catch it is the new pin. Ablation A1b: the same deletion, run against
tsc -p packages/types/tsconfig.test.json, exits 2 with one error, at the subject row:⛔ Falsified premise 6 — the failure mode here is STRIP, not KEEP
objectui#7997 recorded that a dropped member key is KEPT, because
BaseSchemaCoreends.passthrough()andBaseSchemacloses with an any-valued index signature. ⛔ That mechanism does not reach this surface.MenuItemSchemais a plainz.objectunder az.lazy, built on no base, andAppMenuItemdeclares no index signature. Measured on the unmodified tree:The key is gone from the output, not carried in it. The dispatch's instruction — "pin an undeclared sibling key through the same parse and watch it survive" — would have pinned a falsehood on this schema, so block (c) pins the reading that is actually true here: the key is accepted and stripped. Same conclusion (a bare non-declaration refuses nothing), different mechanism, and the card's own phrase "stripped in silence" turns out to be the accurate one.
Before this change, every⚠️ At this head the same grep no longer says that, because this card's own refusal is now one of the hits: resolved by brace-walk rather than proximity,
shortcuthit inapp.ts/app.zod.tsbelonged toAppAction— the header BUTTON's own shortcut, one level up fromitems[]— and none toAppMenuItem. That is the trap: the seat's first reading of those hits was "option B has already been landed".app.tsdeclaresshortcutonAppMenuItem(the refusal) and onAppAction(the authorable member), andapp.zod.tsonMenuItemSchemaand onAppActionSchema. ⇒ ⛔ resolve which declaration OWNS a hit before acting on it; that is the durable lesson, and it is the same one theAppMenuItemdocblock states in the source.AppAction.shortcutremains declared, authorable and deliberately untouched. It is protected here by non-target controls on both faces: a mutual-assignability row pinningAppAction['shortcut']asstring | undefined, and a parse row requiring an action carryingshortcutat the action level to stay green. Ablation-wise, over-applying this card to everyshortcutin the file reds both.Consequence for the refusal message: it names
AppAction.shortcutexplicitly so it cannot be read as "shortcut is refused on app actions generally", and block (b) asserts that sentence is present.⛔ SURFACE BREACH — declared, not silent
The dispatched file surface named
packages/types/src/app.ts,packages/types/src/zod/app.zod.ts, a new pin, a changeset, and conditionallypackages/runner/src/LayoutRenderer.tsx. This PR also edits three files not on that list:packages/runner/src/__tests__/LayoutRenderer.appActionItems-6854.test.tsx— comment-only, the:30staleness repair the dispatch explicitly asked for. Its assertions are unchanged; every line in its diff begins with a comment marker.packages/types/src/__tests__/app-action-onclick-refusal-6854.test.ts—onClickandshortcutin one fixture and asserted the parse was green with both stripped. The ruling makes theshortcuthalf false, so that block goes red the moment the mandated change lands — it is not optional collateral, it is a mandated consequence. The triage keeps the block's actual subject (onClickis still stripped in silence, which is why the deleted cast could never have been fed by an author) and splitsshortcutout into a row asserting the new refusal, cross-referencing the new pin. ⛔ Nothing was skipped, disabled or quarantined.⛔ SECOND SURFACE BREACH, added in round 2:⚠️ That report is itself the evidence the gate saw the edit; a silent pass there would have meant the opposite.
.changeset/6854-layout-renderer-retired-onclick.md— a changeset belonging to another card, pre-authorised by the PM after the review showed the gate that would normally catch it cannot see this one. The alternative was shipping three false sentences into users' CHANGELOG. Conditions honoured: prose only, frontmatter byte-identical (asserted programmatically in the edit, not eyeballed), and exactly the three named sentences corrected and nothing else.check-changeset-overwrite.mjsreports the edit — its case 2, report-only — and it does: "1 changeset(s) added, 1 modified", naming that file.Reported rather than worked around. For (2): the card cannot be implemented without that file changing, and the alternative was landing the ruling with a red pin. For (3): the alternative was shipping three sentences to users that this PR makes false, in a file the relevant gate is structurally unable to see.
Census run before deciding, so the triage is bounded rather than hopeful — zero JSON documents anywhere in the tree author
shortcutinside anitemsarray.*.jsoncontaining the word", and that population is 0 files — thegit greppathspec matched nothing, so the rule was handed an empty corpus and could not have found anything. ⛔ A zero from an empty corpus is not a reading, even when the conclusion is right. Reproduced and confirmed at this head before replacing it.The replacement is structural, and its population is derived rather than filtered:
JSON.parseevery tracked*.json, walk the whole value tree to unlimited depth, and report every object that is an element of an array reached through a key literally nameditemsand carries an ownshortcut. Populationgit ls-files '*.json'; the unparsable JSONC tsconfig family is checked separately for the literal"shortcut"so the zero is bounded over the whole population, not just the parsable part. Positive control fires: injected fixtures yield 2 hits including one nested at$.actions[0].items[1].children[0].items[0], and the control correctly does not count an action-levelshortcutsibling. ⛔ No population figure is frozen into this prose — the rule is the deliverable, and the counts move with the tree.Red-first, verbatim
The pin was written and run before any source change. Exit codes captured before any pipe.
npx vitest run packages/types/src/__tests__/app-menu-item-shortcut-refusal-7719.test.ts→ exit 1:The seven reds were exactly the runtime subject rows — the mirror refusal, its issue code, the nested-⚠️ The four type-level rows in block (a) passed vacuously under vitest, which strips types.
childreninheritance, the strip/refuse pair, theitems.0.shortcutpath, and the emitted JSON-Schema property.npx tsc -p packages/types/tsconfig.test.json→ exit 2, one error, which is where those four rows actually live:Ablations
Each ran from the committed state under
trap … EXIT INT TERM, with absolute paths, the mutation proved on disk by a blob-hash comparison before any result was read, and the restore proved bygit diff HEADnaming no file. Restore usesgit checkout HEAD -- path, never a baregit checkout --. The subject resolves through vitest's@object-ui/typesalias topackages/types/src, and through sibling source imports fortsc— ⛔ nodistis in the path, so no staleness sits between the mutation and the reading.shortcut?: neverfromAppMenuItemtsc --noEmit -p packages/types/tsconfig.jsontsc -p packages/types/tsconfig.test.json(105,39)retirementTombstone()arm fromMenuItemSchemashortcutrow in the 6854 pin; ⭐ every CONTROL row and every TS-face row stayed green.catchall(z.unknown())):235— block (c)'s "never reaches.data" assertion; ⇒ that row reads real stripping behaviourhandlerKeyRefusal()NavigationItem, code becomescustom, andz.toJSONSchemathrows "Custom types cannot be represented in JSON Schema" ⇒ the helper choice is a measurementTests no tests, and an empty control has failed, not fired. It was corrected and re-run; only the corrected run is above.Each proving removal is recorded beside its assertion in the pin, not only here.
Derived gate list
Enumerated from⚠️ the round-1 through round-3 bodies said 38, which was never true at any head, not a staling) — by extracting every reachable
.github/workflows/— 39 entries at this head, counting rulegit ls-tree --name-only HEAD .github/workflows/ | wc -l, and 39 at every head in this PR's history (pnpm check:*/pnpm lint/pnpm type-check/node scripts/*.mjsinvocation, then filtered to those my diff can reach. Exit codes captured withcmd > file 2>&1; EXIT=$?, never through a pipe.packages/types+packages/runnerpnpm test(from the repository ROOT, objectui#3378)packages/typestype-check(all three programs)packages/runnertype-check(both programs)turbo run lintfor both packagespackages/types, unchanged)check-changeset-presence.mjs4; the gate said6)check-changeset-no-major.mjs/-fixed.mjs/-claims.mjs/-overwrite.mjscheck-control-bytes.mjscheck-new-cross-file-line-citations.mjscheck-spec-symbol-derivation.mjscheck-handler-key-read-sites.mjscheck-test-path-roots.mjscheck-unreferenced-sources.mjscheck-governed-queue-guard.mjs --test, fed every path ingit diff --name-onlyagainst the merge-base7staledcheck-package-self-import.mjs/check-side-effects-array.mjs/check-dist-completeness.mjs --allvitest.config.mtsputs./apps/console/vitest.config.tsin itsprojectsarray, and when a console test is collected the reporter prints its path project-relative (src/__tests__/App.docsPortalLazy.test.tsx), ⛔ notapps/console/src/.... So neither "the raw tokenapps/consoleappears 0 times" nor "0 collected paths start withapps/console/" can see a console file at all.RULE (the discriminator): count result lines whose project tag is
|@object-ui/console|. Self-tested on a leg with a known answer before being trusted — collecting one real console file (apps/console/src/__tests__/App.docsPortalLazy.test.tsx) makes the tag rule read 1 while the raw-token rule reads 0 and the path-prefix rule reads 0. ⇒ the two older rules are demonstrably dead instruments; the tag rule fires.Reading on this PR's own run: project-tag 0, path-prefix 0, raw-token 0 — all three agree, and the tag rule is what makes that agreement a reading rather than three silences. Corroboration that the tag field is populated rather than absent: the only project tags present are
unit(4300 rows) anddom(8).Beyond the gates, a self-scan for control bytes with
grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'over every file ingit diff --name-onlyagainst the merge-base returned no hits (grep exit 1)..changeset/6854-layout-renderer-retired-onclick.md— the one that publishes verbatim into users' CHANGELOG, which is exactly where a control byte would matter most. A count re-stales every time the diff grows; the rule cannot. The scan is re-run over the rule's full output at each push, and the scanner is self-tested each time by seeding a\x07into a scratch file and confirming the same invocation exits 0 on it — ⛔ a scan that cannot fire returns the same "no hits" as a clean one.All readings above were re-taken at⚠️ Every state-dependent sentence in this body was re-read against that head before it was published, not against the tree the round began on — the discipline the round-2 review named, after three separate claims in this body proved to be prose describing a prior state.
4a1a7217e1, the merged head this PR points at, whose merge-base withmainis8fa7d69af2.⛔ NOT MEASURED — each with its reason
pnpm lint(eslint . --no-inline-config) andpnpm test— CI-owned full-farm runs. The two packages my diff touches were linted and tested in full instead. Supporting the narrowing:eslint.config.jsconfigures no type-aware linting (noprojectService, noproject:parser option), so this diff cannot move the verdict on any file it does not contain.check:*in the workflow enumeration (i18n, doc-snippet/doc-example/doc-fence, skills, shadcn, lockfile, e2e, performance-budget, eager-closure, upstream-port-parity, vi-mock family, and the patrol workflows) — none of their input surfaces appears in this diff, and running the farm locally is what the derivation exists to avoid. CI-only.@objectstack/spectombstone foraction.shortcutunmeasured in the resolved package while the card's changeset simultaneously asserted it as fact, and wrongly. It costs one parse, so it was measured: resolved@objectstack/spec@17.4.0,ActionSchema.safeParse({name,type,label,shortcut})returnssuccess: falsewith ashortcut-addressed issue,code: invalid_type, message opening "action.shortcutwas removed in @objectstack/spec 17.0.0 (audit close-out)". Control: an undeclared sibling key is also refused but produces noshortcut-addressed issue, so the reading is about this key and not about generic strictness. ⇒ the key lost its authorability; its tombstone is live. The changeset now says that, and states the difference in route rather than flattening it — nothing was removed as authorable in objectui, because the key was never declared on this face.shortcutinto the DOM" included. ⛔ Cited by NAME, with no line number: round 1 cited:116, and this PR's own +10-line docblock had already moved that test down to:126— the "a line number near a symbol is not a reading of which declaration owns it" hazard, committed by the very body that warns about it.check-changeset-claims.mjswarns describe files this change touches — read as a warning (the gate exits 0) and left alone; ⛔ not this card's to edit.Changeset
.changeset/7719-app-menu-item-shortcut-named-refusal.md, gradedminorfor@object-ui/typesper this repo's fixed-group convention (⛔ nevermajor: one major carries all 39 packages off the@objectstackmajor this repo is pinned to). It spells out that this is a narrowing of a published mirror — a document that parsed green now reds at that key — and records the zero-migration measurement, the untouchedAppAction.shortcut, and that@object-ui/runner's change is comment-only.Review gate
Clause-②: yes, per the claim comment. It overrides the 2026-09-07 comment'sClause-②: no: that citedComboboxSchema.defaultValue(PR objectui#8324, no carrier), while objectui#7997's retirement ofDetailViewSchema.related— the same?: neverplus tombstone shape on the same kind of published mirror — went through a full contract review with the carriers hung and cleared on record. Two precedents in opposite directions ⇒ act on the stricter. ⛔ Batch #70 does not grade clause ②, so this fills a gap rather than overriding a ruling.⛔ This PR stays draft. ⛔ No label was added or removed by this seat. ⛔ Nothing merged or enqueued.
Implemented and repaired in Claude Code session⚠️ The durable attribution is stated here in prose deliberately: editing this body makes the platform append its own footer block, so re-posting a second one each round would accumulate footers rather than attribute anything.
session_01L5xpA5q533BgTTNADibEFt.Generated by Claude Code