Repository navigation
fix(approvals): execute approved stream action exactly once - #1788
Open
samkelly-dev wants to merge 6 commits into
Open
samkelly-dev wants to merge 6 commits into
samkelly-dev wants to merge 6 commits into
Conversation
|
@samkelly-dev Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
Contributor
|
Held: |
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.
Closes #1729
Summary of Changes
Shared Stream Action Executor (app/lib/stream-executor.ts):
Implemented executeStreamAction(streamId, action, options) which handles authoritative stream transitions (start, pause, stop, settle, withdraw).
Standardized state guards across routes, side fields (pausedAt, settlementTxHash, withdrawal, nextAction), audit logging with metadata.approvalId, and error handling (atomic rollback on Soroban settlement failures).
Achieved 95.4% statement coverage (>90% requirement).
Refactored Direct Lifecycle Routes:
Refactored /pause, /start, /stop, /settle, and /withdraw to delegate post-authorization transition execution to executeStreamAction.
Approval Execution on Threshold (app/api/streams/[id]/approvals/[approvalId]/approve/route.ts):
Protected execution inside withLock(streamId).
Replaced all mock comments and mock request IDs with correlation context IDs.
When the threshold is met (thresholdMet: true), executes the action exactly once, setting status: "executed" and executedAt.
Returns 409 APPROVAL_ALREADY_EXECUTED if a subsequent or concurrent vote arrives on an executed approval.
Handles state drift: if the stream state changed and can no longer transition when threshold is reached, marks the approval as status: "failed", failureReason: "ILLEGAL_TRANSITION", and returns 409 ILLEGAL_TRANSITION.
Enforces TTL expiry: approvals older than TTL return 410 APPROVAL_EXPIRED.
On transient errors (e.g. 502 SETTLEMENT_FAILED), the approval remains approved (not executed) and retryable.
Approval State Diagram & Lifecycle Documentation:
Documented the approval lifecycle in docs/adr/001-org-stream-ownership.md.
Added /api/streams/{id}/approvals and /api/streams/{id}/approvals/{approvalId}/approve endpoints to openapi.json and regenerated types via npm run openapi:generate.
Test Coverage & Verification:
Added comprehensive route tests in app/api/streams/[id]/approvals/[approvalId]/approve/route.test.ts covering all 8 scenarios from the specification:
Threshold of 2, two votes for pause -> stream paused, approval executed.
Third vote after execution -> 409 APPROVAL_ALREADY_EXECUTED, no duplicate mutation.
Two concurrent final votes (Promise.all) -> exactly one execution, one audit record.
Stream stopped before final vote -> approval failed, 409 ILLEGAL_TRANSITION, stream unchanged.
Expired approval -> 410 APPROVAL_EXPIRED.
Settle rejection -> approval stays approved (not executed), 502, retryable.
Direct pause route behavior unchanged.
Response request_id matches correlation context.
Verified that grep -n "In a real implementation" app/api/streams returns nothing.
Verified npm test, npm run lint, npm run build, and npm run openapi:check all pass.