diff --git a/sql/009_analytics_execution_metrics_governance.sql b/sql/009_analytics_execution_metrics_governance.sql
new file mode 100644
index 0000000..6b78200
--- /dev/null
+++ b/sql/009_analytics_execution_metrics_governance.sql
@@ -0,0 +1,346 @@
+-- 0pt analytics: execution metadata, metric governance, semantic registry,
+-- quarantine (BI-P018, BI-P021, BI-P025, BI-P028, BI-P030, BI-P031).
+--
+-- Extends sql/008. Every statement is ADDITIVE and idempotent: ALTER TABLE ...
+-- ADD COLUMN IF NOT EXISTS, CREATE TABLE IF NOT EXISTS, CREATE OR REPLACE
+-- FUNCTION, DROP TRIGGER IF EXISTS before CREATE TRIGGER. It can be replayed
+-- onto an already-migrated database, which is what the restore drill needs.
+--
+-- Ordering: requires sql/008 (it alters two of its tables).
+--
+-- WHY COLUMNS RATHER THAN NEW TABLES for the metric fields: BI-P028 names
+-- fifteen fields and BI-P002 already persists nine of them. A second metric
+-- table would be exactly the failure BI-P028's DoD is about ("no production
+-- metric exists only inside a dashboard tool" generalises to "one metric, one
+-- home"), so the six missing fields are added where the metric already lives.
+
+-- ═══════════════════════════════════════════════════════════════════════════
+-- BI-P018 — QUERY EXECUTION MODEL
+-- ═══════════════════════════════════════════════════════════════════════════
+-- The fields BI-P018 lists that sql/008's QueryExecution did not carry.
+--
+-- `compiled_query_hash` and NOT the statement: BI-P018 says "without
+-- unnecessarily storing sensitive raw SQL". There is deliberately no
+-- `query_text` column, and the CHECK below makes adding one by accident
+-- visible — a hash is 64 hex characters and nothing else.
+ALTER TABLE opt_analytics_query_execution
+ ADD COLUMN IF NOT EXISTS principal_id TEXT,
+ ADD COLUMN IF NOT EXISTS workload TEXT,
+ ADD COLUMN IF NOT EXISTS compiled_query_hash TEXT,
+ ADD COLUMN IF NOT EXISTS datasets TEXT[] NOT NULL DEFAULT '{}',
+ ADD COLUMN IF NOT EXISTS metrics TEXT[] NOT NULL DEFAULT '{}',
+ ADD COLUMN IF NOT EXISTS cache_status TEXT NOT NULL DEFAULT 'miss',
+ ADD COLUMN IF NOT EXISTS cost_units NUMERIC(20, 6);
+
+-- NOTE ON THE GUARD SHAPE. `pg_constraint.conname` is NOT unique across
+-- schemas: a bare `WHERE conname = '...'` matches a same-named constraint in
+-- ANY schema and silently skips the ADD here. Found by running this file into
+-- a second schema on a database that already had it in `public` — every CHECK
+-- below was quietly absent, and the tests that should have proven them passed
+-- because the INSERTs they expected to fail succeeded. Every guard therefore
+-- scopes to the table via `conrelid = '
'::regclass`, which resolves
+-- through search_path.
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM pg_constraint
+ WHERE conname = 'opt_analytics_query_execution_hash_shape'
+ AND conrelid = 'opt_analytics_query_execution'::regclass
+ ) THEN
+ ALTER TABLE opt_analytics_query_execution
+ ADD CONSTRAINT opt_analytics_query_execution_hash_shape
+ CHECK (compiled_query_hash IS NULL OR compiled_query_hash ~ '^[0-9a-f]{64}$');
+ END IF;
+ IF NOT EXISTS (
+ SELECT 1 FROM pg_constraint
+ WHERE conname = 'opt_analytics_query_execution_cache_status'
+ AND conrelid = 'opt_analytics_query_execution'::regclass
+ ) THEN
+ ALTER TABLE opt_analytics_query_execution
+ ADD CONSTRAINT opt_analytics_query_execution_cache_status
+ CHECK (cache_status IN ('hit','miss','bypass','stale_refused','not_cacheable'));
+ END IF;
+ IF NOT EXISTS (
+ SELECT 1 FROM pg_constraint
+ WHERE conname = 'opt_analytics_query_execution_workload'
+ AND conrelid = 'opt_analytics_query_execution'::regclass
+ ) THEN
+ ALTER TABLE opt_analytics_query_execution
+ ADD CONSTRAINT opt_analytics_query_execution_workload
+ CHECK (workload IS NULL OR workload IN
+ ('dashboard','report','api','analyst_sql','ai_analyst','scheduled','internal'));
+ END IF;
+END $$;
+
+-- Cost reporting is per tenant per window; index for it rather than making
+-- every billing query a sequential scan of every tenant's executions.
+CREATE INDEX IF NOT EXISTS opt_analytics_query_execution_cost_idx
+ ON opt_analytics_query_execution (tenant_id, submitted_at DESC)
+ WHERE cost_units IS NOT NULL;
+
+-- ═══════════════════════════════════════════════════════════════════════════
+-- BI-P017 — ANALYST SQL STATEMENT HISTORY
+-- ═══════════════════════════════════════════════════════════════════════════
+-- The ONE place raw SQL is retained, and it is a separate table with a
+-- separate retention posture precisely so that decision is visible.
+--
+-- `statement_normalized` (literals masked) is NOT NULL: it is always present
+-- and is the searchable, non-PII form. `statement_raw` is nullable, so a
+-- deployment that chooses not to retain it simply leaves it NULL rather than
+-- losing the history entirely.
+CREATE TABLE IF NOT EXISTS opt_analytics_sql_statement (
+ statement_id TEXT PRIMARY KEY,
+ tenant_id TEXT NOT NULL,
+ project_id TEXT,
+ principal_id TEXT NOT NULL,
+ statement_normalized TEXT NOT NULL,
+ statement_raw TEXT,
+ statement_hash TEXT NOT NULL CHECK (statement_hash ~ '^[0-9a-f]{64}$'),
+ referenced_objects TEXT[] NOT NULL DEFAULT '{}',
+ outcome TEXT NOT NULL CHECK (outcome IN ('admitted','refused','succeeded','failed','cancelled')),
+ error_code TEXT,
+ refusal_reason TEXT,
+ submitted_at TIMESTAMPTZ NOT NULL DEFAULT now(),
+ finished_at TIMESTAMPTZ,
+ duration_ms INTEGER,
+ rows_returned BIGINT,
+ rows_read BIGINT,
+ bytes_read BIGINT,
+ correlation_id TEXT,
+ -- A refusal must say why. An unreviewable refusal log is not an audit trail,
+ -- and an analyst probing the boundary is exactly what an operator needs to see.
+ CHECK (outcome <> 'refused' OR (refusal_reason IS NOT NULL AND length(btrim(refusal_reason)) > 0))
+);
+CREATE INDEX IF NOT EXISTS opt_analytics_sql_statement_tenant_idx
+ ON opt_analytics_sql_statement (tenant_id, submitted_at DESC);
+CREATE INDEX IF NOT EXISTS opt_analytics_sql_statement_refused_idx
+ ON opt_analytics_sql_statement (tenant_id, submitted_at DESC)
+ WHERE outcome = 'refused';
+
+-- ═══════════════════════════════════════════════════════════════════════════
+-- BI-P028 / BI-P031 — METRIC DOMAIN MODEL AND GOVERNANCE
+-- ═══════════════════════════════════════════════════════════════════════════
+ALTER TABLE opt_analytics_metric_definition
+ ADD COLUMN IF NOT EXISTS metric_type TEXT NOT NULL DEFAULT 'simple',
+ ADD COLUMN IF NOT EXISTS time_dimension TEXT,
+ ADD COLUMN IF NOT EXISTS unit TEXT,
+ ADD COLUMN IF NOT EXISTS currency TEXT,
+ ADD COLUMN IF NOT EXISTS display_format TEXT NOT NULL DEFAULT 'decimal',
+ ADD COLUMN IF NOT EXISTS status TEXT NOT NULL DEFAULT 'DRAFT',
+ ADD COLUMN IF NOT EXISTS filters_json TEXT NOT NULL DEFAULT '[]',
+ ADD COLUMN IF NOT EXISTS certification TEXT NOT NULL DEFAULT 'experimental',
+ ADD COLUMN IF NOT EXISTS business_definition TEXT,
+ ADD COLUMN IF NOT EXISTS technical_definition TEXT,
+ ADD COLUMN IF NOT EXISTS meaning_hash TEXT,
+ ADD COLUMN IF NOT EXISTS certified_at TIMESTAMPTZ,
+ ADD COLUMN IF NOT EXISTS certified_by TEXT;
+
+DO $$
+BEGIN
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'opt_analytics_metric_status'
+ AND conrelid = 'opt_analytics_metric_definition'::regclass) THEN
+ ALTER TABLE opt_analytics_metric_definition ADD CONSTRAINT opt_analytics_metric_status
+ CHECK (status IN ('DRAFT','REVIEW','ACTIVE','DEPRECATED','RETIRED'));
+ END IF;
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'opt_analytics_metric_type'
+ AND conrelid = 'opt_analytics_metric_definition'::regclass) THEN
+ ALTER TABLE opt_analytics_metric_definition ADD CONSTRAINT opt_analytics_metric_type
+ CHECK (metric_type IN ('simple','ratio','derived','cumulative','conversion'));
+ END IF;
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'opt_analytics_metric_format'
+ AND conrelid = 'opt_analytics_metric_definition'::regclass) THEN
+ ALTER TABLE opt_analytics_metric_definition ADD CONSTRAINT opt_analytics_metric_format
+ CHECK (display_format IN ('integer','decimal','percent','currency','duration_ms','bytes'));
+ END IF;
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'opt_analytics_metric_certification'
+ AND conrelid = 'opt_analytics_metric_definition'::regclass) THEN
+ ALTER TABLE opt_analytics_metric_definition ADD CONSTRAINT opt_analytics_metric_certification
+ CHECK (certification IN ('experimental','certified','deprecated'));
+ END IF;
+ -- A currency-formatted metric with no currency is how SEK gets rendered as
+ -- USD on a dashboard nobody double-checks.
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'opt_analytics_metric_currency_pairing'
+ AND conrelid = 'opt_analytics_metric_definition'::regclass) THEN
+ ALTER TABLE opt_analytics_metric_definition ADD CONSTRAINT opt_analytics_metric_currency_pairing
+ CHECK (
+ (display_format = 'currency' AND currency IS NOT NULL AND currency ~ '^[A-Z]{3}$')
+ OR (display_format <> 'currency' AND currency IS NULL)
+ );
+ END IF;
+ -- Certification is an ACT: it has an actor and a time, or it has not happened.
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'opt_analytics_metric_certification_act'
+ AND conrelid = 'opt_analytics_metric_definition'::regclass) THEN
+ ALTER TABLE opt_analytics_metric_definition ADD CONSTRAINT opt_analytics_metric_certification_act
+ CHECK (
+ certification <> 'certified'
+ OR (certified_by IS NOT NULL AND certified_at IS NOT NULL
+ AND business_definition IS NOT NULL AND technical_definition IS NOT NULL)
+ );
+ END IF;
+END $$;
+
+-- ── BI-P031's DoD, in the DATABASE ────────────────────────────────────────
+-- "Certified metric cannot be modified in-place; new version required."
+--
+-- The TypeScript check (assertMetricEditAllowed) is layer 1. This trigger is
+-- layer 2, and it is the one that holds when something writes to the table
+-- without going through the application — a migration script, a psql session,
+-- an admin tool. A compiler opinion does not survive `psql`; a trigger does.
+--
+-- The rule is on the MEANING HASH, so a change this trigger has never heard of
+-- still counts as a change: new fields default to "changes the meaning" rather
+-- than to "allowed", which is the fail-closed direction.
+CREATE OR REPLACE FUNCTION opt_analytics_metric_certified_immutable() RETURNS trigger AS $$
+BEGIN
+ IF OLD.certification = 'certified'
+ AND NEW.current_version = OLD.current_version
+ AND NEW.meaning_hash IS DISTINCT FROM OLD.meaning_hash THEN
+ RAISE EXCEPTION
+ 'BI-P031: metric % is CERTIFIED; its definition cannot be modified in place - mint version %',
+ OLD.name, OLD.current_version + 1
+ USING ERRCODE = 'restrict_violation';
+ END IF;
+ RETURN NEW;
+END;
+$$ LANGUAGE plpgsql;
+
+DROP TRIGGER IF EXISTS opt_analytics_metric_certified_guard ON opt_analytics_metric_definition;
+CREATE TRIGGER opt_analytics_metric_certified_guard
+ BEFORE UPDATE ON opt_analytics_metric_definition
+ FOR EACH ROW EXECUTE FUNCTION opt_analytics_metric_certified_immutable();
+
+-- ═══════════════════════════════════════════════════════════════════════════
+-- BI-P030 — SEMANTIC MODEL REGISTRY
+-- ═══════════════════════════════════════════════════════════════════════════
+-- Persisted SEPARATELY from any implementation engine, which is the whole
+-- point of the prompt. There is no column here for a provider manifest, and
+-- `assertOnlySemanticModelFields` refuses one in the application; the schema
+-- refuses it by simply not having anywhere to put it.
+--
+-- sql/008's opt_analytics_semantic_model holds the current model; this table
+-- holds the VERSION HISTORY BI-P030 enumerates, and its rows are immutable for
+-- the same reason a MetricVersion's are: a dashboard pinned to version 7 must
+-- mean the same thing tomorrow.
+CREATE TABLE IF NOT EXISTS opt_analytics_semantic_model_version (
+ semantic_model_version_id TEXT PRIMARY KEY,
+ model_id TEXT NOT NULL,
+ tenant_id TEXT NOT NULL,
+ project_id TEXT NOT NULL,
+ version INTEGER NOT NULL CHECK (version >= 1),
+ datasets TEXT[] NOT NULL DEFAULT '{}',
+ relationships_json TEXT NOT NULL DEFAULT '[]',
+ metrics_json TEXT NOT NULL DEFAULT '[]',
+ dimensions_json TEXT NOT NULL DEFAULT '[]',
+ valid_from TIMESTAMPTZ NOT NULL DEFAULT now(),
+ status TEXT NOT NULL CHECK (status IN ('draft','active','superseded','retired')),
+ hash TEXT NOT NULL CHECK (hash ~ '^[0-9a-f]{64}$'),
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
+ created_by TEXT,
+ UNIQUE (tenant_id, model_id, version)
+);
+CREATE INDEX IF NOT EXISTS opt_analytics_semantic_model_version_tenant_idx
+ ON opt_analytics_semantic_model_version (tenant_id, project_id, model_id, version DESC);
+
+-- At most ONE active version per model per tenant. A partial unique index
+-- rather than application discipline: two active versions is a silent
+-- correctness failure in which half the dashboards read a different model.
+CREATE UNIQUE INDEX IF NOT EXISTS opt_analytics_semantic_model_one_active
+ ON opt_analytics_semantic_model_version (tenant_id, model_id)
+ WHERE status = 'active';
+
+-- A published version's CONTENT is immutable; only its status and valid_from
+-- may move (draft -> active -> superseded -> retired).
+CREATE OR REPLACE FUNCTION opt_analytics_semantic_version_content_immutable() RETURNS trigger AS $$
+BEGIN
+ IF NEW.hash IS DISTINCT FROM OLD.hash
+ OR NEW.metrics_json IS DISTINCT FROM OLD.metrics_json
+ OR NEW.dimensions_json IS DISTINCT FROM OLD.dimensions_json
+ OR NEW.relationships_json IS DISTINCT FROM OLD.relationships_json
+ OR NEW.datasets IS DISTINCT FROM OLD.datasets
+ OR NEW.version IS DISTINCT FROM OLD.version THEN
+ RAISE EXCEPTION
+ 'BI-P030: semantic model % v% is immutable; publish a new version instead',
+ OLD.model_id, OLD.version
+ USING ERRCODE = 'restrict_violation';
+ END IF;
+ RETURN NEW;
+END;
+$$ LANGUAGE plpgsql;
+
+DROP TRIGGER IF EXISTS opt_analytics_semantic_version_guard ON opt_analytics_semantic_model_version;
+CREATE TRIGGER opt_analytics_semantic_version_guard
+ BEFORE UPDATE ON opt_analytics_semantic_model_version
+ FOR EACH ROW EXECUTE FUNCTION opt_analytics_semantic_version_content_immutable();
+
+-- ═══════════════════════════════════════════════════════════════════════════
+-- BI-P025 / BI-P026 — QUARANTINE AND FRESHNESS
+-- ═══════════════════════════════════════════════════════════════════════════
+-- The quarantine record BI-P025's DoD acts on. The READ-side gate lives in the
+-- application (`assertProjectionReadable`); this is its durable state, so a
+-- process restart does not release a quarantine.
+CREATE TABLE IF NOT EXISTS opt_analytics_projection_quarantine (
+ tenant_id TEXT NOT NULL,
+ dataset_id TEXT NOT NULL REFERENCES opt_analytics_dataset (dataset_id),
+ quarantined BOOLEAN NOT NULL,
+ caused_by TEXT[] NOT NULL DEFAULT '{}',
+ reason TEXT,
+ since TIMESTAMPTZ,
+ released_at TIMESTAMPTZ,
+ released_by TEXT,
+ PRIMARY KEY (tenant_id, dataset_id),
+ -- Release is an ACT with an operator behind it, never an automatic
+ -- consequence of a later passing run: a flapping rule would otherwise
+ -- quarantine and un-quarantine a dataset between two reads of one dashboard.
+ CHECK (quarantined OR released_at IS NULL OR (released_by IS NOT NULL AND reason IS NOT NULL))
+);
+
+-- BI-P026 freshness state, per dataset. `UNKNOWN` is the DEFAULT, deliberately:
+-- a dataset nobody has computed freshness for must not read as FRESH.
+CREATE TABLE IF NOT EXISTS opt_analytics_dataset_freshness (
+ tenant_id TEXT NOT NULL,
+ dataset_id TEXT NOT NULL REFERENCES opt_analytics_dataset (dataset_id),
+ state TEXT NOT NULL DEFAULT 'UNKNOWN'
+ CHECK (state IN ('FRESH','DELAYED','STALE','FAILED','UNKNOWN')),
+ age_ms BIGINT,
+ last_successful_load_at TIMESTAMPTZ,
+ last_run_at TIMESTAMPTZ,
+ last_run_status TEXT CHECK (last_run_status IN ('succeeded','failed','running','never_run')),
+ expected_interval_ms BIGINT NOT NULL,
+ disclosure TEXT NOT NULL,
+ computed_at TIMESTAMPTZ NOT NULL DEFAULT now(),
+ PRIMARY KEY (tenant_id, dataset_id),
+ -- Every state must carry a disclosure a UI can render verbatim. A state with
+ -- no words attached is a state a dashboard will render as a silent green dot.
+ CHECK (length(btrim(disclosure)) > 0)
+);
+
+-- ═══════════════════════════════════════════════════════════════════════════
+-- BI-P021 — USAGE LEDGER
+-- ═══════════════════════════════════════════════════════════════════════════
+-- What CL0UD quotas, AUD0 usage and B00K accounting read. Append-only.
+--
+-- `terminated_by_budget` is recorded because a query the budget killed still
+-- COST what it burned before it died; not charging for it is how a tenant
+-- learns that the cheapest query is one that gets cancelled.
+CREATE TABLE IF NOT EXISTS opt_analytics_usage_ledger (
+ usage_id TEXT PRIMARY KEY,
+ tenant_id TEXT NOT NULL,
+ project_id TEXT,
+ principal_id TEXT NOT NULL,
+ query_id TEXT NOT NULL,
+ engine TEXT NOT NULL,
+ rows_scanned BIGINT NOT NULL DEFAULT 0,
+ bytes_scanned BIGINT NOT NULL DEFAULT 0,
+ storage_read_bytes BIGINT NOT NULL DEFAULT 0,
+ execution_ms BIGINT NOT NULL DEFAULT 0,
+ cost_units NUMERIC(20, 6) NOT NULL,
+ terminated_by_budget BOOLEAN NOT NULL DEFAULT false,
+ recorded_at TIMESTAMPTZ NOT NULL DEFAULT now()
+);
+CREATE INDEX IF NOT EXISTS opt_analytics_usage_ledger_window_idx
+ ON opt_analytics_usage_ledger (tenant_id, recorded_at DESC);
+
+DROP TRIGGER IF EXISTS opt_analytics_usage_ledger_immutable ON opt_analytics_usage_ledger;
+CREATE TRIGGER opt_analytics_usage_ledger_immutable
+ BEFORE UPDATE OR DELETE ON opt_analytics_usage_ledger
+ FOR EACH ROW EXECUTE FUNCTION opt_analytics_forbid_mutation();
diff --git a/tests/analytics/analytics-governance-postgres.realinfra.test.ts b/tests/analytics/analytics-governance-postgres.realinfra.test.ts
new file mode 100644
index 0000000..d92ce10
--- /dev/null
+++ b/tests/analytics/analytics-governance-postgres.realinfra.test.ts
@@ -0,0 +1,349 @@
+/**
+ * tests/analytics/analytics-governance-postgres.realinfra.test.ts
+ * ================================================================
+ * sql/009 against a REAL Postgres.
+ *
+ * A migration nobody ran is a migration that does not work, and a trigger
+ * nobody fired is a comment. So this applies sql/008 and sql/009 into a
+ * throwaway schema and then attempts, against the database, every write the
+ * constraints exist to refuse:
+ *
+ * BI-P031 UPDATE a CERTIFIED metric's meaning in place -> must RAISE.
+ * This is the layer that holds when something writes without going
+ * through the application: a migration script, a psql session, an
+ * admin tool. A TypeScript guard does not survive `psql`.
+ * BI-P030 UPDATE a published semantic model version's content -> must RAISE.
+ * Two ACTIVE versions of one model -> must violate a unique index.
+ * BI-P028 A currency-formatted metric with no currency -> must be refused.
+ * Certification without an actor or the two definitions -> refused.
+ * BI-P018 A `compiled_query_hash` that is not a 64-hex digest -> refused.
+ * BI-P021 UPDATE or DELETE a usage-ledger row -> must RAISE (append-only).
+ *
+ * Gated on ANALYTICS_TEST_DATABASE_URL, matching the convention in
+ * analytics-domain-postgres.realinfra.test.ts. With it set and Postgres down,
+ * this FAILS rather than skipping.
+ *
+ * docker run -d --name pg -p 25433:5432 -e POSTGRES_PASSWORD=test \
+ * -e POSTGRES_DB=opt postgres:16-alpine
+ * ANALYTICS_TEST_DATABASE_URL=postgres://postgres:test@127.0.0.1:25433/opt \
+ * pnpm vitest run tests/analytics/analytics-governance-postgres.realinfra.test.ts
+ */
+import fs from 'node:fs';
+import path from 'node:path';
+import { afterAll, beforeAll, describe, expect, it } from 'vitest';
+import postgres from 'postgres';
+
+const TEST_DATABASE_URL = process.env.ANALYTICS_TEST_DATABASE_URL;
+const run = TEST_DATABASE_URL ? describe : describe.skip;
+const SCHEMA = `bi_p009_${Date.now().toString(36)}`;
+
+let sql: ReturnType;
+
+/** Did the statement RAISE? Returns the message, or undefined if it succeeded. */
+async function refusalOf(fn: () => Promise): Promise {
+ try {
+ await fn();
+ return undefined;
+ } catch (e) {
+ return String((e as Error).message);
+ }
+}
+
+beforeAll(async () => {
+ if (!TEST_DATABASE_URL) return;
+ // The search_path is pinned on the CONNECTION rather than prefixed to every
+ // statement. Prefixing makes each call a multi-statement simple query, and
+ // postgres.js then returns the LAST statement's result — which silently
+ // turned `SELECT state FROM ...` into the connection object in the first
+ // version of this file, and made two assertions compare against nonsense.
+ const admin = postgres(TEST_DATABASE_URL, { max: 1, onnotice: () => undefined });
+ await admin.unsafe(`CREATE SCHEMA IF NOT EXISTS ${SCHEMA}`);
+ await admin.end({ timeout: 5 });
+
+ sql = postgres(TEST_DATABASE_URL, {
+ max: 2,
+ onnotice: () => undefined,
+ connection: { search_path: SCHEMA },
+ });
+ for (const file of [
+ 'sql/008_analytics_domain_model.sql',
+ 'sql/009_analytics_execution_metrics_governance.sql',
+ ]) {
+ await sql.unsafe(fs.readFileSync(path.join(process.cwd(), file), 'utf8'));
+ }
+
+ // A dataset and a metric to govern.
+ await sql.unsafe(`
+ INSERT INTO opt_analytics_dataset
+ (dataset_id, tenant_id, project_id, name, tier, schema_contract_id, schema_contract_version,
+ owner_id, source_ref, freshness_slo_seconds, retention_days, classification, status)
+ VALUES ('ds_sales','t_alpha','p_main','sales','SERVING','c_sales','1.0.0',
+ 'u_1','0pt://sourcing/sales', 3600, 90, 'none', 'active')
+ ON CONFLICT DO NOTHING;`);
+}, 120_000);
+
+afterAll(async () => {
+ if (!TEST_DATABASE_URL || !sql) return;
+ await sql.unsafe(`DROP SCHEMA IF EXISTS ${SCHEMA} CASCADE`);
+ await sql.end({ timeout: 5 });
+});
+
+run('sql/009 applies, and replays', () => {
+ it('is idempotent — a replay onto an already-migrated schema succeeds', async () => {
+ const text = fs.readFileSync(
+ path.join(process.cwd(), 'sql/009_analytics_execution_metrics_governance.sql'),
+ 'utf8',
+ );
+ // The restore drill replays every file in order onto a live database.
+ await expect(sql.unsafe(text)).resolves.toBeDefined();
+ });
+
+ it('added every BI-P018 column the execution record needs', async () => {
+ const rows = await sql.unsafe(
+ `SELECT column_name FROM information_schema.columns
+ WHERE table_schema = '${SCHEMA}' AND table_name = 'opt_analytics_query_execution'`,
+ );
+ const names = new Set((rows as unknown as Array<{ column_name: string }>).map((r) => r.column_name));
+ for (const col of [
+ 'principal_id',
+ 'workload',
+ 'compiled_query_hash',
+ 'datasets',
+ 'metrics',
+ 'cache_status',
+ 'cost_units',
+ ]) {
+ expect(names.has(col), `missing ${col}`).toBe(true);
+ }
+ // And the one it must NOT have.
+ expect(names.has('query_text')).toBe(false);
+ expect(names.has('sql')).toBe(false);
+ });
+});
+
+run('BI-P031 — a CERTIFIED metric cannot be modified in place, enforced by the DATABASE', () => {
+ const base = '';
+
+ beforeAll(async () => {
+ await sql.unsafe(`${base}
+ INSERT INTO opt_analytics_metric_definition
+ (metric_id, tenant_id, project_id, name, dataset_id, aggregation, measure_field,
+ allowed_dimension_ids, current_version, owner_id, description,
+ metric_type, time_dimension, unit, currency, display_format, status, filters_json,
+ certification, business_definition, technical_definition, meaning_hash,
+ certified_at, certified_by)
+ VALUES ('m_revenue','t_alpha','p_main','net_revenue','ds_sales','sum','amount_sek',
+ ARRAY['country'], 3, 'finance@siax.io', 'Invoiced revenue net of credits',
+ 'simple','invoiced_at','SEK','SEK','currency','ACTIVE','[]',
+ 'certified','What we invoiced','sum(amount_sek)', repeat('a',64),
+ now(), 'u_1')
+ ON CONFLICT (metric_id) DO NOTHING;`);
+ });
+
+ it('REFUSES an in-place change to the meaning hash', async () => {
+ const message = await refusalOf(() =>
+ sql.unsafe(`${base} UPDATE opt_analytics_metric_definition
+ SET meaning_hash = repeat('b',64) WHERE metric_id = 'm_revenue'`),
+ );
+ expect(message, "a certified metric's meaning was changed in place, in the database").toBeDefined();
+ expect(message).toContain('BI-P031');
+ expect(message).toContain('mint version 4');
+ });
+
+ it('ALLOWS an editorial change that leaves the meaning hash alone', async () => {
+ await expect(
+ sql.unsafe(`${base} UPDATE opt_analytics_metric_definition
+ SET description = 'Clearer wording' WHERE metric_id = 'm_revenue'`),
+ ).resolves.toBeDefined();
+ });
+
+ it('ALLOWS a meaning change that comes with a version bump', async () => {
+ await expect(
+ sql.unsafe(`${base} UPDATE opt_analytics_metric_definition
+ SET meaning_hash = repeat('c',64), current_version = 4
+ WHERE metric_id = 'm_revenue'`),
+ ).resolves.toBeDefined();
+ // Put it back for the remaining assertions.
+ await sql.unsafe(`${base} UPDATE opt_analytics_metric_definition
+ SET current_version = 3 WHERE metric_id = 'm_revenue'`);
+ });
+
+ it('refuses a currency-formatted metric with no currency', async () => {
+ const message = await refusalOf(() =>
+ sql.unsafe(`${base} INSERT INTO opt_analytics_metric_definition
+ (metric_id, tenant_id, project_id, name, dataset_id, aggregation, measure_field,
+ owner_id, display_format, currency, time_dimension)
+ VALUES ('m_bad','t_alpha','p_main','bad_metric','ds_sales','sum','x','u','currency',NULL,'t')`),
+ );
+ expect(message).toContain('currency_pairing');
+ });
+
+ it('refuses a currency on a non-currency format', async () => {
+ const message = await refusalOf(() =>
+ sql.unsafe(`${base} INSERT INTO opt_analytics_metric_definition
+ (metric_id, tenant_id, project_id, name, dataset_id, aggregation, measure_field,
+ owner_id, display_format, currency, time_dimension)
+ VALUES ('m_bad2','t_alpha','p_main','bad_metric2','ds_sales','sum','x','u','integer','SEK','t')`),
+ );
+ expect(message).toContain('currency_pairing');
+ });
+
+ it('refuses certification without an actor, a time, and both definitions', async () => {
+ const message = await refusalOf(() =>
+ sql.unsafe(`${base} INSERT INTO opt_analytics_metric_definition
+ (metric_id, tenant_id, project_id, name, dataset_id, aggregation, measure_field,
+ owner_id, display_format, time_dimension, certification)
+ VALUES ('m_bad3','t_alpha','p_main','bad_metric3','ds_sales','sum','x','u','decimal','t','certified')`),
+ );
+ expect(message).toContain('certification_act');
+ });
+
+ it('refuses an unknown lifecycle status', async () => {
+ const message = await refusalOf(() =>
+ sql.unsafe(`${base} UPDATE opt_analytics_metric_definition
+ SET status = 'PROBABLY_FINE' WHERE metric_id = 'm_revenue'`),
+ );
+ expect(message).toContain('metric_status');
+ });
+});
+
+run('BI-P030 — a published semantic model version is immutable, and only one is active', () => {
+ const base = '';
+
+ beforeAll(async () => {
+ for (const [id, version, status] of [
+ ['smv_1', 1, 'active'],
+ ['smv_2', 2, 'draft'],
+ ] as const) {
+ await sql.unsafe(`${base}
+ INSERT INTO opt_analytics_semantic_model_version
+ (semantic_model_version_id, model_id, tenant_id, project_id, version,
+ datasets, relationships_json, metrics_json, dimensions_json, status, hash)
+ VALUES ('${id}','sm_sales','t_alpha','p_main',${version},
+ ARRAY['ds_sales'],'[]','[]','[]','${status}', repeat('${version}',64))
+ ON CONFLICT DO NOTHING;`);
+ }
+ });
+
+ it("REFUSES an update to a published version's content", async () => {
+ const message = await refusalOf(() =>
+ sql.unsafe(`${base} UPDATE opt_analytics_semantic_model_version
+ SET metrics_json = '[{"id":"m_new"}]' WHERE semantic_model_version_id = 'smv_1'`),
+ );
+ expect(message, 'a pinned semantic model version was rewritten under a dashboard').toBeDefined();
+ expect(message).toContain('BI-P030');
+ });
+
+ it('ALLOWS a status transition, which is not a content change', async () => {
+ await expect(
+ sql.unsafe(`${base} UPDATE opt_analytics_semantic_model_version
+ SET status = 'superseded' WHERE semantic_model_version_id = 'smv_1'`),
+ ).resolves.toBeDefined();
+ await sql.unsafe(`${base} UPDATE opt_analytics_semantic_model_version
+ SET status = 'active' WHERE semantic_model_version_id = 'smv_1'`);
+ });
+
+ it('refuses TWO active versions of the same model — half the dashboards would read the other one', async () => {
+ const message = await refusalOf(() =>
+ sql.unsafe(`${base} UPDATE opt_analytics_semantic_model_version
+ SET status = 'active' WHERE semantic_model_version_id = 'smv_2'`),
+ );
+ expect(message).toContain('opt_analytics_semantic_model_one_active');
+ });
+
+ it('refuses a hash that is not a sha256 digest', async () => {
+ const message = await refusalOf(() =>
+ sql.unsafe(`${base}
+ INSERT INTO opt_analytics_semantic_model_version
+ (semantic_model_version_id, model_id, tenant_id, project_id, version,
+ datasets, relationships_json, metrics_json, dimensions_json, status, hash)
+ VALUES ('smv_bad','sm_sales','t_alpha','p_main',9,ARRAY['ds_sales'],'[]','[]','[]','draft','not-a-hash')`),
+ );
+ expect(message).toBeDefined();
+ });
+});
+
+run('BI-P018 / BI-P021 — the execution record and the usage ledger', () => {
+ const base = '';
+
+ it('refuses a compiled_query_hash that is not a 64-hex digest', async () => {
+ const message = await refusalOf(() =>
+ sql.unsafe(`${base} INSERT INTO opt_analytics_query_execution
+ (execution_id, tenant_id, project_id, engine_query_id, state, provider, compiled_query_hash)
+ VALUES ('e_bad','t_alpha','p_main','q1','queued','clickhouse','SELECT * FROM sales')`),
+ );
+ expect(message).toContain('hash_shape');
+ });
+
+ it('accepts a proper execution record', async () => {
+ await expect(
+ sql.unsafe(`${base} INSERT INTO opt_analytics_query_execution
+ (execution_id, tenant_id, project_id, engine_query_id, state, provider,
+ compiled_query_hash, principal_id, workload, cache_status, cost_units)
+ VALUES ('e_ok','t_alpha','p_main','q1','succeeded','clickhouse',
+ repeat('a',64),'u_1','dashboard','hit',0.5)`),
+ ).resolves.toBeDefined();
+ });
+
+ it('refuses an unknown cache status and an unknown workload', async () => {
+ expect(
+ await refusalOf(() =>
+ sql.unsafe(`${base} UPDATE opt_analytics_query_execution
+ SET cache_status = 'probably' WHERE execution_id = 'e_ok'`),
+ ),
+ ).toContain('cache_status');
+ expect(
+ await refusalOf(() =>
+ sql.unsafe(`${base} UPDATE opt_analytics_query_execution
+ SET workload = 'vibes' WHERE execution_id = 'e_ok'`),
+ ),
+ ).toContain('workload');
+ });
+
+ it('the usage ledger is APPEND-ONLY — update and delete both raise', async () => {
+ await sql.unsafe(`${base} INSERT INTO opt_analytics_usage_ledger
+ (usage_id, tenant_id, principal_id, query_id, engine, rows_scanned, bytes_scanned,
+ execution_ms, cost_units, terminated_by_budget)
+ VALUES ('u_1','t_alpha','p_1','q1','clickhouse',1000,2000,50,0.25,true)`);
+
+ expect(
+ await refusalOf(() =>
+ sql.unsafe(`${base} UPDATE opt_analytics_usage_ledger SET cost_units = 0 WHERE usage_id = 'u_1'`),
+ ),
+ 'a billing row was editable',
+ ).toBeDefined();
+ expect(
+ await refusalOf(() =>
+ sql.unsafe(`${base} DELETE FROM opt_analytics_usage_ledger WHERE usage_id = 'u_1'`),
+ ),
+ 'a billing row was deletable',
+ ).toBeDefined();
+ });
+
+ it('an analyst statement refusal must carry a reason', async () => {
+ const message = await refusalOf(() =>
+ sql.unsafe(`${base} INSERT INTO opt_analytics_sql_statement
+ (statement_id, tenant_id, principal_id, statement_normalized, statement_hash, outcome)
+ VALUES ('s_bad','t_alpha','u_1','SELECT ?', repeat('a',64), 'refused')`),
+ );
+ expect(message, 'a refusal with no reason was accepted — an unreviewable audit trail').toBeDefined();
+ });
+
+ it('a freshness row must carry a disclosure a UI can render', async () => {
+ const message = await refusalOf(() =>
+ sql.unsafe(`${base} INSERT INTO opt_analytics_dataset_freshness
+ (tenant_id, dataset_id, state, expected_interval_ms, disclosure)
+ VALUES ('t_alpha','ds_sales','STALE',3600000,' ')`),
+ );
+ expect(message).toBeDefined();
+ });
+
+ it('freshness defaults to UNKNOWN, never to FRESH', async () => {
+ await sql.unsafe(`${base} INSERT INTO opt_analytics_dataset_freshness
+ (tenant_id, dataset_id, expected_interval_ms, disclosure)
+ VALUES ('t_beta','ds_sales',3600000,'not yet computed')`);
+ const rows = await sql.unsafe(`${base} SELECT state FROM opt_analytics_dataset_freshness
+ WHERE tenant_id = 't_beta'`);
+ expect((rows as unknown as Array<{ state: string }>)[0].state).toBe('UNKNOWN');
+ });
+});