Skip to content

Decision: registry-inputs-spec-parity fails on the 17.3.0 merge — a calibration control now asserts something the contract accepts, and the kind classifier may not recurse through z.union #8137

Description

@os-sam

Filed by the domain:spec @ objectui execution seat (session session_0114Ytxr5sM1vdW19Y9WAx6E), R1. Blocks PR #7685 (#7122's 17.3.0 pin). ⛔ Not decided here — one half sits on the manual floor.

What fails

apps/console/src/__tests__/registry-inputs-spec-parity.test.ts, 2 failed / 179 passed, deterministic on a targeted re-run.

⚠️ Merge-only, and not a resolution anyone could have made differently. Green on main (spec 17.2.0), green on the branch (the gate did not exist there), red only in the merge. main's cf1d29e2f (#8097, +1077 lines) added the ComponentInput.of gate; it auto-merged textually with this branch because the two sides edited disjoint regions.

Root cause — measured on the installed artifact, not inferred

@objectstack/spec 17.3.0 changed RecordActivityProps.types to an open vocabulary:

z.array(z.union([FeedItemType, z.string().min(1)]))

That is objectstack#11658 executing the maintainer's 2026-08-24 ruling on #11507 — "every closed map over this vocabulary is now the bug". safeParse against @objectstack/spec/ui:

['comment'] → ACCEPTS        [42] → refuses: invalid_union @ [0]
['Account'] → ACCEPTS        [[]] → refuses: invalid_union @ [0]

The two failures are different in kind, and only one is a decision

1. A calibration control that reality overtook — ⛔ manual floor

The test asserts specMemberVerdict('record:activity', 'types', 'array', 'Account') === 'refuses-content'. 17.3.0 genuinely accepts 'Account', so the assertion is now false. The test's own comment states the retired premise: "record:activity.types is a spec enum of strings."

⇒ The fix is to update a calibration control. That is the move the gate's own header forbids — and the protocol puts gate weakening on the manual floor precisely because an agent has a structural incentive to call weakening "correcting". ⭐ The distinction here is real but it is not mine to draw: the control is factually wrong, yet "the control is wrong" is also exactly what someone would say who wanted the gate quiet.

2. A probable classifier gap — likely an ordinary bug, reported for confirmation

record:activity.types:of=string fails the single-kind direction with the gate reporting contract accepts {string, array}. ⚠️ That reading disagrees with the contract as measured above: an array member is refused (invalid_union @ [0]).

Mechanism, from the dev that found it: acceptedMemberKinds counts a kind when the verdict is accepts or refuses-content, and refusesKind has to recurse through a z.union to answer correctly. If that is the whole story, this is a classifier that cannot see inside a union — not a genuinely multi-kind member — and the fix is in the gate's own helper, not in an exemption list.

⇒ Which it is decides the shape of the fix: a MULTI_KIND_MEMBER_CONTRACTS entry (declaring the member multi-kind) versus fixing refusesKind to recurse. The first would be wrong if the second is true, and it would also silently widen what the gate tolerates.

Four-axis analysis

Real business needrecord:activity is a shipped block; the open vocabulary is the maintainer's own ruling being executed upstream. objectui has to follow it either way. The question is only whether the gate that guards this follows it correctly or is quieted.

Long-term soundness — ⭐ This is the axis that decides it. If (2) is a classifier gap, then every union-typed member in the contract is currently mis-classified by this gate, and an exemption entry would hide that for all of them, not just this one. Fixing the helper restores the gate's meaning; exempting one member spends the gate's credibility to make one PR green.

Preventing AI-written mistakes — the calibration control exists because an agent will otherwise adjust the thing being measured. Whatever is decided, the control should end up asserting something still true and still discriminating — ⛔ not deleted, and ⛔ not softened to "accepts anything".

Startup-stage focus — both fixes are small and neither adds surface. No expansion pressure here.

Options

  • A — Rule that updating the calibration control is a correction, not a weakening, since 17.3.0 genuinely accepts 'Account'; re-point it at a value the contract still refuses ([42] and [[]] both are), so it keeps discriminating. Then fix refusesKind to recurse through z.union and confirm the {string, array} reading collapses to {string}. Recommended. It keeps the control load-bearing and repairs the classifier rather than exempting around it.
  • B — Same control update, but declare record:activity.types in MULTI_KIND_MEMBER_CONTRACTS instead of touching the classifier. ⚠️ Cheaper now, but if the classifier really cannot see into unions this hides the same defect everywhere else it occurs.
  • C — Leave both red and land PR chore(deps): resolve @objectstack/spec at 17.3.0 in the lockfile #7685 with a known failure. ⛔ Not viable: the queue requires every check green, and a red parity gate on main is shared damage.

Related

#7122 / PR #7685 (the pin that surfaced it) · objectstack#11658 and objectstack#11507 (the open-vocabulary ruling) · #8097 / cf1d29e2f (the gate) · #8127 (a different 17.3.0 union-drift finding from the same round)

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

    bugSomething isn't workingdomain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec laneneeds-user-decisionpriority:p2

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions