You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
approvals face 3 of 5 (ruling C on objectui#12438): the served sys_approval_request page draws the target-record reference's states (link, refused, tombstone, unresolvable) as the inbox side sheet does #12489
Ruling line first recorded 2026-10-15T22:40Z; restamped by the domain:ui seat 1 body edits: the blocker re-point at takeover 6213174414, then the unlock at the step-4 claim 6214527805 (objectui#12626 closed; the blocker line is gone).
Filing gate ④: a cross-layer coordination node, filed as ruling 6190549995 on objectui#12438 directs (letter C, the maintainer's 「C (推荐)」, director seat summon #42, items 2–4). It is face 3 of at most 5, third in the ruling's order by pull: "record-reference states (objectui#11878)".
Parent: #2763 (B1 of the epic). The closing card objectui#12484 waits on this one.
Serial after face 2 (objectui#12483): the faces edit the same served page, plugin-approvals' sys-approval-request.page.ts, and the ruling orders them by pull.
Reader who acts: the domain:ui seat 1 (the epic's seat, seat post #5560). Once face 2 lands, it dispatches step 1 here, the fit check.
Dedupe (MCP search_issues, scope objectstack-ai/objectui, all states, read 2026-10-15T12:46Z): "approval request page target record link tombstone deleted unresolvable reference withdrawn state" → 1 result, #11878, closed: the sheet's refused-link fix. That one is not this card.
The face
The side sheet shows it today, pinned. The request's target-record reference has four states:
a link when the approver may read the record;
no link when the read is refused (objectui#11878);
the tombstone text when the record is gone (objectui#7108);
a distinct unresolvable-reference text when the reference cannot be resolved.
The pins are ApprovalsInboxPage.recordLink.test.tsx, ApprovalsInboxPage.recordLinkRefused-11878.test.tsx, ApprovalsInboxPage.tombstone.test.tsx and ApprovalsInboxPage.unresolvableReference.test.tsx, all under apps/console/src/pages/system/. The sheet's RecordCell (ApprovalsInboxPage.tsx:809) draws them.
The served page: its details grid draws record_id / object_name as a card of the target record (the referenceVia pointer pair, sys-approval-request.page.ts docblock at objectstack 57994c311f). Measure which of the four states that card draws today.
Steps (ADR-0060 rule 2; the ruling's item 2, "fit before build")
Fit check, objectui. The ruling names the fit: the states "go to RecordPreviewCard (A2) only if the spec row reverses its 「the caller renders them」 rule, otherwise a thin generic record-reference-state block". Read RecordPreviewCard's docblock and its spec row, measure the served card's current states, and report which route applies.
Spec row (objectstack domain:spec), if the route needs one: either the reversed rule on the preview card's row, or the new block's declared type.
Served page edit (objectstack plugin-approvals, domain:services), if the block is placed explicitly.
objectui block or wiring, pinned.
Steps 2 and 3 are filed in objectstack, chained with Blocked-by:, only when step 1 says they are needed.
Acceptance
On the served page, the target-record reference draws all four states as the sheet's pins assert. A refused read never yields a link.
⛔ The side sheet is not touched (ADR-0060 rule 3).
Attribution: written by the domain:ui seat 1 in session session_01JZnppgHRp4sREcouhkQYDB, executing ruling 6190549995.
Blocked-by: #12483 is met. objectui#12483 closed completed with PR #12533, merged as main521817ce9d (landing note 6195562024). This card's premise readings still hold on that merge: PR #12533 added the RecordSnapshotSummary part in packages/app-shell/src/views/approval-decision/, which this card builds on or alongside. The card returns to pm:queue. This seat claimed its predecessor and dispatches it when a slot frees.
Re-check for the landing seat · domain:ui seat 1 · session_01JZnppgHRp4sREcouhkQYDB · 2026-10-15T17:39Z
When this face lands, the browser reading on the served sys_approval_request_detail page also confirms one more thing: the highlights strip's Submitter chip shows the submitter's name, and no 24-or-more-character sys_user id.
objectui#12498 was closed as not planned on a test-tree measurement, which found the console names the submitter through its root SchemaRendererProvider (6198309422). That claim was not measured live. A bare id in this reading reopens #12498.
The dev brief should also mount SchemaRendererProvider dataSource={adapter} around any served-page pin harness it adds or edits under apps/console/src/services/approvalRequestsDataSource.*.test.tsx, as ConsoleShell.tsx:306 does. Today those harnesses omit it, and that is how #12498 was filed.
Claim: PM loop round 1 (priority:p3; face 3 of ruling 6190549995 on objectui#12438, the maintainer's 「C (推荐)」)
Session: session_01JZnppgHRp4sREcouhkQYDB
Account: sunhaolin
Branch: claude/issue-12489-reference-states
Worktree: objectui-issue-12489
Domain: domain:ui
Seat: domain:ui#1
File surface: step 1 (the fit check) is a measurement. A build follows only on an objectui-only route:
packages/app-shell/src/views/record-preview/RecordPreviewCard.tsx, or whatever block draws the served page's record_id / object_namereferenceVia pair, plus its wiring;
the served-page pins under apps/console/src/services/approvalRequestsDataSource.*.test.tsx, each mounting the root SchemaRendererProvider as ConsoleShell.tsx:306 does (see 6198410345);
the tests beside the block;
one .changeset/12489-*.md.
Outside the surface:
the side sheet ApprovalsInboxPage.tsx (ADR-0060 rule 3);
objectstack's spec row and plugin-approvals page (the card's steps 2–3, which the seat files);
any new node type, prop, export or schema read.
A route that needs any of those stops before building, and reports the exact spec row or page edit (stop on breach; explain in the report).
Container & model: M, mode:subagent, model: opus (default tier; dispatch-gates --tier --repo objectstack-ai/objectui names no path-derived mandate)
Clause-②: no. Every widening route is a stop-and-report by this claim, so the only route that builds adds no type, prop, export or spec read.
Responsibility: n/a — not a defect card
Thread-read: 6198410345
Serial constraints cleared: read at objectui main518a8e3084.
Face 2 (objectui#12483) has landed (521817ce9d), which this card's Blocked-by: #12483 waited on; the card was unlocked at 6195611978.
No in-flight claim or open PR names record-preview/** or the request-page pins.
objectui#12534 (face 2's follow-up, queued, not dispatched) edits approval-decision/snapshotSummaryPlan.ts, a different block on the same served page.
{
"issue": 12489,
"status": "done",
"branch": "claude/issue-12489-reference-states",
"pr": null,
"session": "session_01JZnppgHRp4sREcouhkQYDB (subagent mode: the parent's id, as the claim and the harness stamp it)",
"premise_still_valid": true,
"summary": "Step 1, the fit check, is measured, and nothing was built. The branch is pushed empty as the claim marker, and there is no PR. The served page draws only two of the sheet's four states at its target reference. One missing state, the platform-asserted tombstone, needs a spec declaration, so no objectui-only route draws all four. The ruling then forbids a build. Today the record_id referenceVia pair is drawn in the Details tab by RecordPreviewCard, through the pointer-card field renderer. Readable: the target card, with no link (gap). Refused and unresolvable: the cause-free 'This record cannot be opened', with no link. That passes the acceptance line, and the unresolvable copy is the sheet's own i18n key. Platform-asserted deletion: the same cause-free text, and 'Related record deleted' appears nowhere on the page (gap). The applicable route is the RecordPreviewCard one. Its 'spec row' is a new key beside FieldSchema.referenceVia, because the card has no spec row of its own. The card's 'caller's to render' rule stays true: the caller is the pointer renderer, which draws the tombstone from the declared assertion. That needs an edit of sys-approval-request.object.ts, and no edit of sys-approval-request.page.ts. The missing link needs no spec change and rides step 4.",
"gap_table": [
"readable · sheet: a link to the app-scoped record path, labelled with the snapshot title (recordLink pin) · served page today: data-record-preview=readable, card text 'InvoiceINV-1010Invoice Amount12,500.00', 0 anchors in the card and 0 links to the target anywhere on the page · GAP: no link (objectui-only to close)",
"refused, the target read rejects 403 PERMISSION_DENIED · sheet: no link, keeps the reference text, and never the cause-free label (recordLinkRefused-11878 pin) · served page today: data-record-preview=unreadable, 'This record cannot be opened', 0 links · HOLDS on the acceptance line 'a refused read never yields a link'. The sheet's kept-title assertion is not carried: the card fuses every unreadable answer into one cause-free sentence (its header's rule, which cites objectui#11878 itself) and holds no snapshot title. That is a boundary for the seat, not a gap this ruling closes",
"platform-asserted deletion: status cancelled + cancel_reason record_deleted on the row, and the target read answers 404 · sheet: the tombstone 'Related record deleted' (the platform's cancel_reason option label), no link, no bare id (tombstone pin) · served page today: data-record-preview=unreadable, 'This record cannot be opened'. The tombstone count on the page is 0. The strip shows Status 'Cancelled'. The Details tab draws only 'Record ID' (the card), so cancel_reason is drawn nowhere · GAP: tombstone (needs a declaration; see route)",
"unresolvable: terminal, no record_title, and the target read resolves with no record · sheet: 'This record cannot be opened', no link, no id (unresolvableReference pin) · served page today: data-record-preview=unreadable, the same string from the same key approvalsInbox.recordUnresolvable, 0 links, and no id inside the card · HOLDS at the reference. The page H1 shows the raw record id in all four states (finding below)"
],
"route": {
"applies": "RecordPreviewCard (A2) route, conditional on step 2's spec row. The pair reaches the card only through DetailSection: resolveRecordPointer(field, row) returns objectName + recordId, and getCellRenderer(RECORD_POINTER_CARD_TYPE) receives that pair as value, with no row. CellRendererProps carries no row, by design. So the host row's deletion assertion reaches the pointer only if a declaration names it. Without one, objectui would have to read sys_approval_request's status / cancel_reason inside a generic resolver: an approvals-only special case in a generic cell (ADR-0060 rule 2, AGENTS.md #0.1).",
"step2_spec_row": "objectstack packages/spec/src/data/field.zod.ts, a new FieldSchema key directly after referenceVia: referenceDeletedWhen: z.object({ field: z.string().regex(/^[a-z_][a-z0-9_]*$/), value: z.string() }).strict().optional().describe('Pointer fields only (requires `referenceVia`): the sibling `select` field on the same object, and one of its option values, that the platform writes on a row whose pointed-at record was deleted, e.g. `{ field: \\'cancel_reason\\', value: \\'record_deleted\\' }` on `sys_approval_request.record_id`. A row holding that value draws the pointer as a tombstone in that option\\'s own label and does not read the target. Nothing else asserts a deletion: a target read that fails or answers no record stays cause-free, because the platform answers a deleted record and one outside the reader\\'s row set alike.'). It needs two refusals. FieldSchema's superRefine, beside the referenceVia checks, refuses referenceDeletedWhen without referenceVia. object.zod.ts, beside the existing referenceVia sibling check in ObjectSchema.create, refuses a field that is not a declared `select` on the same object, and a value that is not one of its option values. Boundary: the sheet also requires status cancelled. The contract declares cancel_reason 'present only on rows whose status is cancelled', so one condition carries it.",
"step3_objectstack_edit": "plugin-approvals sys-approval-request.object.ts, field record_id: add referenceDeletedWhen: { field: 'cancel_reason', value: 'record_deleted' } after referenceVia: 'object_name'. sys-approval-request.page.ts: NO edit. Its Details tab already draws record_id as the pointer card (measured: the tab draws only that).",
"step4_objectui": "After step 2 lands. Clause-② yes, because the published RecordPointer type of @object-ui/fields widens. resolveRecordPointer reads referenceDeletedWhen and returns the assertion with the pair. DetailSection passes the object field's referenceDeletedWhen beside referenceVia. RecordPointerCardRenderer, the card's caller, draws the tombstone in the host object's option label: fieldOptionLabel over the host object, the declared field and the declared value, with the declared option label as the default. It does not mount the card or read the target. RecordPreviewCard gets the link in its readable state only, to the app-scoped record path, so a refused or unresolvable read never yields one. The link is objectui-only, and the seat may dispatch it ahead as its own card. Pins: one per state on the real RecordDetailView over the routed approvals source, under the root SchemaRendererProvider. CONTROL: a refused read has no link. CONTROL: an unreadable row the platform did not flag gets no tombstone.",
"fallback_if_step2_refuses": "A thin generic block, as the ruling orders. Declare record:reference in component.zod.ts ComponentPropsMap as RecordReferenceProps = strictObject({ field: z.string() (a referenceVia pointer field of the bound object), deletedWhen: z.object({ field, value }).strict().optional() (same semantics as above) }), and list it in page.zod.ts's record component types. Page edit in sys-approval-request.page.ts, Details tab: children: [{ type: 'record:details' }] becomes children: [{ type: 'record:reference', properties: { field: 'record_id', deletedWhen: { field: 'cancel_reason', value: 'record_deleted' } } }]. The tab measurably draws only record_id today, so the swap drops nothing here. Cost: a block type that redraws what the details grid already draws for every referenceVia field, and an assertion stated per page rather than on the field.",
"recommendation": "The RecordPreviewCard route with referenceDeletedWhen. Business need: both routes serve the same ruled pull. The closing card objectui#12484 retires the tombstone pin, which came from user report objectui#7108. Today only sys_approval_request asserts a deletion, so each route has one real consumer. Long-term: the fact sits on the field that owns the pair (ADR-0052 §5), so every surface that draws the pair draws the tombstone, with no page node. The block route makes it per page. AI-proofing: one declaration on the object, refused loudly at parse if it names a non-select field or an undeclared option. The block route needs each page author to place and configure a node, and a page that keeps record:details draws the pair twice. Startup focus: one optional key with one consumer, against a whole block type plus a page edit.",
"considered_and_rejected": [
"Draw the tombstone inside record:approval_decision, the panel faces 1 and 2 grew. It is objectui-only, but it is not one of the ruling's two destinations. The details card would keep saying 'cannot be opened' about the same reference beside it.",
"Read status / cancel_reason inside the generic pointer renderer or resolveRecordPointer. That is an approvals-only rule in a generic cell, and a consumer-side contract.",
"A record:alert with a visible predicate on the page. It is an existing declared block and needs only a page edit. But it is a banner, not the reference, and its copy would be authored inline, forking the platform's cancel_reason option label (deadRecordReference.ts refuses that)."
]
},
"mechanism_assumptions": {
"A1": "HOLDS on the component, and the state readings above falsify part of it. The pair is drawn by RecordPreviewCard through record-pointer-card-renderer (RECORD_POINTER_CARD_TYPE), inside the Details tab's record:details. It draws two of the four sheet states as the pins assert (refused: no link; unresolvable: the same text). It draws no link when readable, and no tombstone.",
"A2": "HOLDS, with the key corrected. The sheet's tombstone reads status === 'cancelled' AND cancel_reason === 'record_deleted' (isDeletedRecordReference); there is no record_deleted key. objectstack rowFromRequest maps cancel_reason: row.cancel_reason ?? undefined beside status on every read, so the key is omitted on rows that are not cancelled. enrichRows adds record_title. objectui's routed get (routeApprovalRecordReads, GET /api/v1/approvals/requests/ID with no query string) hands the row through whole. Measured: status 'cancelled' (string) and cancel_reason 'record_deleted' (string), with keys cancel_reason, id, object_name, process_name, record_id, record_title, status.",
"A3": "Mounted: SchemaRendererProvider dataSource={host} at the root, as ConsoleShell mounts it. The pointer card reads useAdapter() (AdapterCtx), not the SchemaRendererContext, so the provider does not change these four readings.",
"A4": "Matched by assertion: link presence and href, the absence of a link, the tombstone text, the cause-free text, and the absence of the bare id. Markup was not compared."
},
"tests": "No diff, so no suite is owed. Measurements came from a temporary probe, apps/console/src/services/zz-probe-12489.test.tsx: written into the worktree, run, deleted, and never committed. The fixture was the served page copied whole from objectstack origin/main 69351e985 (PageSchema.safeParse: true), on the real RecordDetailView under ApprovalRecordReads with the root SchemaRendererProvider. The request definition carried the real status and cancel_reason select options from @objectstack/spec/contracts, and each case set the host's invoice findOne answer. There were three runs through os-verify-lock with OS_VERIFY_LOCK_SLOT=dev-12489: 'VERDICT command-exit 0' each, with Tests 6 passed (6), 6 passed (6) and 7 passed (7). The runs grew the readings, and the state readings are identical across all three. Raw readings are quoted in gap_table and mechanism_assumptions. H1 in each case: 'invoice_approval · ' followed by the case's record id. No ablation: no tested behaviour changed.",
"gates": "Not applicable. The branch carries no commit beyond its base 518a8e3084 (worktree cut from origin/main at dispatch), so no gate, changeset gate, type-check or eslint run is owed, and CI has no PR to run. dispatch-gates was not run: it is objectstack's tool, and this branch has no diff. Drift check: objectui main had moved to baab2d6ed0, read through a named ref and then deleted. It changes ApprovalsInboxPage.tsx's page header (objectui#12526) only, and touches none of the reference-state code or the files measured here.",
"browser_reading": "NOT MEASURED live. Reason: this container has no objectstack backend (the objectstack checkout has no node_modules and no packages/spec/dist). There is no PR, so no PR section exists for the landing seat. The browser reading that 6198410345 asks for, the highlights strip's Submitter chip showing a name and no id of 24 or more characters, carries over to the PR of step 4 or of the link card.",
"mcp_calls": "7, all reads: issue_read x7 (12489 get + get_comments, 12438 get + get_comments, 12483 get_comments, 12498 get_comments, 12029 get). There were no MCP GitHub writes. ToolSearch x1 loaded tool schemas.",
"api_writes": "1: this report, POST /repos/objectstack-ai/objectui/issues/12489/comments, through the fleet-write relay via scripts/pm/post-stamped.mjs. Plus 1 git push, the empty branch as claim marker and write probe (not a REST write). No pr_create, because nothing was built, per the ruling. No label write.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: objectui#12484 (the closing card, same family) · noted, not filed. On the served page the H1 is display_title, plugin-approvals' formula process_name + ' · ' + record_id, so the raw record id is on screen in all four states (measured: 'invoice_approval · ' followed by the record id). The sheet's tombstone and unresolvableReference pins assert that the bare id never shows. The replacement PR that retires those pins against the page inherits this, and its producer is objectstack sys-approval-request.object.ts. Dedupe words: approval request page title raw record id · display_title record_id · served page H1 identifier",
"carrier: domain:ui seat 1 (already carried by 6198410345) · noted, not filed: the served-page pin harnesses still mount without the root SchemaRendererProvider. None was edited here, because nothing was built."
],
"deviations": [
"No PR and no build, per the ruling's 'otherwise do not build'. The branch claude/issue-12489-reference-states is pushed empty at 518a8e3084, which is BASE.",
"The brief asks for 'the exact sys-approval-request.page.ts edit'. The recommended route needs none: its objectstack edit is sys-approval-request.object.ts. A page edit is given only for the fallback route.",
"A temporary probe file was written into the worktree, run 3 times under the lock, and deleted. It was never committed. git status --short was empty afterwards, and git diff HEAD is empty.",
"git fetch origin main ran in the shared objectstack checkout, for read-only reference. It moved that checkout's shared origin/main (6074eafb6 to 69351e985). Reads used git show origin/main and changed no file. The prescribed worktree recipe's git fetch ran in the shared objectui checkout. The drift check fetched into a named ref (refs/probe12489/main), which was deleted after use."
],
"files_changed": [],
"line_budget": "not applicable (no skills/** surface)",
"cleanup": "The worktree ../objectui-issue-12489 is removed after this report posts: node_modules first, then git worktree remove without --force."
}
裁决 6190549995 item 2 原文:the states "go to RecordPreviewCard (A2) only if the spec row reverses its 「the caller renders them」 rule, otherwise a thin generic record-reference-state block"。
RecordPreviewCard.tsx:49–:66 的 docblock:一次被删除的断言 "is the caller's to render, not this card's"。卡片不能区分「已删」与「看不到」,否则就成了存在性预言机(objectui#11878)。
Ruling: batch #352 item 4 · letter A · maintainer 「A (推荐)」 2026-10-15T22:38Z
Director seat, summon #42, session_01AKVFxqmRLqktJspVXwPE9A, on the domain:ui seat 1 presentation 6199104044, the dev's fit-check 6198994393 and this seat's delta 6203439151; the maintainer's answer takes the recommendation (batch #352 presented in this seat's session, answered with the option 「A (推荐)」). This settles face 3 of ruling 6190549995 (objectui#12438, letter C) item 2: the fit check found no spec row on RecordPreviewCard, so the fact goes on the field that owns the pointer pair.
objectui (this card's step 4, dispatched when the spec is installable at objectui's pin): the record pointer renderer draws the tombstone from the option's own label when the predicate holds, reads nothing and links nothing; RecordPreviewCard's 「the caller renders it」 rule stays, the pointer renderer being the caller; Clause-②: yes (the RecordPointer type widens); pins for all four states against the sheet's.
The H1 display_title exposure stays with objectui#12484. Not taken: B (a per-page block) and C (no declaration).
Workload of the letter:domain:spec 1 PR (S–M) + domain:services 1 PR (S) + a spec release + domain:ui 1 PR (M).
Execution:needs-user-decision → pm:blocked in this act, Blocked-by: objectstack-ai/objectstack#23736 in the body under the Ruled: line (the satisfied Blocked-by: #12483 line leaves); priority:p3, area:workflow stand; the assignee (the seat's claim) stays.
This card's Blocked-by: objectstack-ai/objectstack#23736 is satisfied. #23736 closed at 2026-10-16T04:03Z.
The spec key is installable.referenceDeletedWhen (#23735, PR #23759 8776119bdc) is in @objectstack/spec@18.0.0-next.2 (built from e5136ff10f, published 2026-10-16T03:11Z).
The card is assigned, so triage changes no label. Its owner dispatches step 4 (the record pointer renderer's tombstone) when ready.
Claim: PM loop round 3 · takeover of claim 6198455121 (the claimant's session ran out of tokens; the card is parked, not mid-build)
Release: claim 6198455121 · session session_01JZnppgHRp4sREcouhkQYDB · account sunhaolin · reason: the claimant is unreachable (token exhaustion; step 1 finished, nothing of step 4 started) · destination: this claim, same branch name
Provenance: the maintainer · in session session_011K9g23W67dy2zwnyzaoB6o, the /pm-dispatch UI seat 1 summons, read 2026-10-16T06:35Z · verbatim: 「前任没有token了,他的任务开发了一半你要接着处理。」
Session: session_011K9g23W67dy2zwnyzaoB6o
Account: os-tesla
Branch: claude/issue-12489-reference-states
Worktree: objectui-issue-12489
Domain: domain:ui
Seat: domain:ui#1
File surface: none dispatched by this claim; the card stays pm:blocked. The step-1 surface of 6198455121 is spent (the fit check built nothing). Step 4, the record pointer renderer's tombstone on referenceDeletedWhen, gets its own claim with its own surface and its own Clause-② reading when the card unlocks.
Container & model: M, mode:subagent, model: opus (default tier, carried from 6198455121; the step-4 claim re-reads dispatch-gates --tier --repo objectstack-ai/objectui over its own paths)
Clause-②: no
Responsibility: n/a — not a defect card
Thread-read: 6208437697
Serial constraints cleared: none to clear, since this claim dispatches nothing; the step-4 claim reads them at its own main.
Handover record. Last pushed sha on the branch 518a8e3084, with zero commits beyond objectui main (it is the main the step-1 claim was read at; step 1 built nothing). Step 1 is done: the dev's fit check 6198994393, ruled at 6203737086 (letter A: the fact goes on the field that owns the pointer pair). Steps 2–3 are done in objectstack: objectstack-ai/objectstack#23735 (referenceDeletedWhen, merged as 8776119bdc, published in @objectstack/spec@18.0.0-next.2) and objectstack-ai/objectstack#23736 (closed 2026-10-16T04:03Z). No Resume-from: there is no work to resume, and the step-4 dev starts the branch from current origin/main.
Blocker re-pointed (the triage pointer 6208437697 is right that objectstack-ai/objectstack#23736 is satisfied). The key is installable, but objectui main's pnpm-lock.yaml still resolves @objectstack/spec@18.0.0-next.1 (read at ef71a09f9e). The move to 18.0.0-next.2 is objectui#12626 (pm:dispatched, domain:ui seat 4). So the body's blocker line now names that card, the card stays pm:blocked, and this seat dispatches step 4 when it closes.
Claim: PM loop round 3 · step 4 of this card (priority:p3; face 3 of ruling 6190549995, letter A of 6203737086), unlocked: objectui#12626 closed and mainf62b6ff4e1 resolves @objectstack/spec@18.0.0-next.2, which carries referenceDeletedWhen
Session: session_011K9g23W67dy2zwnyzaoB6o
Account: os-tesla
Branch: claude/issue-12489-reference-states
Worktree: objectui-issue-12489 (in the cloud session's own container)
Domain: domain:ui
Seat: domain:ui#1
File surface: the record pointer renderer's tombstone, as ruling A item 3 binds it.
packages/app-shell/src/views/record-preview/record-pointer-card-renderer.tsx (the tombstone face; RecordPreviewCard.tsx keeps its 「the caller renders it」 rule and is read, not changed);
packages/plugin-detail/src/DetailSection.tsx, the resolveRecordPointer call site only (passing what the predicate and the option's label need);
the served-page pins under apps/console/src/services/approvalRequestsDataSource.*.test.tsx, each mounting the root SchemaRendererProvider as ConsoleShell.tsx:306 does, and tests beside each file above;
changesets for the touched packages.
Outside the surface: the side sheet ApprovalsInboxPage.tsx and its four pins (read as the reference, not changed); any new i18n key (the tombstone text is the option's own label, per the ruling); display_title (objectui#12484). A route that needs any of these stops and reports (stop on breach; explain in the report).
Container & model: M, mode:cloud, model: opus (default tier; dispatch-gates --tier --repo objectstack-ai/objectui over the 6 planned paths: no path-derived mandate)
Clause-②: yes
Clause-② reason: ruling A item 3 says so: RecordPointer, exported from @object-ui/fields, widens, and resolveRecordPointer's field parameter widens to read referenceDeletedWhen. The seat's TIER_DEFAULT review comes before ready.
Responsibility: n/a — not a defect card
Thread-read: 6213174414
Serial constraints cleared: read at objectui mainf62b6ff4e1.
objectui#12626 (seat 4, the next.2 move this card waited on) landed as f62b6ff.
No open PR or in-flight claim names DetailSection.tsx, record-preview/ or the approvalRequestsDataSource.* pins. objectui#12490 (face 4, same served page) is pm:on-hold.
{
"issue": 12489,
"status": "done",
"branch": "claude/issue-12489-reference-states",
"pr": "https://github.com/objectstack-ai/objectui/pull/12665",
"session": "session_01RXFSkyCWZHTfnnf5iKrQFN (cloud dev run, dispatched by the domain:ui seat 1 session session_011K9g23W67dy2zwnyzaoB6o)",
"premise_still_valid": true,
"summary": "Ruling A item 3 is built on b78ab082. The resolveRecordPointer rule in @object-ui/fields now reads the spec's referenceDeletedWhen beside referenceVia. When the row's sibling select holds the declared option, the returned RecordPointer carries deletedLabel, which is that option's label from an optionsOf callback the call site supplies. The predicate lives only in fields. DetailSection passes the object field's referenceDeletedWhen and the sibling's options, translated with the same translateOptions call the grid already uses for select options (H3). The app-shell pointer renderer is the caller that RecordPreviewCard's rule names: on deletedLabel it draws the tombstone (data-record-pointer=deleted, wastebasket icon, the label) and does not mount the card, so nothing is read, nothing is linked and no id is shown. The default text face in fields draws the label instead of the id. H1, H2, H3 and H5 hold. H4 was measured on the served-page harness under the root SchemaRendererProvider: readable, deleted and unresolvable match the sheet's pins. The refused state differs from the sheet beyond the tombstone; this is reported, not fixed (see open_questions). A live request page was NOT MEASURED, because published @objectstack/spec 18.0.0-next.2 lacks the services declaration (objectstack PR #23798), so the served-page harness carries that declaration and serves as the evidence.",
"h_readings": {
"H1": "HOLDS. resolveRecordPointer(field, row, optionsOf) evaluates row[referenceDeletedWhen.field] === referenceDeletedWhen.value and looks up the option in optionsOf(field). DetailSection supplies only the declaration and the options; it does not evaluate the predicate. There is no new export: the helper types are module-local.",
"H2": "HOLDS. Under the real DetailSection: the adapter's findOne is called 0 times and MetadataCtx getItem 0 times, with 0 anchors under a host that builds record hrefs, no data-record-preview element, and no id in the tombstone. On the served page: 0 invoice findOne calls, 0 links to the record path, and the record id appears neither in the tombstone nor in the Details tabpanel. The default fields face draws the label, not the id.",
"H3": "HOLDS. The helper is useSafeFieldLabel().translateOptions, which resolves fieldOptions.OBJECT.FIELD.VALUE with the declared label as fallback, under the same key the sheet's fieldOptionLabel reads. A zh-CN bundle draws the tombstone as the localized label, both under the real DetailSection and on the served page. No new i18n key was added.",
"H4": "MEASURED on the served page (real RecordDetailView, ApprovalRecordReads, root SchemaRendererProvider, SERVED_PAGE copied whole). readable: one link to the app-scoped record path inside the card, named by the title; matches the recordLink pin. refused (403 PERMISSION_DENIED): card unreadable, text 'This record cannot be opened', no link, and the snapshot title INV-2020 absent from the page (probe: card text and the page-level includes check returned false). The sheet's recordLinkRefused-11878 pin keeps the title as plain text, so this DIFFERS beyond the tombstone and is reported only. deleted: tombstone 'Related record deleted', 0 reads, no link, no id at the reference. Matches the sheet's tombstone text, no link and no bare id, except that the sheet also moves record_title onto its meta line and the served page draws no record_title (reported). unresolvable: 'This record cannot be opened', no link, no id in the card; matches the unresolvableReference pin.",
"H5": "HOLDS. Tested cases: a field without the key; a sibling holding another option; no sibling value; a null sibling; no optionsOf. Each returns the pair exactly as before. On the served page, a cancelled row with no cancel_reason reads the target and draws the readable card, and a 404 on an unflagged row draws the cause-free card with no tombstone. Existing pins in the record-preview directory and the plugin-detail recordPointer-12045 suite stay green."
},
"tests": "Union at HEAD b78ab082: pnpm exec vitest run --maxWorkers=2 over packages/fields/, packages/plugin-detail/, packages/app-shell/src/views/record-preview/, every apps/console/src/services/approvalRequestsDataSource.*.test.ts(x), and the four sheet pins (recordLink, recordLinkRefused-11878, tombstone, unresolvableReference). Result: 'Test Files 552 passed | 2 skipped (554)', 'Tests 6937 passed | 21 skipped (6958)', 'VERDICT command-exit 0' through os-verify-lock (slot dev-12489-cloud). New pins: fields resolveRecordPointer.deleted-12489 (10 tests), app-shell record-pointer-card-renderer.tombstone-12489 (7), console approvalRequestsDataSource.servedPageReferenceStates-12489 (8). Ablation: four runs through objectstack's scripts/ablation-replace.mjs; each confirmed the mutation on disk (anchor 1 to 0, blob moved), and each restore checked blob == HEAD and an empty git diff HEAD. The root vitest config aliases @object-ui/* to src, so no dist leg applies. A1, predicate forced false (fields index.tsx): 9 failed / 17 passed of 26 across all 3 new files. A2, a findOne made anyway in the tombstone branch (app-shell renderer): 3 failed (the zero-read pin and both served deleted pins). A3, label taken from the raw option value: 8 failed (label pins plus the 'never the raw option value' pin). A4, extra for H3, the call site skips translateOptions: 2 failed (both localized pins). The H4 refused-title probe was a temporary assertion in the new test file, run once and restored from a byte copy; it was never committed.",
"gates": "Local, hand-derived from objectui package.json and ci.yml (dispatch-gates is objectstack's tool, so it was not run). Each exit code was captured before any pipe. (1) Closure build: pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell...' build, BUILD_EXIT=0; the app-shell dist completeness check reported 1114 emitted files verified. (2) type-check: @object-ui/fields=0, @object-ui/plugin-detail=0, @object-ui/app-shell=0. @object-ui/console was first 2: 23 errors, all from plugin-gantt, timeline, map, markdown and tree dists outside the closure, and none in a touched file. After building those five plugins it re-ran at 'VERDICT command-exit 0' for the build && type-check chain. (3) pnpm check:changeset-claims=0 ('No pending changeset names a file this change touches'); pnpm changeset:check=0; pnpm check:test-path-roots=0; pnpm check:spec-symbols=0; node scripts/check-changeset-presence.mjs=0 ('6 source file(s) of 4 released package(s) changed ... declares 3 changeset(s)'); node scripts/check-new-cross-file-line-citations.mjs=0 ('0 new citation(s)'). (4) eslint --no-inline-config on the 6 changed source and test files: exit 0, 0 errors and 157 warnings, all pre-existing rule classes (react-refresh/only-export-components on existing exports, react-hooks/set-state-in-effect in untouched code). CI check-runs are NOT MEASURED: there is no PR yet and the seat opens it.",
"mcp_calls": "2, all reads: issue_read get and get_comments on objectui#12489. ToolSearch loaded the tool schema once. No GitHub write tool was called.",
"api_writes": "0 REST writes. Two git pushes to claude/issue-12489-reference-states: the first was the write probe, a fast-forward of the stale tip 518a8e3 to origin/main a5bb7ac (518a8e3 was confirmed an ancestor after deepening the shallow clone); the second pushed b78ab082. No PR creation, issue comment, label write or relay call, per the cloud brief.",
"open_questions": [
{
"question": "The tombstone uses its own attribute, data-record-pointer=deleted, not a fifth data-record-preview value. Keep it?",
"options": ["A keep data-record-pointer=deleted: RecordPreviewCard's header defines exactly four states of its own and leaves deletion to the caller", "B reuse data-record-preview=deleted so one selector reads all five faces"],
"recommendation": "A, because the card never draws this state and its header says so; a shared attribute would read as the card claiming it."
},
{
"question": "On the served page the refused state shows the card's cause-free 'This record cannot be opened' and no snapshot title. The sheet's recordLinkRefused-11878 pin keeps the title as plain text. Close this here or elsewhere?",
"options": ["A leave it to the closing card objectui#12484, which retires the sheet pins against the page", "B a new card to carry record_title into the pointer face"],
"recommendation": "A, because the acceptance line ('a refused read never yields a link') holds, the card fuses unreadable answers by design (the existence-oracle rule), and the brief forbids widening this build."
},
{
"question": "The served tombstone fires on cancel_reason = record_deleted alone. The sheet's isDeletedRecordReference also requires status = cancelled. Accept the single condition?",
"options": ["A accept: ruling A put one declared predicate on the field, and the contract says cancel_reason is present only on cancelled rows", "B a spec change for compound predicates"],
"recommendation": "A, because the ruling and the spec key are single-condition and the contract carries the second half; B would be speculative surface."
},
{
"question": "The sheet's tombstone moves record_title to its meta line; the served tombstone draws the label alone. Add it?",
"options": ["A no, ruling A says the tombstone is the option's own label", "B carry it with objectui#12484"],
"recommendation": "A here, with B noted for #12484's page migration."
}
],
"out_of_scope_findings": [
"carrier: objectui#12484 · noted, not filed. The page H1 is display_title ('{process_name} · {record_id}'), so the raw record id stays on screen in all four states. This card's id pins are scoped to the reference (the tombstone and the Details tabpanel). Dedupe words: approval request page title raw record id, display_title record_id.",
"carrier: objectui#12484 · noted, not filed. The H4 differences beyond the tombstone: the refused state keeps no snapshot title on the served page (the sheet keeps it as plain text), and the deleted state shows no record_title meta (the sheet shows one). Dedupe words: served page refused snapshot title, tombstone record_title meta."
],
"live_reading": "NOT MEASURED: live reading. Reason: published @objectstack/spec 18.0.0-next.2 does not carry the services declaration on sys_approval_request.record_id (objectstack PR #23798), and this container has no objectstack backend. The served-page harness, which carries that declaration, is the evidence. The Submitter-chip browser reading asked in 6198410345 is NOT MEASURED for the same reason.",
"deviations": [
"The shared checkout /home/user/objectui had the branch checked out. It was detached at a5bb7ac to free the branch for the worktree ../objectui-issue-12489 (git worktree add -B ... origin/main). The worktree was removed after the push, node_modules first and without --force.",
"The commit ends with AGENTS.md's model-free trailer pair (Claude-Session plus Co-authored-by: Claude), not the harness reminder's model-named Co-Authored-By, per AGENTS.md's precedence rule and the brief.",
"There are three changesets, one per released package touched: fields minor, app-shell minor, plugin-detail minor. The console change is a test only and is covered by check-changeset-presence's pass.",
"Five plugin dists outside the closure (gantt, timeline, map, markdown, tree) were built only to get a clean console type-check reading.",
"objectstack was cloned read-only at depth 1 beside the worktree. Only its os-verify-lock.sh and ablation-replace.mjs were used.",
"Docs updated per AGENTS.md #2: content/docs/guide/fields.md and packages/fields/README.md."
],
"files_changed": [
"packages/fields/src/index.tsx (RecordPointer, resolveRecordPointer, the default pointer face and their docblocks only)",
"packages/fields/src/__tests__/resolveRecordPointer.deleted-12489.test.tsx",
"packages/fields/README.md",
"packages/plugin-detail/src/DetailSection.tsx (the resolveRecordPointer call site only)",
"packages/app-shell/src/views/record-preview/record-pointer-card-renderer.tsx",
"packages/app-shell/src/views/record-preview/record-pointer-card-renderer.tombstone-12489.test.tsx",
"apps/console/src/services/approvalRequestsDataSource.servedPageReferenceStates-12489.test.tsx",
"content/docs/guide/fields.md",
".changeset/12489-record-pointer-deleted-label.md, .changeset/12489-pointer-card-tombstone.md, .changeset/12489-detail-section-deleted-when.md"
],
"cleanup": "Worktree removed (node_modules first, no --force). The temporary ref refs/probe12489/remote was deleted. No dev server or background job is left running."
}
Posted by the domain:ui seat 1 PM (session_011K9g23W67dy2zwnyzaoB6o) verbatim from the cloud session's final message, because a cloud session cannot comment from its container. The seat filled pr with the draft PR it opened, #12665. The first deviation's "shared checkout" is the cloud container's own clone, not a fleet checkout.
Verified by content: the merge commit's own diff equals the PR's diff from its merge-base a5bb7ac2fd to the reviewed head b78ab082fa, changed line for changed line in each of the 11 files (+854/−16). Positive controls at the merge commit:
resolveRecordPointer calls resolveDeletedLabel(field.referenceDeletedWhen, row, optionsOf) in packages/fields/src/index.tsx;
the app-shell pointer face draws data-record-pointer="deleted".
Card: closed completed by Fixes #12489. This note precedes the strip of pm:dispatched.
Carried, not this card's:
Face 4, objectui#12490, stays pm:on-hold until a spec release carries record:json.
The refused state's snapshot title rides objectui#12484.
Activity
objectstack-fleet commentedon Oct 15, 2026
Unlock:
pm:blocked→pm:queue·domain:uiseat 1 ·session_01JZnppgHRp4sREcouhkQYDB· 2026-10-15T15:19ZBlocked-by: #12483is met. objectui#12483 closedcompletedwith PR #12533, merged asmain521817ce9d(landing note6195562024). This card's premise readings still hold on that merge: PR #12533 added theRecordSnapshotSummarypart inpackages/app-shell/src/views/approval-decision/, which this card builds on or alongside. The card returns topm:queue. This seat claimed its predecessor and dispatches it when a slot frees.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
Re-check for the landing seat ·
domain:uiseat 1 ·session_01JZnppgHRp4sREcouhkQYDB· 2026-10-15T17:39ZWhen this face lands, the browser reading on the served
sys_approval_request_detailpage also confirms one more thing: the highlights strip's Submitter chip shows the submitter's name, and no 24-or-more-charactersys_userid.objectui#12498 was closed as not planned on a test-tree measurement, which found the console names the submitter through its root
SchemaRendererProvider(6198309422). That claim was not measured live. A bare id in this reading reopens #12498.The dev brief should also mount
SchemaRendererProvider dataSource={adapter}around any served-page pin harness it adds or edits underapps/console/src/services/approvalRequestsDataSource.*.test.tsx, asConsoleShell.tsx:306does. Today those harnesses omit it, and that is how #12498 was filed.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
Claim: PM loop round 1 (
priority:p3; face 3 of ruling6190549995on objectui#12438, the maintainer's 「C (推荐)」)Session:
session_01JZnppgHRp4sREcouhkQYDBAccount:
sunhaolinBranch:
claude/issue-12489-reference-statesWorktree:
objectui-issue-12489Domain:
domain:uiSeat:
domain:ui#1File surface: step 1 (the fit check) is a measurement. A build follows only on an objectui-only route:
packages/app-shell/src/views/record-preview/RecordPreviewCard.tsx, or whatever block draws the served page'srecord_id/object_namereferenceViapair, plus its wiring;apps/console/src/services/approvalRequestsDataSource.*.test.tsx, each mounting the rootSchemaRendererProviderasConsoleShell.tsx:306does (see6198410345);.changeset/12489-*.md.Outside the surface:
ApprovalsInboxPage.tsx(ADR-0060 rule 3);plugin-approvalspage (the card's steps 2–3, which the seat files);A route that needs any of those stops before building, and reports the exact spec row or page edit (stop on breach; explain in the report).
Container & model:
M,mode:subagent,model: opus(default tier;dispatch-gates --tier --repo objectstack-ai/objectuinames no path-derived mandate)Clause-②: no. Every widening route is a stop-and-report by this claim, so the only route that builds adds no type, prop, export or spec read.
Responsibility: n/a — not a defect card
Thread-read: 6198410345
Serial constraints cleared: read at objectui
main518a8e3084.521817ce9d), which this card'sBlocked-by: #12483waited on; the card was unlocked at6195611978.record-preview/**or the request-page pins.approval-decision/snapshotSummaryPlan.ts, a different block on the same served page.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
os-dev-report
{ "issue": 12489, "status": "done", "branch": "claude/issue-12489-reference-states", "pr": null, "session": "session_01JZnppgHRp4sREcouhkQYDB (subagent mode: the parent's id, as the claim and the harness stamp it)", "premise_still_valid": true, "summary": "Step 1, the fit check, is measured, and nothing was built. The branch is pushed empty as the claim marker, and there is no PR. The served page draws only two of the sheet's four states at its target reference. One missing state, the platform-asserted tombstone, needs a spec declaration, so no objectui-only route draws all four. The ruling then forbids a build. Today the record_id referenceVia pair is drawn in the Details tab by RecordPreviewCard, through the pointer-card field renderer. Readable: the target card, with no link (gap). Refused and unresolvable: the cause-free 'This record cannot be opened', with no link. That passes the acceptance line, and the unresolvable copy is the sheet's own i18n key. Platform-asserted deletion: the same cause-free text, and 'Related record deleted' appears nowhere on the page (gap). The applicable route is the RecordPreviewCard one. Its 'spec row' is a new key beside FieldSchema.referenceVia, because the card has no spec row of its own. The card's 'caller's to render' rule stays true: the caller is the pointer renderer, which draws the tombstone from the declared assertion. That needs an edit of sys-approval-request.object.ts, and no edit of sys-approval-request.page.ts. The missing link needs no spec change and rides step 4.", "gap_table": [ "readable · sheet: a link to the app-scoped record path, labelled with the snapshot title (recordLink pin) · served page today: data-record-preview=readable, card text 'InvoiceINV-1010Invoice Amount12,500.00', 0 anchors in the card and 0 links to the target anywhere on the page · GAP: no link (objectui-only to close)", "refused, the target read rejects 403 PERMISSION_DENIED · sheet: no link, keeps the reference text, and never the cause-free label (recordLinkRefused-11878 pin) · served page today: data-record-preview=unreadable, 'This record cannot be opened', 0 links · HOLDS on the acceptance line 'a refused read never yields a link'. The sheet's kept-title assertion is not carried: the card fuses every unreadable answer into one cause-free sentence (its header's rule, which cites objectui#11878 itself) and holds no snapshot title. That is a boundary for the seat, not a gap this ruling closes", "platform-asserted deletion: status cancelled + cancel_reason record_deleted on the row, and the target read answers 404 · sheet: the tombstone 'Related record deleted' (the platform's cancel_reason option label), no link, no bare id (tombstone pin) · served page today: data-record-preview=unreadable, 'This record cannot be opened'. The tombstone count on the page is 0. The strip shows Status 'Cancelled'. The Details tab draws only 'Record ID' (the card), so cancel_reason is drawn nowhere · GAP: tombstone (needs a declaration; see route)", "unresolvable: terminal, no record_title, and the target read resolves with no record · sheet: 'This record cannot be opened', no link, no id (unresolvableReference pin) · served page today: data-record-preview=unreadable, the same string from the same key approvalsInbox.recordUnresolvable, 0 links, and no id inside the card · HOLDS at the reference. The page H1 shows the raw record id in all four states (finding below)" ], "route": { "applies": "RecordPreviewCard (A2) route, conditional on step 2's spec row. The pair reaches the card only through DetailSection: resolveRecordPointer(field, row) returns objectName + recordId, and getCellRenderer(RECORD_POINTER_CARD_TYPE) receives that pair as value, with no row. CellRendererProps carries no row, by design. So the host row's deletion assertion reaches the pointer only if a declaration names it. Without one, objectui would have to read sys_approval_request's status / cancel_reason inside a generic resolver: an approvals-only special case in a generic cell (ADR-0060 rule 2, AGENTS.md #0.1).", "step2_spec_row": "objectstack packages/spec/src/data/field.zod.ts, a new FieldSchema key directly after referenceVia: referenceDeletedWhen: z.object({ field: z.string().regex(/^[a-z_][a-z0-9_]*$/), value: z.string() }).strict().optional().describe('Pointer fields only (requires `referenceVia`): the sibling `select` field on the same object, and one of its option values, that the platform writes on a row whose pointed-at record was deleted, e.g. `{ field: \\'cancel_reason\\', value: \\'record_deleted\\' }` on `sys_approval_request.record_id`. A row holding that value draws the pointer as a tombstone in that option\\'s own label and does not read the target. Nothing else asserts a deletion: a target read that fails or answers no record stays cause-free, because the platform answers a deleted record and one outside the reader\\'s row set alike.'). It needs two refusals. FieldSchema's superRefine, beside the referenceVia checks, refuses referenceDeletedWhen without referenceVia. object.zod.ts, beside the existing referenceVia sibling check in ObjectSchema.create, refuses a field that is not a declared `select` on the same object, and a value that is not one of its option values. Boundary: the sheet also requires status cancelled. The contract declares cancel_reason 'present only on rows whose status is cancelled', so one condition carries it.", "step3_objectstack_edit": "plugin-approvals sys-approval-request.object.ts, field record_id: add referenceDeletedWhen: { field: 'cancel_reason', value: 'record_deleted' } after referenceVia: 'object_name'. sys-approval-request.page.ts: NO edit. Its Details tab already draws record_id as the pointer card (measured: the tab draws only that).", "step4_objectui": "After step 2 lands. Clause-② yes, because the published RecordPointer type of @object-ui/fields widens. resolveRecordPointer reads referenceDeletedWhen and returns the assertion with the pair. DetailSection passes the object field's referenceDeletedWhen beside referenceVia. RecordPointerCardRenderer, the card's caller, draws the tombstone in the host object's option label: fieldOptionLabel over the host object, the declared field and the declared value, with the declared option label as the default. It does not mount the card or read the target. RecordPreviewCard gets the link in its readable state only, to the app-scoped record path, so a refused or unresolvable read never yields one. The link is objectui-only, and the seat may dispatch it ahead as its own card. Pins: one per state on the real RecordDetailView over the routed approvals source, under the root SchemaRendererProvider. CONTROL: a refused read has no link. CONTROL: an unreadable row the platform did not flag gets no tombstone.", "fallback_if_step2_refuses": "A thin generic block, as the ruling orders. Declare record:reference in component.zod.ts ComponentPropsMap as RecordReferenceProps = strictObject({ field: z.string() (a referenceVia pointer field of the bound object), deletedWhen: z.object({ field, value }).strict().optional() (same semantics as above) }), and list it in page.zod.ts's record component types. Page edit in sys-approval-request.page.ts, Details tab: children: [{ type: 'record:details' }] becomes children: [{ type: 'record:reference', properties: { field: 'record_id', deletedWhen: { field: 'cancel_reason', value: 'record_deleted' } } }]. The tab measurably draws only record_id today, so the swap drops nothing here. Cost: a block type that redraws what the details grid already draws for every referenceVia field, and an assertion stated per page rather than on the field.", "recommendation": "The RecordPreviewCard route with referenceDeletedWhen. Business need: both routes serve the same ruled pull. The closing card objectui#12484 retires the tombstone pin, which came from user report objectui#7108. Today only sys_approval_request asserts a deletion, so each route has one real consumer. Long-term: the fact sits on the field that owns the pair (ADR-0052 §5), so every surface that draws the pair draws the tombstone, with no page node. The block route makes it per page. AI-proofing: one declaration on the object, refused loudly at parse if it names a non-select field or an undeclared option. The block route needs each page author to place and configure a node, and a page that keeps record:details draws the pair twice. Startup focus: one optional key with one consumer, against a whole block type plus a page edit.", "considered_and_rejected": [ "Draw the tombstone inside record:approval_decision, the panel faces 1 and 2 grew. It is objectui-only, but it is not one of the ruling's two destinations. The details card would keep saying 'cannot be opened' about the same reference beside it.", "Read status / cancel_reason inside the generic pointer renderer or resolveRecordPointer. That is an approvals-only rule in a generic cell, and a consumer-side contract.", "A record:alert with a visible predicate on the page. It is an existing declared block and needs only a page edit. But it is a banner, not the reference, and its copy would be authored inline, forking the platform's cancel_reason option label (deadRecordReference.ts refuses that)." ] }, "mechanism_assumptions": { "A1": "HOLDS on the component, and the state readings above falsify part of it. The pair is drawn by RecordPreviewCard through record-pointer-card-renderer (RECORD_POINTER_CARD_TYPE), inside the Details tab's record:details. It draws two of the four sheet states as the pins assert (refused: no link; unresolvable: the same text). It draws no link when readable, and no tombstone.", "A2": "HOLDS, with the key corrected. The sheet's tombstone reads status === 'cancelled' AND cancel_reason === 'record_deleted' (isDeletedRecordReference); there is no record_deleted key. objectstack rowFromRequest maps cancel_reason: row.cancel_reason ?? undefined beside status on every read, so the key is omitted on rows that are not cancelled. enrichRows adds record_title. objectui's routed get (routeApprovalRecordReads, GET /api/v1/approvals/requests/ID with no query string) hands the row through whole. Measured: status 'cancelled' (string) and cancel_reason 'record_deleted' (string), with keys cancel_reason, id, object_name, process_name, record_id, record_title, status.", "A3": "Mounted: SchemaRendererProvider dataSource={host} at the root, as ConsoleShell mounts it. The pointer card reads useAdapter() (AdapterCtx), not the SchemaRendererContext, so the provider does not change these four readings.", "A4": "Matched by assertion: link presence and href, the absence of a link, the tombstone text, the cause-free text, and the absence of the bare id. Markup was not compared." }, "tests": "No diff, so no suite is owed. Measurements came from a temporary probe, apps/console/src/services/zz-probe-12489.test.tsx: written into the worktree, run, deleted, and never committed. The fixture was the served page copied whole from objectstack origin/main 69351e985 (PageSchema.safeParse: true), on the real RecordDetailView under ApprovalRecordReads with the root SchemaRendererProvider. The request definition carried the real status and cancel_reason select options from @objectstack/spec/contracts, and each case set the host's invoice findOne answer. There were three runs through os-verify-lock with OS_VERIFY_LOCK_SLOT=dev-12489: 'VERDICT command-exit 0' each, with Tests 6 passed (6), 6 passed (6) and 7 passed (7). The runs grew the readings, and the state readings are identical across all three. Raw readings are quoted in gap_table and mechanism_assumptions. H1 in each case: 'invoice_approval · ' followed by the case's record id. No ablation: no tested behaviour changed.", "gates": "Not applicable. The branch carries no commit beyond its base 518a8e3084 (worktree cut from origin/main at dispatch), so no gate, changeset gate, type-check or eslint run is owed, and CI has no PR to run. dispatch-gates was not run: it is objectstack's tool, and this branch has no diff. Drift check: objectui main had moved to baab2d6ed0, read through a named ref and then deleted. It changes ApprovalsInboxPage.tsx's page header (objectui#12526) only, and touches none of the reference-state code or the files measured here.", "browser_reading": "NOT MEASURED live. Reason: this container has no objectstack backend (the objectstack checkout has no node_modules and no packages/spec/dist). There is no PR, so no PR section exists for the landing seat. The browser reading that 6198410345 asks for, the highlights strip's Submitter chip showing a name and no id of 24 or more characters, carries over to the PR of step 4 or of the link card.", "mcp_calls": "7, all reads: issue_read x7 (12489 get + get_comments, 12438 get + get_comments, 12483 get_comments, 12498 get_comments, 12029 get). There were no MCP GitHub writes. ToolSearch x1 loaded tool schemas.", "api_writes": "1: this report, POST /repos/objectstack-ai/objectui/issues/12489/comments, through the fleet-write relay via scripts/pm/post-stamped.mjs. Plus 1 git push, the empty branch as claim marker and write probe (not a REST write). No pr_create, because nothing was built, per the ruling. No label write.", "open_questions": [], "out_of_scope_findings": [ "carrier: objectui#12484 (the closing card, same family) · noted, not filed. On the served page the H1 is display_title, plugin-approvals' formula process_name + ' · ' + record_id, so the raw record id is on screen in all four states (measured: 'invoice_approval · ' followed by the record id). The sheet's tombstone and unresolvableReference pins assert that the bare id never shows. The replacement PR that retires those pins against the page inherits this, and its producer is objectstack sys-approval-request.object.ts. Dedupe words: approval request page title raw record id · display_title record_id · served page H1 identifier", "carrier: domain:ui seat 1 (already carried by 6198410345) · noted, not filed: the served-page pin harnesses still mount without the root SchemaRendererProvider. None was edited here, because nothing was built." ], "deviations": [ "No PR and no build, per the ruling's 'otherwise do not build'. The branch claude/issue-12489-reference-states is pushed empty at 518a8e3084, which is BASE.", "The brief asks for 'the exact sys-approval-request.page.ts edit'. The recommended route needs none: its objectstack edit is sys-approval-request.object.ts. A page edit is given only for the fallback route.", "A temporary probe file was written into the worktree, run 3 times under the lock, and deleted. It was never committed. git status --short was empty afterwards, and git diff HEAD is empty.", "git fetch origin main ran in the shared objectstack checkout, for read-only reference. It moved that checkout's shared origin/main (6074eafb6 to 69351e985). Reads used git show origin/main and changed no file. The prescribed worktree recipe's git fetch ran in the shared objectui checkout. The drift check fetched into a named ref (refs/probe12489/main), which was deleted after use." ], "files_changed": [], "line_budget": "not applicable (no skills/** surface)", "cleanup": "The worktree ../objectui-issue-12489 is removed after this report posts: node_modules first, then git worktree remove without --force." }Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
Decision needed ·
domain:uiseat 1 ·session_01JZnppgHRp4sREcouhkQYDB· 2026-10-15T18:15Z一句话问题: 审批人打开一条审批请求,而被审批的那条记录已经删除时,页面上那张目标记录卡今天只说「This record cannot be opened」,与「无权查看」一模一样。要让它说出「关联记录已删除」,平台得先多一条声明;这条声明加在哪里,是本次要定的。
背景
本卡是裁决
6190549995(objectui#12438,字母 C,维护者「C (推荐)」)的第 3 面。第一步「先对位再建」已由 dev 实测完成,报告6198994393,未写代码,分支空推作认领标记。差距表(served
sys_approval_request_detail页,真实RecordDetailView+ 根SchemaRendererProvider):status为 cancelled、cancel_reason为record_deleted)cancel_reason全页未画可读无链接那一半不需要裁决。席位已拆成子卡 objectui#12557 单独派发,本决策只管墓碑。
Governing text
6190549995item 2 原文:the states "go toRecordPreviewCard(A2) only if the spec row reverses its 「the caller renders them」 rule, otherwise a thin generic record-reference-state block"。RecordPreviewCard.tsx:49–:66的 docblock:一次被删除的断言 "is the caller's to render, not this card's"。卡片不能区分「已删」与「看不到」,否则就成了存在性预言机(objectui#11878)。@objectstack/specFieldSchema.referenceVia(packages/spec/src/data/field.zod.ts)声明指针对。卡片经resolveRecordPointer拿到的只是(objectName, recordId),没有宿主行。协议声明与是否改协议
RecordPreviewCard没有自己的 spec 行。所以裁决里「反转该行规则」那条路的前提不成立。objectstack的 spec 车道实施。前提(各带 re-check 命令)
git -C objectstack grep -n "RecordPreviewCard\|record:preview" origin/main -- packages/spec/src,应零命中。plugin-approvals的rowFromRequest映射cancel_reason(dev 实测路由读返回status: 'cancelled'、cancel_reason: 'record_deleted')。record_id。re-check:git -C objectstack show origin/main:packages/plugins/plugin-approvals/src/sys-approval-request.page.ts | grep -n "record:details"。选项 × 真实代价 × 工作量
FieldSchema上referenceVia旁加可选键referenceDeletedWhen: { field, value }(须同对象的select字段与其选项值,解析时响亮拒绝);sys-approval-request.object.ts的record_id写{ field: 'cancel_reason', value: 'record_deleted' };objectui 指针渲染器据此画墓碑,文案取该选项自己的标签domain:spec1 PR(S–M)+domain:services1 PR(S)+ spec 发版 +domain:ui1 PR(M,Clause-②: yes);无先例record:reference(field+deletedWhen);服务端页面把详情页签的record:details换成它;objectui 实现该块domain:spec1 PR(M)+domain:services1 PR(S)+ 发版 +domain:ui1 PR(M);无先例cancel_reason列入亮点或详情(纯元数据改动,不动 spec),引用卡继续说「cannot be opened」domain:services1 PR(S);objectui 零代码业务含义直译
四轴(从业务立场)
sys_approval_request一个消费者。推荐 A,回退 C。只看①选 A;②③④ 是否翻转:否。④ 偏向 C,但有实测拉动时,荐长远形态一次付清。A 与 B 工作量相近,C 约为 A 的 1/4,超过 3 倍差,特此点名。
置信缺口:
裁后执行
domain:spec卡(新键 + 两条拒绝,dev 已给出精确 zod 行)和domain:services卡(sys-approval-request.object.ts一行),后者Blocked-by前者。pm:blocked+Blocked-by:服务端卡。Clause-②: yes,RecordPointer类型变宽)。domain:services卡(把cancel_reason列进页面);本卡在可读链接子卡落地后关闭,并在 objectui#12484 上注明墓碑钉子迁移时改为断言亮点。os-decision-facets
Prior rulings read: "record reference tombstone deleted referenceVia pointer card spec" → ruling
6190549995(objectui#12438, letter C); ADR-0060 rules 2 and 3; ADR-0052 §5; thread: none.相关:
6198994393(含 A 的精确 zod 行、B 的块与页面改动);display_title公式process_name · record_id,四种状态下都露出原始记录 id。产者在 objectstacksys-approval-request.object.ts,承接者是 approvals B1 closing card (ruling C on objectui#12438): the inbox opens a request on the served sys_approval_request page where the side sheet is, deletes the sheet, and lists each retired pin against its user report #12484,已注在该卡。Generated by Claude Code
3 remaining items
objectstack-fleet commentedon Oct 15, 2026
Ruling: batch #352 item 4 · letter A · maintainer 「A (推荐)」 2026-10-15T22:38Z
Director seat, summon #42,
session_01AKVFxqmRLqktJspVXwPE9A, on thedomain:uiseat 1 presentation 6199104044, the dev's fit-check 6198994393 and this seat's delta 6203439151; the maintainer's answer takes the recommendation (batch #352 presented in this seat's session, answered with the option 「A (推荐)」). This settles face 3 of ruling 6190549995 (objectui#12438, letter C) item 2: the fit check found no spec row onRecordPreviewCard, so the fact goes on the field that owns the pointer pair.What the ruling binds (letter A):
referenceViaonFieldSchema,referenceDeletedWhen: { field, value }(aselectsibling and one of its declared options; anything else refused at parse). spec(field): referenceDeletedWhen beside referenceVia, a declared predicate on a sibling select field that marks the referenced record as platform-deleted (objectui#12489 letter A) objectstack#23735 (domain:spec, filed by this seat).sys_approval_request.record_iddeclares{ field: 'cancel_reason', value: 'record_deleted' }. plugin-approvals: sys_approval_request.record_id declares referenceDeletedWhen on cancel_reason = record_deleted (objectui#12489 letter A) objectstack#23736 (domain:services,Blocked-by:the spec card, filed by this seat). No page edit.RecordPreviewCard's 「the caller renders it」 rule stays, the pointer renderer being the caller;Clause-②: yes(theRecordPointertype widens); pins for all four states against the sheet's.display_titleexposure stays with objectui#12484. Not taken: B (a per-page block) and C (no declaration).Workload of the letter:
domain:spec1 PR (S–M) +domain:services1 PR (S) + a spec release +domain:ui1 PR (M).Execution:
needs-user-decision→pm:blockedin this act,Blocked-by: objectstack-ai/objectstack#23736in the body under theRuled:line (the satisfiedBlocked-by: #12483line leaves);priority:p3,area:workflowstand; the assignee (the seat's claim) stays.objectstack-fleet commentedon Oct 16, 2026
Pointer · triage seat (objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-16T04:59ZBlocked-by: objectstack-ai/objectstack#23736is satisfied. #23736 closed at 2026-10-16T04:03Z.referenceDeletedWhen(#23735, PR #237598776119bdc) is in@objectstack/spec@18.0.0-next.2(built frome5136ff10f, published 2026-10-16T03:11Z).⛔ Not a claim.
objectstack-fleet commentedon Oct 16, 2026
Claim: PM loop round 3 · takeover of claim
6198455121(the claimant's session ran out of tokens; the card is parked, not mid-build)Release: claim
6198455121· sessionsession_01JZnppgHRp4sREcouhkQYDB· accountsunhaolin· reason: the claimant is unreachable (token exhaustion; step 1 finished, nothing of step 4 started) · destination: this claim, same branch nameProvenance: the maintainer · in session
session_011K9g23W67dy2zwnyzaoB6o, the/pm-dispatch UI seat 1summons, read 2026-10-16T06:35Z · verbatim: 「前任没有token了,他的任务开发了一半你要接着处理。」Session:
session_011K9g23W67dy2zwnyzaoB6oAccount:
os-teslaBranch:
claude/issue-12489-reference-statesWorktree:
objectui-issue-12489Domain:
domain:uiSeat:
domain:ui#1File surface: none dispatched by this claim; the card stays
pm:blocked. The step-1 surface of6198455121is spent (the fit check built nothing). Step 4, the record pointer renderer's tombstone onreferenceDeletedWhen, gets its own claim with its own surface and its ownClause-②reading when the card unlocks.Container & model:
M,mode:subagent,model: opus(default tier, carried from6198455121; the step-4 claim re-readsdispatch-gates --tier --repo objectstack-ai/objectuiover its own paths)Clause-②: no
Responsibility: n/a — not a defect card
Thread-read: 6208437697
Serial constraints cleared: none to clear, since this claim dispatches nothing; the step-4 claim reads them at its own
main.Handover record. Last pushed sha on the branch
518a8e3084, with zero commits beyond objectuimain(it is themainthe step-1 claim was read at; step 1 built nothing). Step 1 is done: the dev's fit check6198994393, ruled at6203737086(letter A: the fact goes on the field that owns the pointer pair). Steps 2–3 are done in objectstack: objectstack-ai/objectstack#23735 (referenceDeletedWhen, merged as8776119bdc, published in@objectstack/spec@18.0.0-next.2) and objectstack-ai/objectstack#23736 (closed 2026-10-16T04:03Z). NoResume-from: there is no work to resume, and the step-4 dev starts the branch from currentorigin/main.Blocker re-pointed (the triage pointer
6208437697is right that objectstack-ai/objectstack#23736 is satisfied). The key is installable, but objectuimain'spnpm-lock.yamlstill resolves@objectstack/spec@18.0.0-next.1(read atef71a09f9e). The move to18.0.0-next.2is objectui#12626 (pm:dispatched,domain:uiseat 4). So the body's blocker line now names that card, the card stayspm:blocked, and this seat dispatches step 4 when it closes.Generated by Claude Code
objectstack-fleet commentedon Oct 16, 2026
Claim: PM loop round 3 · step 4 of this card (
priority:p3; face 3 of ruling6190549995, letter A of6203737086), unlocked: objectui#12626 closed andmainf62b6ff4e1resolves@objectstack/spec@18.0.0-next.2, which carriesreferenceDeletedWhenSession:
session_011K9g23W67dy2zwnyzaoB6oAccount:
os-teslaBranch:
claude/issue-12489-reference-statesWorktree:
objectui-issue-12489(in the cloud session's own container)Domain:
domain:uiSeat:
domain:ui#1File surface: the record pointer renderer's tombstone, as ruling A item 3 binds it.
packages/fields/src/index.tsx:RecordPointer,resolveRecordPointer, the defaultRECORD_POINTER_CARD_TYPEface and their docblocks only (⛔ not the cell-renderer regions, which PR feat(fields,plugin-grid): a declared object-ref / recipient-picker widget names what the value references in every list (objectui#12613) #12646 edits, norfieldWidgetMap, which objectui#12642 edits);packages/app-shell/src/views/record-preview/record-pointer-card-renderer.tsx(the tombstone face;RecordPreviewCard.tsxkeeps its 「the caller renders it」 rule and is read, not changed);packages/plugin-detail/src/DetailSection.tsx, theresolveRecordPointercall site only (passing what the predicate and the option's label need);apps/console/src/services/approvalRequestsDataSource.*.test.tsx, each mounting the rootSchemaRendererProviderasConsoleShell.tsx:306does, and tests beside each file above;Outside the surface: the side sheet
ApprovalsInboxPage.tsxand its four pins (read as the reference, not changed); any new i18n key (the tombstone text is the option's own label, per the ruling);display_title(objectui#12484). A route that needs any of these stops and reports (stop on breach; explain in the report).Container & model:
M,mode:cloud,model: opus(default tier;dispatch-gates --tier --repo objectstack-ai/objectuiover the 6 planned paths: no path-derived mandate)Clause-②: yes
Clause-② reason: ruling A item 3 says so:
RecordPointer, exported from@object-ui/fields, widens, andresolveRecordPointer's field parameter widens to readreferenceDeletedWhen. The seat'sTIER_DEFAULTreview comes before ready.Responsibility: n/a — not a defect card
Thread-read: 6213174414
Serial constraints cleared: read at objectui
mainf62b6ff4e1.next.2move this card waited on) landed asf62b6ff.packages/fields/src/index.tsx, infieldWidgetMap/FORM_FACE_LOADERSand in the cell-renderer regions respectively; neither touchesRecordPointerorresolveRecordPointer. Ordinary concurrency: the later lander mergesmain.DetailSection.tsx,record-preview/or theapprovalRequestsDataSource.*pins. objectui#12490 (face 4, same served page) ispm:on-hold.74b436263a) is NOT in publishednext.2(its build pointe5136ff10fis 12 commits behind it), so a live reading of the request page cannot show the predicate yet; the pins carry the measurement.Generated by Claude Code
objectstack-fleet commentedon Oct 16, 2026
os-dev-report
{ "issue": 12489, "status": "done", "branch": "claude/issue-12489-reference-states", "pr": "https://github.com/objectstack-ai/objectui/pull/12665", "session": "session_01RXFSkyCWZHTfnnf5iKrQFN (cloud dev run, dispatched by the domain:ui seat 1 session session_011K9g23W67dy2zwnyzaoB6o)", "premise_still_valid": true, "summary": "Ruling A item 3 is built on b78ab082. The resolveRecordPointer rule in @object-ui/fields now reads the spec's referenceDeletedWhen beside referenceVia. When the row's sibling select holds the declared option, the returned RecordPointer carries deletedLabel, which is that option's label from an optionsOf callback the call site supplies. The predicate lives only in fields. DetailSection passes the object field's referenceDeletedWhen and the sibling's options, translated with the same translateOptions call the grid already uses for select options (H3). The app-shell pointer renderer is the caller that RecordPreviewCard's rule names: on deletedLabel it draws the tombstone (data-record-pointer=deleted, wastebasket icon, the label) and does not mount the card, so nothing is read, nothing is linked and no id is shown. The default text face in fields draws the label instead of the id. H1, H2, H3 and H5 hold. H4 was measured on the served-page harness under the root SchemaRendererProvider: readable, deleted and unresolvable match the sheet's pins. The refused state differs from the sheet beyond the tombstone; this is reported, not fixed (see open_questions). A live request page was NOT MEASURED, because published @objectstack/spec 18.0.0-next.2 lacks the services declaration (objectstack PR #23798), so the served-page harness carries that declaration and serves as the evidence.", "h_readings": { "H1": "HOLDS. resolveRecordPointer(field, row, optionsOf) evaluates row[referenceDeletedWhen.field] === referenceDeletedWhen.value and looks up the option in optionsOf(field). DetailSection supplies only the declaration and the options; it does not evaluate the predicate. There is no new export: the helper types are module-local.", "H2": "HOLDS. Under the real DetailSection: the adapter's findOne is called 0 times and MetadataCtx getItem 0 times, with 0 anchors under a host that builds record hrefs, no data-record-preview element, and no id in the tombstone. On the served page: 0 invoice findOne calls, 0 links to the record path, and the record id appears neither in the tombstone nor in the Details tabpanel. The default fields face draws the label, not the id.", "H3": "HOLDS. The helper is useSafeFieldLabel().translateOptions, which resolves fieldOptions.OBJECT.FIELD.VALUE with the declared label as fallback, under the same key the sheet's fieldOptionLabel reads. A zh-CN bundle draws the tombstone as the localized label, both under the real DetailSection and on the served page. No new i18n key was added.", "H4": "MEASURED on the served page (real RecordDetailView, ApprovalRecordReads, root SchemaRendererProvider, SERVED_PAGE copied whole). readable: one link to the app-scoped record path inside the card, named by the title; matches the recordLink pin. refused (403 PERMISSION_DENIED): card unreadable, text 'This record cannot be opened', no link, and the snapshot title INV-2020 absent from the page (probe: card text and the page-level includes check returned false). The sheet's recordLinkRefused-11878 pin keeps the title as plain text, so this DIFFERS beyond the tombstone and is reported only. deleted: tombstone 'Related record deleted', 0 reads, no link, no id at the reference. Matches the sheet's tombstone text, no link and no bare id, except that the sheet also moves record_title onto its meta line and the served page draws no record_title (reported). unresolvable: 'This record cannot be opened', no link, no id in the card; matches the unresolvableReference pin.", "H5": "HOLDS. Tested cases: a field without the key; a sibling holding another option; no sibling value; a null sibling; no optionsOf. Each returns the pair exactly as before. On the served page, a cancelled row with no cancel_reason reads the target and draws the readable card, and a 404 on an unflagged row draws the cause-free card with no tombstone. Existing pins in the record-preview directory and the plugin-detail recordPointer-12045 suite stay green." }, "tests": "Union at HEAD b78ab082: pnpm exec vitest run --maxWorkers=2 over packages/fields/, packages/plugin-detail/, packages/app-shell/src/views/record-preview/, every apps/console/src/services/approvalRequestsDataSource.*.test.ts(x), and the four sheet pins (recordLink, recordLinkRefused-11878, tombstone, unresolvableReference). Result: 'Test Files 552 passed | 2 skipped (554)', 'Tests 6937 passed | 21 skipped (6958)', 'VERDICT command-exit 0' through os-verify-lock (slot dev-12489-cloud). New pins: fields resolveRecordPointer.deleted-12489 (10 tests), app-shell record-pointer-card-renderer.tombstone-12489 (7), console approvalRequestsDataSource.servedPageReferenceStates-12489 (8). Ablation: four runs through objectstack's scripts/ablation-replace.mjs; each confirmed the mutation on disk (anchor 1 to 0, blob moved), and each restore checked blob == HEAD and an empty git diff HEAD. The root vitest config aliases @object-ui/* to src, so no dist leg applies. A1, predicate forced false (fields index.tsx): 9 failed / 17 passed of 26 across all 3 new files. A2, a findOne made anyway in the tombstone branch (app-shell renderer): 3 failed (the zero-read pin and both served deleted pins). A3, label taken from the raw option value: 8 failed (label pins plus the 'never the raw option value' pin). A4, extra for H3, the call site skips translateOptions: 2 failed (both localized pins). The H4 refused-title probe was a temporary assertion in the new test file, run once and restored from a byte copy; it was never committed.", "gates": "Local, hand-derived from objectui package.json and ci.yml (dispatch-gates is objectstack's tool, so it was not run). Each exit code was captured before any pipe. (1) Closure build: pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell...' build, BUILD_EXIT=0; the app-shell dist completeness check reported 1114 emitted files verified. (2) type-check: @object-ui/fields=0, @object-ui/plugin-detail=0, @object-ui/app-shell=0. @object-ui/console was first 2: 23 errors, all from plugin-gantt, timeline, map, markdown and tree dists outside the closure, and none in a touched file. After building those five plugins it re-ran at 'VERDICT command-exit 0' for the build && type-check chain. (3) pnpm check:changeset-claims=0 ('No pending changeset names a file this change touches'); pnpm changeset:check=0; pnpm check:test-path-roots=0; pnpm check:spec-symbols=0; node scripts/check-changeset-presence.mjs=0 ('6 source file(s) of 4 released package(s) changed ... declares 3 changeset(s)'); node scripts/check-new-cross-file-line-citations.mjs=0 ('0 new citation(s)'). (4) eslint --no-inline-config on the 6 changed source and test files: exit 0, 0 errors and 157 warnings, all pre-existing rule classes (react-refresh/only-export-components on existing exports, react-hooks/set-state-in-effect in untouched code). CI check-runs are NOT MEASURED: there is no PR yet and the seat opens it.", "mcp_calls": "2, all reads: issue_read get and get_comments on objectui#12489. ToolSearch loaded the tool schema once. No GitHub write tool was called.", "api_writes": "0 REST writes. Two git pushes to claude/issue-12489-reference-states: the first was the write probe, a fast-forward of the stale tip 518a8e3 to origin/main a5bb7ac (518a8e3 was confirmed an ancestor after deepening the shallow clone); the second pushed b78ab082. No PR creation, issue comment, label write or relay call, per the cloud brief.", "open_questions": [ { "question": "The tombstone uses its own attribute, data-record-pointer=deleted, not a fifth data-record-preview value. Keep it?", "options": ["A keep data-record-pointer=deleted: RecordPreviewCard's header defines exactly four states of its own and leaves deletion to the caller", "B reuse data-record-preview=deleted so one selector reads all five faces"], "recommendation": "A, because the card never draws this state and its header says so; a shared attribute would read as the card claiming it." }, { "question": "On the served page the refused state shows the card's cause-free 'This record cannot be opened' and no snapshot title. The sheet's recordLinkRefused-11878 pin keeps the title as plain text. Close this here or elsewhere?", "options": ["A leave it to the closing card objectui#12484, which retires the sheet pins against the page", "B a new card to carry record_title into the pointer face"], "recommendation": "A, because the acceptance line ('a refused read never yields a link') holds, the card fuses unreadable answers by design (the existence-oracle rule), and the brief forbids widening this build." }, { "question": "The served tombstone fires on cancel_reason = record_deleted alone. The sheet's isDeletedRecordReference also requires status = cancelled. Accept the single condition?", "options": ["A accept: ruling A put one declared predicate on the field, and the contract says cancel_reason is present only on cancelled rows", "B a spec change for compound predicates"], "recommendation": "A, because the ruling and the spec key are single-condition and the contract carries the second half; B would be speculative surface." }, { "question": "The sheet's tombstone moves record_title to its meta line; the served tombstone draws the label alone. Add it?", "options": ["A no, ruling A says the tombstone is the option's own label", "B carry it with objectui#12484"], "recommendation": "A here, with B noted for #12484's page migration." } ], "out_of_scope_findings": [ "carrier: objectui#12484 · noted, not filed. The page H1 is display_title ('{process_name} · {record_id}'), so the raw record id stays on screen in all four states. This card's id pins are scoped to the reference (the tombstone and the Details tabpanel). Dedupe words: approval request page title raw record id, display_title record_id.", "carrier: objectui#12484 · noted, not filed. The H4 differences beyond the tombstone: the refused state keeps no snapshot title on the served page (the sheet keeps it as plain text), and the deleted state shows no record_title meta (the sheet shows one). Dedupe words: served page refused snapshot title, tombstone record_title meta." ], "live_reading": "NOT MEASURED: live reading. Reason: published @objectstack/spec 18.0.0-next.2 does not carry the services declaration on sys_approval_request.record_id (objectstack PR #23798), and this container has no objectstack backend. The served-page harness, which carries that declaration, is the evidence. The Submitter-chip browser reading asked in 6198410345 is NOT MEASURED for the same reason.", "deviations": [ "The shared checkout /home/user/objectui had the branch checked out. It was detached at a5bb7ac to free the branch for the worktree ../objectui-issue-12489 (git worktree add -B ... origin/main). The worktree was removed after the push, node_modules first and without --force.", "The commit ends with AGENTS.md's model-free trailer pair (Claude-Session plus Co-authored-by: Claude), not the harness reminder's model-named Co-Authored-By, per AGENTS.md's precedence rule and the brief.", "There are three changesets, one per released package touched: fields minor, app-shell minor, plugin-detail minor. The console change is a test only and is covered by check-changeset-presence's pass.", "Five plugin dists outside the closure (gantt, timeline, map, markdown, tree) were built only to get a clean console type-check reading.", "objectstack was cloned read-only at depth 1 beside the worktree. Only its os-verify-lock.sh and ablation-replace.mjs were used.", "Docs updated per AGENTS.md #2: content/docs/guide/fields.md and packages/fields/README.md." ], "files_changed": [ "packages/fields/src/index.tsx (RecordPointer, resolveRecordPointer, the default pointer face and their docblocks only)", "packages/fields/src/__tests__/resolveRecordPointer.deleted-12489.test.tsx", "packages/fields/README.md", "packages/plugin-detail/src/DetailSection.tsx (the resolveRecordPointer call site only)", "packages/app-shell/src/views/record-preview/record-pointer-card-renderer.tsx", "packages/app-shell/src/views/record-preview/record-pointer-card-renderer.tombstone-12489.test.tsx", "apps/console/src/services/approvalRequestsDataSource.servedPageReferenceStates-12489.test.tsx", "content/docs/guide/fields.md", ".changeset/12489-record-pointer-deleted-label.md, .changeset/12489-pointer-card-tombstone.md, .changeset/12489-detail-section-deleted-when.md" ], "cleanup": "Worktree removed (node_modules first, no --force). The temporary ref refs/probe12489/remote was deleted. No dev server or background job is left running." }Posted by the
domain:uiseat 1 PM (session_011K9g23W67dy2zwnyzaoB6o) verbatim from the cloud session's final message, because a cloud session cannot comment from its container. The seat filledprwith the draft PR it opened, #12665. The first deviation's "shared checkout" is the cloud container's own clone, not a fleet checkout.Generated by Claude Code
objectstack-fleet commentedon Oct 16, 2026
ACCEPT → PR #12665 comment 6216761552 · contract review
6216747309(TIER_DEFAULTPASS) ·domain:uiseat 1 ·session_011K9g23W67dy2zwnyzaoB6o· 2026-10-16T13:40ZGenerated by Claude Code
objectstack-fleet commentedon Oct 16, 2026
Landed ·
domain:uiseat 1 ·session_011K9g23W67dy2zwnyzaoB6o· 2026-10-16T14:04Zmain023087f63b, with one parent,c6b7d82032(PR feat(fields): action-param and bulk-action dialogs draw the platform date picker (objectui#12642) #12663's merge). It is an ancestor oforigin/main.a5bb7ac2fdto the reviewed headb78ab082fa, changed line for changed line in each of the 11 files (+854/−16). Positive controls at the merge commit:resolveRecordPointercallsresolveDeletedLabel(field.referenceDeletedWhen, row, optionsOf)inpackages/fields/src/index.tsx;data-record-pointer="deleted".completedbyFixes #12489. This note precedes the strip ofpm:dispatched.pm:on-holduntil a spec release carriesrecord:json.Generated by Claude Code