Skip to content

feat(spec): declare record:approvals and record:attachments with measured strict ComponentPropsMap rows - #22595

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-22537-record-approvals-attachments-rows
Oct 10, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-22537-record-approvals-attachments-rows

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #22537
Clause-②: yes (widening)

What this declares

record:approvals and record:attachments are now declared page component types in @objectstack/spec. They are the record page's Approvals and Attachments panels. objectui registered both inside the spec-reserved record namespace with no PageComponentType member and no ComponentPropsMap row, so the component-type-unknown authoring rule refused a node that the platform's own default-page synthesizer emits. This follows the triage remedy (6089894781, unlocked in 6091349791): measured rows in the spec, not a rename.

File Change
packages/spec/src/ui/page.zod.ts both join PageComponentType beside record:approval_decision; PRINT_REFUSED_PAGE_COMPONENT_TYPES gets 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; the record:line_items row comment no longer calls it "the last registered record:* renderer without a row"; the emptyProps docblock lists record:attachments
content/docs/references/ui/page.mdx regenerated (gen:docs): the two enum members
docs/audits/2026-07-unknown-key-strictness-ledger.counts/ui.md regenerated (gen:strictness-ledger): one more strict site in component.zod.ts
packages/spec/src/ui/component-record-approvals-attachments-22537.test.ts pins at the rows, the node, the page, the vocabulary and the print classification
packages/lint/src/validate-record-approvals-attachments-22537.test.ts pins at the two authoring rules and at the verdict os validate reaches on Studio's seeded page
.changeset/22537-spec-record-approvals-attachments.md @objectstack/spec minor, Clause-②: yes (widening)

The doors on main, measured before the change

The triage asked for the save door first. Reading on main at d6c37919c7 (this branch's base), with a page shaped like the one Studio's page create seeds for an enable.files: true object. That page is the regions and template of objectui's buildDefaultPageSchema(objectDef) called with no options (app-shell/src/views/metadata-admin/anchors.ts:196-216 at the .objectui-sha pin 47b1f0bb71), so its Attachments tab holds a bare record:attachments. Studio's seed never emits record:approvals: the synthesizer emits it only when the runtime host passes its live approvals read.

Page Save door (saveMetaItem, what PUT /api/v1/meta/page/:name lands in) os validate verdict
A. Studio's seed (record:attachments) admitted, stored active refused: component-type-unknown at pages[0].regions[0].components[2].properties.items[1].children[0].type
B. A plus an authored Approvals tab (bare record:approvals) admitted refused: component-type-unknown on both nodes
C. A plus the runtime host's Approvals node (with approvals and currentUserId) admitted refused: component-type-unknown on both nodes
control: A with print: {} refused, 422 INVALID_METADATA (print-page-block-unprintable)
control: a user:profile node refused, 422 INVALID_METADATA (the parse)
  • The save door does not refuse the platform's own page, on main or on this branch. validateComponentTypes is registered CLI_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 were validatePresetComparands and validatePrintPageBlocks. The two controls show the harness's gate was live.
  • os validate does refuse it. That is the door this change opens.
  • How it was measured: two throwaway in-process tests, not committed. The save door used the stub engine of protocol.runtime-authoring-gate.test.ts driving ObjectStackProtocolImplementation.saveMetaItem. os validate used normalizeStackInput, then ObjectStackDefinitionSchema.safeParse, then runAuthoringRules('validate', …), with an error finding as the refusal. That is the CLI's judgeAuthorTimeRules without the JSX gate and the per-package pass, which this config never reaches. The os binary itself was not run, because its 60-package build closure was not built.

After the change, same pages, same doors

Page Save door os validate
A admitted passes: 0 errors, 0 component findings
B admitted passes: 0 errors, 0 component findings
C admitted passes with 2 advisory component-props-unknown-key warnings (approvals, currentUserId)

Inside a print page, record:attachments is 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-sha pin 47b1f0bb71)

  • 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 and className. The row is emptyProps.
  • record:approvals (app-shell/src/views/record-approvals-renderer.tsx, registered :80). SchemaRenderer hoists properties onto 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, via buildDefaultPageSchema({ approvals }), buildDefaultPageSchema.ts:931). With no payload, the renderer self-fetches (:64-68).
    • schema.currentUserId (:71): the host passes user?.id, and the panel's Remind gate falls back to it (RecordApprovalsPanel.tsx:287).
  • Both keys are the host's runtime channel. That is the class record:history's entries / loading and record:quick_actions' inline actions belong to. That precedent refuses such keys with a prescription and does not declare them. So the row accepts no key, and it refuses approvals ("a static, fake approval history that never updates") and currentUserId ("would decide for every viewer who counts as the submitter"), each with the fix: omit it.
  • This departs from the dispatch's working rule ("the row is the union the renderer reads"). The departure is deliberate and is raised in the report. Declaring the two keys would bless exactly the trap the precedent closes.
  • The synthesizer emits no key that the renderer does not read.

