Files
0pt/tests/analytics/dbt-contract.test.ts
Claude Opus 5 ec26ddc296
CI (SIAX Cloud) / security (push) Successful in 22s
CI (SIAX Cloud) / integration (push) Successful in 38s
CI (SIAX Cloud) / quality (push) Successful in 52s
Security Scan (SIAX Cloud) / security (push) Successful in 20s
CI (SIAX Cloud) / security (pull_request) Successful in 22s
CI (SIAX Cloud) / integration (pull_request) Successful in 37s
Security Scan (SIAX Cloud) / security (pull_request) Successful in 20s
CI (SIAX Cloud) / quality (pull_request) Successful in 1m1s
feat(analytics): BI-P022 — dbt canonical transformation CONTRACT and reference project
Scope stated in the module's first paragraph: this defines the contract, it
does not run dbt, and nothing in this repo does. Execution is N0D's (n0d#38),
which consumes this from the other side. There is deliberately no DbtExecutor
implementation here — only the interface — so "0pt can run dbt" cannot become
true by accident and the two repos share one versioned shape.

`analytics/dbt/` is a real reference project covering raw -> staging ->
curated -> serving, with the four layers mapped onto BI-P003's existing tiers
rather than inventing a fifth vocabulary. The tenant predicate is applied at
the FIRST modelled layer so every downstream model inherits it and no later
model has to remember — the same forced-predicate rule as the query compiler's,
one layer up.

Two singular tests ship with it, both aimed at false greens:
  * `assert_mart_is_not_empty` — without it every column test passes trivially
    over zero rows and the whole suite reports green while the pipeline is dead;
  * `assert_no_cross_tenant_rows` — which should be impossible to fail, and
    exists precisely for that reason. An isolation guarantee nobody tests is the
    one that turns out to have four holes rather than the one reported.

BI-P022's version rule is a GATE. `DBT_RUNTIME_PIN` records dbt Core 1.10.13
(stable) with dbt-clickhouse 1.9.2 and BOTH review flags FALSE — no licence or
supply-chain review has been performed in this repo and none is claimed.
`assertDbtRuntimeCertified` refuses a production run until they are true, and
refuses an alpha line in production without a separate certification reference.
A version constant on its own would be the documented-but-unenforced pattern;
a test pins the flags at false so flipping them is a visible deliberate act.

The result is treated as UNTRUSTED INPUT, because it arrives from another
repo's executor and this estate has been burned by green statuses that meant
nothing. Refused: `status: success` with ZERO nodes executed (a dbt selector
matching nothing exits 0 — the dbt-shaped false green); an observed runtime
different from the pin (the models mean something else and nothing says so); a
revision that is not an exact 40-char SHA (a branch name is not reproducible,
and reproducibility is the DoD); a missing manifest.json or run_results.json,
without which the run cannot be audited or its tests ingested by BI-P025.

Layer dependencies are checked against the manifest GRAPH, not asserted as a
convention: a mart reading a source directly, or skipping a layer, or a
staging model reading a mart, are each refused.

Selectors are validated rather than passed through — a free-form argument list
is a shell surface, and a shell surface on a job holding warehouse credentials
is the whole problem. The job also cannot name an arbitrary target schema: it
must be scoped to the job's tenant.

39 tests. typecheck + eslint clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 02:45:41 +02:00

322 lines
12 KiB
TypeScript

