Proposals (Öneri)
The consent mechanism for agent work (ADR 0027 §2). An Ajan token's live mutations are never executed — each is dry-run previewed and recorded as an item of the token's one open proposal, answered with HTTP 202 {proposed: true, auditId: null}. "No audit row ⇔ nothing applied" stays literally true: a proposal stores envelopes and previews only — never data snapshots, no storefront bump, nothing visible until apply.
Proposal statuses
| status | meaning |
|---|---|
open | the token's working set — every recorded mutation appends an item (one open proposal per token; idle proposals expire after 7 days) |
submitted | proposal.submit with a title + summary; the owner is mailed a review link; expires unreviewed after 14 days |
applying | the owner approved — proposal.approve is the audited consent event (owner sessions only; tokens structurally cannot approve) |
paused | a stale or failed item stopped the run; the owner skips, re-approves, or rejects |
applied | every item ran (or was skipped) |
rejected | owner refused (proposal.reject) |
withdrawn | the agent (or owner) dropped it (proposal.withdraw) |
expired | the open-idle or submitted window lapsed |
Item statuses: pending → applied, or stale (preview drift), failed (execution error), skipped (owner's proposal.item.skip). A proposal holds at most 500 items (proposalMaxItems).
Preview-hash drift
At record time the item stores its dry-run preview and sha256(canonicalJson(preview)). At apply, each item is re-previewed against the current world and the hashes must match; design items additionally require the workspace hash unchanged and liveSeq === baseSeq. A mismatch means the world moved under the proposal (a price edited, an order shipped, the live design advanced): the item goes stale with the current preview attached, the proposal paused. The owner skips the item or re-approves to accept the new preview; POST /v1/proposals/{prp}/devam resumes.
Apply semantics
- Items run in order, stop at the first failure, and are never rolled back — every applied item is an ordinary audited command the owner undoes by the usual means.
- Each item executes as the approver's merchant actor with
idempotencyKey = command_uid(crash-retry replays produce one outcome), attributed{viaProposalId, proposedByToken}— proposer and approver land on ONE audit row. - Route items replay through the same handler functions the admin buttons call, so mail and provider side effects match exactly.
proposal.approvepinsexpectedItemCount— an item appended between render and click invalidates the approval.