# Continuous delivery integration

This is a product API over existing agreements, delivery commands, drafts and result packages. It changes no JEP/TSTO semantics and grants no acceptance or payment authority.

## Material intake and continuation

In the agreement editor, **Start from existing materials** maps pasted text to an explicitly selected field or accepts JSON containing `title`, `scope`, `baselineStatement`, `contractClauses`, `originalAccess`, and `criteria` (`title`, `requirement`, `evidenceRequired`). There is no model call or natural-language inference. Review the field diff and remaining gaps. Source fragments stay with the editable draft; they are not added to the signed agreement. Undo restores only fields whose imported value and criterion identity still match, preserving later edits.

Anonymous drafts remain local. Specification handoffs last at most 24 hours in the browser, including other tabs, and contain no credentials. An editor URL carries only a public specification/version reference. A different device can restore an explicitly saved account draft under the same identity and workspace; an unsaved handoff is unavailable there. A missing handoff shows the pinned specification recovery link while preserving any restored draft. Old rendering formats are rejected. Replacing a source requires a diff and explicit choice. Account saves retain optimistic revision checks.

## Persist, then submit

All following routes use `Authorization: Bearer <project-scoped-agent-token>` and the base `https://prooftask.com/api/agents/v1`.

1. `POST /jobs` with a stable UUID v4 `requestId`, exact `projectId`, an opaque `workflowReference` (for example `crm:case-42`), `summary`, and `materials`. Material items follow the existing submission schema, one item per agreed criterion. The server reads the authorized current project, constructs the exact request envelope, and persists it before returning a `job_…` ID. No submission occurs.
2. Persist the returned job ID in the originating workflow. An uncertain preparation response can be retried with the same request ID and identical body. Changed input under the same ID is rejected.
3. Deliberately `POST /jobs/{id}/submit` with `{"confirmed":true}`. Every retry uses the same stored envelope. Business command and submitted-job marker are committed together. A stale head, revoked grant, signing requirement or human pause stops the operation through the existing policy.
4. `GET /jobs/{id}` reads current state. `appliesToSavedSubmission` and `decisionForSavedSubmission` bind the decision to that saved request. `GET /jobs?projectId=…` returns the current results of this grant's jobs for that exact project. It returns the original `workflowReference`; no manual copying of project IDs is needed after preparation.

