Repository navigation
feat(spec): declare record:approvals and record:attachments with measured strict ComponentPropsMap rows - #22595
Conversation
…measured strict ComponentPropsMap rows Both are objectui renderers registered inside the reserved `record` namespace with no PageComponentType member and no row, so lint's `component-type-unknown` refused a node the platform's own default-page synthesizer emits. Each becomes an enum member with a strict row measured from the renderer's read points at the `.objectui-sha` pin: `record:attachments` reads no props (`emptyProps`), and `record:approvals` reads only the host's runtime channel (`approvals`, `currentUserId`), refused with prescriptions. Both get a print-refusal reason, and the `record:line_items` row comment no longer calls it the last registered `record:*` renderer without a row. Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <noreply@anthropic.com>
…he rows and the authoring doors The row stays module-private (its accepted key set is empty, like the `emptyProps` rows), and the generated references and the strictness-ledger counts are regenerated for the two new members and the new strict site. Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <noreply@anthropic.com>
…d `record:attachments` Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <noreply@anthropic.com>
…cord-approvals-attachments-rows
📓 Docs Drift CheckThis PR changes 1 package(s): 2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 5 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 139 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 adfc0e4ee70c03af4dbb465572ebb0e139adc2da && git checkout adfc0e4ee70c03af4dbb465572ebb0e139adc2da
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 99801d831faa07fa374cb21acc30a469fa6aa6e1 e492d331d4b16bb9a2bc2d909a1c9466bd6cb2cf && git checkout -B drift-repro 99801d831faa07fa374cb21acc30a469fa6aa6e1 && git merge --no-ff e492d331d4b16bb9a2bc2d909a1c9466bd6cb2cf
node scripts/docs-audit/affected-docs.mjs --json 99801d831faa07fa374cb21acc30a469fa6aa6e1
|
Contract reviewServed-tier: Head resolved from ① Derived judgmentsEvery accept-set and public-surface change the diff implies, each judged against the renderer read points at the pin:
Nothing in the diff is judged wrong. ② Semver level
③ Boundary flagsEvery dev deviation and every
Implemented-by: VERDICT: PASS Generated by Claude Code |
Landing pre-checks at
|
Fixes #22537
Clause-②: yes (widening)
What this declares
record:approvalsandrecord:attachmentsare now declared page component types in@objectstack/spec. They are the record page's Approvals and Attachments panels. objectui registered both inside the spec-reservedrecordnamespace with noPageComponentTypemember and noComponentPropsMaprow, so thecomponent-type-unknownauthoring rule refused a node that the platform's own default-page synthesizer emits. This follows the triage remedy (6089894781, unlocked in6091349791): measured rows in the spec, not a rename.packages/spec/src/ui/page.zod.tsPageComponentTypebesiderecord:approval_decision;PRINT_REFUSED_PAGE_COMPONENT_TYPESgets a reason for each (the print pin requires every vocabulary member to be classified)packages/spec/src/ui/component.zod.ts'record:attachments': emptyProps(...);'record:approvals': RecordApprovalsProps, a module-private strict row that accepts no key and refuses the two host-channel keys with a prescription; therecord:line_itemsrow comment no longer calls it "the last registeredrecord:*renderer without a row"; theemptyPropsdocblock listsrecord:attachmentscontent/docs/references/ui/page.mdxgen:docs): the two enum membersdocs/audits/2026-07-unknown-key-strictness-ledger.counts/ui.mdgen:strictness-ledger): one more strict site incomponent.zod.tspackages/spec/src/ui/component-record-approvals-attachments-22537.test.tspackages/lint/src/validate-record-approvals-attachments-22537.test.tsos validatereaches on Studio's seeded page.changeset/22537-spec-record-approvals-attachments.md@objectstack/specminor,Clause-②: yes (widening)The doors on
main, measured before the changeThe triage asked for the save door first. Reading on
mainatd6c37919c7(this branch's base), with a page shaped like the one Studio's page create seeds for anenable.files: trueobject. That page is theregionsandtemplateof objectui'sbuildDefaultPageSchema(objectDef)called with no options (app-shell/src/views/metadata-admin/anchors.ts:196-216at the.objectui-shapin47b1f0bb71), so its Attachments tab holds a barerecord:attachments. Studio's seed never emitsrecord:approvals: the synthesizer emits it only when the runtime host passes its live approvals read.saveMetaItem, whatPUT /api/v1/meta/page/:namelands in)os validateverdictrecord:attachments)activecomponent-type-unknownatpages[0].regions[0].components[2].properties.items[1].children[0].typerecord:approvals)component-type-unknownon both nodesapprovalsandcurrentUserId)component-type-unknownon both nodesprint: {}INVALID_METADATA(print-page-block-unprintable)user:profilenodeINVALID_METADATA(the parse)mainor on this branch.validateComponentTypesis registeredCLI_ONLY(packages/lint/src/authoring-rules.ts), held off the runtime door until a false-refusal budget over stored tenant rows is measured. The rules the page door ran werevalidatePresetComparandsandvalidatePrintPageBlocks. The two controls show the harness's gate was live.os validatedoes refuse it. That is the door this change opens.protocol.runtime-authoring-gate.test.tsdrivingObjectStackProtocolImplementation.saveMetaItem.os validateusednormalizeStackInput, thenObjectStackDefinitionSchema.safeParse, thenrunAuthoringRules('validate', …), with anerrorfinding as the refusal. That is the CLI'sjudgeAuthorTimeRuleswithout the JSX gate and the per-package pass, which this config never reaches. Theosbinary itself was not run, because its 60-package build closure was not built.After the change, same pages, same doors
os validatecomponent-props-unknown-keywarnings (approvals,currentUserId)Inside a
printpage,record:attachmentsis now refused with its own reason. Before, it got the generic "not in the printable block subset" reason.The row measurement (objectui at the
.objectui-shapin47b1f0bb71)record:attachments(app-shell/src/views/record-attachments-renderer.tsx, registered:69): the renderer discards the schema node (schema: _schema,:45) and reads the record context, the auth user andclassName. The row isemptyProps.record:approvals(app-shell/src/views/record-approvals-renderer.tsx, registered:80).SchemaRendererhoistspropertiesonto the schema the renderer reads. The renderer reads two keys:schema.approvals(:61): the default record page threads its live approvals read through it (RecordDetailView.tsx:2492-2501, viabuildDefaultPageSchema({ approvals }),buildDefaultPageSchema.ts:931). With no payload, the renderer self-fetches (:64-68).schema.currentUserId(:71): the host passesuser?.id, and the panel's Remind gate falls back to it (RecordApprovalsPanel.tsx:287).record:history'sentries/loadingandrecord:quick_actions' inlineactionsbelong to. That precedent refuses such keys with a prescription and does not declare them. So the row accepts no key, and it refusesapprovals("a static, fake approval history that never updates") andcurrentUserId("would decide for every viewer who counts as the submitter"), each with the fix: omit it.RecordApprovalsPropsis not exported. Its accepted key set is empty, like theemptyPropsrows, so there is no author-state type to publish. The row is reached asComponentPropsMap['record:approvals']. The API surface is unchanged (check:api-surfacegreen, noapi-surface/diff).The false sentence, and the vocabulary claims
component.zod.ts'srecord:line_itemsrow comment said "the last registeredrecord:*renderer without a row". This PR corrects it.component-type-vocabulary.tsclaims that the map covers every measured registration. "EMPTY since finding(showcase): project-detail'srecord:line_itemsblock keys its five columns asfield, while the line-items grid bindsname— the Tasks grid renders empty cells #21142" is a statement about the ledger, and it stays true. TheKNOWN_COMPONENT_TYPESdocblock ("a superset of the enum by exactly the measured string-arm registrations that DID get a row") is not made false: both types are enum members now. That file is untouched.Pins
component-record-approvals-attachments-22537.test.ts):{}at the row, node and page doors, and refuses an invented prop by name;propertiesgets the node prescription;approvalsandcurrentUserIdare refused with their prescriptions;validate-record-approvals-attachments-22537.test.ts):component-type-unknownand no props finding, and reaches a zero-erroros validateverdict;record:aprovalsis refused at the exact path;record:attachmentandrecord:aprovalsoffer the declared spelling;component-props-unknown-keywarning naming the key;Verification, at
e492d331d4(this branch's head, after one merge oforigin/mainat96e4be4829)Suites and typechecks. Every one of these ran on this head. Spec and lint were run whole, and the three consumer suites were run at their envelope test:
@objectstack/spectscpluscheck:test-typecheckOK@objectstack/linttscpluscheck:test-typecheckOK@objectstack/mcpcanonical-expression-envelopes.test.ts11 passed@objectstack/platform-objectspages/canonical-expression-envelopes.test.ts20 passed@objectstack/cloud-connectioncanonical-expression-envelopes.test.ts16 passedThose are the packages that read
PageComponentTypeorComponentPropsMap. The three consumer packages reach them only through@objectstack/lint's envelope audit and aPagetype import.Generated artifacts.
pnpm --filter @objectstack/spec check:generated: "All 15 generated artifacts are up to date". The--fixround regenerated onlypage.mdxand the strictness-ledger counts.Derived gates.
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, with no paths, derived 109 families. 108 exited 0. The reconciliation (--ran, exit-annotated) reads "109 derived famil(ies) accounted for — 108 run, 1 NOT-MEASURED".check:dual-build-cjs-loads. Reason: the dispatch forbids the whole-workspace build it loads.cloud-connectionclosure build was rewritingdist/. Six exited 3 (PREREQUISITE NOT MET) and two exited 1 (check:generated,check:dts-closure). All 34 were re-run once the build had finished, and all exited 0. The record carries the re-run codes.Artifact rosters. The 48 roster commands outside the PR-context guards all exited 0, the 14 checker-health
--self-testrows among them. The three PR-context guards (check-closing-target-claim,check-partof-closing-keyword,check-single-claim-paths) are run against this PR once it exists, and the report carries their readings.ESLint, narrowed and proved. CI runs the whole repo.
eslint.config.mjs: every**/*.{ts,…}file outsideNEVER_LINTED, and the four touched TypeScript files are inside it.--format jsonreports 4 files, 0 errors and 0 warnings.parserOptions.project, stated in its own header). So this diff cannot move the verdict on any file it does not touch.Not measured locally:
osbinary itself (its 60-package closure was not built; its verdict function was measured instead);Acceptance notes
kind: 'react'page that names the RecordApprovals or RecordAttachments JSX tag is now refused byreact-block-needs-record-context.RECORD_CONTEXT_BLOCK_TAGSderives from the map'srecord:*keys.error, as RecordApprovalDecision did from spec(ui) v18: declare the approval decision panel as a page component type with a strictComponentPropsMaprow (objectui#12045 B1, ruling 乙) #22472 on.ComponentRegistry.getPublicConfigs()(components/src/renderers/layout/react-page.tsx:90), and neither type is in the curated public-block list (core/src/registry/public-blocks.ts). So the new refusal names a page that never rendered.(narrowing): no page that worked is refused. The report asks the seat to confirm.component-type-unknownstays off the save door (CLI_ONLY, its crossing is its own rollout). This change neither moves it nor needs to.@objectstack/linthas no changeset. Its behaviour changes only by reading the spec's map, the same as spec(ui) v18: declare the approval decision panel as a page component type with a strictComponentPropsMaprow (objectui#12045 B1, ruling 乙) #22472.Generated by Claude Code