Files
0pt/sql/022_canonical_facts.sql
T
admin c46ebbf810
CI (SIAX Cloud) / sonar (push) Failing after 13s
Security Scan (SIAX Cloud) / security (push) Successful in 15s
CI (SIAX Cloud) / security (push) Successful in 20s
Release chain (G17, ADR-W16-003) / Build, push, G17 (SBOM/scan/attest/verify/evidence) (push) Successful in 1m54s
Deploy to Coolify / deploy (push) Successful in 2m0s
CI (SIAX Cloud) / integration (push) Successful in 9m49s
CI (SIAX Cloud) / quality (push) Successful in 10m4s
CI (SIAX Cloud) / sonar (pull_request) Skipped
masterplan-lock / check (pull_request) Successful in 7s
masterplan-lock / check (push) Successful in 7s
CI (SIAX Cloud) / security (pull_request) Successful in 20s
Security Scan (SIAX Cloud) / security (pull_request) Successful in 44s
CI (SIAX Cloud) / integration (pull_request) Successful in 1m28s
CI (SIAX Cloud) / quality (pull_request) Successful in 10m3s
feat(0pt): canonical-fact machine route + renumber migration to 022 (#484)
2026-11-05 19:16:13 +00:00

100 lines
5.0 KiB
SQL

-- 020_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;
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;
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;
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));