e7738de6d2
CI / Deep Native Runtime Cases (1/6) (push) Has been skipped
CI / Native Preflight (push) Failing after 1s
CI / Native Runtime Cases (1/2) (push) Failing after 0s
CI / Native Runtime Cases (2/2) (push) Failing after 1s
CI / Native Metadata Reports (push) Failing after 0s
CI / Native Direct Backend Artifacts (push) Failing after 0s
CI / Native Sanitizer Smoke (push) Failing after 1s
CI / Command Contract Snapshots (push) Failing after 1s
CI / Deep Conformance Suite (push) Has been skipped
CI / Graph Build Perf (push) Failing after 1s
CI / Deep Native Preflight (push) Has been skipped
CI / Deep Native Runtime Cases (2/6) (push) Has been skipped
CI / Deep Native Runtime Cases (3/6) (push) Has been skipped
CI / Conformance Suite (push) Failing after 1s
CI / Workspace Checks (push) Failing after 0s
CI / Deep Native Runtime Cases (5/6) (push) Has been skipped
CI / Deep Native Runtime Cases (6/6) (push) Has been skipped
CI / Deep Native Runtime Cases (4/6) (push) Has been skipped
CI / Deep Graph Build Perf (push) Has been skipped
665 lines
42 KiB
TypeScript
Executable File
665 lines
42 KiB
TypeScript
Executable File
#!/usr/bin/env -S node --experimental-strip-types --disable-warning=ExperimentalWarning
|
|
import { existsSync, readFileSync } from "node:fs";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
const repoRoot = path.resolve(scriptDir, "..");
|
|
const checkerPath = path.join(repoRoot, "native/zero-c/src/checker.c");
|
|
const callResolvePath = path.join(repoRoot, "native/zero-c/src/call_resolve.h");
|
|
const stdSigPath = path.join(repoRoot, "native/zero-c/src/std_sig.h");
|
|
const matrixPath = path.join(repoRoot, "conformance/provenance-surface.json");
|
|
const conformancePath = path.join(repoRoot, "conformance/run.mjs");
|
|
|
|
const checker = readFileSync(checkerPath, "utf8");
|
|
const callResolve = readFileSync(callResolvePath, "utf8");
|
|
const stdSig = readFileSync(stdSigPath, "utf8");
|
|
const surfaceSpec = JSON.parse(readFileSync(matrixPath, "utf8"));
|
|
const conformance = readFileSync(conformancePath, "utf8");
|
|
|
|
const failures = [];
|
|
|
|
function fail(message) {
|
|
failures.push(message);
|
|
}
|
|
|
|
function assertIncludes(label, text, needle) {
|
|
if (!text.includes(needle)) fail(`${label}: missing ${needle}`);
|
|
}
|
|
|
|
function assertNotIncludes(label, text, needle) {
|
|
if (text.includes(needle)) fail(`${label}: unexpected ${needle}`);
|
|
}
|
|
|
|
function assertBefore(label, text, first, second) {
|
|
const firstIndex = text.indexOf(first);
|
|
const secondIndex = text.indexOf(second);
|
|
if (firstIndex < 0) fail(`${label}: missing ${first}`);
|
|
if (secondIndex < 0) fail(`${label}: missing ${second}`);
|
|
if (firstIndex >= 0 && secondIndex >= 0 && firstIndex > secondIndex) fail(`${label}: ${first} must appear before ${second}`);
|
|
}
|
|
|
|
function assertMatches(label, text, pattern) {
|
|
if (!pattern.test(text)) fail(`${label}: missing ${pattern}`);
|
|
}
|
|
|
|
function assertFixtureCoverage(label, fixture) {
|
|
const fullPath = path.join(repoRoot, fixture);
|
|
if (!existsSync(fullPath)) {
|
|
fail(`${label}: fixture '${fixture}' does not exist`);
|
|
return;
|
|
}
|
|
const basename = path.basename(fixture);
|
|
if (!conformance.includes(fixture) && !conformance.includes(basename)) {
|
|
fail(`${label}: fixture '${fixture}' is not run by conformance/run.mjs`);
|
|
}
|
|
}
|
|
|
|
function sliceBetween(text, start, end) {
|
|
const startIndex = text.indexOf(start);
|
|
if (startIndex < 0) return "";
|
|
const endIndex = text.indexOf(end, startIndex + start.length);
|
|
if (endIndex < 0) return text.slice(startIndex);
|
|
return text.slice(startIndex, endIndex);
|
|
}
|
|
|
|
function checkerFunctionNames(source) {
|
|
const names = new Set();
|
|
for (const line of source.split("\n")) {
|
|
if (line.trim().endsWith(";")) continue;
|
|
const match = line.match(/^static\b[^{;]*?\b([A-Za-z_][A-Za-z0-9_]*)\s*\(/);
|
|
if (match) names.add(match[1]);
|
|
}
|
|
return names;
|
|
}
|
|
|
|
function currentFunctionByLine(source) {
|
|
const current = [];
|
|
let name = "<top-level>";
|
|
const lines = source.split("\n");
|
|
for (const line of lines) {
|
|
if (!line.trim().endsWith(";")) {
|
|
const match = line.match(/^static\b[^{;]*?\b([A-Za-z_][A-Za-z0-9_]*)\s*\(/);
|
|
if (match) name = match[1];
|
|
}
|
|
current.push(name);
|
|
}
|
|
return current;
|
|
}
|
|
|
|
function surfaceFixturePaths(fixtures) {
|
|
if (Array.isArray(fixtures)) return fixtures;
|
|
if (!fixtures || typeof fixtures !== "object") return [];
|
|
const paths = [];
|
|
for (const group of ["fail", "pass", "other"]) {
|
|
if (fixtures[group] === undefined) continue;
|
|
if (!Array.isArray(fixtures[group])) {
|
|
fail(`provenance surface spec: fixtures.${group} must be an array`);
|
|
continue;
|
|
}
|
|
paths.push(...fixtures[group]);
|
|
}
|
|
return paths;
|
|
}
|
|
|
|
function parseSurfaceRows(spec) {
|
|
const rows = [];
|
|
if (!spec || typeof spec !== "object") {
|
|
fail("provenance surface spec: expected JSON object");
|
|
return rows;
|
|
}
|
|
if (spec.schemaVersion !== 1) fail("provenance surface spec: schemaVersion must be 1");
|
|
if (spec.kind !== "zero-provenance-surface-matrix") fail("provenance surface spec: unexpected kind");
|
|
if (!Array.isArray(spec.surfaces)) {
|
|
fail("provenance surface spec: surfaces must be an array");
|
|
return rows;
|
|
}
|
|
const seen = new Set();
|
|
for (let index = 0; index < spec.surfaces.length; index++) {
|
|
const row = spec.surfaces[index];
|
|
if (!row || typeof row !== "object") {
|
|
fail(`provenance surface spec: surface ${index + 1} must be an object`);
|
|
continue;
|
|
}
|
|
if (!row.surface || typeof row.surface !== "string") fail(`provenance surface spec: surface ${index + 1} has no name`);
|
|
if (!row.action || typeof row.action !== "string") fail(`provenance surface spec: '${row.surface ?? index + 1}' has no action`);
|
|
if (!Array.isArray(row.owners)) fail(`provenance surface spec: '${row.surface ?? index + 1}' owners must be an array`);
|
|
if (row.surface && seen.has(row.surface)) fail(`provenance surface spec: duplicate surface '${row.surface}'`);
|
|
if (row.surface) seen.add(row.surface);
|
|
rows.push({
|
|
surface: row.surface,
|
|
action: row.action,
|
|
owners: Array.isArray(row.owners) ? row.owners : [],
|
|
fixtures: surfaceFixturePaths(row.fixtures),
|
|
});
|
|
}
|
|
return rows;
|
|
}
|
|
|
|
const functionNames = checkerFunctionNames(checker);
|
|
const rows = parseSurfaceRows(surfaceSpec);
|
|
const rowsBySurface = new Map(rows.map((row) => [row.surface, row]));
|
|
|
|
const requiredSurfaces = [
|
|
"Identifier reads",
|
|
"Field reads",
|
|
"Index reads",
|
|
"Direct borrows",
|
|
"Mutable borrows",
|
|
"Shape literals",
|
|
"Array literals",
|
|
"Maybe<T> values",
|
|
"Choice payloads",
|
|
"check unwraps",
|
|
"rescue values",
|
|
"Primitive casts",
|
|
"Plain calls",
|
|
"Generic calls",
|
|
"Shape namespace calls",
|
|
"Receiver calls",
|
|
"Static interface calls",
|
|
"Incomplete summaries",
|
|
"Let bindings",
|
|
"Assignments",
|
|
"Field assignments",
|
|
"Index assignments",
|
|
"Returns",
|
|
"If joins",
|
|
"Match joins",
|
|
"Loop-carried values",
|
|
"Short-circuit expressions",
|
|
"Early returns",
|
|
"Mutref aliases",
|
|
];
|
|
|
|
for (const surface of requiredSurfaces) {
|
|
if (!rowsBySurface.has(surface)) fail(`provenance matrix: missing surface row '${surface}'`);
|
|
}
|
|
|
|
for (const row of rows) {
|
|
if (row.owners.length === 0) fail(`provenance matrix: '${row.surface}' has no checker owner`);
|
|
for (const owner of row.owners) {
|
|
if (!functionNames.has(owner)) fail(`provenance matrix: owner '${owner}' for '${row.surface}' is not a checker function`);
|
|
}
|
|
for (const fixture of row.fixtures) {
|
|
assertFixtureCoverage(`provenance matrix '${row.surface}'`, fixture);
|
|
}
|
|
}
|
|
|
|
const positivePrecisionClasses = [
|
|
["direct same-origin mutable reassignment", "conformance/native/pass/borrow-aggregate-reassignment-same-origin.0"],
|
|
["alias same-origin mutable reassignment", "conformance/native/pass/mutref-alias-assignment-same-origin.0"],
|
|
["field overwrite clears old origin", "conformance/native/pass/shape-field-reference-reassignment-clears-origin.0"],
|
|
["disjoint field assignment", "conformance/native/pass/borrow-field-independent-assignment.0"],
|
|
["caller-owned return ref", "conformance/native/pass/borrow-return-param-ref.0"],
|
|
["caller-owned field return ref", "conformance/native/pass/borrow-return-param-field-subpath.0"],
|
|
["plain mutref stores caller-owned ref", "conformance/native/pass/function-mutref-reference-store.0"],
|
|
["generic mutref stores caller-owned ref", "conformance/native/pass/generic-mutref-reference-store.0"],
|
|
["receiver stores caller-owned ref", "conformance/native/pass/receiver-method-reference-store.0"],
|
|
["static interface stores caller-owned ref", "conformance/native/pass/static-interface-mutref-reference-store.0"],
|
|
["static interface returns caller-owned ref", "conformance/native/pass/static-interface-return-reference-origin.0"],
|
|
["choice payload returns caller-owned ref", "conformance/native/pass/choice-payload-reference-return.0"],
|
|
["choice match payload preserves caller-owned ref", "conformance/native/pass/choice-match-payload-reference-origin.0"],
|
|
["choice match payload returns caller-owned ref", "conformance/native/pass/choice-match-payload-return-origin.0"],
|
|
["branches merge same origin", "conformance/native/pass/borrow-branch-reassignment.0"],
|
|
["branches overwrite away unsafe origin", "conformance/native/pass/branch-overwrite-away-reference-origin.0"],
|
|
["indexed assignment clears overwritten origin", "conformance/native/pass/index-reference-assignment-clears-origin.0"],
|
|
];
|
|
|
|
for (const [label, fixture] of positivePrecisionClasses) {
|
|
assertFixtureCoverage(`positive provenance class '${label}'`, fixture);
|
|
}
|
|
|
|
const canonicalPlaceClasses = [
|
|
["direct field write", "conformance/native/pass/function-mutref-reference-store.0"],
|
|
["mutref alias field write", "conformance/native/pass/mutref-alias-assignment-clears-old-origin.0"],
|
|
["receiver self field write", "conformance/native/pass/receiver-method-reference-store.0"],
|
|
["generic mutref write", "conformance/native/pass/generic-mutref-reference-store.0"],
|
|
["constrained interface mutref write", "conformance/native/pass/static-interface-mutref-reference-store.0"],
|
|
["precise indexed write clears target", "conformance/native/pass/index-reference-assignment-clears-origin.0"],
|
|
];
|
|
|
|
for (const [label, fixture] of canonicalPlaceClasses) {
|
|
if (existsSync(path.join(repoRoot, fixture))) assertFixtureCoverage(`canonical place class '${label}'`, fixture);
|
|
}
|
|
|
|
const requiredFunctions = [
|
|
"expr_value_provenance",
|
|
"expr_reference_provenance",
|
|
"resolve_provenance_call",
|
|
"call_result_value_provenance",
|
|
"function_provenance_summary",
|
|
"function_return_value_provenance",
|
|
"function_storage_effect_summary",
|
|
"choice_constructor_value_provenance",
|
|
"register_match_payload_binding_provenance",
|
|
"apply_checked_call_storage_effects",
|
|
"check_call_expr_expected",
|
|
"check_named_function_call_expected",
|
|
"check_stdlib_table_arg_range_expected",
|
|
"check_receiver_shape_call_expected",
|
|
"fallible_callee_in_context",
|
|
"resolve_stdlib_fallible_call",
|
|
"stdlib_call_error_sets_covered",
|
|
"function_error_sets_include_stdlib_resolution",
|
|
"find_shape_owning_method",
|
|
"finish_shape_method_provenance_call",
|
|
"resolve_shape_namespace_provenance_call",
|
|
"resolve_concrete_constrained_shape_provenance_call",
|
|
"resolve_named_provenance_call",
|
|
"resolve_constrained_interface_provenance_call",
|
|
"resolve_receiver_shape_provenance_call",
|
|
"bind_type_args_to_bindings",
|
|
"checked_call_type_args",
|
|
"generic_bindings_from_type_args",
|
|
"checked_call_bindings_from_recorded_type_args",
|
|
"shape_method_bindings_from_recorded_type_args",
|
|
"interface_method_bindings_from_recorded_type_args",
|
|
"generic_call_bindings_from_checked_call",
|
|
"call_resolution_record_bindings",
|
|
"call_resolution_record_param_facts",
|
|
"call_resolution_param_type_text",
|
|
"resolved_call_param_type_text",
|
|
"apply_provenance_storage_effect",
|
|
"stdlib_install_item_write_provenance",
|
|
"collect_effect_target_places",
|
|
"collect_assignment_target_places",
|
|
"expr_static_index_segment",
|
|
"origin_path_is_definitely_within",
|
|
"update_borrow_assignment",
|
|
"assignment_provenance_snapshot_clear",
|
|
"provenance_scope_snapshot_restore_union",
|
|
];
|
|
|
|
for (const name of requiredFunctions) {
|
|
if (!functionNames.has(name)) fail(`checker provenance foundation: missing function '${name}'`);
|
|
}
|
|
|
|
for (const kind of [
|
|
"Z_CALL_FUNCTION",
|
|
"Z_CALL_STDLIB",
|
|
"Z_CALL_SHAPE_NAMESPACE",
|
|
"Z_CALL_RECEIVER",
|
|
"Z_CALL_CONSTRAINED_INTERFACE",
|
|
"Z_CALL_CONCRETE_CONSTRAINED_SHAPE",
|
|
"Z_CALL_CHOICE_CONSTRUCTOR",
|
|
]) {
|
|
assertIncludes("shared call resolver", callResolve, kind);
|
|
assertIncludes("checker provenance call resolver", checker, kind);
|
|
}
|
|
|
|
for (const needle of [
|
|
"ZCallArgument",
|
|
"ZCallBinding",
|
|
"ZCallError",
|
|
"z_call_resolution_add_arg",
|
|
"z_call_resolution_expected_arg_count",
|
|
"z_call_resolution_param_type",
|
|
"z_call_resolution_add_binding",
|
|
"z_call_resolution_binding_type",
|
|
"z_call_resolution_add_error",
|
|
"z_call_resolution_error_set_text",
|
|
]) {
|
|
assertIncludes("shared call argument facts", callResolve, needle);
|
|
}
|
|
|
|
assertIncludes("shared stdlib error facts", stdSig, "Z_STD_HELPER_MAX_ERRORS");
|
|
assertIncludes("shared stdlib error facts", stdSig, "error_names");
|
|
assertIncludes("shared stdlib error facts", stdSig, "z_std_helper_error_name");
|
|
assertIncludes("shared stdlib error facts", stdSig, "z_std_helper_error_set_text");
|
|
assertIncludes("shared stdlib helper classification", stdSig, "ZStdHelperKind");
|
|
assertIncludes("shared stdlib helper classification", stdSig, "z_std_helper_kind");
|
|
assertNotIncludes("shared stdlib fallibility facts", checker, "is_builtin_fallible_call");
|
|
assertNotIncludes("shared stdlib fallibility facts", checker, "builtin_fallible_return_type");
|
|
|
|
const callResultBody = sliceBetween(checker, "static bool call_result_value_provenance", "static bool expr_reference_provenance");
|
|
assertIncludes("call result provenance", callResultBody, "resolve_provenance_call");
|
|
assertIncludes("call result provenance", callResultBody, "function_return_value_provenance");
|
|
assertIncludes("call result provenance", callResultBody, "instantiate_call_provenance_entry");
|
|
assertIncludes("call result provenance", callResultBody, "resolved_call_param_type_text");
|
|
|
|
const shapeMethodProvenanceBody = sliceBetween(checker, "static bool finish_shape_method_provenance_call", "static bool resolve_shape_namespace_provenance_call");
|
|
assertIncludes("shape method provenance facts", shapeMethodProvenanceBody, "shape_method_bindings_from_recorded_type_args");
|
|
assertIncludes("shape method provenance facts", shapeMethodProvenanceBody, "build_shape_method_bindings");
|
|
assertIncludes("shape method provenance facts", shapeMethodProvenanceBody, "provenance_context_substitute_bindings");
|
|
assertIncludes("shape method provenance facts", shapeMethodProvenanceBody, "call_resolution_record_bindings");
|
|
assertIncludes("shape method provenance facts", shapeMethodProvenanceBody, "call_resolution_record_param_facts");
|
|
assertBefore("shape method provenance binding source order", shapeMethodProvenanceBody, "shape_method_bindings_from_recorded_type_args", "build_shape_method_bindings");
|
|
|
|
const shapeNamespaceProvenanceBody = sliceBetween(checker, "static bool resolve_shape_namespace_provenance_call", "static bool resolve_concrete_constrained_shape_provenance_call");
|
|
assertIncludes("shape namespace provenance resolver", shapeNamespaceProvenanceBody, "resolve_shape_namespace_call");
|
|
assertIncludes("shape namespace provenance resolver", shapeNamespaceProvenanceBody, "finish_shape_method_provenance_call");
|
|
|
|
const concreteConstrainedShapeProvenanceBody = sliceBetween(checker, "static bool resolve_concrete_constrained_shape_provenance_call", "static bool resolve_named_provenance_call");
|
|
assertIncludes("concrete constrained-shape provenance resolver", concreteConstrainedShapeProvenanceBody, "resolve_concrete_constrained_shape_call");
|
|
assertIncludes("concrete constrained-shape provenance resolver", concreteConstrainedShapeProvenanceBody, "finish_shape_method_provenance_call");
|
|
|
|
const namedProvenanceBody = sliceBetween(checker, "static bool resolve_named_provenance_call", "static bool resolve_constrained_interface_provenance_call");
|
|
assertIncludes("named provenance resolver", namedProvenanceBody, "resolve_named_function_call");
|
|
assertIncludes("named provenance resolver", namedProvenanceBody, "generic_call_bindings_from_checked_call");
|
|
assertIncludes("named provenance resolver", namedProvenanceBody, "provenance_context_substitute_bindings");
|
|
assertIncludes("named provenance resolver", namedProvenanceBody, "call_resolution_record_bindings");
|
|
assertIncludes("named provenance resolver", namedProvenanceBody, "call_resolution_record_param_facts");
|
|
|
|
const checkedCallTypeArgsBody = sliceBetween(checker, "static const TypeArgVec *checked_call_type_args", "static bool generic_bindings_from_type_args");
|
|
assertIncludes("checked call type args", checkedCallTypeArgsBody, "checked_type_args");
|
|
|
|
const checkedCallBindingsBody = sliceBetween(
|
|
checker,
|
|
"static bool checked_call_bindings_from_recorded_type_args(CheckContext *ctx, const Program *program, const Function *callee, const Expr *call, GenericBinding **out_bindings, size_t *out_len, bool *out_recorded) {",
|
|
"static bool generic_call_bindings_from_checked_call"
|
|
);
|
|
assertIncludes("checked call bindings", checkedCallBindingsBody, "checked_call_type_args");
|
|
assertIncludes("checked call bindings", checkedCallBindingsBody, "generic_bindings_from_type_args");
|
|
|
|
const genericCallBindingsBody = sliceBetween(
|
|
checker,
|
|
"static bool generic_call_bindings_from_checked_call(CheckContext *ctx, const Program *program, const Function *callee, const Expr *call, Scope *scope, const char *return_type, GenericBinding **out_bindings, size_t *out_len) {",
|
|
"static char *call_param_type_text"
|
|
);
|
|
assertIncludes("generic call bindings", genericCallBindingsBody, "checked_call_bindings_from_recorded_type_args");
|
|
assertIncludes("generic call bindings", genericCallBindingsBody, "call_type_args(call)");
|
|
assertBefore("generic call binding source order", genericCallBindingsBody, "checked_call_bindings_from_recorded_type_args", "call_type_args(call)");
|
|
|
|
const constrainedInterfaceProvenanceBody = sliceBetween(checker, "static bool resolve_constrained_interface_provenance_call", "static bool resolve_receiver_shape_provenance_call");
|
|
assertIncludes("constrained interface provenance resolver", constrainedInterfaceProvenanceBody, "resolve_constrained_interface_call");
|
|
assertIncludes("constrained interface provenance resolver", constrainedInterfaceProvenanceBody, "interface_method_bindings_from_recorded_type_args");
|
|
assertIncludes("constrained interface provenance resolver", constrainedInterfaceProvenanceBody, "build_constrained_interface_method_bindings");
|
|
assertIncludes("constrained interface provenance resolver", constrainedInterfaceProvenanceBody, "call_resolution_record_bindings");
|
|
assertIncludes("constrained interface provenance resolver", constrainedInterfaceProvenanceBody, "call_resolution_record_param_facts");
|
|
assertBefore("constrained interface provenance binding source order", constrainedInterfaceProvenanceBody, "interface_method_bindings_from_recorded_type_args", "build_constrained_interface_method_bindings");
|
|
|
|
const receiverProvenanceBody = sliceBetween(checker, "static bool resolve_receiver_shape_provenance_call", "static bool resolve_provenance_call");
|
|
assertIncludes("receiver provenance resolver", receiverProvenanceBody, "resolve_receiver_shape_call");
|
|
assertIncludes("receiver provenance resolver", receiverProvenanceBody, "shape_method_bindings_from_recorded_type_args");
|
|
assertIncludes("receiver provenance resolver", receiverProvenanceBody, "build_receiver_shape_method_bindings");
|
|
assertIncludes("receiver provenance resolver", receiverProvenanceBody, "call_resolution_record_bindings");
|
|
assertIncludes("receiver provenance resolver", receiverProvenanceBody, "call_resolution_record_param_facts");
|
|
assertBefore("receiver provenance binding source order", receiverProvenanceBody, "shape_method_bindings_from_recorded_type_args", "build_receiver_shape_method_bindings");
|
|
|
|
const provenanceCallBody = sliceBetween(checker, "static bool resolve_provenance_call", "static bool function_return_value_provenance");
|
|
assertIncludes("provenance call dispatcher", provenanceCallBody, "resolve_named_provenance_call");
|
|
assertIncludes("provenance call dispatcher", provenanceCallBody, "resolve_shape_namespace_provenance_call");
|
|
assertIncludes("provenance call dispatcher", provenanceCallBody, "resolve_concrete_constrained_shape_provenance_call");
|
|
assertIncludes("provenance call dispatcher", provenanceCallBody, "resolve_constrained_interface_provenance_call");
|
|
assertIncludes("provenance call dispatcher", provenanceCallBody, "resolve_receiver_shape_provenance_call");
|
|
assertBefore("provenance call resolver order", provenanceCallBody, "resolve_concrete_constrained_shape_provenance_call", "resolve_constrained_interface_provenance_call");
|
|
|
|
const callResolutionFactsResolverBody = sliceBetween(checker, "static bool call_facts_resolve_call", "static void call_facts_collect_expr");
|
|
assertIncludes("call-resolution graph facts", callResolutionFactsResolverBody, "resolve_stdlib_call");
|
|
assertIncludes("call-resolution graph facts", callResolutionFactsResolverBody, "resolve_choice_constructor_call");
|
|
assertIncludes("call-resolution graph facts", callResolutionFactsResolverBody, "resolve_provenance_call");
|
|
assertIncludes("call-resolution graph facts", callResolutionFactsResolverBody, "ResolvedProvenanceCall");
|
|
|
|
const callResolutionFactsJsonBody = sliceBetween(checker, "void z_append_call_resolution_facts_json", "static bool function_return_value_provenance");
|
|
assertIncludes("call-resolution graph facts", callResolutionFactsJsonBody, "Z_CALL_FUNCTION");
|
|
assertIncludes("call-resolution graph facts", callResolutionFactsJsonBody, "Z_CALL_CHOICE_CONSTRUCTOR");
|
|
assertIncludes("call-resolution graph facts", callResolutionFactsJsonBody, "call_facts_collect_function");
|
|
assertIncludes("call-resolution graph fixture", conformance, "conformance/check/pass/call-resolution-inspection.0");
|
|
assertIncludes("call-resolution graph fixture", conformance, "callResolution");
|
|
|
|
const exprCallResolverBody = sliceBetween(checker, "static bool resolve_expr_call_for_type", "static const char *expr_call_return_type");
|
|
assertIncludes("expr call return resolver", exprCallResolverBody, "resolve_named_function_call");
|
|
assertIncludes("expr call return resolver", exprCallResolverBody, "resolve_shape_namespace_call");
|
|
assertIncludes("expr call return resolver", exprCallResolverBody, "resolve_concrete_constrained_shape_call");
|
|
assertIncludes("expr call return resolver", exprCallResolverBody, "resolve_constrained_interface_call");
|
|
assertIncludes("expr call return resolver", exprCallResolverBody, "resolve_receiver_shape_call");
|
|
assertIncludes("expr call return resolver", exprCallResolverBody, "resolve_choice_constructor_call");
|
|
assertIncludes("expr call return resolver", exprCallResolverBody, "resolve_stdlib_call");
|
|
|
|
const exprCallReturnBody = sliceBetween(checker, "static const char *expr_call_return_type", "static const char *expr_type");
|
|
assertIncludes("expr call return facts", exprCallReturnBody, "resolve_expr_call_for_type");
|
|
assertIncludes("expr call return facts", exprCallReturnBody, "resolution.return_type");
|
|
assertIncludes("expr call return facts", exprCallReturnBody, "Z_CALL_FUNCTION");
|
|
assertIncludes("expr call return facts", exprCallReturnBody, "Z_CALL_SHAPE_NAMESPACE");
|
|
assertIncludes("expr call return facts", exprCallReturnBody, "Z_CALL_CONSTRAINED_INTERFACE");
|
|
assertIncludes("expr call return facts", exprCallReturnBody, "Z_CALL_RECEIVER");
|
|
assertIncludes("expr call return facts", exprCallReturnBody, "generic_call_bindings_from_checked_call");
|
|
assertIncludes("expr call return facts", exprCallReturnBody, "shape_method_bindings_from_recorded_type_args");
|
|
assertIncludes("expr call return facts", exprCallReturnBody, "interface_method_bindings_from_recorded_type_args");
|
|
assertNotIncludes("expr call return facts", exprCallReturnBody, "build_generic_bindings");
|
|
assertBefore("expr call return shape binding source order", exprCallReturnBody, "shape_method_bindings_from_recorded_type_args", "build_shape_method_bindings");
|
|
assertBefore("expr call return interface binding source order", exprCallReturnBody, "interface_method_bindings_from_recorded_type_args", "build_constrained_interface_method_bindings");
|
|
assertBefore("expr call return receiver binding source order", exprCallReturnBody, "shape_method_bindings_from_recorded_type_args", "build_receiver_shape_method_bindings");
|
|
|
|
const exprTypeCallBody = sliceBetween(
|
|
checker,
|
|
"static const char *expr_type(CheckContext *ctx, const Program *program, const Expr *expr, Scope *scope) {",
|
|
"case EXPR_INDEX:"
|
|
);
|
|
assertIncludes("expr_type call return dispatch", exprTypeCallBody, "expr_call_return_type");
|
|
assertNotIncludes("expr_type call return dispatch", exprTypeCallBody, "resolve_named_function_call");
|
|
assertNotIncludes("expr_type call return dispatch", exprTypeCallBody, "resolve_shape_namespace_call");
|
|
assertNotIncludes("expr_type call return dispatch", exprTypeCallBody, "resolve_stdlib_call");
|
|
|
|
const namedCallBody = sliceBetween(checker, "static bool build_named_call_bindings_expected", "static bool check_stdlib_table_arg_range_expected");
|
|
assertIncludes("named call checking argument facts", namedCallBody, "resolve_named_function_call");
|
|
assertIncludes("named call checking argument facts", namedCallBody, "build_named_call_bindings_expected");
|
|
assertIncludes("named call checking argument facts", namedCallBody, "check_named_call_args_expected");
|
|
assertIncludes("named call checking argument facts", namedCallBody, "check_call_resolution_args_expected");
|
|
assertIncludes("named call checking argument facts", namedCallBody, "check_named_call_fallibility_expected");
|
|
assertIncludes("named call checking argument facts", namedCallBody, "prepare_named_call_return_and_storage_expected");
|
|
assertIncludes("named call checking argument facts", namedCallBody, "finish_named_call_return_expected");
|
|
assertIncludes("named call checking argument facts", namedCallBody, "z_call_resolution_expected_arg_count");
|
|
assertIncludes("named call checking argument facts", namedCallBody, "call_resolution_record_param_facts");
|
|
assertIncludes("named call checking argument facts", namedCallBody, "call_resolution_param_type_text");
|
|
assertIncludes("named call checking storage effects", namedCallBody, "apply_checked_call_storage_effects");
|
|
|
|
const stdlibTableCallBody = sliceBetween(checker, "static bool check_stdlib_table_arg_range_expected", "static bool check_stdlib_allocator_arg");
|
|
assertIncludes("stdlib table call checking argument facts", stdlibTableCallBody, "z_call_resolution_add_arg");
|
|
assertIncludes("stdlib table call checking argument facts", stdlibTableCallBody, "call_resolution_param_type_text");
|
|
assertIncludes("stdlib table call checking argument facts", stdlibTableCallBody, "z_call_resolution_param_type");
|
|
assertNotIncludes("stdlib table call checking argument facts", stdlibTableCallBody, "std_call_arg_type");
|
|
|
|
const stdlibTableDispatchBody = sliceBetween(checker, "static bool check_stdlib_table_call_expected", "static bool check_stdlib_known_call_expected");
|
|
assertIncludes("stdlib table call checking argument facts", stdlibTableDispatchBody, "check_stdlib_table_arg_range_expected");
|
|
|
|
const stdlibKnownCallBody = sliceBetween(checker, "static bool check_stdlib_known_call_expected", "static bool check_stdlib_call_expected");
|
|
assertIncludes("stdlib known call checking argument facts", stdlibKnownCallBody, "ZCallResolution *resolution");
|
|
assertIncludes("stdlib known call helper classification", stdlibKnownCallBody, "z_std_helper_kind");
|
|
assertIncludes("stdlib known call helper classification", stdlibKnownCallBody, "switch (kind)");
|
|
assertIncludes("stdlib known call checking argument facts", stdlibKnownCallBody, "check_stdlib_mem_get_call_expected");
|
|
assertIncludes("stdlib known call checking argument facts", stdlibKnownCallBody, "check_stdlib_table_call_expected");
|
|
assertNotIncludes("stdlib known call helper classification", stdlibKnownCallBody, "strcmp(");
|
|
|
|
const choiceCallBody = sliceBetween(checker, "static bool check_choice_constructor_call_expected", "static bool check_shape_namespace_call_expected");
|
|
assertIncludes("choice call checking argument facts", choiceCallBody, "resolve_choice_constructor_call");
|
|
assertIncludes("choice call checking argument facts", choiceCallBody, "z_call_resolution_add_arg(&choice_resolution");
|
|
assertIncludes("choice call checking argument facts", choiceCallBody, "call_resolution_param_type_text(&choice_resolution");
|
|
assertIncludes("choice call checking argument facts", choiceCallBody, "z_call_resolution_expected_arg_count");
|
|
assertBefore("choice call resolution order", choiceCallBody, "resolve_choice_constructor_call", "find_choice(program");
|
|
|
|
const shapeNamespaceCallBody = sliceBetween(checker, "static bool check_shape_namespace_call_expected", "static bool receiver_member_call_should_resolve");
|
|
assertIncludes("shape namespace call checking argument facts", shapeNamespaceCallBody, "call_resolution_record_param_facts");
|
|
assertIncludes("shape namespace call checking argument facts", shapeNamespaceCallBody, "check_call_resolution_args_expected");
|
|
assertIncludes("shape namespace call checking argument facts", shapeNamespaceCallBody, "z_call_resolution_expected_arg_count");
|
|
assertIncludes("shape namespace call checking argument facts", shapeNamespaceCallBody, "set_expr_checked_type_args");
|
|
assertIncludes("shape namespace call checking storage effects", shapeNamespaceCallBody, "apply_checked_call_storage_effects");
|
|
|
|
const receiverCallBody = sliceBetween(checker, "static bool check_receiver_method_receiver_access", "static bool check_constrained_interface_call_expected");
|
|
assertIncludes("receiver call checking argument facts", receiverCallBody, "resolve_receiver_shape_call");
|
|
assertIncludes("receiver call checking argument facts", receiverCallBody, "call_resolution_record_param_facts");
|
|
assertIncludes("receiver call checking argument facts", receiverCallBody, "check_call_resolution_args_expected");
|
|
assertIncludes("receiver call checking argument facts", receiverCallBody, "z_call_resolution_expected_arg_count");
|
|
assertIncludes("receiver call checking argument facts", receiverCallBody, "set_expr_checked_type_args");
|
|
assertIncludes("receiver call checking storage effects", receiverCallBody, "apply_checked_call_storage_effects");
|
|
|
|
const constrainedInterfaceCallBody = sliceBetween(checker, "static bool check_constrained_interface_call_expected", "static bool check_world_stream_write_call_expected");
|
|
assertIncludes("constrained interface call checking argument facts", constrainedInterfaceCallBody, "call_resolution_record_param_facts");
|
|
assertIncludes("constrained interface call checking argument facts", constrainedInterfaceCallBody, "check_call_resolution_args_expected");
|
|
assertIncludes("constrained interface call checking argument facts", constrainedInterfaceCallBody, "z_call_resolution_expected_arg_count");
|
|
assertIncludes("constrained interface call checking argument facts", constrainedInterfaceCallBody, "set_expr_checked_type_args");
|
|
assertIncludes("constrained interface call checking storage effects", constrainedInterfaceCallBody, "apply_checked_call_storage_effects");
|
|
|
|
const callExprBody = sliceBetween(checker, "static bool check_call_expr_expected", "static bool check_expr_expected");
|
|
assertIncludes("call expression checking dispatch", callExprBody, "check_choice_constructor_call_expected");
|
|
assertIncludes("call expression checking dispatch", callExprBody, "check_shape_namespace_call_expected");
|
|
assertIncludes("call expression checking dispatch", callExprBody, "check_receiver_shape_call_expected");
|
|
assertIncludes("call expression checking dispatch", callExprBody, "check_constrained_interface_call_expected");
|
|
assertIncludes("call expression checking dispatch", callExprBody, "check_named_function_call_expected");
|
|
assertIncludes("call expression checking dispatch", callExprBody, "check_stdlib_call_expected");
|
|
assertBefore("stdlib call resolution before fallback argument checks", callExprBody, "check_stdlib_call_expected", "for (size_t i = 0; i < expr->args.len; i++)");
|
|
|
|
const stdlibCallBody = sliceBetween(checker, "static bool check_stdlib_call_expected", "static bool check_choice_constructor_call_expected");
|
|
assertIncludes("stdlib call checking dispatch", stdlibCallBody, "resolve_stdlib_call");
|
|
assertIncludes("stdlib call checking dispatch", stdlibCallBody, "check_stdlib_known_call_expected");
|
|
assertIncludes("stdlib call checking fallibility", stdlibCallBody, "check_stdlib_call_fallibility_expected");
|
|
assertIncludes("stdlib call checking dispatch", stdlibCallBody, "z_call_resolution_free");
|
|
assertIncludes("stdlib call checking dispatch", stdlibCallBody, "z_call_resolution_expected_arg_count");
|
|
assertNotIncludes("stdlib call checking dispatch", stdlibCallBody, "std_call_arg_count");
|
|
|
|
const stdlibResolverBody = sliceBetween(checker, "static bool resolve_stdlib_callee", "static bool resolve_choice_constructor_call");
|
|
assertIncludes("stdlib call resolver facts", stdlibResolverBody, "z_std_helper_find");
|
|
assertIncludes("stdlib call resolver facts", stdlibResolverBody, "z_call_resolution_set_return_type(out, helper->return_type)");
|
|
assertIncludes("stdlib call resolver facts", stdlibResolverBody, "helper->arg_types");
|
|
assertIncludes("stdlib call resolver facts", stdlibResolverBody, "z_call_resolution_add_arg");
|
|
assertIncludes("stdlib call resolver facts", stdlibResolverBody, "z_std_helper_error_name");
|
|
assertIncludes("stdlib call resolver facts", stdlibResolverBody, "z_call_resolution_add_error");
|
|
assertIncludes("stdlib call resolver facts", stdlibResolverBody, "resolve_stdlib_fallible_call");
|
|
assertNotIncludes("stdlib call resolver facts", stdlibResolverBody, "std_call_arg_count");
|
|
|
|
const callFunctionContextBody = sliceBetween(checker, "static const Function *resolve_call_function_in_context", "static bool expr_call_has_error_flow");
|
|
assertIncludes("fallible call function context resolver", callFunctionContextBody, "resolve_named_function_call");
|
|
assertIncludes("fallible call function context resolver", callFunctionContextBody, "resolve_shape_namespace_call");
|
|
assertIncludes("fallible call function context resolver", callFunctionContextBody, "resolve_concrete_constrained_shape_call");
|
|
assertIncludes("fallible call function context resolver", callFunctionContextBody, "resolve_constrained_interface_call");
|
|
assertIncludes("fallible call function context resolver", callFunctionContextBody, "resolve_receiver_shape_call");
|
|
assertNotIncludes("fallible call function context resolver", callFunctionContextBody, "find_shape_method_decl");
|
|
|
|
const memberCallSkipBody = sliceBetween(checker, "static bool member_call_skips_callee_expr_check", "static bool check_call_callee");
|
|
assertIncludes("member callee precheck", memberCallSkipBody, "member_root_ident_is");
|
|
assertNotIncludes("member callee precheck", memberCallSkipBody, "member_name_buf");
|
|
|
|
const functionErrorFlowBody = sliceBetween(
|
|
checker,
|
|
"static bool function_has_error_flow_inner(CheckContext *ctx, const Program *program, const Function *fun, size_t depth) {",
|
|
"static bool function_has_error_flow(CheckContext"
|
|
);
|
|
assertIncludes("fallible function body context", functionErrorFlowBody, "find_shape_owning_method");
|
|
assertIncludes("fallible function body context", functionErrorFlowBody, "fun_ctx.shape");
|
|
|
|
const uncheckedFallibleCallBody = sliceBetween(checker, "static bool check_unchecked_fallible_call_expected", "static bool check_call_expr_expected");
|
|
assertIncludes("unchecked fallible call resolver", uncheckedFallibleCallBody, "fallible_callee_in_context");
|
|
assertNotIncludes("unchecked fallible call resolver", uncheckedFallibleCallBody, "resolve_stdlib_fallible_call");
|
|
assertNotIncludes("unchecked fallible call resolver", uncheckedFallibleCallBody, "fallible_callee(ctx");
|
|
|
|
const callCalleeBody = sliceBetween(checker, "static bool check_call_callee", "static bool build_named_call_bindings_expected");
|
|
assertIncludes("call callee precheck", callCalleeBody, "member_call_skips_callee_expr_check");
|
|
assertNotIncludes("call callee precheck", callCalleeBody, "resolve_shape_namespace_call");
|
|
assertNotIncludes("call callee precheck", callCalleeBody, "resolve_constrained_interface_call");
|
|
assertNotIncludes("call callee precheck", callCalleeBody, "resolve_stdlib_call");
|
|
|
|
const receiverShouldResolveBody = sliceBetween(checker, "static bool receiver_member_call_should_resolve", "static bool check_receiver_method_receiver_access");
|
|
assertIncludes("receiver resolution gate", receiverShouldResolveBody, "member_root_ident_is");
|
|
assertIncludes("receiver resolution gate", receiverShouldResolveBody, "resolve_stdlib_call");
|
|
assertNotIncludes("receiver resolution gate", receiverShouldResolveBody, "member_name_buf");
|
|
|
|
const checkCallBody = sliceBetween(
|
|
checker,
|
|
"static bool check_expr_expected(CheckContext *ctx, const Program *program, const Expr *expr, Scope *scope, ZDiag *diag, const char *expected) {",
|
|
"case EXPR_CAST:"
|
|
);
|
|
assertIncludes("call checking dispatch", checkCallBody, "check_call_expr_expected");
|
|
|
|
const checkedCallBody = sliceBetween(checker, "static bool apply_checked_call_storage_effects", "static bool apply_resolved_call_storage_effects");
|
|
assertIncludes("checked call storage effects", checkedCallBody, "resolve_provenance_call");
|
|
assertIncludes("checked call storage effects", checkedCallBody, "apply_provenance_call_storage_effects");
|
|
|
|
const stdMemGetProvenanceBody = sliceBetween(checker, "static bool std_mem_get_value_provenance", "static bool value_provenance_add_actual_place");
|
|
assertIncludes("std.mem.get provenance resolver", stdMemGetProvenanceBody, "resolve_stdlib_call");
|
|
assertIncludes("std.mem.get provenance resolver", stdMemGetProvenanceBody, "z_std_helper_kind");
|
|
assertIncludes("std.mem.get provenance resolver", stdMemGetProvenanceBody, "z_call_resolution_add_arg");
|
|
assertNotIncludes("std.mem.get provenance resolver", stdMemGetProvenanceBody, "member_name_buf");
|
|
assertNotIncludes("std.mem.get provenance resolver", stdMemGetProvenanceBody, "strcmp(");
|
|
|
|
const summaryBody = sliceBetween(checker, "static bool function_provenance_summary", "static bool function_return_value_provenance");
|
|
assertIncludes("function provenance summary", summaryBody, "collect_return_value_provenance_from_stmt_vec");
|
|
assertIncludes("function provenance summary", summaryBody, "body_complete");
|
|
assertIncludes("function provenance summary", summaryBody, "seed_param_storage_value_provenance");
|
|
assertIncludes("function provenance summary", summaryBody, "provenance_storage_effect_vec_add");
|
|
assertIncludes("function provenance summary", summaryBody, "return summary->return_complete && summary->effect_complete");
|
|
assertIncludes("choice type provenance", checker, "const Choice *choice = find_choice(program, type)");
|
|
assertIncludes("choice constructor provenance", checker, "choice_constructor_value_provenance(ctx, program, expr, scope, origins)");
|
|
const choiceConstructorBody = sliceBetween(checker, "static bool choice_constructor_value_provenance", "static void register_match_payload_binding_provenance");
|
|
assertIncludes("choice constructor provenance", choiceConstructorBody, "resolve_choice_constructor_call");
|
|
assertIncludes("choice constructor provenance", choiceConstructorBody, "z_call_resolution_add_arg");
|
|
assertIncludes("choice match payload provenance", checker, "register_match_payload_binding_provenance(ctx, program, stmt->expr, scope, &arm_scope, arm->payload_name, arm->case_name)");
|
|
|
|
const storageSummaryBody = sliceBetween(checker, "static bool function_storage_effect_summary", "static bool apply_provenance_storage_effect");
|
|
assertIncludes("storage-effect summary", storageSummaryBody, "function_provenance_summary");
|
|
|
|
const returnSummaryBody = sliceBetween(checker, "static bool function_return_value_provenance", "static const Expr *call_actual_for_param");
|
|
assertIncludes("return summary", returnSummaryBody, "function_provenance_summary");
|
|
|
|
const expressionEffectBody = sliceBetween(checker, "static bool apply_expr_call_storage_effects", "static bool check_assignment_not_borrowed");
|
|
assertIncludes("expression storage effects", expressionEffectBody, "if (expr->kind == EXPR_CALL) return apply_resolved_call_storage_effects");
|
|
assertIncludes("expression storage effects", expressionEffectBody, "provenance_scope_snapshot_restore_optional_branch");
|
|
assertIncludes("array literal provenance", checker, "value_provenance_add_all_with_prefix(origins, &item_origins, element_path)");
|
|
assertIncludes("assignment path clearing", checker, "origin_path_is_definitely_within(entry->value_path, path)");
|
|
|
|
const checkExprExpectedBody = sliceBetween(
|
|
checker,
|
|
"static bool check_expr_expected(CheckContext *ctx, const Program *program, const Expr *expr, Scope *scope, ZDiag *diag, const char *expected) {",
|
|
"static bool check_expr(CheckContext *ctx, const Program *program, const Expr *expr, Scope *scope, ZDiag *diag) {"
|
|
);
|
|
const callCase = sliceBetween(checkExprExpectedBody, "case EXPR_CALL:", "case EXPR_CAST:");
|
|
assertIncludes("call checking dispatcher", callCase, "check_call_expr_expected");
|
|
const callCaseStorageApplications = (callCase.match(/apply_checked_call_storage_effects\(ctx, program, expr, scope, diag\)/g) ?? []).length;
|
|
const namedCallStorageApplications = (namedCallBody.match(/apply_checked_call_storage_effects\(ctx, program, expr, scope, diag\)/g) ?? []).length;
|
|
const shapeNamespaceCallStorageApplications = (shapeNamespaceCallBody.match(/apply_checked_call_storage_effects\(ctx, program, expr, scope, diag\)/g) ?? []).length;
|
|
const receiverCallStorageApplications = (receiverCallBody.match(/apply_checked_call_storage_effects\(ctx, program, expr, scope, diag\)/g) ?? []).length;
|
|
const constrainedInterfaceCallStorageApplications = (constrainedInterfaceCallBody.match(/apply_checked_call_storage_effects\(ctx, program, expr, scope, diag\)/g) ?? []).length;
|
|
const totalCallStorageApplications =
|
|
callCaseStorageApplications +
|
|
namedCallStorageApplications +
|
|
shapeNamespaceCallStorageApplications +
|
|
receiverCallStorageApplications +
|
|
constrainedInterfaceCallStorageApplications;
|
|
if (totalCallStorageApplications < 4) {
|
|
fail(`EXPR_CALL provenance: expected storage-effect application for all user call forms, found ${totalCallStorageApplications}`);
|
|
}
|
|
|
|
const lines = checker.split("\n");
|
|
const currentFunction = currentFunctionByLine(checker);
|
|
const mutationAllowlist = new Map([
|
|
["scope_set_value_provenance(", new Set(["scope_set_value_provenance", "register_borrow_binding", "seed_param_storage_value_provenance", "register_match_payload_binding_provenance"])],
|
|
["scope_set_value_provenance_path_in_scope(", new Set(["scope_set_value_provenance_path_in_scope", "update_borrow_assignment", "assignment_provenance_snapshot_clear", "assignment_provenance_snapshot_restore", "apply_provenance_storage_effect", "stdlib_install_item_write_provenance"])],
|
|
["scope_borrow_counts_for_place(", new Set(["scope_borrow_counts_for_place", "check_borrow_conflict_at", "check_read_not_mutably_borrowed", "check_assignment_not_borrowed"])],
|
|
]);
|
|
|
|
for (let index = 0; index < lines.length; index++) {
|
|
const line = lines[index];
|
|
if (line.trimStart().startsWith("static ")) continue;
|
|
for (const [needle, allowed] of mutationAllowlist) {
|
|
if (!line.includes(needle)) continue;
|
|
const owner = currentFunction[index];
|
|
if (!allowed.has(owner)) {
|
|
fail(`checker provenance bypass: '${needle}' at ${checkerPath}:${index + 1} is in '${owner}', not ${[...allowed].join(", ")}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
assertMatches(
|
|
"callee-local storage rejection",
|
|
checker,
|
|
/cannot store a reference to a callee-local binding through a mutable parameter/
|
|
);
|
|
assertMatches(
|
|
"incomplete mutref summary rejection",
|
|
checker,
|
|
/cannot verify provenance effects for mutable parameter call/
|
|
);
|
|
assertMatches(
|
|
"shorter-lived storage rejection",
|
|
checker,
|
|
/cannot assign a reference to a shorter-lived binding/
|
|
);
|
|
|
|
if (failures.length > 0) {
|
|
console.error("provenance guardrails failed:");
|
|
for (const failure of failures) console.error(`- ${failure}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log(`provenance guardrails ok (${rows.length} surfaces)`);
|