Skip to content

feat(approvals+automation): flow-level send-back-for-revision — revise branch + typed back-edge re-entry (ADR-0044)#1769

Merged
os-zhuang merged 1 commit into
mainfrom
feat-approvals-send-back
Jun 12, 2026
Merged

feat(approvals+automation): flow-level send-back-for-revision — revise branch + typed back-edge re-entry (ADR-0044)#1769
os-zhuang merged 1 commit into
mainfrom
feat-approvals-send-back

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Closes #1744. Implements ADR-0044 (docs/adr/0044-approval-send-back-for-revision.md).

What

requestInfo() (#1740) is a thread conversation; this lands the real flow movement mainstream approval centers call 退回修改 / send back for revision:

approval (round N) ──revise──▶ wait (record unlocked, submitter edits)
        ▲                            │
        └────────[back-edge]── resubmit (round N+1, fresh slate, re-lock)

Decisions (ADR-0044)

  • returned — third terminal ApprovalStatus (approver-initiated rework ≠ submitter-initiated recalled); lock + pending-dedupe key on pending, so unlock/round-N+1 need no lock-machinery change
  • revise branch + maxRevisions (default 3) on the node config — overflow auto-rejects (resumes down reject, output.autoRejected: true); send-back validated against the flow graph before any mutation (no revise edge ⇒ VALIDATION_FAILED)
  • wait node + REST resubmitPOST …/revise, POST …/resubmit; revise window is visible flow state
  • __round config-snapshot numberingrow.round, no migration (precedent: __flowLabel)
  • Engine: typed back-edgesFlowEdgeSchema.type: 'back'; cycle validation = DAG on the graph minus back-edges (unmarked cycles still rejected); re-entry overwrites node outputs / appends steps; 100-re-entry runaway guard; cancelRun() lands as the first run-cancel primitive (recall crossing a revise window cancels the parked run — the wait node has no reject edge)
  • Collision guard — resubmit refuses DUPLICATE_REQUEST while another pending request holds the record (refuses before the suspension is consumed, so the run stays resumable)

Also fixes a latent orderBy bug (direction: is not a SortNode key — silently sorted asc) in remind()'s cool-down and the new latest-row guard, with regression tests.

Tests

  • Engine suite: back-edge registration, unmarked-cycle rejection, re-entry output overwrite + step append, runaway guard, cancelRun (185 ✓)
  • ADR-0039-style matrix (approval-revise.test.ts): multi-round 1→3 · unanimous slate reset · lock locked→unlocked→re-locked · recall crossing the window · superseded rows · overflow auto-reject · no-revise-edge rejection · resubmit collision guard (83 ✓)
  • spec 6529 ✓ · rest 103 ✓

Browser-verified (showcase stack, 3700/5790)

Send back from the inbox (violet returned badge, timeline revise entry) → record unlocks (PATCH 200 while returned, 409 while pending) → resubmit from the submitter view → round 2 opens (Round-2 chip, fresh trail, re-lock, survives server restart via the durable store) → round-2 approve completes the flow. Third send-back on maxRevisions: 2 auto-rejects with the audit trail revise → reject ("Auto-rejected: revision limit (2) exceeded").

Console UI counterpart: objectui PR (send-back button/dialog, resubmit entry, timeline, ten-locale i18n). Studio designer authoring for the revise edge is a follow-up issue.

🤖 Generated with Claude Code

…e branch + typed back-edge re-entry (ADR-0044)

Closes #1744.

- ADR-0044: returned status, revise branch label, wait+resubmit selection,
  __round snapshot numbering, back-edge engine semantics, interaction matrix
- spec: FlowEdgeSchema type 'back'; APPROVAL_BRANCH_LABELS.revise/resubmit;
  ApprovalNodeConfig.maxRevisions (default 3); ApprovalStatus 'returned';
  ApprovalActionKind 'revise'/'resubmit'; IApprovalService.sendBack/resubmit
- engine: cycle validation on the graph minus declared back-edges (unmarked
  cycles still rejected); top-level re-entry runaway guard (100); cancelRun()
  primitive — first run-cancel, used by recall crossing a revise window
- plugin-approvals: sendBack() finalizes the round as 'returned' and resumes
  down revise (auto-rejects past maxRevisions); resubmit() re-enters the
  approval node over the back-edge opening round N+1 (fresh approver slate,
  re-lock); collision guard refuses resubmit while another pending request
  holds the record (before the suspension is consumed); recall extends to the
  latest returned request via cancelRun; __round stamped on the config
  snapshot and surfaced as row.round; sys object enums synced (returned,
  revise, resubmit)
- fix: orderBy used non-canonical `direction:` key (SortNode strips it →
  asc) in remind() cool-down + assertLatestForRun; regression tests
- rest: POST /approvals/requests/:id/revise and /resubmit
- showcase: budget approval manager step gains the revise loop
  (wait_revision + back-edge, maxRevisions 2)
- tests: ADR-0039-style matrix — multi-round rounds 1→3, unanimous slate
  reset, lock lifecycle, recall crossing the window, superseded rows,
  overflow auto-reject, no-revise-edge rejection, engine back-edge suite

Browser-verified end-to-end on the showcase stack: send back → record
unlocks → edit → resubmit → round 2 approve; third send-back auto-rejects.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Building Building Preview, Comment Jun 12, 2026 6:56am

Request Review

@os-zhuang
os-zhuang merged commit c2a3c38 into main Jun 12, 2026
7 of 8 checks passed
@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Jun 12, 2026
@os-zhuang
os-zhuang deleted the feat-approvals-send-back branch June 12, 2026 06:56
import { AutomationEngine } from '@objectstack/service-automation';
import { ApprovalService } from './approval-service.js';
import { registerApprovalNode } from './approval-node.js';
import { bindApprovalLockHook, APPROVALS_HOOK_PACKAGE } from './lifecycle-hooks.js';
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/xl tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(automation+approvals): flow-level send-back-for-revision — revise branch + back-edge re-entry

2 participants