The [prepare-job](https://prooftask.com/developers/n8n/prepare-job.json), [send-job](https://prooftask.com/developers/n8n/send-job.json), and [events](https://prooftask.com/developers/n8n/events.json) n8n templates implement these steps. They are inactive exports with no embedded credentials. Map your source fields in Input, select a Header Auth credential, and connect the last event node to your existing record-update node. Store/upsert by job ID or workflowReference; receiver-side downstream effects remain the receiver's responsibility.

## Credential replacement

Jobs and subscriptions belong to the issuing agent grant. A replacement grant
cannot read, submit or manage them merely because it represents the same human.
Before reauthorization, retain the original workspace (`/me.tenantId`), organization,
`/me.id` grant, project ID, agreement digest, request ID, prepared envelope, job ID
and workflow reference. A 404 from the new grant is not proof the old submission
failed. Follow [credential replacement and unknown results](https://prooftask.com/developers/agent-workflows.md#credential-replacement-and-unknown-results):
read the exact authorized project's history, match the original `SUBMIT` request
and recorded agent/grant, and retain an existing result without resubmitting.
If access or outcome cannot be established, hand the saved context to the owner.
No automatic ownership transfer, new permission or cross-workspace search occurs.

## Authorize project callbacks

`POST /webhooks` takes:

```json
{
  "requestId": "11111111-1111-4111-8111-111111111111",
  "target": "https://your-workflow.example/webhook/prooftask-project-event",
  "projectIds": ["delivery_22222222-2222-4222-8222-222222222222"],
  "eventTypes": ["agreement.confirmed", "delivery.submitted", "changes.requested", "acceptance.decided", "record.changed"],
  "expiresAt": "REPLACE_WITH_CANONICAL_UTC_WITHIN_30_DAYS",
  "confirmed": true
}
```

The signing secret is returned once. If that response is lost, retry returns metadata without the secret; use `POST /webhooks/{id}/rotate-secret` with `{}` to obtain a new secret. Rotation invalidates signatures created with the previous secret. Configure the receiver before relying on deliveries. No automatic test request is sent to a newly configured URL.

Each event has a stable `id`, exact `projectId`, per-project `sequence`, `agreementVersion`, `agreementDigest`, `headEventHash`, `submissionId`, `reviewId`, `state`, and `occurredAt`. It contains no title, correspondence, material bodies, actor identities or access tokens. Events are recorded in the business SQLite transaction. Only subscriptions active for the selected project and type at commit time receive a queued delivery; creating/resuming a subscription does not backfill earlier events.

Agreement revision creates a different exact project: explicitly authorize that version. `record.changed` covers other recorded changes such as concerns, withdrawals, supersession and representative changes. Notifications are historical observations; delivery success is not buyer acceptance, payment, or proof that a downstream action ran.

## Verify before reading or acting

The webhook envelope also includes `currentNextAction`, read from the same projection used by Workspace, project pages, agent responses and notifications. The original event fields remain historical and immutable. The next action describes the state at this send attempt, so its `expectedHeadEventHash` may be newer than the event's `headEventHash`. Retries can update this navigation while retaining the event/delivery IDs. The sender rechecks it immediately before transport, including after DNS resolution. Never interpret this projection as a new business event or authority. Signature verification accepts the exact attempted envelope; its digest is recorded with the attempt.

Requests use HTTPS on port 443 and these headers:

- `prooftask-event-id`: stable event ID (deduplicate using this).
- `prooftask-delivery-id`: subscription/event delivery identity.
- `prooftask-timestamp`: attempt time in Unix seconds.
- `prooftask-signature`: `v1=` followed by lowercase hex HMAC-SHA256.

The signed bytes are `timestamp + "." + raw_request_body`. Compare in constant time, reject timestamps more than five minutes away, and preserve the exact raw bytes before JSON parsing. Duplicate valid events are expected: acknowledge after durably queuing your work, deduplicate side effects, and retain a per-project sequence to identify older snapshots. Always read current authorized state and check agreement/submission identifiers before updating another system. Never apply a previous submission's review to a newer submission.

The n8n event template receives a raw binary body and calls `POST /webhooks/{id}/verify` with `rawBody`, `timestamp`, and `signature` using the owning agent credential. This endpoint verifies freshness, exact known queued event bytes, subscription scope and live authority. It then reads current saved jobs. It does not mark external work completed. Verification may be repeated while fresh to recover a failed downstream attempt; durable deduplication belongs in the original workflow's job store.

## Delivery and control

The existing runtime worker drains up to 20 due deliveries per pass, every 15 seconds. A 30-second lease permits recovery after a crash. Attempts use a total 10-second HTTPS timeout, exponential backoff starting at 15 seconds (capped at one hour), and stop after eight attempts. An external timeout never rolls back a committed business operation. Events may be delayed, repeated or reordered; there is no exactly-once external execution guarantee.

- `GET /webhooks`: list this grant's subscriptions.
- `POST /webhooks/{id}/pause`, `/resume`, `/revoke`, `/rotate-secret`: body `{}`. Revocation is irreversible for that subscription. Pause keeps pending deliveries; resume makes them eligible again (up to the next one-minute deferred check).
- `GET /webhooks/{id}/deliveries?after=…`: up to 100 records in durable insertion order, with classified failures and exact events. `next` is non-null for a full page; `checkpoint` is the last position returned (or the incoming checkpoint for an empty read). No remote response bodies are retained.
- `POST /webhooks/{id}/deliveries/{deliveryId}/redeliver`: body `{}`; rechecks current authority and requeues the same event/delivery identity, at most 80 total attempts.

Authority is checked at every attempt and again after DNS resolution, immediately before opening the connection. Agent revocation, expiry, parent authority changes and project-scope removal stop queued sends. An HTTP request already sent cannot be recalled. Pausing agent business writes is separate from revoking a notification subscription. Human/session API equivalents live under `/api/acceptance/project-webhooks`; they use the authenticated workspace's identity and current authority.

Targets must be credential-free HTTPS hostnames on port 443. Literal IPs, local domains, private/special IP ranges and mixed public/private DNS answers are rejected. DNS is rechecked each attempt and the validated address is pinned to the HTTPS socket while preserving certificate hostname validation. Redirects are never followed. These controls follow the [OWASP SSRF prevention guidance](https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html). Deployment-level outbound network restrictions remain useful defense in depth.

### Read, persist and resume delivery history

1. Start without `after`. Durably process the returned rows, deduplicating by
   event ID (and subscription/delivery ID for transport bookkeeping).
2. Only after processing the **whole page**, persist its opaque `checkpoint`.
   If `next` is non-null, request `after=next` immediately. Retrying the same page
   may repeat rows and is safe when the receiver deduplicates its own side effects.
3. When `next` is null, retain `checkpoint` and poll with `after=checkpoint` later.
   Empty reads preserve that position. New arrivals, including different projects
   at the same timestamp, appear after it; no clock/hash comparison is required.
4. This cursor tracks newly queued deliveries, not every update to existing
   attempts. To reconcile retry/failure state, reread history from the beginning
   and upsert by delivery ID. Read current authorized projects before acting;
   historical event state is not current business state.

API 1.3.1 replaces the former hash-order cursor. A saved pre-1.3.1 `whd_…` cursor
returns HTTP 409 `WEBHOOK_CURSOR_RESET_REQUIRED`, `retryable: false`, and
`nextAction: RESTART_DELIVERY_HISTORY`. Restart **once** without `after`, preserve
receiver deduplication and save the new checkpoint. Do not drop known events or
repeat downstream effects. Cursor values are opaque and scoped to the subscription;
an unknown or other-subscription cursor returns `WEBHOOK_CURSOR_INVALID` without
revealing that other resource. A new credential still does not own the old subscription.

The stored positions survive process restart and database maintenance. Event IDs,
signed business history and queued delivery identities are unchanged. The first
startup backfills positions for existing deliveries; it does not resend them.

## Record handoff and observation

The result page obtains one exact-head handoff: displayed summary, copied text, derived JSON and signed export use the same existing Result Package content. A changed project head requires refresh. The package's `result.txt` remains the deterministic summary. The page distinguishes status observation time from the historical snapshot and links to existing controlled sharing, similar-project, repeat-work and private-template flows.

Browser observations are bounded, private 30-day aggregates: action and reason codes, page time, estimated recent input time and wait time. No field text, private URLs or credentials are collected. Test observations are stored separately. They never count as server-confirmed business success. Webhook logs independently report server transport outcomes. These observations appear in the existing Delivery insights view and `/api/acceptance/workspace/friction`.

## Limits

Subscriptions expire within 30 days and are bounded to 50 projects each / 50 subscriptions per owner. Saved jobs are bounded to 500 per agent grant. Failed deliveries require explicit operator review/redelivery after retries are exhausted. The first version retains durable event/job/delivery records with the application's SQLite backup policy; it has no automatic destructive purge. Monitor database capacity and apply the workspace's retention obligations before introducing cleanup.
