Findings 4 and 7 of REVIEW-2026-07-30.md, as a single user_version step
(11 -> 12) because they alter the same table.
confirmation_id was written by the INSERT and absent from all three executions
SELECTs, so it always read back empty: "which confirmation authorized this
destructive act" was unanswerable from the API. causation_id was set on the
struct by the engine with no column to land in, and capability_version was
missing entirely despite spec §4.1 and the precedent on confirmations. All
three are now persisted and selected back.
The one-in-flight-per-(capability, target) rule was check-then-insert with no
constraint between, so two concurrent requests could both proceed. It is now a
partial UNIQUE index; the engine's pre-insert query is demoted to an advisory
fast path and a constraint violation maps to ErrExecutionInFlight (409), kept
distinct from the idempotency-key replay case.
Note the migration also rewrites pre-existing duplicate in-flight rows to
status='unknown', keeping the lowest id per pair — creating the index would
otherwise fail outright on any database holding stale started rows, which the
old non-unique index permitted indefinitely. That is a write to existing audit
rows, not just DDL. Rows predating this migration get capability_version=0,
which is indistinguishable from a genuine 0; backfill is not possible.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uea55zaiWuEByEDC4UBSdd
Closes the biggest gap between the running execution engine and
ECOSYSTEM-SPEC.md §4.3: confirmations were entirely unmodeled, so any
capability could execute unconfirmed regardless of requires_confirmation.
- New confirmations table + Confirmation domain type; POST
/api/v1/confirmations mints a TTL-bound (120s) confirmation binding
capability id+version, target entity, and a sorted-key args hash.
- Execute() now requires a valid pending confirmation when the
capability demands one: rejects missing, expired, consumed, or
args/version-mismatched confirmations; consumes on success.
- Capabilities gain enabled (destructive risk defaults to disabled,
matching "must be turned on explicitly") and timeout_seconds.
- One in-flight execution per (capability_id, target_entity_id); a
second concurrent attempt is rejected (surfaced as 409 over HTTP).
- Wall-clock timeout per capability now wraps the provider call; on
timeout the outcome is "unknown" (new ExecutionStatus), never
"failed", and the run is never auto-retried.
- 9 new engine tests cover each guard from the spec's Phase 6 gate.
Vikunja #274.
Go daemon (hexisd/hexisctl) implementing capability registry, guarded
execution (confirmations, blessed-entity checks), systemd/workspace-mcp
providers per ECOSYSTEM-SPEC.md. Snapshotting existing working state
before further development.