v0.4.0 — the gate holds
Governance you can prove: approvals that survive anything, spend capped in money, and an audit trail you can read back.
Everything is additive. With no cost keys, no approvers, and no signature header, v0.3.1 behavior is unchanged.
Approval gates that survive a restart
Every gate is written to SQLite before the draft goes out, so an approval in flight outlives a redeploy, a crash, or a host reboot. Whatever happens to the process, the outcome of every gate lands in the audit trail, and the operator is told where things stand.
Spend caps in money
budgets:
per_day_cost: 5.00 # same unit as your model rates
per_pipeline_cost: 0.50Token caps tell you how much the model thought. These answer what it costs. 0 leaves them off.
Per-operator approver scoping
- name: release-payment
type: approval
quorum: 2 # how many must approve
approvers: [111111, 222222] # which onesQuorum says how many. approvers says which ones, so the person who clears inbox drafts is not automatically the person who releases a payment. It narrows the channel's allowed_users and can never widen it.
Body-signed webhooks
Bind each inbound trigger to its exact body and a timestamp:
X-Draftcat-Signature: t=<unix>,v1=<hex hmac-sha256(t + "." + body)>
Enable with webhook.require_signature: true. Requests outside the skew window are refused and each signature is spent once, so a captured request cannot be re-fired. Verified whenever the header is present, even when not required.
Config validated on the boot path
The engine now runs the full draftcat validate check set at startup and refuses to start on errors, so problems surface at boot rather than mid-run. Typos get a suggestion:
ERROR pipelines[0].steps[1].type: invalid type "ia" — did you mean "ai"? (must be one of: deterministic, ai, approval)
DRAFTCAT_SKIP_VALIDATE=1 overrides.
draftcat runs
Read the governance record back out: what ran, when, and who decided what.
draftcat runs [pipeline] [--limit N] [--json]
2026-07-26T05:37:31Z invoices ok 60.0s
release-payment adjust by 111 (0/2)
release-payment approve by 222 (2/2) [signed]
--json gives the archivable form on stdout, ready to pipe into jq or a log shipper. Per-step timings and token counts stay in the observability spans.
Also in this release
- WhatsApp intake (
whatsapp_intake) for governed inbound messages. internal/channelsis the single registry the validator and engine share, sochannel:only accepts what the binary can actually route to. Telegram remains the operator channel.
Issues #1, #2, #3 and #4 are closed by this release.
Full diff: v0.3.1...v0.4.0