Repository navigation
Description
Round R3 of the #9296 QA wave. One selector, one run, one issue. Sibling rounds: #9351 (records-forms).
Environment fingerprint
| reading | value |
|---|---|
| framework sha | e4e5c6e3c608b1b807c83a0d5b734f213eb1a1dd (2026-08-17 11:29:50Z) |
.objectui-sha (pin) |
665661ab093263f39f2e660a295ea615dbcee35a |
| console dist stamp | 665661ab093263f39f2e660a295ea615dbcee35a — stamp == pin, pnpm check:console-sha exits 0 |
| app | showcase, --seed-admin (admin@objectos.ai / admin123) |
| port / db | 3202 · file:/tmp/qa-r3a/ap.db (fresh, rm -f before boot) |
| runner | R3 runner session, 2026-08-17 |
Spec-forward step: identical to #9351 — PR #9309 has not merged, and its diff does not contain approvals.json (all its area-file changes are +N −0 on other areas), so this area's spec is byte-identical either way. Nothing was committed.
Scope
node scripts/checklist-select.mjs area:approvals --json → 11 runnable items (the resolver excludes 3 blocked items by design).
Result — this round is thin, and says so
0 PASS · 1 PARTIAL · 0 FAIL · 10 NOT-RUN · 3 BLOCKED(fixture, pre-existing).
Only decision-only-via-service was driven. The round ran out of budget after the records-forms area (#9351), which consumed most of the session — including ~21 minutes of unavoidable cold-tree building. No approvals item is being reported as passing on the strength of anything but the evidence quoted below, and the ten untouched items are not-run, not pass. This area needs a dedicated round.
Per-clause verdicts
approvals.decision-only-via-service rev 1 (P1) — partial (3 pass, 1 partial)
The item's automated pin was run first (RUNNER rule 6):
pnpm --filter @objectstack/service-automation exec vitest run src/resume-authority-gate.test.ts
→ Test Files 1 passed (1) · Tests 22 passed (22)
| # | oracle | verdict | evidence (one line) |
|---|---|---|---|
| 0 | api | pass | showcase_invoice_signoff run run_d424743b… sits paused at dual_signoff. POST /api/v1/automation/showcase_invoice_signoff/runs/<runId>/resume with {} → 403; the same route with {branchLabel:'approve'} → 403. Neither 2xx nor a 404 that would mask the gate — the run demonstrably exists (it is readable and later resumes) |
| 1 | api | pass | after both forged POSTs: the run re-reads status: paused, last step still dual_signoff; GET /approvals/requests/<id>/actions count is 1 before and 1 after, byte-identical — the forged attempts recorded no decision and changed nothing |
| 2 | api | pass | the approvals door works: POST /approvals/requests/<id>/approve {comment:'qa approve 1'} → 200 satisfied the slate; request re-reads status: approved; the run transitions paused → completed with steps [start, dual_signoff, notify_cleared] — the notify_cleared step executed, exactly as the clause requires |
| 3 | api | partial | the discriminating half is proven — the same generic route on a screen node is NOT dead: POST /automation/showcase_reassign_wizard/runs/<runId>/resume {inputs:{new_assignee:'QA Assignee'}} → 200, and the run advanced start → collect → apply. The clause also asks the run to complete; it ended failed at apply, because I supplied the free-text string 'QA Assignee' to a step that writes showcase_task.assignee (a user reference). That is my input's fault, not the gate's, so the clause is partial rather than pass and definitely not a fail |
Worth recording for the next runner, since it sharpens the gate's shape: an empty-input resume on the screen node returns 400 VALIDATION_ERROR — Screen field "new_assignee" is required — declared fields: 'new_assignee'. So the three responses are cleanly separated: 403 = approval-node authority gate, 400 = the screen node's own input validator (route reached the node), 200 = valid resume. That 400/403 split is independent confirmation that the 403 is node-scoped authority and not a broken route.
Not-run items (10)
per-group-signoff (rev 4) · inbox-metadata-actions (rev 3) · notification-deep-link (rev 4) · decision-action-matrix (rev 2) · dynamic-approver-routing (rev 2) · ooo-delegation-reroute (rev 2) · record-page-decisions (rev 1) · account-app-entry (rev 1, P0) · setup-nav-entry (rev 1) · pending-count-surfaces (rev 1)
account-app-entry is a P0 and was not run. It should lead the next round.
Groundwork done that the next round can reuse
Not verdicts — just state observed on this boot, so the next runner does not re-derive it:
- Three requests are seeded pending on a fresh boot:
showcase_expense_report/dual_signoff(pending slate = two distinct users, the admin andusr_showcase_auditor_demo— the per-group fixture theper-group-signoffitem needs),showcase_expense_report/committee(quorum, slate collapses onto the admin ×3 — the shapequorum-m-of-nis blocked on), andshowcase_invoice/dual_signoff(consumed by this round's decision). decision-action-matrixneeds a fresh budget request; the trigger isbudget != previous.budgetand above the threshold. My first attempt silently did nothing because the seededMobile Appproject already hadbudget: 200000— patching it to the same value is not a change. Bumping to350000openedareq_ca40f5da…atmanager_review. Use a project whose budget you have first read.- The list projection
GET /approvals/requests?status=pendingdoes not carrypending_approver_groups(it readsundefinedthere); the per-group tallies the item asks for need the single-request readGET /approvals/requests/:id.
Fixture gaps (pre-existing, resolver-excluded — re-confirmed, unchanged)
| item | blocked.by |
reason recorded on the item |
|---|---|---|
quorum-m-of-n |
fixture | #3358 — quorum slate collapses onto the admin; showcase design call pending. Re-confirmed on this boot: the committee request's slate is the same user three times |
viewer-gating-submitter-side |
fixture | #3358 — needs a request routed to a position the viewing submitter does not hold |
sla-escalation |
fixture | hour-granular SLA needs a clock-control / runEscalations timing harness (timeoutHours min 1, sweep on a real-time interval) — no stock-fixture way to advance past the deadline in-session |
Additionally, and blocking several of the not-run items: no second real login was provisioned this round. per-group-signoff, ooo-delegation-reroute and account-app-entry all need one (the seeded personas are sys_user rows without better-auth accounts). The items' own knownGaps record the recipe — the credential account's issuer must equal local:credential (packages/plugins/plugin-auth/src/backfill-account-issuer.ts), or sign-in fails INVALID_EMAIL_OR_PASSWORD behind a misleading "User not found" warn.
Browser note
Every remaining approvals item is browser or mixed. Chromium is launchable in this container with an explicit path (/opt/pw-browsers/chromium-1194/chrome-linux/chrome, verified working against the live server), but the repo's pinned @playwright/test@1.62.1 resolves revision 1234 and fails instantly — see #9351 for the full evidence. So the next round can drive these; it just must not rely on the repo's own playwright config.
Links back to #9296.
Activity
os-zhuang commentedon Aug 18, 2026
Close-out — superseded. Wave #9296, executed under #9480. This re-judges nothing.
Supersede check. This round drove exactly one item,
decision-only-via-service(P1), and it carries anautomated.ref: Tier-1 #9401 re-covered it against the same pin —resume-authority-gate22/22, scored pass (raw resume refused, owning service admitted, ungated pause still resumable, node-scoped across subflow/map/restart). This record's clause-3partialwas owed to the runner's own free-text input to a user-reference field, which it correctly declined to score as a gate failure; Tier-1's verdict stands over it.The two items this record named as must-lead-the-next-round were both run.
account-app-entry(P0) — Tier-2B #9453, pass.per-group-signoff— Tier-2B #9453, partial, with c3 still blocked for the reason this record predicted: no credentialed persona holds thefinancegroup (FG-3 there), and reassigning the slot to an out-of-group user yields a 200 approve that satisfies no group.Nothing here is P2-unpinned with a verdict of its own, so no measurement falls outside tier coverage. The three resolver-excluded fixture blocks (
quorum-m-of-n,viewer-gating-submitter-side,sla-escalation) were re-confirmed unchanged and are ledger state, not verdicts of this run; they stay with the wave's fixture gaps. The groundwork notes (seeded pending requests, the budget-change trigger precondition, the missingpending_approver_groupson the list projection) remain readable here for whoever runs the area next.No clause failed, so nothing is owed to the four-way FAIL disposition.
Superseded-by: #9401
Carried-forward: (none — the single consulted item was re-derived by Tier-1)
Parked-on: #9296
Generated by Claude Code