Migrations 0008-0011 lay down the triggers framework's storage: - `triggers` + `kv_trigger_details` + `dead_letter_trigger_details` (Layout E, design notes §2). Parent table carries common columns including `registered_by_principal` — the dispatcher uses this to run the trigger as the user that registered it (design notes §4). - `outbox`: universal async dispatch substrate. KV/cron/pubsub/queue/ email/dead-letter all write rows in the same shape; the dispatcher claims due rows via FOR UPDATE SKIP LOCKED. `reply_to` is the NATS-style inbox id for sync HTTP (commit 6) — its presence flags "don't retry" per the design. - `dead_letters`: exact schema from design notes §4 with the four- value `resolution` CHECK constraint (`replayed | ignored | handled_by_script | handler_failed`) and partial index on unresolved rows for the dashboard badge. - `abandoned_executions`: forensic table for the dispatcher's "tried to resolve a dropped inbox" edge case (design notes §3 #9). Repo surfaces with Postgres impls behind traits so unit tests can swap in-memory backings: - `TriggerRepo` — CRUD + the `list_matching_kv` / `list_matching_dead_letter` hot paths the dispatcher uses. Includes a `collection_matches` helper that handles `*`, `prefix:*`, and exact-name globs. - `OutboxRepo` — insert + claim-due + delete + reschedule. - `DeadLetterRepo` — insert + get + list + unresolved-count + resolve + GC. - `AbandonedRepo` — insert + GC. `TriggerConfig::from_env` (new module) follows the existing `SandboxCeiling` env-loading pattern for `PICLOUD_MAX_TRIGGER_DEPTH`, `PICLOUD_TRIGGER_RETRY_*`, `PICLOUD_DEAD_LETTER_RETENTION_DAYS`, and `PICLOUD_ABANDONED_EXECUTIONS_RETENTION_DAYS`. `Capability::AppManageTriggers(AppId)` and `AppDeadLetterManage(AppId)` join the enum. Both map onto the existing `Scope::AppAdmin` per the seven-scope commitment; `role_satisfies` grants them at the `AppAdmin` per-app role. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
32 lines
1.4 KiB
SQL
32 lines
1.4 KiB
SQL
-- v1.1.1: abandoned_executions — design notes §3 #9.
|
|
--
|
|
-- Forensic table for the "dispatcher tried to resolve a oneshot inbox
|
|
-- but the receiver was already dropped" edge case. The orchestrator
|
|
-- timed out (returned 504 to the caller) and gave up on the channel,
|
|
-- but then the dispatcher's execution succeeded later. The caller
|
|
-- never sees the result; the row exists so the operator can
|
|
-- correlate when the abandoned-counter metric spikes.
|
|
--
|
|
-- Only the dispatcher-after-orchestrator-timeout edge case writes
|
|
-- here; ordinary "script timed out, caller got 504" stays uneventful.
|
|
--
|
|
-- 7-day retention, GC by `created_at`, sweep alongside dead_letters.
|
|
|
|
CREATE TABLE abandoned_executions (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
app_id UUID NOT NULL REFERENCES apps(id) ON DELETE CASCADE,
|
|
-- Original outbox row id (the row itself has been deleted).
|
|
outbox_id UUID NOT NULL,
|
|
script_id UUID,
|
|
-- The inbox channel id the dispatcher tried to resolve.
|
|
inbox_id UUID NOT NULL,
|
|
-- The HTTP status code the dispatcher attempted to send back.
|
|
status_code INT NOT NULL,
|
|
-- Truncated body / error description (capped at write time —
|
|
-- the dispatcher doesn't need to ship megabytes here).
|
|
result_summary TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_abandoned_executions_gc ON abandoned_executions (created_at);
|