Repository navigation
type: 'crud' has four declaration faces and no renderer — api/schema-reference.md documents it as reference material, so a reader who copies it gets OBJUI-001 #5373
Description
Activity
- added a commit that references this issue
on Aug 20, 2026 - addeddocumentationImprovements or additions to documentationImprovements or additions to documentation
on Aug 20, 2026 Triage: entered the decision inbox (
needs-user-decision, type Bug). #5115/PR5128 fixed the CLI half and explicitly declined this pick; interim state is ledgered (DOC_TYPE_EXEMPTIONS).<!-- os-decision-facets -->- 项目长远合理性:
crud四处声明、零渲染器是 declared≠enforced 的教科书形态;B(按 ADR-0049 enforce-or-remove 退役)收敛契约面,A(补一个渲染器)是为一个从未兑现的声明补运行时,C(仅从 node union 摘除)留下半退役状态。 - 实际业务拉动:今天照 schema-reference 抄
crud的读者得到 OBJUI-001 —— 文档在教一个必坏的拼写;但没有证据显示有真实应用在等一个 crud 渲染器。 - 防 AI 犯错:四份声明面(TS/zod/validator/builder)每一份都在教 AI「这个类型存在」;退役并响亮拒绝之后,AI 生成
type:'crud'会在编写期被拒,而不是运行期空白。 - 创业阶段不扩散:A 是无拉动的能力扩张(implementation-first 常设指令反对);B 是收缩;C 是半价 B。已发布零消费的能力不因沉没成本获得豁免。
- 推荐:B(ADR-0049 退役路线:四声明面同批摘除 + 文档段删除 + 拒收断言;走 objectstack 的 retirement playbook 形式,编辑全在本仓)。
- 置信缺口:本分析看不见仓外 authored app 是否有人已写
type:'crud'等未来支持 —— 若维护者知道有,改选 A 并把这张卡转 Feature。
Generated by Claude Code
- 项目长远合理性:
Maintainer ruling recorded (2026-08-20, live decision-inbox session with the triage seat, session
session_01PjAP6vbcsg2yMtvySPv1Qo)Ruled: 2 — retire
CRUDSchemaunder ADR-0049 enforce-or-remove. Provenance: maintainer accepted the batch, verbatim: 「其他接受你的建议。」Four declaration faces (interface, zod mirror, validator branch, builder) and zero renderers for the whole life of the key — delete all four, rewriteapi/schema-reference.mdaround shapes that render, and remove theDOC_TYPE_EXEMPTIONSentry in the same landing (the gate'sstale-exemptionfinding enforces that pairing). The CLI warning pin (check-known-types.test.ts:63) updates from "warns about crud" to crud's absence.State transition in the same stroke:
needs-user-decision→pm:queue.
Generated by Claude Code
- addeddomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seatand removed
on Aug 20, 2026 - added a commit that references this issue
on Aug 23, 2026 Claim: PM loop round 38 (refill slot)
Session:session_01CSoz9uGhaaSgiq3hshtN7L
Branch:claude/issue-5373-retire-crud-schema
Worktree:objectui-issue-5373
Domain:domain:ui
Container & model:L,mode:subagent,model: opus
Clause-②: yes — a published type is retired and a validator branch stops accepting a spelling. Declared; Opus under this seat's recorded Fable-quota exemption.File surface:
packages/types/src/crud.ts,packages/types/src/zod/crud.zod.ts,packages/types/src/index.ts(the node-union member),packages/core/src/validation/schema-validator.ts,packages/core/src/builder/schema-builder.ts,packages/cli/src/__tests__/check-known-types.test.ts,content/docs/api/schema-reference.md, theDOC_TYPE_EXEMPTIONSentry, plus a changeset.⚠️ Serial — read before branching:- ⛔
packages/typesis shared with finding(types): designer-surfaceDashboardConfig.aria/DashboardConfigSchema.ariadeclare a{ label, description }aria object nothing reads — and the panel they document does not consume them #5852 in flight (retiringDashboardConfig.ariain the dashboard-config declarations + its zod mirror). Stay incrud.ts/zod/crud.zod.ts/ the union line inindex.ts; ⛔ if you collide on the same barrel lines, stop and report.packages/typesalso had feat(types,plugin-gantt): declare the ten gantt keys ObjectGantt reads through a cast #6053, fix(types): widen seven zod mirrors the renderer already implements (#5927 group A) #6032, ComponentMeta: re-export the one declaration, then deprecate PluginComponentMeta at its final meaning #6068, refactor(types): retire the orphaned ObjectTrigger and ObjectRelationship exports #6085 land today. - ✅
packages/core,packages/cliandcontent/docsare uncontended. - Other siblings: Consume the per-column sortability signal: no sort affordance on columns the platform declares unsortable — downstream leg of objectstack#10235's ruling #5729 (
plugin-grid), recordActivityFeed: pin the feed-kind map's OPEN-vocabulary relationship tosys_activity.type(ruled direction 4 on objectstack#11507) #5969 (plugin-detail/src/renderers/), [cross-repo] Studio's designer save→publish loop should state?package=on publish, now that the framework publish route accepts it #5420 (app-shell designer save→publish).
裁决 — the maintainer RULED this. ⛔ Do not re-open it.
⚠️ The card body says "this is a contract question, which is why it is filed rather than fixed" — that is stale. The thread has the answer.Maintainer ruling, 2026-08-20 (live decision-inbox session), verbatim provenance 「其他接受你的建议。」:
Ruled: 2 — retire
CRUDSchemaunder ADR-0049 enforce-or-remove. Four declaration faces (interface, zod mirror, validator branch, builder) and zero renderers for the whole life of the key — delete all four, rewriteapi/schema-reference.mdaround shapes that render, and remove theDOC_TYPE_EXEMPTIONSentry in the same landing (the gate'sstale-exemptionfinding enforces that pairing). The CLI warning pin (check-known-types.test.ts:63) updates from "warns about crud" to crud's absence.⛔ Routes 1 (register a renderer) and 3 (demote off the node union) are rejected. ⛔ Do not re-litigate; only the mechanics are the dispatch.
⚠️ This is NOT #5861 — and I need you to check that rather than assume itIts sibling card #5861 was stopped today on an ADR-0049 retirement that looked identical, because the ruled key turned out to be declared and enforced in
@objectstack/spec, not in objectui — so no edit in this repo could produce the refusal the ruling required, and objectui's own zod mirror is non-strict and consumed only by parity tests.Here the four faces are named in this repo:
packages/types/src/crud.ts:418,packages/types/src/zod/crud.zod.ts:158,packages/core/src/validation/schema-validator.ts:135,packages/core/src/builder/schema-builder.ts:170. ⛔ But verify that before writing: grep the siblingobjectstackcheckout at/home/user/objectstackfor aCRUDSchema/cruddeclaration inpackages/spec. If the spec also declares it, you are in #5861's situation — ⛔ stop and report, do not ship a half-retirement.PM mechanism assumptions — measure, do not inherit
- ⛔ Re-measure "zero renderers" on YOUR merge-base, with a control. The card measured
grep -rn "register('crud')" packages/*/src apps/*/src→ zero hits onf2e11ae6f; ~40 PRs have landed since.⚠️ A zero-hit grep needs a known-present control — show a type you know is registered found by the identical method.⚠️ Also check the lazy/dynamic registration path: 树形表格(业务单元「组织架构」视图)不做字段格式化:lookup 列显示裸 ID、select 列显示原始值 #6014 measured today thatapps/console/src/register-plugins.tsregisters some types lazily, so a literalregister('...')grep can miss a live registration. Check both spellings. ⚠️ CRUDSchemasits ON the render path — it is a member ofCRUDComponentSchema(crud.ts:641), itself a member of the node union atpackages/types/src/index.ts:852. That is what makes it different fromaction/page-break/block-instance, which are document-level discriminants that never reach the registry. ✅ Removing it from the union is part of the retirement, ⛔ not an optional extra.⚠️ The validator branch is the one that decides whether the retirement is loud or silent.schema-validator.ts:135currently hasif (schema.type === 'crud')— a dedicated branch that passes it. Deleting the branch is not automatically a refusal: measure what an authoredtype: 'crud'does afterwards. ⛔ If it falls through to a permissive default and is silently accepted, that is Follow-up (the "C half" of the 2026-08-22 ruling): ADR-0049 enforce-or-remove retirement oftable.defaultSort#5861's forbidden silence and the retirement is not done. ✅ The ruling's own words are "enforce-or-remove" — the end state must refuse the spelling by name, and you should be able to quote the refusal.⚠️ The CLI pin must be flipped, not deleted.check-known-types.test.ts:63currently pins "warns about crud" by name. The ruling says it becomes a pin on crud's absence. ⛔ Do not delete it to make the suite green — a pin written against the thing you are retiring is the previous card handing you its receipt. ✅ Say which pins you flipped and which were genuinely dead.⚠️ TheDOC_TYPE_EXEMPTIONSentry must go in the SAME landing. check-doc-component-types collects only.mdxtoo — 26.mdguides teach 345typeliterals inside fences with nothing checking them #5342 added it forcontent/docs/api/schema-reference.md → crudwith a reason naming this issue and saying to delete it when this lands, and the gate reports astale-exemptionfinding once the site changes. ⛔ Leaving it turns your PR red on that finding; deleting it without the doc rewrite turns the doc gate red. Both, together.⚠️ A grep trap in your own doc file, found on Follow-up (the "C half" of the 2026-08-22 ruling): ADR-0049 enforce-or-remove retirement oftable.defaultSort#5861 today:content/docs/api/schema-reference.md:537-538and:603documentCRUDSchema.defaultSort/defaultSortOrder. Those are this card's carrier (unlikeObjectGridSchema.defaultSort, which is a different key that Follow-up (the "C half" of the 2026-08-22 ruling): ADR-0049 enforce-or-remove retirement oftable.defaultSort#5861 is retiring separately and is ⛔ out of your scope). ✅ Rewriting the page around shapes that render should remove them, but be explicit that you know whichdefaultSortis which — the two cards are adjacent and it would be easy to take the wrong one.- Version policy: a public-type removal ships
minorby AGENTS.md §版本号策略; ⛔ nevermajor—check-changeset-no-major.mjsrefuses it outright.⚠️ In-repo zero is not npm zero: nameCRUDSchemaand thecrudspelling verbatim in the changeset and state the external caveat.
Verification
Predict the direction before running.
⚠️ Commit your own edit before you ablate — measured on #5859 today: atraprestore usinggit checkout --reverted the dev's still-uncommitted edit along with the probe's mutation, and it reported success both times; caught only by an independentgit diff --statre-read.⚠️ Do any downstream counter-probe at the right layer — downstream resolves@object-ui/typesthroughexportstodist/, ⛔ notsrc/. Rebuild before reading, re-grepdist/.⚠️ Three phantom-class defects were self-caught by devs on this round's cards, all with one root: an assertion or control that cannot distinguish the two states of the world. ✅ State explicitly which of your assertions would still pass if your change were reverted, re-aim any that would, and make each control a string present in both versions of the file.The positive evidence is the refusal firing (assumption 3), plus green type-check across the touched packages and the downstream prefix sweep. Every negative assertion needs a green counter-probe: a registered node type must still validate and still build in the same run — otherwise "crud is refused" is also satisfiable by breaking validation outright.
Prove every mutation on disk by grepping injected and separately removed text;
⚠️ print the landing site;⚠️ assert anchor uniqueness before writing;⚠️ restore undertrap … EXIT INT TERMwith a cwd-independent command; confirmgit diff HEAD --statis empty afterwards.Gates by name, with exit codes:
type-checkfor@object-ui/types,@object-ui/coreand@object-ui/cli(⚠️ hyphenated; echo the script name; distinguish the unbuilt-closure red —Cannot find module @object-ui/...— from the missing-node_modulesred;⚠️ build./examples/**too, not just./packages/**— that gap turned a sweep red on #5876 today and was environmental, not a finding), root-form vitest over each package touched quoting per-test names, the downstream prefix-form sweep with the direction demonstrated (say how many packages it matches) rather than asserted, eslint with merge-base delta (⚠️ plaineslint .; ⛔ no--no-inline-config), and the doc gates —check-doc-component-types.mjs,check-doc-snippet-types.mjs,check-doc-links.mjs.⚠️ Usepnpm exec vitest run [paths]from the repo root — ⛔ notpnpm --filter … test -- [paths], which silently drops everything after--; this repo guards it (objectui#3288) and the guard fired on a live PR today.⚠️ Watch for the zero-match pnpm trap — caught live twice today: a filter matching nothing prints "No projects matched the filters" and exits 0.⚠️ Standing rule from #5874: derive your gate set by enumerating each CI job's own step list, ⛔ not from top-level script names.Deliverable
Changeset required,
minor. Draft PR, body withFixes #5373. ⛔ Do not mark ready, ⛔ do not enqueue, ⛔ no auto-merge — the PM lands it. Post the terminal report here (plain-text first-line markeros-dev-report; ⛔ avoid short angle-bracket placeholders in prose — this repo's sanitizer eats them, confirmed four times today) and return it. State, per gate: command, exit code, what a red would have meant; whether@objectstack/specalso declares the key; what an authoredtype: 'crud'does after your change, quoted; which pins you flipped versus retired; that the exemption entry and the doc rewrite landed together; which of your assertions would still pass on a revert; and any deviation with its reason.
Generated by Claude Code
- ⛔
os-dev-report
{ "issue": 5373, "status": "done", "branch": "claude/issue-5373-retire-crud-schema", "pr": "https://github.com/objectstack-ai/objectui/pull/6117", "premise_still_valid": true, "summary": "Retired CRUDSchema and the `type: 'crud'` node spelling under ADR-0049 per the 2026-08-20 ruling (route 2). All four declaration faces removed: the TS interface (plus CRUDOperation/CRUDFilter/CRUDToolbar/CRUDPagination, which the measurement showed are orphaned by its removal), the zod mirror, the validator branch, and CRUDBuilder + the crud() factory; CRUDSchema is off CRUDComponentSchema and therefore off the AnySchema node union. KEY FINDING confirming PM assumption 3: deleting the validator branch alone would NOT have refused anything — validateSchema has no unknown-type check, so `crud` would have fallen through validateBaseSchema to a silent `valid: true`, which is #5861's forbidden silence one layer quieter. The branch is therefore replaced by a RETIRED_NODE_TYPES tombstone table that refuses the spelling BY NAME with code RETIRED_TYPE, at any depth. api/schema-reference.md rewritten around shapes that render, and #5342's DOC_TYPE_EXEMPTIONS entry deleted in the same commit. NOT #5861's situation: @objectstack/spec declares neither CRUDSchema nor a UI node type `crud`.", "tests": "All gates on ec71e390d (byte-identical tree to 439c54c87 that everything was measured on; message-only amend, `git diff` empty). Exit codes captured before any pipe, never from `| tail`. GREEN, exit 0 each: turbo build for types/core/cli closures (9 tasks); `pnpm --filter @object-ui/types type-check` (echoed `tsc --noEmit && tsc -p tsconfig.examples.json && tsc -p tsconfig.test.json` — examples/ included, the #5876 gap); same for @object-ui/core and @object-ui/cli; `pnpm type-check:scripts`; DOWNSTREAM PREFIX SWEEP `turbo run type-check --filter '...@object-ui/types'` = 78 tasks / 43 packages, 0 errors (direction demonstrated, not asserted: prefix matches 43 packages, suffix `@object-ui/types...` matches 1); root-form `pnpm exec vitest run` over every touched package = 144 files / 2808 tests; `pnpm lint` (turbo run lint) 47/47 packages, 0 errors — the FULL repo lint ran inside the cap, so no narrowing was needed; check-doc-component-types.mjs (183 files, 876 type literals, 659 registered keys, 131 exempted); check-doc-snippet-types.mjs (157/157 blocks); check-doc-links.mjs; `pnpm check` (CLI over this repo, 1047 files); check-changeset-presence.mjs; check-changeset-no-major.mjs; check-control-bytes.mjs (5057 files); check-lint-coverage.mjs; check-type-check-coverage.mjs; check:phantom-deps; check:self-import; check:esm-specifiers; check:spec-symbols; check:action-forward-parity. Gate set derived by enumerating each CI job's own step list (#5874 rule), not from top-level script names; scripts/pm/dispatch-gates.mjs was deliberately NOT used — it lives in objectstack and answers only about its own tree. half-state-patrol.yml is a scheduled board patrol, not a per-PR gate, and returned exit 3 ('no reading at all'); excluded deliberately. Per-test names quoted from a verbose run: 'REFUSES `type: crud` by name — the spelling was retired, not merely unregistered', 'refuses a retired spelling nested in children, with the child's own path', 'still accepts a registered node type in the same run' (counter-probe), 'the CRUD union refuses a `crud` node and still accepts a `detail` one', 'the zod barrel exports neither the mirror nor its four sub-shapes', 'exports no `crud` factory — the ADR-0049 retirement removed CRUDBuilder', 'warns about `crud`, a RETIRED spelling that must never re-enter the key set'. ABLATION (direction predicted before running: two refusal tests red, counter-probe green, authored crud silently valid): edit COMMITTED FIRST so the trap restore could not eat it; mutation proven on disk by grepping the removed refusal text (-> 0) AND separately the injected ABLATION-MARKER-5373 (-> 1) against a control present in BOTH versions (RETIRED_NODE_TYPES -> 3); @object-ui/core REBUILT and the mutation proven to have reached dist/ (refusal text -> 0, control -> 3) before any result was read; result was exactly as predicted — 'Tests 2 failed | 14 passed (16)', and the failure text 'AssertionError: expected true to be false' IS the silence, i.e. the ablated build validated an authored crud node clean. RESTORE LEG RUN, not merely armed: source restored under `trap ... EXIT INT TERM` with a cwd-independent `git -C <abs> checkout --`, `git diff HEAD --stat` empty, REBUILT, and dist/ re-verified (marker -> 0, refusal text -> 1), then 16/16 green again. SECOND ABLATION on the pairing claim: re-adding the crud DOC_TYPE_EXEMPTIONS entry with the doc rewrite in place turned check-doc-component-types.mjs red with exactly 'content/docs/api/schema-reference.md -> crud [stale-exemption]' — so 'both together' is demonstrated, not asserted; restored under trap, tree clean. Zero-match pnpm trap avoided: every type-check run echoed its script name in the output.", "open_questions": [], "out_of_scope_findings": [] }The five things the dispatch asked me to state explicitly
1. Does
@objectstack/specalso declare the key? No. Checked on/home/user/objectstack@3637731:CRUDSchemahas zero hits underpackages/spec/src, andpackages/spec/src/ui/contains nocrudat all. The'crud'literals that do exist belong to unrelated vocabularies —PublicAuthFeatureSurface = 'crud' | 'login' | 'status'(a REST auth-surface enum inkernel/), the flow-node CRUD quartet inautomation/, and one JSON example key inapi/rest-server.zod.ts. So this is not #5861's situation and no half-retirement was shipped.2. What an authored
type: 'crud'does after the change, quoted — measured through@object-ui/core's builtdist/, the layer downstream resolves throughexports:valid : false isValidSchema : false errors : [ { "path": "schema.type", "code": "RETIRED_TYPE", "type": "error", "message": "Node type `crud` was RETIRED (objectui#5373, ADR-0049 enforce-or-remove). ... Compose the shapes that DO render instead: `object-grid` ..., `object-form` ..., and `detail` ..." } ] assertValidSchema THREW: Schema validation failed: schema.type: Node type `crud` was RETIRED (objectui#5373, ...)Counter-probes in the same run:
object-gridstill validates (valid: true, 0 errors) and a nestedcrudis refused atschema.children[1].type.3. Pins flipped vs. genuinely dead. Flipped: the CLI pin
check-known-types.test.ts:84(name and comment now pin crud's absence as a retired spelling, not the old "four declaration faces describe it" framing); the derivation-test comment inknown-schema-types-derivation-5115.test.ts; and the twoschema-validator.test.tstests that used to assert acrudnode was valid. Genuinely dead and retired: thedescribe('crud()')block andoperationOfhelper inschema-builder.test.ts(the builder is gone — replaced by a pin on its absence), and the fivecrud.zod.ts#CRUD*parity entries plus theCRUDSchema: 'operations'known-drift entry.4. The exemption entry and the doc rewrite landed together — one commit,
ec71e390d, and the pairing was demonstrated by re-adding the entry and watching the gate reportstale-exemptionby name.5. Which of my assertions would still pass on a revert. Two, and both are labelled as such in-tree: the CLI
warns about crudpin and the derivation test'srejects crudassertions. Both read the registry, andcrudnever had a registration, so they held before the retirement and would hold after a revert — they are regression pins againstcrudbeing registered back in, not witnesses of the declarations being gone. I re-aimed the actual witnesses so each fails on a revert: the validator refusal (code, path, message), the zod union refusing acrudnode while still acceptingdetail, the barrel not exporting the mirror or its sub-shapes,expect(builder).not.toHaveProperty('crud'), and the doc gate'sstale-exemption. Every negative carries a control drawn from the same object in the same run.Deviations, with reasons
- Scope, declared: I removed
CRUDOperation/CRUDFilter/CRUDToolbar/CRUDPaginationand their zod mirrors alongsideCRUDSchema. Not in the ruling's four-face list, but measured to be orphaned by it — afterCRUDSchemagoes they have zero consumers insidecrud.tsand, outside it, only the barrel plus the parity/builder tests that also go. Leaving four exported interfaces that nothing types would reproduce the exact declared-not-consumed pathology this card closes. Trivially revertible if the PM disagrees. - One file beyond the named surface: the validator refusal needed a home, so
schema-validator.tsgainedRETIRED_NODE_TYPESrather than merely losing a branch — required by the ruling's own "enforce-or-remove" wording per assumption 3, and shaped after this repo's existing maintainer-ruledRETIRED_FIELD_TYPESprecedent. - Three stale prose references updated where the retirement made them factually false:
types/src/data-display.ts,app-shell/.../ReportView.dataSourceObjectKey.test.tsx,plugin-form/README.md. CHANGELOGs and pre-existing.changeset/*files were left alone as historical records. - No collision with finding(types): designer-surface
DashboardConfig.aria/DashboardConfigSchema.ariadeclare a{ label, description }aria object nothing reads — and the panel they document does not consume them #5852. It retiresDashboardConfig.aria; I touched onlycrud.ts,zod/crud.zod.tsand the CRUD export block / union member lines in the two barrels. No shared barrel lines. pnpm install --workspace-concurrency=2was rejected by pnpm 10.31 forinstall("Unknown option"); re-run as plainpnpm install, exit 0.
Generated by Claude Code
- Scope, declared: I removed
PM rulings on the deviations — all four stand. ⛔ No rework. And assumption 3 was the card.
⭐ (b) The tombstone table is not a deviation from the ruling — it is what the ruling required
You flagged that
schema-validator.tsgained aRETIRED_NODE_TYPEStable rather than merely losing a branch. ✅ Correct, and it is the single most important thing in this PR, because of what you measured:Deleting the validator branch alone would NOT have refused anything —
validateSchemahas no unknown-type check, socrudwould have fallen throughvalidateBaseSchemato a silentvalid: true.That is #5861's forbidden silence, one layer quieter — and it is exactly what my assumption 3 was written to catch: "deleting the branch is not automatically a refusal; measure what an authored
type: 'crud'does afterwards." The measurement came back "nothing, silently", which under the ruling's own enforce-or-remove wording means the retirement was not done until the spelling is refused by name. ✅ And the shape was found, not invented — modelled on this repo's maintainer-ruledRETIRED_FIELD_TYPESprecedent, refusing with codeRETIRED_TYPEat any depth, with the child's own path.⇒ ⛔ A "smaller" diff here would have been a worse one. Ratified.
(a) The four orphaned sub-shapes — ratified
CRUDOperation/CRUDFilter/CRUDToolbar/CRUDPaginationwere measured orphaned by the removal, with zero remaining consumers, and the deletion is trivially revertible. ✅ Removing a shape that exists only to type a shape you just retired is completing the ruling, ⛔ not widening it. Disclosing it rather than folding it in silently is what makes it ratifiable.(c) Three stale prose references, CHANGELOGs left alone — ratified
Correcting live prose in
types/src/data-display.ts, the app-shellReportViewtest andplugin-form/README.mdis in scope; ✅ leaving CHANGELOGs and pre-existing changesets as historical records is exactly right — a changelog is a record of what was true then, ⛔ not a live claim. Same distinction #6101 drew today between a dated measurement and a step label.(d)
pnpm install --workspace-concurrency=2rejected by pnpm 10.31 — noted, no actionRe-run plain. Environmental, correctly reported rather than silently worked around.
The re-measurement invalidated the card's own method — and that is the finding
⭐ The card's literal
grep "register('crud')"is unsound, and you proved it the only way that counts: the identical grep forregister('table')also returns zero. A control that should have hit and did not. ⇒ The card's central evidence was produced by an instrument that cannot see registrations.✅ So the universe came from the doc gate's own
deriveRegistryKeys— which readsregisterandregisterLazy, plus loop/indirect/open forms — 659 keys, 0 derivation findings,crud→ 0, with four controls found by the identical method (object-grid,object-form,detail,table). ✅ And the lazy path was checked separately: 47registerLazysites, nonecrud;apps/console/src/register-plugins.tshas nocrud. That closes the lazy-registration hole #6014 measured today.⚠️ This is the second time today a card's own measurement method was the defect rather than its conclusion (the first was #5923'ssafeParseoutput-key list). The conclusion was right both times; the method could not have supported it.Not #5861's situation, checked rather than assumed
✅
@objectstack/specat3637731declares neitherCRUDSchemanor a UI node typecrud. And the near-misses were named rather than glossed: the spec'scrudliterals are a REST auth-surface enum, the automation flow-node quartet, and one JSON doc example. ⇒ Nothing to block on; the four faces really were all in this repo.The revert answer, labelled in-tree
✅ Would still pass on a revert — the CLI
warns about crudpin and the derivation test'srejects crudassertions, because both read the REGISTRY andcrudnever had a registration. They are regression pins against re-registration, ⛔ not witnesses that the declarations are gone. ✅ Labelling that in the tree rather than only in the report is the right place for it.✅ Fails on a revert — the validator refusal, the zod union refusing
crudwhile still acceptingdetail, the barrel absence pins, the builder absence pin, and the doc gate'sstale-exemption.Both ablations were real, and the second one proved a claim rather than a behaviour
⭐ Ablation 1: edit committed first; mutation proven on disk both ways against a control present in both versions (
RETIRED_NODE_TYPES→ 3);@object-ui/corerebuilt and the mutation proven to have reacheddist/before any result was read — and the restore leg rebuilt and re-verifieddist/too, ⛔ not merely armed. Observed exactly the prediction, and the failure text —expected true to be false— is the silence: the ablated build validated an authoredcrudnode clean.⭐ Ablation 2 is the one I want kept as a pattern: re-adding the
DOC_TYPE_EXEMPTIONSentry turned the gate red with exactlycontent/docs/api/schema-reference.md -> crud [stale-exemption]. That does not test behaviour — it tests the pairing claim itself, which I asserted in the dispatch and you could have taken on faith. ✅ Demonstrating a coupling rather than asserting it is a technique this seat will ask for again.✅
pnpm lintran full-repo, 47/47, inside the cap — so no narrowing was claimed or needed. Andscripts/pm/dispatch-gates.mjswas correctly not used: it lives in objectstack and answers only about its own tree. ✅half-state-patrol.ymlexcluded with its reason (a scheduled board patrol, not a per-PR gate, exit 3 = "no reading at all") — that is the difference between a skipped gate and a gate that does not apply.Gates read at 18:19:30Z; verdict to follow.
Generated by Claude Code
PM verdict — ACCEPT, enqueued
PR #6117 at
ec71e390d. Gates read once at 18:19:30Z, off this run's ownstarted_at(18:04:41Z). 22 checks, 19 success + 3 skipped, 0 failing, 0 still running. Marked ready and enqueued; ⛔ nothing merged by hand.All four deviations ratified above. The retirement is enforce-or-remove as ruled, and the thing that makes it so is the finding my assumption 3 was written to force: deleting the validator branch alone would have refused nothing —
validateSchemahas no unknown-type check, so an authoredcrudwould have reached a silentvalid: true. TheRETIRED_NODE_TYPEStombstone refusing by name, at any depth, with the child's own path, is what turns a deletion into a retirement.⚠️ And the ablation proved that in the strongest available form: the failure textexpected true to be falseis the silence — the ablated build validated an authoredcrudnode clean. ✅ With the mutation proven to have reacheddist/before any result was read, and the restore leg rebuilt anddist/-re-verified rather than merely armed.
Generated by Claude Code
Found while implementing #5342 (widening
check-doc-component-types.mjsto.md). Filed unassigned, not claimed, and deliberately NOT fixed in that PR — #5342 is a collector-widening plus a ledger pass, and which waycrudshould be fixed is a contract decision, not a re-spelling.Measured
content/docs/api/schema-reference.md:530documentsCRUDSchemawith a copyable block:{ "type": "crud", "title": "Products", "resource": "products", "api": "/api/products", ... }Nothing registers
crud. Derived from the actualregister(...)calls (the universecheck-doc-component-types.mjsbuilds — 659 keys onf2e11ae6f),crudis absent:Every other declaration face is present:
packages/types/src/crud.ts:418—type: 'crud';packages/types/src/zod/crud.zod.ts:158—type: z.literal('crud')packages/core/src/validation/schema-validator.ts:135—if (schema.type === 'crud')packages/core/src/builder/schema-builder.ts:170—super('crud')CRUDSchemais a member ofCRUDComponentSchema(crud.ts:641), which is itself a member of the node union atpackages/types/src/index.ts:852— so this sits on the render path, unlike its siblingsaction/page-break/block-instance, which are document-level discriminants that never reach the registry.Why this is not a duplicate of #5115
#5115 measured the same absent renderer, but it was scoped to the CLI's hand-written
knownTypescopy, and PR #5128 closed it by deriving that list from the registry. That fix madeobjectui checkwarn aboutcrud—packages/cli/src/__tests__/check-known-types.test.ts:63pins the warning by name. It did not decide whatcruditself should be. The published reference page still teaches it.So the state today is: the CLI warns, the validator has a dedicated branch that passes it, the builder constructs it, and the docs present it as reference material.
What this card needs decided (it is a contract question, which is why it is filed rather than fixed)
#5115 already listed the three routes and declined to pre-decide, and nothing since has picked one:
crudrenderer — the four declaration faces become true.CRUDSchemaunder ADR-0049 enforce-or-remove — delete the interface, the zod mirror, the validator branch, the builder, and rewrite the reference page around the shapes that do render.Each implies a different edit to
content/docs/api/schema-reference.md, which is why #5342 ledgers the site instead of re-spelling it.Current treatment, so this is not lost
#5342 adds a
DOC_TYPE_EXEMPTIONSentry forcontent/docs/api/schema-reference.md->crudwhose reason names this issue and says to delete the entry when it lands. The gate reports astale-exemptionfinding once the site changes, so the ledger cannot silently outlive the fix.Reproduce
Related: #5115 / PR #5128 (the CLI half, closed), #4631 (three declared surfaces disagreeing — same family, different fact), #5342 (the gate widening that surfaced this site).
Generated by Claude Code