Repository navigation
fix(app-showcase): write the expense approvals' decision back onto the report - #25525
Merged
objectstack-fleet[bot] merged 2 commits intoOct 23, 2026
Merged
Conversation
…e report showcase_expense_signoff and showcase_committee_quorum ended both outcome arms on bare end nodes, so an approved expense report kept reading Submitted. Each arm now runs an update_record (approve -> approved, reject -> draft) under runAs: system, and a showcase test drives a real decision through the approvals service on both flows with a pre-fix control. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016g7nYP9wdNx1GDa5XDr7Uy
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016g7nYP9wdNx1GDa5XDr7Uy
Contributor
Author
席位核对 · ACCEPT · 2026-10-23T19:29Z
Generated by Claude Code |
objectstack-fleet
Bot
deleted the
claude/issue-25469-showcase-signoff-mirror
branch
October 23, 2026 19:49
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requested by Sales · project thread
Fixes #25469
Clause-②: no
Metadata-protocol: none
What changed
showcase_expense_signoff(per-group 会签, nodedual_signoff) andshowcase_committee_quorum(2-of-3, nodecommittee) ranstart → approval → end, with bareendnodes on both outcome arms. The approval request finalized, but nothing wrote the decision onto the expense report, so EXP-2001 kept readingstatus: submittedafter both groups approved.Each flow now runs an
update_recordon each outcome arm and hasrunAs: 'system':mark_approved→status: 'approved'return_to_draft→status: 'draft'(the report goes back to the submitter, who can rework and resubmit; this is the same revert-on-rejectshowcase_invoice_signoff'sflag_helddoes)runAs: 'system'follows the invoice flow's precedent. The write-back is an outcome of the approval, not an act of the submitter, so it has to land whatever the submitter's edit rights are.Landing:
examples/app-showcase/src/automation/flows/index.ts(the file the claim names) plus the new pinexamples/app-showcase/test/expense-approval-write-back.test.ts. No package source changed.Why
update_recordper branch and not theapprovalStatusFieldmirrorI checked how the mirror behaves in
packages/plugins/plugin-approvals/src/approval-service.ts(mirrorStatusField/mirroredStatusValue):pendingwhen the request opens,approved/rejectedon a decision,recalledon a recall or dead-run sweep,returnedon a send-back, andrejectedon the revision-limit auto-reject.approvalStatusField: z.string(), and adding a mapping would mean editingpackages/spec, which is out of bounds for this card.approval status 'X' was not written to OBJECT.FIELD). The only exception is a transition that abandons the request, where it restores the value from before submission.showcase_expense_report.statusdeclaresdraft / submitted / approved / reimbursed. So a mirror onstatuswould:pendingand log a warning on every submitapprovedcorrectlyrejected, so a rejected report would keep reading "Submitted", which is the same defect on the other armThe spec also says the mirror target "should be readonly on the object", and
statusis the field the submitter edits. When the decision drives a business-lifecycle field, the right tool is an explicit write per branch. Both flows' docblocks now explain this.Committee quorum flow
showcase_committee_quorumhad the same gap, with bareendnodes on both arms. It gets the same fix.Pin
test/expense-approval-write-back.test.tsboots the production stack: a realObjectKernelwith the ObjectQL, automation and approvals plugins over real sqlite, and the app's realExpenseReport/ExpenseLineobjects and real flows. It opens each request the way the seed'slaunchSignoffdoes, then decides it throughApprovalService.decide.describe.eachruns it over both flows:submittedand the request is not finalized. After the finalizing approval the run iscompleted, it steppedmark_approvedand notreturn_to_draft, and the report readsapproved.return_to_draftand the report readsdraft, a value the status field declares.endarms, noupdate_recordnodes) on the same harness. The same approvals finalize and resume the run, and the report stays atsubmitted, which is the defect as reported.The committee case adds an expense line of 8,900 so the
total_amount >= 5000start gate opens (the seeded EXP-DEMO is 8,900). The per-group case uses 1,500, under the threshold like EXP-2001.Ablation (
scripts/ablation-replace.mjs, on committed HEAD3e661a73f6)The mutation pointed the signoff approve edge back at the bare end node: anchor
{ id: 'e2', source: 'dual_signoff', target: 'mark_approved', label: 'approve' },was replaced withtarget: 'approved'.The test went red under the mutation and green again once the file was restored. The example is source-imported by the test (relative
../srcimport), so there is nodist/leg.Verification (head
3e661a73f6)pnpm --filter @objectstack/example-showcase exec vitest run test/expense-approval-write-back.test.ts: 6/6 passedpnpm --filter @objectstack/example-showcase typecheck && pnpm --filter @objectstack/example-showcase test: lock VERDICT command-exit 0; Test Files 38/38, Tests 461/461. The new test file is in the tsc program (--listFiles: 1 hit).node scripts/pm/dispatch-gates.mjs --commands: 45 commands, each run with its exit code recorded. 44 exited 0.pnpm check:dual-build-cjs-loadsexited 3 (PREREQUISITE NOT MET: unbuilt packages outside this diff's closure), so it is NOT MEASURED locally and left to CI.--ranreconciliation:45 derived famil(ies) accounted for — 44 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3).No changeset:
@objectstack/example-showcaseisprivate: true, so nothing publishes (skip-changeset).Acceptance notes
docs/qa/platform-checklist/areas/approvals.json→approvals.status-mirror-fieldstill records underknownGapsthat no showcase flow declaresapprovalStatusField. That stays true after this PR, deliberately (see above). The checklist is not edited here.showcase_expense_signoff's start gate has no amount ceiling, so a report of $5,000 or more matches both flows' start conditions on submit. The seed routes the two demos to different records on purpose. I did not measure what a live submit of such a report does, so this is noted here and not filed.lockRecordquestion: the write-back runs after the request has leftpending, and the lock is keyed on pending. It also runs as the system principal, which the lock hook exempts. The pin shows the write lands underlockRecord: true.🤖 Generated with Claude Code
https://claude.ai/code/session_016g7nYP9wdNx1GDa5XDr7Uy