> Call POST /v1/check before a tool that writes. DENY means do not invoke the tool. For clean Markdown of any page, append .md to the page URL. For the complete index, fetch https://docs.withwrit.com/llms.txt.

# Enforcement

# Enforcement

Enforcement adds three gate capabilities for tenant agents and CI pipelines:

1. **Credential vault** — envelope-encrypted API keys (Stripe only in Phase 1).
2. **Gated execution** — single-use leases that call a fixed upstream host with a vaulted credential.
3. **CI attestation** — signed `writ scan` manifests and per-repo drift.

These endpoints are tenant-facing and documented here. Portal-only endpoints (vault enrollment and revocation, which require a signed-in session + TOTP) are internal and omitted.

---

## Credential vault

The vault stores one credential per enrolled system per tenant. Phase 1 supports Stripe only (`systemId: stripe`).

* Credentials are **envelope-encrypted** with AWS KMS `GenerateDataKey` and an encryption context `{tenant_id, system_id}`.
* The plaintext credential exists only in gate memory at execute time.
* After enrollment the API key is **never displayed or returned** again.
* Revocation sets the credential status to `revoked` and burns outstanding leases.

Enroll and revoke are portal-only actions: sign in at [https://app.withwrit.com](https://app.withwrit.com), go to **Enforcement**, and confirm with your authenticator app.

---

## Gated execution

### 1. Get an ALLOW token

Your agent calls `POST /v1/check` as usual. An ALLOW response contains an `authToken` bound to the exact write.

```bash
curl -X POST https://api.withwrit.com/v1/check \
  -H "Authorization: Bearer $WRIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "sponsorId": "acme",
    "agentId": "refunds-agent",
    "verb": "refund",
    "target": "order-1001",
    "purpose": "refund duplicate charge"
  }'
```

### 2. Create a lease

Convert the ALLOW token into a single-use lease bound to the exact Stripe request you intend to send. The lease TTL is capped to the token's remaining life (≤ 90 seconds).

```bash
curl -X POST https://api.withwrit.com/v1/leases \
  -H "Authorization: Bearer $WRIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "token": "$AUTH_TOKEN",
    "method": "POST",
    "path": "/v1/refunds",
    "body": {"charge": "ch_3N..."}
  }'
```

### 3. Execute

Call `POST /v1/execute` with the lease id and the **same** method/path/body.

```bash
curl -X POST https://api.withwrit.com/v1/execute \
  -H "Authorization: Bearer $WRIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "leaseId": "$LEASE_ID",
    "systemId": "stripe",
    "method": "POST",
    "path": "/v1/refunds",
    "body": {"charge": "ch_3N..."},
    "idempotencyKey": "refund-order-1001"
  }'
```

The gate returns Stripe's status and body verbatim. If the lease is expired, reused, the request bytes don't match, or the credential is revoked, the gate fails closed: no upstream call, and an error receipt.

### Constraints

* `systemId` must be `stripe` in Phase 1.
* `path` must begin with `/v1/`.
* `body` must be JSON and ≤ 1 MB.
* No streaming, no multipart, no arbitrary URLs.

---

## CI attestation

`writ scan` can emit a signed manifest for CI.

### Fetch the manifest key

Issue once per tenant:

```bash
writ manifest-key --key $WRIT_KEY
```

Store the returned key in CI as `WRIT_MANIFEST_KEY`.

### Produce a signed manifest

```bash
writ scan --manifest-out manifest.json --key $WRIT_KEY
```

The manifest includes:

* `repoId`
* `commitSha`
* `scannerVersion`
* per-language findings summary
* ungated write-sites (`path` + `verb`)
* `policyFileHash`
* `timestamp`

It is signed with HMAC-SHA256 over the canonical JSON.

### Submit the manifest

```bash
curl -X POST https://api.withwrit.com/v1/manifests \
  -H "Authorization: Bearer $WRIT_KEY" \
  -H "Content-Type: application/json" \
  -d @manifest-payload.json
```

where `manifest-payload.json` is:

```json
{
  "repoId": "myrepo",
  "manifest": { ... },
  "signature": "..."
}
```

The gate verifies the signature, stores the manifest, and returns drift vs the previous manifest:

* `newUngatedSites`
* `resolvedSites`
* `policyHashChanged`
* `scannerVersionChanged`

View the latest manifest and drift in the portal under **Enforcement**, or via `GET /v1/manifests` and `GET /v1/manifests/{repoId}`.

---

## API reference

See the **Enforcement** endpoints in the API reference:

* `POST /v1/leases`
* `POST /v1/execute`
* `GET /v1/manifest-key`
* `POST /v1/manifests`
* `GET /v1/manifests`
* `GET /v1/manifests/{repoId}`