Files
0pt/tests/analytics/analytics-domain-postgres.realinfra.test.ts
T
Claude Opus 5 84bb9faa4f
CI (SIAX Cloud) / security (push) Successful in 41s
Security Scan (SIAX Cloud) / security (push) Successful in 40s
CI (SIAX Cloud) / integration (push) Successful in 52s
CI (SIAX Cloud) / quality (push) Successful in 1m5s
CI (SIAX Cloud) / security (pull_request) Successful in 31s
Security Scan (SIAX Cloud) / security (pull_request) Successful in 29s
CI (SIAX Cloud) / integration (pull_request) Successful in 45s
CI (SIAX Cloud) / quality (pull_request) Successful in 53s
feat(analytics): BI-P002..BI-P012 analytics core — domain model, tiers, contracts, Arrow, ClickHouse engine, isolation, governance
Implements the eight BI program prompts that make 0pt the canonical SIAX
analytics capability. Every file is NEW: not one existing file is modified,
and src/analytics/clickhouse-analytics.ts is byte-identical to main.

BI-P002 — the nineteen canonical entities, verbatim, with tenant_id +
project_id on every tenant-owned record. sql/008 gives each one a table.
The four version entities BI-P002 names as immutable are enforced by BEFORE
UPDATE/DELETE triggers, not by a TypeScript `readonly` — proven by direct SQL
against a live Postgres.

BI-P003 — RAW/NORMALIZED/CURATED/SERVING with the read rule enforced:
assertReadableByBiSurface refuses a RAW dataset to a dashboard unless a
recorded exception exists. All eight required declarations are NOT NULL.

BI-P004 — DatasetContract and a fail-closed compatibility gate. Fifteen
breaking-change classes are each tested twice: refused under a minor bump,
accepted under a major one.

BI-P005 — Arrow IPC alongside JSON, with a hand-written FlatBuffers writer so
no npm dependency (or CVE surface) is added. Conformance comes from three
independent implementations, not from self-agreement: pyarrow 25.0.1 and
DuckDB 1.5.5 read identical values from both ClickHouse's Arrow and ours, and
ClickHouse's own Arrow C++ ingests our 20,000-row batch with exact aggregates.

BI-P009 — SiaxAnalyticsEngine with all eight capabilities. The existing
ClickHouseAnalytics is COMPOSED, never replaced. executeQuery (analyst SQL) is
declared and deliberately refused with CAPABILITY_UNAVAILABLE until BI-P017 and
BI-P033 land, rather than opening the raw-SQL hole BI-P011 enumerates.

BI-P010 — four purpose-built tables, not one giant one, and the difference is
measured against the counterfactual: 48.8ms/40,960 rows vs 174.4ms/5,400,000
rows for the same question; 5/660 granules vs 660/1149.

BI-P011 — layered isolation, each layer proven with the layer above removed.
Without the forced predicate the same statement returns 50,050 (both tenants);
with it, t_alpha gets 50. Without a row policy an unrestricted user sees 2
tenants; the restricted user running the IDENTICAL statement sees 1.

BI-P012 — every named limit, fired individually and identified by ClickHouse
error code, plus the half usually skipped: an unrelated tenant is timed while
a real CPU bomb is in flight and must still return the CORRECT answer.

Three defects found by writing tests that can fail:
  * `CHECK (array_length(purposes,1) >= 1)` can NEVER fail — array_length is
    NULL for '{}' and a NULL CHECK passes. Fixed with coalesce(); the test
    that caught it is pinned.
  * EXPLAIN ESTIMATE run under the caller's own max_rows_to_read fails with
    code 158 exactly when the estimate would be over budget. Planner calls now
    use a narrower envelope.
  * result_overflow_mode=break returns HTTP 200 with 65,409 of 100,000 rows
    and no indication it is short. Every SELECT ships `throw`.

Verified rather than assumed: server7's ClickHouse holds `opt_scraped` (51
tables, 407,793,471 rows, `persons`/`cz_owners` included) — the b0ts plane this
module is prohibited from touching — has no `opt_analytics` database, zero
tenant_id columns and zero row policies. Nothing there was modified; every
proof ran against disposable containers.

Evidence: evidence/bi-p002-p012/{test-transcript.txt,bi-p010-benchmark.json,
bi-p005-arrow-roundtrip.json,server7-clickhouse-probe.md}
Docs: docs/analytics/BI_ANALYTICS_CORE.md

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 23:01:27 +02:00

