fix(recovery): restore source removed by production documentation merge

This commit is contained in:
2026-11-10 03:42:10 +01:00
parent 585bdd4b44
commit 3d2fc6f913
957 changed files with 115754 additions and 0 deletions
+161
View File
@@ -0,0 +1,161 @@
#!/usr/bin/env node
/**
* BI-P005 DoD, executed literally:
*
* "Same query result roundtrips ClickHouse -> Arrow -> DuckDB/Python
* without semantic loss for supported types."
*
* Pulls a result from a LIVE ClickHouse as Arrow IPC, hands the identical
* bytes to pyarrow and DuckDB, and records what each of them read back. Also
* runs the reverse direction — 0pt's own encoder's bytes decoded by pyarrow +
* DuckDB — because the encoder is hand-rolled and the only conformance
* evidence worth having comes from somebody else's parser.
*
* CLICKHOUSE_URL=http://localhost:18123 CLICKHOUSE_AUTH=user:pass \
* node scripts/bi-p005-arrow-roundtrip.mjs
*
* pyarrow/DuckDB run in a throwaway `python:3.12-slim` container, so the host
* needs docker but no Python packages. Output:
* evidence/bi-p002-p012/bi-p005-arrow-roundtrip.json
*
* The 0pt-encoded fixture is produced by `pnpm vitest run
* tests/analytics/arrow-fixture.emit.ts`-style code inlined here via a tiny
* tsx-free path: the encoder is re-implemented ONLY in the sense that this
* script shells out to vitest to write the file, so the bytes tested are the
* bytes the library produces.
*/
import fs from "node:fs";
import path from "node:path";
import os from "node:os";
import { execFileSync } from "node:child_process";
const URL_ = process.env.CLICKHOUSE_URL;
const AUTH = process.env.CLICKHOUSE_AUTH;
if (!URL_) {
console.error("CLICKHOUSE_URL is required — this script measures, it does not simulate.");
process.exit(2);
}
const work = fs.mkdtempSync(path.join(os.tmpdir(), "bi-p005-"));
const headers = AUTH ? { Authorization: `Basic ${Buffer.from(AUTH).toString("base64")}` } : {};
const QUERY = `
SELECT toString(t) AS tenant_id, toInt64(n) AS n, toFloat64(v) AS v, b AS flag,
toDateTime64(ts, 3, 'UTC') AS ts
FROM (
SELECT arrayJoin([
('t_alpha', 1, 1.5, true, '2026-01-01 00:00:00.000'),
('t_beta', 2, 2.5, false, '2026-06-15 08:09:10.123'),
('t_gamma', 3, 3.25, true, '2026-09-06 12:34:56.789')
]) AS tup, tup.1 AS t, tup.2 AS n, tup.3 AS v, tup.4 AS b, tup.5 AS ts
)`;
async function chArrow(sql, settings) {
const u = new URL(URL_);
u.searchParams.set("query", sql);
for (const [k, v] of Object.entries(settings)) u.searchParams.set(k, v);
const res = await fetch(u.toString(), { method: "POST", headers, body: "" });
const buf = Buffer.from(await res.arrayBuffer());
if (!res.ok) throw new Error(`CH ${res.status}: ${buf.toString("utf8").slice(0, 300)}`);
return buf;
}
const PY = `
import pyarrow as pa, pyarrow.ipc as ipc, duckdb, json, sys, os
out = {}
for name in sorted(os.listdir('/w')):
if not name.endswith('.arrow'):
continue
tbl = ipc.open_stream(pa.BufferReader(open('/w/'+name,'rb').read())).read_all()
con = duckdb.connect(); con.register('t', tbl)
out[name] = {
"pyarrow_version": pa.__version__,
"duckdb_version": duckdb.__version__,
"schema": [{"name": f.name, "type": str(f.type)} for f in tbl.schema],
"num_rows": tbl.num_rows,
"pyarrow_values": json.loads(json.dumps(tbl.to_pydict(), default=str)),
"duckdb_types": [(r[0], r[1]) for r in con.execute("describe select * from t").fetchall()],
"duckdb_rows": [[str(c) for c in row] for row in con.execute("select * from t order by n").fetchall()],
}
print(json.dumps(out))
`;
async function main() {
const version = (await (async () => {
const u = new URL(URL_);
u.searchParams.set("query", "SELECT version() FORMAT TSVRaw");
const r = await fetch(u.toString(), { method: "POST", headers, body: "" });
return (await r.text()).trim();
})());
// Direction 1: ClickHouse's own Arrow C++ output.
const chBytes = await chArrow(`${QUERY} FORMAT ArrowStream`, {
output_format_arrow_string_as_string: "1",
output_format_arrow_low_cardinality_as_dictionary: "0",
});
fs.writeFileSync(path.join(work, "01_clickhouse_encoded.arrow"), chBytes);
// Direction 2: 0pt's own encoder. Driven through the SHIPPED module via
// tests/analytics/arrow-fixture.test.ts (ARROW_FIXTURE_DIR) so the bytes
// pyarrow sees are the library's, not a copy pasted into this script.
execFileSync(
"npx",
["vitest", "run", "tests/analytics/arrow-fixture.test.ts"],
{ stdio: "inherit", env: { ...process.env, ARROW_FIXTURE_DIR: work } },
);
// The reader script goes in as a FILE, not as `python -c "..."`: passing a
// multi-line program through a shell argument mangles the newlines.
fs.writeFileSync(path.join(work, "read_arrow.py"), PY);
const raw = execFileSync(
"docker",
[
"run", "--rm", "-v", `${work}:/w`, "-w", "/w", "python:3.12-slim",
"bash", "-lc",
"pip install --quiet pyarrow duckdb pytz >/dev/null 2>&1 && python /w/read_arrow.py",
],
{ encoding: "utf8", maxBuffer: 32 * 1024 * 1024 },
);
const decoded = JSON.parse(raw);
const evidence = {
generated_at: new Date().toISOString(),
clickhouse_version: version,
query: QUERY.trim(),
directions: {
"clickhouse -> arrow -> pyarrow/duckdb": decoded["01_clickhouse_encoded.arrow"],
"0pt encoder -> arrow -> pyarrow/duckdb": decoded["02_0pt_encoded.arrow"],
},
byte_sizes: {
clickhouse_encoded: chBytes.length,
opt_encoded: fs.statSync(path.join(work, "02_0pt_encoded.arrow")).size,
},
verdict: null,
};
// The DoD is "without semantic loss": the two independent readers must agree
// on the SAME values for both encoders.
const a = evidence.directions["clickhouse -> arrow -> pyarrow/duckdb"];
const b = evidence.directions["0pt encoder -> arrow -> pyarrow/duckdb"];
const same =
JSON.stringify(a.pyarrow_values) === JSON.stringify(b.pyarrow_values) &&
JSON.stringify(a.duckdb_rows) === JSON.stringify(b.duckdb_rows) &&
JSON.stringify(a.duckdb_types) === JSON.stringify(b.duckdb_types);
evidence.verdict = same
? "PASS — pyarrow and DuckDB read identical values and types from BOTH encoders"
: "FAIL — the two encoders do not round-trip to the same values";
const out = path.join(process.cwd(), "evidence", "bi-p002-p012", "bi-p005-arrow-roundtrip.json");
fs.mkdirSync(path.dirname(out), { recursive: true });
fs.writeFileSync(out, JSON.stringify(evidence, null, 2) + "\n");
console.log(evidence.verdict);
console.log(`wrote ${out}`);
if (!same) process.exitCode = 1;
}
main()
.catch((e) => {
console.error(e);
process.exitCode = 1;
})
.finally(() => fs.rmSync(work, { recursive: true, force: true }));
+311
View File
@@ -0,0 +1,311 @@
#!/usr/bin/env node
/**
* BI-P010 DoD: "Benchmark representative VehIQ-scale fixture and generic SaaS
* fixture."
*
* Builds both fixtures in a THROWAWAY database on a ClickHouse the operator
* points it at, times the query shapes each schema exists to serve, and writes
* a machine-readable transcript to
* evidence/bi-p002-p012/bi-p010-benchmark.json.
*
* CLICKHOUSE_URL=http://localhost:18123 \
* CLICKHOUSE_AUTH=user:pass \
* node scripts/bi-p010-benchmark.mjs
*
* It also measures the COUNTERFACTUAL the program warns about — "one giant
* unbounded table for every analytics use case" — by running the same
* questions against a single wide table with a non-tenant-leading sort key.
* Without that comparison the split is an assertion; with it, it is a number.
*
* NEVER point this at the b0ts ClickHouse or any quarantined market data: it
* CREATEs and DROPs a database.
*/
import fs from "node:fs";
import path from "node:path";
const URL_ = process.env.CLICKHOUSE_URL;
const AUTH = process.env.CLICKHOUSE_AUTH;
if (!URL_) {
console.error("CLICKHOUSE_URL is required. Refusing to invent numbers.");
process.exit(2);
}
const DB = `opt_bench_${Date.now().toString(36)}`;
const headers = AUTH ? { Authorization: `Basic ${Buffer.from(AUTH).toString("base64")}` } : {};
async function ch(sql, body, settings = {}) {
const u = new URL(URL_);
u.searchParams.set("query", sql);
for (const [k, v] of Object.entries(settings)) u.searchParams.set(k, v);
const res = await fetch(u.toString(), { method: "POST", headers, body: body ?? "" });
const text = await res.text();
if (!res.ok) throw new Error(`CH ${res.status}: ${text.slice(0, 400)}`);
return text;
}
async function json(sql, settings) {
const t = await ch(sql, undefined, settings);
return t ? JSON.parse(t) : { data: [] };
}
/** Run `sql` n times, return {p50, p95, min, max, rows_read, bytes_read}. */
async function timeQuery(label, sql, n = 12) {
// One warm-up so we time the query, not the page cache miss.
await ch(sql);
const times = [];
let stats = {};
for (let i = 0; i < n; i++) {
const t0 = process.hrtime.bigint();
const out = await json(sql);
times.push(Number(process.hrtime.bigint() - t0) / 1e6);
stats = out.statistics ?? stats;
}
times.sort((a, b) => a - b);
const pick = (p) => Number(times[Math.min(times.length - 1, Math.floor((times.length * p) / 100))].toFixed(2));
return {
label,
runs: n,
p50_ms: pick(50),
p95_ms: pick(95),
min_ms: Number(times[0].toFixed(2)),
max_ms: Number(times[times.length - 1].toFixed(2)),
rows_read: stats.rows_read ?? null,
bytes_read: stats.bytes_read ?? null,
};
}
const evidence = {
generated_at: new Date().toISOString(),
clickhouse_version: null,
database: DB,
fixtures: {},
measurements: [],
notes: [],
};
async function main() {
evidence.clickhouse_version = (await ch("SELECT version() FORMAT TSVRaw")).trim();
await ch(`CREATE DATABASE ${DB}`);
// ── the BI-P010 schema under test ──────────────────────────────────────
await ch(`CREATE TABLE ${DB}.opt_analytics_event (
tenant_id LowCardinality(String), project_id LowCardinality(String),
dataset_id LowCardinality(String), metric LowCardinality(String),
event_id String, event_time DateTime64(3,'UTC'), value Float64,
dim_names Array(LowCardinality(String)), dim_values Array(String),
ingested_at DateTime64(3,'UTC') DEFAULT now64(3),
INDEX idx_dim_values dim_values TYPE bloom_filter(0.01) GRANULARITY 4)
ENGINE = MergeTree() PARTITION BY toYYYYMM(event_time)
ORDER BY (tenant_id, project_id, dataset_id, metric, event_time)`);
await ch(`CREATE TABLE ${DB}.opt_metric_daily (
tenant_id LowCardinality(String), project_id LowCardinality(String),
dataset_id LowCardinality(String), metric LowCardinality(String), day Date,
sum_state AggregateFunction(sum, Float64), count_state AggregateFunction(count, Float64),
min_state AggregateFunction(min, Float64), max_state AggregateFunction(max, Float64))
ENGINE = AggregatingMergeTree() PARTITION BY toYYYYMM(day)
ORDER BY (tenant_id, project_id, dataset_id, metric, day)`);
await ch(`CREATE MATERIALIZED VIEW ${DB}.opt_metric_daily_mv TO ${DB}.opt_metric_daily AS
SELECT tenant_id, project_id, dataset_id, metric, toDate(event_time) AS day,
sumState(value) AS sum_state, countState(value) AS count_state,
minState(value) AS min_state, maxState(value) AS max_state
FROM ${DB}.opt_analytics_event
GROUP BY tenant_id, project_id, dataset_id, metric, day`);
await ch(`CREATE TABLE ${DB}.opt_dataset_projection (
tenant_id LowCardinality(String), project_id LowCardinality(String),
dataset_id LowCardinality(String), entity_key String,
as_of DateTime64(3,'UTC'), version UInt64,
attributes Map(String,String), measures Map(String,Float64),
INDEX idx_entity entity_key TYPE bloom_filter(0.01) GRANULARITY 4)
ENGINE = ReplacingMergeTree(version) PARTITION BY (tenant_id)
ORDER BY (tenant_id, project_id, dataset_id, entity_key)`);
// ── the COUNTERFACTUAL the program warns about ─────────────────────────
// One table for everything, sorted by time (the shape people reach for when
// "we'll just put it all in events").
await ch(`CREATE TABLE ${DB}.giant_everything (
tenant_id String, project_id String, dataset_id String, metric String,
entity_key String, event_time DateTime64(3,'UTC'), value Float64,
dim_names Array(String), dim_values Array(String),
attributes Map(String,String), measures Map(String,Float64))
ENGINE = MergeTree() PARTITION BY toYYYYMM(event_time)
ORDER BY (event_time, tenant_id)`);
// ── FIXTURE 1: generic SaaS event stream ───────────────────────────────
// 200 tenants x 12 metrics x 90 days x 25 events/day ~= 5.4M events.
const SAAS_TENANTS = 200, SAAS_METRICS = 12, SAAS_DAYS = 90, SAAS_PER_DAY = 25;
const saasRows = SAAS_TENANTS * SAAS_METRICS * SAAS_DAYS * SAAS_PER_DAY;
console.log(`seeding SaaS fixture: ${saasRows.toLocaleString()} events...`);
const t0saas = Date.now();
await ch(`INSERT INTO ${DB}.opt_analytics_event
SELECT concat('t_', toString(number % ${SAAS_TENANTS})) AS tenant_id,
'p_main' AS project_id,
concat('ds_', toString(number % 8)) AS dataset_id,
concat('metric_', toString(number % ${SAAS_METRICS})) AS metric,
toString(number) AS event_id,
toDateTime64('2026-06-01 00:00:00', 3, 'UTC') + toIntervalSecond(number % ${SAAS_DAYS * 86400}) AS event_time,
toFloat64(number % 997) AS value,
['plan','region'] AS dim_names,
[if(number % 3 = 0, 'pro', 'free'), if(number % 5 = 0, 'eu-north', 'eu-central')] AS dim_values,
now64(3) AS ingested_at
FROM numbers(${saasRows})`);
const saasSeedMs = Date.now() - t0saas;
// ── FIXTURE 2: VehIQ-scale vehicle projections ─────────────────────────
// 2M vehicles for the tenant under test, plus 2M belonging to other
// tenants — so a tenant-scoped lookup has to prove it prunes.
const VEHICLES = 2_000_000, DECOYS = 2_000_000;
console.log(`seeding VehIQ fixture: ${(VEHICLES + DECOYS).toLocaleString()} vehicle projections...`);
const t0veh = Date.now();
await ch(`INSERT INTO ${DB}.opt_dataset_projection
SELECT 't_vehiq' AS tenant_id, 'p_main' AS project_id, 'ds_vehicles' AS dataset_id,
concat('YV1', leftPad(toString(number), 14, '0')) AS entity_key,
toDateTime64('2026-09-01 00:00:00', 3, 'UTC') AS as_of,
1 AS version,
map('make', arrayElement(['volvo','saab','audi','vw','toyota'], (number % 5) + 1),
'fuel', arrayElement(['diesel','petrol','ev','hybrid'], (number % 4) + 1)) AS attributes,
map('asking_price', toFloat64(50000 + number % 900000),
'mileage_km', toFloat64(number % 400000)) AS measures
FROM numbers(${VEHICLES})`);
await ch(`INSERT INTO ${DB}.opt_dataset_projection
SELECT concat('t_other_', toString(number % 50)) AS tenant_id, 'p_main', 'ds_vehicles',
concat('WBA', leftPad(toString(number), 14, '0')),
toDateTime64('2026-09-01 00:00:00', 3, 'UTC'), 1,
map('make','bmw'), map('asking_price', toFloat64(number % 500000))
FROM numbers(${DECOYS})`);
const vehSeedMs = Date.now() - t0veh;
// ── the same data in the giant table ───────────────────────────────────
console.log("seeding the counterfactual giant table...");
await ch(`INSERT INTO ${DB}.giant_everything
SELECT tenant_id, project_id, dataset_id, metric, '' AS entity_key, event_time, value,
dim_names, dim_values, map() AS attributes, map() AS measures
FROM ${DB}.opt_analytics_event`);
await ch(`INSERT INTO ${DB}.giant_everything
SELECT tenant_id, project_id, dataset_id, '' AS metric, entity_key,
as_of AS event_time, 0 AS value, [] AS dim_names, [] AS dim_values, attributes, measures
FROM ${DB}.opt_dataset_projection`);
await ch(`OPTIMIZE TABLE ${DB}.opt_analytics_event FINAL`, undefined, { max_execution_time: "600" });
await ch(`OPTIMIZE TABLE ${DB}.opt_dataset_projection FINAL`, undefined, { max_execution_time: "600" });
await ch(`OPTIMIZE TABLE ${DB}.giant_everything FINAL`, undefined, { max_execution_time: "900" });
const sizes = await json(`SELECT table, sum(rows) AS rows, sum(bytes_on_disk) AS bytes
FROM system.parts WHERE database='${DB}' AND active GROUP BY table ORDER BY table FORMAT JSON`);
evidence.fixtures = {
saas: { events: saasRows, tenants: SAAS_TENANTS, metrics: SAAS_METRICS, days: SAAS_DAYS, seed_ms: saasSeedMs },
vehiq: { vehicles_under_test: VEHICLES, decoy_vehicles: DECOYS, seed_ms: vehSeedMs },
on_disk: sizes.data.map((r) => ({ table: r.table, rows: Number(r.rows), bytes: Number(r.bytes) })),
};
// ── measurements ───────────────────────────────────────────────────────
const T = "t_57"; // one representative SaaS tenant
evidence.measurements.push(
await timeQuery(
"SaaS dashboard panel: 90-day metric total for ONE tenant (purpose-built event table)",
`SELECT sum(value) AS value FROM ${DB}.opt_analytics_event
WHERE tenant_id = '${T}' AND metric = 'metric_3' FORMAT JSON`,
),
await timeQuery(
"SAME question against ONE GIANT TABLE (time-leading sort key)",
`SELECT sum(value) AS value FROM ${DB}.giant_everything
WHERE tenant_id = '${T}' AND metric = 'metric_3' FORMAT JSON`,
),
await timeQuery(
"SaaS time series: daily series for ONE tenant from the AggregatingMergeTree rollup",
`SELECT day, sumMerge(sum_state) AS value FROM ${DB}.opt_metric_daily
WHERE tenant_id = '${T}' AND metric = 'metric_3' GROUP BY day ORDER BY day FORMAT JSON`,
),
await timeQuery(
"SAME time series computed from raw events instead of the rollup",
`SELECT toDate(event_time) AS day, sum(value) AS value FROM ${DB}.opt_analytics_event
WHERE tenant_id = '${T}' AND metric = 'metric_3' GROUP BY day ORDER BY day FORMAT JSON`,
),
await timeQuery(
"SaaS group-by: one tenant sliced by a dimension",
`SELECT dim_values[indexOf(dim_names, 'plan')] AS grp, count() AS value
FROM ${DB}.opt_analytics_event WHERE tenant_id = '${T}' AND metric = 'metric_3'
GROUP BY grp ORDER BY value DESC FORMAT JSON`,
),
await timeQuery(
"VehIQ point lookup: one vehicle by key, tenant-scoped (purpose-built projection)",
`SELECT entity_key, measures['asking_price'] AS p FROM ${DB}.opt_dataset_projection
WHERE tenant_id = 't_vehiq' AND project_id = 'p_main' AND dataset_id = 'ds_vehicles'
AND entity_key = 'YV100000001234567' FORMAT JSON`,
),
await timeQuery(
"SAME point lookup against ONE GIANT TABLE",
`SELECT entity_key, measures['asking_price'] AS p FROM ${DB}.giant_everything
WHERE tenant_id = 't_vehiq' AND dataset_id = 'ds_vehicles'
AND entity_key = 'YV100000001234567' FORMAT JSON`,
),
await timeQuery(
"VehIQ aggregate: average asking price by make for ONE tenant across 2M vehicles",
`SELECT attributes['make'] AS grp, avg(measures['asking_price']) AS value
FROM ${DB}.opt_dataset_projection
WHERE tenant_id = 't_vehiq' AND project_id = 'p_main' AND dataset_id = 'ds_vehicles'
GROUP BY grp ORDER BY value DESC FORMAT JSON`,
8,
),
await timeQuery(
"SAME aggregate against ONE GIANT TABLE",
`SELECT attributes['make'] AS grp, avg(measures['asking_price']) AS value
FROM ${DB}.giant_everything
WHERE tenant_id = 't_vehiq' AND dataset_id = 'ds_vehicles' AND entity_key != ''
GROUP BY grp ORDER BY value DESC FORMAT JSON`,
8,
),
);
// Index pruning evidence: how many granules the tenant predicate skips.
const plan = await ch(
`EXPLAIN indexes = 1 SELECT sum(value) FROM ${DB}.opt_analytics_event
WHERE tenant_id = '${T}' AND metric = 'metric_3' FORMAT TSVRaw`,
);
const giantPlan = await ch(
`EXPLAIN indexes = 1 SELECT sum(value) FROM ${DB}.giant_everything
WHERE tenant_id = '${T}' AND metric = 'metric_3' FORMAT TSVRaw`,
);
const granules = (t) => {
const m = [...t.matchAll(/Granules: (\d+)\/(\d+)/g)].pop();
return m ? { read: Number(m[1]), total: Number(m[2]) } : null;
};
evidence.index_pruning = {
purpose_built_event_table: granules(plan),
one_giant_table: granules(giantPlan),
note:
"Granules read/total for the SAME question. The tenant-leading ORDER BY is what turns the " +
"forced tenant predicate (BI-P011) into a primary-key range scan instead of a full scan.",
};
evidence.notes.push(
"Fixtures are synthetic and generated in-server with numbers(); no production or b0ts data was read or written.",
"Each timing is the p50/p95 of repeated runs after one warm-up, measured client-side over HTTP on the same host.",
"The 'ONE GIANT TABLE' rows are the counterfactual BI-P010 warns against, measured rather than asserted.",
);
const out = path.join(process.cwd(), "evidence", "bi-p002-p012", "bi-p010-benchmark.json");
fs.writeFileSync(out, JSON.stringify(evidence, null, 2) + "\n");
console.log(`wrote ${out}`);
for (const m of evidence.measurements) {
console.log(` ${m.p50_ms.toString().padStart(9)} ms p50 ${String(m.rows_read).padStart(10)} rows ${m.label}`);
}
console.log(" pruning:", JSON.stringify(evidence.index_pruning));
}
main()
.catch((e) => {
console.error(e);
process.exitCode = 1;
})
.finally(async () => {
try {
await ch(`DROP DATABASE IF EXISTS ${DB}`);
console.log(`dropped ${DB}`);
} catch (e) {
console.error("cleanup failed:", e.message);
}
});
+133
View File
@@ -0,0 +1,133 @@
#!/usr/bin/env node
/**
* BI-P061: is "only one published release per dashboard" actually enforced?
*
* The application checks for a live release before publishing, and that check
* is a read-then-write race. This script measures BOTH directions against a
* real Postgres and writes evidence/bi-p061/dashboard-release-race.json:
*
* guard_absent the partial unique index dropped -> both concurrent
* publishes commit, and the dashboard has TWO live versions.
* guard_present the index in place -> the loser fails with 23505.
*
* Without the first run, the second proves nothing: a race that never
* interleaved would also show one published release.
*
* ANALYTICS_TEST_DATABASE_URL=postgres://postgres:test@127.0.0.1:25441/opt \
* node scripts/bi-p061-release-race.mjs
*
* It CREATEs and DROPs its own throwaway schemas and touches nothing else.
*/
import fs from 'node:fs';
import path from 'node:path';
import postgres from 'postgres';
const URL_ = process.env.ANALYTICS_TEST_DATABASE_URL;
if (!URL_) {
console.error('ANALYTICS_TEST_DATABASE_URL is required. Refusing to invent numbers.');
process.exit(2);
}
const STAMP = Date.now().toString(36);
const SCHEMAS = { guard_present: `bi_p061_ev_on_${STAMP}`, guard_absent: `bi_p061_ev_off_${STAMP}` };
const MIGRATIONS = ['sql/008_analytics_domain_model.sql', 'sql/010_analytics_dashboard_releases.sql'];
const HASH = (c) => c.repeat(64);
const connect = (schema, max = 1) =>
postgres(URL_, { max, onnotice: () => undefined, connection: { search_path: schema } });
async function setup(schema, { dropIndex }) {
const admin = postgres(URL_, { max: 1, onnotice: () => undefined });
await admin.unsafe(`CREATE SCHEMA IF NOT EXISTS ${schema}`);
await admin.end({ timeout: 5 });
const c = connect(schema, 2);
for (const f of MIGRATIONS) await c.unsafe(fs.readFileSync(path.join(process.cwd(), f), 'utf8'));
if (dropIndex) await c.unsafe('DROP INDEX opt_analytics_dashboard_release_one_published');
await c.unsafe(`
INSERT INTO opt_analytics_dashboard_definition
(dashboard_id, tenant_id, project_id, name, current_version, owner_id)
VALUES ('dash_race', 't_alpha', 'p_main', 'Race', 1, 'u_1');
INSERT INTO opt_analytics_dashboard_version
(dashboard_version_id, dashboard_id, tenant_id, project_id, version, layout_json, layout_hash)
VALUES ('dv_1', 'dash_race', 't_alpha', 'p_main', 1, '{}', '${HASH('a')}'),
('dv_2', 'dash_race', 't_alpha', 'p_main', 2, '{}', '${HASH('b')}');`);
return c;
}
/** Both sessions read "nothing is live", then both write. */
async function race(schema) {
const conns = [connect(schema), connect(schema)];
const publish = (conn, n) =>
conn.begin(async (tx) => {
await tx.unsafe(
`SELECT release_id FROM opt_analytics_dashboard_release
WHERE dashboard_id='dash_race' AND state='published'`,
);
await new Promise((r) => setTimeout(r, 60));
await tx.unsafe(`
INSERT INTO opt_analytics_dashboard_release
(release_id, dashboard_id, dashboard_version_id, tenant_id, project_id, version, state,
document_hash, published_at)
VALUES ('rel_${n}', 'dash_race', 'dv_${n}', 't_alpha', 'p_main', ${n},
'published', '${HASH(n === 1 ? 'a' : 'b')}', now())`);
});
const results = await Promise.allSettled([publish(conns[0], 1), publish(conns[1], 2)]);
for (const c of conns) await c.end({ timeout: 5 });
return {
committed: results.filter((r) => r.status === 'fulfilled').length,
rejected: results
.filter((r) => r.status === 'rejected')
.map((r) => ({ code: r.reason?.code ?? null, constraint: r.reason?.constraint_name ?? null })),
};
}
async function publishedRows(conn) {
const rows = await conn.unsafe(
`SELECT release_id, version FROM opt_analytics_dashboard_release
WHERE dashboard_id='dash_race' AND state='published' ORDER BY version`,
);
return rows.map((r) => ({ release_id: r.release_id, version: r.version }));
}
const out = { recorded_at: new Date().toISOString() };
const cleanup = [];
try {
for (const [label, schema] of Object.entries(SCHEMAS)) {
const conn = await setup(schema, { dropIndex: label === 'guard_absent' });
cleanup.push([schema, conn]);
if (!out.postgres_version) {
const v = await conn.unsafe('SHOW server_version');
out.postgres_version = v[0].server_version;
}
const result = await race(schema);
out[label] = {
partial_unique_index_present: label === 'guard_present',
transactions_committed: result.committed,
rejections: result.rejected,
published_releases_after: await publishedRows(conn),
};
}
out.verdict = {
counterfactual_produced_two_live_dashboards: out.guard_absent.published_releases_after.length === 2,
guard_left_exactly_one: out.guard_present.published_releases_after.length === 1,
loser_error_code: out.guard_present.rejections[0]?.code ?? null,
application_check_alone_is_sufficient: false,
note:
'DashboardReleaseService.publish() reads for a live release before writing. Both sessions in ' +
'guard_absent passed that check and both committed. The partial unique index is what holds.',
};
const dir = path.join(process.cwd(), 'evidence/bi-p061');
fs.mkdirSync(dir, { recursive: true });
const file = path.join(dir, 'dashboard-release-race.json');
fs.writeFileSync(file, `${JSON.stringify(out, null, 2)}\n`);
console.log(`wrote ${file}`);
console.log(JSON.stringify(out.verdict, null, 2));
} finally {
for (const [schema, conn] of cleanup) {
await conn.unsafe(`DROP SCHEMA IF EXISTS ${schema} CASCADE`);
await conn.end({ timeout: 5 });
}
}
+53
View File
@@ -0,0 +1,53 @@
#!/usr/bin/env sh
# check-masterplan-lock.sh
#
# Fails the build if this repo has drifted from the canonical SIAX Non-Platform
# masterplan (sax3l/siax-masterplan). Dependency-free (POSIX sh + grep only) so
# it runs before any language toolchain is installed.
#
# What it guards against:
# 1. PLAN/MASTERPLAN_INDEX.md being deleted (silent regression to a parallel
# truth with no pointer back to the canonical docs).
# 2. The "locked architecture principles" section being stripped or edited
# away, which is the actual enforcement text (one canonical owner per
# domain/truth, no cross-service SQL, no frontend-tenant-trust, no
# production-mock-fallbacks, API-first).
# 3. The status vocabulary line (MISSING -> ... -> PRODUCTION_READY) going
# missing, which would let a repo invent its own status labels.
#
# See PLAN/MASTERPLAN_INDEX.md and sax3l/siax-masterplan for the source of
# truth. Do not weaken this check to make a red build pass -- fix the drift
# in PLAN/MASTERPLAN_INDEX.md instead (or resync it from siax-masterplan).
set -eu
PLAN_FILE="PLAN/MASTERPLAN_INDEX.md"
fail=0
if [ ! -f "$PLAN_FILE" ]; then
echo "::error::${PLAN_FILE} is missing. This repo must carry a repo-local copy of the SIAX Non-Platform masterplan index (source: sax3l/siax-masterplan). Restore it -- do not remove this check."
exit 1
fi
check_contains() {
needle="$1"
label="$2"
if ! grep -qF "$needle" "$PLAN_FILE"; then
echo "::error::${PLAN_FILE} no longer contains ${label} ('${needle}'). This looks like drift away from the canonical masterplan (sax3l/siax-masterplan). Refresh PLAN/MASTERPLAN_INDEX.md from source instead of editing this check."
fail=1
fi
}
check_contains "Låsta arkitekturprinciper" "the locked-architecture-principles heading"
check_contains "En canonical owner per domän och sanning" "the one-canonical-owner-per-domain principle"
check_contains "aldrig egen SQL" "the no-own-SQL / no-parallel-truth clause"
check_contains "ingen frontend-tenant-trust" "the no-frontend-tenant-trust clause"
check_contains "PRODUCTION_READY" "the status vocabulary"
check_contains "sax3l/siax-masterplan" "the pointer back to the canonical masterplan repo"
if [ "$fail" -ne 0 ]; then
echo "::error::masterplan lock check FAILED -- see above."
exit 1
fi
echo "masterplan lock check OK -- ${PLAN_FILE} still matches the canonical principles."
+74
View File
@@ -0,0 +1,74 @@
#!/usr/bin/env node
/**
* Blocking guard for finding UI-09 (measured 2026-09-26): 0pt.app carried 82
* `color-contrast` violations at axe-core 4.10.2 impact=serious — the estate's
* single largest a11y defect. Every one of them came from the same shape: a
* Tailwind alpha modifier on a muted text colour.
*
* Measured WCAG 2.2 contrast against the two theme backgrounds in
* styles/globals.css (--background: oklch(1 0 0) light / oklch(0.145 0 0) dark,
* --muted-foreground: oklch(0.556 0 0) / oklch(0.708 0 0)):
*
* text-muted-foreground 4.73:1 light 7.63:1 dark PASS (AA needs 4.5)
* text-muted-foreground/70 2.71:1 4.20:1 FAIL
* text-muted-foreground/60 2.29:1 3.37:1 FAIL
* text-muted-foreground/50 1.96:1 2.67:1 FAIL
* text-muted-foreground/40 1.69:1 2.11:1 FAIL
* text-muted-foreground/10 1.13:1 1.13:1 FAIL
*
* The unmodified token passes in both themes, so the fix is simply never to
* fade it. Purely decorative text is exempt — but it has to SAY so: the element
* must carry `aria-hidden`, which is also what makes axe skip it. The single
* remaining case (app/bestall/page.tsx, the oversized "01" masthead numeral) is
* `aria-hidden` + `select-none` + `pointer-events-none` and is allowed by that
* rule.
*
* This is a source-level guard, not a browser run. The full axe-core job that
* UI-09 also asks for needs a build + serve in CI and is tracked separately;
* until it exists this stops the regression at the shape that caused it.
*/
import { readdirSync, readFileSync, statSync } from 'node:fs';
import { join, relative } from 'node:path';
const ROOT = new URL('../..', import.meta.url).pathname.replace(/\/$/, '');
const SKIP = new Set(['node_modules', '.next', '.git', 'dist', 'build', 'coverage', '.turbo']);
const EXT = /\.(tsx|ts|jsx|js|css)$/;
/** Alpha-modified colour utilities on TEXT. Background/border alphas are fine. */
const PATTERN = /\btext-(muted-foreground|foreground|primary|secondary|accent-foreground|card-foreground|popover-foreground)\/\d+/g;
function walk(dir, out = []) {
for (const name of readdirSync(dir)) {
if (SKIP.has(name)) continue;
const p = join(dir, name);
if (statSync(p).isDirectory()) walk(p, out);
else if (EXT.test(name)) out.push(p);
}
return out;
}
const violations = [];
for (const file of walk(ROOT)) {
const src = readFileSync(file, 'utf8');
const lines = src.split('\n');
lines.forEach((line, i) => {
for (const m of line.matchAll(PATTERN)) {
// Decorative text is exempt when the element declares itself hidden to AT.
// The attribute may sit on an earlier line of the same JSX element.
const window = lines.slice(Math.max(0, i - 6), i + 1).join('\n');
if (/\baria-hidden\b/.test(window)) continue;
violations.push({ file: relative(ROOT, file), line: i + 1, token: m[0], text: line.trim() });
}
});
}
if (violations.length) {
console.error(`\n✗ ${violations.length} alpha-faded text colour(s) found — WCAG 2.2 AA 1.4.3 (finding UI-09).\n`);
for (const v of violations) {
console.error(` ${v.file}:${v.line} ${v.token}`);
console.error(` ${v.text.slice(0, 160)}`);
}
console.error('\n Use the unmodified token (text-muted-foreground = 4.73:1 light / 7.63:1 dark).');
console.error(' If the text really is decoration, put aria-hidden on the element.\n');
process.exit(1);
}
console.log('✓ no alpha-faded text colours — WCAG 2.2 AA 1.4.3 contrast shape is clean (UI-09)');
Executable
+5
View File
@@ -0,0 +1,5 @@
<!-- GENERATED by siax-doctor repo-contract --apply (repo-contract-gate/v2 — 2026-09-23T21:12:27.075Z); template-fil, redigera via siax-templates - EJ handredigering. Saknas en rad här upptäcks det av RC-lagret. -->
#!/usr/bin/env sh
# GENERATED by siax-doctor repo-contract --apply (repo-contract-gate/v2). Stub = ej kopplad an; koppla till @siax/doctor via siax-templates.
echo "repo-contract-gate: scripts/doctor ar en GENERATED stub - koppla den till @siax/doctor via siax-templates" >&2
exit 0
+608
View File
@@ -0,0 +1,608 @@
#!/usr/bin/env node
// siax-doctor (BACKLOG P1-19, control AGENT-04): "Repot är
// self-diagnostiserande: `pnpm siax:doctor` kör hela standarden lokalt och
// skriver ut avvikelser med kontroll-ID."
//
// This is a repo-local, zero-dependency subset -- NOT the published
// @siax/doctor package from sax3l/siax-standard. That package was
// deliberately not taken on as a dependency here: at the time this was
// written it wasn't verified reachable/stable from this repo's frozen
// lockfile install, and a doctor script that can silently fail to install
// is worse than no doctor script. This checks the same class of
// machine-checkable facts using only Node's built-ins -- no network in the
// default mode, no registry.
//
// =============================================================================
// 2026-09-07 -- WHY THIS SCRIPT WAS REWRITTEN: it validated PRESENCE, never TRUTH
// =============================================================================
// The previous version's manifest checks were all `requireJsonField`, whose
// entire body is "does this key exist". Measured consequence: running it with
// siax.repo.json `operationalState: "running"` (false at the time -- every
// declared host answered 402/404) and with `"down"` (true) produced 39/40 and
// byte-identical output. A descriptor that asserted a live service therefore
// survived every gate this repo had.
//
// DESIGN, and what is deliberately NOT done:
//
// 1. OFFLINE (default, and the only thing anything automated runs).
// Everything here is a pure function of the repo's own bytes, so it is
// deterministic and cannot go red because a network was missing. Three
// kinds of check:
// (a) VALUE checks -- enums, formats, patterns. `operationalState` must
// be one of the standard's five states, `gitHost` must literally be
// git.siax.io, `node` must match the fleet's server pattern, every
// declared fqdn must be a bare well-formed hostname.
// (b) INTERNAL CONSISTENCY -- the repo's files must agree with each
// other. Every prod hostname in siax.repo.json must actually appear
// in this repo's own deploy/contract config (siax.config.json
// `environments.*.domain`, contracts/openapi.yaml `servers:`), and
// vice versa. A hostname nothing deploys is a hostname nobody owns.
// (c) SUBSTANTIATION -- the one that closes the historical hole.
// Liveness is not knowable offline. But whether a liveness CLAIM is
// BACKED is. `operationalState` must be matched by a dated probe
// record in evidence/operational-state.json whose results agree
// with it: "running" requires every prod probe to be a 2xx,
// "down" requires none of them to be. Claiming "running" next to a
// recorded 402 now fails. The record's own `ok` flags are re-derived
// from `httpStatus`, so a hand-edited `ok: true` next to a 402 fails
// too.
//
// 2. LIVE (`--live`, or SIAX_DOCTOR_LIVE=1) -- opt-in, for humans and for a
// scheduled job that HAS network. Actually probes the declared prod hosts,
// compares reality to the declared state, and checks that the security
// headers next.config.mjs promises are really on the wire.
// `--write-evidence` rewrites evidence/operational-state.json from that
// probe. This is NOT wired into the blocking CI path on purpose: a gate
// that needs the public internet fails for reasons that have nothing to do
// with the change under review, and people learn to ignore it.
//
// Exit code 0 = every check passed. Exit code 1 = at least one check failed
// (see the printed control-ID to find the BACKLOG/standard item).
// `--json` prints a machine-readable result instead of the table; that is what
// tests/repo/siax-doctor.test.ts consumes so this script is enforced by the
// blocking "CI (SIAX Cloud) / quality" job without any workflow change.
// =============================================================================
import { existsSync, readFileSync, writeFileSync } from "node:fs";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
// ROOT is the repo being diagnosed. SIAX_DOCTOR_ROOT lets a test point the
// SAME checks at a fixture tree, so the negative cases ("operationalState says
// running while the recorded probes say 402") are proven by the suite on every
// run -- not just once, in a transcript, by whoever wrote the check.
const ROOT = process.env.SIAX_DOCTOR_ROOT
? process.env.SIAX_DOCTOR_ROOT
: join(dirname(fileURLToPath(import.meta.url)), "..");
const argv = process.argv.slice(2);
const WANT_JSON = argv.includes("--json");
const WANT_LIVE = argv.includes("--live") || process.env.SIAX_DOCTOR_LIVE === "1";
const WANT_WRITE_EVIDENCE = argv.includes("--write-evidence");
const PROBE_TIMEOUT_MS = Number(process.env.SIAX_DOCTOR_TIMEOUT_MS || 15000);
/**
* How stale a liveness claim may be before it stops counting as evidence.
* Only enforced for states that assert the service is up ("running"/"degraded")
* -- a repo that is honestly "down" does not decay into a lie by sitting still.
*/
const MAX_EVIDENCE_AGE_DAYS = 90;
/** @type {{id: string, label: string, check: () => boolean | string}[]} */
const checks = [];
/** @type {{id: string, label: string, run: () => Promise<boolean | string>}[]} */
const liveChecks = [];
function file(path) {
return join(ROOT, path);
}
function readJson(path) {
if (!existsSync(file(path))) return { error: `missing file: ${path}` };
try {
return { value: JSON.parse(readFileSync(file(path), "utf8")) };
} catch (e) {
return { error: `invalid JSON in ${path}: ${e.message}` };
}
}
function readText(path) {
if (!existsSync(file(path))) return { error: `missing file: ${path}` };
return { value: readFileSync(file(path), "utf8") };
}
function requireFile(id, label, path) {
checks.push({
id,
label,
check: () => (existsSync(file(path)) ? true : `missing: ${path}`),
});
}
function requireJsonField(id, label, path, fieldPath) {
checks.push({
id,
label,
check: () => {
const { value: data, error } = readJson(path);
if (error) return error;
let cur = data;
for (const key of fieldPath.split(".")) {
if (cur == null || !(key in cur)) return `missing field "${fieldPath}" in ${path}`;
cur = cur[key];
}
return true;
},
});
}
/**
* The check the old `requireJsonField` should have been: presence AND a
* predicate on the value. `expectation` is prose printed on failure.
*/
function requireJsonValue(id, label, path, fieldPath, predicate, expectation) {
checks.push({
id,
label,
check: () => {
const { value: data, error } = readJson(path);
if (error) return error;
let cur = data;
for (const key of fieldPath.split(".")) {
if (cur == null || !(key in cur)) return `missing field "${fieldPath}" in ${path}`;
cur = cur[key];
}
if (predicate(cur)) return true;
return `${path} "${fieldPath}" = ${JSON.stringify(cur)} -- expected ${expectation}`;
},
});
}
function requireTextMatch(id, label, path, pattern, patternLabel) {
checks.push({
id,
label,
check: () => {
const { value: text, error } = readText(path);
if (error) return error;
return pattern.test(text) ? true : `${path} does not contain ${patternLabel}`;
},
});
}
function check(id, label, fn) {
checks.push({ id, label, check: fn });
}
// -- REPO-* / DOC-* : required root files --------------------------------
requireFile("DOC-01", "README.md exists", "README.md");
requireFile("REPO-08", "LICENSE exists", "LICENSE");
requireFile("REPO-13", ".editorconfig exists", ".editorconfig");
checks.push({
id: "REPO-15/P0-19",
label: "no committed .env (real secrets)",
check: () => (existsSync(file(".env")) ? "a real .env file is committed -- should never happen" : true),
});
requireFile("P1-15", "CODEOWNERS exists", "CODEOWNERS");
requireFile("P1-3", "CHANGELOG.md exists", "CHANGELOG.md");
requireFile("SEC-*", "SECURITY.md exists", "SECURITY.md");
requireFile("AGENT-01", "AGENTS.md exists", "AGENTS.md");
requireFile("DOC-01", "CONTRIBUTING.md exists", "CONTRIBUTING.md");
// -- DOC-02/DOC-04 : ARCHITECTURE.md at the exact standard path -----------
requireFile("DOC-02", "docs/architecture/ARCHITECTURE.md exists at the standard path", "docs/architecture/ARCHITECTURE.md");
requireTextMatch(
"DOC-02/v2.0",
"ARCHITECTURE.md has the v2.0 '## 5. Ytor och paritet' section",
"docs/architecture/ARCHITECTURE.md",
/^## 5\. Ytor och paritet/m,
"'## 5. Ytor och paritet'",
);
requireTextMatch(
"DOC-04",
"ARCHITECTURE.md has a 'Senast verifierad mot kod' date field",
"docs/architecture/ARCHITECTURE.md",
/Senast verifierad mot kod:\*\*\s*\d{4}-\d{2}-\d{2}/,
"a dated 'Senast verifierad mot kod' field",
);
// -- P1-1 / REPO-03/04/06 : manifests, PRESENCE ---------------------------
requireFile("P1-1/REPO-04", "siax.repo.json exists", "siax.repo.v3.json");
for (const field of ["repo", "category", "subcategory", "appId", "owner", "lifecycle", "gitHost", "node", "domains", "consumes", "consumedBy", "replacesSaaS", "children", "operationalState"]) {
requireJsonField("P1-1/REPO-04", `siax.repo.json has required field "${field}"`, "siax.repo.v3.json", field);
}
requireFile("P1-1/REPO-06", "siax.config.json exists", "siax.config.json");
for (const field of ["framework", "build", "start", "port", "healthcheck", "environments"]) {
requireJsonField("P1-1/REPO-06", `siax.config.json has required field "${field}"`, "siax.config.json", field);
}
// -- REPO-04/value : manifests, VALUES (not just presence) -----------------
// Enums mirror schema/siax.repo.schema.json (standard v2.0). Kept literal here
// on purpose: the doctor must work offline, and fetching the schema over the
// network is the exact dependency this script refuses to take on.
const OPERATIONAL_STATES = ["planned", "building", "running", "degraded", "down"];
const LIFECYCLES = ["active", "archive", "retire", "external"];
const CATEGORIES = ["platform", "extension", "business", "consultant", "customer", "mobile"];
const ENVS = ["dev", "test", "staging", "prod"];
requireJsonValue(
"REPO-04/GOV-06", 'siax.repo.json "operationalState" is one of the standard\'s five states',
"siax.repo.v3.json", "operationalState",
(v) => OPERATIONAL_STATES.includes(v), `one of ${OPERATIONAL_STATES.join("|")}`,
);
requireJsonValue(
"REPO-04/GOV-06", 'siax.repo.json "lifecycle" is a standard lifecycle',
"siax.repo.v3.json", "lifecycle",
(v) => LIFECYCLES.includes(v), `one of ${LIFECYCLES.join("|")}`,
);
requireJsonValue(
"REPO-04/GOV-01", 'siax.repo.json "category" is a standard category',
"siax.repo.v3.json", "category",
(v) => CATEGORIES.includes(v), `one of ${CATEGORIES.join("|")}`,
);
requireJsonValue(
"REPO-04", 'siax.repo.json "gitHost" is git.siax.io (schema const)',
"siax.repo.v3.json", "gitHost",
(v) => v === "git.siax.io", '"git.siax.io"',
);
requireJsonValue(
"REPO-04", 'siax.repo.json "node" is null or a real fleet node',
"siax.repo.v3.json", "node",
(v) => v === null || (typeof v === "string" && /^server(5|6|7|8|9|10|11)$/.test(v)),
"null, or server5..server11",
);
requireJsonValue(
"REPO-06", 'siax.config.json "port" is a plausible TCP port',
"siax.config.json", "port",
(v) => Number.isInteger(v) && v > 0 && v < 65536, "an integer in 1..65535",
);
requireJsonValue(
"REPO-06/CD-04", 'siax.config.json "healthcheck.path" is an absolute path',
"siax.config.json", "healthcheck.path",
(v) => typeof v === "string" && v.startsWith("/"), "a path starting with /",
);
// A bare hostname: labels of [a-z0-9-], at least one dot, no scheme/port/path.
const FQDN_RE = /^(?=.{1,253}$)(?!-)[a-z0-9-]{1,63}(?<!-)(\.(?!-)[a-z0-9-]{1,63}(?<!-))+$/;
check("NET-01", "every siax.repo.json domain entry is well-formed", () => {
const { value: repo, error } = readJson("siax.repo.v3.json");
if (error) return error;
if (!Array.isArray(repo.domains)) return '"domains" is not an array';
const problems = [];
for (const [i, d] of repo.domains.entries()) {
if (!d || typeof d !== "object") { problems.push(`domains[${i}] is not an object`); continue; }
if (typeof d.fqdn !== "string" || !FQDN_RE.test(d.fqdn)) {
problems.push(`domains[${i}].fqdn=${JSON.stringify(d.fqdn)} is not a bare lowercase hostname (no scheme, port or path)`);
}
if (!ENVS.includes(d.env)) problems.push(`domains[${i}].env=${JSON.stringify(d.env)} not in ${ENVS.join("|")}`);
if (typeof d.purpose !== "string" || d.purpose.trim() === "") problems.push(`domains[${i}].purpose is empty`);
}
const seen = new Set();
for (const d of repo.domains) {
if (d && typeof d.fqdn === "string") {
if (seen.has(d.fqdn)) problems.push(`duplicate fqdn ${d.fqdn}`);
seen.add(d.fqdn);
}
}
return problems.length === 0 ? true : problems.join("; ");
});
/** Hostnames this repo's own deploy/contract config actually points at. */
function hostsInDeployConfig() {
const hosts = new Set();
const { value: cfg } = readJson("siax.config.json");
if (cfg && cfg.environments && typeof cfg.environments === "object") {
for (const env of Object.values(cfg.environments)) {
if (env && typeof env.domain === "string") hosts.add(env.domain.toLowerCase());
}
}
const { value: openapi } = readText("contracts/openapi.yaml");
if (openapi) {
// `servers:` entries are `- url: https://host`. Deliberately a narrow match:
// any https URL in the contract counts as a host this repo points at.
for (const m of openapi.matchAll(/^\s*-?\s*url:\s*["']?https?:\/\/([a-z0-9.-]+)/gim)) {
hosts.add(m[1].toLowerCase());
}
}
return hosts;
}
check("NET-01/REPO-06", "every declared prod hostname appears in this repo's own deploy/contract config", () => {
const { value: repo, error } = readJson("siax.repo.v3.json");
if (error) return error;
if (!Array.isArray(repo.domains)) return '"domains" is not an array';
const configured = hostsInDeployConfig();
const orphans = repo.domains
.filter((d) => d && d.env === "prod" && typeof d.fqdn === "string")
.map((d) => d.fqdn.toLowerCase())
.filter((f) => !configured.has(f));
return orphans.length === 0
? true
: `declared but nothing in siax.config.json/contracts/openapi.yaml points at: ${orphans.join(", ")}`;
});
check("REPO-06/NET-01", "every siax.config.json environment domain is declared in siax.repo.json domains", () => {
const { value: repo, error: e1 } = readJson("siax.repo.v3.json");
if (e1) return e1;
const { value: cfg, error: e2 } = readJson("siax.config.json");
if (e2) return e2;
const declared = new Map(
(Array.isArray(repo.domains) ? repo.domains : [])
.filter((d) => d && typeof d.fqdn === "string")
.map((d) => [d.fqdn.toLowerCase(), d.env]),
);
const problems = [];
for (const [envName, env] of Object.entries(cfg.environments ?? {})) {
if (!env || typeof env.domain !== "string") continue;
const fqdn = env.domain.toLowerCase();
if (!declared.has(fqdn)) {
problems.push(`siax.config.json environments.${envName}.domain=${fqdn} is not in siax.repo.json domains`);
} else if (declared.get(fqdn) !== envName) {
problems.push(`${fqdn} is env=${declared.get(fqdn)} in siax.repo.json but environments.${envName} in siax.config.json`);
}
}
return problems.length === 0 ? true : problems.join("; ");
});
// -- GOV-06 : operationalState must be SUBSTANTIATED, not merely present ----
const EVIDENCE_PATH = "evidence/operational-state.json";
requireFile("GOV-06", `${EVIDENCE_PATH} exists (substantiates operationalState)`, EVIDENCE_PATH);
check("GOV-06", "the recorded probe evidence is internally coherent", () => {
const { value: ev, error } = readJson(EVIDENCE_PATH);
if (error) return error;
const problems = [];
if (!Array.isArray(ev.probes) || ev.probes.length === 0) problems.push("no probes recorded");
const when = Date.parse(ev.checkedAt ?? "");
if (Number.isNaN(when)) problems.push(`checkedAt=${JSON.stringify(ev.checkedAt)} is not a parseable timestamp`);
else if (when > Date.now() + 86_400_000) problems.push(`checkedAt=${ev.checkedAt} is in the future`);
for (const [i, p] of (ev.probes ?? []).entries()) {
if (!p || typeof p !== "object") { problems.push(`probes[${i}] is not an object`); continue; }
if (typeof p.fqdn !== "string" || !FQDN_RE.test(p.fqdn)) problems.push(`probes[${i}].fqdn is not a hostname`);
if (!Number.isInteger(p.httpStatus) && p.httpStatus !== null) problems.push(`probes[${i}].httpStatus must be an integer or null (unreachable)`);
if (typeof p.ok !== "boolean") problems.push(`probes[${i}].ok must be a boolean`);
// `ok` is not allowed to disagree with the status it sits next to.
const derived = Number.isInteger(p.httpStatus) && p.httpStatus >= 200 && p.httpStatus < 400;
if (typeof p.ok === "boolean" && p.ok !== derived) {
problems.push(`probes[${i}] (${p.fqdn}) records ok=${p.ok} next to httpStatus=${JSON.stringify(p.httpStatus)}`);
}
}
return problems.length === 0 ? true : problems.join("; ");
});
check("GOV-06", "every declared prod hostname has a probe result on record", () => {
const { value: repo, error: e1 } = readJson("siax.repo.v3.json");
if (e1) return e1;
const { value: ev, error: e2 } = readJson(EVIDENCE_PATH);
if (e2) return e2;
const probed = new Set((ev.probes ?? []).map((p) => String(p?.fqdn ?? "").toLowerCase()));
const missing = (Array.isArray(repo.domains) ? repo.domains : [])
.filter((d) => d && d.env === "prod" && typeof d.fqdn === "string")
.map((d) => d.fqdn.toLowerCase())
.filter((f) => !probed.has(f));
return missing.length === 0 ? true : `no probe recorded for: ${missing.join(", ")}`;
});
check("GOV-06", "operationalState agrees with the recorded probe results", () => {
const { value: repo, error: e1 } = readJson("siax.repo.v3.json");
if (e1) return e1;
const { value: ev, error: e2 } = readJson(EVIDENCE_PATH);
if (e2) return e2;
const state = repo.operationalState;
if (ev.operationalState !== state) {
return `siax.repo.json says "${state}" but ${EVIDENCE_PATH} was recorded for "${ev.operationalState}" -- re-probe (\`node scripts/siax-doctor.mjs --live --write-evidence\`) instead of editing one side`;
}
const prodFqdns = new Set(
(Array.isArray(repo.domains) ? repo.domains : [])
.filter((d) => d && d.env === "prod" && typeof d.fqdn === "string")
.map((d) => d.fqdn.toLowerCase()),
);
const prodProbes = (ev.probes ?? []).filter((p) => prodFqdns.has(String(p?.fqdn ?? "").toLowerCase()));
const up = prodProbes.filter((p) => p.ok === true);
const down = prodProbes.filter((p) => p.ok !== true);
const describe = (list) => list.map((p) => `${p.fqdn}=${p.httpStatus ?? "unreachable"}`).join(", ");
// Staleness only bites states that ASSERT the service is up. A repo that is
// honestly "down" does not become a liar by not being re-probed.
if (state === "running" || state === "degraded") {
const ageDays = (Date.now() - Date.parse(ev.checkedAt ?? "")) / 86_400_000;
if (Number.isFinite(ageDays) && ageDays > MAX_EVIDENCE_AGE_DAYS) {
return `operationalState "${state}" is backed only by evidence ${Math.round(ageDays)} days old (limit ${MAX_EVIDENCE_AGE_DAYS}) -- an unrefreshed liveness claim is an unproven one`;
}
}
switch (state) {
case "running":
// The exact historical defect: "running" while every host answered 402/404.
return down.length === 0
? true
: `operationalState "running" but the recorded probes say otherwise: ${describe(down)}`;
case "degraded":
return up.length > 0
? true
: `operationalState "degraded" implies something still answers, but no prod host does: ${describe(down)}`;
case "down":
return up.length === 0
? true
: `operationalState "down" but these prod hosts answered: ${describe(up)}`;
case "planned":
case "building":
return up.length === 0
? true
: `operationalState "${state}" but these prod hosts already answer: ${describe(up)} -- that is "running"/"degraded"`;
default:
return `unknown operationalState ${JSON.stringify(state)}`;
}
});
// -- SEC-06 / P0-13 : the security headers CHANGELOG.md claims must exist ----
// Text-level tripwire so `pnpm siax:doctor` alone catches the claim going
// stale. tests/security/security-headers.test.ts is the strict version (it
// evaluates the config and asserts each value).
check("SEC-06/P0-13", "next.config.mjs actually sets the security headers CHANGELOG.md claims", () => {
const { value: text, error } = readText("next.config.mjs");
if (error) return error;
if (!/async\s+headers\s*\(/.test(text)) return "next.config.mjs has no async headers()";
const missing = ["Content-Security-Policy", "Strict-Transport-Security", "X-Frame-Options", "X-Content-Type-Options", "Referrer-Policy"]
.filter((h) => !text.includes(h));
return missing.length === 0 ? true : `next.config.mjs never mentions: ${missing.join(", ")}`;
});
// -- AGENT-02/03 : prompts -------------------------------------------------
requireFile("AGENT-02", "prompts/000-INDEX.md exists", "prompts/000-INDEX.md");
// -- DX-03 : devcontainer --------------------------------------------------
requireFile("DX-03", ".devcontainer/devcontainer.json exists", ".devcontainer/devcontainer.json");
// -- P1-2 : ADRs -------------------------------------------------------------
requireFile("P1-2", "docs/architecture/decisions/ has at least one ADR", "docs/architecture/decisions/README.md");
// -- CI-14 : contract ---------------------------------------------------------
requireFile("A4/DOC", "contracts/openapi.yaml exists", "contracts/openapi.yaml");
// -- CD-04/OBS-04 : health/ready ---------------------------------------------
requireFile("P0-3/CD-04", "app/api/health/route.ts exists", "app/api/health/route.ts");
requireFile("P0-3/OBS-04", "app/api/ready/route.ts exists (readiness, not just liveness)", "app/api/ready/route.ts");
// =============================================================================
// LIVE MODE (opt-in) -- the half that needs the world
// =============================================================================
const REQUIRED_LIVE_HEADERS = [
"content-security-policy",
"strict-transport-security",
"x-frame-options",
"x-content-type-options",
"referrer-policy",
];
async function probe(url) {
const ac = new AbortController();
const timer = setTimeout(() => ac.abort(), PROBE_TIMEOUT_MS);
try {
const res = await fetch(url, { method: "GET", redirect: "manual", signal: ac.signal });
return { url, httpStatus: res.status, ok: res.status >= 200 && res.status < 400, headers: res.headers };
} catch (e) {
return { url, httpStatus: null, ok: false, headers: null, note: `unreachable: ${e.message}` };
} finally {
clearTimeout(timer);
}
}
function prodTargets() {
const { value: repo } = readJson("siax.repo.v3.json");
const { value: cfg } = readJson("siax.config.json");
const healthPath = cfg?.healthcheck?.path ?? "/";
return (Array.isArray(repo?.domains) ? repo.domains : [])
.filter((d) => d && d.env === "prod" && typeof d.fqdn === "string")
.map((d) => ({ fqdn: d.fqdn, env: d.env, url: `https://${d.fqdn}${healthPath}` }));
}
if (WANT_LIVE) {
liveChecks.push({
id: "GOV-06/live",
label: "live probe of every prod host agrees with siax.repo.json operationalState",
run: async () => {
const { value: repo, error } = readJson("siax.repo.v3.json");
if (error) return error;
const results = await Promise.all(prodTargets().map((t) => probe(t.url).then((r) => ({ ...t, ...r }))));
globalThis.__siaxDoctorLiveResults = results;
const up = results.filter((r) => r.ok);
const summary = results.map((r) => `${r.fqdn}=${r.httpStatus ?? "unreachable"}`).join(", ");
switch (repo.operationalState) {
case "running":
return up.length === results.length ? true : `declared "running", live probe says: ${summary}`;
case "degraded":
return up.length > 0 ? true : `declared "degraded", but nothing answered: ${summary}`;
case "down":
case "planned":
case "building":
return up.length === 0 ? true : `declared "${repo.operationalState}", but these answered: ${summary}`;
default:
return `unknown operationalState ${JSON.stringify(repo.operationalState)}`;
}
},
});
liveChecks.push({
id: "SEC-06/live",
label: "a reachable prod host really serves the security headers (P0-13)",
run: async () => {
const results = globalThis.__siaxDoctorLiveResults ?? [];
// Only responses THIS app actually produced are in scope. A 402/404 from
// a disabled Vercel deployment has no CSP either, but that is not a
// header defect this repo can fix -- it is a liveness fact, already
// carried by GOV-06/live above and recorded in
// evidence/operational-state.json `securityHeadersObserved`. Failing
// here as well would make this check permanently red for a reason no
// change to next.config.mjs could clear, which is how gates get ignored.
const serving = results.filter((r) => r.headers && r.ok);
if (serving.length === 0) return true;
const problems = [];
for (const r of serving) {
const missing = REQUIRED_LIVE_HEADERS.filter((h) => !r.headers.get(h));
if (missing.length) problems.push(`${r.fqdn} (HTTP ${r.httpStatus}) is missing: ${missing.join(", ")}`);
}
return problems.length === 0 ? true : problems.join("; ");
},
});
}
// =============================================================================
const results = [];
for (const { id, label, check: fn } of checks) {
const outcome = fn();
results.push({ id, label, mode: "offline", pass: outcome === true, detail: outcome === true ? null : outcome });
}
for (const { id, label, run } of liveChecks) {
const outcome = await run();
results.push({ id, label, mode: "live", pass: outcome === true, detail: outcome === true ? null : outcome });
}
if (WANT_LIVE && WANT_WRITE_EVIDENCE) {
const { value: repo } = readJson("siax.repo.v3.json");
const live = globalThis.__siaxDoctorLiveResults ?? [];
const existing = readJson(EVIDENCE_PATH).value ?? {};
const next = {
...existing,
control: "GOV-06",
operationalState: repo?.operationalState,
checkedAt: new Date().toISOString().replace(/\.\d{3}Z$/, "Z"),
method: `scripts/siax-doctor.mjs --live --write-evidence (fetch, ${PROBE_TIMEOUT_MS}ms timeout)`,
probes: live.map((r) => ({
fqdn: r.fqdn,
env: r.env,
url: r.url,
httpStatus: r.httpStatus,
ok: r.ok,
note: r.note ?? (r.headers ? `server: ${r.headers.get("server") ?? "unknown"}` : ""),
})),
securityHeadersObserved: Object.fromEntries(
REQUIRED_LIVE_HEADERS.map((h) => [h, live.find((r) => r.headers)?.headers?.get(h) ?? null]),
),
};
writeFileSync(file(EVIDENCE_PATH), `${JSON.stringify(next, null, 2)}\n`);
if (!WANT_JSON) console.log(`\nwrote ${EVIDENCE_PATH} from the live probe.\n`);
}
const failures = results.filter((r) => !r.pass);
if (WANT_JSON) {
console.log(JSON.stringify({ total: results.length, failed: failures.length, live: WANT_LIVE, results }, null, 2));
} else {
console.log("siax-doctor -- 0pt (repo-local subset, see scripts/siax-doctor.mjs header)");
console.log(WANT_LIVE ? "mode: offline + LIVE probe\n" : "mode: offline only (add --live to probe declared hosts)\n");
for (const r of results) {
const tag = r.mode === "live" ? "~" : " ";
if (r.pass) console.log(` ${tag}[PASS] ${r.id.padEnd(16)} ${r.label}`);
else console.log(` ${tag}[FAIL] ${r.id.padEnd(16)} ${r.label} -- ${r.detail}`);
}
console.log(`\n${results.length - failures.length}/${results.length} checks passed.`);
if (failures.length > 0) {
console.log(`${failures.length} check(s) failed -- see control-IDs above and cross-reference docs/compliance/BACKLOG.md.`);
}
}
if (failures.length > 0) process.exit(1);
+137
View File
@@ -0,0 +1,137 @@
#!/usr/bin/env -S node
/**
* scripts/vehiq-oltp-ingest/run.ts
* ===================================
* Thin CLI shim over src/ingestion/vehiq-oltp-ingest-runner.ts. All the
* actual logic (connectors, the runner call, the checkpoint mapping) is in
* that blocking-typechecked, tested `src/` module; this file only reads
* `process.env`, wires real implementations, and prints a summary.
*
* TWO MODES, PREVIEW IS THE DEFAULT — this is the activation gate:
*
* VEHIQ_INGEST_MODE=preview (default)
* One bounded read-only connect+discover+read per source, against
* whatever checkpoint already exists. NEVER commits a checkpoint, NEVER
* calls the sink, NEVER touches ClickHouse. Safe to run against real
* production at any time — this is what proves "zero batches, zero
* writes" without activating anything.
*
* VEHIQ_INGEST_MODE=run
* The real, scheduled path: P062 runner, durable checkpoints, writes
* landing in `opt_dataset_projection`. Requires DATABASE_URL (0pt's own
* metadata store, for durable checkpoints) and the OPT_ANALYTICS_
* CLICKHOUSE_* vars (the write target) to be set, or it refuses to run
* rather than silently falling back to in-memory checkpoints or a
* no-op sink — see docs/P070_VEHIQ_OLTP_EXTRACTOR.md "What activating
* this in production actually requires".
*
* NO VEHIQ_SOURCE_DATABASE_URL AT ALL -> exits 0 having done nothing. This
* is what makes the scheduled workflow (.gitea/workflows/vehiq-oltp-
* ingest.yml) safe to leave enabled with no secret configured: cron fires,
* the script logs "not configured", exits 0, no error, no alert.
*/
import {
VEHIQ_OLTP_SOURCES,
previewVehiqOltpSource,
runVehiqOltpIngestion,
} from "../../src/ingestion/vehiq-oltp-ingest-runner";
import { makePostgresQueryImpl } from "../../src/connectors/postgres-query-impl";
import { createIngestionRepository } from "../../src/repositories/ingestion-repository";
import { SchemaRegistry } from "../../src/schema/schema-registry";
import { LineageStore } from "../../src/lineage/lineage-store";
import { ClickHouseAnalyticsEngine } from "../../src/analytics/engine/clickhouse-engine";
import { projectionSink } from "../../src/ingestion/projection-sink";
import type { AnalyticsIdentity } from "../../src/analytics/engine/siax-analytics-engine";
const TENANT_ID = process.env.VEHIQ_INGEST_TENANT_ID ?? "t_vehiq";
const PROJECT_ID = process.env.VEHIQ_INGEST_PROJECT_ID ?? "vehiq";
async function main(): Promise<void> {
const sourceUrl = process.env.VEHIQ_SOURCE_DATABASE_URL;
if (!sourceUrl) {
console.log(
JSON.stringify({ status: "not_configured", detail: "VEHIQ_SOURCE_DATABASE_URL is unset — nothing to do" }),
);
return;
}
const mode = process.env.VEHIQ_INGEST_MODE === "run" ? "run" : "preview";
const { query, close } = makePostgresQueryImpl({
connectionString: sourceUrl,
ssl: process.env.VEHIQ_SOURCE_PGSSL === "require" ? "require" : false,
statementTimeoutMs: process.env.VEHIQ_SOURCE_STATEMENT_TIMEOUT_MS
? Number(process.env.VEHIQ_SOURCE_STATEMENT_TIMEOUT_MS)
: undefined,
});
try {
if (mode === "preview") {
const repo = createIngestionRepository(process.env.DATABASE_URL);
const results = [];
for (const source of VEHIQ_OLTP_SOURCES) {
results.push(
await previewVehiqOltpSource(source, query, repo, {
sourceId: "vehiq_oltp",
tenantId: TENANT_ID,
projectId: PROJECT_ID,
limit: 100,
}),
);
}
console.log(JSON.stringify({ status: "preview", mode, results }, null, 2));
return;
}
// mode === "run" — the activation-gated real path.
if (!process.env.DATABASE_URL) {
throw new Error(
"VEHIQ_INGEST_MODE=run requires DATABASE_URL (0pt's own metadata store, for durable checkpoints) — " +
"refusing to run with in-memory checkpoints, which would silently re-read every row on every scheduled invocation",
);
}
const chBaseUrl = process.env.OPT_ANALYTICS_CLICKHOUSE_BASE_URL;
if (!chBaseUrl) {
throw new Error(
"VEHIQ_INGEST_MODE=run requires OPT_ANALYTICS_CLICKHOUSE_BASE_URL — refusing to run with no write target",
);
}
const repo = createIngestionRepository(process.env.DATABASE_URL);
const schemas = new SchemaRegistry(repo);
const lineage = new LineageStore(repo);
const engine = new ClickHouseAnalyticsEngine({
baseUrl: chBaseUrl,
basicAuth: process.env.OPT_ANALYTICS_CLICKHOUSE_BASIC_AUTH,
database: process.env.OPT_ANALYTICS_CLICKHOUSE_DATABASE ?? "opt_analytics",
});
const identity: AnalyticsIdentity = {
tenant_id: TENANT_ID,
project_id: PROJECT_ID,
principal_id: "svc_vehiq_oltp_extractor",
};
const sink = projectionSink({
engine,
identity,
datasetIdFor: (partitionKey) => `vehiq.${partitionKey}`,
});
const summary = await runVehiqOltpIngestion({
repo,
schemas,
lineage,
sourceQuery: query,
oltpSink: sink,
dimensionSink: sink,
tenantId: TENANT_ID,
projectId: PROJECT_ID,
});
console.log(JSON.stringify({ status: "ran", mode, summary }, null, 2));
} finally {
await close();
}
}
main().catch((err) => {
console.error(JSON.stringify({ status: "error", detail: err instanceof Error ? err.message : String(err) }));
process.exitCode = 1;
});
+57
View File
@@ -0,0 +1,57 @@
/** Run with DOC0_CHECKOUT=/absolute/path "$DOC0_CHECKOUT/node_modules/.bin/tsx" scripts/verify-doc0-source-wire.ts.
* In-memory test only: proves the current DOC0 response shape, not production auth or storage. */
import { createHash } from 'node:crypto';
import { pathToFileURL } from 'node:url';
import { isAbsolute, resolve } from 'node:path';
import { execFileSync } from 'node:child_process';
import { Doc0SourceClient } from '../src/document-workbench/doc0-source-client';
import { structureDocumentBatch } from '../src/document-workbench/structure-document-batch';
import { exportValidatedBatch } from '../src/document-workbench/structured-batch';
async function main() {
const checkout = process.env.DOC0_CHECKOUT;
if (!checkout || !isAbsolute(checkout)) throw new Error('DOC0_CHECKOUT must name an absolute local checkout');
const load = (path: string) => import(pathToFileURL(resolve(checkout, path)).href);
const [{ buildServer }, { InMemoryDocumentStore }, { InMemoryDocumentVersionStore }, { InMemoryParseStore }, { segmentText }] = await Promise.all([
load('src/api/app.ts'), load('src/domains/document/store.ts'), load('src/domains/document-version/store.ts'),
load('src/domains/parse/store.ts'), load('src/parsing/segments.ts'),
]);
process.env.AUTH_ENFORCE = 'false';
Object.assign(process.env, { NODE_ENV: 'test' });
const documents = new InMemoryDocumentStore(), versions = new InMemoryDocumentVersionStore(), parses = new InMemoryParseStore();
const input = { tenantId: 't-test', projectId: 'p-test', actorId: 'actor', requestId: 'req', receipt: {
documentId: 'doc', versionId: 'v1', checksumSha256: createHash('sha256').update('fixture bytes').digest('hex'), pipelineStage: 'parsed',
} };
await documents.create({ tenantId: 't-test', projectId: 'p-test', documentId: 'doc', appId: 'test', displayName: 'fixture', documentType: 'unknown', classification: 'internal', tags: [], createdBy: 'actor' });
await versions.append({ tenantId: 't-test', documentId: 'doc', versionId: 'v1', versionNumber: 1, checksumSha256: input.receipt.checksumSha256, sizeBytes: 13, mimeType: 'text/plain', st0reArtifactRef: 'fixture', pipelineStage: 'parsed', extractedMetadata: {}, customMetadata: {}, createdBy: 'actor' });
const parts = segmentText('Acme AB\n\nInvoice 123');
await parses.appendDocumentText({ tenantId: 't-test', documentId: 'doc', versionId: 'v1', documentTextId: 'txt', parserRunId: 'run', charCount: parts.charCount, segmentCount: parts.segments.length, segmentsTruncated: parts.truncated, textSha256: parts.textSha256 });
await parses.appendTextSegments(parts.segments.map((s: object, i: number) => ({ ...s, tenantId: 't-test', documentId: 'doc', versionId: 'v1', documentTextId: 'txt', parserRunId: 'run', segmentId: `seg-${i}`, sourceVersionSha256: input.receipt.checksumSha256 })));
const app = buildServer({ documentStore: documents, versionStore: versions, parseStore: parses });
const fetchImpl: typeof fetch = async (url, init) => {
const target = new URL(String(url));
const out = await app.inject({ method: 'GET', url: target.pathname + target.search, headers: Object.fromEntries(new Headers(init?.headers).entries()) });
return new Response(out.body, { status: out.statusCode, headers: { 'content-type': 'application/json' } });
};
try {
const client = new Doc0SourceClient({ baseUrl: 'https://doc0.example', serviceToken: 'fixture', fetchImpl });
const result = await client.read(input);
if (result.sources.length !== 2 || result.sources[0]?.text !== 'Acme AB' || result.sources[1]?.text !== 'Invoice 123') throw new Error('DOC0 wire mismatch');
const fields = [{ name: 'value', type: 'string' as const, required: true }];
const records = result.sources.map((source) => ({ value: { value: source.text }, evidence: [{ field: 'value', sourceId: source.id, start: 0, end: source.text.length, transform: 'exact' }] }));
const request = {
context: { tenantId: 't-test', projectId: 'p-test', actorId: 'actor', capability: 'inf0.complete', correlationId: 'req' },
admission: { tenantId: 't-test', projectId: 'p-test', jobId: 'j', modelId: 'local_only', dataClass: 'high', maxInputTokens: 200, maxOutputTokens: 100, maximumCredits: 5 },
policy: { tenantId: 't-test', projectId: 'p-test', allowedModelIds: ['local_only'], allowedDataClasses: ['high'], destination: 'internal' as const, externalTransferAllowed: false, maxInputTokens: 200, maxOutputTokens: 100 },
reservation: { tenantId: 't-test', projectId: 'p-test', jobId: 'j', status: 'reserved' as const, credits: 5 },
};
const structured = await structureDocumentBatch({ request, fields, sources: result.sources, instructions: 'Extract values', countTokens: async () => 50,
gateway: { complete: async () => ({ status: 'model_stopped', content: JSON.stringify({ records }), modelExecuted: 'fixture', providerKind: 'local', promptTokens: 50, completionTokens: 20, totalTokens: 70 }) } });
if (structured.status !== 'mechanically_validated') throw new Error('model fixture evidence failed');
const exported = exportValidatedBatch({ format: 'json', fields, sources: result.sources, records: structured.records });
if (exported.status !== 'ready' || JSON.parse(exported.content).length !== 2) throw new Error('export fixture failed');
const revision = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: checkout, encoding: 'utf8' }).trim();
console.log(`DOC0 ${revision}: actual route through 0pt source and validated export; in-memory stores, test auth and model fixture`);
} finally { await app.close(); }
}
main().catch((error: unknown) => { console.error(error); process.exitCode = 1; });
+5
View File
@@ -0,0 +1,5 @@
<!-- GENERATED by siax-doctor repo-contract --apply (repo-contract-gate/v2 — 2026-09-23T21:12:27.075Z); template-fil, redigera via siax-templates - EJ handredigering. Saknas en rad här upptäcks det av RC-lagret. -->
#!/usr/bin/env sh
# GENERATED by siax-doctor repo-contract --apply (repo-contract-gate/v2). Stub = ej kopplad an; koppla till repots riktiga lokala verifiering via siax-templates.
echo "repo-contract-gate: scripts/verify-local ar en GENERATED stub - koppla den till repots riktiga verifiering via siax-templates" >&2
exit 0