feat(analytics): sql/009 — execution metadata, metric governance, semantic registry, quarantine, usage ledger
CI (SIAX Cloud) / integration (push) Failing after 38s
CI (SIAX Cloud) / quality (push) Successful in 1m0s
CI (SIAX Cloud) / security (push) Successful in 25s
Security Scan (SIAX Cloud) / security (push) Successful in 28s
CI (SIAX Cloud) / integration (pull_request) Failing after 48s
CI (SIAX Cloud) / quality (pull_request) Successful in 1m16s
CI (SIAX Cloud) / security (pull_request) Successful in 27s
Security Scan (SIAX Cloud) / security (pull_request) Successful in 23s

The migration the previous commits' comments promised. Additive and idempotent
throughout, so the restore drill can replay it onto a live database.

BI-P018 adds principal_id, workload, compiled_query_hash, datasets, metrics,
cache_status and cost_units to the execution record — and deliberately NO
`query_text` column, with a CHECK pinning the hash to 64 hex characters so a
statement smuggled into that field is refused by the database. A test asserts
the absence of `query_text` and `sql` as columns.

BI-P031's DoD gets a Postgres TRIGGER, not just the TypeScript guard: a
certified metric whose meaning_hash changes without a version bump raises. That
is the layer that holds when something writes without going through the
application — a migration script, a psql session, an admin tool. A compiler
opinion does not survive `psql`; a trigger does.

BI-P030 gets a version-history table whose CONTENT is immutable (status and
valid_from may still move) plus a PARTIAL UNIQUE INDEX enforcing at most one
ACTIVE version per model per tenant. Two active versions is a silent
correctness failure in which half the dashboards read a different model, so it
is an index rather than application discipline.

BI-P021's usage ledger is append-only by trigger: update and delete both raise.
BI-P026's freshness table DEFAULTS TO 'UNKNOWN' — a dataset nobody has computed
freshness for must not read as FRESH — and requires a non-empty disclosure,
because a state with no words attached is a state a dashboard renders as a
silent green dot. BI-P025's quarantine record requires a principal and a reason
on release.

A BUG IN THIS MIGRATION, FOUND BY RUNNING IT:

  `SELECT 1 FROM pg_constraint WHERE conname = '...'` is NOT schema-scoped.
  conname is unique per schema, not per database, so on a database that already
  had these tables in `public` every guarded CHECK was silently skipped when
  the file was applied into a second schema. The constraints simply were not
  there, and the tests that should have proven them PASSED — because the
  INSERTs they expected to fail succeeded, and `expect(message).toContain(...)`
  on an undefined message reads as a type error, not as "the constraint is
  missing". Every guard now scopes via `conrelid = '<table>'::regclass`, and
  the fix is proven by applying the file to `public` FIRST and then running the
  suite into a fresh schema: 20/20.

Two flaws in my own test harness, also found by running it:
  * `sql.unsafe("SET search_path ...; SELECT ...")` is a multi-statement simple
    query, and postgres.js returns the LAST statement's result — so a SELECT
    silently returned the connection object and two assertions compared against
    nonsense. search_path is now pinned on the CONNECTION.
  * the fixture INSERT used column names that do not exist in sql/008's dataset
    table, which surfaced as a suite-level failure rather than as 20 skips.

20 tests against a real Postgres 16, gated on ANALYTICS_TEST_DATABASE_URL, and
each of them attempts the write the constraint exists to refuse.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Claude Opus 5
2026-09-07 03:00:56 +02:00
parent a9ae7f25a4
commit 2bfbd3f159
2 changed files with 695 additions and 0 deletions
@@ -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 = '<table>'::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();
@@ -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<typeof postgres>;
/** Did the statement RAISE? Returns the message, or undefined if it succeeded. */
async function refusalOf(fn: () => Promise<unknown>): Promise<string | undefined> {
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');
});
});