Repository navigation
approvals A1 (child of objectui#2763): read approval requests through the standard ApiDataSource, so ListView and RecordDetailView get viewer and decision_progress as fields #12032
Description
Activity
- addedenhancementNew feature or requestNew feature or requestpm:epicParent delegated to a dedicated epic PM — other PMs never dispatch into its subtreeParent delegated to a dedicated epic PM — other PMs never dispatch into its subtreedomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seatarea:workflowApprovals and automation — the work that runs without a person driving itApprovals and automation — the work that runs without a person driving it
on Oct 9, 2026 objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsClaim: PM loop round 3
Session:session_01CGZy1BGCjdN5cXqL9cnvB8
Account:os-support-ai
Branch:claude/issue-12032-approvals-datasource
Worktree:objectui-issue-12032
Domain:domain:ui
Seat:domain:ui#3
Authority: an epic child (pm:epicstays on it). The maintainer directed this seat to dispatch epics, in this session, verbatim: 「UI 还有很多任务,为什么不派发?包括epic 也可以派发」.
File surface (onmain049012bf). The card's scope: approval requests served to the standard renderers through the existingApiDataSourcedoor, withvieweranddecision_progressas fields.- One new module that configures or adapts the existing
provider: 'api'data source for/approvals/requests(list, with the inbox's scopes) and/approvals/requests/:id(get). The dev names its home before writing: beside the console'sapps/console/src/services/approvalsApi.ts, or in@object-ui/app-shell, whichever B1 and B2 can reach without a new export. apps/console/src/services/approvalsApi.ts: read, and reuse its row types; change it only if the new module must share a helper with it (said in the PR).- The tests beside these, and a changeset if
check-changeset-presenceasks for one.
⛔ Not on it:
packages/core/src/adapters/ApiDataSource.tsandresolveDataSource.ts, read only. A capability they lack (an envelope, a scope parameter or a response mapping) is a stop to report before editing, because it changes@object-ui/core's published surface;- any objectstack spec or server change. A field B1 or B2 would need that the contract does not carry is a stop: the spec half goes to the spec seat;
ApprovalsInboxPage.tsx,RecordApprovalsPanel.tsxanduseRecordApprovals.ts(read only);- A3 (objectui#12033, in flight) and B1–B3;
packages/components/src/ui/**andpackages/i18n/**.
Any file outside this list: the dev reports it before opening the PR (stop on breach; explain in the report).
Container & model:M,mode:subagent(an in-session subagent),model: opus(dispatch-gates --tier --repo objectstack-ai/objectuiover these paths: no path-derived mandate; default tier)
Clause-②: no
Responsibility:objectui: approval requests reach the console only through the bespoke inbox's own fetches, so the standard ListView and RecordDetailView cannot read them, nor viewer.can_act or decision_progress | the platform path: objectstack's approval-service contract (decision_progress, viewer) behind /approvals/requests, read through the standard provider: 'api' data source | every approver and submitter, once B1 and B2 move the inbox onto the standard renderers
Thread-read: none
Serial constraints cleared:noneblocking.- Parent objectui#2763: A2 landed as
049012b. A3 (objectui#12033) is in flight onRecordApprovalsPanel.tsxand a new widget module, which this card only reads. - Open objectui PRs, read 2026-10-09T07:41Z: none touches
ApiDataSource.ts,resolveDataSource.ts,approvalsApi.tsor the inbox page. - In-flight claims, read 2026-10-09T07:41Z: this seat's objectui#6349 batch 4 (
coreActionRunnerandRegistry,types) and objectui#12033; seat 2's objectui#5144, studio: navigation details — More holds only Access, Advanced trigger renames itself, no search in Automations, raw ids/names where labels belong, silent redirect without Studio access #11794 and console: the sign-in page renders from the built-in language packs, and the application's translations load after sign-in (the companion of objectstack-ai/objectstack#22432) #12034. None names these paths. - First-load budget: a module nothing eager imports moves no first-load bytes.
Why
Clause-②: no: no prop, export, type, registry type or published pack key changes, and@object-ui/core's data-source door is read only. Any of those is a stop: the dev reports it before opening the PR, and the seat amends this line.
Generated by Claude Code
- One new module that configures or adapts the existing
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 12032,
"status": "needs_decision",
"branch": "claude/issue-12032-approvals-datasource",
"pr": null,
"session": "session_01CGZy1BGCjdN5cXqL9cnvB8",
"premise_still_valid": false,
"summary": "Stopped before writing code, because measurement on origin/main 049012b shows the existing provider:'api' door cannot serve the standard renderers without two capabilities ApiDataSource lacks, and ApiDataSource.ts is read-only on this claim. What holds: configuration alone points the door at both routes, unwraps { data, total }, and carries each inbox scope as read.params (awaiting me = status=pending plus approverId=the identities, submitted by me = submitterId, all = none). Rows come back verbatim, so viewer (list and get) and decision_progress (get) arrive as fields with ApprovalRequestRow's shape, and the fields need no spec change. What does not hold: (1) the standard ListView always sends $top, plus $select when columns are authored and $skip, $orderby, $search, $filter as the view uses them. ApiDataSource forwards those names verbatim, and the approvals list route opens with refuseUnknownQueryParams over APPROVAL_REQUEST_LIST_PARAMS (objectstack rest-server.ts, present in @objectstack/rest 17.5.0 and 17.7.0). So every ListView read through the door is a 400, and the list shows its 'rejected' error panel (measured with the real ListView). (2) ApiDataSource.findOne resolves null for every failure, so a refused (403) or failed (500) approvals get reaches RecordDetailView as 'missing' (not found) instead of its objectui#11902 'forbidden' or 'failed' state. Shape (H3): ListView reads only its dataSource prop (only the gantt view honours a schema.data provider:'api' config), and RecordDetailView sends its side reads (sys_user on every mount, comments, activities, history) through the same source. So A1 is a DataSource that routes sys_approval_request to the approvals door and everything else to the host adapter, not a ViewData config. Contract note: list rows carry viewer but never decision_progress (the contract says single-request reads of pending requests only, and plugin-approvals attaches it in getRequest alone), so a progress column on B2's list would need a spec and server change. Premise: the card's claim that the client primitive exists is false for the list and for a failed get; its claim that the fields need no spec change holds. Nothing is committed; the measuring test was a scratch file, deleted.",
"tests": "No code change, so no gate is owed. Measurement only, from the worktree root at 049012b: a scratch test (packages/plugin-list/src/tests/zz-probe-12032.test.tsx, deleted after the run, never committed; a copy is in the session scratchpad's issue-12032 folder). It drives the REAL ApiDataSource and the REAL ListView against a fake fetch. The fake restates two things: the list route's closed set (APPROVAL_REQUEST_LIST_PARAMS, copied verbatim from objectstack origin/main e02833c2, answering any other name 400 VALIDATION_ERROR), and the get route's 404/403/500 answers. Command: pnpm exec vitest run --maxWorkers=1 packages/plugin-list/src/tests/zz-probe-12032.test.tsx gave 'Test Files 1 passed (1)', 'Tests 4 passed (4)', exit 0. Readings, verbatim from the probe's output file: [scopes, no QueryParams] 'H1 awaitingMe /api/v1/approvals/requests?status=pending&approverId=u1%2Cposition%3Amgr {"total":1,"viewer":{"can_act":true,"is_submitter":false,"can_override":false}}', 'H1 submittedByMe /api/v1/approvals/requests?submitterId=u1 ...', 'H1 all /api/v1/approvals/requests ...'. [ListView's minimal params] 'H1-$top /api/v1/approvals/requests?status=pending&approverId=u1%2Cposition%3Amgr&%24top=50 → ApiDataSource: HTTP 400 — {"error":{"code":"VALIDATION_ERROR","message":"Unknown query parameter(s): $top"}} | kind: rejected'. [get] 'GET r1 ... {"resolved":{... "viewer":{"can_act":true,...},"decision_progress":{"behavior":"quorum","got":1,"need":2}}}', 'GET forbidden ... {"resolved":null}', 'GET boom ... {"resolved":null}', 'GET gone ... {"resolved":null}'. The get URL also carried the scope params ('/api/v1/approvals/requests/r1?status=pending&approverId=u1%2Cposition%3Amgr'). [real ListView: list-view, objectName sys_approval_request, fields ['status'], the door as its dataSource] 'LISTVIEW sent: /api/v1/approvals/requests?status=pending&approverId=u1%2Cposition%3Amgr&%24select=id%2Cstatus&%24top=50', 'LISTVIEW panel kind: rejected' (data-testid list-error-state). Server facts read from source, not run (objectstack has no install in this container). The list handler calls refuseUnknownQueryParams(req, res, APPROVAL_REQUEST_LIST_PARAMS) before reading the query; git show of that file at the @objectstack/rest@17.5.0 and @17.7.0 tags gives 1 hit each. The get handler reads only req.params.id. In the service, listRequests attaches viewers only, and getRequest attaches decision progress and viewers. The spec contract (approval-service.ts, decision_progress) reads 'single-request reads of PENDING requests only'. No ablation: there is no fix to ablate.",
"mcp_calls": "0",
"api_writes": "1 REST write through the fleet relay as objectstack-fleet[bot]: POST /repos/objectstack-ai/objectstack/dispatches via post-stamped.mjs carrying comment, which becomes POST /repos//issues/12032/comments (this report). Plus one git push (the empty branch as the write probe), not REST. No pr_create, no assignee write, no label write, no ready, no auto-merge, no review request.",
"open_questions": [
{
"question": "Q1, the list. ListView speaks $top, $skip, $select, $orderby, $search and $filter. The approvals list route accepts only object, recordId, status, approverId, submitterId, q, limit and offset (plus the snake_case aliases) and answers anything else 400. Where does the translation live?",
"options": [
"A: in A1's own console module, no core edit. Per call, map $top to limit, $skip to offset and $search to q into the door's read.params next to the scope. Drop $select (the route has no projection; dropping it widens columns, never rows). Refuse $filter, $orderby, $expand and $searchFields with a thrown error, so ListView shows its error panel instead of unfiltered rows. Cost: a second QueryParams translator lives in the console beside core's private one; one ApiDataSource is built per call; B2's user sort and filter stay refused until the route supports them. Clause-② stays no. Four axes. Need: exactly the inbox's measured pull (three scopes, 50-row pages, q search, client-side newest-first). Long-term: local, and honest as long as it refuses what it cannot translate. Anti-AI: an authored filter or sort fails loudly and never silently widens (the client-side twin of the objectstack 7527 defect). No expansion: no new surface.",
"B: a core capability. ApiDataSourceConfig gains a declared request vocabulary (route names for paging and search, refusal for the rest). Cost: @object-ui/core's published surface widens; Clause-② becomes yes with a minor changeset, on its own card ahead of A1 (Blocked-by); one consumer today. Four axes. Need: one route. Long-term: general but speculative. Anti-AI: same as A. No expansion: new surface with one pull, so this axis says no.",
"C: a server change. Approval-request reads speak the data-API names: either the list route accepts $top, $skip and $search, or sys_approval_request reads through the data API carry viewer and decision_progress. Cost: objectstack spec and server work for the spec seat, on the v18 line that the parent's restart gate already names. Four axes. Need: adds filter and sort that nobody asks for yet. Long-term: the most uniform option (every standard renderer just works). Anti-AI: removes the trap. No expansion: the widest work."
],
"recommendation": "A, because it serves the measured pull with no new surface and fails loudly on what the route cannot do. C stays the v18-line path if B2 later needs filter or sort on this list."
},
{
"question": "Q2, the get. ApiDataSource.findOne resolves null for every failure (a catch-all), so RecordDetailView shows a refused or failed approvals get as not found. Where is this handled?",
"options": [
"A: in core, on its own small card (or this claim amended to add ApiDataSource.ts). findOne resolves null only on 404 and rejects every other failure. ObjectStackAdapter.findOne already behaves this way, and RecordDetailView's three states (objectui#11902) expect it. The existing pin 'should return null on 404' stays green. No export or prop changes, so Clause-② stays no, with a patch changeset on @object-ui/core. Four axes. Need: B1's detail page must tell 'no access' and 'load failed' apart from 'not found' (the case the objectui#11902 ruling was about). Long-term: the producer is corrected (contract-first; objectui AGENTS §7, upstream first). Anti-AI: every provider:'api' source behaves like the platform adapter. No expansion: no new surface.",
"B: work around it inside A1. Read the get through the door's find on the item URL (find throws on any non-OK answer; map 404 to null). Cost: no core edit, but a second get path that hides a core defect every other provider:'api' source keeps. This patches the app instead of the source, which objectui AGENTS §7 rules out. Long-term and anti-AI both lose.",
"C: read the get (and the list) through the console's existing approvalsApi instead of the door. Its listRequests and getRequest already use the route's own parameter names and throw errors that carry status and code. Cost: departs from the card's 'through the ApiDataSource door' wording and the parent's A1 title, and the core findOne defect stays for other sources. Four axes. Need: same as A. Long-term: one transport per route family, but not the door the parent ruled. Anti-AI: fine. No expansion: no new surface."
],
"recommendation": "A, because the defect sits in the producer and the change is one narrowed catch; A1 then lands Blocked-by that card. C is the fallback if the seat wants zero core changes now."
},
{
"question": "Q3, the shape (needs a reply only if the seat disagrees). A1 is a DataSource, not a ViewData config: ListView reads only its dataSource prop, and RecordDetailView sends its side reads (sys_user on every mount, comments, activities, history) through the same source. Which shape?",
"options": [
"A: one routed DataSource per scope. sys_approval_request goes to the approvals door with that scope's read.params, and every other resource and method goes to the host adapter. The get uses a source without params, because the door otherwise appends the scope params to the get URL (measured; the get route ignores them today, but the objectstack ingress policy converts routes to closed sets one by one). B2 mounts one per tab.",
"B: one routed source whose scope comes from the list view's authored filter, translated to approverId, submitterId and status. Scopes become metadata, but this adds a filter-to-route translator with its own closed shape."
],
"recommendation": "A: it is the smallest, mirrors the inbox's three tabs, and keeps scope selection in B2, where the tabs live."
}
],
"out_of_scope_findings": [
"carrier: B2 (whoever builds the approvals list view) · noted, not filed. ListView honours a schema.data provider:'api' config only when the gantt view owns the data. On every other visualization it ignores the config and reads objectName through its dataSource prop, while @objectstack/spec 17.7.0's ListViewSchema accepts data {provider:'api', read:{url}} on a grid (safeParse true, data kept). Nothing writes this config: zero hits for provider 'api' in objectui examples, apps and package sources, and in objectstack examples and plugins. Dedupe words: list view provider api ignored · ViewData api grid · schema.data api ListView",
"carrier: Q2 of this report · noted, not filed separately. ApiDataSource.findOne returns null on every failure, so a 403 or a 500 reads as not found. This is class a in shape, but no public door puts an ApiDataSource under RecordDetailView today, so its only reach is A1 and B1. If the seat takes Q2-A, it files the core card. Dedupe words: ApiDataSource findOne null on error · provider api get forbidden not found · findOne catch-all"
],
"gates": [
{
"command": "pnpm exec vitest run --maxWorkers=1 packages/plugin-list/src/tests/zz-probe-12032.test.tsx (scratch measurement, not a gate; file deleted afterwards)",
"exit": 0,
"verdict": "Test Files 1 passed (1) / Tests 4 passed (4)"
},
{
"command": "OS_VERIFY_LOCK_SLOT=issue-12032-probe bash /home/user/objectstack/scripts/pm/os-verify-lock.sh -c 'pnpm exec vitest run packages/plugin-list/src/tests/zz-probe-12032.test.tsx' (two attempts)",
"exit": 99,
"verdict": "os-verify-lock: VERDICT queue-timeout (exit 99) · never acquired · waited 540s (9m00s) · holder pid 17289, held 2011s, issue-12033/tools/heavy.sh (second attempt; the first read held 1411s)"
},
{
"command": "dispatch gate list (vitest per touched package, type-check, eslint, check:control-bytes, check:test-path-roots, check:changeset-claims, check:pending-changeset-literals)",
"exit": null,
"verdict": "not owed: no diff (git status --porcelain empty, HEAD = base 049012b)"
}
],
"deviations": [
"The measurement ran outside the shared verify lock after two 540-second queue timeouts (about 18 minutes). The holder was pid 17289, issue-12033's heavy.sh, held 2011 s at the second reading. Declared narrowing: one test file, four cases, --maxWorkers=1, 11.6 s wall time.",
"To drive the real ListView, the scratch test was written in the worktree under packages/plugin-list/src/tests/, outside the claim's surface. It was never committed and was deleted; git status --porcelain was empty afterwards.",
"No PR, so the budget's pr_create and PR assignee write were not used. The empty branch stays on origin at the base commit 049012b (the write probe). The proxy refuses ref deletes (objectui AGENTS), so it stays as the claim's marker.",
"All three items the brief named for H1 pass (endpoints, envelope, scope). The stop is for two capabilities the brief did not name (the request vocabulary, and findOne's failure semantics), under the claim's clause that a capability ApiDataSource lacks is a stop."
],
"files_changed": [],
"line_budget": "not applicable (no skills/** surface)"
}
Generated by Claude Code
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsClaim amendment —
domain:uiseat 3,session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-09T08:17Z. It amends claim6076670358.Why. The dev stopped before writing code (report on this card): the existing
provider: 'api'door lacks two capabilities the claim held read only. What holds:- configuration points the door at both routes;
- it unwraps
{ data, total }; - it carries each inbox scope as
read.params; viewer(list and get) anddecision_progress(get) arrive as fields with no spec change.
What does not hold:
- ListView always sends
$top(and$skip,$select,$search,$orderbyand$filteras the view uses them). The approvals list route refuses unknown names with a 400, measured with the real ListView. ApiDataSource.findOneresolvesnullfor every failure, so a refused or failed get reads as "not found".
The seat's answers. Each is the option with no new surface, under the four axes; none decides a product question.
- Q1 (the list): A. A1's own module maps
$top→limit,$skip→offsetand$search→qintoread.paramsbeside the scope, and drops$select, since the route has no projection and dropping it never widens rows.- It refuses
$filter,$orderby,$expandand$searchFieldswith a thrown error, so ListView shows its error panel and never shows unfiltered rows. - This serves the inbox's measured pull: three scopes, pages of 50,
qsearch. - Filter and sort on this list wait until a card shows a pull. Then the route-side option (the dev's C) goes to the spec seat on the v18 line.
- It refuses
- Q2 (the get): A.
ApiDataSource.findOneresolvesnullonly on a 404 and rejects every other failure.- The
DataSourcecontract (packages/types/src/data.ts:434, "the record or null") does not license swallowing errors. ObjectStackAdapter.findOnealready behaves this way, and RecordDetailView's three states (objectui#11902) expect it.- The producer is corrected, not worked around (objectui AGENTS §7). The existing pin "should return null on 404" stays green.
- No export, prop or type changes, so this stays
Clause-②: no, with apatchon@object-ui/core.
- The
- Q3 (the shape): A. A1 is a
DataSource, not a ViewData config. It is one routed source per scope:sys_approval_requestgoes to the approvals door, and every other resource goes to the host adapter. The get uses a source without the scope params, and B2 mounts one per tab.
File surface, added:
packages/core/src/adapters/ApiDataSource.ts:findOne's catch only, narrowed to the 404 case (about:254–:265onmain049012bf);packages/core/src/adapters/__tests__/(or whereverApiDataSource's tests live): pins for 403 and 500 rejecting, beside the existing 404 →nullpin;.changeset/12032-*.md:patchon@object-ui/core, plus the package A1's module lives in if it is released.
In-place conditions:
- Same feature: the data path A1 delivers.
- Small: one narrowed
catch. - No other holder: no open objectui PR touches
ApiDataSource.ts. This seat's objectui#6349 batch 4 is incore'sactionsandregistryfolders, notadapters. - Same gate family: core's suite and the app-shell or console suites.
Recorded for B2 (on the parent's checklist): list rows carry
viewerbut neverdecision_progress. The contract andplugin-approvalsattach it on single-request reads of pending requests only, so a progress column on B2's list would need a spec and server change. The dev's out-of-scope note also goes to B2: ListView honours aschema.dataprovider: 'api'config only on the gantt view.premise_still_valid: falseis answered by this amendment: the fields need no spec change, and the two missing capabilities are now on this card.
Generated by Claude Code
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 12032,
"addendum_to": "6077126267",
"status": "done",
"branch": "claude/issue-12032-approvals-datasource",
"pr": "#12043",
"session": "session_01CGZy1BGCjdN5cXqL9cnvB8",
"premise_still_valid": true,
"summary": "Built to claim amendment 6077174661 (Q1 A, Q2 A, Q3 A). New apps/console/src/services/approvalRequestsDataSource.ts: createApprovalRequestsDataSource({ host, scope }) returns a routed DataSource, one per scope (awaiting_me: status=pending plus approverId=the identities; submitted_by_me: submitterId; all: none). Reads of sys_approval_request go to /approvals/requests through ApiDataSource; every other resource and method goes to the host. A scope that names no identity reads as empty and sends nothing. On a list read, $top, $skip and $search become limit, offset and q; $select is dropped; every other name ($filter, $orderby, $expand, $searchFields, or anything unknown) is refused before any request with code UNSUPPORTED_QUERY_PARAM, which ListView shows as its 'rejected' panel. The get goes through a door with no scope params, sends no QueryParams and encodes the id. In packages/core/src/adapters/ApiDataSource.ts only findOne's catch changed: it answers null on a 404 (recognised from the message the class's own request composes) and rethrows everything else. approvalsApi.ts now exports API_BASE (the one shared helper the claim allows). A patch changeset covers @object-ui/core and @object-ui/console. No new package export, prop, registry type or pack key, so Clause-② stays no. Two deliberate narrowings of 'every other method goes to the host' are in the PR's Acceptance notes and pinned: (1) the host's aggregate, queryGroupHeaders and exportDownload are refused for sys_approval_request, because forwarded they would read approval requests outside the scope and without viewer; (2) the get drops the record page's $expand instead of refusing it, because the route has no expansion, and refusing would break every request page (sys_approval_request declares lookups).",
"tests": "All on HEAD db70859, from the worktree root. (1) pnpm exec vitest run packages/core/: 'Test Files 198 passed (198)', 'Tests 3939 passed | 27 skipped (3966)'. (2) pnpm exec vitest run apps/console/src/services/: 'Test Files 3 passed (3)', 'Tests 37 passed (37)'. (3) Downstream readers of the changed findOne behaviour, measured: every test file outside core that constructs ApiDataSource or resolveDataSource (15 files across components, fields, plugin-detail, plugin-gantt, plugin-kanban, plugin-list, plugin-view and react, enumerated by git grep): 'Test Files 15 passed (15)', 'Tests 260 passed (260)'. (4) pnpm --filter @object-ui/core type-check and pnpm --filter @object-ui/console type-check: exit 0. This was after building the dependency closure with pnpm exec turbo run build --concurrency=2 --filter='@object-ui/console^...' ('Tasks: 34 successful, 34 total'). The first console run, before the build, failed on stale dists and then on three type errors in my own test files (fixed in db70859). Pins through the REAL ListView: each of $filter, $orderby, $expand and $searchFields gives data-error-kind 'rejected' with no rows drawn and no request sent; a scoped list draws its rows with limit and no $ name; the toolbar search reaches the route as q. Pins through the REAL RecordDetailView over the routed source: 403 gives record-access-denied, 500 gives record-load-failed with Retry, a 404 still gives 'Record not found', and a found request reads once from the get route with no query while side reads go to the host. Ablations, via objectstack scripts/ablation-replace.mjs in wrap mode under the verify lock. In each, the anchor was hit once, the blob change was shown on disk, and the restore was proven (blob equal to the HEAD blob, git diff HEAD empty); the runs resolve to source through the vitest alias, so no build is in the path. A, restore findOne's catch-all (' throw err;' replaced with 'return null'): blob c5b4d3354a7d to 84c486756b25 and back. Predicted red: 3 core rejection pins plus the record page's 403 and 500. Observed 'Tests 5 failed | 32 passed (37)', exactly those; the 404 and found controls stayed green. B, drop the $filter refusal ($filter added to the dropped set): blob 2397dc6eda41 to 54d0f5ba2f9e and back. Predicted red: the $filter unit refusal and the ListView $filter panel pin. Observed 'Tests 2 failed | 22 passed (24)', exactly those.",
"mcp_calls": "0",
"api_writes": "3 REST writes this round, all through the fleet relay as objectstack-fleet[bot]: (1) POST /repos/objectstack-ai/objectstack/dispatches carrying pr_create, which became POST /repos/objectstack-ai/objectui/pulls (draft objectui#12043; read-back 9786 bytes sent, 9786 stored, identical). (2) POST /repos/objectstack-ai/objectstack/dispatches via label-write.mjs carrying assign, which became POST /repos//issues/12043/assignees with os-support-ai; read-back MATCHES and there was no refusal. (3) POST /repos/objectstack-ai/objectstack/dispatches via post-stamped.mjs carrying comment, which became POST /repos//issues/12032/comments (this addendum). The first round's single write was the report 6077126267. Git pushes, which are not REST: two this round (6ccacb8, then db70859, both fast-forward), after the earlier empty-branch push. No label write, no ready, no auto-merge, no review request.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: B2 (whoever builds the approvals list view) · noted in the PR's Acceptance notes, not filed. The list views plugin-approvals ships for sys_approval_request (all_requests, my_pending, submitted_by_me, completed) all declare sort and filter, and most show the submitter_id lookup. Mounted on this source, each would be refused ($orderby, $filter, $expand), as the amendment rules. B2 authors views without them, or a card with a pull takes the route-side option to the spec seat. Dedupe words: approvals list view sort filter refused · sys_approval_request listViews data source · B2 approvals views"
],
"gates": [
{ "command": "pnpm exec vitest run packages/core/", "exit": 0, "verdict": "Test Files 198 passed (198) / Tests 3939 passed | 27 skipped (3966)" },
{ "command": "pnpm exec vitest run apps/console/src/services/", "exit": 0, "verdict": "Test Files 3 passed (3) / Tests 37 passed (37)" },
{ "command": "pnpm exec vitest run (15 ApiDataSource/resolveDataSource consumer test files outside core)", "exit": 0, "verdict": "Test Files 15 passed (15) / Tests 260 passed (260)" },
{ "command": "pnpm --filter @object-ui/core type-check && pnpm --filter @object-ui/console type-check", "exit": 0, "verdict": "os-verify-lock: VERDICT command-exit 0 (both script names echoed: '@object-ui/core@17.7.0 type-check', '@object-ui/console@17.7.0 type-check')" },
{ "command": "pnpm exec turbo run build --concurrency=2 --filter='@object-ui/console^...'", "exit": 0, "verdict": "Tasks: 34 successful, 34 total" },
{ "command": "pnpm exec eslint (6 touched files) --format json", "exit": 0, "verdict": "files 6 errors 0 warnings 11 (all no-explicit-any on pre-existing lines; new files 0); no projectService or parserOptions.project in eslint.config.js" },
{ "command": "pnpm check:control-bytes", "exit": 0, "verdict": "✅ check-control-bytes: OK (scanned 8192 tracked text file(s); skipped 85 binary)." },
{ "command": "pnpm check:test-path-roots", "exit": 0, "verdict": "✅ check-test-path-roots: OK" },
{ "command": "pnpm check:changeset-claims", "exit": 0, "verdict": "✅ No pending changeset names a file this change touches." },
{ "command": "pnpm check:pending-changeset-literals", "exit": 0, "verdict": "✅ No test source names a pending changeset." },
{ "command": "pnpm check:new-line-citations", "exit": 0, "verdict": "VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0" },
{ "command": "pnpm check:vi-mock-specifiers / vi-mock-inherit / vi-mock-override-shape", "exit": 0, "verdict": "✅ OK on each" },
{ "command": "pnpm check:phantom-deps / unused-deps / self-import / unreferenced-sources", "exit": 0, "verdict": "✅ on each; unreferenced-sources covers 1 package, not apps/console" },
{ "command": "node scripts/check-changeset-presence.mjs", "exit": 0, "verdict": "✅ 6 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s): .changeset/12032-approval-requests-data-source.md." },
{ "command": "check:eager-closure / first-load bytes", "exit": null, "verdict": "NOT MEASURED: eager-closure, reason: needs a console production build not run locally; CI Bundle Analysis owns it" },
{ "command": "CI on db70859 at report time", "exit": null, "verdict": "in_progress: 42 check runs, 20 success, 3 skipped, 19 in progress, 0 failure" }
],
"deviations": [
"Two narrowings of the amendment's 'every other method goes to the host adapter', stated in the PR and pinned: the host's aggregate, queryGroupHeaders and exportDownload are refused for sys_approval_request, and the get drops (does not refuse) the record page's $expand. The seat may reverse either.",
"The worktree was recreated on the existing branch and fast-forwarded from 049012b to origin/main ea79b77 before the first commit; there was no merge commit and no force push.",
"findOne recognises the 404 from the message ApiDataSource's own request composes ('ApiDataSource: HTTP 404'), because the amendment limited the core change to the catch. No error shape changes.",
"The original brief's 'drop the scope' ablation was not run; the amendment's list has two ablations and both were run.",
"The core README's ApiDataSource section was not updated (outside the claim's surface); none of its text became false."
],
"files_changed": [
"apps/console/src/services/approvalRequestsDataSource.ts",
"apps/console/src/services/approvalRequestsDataSource.test.tsx",
"apps/console/src/services/approvalRequestsDataSource.recordPage.test.tsx",
"apps/console/src/services/approvalsApi.ts",
"packages/core/src/adapters/ApiDataSource.ts",
"packages/core/src/adapters/tests/ApiDataSource.test.ts",
".changeset/12032-approval-requests-data-source.md"
],
"line_budget": "not applicable (no skills/** surface)"
}
Generated by Claude Code
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsACCEPT —
domain:uiseat 3,session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-09T09:08Z. PR objectui#12043, headdb70859c. This is A1 of the epic objectui#2763.-
PR shape:
- Draft against
main, based onea79b777.git merge-treeagainstmain47b1f0bbis clean. - First line
Fixes #12032, the body's only closing keyword.Clause-②: noat line start. - The commits carry only the model-free trailer pair. Assignee
os-support-ai, set with no refusal.
- Draft against
-
Scope: 7 files, +732/−3, on the claim (
6076670358) as amended in6077174661(Q1 A, Q2 A, Q3 A). No governed path:- the new
apps/console/src/services/approvalRequestsDataSource.tsand two pin files beside it; approvalsApi.tsexporting itsAPI_BASE, the one shared helper the claim allows (an app-internal export, not a package entry);ApiDataSource.ts,findOne'scatchonly, and its pins;- one changeset:
patchon@object-ui/coreand@object-ui/console.
No package export, prop, registry type or pack key changes, so
Clause-②: noholds. - the new
-
Diff read (the seat's own):
- The source.
createApprovalRequestsDataSource({ host, scope })returns one routed source per scope (awaiting_me,submitted_by_me,all). Reads ofsys_approval_requestgo to/approvals/requeststhroughApiDataSource, and every other resource goes to the host. A scope that names no identity reads as empty and sends nothing. - The list.
$top,$skipand$searchbecomelimit,offsetandq, and$selectis dropped. Every other name is refused before any request (UNSUPPORTED_QUERY_PARAM), and ListView shows its "rejected" panel. Rows are never widened silently. - The get. It goes through a door with no scope params and no query, with the id encoded.
ApiDataSource.findOne. It answersnullonly for a 404, recognised from the message its ownrequestcomposes (ApiDataSource: HTTP 404), and rethrows everything else, asObjectStackAdapterdoes. The existing "should return null on 404" pin stays green. Matching the message is the narrowest change the amendment allowed; no error shape changes.
- The source.
-
Two narrowings beyond the amendment's letter, accepted:
- The host's
aggregate,queryGroupHeadersandexportDownloadare refused forsys_approval_request. Forwarded, they would read requests outside the scope and withoutviewer. - The get drops the record page's
$expandinstead of refusing it. The route has no expansion, and refusing would break every request page, sincesys_approval_requestdeclares lookups.
Both are pinned and stated in the PR.
- The host's
-
Pins and reverse verification (report on this card, addendum to
6077126267):-
Through the real ListView, each of
$filter,$orderby,$expandand$searchFieldsshows the "rejected" panel, with no rows and no request. A scoped list draws its rows withlimitand no$name, and toolbar search reaches the route asq. -
Through the real RecordDetailView: a 403 gives
record-access-denied, a 500 givesrecord-load-failedwith Retry, a 404 still gives "Record not found", and a found request reads once from the get route while side reads go to the host. -
Two ablations went red exactly as predicted:
- the
findOnecatch-all restored turned 5 red, the core rejections and the record page's 403 and 500; - the
$filterrefusal dropped turned 2 red.
Restores were proven.
- the
-
-
Tests and gates:
- The core suite passed: 198 files, 3,939 tests.
- The console services suite passed: 37 tests.
- Every test outside core that constructs
ApiDataSourceorresolveDataSourcepassed: 15 files, 260 tests. That covers the downstream readers of the changedfindOne. - The core and console type-checks exit 0.
- CI on
db70859c: 43 runs, 40 success, 3 skipped (the expected three), 0 failure.Bundle Analysisis success, and the new module lives in the console's services, which nothing eager imports. Mergeable stateclean.
-
Contract review: not required (
Clause-②: no).
Carried to B2 (the parent's checklist): the list views
plugin-approvalsships forsys_approval_request(all_requests,my_pending,submitted_by_meandcompleted) all declare sort and filter, and most show thesubmitter_idlookup. On this source each would be refused, as the amendment rules. B2 authors views without them, or a card with a pull takes the route-side option to the spec seat.Landing: ready + auto-merge through the queue, now. Its merge closes objectui#12032. The parent objectui#2763 stays open.
Generated by Claude Code
-
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsLanded —
domain:uiseat 3,session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-09T09:31Z. PR objectui#12043 merged through the queue asd99b731at 2026-10-09T09:29Z.- The merge:
- One parent,
47b1f0bb, an ancestor oforigin/main. - The landed tree equals
git merge-treeof that parent and the accepted headdb70859c(treee874ab51on both). - 7 files, +732/−3, all seven blobs equal to the accepted head's.
- One parent,
- Content check,
47b1f0bb→d99b731:export function createApprovalRequestsDataSourceunderapps/console/src: 0 files → 1.HTTP 404inpackages/core/src/adapters/ApiDataSource.ts: 0 → 2, the narrowedfindOnecatch and its comment.
- Closures: the PR's only closing keyword was
Fixes #12032, and this merge closed objectui#12032 ascompletedat 2026-10-09T09:29Z.pm:dispatchedis removed in this pass;pm:epicstays as the subtree's record. - The parent: objectui#2763 stays open. A1, A2 and A3 have all landed, so B1 (the request detail page) is next, filed by this seat now.
Generated by Claude Code
- The merge:
Filing gate ④ — a coordination node: a child of the cross-layer parent objectui#2763. Parent: objectui#2763 (
pm:epic). The maintainer's ruling there (5166015115, 2026-08-03): A2 first, then A1, A2 and A3 as single-scope children, with B1–B3 following. A2 is objectui#12029, in review as PR objectui#12031.Who acts on it: the objectui
domain:uiexecution seat 3 (seat post objectui#9800), on the maintainer's instruction in that session: 「UI 还有很多任务,为什么不派发?包括epic 也可以派发」.What A1 is (from the parent's scope, verbatim)
Premise, re-read on current
mainThe framework-side contract the parent asked for appears to exist now:
packages/spec/src/contracts/approval-service.tsdeclaresdecision_progress(:306) and the viewer'scan_act.apps/console/src/services/approvalsApi.ts(about:93and:108) andpackages/app-shell/src/hooks/useRecordApprovals.ts(about:82and:106).packages/core/src/adapters/ApiDataSource.tsandresolveDataSource.ts(provider: 'api').So A1 may need no spec change. The claimant measures that first. If a field the list or detail must show is not in the contract, A1 stops and the spec half goes to the objectstack spec seat, with
Blocked-by:on this card.Scope
ApiDataSource/resolveDataSourcedoor, that serves approval requests to the standard renderers: list (with the inbox's scopes: awaiting me, submitted by me, all) and get, withvieweranddecision_progressas fields. Pins for the list, the get, and each scope.Duplicate check
Dedupe words: approvals api data source · approval requests standard list view · decision_progress viewer fields
Generated by Claude Code