Governed substrate for autonomous agents: scoped identity (passports), audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
237 lines
14 KiB
PL/PgSQL
237 lines
14 KiB
PL/PgSQL
-- ADR-0030 Phase 1 — Run-state machine + _workflow_config table
|
||
-- Date: 2026-05-04
|
||
-- Author: @developer-ralph
|
||
-- Doc: 139182
|
||
-- Companion rollback: scripts/rollback/adr-0030-p1-down.sql
|
||
--
|
||
-- Scope:
|
||
-- 1. Add 10 run_* virtual columns to tickets table (table_id=1708) — ADR-0030 §3.4
|
||
-- 2. Create _workflow_config CRM virtual table (table_id=100000) — ADR-0030 §3.2
|
||
-- 3. Insert singleton row with §13 defaults
|
||
--
|
||
-- Idempotent: every INSERT is gated by WHERE NOT EXISTS, safe to re-run.
|
||
-- Storage model: virtual columns (rows in table_columns), data in table_rows.data JSONB.
|
||
-- No physical tables created — matches CRM convention (Path A from architect ack).
|
||
|
||
BEGIN;
|
||
|
||
-- =============================================================
|
||
-- Part 1 — Run-state columns on tickets (table_id=1708)
|
||
-- ADR-0030 §3.4: 10 columns, all nullable, no defaults at row level
|
||
-- =============================================================
|
||
|
||
-- run_state: select with 12 valid states (idle is implicit default via app code)
|
||
INSERT INTO table_columns (table_id, column_name, display_name, type, config, order_index, is_visible, is_required, is_system)
|
||
SELECT 1708, 'run_state', 'Run', 'select',
|
||
'{"appearance":{"align":"left","indicator":{"type":"emoji","value":"🎼"}},"options":[{"label":"idle"},{"label":"queued"},{"label":"preparing"},{"label":"running"},{"label":"streaming"},{"label":"awaiting_approval"},{"label":"succeeded"},{"label":"failed"},{"label":"timed_out"},{"label":"stalled"},{"label":"canceled"},{"label":"retry_after"}]}',
|
||
100, 1, 0, 0
|
||
WHERE NOT EXISTS (SELECT 1 FROM table_columns WHERE table_id=1708 AND column_name='run_state');
|
||
|
||
-- run_attempt: number (1-based)
|
||
INSERT INTO table_columns (table_id, column_name, display_name, type, config, order_index, is_visible, is_required, is_system)
|
||
SELECT 1708, 'run_attempt', 'Attempt', 'number',
|
||
'{"min":0}',
|
||
101, 1, 0, 0
|
||
WHERE NOT EXISTS (SELECT 1 FROM table_columns WHERE table_id=1708 AND column_name='run_attempt');
|
||
|
||
-- run_thread_id: text (claude --continue key)
|
||
INSERT INTO table_columns (table_id, column_name, display_name, type, config, order_index, is_visible, is_required, is_system)
|
||
SELECT 1708, 'run_thread_id', 'Thread', 'text',
|
||
'{}',
|
||
102, 0, 0, 0
|
||
WHERE NOT EXISTS (SELECT 1 FROM table_columns WHERE table_id=1708 AND column_name='run_thread_id');
|
||
|
||
-- run_workspace_path: text (e.g. /root/workspaces/T-138981)
|
||
INSERT INTO table_columns (table_id, column_name, display_name, type, config, order_index, is_visible, is_required, is_system)
|
||
SELECT 1708, 'run_workspace_path', 'Workspace', 'text',
|
||
'{}',
|
||
103, 0, 0, 0
|
||
WHERE NOT EXISTS (SELECT 1 FROM table_columns WHERE table_id=1708 AND column_name='run_workspace_path');
|
||
|
||
-- run_started_at: datetime (first transition out of idle)
|
||
INSERT INTO table_columns (table_id, column_name, display_name, type, config, order_index, is_visible, is_required, is_system)
|
||
SELECT 1708, 'run_started_at', 'Run Started', 'datetime',
|
||
'{}',
|
||
104, 0, 0, 0
|
||
WHERE NOT EXISTS (SELECT 1 FROM table_columns WHERE table_id=1708 AND column_name='run_started_at');
|
||
|
||
-- run_finished_at: datetime (terminal-state transition)
|
||
INSERT INTO table_columns (table_id, column_name, display_name, type, config, order_index, is_visible, is_required, is_system)
|
||
SELECT 1708, 'run_finished_at', 'Run Finished', 'datetime',
|
||
'{}',
|
||
105, 0, 0, 0
|
||
WHERE NOT EXISTS (SELECT 1 FROM table_columns WHERE table_id=1708 AND column_name='run_finished_at');
|
||
|
||
-- run_last_event_at: datetime (heartbeat — every chat msg or claude stdout line)
|
||
INSERT INTO table_columns (table_id, column_name, display_name, type, config, order_index, is_visible, is_required, is_system)
|
||
SELECT 1708, 'run_last_event_at', 'Last Event', 'datetime',
|
||
'{}',
|
||
106, 0, 0, 0
|
||
WHERE NOT EXISTS (SELECT 1 FROM table_columns WHERE table_id=1708 AND column_name='run_last_event_at');
|
||
|
||
-- run_terminal_reason: text (free-form: agent_exit_zero, stall, turn_timeout, etc.)
|
||
INSERT INTO table_columns (table_id, column_name, display_name, type, config, order_index, is_visible, is_required, is_system)
|
||
SELECT 1708, 'run_terminal_reason', 'Terminal Reason', 'text',
|
||
'{}',
|
||
107, 0, 0, 0
|
||
WHERE NOT EXISTS (SELECT 1 FROM table_columns WHERE table_id=1708 AND column_name='run_terminal_reason');
|
||
|
||
-- run_next_attempt_after: datetime (computed retry time, exp backoff)
|
||
INSERT INTO table_columns (table_id, column_name, display_name, type, config, order_index, is_visible, is_required, is_system)
|
||
SELECT 1708, 'run_next_attempt_after', 'Retry After', 'datetime',
|
||
'{}',
|
||
108, 0, 0, 0
|
||
WHERE NOT EXISTS (SELECT 1 FROM table_columns WHERE table_id=1708 AND column_name='run_next_attempt_after');
|
||
|
||
-- run_pending_approval_token: text (TOTP challenge hash)
|
||
INSERT INTO table_columns (table_id, column_name, display_name, type, config, order_index, is_visible, is_required, is_system)
|
||
SELECT 1708, 'run_pending_approval_token', 'Approval Token', 'text',
|
||
'{}',
|
||
109, 0, 0, 0
|
||
WHERE NOT EXISTS (SELECT 1 FROM table_columns WHERE table_id=1708 AND column_name='run_pending_approval_token');
|
||
|
||
|
||
-- =============================================================
|
||
-- Part 2 — _workflow_config CRM virtual table (table_id=100000)
|
||
-- ADR-0030 §3.2: singleton row, hot-reloaded every 5 min
|
||
-- =============================================================
|
||
|
||
-- Register table metadata. tables.id is a plain INTEGER PK (no sequence).
|
||
INSERT INTO tables (id, name, display_name, icon)
|
||
SELECT 100000, '_workflow_config', 'Workflow Config', '🎼'
|
||
WHERE NOT EXISTS (SELECT 1 FROM tables WHERE id=100000);
|
||
|
||
-- Columns 1-9: numeric tunables (poll, concurrency, timeouts, retry)
|
||
INSERT INTO table_columns (table_id, column_name, display_name, type, config, order_index, is_visible, is_required, is_system)
|
||
SELECT 100000, 'poll_interval_ms', 'Poll Interval (ms)', 'number', '{"min":1000}', 0, 1, 1, 0
|
||
WHERE NOT EXISTS (SELECT 1 FROM table_columns WHERE table_id=100000 AND column_name='poll_interval_ms');
|
||
|
||
INSERT INTO table_columns (table_id, column_name, display_name, type, config, order_index, is_visible, is_required, is_system)
|
||
SELECT 100000, 'max_concurrent_runs', 'Max Concurrent Runs', 'number', '{"min":1}', 1, 1, 1, 0
|
||
WHERE NOT EXISTS (SELECT 1 FROM table_columns WHERE table_id=100000 AND column_name='max_concurrent_runs');
|
||
|
||
INSERT INTO table_columns (table_id, column_name, display_name, type, config, order_index, is_visible, is_required, is_system)
|
||
SELECT 100000, 'max_per_agent', 'Max Per Agent', 'number', '{"min":1}', 2, 1, 1, 0
|
||
WHERE NOT EXISTS (SELECT 1 FROM table_columns WHERE table_id=100000 AND column_name='max_per_agent');
|
||
|
||
INSERT INTO table_columns (table_id, column_name, display_name, type, config, order_index, is_visible, is_required, is_system)
|
||
SELECT 100000, 'max_turns', 'Max Turns', 'number', '{"min":1}', 3, 1, 1, 0
|
||
WHERE NOT EXISTS (SELECT 1 FROM table_columns WHERE table_id=100000 AND column_name='max_turns');
|
||
|
||
INSERT INTO table_columns (table_id, column_name, display_name, type, config, order_index, is_visible, is_required, is_system)
|
||
SELECT 100000, 'stall_timeout_ms', 'Stall Timeout (ms)', 'number', '{"min":1000}', 4, 1, 1, 0
|
||
WHERE NOT EXISTS (SELECT 1 FROM table_columns WHERE table_id=100000 AND column_name='stall_timeout_ms');
|
||
|
||
INSERT INTO table_columns (table_id, column_name, display_name, type, config, order_index, is_visible, is_required, is_system)
|
||
SELECT 100000, 'turn_timeout_ms', 'Turn Timeout (ms)', 'number', '{"min":1000}', 5, 1, 1, 0
|
||
WHERE NOT EXISTS (SELECT 1 FROM table_columns WHERE table_id=100000 AND column_name='turn_timeout_ms');
|
||
|
||
INSERT INTO table_columns (table_id, column_name, display_name, type, config, order_index, is_visible, is_required, is_system)
|
||
SELECT 100000, 'retry_backoff_min_ms', 'Retry Backoff Min (ms)', 'number', '{"min":1000}', 6, 1, 1, 0
|
||
WHERE NOT EXISTS (SELECT 1 FROM table_columns WHERE table_id=100000 AND column_name='retry_backoff_min_ms');
|
||
|
||
INSERT INTO table_columns (table_id, column_name, display_name, type, config, order_index, is_visible, is_required, is_system)
|
||
SELECT 100000, 'retry_backoff_max_ms', 'Retry Backoff Max (ms)', 'number', '{"min":1000}', 7, 1, 1, 0
|
||
WHERE NOT EXISTS (SELECT 1 FROM table_columns WHERE table_id=100000 AND column_name='retry_backoff_max_ms');
|
||
|
||
INSERT INTO table_columns (table_id, column_name, display_name, type, config, order_index, is_visible, is_required, is_system)
|
||
SELECT 100000, 'max_attempts', 'Max Attempts', 'number', '{"min":1}', 8, 1, 1, 0
|
||
WHERE NOT EXISTS (SELECT 1 FROM table_columns WHERE table_id=100000 AND column_name='max_attempts');
|
||
|
||
-- Column 10: workspace root path
|
||
INSERT INTO table_columns (table_id, column_name, display_name, type, config, order_index, is_visible, is_required, is_system)
|
||
SELECT 100000, 'workspace_root', 'Workspace Root', 'text', '{}', 9, 1, 1, 0
|
||
WHERE NOT EXISTS (SELECT 1 FROM table_columns WHERE table_id=100000 AND column_name='workspace_root');
|
||
|
||
-- Columns 11-13: hooks (bash snippets)
|
||
INSERT INTO table_columns (table_id, column_name, display_name, type, config, order_index, is_visible, is_required, is_system)
|
||
SELECT 100000, 'hook_after_create', 'Hook: After Create', 'text', '{"cellFormat":{"mode":"markdown"}}', 10, 1, 0, 0
|
||
WHERE NOT EXISTS (SELECT 1 FROM table_columns WHERE table_id=100000 AND column_name='hook_after_create');
|
||
|
||
INSERT INTO table_columns (table_id, column_name, display_name, type, config, order_index, is_visible, is_required, is_system)
|
||
SELECT 100000, 'hook_before_run', 'Hook: Before Run', 'text', '{"cellFormat":{"mode":"markdown"}}', 11, 1, 0, 0
|
||
WHERE NOT EXISTS (SELECT 1 FROM table_columns WHERE table_id=100000 AND column_name='hook_before_run');
|
||
|
||
INSERT INTO table_columns (table_id, column_name, display_name, type, config, order_index, is_visible, is_required, is_system)
|
||
SELECT 100000, 'hook_after_run', 'Hook: After Run', 'text', '{"cellFormat":{"mode":"markdown"}}', 12, 1, 0, 0
|
||
WHERE NOT EXISTS (SELECT 1 FROM table_columns WHERE table_id=100000 AND column_name='hook_after_run');
|
||
|
||
-- Column 14: default model
|
||
INSERT INTO table_columns (table_id, column_name, display_name, type, config, order_index, is_visible, is_required, is_system)
|
||
SELECT 100000, 'default_model', 'Default Model', 'select',
|
||
'{"options":[{"label":"opus"},{"label":"sonnet"},{"label":"haiku"}]}',
|
||
13, 1, 1, 0
|
||
WHERE NOT EXISTS (SELECT 1 FROM table_columns WHERE table_id=100000 AND column_name='default_model');
|
||
|
||
-- Column 15: default approval mode
|
||
INSERT INTO table_columns (table_id, column_name, display_name, type, config, order_index, is_visible, is_required, is_system)
|
||
SELECT 100000, 'default_approval_mode', 'Default Approval Mode', 'select',
|
||
'{"options":[{"label":"per-run"},{"label":"per-sensitive-op"},{"label":"never"}]}',
|
||
14, 1, 1, 0
|
||
WHERE NOT EXISTS (SELECT 1 FROM table_columns WHERE table_id=100000 AND column_name='default_approval_mode');
|
||
|
||
-- Column 16: paused (emergency kill-switch)
|
||
INSERT INTO table_columns (table_id, column_name, display_name, type, config, order_index, is_visible, is_required, is_system)
|
||
SELECT 100000, 'paused', 'Paused (Kill Switch)', 'checkbox',
|
||
'{"appearance":{"align":"center","indicator":{"type":"emoji","value":"🛑"}}}',
|
||
15, 1, 1, 0
|
||
WHERE NOT EXISTS (SELECT 1 FROM table_columns WHERE table_id=100000 AND column_name='paused');
|
||
|
||
-- Column 17: paused_verification — TOTP gate on paused (ADR-0011 pattern)
|
||
-- Toggling paused requires TOTP because it can halt all autonomous runs.
|
||
INSERT INTO table_columns (table_id, column_name, display_name, type, config, order_index, is_visible, is_required, is_system)
|
||
SELECT 100000, 'paused_verification', 'Paused Verification', 'verification',
|
||
'{"guards":["paused"],"method":"totp","policy":"all","ttl_ms":86400000,"ttl_seconds":86400,"cooldown_ms":300000,"cooldown_seconds":300,"rate_limit":null,"method_config":{},"required_methods":1,"available_methods":["totp"],"locks_on_statuses":[],"unlocks_on_statuses":[]}',
|
||
16, 0, 0, 0
|
||
WHERE NOT EXISTS (SELECT 1 FROM table_columns WHERE table_id=100000 AND column_name='paused_verification');
|
||
|
||
-- Columns 18-19: audit
|
||
INSERT INTO table_columns (table_id, column_name, display_name, type, config, order_index, is_visible, is_required, is_system)
|
||
SELECT 100000, 'updated_at', 'Updated At', 'datetime', '{}', 17, 1, 0, 0
|
||
WHERE NOT EXISTS (SELECT 1 FROM table_columns WHERE table_id=100000 AND column_name='updated_at');
|
||
|
||
INSERT INTO table_columns (table_id, column_name, display_name, type, config, order_index, is_visible, is_required, is_system)
|
||
SELECT 100000, 'updated_by', 'Updated By', 'select',
|
||
'{"relation":{"enabled":true,"tableId":"1782","valueColumn":"id","labelColumn":"name"}}',
|
||
18, 1, 0, 0
|
||
WHERE NOT EXISTS (SELECT 1 FROM table_columns WHERE table_id=100000 AND column_name='updated_by');
|
||
|
||
|
||
-- =============================================================
|
||
-- Part 3 — Singleton config row with §13 F5/F6/F7/F8 defaults
|
||
-- =============================================================
|
||
|
||
INSERT INTO table_rows (table_id, base_id, data, created_at, updated_at)
|
||
SELECT 100000, 'WORKFLOW1', jsonb_build_object(
|
||
'poll_interval_ms', 30000, -- §13 F5 default
|
||
'max_concurrent_runs', 3, -- §13 F6 default
|
||
'max_per_agent', 1, -- §13 F6 default
|
||
'max_turns', 20,
|
||
'stall_timeout_ms', 300000, -- §13 F7 default (5 min)
|
||
'turn_timeout_ms', 3600000, -- 60 min per turn
|
||
'retry_backoff_min_ms', 10000, -- §13 F8 default (10s × 2^n)
|
||
'retry_backoff_max_ms', 300000, -- 5 min cap
|
||
'max_attempts', 3, -- §13 F8 default
|
||
'workspace_root', '/root/workspaces',
|
||
'hook_after_create', NULL,
|
||
'hook_before_run', NULL,
|
||
'hook_after_run', NULL,
|
||
'default_model', 'opus',
|
||
'default_approval_mode', 'per-sensitive-op', -- §13 F4 default
|
||
'paused', false,
|
||
'updated_at', to_char(now(), 'YYYY-MM-DD"T"HH24:MI:SS"Z"'),
|
||
'updated_by', NULL
|
||
), now(), now()
|
||
WHERE NOT EXISTS (SELECT 1 FROM table_rows WHERE table_id=100000 AND base_id='WORKFLOW1');
|
||
|
||
COMMIT;
|
||
|
||
-- Verification queries (run manually to confirm):
|
||
-- SELECT column_name FROM table_columns WHERE table_id=1708 AND column_name LIKE 'run_%' ORDER BY column_name;
|
||
-- -> 10 rows
|
||
-- SELECT id, name FROM tables WHERE id=100000;
|
||
-- -> 1 row, name='_workflow_config'
|
||
-- SELECT count(*) FROM table_columns WHERE table_id=100000;
|
||
-- -> 19 rows
|
||
-- SELECT data->>'paused', data->>'default_model' FROM table_rows WHERE table_id=100000;
|
||
-- -> 'false', 'opus'
|