feat(db): hierarchy record class schema + witnesses (contract 1, M4-1a) #1459

Merged
fred merged 10 commits from feat/m4-1a-hierarchy-schema into next 2026-08-28 00:42:36 +00:00
2 changed files with 653 additions and 176 deletions
Showing only changes of commit 8305d129a2 - Show all commits
@@ -265,6 +265,36 @@ function witnessSuite(getHandle: () => AnyDb): void {
);
});
it('scopes platform_projects and workspaces slugs per parent (refuse same-parent duplicate, accept cross-parent)', async () => {
// Dedicated parent estate so this test leaves estate2 a leaf (the §3.4
// cascade witness depends on that).
const estate3Id = randomUUID();
await db()
.insert(estates)
.values({ id: estate3Id, name: 'Estate 3', slug: `${T}-e3`, companyId });
// platform_projects: (estate_id, slug) unique.
await expectViolation(
db()
.insert(platformProjects)
.values({ id: randomUUID(), name: 'dup', slug: `${T}-pp1`, estateId }),
/duplicate key|unique/i,
);
const pp2Id = randomUUID();
await db()
.insert(platformProjects)
.values({ id: pp2Id, name: 'ok', slug: `${T}-pp1`, estateId: estate3Id });
// workspaces: (platform_project_id, slug) unique.
await expectViolation(
db()
.insert(workspaces)
.values({ id: randomUUID(), name: 'dup', slug: `${T}-ws1`, platformProjectId: ppId }),
/duplicate key|unique/i,
);
await db()
.insert(workspaces)
.values({ id: randomUUID(), name: 'ok', slug: `${T}-ws1`, platformProjectId: pp2Id });
});
// ── §6.2 column allowlist ──────────────────────────────────────────────────
it('column allowlist: each class table has exactly its declared columns (no payload, no owner_id)', async () => {
+623 -176
View File
@@ -2,46 +2,65 @@
* Hierarchy writer-coverage assertion — contract 1
* (docs/requirements/hierarchy-schema.md) §6.3(b).
*
* A static CI assertion over the Gateway and package production sources with
* three prongs, each bound to a closed, explicitly enumerated allowlist:
* A static CI assertion over all production sources (apps/, packages/,
* plugins/) with three prongs, each bound to a closed, explicitly enumerated
* allowlist:
*
* (i) Symbol prong — write references (insert/update/delete) to the
* class-table schema symbols occur only in allowlisted modules.
* Import aliasing is followed: `import { companies as c }` makes `c`
* a class-table symbol in that file.
* (ii) Literal prong — a class-table name inside a SQL string or tagged
* SQL template outside the allowlist fails. Schema definitions and
* generated migrations are excluded from this prong (per contract).
* (iii) Raw-execution prong — raw-SQL execution primitives (the ORM's
* raw/unsafe constructors, driver-level clients) outside the writer
* allowlist and the infrastructure register fail, regardless of SQL
* content. Direct database-driver imports count as raw-execution
* capability: they are what makes dynamically assembled SQL
* executable, and the import is statically detectable even when the
* SQL string is not.
* Schema symbols are tracked through named imports (aliased or not),
* namespace imports, and re-export conduits: any scanned module that
* re-exports the schema (or another conduit) is itself treated as a
* schema source, computed to a fixpoint.
* (ii) Literal prong — a class-table name inside a string or template
* span that also carries SQL context fails outside the allowlist.
* Spans are produced by a real lexer, so comments cannot hide code
* and strings cannot hide comments. Schema definitions and generated
* migrations are excluded from this prong only (per contract).
* (iii) Raw-execution prong — content-independent. A file is RAW-CAPABLE
* when it imports a database driver or the createDb/createPgliteDb
* factories (directly or via namespace). In a raw-capable file
* outside the allowlist and register, EVERY `.execute(`, `.query(`,
* and `.unsafe(` call fails, on any receiver, with any argument —
* there is no tagged-template exemption (§6.3(b): "regardless of what
* the SQL string contains or how it is constructed"). In files
* without detected capability (the DI residual), a conventional
* receiver backstop still fires on db/client-shaped receivers.
* `sql.raw` is tracked through import aliasing and namespaces.
*
* Runtime code-construction primitives (eval, new Function) and non-literal
* dynamic imports fail anywhere — allowlist and register included.
* dynamic imports fail anywhere — allowlist and register included. This is
* deliberately stricter than the contract's minimum: an unanalyzable import
* or constructed code defeats every static prong, so there is no enumerated
* disposition path for them.
*
* The writer allowlist names hierarchy command/repository modules ONLY. It is
* empty today: the hierarchy command family (M4-1b) has not landed, so no
* production module may write the class tables. The infrastructure register
* holds legitimate non-hierarchy raw execution (migration runner, storage
* adapters, health probes); registered modules are exempt from prong (iii)
* only — prongs (i) and (ii) apply to them with no exemption, and no
* registered module may appear on the writer allowlist. Neither list may take
* a generic raw-SQL helper, and an allowlisted module must not export a
* function that executes caller-supplied SQL (review-enforced, §5.1).
* holds legitimate non-hierarchy raw execution; registered modules are exempt
* from prong (iii) only — prongs (i) and (ii) apply to them with no
* exemption, and no registered module may appear on the writer allowlist.
*
* Register modules whose exports let a CALLER reach SQL execution carry their
* own closed importer enumerations (the laundering path §6.3(b) closes):
* the migration runner, migrate-tier, and the createDb/createPgliteDb
* factories. The remaining registered modules execute only fixed statements
* or collection-CRUD over the storage `(id, data)` shape — which cannot
* address class-table columns — and export no caller-supplied-SQL surface;
* that composition property is review-enforced (§5.1) on any change to them.
*
* A false positive is resolved in the same PR by adding the module to the one
* enumerated list its role permits — never by weakening the assertion.
*
* Test files (*.spec.*, *.test.*, __tests__/) are not scanned: they are not
* production mutation paths, and the contract's own §6 witnesses must write
* class tables directly to witness database constraints.
* class tables directly to witness database constraints. The known evasion
* forms from the M4-1a review are kept below as permanent controls: the
* analyzer must flag every one of them, so a regression that reopens an
* escape fails this suite.
*/
import { readdirSync, readFileSync, statSync, existsSync } from 'node:fs';
import { join, relative, resolve, sep } from 'node:path';
import { dirname, join, relative, resolve, sep } from 'node:path';
import { fileURLToPath } from 'node:url';
import { describe, expect, it } from 'vitest';
@@ -63,7 +82,8 @@ const CLASS_TABLES = [
* Writer allowlist (§6.3b): hierarchy command/repository modules only.
* EMPTY until the hierarchy command family lands (M4-1b). Adding a module
* here is a contract-conformance decision reviewed under §5.1 — the module
* must be part of the Gateway hierarchy command path.
* must be part of the Gateway hierarchy command path, and it must not export
* a function that executes caller-supplied SQL.
*/
const WRITER_ALLOWLIST: string[] = [];
@@ -76,9 +96,10 @@ const INFRA_REGISTER: string[] = [
'packages/db/src/client.ts', // connection factory (imports postgres driver)
'packages/db/src/client-pglite.ts', // PGlite factory (imports the pglite driver)
'packages/db/src/migrate.ts', // migration runner (hash-ledger DDL execution)
'packages/db/src/backlog.ts', // backlog domain module: parameterized sql`` over backlog tables only
'packages/memory/src/insights.ts', // analytics raw query over memory tables
'packages/storage/src/tier-detection.ts', // driver import for tier probing
'packages/storage/src/adapters/pglite.ts', // storage adapter (driver-level query)
'packages/storage/src/tier-detection.ts', // driver import for tier probing (fixed statements)
'packages/storage/src/adapters/pglite.ts', // storage adapter (collection CRUD over (id, data))
'packages/storage/src/adapters/postgres.ts', // storage adapter (extension bootstrap)
'packages/storage/src/migrate-tier.ts', // storage tier migration
'packages/storage/src/cli.ts', // storage CLI health probe
@@ -86,17 +107,13 @@ const INFRA_REGISTER: string[] = [
];
/**
* Closed importer enumerations for registered modules that EXPORT
* SQL-executing functions (the laundering path §6.3b closes). Import edges
* are checked re-export-aware — the db package barrel and literal dynamic
* `import('@mosaicstack/db')` are edges like any static import. The measured
* production importer set of the migration runner (contract 1 revision 9):
* the Gateway database module, the storage Postgres adapter, and two mosaic
* CLI commands routed through literal dynamic imports. The gateway
* schema-check module receives the runner's functions by parameter injection
* and has no import edge, so it is not enumerated. Being enumerated confers
* nothing else: importers stay subject to prongs (i)/(ii) and gain no writer
* standing.
* Closed importer enumerations for registered modules whose exports execute
* SQL or hand out an executing handle. An import edge is a static value
* import naming the symbol (from the module path or the package barrel), or
* a literal dynamic import of the package in a file using the symbol.
* `import type` is erased and is not an edge; parameter injection (the
* gateway schema-check module) has no edge. Being enumerated confers nothing
* else: importers stay subject to every prong and gain no writer standing.
*/
const MIGRATION_RUNNER_SYMBOLS = ['runMigrations', 'runPgliteMigrations', 'getMigrationStatus'];
const MIGRATION_RUNNER_IMPORTERS: string[] = [
@@ -115,9 +132,31 @@ const MIGRATE_TIER_IMPORTERS: string[] = [
'packages/storage/src/cli.ts', // storage CLI command surface
'packages/storage/src/index.ts', // package barrel re-export (public API)
];
/**
* Enumerated disposition for non-literal dynamic imports (§6.3b review F8):
* files here may use a computed import specifier; every other prong still
* applies to them in full. Each entry needs a justification.
*/
const DYNAMIC_IMPORT_REGISTER: string[] = [
'plugins/macp/src/index.ts', // loads the ACP runtime SDK from a configured sdkRoot; no db access
];
/** Importers of the db-handle factories (measured set; a new importer must be reviewed in). */
const DB_FACTORY_SYMBOLS = ['createDb', 'createPgliteDb'];
const DB_FACTORY_IMPORTERS: string[] = [
'apps/gateway/src/database/database.module.ts',
'packages/brain/src/cli.ts',
'packages/log/src/cli.ts',
'packages/memory/src/adapters/pgvector.ts',
'packages/memory/src/cli.ts',
'packages/mosaic/src/commands/fleet-backlog.ts',
'packages/storage/src/adapters/postgres.ts',
'packages/storage/src/cli.ts',
'packages/storage/src/migrate-tier.ts',
];
const SCAN_ROOTS = ['apps', 'packages'];
const SCAN_ROOTS = ['apps', 'packages', 'plugins'];
const EXTENSIONS = new Set(['.ts', '.tsx', '.mts', '.cts']);
const DRIVER_SPECIFIERS = ['postgres', 'pg', '@electric-sql/pglite'];
function isTestPath(rel: string): boolean {
return (
@@ -143,7 +182,7 @@ function collectSources(): string[] {
if (entry === 'node_modules' || entry === 'dist') continue;
walk(full);
} else if (EXTENSIONS.has(full.slice(full.lastIndexOf('.')))) {
const rel = relative(REPO_ROOT, full);
const rel = relative(REPO_ROOT, full).split(sep).join('/');
if (!isTestPath(rel)) files.push(rel);
}
}
@@ -154,36 +193,187 @@ function collectSources(): string[] {
return files.sort();
}
/** Strip line and block comments so commented-out code cannot trip prongs. */
function stripComments(src: string): string {
return src.replace(/\/\*[\s\S]*?\*\//g, ' ').replace(/(^|[^:])\/\/[^\n]*/g, '$1');
// ---------------------------------------------------------------------------
// Lexer: single pass producing (a) comment-free source with strings intact
// (for import/export and call-site regexes) and (b) the string/template text
// spans (for the literal prong). String-aware, so `//` inside a string is not
// a comment and a quote inside a comment does not open a string. Template
// expressions re-enter code mode (nesting supported); regex literals are
// recognized with the standard prev-token heuristic so their contents cannot
// open a phantom string.
// ---------------------------------------------------------------------------
interface Lexed {
code: string;
spans: string[];
}
/** Extract string and template literal spans (approximation, multi-line for templates). */
function stringSpans(src: string): string[] {
function lexSource(src: string): Lexed {
let code = '';
const spans: string[] = [];
const re = /`[^`]*`|'(?:[^'\\\n]|\\.)*'|"(?:[^"\\\n]|\\.)*"/gs;
for (const m of src.matchAll(re)) spans.push(m[0]);
return spans;
let i = 0;
const n = src.length;
// Template nesting: each entry is the accumulated text of one template.
const tplStack: string[] = [];
// Brace depth inside the current ${ } expression, one entry per nesting level.
const exprDepth: number[] = [];
let lastSig = ''; // last significant code char (regex-vs-division heuristic)
let lastWord = ''; // last identifier/keyword emitted to code
const emit = (ch: string): void => {
code += ch;
if (!/\s/.test(ch)) {
lastSig = ch;
if (/[A-Za-z0-9_$]/.test(ch)) lastWord += ch;
else lastWord = '';
}
};
const regexCanStart = (): boolean => {
if (lastSig === '' || '([{,;=:!&|?+-*%^<>~'.includes(lastSig)) return true;
return ['return', 'typeof', 'case', 'in', 'of', 'new', 'delete', 'void', 'do', 'else'].includes(
lastWord,
);
};
while (i < n) {
const ch = src[i]!;
const next = i + 1 < n ? src[i + 1]! : '';
if (tplStack.length > 0 && exprDepth.length < tplStack.length) {
// Inside template literal text.
const top = tplStack.length - 1;
if (ch === '\\') {
tplStack[top] += src.slice(i, i + 2);
i += 2;
continue;
}
if (ch === '`') {
spans.push(tplStack.pop()!);
emit('`');
i += 1;
continue;
}
if (ch === '$' && next === '{') {
exprDepth.push(0);
emit('$');
emit('{');
i += 2;
continue;
}
tplStack[top] += ch;
i += 1;
continue;
}
// Code mode (possibly inside a ${ } expression).
if (ch === '/' && next === '/') {
while (i < n && src[i] !== '\n') i += 1;
continue;
}
if (ch === '/' && next === '*') {
i += 2;
while (i < n && !(src[i] === '*' && src[i + 1] === '/')) i += 1;
i += 2;
emit(' ');
continue;
}
if (ch === "'" || ch === '"') {
let span = '';
i += 1;
while (i < n && src[i] !== ch) {
if (src[i] === '\\') {
span += src.slice(i, i + 2);
i += 2;
} else {
span += src[i];
i += 1;
}
}
i += 1;
spans.push(span);
// Keep quoted strings in code output so import specifiers stay parseable.
emit(ch);
code += span;
emit(ch);
continue;
}
if (ch === '`') {
tplStack.push('');
emit('`');
i += 1;
continue;
}
if (ch === '/' && regexCanStart()) {
// Regex literal: consume without interpreting quotes/backticks inside.
i += 1;
let inClass = false;
while (i < n) {
const rc = src[i]!;
if (rc === '\\') {
i += 2;
continue;
}
if (rc === '[') inClass = true;
else if (rc === ']') inClass = false;
else if (rc === '/' && !inClass) break;
else if (rc === '\n') break; // not a regex after all; bail safely
i += 1;
}
i += 1;
while (i < n && /[a-z]/.test(src[i]!)) i += 1; // flags
emit('/');
continue;
}
if (exprDepth.length > 0) {
const top = exprDepth.length - 1;
if (ch === '{') exprDepth[top] = exprDepth[top]! + 1;
if (ch === '}') {
if (exprDepth[top] === 0) {
exprDepth.pop();
emit('}');
i += 1;
continue;
}
exprDepth[top] = exprDepth[top]! - 1;
}
}
emit(ch);
i += 1;
}
while (tplStack.length > 0) spans.push(tplStack.pop()!);
return { code, spans };
}
/** Local names (including aliases) under which class-table symbols are imported. */
function classSymbolAliases(src: string): string[] {
const names = new Set<string>();
const importRe = /import\s*(?:type\s*)?\{([^}]*)\}\s*from\s*['"]([^'"]+)['"]/g;
for (const m of src.matchAll(importRe)) {
const specifier = m[2]!;
if (!/@mosaicstack\/db|\.\.?\/(?:.*\/)?(?:schema|index)(?:\.js)?$/.test(specifier)) continue;
for (const part of m[1]!.split(',')) {
const seg = part.trim().replace(/^type\s+/, '');
if (!seg) continue;
const asMatch = /^(\w+)\s+as\s+(\w+)$/.exec(seg);
const original = asMatch ? asMatch[1]! : seg;
const local = asMatch ? asMatch[2]! : seg;
if (CLASS_SYMBOLS.includes(original)) names.add(local);
}
// ---------------------------------------------------------------------------
// Import graph helpers
// ---------------------------------------------------------------------------
function resolveSpecifier(fromRel: string, spec: string, fileSet: Set<string>): string | null {
if (!spec.startsWith('.')) return null;
const base = join(dirname(fromRel), spec).split(sep).join('/');
const noJs = base.replace(/\.(js|mjs|cjs)$/, '');
for (const cand of [
base,
noJs,
`${noJs}.ts`,
`${noJs}.tsx`,
`${noJs}.mts`,
`${noJs}.cts`,
`${noJs}/index.ts`,
]) {
if (fileSet.has(cand)) return cand;
}
return [...names];
return null;
}
const IMPORT_RE =
/import\s*(type\s+)?(?:(\w+)\s*,\s*)?(?:\{([^}]*)\}|\*\s*as\s+(\w+)|(\w+))?\s*from\s*['"]([^'"]+)['"]/g;
const EXPORT_FROM_RE =
/export\s*(type\s+)?(?:\{([^}]*)\}|\*(?:\s*as\s+\w+)?)\s*from\s*['"]([^'"]+)['"]/g;
interface FileFacts {
rel: string;
code: string;
spans: string[];
}
interface Violation {
@@ -192,11 +382,252 @@ interface Violation {
detail: string;
}
describe('hierarchy writer coverage (contract 1 §6.3b)', () => {
const sources = collectSources();
/**
* Compute the fixpoint set of "schema sources": module paths from which a
* class-table symbol can be imported. Seeds: the schema module and the db
* package barrel (plus the bare '@mosaicstack/db' specifier, handled
* separately). Any scanned module that re-exports from a schema source
* (star, or a named list carrying a class symbol) — or imports class symbols
* and re-exports those local names — joins the set.
*/
function computeSchemaConduits(files: FileFacts[], fileSet: Set<string>): Set<string> {
const conduits = new Set<string>(['packages/db/src/schema.ts', 'packages/db/src/index.ts']);
const isSchemaSpec = (rel: string, spec: string): boolean => {
if (spec === '@mosaicstack/db') return true;
const resolved = resolveSpecifier(rel, spec, fileSet);
return resolved !== null && conduits.has(resolved);
};
let changed = true;
while (changed) {
changed = false;
for (const f of files) {
if (conduits.has(f.rel)) continue;
let isConduit = false;
for (const m of f.code.matchAll(EXPORT_FROM_RE)) {
if (m[1]) continue; // export type — erased
if (!isSchemaSpec(f.rel, m[3]!)) continue;
if (m[2] === undefined) {
isConduit = true; // export * from schema source
} else if (CLASS_SYMBOLS.some((s) => new RegExp(`\\b${s}\\b`).test(m[2]!))) {
isConduit = true;
}
}
if (!isConduit) {
const aliases = classAliases(f, conduits, fileSet);
if (aliases.named.length > 0) {
const exported = [...f.code.matchAll(/export\s*\{([^}]*)\}(?!\s*from)/g)]
.map((m) => m[1]!)
.join(',');
if (aliases.named.some((a) => new RegExp(`\\b${a}\\b`).test(exported))) isConduit = true;
}
}
if (isConduit) {
conduits.add(f.rel);
changed = true;
}
}
}
return conduits;
}
it('scans a non-empty production source set', () => {
expect(sources.length).toBeGreaterThan(100);
interface ClassAliases {
named: string[]; // local identifiers bound to class-table symbols
namespaces: string[]; // namespace identifiers over a schema source
}
function classAliases(f: FileFacts, conduits: Set<string>, fileSet: Set<string>): ClassAliases {
const named = new Set<string>();
const namespaces = new Set<string>();
for (const m of f.code.matchAll(IMPORT_RE)) {
const [, typeOnly, , namedList, nsName, , spec] = m;
if (typeOnly) continue;
const fromSchema =
spec === '@mosaicstack/db' ||
(() => {
const r = resolveSpecifier(f.rel, spec!, fileSet);
return r !== null && conduits.has(r);
})();
if (!fromSchema) continue;
if (nsName) namespaces.add(nsName);
if (namedList) {
for (const part of namedList.split(',')) {
const seg = part.trim();
if (!seg || seg.startsWith('type ')) continue;
const asMatch = /^(\w+)\s+as\s+(\w+)$/.exec(seg);
const original = asMatch ? asMatch[1]! : seg;
const local = asMatch ? asMatch[2]! : seg;
if (CLASS_SYMBOLS.includes(original)) named.add(local);
}
}
}
return { named: [...named], namespaces: [...namespaces] };
}
/** Value-import edge naming one of `symbols` (named import from anywhere, or literal dynamic package import while using the symbol). */
function hasSymbolImportEdge(code: string, symbols: string[], packageName: string): boolean {
const staticEdge = new RegExp(
`import\\s*(?!type\\b)(?:\\w+\\s*,\\s*)?\\{[^}]*\\b(${symbols.join('|')})\\b[^}]*\\}\\s*from\\s*['"][^'"]+['"]`,
).test(code);
const usesSymbol = new RegExp(`\\b(${symbols.join('|')})\\b`).test(code);
const dynamicEdge =
usesSymbol &&
new RegExp(`import\\s*\\(\\s*['"]${packageName.replace('/', '\\/')}['"]\\s*\\)`).test(code);
return staticEdge || dynamicEdge;
}
// ---------------------------------------------------------------------------
// The analyzer — pure over (rel, source) so the evasion controls below can
// feed synthetic files through the exact production logic.
// ---------------------------------------------------------------------------
function analyzeFile(f: FileFacts, conduits: Set<string>, fileSet: Set<string>): Violation[] {
const violations: Violation[] = [];
const rel = f.rel;
const code = f.code;
const inAllowlist = WRITER_ALLOWLIST.includes(rel);
const inRegister = INFRA_REGISTER.includes(rel);
const isSchemaDefinition = rel === 'packages/db/src/schema.ts';
// Runtime code construction: fails anywhere.
if (/\beval\s*\(|\bnew\s+Function\s*\(/.test(code)) {
violations.push({ file: rel, prong: 'code-construction', detail: 'eval/new Function' });
}
// Non-literal dynamic import: makes the import graph unanalyzable.
if (!DYNAMIC_IMPORT_REGISTER.includes(rel) && /\bimport\s*\(\s*(?!['"])/.test(code)) {
violations.push({ file: rel, prong: 'dynamic-import', detail: 'non-literal import()' });
}
const aliases = classAliases(f, conduits, fileSet);
// Prong (i): schema-symbol writes — alias-, namespace-, and conduit-aware.
if (!inAllowlist) {
const targets: string[] = [...aliases.named];
for (const ns of aliases.namespaces) {
for (const s of CLASS_SYMBOLS) targets.push(`${ns}\\.${s}`);
}
if (targets.length > 0) {
const writeRe = new RegExp(
`\\.(insert|update|delete)\\s*\\(\\s*(${targets.join('|')})\\b`,
'g',
);
for (const m of code.matchAll(writeRe)) {
violations.push({
file: rel,
prong: 'i-symbol',
detail: `.${m[1]}(${m[2]}) outside the writer allowlist`,
});
}
}
}
// Prong (ii): class-table names in string/template spans with SQL context.
// The SQL keyword must be ADJACENT to the table name (optionally quoted):
// co-residence anywhere in one span over-matches prose (English "from" plus
// "pnpm workspaces" in an embedded doc string is not SQL).
if (!inAllowlist && !isSchemaDefinition) {
const sqlAdjacentRe = new RegExp(
`\\b(insert\\s+into|update|delete\\s+from|from|join|truncate(\\s+table)?|alter\\s+table|drop\\s+table|references|into)\\s+["'\`]?(${CLASS_TABLES.join('|')})\\b`,
'i',
);
for (const span of f.spans) {
if (sqlAdjacentRe.test(span)) {
violations.push({
file: rel,
prong: 'ii-literal',
detail: `class-table name in SQL context: ${span.slice(0, 80)}`,
});
}
}
}
// Prong (iii): content-independent raw execution.
if (!inAllowlist && !inRegister) {
const driverImport = new RegExp(
`(from\\s*|import\\s*\\(\\s*)['"](${DRIVER_SPECIFIERS.map((s) => s.replace('/', '\\/')).join('|')})['"]`,
).test(code);
const factoryImport =
hasSymbolImportEdge(code, DB_FACTORY_SYMBOLS, '@mosaicstack/db') ||
(aliases.namespaces.length > 0 &&
new RegExp(`\\b(${aliases.namespaces.join('|')})\\.(createDb|createPgliteDb)\\b`).test(
code,
));
if (driverImport) {
violations.push({ file: rel, prong: 'iii-raw-execution', detail: 'direct driver import' });
// A driver client exposes query/execute/unsafe as raw primitives:
// flag them all on any receiver, any argument.
for (const m of code.matchAll(/\.(execute|query|unsafe)\s*\(/g)) {
violations.push({
file: rel,
prong: 'iii-raw-execution',
detail: `.${m[1]}() in a driver-importing module`,
});
}
} else if (factoryImport) {
// A drizzle handle's raw primitive is .execute (its .query namespace is
// the relational builder, and unrelated .query() methods are common),
// so factory capability flags execute/unsafe on any receiver and query
// only on the db/client-shaped receiver backstop below.
for (const m of code.matchAll(/\.(execute|unsafe)\s*\(/g)) {
violations.push({
file: rel,
prong: 'iii-raw-execution',
detail: `.${m[1]}() in a factory-importing module`,
});
}
}
if (!driverImport) {
// DI residual backstop: db/client-shaped receivers fire regardless of
// detected capability (a handle can arrive by injection).
for (const m of code.matchAll(
/\b(?:db|database|client|conn|connection|pool|pg|pglite|tx|trx)\.(execute|query|unsafe)\s*\(/g,
)) {
violations.push({
file: rel,
prong: 'iii-raw-execution',
detail: `.${m[1]}() on a db-shaped receiver`,
});
}
}
// sql.raw through aliases and namespaces (drizzle-orm and the db barrel).
const sqlAliases = new Set<string>();
for (const m of code.matchAll(IMPORT_RE)) {
const [, typeOnly, , namedList, nsName, , spec] = m;
if (typeOnly) continue;
if (!/^drizzle-orm|^@mosaicstack\/db$/.test(spec!)) continue;
if (namedList) {
for (const part of namedList.split(',')) {
const seg = part.trim();
const asMatch = /^sql\s+as\s+(\w+)$/.exec(seg);
if (seg === 'sql') sqlAliases.add('sql');
else if (asMatch) sqlAliases.add(asMatch[1]!);
}
}
if (nsName) sqlAliases.add(`${nsName}\\.sql`);
}
if (sqlAliases.size > 0) {
const rawRe = new RegExp(`\\b(${[...sqlAliases].join('|')})\\.raw\\s*\\(`);
if (rawRe.test(code)) {
violations.push({ file: rel, prong: 'iii-raw-execution', detail: 'sql.raw()' });
}
}
}
return violations;
}
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
describe('hierarchy writer coverage (contract 1 §6.3b)', () => {
const sourceRels = collectSources();
const fileSet = new Set(sourceRels);
const files: FileFacts[] = sourceRels.map((rel) => {
const { code, spans } = lexSource(readFileSync(join(REPO_ROOT, rel), 'utf8'));
return { rel, code, spans };
});
const conduits = computeSchemaConduits(files, fileSet);
it('scans a non-empty production source set including plugins', () => {
expect(files.length).toBeGreaterThan(100);
expect(sourceRels.some((r) => r.startsWith('plugins/'))).toBe(true);
});
it('enumerated modules exist on disk (no stale allowlist/register entries)', () => {
@@ -205,6 +636,8 @@ describe('hierarchy writer coverage (contract 1 §6.3b)', () => {
...INFRA_REGISTER,
...MIGRATION_RUNNER_IMPORTERS,
...MIGRATE_TIER_IMPORTERS,
...DB_FACTORY_IMPORTERS,
...DYNAMIC_IMPORT_REGISTER,
]) {
expect(existsSync(join(REPO_ROOT, p)), `enumerated module missing: ${p}`).toBe(true);
}
@@ -216,87 +649,8 @@ describe('hierarchy writer coverage (contract 1 §6.3b)', () => {
}
});
it('three-prong writer coverage holds', () => {
const violations: Violation[] = [];
const allow = new Set(WRITER_ALLOWLIST);
const register = new Set(INFRA_REGISTER);
for (const rel of sources) {
const raw = readFileSync(join(REPO_ROOT, rel), 'utf8');
const src = stripComments(raw);
const inAllowlist = allow.has(rel);
const inRegister = register.has(rel);
const isSchemaDefinition = rel === 'packages/db/src/schema.ts';
// Runtime code construction: fails anywhere.
if (/\beval\s*\(|\bnew\s+Function\s*\(/.test(src)) {
violations.push({ file: rel, prong: 'code-construction', detail: 'eval/new Function' });
}
// Non-literal dynamic import: makes the import graph unanalyzable.
if (/\bimport\s*\(\s*(?!['"`])/.test(src)) {
violations.push({ file: rel, prong: 'dynamic-import', detail: 'non-literal import()' });
}
// Prong (i): schema-symbol writes — alias-aware.
if (!inAllowlist) {
const aliases = classSymbolAliases(src);
if (aliases.length > 0) {
const writeRe = new RegExp(
`\\.(insert|update|delete)\\s*\\(\\s*(${aliases.join('|')})\\b`,
'g',
);
for (const m of src.matchAll(writeRe)) {
violations.push({
file: rel,
prong: 'i-symbol',
detail: `.${m[1]}(${m[2]}) outside the writer allowlist`,
});
}
}
}
// Prong (ii): class-table names in SQL strings/templates.
// Schema definitions and generated migrations are excluded (contract);
// migrations live outside the scanned source roots already.
if (!inAllowlist && !isSchemaDefinition) {
const tableRe = new RegExp(`\\b(${CLASS_TABLES.join('|')})\\b`);
const sqlContextRe =
/\b(select|insert\s+into|update|delete\s+from|join|truncate|alter\s+table|drop\s+table|references)\b/i;
for (const span of stringSpans(src)) {
if (tableRe.test(span) && sqlContextRe.test(span)) {
violations.push({
file: rel,
prong: 'ii-literal',
detail: `class-table name in SQL context: ${span.slice(0, 80)}`,
});
}
}
}
// Prong (iii): raw-execution primitives outside allowlist register.
if (!inAllowlist && !inRegister) {
const rawPatterns: Array<[RegExp, string]> = [
[/\bsql\.raw\s*\(/, 'sql.raw()'],
[/\.unsafe\s*\(/, '.unsafe()'],
// `.execute(sql\`...\`)` is exempt: the tagged template keeps the
// SQL literal in source, where prong (ii) scans it. The flagged
// forms are the ones whose SQL content is not statically visible
// at the call site: `.execute(variable)` and `.execute("string")`.
[/\b(?:db|database|tx|trx)\.execute\s*\((?!\s*sql`)/, 'raw db.execute()'],
[/from\s*['"](?:postgres|pg|@electric-sql\/pglite)['"]/, 'direct driver import'],
[
/\bimport\s*\(\s*['"](?:postgres|pg|@electric-sql\/pglite)['"]\s*\)/,
'dynamic driver import',
],
];
for (const [re, label] of rawPatterns) {
if (re.test(src)) {
violations.push({ file: rel, prong: 'iii-raw-execution', detail: label });
}
}
}
}
it('three-prong writer coverage holds over the production tree', () => {
const violations = files.flatMap((f) => analyzeFile(f, conduits, fileSet));
expect(
violations,
violations.map((v) => `[prong ${v.prong}] ${v.file}: ${v.detail}`).join('\n'),
@@ -305,22 +659,14 @@ describe('hierarchy writer coverage (contract 1 §6.3b)', () => {
it('migration-runner import edges are exactly the closed importer enumeration', () => {
const offenders: string[] = [];
for (const rel of sources) {
if (rel.startsWith('packages/db/src/')) continue; // the runner's own package
const src = stripComments(readFileSync(join(REPO_ROOT, rel), 'utf8'));
const symbolRe = new RegExp(`\\b(${MIGRATION_RUNNER_SYMBOLS.join('|')})\\b`);
if (!symbolRe.test(src)) continue;
// An import edge is a static value import from the db package (or its
// migrate module) naming a runner symbol, or a literal dynamic
// import('@mosaicstack/db') in a file that uses a runner symbol.
// `import type` is erased at runtime and is not an edge; parameter
// injection (the gateway schema-check module) has no edge.
const staticEdge = new RegExp(
`import\\s*(?!type\\b)\\{[^}]*\\b(${MIGRATION_RUNNER_SYMBOLS.join('|')})\\b[^}]*\\}\\s*from\\s*['"](@mosaicstack/db|[^'"]*migrate(\\.js)?)['"]`,
).test(src);
const dynamicEdge = /import\s*\(\s*['"]@mosaicstack\/db['"]\s*\)/.test(src);
if ((staticEdge || dynamicEdge) && !MIGRATION_RUNNER_IMPORTERS.includes(rel)) {
offenders.push(rel);
for (const f of files) {
if (f.rel.startsWith('packages/db/src/')) continue; // the runner's own package
if (!new RegExp(`\\b(${MIGRATION_RUNNER_SYMBOLS.join('|')})\\b`).test(f.code)) continue;
if (
hasSymbolImportEdge(f.code, MIGRATION_RUNNER_SYMBOLS, '@mosaicstack/db') &&
!MIGRATION_RUNNER_IMPORTERS.includes(f.rel)
) {
offenders.push(f.rel);
}
}
expect(offenders, `unenumerated migration-runner importers:\n${offenders.join('\n')}`).toEqual(
@@ -330,20 +676,121 @@ describe('hierarchy writer coverage (contract 1 §6.3b)', () => {
it('migrate-tier import edges are exactly the closed importer enumeration', () => {
const offenders: string[] = [];
for (const rel of sources) {
if (rel === 'packages/storage/src/migrate-tier.ts') continue;
const src = stripComments(readFileSync(join(REPO_ROOT, rel), 'utf8'));
// Edge = direct module-path import, or a value import of a migrate-tier
// symbol from the storage package barrel (the barrel is itself
// enumerated as a re-exporter, so barrel consumers must not escape).
for (const f of files) {
if (f.rel === 'packages/storage/src/migrate-tier.ts') continue;
const pathEdge =
/from\s*['"][^'"]*migrate-tier(\.js)?['"]/.test(src) ||
/import\s*\(\s*['"][^'"]*migrate-tier(\.js)?['"]\s*\)/.test(src);
const barrelEdge = new RegExp(
`import\\s*(?!type\\b)\\{[^}]*\\b(${MIGRATE_TIER_SYMBOLS.join('|')})\\b[^}]*\\}\\s*from\\s*['"]@mosaicstack/storage['"]`,
).test(src);
if ((pathEdge || barrelEdge) && !MIGRATE_TIER_IMPORTERS.includes(rel)) offenders.push(rel);
/from\s*['"][^'"]*migrate-tier(\.js)?['"]/.test(f.code) ||
/import\s*\(\s*['"][^'"]*migrate-tier(\.js)?['"]\s*\)/.test(f.code);
const symbolEdge =
new RegExp(`\\b(${MIGRATE_TIER_SYMBOLS.join('|')})\\b`).test(f.code) &&
hasSymbolImportEdge(f.code, MIGRATE_TIER_SYMBOLS, '@mosaicstack/storage');
if ((pathEdge || symbolEdge) && !MIGRATE_TIER_IMPORTERS.includes(f.rel)) {
offenders.push(f.rel);
}
}
expect(offenders, `unenumerated migrate-tier importers:\n${offenders.join('\n')}`).toEqual([]);
});
it('db-factory (createDb/createPgliteDb) import edges are exactly the closed importer enumeration', () => {
const offenders: string[] = [];
for (const f of files) {
if (f.rel.startsWith('packages/db/src/')) continue; // the factories' own package
if (!new RegExp(`\\b(${DB_FACTORY_SYMBOLS.join('|')})\\b`).test(f.code)) continue;
if (
hasSymbolImportEdge(f.code, DB_FACTORY_SYMBOLS, '@mosaicstack/db') &&
!DB_FACTORY_IMPORTERS.includes(f.rel)
) {
offenders.push(f.rel);
}
}
expect(offenders, `unenumerated db-factory importers:\n${offenders.join('\n')}`).toEqual([]);
});
// -------------------------------------------------------------------------
// Permanent evasion controls: every known escape from the M4-1a review must
// be flagged by the analyzer, and the two legitimate controls must pass.
// Synthetic files run through the exact production analyzer.
// -------------------------------------------------------------------------
const EVASIONS: Array<{ name: string; src: string }> = [
{
name: 'E1 namespace import write',
src: `import * as dbSchema from '@mosaicstack/db';\nimport { db } from './x.js';\nexport async function f() { await db.insert(dbSchema.companies).values({}); }`,
},
{
name: 'E2 re-export laundering (conduit consumer)',
src: `import { companies } from './evasion-barrel.js';\nimport { db } from './x.js';\nexport async function f() { await db.insert(companies).values({}); }`,
},
{
name: 'E3 renamed createDb handle',
src: `import { createDb } from '@mosaicstack/db';\nexport async function f(t: string) { const d = createDb('u'); await d.execute('DELETE FROM ' + t); }`,
},
{
name: 'E4 aliased sql.raw',
src: `import { sql as q } from 'drizzle-orm';\nimport { db } from './x.js';\nexport async function f(t: string) { await db.execute(q.raw('TRUNCATE ' + t)); }`,
},
{
name: 'E5 interpolated tagged template on db receiver',
src: `import { sql } from 'drizzle-orm';\nimport { db } from './x.js';\nexport async function f() { const tbl = 'hierarchy_grants'; await db.execute(sql\`DELETE FROM \${tbl}\`); }`,
},
{
name: 'E6 driver client via import',
src: `import postgres from 'postgres';\nexport async function f(t: string) { const c = postgres('u'); await c.unsafe('TRUNCATE ' + t); }`,
},
{
name: 'E7 DI-shaped driver client query',
src: `export class R { constructor(private client: { query(s: string): Promise<unknown> }) {}\n async f(t: string) { await this.client.query('TRUNCATE ' + t); } }`,
},
{
name: 'E8 string containing // does not hide code',
src: `import { companies } from '@mosaicstack/db';\nimport { db } from './x.js';\nexport async function f() { const x = 'oops//'; await db.insert(companies).values({}); }`,
},
{
name: 'C-literal SQL string with class table',
src: `export const q = 'DELETE FROM hierarchy_grants WHERE role = $1';`,
},
{
name: 'C-eval',
src: `export function f(s: string) { return eval(s); }`,
},
];
const CLEAN_CONTROLS: Array<{ name: string; src: string }> = [
{
name: 'clean: schema read via select',
src: `import { companies } from '@mosaicstack/db';\nimport { db } from './x.js';\nexport async function f() { return db.select().from(companies); }`,
},
{
name: 'clean: comment mentioning a table is not SQL',
src: `// syncs hierarchy_grants downstream\nexport const n = 1;`,
},
];
it('analyzer flags every known evasion form', () => {
// A synthetic conduit so E2 exercises the fixpoint through a real re-export.
const barrel: FileFacts = {
rel: 'packages/db/src/evasion-barrel.ts',
...lexSource(`export { companies } from '@mosaicstack/db';`),
};
for (const e of EVASIONS) {
const rel = 'packages/db/src/evasion-sample.ts';
const synthetic: FileFacts = { rel, ...lexSource(e.src) };
const synthSet = new Set([...fileSet, rel, barrel.rel]);
const synthConduits = computeSchemaConduits([...files, barrel, synthetic], synthSet);
const v = analyzeFile(synthetic, synthConduits, synthSet);
expect(v.length, `evasion not caught: ${e.name}`).toBeGreaterThan(0);
}
});
it('analyzer passes legitimate non-writer code (no false positives on controls)', () => {
for (const c of CLEAN_CONTROLS) {
const rel = 'packages/db/src/clean-sample.ts';
const synthetic: FileFacts = { rel, ...lexSource(c.src) };
const synthSet = new Set([...fileSet, rel]);
const synthConduits = computeSchemaConduits([...files, synthetic], synthSet);
const v = analyzeFile(synthetic, synthConduits, synthSet);
expect(
v,
`false positive on ${c.name}: ${v.map((x) => `${x.prong}:${x.detail}`).join('; ')}`,
).toEqual([]);
}
});
});