365 lines
15 KiB
TypeScript

/**
* BI-P002 DoD: "Database migrations + persistence + tenant isolation +
* restore tests."
*
* REAL Postgres, same honest-degrade convention as
* tests/p0/source-registry-postgres.realinfra.test.ts: gated on
* ANALYTICS_TEST_DATABASE_URL, SKIPS HONESTLY when unset, never fabricates.
*
* docker run -d --name opt-bi-pg -p 15455:5432 \
* -e POSTGRES_USER=opt -e POSTGRES_PASSWORD=opt_bi_throwaway \
* -e POSTGRES_DB=opt_bi_test postgres:16-alpine
* ANALYTICS_TEST_DATABASE_URL=postgres://opt:opt_bi_throwaway@localhost:15455/opt_bi_test \
* pnpm vitest run tests/analytics/analytics-domain-postgres.realinfra.test.ts
*
* The RESTORE half is the part that catches migration-chain rot: it replays
* sql/001..008 in order into a FRESH database, exactly as
* docs/runbook/backup-restore.md describes, and only then runs the
* persistence assertions. sql/003_datasets.sql exists because that same drill
* found a table no file ever created; this suite keeps that class of defect
* from coming back through sql/008.
*/
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import fs from "node:fs";
import path from "node:path";
import postgres from "postgres";
import {
ANALYTICS_DOMAIN_ENTITIES,
ANALYTICS_ENTITY_TABLES,
IMMUTABLE_VERSION_ENTITIES,
} from "@/src/analytics/domain/entities";
import { AnalyticsDomainRepository } from "@/src/analytics/repositories/analytics-domain-repository";
import { contractHash, type DatasetContract } from "@/src/analytics/domain/contract";
const TEST_DATABASE_URL = process.env.ANALYTICS_TEST_DATABASE_URL;
const run = TEST_DATABASE_URL ? describe : describe.skip;
/** A schema of its own so the drill starts from genuinely nothing. */
const SCHEMA = `bi_p002_${Date.now().toString(36)}`;
let sql: ReturnType<typeof postgres> | undefined;
let reachable = false;
let unreachableReason = "";
/** Which sql/*.sql files the restore drill actually applied. */
const applied: string[] = [];
const ALPHA = { tenant_id: "t_alpha", project_id: "p_main" };
const BETA = { tenant_id: "t_beta", project_id: "p_main" };
function repo(): AnalyticsDomainRepository {
return new AnalyticsDomainRepository(sql!);
}
function contract(datasetId: string): DatasetContract {
return {
dataset_id: datasetId,
version: "1.0.0",
tier: "CURATED",
fields: [{ name: "id", type: "string", nullable: false, role: "identifier", primary_identifier: true }],
primary_identifiers: ["id"],
dimensions: [],
measures: [],
freshness_slo_seconds: 3600,
retention_days: 365,
pii_classification: "none",
source_lineage: ["src_x"],
quality_rules: [],
allowed_purposes: ["analytics"],
};
}
beforeAll(async () => {
if (!TEST_DATABASE_URL) {
console.warn("[realinfra] ANALYTICS_TEST_DATABASE_URL not set — skipping honestly, not fabricating success.");
return;
}
try {
sql = postgres(TEST_DATABASE_URL, { prepare: false, ssl: false, max: 3 });
await Promise.race([
sql`SELECT 1`,
new Promise((_, reject) => setTimeout(() => reject(new Error("connect timeout")), 5000)),
]);
// RESTORE DRILL: a brand-new schema, then every migration in order.
await sql.unsafe(`CREATE SCHEMA ${SCHEMA}`);
await sql.unsafe(`SET search_path TO ${SCHEMA}, public`);
const dir = path.join(process.cwd(), "sql");
for (const file of fs.readdirSync(dir).filter((f) => f.endsWith(".sql")).sort()) {
const body = fs.readFileSync(path.join(dir, file), "utf8");
try {
await sql.unsafe(`SET search_path TO ${SCHEMA}, public; ${body}`);
applied.push(file);
} catch (err) {
// 001 needs the `vector` extension, which a stock postgres:16-alpine
// does not carry. Record the skip rather than hiding it — the files
// sql/008 actually depends on must still apply.
applied.push(`${file} (FAILED: ${String(err).slice(0, 120)})`);
}
}
reachable = true;
} catch (err) {
reachable = false;
unreachableReason = err instanceof Error ? err.message : String(err);
}
}, 60_000);
afterAll(async () => {
if (sql && reachable) {
await sql.unsafe(`DROP SCHEMA IF EXISTS ${SCHEMA} CASCADE`).catch(() => undefined);
}
await sql?.end({ timeout: 5 });
});
run("BI-P002 analytics domain model against a LIVE Postgres", () => {
it("restore drill: sql/008 applies cleanly on top of the replayed chain", (ctx) => {
if (!reachable) ctx.skip();
expect(unreachableReason).toBe("");
const p008 = applied.find((f) => f.startsWith("008_"));
expect(p008, `sql/008 was not applied. Applied: ${applied.join(", ")}`).toBe(
"008_analytics_domain_model.sql",
);
});
it("all nineteen entity tables really exist after the restore", async (ctx) => {
if (!reachable) ctx.skip();
const rows = await sql!<{ table_name: string }[]>`
SELECT table_name FROM information_schema.tables WHERE table_schema = ${SCHEMA}`;
const present = new Set(rows.map((r) => r.table_name));
const missing = ANALYTICS_DOMAIN_ENTITIES.filter((e) => !present.has(ANALYTICS_ENTITY_TABLES[e]));
expect(missing).toEqual([]);
});
it("replaying sql/008 a second time is a no-op, not an error", async (ctx) => {
if (!reachable) ctx.skip();
const body = fs.readFileSync(path.join(process.cwd(), "sql", "008_analytics_domain_model.sql"), "utf8");
await expect(sql!.unsafe(`SET search_path TO ${SCHEMA}, public; ${body}`)).resolves.toBeDefined();
});
it("persists a dataset and reads it back through the repository", async (ctx) => {
if (!reachable) ctx.skip();
const r = repo();
const created = await r.createDataset(ALPHA, {
dataset_id: "ds_alpha_1",
name: "Alpha listings",
tier: "CURATED",
schema_contract_id: "dc_alpha",
schema_contract_version: "1.0.0",
owner_id: "team_data",
source_ref: "src_feed",
freshness_slo_seconds: 3600,
quality_rule_ids: [],
retention_days: 365,
classification: "none",
status: "active",
created_by: "usr_1",
});
expect(created.tenant_id).toBe("t_alpha");
const read = await r.getDataset(ALPHA, "ds_alpha_1");
expect(read?.name).toBe("Alpha listings");
expect(read?.freshness_slo_seconds).toBe(3600);
// Cross-check with direct SQL — proving the row is really in the table,
// not an artifact of the repository's own bookkeeping.
const direct = await sql!<{ c: string }[]>`
SELECT count(*)::text AS c FROM opt_analytics_dataset WHERE dataset_id = 'ds_alpha_1'`;
expect(Number(direct[0].c)).toBe(1);
});
it("TENANT ISOLATION: t_beta cannot read t_alpha's dataset, even with the exact id", async (ctx) => {
if (!reachable) ctx.skip();
const r = repo();
expect(await r.getDataset(BETA, "ds_alpha_1")).toBeUndefined();
expect(await r.listDatasets(BETA)).toEqual([]);
// And the row IS there — the empty result is scoping, not absence.
const direct = await sql!<{ c: string }[]>`SELECT count(*)::text AS c FROM opt_analytics_dataset`;
expect(Number(direct[0].c)).toBeGreaterThanOrEqual(1);
});
it("TENANT ISOLATION: a version cannot be minted against another tenant's dataset", async (ctx) => {
if (!reachable) ctx.skip();
await expect(
repo().createDatasetVersion(BETA, {
dataset_id: "ds_alpha_1",
contract_hash: "deadbeef",
schema_contract_version: "1.0.0",
}),
).rejects.toMatchObject({ code: "NOT_FOUND" });
});
it("dataset versions are monotonic and carry the contract hash", async (ctx) => {
if (!reachable) ctx.skip();
const r = repo();
const c = contract("ds_alpha_1");
const v1 = await r.createDatasetVersion(ALPHA, {
dataset_id: "ds_alpha_1",
contract_hash: contractHash(c),
schema_contract_version: "1.0.0",
});
const v2 = await r.createDatasetVersion(ALPHA, {
dataset_id: "ds_alpha_1",
contract_hash: contractHash({ ...c, version: "2.0.0" }),
schema_contract_version: "2.0.0",
});
expect(v1.version).toBe(1);
expect(v2.version).toBe(2);
expect(v2.contract_hash).not.toBe(v1.contract_hash);
expect((await r.listDatasetVersions(ALPHA, "ds_alpha_1")).map((v) => v.version)).toEqual([1, 2]);
});
it("IMMUTABILITY is enforced by the DATABASE: UPDATE and DELETE on a version row are refused", async (ctx) => {
if (!reachable) ctx.skip();
// Direct SQL, bypassing every TypeScript `readonly` — the only test that
// proves the guarantee survives a psql session.
await expect(
sql!.unsafe(
`UPDATE ${SCHEMA}.opt_analytics_dataset_version SET contract_hash = 'tampered' WHERE dataset_id = 'ds_alpha_1'`,
),
).rejects.toThrow(/immutable/i);
await expect(
sql!.unsafe(`DELETE FROM ${SCHEMA}.opt_analytics_dataset_version WHERE dataset_id = 'ds_alpha_1'`),
).rejects.toThrow(/immutable/i);
// The rows are untouched.
const rows = await sql!<{ contract_hash: string }[]>`
SELECT contract_hash FROM opt_analytics_dataset_version WHERE dataset_id = 'ds_alpha_1'`;
expect(rows).toHaveLength(2);
for (const r of rows) expect(r.contract_hash).not.toBe("tampered");
});
it("all four immutable entities refuse an UPDATE, not just the one that was tested first", async (ctx) => {
if (!reachable) ctx.skip();
const r = repo();
await r.createDataset(ALPHA, {
dataset_id: "ds_imm",
name: "Immutability fixture",
tier: "SERVING",
schema_contract_id: "dc_i",
schema_contract_version: "1.0.0",
owner_id: "team_data",
source_ref: "src",
freshness_slo_seconds: 60,
quality_rule_ids: [],
retention_days: 30,
classification: "none",
status: "active",
});
const metric = await r.createMetric(ALPHA, {
metric_id: "m_imm",
name: "immutable metric",
dataset_id: "ds_imm",
aggregation: "sum",
measure_field: "value",
allowed_dimension_ids: [],
owner_id: "team_data",
});
await r.createMetricVersion(ALPHA, { metric_id: metric.metric_id, definition_json: '{"v":1}' });
await r.createDatasetVersion(ALPHA, {
dataset_id: "ds_imm",
contract_hash: "h1",
schema_contract_version: "1.0.0",
});
await sql!.unsafe(`INSERT INTO ${SCHEMA}.opt_analytics_semantic_model
(semantic_model_id, tenant_id, project_id, name, version, model_json, model_hash, owner_id)
VALUES ('sm_1','t_alpha','p_main','model_a',1,'{}','h','team_data')`);
await sql!.unsafe(`INSERT INTO ${SCHEMA}.opt_analytics_dashboard_definition
(dashboard_id, tenant_id, project_id, name, owner_id) VALUES ('db_1','t_alpha','p_main','dash','team_data')`);
await sql!.unsafe(`INSERT INTO ${SCHEMA}.opt_analytics_dashboard_version
(dashboard_version_id, dashboard_id, tenant_id, project_id, version, layout_json, layout_hash)
VALUES ('dv_1','db_1','t_alpha','p_main',1,'{}','h')`);
const updates: Record<string, string> = {
DatasetVersion: `UPDATE ${SCHEMA}.opt_analytics_dataset_version SET contract_hash='x' WHERE dataset_id='ds_imm'`,
MetricVersion: `UPDATE ${SCHEMA}.opt_analytics_metric_version SET definition_json='{}' WHERE metric_id='m_imm'`,
SemanticModel: `UPDATE ${SCHEMA}.opt_analytics_semantic_model SET model_json='{}' WHERE semantic_model_id='sm_1'`,
DashboardVersion: `UPDATE ${SCHEMA}.opt_analytics_dashboard_version SET layout_json='{}' WHERE dashboard_version_id='dv_1'`,
};
for (const entity of IMMUTABLE_VERSION_ENTITIES) {
await expect(sql!.unsafe(updates[entity]), `${entity} is NOT immutable`).rejects.toThrow(/immutable/i);
}
});
it("a metric version after the first must state WHY the number changed meaning", async (ctx) => {
if (!reachable) ctx.skip();
const r = repo();
await expect(
r.createMetricVersion(ALPHA, { metric_id: "m_imm", definition_json: '{"v":2}' }),
).rejects.toThrow();
const v2 = await r.createMetricVersion(ALPHA, {
metric_id: "m_imm",
definition_json: '{"v":2}',
change_reason: "excluded internal test accounts from the numerator",
});
expect(v2.version).toBe(2);
});
it("a DatasetGrant without a purpose is refused by the DATABASE (purpose limitation)", async (ctx) => {
if (!reachable) ctx.skip();
// Regression pin: the first version of this constraint was written
// `array_length(allowed_purposes, 1) >= 1`, which is NULL for '{}' — and a
// CHECK evaluating to NULL PASSES in Postgres. It could never fail. This
// test found that; keep it.
await expect(
sql!.unsafe(`INSERT INTO ${SCHEMA}.opt_analytics_dataset_grant
(grant_id, dataset_id, tenant_id, project_id, subject_id, subject_type, level, allowed_purposes)
VALUES ('g_1','ds_imm','t_alpha','p_main','usr_1','user','read', '{}')`),
).rejects.toThrow(/purposes_nonempty/);
// A grant WITH a purpose is accepted, so the constraint is not simply
// rejecting everything.
await expect(
sql!.unsafe(`INSERT INTO ${SCHEMA}.opt_analytics_dataset_grant
(grant_id, dataset_id, tenant_id, project_id, subject_id, subject_type, level, allowed_purposes)
VALUES ('g_ok','ds_imm','t_alpha','p_main','usr_1','user','read', ARRAY['market_analytics'])`),
).resolves.toBeDefined();
});
it("a BIApplicationBinding without a purpose is refused too, and carries no credential column", async (ctx) => {
if (!reachable) ctx.skip();
await expect(
sql!.unsafe(`INSERT INTO ${SCHEMA}.opt_analytics_bi_application_binding
(binding_id, tenant_id, project_id, kind, name, principal_id, allowed_purposes)
VALUES ('b_1','t_alpha','p_main','superset','sup','svc_1','{}')`),
).rejects.toThrow(/purposes_nonempty/);
const cols = await sql!<{ column_name: string }[]>`
SELECT column_name FROM information_schema.columns
WHERE table_schema = ${SCHEMA} AND table_name = 'opt_analytics_bi_application_binding'`;
const names = cols.map((c) => c.column_name.toLowerCase());
for (const forbidden of ["password", "secret", "dsn", "token", "connection_string", "clickhouse_url"]) {
expect(names, `BI binding must never store ${forbidden}`).not.toContain(forbidden);
}
});
it("the dimension catalog is tenant-scoped and FAILS CLOSED for a tenant with none", async (ctx) => {
if (!reachable) ctx.skip();
const r = repo();
await r.createDimension(ALPHA, {
dimension_id: "dim_1",
name: "make",
dataset_id: "ds_imm",
field_name: "vehicle_make",
field_type: "string",
classification: "none",
});
expect(await r.dimensionCatalog(ALPHA)).toEqual({ make: "vehicle_make" });
// t_beta declared nothing, so it may reference nothing.
expect(await r.dimensionCatalog(BETA)).toEqual({});
});
it("a dimension whose physical field is not identifier-safe is refused by the DATABASE", async (ctx) => {
if (!reachable) ctx.skip();
await expect(
sql!.unsafe(`INSERT INTO ${SCHEMA}.opt_analytics_dimension_definition
(dimension_id, tenant_id, project_id, name, dataset_id, field_name, field_type)
VALUES ('dim_bad','t_alpha','p_main','evil','ds_imm', 'x''; DROP TABLE opt_analytics_dataset; --','string')`),
).rejects.toThrow();
});
it("the repository refuses to act without a verified tenant scope", async (ctx) => {
if (!reachable) ctx.skip();
const r = repo();
await expect(r.getDataset({ tenant_id: "", project_id: "p" }, "ds_imm")).rejects.toMatchObject({
code: "UNAUTHENTICATED",
});
await expect(r.listDatasets({ tenant_id: "t_alpha", project_id: " " })).rejects.toMatchObject({
code: "UNAUTHENTICATED",
});
});
});