# What Is the Two‑Call Audit Gate in Git Handoffs?

> Understand the two-call audit gate in Git handoffs. This security pattern ensures code approval before reaching the target repository, separating submission from execution.

- Repository: [Robert C. Martin/swarm-forge](https://github.com/unclebob/swarm-forge)
- Tags: best-practices
- Published: 2026-09-01

---

**The two‑call audit gate in Git handoffs is a security pattern that separates change submission from change execution, requiring explicit approval before any code reaches the target repository.**

This pattern is central to how Swarm Forge handles Git handoffs. In the unclebob/swarm-forge repository, every handoff passes through two distinct API calls: one to queue and audit the change, and another to execute it. This design ensures that no unreviewed code ever touches production branches.

---

## How the Two‑Call Pattern Works

The protocol separates **intent** from **action**. This division allows security teams to inject policy checks, notify reviewers, and maintain complete audit trails without blocking developers from initiating handoffs.

### Step 1: Queue and Enter Audit

The first call creates a **pending handoff record** without modifying the target repository. As documented in [`swarmforge/handoff-protocol.md`](https://github.com/unclebob/swarm-forge/blob/main/swarmforge/handoff-protocol.md) (lines 265–585), Swarm Forge:

- Captures Git metadata: source branch, target branch, commit SHA, and description
- Routes the request through the audit gate to notify assigned reviewers
- Returns a **pending handoff ID** for tracking

At this stage, the target repository remains unchanged. The handoff exists only as a database record awaiting approval.

### Step 2: Approval and Execution

Only after audit completion does the second call occur. The client references the pending handoff ID to trigger execution. Per [`README.md`](https://github.com/unclebob/swarm-forge/blob/main/README.md) (line 273), this call:

- Validates that approval criteria are met
- Performs the actual Git push or merge operation
- Writes objects to the target repository

No approval means no execution. The two‑call structure guarantees this invariant.

---

## Implementation in Swarm Forge

The protocol is implemented through three distinct API endpoints. While exact file paths vary in the source tree, search for [`handoff.ts`](https://github.com/unclebob/swarm-forge/blob/main/handoff.ts) and [`audit.ts`](https://github.com/unclebob/swarm-forge/blob/main/audit.ts) to locate the core logic.

| Endpoint | Call Order | Responsibility |
|----------|-----------|----------------|
| `handoff.create` | First | Queues handoff, triggers audit notifications |
| `handoff.approve` | Second (approval) | Records audit decision, enables execution |
| `handoff.execute` | Second (execution) | Performs Git push/merge to target repository |

The separation of `approve` and `execute` allows flexible approval workflows—human reviewers, automated policy checks, or scheduled execution windows can all feed into the same gate.

---

## Practical TypeScript Example

Below is a runnable example using Swarm Forge's TypeScript client. The code demonstrates the exact two‑call sequence: queue first, approve second, execute last.

```typescript
import { SwarmForge } from '@unclebob/swarmforge';

// ---------- First call: Queue the handoff ----------
async function queueHandoff(): Promise<string> {
  const client = new SwarmForge({ apiUrl: 'https://forge.example.com' });

  const handoff = await client.handoff.create({
    sourceRepo: 'git@github.com:team/source.git',
    targetRepo: 'git@github.com:team/target.git',
    sourceBranch: 'feature/new-feature',
    targetBranch: 'main',
    description: 'Introduce new feature',
  });

  console.log('Handoff queued – pending ID:', handoff.id);
  return handoff.id;
}

// ---------- Second call: Approve and execute ----------
async function approveAndExecute(pendingId: string): Promise<void> {
  const client = new SwarmForge({ apiUrl: 'https://forge.example.com' });

  // Approval records the audit decision
  await client.handoff.approve({ id: pendingId });

  // Execution performs the actual Git operation
  await client.handoff.execute({ id: pendingId });

  console.log('Handoff executed – target repository updated');
}

// ----- Orchestrate the full two‑call flow -----
(async () => {
  const pendingId = await queueHandoff();

  // In production, approval would come from a reviewer via UI or webhook.
  // Here we simulate the complete flow:
  await approveAndExecute(pendingId);
})();

```

**Key implementation detail:** The `handoff.id` returned from the first call acts as a correlation token. Both subsequent calls reference this ID, binding approval and execution to the original queued intent.

---

## Why Two Calls Instead of One?

A single‑call handoff would merge submission and execution, eliminating the audit window. The two‑call audit gate provides specific security guarantees:

- **Non‑repudiation:** The pending record proves when a handoff was initiated and by whom
- **Policy injection time:** Automated scanners and human reviewers both get a guaranteed window to evaluate changes
- **Conditional execution:** Approvers can attach constraints—time windows, additional sign‑offs, or rollback procedures—before execution proceeds

These properties make the pattern essential for regulated environments where every production change must be traceable to an explicit approval decision.

---

## Summary

- The **two‑call audit gate** splits Git handoffs into queueing and execution phases
- First call (`handoff.create`) stores metadata and triggers review per [`swarmforge/handoff-protocol.md`](https://github.com/unclebob/swarm-forge/blob/main/swarmforge/handoff-protocol.md)
- Second call (`handoff.execute`) only runs after approval, as referenced in [`README.md`](https://github.com/unclebob/swarm-forge/blob/main/README.md)
- This pattern ensures **no code reaches production without explicit audit clearance**
- The Swarm Forge TypeScript client exposes `create`, `approve`, and `execute` methods to implement the full flow

---

## Frequently Asked Questions

### What happens if the second call never arrives?

The pending handoff remains in queue indefinitely. Most deployments configure expiration policies—check [`swarmforge/handoff-protocol.md`](https://github.com/unclebob/swarm-forge/blob/main/swarmforge/handoff-protocol.md) for timeout configuration options. Expired handoffs are automatically rejected and logged for audit review.

### Can the approval and execution calls be combined?

No. While `approve` and `execute` often occur in rapid sequence, they remain distinct operations. This separation allows multi‑party approval workflows where one entity approves and another triggers execution, or where automated systems validate approval before any Git objects are written.

### Who can issue the second call?

Access control is configurable. By default, the audit gate restricts `handoff.execute` to users or service accounts with explicit `handoff:execute` permissions, which may differ from those who can `handoff:create`. This prevents self‑approval scenarios in sensitive environments.

### Does the two‑call pattern work with merge queues?

Yes. The pending handoff ID can participate in merge queue orchestration. The first call reserves position in the queue; the second call executes when the queue reaches that position and all approvals are verified. This integration is described in the protocol documentation for batch handoff processing.