… older reason may hide one (objectui#10033)
The unjudged-block ledger in registry-inputs-spec-parity carried the three
ComponentPropsMap rows @objectstack/spec retired under reasons that did not
mention the retirement: user:profile as EMPTY SPEC SHAPE with a false
placeholder claim, element:form as NOT REGISTERED citing a palette entry the
17.3.0 reconciliation deleted, element:filter as EMPTY SPEC SHAPE.
The population is spec-derived (Object.keys(ComponentPropsMap)), and the spec
keeps a retired type's row on purpose as a refusal door, so the rows stay in
the population and stay accounted for. What changes is the reason:
- a third ledger class, RETIRED UPSTREAM, asserted by isRetiredUpstream(),
which reads both retirement spellings from the spec (out of
PageComponentType, and either named in RETIRED_PAGE_COMPONENT_TYPES or a
row whose every listed key is a tombstone), plus an empty accepted set;
- the EMPTY SPEC SHAPE and NOT REGISTERED classes now fail on a type the spec
retired, so a later retirement cannot leave an old reason in place.
Test-only; empty-frontmatter changeset declares no release.
Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC
Fixes #10033
Clause-②: no
The card's question, answered from the test's own population logic
Is the waiver population derived from the spec at all? Yes. And retired keys are in it, in a special form. That makes this case (b) of the dispatch.
covered,registeredWithoutInputsandaccounts for every spec-carried blockall start fromObject.keys(ComponentPropsMap). The partition test requirescovered∪registeredWithoutInputs∪UNJUDGED_SPEC_BLOCKSto equal that set exactly, with each block counted once.accounted for a block ComponentPropsMap does not carry). The second is the ledger rot check (is ledgered but the spec no longer carries it — delete the entry). So this PR adds no new tripwire for that case.@objectstack/spec17.4.0 (lockfile-resolved), taken at baseea02938cd, using the@object-ui/test-supporttombstone judge:PageComponentTypeRETIRED_PAGE_COMPONENT_TYPESuser:profilez.never)element:formelement:filtercloud-connection:panel,marketplace:installed-list,mcp:connect-agentThree things follow from that reading:
RETIRED_PAGE_COMPONENT_TYPEShas exactly one member,user:profile.element:formleftPageComponentTypethrough the element-grain retirement (objectstack#9249, listed in the spec's 17.3.0 changelog), not through that table.element:filterleft the same way earlier (objectstack#9220, 17.1.0). So "read the spec's retired set" as that table alone would not coverelement:form.coveredtogether with its row, and nothing would judge it. objectui#8176 built this ledger to end exactly that absence. With the rows kept in, such a registration is judged (every input it publishes is off-spec) and is also counted twice, which the partition refuses.user:profileis false in both halves, as the card measured.element:formcites ablock-types.tspalette exclusion that says "no renderer — use the object-boundobject-formblock". That entry was deleted by commit639114c4d, the delivery of objectui#7122 through PR chore(deps): resolve @objectstack/spec at 17.3.0 in the lockfile #7685. It now appears in that file only as a note that the type is RETIRED UPSTREAM.What changed (test-only, one file plus an empty changeset)
isRetiredUpstream(type)reads both of the spec's retirement spellings, and never a list kept here. A type counts as retired when it is out ofPageComponentTypeand one of these holds:RETIRED_PAGE_COMPONENT_TYPES(retired by name);Leaving the enum is required but is not enough on its own: the controls above are outside the enum too, and the predicate answers
falsefor them.A third ledger class,
RETIRED UPSTREAM. The rot check asserts two things for it:isRetiredUpstream(type)is true, and the accepted key set is empty. If upstream restores the type or gives its row a key, the entry goes red.The class-closing guard. An entry under
EMPTY SPEC SHAPEorNOT REGISTERED, DELIBERATELYnow fails when the spec retires its type. That is the check that stops a hand-written reason from outliving a retirement, which is what happened to both entries here.Entries rewritten.
user:profileandelement:formnow say what is true, citing the upstream ruling and landing (objectstack#14159, objectstack#15112, objectstack#9249) and the objectui-side reconciliation (objectui#7122).element:filtermoves too. This is an in-place fix under the same-class exemption, and I am naming it here because the claim's file surface names only the other two entries. It was retired at element grain by objectstack#9220 (17.1.0), and itsEMPTY SPEC SHAPEreason never mentioned that. The new guard turns exactly that state red, so leaving it behind would not pass. It is the same file, the same defect class and the same gate, with nothing new to verify.The docblock now has three classes (it said two) and a paragraph on why a retired block stays in the population.
The census does not move:
specCarried45,judged29,registeredPropless7,ledgeredUnjudgeable9. It is the same nine entries under corrected reasons.Changeset. The dispatch expected none.
node scripts/check-changeset-presence.mjsrefused without one, because it counts any file under a released package'ssrc/, test files included. So the PR adds.changeset/10033-parity-ledger-retired-upstream.mdwith an empty frontmatter, which declares that nothing is released. The gate now passes.Verification record at head
4893d87cbEvery exit code is captured before any pipe, and each verdict line is quoted from the gate's own output.
pnpm exec vitest run apps/console/src/__tests__/registry-inputs-spec-parity.test.ts scripts/__tests__/check-installed-spec-pin-claims.test.ts(repo root, underos-verify-lock.sh). The second file is the only other test that reads this file, by content.Test Files 2 passed (2)·Tests 228 passed (228)·VERDICT command-exit 0ea02938cd, before the edit (control: the stale reasons were green then)Tests 199 passed (199)pnpm exec turbo run build --filter='@object-ui/console^...' --concurrency=2(the dependency closure the type-check reads, under the lock)Tasks: 34 successful, 34 total·VERDICT command-exit 0pnpm --filter @object-ui/console type-check(script echoed:tsc --noEmit && tsc -b tsconfig.node.json --force)tsc --listFiles -p apps/console/tsconfig.jsonincludesregistry-inputs-spec-parity.test.ts(1 hit), so the test file is in the checked set.apps/console, the console's owneslint .config)--format json: 1 file, 0 errors, 0 warnings. This is a narrowing, with its evidence: (1) the population comes from eslint's own config, whereisPathIgnoredis false for the test file and true for the changeset; (2) the file count comes from the JSON output; (3) type-aware linting is off (calculateConfigForFileshows noparserOptions.projectorprojectService), so this diff cannot change the verdict on any file it does not touch.pnpm check:control-bytesOK (scanned 8435 tracked text file(s); skipped 85 binary)pnpm check:new-line-citationsVERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0node scripts/check-changeset-presence.mjs1 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)(empty frontmatter)pnpm check:changeset-claims.changeset/8067-component-input-member-kind.md. I read that paragraph: it is aboutpage:header.actionsand is still true.check-changeset-no-major·-overwrite·-fixed·check-pending-changeset-literals·check-installed-spec-pin-claims·check-test-path-roots·check-vi-mock-{specifiers,inherit,override-shape}·check-type-check-coverage·check-lint-coveragenode scripts/check-governed-queue-guard.mjs --testover both pathsNOT GOVERNED — 2 path(s) checked against 5 governed surface(s); none matched.objectui has no
dispatch-gates.mjs. I derived the gate list by hand from.github/workflows/*.yml, keeping the gates whose scripts read test sources,apps/consoleor.changeset/. The fullpnpm lintandpnpm testshards are left to CI.Ablation: three legs, from the committed state
Each leg used
node ../objectstack/scripts/ablation-replace.mjsin wrap mode, with the test running under the lock. I wrote down the expected result before each run: exactly one failed test, the rot check, naming the mutated type. The observed direction was red on all three, as expected.user:profileentry → its exact pre-fix text1 failed / 198 passed·user:profile is retired upstream, and its ledger reason does not say so — rewrite the entry as RETIRED UPSTREAMelement:formentry → its exact pre-fix text1 failed / 198 passed·element:form is retired upstream, and its ledger reason does not say so …cloud-connection:panel(outside the enum, empty shape, not retired) relabelledRETIRED UPSTREAM1 failed / 198 passed·cloud-connection:panel is ledgered as retired upstream, and the spec does not retire it …Every restore was
git checkout HEAD --on the absolute path, proven by blob == HEAD (f714fdd786dc),git diff HEADempty andgit status --porcelainempty. The control run at the base (199 passed) shows that legs 1 and 2 describe a state that was green before this PR.Acceptance notes
path:linecitations in a file you touch. This file has older ones, outside the ledger entries, and I left them alone because the claim's surface is the ledger. Nobody is carrying that repair.ai:chat_window'sNOT REGISTERED, DELIBERATELYreason was re-read againstplaceholders.tsxandblock-types.tsand is still true. The new guard also passes for it.Generated by Claude Code