Skip to main content
← Back to list
01Issue
FeatureOpenSwamp CLIPublicTeam
AssigneesNone

Relationships

#4493 Expose a manual_approval gate's decision identity (decidedBy) to workflow expressions

Opened by kneel · 11/7/2026

Summary

A workflow step that runs after a manual_approval gate has no way to record WHO approved the gate. The runtime captures the approver, but it is not reachable from any expression a step can bind, so a method that needs to persist "approved by X" is forced to bind run.initiatedBy -- the identity that STARTED the run, not the one that APPROVED the gate. When those differ (the whole point of a separation-of-duties gate), the persisted approver is wrong.

Evidence (swamp source)

  • The approver IS captured: src/libswamp/workflows/run.ts:451 records approvedBy: decision.decidedBy into the run's approval record, and it renders in the CLI (presentation/renderers/workflow_run_display.ts).
  • But the run.* expression context exposes only seven fields (design/enablers/expressions.md): id, workflowId, workflowName, startedAt, tags, initiatedBy, inputs. There is no decidedBy/approvedBy.
  • run.initiatedBy is resolved once at run start (src/cli/auth_context.ts:148 -> user:<username> or ghost); it does not change when the gate is later approved.
  • A model method's execute() context (src/domain/models/method_execution_service.ts) exposes logger/redactor/vault/data but no verified caller identity, so the method cannot self-stamp the approver either.

Net: there is no correct field, anywhere, for a post-gate step to bind the real approver.

Impact

Any gated approval that must be RECORDED (not just enforced) cannot attribute the approval truthfully under segregation-of-duties. Concretely, an HR timesheet-approval workflow that writes an approval record stores the run's initiator instead of the approver whenever they differ -- a correctness/audit-integrity problem for compliance records. It is only correct when the same identity starts and approves the run.

Proposed solution

Expose the gate decision identity to workflow expressions so a downstream step can bind it. Either:

  • add decidedBy (and ideally decidedAt) to a per-gate/step context, e.g. steps.<gateStep>.decidedBy, as the approval step's outcome; or
  • surface the most-recent approval decision on the run context, e.g. run.lastApproval.decidedBy.

The value already exists at run.ts:451; this is a plumbing/exposure change, not new capture logic.

Alternatives considered

  • Run-as-approver convention (the approver runs the workflow, so initiatedBy == approver): works only when start == approve; does not support true separation-of-duties. This is the current stopgap.
  • Method self-stamps the caller: not possible -- execute() has no verified caller identity.

Scope note

Not a code bug; a deliberate capability gap in what gate decisions expose to expressions.

02Bog Flow
◉OPEN○TRIAGED○IN PROGRESS○SHIPPED

Open

11/7/2026, 3:56:49 AM

No activity in this phase yet.

03Sludge Pulse
kneel moved this from a private collective to public11/7/2026, 3:57:34 AM

Sign in to post a ripple.