retire(types): remove the EventHandlersSchema published export (ADR-0049 enforce-or-remove) - #9552
Conversation
…erately untouched ⛔ NOT a finished change. The public export `EventHandlersSchema` (`z.record(z.string(), z.function())`) and its barrel line are removed and the knowledge is preserved as a NOTE at the declaration site, per the maintainer ruling on objectui#6124 (decision batch #8, reconfirmed batch #25) recorded on objectui#6910. The third half of that ruling — deleting the parity-ledger row `'base.zod.ts#EventHandlersSchema'` in `packages/types/src/__tests__/zod-mirror-parity.test.ts` — is NOT in this commit. That file is held by the open PR #8763, and this seat's dispatch forbids editing it. So this tree is RED BY CONSTRUCTION: `zod mirror parity — the population is closed > no map entry names a const that no longer exists` fails with `['base.zod.ts#EventHandlersSchema']`. Measured, not predicted. No PR is opened from this commit. It exists so the NOTE and the measurements survive the hand-off. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012W3vMLTFY9SPr2LyxhSeYi
…ire-event-handlers-schema
…e halves Completes the retirement the WIP commit on this branch left deliberately incomplete. The public export and its barrel line were already gone; this commit removes the parity-ledger row the removal reddens, repairs the one docblock the removal falsifies, and declares the change. - zod-mirror-parity.test.ts: delete the self-contained two-line EXCLUSIONS entry for the removed const. Its stale-entry census reads exported consts off disk, so the row went red the moment the export left; the sibling HTMLAttributesSchema entry shares the same reason string and is untouched. - handler-keys-json-refusal-6124.test.ts: the docblock over MIRROR_FILES said base.zod.ts "holds only EventHandlersSchema ... and no named key", which the removal makes false. Rewritten to state why base.zod.ts is not one of the nine mirror files; the block is kept, since it is what explains the census population. - base.zod.ts: correct the NOTE. It claimed handlers are authored as BaseSchema.events, an event-name string keying ActionSchema[]. Checked against the tree rather than inherited: BaseSchemaCore declares no events member, nothing reads schema.events, and every authored events in the corpus is TimelineSchema.events, an unrelated array. The NOTE now points at the real JSON face -- handlerKeyRefusal() and the node-type spelling -- and records the stale sketch as a trap rather than repeating it. - One changeset, scored minor by the presence gate's own verdict, with no BREAKING banner: the four-repo consumer census is zero. 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 reviewVerdict: PASS — with one MAJOR finding that should be corrected before release (it publishes into the emitted Head judged: Implemented-by: claude/issue-6910-retire-event-handlers-schema Direction is not re-litigated: maintainer decision batch #25 re-affirming batch #8 on objectui#6124. This is a judgement of execution only. Claim 1 — five occurrence sites on
|
| # | site | line on origin/main |
|---|---|---|
| 1 | declaration | packages/types/src/zod/base.zod.ts:723 |
| 2 | barrel re-export | packages/types/src/zod/index.zod.ts:83 |
| 3 | parity ledger EXCLUSIONS key |
packages/types/src/__tests__/zod-mirror-parity.test.ts:3078 |
| 4 | MIRROR_FILES docblock prose |
packages/types/src/__tests__/handler-keys-json-refusal-6124.test.ts:415 |
| 5 | sibling changeset scope sentence | .changeset/6124-handler-keys-json-refusal.md:15 |
On the head, 5 occurrences remain and every one is prose: the new changeset (2), the rewritten docblock (1), the NOTE (1), and site 5 left alone (1). zod-mirror-parity.test.ts and index.zod.ts are at 0.
Claim 2 — sites 1 and 2 inherited from WIP, verified not redone · VERIFIED
git show --stat 0b99a5f423 touches exactly base.zod.ts and index.zod.ts. On the head:
git grep -n 'export const EventHandlersSchema' HEAD -- . -> exit 1 (no match)
git grep -c 'export const HTMLAttributesSchema' HEAD -- . -> 1 <- CONTROL, same form fires
Claim 3 — the EXCLUSIONS delete anchored on shared text · VERIFIED, and the sibling is intact
The reason string is genuinely shared, so this was the right thing to be careful about. I did not settle it by tallying the phrase — I took the set difference over every exclusion key:
main keys 489 -> head keys 488
diff: < 'base.zod.ts#EventHandlersSchema': (the only line; nothing else moved)
Sibling survives at the identical address, zod-mirror-parity.test.ts:3076 on both refs, and the shared reason string goes 2 -> 1 in that file, which is what removing exactly one of the twins looks like.
Red-first proof that the delete was mandatory, not optional. I re-added the retired row to a scratch copy and ran the ledger:
× zod mirror parity — the population is closed > no map entry names a const that no longer exists
AssertionError: stale entries — the const was renamed or removed:
expected [ 'base.zod.ts#EventHandlersSchema' ] to deeply equal []
Test Files 1 failed (1) · Tests 1 failed | 31 passed (32)
Leaving the row would have gone RED, and the failure names only that one key. So this is not a test weakened to go green — it is the guard's required consequence. File restored; git status --porcelain empty.
Claim 4 — the docblock rewritten, not deleted · VERIFIED, and the new sentence is true
git diff --numstat on that file: 6 4 — comment lines only, no assertion touched. The replacement claims base.zod.ts "is not among" the nine mirror files:
MIRROR_FILES array, origin/main vs HEAD: diff -> IDENTICAL (byte-for-byte, not a length check)
element count -> 9 ; 'base.zod.ts' in MIRROR_FILES -> absent (exit 1)
Claim 5 — site 5 left alone · VERIFIED, and correct
.changeset/6124-handler-keys-json-refusal.md is untouched (empty --stat). Its sentence reads "Out of scope, per the ruling: … EventHandlersSchema is objectui#6910's card." That is a scope statement about which card owns the symbol, and this PR is objectui#6910's card, so the retirement does not falsify it. Leaving it is right.
Claim 6 — consumer census · VERIFIED, with the inherited legs honestly declared
objectui needs no sampling: the whole-tree census is closed at five sites and all five are the declaration, the barrel, the ledger key, and two pieces of prose. There is no import and no composition site — the symbol was never used as a field type anywhere in this repository.
objectstack, re-taken here against origin/main @ b3b43b6ea0c30f35d861c9cc0fedc9c085027b7b:
corpus (tracked text files) -> 8666
EventHandlersSchema occurrences / files -> 0 / 0
BaseSchema files -> 21 <- CONTROL, fires
'@object-ui/types' importer files -> 29 <- CONTROL, fires
qqzz_absent_token_9999 -> 0 <- CONTROL, clean
cloud and hotcrm are not re-taken here, and the changeset says so in its own body ("carried forward from the published reading on objectstack#15886 rather than re-taken here"). An inherited reading declared as inherited is the honest form; this is not an inherited reading passed off as fresh.
No export-list pin was missed: no count pin on the zod barrel exists, packages/types/README.md and content/ never name the symbol, and apps/console/src/__tests__/public-contract.test.ts (outside packages/types) is 19/19 green.
Claim 7 — reverse verification against a rebuilt dist · VERIFIED
pnpm --filter @object-ui/types run build exit 0, then importing the built barrel:
NAMED_EXPORTS = 211
EventHandlersSchema present? NO
CONTROL HTMLAttributesSchema RESOLVES (object)
CONTROL BaseSchema RESOLVES (object)
CONTROL ClassNameStylePropsSchema RESOLVES (object)
CONTROL ComponentMetaSchema RESOLVES (object)
CONTROL TimelineSchema RESOLVES (object)
CONTROL qqzzAbsentToken9999 ABSENT
Also checked in the reverse direction on the type face: no declare const EventHandlersSchema in dist/zod/base.zod.d.ts (exit 1), while the control declare const HTMLAttributesSchema is present — so the instrument fires.
Claim 8 — tests and type-check · VERIFIED, figures match exactly
vitest run <the two affected files> -> Test Files 2 passed (2) · Tests 328 passed (328)
vitest run packages/types/ -> Test Files 190 passed (190) · Tests 4479 passed (4479)
pnpm --filter @object-ui/types run type-check -> exit 0
(tsc --noEmit && tsc -p tsconfig.examples.json && tsc -p tsconfig.test.json)
The NOTE — old claim and new pointers
Old claim really was false. The inherited WIP asserted "Events are declared on the node as BaseSchema.events — an event-name string keying ActionSchema[]". BaseSchemaCore enumerates 21 members and events is not one of them:
ariaLabel bind body children className data description disabled disabledOn hidden hiddenOn
id label name placeholder style testId type visible visibleOn visibleWhen
BaseSchema = BaseSchemaCore, and it is .passthrough(). So the rewrite was warranted, and filing the underlying trap as objectui#9553 (open) is the right disposal — the instruction file really does sketch events?: Record<string, ActionSchema[]> and Commandment #4 really does author one.
New pointers all exist and are authorable. handlerKeyRefusal() is exported from packages/types/src/zod/tombstone.zod.ts and is referenced across 43 files; its own message carries the remedy the NOTE attributes to it. The action: node family is real and registered — action:bar, action:button, action:carousel, action:group, action:icon, action:menu, action:submit, with action:button in packages/core/src/registry/public-blocks.ts and 306 occurrences tree-wide. EventHandlers is still declared (packages/types/src/base.ts:1079) and still exported (packages/types/src/index.ts:115), as the changeset claims. The NOTE does not point at a surface that does not exist.
Findings
F1 · MAJOR — the rewritten NOTE contains a false census sentence, and it ships into the published .d.ts
The NOTE asserts:
"…no renderer reads
schema.events, and every authoredeventsin the corpus isTimelineSchema.events, an unrelated array."
Measured over the 5,406 tracked text files under packages/, apps/, examples/ at this head (controls in the same pass: className: authorings 391, fires; qqzz_absent_token_9999 0, clean):
events: { (the AGENTS.md sketch shape, node-level record) -> 7 occurrences
events: ['…'] (hook / validation string arrays) -> 13 occurrences
files declaring a type:'timeline' node -> 36
…of those 36, files containing any `events:` key -> 1
and that one is data-display.zod.ts — the DECLARATION, not an authoring
So the sentence is wrong in both directions: TimelineSchema.events is authored zero times, and at least 20 non-Timeline events authorings exist. Seven of them are precisely the shape the paragraph is warning about, e.g.
examples/schema-catalog/test/sidebar-trigger-dom-leak-5632.test.tsx:104
events: { onClick: [{ action: 'navigate', params: { url: '/x' } }] },
on a node whose type is sidebar-trigger — not a timeline.
Why this is MAJOR rather than cosmetic:
- The NOTE is the entire deliverable of this PR — the one thing an author finds where the export used to be — and the whole comment block is emitted into
dist/zod/base.zod.d.ts, so the false sentence is published to consumers, not merely kept in source. - It is the same defect class the rewrite was performed to fix, recurring inside its own fix: a census answer frozen into prose. AGENTS.md Commandment 完善设计器的每一个细节 #9 forbids exactly this ("point at the instrument that re-derives it, and never write down its answer"), and in
check-changeset-claims.mjs's own taxonomy this one is BORN FALSE — wrong at the moment it was written, against the very tree it describes.
Why it is nonetheless not a FAIL: the paragraph's operative instruction — that BaseSchemaCore is .passthrough(), so such a node is kept, judged by nothing and run by nothing, "⛔ Do not send an author there" — is correct, and the false clause understates the trap rather than misdirecting. Every reader is still steered to the right action. No gate reads it.
Suggested repair (prose only, no re-measurement needed to land): drop the clause, or replace it with a pointer rather than an answer — e.g. "authored events keys in this repository belong to unrelated surfaces (a timeline array, hook event lists) or are inert canaries in DOM-leak pins; objectui#9553 carries the census."
F2 · MINOR — the "no BREAKING banner" distinction from objectui#9040 is not established by measurement
The card asked me to verify rather than accept this. I could not confirm the stated distinction ("that one had a live consumer face and this has none"):
- Both changesets are
'@object-ui/types': minor;9040-retire-record-details-layout.mdcarries BREAKING, this one carries it 0 times. - Both remove a published TypeScript surface from the same package, and both produce a compile error in an external consumer — TS2353 for 9040's interface key, TS2305 for this named export. This changeset concedes exactly that ("a TypeScript consumer outside those four repositories that imported the symbol gets a compile error naming it").
- Both scored zero live in-repo usage; 9040's own body records an authored
layoutcensus of 0 and still took the banner. - Both argue the removed thing could never do anything — 9040: "no value it ever accepted could reach a published document"; this one: "no JSON document could ever satisfy it". Both even share the sentence "Nothing that worked stops working."
That said, this is not the FAIL condition on the card, for two independent reasons, which is why it is filed as minor:
- The disposition is derived here, not copied. The body states its own census and reasons from it, which is what the card required.
- No gate and no written rule mandates the banner word. The four
.changesetgates judge presence, bump level, fixed-group classification and overwrite — never a body claim;check-changeset-claims.mjssays in its own header that judging prose truth was fenced off deliberately. AGENTS.md §版本号策略 requires onlyminor+ "在正文里写清 breaking 语义即可" — spell the breaking semantics out in the body — and the FROM/TO block plus the compile-error sentence do satisfy that in substance.
So this is a legibility point for the PM, not a rule violation: the two retirements will publish verbatim into the same CHANGELOG, and a reader comparing them will see one labelled and one not, for a difference that the bodies do not make visible. Adding the banner would cost nothing and would not change the bump.
F3 · MINOR — the dist-residue description is imprecise
The claim describes the 3 remaining dist occurrences as the NOTE's prose "carried into the emitted .d.ts as a comment". There are 3, and all 3 are comment lines, but they are spread across one .d.ts and two .js:
dist/zod/base.zod.d.ts:581 · dist/zod/base.zod.js:663 · dist/zod/index.zod.js:1250
The reading is sound; only the file-shape wording is off. Worth correcting because it is the sentence that establishes the residue is inert.
F4 · INFO — check-clause2-carriers --pair 9552 needs an explicit board, or it clears nothing
The bare invocation on the card exits 2, not 0, because the script's fallback board is objectstack-ai/objectstack:
"PR #9552 is not open, or names no card this file can derive … ⛔ Not a clearance — the pair could not be formed, so nothing about it was judged."
With the target named it is genuinely green:
PM_SWEEP_REPO=objectstack-ai/objectui node scripts/pm/check-clause2-carriers.mjs --pair 9552 -> exit 0
✓ PR #9552 / card #6910 — the clause-② declaration is readable in the fixed spelling and both carriers agree.
(source: PM_SWEEP_REPO)
The carriers do agree, so the conclusion on the card stands. Flagging only because the command as written fails toward "nothing was judged" while reading like a pass.
F5 · INFO — objectui#9553 is open but carries no labels
The carded follow-up exists and is correctly scoped (filed 2026-09-15T02:51:39Z, ahead of this head). It currently has an empty label set, so it will not surface in a label-driven sweep. I have hung nothing — labels are the PM's.
Disposition
PASS. The retirement itself is clean and complete: the census is closed at five sites with every one accounted for, the ledger delete is provably mandatory and provably did not damage its twin, the docblock was rewritten into a true statement, no consumer or composition site exists in either repository that could be measured here, the inherited legs are declared as inherited, the rebuilt dist confirms the removal from the other direction with controls that fire, and the suites and type-check are green at the figures claimed. F1 should be fixed before release since it publishes into the .d.ts; F2 and F3 are one-line prose corrections; F4 and F5 are for the PM.
No label was hung, moved or stripped; the PR remains a draft; no approval review was submitted and no merge path was touched.
Generated by Claude Code
…ire-event-handlers-schema
Contract review F1 (MAJOR). The NOTE's warning paragraph asserted that "every authored events in the corpus is TimelineSchema.events, an unrelated array". That was wrong in both directions on the day it was written, and the whole comment block is emitted into the published .d.ts, so it shipped. The instrument behind the claim was the defect: it matched only the JSON-quoted "events": spelling, which cannot see a TypeScript object literal. Re-measured here over the tracked files under packages/, apps/ and examples/ with a control that fires, the object-literal form finds the node-level record shape the paragraph warns about, plus hook and validation string arrays; and of the files declaring a timeline node, the only ones carrying an events key are the declaration itself and a refusal probe. TimelineSchema.events is authored zero times. No replacement count is written. The paragraph now states the mechanism, which does not rot -- nothing declares the key and nothing reads it, so whatever a document writes there, no renderer runs it -- and points at the card that carries the census. A census answer frozen into a comment is what AGENTS.md commandment #9 forbids, and this note had reproduced that defect inside the very fix performed to record it; the paragraph now says so. The operative instruction is unchanged: BaseSchemaCore is .passthrough(), such a node is kept, judged by nothing and run by nothing, do not send an author there. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt
F1 repaired in
|
✅ 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 reviewTargeted re-check of the F1 repair only, as dispatched. The verdict does not move: PASS, now on head Implemented-by: claude/issue-6910-retire-event-handlers-schema History is intact: the reviewed head F1 — RESOLVED(a) Gone from source and from the rebuilt dist. One thing I had to localise rather than tally: a bare (b) What replaced it is true, and it does not rot. Re-measured at the new head, controls in the same pass: The only (c) The operative instruction — substantively intact, but not verbatim. Localising the first difference rather than trusting a length check: The load-bearing clause — "is KEPT, judged by nothing and run by nothing. ⛔ Do not send an author there" — is verbatim. Two things around it changed: "a node written from that sketch" became "such a node", and the trailing "; the finding is carded separately" became a full stop, with the card now named explicitly as objectui#9553 two sentences later. The antecedent still resolves against the preceding sentence, so this is no defect — but "unchanged verbatim" is a shade stronger than what shipped, and this lane does not let that pass unlocalised. (d) Pointers still real and authorable at the new head.
|
|
|
| field | value |
|---|---|
| verdict | PASS — comment 5674344635, 2026-09-15T03:35:56Z |
| declared head | 53df540f0847ca38db6b27586bda9238644840bc |
pulls/9552.head.sha now |
53df540f0847ca38db6b27586bda9238644840bc |
| comparison | full 40-character string equality, both operands length 40 — ⛔ not a prefix and not a short sha |
| authorship pair | Implemented-by: claude/issue-6910-retire-event-handlers-schema · Reviewed-by: session_01L5xpA5q533BgTTNADibEFt, each token immediately after its colon |
The round-1 verdict returned FAIL on F1 (MAJOR) — a born-false census sentence in the rewritten NOTE, produced by a JSON-quoted matcher that structurally cannot see a TypeScript object literal. The repair removed the count rather than replacing it, because the defect was the shape of the claim, not its value. The targeted re-check confirmed the repaired paragraph carries no census, that the reviewed head ee48fcee7b is still an ancestor of 53df540f08 (nothing rebased, amended or force-pushed), and that F1's text is gone from source and from the rebuilt dist.
The red that was not this PR's, and its resolution
Test (shard 1/4) failed on a repo-root scratch-directory race, which is the already-filed objectui#9468 and was recorded on that card as its third instance — falsifying that card's own "did not recur". I spent the one re-run this lane allows, said so before spending it, and committed to reporting a second failure as a second reading of the same race rather than re-running further. It came back completed: success at 2026-09-15T04:02:09Z, so that commitment did not have to be exercised.
Gates at this head
- ① declared head ==
pulls/9552.head.sha, compared as full strings. MET. - ③
total_count36 == 36 returned;{success: 33, skipped: 3}; 0 not-green, 0 still running; legacy combined statussuccess(1 context);mergeable_state: clean. MET.⚠️ This is the draft check set — a check that only starts on undraft would be a 37th, so ③ is re-taken after the flip and before the queue, not carried forward from here. - ② is taken after this removal, deliberately. See below.
⭐ Why gate ② is read after the carrier comes off, and not before
On PR #9548 earlier this shift I removed this same carrier while the verdict still carried a half-written authorship pair, opening a five-minute carrier-free window over a verdict the gate would have rejected. The root cause is not carelessness about ordering — it is that the ordering the obvious reading suggests cannot work: while needs:contract-review hangs, check-clause2-carriers cannot read the PR thread, so every row that reads the verdict's text is silent, and a blind run and a genuine pass emit the same bytes.
One line, so it survives out of context: a checker whose inputs are gated by the carrier it is deciding to remove cannot produce the reading that authorises the removal.
So the sequence is: this record → the targeted DELETE on PR #9552 and on card objectui#6910, each read back as a JSON set difference → then --pair 9552 as the landing reading of ②. If ② comes back non-zero after the removal, the carrier goes back on in the same shape and this PR does not move.
Generated by Claude Code
Enqueued — three of three at
|
| # | gate | reading | at |
|---|---|---|---|
| ① | declared head == pulls/9552.head.sha |
53df540f0847ca38db6b27586bda9238644840bc both sides, full 40-character string equality |
04:02:35Z |
| ② | PM_SWEEP_REPO=objectstack-ai/objectui node scripts/pm/check-clause2-carriers.mjs --pair 9552 |
exit 0 — declaration readable in the fixed spelling, both carriers agree, and C6-RECORD names comment 5674344635 as the review of record on this head |
04:03:33Z, after the carrier came off |
| ③ | check census, pre-flip | 36/36, {success: 33, skipped: 3}, 0 not-green, 0 running, legacy combined success, mergeable_state: clean |
04:02:35Z |
| ③ | check census, re-taken post-flip | 37/37, {success: 34, skipped: 3}, 0 not-green, 0 running, legacy combined success, mergeable_state: clean, head unmoved |
04:04:27Z |
⭐ The 37th check is the reason ③ is taken twice. Governed Surface Queue Guard ran once on the draft (03:22:57 → 03:23:11Z) and again on the undraft (04:03:54 → 04:04:05Z), and only the second reading is about the PR that is being enqueued.
Sequence, with its timestamps, since the ordering is the point: discharge record 5674547457 → targeted DELETE of needs:contract-review on this PR and on card objectui#6910, each read back as a JSON set difference ({needs:contract-review, package: types, tests} → {package: types, tests}; {domain:spec, needs:contract-review, pm:dispatched, priority:p3} → {domain:spec, pm:dispatched, priority:p3}; nothing unexpectedly added or removed on either) → gate ② → ready_for_review 04:03:49Z → guard → gate ③ again → auto_merge {"merge_method":"SQUASH"} 04:04:39Z.
added_to_merge_queue is on the timeline at 04:04:40Z. auto_merge reads back null once a PR is queued, so the timeline event is the evidence and the field is not. Queue transit has been 18.5–20.5 min over ten readings this shift, the most recent being PR #9551 at 20 min 09 s — so a merge is expected around 04:23–04:25Z, and that is a range this seat keeps re-measuring rather than a promise.
Generated by Claude Code
Fixes #6910
Retires the published export
EventHandlersSchema(z.record(z.string(), z.function())) from@object-ui/typesunder ADR-0049 enforce-or-remove, per the maintainer ruling recorded on the card (decision batch #25, reconfirming batch #8 on objectui#6124).This is a resume. The branch already carried a WIP commit that deleted the export and the barrel line and left a NOTE in place; that commit was deliberately incomplete and red by construction. This PR merges
origin/maininto the branch (merge, never rebase), then finishes the remaining halves.What landed
zod-mirror-parity.test.tsEXCLUSIONSentry for the removed const, deletedhandler-keys-json-refusal-6124.test.tsMIRROR_FILESdocblock sentence the removal falsifies, rewritten.changeset/6910-retire-event-handlers-schema.mdThe expected red, and its resolution
Removing the export turns the stale-entry census in
zod-mirror-parity.test.tsred with exactly['base.zod.ts#EventHandlersSchema']— that census reads exported consts off disk, so it fires for any route that removes the export. Edit 3 is what clears it. The whole package runs 190 files / 4515 tests, all passing, withtype-checkgreen across all threetscpasses.The deleted row's reason string is shared verbatim with the sibling
HTMLAttributesSchemaentry, so the deletion was anchored on the key line, not on the reason text; the sibling entry is untouched and still present.Round 2 — contract review F1 (MAJOR), repaired
The review reproduced 8 of 8 claims and added two checks of its own that strengthen this PR: a set difference over all 489
EXCLUSIONSkeys (exactly one key removed, the sibling surviving at the identical address on both refs), and a red-first proof that re-adding the retired row turns the census red naming only that key — so the delete was mandatory, not a test weakened to go green.It also found that my rewritten NOTE had shipped a born-false census sentence: "every authored
eventsin the corpus isTimelineSchema.events, an unrelated array."The instrument behind that claim was itself the defect. I had matched only the JSON-quoted
"events":spelling, which structurally cannot see a TypeScript object literal, and then generalised the result to "the corpus". Re-measured on this branch over the tracked files underpackages/,apps/andexamples/, with a live control that fires and a clean absent-token control at zero, the object-literal form finds the node-level record shape the paragraph warns about — on nodes that are not timelines — plus a larger set of hook and validation string arrays. Of the files declaring atype: 'timeline'node, the only ones carrying aneventskey are the declaration itself and a refusal probe.TimelineSchema.eventsis authored zero times. The sentence was wrong in both directions.This was the defect the rewrite existed to remove, recurring inside its own fix, and it reached the emitted
.d.ts. No replacement count is written. The paragraph now states the mechanism, which does not rot — nothing declares the key, nothing reads it, so whatever a document writes there no renderer runs it — points at objectui#9553 for the census, and records that it shipped a frozen census answer once already. The operative instruction is unchanged:.passthrough()⇒ kept, judged by nothing, run by nothing, do not send an author there.Deviation from the WIP commit, stated rather than folded in
The WIP NOTE told the reader that handlers are authored as
BaseSchema.events. That is false on this tree, and it is the one thing in the inherited work that was not adopted as-is:BaseSchemaCorehas 21 members and declares noevents; the TypeScriptBaseSchemainterface declares none either;schema.eventsandprops.eventshave 0 read sites acrosspackages,appsandexamples, against live controlsschema.classNameandschema.visiblefiring in the same pass; and@objectstack/spec17.4.0 declares no UI-nodeeventskeyed to actions.Shipping that sentence would have replaced one unauthorable surface with a pointer to a non-existent one, which is the exact hazard this card exists to remove. The NOTE now points at the real JSON face —
handlerKeyRefusal()and the node-type spelling PR #6498 established.Consumer verification
origin/main, zero composition consumersBaseSchema→ 429 files, fires; absent-token → 0BaseSchemafires in the same corpus@object-ui/*, 16 files@object-ui/*, 3 filesThe cloud and hotcrm legs are carried forward from the published reading on objectstack#15886 and are not presented as readings this seat took; this container holds no access to either repo.
Reverse verification against the rebuilt
distrather than a cache: the built zod barrel exposes 211 named exports, the removed symbol is absent from it, and three sibling controls (HTMLAttributesSchema,BaseSchema,ClassNameStylePropsSchema) all resolve.Acceptance notes
The changeset's level came from the gate's own verdict, not from a sibling card.
check-changeset-presencescored this change@object-ui/types, "Scoredminorat most, nevermajor", so the declaration isminor.On the BREAKING banner (review F2), answered explicitly. The reviewer is right that the distinction I originally drew from objectui#9040 — "that one had a live consumer face, this has none" — is not supported: 9040 also recorded a zero in-repo census and took the banner anyway, and both removals produce an external compile error. I withdraw that reasoning. The banner is nonetheless omitted, for a different and narrower reason: this card's ruling names the banner as a conditional and the condition was not met. The ruling states that a live import anywhere means "the ruling stays retire but the changeset becomes BREAKING and the removal needs ADR-0087's conversion path rather than a plain delete". The four-repo census came back zero, so the plain-delete branch is the one the ruling itself selects. That is a ruled branch, not a house-style judgement, and it is not mine to override; 9040 is a different card under its own ruling. The body still tells a consumer plainly that an external import breaks and gives the FROM/TO, which is what a CHANGELOG reader needs either way.
The fifth occurrence is left alone, and three instruments agree.
.changeset/6124-handler-keys-json-refusal.mdsays "EventHandlersSchemais objectui#6910's card". That is a scope statement about what objectui#6124 did not cover, it stays true after the removal,check-changeset-overwriteexists precisely to report editing a changeset you do not own, and AGENTS.md #11 forbids re-addressing a published record. Not touched.Dist residue (review F3), corrected count. The removed symbol survives in the built output 3 times: 1 in
dist/zod/base.zod.d.tsand 2 in.jsfiles (zod/base.zod.js,zod/index.zod.js) — all of them the NOTE's own prose carried through as a comment. No declaration and no export: a grep for either indistreturns 0.Out-of-scope finding, filed rather than fixed here — AGENTS.md's abridged JSON-protocol sketch publishes an optional
eventsmember typed as a record of event-name string to ActionSchema array as aBaseSchemamember, and its action-system commandment authors one, while no schema declares the key and nothing reads it. BecauseBaseSchemaCoreis.passthrough(), a node written from that sketch is kept by the parser, judged by nothing and run by nothing — a silent discard, and the carrier is the file agents read first. Filed as objectui#9553, whose own census has been corrected for the same instrument error described above. AGENTS.md is a governed surface and is not touched by this PR.Gate readings. Green here: the changeset family (presence, no-major, overwrite, claims), control-bytes, new-line-citations, handler-key-reads, spec-symbols, test-path-roots, unreferenced-sources, the package's
type-check, and the package suite.check:readme-exportsis NOT MEASURED locally — it exits 1 on an unbuilt tree and prints "the population COLLAPSED -- this run proves nothing", a prerequisite failure and not a finding; it needs a full repo build and belongs to CI. It cannot be moved by this diff in any case: 0 READMEs name the removed symbol, against controlBaseSchemafiring in 13 of 54 tracked READMEs.check-governed-queue-guardis CI-only; its surface list was read directly instead, and none of these paths is governed.Clause-②: yes — a published export is removed. The
needs:contract-reviewcarrier is the PM's to hang; no label was touched by this seat, on the card or here.Implemented-by: claude/issue-6910-retire-event-handlers-schema
Generated by Claude Code