# `phases/commit` ## Purpose Phase-2 commit for a single participant. Once every participant has returned `commit` from phase 1 and the coordinator has persisted the `all-voted-commit` state to the ledger, this module emits `COMMIT PREPARED ''` against each writer participant and records `2pc.commit_duration_ms`. Phase 2 is intentionally unbounded by timeout — the protocol cannot tolerate giving up on a commit-prepared mid-flight, because doing so would leave the system in `in_doubt` state and require the recovery runner to resolve it on the next boot. ## Features - Skips participants that did not actually prepare (`outcome.prepared === false`) — read-only voters and abort-voters never see commit SQL. - Emits a `2pc.commit` span with `tx.id`, `participant.id`, and `gid` attributes. - Records `2pc.commit_duration_ms` tagged with `outcome: "committed"` on success. - Failure preserves the underlying cause on a thrown `TwoPcError(TWOPC_COMMIT_FAILED)`; the coordinator escalates to `TWOPC_IN_DOUBT` once it has tried every participant. ## Usage: Basic ```ts import { commitPhaseTwo } from "@suaveplan/api-2pc/phases"; await commitPhaseTwo({ txId, participant, outcome: preparedOutcome, instruments, }); ``` ## Usage: Advanced The coordinator collects per-participant failures rather than short-circuiting — every participant gets attempted exactly once so operators can see the full picture in the ledger: ```ts import { TWOPC_CODES, TwoPcError } from "@suaveplan/api-2pc/error-codes"; import { commitPhaseTwo } from "@suaveplan/api-2pc/phases"; const failures: TwoPcError[] = []; for (const outcome of preparedOutcomes) { if (!outcome.prepared) continue; try { await commitPhaseTwo({ txId, participant: registry.get(outcome.participantId)!, outcome, instruments, }); } catch (err) { if (err instanceof TwoPcError) failures.push(err); else throw err; } } if (failures.length > 0) { // ledger.markInDoubt(txId); recovery resolves on next boot } ``` ## API Reference - `interface CommitPhaseInput` - `txId: string` - `participant: RegisteredTwoPcParticipant` - `outcome: ParticipantPrepareOutcome` — the phase-1 result. - `instruments: TwoPcInstruments` - `function commitPhaseTwo(input): Promise` - no-op for `outcome.prepared === false` - throws `TwoPcError(TWOPC_COMMIT_FAILED)` on SQL failure. ## Implementation Notes The gid stored on the prepare outcome is the single source of truth — this module never rebuilds it. Reusing the prepare-time value defends against the unlikely case where a host-config change between phases would have produced a different gid: the SQL the coordinator needs to send is exactly the one it prepared with. The SQL escape (`replace(/'/g, "''")`) matches the prepare-phase treatment so a gid containing the unlikely quote character would still produce valid SQL. The escape is cheap; the defence-in-depth costs nothing on the happy path.