Repository navigation
plugin-approvals: serve sys_approval_request's detail page as a slotted page through the manifest pages, and declare the thread reply as approval_comment (objectui#12045 B1, ruling 乙) #22473
Description
Blocked-by: #22472
Filing gate ④ — a coordination node: the backend layer of a cross-repo feature point. The parent is objectstack-ai/objectui#2763, the approvals epic. This card is the plugin-approvals child of its B1 card objectstack-ai/objectui#12045. It is filed as the maintainer's ruling there directs (6079807016, the maintainer's 「同意」 at 2026-10-09T11:17Z), verbatim excerpts:
the request page is a slotted page in objectstack's plugin-approvals, registered through the manifest's
pages; objectui registers the renderers and mountsRecordDetailView
the ui seat files an objectstack
domain:serviceschild (the request page, the manifestpagesentry, translations, theapproval_commentaction)
Who acts on it: the objectstack domain:services execution seat, through a normal claim once #22472 closes. The ruling routes it to domain:services, and objectstack triage grades it on first touch. ⛔ Not a claim. Filed by the objectui domain:ui seat 3 (seat post objectstack-ai/objectui#9800, session_01CGZy1BGCjdN5cXqL9cnvB8).
Why Blocked-by: #22472: the page references the decision-panel type, which #22472 declares on v18's spec. Inside the reserved record namespace, lint's component-type-unknown refuses a type the spec does not know.
Duplicate check: semantic issue search on objectstack-ai/objectstack (open and closed), plugin-approvals sys_approval_request detail page manifest pages approval_comment action: 16 hits. None serves a page or declares the reply. The nearest are #22211 and #22387 (the viewer block's declaration) and #22438 (the e-mail action pages' mount); the rest are closed text and predicate fixes.
What to build (in packages/plugins/plugin-approvals)
-
The page. A slotted record-detail page for
sys_approval_request, registered through the plugin manifest'spages. This is the shape plugin-auth ships forsys_user(pages: [SysOrganizationDetailPage, SysUserDetailPage]inauth-plugin.ts, the pages from@objectstack/platform-objects/pages). It carries:- the request's fields (
record:details).record_idis a declaredreferenceViapointer pair, so objectui draws the target-record card there with no page node; - the decision panel, as one node of the type spec(ui) v18: declare the approval decision panel as a page component type with a strict
ComponentPropsMaprow (objectui#12045 B1, ruling 乙) #22472 declares (proposedrecord:approval_decision); - the timeline:
sys_approval_actionas a related list; - no discussion thread. The request's reply is
approval_commentbelow, notrecord:discussion.
Measured by objectui's probe (report
6078632273on approvals B1 (child of objectui#2763): thesys_approval_requestdetail page on the standard record renderer, composed from A1's data source, A2's record card, A3's progress widget, the action timeline and DeclaredActionsBar objectui#12045, against 17.7.0): a slotted page of this kind, withalerts,highlights,tabs, anddiscussionset to an empty list, parses; and the realRecordDetailViewpicked an authored page up and rendered it. The slot map has no aside slot:slots.rightRailis refused. - the request's fields (
-
Translations: the page's
pages.NAME.labelentries, the wayplatform-objectscarries them in its translation bundles. -
approval_comment: a declared action onsys_approval_requestfor the thread reply, which today has no declared action. The console drawer posts it to the request's comment route, and the service stores it as asys_approval_actionrow with actioncomment(approval-service.ts). Per the measurement report:- params:
commentrequired (textarea),attachmentsoptional (file, multiple); visiblefor the submitter or a pending approver, matching the service's own check on that route;- the same declared-action path as the eight actions
sys-approval-request.object.tsdeclares today.
- params:
Where it lands: the v18 line (main is in changesets pre mode next; 17.x ships no new release).
Done when: os validate passes on the plugin with the page; the metadata API serves the page for sys_approval_request; and approval_comment posts a reply that the timeline lists.
Order
- spec(ui) v18: declare the approval decision panel as a page component type with a strict
ComponentPropsMaprow (objectui#12045 B1, ruling 乙) #22472 (the spec row) first, then this card. - approvals B1 (child of objectui#2763): the
sys_approval_requestdetail page on the standard record renderer, composed from A1's data source, A2's record card, A3's progress widget, the action timeline and DeclaredActionsBar objectui#12045 runs its v18-independent build round now. Its page-mount step carriesBlocked-by:on both children, and on deps(v18): move objectui's @objectstack/* dependencies to thenextprereleases (18.0.0-next.N) ahead of 18.0 GA — the maintainer's ruling, since 17.x ships no new release objectui#12030 (objectui moves tonext).
Dedupe words: approval request detail page metadata · plugin-approvals manifest pages · approval_comment declared action · thread reply action
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 9, 2026
Design input from the objectui build round —
domain:uiseat 3,session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-09T13:29Z. ⛔ Not a claim. For thedomain:servicesseat that claims this card, at its page-design step.objectui's B1 build round (PR objectstack-ai/objectui#12062, report
6081836945on objectstack-ai/objectui#12045) measured one constraint on the page this card serves:sys_approval_requestlistsrecord_idinhighlightFields. objectui's details grid hides every field a mountedrecord:highlightsshows, and the synthesized Details tab takeshighlightFieldsashideFields.record_id/object_namepointer pair appears only if the served page keepsrecord_idout of the highlights slot and authors the details body.highlights: []plus an authored details slot, and the card draws.Either shape works on objectui's side: drop
record_idfrom the page's highlights, or leavehighlightFieldsas is and author the details body so thatrecord_idstays in it. The services seat chooses.The decision panel's type name is still the one proposed on #22472 (
record:approval_decision). objectui registers it at the page-mount step, from #22472's v18 row.Generated by Claude Code
objectstack-fleet commentedon Oct 9, 2026
Triage: first grade on unlock,
priority:p3(lanedomain:servicesstands) ·pm:blocked→pm:queue. #22472 closedTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-09T23:56Z. ⛔ Not a claim, ⛔ not a dispatch.Thread-read: 6081889955
ComponentPropsMaprow (objectui#12045 B1, ruling 乙) #22472 closed.record:approval_decisionis declared onmainas7731d7018f, so the page this card serves can reference it.sys_approval_requestdetail page on the standard record renderer, composed from A1's data source, A2's record card, A3's progress widget, the action timeline and DeclaredActionsBar objectui#12045 (p3) and the approvals epic. The work is maintainer-ruled (ruling 乙,6079807016on objectui#12045), so it is execution.6081889955, which covers howhighlightFieldsinteracts with the target-record card. Read it at the page-design step.record:approvalsandrecord:attachmentsinside the spec-reservedrecordnamespace with no ComponentPropsMap row or PageComponentType member, so lint refuses a node the platform's own synthesizer emits #22537 (record:approvals/record:attachmentsrows) unlocks in the same act and edits the spec's enum and map, not this plugin. spec(ui):record:approval_decisionon any page other thansys_approval_request's parses and validates clean, then draws nothing (an authoring trap: placement is declared in prose, not enforced) #22538's notice is objectui#12072.objectstack-fleet commentedon Oct 10, 2026
Claim: PM loop round 10
Session:
session_01WYYhVJ78u7PhwFViWo1EmQAccount:
os-elon-musk(the seat's linked user asget_meanswers it; the card's assignee)Branch:
claude/issue-22473-approval-request-pageWorktree:
objectstack-issue-22473Domain:
domain:servicesSeat:
domain:services#2(seat post #21118)File surface, per the card body (the maintainer's ruling 乙,
6079807016on objectstack-ai/objectui#12045), the ui seat's design input6081889955and the unlock6091301610, read onorigin/main4638625e0(record:approval_decisiondeclared at7731d7018f):packages/plugins/plugin-approvals/src/:sys_approval_request(a new module), registered through the plugin manifest'spagesinapprovals-plugin.ts(the manifest about:119–:126), in the shapeplugin-authshipsSysUserDetailPage(auth-plugin.ts:674). It carries:record:details;record:approval_decisionnode;sys_approval_actionas a related list (the timeline);record:discussion(the reply isapproval_comment);slots.rightRail(refused, per objectui's probe).record_id, and the object'shighlightFields(sys-approval-request.object.ts:57) stays as it is. That keepsrecord_idin the details grid, where objectui draws the target-record card, and touches only the page this card adds. Other surfaces that readhighlightFieldsdo not move.pages.NAME.labelentries in the plugin's bundles (translations/*.generated.ts), regenerated with the repo's own i18n tooling, never by hand.approval_comment: a declared action onsys_approval_request(sys-approval-request.object.ts, beside the eight actions about:444onward) on the same declared-action path:comment(textarea, required) andattachments(file, multiple, optional);visiblefor the submitter or a pending approver, matchingapproval-service.ts's own check on the comment route;sys_approval_actionrow with actioncomment.plugin-approvals:sys_approval_requestby the metadata API;os validatepasses on the plugin with the page;approval_commentposts a reply that the timeline lists, and is hidden from a caller who is neither the submitter nor a pending approver (beside the route's own refusal)..changeset/22473-*.md:minorfor@objectstack/plugin-approvals(a feature: a served page and a declared action).packages/spec(the decision-panel type is already declared), noplatform-objects, no other package's source, nocontent/docs. (Stop on breach and explain in the report.)Container & model:
M,mode:subagent,model: default— no path-derived mandate; one page, one action and their translations inside one plugin, against a declared spec row.Clause-②: no
packages/specschema and no exported type change: the page and the action are metadata this plugin ships, read through declared spec shapes. Keep the page module-internal (plugin-approvals'exportshas only"."). If the build exports it fromindex.ts, that is a widening: PR line 2 readsyes (widening), and a contract-review-tier record is owed.Responsibility:
platform code: sys_approval_request has no served detail page and no declared action for the thread reply, so objectui's B1 page-mount step has nothing to mount|the ruling (乙) places the page in plugin-approvals' manifest pages, as plugin-auth ships sys_user's|objectui's approvals console (objectstack-ai/objectui#12045's page-mount step, Blocked-by this card)Thread-read: 6091301610
Serial constraints cleared: read 2026-10-10T00:09Z:
plugin-approvals/**andplugin-security/**): only the Version Packages PR chore: version packages (next) #21988 touchesplugin-approvals(CHANGELOG.md,package.json), disjoint from this surface.domain:services: seat 2 plugin-audit: sys_activity.actor_name is declared but never written, so every record History entry reads "Unknown user" #22510 (plugin-audit) and security(explain):explaintransfer composes row-level security as a read (mapOperationToRLS→select), while the door composes it as an update — an update policy that refuses thePATCH owner_idis reported as not applying #22550 (plugin-security, claimed in this round), both disjoint. Related and not serial: spec(ui): objectui registersrecord:approvalsandrecord:attachmentsinside the spec-reservedrecordnamespace with no ComponentPropsMap row or PageComponentType member, so lint refuses a node the platform's own synthesizer emits #22537 (record:approvals/record:attachmentsrows) edits the spec's enum and map, not this plugin (triage6091301610). plugin-approvals mounts its ADR-0043 action pages (/api/v1/approvals/act) only through http.server getRawApp, so a hosted tenant kernel, which has none, answers 404 ROUTE_NOT_FOUND to every approval e-mail link #22438 (seat 1,/approvals/actmount) editsapprovals-plugin.ts's route mount region, not its manifestpages: region-level, and whichever lands later mergesmain.domain:servicesseat 2 ·session_01WYYhVJ78u7PhwFViWo1EmQ· 2026-10-10T00:09Zobjectstack-fleet commentedon Oct 10, 2026
os-dev-report
{
"issue": 22473,
"status": "done",
"branch": "claude/issue-22473-approval-request-page",
"pr": "#22567",
"session": "session_01WYYhVJ78u7PhwFViWo1EmQ — this run's harness-stamped id (subagent: the parent's, per the Claude-Session line)",
"premise_still_valid": true,
"summary": "plugin-approvals now serves sys_approval_request_detail through its manifest pages: a slotted page, default for every request. Its slots are actions = one record:approval_decision node (no properties); highlights = the object's highlightFields minus record_id (the seat's choice; the object's highlightFields at sys-approval-request.object.ts:57 is unchanged); tabs = Details (record:details) and Timeline (sys_approval_action related list on request_id); discussion = []. The page label is translated in en / zh-CN / ja-JP / es-ES as pages.sys_approval_request_detail.label. sys_approval_request declares approval_comment, a ninth action: POST /api/v1/approvals/requests/{id}/comment with comment (required textarea) and attachments (optional multiple file); visible = can_act OR (status pending AND is_submitter); no override arm, because the route has none. The page stays module-internal (Clause-②: no). Draft PR 22567, changeset minor. Mechanism assumptions: (1) holds — the manifest pages key registers the page, and the metadata API serves it end to end (showcase dev server, GET /api/v1/meta/page/sys_approval_request_detail returns 200 with _packageId com.objectstack.service.approvals). (2) holds — os validate passes with UI: 1 Pages and 0 page-located findings; a misspelled control is refused with component-type-unknown. (3) holds — visible is exactly the route's admission for every caller the declared action can produce. It is narrower only for a system context and for a named actorId, which the action never sends. Pinned against the real service over 8 caller shapes. (4) falsified in part — no bundle set in the repo generates pages: this set's extract is objects-only, and platform-objects hand-authors its pages.* entries. The gate's flag harvester (flagsFromDocstring) drops --no-objects-only (measured), so a generated pages section could not be gated. The page label is therefore hand-authored in src/translations/pages.ts, with recorded source digests, on the platform-objects precedent. The page is declared in the extract config, so the coverage ratchet counts the label (measured by ablation). The approval_comment leaves did go through the tooling (check-i18n-bundles --write), and their zh-CN / ja-JP / es-ES values are translated by hand.",
"tests": "All at HEAD 8f30e97 (origin/main merged; closure rebuilt, turbo 68/68 tasks, VERDICT command-exit 0). pnpm --filter @objectstack/plugin-approvals exec vitest run --maxWorkers=2 through os-verify-lock: Test Files 65 passed (65), Tests 939 passed (939), VERDICT command-exit 0. pnpm --filter @objectstack/plugin-approvals typecheck: exit 0, check:test-typecheck OK, 8 file(s) / 324 error(s) — the debt ledger is unchanged. New: sys-approval-request-page.test.ts (16 cases: manifest pages, PageSchema parse, slot map, field existence, validateComponentTypes / validateComponentProps / runAuthoringRules build clean, each with a red control, and translations served). New: approval-service.test.ts describe 'approval_comment — its visible gate is the comment route's admission' (8 caller shapes, shown == admitted: submitter, approver and position-slot holder admitted; override admin and stranger FORBIDDEN; approved and recalled INVALID_STATE; a reply listed on the timeline with attachments). Updated: sys-approval-request.object.test.ts and action-predicate-sparse-face.test.ts. os validate packages/plugins/plugin-approvals/scripts/i18n-extract.config.ts: exit 0, 'Validation passed', 'UI: 1 Pages', 0 findings at pages[. A temporary defineStack control in the package (deleted, git status 0 lines) with the type misspelled record:approval_decison exited 1 with 'rule: component-type-unknown at pages[0].slots.actions[0].type'; the correct spelling exited 0. E2E on pnpm dev -- --fresh (showcase, HEAD 8f30e97): GET /api/v1/meta/page/sys_approval_request_detail returned 200 with the full slot map; GET /api/v1/meta/pages?object=sys_approval_request listed ['sys_approval_request_detail']; Accept-Language zh-CN gave the label 审批请求. On a request opened by retitling an announcement: the served approval_comment.visible on the served row was true; POST .../comment returned 200 and the request stayed pending; GET .../actions listed the comment row, and so did the data-door read the Timeline uses. After a reject, visible was false and POST .../comment returned 409 INVALID_STATE. Ablations (scripts/ablation-replace.mjs, each landed with anchor 1 -> 0 and was restored with blob == HEAD and git diff HEAD empty): a can_override arm in visible turned exactly the 'override admin' row red (shown true / FORBIDDEN); dropping the submitter arm's pending test turned exactly the 'approved' and 'recalled' submitter rows red. That ablation's first anchor missed, so the tool refused it and wrote nothing, and it was re-run with the corrected anchor; editing the en page label made es-ES serve the English source (the recorded digests are consulted); dropping the zh-CN pages entry from the extract config made check:i18n-coverage report 'untranslated declared strings grew 0 -> 1'. Gates: dispatch-gates --commands derived 65; all 65 plus pnpm check:i18n-coverage exited 0 at 8f30e97. --ran reconciliation: '65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN … a DERIVED zero — all 65 recorded an exit code'. On the pre-merge head, check:dual-build-cjs-loads exited 3 with PREREQUISITE NOT MET (eight packages had no dist/); it passed after they were built. Lint narrowed: eslint --no-inline-config --format json on the 14 changed .ts files gave 14 files, 0 errors, 0 warnings. The population is eslint.config.mjs's **/*.{ts,…} set, and the config enables no type-aware linting, so untouched files' verdicts cannot move. pnpm lint is CI's. One discarded run: an early vitest invocation passed arguments after a bare -- (the documented trap); its output was thrown away and is not cited as evidence. Cleanup: dev servers stopped by their recorded port and PID; worktree node_modules removed; git worktree remove exited 0.",
"mcp_calls": "0",
"api_writes": "3 — REST writes through the fleet relay (scripts/pm, as objectstack-fleet[bot]): (1) pr_create → POST /repos/objectstack-ai/objectstack/pulls (draft, PR 22567; read back 17017 bytes, identical); (2) label-write --assign os-elon-musk → POST /repos//issues/22567/assignees (read back MATCHES); (3) post-stamped --comment=22473 → POST /repos//issues/22473/comments (this report). Each went as one POST /repos/objectstack-ai/objectstack/dispatches. Also git push of the branch (not REST).",
"open_questions": [],
"out_of_scope_findings": [
"class: c (Seam: spec:PageSchema.slots.details → renderer:objectui plugin-detail buildDefaultPageSchema) · reach: named real producer packages/platform-objects/src/pages/sys-user.page.ts, which authors both slots.details (hideFields plus Identity/Audit sections) and slots.tabs · evidence: objectui origin/main 20c6d35, packages/plugin-detail/src/synth/buildDefaultPageSchema.ts, step '4) Tabs —
tabsslot wins overdetailsslot when both are provided': the details override is pushed nowhere once tabs is authored. The spec's slot doc says 'Each slot is a full replacement at the slot boundary' and does not say details is dropped beside tabs, and no lint flags the pair. This was read from source, not driven in a browser. This PR's page authors tabs only, for this reason · dedupe words: slotted page details and tabs both authored, details slot silently dropped, buildDefaultPageSchema tabs wins over details, sys_user_detail details override","carrier: 承接者:无 · noted, not filed: scripts/i18n-bundle-surface.mjs flagsFromDocstring drops a documented --no-objects-only. Measured: a docstring with --no-objects-only harvests [--locales, --fill, --no-metadata-forms, --source-hashes, --out], so check:i18n would run objects-only against a stack-shaped bundle. Dormant: no extract config documents that spelling (zero producers)",
"carrier: 承接者:无 · noted, not filed: sys_approval_request declares no fieldGroups, so the Details tab's record:details renders the Target / State / System fields in the ungrouped bucket. Pre-existing, and os validate already reports it (20 field-group-undeclared warnings at objects[0])"
]
}
objectstack-fleet commentedon Oct 10, 2026
ACCEPT — PR #22567 at
8f30e97c, pending CIdomain:servicesseat 2 ·session_01WYYhVJ78u7PhwFViWo1EmQ· read on GitHub 2026-10-10T01:30ZThread-read: 6092186396
Checked on GitHub and in the diff, not from the report:
main; line 1Fixes #22473, line 2Clause-②: no; no other closing keyword (approvals B1 (child of objectui#2763): thesys_approval_requestdetail page on the standard record renderer, composed from A1's data source, A2's record card, A3's progress widget, the action timeline and DeclaredActionsBar objectui#12045 is named in plain words); assigneeos-elon-musk. 15 files, +749 / −24, all insidepackages/plugins/plugin-approvalsplus the changeset. Nopackages/spec, noplatform-objects, nocontent/docs.check-governed-merges: not governed.sys-approval-request.page.ts, registered byapprovals-plugin.ts's manifestpages, asplugin-authshipssys_user's):kind: 'slotted',isDefault: true, forsys_approval_request.actions: onerecord:approval_decisionnode, with noproperties(the type's props row is empty and strict).highlights: the object'shighlightFieldswithoutrecord_id. This is the seat's choice on the ui seat's question (6081889955). The object's ownhighlightFields(sys-approval-request.object.ts:57) is untouched.tabs: Details (record:details), then Timeline (sys_approval_actionrelated list onrequest_id, newest first).discussion: []; no right-rail slot.tabsrather thandetailsis the dev's measured reason, not a deviation: objectui's synthesizer builds the tab strip fromtabswhen both are present. The cross-repo consequence of that rule for other pages is filed separately (below).approval_comment: a ninth declared action,type: 'api',POST /api/v1/approvals/requests/{id}/comment.commentrequired textarea;attachmentsoptional multiple file.visible=can_actOR (status == "pending"ANDis_submitter). Read against the route (approval-service.tscomment, about:4821):loadPendingRowrefuses any status butpending, then admits the submitter or a slot taker.attachViewerscomputescan_actas pending ANDheldSlot(about:7027), andis_submitterwith no status test (about:7028). So the predicate's addedpendingtest is the route's own.actorId) are narrower and never produced by the action.pendingtest dropped) red exactly their rows.approval_commentleaves went through the repo's tooling (check-i18n-bundles --write) into the four generated bundles.translations/pages.ts, with recorded source digests read bywithSourceFallback's 3rd argument. Falsified assumption 4, accepted: this set's extract is objects-only, andplatform-objectshand-authors itspages.*the same way.untranslated declared strings grew 0 -> 1.os validatepasses withUI: 1 Pagesand 0 page findings; a misspelled type is refused withcomponent-type-unknown.GET /api/v1/meta/page/sys_approval_request_detailanswers 200 with the slot map.visibleis false and the route answers 409.index.tsis unchanged, andexportshas only".". The page,pages.tsand its types are module-internal. A new page and a declared action are metadata this plugin serves through declared spec shapes, not a change to an accepted input set or an exported type. SoClause-②: no, and no contract-review record is owed (the seat's posture since the maintainer's word on [PM seat] domain:services · seat 2 — 🟢 zhuangjianguo · session_01FzQy26KznSa5j2gnf3AzsE #21118: such a record would now wait for the director seat).minor(a feature). Every sentence matches the page, the action and the bundles, including "nothing new is exported from the package entry".Out-of-scope findings:
detailsslot whentabsis authored too, andplatform-objects'sys-user.page.tsauthors both → filed as ui(pages): a slotted page that authors bothdetailsandtabsloses itsdetailsslot in the console —sys_user_detailauthors both, so its sections andhideFields(incl.ban_reason) never apply #22568.carrier: none,i18n-bundle-surface.mjs'sflagsFromDocstringdrops a documented--no-objects-only(zero producers) → Acceptance notes, not filed.carrier: none,sys_approval_requestdeclares nofieldGroups(pre-existing;os validatealready warns) → Acceptance notes, not filed.Owed before landing: every check green on
8f30e97c.At landing (
Fixes): the seat confirms the card closed, clearspm:dispatchedand the assignee, and points objectstack-ai/objectui#12045's page-mount step at the landed page.objectstack-fleet commentedon Oct 10, 2026
os-dev-report
{
"issue": 22473,
"status": "done",
"branch": "claude/issue-22473-approval-request-page",
"pr": "#22567",
"session": "session_01WYYhVJ78u7PhwFViWo1EmQ — this run's harness-stamped id (subagent: the parent's, per the Claude-Session line)",
"premise_still_valid": true,
"summary": "Patch round 1. New head 69fef50 (pushed; git ls-remote agrees). Commits: 8ec26f0 (the dogfood pin), 9216081 (the docs line), and 69fef50 (a merge of origin/main fbb065f, which moved 2 commits). (1) packages/qa/dogfood/test/approval-override-composite-pin.dogfood.test.ts. The served-action count goes 8 → 9. approval_comment joins the presence/dialect loop, and its served visible must evaluate false for the override-only viewer. 'Exactly the four' is now literal: every served action is evaluated, and the true set must equal the four override levers. The route half is asserted too: the same admin's POST /approvals/requests/ID/comment answers 403 with code FORBIDDEN, and GET .../actions holds no comment row afterwards. (2) content/docs/automation/approvals.mdx, the 'Acting on requests in the console' list, one paragraph. Before: '
approval_recall,approval_resubmit. The console's generic action runtime'. After: 'approval_recall,approval_resubmit,approval_comment.approval_commentis the thread reply: a pending approver sees it, and so does the submitter while the request ispending; it has no admin-override arm. The console's generic action runtime'. No other line on the page contradicts the diff. Checked: no count of actions; the routes table's comment row still reads 'participant · Free-form reply…'; the visibility paragraph does not name the reply. Nothing else was touched. No source outside plugin-approvals changed. The PR body (written once, at create) is now stale in one sentence, and the seat writes it: 'Nothing changes inpackages/spec,platform-objects, any other package, orcontent/docs.' becomes 'Nothing changes inpackages/specorplatform-objects. Outside the plugin, this PR touches one test (packages/qa/dogfood/test/approval-override-composite-pin.dogfood.test.ts, which pins this object's served actions) and one docs paragraph (content/docs/automation/approvals.mdx, the list of declared actions).' Its Tests and Gates sections cite 8f30e97; this round's readings are below. CI: on the old head 8f30e97, the real red was Dogfood Regression Gate (1/3) (aggregate: Dogfood Regression Gate). The Test Core / TypeScript Type Check aggregates read failure only because their shards were cancelled by the new push. CI on 69fef50 is in_progress, and this report does not wait on it.","tests": "All at HEAD 69fef50. The closure was rebuilt after the merge: turbo 71/71 tasks, VERDICT command-exit 0. Dogfood, every file under packages/qa/dogfood/test that names approvals (23 files, listed by git grep -il approval), through os-verify-lock, verbose: Test Files 23 passed (23), Tests 282 passed (282), VERDICT command-exit 0. Among them: '✓ test/approval-override-composite-pin.dogfood.test.ts > approval override composite (#16679) > serves an override-only viewer whose CEL-evaluated actions admit exactly the four override levers 3564ms'. The pre-merge run at 8ec26f0 gave the same 23/282. pnpm --filter @objectstack/dogfood typecheck: exit 0 (VERDICT command-exit 0), and the pin is in that tsc program (--listFiles count 1). plugin-approvals: pnpm --filter @objectstack/plugin-approvals exec vitest run --maxWorkers=2 gave Test Files 65 passed (65), Tests 939 passed (939), VERDICT command-exit 0. Gates: dispatch-gates --commands on 69fef50 derived 94 (the 65 from round 1 plus 29 docs families: check:doc-frontmatter, check-doc-route-spelling, check-docs-section-name, check-section-landing-index, lint check:doc-formula-expressions / check:doc-security-posture, spec check:docs / check:empty-state / check:liveness / check:skill-examples / check:strictness-ledger / check:variant-docs / check:yaml-examples, check:corpus-claim-drift, check:doc-anchors, check:docs-audit-scope, check:docs-redirects, check:docs-single-h1, check:docs-spec-enumerations, check:docs-transcript-drift, check:published-readme-links, check:react-page-adapter-contract, check:role-word, check:skill-identifier-liveness, check:vendor-version-stamps). check:doc-authoring is in the 94. All 94 plus pnpm check:i18n-coverage exited 0. --ran reconciliation: 'Run reconciliation — 94 derived, 94 run, 0 NOT-MEASURED, 0 UNRUN' and 'a DERIVED zero — all 94 recorded an exit code and none of them is 3'. Samples: spec check:docs '225 generated files in sync'; check:doc-anchors '475 internal #fragment link(s) … all resolve'. NOT MEASURED: the docs-site build (apps/docs) is not in the derivation and was not run locally; CI owns it. Lint narrowed: eslint --no-inline-config --format json on the changed pin gave 1 file, 0 errors, 0 warnings. The config enables no type-aware linting. Not ablated this round: the new dogfood assertions mirror the unit-level parity rows that round 1's ablations turned red (the can_override arm). The dogfood pin's own new rows were not mutated.",
"mcp_calls": "0",
"api_writes": "1 this round — POST /repos//issues/22473/comments (this report, via scripts/pm/post-stamped.mjs and the relay's POST /repos/objectstack-ai/objectstack/dispatches). Also git push of the branch (not REST). No PR body edit: the stale sentence is named in summary for the seat to write.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: 承接者:无 · sweep hit list, for the record (grep for approval_resubmit / approval_request_info / approval_send_back / approval_remind, actions.length, and importers of @objectstack/plugin-approvals outside the plugin). CHANGED: packages/qa/dogfood/test/approval-override-composite-pin.dogfood.test.ts:133 (this round); content/docs/automation/approvals.mdx:612-616 (this round); packages/plugins/plugin-approvals/src/sys-approval-request.object.test.ts (roster, route verbs; round 1); packages/plugins/plugin-approvals/src/action-predicate-sparse-face.test.ts (count 8 → 9; round 1). NOT CHANGED, because the ninth action breaks none of them: packages/plugins/plugin-approvals/src/sys-approval-request-attached-on-read.test.ts derives its population from the object, so it counts nine without an edit; its header prose 'Eight shipped visible predicates' is historical (#22387) and was left alone. packages/spec/src/system/i18n-resolver.test.ts:3152, packages/cli/test/i18n-declared-surface-gate.test.ts and packages/cli/test/i18n-extract-action-description.test.ts are synthetic fixtures with their own action arrays, and none of them reads the real object. docs/qa/platform-checklist/areas/approvals.json:218/246/254 is prose about specific levers, with no enumeration or count. The other 22 approval-touching dogfood files contain no action count (they ran green above). No other pin counts served pages.",
"(carried from round 1, unchanged) class: c · Seam: spec:PageSchema.slots.details → renderer:objectui plugin-detail buildDefaultPageSchema · reach: named real producer packages/platform-objects/src/pages/sys-user.page.ts (authors both slots.details and slots.tabs; tabs wins, so details is silently not drawn) · read from objectui origin/main 20c6d35 source, not driven · dedupe words: slotted page details and tabs both authored, details slot silently dropped",
"(carried) carrier: 承接者:无 · flagsFromDocstring drops a documented --no-objects-only (dormant, zero producers) · noted, not filed",
"(carried) carrier: 承接者:无 · sys_approval_request declares no fieldGroups, so the Details tab renders ungrouped (20 field-group-undeclared warnings, pre-existing) · noted, not filed"
]
}
objectstack-fleet commentedon Oct 10, 2026
ACCEPT carried to
69fef50aafter patch round 1, pending CIdomain:servicesseat 2 ·session_01WYYhVJ78u7PhwFViWo1EmQ· read on GitHub 2026-10-10T02:09ZThread-read: 6092535047
The ACCEPT
6092203390on8f30e97cstands. This addendum covers what patch round 1 changed, read in the diff8f30e97c..921608182;69fef50aadds only a merge ofmain.8f30e97c,Dogfood Regression Gate (1/3)failed atapproval-override-composite-pin.dogfood.test.ts:133(expected 9 to be 8), because the pin countssys_approval_request's served actions andapproval_commentis the ninth. TheTest Core/TypeScript Type Checkaggregates on that head read failure only because the push cancelled their shards.approval_commentjoins the presence and dialect loop, and its servedvisiblemust evaluate false for the override-only viewer;POST …/commentanswers 403FORBIDDEN, and the timeline holds nocommentrow afterwards.plugin-approvalstests were already updated in round 1.sys-approval-request-attached-on-read.test.tsderives its population from the object, so it counts nine with no edit. The cli/spec fixtures carry their own synthetic arrays. 23 approval dogfood files, 282 tests, all passed at69fef50a.content/docs/automation/approvals.mdx's list of declared actions gainsapproval_commentand one sentence: "a pending approver sees it, and so does the submitter while the request ispending; it has no admin-override arm". That matches the predicate. No other line on the page contradicts the diff. The cross-lane edit is declared on [PM seat] domain:devx @ objectstack — 🟢 zhuangjianguo · session_012zwsLMxQBwHUYJgPyrpEvx #6023.content/docs") is corrected by the seat to name the two out-of-plugin files. Lines 1–2 are unchanged.--ranreconciled with 0 NOT-MEASURED. The docs-site build is CI's.Clause-②: no; no published surface moved in this round. Owed before landing: every check green on69fef50a.objectstack-fleet commentedon Oct 10, 2026
Landed: PR #22567 →
5495331fc, a single-parent queue squash; this card closescompleteddomain:servicesseat 2 ·session_01WYYhVJ78u7PhwFViWo1EmQ· 2026-10-10T02:47Z5495331fchas one parent and is an ancestor oforigin/main. It merged 2026-10-10T02:47Z on its first queue entry. 17 files, +797 / −31, the reviewed head69fef50a(patch round 1).Fixes #22473closed this card.origin/main:plugin-approvalsservessys_approval_request_detailthrough its manifestpages: a slotted page, default for every request. It has onerecord:approval_decisionnode, highlights withoutrecord_id, and the Details / Timeline tabs, with no discussion thread. Its label is translated in all four bundles.sys_approval_requestdeclaresapproval_comment, the thread reply, posting to the comment route. Itsvisibleis the route's own admission, with no override arm.content/docs/automation/approvals.mdxlists it (cross-lane declaration on [PM seat] domain:devx @ objectstack — 🟢 zhuangjianguo · session_012zwsLMxQBwHUYJgPyrpEvx #6023).6092203390(on8f30e97c) and its addendum6092550589(on69fef50a).Clause-②: no,minorfor@objectstack/plugin-approvals(no export added), so no contract-review record was owed.sys_approval_requestdetail page on the standard record renderer, composed from A1's data source, A2's record card, A3's progress widget, the action timeline and DeclaredActionsBar objectui#12045's page-mount step can now mount the served page. It is told on its own thread.detailsandtabsloses itsdetailsslot in the console —sys_user_detailauthors both, so its sections andhideFields(incl.ban_reason) never apply #22568 (an authoreddetailsslot is dropped besidetabs;sys_user_detailauthors both), for triage.pm:dispatchedand the assignee are cleared after this note.