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

vercelBot commented Jun 12, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
specBuildingBuildingPreview, CommentJun 12, 2026 6:56am

Request Review

@os-zhuang
os-zhuang merged commit c2a3c38 into mainJun 12, 2026
7 of 8 checks passed
@github-actionsgithub-actionsBot 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 freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/xlteststooling

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

@os-zhuang@github-advanced-security