Skip to content

QA run · approvals (FULL area) · e4e5c6e3 · 2026-08-17 · 0 PASS / 1 PARTIAL / 0 FAIL (10 not-run) #9352

Description

@os-zhuang

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 and usr_showcase_auditor_demo — the per-group fixture the per-group-signoff item needs), showcase_expense_report/committee (quorum, slate collapses onto the admin ×3 — the shape quorum-m-of-n is blocked on), and showcase_invoice/dual_signoff (consumed by this round's decision).
  • decision-action-matrix needs a fresh budget request; the trigger is budget != previous.budget and above the threshold. My first attempt silently did nothing because the seeded Mobile App project already had budget: 200000 — patching it to the same value is not a change. Bumping to 350000 opened areq_ca40f5da… at manager_review. Use a project whose budget you have first read.
  • The list projection GET /approvals/requests?status=pending does not carry pending_approver_groups (it reads undefined there); the per-group tallies the item asks for need the single-request read GET /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

  1. os-zhuang commented on Aug 18, 2026

    @os-zhuang
    ContributorAuthor

    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 an automated.ref: Tier-1 #9401 re-covered it against the same pin — resume-authority-gate 22/22, scored pass (raw resume refused, owning service admitted, ungated pause still resumable, node-scoped across subflow/map/restart). This record's clause-3 partial was 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 the finance group (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 missing pending_approver_groups on 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions