Status
Proposed. Independent of ADR 0020 (recipes);verify reuses the generic adapter of ADR 0017.
Context
Some of what ships beside the code can only be changed by a person in a console: a Google OAuth web client’s authorized redirect URIs (no public API), a Clerk webhook endpoint (Clerk exposes only a dashboard link to its Svix app), a Stripe sandbox per pull request (no public API). docs/coverage.md lists them as rows “no adapter can reach”. Today they live in a README or in someone’s head; every preview needs them, nobody can see whether they were done for this one, and an agent driving Sponson has no way to say “a person must do this now”. Sponson cannot do these steps. What it can do is the rest of its job for them: say exactly what to do for this environment (with the preview URL filled in), hold back what depends on it, remember that it was done and by whom, notice when it needs doing again, and ask for it to be undone when the scope goes away. Options considered:Decision
A built-in adaptermanual with one op, step, and an engine hook any adapter may use, OpSpec.manual(params, ctx), which returns the step a person must do (ManualStep: key, title, instructions, undo, hash, whether a verify
request can observe it) or null while an input is pending. For such ops the engine owns the line’s state; the adapter
never writes anything (apply is never called).
- Params.
title(one line; the identity, keystep:<title>),instructions(Markdown), optionalundo,vars(values for{name}placeholders, literals or{ from }references, as in thehttpadapter’s paths), optionalverify.${ctx.*}works everywhere. A{ secret }or{ keep: true }anywhere isPARAM_INVALIDbefore any provider call: the text is shown to people and kept in receipts. - State. The hash covers what a person is asked to do (title and rendered instructions). A step is done when its verify request sees it, or when the ledger records a person’s confirmation of these very instructions. A new preview URL changes the instructions, so the step becomes a todo again.
- Plan shows a step that is not done as
todo(a newPlanLineStatus) withmanual: { title, instructions, … }; a step whose inputs are pending ispendingas usual.planexits 0. - Apply records a done step (
unchanged, orappliedwhen first seen). A todo is recorded only when the run confirms it,RunOptions.confirm(apply --confirm <line>, repeatable; MCPconfirm), withconfirmedBy(the CLI takes--approved-by, else the git user); the ledger entry keepsmanual: { how: confirmed, by, at }. Otherwise the line iswaitingwithwaitingFor: confirmationand the new codeMANUAL_STEP_PENDING, its dependents wait too, and independent lines are applied. The receipt ispartial; the CLI exits 2 (the code’s exit, as for refusals) witherror.code: MANUAL_STEP_PENDING, thecliHint, andmanual: [...], each step’s instructions. A confirmation that names something other than a manual line, or comes without a name, isUSAGEbefore anything runs. - Verify is an optional GET with the generic adapter’s client, on an API block under
providers.manual(thehttpblock format,authoptional for public endpoints such as DNS-over-HTTPS): done when it answers 2xx and everymatch(JSON pointer → value) holds; 404 orabsent_statusmean not yet. A step a verify request once saw done and no longer sees ismissingdrift, and a todo again. A person’s confirmation counts even when the verify request does not (yet) agree. - Rollback never undoes a manual step: a person did it, and a failed later line does not change that.
--recreaterefuses manual lines. - Destroy shows
undo(kept, masked, in the ledger, so a line removed from the plan is still undone) and forgets the step only when the run confirms it (apply --destroy --confirm <line>); until then the line waits withMANUAL_STEP_PENDING, the receipt ispartialand the CLI exits 2. A step withoutundois forgotten. - Agents never confirm on their own. SKILL.md and the MCP tool descriptions tell an agent to show each step’s
instructions to the human verbatim and to pass
confirmonly with the lines the human says they did.
Consequences
- The no-API rows of the coverage survey get a home in the plan, with receipts, drift (for verifiable steps) and teardown. They are counted as manual, never as automated coverage.
PlanLineStatusgainstodo,PlanLineandApplyResultSummarygainmanual,LedgerEntrygainsmanual,RunOptionsgainsconfirmandconfirmedBy,OpSpecgainsmanual. All are additive; older Sponson versions reading a newer receipt ignore the field (and would treat the step as an ordinary resource of an unknown op).- A pull request with a manual step has a red apply job until someone confirms the step. That is the point: the preview does not work until then.
- A confirmation records what the instructions said when it was given. If they changed between a person reading them
and confirming (a new commit, a new preview URL), the confirmation covers the new ones; the person confirms what
planshows at that moment. - Sponson trusts the person: it cannot check a confirmation of a step without
verify.