/**
* tests/analytics/dbt-contract.test.ts
* =====================================
* BI-P022 — DBT CORE CANONICAL TRANSFORMATION (the CONTRACT).
*
* This repo does not run dbt; N0D (n0d#38) does, against this contract. So
* what is testable here is exactly what the contract must refuse, and the
* interesting refusals are the ones that look like successes:
*
* * `status: "success"` with ZERO nodes executed — a dbt selector that
* matches nothing exits 0. The dbt-shaped false green.
* * a result whose observed dbt version differs from the pin — the models
* mean something different and nothing says so.
* * a branch name as the revision — "main" today and "main" tomorrow are
* different projects, and BI-P022's DoD is about reproducibility.
* * a production run against an unreviewed runtime, which is BI-P022's
* "after current license/security verification" turned into a gate.
*
* The scaffold itself is asserted against the filesystem, because a directory
* convention that nothing checks is a directory convention that drifts.
*/
import fs from 'node:fs';
import path from 'node:path';
import { describe, expect, it } from 'vitest';
import { SiaxError } from '@/src/platform/errors';
import {
assertDbtRuntimeCertified,
assertLayerDependenciesLegal,
assertValidDbtJobRequest,
assertValidDbtJobResult,
DBT_LAYER_TIERS,
DBT_LAYERS,
DBT_RUNTIME_PIN,
REQUIRED_DBT_ARTIFACTS,
REQUIRED_DBT_DIRECTORIES,
tierForModelPath,
type DbtJobRequest,
type DbtJobResult,
} from '@/src/analytics/transform/dbt-contract';
const DBT_ROOT = path.join(process.cwd(), 'analytics/dbt');
const SHA = 'a'.repeat(40);
const CERTIFIED_PIN = {
...DBT_RUNTIME_PIN,
licenseReviewed: true,
securityReviewed: true,
reviewedBy: 'u_1',
reviewedAt: '2026-09-07T00:00:00Z',
};
function request(over: Partial<DbtJobRequest> = {}): DbtJobRequest {
return {
tenant_id: 't_alpha',
project_id: 'p_main',
dbt_project: 'siax_analytics',
revision: SHA,
command: 'build',
target_layer: 'marts',
environment: 'ci',
runtime: DBT_RUNTIME_PIN,
idempotency_key: 'idem-1',
expected_target_schema: 't_alpha_marts',
...over,
};
}
function result(over: Partial<DbtJobResult> = {}): DbtJobResult {
return {
tenant_id: 't_alpha',
dbt_project: 'siax_analytics',
revision: SHA,
command: 'build',
status: 'success',
started_at: '2026-09-07T00:00:00Z',
finished_at: '2026-09-07T00:05:00Z',
artifacts: {
'manifest.json': 'st0re://a/manifest.json',
'run_results.json': 'st0re://a/run_results.json',
},
nodes: [
{ unique_id: 'model.siax_analytics.mart_sales_daily', resource_type: 'model', status: 'success' },
],
observed_dbt_version: DBT_RUNTIME_PIN.version,
...over,
};
}
describe('BI-P022 — the scaffold exists and matches the declared conventions', () => {
it.each([...REQUIRED_DBT_DIRECTORIES])('analytics/dbt/%s exists', (dir) => {
expect(fs.existsSync(path.join(DBT_ROOT, dir)), `${dir} is missing`).toBe(true);
});
it('declares a reference project covering raw -> staging -> curated -> serving', () => {
expect(fs.existsSync(path.join(DBT_ROOT, 'models/sources/sources.yml'))).toBe(true);
expect(fs.existsSync(path.join(DBT_ROOT, 'models/staging/stg_sales_events.sql'))).toBe(true);
expect(fs.existsSync(path.join(DBT_ROOT, 'models/intermediate/int_sales_enriched.sql'))).toBe(true);
expect(fs.existsSync(path.join(DBT_ROOT, 'models/marts/mart_sales_daily.sql'))).toBe(true);
expect(fs.existsSync(path.join(DBT_ROOT, 'snapshots/snap_customers.sql'))).toBe(true);
});
it('the staging model applies the tenant predicate at the first modelled layer', () => {
const sql = fs.readFileSync(path.join(DBT_ROOT, 'models/staging/stg_sales_events.sql'), 'utf8');
expect(sql).toContain('where tenant_id = \'{{ var("siax_tenant_id") }}\'');
});
it('the mart reads intermediate, never a source directly', () => {
const sql = fs.readFileSync(path.join(DBT_ROOT, 'models/marts/mart_sales_daily.sql'), 'utf8');
expect(sql).toContain("ref('int_sales_enriched')");
expect(sql).not.toContain('source(');
});
it('ships a test that fails on an EMPTY mart', () => {
// Without it, every column test passes trivially over zero rows.
const sql = fs.readFileSync(path.join(DBT_ROOT, 'tests/assert_mart_is_not_empty.sql'), 'utf8');
expect(sql).toContain('= 0');
});
it('ships a cross-tenant test even though it should be impossible to fail', () => {
const sql = fs.readFileSync(path.join(DBT_ROOT, 'tests/assert_no_cross_tenant_rows.sql'), 'utf8');
expect(sql).toContain('tenant_id !=');
});
it("maps the four layers onto BI-P003's tiers", () => {
expect(DBT_LAYER_TIERS).toEqual({
sources: 'RAW',
staging: 'NORMALIZED',
intermediate: 'CURATED',
marts: 'SERVING',
});
expect([...DBT_LAYERS]).toEqual(['sources', 'staging', 'intermediate', 'marts']);
});
it('assigns a tier from a model path, and refuses one it cannot place', () => {
expect(tierForModelPath('models/marts/mart_sales_daily.sql')).toBe('SERVING');
expect(tierForModelPath('analytics/dbt/models/staging/x.sql')).toBe('NORMALIZED');
expect(() => tierForModelPath('models/scratch/x.sql')).toThrowError(SiaxError);
});
});
describe('BI-P022 — the version rule is a GATE, not a paragraph', () => {
it('the shipped pin is honestly UNREVIEWED', () => {
// If someone flips these without doing the review, that is a deliberate
// act visible in a diff — which is the point of storing the state.
expect(DBT_RUNTIME_PIN.licenseReviewed).toBe(false);
expect(DBT_RUNTIME_PIN.securityReviewed).toBe(false);
expect(DBT_RUNTIME_PIN.line).toBe('stable');
});
it('refuses a PRODUCTION run against an unreviewed runtime', () => {
expect(() => assertDbtRuntimeCertified(DBT_RUNTIME_PIN, 'production')).toThrowError(SiaxError);
});
it('allows development and CI against the same unreviewed runtime', () => {
expect(() => assertDbtRuntimeCertified(DBT_RUNTIME_PIN, 'ci')).not.toThrow();
expect(() => assertDbtRuntimeCertified(DBT_RUNTIME_PIN, 'development')).not.toThrow();
});
it('allows production once both reviews are recorded', () => {
expect(() => assertDbtRuntimeCertified(CERTIFIED_PIN, 'production')).not.toThrow();
});
it('refuses an ALPHA line in production without a separate certification reference', () => {
const alpha = { ...CERTIFIED_PIN, line: 'alpha' as const };
expect(() => assertDbtRuntimeCertified(alpha, 'production')).toThrowError(SiaxError);
expect(() =>
assertDbtRuntimeCertified({ ...alpha, separateCertificationRef: 'CERT-2026-11' }, 'production'),
).not.toThrow();
});
it('refuses a version range — a pin is exact or it is not a pin', () => {
expect(() => assertDbtRuntimeCertified({ ...CERTIFIED_PIN, version: '1.10' }, 'ci')).toThrowError(
SiaxError,
);
expect(() => assertDbtRuntimeCertified({ ...CERTIFIED_PIN, version: '^1.10.0' }, 'ci')).toThrowError(
SiaxError,
);
});
});
describe('BI-P022 — the job request', () => {
it('accepts a well-formed request', () => {
expect(() => assertValidDbtJobRequest(request())).not.toThrow();
});
it("refuses a branch name as the revision — BI-P022's DoD is reproducibility", () => {
for (const rev of ['main', 'HEAD', 'v1.0.0', 'a'.repeat(39)]) {
expect(() => assertValidDbtJobRequest(request({ revision: rev }))).toThrowError(SiaxError);
}
});
it('refuses a target schema that is not scoped to the tenant', () => {
let thrown: unknown;
try {
assertValidDbtJobRequest(request({ expected_target_schema: 't_beta_marts' }));
} catch (e) {
thrown = e;
}
expect((thrown as SiaxError).code).toBe('AUTHORIZATION_DENIED');
});
it('refuses a selector carrying shell metacharacters', () => {
for (const selector of ['stg_sales; rm -rf /', '$(whoami)', 'a`b`', 'x && y', 'a|b']) {
expect(() => assertValidDbtJobRequest(request({ select: [selector] }))).toThrowError(SiaxError);
}
// ...while accepting real dbt selector syntax.
expect(() =>
assertValidDbtJobRequest(
request({ select: ['tag:tier:SERVING+', 'state:modified', '+mart_sales_daily'] }),
),
).not.toThrow();
});
it('refuses a job targeting the sources layer — sources are declared, not built', () => {
expect(() => assertValidDbtJobRequest(request({ target_layer: 'sources' }))).toThrowError(SiaxError);
});
it('refuses a request with no idempotency key', () => {
expect(() => assertValidDbtJobRequest(request({ idempotency_key: '' }))).toThrowError(SiaxError);
});
it('refuses a production request on the unreviewed pin', () => {
expect(() =>
assertValidDbtJobRequest(
request({ environment: 'production', expected_target_schema: 't_alpha_marts' }),
),
).toThrowError(SiaxError);
});
});
describe('BI-P022 — the job RESULT is untrusted input', () => {
it('accepts a well-formed result', () => {
expect(() => assertValidDbtJobResult(result(), request())).not.toThrow();
});
it('REFUSES success with zero nodes — a selector matching nothing exits 0', () => {
let thrown: unknown;
try {
assertValidDbtJobResult(result({ nodes: [] }), request());
} catch (e) {
thrown = e;
}
expect(thrown).toBeInstanceOf(SiaxError);
expect((thrown as SiaxError).message).toContain('zero nodes');
});
it('refuses success while nodes failed', () => {
expect(() =>
assertValidDbtJobResult(
result({ nodes: [{ unique_id: 't1', resource_type: 'test', status: 'fail' }] }),
request(),
),
).toThrowError(SiaxError);
});
it('refuses a runtime that is not the pinned one', () => {
expect(() => assertValidDbtJobResult(result({ observed_dbt_version: '1.9.0' }), request())).toThrowError(
SiaxError,
);
});
it('refuses a result for a different revision', () => {
expect(() => assertValidDbtJobResult(result({ revision: 'b'.repeat(40) }), request())).toThrowError(
SiaxError,
);
});
it('refuses a result for a different tenant', () => {
expect(() => assertValidDbtJobResult(result({ tenant_id: 't_beta' }), request())).toThrowError(SiaxError);
});
it.each([...REQUIRED_DBT_ARTIFACTS])('refuses a result missing %s', (artifact) => {
const artifacts = { ...result().artifacts };
delete artifacts[artifact];
expect(() => assertValidDbtJobResult(result({ artifacts }), request())).toThrowError(SiaxError);
});
});
describe('BI-P022 — layer dependencies are checked against the graph, not the convention', () => {
it("accepts the reference project's own edges", () => {
expect(() =>
assertLayerDependenciesLegal([
{ from: 'models/staging/stg_sales_events.sql', to: 'models/sources/sources.yml' },
{ from: 'models/intermediate/int_sales_enriched.sql', to: 'models/staging/stg_sales_events.sql' },
{ from: 'models/marts/mart_sales_daily.sql', to: 'models/intermediate/int_sales_enriched.sql' },
]),
).not.toThrow();
});
it('refuses a mart reading a source directly — raw data reaching a dashboard', () => {
expect(() =>
assertLayerDependenciesLegal([
{ from: 'models/marts/mart_sales_daily.sql', to: 'models/sources/sources.yml' },
]),
).toThrowError(SiaxError);
});
it('refuses a mart reading staging — one layer skipped is still a skip', () => {
expect(() =>
assertLayerDependenciesLegal([
{ from: 'models/marts/mart_sales_daily.sql', to: 'models/staging/stg_sales_events.sql' },
]),
).toThrowError(SiaxError);
});
it('refuses a staging model reading a mart — an upward edge', () => {
expect(() =>
assertLayerDependenciesLegal([
{ from: 'models/staging/stg_sales_events.sql', to: 'models/marts/mart_sales_daily.sql' },
]),
).toThrowError(SiaxError);
});
it('allows a model reading another in the same layer', () => {
expect(() =>
assertLayerDependenciesLegal([
{ from: 'models/staging/stg_sales_events.sql', to: 'models/staging/stg_customers.sql' },
]),
).not.toThrow();
});
});