Deep dive
A signature is not permission
A signed agent plan can freeze exact intent without freezing authority, budget, price, or current state.

While reviewing the workflow-plan threat model, we kept returning to one question: what, exactly, does the signature buy us?
Suppose an agent prepares this plan at 10:00:
create project
→ launch server
→ attach example.com
→ create hello@example.com
The server validates the operations, computes a maximum cost, binds the project and caller, serializes the plan, and signs it. The agent comes back at 10:07 to apply it. Seven minutes is plenty of time for a key to be revoked, a budget to change, or another task to alter the project.
What does the signature prove?
Only this: the server produced the signed bytes and they have not changed.
It does not prove that the caller still has access, the project still has budget, the quoted price still holds, the resource versions still match, the signing key is still trusted, or a person approved an irreversible step.
This can sound like security vocabulary splitting hairs. It is not. Once the object crosses an API boundary, the answer determines whether a stolen copy is evidence or a credential.
Four records, four meanings
We keep four concepts separate:
| Record | What it proves | What it does not prove |
|---|---|---|
| Prepared plan | The server accepted a bounded description for inspection | Permission to execute |
| Plan signature | The exact canonical bytes came from the named key and remain unchanged | Current policy, state, or budget |
| Apply admission | One authenticated request claimed one durable execution identity | Provider completion |
| Receipt and verification | The recorded outcome and, separately, observed final state | That every external effect was reversible |
Collapsing these records is tempting. The plan already contains the operations and tenant context. Letting any holder execute it would remove an entire round of checks and make the API look pleasantly simple.
It would also be wrong for our threat model. Preparation and execution happen at different times under different conditions.
Authorization is a live question
Between prepare and apply:
- the agent key may be revoked;
- the organization may be suspended;
- the project may move or close;
- a policy may become stricter;
- another task may consume the available budget;
- a resource version may change;
- an operation may leave the workflow allowlist;
- a signing key may be revoked after an incident;
- or the plan may simply expire.
The plan should make those changes detectable, not override them.
Apply therefore verifies integrity and asks the authorization questions again. It locks and claims the plan, binds a stable apply idempotency key, checks the current principal and tenant, recomputes relevant cost and policy decisions, compares expected versions, and creates one durable task and admission.
The worker does not receive a generic handler name from the agent. It loads the persisted plan by its bounded identity, verifies the digest and version binding, and resolves every node through an explicit server-owned operation allowlist.
Before each consequential node, the canonical application service performs its ordinary current checks. Prepare is not a permanent allow verdict.
Canonicalization is part of the contract
Signing arbitrary JSON is underspecified. Two serializers can produce different bytes for objects that appear equivalent. Defaults added after signing can change execution without changing the signed fields. Re-parsing into mutable objects can lose the exact representation that was verified.
A useful signed plan therefore needs:
- a versioned canonical serializer;
- finite JSON values with bounded depth and size;
- deterministic object-key order and explicit defaults;
- a digest over the exact UTF-8 payload bytes;
- a plan identity, format version, algorithm, and key identifier bound into the
- immutable storage of those exact bytes;
- and verification against the declared key rather than trying every key.
envelope;
The execution path should use the same validated, branded representation that the signer accepted. A type cast or freshly assembled object is not equivalent to a verified plan.
Nobody puts canonical serialization in the product screenshot. It is still the part that makes "signed" mean something reproducible.
Human approval remains a separate authority
A prepared plan may state that a domain registration can require approval. It cannot contain the approval itself.
When execution reaches the gate, the system creates or attaches the durable approval record and pauses the same task. The approval surface loads a current, redacted description of the exact action. The signed-in human decides under current policy, price, project ownership, session, and CSRF checks.
The workflow signal is not the decision. It only tells a waiting workflow to look at the durable decision record again.
This prevents three shortcuts:
- an agent cannot approve its own plan;
- a leaked plan cannot be treated as an approval capability;
- an old approval cannot silently authorize a materially changed action.
The useful sentence
When reviewing a signed-plan design, ask the team to finish this sentence:
> The signature proves __, while current authority comes from __.
For SproutPad:
> The signature proves the plan's exact bytes and provenance, while current > authority comes from the authenticated principal, policy, budget, state, and > any required human decision at execution time.
If the answer is "the signature proves the holder may execute everything in the plan," the signed object is a bearer credential. That may be intentional, but it deserves the threat model of a credential: theft, delegation, revocation, scope, audience, replay, and disclosure.
We chose the narrower object. A SproutPad plan is inspectable evidence of frozen intent. Leave one on a public web page and it still cannot spend a dollar by itself.
The public API contract is at sproutpad.ai/openapi.json. Verify the current production build and retained signed reports at sproutpad.ai/conformance.