Skip to content

spec(types)!: remove BaseSchema's index signature from the authoring face, declare the riders, pin the compile-fail — executes the #7927 removal ruling (governed: AGENTS.md #0.1) #8347

Description

@claude

Priced by the domain:spec @ objectui seat on #7935. ⛔ Filed unassigned, not claiming.

Blocked-by: #8345 · Blocked-by: #8648 · Blocked-by: #8649 · Blocked-by: #8650 · Blocked-by: #8651 · Blocked-by: #8652 · Blocked-by: #8653 · Blocked-by: #8654 · Blocked-by: #8655 · Blocked-by: #11466 · Blocked-by: #11564

⭐ RE-SCOPED 2026-09-09T00:1xZ by the domain:spec@objectui seat — the previous body's blast radius was 2.8× understated

Ruled at #8327 5587717012, option A («re-scope now, do not let #8347 discover it mid-flight»), on the measurement at #8327 5587605667. What changed is below under The blast radius, corrected; ⛔ nothing about the ruling, the direction, or the governed status changed. Card moved pm:queue → pm:blocked, which is the state its Blocked-by: lines already implied.

This is the TypeScript half of the #5250 ruling — "the TypeScript side gets the matching authoring types without the [key: string]: any index signature ... so an invented or misspelled key is distinguishable from a declared one at the authoring site" — and it is the execution of #7927, ruled 2026-09-07 (director seat, decision batch #68): the direction is removal, and the TypeScript face becomes an executable contract.

⚠️ The AGENTS.md §5 #0.1 amendment rides here, and it makes this PR governed (draft PR + human merge). It belongs with this card, not earlier, because the amendment should describe what shipped rather than what was intended.

What is being removed, and what inherits it

Measured at origin/main fa7d66c45 by #7581's TypeScript-face census, re-confirmed present at 5505aec:

  • [key: string]: any is declared at 12 sites in packages/types/src.
  • 120 interfaces extends BaseSchema directly and inherit it.
  • The two the ruling cites as base.ts lines 409 / 441 are, located by content, 467 (BaseSchema) and 499 (ComponentRendererProps). ⚠️ The second is the renderer props type, not an authoring type — out of scope for a card about the authoring face, and conflating them is the first way this card goes wrong.

⭐ The blast radius, corrected — 112 reads / 88 keys / 23 files, ⛔ not 58 / 21

This card previously carried "58 real code reads across 21 files" from #8327. That figure does not reproduce, and #8327 falsified it against its own instrument, its own population and its own ref (5587605667):

