feat(db): add document revision ledger schema 00028
Introduce immutable blobs/snapshots, versions, events, candidates, change reports, and ledger_* root pointers; rename freeform current_version_id to legacy_version_id. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,197 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- Document revision ledger: immutable snapshots, Version/Revision, agent candidates, change reports.
|
||||||
|
-- Pointer columns use ledger_*/head_* to avoid colliding with freeform_documents.current_version_id.
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS document_content_blobs (
|
||||||
|
content_sha256 CHAR(64) PRIMARY KEY,
|
||||||
|
content_markdown TEXT NOT NULL,
|
||||||
|
byte_size BIGINT NOT NULL CHECK (byte_size >= 0),
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS document_snapshots (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
doc_id TEXT NOT NULL,
|
||||||
|
content_sha256 CHAR(64) NOT NULL REFERENCES document_content_blobs(content_sha256),
|
||||||
|
parent_snapshot_id BIGINT REFERENCES document_snapshots(id),
|
||||||
|
block_map JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||||
|
source_kind TEXT NOT NULL,
|
||||||
|
actor_user_id BIGINT,
|
||||||
|
actor_ref TEXT NOT NULL DEFAULT '',
|
||||||
|
actor_snapshot JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||||
|
source_run_id TEXT NOT NULL DEFAULT '',
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
CONSTRAINT chk_snapshot_source_kind CHECK (source_kind IN ('template','human','agent','agent_squad','migration','system'))
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_document_snapshots_doc ON document_snapshots(doc_id, created_at);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS document_versions (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
doc_id TEXT NOT NULL,
|
||||||
|
version_no INT NOT NULL,
|
||||||
|
status TEXT NOT NULL,
|
||||||
|
origin_kind TEXT NOT NULL,
|
||||||
|
baseline_snapshot_id BIGINT NOT NULL REFERENCES document_snapshots(id),
|
||||||
|
head_snapshot_id BIGINT NOT NULL REFERENCES document_snapshots(id),
|
||||||
|
human_revision_count INT NOT NULL DEFAULT 0,
|
||||||
|
source_run_id TEXT NOT NULL DEFAULT '',
|
||||||
|
change_report_id BIGINT,
|
||||||
|
opened_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
sealed_at TIMESTAMPTZ,
|
||||||
|
CONSTRAINT uq_document_versions_doc_no UNIQUE (doc_id, version_no),
|
||||||
|
CONSTRAINT chk_document_versions_status CHECK (status IN ('open','sealed')),
|
||||||
|
CONSTRAINT chk_document_versions_origin CHECK (origin_kind IN ('template','agent','agent_squad','migration'))
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS uq_document_versions_one_open
|
||||||
|
ON document_versions(doc_id) WHERE status = 'open';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS document_change_events (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
doc_id TEXT NOT NULL,
|
||||||
|
version_id BIGINT NOT NULL REFERENCES document_versions(id),
|
||||||
|
event_no INT NOT NULL,
|
||||||
|
from_snapshot_id BIGINT REFERENCES document_snapshots(id),
|
||||||
|
to_snapshot_id BIGINT NOT NULL REFERENCES document_snapshots(id),
|
||||||
|
change_kind TEXT NOT NULL,
|
||||||
|
actor_kind TEXT NOT NULL,
|
||||||
|
actor_user_id BIGINT,
|
||||||
|
actor_ref TEXT NOT NULL DEFAULT '',
|
||||||
|
actor_snapshot JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||||
|
change_set JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||||
|
edit_session_id TEXT NOT NULL DEFAULT '',
|
||||||
|
request_id TEXT NOT NULL DEFAULT '',
|
||||||
|
idempotency_key TEXT NOT NULL,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
CONSTRAINT uq_document_change_events_doc_event UNIQUE (doc_id, event_no),
|
||||||
|
CONSTRAINT uq_document_change_events_idemp UNIQUE (doc_id, idempotency_key),
|
||||||
|
CONSTRAINT chk_change_kind CHECK (change_kind IN ('human_revision','agent_commit','agent_noop','import','restore_as_new')),
|
||||||
|
CONSTRAINT chk_actor_kind CHECK (actor_kind IN ('human','agent','agent_squad','template','migration','system'))
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS document_change_reports (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
doc_id TEXT NOT NULL,
|
||||||
|
from_snapshot_id BIGINT NOT NULL REFERENCES document_snapshots(id),
|
||||||
|
to_snapshot_id BIGINT NOT NULL REFERENCES document_snapshots(id),
|
||||||
|
schema_version INT NOT NULL DEFAULT 1,
|
||||||
|
summary JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||||
|
risk_level TEXT NOT NULL,
|
||||||
|
risk_items JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||||
|
project_impacts JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||||
|
document_impacts JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||||
|
open_questions JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||||
|
analysis_status TEXT NOT NULL,
|
||||||
|
model_snapshot TEXT NOT NULL DEFAULT '',
|
||||||
|
prompt_version TEXT NOT NULL DEFAULT '',
|
||||||
|
deterministic_facts JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||||
|
corrects_report_id BIGINT REFERENCES document_change_reports(id),
|
||||||
|
raw_json TEXT NOT NULL DEFAULT '',
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
CONSTRAINT chk_report_risk CHECK (risk_level IN ('low','medium','high','critical')),
|
||||||
|
CONSTRAINT chk_report_analysis CHECK (analysis_status IN ('complete','fallback','unassessed','corrected'))
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE document_versions
|
||||||
|
ADD CONSTRAINT fk_document_versions_change_report
|
||||||
|
FOREIGN KEY (change_report_id) REFERENCES document_change_reports(id);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS document_agent_candidates (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
doc_id TEXT NOT NULL,
|
||||||
|
input_snapshot_id BIGINT NOT NULL REFERENCES document_snapshots(id),
|
||||||
|
output_snapshot_id BIGINT REFERENCES document_snapshots(id),
|
||||||
|
run_id TEXT NOT NULL,
|
||||||
|
proposal_id TEXT NOT NULL DEFAULT '',
|
||||||
|
status TEXT NOT NULL,
|
||||||
|
authorized_scope TEXT NOT NULL DEFAULT '',
|
||||||
|
quality_result TEXT NOT NULL DEFAULT '',
|
||||||
|
change_report_id BIGINT REFERENCES document_change_reports(id),
|
||||||
|
actor_kind TEXT NOT NULL,
|
||||||
|
actor_snapshot JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
decided_at TIMESTAMPTZ,
|
||||||
|
CONSTRAINT uq_document_agent_candidates_run UNIQUE (doc_id, run_id),
|
||||||
|
CONSTRAINT chk_candidate_status CHECK (status IN ('ready','needs_confirmation','committed','rejected','stale','quality_failed')),
|
||||||
|
CONSTRAINT chk_candidate_actor CHECK (actor_kind IN ('agent','agent_squad'))
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE document_records
|
||||||
|
ADD COLUMN IF NOT EXISTS ledger_version_id BIGINT REFERENCES document_versions(id),
|
||||||
|
ADD COLUMN IF NOT EXISTS head_snapshot_id BIGINT REFERENCES document_snapshots(id),
|
||||||
|
ADD COLUMN IF NOT EXISTS head_revision_no INT NOT NULL DEFAULT 0,
|
||||||
|
ADD COLUMN IF NOT EXISTS review_snapshot_id BIGINT REFERENCES document_snapshots(id),
|
||||||
|
ADD COLUMN IF NOT EXISTS revision_schema_version INT NOT NULL DEFAULT 0,
|
||||||
|
ADD COLUMN IF NOT EXISTS row_revision BIGINT NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
-- Rename freeform legacy pointer to avoid semantic collision with ledger version id.
|
||||||
|
ALTER TABLE freeform_documents
|
||||||
|
RENAME COLUMN current_version_id TO legacy_version_id;
|
||||||
|
|
||||||
|
ALTER TABLE freeform_documents
|
||||||
|
ADD COLUMN IF NOT EXISTS ledger_version_id BIGINT REFERENCES document_versions(id),
|
||||||
|
ADD COLUMN IF NOT EXISTS head_snapshot_id BIGINT REFERENCES document_snapshots(id),
|
||||||
|
ADD COLUMN IF NOT EXISTS head_revision_no INT NOT NULL DEFAULT 0,
|
||||||
|
ADD COLUMN IF NOT EXISTS revision_schema_version INT NOT NULL DEFAULT 0,
|
||||||
|
ADD COLUMN IF NOT EXISTS row_revision BIGINT NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
-- Prevent UPDATE/DELETE on immutable snapshot bodies via trigger.
|
||||||
|
-- +goose StatementBegin
|
||||||
|
CREATE OR REPLACE FUNCTION document_snapshots_immutable() RETURNS trigger AS $$
|
||||||
|
BEGIN
|
||||||
|
RAISE EXCEPTION 'document_snapshots is immutable';
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
DROP TRIGGER IF EXISTS trg_document_snapshots_immutable ON document_snapshots;
|
||||||
|
CREATE TRIGGER trg_document_snapshots_immutable
|
||||||
|
BEFORE UPDATE OR DELETE ON document_snapshots
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION document_snapshots_immutable();
|
||||||
|
|
||||||
|
-- +goose StatementBegin
|
||||||
|
CREATE OR REPLACE FUNCTION document_content_blobs_immutable() RETURNS trigger AS $$
|
||||||
|
BEGIN
|
||||||
|
RAISE EXCEPTION 'document_content_blobs is immutable';
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
DROP TRIGGER IF EXISTS trg_document_content_blobs_immutable ON document_content_blobs;
|
||||||
|
CREATE TRIGGER trg_document_content_blobs_immutable
|
||||||
|
BEFORE UPDATE OR DELETE ON document_content_blobs
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION document_content_blobs_immutable();
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
DROP TRIGGER IF EXISTS trg_document_content_blobs_immutable ON document_content_blobs;
|
||||||
|
DROP FUNCTION IF EXISTS document_content_blobs_immutable();
|
||||||
|
DROP TRIGGER IF EXISTS trg_document_snapshots_immutable ON document_snapshots;
|
||||||
|
DROP FUNCTION IF EXISTS document_snapshots_immutable();
|
||||||
|
|
||||||
|
ALTER TABLE freeform_documents
|
||||||
|
DROP COLUMN IF EXISTS row_revision,
|
||||||
|
DROP COLUMN IF EXISTS revision_schema_version,
|
||||||
|
DROP COLUMN IF EXISTS head_revision_no,
|
||||||
|
DROP COLUMN IF EXISTS head_snapshot_id,
|
||||||
|
DROP COLUMN IF EXISTS ledger_version_id;
|
||||||
|
|
||||||
|
ALTER TABLE freeform_documents
|
||||||
|
RENAME COLUMN legacy_version_id TO current_version_id;
|
||||||
|
|
||||||
|
ALTER TABLE document_records
|
||||||
|
DROP COLUMN IF EXISTS row_revision,
|
||||||
|
DROP COLUMN IF EXISTS revision_schema_version,
|
||||||
|
DROP COLUMN IF EXISTS review_snapshot_id,
|
||||||
|
DROP COLUMN IF EXISTS head_revision_no,
|
||||||
|
DROP COLUMN IF EXISTS head_snapshot_id,
|
||||||
|
DROP COLUMN IF EXISTS ledger_version_id;
|
||||||
|
|
||||||
|
ALTER TABLE document_versions DROP CONSTRAINT IF EXISTS fk_document_versions_change_report;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS document_agent_candidates;
|
||||||
|
DROP TABLE IF EXISTS document_change_reports;
|
||||||
|
DROP TABLE IF EXISTS document_change_events;
|
||||||
|
DROP TABLE IF EXISTS document_versions;
|
||||||
|
DROP TABLE IF EXISTS document_snapshots;
|
||||||
|
DROP TABLE IF EXISTS document_content_blobs;
|
||||||
Reference in New Issue
Block a user