From eb75f855a566aec599716b51f515c624787a88fe Mon Sep 17 00:00:00 2001 From: weichen Date: Tue, 15 Sep 2026 02:05:33 +0000 Subject: [PATCH] feat(db): add goose 00037 task_run_manifests Persist typed RunManifest bytes and SHA per run, and allow a finalizing status so U8 can freeze a unique terminal without sharing env databases. Co-authored-by: Cursor --- .../00037_2026-09-15-task-run-manifests.sql | 54 +++++++++++++++++++ internal/db/migrate_test.go | 5 +- internal/db/testdb.go | 10 ++++ 3 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 deploy/postgres/goose/00037_2026-09-15-task-run-manifests.sql diff --git a/deploy/postgres/goose/00037_2026-09-15-task-run-manifests.sql b/deploy/postgres/goose/00037_2026-09-15-task-run-manifests.sql new file mode 100644 index 00000000..c2195c9e --- /dev/null +++ b/deploy/postgres/goose/00037_2026-09-15-task-run-manifests.sql @@ -0,0 +1,54 @@ +-- +goose Up +-- U8: typed RunManifest persistence and optional finalizing status. + +ALTER TABLE task_runs DROP CONSTRAINT IF EXISTS chk_task_run_status; + +ALTER TABLE task_runs ADD CONSTRAINT chk_task_run_status CHECK ( + status IN ( + 'pending', + 'running', + 'finalizing', + 'completed', + 'failed', + 'cancelled', + 'finalizing_failed', + 'quality_failed', + 'interrupted', + 'pending_author_confirmation', + 'blocked_pending_governance' + ) +); + +CREATE TABLE IF NOT EXISTS task_run_manifests ( + run_id TEXT PRIMARY KEY REFERENCES task_runs(run_id) ON DELETE CASCADE, + canonical_bytes BYTEA NOT NULL, + sha256 CHAR(64) NOT NULL, + workspace_id TEXT NOT NULL DEFAULT '', + storage_identity TEXT NOT NULL DEFAULT '', + members JSONB NOT NULL DEFAULT '[]'::jsonb, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + CONSTRAINT uq_task_run_manifests_run UNIQUE (run_id) +); + +CREATE INDEX IF NOT EXISTS idx_task_run_manifests_sha ON task_run_manifests (sha256); + +-- +goose Down +DROP INDEX IF EXISTS idx_task_run_manifests_sha; +DROP TABLE IF EXISTS task_run_manifests; + +ALTER TABLE task_runs DROP CONSTRAINT IF EXISTS chk_task_run_status; + +ALTER TABLE task_runs ADD CONSTRAINT chk_task_run_status CHECK ( + status IN ( + 'pending', + 'running', + 'completed', + 'failed', + 'cancelled', + 'finalizing_failed', + 'quality_failed', + 'interrupted', + 'pending_author_confirmation', + 'blocked_pending_governance' + ) +); diff --git a/internal/db/migrate_test.go b/internal/db/migrate_test.go index 4c3da960..dfd3f360 100644 --- a/internal/db/migrate_test.go +++ b/internal/db/migrate_test.go @@ -100,6 +100,7 @@ func TestGooseM2_00034to00036_DownThenUp(t *testing.T) { "00034_2026-09-14-workspace-lease-fencing.sql", "00035_2026-09-14-durable-event-delivery.sql", "00036_2026-09-14-final-artifact-vault.sql", + "00037_2026-09-15-task-run-manifests.sql", } joined := strings.Join(files, "\n") for _, name := range want { @@ -135,7 +136,7 @@ func TestGooseM2_00034to00036_DownThenUp(t *testing.T) { t.Fatalf("up: %v", err) } ver, err := CurrentVersion(ctx, pool) - if err != nil || ver < 36 { + if err != nil || ver < 37 { t.Fatalf("version after up: %d %v", ver, err) } @@ -148,7 +149,7 @@ func TestGooseM2_00034to00036_DownThenUp(t *testing.T) { if err := goose.DownToContext(ctx, sqlDB, migDir, 33); err != nil { t.Fatalf("down to 33: %v", err) } - for _, table := range []string{"event_envelopes", "event_deliveries", "event_dead_letters", "final_artifacts"} { + for _, table := range []string{"event_envelopes", "event_deliveries", "event_dead_letters", "final_artifacts", "task_run_manifests"} { var n int if err := pool.QueryRow(ctx, `SELECT COUNT(*) FROM information_schema.tables WHERE table_name=$1`, table).Scan(&n); err != nil { t.Fatal(err) diff --git a/internal/db/testdb.go b/internal/db/testdb.go index 4abbb032..e0a8d35a 100644 --- a/internal/db/testdb.go +++ b/internal/db/testdb.go @@ -49,3 +49,13 @@ func SkipIfNoTestDatabaseURL(t *testing.T) string { } return dsn } + +// RefuseSharedEnvDSN fails if the DSN targets a shared env database. +func RefuseSharedEnvDSN(t *testing.T, dsn string) { + t.Helper() + if strings.Contains(dsn, "5433") || strings.Contains(dsn, "agent_system_dev") || + (strings.Contains(dsn, "5434") && strings.Contains(dsn, "agent_system_test")) || + strings.Contains(dsn, "5435") { + t.Fatal("TEST_DATABASE_URL points at a shared env port; refuse agent_system_dev/test/main") + } +}