Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions docs/ADRs/0020-locks-paykit-v1-integration-boundary.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,14 @@ The v1 content-lock criterion has verifier wire value `paykit-payment` and param

1. apply rate limiting;
2. validate proof shape;
3. load and validate the current canonical Lock Resource and payment policy;
4. resolve the current reader through Pubky discovery;
5. compare any persisted lifecycle under `{ creator, bundle_id }`;
6. reject changed submitted proof material with `409 task_state_conflict`;
7. require configured Paykit and call its idempotent invoice endpoint only for new payment submissions; and
8. insert the verification task with post-invoice race reconciliation.
3. compare any durable lifecycle or admission reservation under `{ creator, bundle_id }` before mutable Lock Resource lookup or reader discovery;
4. return an exact ready replay, resume an exact unready reservation from its persisted submission fields, or reject changed submitted proof material with `409 task_state_conflict`;
5. only for a genuinely new identity, load and validate the current canonical Lock Resource and payment policy and resolve the current reader through Pubky discovery;
6. require configured Paykit and atomically persist a hidden, unclaimable admission reservation under the per-lock deletion/admission fence;
7. create or idempotently replay the Paykit invoice; and
8. mark the reservation ready so the verification task becomes publicly visible and worker-claimable.

Exact persisted payment replay returns the stored lifecycle without calling Paykit or repeating invoice side effects. Changed replay does not call Paykit. Clients needing another attempt must generate a new Bundle ID. Live Noise observation uses the dedicated task-bound lookup below.
Exact ready replay does not call Paykit. Exact unready replay requires configured Paykit and repeats the same persisted idempotent invoice request before marking the reservation ready. Changed replay conflicts without calling Paykit. Terminal lifecycle state is not restarted under the same identity; clients needing another attempt must generate a new Bundle ID. Live Noise observation uses the dedicated task-bound lookup below.

### Invoice request

Expand All @@ -69,7 +69,7 @@ For a new lifecycle identity, Locks sends RFC 8785 canonical JSON to `POST /invo

Locks signs the exact canonical body bytes with its existing Ed25519 keypair and sends the unpadded-base64url signature in `X-Paykit-Signature`.

The durable Paykit invoice identity is `(creator, bundle_id)`, where Paykit derives `creator` from `lock_resource`. Exact proof submission replay returns the persisted verification lifecycle without calling Paykit. A different binding under the same identity returns Paykit `409 Conflict`, which Locks maps to `409 task_state_conflict`. Invoice creation and Noise state observation are separate operations.
The durable Paykit invoice identity is `(creator, bundle_id)`, where Paykit derives `creator` from `lock_resource`. Exact replay must return the original generic success without repeating mutable lookups, allocation, address creation, or delivery side effects. A different binding under the same identity returns Paykit `409 Conflict`, which Locks maps to `409 task_state_conflict`. Locks accepts any Paykit 2xx response and ignores its body; other invoice failures return `502 paykit_invoice_creation_failed` while the internal Locks reservation remains hidden and unclaimable for exact retry.

For connection observation, the Reader sends its existing `{ creator, bundle_id }` task handle to Locks `POST /paykit-connection-state-lookups`. Locks loads the persisted task, rejects non-payment tasks, then sends signed canonical `{ creator, bundle_id }` to Paykit `POST /connections/status`. Paykit resolves its persisted invoice binding and returns exactly one local state: `none`, `handshake`, `connected`, `recovery_required`, or `blocked`. Locks rejects missing, malformed, unknown, or extra successful response fields as `502 paykit_connection_state_unavailable`. Browser callers cannot probe arbitrary peer keys or receiver paths.

Expand All @@ -96,7 +96,7 @@ V1 has no invoice expiry, TTL, `expires_at`, or terminal Paykit payment-failure

### Runtime boundary

- New payment submissions require `[paykit]`; exact replay returns persisted lifecycle without calling Paykit. Dedicated connection-state lookup requires `[paykit]`.
- A new payment lifecycle and exact unready reconciliation require `[paykit]`; exact ready replay does not. Dedicated connection-state lookup also requires `[paykit]`.
- Paykit HTTP connect timeout is 5 seconds and whole-request timeout is 20 seconds.
- An enabled in-process Paykit worker requires `claim_timeout_seconds > 20`.
- `worker.poll_interval_ms` must be greater than zero whether the worker is enabled or disabled.
Expand All @@ -116,7 +116,7 @@ V1 has no invoice expiry, TTL, `expires_at`, or terminal Paykit payment-failure

- Both services must implement the same canonical-body signing contract.
- Paykit must parse the public Locks payment criterion and therefore depends on its versioned shape.
- Exact submission replay intentionally performs current canonical lock and reader preflight before returning persisted lifecycle state.
- Incomplete admission reservations require durable reconciliation before deletion may start the Paykit drain.
- Unpaid invoices and pending Locks tasks have no protocol expiry in v1 and therefore require operational retention policy outside the payment-status contract.

## Rejected alternatives
Expand Down
Loading
Loading