Files
foundry-core/db/008-canvas-model.sql
T
codex-bot b85e8bd2ca
Foundry Go checks / check (push) Failing after 2m29s
Foundry Go checks / check (pull_request) Failing after 2m31s
Add canonical canvas rows, stable identity migration and model operations
2026-11-07 22:26:06 +08:00

104 lines
5.3 KiB
PL/PgSQL

BEGIN;
-- Canonical rows are separate from the legacy snapshot. Adoption is an explicit,
-- version-checked transaction; installing this schema does not migrate user data.
CREATE TABLE canvas_model_projects (
project text NOT NULL,
workspace_id uuid NOT NULL,
root_id uuid NOT NULL,
document_format_version integer NOT NULL DEFAULT 1 CHECK(document_format_version=1),
migration_id uuid NOT NULL,
source_versions jsonb NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY(project,workspace_id,root_id),
FOREIGN KEY(project,workspace_id,root_id) REFERENCES materials ON DELETE CASCADE
);
CREATE TABLE canvas_model_canvases (
project text NOT NULL,
workspace_id uuid NOT NULL,
root_id uuid NOT NULL,
canvas_id uuid NOT NULL,
parent_canvas_id uuid,
entry_element_id text,
version bigint NOT NULL CHECK(version>0),
archived boolean NOT NULL DEFAULT false,
PRIMARY KEY(project,workspace_id,canvas_id),
UNIQUE(project,canvas_id),
UNIQUE(project,workspace_id,root_id,canvas_id),
CHECK((parent_canvas_id IS NULL)=(entry_element_id IS NULL)),
CHECK(parent_canvas_id IS NOT NULL OR canvas_id=root_id),
FOREIGN KEY(project,workspace_id,root_id) REFERENCES canvas_model_projects ON DELETE CASCADE,
FOREIGN KEY(project,workspace_id,canvas_id) REFERENCES materials ON DELETE CASCADE,
FOREIGN KEY(project,workspace_id,root_id,parent_canvas_id) REFERENCES canvas_model_canvases(project,workspace_id,root_id,canvas_id) DEFERRABLE INITIALLY DEFERRED
);
CREATE UNIQUE INDEX canvas_model_active_entry ON canvas_model_canvases(project,workspace_id,entry_element_id) WHERE NOT archived;
CREATE TABLE canvas_model_elements (
project text NOT NULL,
workspace_id uuid NOT NULL,
root_id uuid NOT NULL,
element_id text NOT NULL CHECK(element_id ~ '^[A-Za-z0-9_-]{1,128}$'),
owner_canvas_id uuid NOT NULL,
incarnation bigint NOT NULL CHECK(incarnation>0 AND incarnation<=9007199254740991),
deleted_incarnation bigint NOT NULL DEFAULT 0 CHECK(deleted_incarnation>=0 AND deleted_incarnation<=incarnation),
record jsonb NOT NULL,
deleted boolean NOT NULL DEFAULT false,
PRIMARY KEY(project,element_id),
UNIQUE(project,workspace_id,element_id),
CHECK(jsonb_typeof(record)='object' AND record->>'id'=element_id AND record->>'ownerCanvasId'=owner_canvas_id::text AND (record->>'incarnation')::bigint=incarnation),
CHECK(NOT deleted OR deleted_incarnation=incarnation),
FOREIGN KEY(project,workspace_id,root_id) REFERENCES canvas_model_projects ON DELETE CASCADE,
FOREIGN KEY(project,workspace_id,root_id,owner_canvas_id) REFERENCES canvas_model_canvases(project,workspace_id,root_id,canvas_id) DEFERRABLE INITIALLY DEFERRED
);
CREATE INDEX canvas_model_owner ON canvas_model_elements(project,workspace_id,root_id,owner_canvas_id) WHERE NOT deleted;
ALTER TABLE canvas_model_canvases ADD CONSTRAINT canvas_model_entry_fk FOREIGN KEY(project,workspace_id,entry_element_id) REFERENCES canvas_model_elements(project,workspace_id,element_id) DEFERRABLE INITIALLY DEFERRED;
CREATE TABLE canvas_model_identity_map (
project text NOT NULL,
workspace_id uuid NOT NULL,
root_id uuid NOT NULL,
source_canvas_id uuid NOT NULL,
source_structure text NOT NULL CHECK(source_structure IN('node','connection')),
source_element_id text NOT NULL,
element_id text NOT NULL,
PRIMARY KEY(project,workspace_id,root_id,source_canvas_id,source_structure,source_element_id),
UNIQUE(project,element_id),
FOREIGN KEY(project,workspace_id,root_id) REFERENCES canvas_model_projects ON DELETE CASCADE,
FOREIGN KEY(project,workspace_id,element_id) REFERENCES canvas_model_elements(project,workspace_id,element_id) DEFERRABLE INITIALLY DEFERRED
);
-- Logical operations cannot acquire a second successful execution under a new request key.
CREATE TABLE canvas_model_requests (
project text NOT NULL,
workspace_id uuid NOT NULL,
account_id uuid NOT NULL,
request_id uuid NOT NULL,
root_id uuid NOT NULL,
action text NOT NULL,
protocol_version integer NOT NULL,
payload_hash text NOT NULL,
response_status integer NOT NULL,
response_body jsonb NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY(project,workspace_id,account_id,request_id),
FOREIGN KEY(project,workspace_id,root_id) REFERENCES materials ON DELETE RESTRICT,
FOREIGN KEY(project,account_id) REFERENCES project_accounts
);
CREATE TABLE canvas_model_operations (
project text NOT NULL,
workspace_id uuid NOT NULL,
operation_id uuid NOT NULL,
request_id uuid NOT NULL,
account_id uuid NOT NULL,
PRIMARY KEY(project,workspace_id,operation_id),
FOREIGN KEY(project,workspace_id,account_id,request_id) REFERENCES canvas_model_requests DEFERRABLE INITIALLY DEFERRED,
FOREIGN KEY(project,account_id) REFERENCES project_accounts
);
CREATE FUNCTION foundry_guard_canonical_canvas() RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
IF (NEW.canvas_document IS DISTINCT FROM OLD.canvas_document OR NEW.canvas_version IS DISTINCT FROM OLD.canvas_version)
AND EXISTS(SELECT 1 FROM canvas_model_canvases c WHERE c.project=NEW.project AND c.workspace_id=NEW.workspace_id AND c.canvas_id=NEW.id)
AND current_setting('foundry.canonical_writer',true) IS DISTINCT FROM 'on' THEN
RAISE EXCEPTION 'canonical_write_required' USING ERRCODE='55000';
END IF;
RETURN NEW;
END $$;
CREATE TRIGGER canonical_canvas_write_barrier BEFORE UPDATE OF canvas_document,canvas_version ON materials FOR EACH ROW EXECUTE FUNCTION foundry_guard_canonical_canvas();
COMMIT;