fix(db): close round-4 review evasions in writer-coverage assertion
ci/woodpecker/pr/ci Pipeline was successful

Default-export slot treated as an export name at both ends (helper-side
export-default passes, consumer-side bare-default-import resolution);
namespace-member write targets match the source's exported names via a
per-file memberSyms union; factory conduits carry a name map computed to
a fixpoint so renames propagate hop by hop (export-from, export-of-local,
export default); tracked dynamic imports of capability conduits confer
capability; factory destructure-rename from a schema namespace; static
module-namespace createRequire destructure; spread-argument writes; DCL
keywords (GRANT/REVOKE ON, LOCK TABLE); bracketed conventional DI
receiver. Permanent controls E40-E53.
This commit is contained in:
fred
2026-08-27 17:28:57 -05:00
parent f44db58b0d
commit 3dcfb264c5
+277 -61
View File
@@ -15,11 +15,17 @@
* (`export { companies as c } from`), an export of a locally bound * (`export { companies as c } from`), an export of a locally bound
* alias (`import { companies }; export { companies as co }`), and a * alias (`import { companies }; export { companies as co }`), and a
* binding derived from a namespace (`export const co = ns.companies`) * binding derived from a namespace (`export const co = ns.companies`)
* all propagate symbol identity to the consumer. Destructuring a * all propagate symbol identity to the consumer. The DEFAULT-export
* NON-class property from a schema namespace over-approximates: the * slot is an export name like any other (`export { companies as
* binding is treated as a schema namespace itself (fail-closed), and * default } from`, `export default companies`), so a bare default
* single-alias derivation from a namespace (`const t = ns.companies`) * import of a conduit resolves against the map too; namespace-member
* is folded in to a fixed depth. * write targets match the source's EXPORTED names — renames included
* (`M.co` after `export { companies as co } from`) — not just the
* original class symbols. Destructuring a NON-class property from a
* schema namespace over-approximates: the binding is treated as a
* schema namespace itself (fail-closed), and single-alias derivation
* from a namespace (`const t = ns.companies`) is folded in to a
* fixed depth.
* (ii) Literal prong — a class-table name inside a string or template * (ii) Literal prong — a class-table name inside a string or template
* span that also carries SQL context fails outside the allowlist. * span that also carries SQL context fails outside the allowlist.
* Spans are produced by a real lexer, so comments cannot hide code * Spans are produced by a real lexer, so comments cannot hide code
@@ -27,7 +33,13 @@
* migrations are excluded from this prong only (per contract). * migrations are excluded from this prong only (per contract).
* (iii) Raw-execution prong — content-independent. A file is RAW-CAPABLE * (iii) Raw-execution prong — content-independent. A file is RAW-CAPABLE
* when it imports a database driver or the createDb/createPgliteDb * when it imports a database driver or the createDb/createPgliteDb
* factories (directly or via namespace). In a raw-capable file * factories directly, via namespace, by destructuring a factory
* symbol out of a schema namespace, or from a capability CONDUIT.
* Conduits carry their own exported-name maps computed to a
* fixpoint, so a factory renamed at any hop (`export { createDb as
* mk } from`, `export default createDb`) still marks every module
* down the chain, and a tracked dynamic import of a conduit is an
* import edge like a static one. In a raw-capable file
* outside the allowlist and register, EVERY `.execute(`, `.query(`, * outside the allowlist and register, EVERY `.execute(`, `.query(`,
* and `.unsafe(` call fails, on any receiver, with any argument — * and `.unsafe(` call fails, on any receiver, with any argument —
* there is no tagged-template exemption (§6.3(b): "regardless of what * there is no tagged-template exemption (§6.3(b): "regardless of what
@@ -43,7 +55,9 @@
* Unanalyzable import routes fail everywhere except their own enumerated * Unanalyzable import routes fail everywhere except their own enumerated
* disposition registers below: a dynamic import whose specifier is not a * disposition registers below: a dynamic import whose specifier is not a
* single string literal, and createRequire (tracked through import aliasing * single string literal, and createRequire (tracked through import aliasing
* — `createRequire as x` — and destructuring from the module namespace; a * — `createRequire as x` — and destructuring from the module namespace,
* whether that namespace came from a static `import * as` or a tracked
* dynamic import; a
* route that hides the NAME entirely, e.g. re-exporting createRequire from a * route that hides the NAME entirely, e.g. re-exporting createRequire from a
* helper, falls under the value-flow residual). A LITERAL dynamic import is * helper, falls under the value-flow residual). A LITERAL dynamic import is
* an import edge like any static import (contract rev 9): its bindings * an import edge like any static import (contract rev 9): its bindings
@@ -543,6 +557,21 @@ function computeSchemaConduits(files: FileFacts[], fileSet: Set<string>): Schema
mine.named.add(m[1]!); mine.named.add(m[1]!);
} }
} }
// The default slot is an export name like any other:
// `export default companies;` / `export default mod.companies;`
for (const m of f.code.matchAll(/export\s+default\s+(\w+)\s*;/g)) {
if (aliases.named.includes(m[1]!)) mine.named.add('default');
if (aliases.namespaces.includes(m[1]!)) mine.ns.add('default');
}
for (const ns of aliases.namespaces) {
if (
new RegExp(
`export\\s+default\\s+${ns}(?:\\s*\\.\\s*\\w+)*\\s*\\.\\s*(?:${CLASS_SYMBOLS.join('|')})\\b`,
).test(f.code)
) {
mine.named.add('default');
}
}
} }
if (mine.named.size + mine.ns.size > before) { if (mine.named.size + mine.ns.size > before) {
conduits.set(f.rel, mine); conduits.set(f.rel, mine);
@@ -556,6 +585,10 @@ function computeSchemaConduits(files: FileFacts[], fileSet: Set<string>): Schema
interface ClassAliases { interface ClassAliases {
named: string[]; // local identifiers bound to class-table symbols named: string[]; // local identifiers bound to class-table symbols
namespaces: string[]; // namespace identifiers over a schema source namespaces: string[]; // namespace identifiers over a schema source
// Member names under which a class symbol is reachable on SOME imported
// schema source (class symbols plus every renamed conduit export the file
// imports) — the alternation for namespace-member write targets.
memberSyms: Set<string>;
} }
/** /**
@@ -605,13 +638,21 @@ function destructureBindings(
function classAliases(f: FileFacts, conduits: SchemaConduits, fileSet: Set<string>): ClassAliases { function classAliases(f: FileFacts, conduits: SchemaConduits, fileSet: Set<string>): ClassAliases {
const named = new Set<string>(); const named = new Set<string>();
const namespaces = new Set<string>(); const namespaces = new Set<string>();
const memberSyms = new Set<string>(CLASS_SYMBOLS);
for (const m of f.code.matchAll(IMPORT_RE)) { for (const m of f.code.matchAll(IMPORT_RE)) {
const [, typeOnly, , namedList, nsName, , spec] = m; const [, typeOnly, defaultWith, namedList, nsName, defaultBare, spec] = m;
if (typeOnly) continue; if (typeOnly) continue;
const src = schemaExportsOf(f.rel, spec!, conduits, fileSet); const src = schemaExportsOf(f.rel, spec!, conduits, fileSet);
if (src === null || (src.named.size === 0 && src.ns.size === 0)) continue; if (src === null || (src.named.size === 0 && src.ns.size === 0)) continue;
for (const n of src.named) memberSyms.add(n);
if (nsName) namespaces.add(nsName); if (nsName) namespaces.add(nsName);
if (namedList) importBindings(namedList, src, named, namespaces); if (namedList) importBindings(namedList, src, named, namespaces);
// A default import binds whatever the source exports as `default`.
const dflt = defaultWith ?? defaultBare;
if (dflt) {
if (src.named.has('default')) named.add(dflt);
if (src.ns.has('default')) namespaces.add(dflt);
}
} }
// Literal dynamic imports of a schema source are import edges like any // Literal dynamic imports of a schema source are import edges like any
// other (contract rev 9): both binding shapes feed prong (i). // other (contract rev 9): both binding shapes feed prong (i).
@@ -621,6 +662,7 @@ function classAliases(f: FileFacts, conduits: SchemaConduits, fileSet: Set<strin
const [, pattern, nsName, , spec] = m; const [, pattern, nsName, , spec] = m;
const src = schemaExportsOf(f.rel, spec!, conduits, fileSet); const src = schemaExportsOf(f.rel, spec!, conduits, fileSet);
if (src === null || (src.named.size === 0 && src.ns.size === 0)) continue; if (src === null || (src.named.size === 0 && src.ns.size === 0)) continue;
for (const n of src.named) memberSyms.add(n);
if (nsName) namespaces.add(nsName); if (nsName) namespaces.add(nsName);
if (pattern) destructureBindings(pattern, src, named, namespaces); if (pattern) destructureBindings(pattern, src, named, namespaces);
} }
@@ -629,7 +671,7 @@ function classAliases(f: FileFacts, conduits: SchemaConduits, fileSet: Set<strin
// const { schema } = ns; → nested namespace (fail-closed) // const { schema } = ns; → nested namespace (fail-closed)
// const t = ns.companies; → named alias // const t = ns.companies; → named alias
// const s2 = ns.schema; → namespace alias // const s2 = ns.schema; → namespace alias
const nsSrc: SchemaExports = { named: new Set(CLASS_SYMBOLS), ns: new Set() }; const nsSrc: SchemaExports = { named: memberSyms, ns: new Set() };
for (let pass = 0; pass < 2; pass += 1) { for (let pass = 0; pass < 2; pass += 1) {
for (const ns of [...namespaces]) { for (const ns of [...namespaces]) {
for (const m of f.code.matchAll( for (const m of f.code.matchAll(
@@ -643,12 +685,12 @@ function classAliases(f: FileFacts, conduits: SchemaConduits, fileSet: Set<strin
'g', 'g',
), ),
)) { )) {
if (CLASS_SYMBOLS.includes(m[2]!)) named.add(m[1]!); if (memberSyms.has(m[2]!)) named.add(m[1]!);
else namespaces.add(m[1]!); else namespaces.add(m[1]!);
} }
} }
} }
return { named: [...named], namespaces: [...namespaces] }; return { named: [...named], namespaces: [...namespaces], memberSyms };
} }
/** /**
@@ -658,27 +700,34 @@ function classAliases(f: FileFacts, conduits: SchemaConduits, fileSet: Set<strin
* another driver conduit; importing ANYTHING from one confers driver * another driver conduit; importing ANYTHING from one confers driver
* capability (the conduit module itself is additionally flagged by prong * capability (the conduit module itself is additionally flagged by prong
* (iii)'s literal check, so these routes fail at both ends). * (iii)'s literal check, so these routes fail at both ends).
* `factory` — re-exports createDb/createPgliteDb from the db package or * `factory` — carries a NAME MAP (module → the exported names under which a
* another factory conduit (via `export … from`, or by exporting a local * factory is reachable), computed to a fixpoint: `export … from` propagates
* binding of the factory under any name); importing ANYTHING from one * the SOURCE's exported factory names through renames (star and star-as
* confers factory capability — the conduit may rename the symbol, so there * copy them all), and a local binding of a factory (named import, namespace
* is no consumer-side name gate. * member extraction, or tracked dynamic-import destructure) exported under
* any name — braces, declaration, or `export default` — adds that name.
* Importing ANYTHING from a factory conduit (statically or via a tracked
* dynamic import) confers factory capability — the conduit may rename the
* symbol at any hop, so there is no consumer-side name gate.
*/ */
function computeCapabilityConduits( function computeCapabilityConduits(
files: FileFacts[], files: FileFacts[],
fileSet: Set<string>, fileSet: Set<string>,
): { driver: Set<string>; factory: Set<string> } { ): { driver: Set<string>; factory: Set<string> } {
const driver = new Set<string>(); const driver = new Set<string>();
const factory = new Set<string>(); // Factory conduits carry a NAME MAP (module → exported names under which a
// factory is reachable) so renames propagate hop by hop, exactly like the
// schema export map — a literal-name gate at any hop would launder.
const factoryNames = new Map<string, Set<string>>();
const isDriverSpec = (rel: string, spec: string): boolean => { const isDriverSpec = (rel: string, spec: string): boolean => {
if (DRIVER_SPECIFIERS.includes(spec)) return true; if (DRIVER_SPECIFIERS.includes(spec)) return true;
const r = resolveSpecifier(rel, spec, fileSet); const r = resolveSpecifier(rel, spec, fileSet);
return r !== null && driver.has(r); return r !== null && driver.has(r);
}; };
const isFactorySpec = (rel: string, spec: string): boolean => { const factoryNamesOf = (rel: string, spec: string): Set<string> | null => {
if (spec === '@mosaicstack/db') return true; if (spec === '@mosaicstack/db') return new Set(DB_FACTORY_SYMBOLS);
const r = resolveSpecifier(rel, spec, fileSet); const r = resolveSpecifier(rel, spec, fileSet);
return r !== null && factory.has(r); return r !== null ? (factoryNames.get(r) ?? null) : null;
}; };
let changed = true; let changed = true;
while (changed) { while (changed) {
@@ -691,31 +740,44 @@ function computeCapabilityConduits(
driver.add(f.rel); driver.add(f.rel);
changed = true; changed = true;
} }
if (!factory.has(f.rel) && isFactorySpec(f.rel, spec)) {
const named = m[2];
if (named === undefined || /\b(createDb|createPgliteDb)\b/.test(named)) {
factory.add(f.rel);
changed = true;
}
}
} }
// `export { default as x } from 'postgres'` matches EXPORT_FROM_RE's // `export { default as x } from 'postgres'` matches EXPORT_FROM_RE's
// named branch above; `export x from` is not valid syntax — covered. // named branch above; `export x from` is not valid syntax — covered.
// Export-of-local factory bindings: a module that BINDS the factory const mine = factoryNames.get(f.rel) ?? new Set<string>();
// (named import, namespace member, or tracked dynamic import) and const before = mine.size;
// exports that binding under any name is a factory conduit even with for (const m of f.code.matchAll(EXPORT_FROM_RE)) {
// no `export … from` clause (`export const mk = mod.createDb`). if (m[1]) continue;
if (!factory.has(f.rel)) { const src = factoryNamesOf(f.rel, m[3]!);
if (src === null || src.size === 0) continue;
if (m[2] === undefined) {
// `export *` / `export * as x` — over-approximated to the source's
// names (membership is what confers capability on consumers).
for (const n of src) mine.add(n);
} else {
for (const part of m[2].split(',')) {
const seg = part.trim();
if (!seg || seg.startsWith('type ')) continue;
const am = /^(\w+)\s+as\s+(\w+)$/.exec(seg);
if (src.has(am ? am[1]! : seg)) mine.add(am ? am[2]! : seg);
}
}
}
// Locals bound to a factory (named import — aliased or not — namespace
// member extraction, or tracked dynamic-import destructure), then
// exported under ANY name and by ANY form, braces or default included
// (`export const mk = mod.createDb`, `export default createDb`).
const locals = new Set<string>(); const locals = new Set<string>();
const nss = new Set<string>(); const nss = new Map<string, Set<string>>();
for (const im of f.code.matchAll(IMPORT_RE)) { for (const im of f.code.matchAll(IMPORT_RE)) {
const [, typeOnly, , namedList, nsName, , spec] = im; const [, typeOnly, , namedList, nsName, , spec] = im;
if (typeOnly || !isFactorySpec(f.rel, spec!)) continue; if (typeOnly) continue;
if (nsName) nss.add(nsName); const src = factoryNamesOf(f.rel, spec!);
if (src === null || src.size === 0) continue;
if (nsName) nss.set(nsName, src);
if (namedList) { if (namedList) {
for (const part of namedList.split(',')) { for (const part of namedList.split(',')) {
const am = /^(createDb|createPgliteDb)(?:\s+as\s+(\w+))?$/.exec(part.trim()); const am = /^(\w+)(?:\s+as\s+(\w+))?$/.exec(part.trim());
if (am) locals.add(am[2] ?? am[1]!); if (am && src.has(am[1]!)) locals.add(am[2] ?? am[1]!);
} }
} }
} }
@@ -723,39 +785,48 @@ function computeCapabilityConduits(
/(?:const|let|var)\s*(?:\{([^}]*)\}|(\w+))\s*=\s*await\s+import\s*\(\s*(['"])([^'"]+)\3\s*\)/g, /(?:const|let|var)\s*(?:\{([^}]*)\}|(\w+))\s*=\s*await\s+import\s*\(\s*(['"])([^'"]+)\3\s*\)/g,
)) { )) {
const [, pattern, nsName, , spec] = dm; const [, pattern, nsName, , spec] = dm;
if (!isFactorySpec(f.rel, spec!)) continue; const src = factoryNamesOf(f.rel, spec!);
if (nsName) nss.add(nsName); if (src === null || src.size === 0) continue;
if (nsName) nss.set(nsName, src);
if (pattern) { if (pattern) {
for (const part of pattern.split(',')) { for (const part of pattern.split(',')) {
const pm = /^(createDb|createPgliteDb)\s*(?::\s*(\w+))?/.exec(part.trim()); const pm = /^(\w+)\s*(?::\s*(\w+))?/.exec(part.trim());
if (pm) locals.add(pm[2] ?? pm[1]!); if (pm && src.has(pm[1]!)) locals.add(pm[2] ?? pm[1]!);
} }
} }
} }
for (const ns of nss) { for (const [ns, src] of nss) {
for (const am of f.code.matchAll( for (const am of f.code.matchAll(
new RegExp( new RegExp(`(?:const|let|var)\\s+(\\w+)\\s*=\\s*${ns}\\s*\\.\\s*(\\w+)\\b`, 'g'),
`(?:const|let|var)\\s+(\\w+)\\s*=\\s*${ns}\\s*\\.\\s*(?:createDb|createPgliteDb)\\b`,
'g',
),
)) { )) {
locals.add(am[1]!); if (src.has(am[2]!)) locals.add(am[1]!);
}
}
if (locals.size > 0) {
for (const m of f.code.matchAll(/export\s*\{([^}]*)\}(?!\s*from)/g)) {
for (const part of m[1]!.split(',')) {
const seg = part.trim();
if (!seg || seg.startsWith('type ')) continue;
const am = /^(\w+)(?:\s+as\s+(\w+))?$/.exec(seg);
if (am && locals.has(am[1]!)) mine.add(am[2] ?? am[1]!);
} }
} }
for (const local of locals) { for (const local of locals) {
if ( if (new RegExp(`export\\s+(?:const|let|var|function)\\s+${local}\\b`).test(f.code)) {
new RegExp(`export\\s+(?:const|let|var|function)\\s+${local}\\b`).test(f.code) || mine.add(local);
new RegExp(`export\\s*\\{[^}]*\\b${local}\\b[^}]*\\}`).test(f.code) }
) { if (new RegExp(`export\\s+default\\s+${local}\\b`).test(f.code)) {
factory.add(f.rel); mine.add('default');
}
}
}
if (mine.size > before) {
factoryNames.set(f.rel, mine);
changed = true; changed = true;
break;
} }
} }
} }
} return { driver, factory: new Set(factoryNames.keys()) };
}
return { driver, factory };
} }
/** Value-import edge naming one of `symbols` (named import from anywhere, or literal dynamic package import while using the symbol). */ /** Value-import edge naming one of `symbols` (named import from anywhere, or literal dynamic package import while using the symbol). */
@@ -854,6 +925,25 @@ function analyzeFile(f: FileFacts, ctx: AnalysisCtx): Violation[] {
const alias = /\bcreateRequire\s*:\s*(\w+)/.exec(m[1]!); const alias = /\bcreateRequire\s*:\s*(\w+)/.exec(m[1]!);
if (alias) crNames.add(alias[1]!); if (alias) crNames.add(alias[1]!);
} }
// Destructuring from a STATIC or dynamically-bound module namespace:
// import * as M from 'node:module'; const { createRequire: x } = M;
const modNs = new Set<string>();
for (const m of code.matchAll(/import\s*\*\s*as\s+(\w+)\s*from\s*['"](?:node:)?module['"]/g)) {
modNs.add(m[1]!);
}
for (const m of code.matchAll(
/(?:const|let|var)\s+(\w+)\s*=\s*await\s+import\s*\(\s*['"](?:node:)?module['"]\s*\)/g,
)) {
modNs.add(m[1]!);
}
for (const ns of modNs) {
for (const m of code.matchAll(
new RegExp(`(?:const|let|var)\\s*\\{([^}]*)\\}\\s*=\\s*${ns}\\b`, 'g'),
)) {
const alias = /\bcreateRequire\s*(?::\s*(\w+))?/.exec(m[1]!);
if (alias) crNames.add(alias[1] ?? 'createRequire');
}
}
for (const n of crNames) { for (const n of crNames) {
if (n === 'createRequire' || new RegExp(`\\b${n}\\s*\\(`).test(code)) { if (n === 'createRequire' || new RegExp(`\\b${n}\\s*\\(`).test(code)) {
violations.push({ file: rel, prong: 'dynamic-import', detail: 'createRequire()' }); violations.push({ file: rel, prong: 'dynamic-import', detail: 'createRequire()' });
@@ -869,7 +959,9 @@ function analyzeFile(f: FileFacts, ctx: AnalysisCtx): Violation[] {
// Word boundaries live inside each alternative: the bracket form ends in // Word boundaries live inside each alternative: the bracket form ends in
// `]` (non-word), where a trailing `\b` could never match. // `]` (non-word), where a trailing `\b` could never match.
const targets: string[] = [...aliases.named].map((a) => `${a}\\b`); const targets: string[] = [...aliases.named].map((a) => `${a}\\b`);
const symAlt = CLASS_SYMBOLS.join('|'); // Namespace members match the source's EXPORTED names (renames included),
// not just the original class symbols.
const symAlt = [...aliases.memberSyms].join('|');
for (const ns of aliases.namespaces) { for (const ns of aliases.namespaces) {
// Allow intermediate property segments (ns.schema.companies — nested // Allow intermediate property segments (ns.schema.companies — nested
// namespace re-exports) and literal computed access (ns['companies']). // namespace re-exports) and literal computed access (ns['companies']).
@@ -877,9 +969,10 @@ function analyzeFile(f: FileFacts, ctx: AnalysisCtx): Violation[] {
targets.push(`${ns}(?:\\s*\\.\\s*\\w+)*\\s*\\[\\s*['"\`](?:${symAlt})['"\`]\\s*\\]`); targets.push(`${ns}(?:\\s*\\.\\s*\\w+)*\\s*\\[\\s*['"\`](?:${symAlt})['"\`]\\s*\\]`);
} }
if (targets.length > 0) { if (targets.length > 0) {
// `\(\s*\(*` tolerates argument parenthesization: .insert((companies)). // The argument prefix tolerates parenthesization, spread, and array
// wrapping: .insert((companies)), .insert(...[companies]).
const writeRe = new RegExp( const writeRe = new RegExp(
`\\.\\s*(insert|update|delete)\\s*\\(\\s*\\(*\\s*(${targets.join('|')})`, `\\.\\s*(insert|update|delete)\\s*\\(\\s*(?:(?:\\(|\\[|\\.\\.\\.)\\s*)*(${targets.join('|')})`,
'g', 'g',
); );
for (const m of code.matchAll(writeRe)) { for (const m of code.matchAll(writeRe)) {
@@ -924,7 +1017,7 @@ function analyzeFile(f: FileFacts, ctx: AnalysisCtx): Violation[] {
// block comments, and quoting (including escaped quotes in span text). // block comments, and quoting (including escaped quotes in span text).
if (!inAllowlist && !isSchemaDefinition) { if (!inAllowlist && !isSchemaDefinition) {
const kw = const kw =
'(?:insert\\s+into|update|delete\\s+from|from|join|truncate(?:\\s+table)?|alter\\s+table|drop\\s+table|references|into|copy)'; '(?:insert\\s+into|update|delete\\s+from|from|join|truncate(?:\\s+table)?|alter\\s+table|drop\\s+table|references|into|copy|on|lock(?:\\s+table)?)';
const gap = '(?:\\s|/\\*[\\s\\S]*?\\*/|--[^\\n]*\\n)+'; const gap = '(?:\\s|/\\*[\\s\\S]*?\\*/|--[^\\n]*\\n)+';
const q = `(?:\\\\?["'\`])?`; const q = `(?:\\\\?["'\`])?`;
const sqlAdjacentRe = new RegExp( const sqlAdjacentRe = new RegExp(
@@ -956,6 +1049,13 @@ function analyzeFile(f: FileFacts, ctx: AnalysisCtx): Violation[] {
if (r !== null && driverConduits.has(r)) conduitDriver = true; if (r !== null && driverConduits.has(r)) conduitDriver = true;
if (r !== null && factoryConduits.has(r)) factoryConduitImport = true; if (r !== null && factoryConduits.has(r)) factoryConduitImport = true;
} }
// A TRACKED dynamic import of a conduit is an import edge like any
// static one (contract rev 9) — for capability too, not just prong (i).
for (const m of f.code.matchAll(/=\s*await\s+import\s*\(\s*(['"])([^'"]+)\1\s*\)/g)) {
const r = resolveSpecifier(rel, m[2]!, fileSet);
if (r !== null && driverConduits.has(r)) conduitDriver = true;
if (r !== null && factoryConduits.has(r)) factoryConduitImport = true;
}
const driverImport = literalDriver || conduitDriver; const driverImport = literalDriver || conduitDriver;
// Factory capability: a named/namespace edge to the factory symbols, OR // Factory capability: a named/namespace edge to the factory symbols, OR
// ANY value import from a factory conduit — the conduit may re-export // ANY value import from a factory conduit — the conduit may re-export
@@ -967,6 +1067,12 @@ function analyzeFile(f: FileFacts, ctx: AnalysisCtx): Violation[] {
(aliases.namespaces.length > 0 && (aliases.namespaces.length > 0 &&
new RegExp( new RegExp(
`\\b(${aliases.namespaces.join('|')})(?:\\s*\\.\\s*\\w+)*\\s*\\.\\s*(createDb|createPgliteDb)\\b`, `\\b(${aliases.namespaces.join('|')})(?:\\s*\\.\\s*\\w+)*\\s*\\.\\s*(createDb|createPgliteDb)\\b`,
).test(code)) ||
// Destructuring a factory symbol OUT of a schema namespace confers
// capability whatever the local rename: const { createDb: mk } = dbns.
(aliases.namespaces.length > 0 &&
new RegExp(
`(?:const|let|var)\\s*\\{[^}]*\\b(?:createDb|createPgliteDb)\\b[^}]*\\}\\s*=\\s*(?:await\\s+)?(?:${aliases.namespaces.join('|')})\\b`,
).test(code)); ).test(code));
if (driverImport) { if (driverImport) {
violations.push({ file: rel, prong: 'iii-raw-execution', detail: 'direct driver import' }); violations.push({ file: rel, prong: 'iii-raw-execution', detail: 'direct driver import' });
@@ -995,8 +1101,10 @@ function analyzeFile(f: FileFacts, ctx: AnalysisCtx): Violation[] {
if (!driverImport) { if (!driverImport) {
// DI residual backstop: db/client-shaped receivers fire regardless of // DI residual backstop: db/client-shaped receivers fire regardless of
// detected capability (a handle can arrive by injection). // detected capability (a handle can arrive by injection).
// The receiver may be a dotted name OR a literal bracketed member with
// a conventional name: this['db'].query(…).
for (const m of code.matchAll( for (const m of code.matchAll(
/\b(?:db|database|client|conn|connection|pool|pg|pglite|tx|trx)\s*\.\s*(execute|query|unsafe)\s*\(/g, /(?:\b(?:db|database|client|conn|connection|pool|pg|pglite|tx|trx)|\[\s*['"](?:db|database|client|conn|connection|pool|pg|pglite|tx|trx)['"]\s*\])\s*\.\s*(execute|query|unsafe)\s*\(/g,
)) { )) {
violations.push({ violations.push({
file: rel, file: rel,
@@ -1356,6 +1464,114 @@ describe('hierarchy writer coverage (contract 1 §6.3b)', () => {
name: 'E39 computed-member eval', name: 'E39 computed-member eval',
src: `export function f(s: string) { return (globalThis as never)['eval'](s); }`, src: `export function f(s: string) { return (globalThis as never)['eval'](s); }`,
}, },
{
name: 'E40 schema symbol renamed to default through conduit',
src: `import c from './evasion-mid9.js';\nimport { db } from './x.js';\nexport async function f() { await db.insert(c).values({}); }`,
extras: [
{
rel: 'packages/db/src/evasion-mid9.ts',
src: `export { companies as default } from '@mosaicstack/db';`,
},
],
},
{
name: 'E41 export default of locally bound schema symbol',
src: `import c from './evasion-mid10.js';\nimport { db } from './x.js';\nexport async function f() { await db.insert(c).values({}); }`,
extras: [
{
rel: 'packages/db/src/evasion-mid10.ts',
src: `import { companies } from '@mosaicstack/db';\nexport default companies;`,
},
],
},
{
name: 'E42 export default of dynamic namespace member',
src: `import c from './evasion-mid11.js';\nimport { db } from './x.js';\nexport async function f() { await db.insert(c).values({}); }`,
extras: [
{
rel: 'packages/db/src/evasion-mid11.ts',
src: `const mod = await import('@mosaicstack/db');\nexport default mod.companies;`,
},
],
},
{
name: 'E43 namespace member under renamed export',
src: `import * as M from './evasion-mid12.js';\nimport { db } from './x.js';\nexport async function f() { await db.insert(M.co).values({}); }`,
extras: [
{
rel: 'packages/db/src/evasion-mid12.ts',
src: `export { companies as co } from '@mosaicstack/db';`,
},
],
},
{
name: 'E44 dynamic namespace member under renamed export',
src: `import { db } from './x.js';\nconst M = await import('./evasion-mid13.js');\nexport async function f() { await db.insert(M.co).values({}); }`,
extras: [
{
rel: 'packages/db/src/evasion-mid13.ts',
src: `export { companies as co } from '@mosaicstack/db';`,
},
],
},
{
name: 'E45 two-hop factory export-from rename',
src: `import { mk } from './evasion-mid15.js';\nexport async function f(t: string) { await mk('u').execute('DELETE FROM ' + t); }`,
extras: [
{
rel: 'packages/db/src/evasion-mid14.ts',
src: `export { createDb as mk } from '@mosaicstack/db';`,
},
{
rel: 'packages/db/src/evasion-mid15.ts',
src: `export { mk } from './evasion-mid14.js';`,
},
],
},
{
name: 'E46 export default of locally bound factory',
src: `import mk from './evasion-mid16.js';\nexport async function f(t: string) { await mk('u').execute('DELETE FROM ' + t); }`,
extras: [
{
rel: 'packages/db/src/evasion-mid16.ts',
src: `import { createDb } from '@mosaicstack/db';\nexport default createDb;`,
},
],
},
{
name: 'E47 tracked dynamic import of a factory conduit',
src: `const F = await import('./evasion-mid17.js');\nexport async function f(t: string) { await F.mk('u').execute('DELETE FROM ' + t); }`,
extras: [
{
rel: 'packages/db/src/evasion-mid17.ts',
src: `export { createDb as mk } from '@mosaicstack/db';`,
},
],
},
{
name: 'E48 factory destructure-rename from schema namespace',
src: `import * as dbns from '@mosaicstack/db';\nconst { createDb: mk } = dbns;\nexport async function f(t: string) { await mk('u').execute('DELETE FROM ' + t); }`,
},
{
name: 'E49 createRequire destructured from static module namespace',
src: `import * as M from 'node:module';\nconst { createRequire: mkReq } = M;\nconst req = mkReq(import.meta.url);\nexport const pg = req('postgres');`,
},
{
name: 'E50 spread-argument schema write',
src: `import { companies } from '@mosaicstack/db';\nimport { db } from './x.js';\nexport async function f() { await db.insert(...[companies]).values({}); }`,
},
{
name: 'E51 GRANT ON class table in SQL span',
src: `export const q = 'GRANT SELECT ON hierarchy_grants TO auditor';`,
},
{
name: 'E52 LOCK TABLE class table in SQL span',
src: `export const q = 'LOCK TABLE hierarchy_grants IN ACCESS EXCLUSIVE MODE';`,
},
{
name: 'E53 bracketed conventional receiver',
src: `export class R { async f(t: string) { await this['db'].query('TRUNCATE ' + t); } }`,
},
]; ];
const CLEAN_CONTROLS: Array<{ name: string; src: string }> = [ const CLEAN_CONTROLS: Array<{ name: string; src: string }> = [
{ {