107 lines
4.3 KiB
PL/PgSQL
107 lines
4.3 KiB
PL/PgSQL
-- Versioned v8 upgrade for immutable Workbench replay and the AgentRun Kafka projector.
|
|
|
|
ALTER TABLE workbench_projection_outbox ADD COLUMN IF NOT EXISTS outbox_event_id TEXT;
|
|
ALTER TABLE workbench_projection_outbox ADD COLUMN IF NOT EXISTS entity_family TEXT;
|
|
ALTER TABLE workbench_projection_outbox ADD COLUMN IF NOT EXISTS entity_id TEXT;
|
|
|
|
-- Pre-v8 rows did not store immutable fact payloads. Mark them as cutover rows so
|
|
-- recovery advances its cursor without fabricating message/turn/trace entities.
|
|
UPDATE workbench_projection_outbox
|
|
SET outbox_event_id = COALESCE(outbox_event_id, 'legacy:' || outbox_seq::text),
|
|
entity_family = COALESCE(entity_family, 'legacy'),
|
|
entity_id = COALESCE(entity_id, 'legacy:' || outbox_seq::text)
|
|
WHERE outbox_event_id IS NULL OR entity_family IS NULL OR entity_id IS NULL;
|
|
|
|
ALTER TABLE workbench_projection_outbox ALTER COLUMN outbox_event_id SET NOT NULL;
|
|
ALTER TABLE workbench_projection_outbox ALTER COLUMN entity_family SET NOT NULL;
|
|
ALTER TABLE workbench_projection_outbox ALTER COLUMN entity_id SET NOT NULL;
|
|
DROP INDEX IF EXISTS idx_workbench_projection_outbox_event_id;
|
|
CREATE UNIQUE INDEX idx_workbench_projection_outbox_event_id ON workbench_projection_outbox(outbox_event_id);
|
|
|
|
CREATE TABLE IF NOT EXISTS workbench_kafka_inbox (
|
|
source_topic TEXT NOT NULL,
|
|
source_partition INTEGER NOT NULL,
|
|
source_offset BIGINT NOT NULL,
|
|
source_key TEXT,
|
|
source_event_id TEXT,
|
|
input_sha256 TEXT NOT NULL,
|
|
trace_id TEXT,
|
|
session_id TEXT,
|
|
run_id TEXT,
|
|
command_id TEXT,
|
|
source_seq INTEGER NOT NULL DEFAULT 0,
|
|
status TEXT NOT NULL DEFAULT 'processing',
|
|
projected_seq INTEGER,
|
|
envelope_json TEXT NOT NULL DEFAULT '{}',
|
|
error_json TEXT NOT NULL DEFAULT '{}',
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL,
|
|
projected_at TEXT,
|
|
PRIMARY KEY (source_topic, source_partition, source_offset)
|
|
);
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_workbench_kafka_inbox_source_event ON workbench_kafka_inbox(source_topic, source_event_id) WHERE source_event_id IS NOT NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_workbench_kafka_inbox_status_updated ON workbench_kafka_inbox(status, updated_at, source_topic, source_partition, source_offset);
|
|
CREATE INDEX IF NOT EXISTS idx_workbench_kafka_inbox_trace ON workbench_kafka_inbox(trace_id, source_seq);
|
|
|
|
CREATE TABLE IF NOT EXISTS workbench_kafka_dlq (
|
|
dlq_seq BIGSERIAL PRIMARY KEY,
|
|
source_topic TEXT NOT NULL,
|
|
source_partition INTEGER NOT NULL,
|
|
source_offset BIGINT NOT NULL,
|
|
source_event_id TEXT,
|
|
input_sha256 TEXT NOT NULL,
|
|
error_code TEXT NOT NULL,
|
|
error_message TEXT NOT NULL,
|
|
envelope_json TEXT NOT NULL DEFAULT '{}',
|
|
created_at TEXT NOT NULL
|
|
);
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_workbench_kafka_dlq_transport ON workbench_kafka_dlq(source_topic, source_partition, source_offset);
|
|
|
|
CREATE TABLE IF NOT EXISTS hwlab_kafka_outbox (
|
|
outbox_seq BIGSERIAL PRIMARY KEY,
|
|
event_id TEXT NOT NULL UNIQUE,
|
|
topic TEXT NOT NULL,
|
|
partition_key TEXT NOT NULL,
|
|
payload_json TEXT NOT NULL,
|
|
headers_json TEXT NOT NULL DEFAULT '{}',
|
|
attempt_count INTEGER NOT NULL DEFAULT 0,
|
|
next_attempt_at TEXT NOT NULL,
|
|
lease_owner TEXT,
|
|
lease_expires_at TEXT,
|
|
published_at TEXT,
|
|
last_error TEXT,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_hwlab_kafka_outbox_due ON hwlab_kafka_outbox(published_at, next_attempt_at, lease_expires_at, outbox_seq);
|
|
|
|
CREATE OR REPLACE FUNCTION notify_hwlab_workbench_projection() RETURNS trigger AS $$
|
|
BEGIN
|
|
PERFORM pg_notify(
|
|
'hwlab_workbench_projection',
|
|
json_build_object(
|
|
'sessionId', NEW.session_id,
|
|
'traceId', NEW.trace_id,
|
|
'outboxSeq', NEW.outbox_seq
|
|
)::text
|
|
);
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
DROP TRIGGER IF EXISTS trg_notify_hwlab_workbench_projection ON workbench_projection_outbox;
|
|
CREATE TRIGGER trg_notify_hwlab_workbench_projection
|
|
AFTER INSERT ON workbench_projection_outbox
|
|
FOR EACH ROW EXECUTE FUNCTION notify_hwlab_workbench_projection();
|
|
|
|
INSERT INTO hwlab_schema_migrations (id, schema_version, applied_at, migration_json)
|
|
VALUES (
|
|
'0008_workbench_kafka_realtime',
|
|
'runtime-durable-postgres-v8',
|
|
CURRENT_TIMESTAMP,
|
|
'{"path":"internal/db/migrations/0008_workbench_kafka_realtime.sql","runtime":"cloud-api"}'
|
|
)
|
|
ON CONFLICT (id) DO UPDATE SET
|
|
schema_version = EXCLUDED.schema_version,
|
|
migration_json = EXCLUDED.migration_json;
|