row at c842594 (the figure's own ref) this card used to say at 154fe2a
production files in the population 1596 1596 1598
raw text (comments + string literals in) 92 keys / 27 files 92 keys / 27 files 92 keys / 27 files
stripped 89 keys / 24 files / 113 reads 58 keys / 21 files 88 keys / 23 files / 112 reads

The raw row reproduces exactly, which localises the defect: 58/21 describes #8327's own per-file table, and that table is a strict subset of the read set — plugin-view/src/ObjectView.tsx alone carries 31 cast reads and was absent from it (58 + 31 = 89). ⛔ Every one of the 58 keys is real; the population was understated, never overstated.

The three-way split, which is what actually re-scopes this card

class reads what it is what it owes
(a) 29 genuinely undeclared on the type the renderer is handed, and read ⛔ a ruling each, ⛔ not a declaration
(b)/(a) 8 union schema; the key is declared on exactly ONE arm a per-arm decision
(b) 38 declared — the cast is stale, or forced by an untyped local ordinary cleanup, ⛔ not this card
(c) 31 ⭐ ALREADY RULED non-author surface, with in-repo pins ⛔ NEVER declare
(c?) 1 the same producer-derived key as a (c) ruling, unpinned at its site a pin, or a ruling
(d) 5 ⛔ NOT ANSWERABLE — the renderer is handed any a typing fix first

⭐ 31 of the 112 must NEVER be declared — the #5091 grid ruling (pinned by plugin-grid/src/__tests__/gridNonAuthorKeys.test.tsx) and the #5097 host-composition ruling of 2026-08-18 (27 keys, pinned by plugin-view/src/__tests__/objectViewHostSurface.test.tsx). Both docblocks say the cast is deliberate. A sweep that read "cast and undeclared" as "needs a declaration" would have walked into two standing rulings and, per the #5097 docblock, silently blanked a stored app-shell document on the way.

⚠️ And class (a) is a routing bucket, not a verdict. Four of the 29 — ChartRenderer.tsx's categories, category, index, value — sit under "Adapt the Tremor/simple format": an explicit foreign dialect whose remedy is enforce-or-remove at the producer. Declaring them would fossilize a second authoring vocabulary, which AGENTS.md #0.1 forbids.

⛔ The hard split condition is ALREADY MET — this card SPLITS, it does not grow

The previous body set it as: "the 58 reads turn out to need more than the declarations already catalogued (i.e. any read that needs a contract decision, not just a declaration)."

That is satisfied before anyone starts, by measurement rather than by discovery: 31 ruled never-declare · 4 foreign-dialect (producer-side) · 5 unanswerable · 8 needing a per-arm decision. Those are contract decisions, ⛔ not declarations.

⇒ Per this card's own rule, the remainder becomes other cards. It has: the class-(a) adjudication was ruled (#8327 5587717012, Q2 option A) to be one card per reader package, and those are filed — #8648 · #8649 · #8650 · #8651 · #8652 · #8653 · #8654 · #8655 (eight; the eighth was declared as a deviation, for the class-(d) half the ruled unit of seven left uncarried). They are this card's inputs, hence the Blocked-by: lines above.

⚠️ A named, falsifiable premise this card now carries — and the fork if it fails

The previous body called #8327's census "real code reads that would stop compiling" and "the load-bearing number for the removal's blast radius."

⛔ The census does not establish that property. Every one of the 112 is a (schema as any).KEY read; as any erases the type, so property access compiles whatever BaseSchema declares. Removing the index signature cannot break a cast read by construction. What the census establishes is the size and shape of the undeclared surface — which is what makes it valuable — ⛔ not a compile blast radius.

⇒ The compile blast radius of the removal is UNMEASURED. It is carried as a premise, ⛔ not as a fact:

Premise P1. Removing [key: string]: any from BaseSchema breaks only un-cast reads and object-literal authoring with extra keys, and the count of those is small enough for this card's appetite.
Re-check command (the implementer's first act, before any other work): in a dedicated worktree, delete the index signature from BaseSchema alone and run pnpm -r type-check, capturing the exit code before any pipe and enumerating every error by file and key.
⛔ If P1 fails — if that run produces a population this card's appetite cannot carry — report the fork and stop. ⛔ Do not grow the card to fit the number; that is the same failure this card was just re-scoped to undo.

⭐ The PM seat ⛔ never writes code, so it cannot run that experiment; it is the implementer's, and it is named here so it cannot be skipped or answered from prose.

The pin

Per #7927: a fixture with a misspelled key must stop type-checking. ⛔ Not a runtime assertion — a compile-fail pin, in both directions (the correct spelling must still compile). #7927's own probe set is the model: P5 (rename a key, predicted GREEN, came back GREEN) is the exact case that must flip to RED.

Appetite — the removal and its pin, and nothing that owes a ruling

The appetite is: the removal on BaseSchema, the compile-fail pin, the AGENTS.md #0.1 amendment, and the declarations that the per-package rulings have ALREADY adjudicated by the time this is dispatched. ⛔ Not the 29. ⛔ Not #7804's 39 handler keys, which drain through their own KNOWN_UNDECLARED_READS ledger.

⛔ Hard split conditions — hit any one and the remainder is a new card:

  • P1 fails (above).
  • Any read needs a contract decision that its per-package card has not yet answered ⇒ it stays with that card.
  • Any rider needs a public-surface widening that is not already ruled.
  • ComponentRendererProps (base.ts:499) turns out to need the signature kept — it is renderer props, and this card ⛔ must not remove it by momentum.

⛔ Out of scope: the zod face (#8345 owns it); the other 10 index-signature sites in packages/types/src that are not the authoring face (QueryParams, AggregateResult, ActionContext, FormField, …) — each its own liveness question, with #7497 as the precedent for one at a time.

The other two rider instruments — unchanged, and neither is load-bearing

① Cards that name the mechanism. 49 open cards match passthrough / [key: string]: any / index signature / BaseSchemaCore over the complete 2026-09-07 enumeration (445 issues + 8 PRs = 453, cross-checked against open_issues_count 453). Controls: a fabricated token returns 0, objectui returns 317. ⚠️ Naming the mechanism is not riding it — a population to triage, ⛔ not 49 riders.

③ Reads a renderer performs that no arm declares. #7804: 39 handler keys, already draining through KNOWN_UNDECLARED_READS. Population derived on both halves — 106 arms, 216 registrations, 62 reachable reads.

Named riders, each with its own card and disposition: #7780 (ObjectKanban inline data) · #7925 (six doc-authored object-calendar keys) · #7924 / #7928 (NamedListView, and the undecided value type) · #8174 (filter / sort — its own body says it "should decide whether that is worth the edit, or whether it should wait behind #7927" ⇒ it waits) · #6152 (121 declared-but-unmirrored keys) · #5155 (the same ceiling, recorded independently).

Grading notes

⛔ Unblocks when

All eight per-package rulings (#8648–#8655) have been adjudicated and #8345's remainder is settled. ⛔ Until then this card is pm:blocked and ⛔ must not be dispatched — least of all on the retired 58 / 21 figure.

Refs: #5250 (the ruling) · #7927 (ruled: removal) · #7935 (this pricing) · #8327 (the corrected census, and the re-scope ruling) · #7804 · #7780 · #7924 · #7925 · #7928 · #8174 · #6152 · #5155 · #7497 · #8648–#8655

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lanepackage: typespm:blockedpriority:p2

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions