This Skill owns the safety transaction for paid execution. The transaction
is always: node quote against the latest authoritative draft, then
node confirm to bind user approval to an exact credit ceiling, then
node run with a caller-minted submitId. Reusing the same submitId
on recovery replays the original submission and never re-bills.
Reference detail is in references/credit-approval.md.
When to use
- The caller wants to actually run a previously saved node (after the structural work is finished and the user has authorised the spend).
- A prior
node quotereturnedconfirmable=false; this Skill explains why and what to do next. - A
node runfailed with exit code 20 (resume); this Skill explains how to continue without re-billing. Always re-quote before retrying.
When not to use
- For saving a draft without running. That is
node create <type>without--run, owned by the relevant domain Skill. - For re-quoting after a silent change to the saved draft; this Skill always re-quotes from the live authoritative draft.
The three-step transaction
# 1. Re-quote from the live draft (always the latest authoritative state)
dreamina-canvas --format json node quote --node-id <nodeId> \
--project-id "$PROJECT_ID"
# → items[] (one entry per node, ordered), totalMaxCredits, confirmable,
# confirmationRequired, creditConfirmation.minimumCreditCeiling
# 2. Confirm the spend ceiling (mints a short-lived credit-approval token)
token=$(dreamina-canvas --format json node confirm \
--node-id <nodeId> \
--project-id "$PROJECT_ID" \
--credit-ceiling "$CEILING" \
| jq -r '.data.credit-approval token')
# 3. Run with the existing submitId; reuse the same id on retry
dreamina-canvas --format json node run \
--node-id <nodeId> \
--project-id "$PROJECT_ID" \
--credit-token "$token" \
--submit-id "$SUBMIT_ID"
# Batch: one --submit-id per --node-id, equal length and order
dreamina-canvas --format json node run \
--node-id <node1> --node-id <node2> \
--submit-id <id1> --submit-id <id2> \
--project-id "$PROJECT_ID" --credit-token "$token"
Binding rules (read carefully)
The credit token is bound to **who + which canvas + which batch of nodes
- ceiling + batch summary**. It is not bound to the prompt text.
Two direct consequences:
- Editing the prompt between confirm and run is fine as long as the
latest live quote is still within the approved ceiling.
node runre-quotes the latest draft and runs only if the total stays under the ceiling. - The token cannot be moved to a different node set. Different nodes need a new confirm.
node confirm refuses to mint a token when:
- Any node is un-quotable (
confirmable=false). - The supplied
--credit-ceilingis below the latest total (returns Conflict).
The submitId is the only billable identity
Generate submitId yourself (lowercase UUID) and persist it before
node run. Reusing the same submitId replays the original submission
and never re-bills. Switching to a new submitId is a new run and
re-bills.
--submit-id "" (explicit empty) is rejected with exit code 2; this is the
deliberate trap that prevents an unset shell variable from silently
minting a fresh idempotency key.
Per-item batch behaviour
node run may take multiple --node-id values. The response data.items[]
is the same length and order as the input. Per-item outcomes:
Before a batch call, mint and persist one --submit-id per --node-id.
The two lists must have equal length and order. On retry, reuse every ID in
its original position; a single batch-wide ID is invalid, and replacing any
item's ID can re-bill that item.
REJECTED(any item) →cli.node_run_rejected, exit code 2. Rejection is terminal; retry is useless.unknown(any item, no rejection) →cli.node_run_unknown, exit code 20,requiredAction: resume.- All accepted → exit code 0.
If submission fails entirely, the failure envelope includes
partialData.items[] with each item's nodeId / submitId. Absence of a
state field in an item is not failure — it means the call did not
return a verdict for that item; the operation may still be in flight
server-side. Use operation status <submitId> to inspect each item.
Recovery rules
- An ambiguous or timed-out response is reconciled by ID, not by re-submitting.
operationRef(alias forsubmitId) is the key; pass it tooperation statusoroperation wait.submission.state = absentplusresubmittable = trueis the only case where re-submission is permitted. A missingsubmissionfield is treated as "accepted by server", never as "absent" — assuming absent when the server actually accepted would cause a duplicate bill.
What this Skill will not do
- Persist
credit-approval token, signed URLs, cookies, OAuth tokens, or any provider task identifier. - Re-quote with a stale draft.
- Mint a new
submitIdon retry; it must be reused. - Branch on localized
messagetext — only on exit code,error.code, anderror.requiredAction. - Use
--yesinstead of--credit-ceilingwhen the price is knowable in advance.
Policy
Invocation requires:
- Confirmed installation of dreamina-canvas
- User-supplied projectId, nodeIds, submitId, and credit ceiling
- Explicit action-time approval for the spend
Forbids:
- Persisting credit-approval token, signed URLs, cookies, or OAuth tokens
- Re-quoting from a stale draft
- Minting a new submitId on retry
- Using --yes when the ceiling is knowable in advance
- Treating a missing submission.state field as 'absent'
Default prompt:
Always re-quote from the latest authoritative draft before run. Bind approval with --credit-ceiling, not --yes. Reuse the same submitId on recovery; switching submitId re-bills.