Explicit bounded compensation
A compensation requests the declared business inverse of one confirmed external effect, such as releasing a reservation. It is never implicitly triggered by failure, fallback, cancellation or deployment rollback.
The original effect remains applied. The inverse has its own run, effect,
budgets, secrets and authority. Its outcome is completed, partial,
unavailable, failed or unknown; only an observed inverse outcome can
determine that result.
Declare the inverse
A touba-manifest-v1 external-write binding identifies an existing strategy in
its effect contract:
{
"manifest_version": "touba-manifest-v1",
"bindings": {
"tool:reservations/CreateReservation": {
"handler": {
"kind": "process",
"command": "node",
"args": ["handlers/create-reservation.mjs"]
},
"grants": ["network"],
"secrets": ["RESERVATION_API_TOKEN"],
"timeout_ms": 5000,
"max_payload_bytes": 65536,
"effect_contract": {
"kind": "external_write",
"retry_mode": "idempotency_key",
"compensation": {
"entry_ref": "strategy:reservations/ReleaseReservation"
}
}
}
}
}
The inverse graph
contains pure bind nodes, at most one human approval, exactly one external
tool call and a terminal give or stop. Branches, loops, fallbacks, nested
calls, interactions, a second effect and recursive compensation are refused.
Handler contract
For one declared input, request contains its value directly:
import { createToolResult, defineTool } from "@touba/sdk";
await defineTool({
toolRef: "tool:reservations/ReleaseReservation",
inputSignature: [
{ name: "reservation_id", type_ref: "builtin:Text" },
],
outputType: "builtin:Text",
run: async ({ request: reservation_id, context }) => {
const response = await releaseReservation({
reservationId: reservation_id,
originalReceipt: context.compensation.original_effect_receipt,
idempotencyKey: context.effect.effect_id,
});
return createToolResult(response.message, {
effectReceipt: context.createEffectReceipt({
outcome: "applied",
receipt_ref: response.receiptRef,
}),
compensationReceipt: context.createCompensationReceipt({
result: response.complete ? "completed" : "partial",
}),
});
},
});
releaseReservation is your business adapter. See the
runnable callback test.
The inverse handler receives context.compensation.original_effect_receipt
and context.effect.effect_id. It returns an effect receipt created with
context.createEffectReceipt(...) and a compensation receipt created with
context.createCompensationReceipt(...). Keep the original receipt in control
data; do not copy it into business arguments, logs or public traces.
The host separately requests creation and resume. Each command checks target authority before reading the journal, then revalidates authority before progress and immediately before spawning a handler. Resume supplies the current revision and digest. An exact already-handled replay is read-only: no handler or new trace. A fresh approval can replace an expired approval for the same subject and reference; approval is not inherited from the original run.
Meaning of completion
Completion means the declared inverse was applied, not that the external world was perfectly restored. The supplied SQLite store atomically checks revisions and digests across local processes. The host coordinates executions and backups; this store supplies no multi-host lease or tamper-proof history. Existing JSON state uses the explicit migration in Storage and retention.
See External effects.