RecordApprovalsProps is not exported. Its accepted key set is empty, like the emptyProps rows, so there is no author-state type to publish. The row is reached as ComponentPropsMap['record:approvals']. The API surface is unchanged (check:api-surface green, no api-surface/ diff).

The false sentence, and the vocabulary claims

Pins

  • spec (component-record-approvals-attachments-22537.test.ts):
    • each type is an enum member with a row, known through the declaration rather than the ledger;
    • near misspellings stay unknown;
    • each row declares no key, admits {} at the row, node and page doors, and refuses an invented prop by name;
    • a node-level key inside properties gets the node prescription;
    • approvals and currentUserId are refused with their prescriptions;
    • Studio's seeded page parses;
    • both carry a print-refusal reason.
  • lint (validate-record-approvals-attachments-22537.test.ts):
    • Studio's seeded page, alone and with an authored Approvals tab, draws no component-type-unknown and no props finding, and reaches a zero-error os validate verdict;
    • control: the same seed with record:aprovals is refused at the exact path;
    • record:attachment and record:aprovals offer the declared spelling;
    • an invented prop on either node is a component-props-unknown-key warning naming the key;
    • the two host-channel keys carry their prescription.

Verification, at e492d331d4 (this branch's head, after one merge of origin/main at 96e4be4829)

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:

Package Test Typecheck
@objectstack/spec 639 files, 19033 passed, 1 todo tsc plus check:test-typecheck OK
@objectstack/lint 134 files, 6109 passed tsc plus check:test-typecheck OK
@objectstack/mcp canonical-expression-envelopes.test.ts 11 passed OK
@objectstack/platform-objects pages/canonical-expression-envelopes.test.ts 20 passed OK
@objectstack/cloud-connection canonical-expression-envelopes.test.ts 16 passed OK, after building its dependency closure

Those are the packages that read PageComponentType or ComponentPropsMap. The three consumer packages reach them only through @objectstack/lint's envelope audit and a Page type import.

Generated artifacts. pnpm --filter @objectstack/spec check:generated: "All 15 generated artifacts are up to date". The --fix round regenerated only page.mdx and 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".

  • NOT MEASURED: check:dual-build-cjs-loads. Reason: the dispatch forbids the whole-workspace build it loads.
  • 34 of the gates first ran while this seat's own cloud-connection closure build was rewriting dist/. 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-test rows 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.

  • ① The population, read from eslint.config.mjs: every **/*.{ts,…} file outside NEVER_LINTED, and the four touched TypeScript files are inside it.
  • ② --format json reports 4 files, 0 errors and 0 warnings.
  • ③ The config never enables type-aware linting (no parserOptions.project, stated in its own header). So this diff cannot move the verdict on any file it does not touch.

Not measured locally:

  • the os binary itself (its 60-package closure was not built; its verdict function was measured instead);
  • the CI-only jobs (Test Core shards, Dogfood, Temporal Conformance, Build Core);
  • the workspace-wide typecheck lane.

Acceptance notes


Generated by Claude Code

…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>
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 5 documentable anchor(s).

2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/protocol/objectui/layout-dsl.mdx (via ComponentPropsMap (symbol, a top-level const object))
  • content/docs/ui/pages.mdx (via PageComponentType (symbol, a top-level const object))

⛔ 5 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17/17-1.mdx (via ComponentPropsMap (symbol, a top-level const object))
  • content/docs/releases/v17/17-3.mdx (via ComponentPropsMap (symbol, a top-level const object))
  • content/docs/releases/v17/17-4.mdx (via ComponentPropsMap (symbol, a top-level const object))
  • content/docs/releases/v17/17-5.mdx (via ComponentPropsMap (symbol, a top-level const object))
  • content/docs/releases/v17/17-7.mdx (via ComponentPropsMap (symbol, a top-level const object), PageComponentType (symbol, a top-level const object))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 3 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 139 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 99801d831faa07fa374cb21acc30a469fa6aa6e1 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from adfc0e4ee70c03af4dbb465572ebb0e139adc2da — the merge of head e492d331d4b16bb9a2bc2d909a1c9466bd6cb2cf into base 99801d831faa07fa374cb21acc30a469fa6aa6e1, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 99801d831faa07fa374cb21acc30a469fa6aa6e1 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: e492d331d4b16bb9a2bc2d909a1c9466bd6cb2cf
Local-runs: none

Head resolved from claude/issue-22537-record-approvals-attachments-rows at review time; it begins e492d331d4, the head the ACCEPT (6093169923) names. Net diff read against the merge-base with origin/main (96e4be4829, the one merge the branch carries): 7 files, +467 / −11, no governed path (GOVERNED_SURFACES: docs/adr/, .claude/, skills/, AGENTS.md, CLAUDE.md, docs/NORTH-STAR.md), under the 3,000-line human-merge threshold. Inputs: card #22537 body and all five comments, PR #22595 body, file list and diff, the check-runs on the head, and objectui at the .objectui-sha pin 47b1f0bb71 (read with git show, never checked out).

① Derived judgments

Every accept-set and public-surface change the diff implies, each judged against the renderer read points at the pin:

  1. PageComponentType gains record:approvals and record:attachments (page.zod.ts:203). RIGHT. Both are registered objectui renderers inside the record namespace at the pin (app-shell/src/views/record-approvals-renderer.tsx:80, record-attachments-renderer.tsx:69, both namespace: 'record', skipFallback: true), both are emitted by the platform's own synthesizer (plugin-detail/src/synth/buildDefaultPageSchema.ts:931 and :978), and the CLI's generated registry census lists both (cli/src/utils/known-schema-types.ts:388-389). Triage ruled rows, not a rename (6089894781); the diff does exactly that and touches nothing in objectui.

  2. ComponentPropsMap['record:attachments'] is emptyProps(...) (accepts {} only). RIGHT. The renderer discards its schema node (schema: _schema, :45) and draws from the record context, the auth user and the node-level className; it reads no schema.key at all. Measured key set is empty.

  3. ComponentPropsMap['record:approvals'] is a strict row with no accepted key that refuses approvals and currentUserId with a prescription. RIGHT, and the deliberate departure from the dispatch's working rule ("the row is the union the renderer reads") is the correct call. The renderer reads exactly two schema.keys: schema.approvals (:61) and schema.currentUserId (:71), and both are filled by the host alone: RecordDetailView.tsx:2491-2501 threads its live useRecordApprovals read and user?.id through buildDefaultPageSchema({ approvals: { node } }), which spreads node verbatim onto the component (buildDefaultPageSchema.ts:931). With no payload the renderer self-fetches (:64-68) and reads the signed-in user itself (:71). This is byte-for-byte the shape of the record:history row (component.zod.ts:2194-2207), which refuses the host's entries / loading with the same prescription form. Declaring the two keys would advertise a static approval history and a pinned "current user" the runtime does not honour as authored (Prime Directive chore: version packages #10, declared vs enforced; Add comprehensive test suite for Zod schema validation #12, contract-first). The refusal text matches the renderer's actual fallbacks. SchemaRenderer's properties hoist (react/src/SchemaRenderer.tsx:257-341) is what makes properties.approvals reach schema.approvals, so measuring properties against schema.key reads is the right method.

  4. RecordApprovalsProps is module-private; no new public export. RIGHT. A row with no accepted key has no author-state type to publish; it is reached as ComponentPropsMap['record:approvals'] like every emptyProps row. api-surface/, export-origins/ and declaration-map/ carry no diff, and Type Check · source gates (the job that runs check:generated --reconcile-only, check:authorable-surface, check:docs and check:react-blocks) is success on this head.

  5. PRINT_REFUSED_PAGE_COMPONENT_TYPES gains a reason for each type (page.zod.ts:919-920). RIGHT and owed, not scope creep: page-print.test.ts:186-190 requires every live vocabulary member to be printable or refused with a reason, and :216 pins the wording form. Both reasons are correct in substance (a viewer-dependent remind control; upload/download/delete controls and files that never reach paper). Inside a print page each type is now refused with its own reason instead of the generic one; nothing printable before is refused now.

  6. RECORD_CONTEXT_BLOCK_TAGS (react-blocks.ts:188) gains RecordApprovals and RecordAttachments by derivation, so react-block-needs-record-context (error) now refuses a kind: 'react' page naming either tag. A derived consequence, not an authored change; its semver reading is in ②. The derivation is the design ("a record component added to the spec is covered by the publish gate the day it lands"), so this is the contract working as written.

  7. KNOWN_COMPONENT_TYPES and KNOWN_COMPONENT_TYPE_CANDIDATES gain both, so component-type-unknown admits them and offers them to near-misspellings (record:attachment, record:aprovals); RESERVED_COMPONENT_TYPE_NAMESPACES is unchanged (record was already claimed). RIGHT; pinned at both doors.

  8. The save door is unchanged. RIGHT. validateComponentTypes and validateComponentProps are both registered CLI_ONLY (authoring-rules.ts:1203, :1219), and PageComponent.properties stays an open bag at the parse, so PUT /api/v1/meta/page/:name admits the same pages before and after. The one door this change opens is os validate / os build / os lint, where the page Studio's page create seeds (anchors.ts:196-216 calls buildDefaultPageSchema(objectDef) with no options, so an enable.files object's Attachments tab holds a bare { type: 'record:attachments' }) was refused with component-type-unknown on main and passes on the branch. Studio's seed never emits record:approvals (only options.approvals does, which only RecordDetailView passes at runtime); the PR body says so, correctly.

  9. The authored host-channel keys at the lint door are a warning, not an error, as the lint pin asserts (component-props-unknown-key, severity: 'warning', with the prescription in message or hint). The changeset's "refused" is exact at the row (the Zod unrecognized_keys issue) and advisory at the lint door; its next clause names the rule, and the spec(ui) v18: declare the approval decision panel as a page component type with a strict ComponentPropsMap row (objectui#12045 B1, ruling 乙) #22472 changeset used the identical wording. Noted, not a finding.

  10. Generated artifacts. content/docs/references/ui/page.mdx (enum list) and the strictness-ledger counts (ui/ sites 209 to 210, component.zod.ts 78 to 79: one new strictObject( call site; emptyProps(...) is a helper call and is not a site) are regenerated in the diff. No other tracked artifact names the spec(ui) v18: declare the approval decision panel as a page component type with a strict ComponentPropsMap row (objectui#12045 B1, ruling 乙) #22472 precedent type, so none is owed. RIGHT.

  11. The false sentence on the record:line_items row is corrected, and the emptyProps docblock now lists record:attachments while naming that record:approvals has its own row. component-type-vocabulary.ts is correctly left alone: "EMPTY since finding(showcase): project-detail's record:line_items block keys its five columns as field, while the line-items grid binds name — the Tasks grid renders empty cells #21142" describes the ledger and stays true (pinned: STRING_ARM_REGISTERED_TYPES does not contain either type), and the map-is-a-superset-of-the-enum sentence is unmoved because both types are enum members.

  12. Pins. The spec file pins the two members, the two rows at the row / node / page doors, the misspellings, the node-prescription for a node-level key inside properties, the two host-channel refusals, Studio's seeded page, and the print classification. The lint file pins the seeded page (alone and with an authored Approvals tab) at validateComponentTypes, validateComponentProps and the runAuthoringRules('validate', ...) verdict, with a misspelled-type control at the exact path, the spelling suggestions, invented-prop warnings on both nodes, and the two host-channel prescriptions. Both halves per door are present. No consumer outside packages/spec and packages/lint reads PageComponentType.options, ComponentPropsMap keys or RECORD_CONTEXT_BLOCK_TAGS at the head, and no metadata under examples/, platform-objects or plugins carries either type.

Nothing in the diff is judged wrong.

② Semver level

  • @objectstack/spec: minor, in pre mode (.changeset/pre.json, tag next). RIGHT. The diff publishes two new enum members, two new map rows and two print-refusal reasons; it removes and renames nothing, adds no export, and leaves api-surface/ unchanged. A widening takes at least minor; minor is that floor. The body states what each type is, what the rows accept, the misspelling refusal, the print refusal and the derived consumer consequence, matching the spec(ui) v18: declare the approval decision panel as a page component type with a strict ComponentPropsMap row (objectui#12045 B1, ruling 乙) #22472 precedent entry in shape and detail.
  • @objectstack/lint: no changeset, correctly. Its only change is a test file; its runtime verdicts move because it reads the spec's map, the same as spec(ui) v18: declare the approval decision panel as a page component type with a strict ComponentPropsMap row (objectui#12045 B1, ruling 乙) #22472, which also carried none.
  • Clause-②: yes (widening) on its own line in the changeset, the PR body and the claim. The live question is the derived refusal in ①.6: does react-block-needs-record-context now refuse anything an author could successfully write before? Measured at the pin: the react scope is built by buildComponentScope() (components/src/renderers/layout/react-page.tsx:87-96) from ComponentRegistry.getPublicConfigs(), which emits the curated PUBLIC_BLOCKS list plus bare registrations stamped tier: 'public' (core/src/registry/Registry.ts:997-1026). public-blocks.ts lists eleven record:* members and neither of these two; both renderers register namespaced (record:approvals, record:attachments, skipFallback: true) with no tier, so neither key ever entered the scope. A kind: 'react' page naming the RecordApprovals or RecordAttachments tag could be written and passed this rule on main, but the tag resolved to no component at render, so no such page ever worked. The repo carries zero authored occurrences of either tag at the head. No working page is refused, so there is no author to migrate and no ADR-0087 disposition to declare: the arm is (widening), not (narrowing), exactly as spec(ui) v18: declare the approval decision panel as a page component type with a strict ComponentPropsMap row (objectui#12045 B1, ruling 乙) #22472 took the RecordApprovalDecision consequence. Confirmed.
  • One nit, not blocking: the changeset spells the two tags with literal angle brackets inside backticks. A CommonMark code span keeps them and changeset version copies the body verbatim into CHANGELOG.md, so the text survives; the precedent avoided the spelling.

③ Boundary flags

Every dev deviation and every open_questions entry from the report (6093147861), with the seat's ACCEPT (6093169923) read as a second input, not as the verdict:

  • Deviation 1 / open question 1 (host-channel keys: refuse with a prescription, or declare). Answered: refuse (option A). Judged in ①.3 on the renderer and the record:history precedent; the four-axis reading in the report holds, and contract-first forbids blessing a static approval history or a pinned submitter. Confirmed.
  • Open question 2 (the Clause-② arm). Answered: yes (widening) stands, by measurement of the react scope at the pin (②). Confirmed.
  • Open question 3 (re-grade to p1). The grade is the seat's; the contract review records only the mechanics: both component rules are CLI_ONLY (①.8), so the save door admits the platform's own page on main, and the os validate refusal is the one this PR removes. Nothing to escalate.
  • Deviation 2 (PRINT_REFUSED entries beyond the claim's file-surface line). Same file, forced by page-print.test.ts; ①.5. Accepted.
  • Deviation 3 (RecordApprovalsProps not exported). ①.4. Accepted.
  • Deviation 4 (model-free commit trailers). AGENTS.md's trailer rule; not a contract question. Noted.
  • Deviations 5 and 6 (contaminated first-pass gate runs, re-run on a quiet tree; the cloud-connection closure build). Disclosed; the check-runs on the head are the gate verdicts here, not the local re-runs. Noted.
  • out_of_scope_findings (the KNOWN_COMPONENT_TYPES docblock's illustrative list omits the finding(spec): objectui's curated action:button/group/menu/icon and element:definition-list/element:repeater blocks have no ComponentPropsMap row, so objectui#10872 cannot arm them by reference #20371 rows). The seat dropped it; agreed: the list is illustrative and not false.
  • Concurrency. PR feat(spec)!: an element binds data through dataSource only — retire the element-layer flat binding keys and object-grid.defaultFilters (#11509) #22421 ([Direction · v18] Retire the flat object/filter data-binding family — dataSource as the single data-binding door (re-anchor of the deleted #6590 tracker) #11509) also edits component.zod.ts. GitHub reports this head mergeable: true against main at 99801d831f (mergeable_state: blocked is the draft plus the pending checks). The later of the two to land resolves it, and the queue rebuilds the merge; nothing for this record.
  • Check-runs on the head at the time of this record. Required contexts: Lint & Repo Gates success, TypeScript Type Check success (all four Type Check · members success), Build Core success, Dogfood Regression Gate success (all shards), Temporal Conformance (live PG + MySQL) success, Governed Surface Queue Guard success; Test Core shards 2/6 and 6/6 success, shards 1/6, 3/6, 4/6 and 5/6 still in_progress after a few minutes' wait. Also green: Check Changeset, Spec property liveness, Dogfood Verify CLI, the three PR-context guards. Console Pin Gate skipped by its path filter, which is right for a diff that removes and renames nothing the pinned sibling imports. The owning seat reads Test Core to success before enqueueing; this PASS is on the diff and does not stand in for that read.

Implemented-by: claude/issue-22537-record-approvals-attachments-rows
Reviewed-by: session_01KNKBCRDJCu5tGy3TEbvtrF

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Landing pre-checks at e492d331d4, by the owning seat

domain:spec seat 3 (#18883) · zhuangjianguo · session session_01KNKBCRDJCu5tGy3TEbvtrF · 2026-10-10T03:32Z · holder of claim 6092331322.

  • The review: contract review PASS 6093256830 names this head. It confirms the Clause-②: yes (widening) arm on measurement: the two JSX tags never resolved in objectui's react scope, so no working page is refused. Nothing it records blocks.
  • CI: 34 success, 5 skipped, with every required context green. check-expected-skips --pr 22595 reads "OK — 5 skipped check-run(s), every one in the roster".
  • Governed: check-governed-merges --pr objectstack-ai/objectstack#22595 reads NOT governed; 478 changed lines.
  • Closing keywords: the body carries Fixes #22537 alone, and no commit message carries one.
  • main drift since the merge base 96e4be4829: main moved none of the PR's 7 paths, content/docs/references/ui/page.mdx (a merge=os-regen path) included. So landing step A owes no sync. GitHub reports mergeable: true, mergeable_state: clean. PR feat(spec)!: an element binds data through dataSource only — retire the element-layer flat binding keys and object-grid.defaultFilters (#11509) #22421 also edits component.zod.ts; the later of the two to land resolves the conflict.

needs:contract-review comes off; pr_ready and automerge_enable follow.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 10, 2026 03:34
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 10, 2026 03:34
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 10, 2026
Merged via the queue into main with commit dab6bf4 Oct 10, 2026
44 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-22537-record-approvals-attachments-rows branch October 10, 2026 04:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:ui size/m tests tooling

Projects

None yet

2 participants