Uh oh!
There was an error while loading. Please reload this page.
feat(spec,lint): author-time rejection for unknown component types in spec-reserved namespaces; Phase-2 readiness read keeps both members - #13117
Conversation
… spec-reserved namespaces The type-vocabulary half of the Component Placeholder gap: PageComponentSchema.type keeps its deliberate open string arm (custom/registered components are measured, load-bearing consumers of it), but the spec now answers for its own namespaces — a type inside them that neither the enum, nor ComponentPropsMap, nor the evidenced string-arm ledger declares is refused at author time by the new gating rule component-type-unknown, with closest-spelling suggestions. The parse is unchanged; the refusal lands at os validate / os build / os lint. Readiness read for the Phase-2 members (evidence in the PR): global:search and global:notifications both have shipped platform data sources, so per the ruling both STAY declared — no retirement rides this change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4
📓 Docs Drift CheckThis PR changes 2 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 126 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 6e4f5790f98211aee7f8bb6a6a9d389e67421cd5 && git checkout 6e4f5790f98211aee7f8bb6a6a9d389e67421cd5
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b15d260d100c78bb8a2c600e7c8263c99775591f 63b361403a81885f45a6a0b6bdfecb1d66233472 && git checkout -B drift-repro b15d260d100c78bb8a2c600e7c8263c99775591f && git merge --no-ff 63b361403a81885f45a6a0b6bdfecb1d66233472
node scripts/docs-audit/affected-docs.mjs --json b15d260d100c78bb8a2c600e7c8263c99775591f
|
Uh oh!
There was an error while loading. Please reload this page.
Fixes#12950
Part of the #12183 decomposition (Phase 2) — #12183 remains open for Phase 1 and the renderer work.
The ruling this implements
Maintainer, 2026-08-26 (comment 5419723931 on #12183), verbatim: 「其他接受你的建议」 accepting:
Work item 1 — readiness read (evidence faces, per member)
global:search— data source SHIPPED, member STAYSMetadataProtocol.searchAll—packages/metadata-protocol/src/protocol.tsline 10066: implemented cross-object search (hits with title/snippet/record, per-object caps,enable.searchable/apiEnabledopt-outs); capability advertised at line 5219 (search: typeof this.searchAll === 'function')GET /api/v1/search—packages/rest/src/rest-route-ledger.tsline 297, with conformance coverage (search-clone-route-schema-conformance.test.tsdrives the mount; metadata-protocol'ssearch-clone-schema-conformance.test.tsparses the realsearchAllproducer)packages/client/src/index.tsline 4617 — client search method, "501s on kernels withoutsearchAll"packages/react/src/hooks/useRecordSearch.tsalready reads/api/v1/searchglobal:notifications— data source SHIPPED with a live UI consumer, member STAYSsys_inbox_message"sys_inbox_messageL5 materialization (packages/services/service-messaging/src/objects/inbox-message.object.ts) +sys_notification_receiptread-state spineMessagingService.listInboxbacksGET /api/v1/notifications(messaging-service.tslines 346-390;runtime/src/domains/notifications.tsbinds the authenticated user), whole-inboxunreadCountbadge semantics per the #6363 ruling;service-realtimepings online usersapp-shellInboxPopover/useHomeInbox— reads the same feed todayDecision per the ruling: retirement B is conditioned on "no data-source plan inside the horizon". Both members have data sources that are not merely planned but shipped, REST-exposed, SDK-exposed, and consumed by the sibling repo's own console chrome. Both members STAY declared; this is reported back rather than retired. Neither face is ambiguous, so no needs_decision arm fires.
Report-back for PM routing: the renderer gap itself remains (both members still draw the placeholder until objectui lands renderers). With readiness now evidenced, the natural follow-up is a Phase-1-style renderer card for these two in objectui — routed by the spec seat that owns the #12183 decomposition, not filed unilaterally here.
Work item 2 — retirement per B: NO-OP, with evidence
No member retires (see above), so: enum UNCHANGED, no ADR-0087 conversion, no
RETIRED_KEYS_BY_MAJORentry, no liveness-ledger movement, no baseline row removed. The keep verdict is pinned incomponent-type-vocabulary.test.ts("the Phase-2 members stay declared") so a later retirement is a conscious edit through the spec-property-retirement playbook.Work item 3 — author-time rejection for unknown type strings
The measured extension story (why a bare union collapse is not viable)
component.zod.tsline 105 andvalidate-component-props.tslines 84-103):typeis an open union BY DESIGN; the example corpus authorsflex,grid,object-chart,record:line_items— SDUI blocks living in objectui's registry and the ADR-0080 manifest, which is a cross-repo artifact this repo cannot read (validate-jsx-pages.tsline 19 records that wiring as future work).conversions/registry.tsline 4805: the union is "an open namespace for custom components"; objectui's kebab legacypage-headeris authorable through it.ComponentPropsMaprows and no enum entry:cloud-connection:panel,marketplace:installed-list(spec:cloud-connection:panelandmarketplace:installed-listhave no ComponentPropsMap row — two more instances of the #8691/#8744 silent no-op class #11575),mcp:connect-agent(mcp:connect-agenthas noComponentPropsMaprow — door 3 of the canonical-envelope gate is blind to its props, and the gate now carries a standing exemption for it #12344).page.test.tspinscustom.dashboard-widget/custom.widgetacceptance.record:line_itemsis REGISTERED in objectui (plugin-form/src/index.tsxline 340, public block atcore/src/registry/public-blocks.tsline 50) and authored by the showcase (project-detail.page.tsline 72); its map row-lessness is pinned deliberately invalidate-component-props.test.tslines 425-427.A union collapse breaks all of the above. A blanket namespace constraint (reject any colon string outside the enum) breaks the three plugin widgets and
record:line_items.The mechanism the tree supports: a namespace-shaped validate-time gate
New gating rule
component-type-unknown(packages/lint/src/validate-component-types.ts), on all three CLI commands via the shared authoring registry, severity error:page:record:nav:global:user:ai:app:element:), never restated —component-type-vocabulary.tsin@objectstack/spec/ui, with derivation pins.ComponentPropsMaprows (coverselement:metadata_viewerand the tombstonedelement:filter/element:form, whose ADR-0049 candidate:element:filterdeclarestargetVariable(and possibly the whole element) with no renderer or reader in any repo #9220/ADR-0049 candidate:element:formdeclares a full form contract with no renderer registration found in objectstack/objectui #9249 bare-node contract is preserved and pinned) ∪STRING_ARM_REGISTERED_TYPES— an evidenced ledger whose single entry isrecord:line_items; a pin forces the ledger row OUT the day the map row lands.PageComponent.properties是开放 record,ComponentPropsMap的 29 个站点从不被 parse(#4001 批 17 的 no gate 判定) #5068's warning-first): corpus measured BEFORE severity was chosen — across examples and packages page sources, the only reserved-namespace strings outside the accept set are conversion-fixture stand-ins inconversions/registry.ts(record:detail,record:list,element:custom— replayed by the conversion harness, never fed to the authoring commands) and the ledgeredrecord:line_items. Zero live findings.surfaceReason: crossing owes the [P2] 运行时授权门扩到 object 写入 + 全量 runtime-safe 规则快照(#4463 P1 之后) #4716 discipline (a measured false-refusal budget over stored tenant page rows; the in-repo 0-finding measurement covers authored config-file metadata only).findClosestMatches.Contract section — exact accept-set movements
PageComponentTypeenumPageComponentSchema.typeparse.describe()prose now states the namespace claimos validate/os build/os lint)component-type-unknownfindings with suggestions@objectstack/spec/uiexportsapi-surface/ui.jsonandexport-origins/ui.jsonregenerated by tooling)Evidence (all runs at head
63b361403, after the final commit)export-originsfreshness guard demanding regeneration for the new exports; regenerated viacheck:generated --fix, re-run green 22/22).typecheckgreen on all three legs (src tsc, scripts, test layer via check:test-typecheck).--listFilesproof:component-type-vocabulary.ts+page.zod.tsin the src program;component-type-vocabulary.test.tsin the test program.typecheckgreen. NOT MEASURED:validate-component-types.test.tsis excluded from lint's tsc program (tsconfig.jsonexcludes all test files — pre-existing package posture, not introduced here); it is exercised by vitest at runtime.grepover packages): cloud-connection 27 files passed, mcp 23 files passed, platform-objects 33 files passed (after building their dependency closures; the first run's 17 file-level failures were unresolved@objectstack/coredist imports on the unbuilt worktree, 0 test failures).check:generatedverdict "3 of 14 artifacts stale" → regenerated exactly those (gen:api-surface,gen:export-origins,gen:docs) via--fix; re-run green.check:authorable-surfacegreen (verdict line in the gate log); the enum and authorable keys did not move, consistent with the playbook's enum-grain visibility table.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, derived from the real changeset at head): 58 gates run with per-gate exit capture before any pipe — 56 exit 0, includingcheck:doc-authoring,check:adr-0087-registration,check:type-check-debt("31 ledger entries re-measured, none above its recorded number"),check:type-check-coverage,check:query-options-erasure,check:nul-bytes("scanned 7286 text files, no raw ASCII control bytes"). Two are prerequisite-not-met, not red:check-test-completeness.mjs(requires the CI turbo test log as an argument) andpm/check-half-states.mjs(exit 3 "no reading at all" — needs a real GitHub token this container does not hold); both are CI/PM-owned runs.global:serch→ one error finding with theglobal:searchsuggestion;global:search,record:line_items,mcp:connect-agent,flex→ accepted.The hotCRM repro pages are untouched, and no renderer work rides here (Phase 1 is objectui#6661; the two never share files).
Generated by Claude Code
Generated by Claude Code