masterplan-lock / check (push) Successful in 5s
CI (SIAX Cloud) / sonar (pull_request) Skipped
masterplan-lock / check (pull_request) Successful in 4s
CI (SIAX Cloud) / Component conformance (blocking) (pull_request) Successful in 15s
Security Scan (SIAX Cloud) / security (pull_request) Successful in 21s
CI (SIAX Cloud) / security (pull_request) Successful in 48s
CI (SIAX Cloud) / quality (pull_request) Successful in 1m47s
CI (SIAX Cloud) / integration (pull_request) Successful in 10m39s
CREATE POLICY saknade skydd mot omkörning, så en retry efter partiell körning skulle fastna på 'policy already exists'. Tabellerna använder redan CREATE TABLE/INDEX IF NOT EXISTS; policynivån görs nu likvärdig. Rubriken rättad från 020 till 022. Verifierat på scratch-databas: två körningar i rad OK, RLS ENABLE+FORCE, 3 policies, 11 index, cross-tenant INSERT nekad och tenant B ser 0 rader.
103 lines
5.2 KiB
SQL
103 lines
5.2 KiB
SQL
-- 022_canonical_facts.sql — 0pt canonical structured observations (X-800 / Scrape).
|
|
--
|
|
-- 0pt owns normalized OBSERVED facts (what a source said, when, from which
|
|
-- artifact). L0RE owns epistemic propositions/assertions and projections; it
|
|
-- must consume these rows through this API/contract, never by writing here.
|
|
--
|
|
-- Conflict preservation is structural: the unique key includes source_ref and
|
|
-- observed_at, so two sources disagreeing about the same predicate are two
|
|
-- rows, never an overwrite. `status` records observed/supported/disputed/
|
|
-- retracted; reconciliation is a separate, explicit decision.
|
|
--
|
|
-- Tenant isolation: RLS is FORCEd and keyed on a transaction-local GUC that
|
|
-- only trusted server code sets (same pattern as 019_document_credit_rls.sql).
|
|
|
|
CREATE TABLE IF NOT EXISTS opt_canonical_entities (
|
|
entity_id UUID PRIMARY KEY,
|
|
tenant_id TEXT NOT NULL,
|
|
entity_type TEXT NOT NULL, -- company|person|web_page|product|...
|
|
identity_namespace TEXT NOT NULL, -- url|orgnr|lei|gtin|...
|
|
identity_value TEXT NOT NULL,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
UNIQUE (tenant_id, entity_type, identity_namespace, identity_value)
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_canonical_entities_tenant_type
|
|
ON opt_canonical_entities (tenant_id, entity_type);
|
|
|
|
CREATE TABLE IF NOT EXISTS opt_canonical_facts (
|
|
fact_id UUID PRIMARY KEY,
|
|
tenant_id TEXT NOT NULL,
|
|
entity_id UUID NOT NULL REFERENCES opt_canonical_entities (entity_id),
|
|
predicate TEXT NOT NULL, -- e.g. web.title, product.price
|
|
value_json JSONB NOT NULL,
|
|
value_type TEXT NOT NULL, -- string|number|boolean|date|json
|
|
value_unit TEXT,
|
|
source_ref TEXT NOT NULL,
|
|
source_record_id TEXT,
|
|
st0re_artifact_id TEXT,
|
|
aud0_evidence_ref TEXT,
|
|
run_ref TEXT,
|
|
extractor_version TEXT NOT NULL,
|
|
observed_at TIMESTAMPTZ NOT NULL,
|
|
valid_from TIMESTAMPTZ,
|
|
valid_to TIMESTAMPTZ,
|
|
confidence NUMERIC CHECK (confidence BETWEEN 0 AND 1),
|
|
quality_status TEXT NOT NULL DEFAULT 'unvalidated', -- passed|warned|failed|unvalidated
|
|
schema_version TEXT NOT NULL DEFAULT '0pt.canonical_fact/v1',
|
|
status TEXT NOT NULL DEFAULT 'observed'
|
|
CHECK (status IN ('observed','supported','disputed','retracted')),
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
UNIQUE (tenant_id, entity_id, predicate, source_ref, observed_at)
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_canonical_facts_entity
|
|
ON opt_canonical_facts (tenant_id, entity_id, predicate);
|
|
CREATE INDEX IF NOT EXISTS idx_canonical_facts_predicate
|
|
ON opt_canonical_facts (tenant_id, predicate, observed_at DESC);
|
|
|
|
CREATE TABLE IF NOT EXISTS opt_canonical_relations (
|
|
relation_id UUID PRIMARY KEY,
|
|
tenant_id TEXT NOT NULL,
|
|
subject_id UUID NOT NULL REFERENCES opt_canonical_entities (entity_id),
|
|
predicate TEXT NOT NULL, -- LINKS_TO|SOLD_BY|USES_SOFTWARE|...
|
|
object_id UUID NOT NULL REFERENCES opt_canonical_entities (entity_id),
|
|
attributes_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
source_ref TEXT NOT NULL,
|
|
st0re_artifact_id TEXT,
|
|
aud0_evidence_ref TEXT,
|
|
run_ref TEXT,
|
|
extractor_version TEXT NOT NULL,
|
|
observed_at TIMESTAMPTZ NOT NULL,
|
|
valid_from TIMESTAMPTZ,
|
|
valid_to TIMESTAMPTZ,
|
|
confidence NUMERIC CHECK (confidence BETWEEN 0 AND 1),
|
|
schema_version TEXT NOT NULL DEFAULT '0pt.canonical_relation/v1',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
UNIQUE (tenant_id, subject_id, predicate, object_id, source_ref, observed_at)
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_canonical_relations_subject
|
|
ON opt_canonical_relations (tenant_id, subject_id, predicate);
|
|
CREATE INDEX IF NOT EXISTS idx_canonical_relations_object
|
|
ON opt_canonical_relations (tenant_id, object_id, predicate);
|
|
|
|
ALTER TABLE opt_canonical_entities ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE opt_canonical_entities FORCE ROW LEVEL SECURITY;
|
|
DROP POLICY IF EXISTS opt_canonical_entities_tenant ON opt_canonical_entities;
|
|
CREATE POLICY opt_canonical_entities_tenant ON opt_canonical_entities
|
|
USING (tenant_id = current_setting('app.opt_canonical_tenant_id', true))
|
|
WITH CHECK (tenant_id = current_setting('app.opt_canonical_tenant_id', true));
|
|
|
|
ALTER TABLE opt_canonical_facts ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE opt_canonical_facts FORCE ROW LEVEL SECURITY;
|
|
DROP POLICY IF EXISTS opt_canonical_facts_tenant ON opt_canonical_facts;
|
|
CREATE POLICY opt_canonical_facts_tenant ON opt_canonical_facts
|
|
USING (tenant_id = current_setting('app.opt_canonical_tenant_id', true))
|
|
WITH CHECK (tenant_id = current_setting('app.opt_canonical_tenant_id', true));
|
|
|
|
ALTER TABLE opt_canonical_relations ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE opt_canonical_relations FORCE ROW LEVEL SECURITY;
|
|
DROP POLICY IF EXISTS opt_canonical_relations_tenant ON opt_canonical_relations;
|
|
CREATE POLICY opt_canonical_relations_tenant ON opt_canonical_relations
|
|
USING (tenant_id = current_setting('app.opt_canonical_tenant_id', true))
|
|
WITH CHECK (tenant_id = current_setting('app.opt_canonical_tenant_id', true));
|