diff --git a/bend2/cache.ts b/bend2/cache.ts new file mode 100644 index 0000000000..201b3a6ec7 --- /dev/null +++ b/bend2/cache.ts @@ -0,0 +1,529 @@ +#!/usr/bin/env bun +// CgenCache stores generated C with a source identity and artifact digest. +// cli_emit uses a matching cache entry or performs ordinary generation. +// Foreign-only refresh applies one exact substitution when all other source +// identities match. Invalid or incomplete entries cause ordinary generation. +// +// This module performs file I/O and hashing. It does not run a compiler or +// linker. Cache hits reuse stored bytes for an identical source identity. + +import * as crypto from "node:crypto"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import * as url from "node:url"; + +import * as Bend from "./bend.ts"; + +export const KEY_SCHEMA = "cgen-ts-cache-key-v1"; +export const RECEIPT_SCHEMA = "cgen-ts-cache-receipt-v1"; + +const HERE = path.dirname(url.fileURLToPath(import.meta.url)); +const COMPILER_FILES = ["comp.ts", "bend.ts", "main.ts", "cache.ts", + "base.bend"]; + +export function cache_dir(): string { + return process.env.CGEN_CACHE_DIR + ?? path.join(os.tmpdir(), "cgen-ts-cache"); +} + +function sha_bytes(data: Buffer | string): string { + return crypto.createHash("sha256").update(data).digest("hex"); +} + +function sha_file(p: string): string | null { + try { + return sha_bytes(fs.readFileSync(p)); + } catch { + return null; + } +} + +function canon(value: unknown): string { + if (value === null || typeof value !== "object") { + return JSON.stringify(value) ?? "null"; + } + if (Array.isArray(value)) { + return "[" + value.map(canon).join(",") + "]"; + } + return "{" + Object.keys(value as Record).sort() + .map((k) => JSON.stringify(k) + ":" + canon( + (value as Record)[k])).join(",") + "}"; +} + +export interface KeyResult { + key: string; + manifest: Record; +} + +type FileRow = { path: string; sha256: string; bytes: number }; + +function is_record(value: unknown): value is Record { + return value !== null && typeof value === "object" + && !Array.isArray(value); +} + +function is_digest(value: unknown): value is string { + return typeof value === "string" && /^[0-9a-f]{64}$/.test(value); +} + +function is_file_rows(value: unknown): value is FileRow[] { + return Array.isArray(value) && value.every((row) => is_record(row) + && typeof row.path === "string" && is_digest(row.sha256) + && Number.isSafeInteger(row.bytes) && (row.bytes as number) >= 0); +} + +function is_manifest(value: unknown): value is Record { + if (!is_record(value) || value.schema !== KEY_SCHEMA + || !is_file_rows(value.k1_compiler) + || !is_record(value.k2_toolchain) + || typeof value.k2_toolchain.bun_version !== "string" + || !Array.isArray(value.k3_closure_ordered) + || !value.k3_closure_ordered.every((part) => typeof part === "string") + || !is_file_rows(value.k3_closure_files) + || !Array.isArray(value.k3_namespaces) + || !value.k3_namespaces.every((row) => Array.isArray(row) + && row.length === 2 && typeof row[0] === "string" + && (typeof row[1] === "string" || row[1] === null)) + || typeof value.k3_entry !== "string" + || typeof value.k4_laws_proof !== "boolean" + || !is_file_rows(value.k5_effs) + || typeof value.k6_emit_kind !== "string" + || !Array.isArray(value.k7_lib_hub) + || typeof value.k8_diagnostics !== "string" + || !is_record(value.k9_host)) { + return false; + } + const host = value.k9_host; + return typeof host.platform === "string" && typeof host.arch === "string" + && typeof host.cc === "string" && typeof host.no_telemetry === "string"; +} + +type CacheReceipt = { + schema: typeof RECEIPT_SCHEMA; + key: string; + artifact_sha256: string; + artifact_bytes: number; + compiler_invoked: false; + manifest: Record; +}; + +function is_receipt(value: unknown, key: string): value is CacheReceipt { + return is_record(value) && value.schema === RECEIPT_SCHEMA + && value.key === key && is_digest(value.artifact_sha256) + && Number.isSafeInteger(value.artifact_bytes) + && (value.artifact_bytes as number) >= 0 + && value.compiler_invoked === false && is_manifest(value.manifest) + && sha_bytes(canon(value.manifest)) === key; +} + +function file_rows(paths: string[]): { path: string; sha256: string; + bytes: number }[] | null { + const rows: { path: string; sha256: string; bytes: number }[] = []; + for (const p of paths) { + let real: string; + try { + real = fs.realpathSync(p); + } catch { + return null; + } + const digest = sha_file(real); + if (digest === null) { + return null; + } + rows.push({ path: real, sha256: digest, + bytes: fs.statSync(real).size }); + } + return rows; +} + +// The complete fail-closed key. null means uncertain: rebuild, never reuse. +export function key_of(book: Bend.Book, seen: Map, + entry: string): KeyResult | null { + const compiler = file_rows(COMPILER_FILES.map((f) => path.join(HERE, f))); + if (compiler === null) { + return null; + } + let entryReal: string; + try { + entryReal = fs.realpathSync(entry); + } catch { + return null; + } + const closure = file_rows([...seen.keys()].sort()); + if (closure === null) { + return null; + } + const foreign: string[] = []; + for (const tld of Object.values(book.tlds)) { + if (tld.$ === "Def" && (tld as Bend.Def & { i?: string[] }).i + !== undefined) { + foreign.push(...((tld as Bend.Def & { i: string[] }).i)); + } + } + const effs = file_rows([...new Set(foreign)].sort()); + if (effs === null) { + return null; + } + const bun = (globalThis as { Bun?: { version?: string } }).Bun?.version + ?? "nobun"; + const manifest = { + schema: KEY_SCHEMA, + k1_compiler: compiler, + k2_toolchain: { bun_version: bun }, + k3_closure_ordered: book.order, + k3_closure_files: closure, + k3_namespaces: [...seen.entries()].sort(), + k3_entry: entryReal, + k4_laws_proof: path.basename(entryReal) === "PROOF.bend", + k5_effs: effs, + k6_emit_kind: "c", + k7_lib_hub: [], + k8_diagnostics: process.env.BEND_COMPILER_DIAGNOSTICS ?? "", + k9_host: { platform: process.platform, arch: process.arch, + cc: process.env.CC ?? "", + no_telemetry: process.env.BEND_NO_TELEMETRY ?? "" }, + }; + return { key: sha_bytes(canon(manifest)), manifest }; +} + +function entry_paths(dir: string, key: string): Record { + const base = path.join(dir, key); + return { base, receipt: path.join(base, "receipt.json"), + digest: path.join(base, "receipt.sha256"), + artifact: path.join(base, "artifact.c") }; +} + +export type Lookup = + | { status: "hit"; key: string; artifact: string } + | { status: "miss"; key: string } + | { status: "uncertain" } + | { status: "invalid"; key: string; reason: string }; + +export function lookup(dir: string, + keyed: KeyResult | null): Lookup { + if (keyed === null) { + return { status: "uncertain" }; + } + const paths = entry_paths(dir, keyed.key); + if (!fs.existsSync(paths.artifact)) { + return { status: "miss", key: keyed.key }; + } + try { + const raw = fs.readFileSync(paths.receipt); + const recorded = fs.readFileSync(paths.digest, "utf8").trim(); + if (sha_bytes(raw) !== recorded) { + return { status: "invalid", key: keyed.key, + reason: "receipt digest mismatch" }; + } + const receipt: unknown = JSON.parse(raw.toString()); + if (!is_receipt(receipt, keyed.key)) { + return { status: "invalid", key: keyed.key, + reason: "receipt shape or key mismatch" }; + } + const bytes = fs.readFileSync(paths.artifact); + if (sha_bytes(bytes) !== receipt.artifact_sha256) { + return { status: "invalid", key: keyed.key, + reason: "artifact hash mismatch" }; + } + return { status: "hit", key: keyed.key, artifact: paths.artifact }; + } catch { + return { status: "invalid", key: keyed.key, reason: "entry unreadable" }; + } +} + +function atomic_json(file: string, obj: unknown): void { + const tmp = file + ".tmp-" + process.pid; + fs.writeFileSync(tmp, JSON.stringify(obj, null, 2) + "\n"); + const fd = fs.openSync(tmp, "r"); + fs.fsyncSync(fd); + fs.closeSync(fd); + fs.renameSync(tmp, file); +} + +export function store(dir: string, keyed: KeyResult, + c: string): "stored" | "existing" | "refused" { + if (/\bundefined\b/.test(c)) { + return "refused"; + } + const paths = entry_paths(dir, keyed.key); + if (fs.existsSync(paths.base)) { + return "existing"; + } + try { + fs.mkdirSync(dir, { recursive: true }); + const tmp = path.join(dir, ".tmp-" + process.pid + "-" + keyed.key); + fs.mkdirSync(tmp, { recursive: true }); + const receipt = { schema: RECEIPT_SCHEMA, key: keyed.key, + manifest: keyed.manifest, artifact_sha256: sha_bytes(c), + artifact_bytes: Buffer.byteLength(c, "utf8"), + compiler_invoked: false }; + atomic_json(path.join(tmp, "receipt.json"), receipt); + const digest = sha_file(path.join(tmp, "receipt.json")) ?? ""; + fs.writeFileSync(path.join(tmp, "receipt.sha256"), digest + "\n"); + fs.writeFileSync(path.join(tmp, "artifact.c"), c); + const foreigns = keyed.manifest.k5_effs as + { path: string; sha256: string }[]; + foreigns.forEach((row, i) => { + fs.copyFileSync(row.path, path.join(tmp, "foreign-" + i + ".bin")); + }); + fs.renameSync(tmp, paths.base); + return "stored"; + } catch { + return "refused"; + } +} + + +// Conservative detector for compiler-owned foreign-fragment identifier +// transforms: CID(...)/FID(...) call syntax in code spans, with comments +// and strings skipped by the compiler's own skip pattern. A hit declines +// raw refresh; the full rebuild then applies (or reports) the real +// transform. Absence is exact: without such syntax the rewrite is the +// identity, so verbatim substitution holds. +function has_id_transform(src: string): boolean { + const code = src.split( + /\/\/(?:\\\n|.)*|\/\*[^]*?\*\/|"(?:\\[^]|[^"\\\n])*"|'(?:\\[^]|[^'\\\n])*'|`(?:\\[^]|[^`\\])*`/g).join("\n"); + return /\b[CF]ID\(/.test(code); +} + +// Exact foreign-fragment refresh: find one origin entry whose manifest +// differs from current in exactly one foreign file's bytes (same paths, +// same Bend closure, compiler, kind, env), then substitute exactly once. +// Anything else (no candidate, several candidates, several deltas, a +// non-foreign delta, a non-unique boundary) returns null: full rebuild. +export function try_refresh(dir: string, keyed: KeyResult, + origin_c_of: (key: string) => string | null): string | null { + let names: string[]; + try { + names = fs.readdirSync(dir); + } catch { + return null; + } + const cur = keyed.manifest; + const curForeign = cur.k5_effs as { path: string; sha256: string }[]; + let match: { key: string; receipt: Record; + oldPath: string; oldBytes: Buffer } | null = null; + for (const name of names) { + if (!/^[0-9a-f]{64}$/.test(name)) { + continue; + } + let receipt: unknown; + try { + const paths = entry_paths(dir, name); + const raw = fs.readFileSync(paths.receipt); + const recorded = fs.readFileSync(paths.digest, "utf8").trim(); + if (sha_bytes(raw) !== recorded) { + continue; + } + receipt = JSON.parse(raw.toString()); + if (!is_receipt(receipt, name)) { + continue; + } + const artifact = fs.readFileSync(paths.artifact); + if (sha_bytes(artifact) !== receipt.artifact_sha256) { + continue; + } + } catch { + continue; + } + const cand = receipt.manifest; + const same = ["k1_compiler", "k2_toolchain", "k3_closure_ordered", + "k3_closure_files", "k3_namespaces", "k3_entry", "k4_laws_proof", + "k6_emit_kind", "k7_lib_hub", "k8_diagnostics", "k9_host"].every( + (k) => canon(cand[k]) === canon( + (cur as Record)[k])); + if (!same) { + continue; + } + const candForeign = cand.k5_effs as { path: string; sha256: string }[]; + if (candForeign.length !== curForeign.length + || !candForeign.every((row, i) => row.path === curForeign[i].path)) { + continue; + } + const diffs = candForeign.map((row, i) => i) + .filter((i) => candForeign[i].sha256 !== curForeign[i].sha256); + if (diffs.length !== 1) { + continue; + } + let oldBytes: Buffer; + try { + oldBytes = fs.readFileSync( + path.join(dir, name, "foreign-" + diffs[0] + ".bin")); + } catch { + continue; + } + if (sha_bytes(oldBytes) !== candForeign[diffs[0]].sha256) { + continue; + } + if (match !== null) { + return null; + } + match = { key: name, receipt, oldPath: curForeign[diffs[0]].path, + oldBytes }; + } + if (match === null) { + return null; + } + const originC = origin_c_of(match.key); + if (originC === null) { + return null; + } + let newBytes: Buffer; + try { + newBytes = fs.readFileSync(match.oldPath); + } catch { + return null; + } + if (sha_bytes(newBytes) + !== (curForeign.find((r) => r.path === match.oldPath)?.sha256 ?? "")) { + return null; + } + const oldText = match.oldBytes.toString("utf8"); + const newText = newBytes.toString("utf8"); + // Raw substitution is exact only for transform-free fragments: the + // compiler rewrites CID()/FID() call syntax on generation, so any + // identifier marker in the changed old or new bytes fails closed to a + // full rebuild, which emits (or errors on) the resolved identifiers. + if (has_id_transform(oldText) || has_id_transform(newText)) { + return null; + } + const first = originC.indexOf(oldText); + if (first < 0 || originC.indexOf(oldText, first + 1) >= 0) { + return null; + } + return originC.slice(0, first) + newBytes.toString("utf8") + + originC.slice(first + oldText.length); +} + +function log_event(dir: string, event: Record): void { + try { + fs.mkdirSync(dir, { recursive: true }); + fs.appendFileSync(path.join(dir, "log.jsonl"), + JSON.stringify(event) + "\n"); + } catch { + // Logging is audit-only; never break the build on it. + } +} + +// Cache-directory authority: the leaf directory must be a real directory +// (never a symlink), owned by the current user, and private to that user, +// and every ancestor up to the filesystem root must be a real directory, +// never a symlink (a symlinked ancestor would redirect the whole subtree). +// Anything else fails closed: the caller rebuilds with no entry read or +// stored and the audit log untouched (this check itself only creates the +// missing directory and lstats the path chain), so a shared, planted, or +// misconfigured cache path can neither serve nor poison output. Missing +// directories are created private (0700). Without a uid API ownership +// cannot be checked, so nothing is trusted either. Residual: rename or +// mount races between this check and later use are not closed here; the +// manifest, receipt, and artifact digests still gate every served byte. +export function cache_authority_ok(dir: string): boolean { + try { + fs.mkdirSync(dir, { mode: 0o700, recursive: true }); + const leaf = path.resolve(dir); + let at = leaf; + for (;;) { + const st = fs.lstatSync(at); + if (!st.isDirectory() || st.isSymbolicLink()) { + return false; + } + if (at === leaf) { + if ((st.mode & 0o077) !== 0) { + return false; + } + const me = typeof process.getuid === "function" ? process.getuid() + : -1; + if (me < 0 || st.uid !== me) { + return false; + } + } + const parent = path.dirname(at); + if (parent === at) { + return true; + } + at = parent; + } + } catch { + return false; + } +} + +export interface EmitResult { + c: string; + event: "hit" | "refresh" | "compile" | "rebuild-uncertain"; + ms: number; + key: string | null; +} + +// Default-path orchestration. compile() runs only on miss-without-refresh +// or uncertainty: a hit observably skips the intended generation work. +export function emit_c_cached(book: Bend.Book, + seen: Map, entry: string, + compile: () => string): EmitResult { + const dir = cache_dir(); + const began = performance.now(); + const done = (c: string, event: EmitResult["event"], + key: string | null): EmitResult => { + const ms = performance.now() - began; + log_event(dir, { at: new Date().toISOString(), event, key, ms }); + return { c, event, ms, key }; + }; + // An untrusted cache directory gets zero cache I/O: no read, no store, + // not even the audit log. The ordinary compiler path runs instead. + if (!cache_authority_ok(dir)) { + return { c: compile(), event: "rebuild-uncertain", + ms: performance.now() - began, key: null }; + } + // Diagnostics are observable compiler output. A cache hit skips the + // compiler traversal that produces them, so diagnostic requests always + // execute the ordinary compiler path. + if (process.env.BEND_COMPILER_DIAGNOSTICS) { + return done(compile(), "rebuild-uncertain", null); + } + const keyed = key_of(book, seen, entry); + if (keyed === null) { + return done(compile(), "rebuild-uncertain", null); + } + const found = lookup(dir, keyed); + if (found.status === "hit") { + return done(fs.readFileSync(found.artifact, "utf8"), "hit", found.key); + } + if (found.status === "invalid") { + return done(finish_compile(dir, keyed, compile), "compile", keyed.key); + } + const origin_c_of = (key: string): string | null => { + try { + const paths = entry_paths(dir, key); + const raw = fs.readFileSync(paths.receipt); + const recorded = fs.readFileSync(paths.digest, "utf8").trim(); + if (sha_bytes(raw) !== recorded) { + return null; + } + const receipt: unknown = JSON.parse(raw.toString()); + if (!is_receipt(receipt, key)) { + return null; + } + const artifact = fs.readFileSync(paths.artifact); + if (sha_bytes(artifact) !== receipt.artifact_sha256) { + return null; + } + return artifact.toString("utf8"); + } catch { + return null; + } + }; + const derived = try_refresh(dir, keyed, origin_c_of); + if (derived !== null) { + store(dir, keyed, derived); + return done(derived, "refresh", keyed.key); + } + return done(finish_compile(dir, keyed, compile), "compile", keyed.key); +} + +function finish_compile(dir: string, keyed: KeyResult, + compile: () => string): string { + const c = compile(); + store(dir, keyed, c); + return c; +} diff --git a/bend2/comp.ts b/bend2/comp.ts index 5a24c7b1b3..fe3ee1e651 100644 --- a/bend2/comp.ts +++ b/bend2/comp.ts @@ -555,6 +555,12 @@ export const PROBES: Of<"Var">[] = []; const DUMMY = probe("~"); +// A pre-opened let's key is fresh and definition-lived: no later definition or +// pass can hit one. memo_gc evicts them at each definition boundary, and +// file_new drops them outright when a new book replaces the memo maps, so a +// sequential compile in one process cannot retain the previous book's keys. +const OPEN_LET = new Set>(); + let FUEL = 0; let FL: File; @@ -690,6 +696,7 @@ function let_open(ps: Of<"Var">[], vs: HTerm[], b: HTerm): Of<"Let"> { const l = Bend.Let(ps.map((p) => p.k), ps.map(() => 0), vs, () => die("a pre-opened let")) as Of<"Let">; FL.memo.opens.set(l, { ps, b }); + OPEN_LET.add(l); return l; } @@ -1416,6 +1423,7 @@ export function io_run(book: Bend.Book, args: string[]): number { function file_new(book: Bend.Book, js: boolean): File { PROBES.length = 1; + OPEN_LET.clear(); return { book, js, @@ -1959,8 +1967,16 @@ function memo(m: Map, k: K, f: (k: K) => V): V { return v; } +// The three structural memos are pure over the frozen book, so they survive a +// definition; folds is not, and folds-only clearing stays the default here. function memo_gc(): void { - Object.values(FL.memo).forEach((m) => m.clear()); + Object.values(FL.memo).forEach((m) => { + if (m !== FL.memo.opens && m !== FL.memo.uses && m !== FL.memo.spines) { + m.clear(); + } + }); + OPEN_LET.forEach((l) => FL.memo.opens.delete(l)); + OPEN_LET.clear(); } // Show @@ -2884,7 +2900,6 @@ export function compile_book(book: Bend.Book): string { for (const [k, tld] of [...done_defs().reverse(), ...done_defs(def_foreign)]) { memo_gc(); - PROBES.length = 1; const [dl, vals] = emit_open(scope_new(), k); FL.segs.push(dl.seg); if (def_foreign(tld)) { diff --git a/bend2/main.ts b/bend2/main.ts index c1568910e2..d2a575d162 100755 --- a/bend2/main.ts +++ b/bend2/main.ts @@ -21,6 +21,7 @@ import * as thr from "node:worker_threads"; import type { BunPlugin } from "bun"; import * as Bend from "./bend.ts"; +import * as CgenCache from "./cache.ts"; import * as Comp from "./comp.ts"; import * as Safe from "./safe.ts"; @@ -319,7 +320,7 @@ async function cli_file(args: string[]): Promise { if (ins.has(at) || (fs.existsSync(at) && fs.statSync(at).isDirectory())) { cli_fail("-o " + out + " is a file the program reads, or a directory"); } - cli_emit(book, out); + cli_emit(book, out, file, seen); } } catch (e) { cli_say(2, book_err(e) + "\n"); @@ -370,13 +371,15 @@ function path_real(p: string): string { return fs.existsSync(p) ? fs.realpathSync(p) : path.resolve(p); } -function cli_emit(book: Bend.Book, out: string): void { +function cli_emit(book: Bend.Book, out: string, entry: string, + seen: Map): void { if (out.endsWith(".mjs")) { fs.writeFileSync(out, Comp.js_lib(book, true)); } else if (/\.c?js$/.test(out)) { fs.writeFileSync(out, Comp.js_book(book)); } else if (out.endsWith(".c")) { - fs.writeFileSync(out, Comp.compile_book(book)); + fs.writeFileSync(out, CgenCache.emit_c_cached(book, seen, entry, + () => Comp.compile_book(book)).c); } else if (out.endsWith(".bendtt")) { const oos = Safe.safe_emit(book, out); if (oos.length !== 0) { @@ -386,7 +389,8 @@ function cli_emit(book: Bend.Book, out: string): void { const dir = fs.mkdtempSync(path.join(os.tmpdir(), "bend-")); try { const c = path.join(dir, path.basename(out) + ".c"); - fs.writeFileSync(c, Comp.compile_book(book)); + fs.writeFileSync(c, CgenCache.emit_c_cached(book, seen, entry, + () => Comp.compile_book(book)).c); Bun.gc(true); cli_build(out, c); } finally { @@ -862,7 +866,7 @@ function book_promises(book: Bend.Book): Array<[string, string[]]> { if (t !== undefined) { const rs = new Set(); for (const c of t.$ === "ADT" ? [t, ...t.c] : [t]) { - term_refs(Bend.term_lower(c.T), rs); + term_refs_higher(c.T, rs); } if (t.$ === "Def" && t.e !== undefined) { term_refs(t.e, rs); @@ -883,6 +887,101 @@ function book_promises(book: Bend.Book): Array<[string, string[]]> { }); } +// term_refs_higher follows HTerm directly, opening binder bodies with the +// same fresh variables as term_lower while avoiding a full lowered-tree copy. +function term_refs_higher(tm: Bend.HTerm, out: Set, depth = 0): void { + const t = Bend.term_force(tm); + switch (t.$) { + case "Var": { + const v = t.v; + if (v !== undefined && v.$ !== "PVar" && v.$ !== "PCtr") { + term_refs_higher(v as Bend.HTerm, out, depth); + } + return; + } + case "Ref": { + out.add(t.k); + return; + } + case "Sub": { + const v = t.v; + if (v.$ !== "PVar" && v.$ !== "PCtr") { + term_refs_higher(v as Bend.HTerm, out, depth); + } + term_refs_higher(t.f, out, depth); + return; + } + case "Let": { + for (const v of t.v) term_refs_higher(v, out, depth); + const xs = t.k.map((k, j) => Bend.Var(k, depth + j)); + term_refs_higher(t.f(xs), out, depth + t.k.length); + return; + } + case "Typ": { + term_refs_higher(t.g, out, depth); + return; + } + case "Qnt": + case "Qua": + case "Lit": + case "Efq": + case "Rfl": + case "Hol": { + return; + } + case "Min": { + term_refs_higher(t.a, out, depth); + term_refs_higher(t.b, out, depth); + return; + } + case "All": { + term_refs_higher(t.A, out, depth); + term_refs_higher(t.B(Bend.Var(t.k, depth)), out, depth + 1); + return; + } + case "Lam": { + term_refs_higher(t.f(Bend.Var(t.k, depth)), out, depth + 1); + return; + } + case "App": { + term_refs_higher(t.f, out, depth); + term_refs_higher(t.x, out, depth); + return; + } + case "ADT": { + out.add(t.k); + for (const x of t.x) term_refs_higher(x, out, depth); + return; + } + case "Ctr": { + for (const x of t.x) term_refs_higher(x, out, depth); + return; + } + case "Mat": { + term_refs_higher(t.h, out, depth); + term_refs_higher(t.m, out, depth); + return; + } + case "Eql": { + term_refs_higher(t.a, out, depth); + term_refs_higher(t.b, out, depth); + term_refs_higher(t.T, out, depth); + return; + } + case "Rwt": { + term_refs_higher(t.e, out, depth); + term_refs_higher(t.p, out, depth); + term_refs_higher(t.f, out, depth); + return; + } + case "Ann": { + term_refs_higher(t.x, out, depth); + term_refs_higher(t.T, out, depth); + return; + } + } +} + // term_refs adds to out the names a term (a span skipped) refers to. function term_refs(t: object, out: Set): void { const seen = new Set([t]); diff --git a/tests/cgen_cache/results/fixture-ts-result.json b/tests/cgen_cache/results/fixture-ts-result.json new file mode 100644 index 0000000000..d2ae9a59d4 --- /dev/null +++ b/tests/cgen_cache/results/fixture-ts-result.json @@ -0,0 +1,1302 @@ +{ + "bun": "pinned toolchain-home/bin/bun", + "checks": [ + { + "detail": { + "at": "2026-10-17T06:41:34.862Z", + "event": "compile", + "key": "006bfce5ea6596ea504553116aa982877c175d1b00035bb8d265f612672e7cff", + "ms": 58.07412199999999 + }, + "name": "A-miss-compile-exit-0", + "pass": true + }, + { + "detail": "", + "name": "A-artifact-nonempty", + "pass": true + }, + { + "detail": { + "artifact_bytes": 79755, + "artifact_sha256": "4ebfd0c5a4b5bacd2bea8fdb3fbf572b0422b0f2c09acec54059119e861e3e5a", + "compiler_invoked": false, + "key": "006bfce5ea6596ea504553116aa982877c175d1b00035bb8d265f612672e7cff", + "manifest": { + "k1_compiler": [ + { + "bytes": 186866, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/comp.ts", + "sha256": "069aa86248b326de7f4c658d278ea458fdbd644de61ac7b1266ba9bbed0643ee" + }, + { + "bytes": 138252, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/bend.ts", + "sha256": "22b5915cd7cccd23b6aa28849b23a7f1e3549333cdd0954ab48103ddd0777e9e" + }, + { + "bytes": 42442, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/main.ts", + "sha256": "08565579081dd996ddadd0e492fa870120a2c0b5d63cf5577bd6a0d03855abff" + }, + { + "bytes": 18322, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/cache.ts", + "sha256": "78823e4f72324b8e2dce8077eb43b1ad3a19fc85cf871c3bc66bc5a2ab55e1d0" + }, + { + "bytes": 76669, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/base.bend", + "sha256": "6bfcfa4f10afdd4767a4fc7a0100b6360098701b31be34ff48546a46ae7b6f41" + } + ], + "k2_toolchain": { + "bun_version": "1.3.14" + }, + "k3_closure_files": [ + { + "bytes": 76669, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/base.bend", + "sha256": "6bfcfa4f10afdd4767a4fc7a0100b6360098701b31be34ff48546a46ae7b6f41" + }, + { + "bytes": 164, + "path": "/tmp/cgen-ts-__tjbpcf/fx/fx.bend", + "sha256": "c492e629bd29964f443fd74341179cf44549bec470f5688255583ef40dca0691" + } + ], + "k3_closure_ordered": [ + "Empty", + "Unit", + "Bool", + "Cmp", + "Either", + "Sigma", + "Nat", + "Maybe", + "Result", + "Poll", + "List", + "Word.Nil", + "Word.Con", + "Word", + "U32", + "F32", + "Char", + "String", + "Array", + "Image", + "Event", + "Map", + "File", + "Socket", + "Listener", + "Window", + "Audio", + "Chan", + "IO.OP", + "App", + "Pair", + "Exists", + "Or", + "Pair.fst", + "Pair.snd", + "IO", + "IO.pure", + "IO.bind", + "IO.print", + "IO.write", + "IO.print_err", + "IO.get_env", + "IO.args", + "Process.run", + "IO.die", + "IO.exit", + "IO.pass", + "IO.try", + "IO.random_u32", + "IO.spawn", + "IO.sleep", + "IO.now", + "IO.thread_count", + "Chan.new", + "Chan.send", + "Chan.recv", + "Chan.try_send", + "Chan.try_recv", + "Chan.close", + "IO.fork.go", + "IO.fork", + "IO.join.go", + "IO.join", + "IO.within", + "File.open", + "File.read", + "File.read_bytes", + "File.read_at", + "File.size", + "File.write", + "File.write_bytes", + "File.close", + "TCP.listen", + "TCP.accept", + "TCP.try_accept", + "TCP.connect", + "TCP.send", + "TCP.try_send", + "TCP.recv", + "TCP.try_recv", + "TCP.send_bytes", + "TCP.try_send_bytes", + "TCP.recv_bytes", + "TCP.try_recv_bytes", + "UDP.bind", + "UDP.send_to", + "UDP.try_send_to", + "UDP.send_bytes_to", + "UDP.try_send_bytes_to", + "UDP.recv_from", + "UDP.try_recv_from", + "UDP.recv_bytes_from", + "UDP.try_recv_bytes_from", + "Socket.close", + "Listener.close", + "Window.open", + "Window.frame", + "Window.set_title", + "Window.grab", + "Window.close", + "Audio.open", + "Audio.write", + "Audio.close", + "Equal.cong", + "Equal.cong", + "Equal.sym", + "Equal.sym", + "Equal.trans", + "Equal.trans", + "Empty.absurd", + "Bool.not", + "Bool.and", + "Bool.or", + "Bool.xor", + "Bool.cmp", + "Bool.full_add", + "Bool.pick", + "Bool.to_u32", + "Cmp.is_lt", + "Cmp.is_eq", + "Cmp.is_gt", + "Cmp.is_le", + "Cmp.is_ge", + "Nat.double", + "Nat.add", + "Nat.sub", + "Nat.mul", + "Nat.divmod.go", + "Nat.divmod", + "Nat.cmp", + "Nat.is_lt", + "Nat.is_eq", + "Nat.is_ne", + "Nat.is_le", + "Nat.is_gt", + "Nat.is_ge", + "Nat.min", + "Nat.max", + "Nat.ge_refl", + "Nat.ge_refl", + "Nat.max_ge_l", + "Nat.max_ge_l", + "Nat.max_ge_r", + "Nat.max_ge_r", + "Nat.div", + "Nat.mod", + "Nat.pow", + "Maybe.pure", + "Maybe.bind", + "Maybe.default", + "Maybe.is_some", + "Maybe.is_none", + "Maybe.map", + "Maybe.or", + "Result.pure", + "Result.bind", + "Result.default", + "Result.is_done", + "Result.is_fail", + "Result.map", + "List.map", + "List.length", + "List.append", + "List.concat", + "List.reverse.go", + "List.reverse", + "List.is_empty", + "List.head", + "List.tail", + "List.last.go", + "List.last", + "List.get", + "List.set", + "List.take", + "List.drop", + "List.zip", + "List.range.go", + "List.range", + "List.replicate", + "List.filter.put", + "List.filter", + "List.foldl", + "List.foldr", + "List.any", + "List.all", + "List.find.put", + "List.find", + "List.contains", + "List.merge.step", + "List.merge.go", + "List.sort.runs", + "List.sort.pass", + "List.sort.go", + "List.sort", + "List.for_each", + "Word.zero", + "Word.not", + "Word.and", + "Word.or", + "Word.xor", + "Word.shl.put", + "Word.shl", + "Word.shl.out.con", + "Word.shl.out", + "Word.shr.pad", + "Word.shr", + "Word.cmp.fin", + "Word.cmp", + "Word.inc", + "Word.adc", + "Word.adc.con", + "Word.adc", + "Word.add", + "Word.sub", + "Word.add_comm.arm", + "Word.add_comm.go", + "Word.add_comm.arm", + "Word.add_comm.go", + "Word.add_comm", + "Word.add_comm", + "Word.to_nat", + "Word.mul.go", + "Word.mul", + "U32.inc", + "U32.add", + "U32.add_comm", + "U32.add_comm", + "U32.sub", + "U32.mul", + "U32.not", + "U32.and", + "U32.or", + "U32.xor", + "U32.shl", + "U32.shr", + "U32.shln", + "U32.shrn", + "U32.cmp", + "U32.is_eq", + "U32.is_ne", + "U32.is_lt", + "U32.is_le", + "U32.is_gt", + "U32.is_ge", + "U32.is_zero", + "U32.to_nat", + "U32.from_nat", + "U32.log2.go", + "U32.log2", + "U32.divmod.go.fin", + "U32.divmod.go.shl", + "U32.divmod.go.rec", + "U32.divmod.go", + "U32.div.fin", + "U32.div.if", + "U32.div", + "U32.mod.fin", + "U32.mod.if", + "U32.mod", + "U32.min", + "U32.max", + "U32.clamp", + "U32.pow", + "U32.is_even", + "U32.to_f32", + "F32.to_u32", + "F32.add", + "F32.sub", + "F32.mul", + "F32.div", + "F32.mod", + "F32.pow", + "F32.atan2", + "F32.neg", + "F32.abs", + "F32.sqrt", + "F32.exp", + "F32.log", + "F32.log2", + "F32.log10", + "F32.sin", + "F32.cos", + "F32.tan", + "F32.asin", + "F32.acos", + "F32.atan", + "F32.sinh", + "F32.cosh", + "F32.tanh", + "F32.floor", + "F32.ceil", + "F32.trunc", + "F32.is_eq", + "F32.is_ne", + "F32.is_lt", + "F32.is_le", + "F32.is_gt", + "F32.is_ge", + "F32.show", + "F32.bits", + "F32.read", + "F32.min", + "F32.max", + "F32.clamp", + "F32.lerp", + "F32.square", + "F32.hypot", + "F32.round.zero", + "F32.round", + "F32.pi", + "F32.from_nat", + "F32.to_nat", + "Char.cmp", + "Char.to_u32", + "Char.from_u32", + "Char.is_eq", + "Char.is_digit", + "Char.is_upper", + "Char.is_lower", + "Char.is_alpha", + "Char.is_space", + "Char.to_upper", + "Char.to_lower", + "String.append", + "String.cmp.rec", + "String.cmp", + "String.cmp.fin", + "String.cmp", + "String.eq", + "String.length", + "String.is_empty", + "String.reverse.go", + "String.reverse", + "String.order.go", + "String.order", + "String.is_lt", + "String.is_le", + "String.is_gt", + "String.is_ge", + "String.starts_with", + "String.starts_with.if", + "String.starts_with", + "String.ends_with", + "String.contains", + "String.contains.if", + "String.contains", + "String.take", + "String.drop", + "String.get", + "String.to_list", + "String.from_list", + "String.concat", + "String.join.go", + "String.join", + "String.split.push", + "String.split.fin", + "String.split", + "String.lines", + "String.repeat", + "String.to_upper", + "String.to_lower", + "String.trim_start", + "String.trim_start.if", + "String.trim_start", + "String.trim_end", + "String.trim", + "Nat.show.put", + "Nat.show.go", + "Nat.show.fin", + "Nat.show.go", + "Nat.show", + "Nat.read.go", + "Nat.read.if", + "Nat.read.go", + "Nat.read.trim", + "Nat.read.fits", + "Nat.read.gate", + "Nat.read", + "U32.show.go", + "U32.show.fin", + "U32.show.go", + "U32.show.if", + "U32.show", + "U32.read.go", + "U32.read.if", + "U32.read.go", + "U32.read", + "Bool.show", + "Char.show", + "Maybe.show", + "List.show.go", + "List.show", + "Array.size.node", + "Array.size", + "Array.swap.lo", + "Array.swap.hi", + "Array.swap.go", + "Array.swap.at", + "Array.swap", + "Array.clone.node", + "Array.clone", + "Array.fork", + "Array.join", + "Array.atomic.add", + "Array.atomic.min", + "Array.atomic.max", + "Array.atomic.and", + "Array.atomic.or", + "Array.atomic.xor", + "Array.atomic.exch", + "Array.atomic.cas", + "Array.atomic.fadd", + "Array.new", + "Array.set.fin", + "Array.set", + "Array.get.go", + "Array.get.at", + "Array.get", + "Array.to_list.go", + "Array.to_list", + "Array.map.go", + "Array.map.at", + "Array.map.seed", + "Array.map", + "Map.bit.u", + "Map.bit.chr", + "Map.bit.go.chr", + "Map.bit.go.rec", + "Map.bit.go", + "Map.bit.at", + "Map.bit", + "Map.msb.u", + "Map.msb.u.if", + "Map.msb.u", + "Map.diff.chr", + "Map.diff.step", + "Map.diff", + "Map.diff.fin", + "Map.diff", + "Map.new", + "Map.put.go", + "Map.put", + "Map.ins.splice.bit", + "Map.ins.splice", + "Map.ins.go", + "Map.ins", + "Map.lo", + "Map.hi", + "Map.seek.go", + "Map.seek", + "Map.set.fin.go", + "Map.set.fin", + "Map.set.go", + "Map.set", + "Map.has.leaf", + "Map.has.go", + "Map.has", + "Map.get.leaf", + "Map.get.go", + "Map.get", + "Map.pop.lo", + "Map.pop.hi", + "Map.pop.leaf", + "Map.pop.go", + "Map.pop", + "Map.del.fin", + "Map.del", + "Map.to_list.go", + "Map.to_list", + "Map.keys.go", + "Map.keys", + "Map.from_list.go", + "Map.from_list", + "Map.union", + "Map.size", + "Map.values.go", + "Map.values", + "Set", + "Set.new", + "Set.add", + "Set.has", + "Set.del", + "Set.size", + "Set.to_list", + "Set.from_list.go", + "Set.from_list", + "Image.sink", + "Image.drop.join", + "Image.free", + "Image.drop", + "App.next", + "App.turn", + "App.draw", + "App.step", + "App.loop", + "App.run", + "App.more", + "App.fold", + "App.play", + "Fx.tag", + "main" + ], + "k3_entry": "/tmp/cgen-ts-__tjbpcf/fx/fx.bend", + "k3_namespaces": [ + [ + "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/base.bend", + "" + ], + [ + "/tmp/cgen-ts-__tjbpcf/fx/fx.bend", + "" + ] + ], + "k4_laws_proof": false, + "k5_effs": [ + { + "bytes": 351, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/args.c", + "sha256": "84e12702f78e7cce5d350a3ccc6cfbda66f812496c39f025afe02bdf090880cf" + }, + { + "bytes": 221, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/args.js", + "sha256": "ed1a7e7b73ae7fe58ac6d428d2762b66f223f4c70a165570fc5d68dc874ddc0b" + }, + { + "bytes": 6953, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/audio.c", + "sha256": "f0fac2bee02918b4958ad1e7508945824fcd98ce9a2e7ede657304c4f8905b85" + }, + { + "bytes": 118, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/audio_close.js", + "sha256": "c700e2fc0eda1f9a67afd317184999d9283912817aaec00637af41b135b84c62" + }, + { + "bytes": 273, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/audio_open.js", + "sha256": "f5a19b5f0efcd3300c27f18315bfbfeb189a2764ad0430d9aeb31dd16d495cbe" + }, + { + "bytes": 446, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/audio_write.js", + "sha256": "facb9aae3951a6b9133d58c796533ea436e05322cf35c86e558eec6194c48eb2" + }, + { + "bytes": 7342, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/chan.c", + "sha256": "551025649b6c154b3d06deb902fd780eba23c62ac6aa9b9197b32cd1a9cac21a" + }, + { + "bytes": 3636, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/chan.js", + "sha256": "d3e9e0551966dbd4f7cefaafaca4c10557ddebafb91edf62f8e947c78c555862" + }, + { + "bytes": 242, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/file_close.c", + "sha256": "033da9beb0b484fbc8784044193bf69004f858f6ad86e0c31b4beb89df1349ca" + }, + { + "bytes": 192, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/file_close.js", + "sha256": "2b83f900113776ab875fa3afd29f31000a191fe48645abe78b1d7b34cb12a7fe" + }, + { + "bytes": 1066, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/file_open.c", + "sha256": "2f5923e325a22eb384edb3fbb023f04ca842d5acd22654983608b575fa7617fa" + }, + { + "bytes": 462, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/file_open.js", + "sha256": "dd549f5f05eb896aef9a3a02ffecf2ee902e653f5463be72781edab1cd1180ae" + }, + { + "bytes": 1913, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/file_read.c", + "sha256": "ab577225fa98a034c09d7eb6326b74808756dc6203b0e2599a459876382f090d" + }, + { + "bytes": 783, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/file_read.js", + "sha256": "7aa8a95a0b02f3c8db1167da5a936958a90c64e7df3e327ba5c66766d5e57da7" + }, + { + "bytes": 719, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/file_size.c", + "sha256": "ed0d74fd6b9f14ea471bfe5dd80ecbb2c5abb6a8406519fb2c7e1a1acf3dd5af" + }, + { + "bytes": 734, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/file_size.js", + "sha256": "508a931334016f725f0446d9c63f91d7e9de79b9fb347d0e148777dba834ef0b" + }, + { + "bytes": 1348, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/file_write.c", + "sha256": "da810ad9aa76ad1431622292529fa3edebabc4b6a7ee20ae833097b71bb595e5" + }, + { + "bytes": 793, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/file_write.js", + "sha256": "fbb706714fe6724e12658ad638d8b7aa2228f6297791bf74bd4894af361776f0" + }, + { + "bytes": 397, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/get_env.c", + "sha256": "508a4aaca91e37b2a0a97770f172100fdbaf4a676aba54623b1cd5d1634805ce" + }, + { + "bytes": 359, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/get_env.js", + "sha256": "ea68796090fbfabcc3fb4a915a5b3ffa5b1df8620b1dcdea37fffa109af13e74" + }, + { + "bytes": 266, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/listener_close.c", + "sha256": "11f6bac6342a37e97b9656e96bdca7d052ed0a7a0cab3b05ea87657020cfb7a2" + }, + { + "bytes": 164, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/listener_close.js", + "sha256": "1b87fb43c373b1a8a54b5afaf0cf8e3ec2f8d39bdfde131e49527caa19ac0cb8" + }, + { + "bytes": 196, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/now.c", + "sha256": "fc4c1d87a4fb2ace7d5b6c57b434cc3d953a15bdf213020912df6c17aa2352d6" + }, + { + "bytes": 105, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/now.js", + "sha256": "f41624aba3f07d56b671f41691b5cb833b4d018266668f5f48329290fc41b2d9" + }, + { + "bytes": 322, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/print.c", + "sha256": "2693b48e0ad8bba77c2babb1cfe00b4adfd63e68b0688229d7efd3a261af3c46" + }, + { + "bytes": 138, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/print.js", + "sha256": "814ceba4efbaa291b7b6a9ff29aa20e3a23c06eb4fa165c69ee0bd22878adaa9" + }, + { + "bytes": 235, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/print_err.c", + "sha256": "f84aa11c8d0d6af6fa2fee28411f95714d6744ad97156990e43e55c292c9c6fb" + }, + { + "bytes": 131, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/print_err.js", + "sha256": "a7a00612edfe98af1a351e8f7b912af4ee33f4bb888c36f119f7712d72a9394e" + }, + { + "bytes": 8189, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/process_run.c", + "sha256": "554cfbdf9a6de163f00fc1b23dc8f3df10821cfce336caa1cb5db439b8a55e1f" + }, + { + "bytes": 2904, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/process_run.js", + "sha256": "7d1ae7a37960d574aa3e4b57214b4f4046e5a20ce8a3a48f8ee48d7ea712ce4f" + }, + { + "bytes": 703, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/random_u32.c", + "sha256": "75429b2b42e00562177fe33a5e18d2730478847f58c4038b048e78131e1b8c0a" + }, + { + "bytes": 581, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/random_u32.js", + "sha256": "ab0b37cb6055ad08573d1263aa380c339c3fe7417530cfa63264629dce7744c2" + }, + { + "bytes": 381, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/sleep.c", + "sha256": "1dde3701026517d5272d2ff5344d3a8643bec264dbad9f266fdae1c4aaf7601c" + }, + { + "bytes": 214, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/sleep.js", + "sha256": "6711ccd804dcaede381da9ff0283b4640feb440e1d413381101f22bfed65a934" + }, + { + "bytes": 254, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/socket_close.c", + "sha256": "abea115d2928d4942a9d89898d2022c0f1b19c52db48d0b060c2356d261bfa77" + }, + { + "bytes": 150, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/socket_close.js", + "sha256": "dc4bf1fa95692c91528d4eb855506dde8dfc9164a90de2c43cd1bd6916e0c380" + }, + { + "bytes": 217, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/spawn.c", + "sha256": "05e11ae3f1461d9c72440af3b1786c076b418d2faff45639ac25ba464ee73cd5" + }, + { + "bytes": 160, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/spawn.js", + "sha256": "8a14c7a7a5ff80b049f0659f9e58d43bf0f3b1a6724d35a199c3f864b3242e4b" + }, + { + "bytes": 1409, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/tcp_accept.c", + "sha256": "bf7ca1685ed388ce97827ea8379881fbdf5359c7c663ad03eeb8da34bd8584ef" + }, + { + "bytes": 1413, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/tcp_accept.js", + "sha256": "26406cce42c497994e597a7a032629fab65f43ec14f06ee69aace2b353afe28d" + }, + { + "bytes": 1292, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/tcp_connect.c", + "sha256": "8d17c097138709d71e9d9e1fbc4255007c5e80c93ee384c4a3510b6ac8d10da3" + }, + { + "bytes": 1122, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/tcp_connect.js", + "sha256": "c51c4b55ffbc4a520f01082a1307498801a17e5123bdffbd01077fb42730f1cb" + }, + { + "bytes": 875, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/tcp_listen.c", + "sha256": "efc7528de96abd9f62f299833c1ac7b0e86528292c746b114d2684b5bac52899" + }, + { + "bytes": 671, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/tcp_listen.js", + "sha256": "0da4cfafaa24ba5e17d9fda5e6a5b207b2b36031b2c888944f67b85842d76399" + }, + { + "bytes": 2508, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/tcp_recv.c", + "sha256": "249c8789cd2f541ad00068882de16a00870c2f81a8168b0ac7a59db1e3cb0c6f" + }, + { + "bytes": 1680, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/tcp_recv.js", + "sha256": "e8c54490445cb71a4328c07be8c40a49cee3e643cbf74f42dc73d31ab95890d7" + }, + { + "bytes": 3042, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/tcp_send.c", + "sha256": "ed724ef8ce712b113eaac478164802509b41fac4e92435e3df0837d6a8c75632" + }, + { + "bytes": 1976, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/tcp_send.js", + "sha256": "ea68cc521fa9b5603c3fccebbf19ecebcd26bf1a8af644ff1f13cd14ba9fe733" + }, + { + "bytes": 343, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/thread_count.c", + "sha256": "d4889ed7757dccce03729c9568b88188128f2c1576166a3dc184a8707ed44568" + }, + { + "bytes": 137, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/thread_count.js", + "sha256": "f4ef6d8ded95fe3114e50b7d2b6eb873e693419b85150f681b4a54a5043c2de9" + }, + { + "bytes": 744, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/udp_bind.c", + "sha256": "a1be1bc5c03d430fc6eb43982651073e144872956fadaee98c3700be59352ee3" + }, + { + "bytes": 502, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/udp_bind.js", + "sha256": "e3afae5bf246184e76703efbcf41cb35bf5289e746cc40dc0300eef98753dfb5" + }, + { + "bytes": 3005, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/udp_recv_from.c", + "sha256": "607085b34605af6c888e1de5c3f5cd8f689f221c71a3ef9e2c50f9ac880b1bae" + }, + { + "bytes": 2094, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/udp_recv_from.js", + "sha256": "d6f88b08f578f088b4ff257a7e544e4fbbf46f375aee3881ba4d44e25ed890d8" + }, + { + "bytes": 3484, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/udp_send_to.c", + "sha256": "b3a87e56958d8071a368da2edb4748e9496b3eed58b93d6cb4f8af398dd1011a" + }, + { + "bytes": 2168, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/udp_send_to.js", + "sha256": "40c835c4d73eb15d089a41b425968d04931d02820cb901aadb8eb3dd4905f247" + }, + { + "bytes": 32131, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/window.c", + "sha256": "bf8d5779775ba9566d4bbeb8706a9ec993efb724dae7af8a76c38501ab4092c2" + }, + { + "bytes": 124, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/window_close.js", + "sha256": "be4ef11e9ac57c59aa4aee847f15ece18034ca9fe1c4a68c6e0b0d7bbd7c3590" + }, + { + "bytes": 153, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/window_frame.js", + "sha256": "78baa53aa46e02165c0c94da68d1f6362386ebcfb97dbb36fffb82792a03d34f" + }, + { + "bytes": 115, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/window_grab.js", + "sha256": "55cfc1925114f2d1f177137fad24d6252fe28707d26a1aae18a98ad97060a668" + }, + { + "bytes": 344, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/window_open.js", + "sha256": "cd716864b8dfb27ca955c8aa58643c7d693ce76eb56472bd8255ff7ac0656e8d" + }, + { + "bytes": 133, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/window_set_title.js", + "sha256": "eee0bf26b7f381f8800364e22e8c735e47dd6db3f58e23ff3dda8478a8411513" + }, + { + "bytes": 295, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/write.c", + "sha256": "8807a5f655db452c1d846354560c26fe1e94289e5dc0be231d0b71ecc876d02f" + }, + { + "bytes": 131, + "path": "/run/baton2-native-homelab/retained/baton-compiler-forkflush-20261016T195048Z/worktrees/upstream-pr1649-amend/bend2/effs/write.js", + "sha256": "e3495066a5d8c53fe1c46cfd2bb32577e3bd7dcae9797808801599073504dacb" + }, + { + "bytes": 221, + "path": "/tmp/cgen-ts-__tjbpcf/fx/host.c", + "sha256": "fb46acf73279510d8ec297d7fe9c06a521db6a6b3cb887f8111b9dcb18be3a9f" + }, + { + "bytes": 51, + "path": "/tmp/cgen-ts-__tjbpcf/fx/host.js", + "sha256": "ee3bb658cee084ec1cacd123200f0064175ca0e8e3d02e2f7ba483ba2e7e7805" + } + ], + "k6_emit_kind": "c", + "k7_lib_hub": [], + "k8_diagnostics": "", + "k9_host": { + "arch": "x64", + "cc": "", + "no_telemetry": "1", + "platform": "linux" + }, + "schema": "cgen-ts-cache-key-v1" + }, + "schema": "cgen-ts-cache-receipt-v1" + }, + "name": "A-receipt-counts-utf8-bytes", + "pass": true + }, + { + "detail": { + "at": "2026-10-17T06:41:35.208Z", + "event": "hit", + "key": "006bfce5ea6596ea504553116aa982877c175d1b00035bb8d265f612672e7cff", + "ms": 10.493516999999997 + }, + "name": "B-hit-exit-0", + "pass": true + }, + { + "detail": "4ebfd0c5a4b5 vs 4ebfd0c5a4b5", + "name": "B-byte-identical", + "pass": true + }, + { + "detail": "hit 10.5ms < compile 58.1ms (tool log)", + "name": "B-hit-skips-generation", + "pass": true + }, + { + "detail": { + "at": "2026-10-17T06:41:35.656Z", + "event": "compile", + "key": "229e3182ae88a8dca18ca1aa2f560be602f13ae178c891ea3c734009e6690332", + "ms": 129.968883 + }, + "name": "C-bend-mutation-miss", + "pass": true + }, + { + "detail": { + "at": "2026-10-17T06:41:35.991Z", + "event": "rebuild-uncertain", + "key": null, + "ms": 26.462030999999968 + }, + "name": "F-diagnostics-env-miss", + "pass": true + }, + { + "detail": { + "at": "2026-10-17T06:41:36.301Z", + "event": "rebuild-uncertain", + "key": null, + "ms": 25.852926000000025 + }, + "name": "G-diagnostics-bypass-same-key", + "pass": true + }, + { + "detail": "4ebfd0c5a4b5 vs 4ebfd0c5a4b5", + "name": "G-diagnostics-byte-identical", + "pass": true + }, + { + "detail": { + "at": "2026-10-17T06:41:36.642Z", + "event": "refresh", + "key": "72034537cf023e17dc18da1464117667698547b5746b45fd160facb8b8f0e800", + "ms": 31.19836600000002 + }, + "name": "D-foreign-refresh", + "pass": true + }, + { + "detail": { + "at": "2026-10-17T06:41:36.971Z", + "event": "compile", + "key": "72034537cf023e17dc18da1464117667698547b5746b45fd160facb8b8f0e800", + "ms": 45.07010199999999 + }, + "name": "E-fresh-compile", + "pass": true + }, + { + "detail": "bf3550464bc8 vs bf3550464bc8", + "name": "E-refresh-byte-identical-to-fresh", + "pass": true + }, + { + "detail": { + "at": "2026-10-17T06:41:37.345Z", + "event": "compile", + "key": "006bfce5ea6596ea504553116aa982877c175d1b00035bb8d265f612672e7cff", + "ms": 56.270992000000035 + }, + "name": "artifact-origin-compiled", + "pass": true + }, + { + "detail": { + "at": "2026-10-17T06:41:37.755Z", + "event": "compile", + "key": "72034537cf023e17dc18da1464117667698547b5746b45fd160facb8b8f0e800", + "ms": 59.920375999999976 + }, + "name": "artifact-origin-rebuilds", + "pass": true + }, + { + "detail": "bf3550464bc8 vs bf3550464bc8", + "name": "artifact-repair-equals-fresh", + "pass": true + }, + { + "detail": { + "at": "2026-10-17T06:41:38.473Z", + "event": "compile", + "key": "006bfce5ea6596ea504553116aa982877c175d1b00035bb8d265f612672e7cff", + "ms": 58.20810799999998 + }, + "name": "receipt-origin-compiled", + "pass": true + }, + { + "detail": { + "at": "2026-10-17T06:41:39.560Z", + "event": "compile", + "key": "72034537cf023e17dc18da1464117667698547b5746b45fd160facb8b8f0e800", + "ms": 736.260775 + }, + "name": "receipt-origin-rebuilds", + "pass": true + }, + { + "detail": "bf3550464bc8 vs bf3550464bc8", + "name": "receipt-repair-equals-fresh", + "pass": true + }, + { + "detail": { + "at": "2026-10-17T06:41:40.482Z", + "event": "compile", + "key": "006bfce5ea6596ea504553116aa982877c175d1b00035bb8d265f612672e7cff", + "ms": 129.86825700000003 + }, + "name": "null-manifest-origin-compiled", + "pass": true + }, + { + "detail": { + "at": "2026-10-17T06:41:40.956Z", + "event": "compile", + "key": "72034537cf023e17dc18da1464117667698547b5746b45fd160facb8b8f0e800", + "ms": 96.14610199999998 + }, + "name": "null-manifest-malformed-origin-rebuilds", + "pass": true + }, + { + "detail": "bf3550464bc8 vs bf3550464bc8", + "name": "null-manifest-repair-equals-fresh", + "pass": true + }, + { + "detail": { + "at": "2026-10-17T06:41:41.871Z", + "event": "compile", + "key": "006bfce5ea6596ea504553116aa982877c175d1b00035bb8d265f612672e7cff", + "ms": 115.486042 + }, + "name": "missing-manifest-key-origin-compiled", + "pass": true + }, + { + "detail": { + "at": "2026-10-17T06:41:42.364Z", + "event": "compile", + "key": "72034537cf023e17dc18da1464117667698547b5746b45fd160facb8b8f0e800", + "ms": 119.992773 + }, + "name": "missing-manifest-key-malformed-origin-rebuilds", + "pass": true + }, + { + "detail": "bf3550464bc8 vs bf3550464bc8", + "name": "missing-manifest-key-repair-equals-fresh", + "pass": true + }, + { + "detail": { + "at": "2026-10-17T06:41:43.276Z", + "event": "compile", + "key": "006bfce5ea6596ea504553116aa982877c175d1b00035bb8d265f612672e7cff", + "ms": 78.28144200000003 + }, + "name": "null-foreign-rows-origin-compiled", + "pass": true + }, + { + "detail": { + "at": "2026-10-17T06:41:44.488Z", + "event": "compile", + "key": "72034537cf023e17dc18da1464117667698547b5746b45fd160facb8b8f0e800", + "ms": 904.317843 + }, + "name": "null-foreign-rows-malformed-origin-rebuilds", + "pass": true + }, + { + "detail": "bf3550464bc8 vs bf3550464bc8", + "name": "null-foreign-rows-repair-equals-fresh", + "pass": true + }, + { + "detail": { + "at": "2026-10-17T06:41:45.465Z", + "event": "compile", + "key": "006bfce5ea6596ea504553116aa982877c175d1b00035bb8d265f612672e7cff", + "ms": 220.92069100000003 + }, + "name": "malformed-foreign-row-origin-compiled", + "pass": true + }, + { + "detail": { + "at": "2026-10-17T06:41:45.835Z", + "event": "compile", + "key": "72034537cf023e17dc18da1464117667698547b5746b45fd160facb8b8f0e800", + "ms": 71.55967000000001 + }, + "name": "malformed-foreign-row-malformed-origin-rebuilds", + "pass": true + }, + { + "detail": "bf3550464bc8 vs bf3550464bc8", + "name": "malformed-foreign-row-repair-equals-fresh", + "pass": true + }, + { + "detail": { + "at": "2026-10-17T06:41:47.156Z", + "event": "compile", + "key": "72034537cf023e17dc18da1464117667698547b5746b45fd160facb8b8f0e800", + "ms": 142.12701799999996 + }, + "name": "I-setup-compile", + "pass": true + }, + { + "detail": { + "at": "2026-10-17T06:41:47.567Z", + "event": "compile", + "key": "49996998b31460955e3125bbd05e22f59978368d4f124bbdd84370f447122092", + "ms": 88.83771999999999 + }, + "name": "I-cid-decline-compiles", + "pass": true + }, + { + "detail": { + "at": "2026-10-17T06:41:48.026Z", + "event": "compile", + "key": "49996998b31460955e3125bbd05e22f59978368d4f124bbdd84370f447122092", + "ms": 121.36231399999997 + }, + "name": "I-fresh-compile", + "pass": true + }, + { + "detail": "928d38965095 vs 928d38965095", + "name": "I-decline-parity", + "pass": true + }, + { + "detail": { + "at": "2026-10-17T06:41:48.519Z", + "event": "compile", + "key": "49996998b31460955e3125bbd05e22f59978368d4f124bbdd84370f447122092", + "ms": 179.71180399999997 + }, + "name": "J-fresh-compile", + "pass": true + }, + { + "detail": [], + "name": "J-open-mode-empty-events", + "pass": true + }, + { + "detail": "0 entries", + "name": "J-open-mode-dir-unchanged", + "pass": true + }, + { + "detail": "928d38965095 vs 928d38965095", + "name": "J-open-mode-fresh-parity", + "pass": true + }, + { + "detail": [], + "name": "J-symlink-dir-empty-events", + "pass": true + }, + { + "detail": "0 entries", + "name": "J-symlink-dir-dir-unchanged", + "pass": true + }, + { + "detail": "928d38965095 vs 928d38965095", + "name": "J-symlink-dir-fresh-parity", + "pass": true + }, + { + "detail": [], + "name": "J-ancestor-symlink-empty-events", + "pass": true + }, + { + "detail": "0 entries", + "name": "J-ancestor-symlink-dir-unchanged", + "pass": true + }, + { + "detail": "928d38965095 vs 928d38965095", + "name": "J-ancestor-symlink-fresh-parity", + "pass": true + } + ], + "clang_invoked": false, + "compiler_invoked": true, + "failed": 0, + "main_artifacts_used": false, + "network_used": false, + "note": "compiler_invoked=true ONLY for bounded synthetic fixtures through the instrumented default path; no Main build, no release impact. Event log proves compile() skipped on hit. No generation-time saving claimed beyond observed per-event ms.", + "passed": 45, + "runs": [ + { + "event": "compile", + "event_ms": 58.07412199999999, + "exit": 0, + "rss_kib": 123940, + "step": "A-first-build", + "wall_s": 0.39855027699741186 + }, + { + "event": "hit", + "event_ms": 10.493516999999997, + "exit": 0, + "rss_kib": 115332, + "step": "B-rebuild", + "wall_s": 0.34681968099903315 + }, + { + "event": "compile", + "event_ms": 129.968883, + "exit": 0, + "rss_kib": 121340, + "step": "C-bend-mutation", + "wall_s": 0.45611875299800886 + }, + { + "event": "refresh", + "event_ms": 31.19836600000002, + "exit": 0, + "rss_kib": 121268, + "step": "D-foreign-refresh", + "wall_s": 0.32361053400018136 + }, + { + "event": "compile", + "event_ms": 45.07010199999999, + "exit": 0, + "rss_kib": 118172, + "step": "E-fresh-of-changed-tree", + "wall_s": 0.32941351200133795 + } + ], + "tool": "compiler/cache.ts via compiler/main.ts default path" +} \ No newline at end of file diff --git a/tests/cgen_cache/run_ts_fixture.py b/tests/cgen_cache/run_ts_fixture.py new file mode 100644 index 0000000000..a74dba4655 --- /dev/null +++ b/tests/cgen_cache/run_ts_fixture.py @@ -0,0 +1,393 @@ +#!/usr/bin/env python3 +"""Bounded TS-flow fixture for compiler/cache.ts (no Main artifacts). + +Runs the pinned Bun against THIS worktree's bend2/main.ts on a tiny +foreign-effect fixture (pure temp dirs): miss+compile+store, rebuild hit with +byte-identity and skipped generation, Bend-mutation miss, diagnostics-env +miss/bypass parity, foreign-only refresh, and refresh-vs-fresh byte-identity, +CID-introducing foreign edit declining raw refresh with fresh parity, +untrusted cache directories (open mode, symlink) rebuilding with zero cache I/O. +Records wall/RSS per bun invocation via /usr/bin/time -v plus the tool's own +log.jsonl event/ms evidence (the event log is what proves compile() was or +was not invoked). Writes tests/results/fixture-ts-result.json. + +EXECUTION GATE: this fixture launches the Bend compiler on synthetic +fixtures. Per tasks/COMMON.md it must not run without Principal Conductor +approval. The gate refuses unless BATON_COMPILER_RUN_APPROVED=1; without +approval the committed status is NOT RUN (no result file is written and no +pass is claimed). +""" + +import hashlib +import json +import os +import re +import subprocess +import sys +import tempfile + +WORKTREE = os.path.normpath(os.path.join( + os.path.dirname(os.path.abspath(__file__)), "..", "..")) +MAIN_TS = os.path.join(WORKTREE, "bend2", "main.ts") +BUN = ("/run/baton2-native-homelab/retained/baton2-compiler-741-20261015" + "/postrelease-751-761-20261015-11/toolchain-home/bin/bun") +HERE = os.path.dirname(os.path.abspath(__file__)) +RESULTS = os.path.normpath(os.path.join(HERE, "results", + "fixture-ts-result.json")) + +FX_BEND = """import Base + +def Fx.tag() -> IO(U32): + import "./host.c" + import "./host.js" + +def main() -> IO(Unit): + do IO: + t : U32 <- Fx.tag() + IO.print("done") +""" +HOST_C = """// IO +// == +// Unicode byte-count fixture: λ + +Term io_fx_tag_run(Env e, Term* f, IoWork* w) { + return (Term)%d; +} + +static void __attribute__((constructor)) io_fx_tag_use(void) { + io_eff(CID_FX_TAG, io_fx_tag_run, 0); +} +""" +HOST_JS = """// IO +// == + +function io_fx_tag() { + return 7n; +} +""" +HOST_CID = """// IO +// == + +Term io_fx_tag_run(Env e, Term* f, IoWork* w) { + return (Term)%d; +} + +static void __attribute__((constructor)) io_fx_tag_use(void) { + io_eff(CID(Fx.tag), io_fx_tag_run, 0); + io_eff(CID_FX_TAG, io_fx_tag_run, 0); +} +""" + +ENV = {"PATH": "/usr/bin:/bin", "BEND_NO_TELEMETRY": "1", + "HOME": os.environ.get("HOME", "/tmp")} + + +def sha(path): + digest = hashlib.sha256() + with open(path, "rb") as handle: + for chunk in iter(lambda: handle.read(1 << 20), b""): + digest.update(chunk) + return digest.hexdigest() + + +def build(fx_dir, cache_dir, out_name, extra_env=None): + """One bounded fixture build. Returns (returncode, out_c, wall_s, rss_kib, + log_events).""" + import time + env = dict(ENV) + env["CGEN_CACHE_DIR"] = cache_dir + if extra_env: + env.update(extra_env) + cmd = ["/usr/bin/time", "-v", BUN, MAIN_TS, + os.path.join(fx_dir, "fx.bend"), "-o", + os.path.join(fx_dir, out_name)] + start = time.perf_counter() + proc = subprocess.run(cmd, cwd=fx_dir, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, env=env) + wall = time.perf_counter() - start + err = proc.stderr.decode() + rss = None + match = re.search(r"Maximum resident set size \(kbytes\): (\d+)", err) + if match: + rss = int(match.group(1)) + events = [] + log_path = os.path.join(cache_dir, "log.jsonl") + if os.path.isfile(log_path): + with open(log_path) as handle: + events = [json.loads(line) for line in handle if line.strip()] + return proc.returncode, os.path.join(fx_dir, out_name), wall, rss, events + + +def main(): + if os.environ.get("BATON_COMPILER_RUN_APPROVED") != "1": + print("REFUSED: set BATON_COMPILER_RUN_APPROVED=1 only with " + "Principal Conductor approval (tasks/COMMON.md). " + "Status: NOT RUN, no pass claimed.") + return 2 + checks = [] + + def check(name, condition, detail=""): + checks.append({"name": name, "pass": bool(condition), + "detail": detail}) + + runs = [] + with tempfile.TemporaryDirectory(prefix="cgen-ts-") as tmp: + fx = os.path.join(tmp, "fx") + os.makedirs(fx) + with open(os.path.join(fx, "fx.bend"), "w") as handle: + handle.write(FX_BEND) + with open(os.path.join(fx, "host.c"), "w") as handle: + handle.write(HOST_C % 7) + with open(os.path.join(fx, "host.js"), "w") as handle: + handle.write(HOST_JS) + cache = os.path.join(tmp, "cache") + + code, c1, wall, rss, events = build(fx, cache, "a.c") + runs.append({"step": "A-first-build", "exit": code, "wall_s": wall, + "rss_kib": rss, + "event": events[-1]["event"] if events else None, + "event_ms": events[-1]["ms"] if events else None}) + check("A-miss-compile-exit-0", code == 0 + and events and events[-1]["event"] == "compile", + events[-1] if events else "no log") + check("A-artifact-nonempty", + code == 0 and os.path.getsize(c1) > 1000) + first_key = events[-1]["key"] if events else None + receipt_path = (os.path.join(cache, first_key, "receipt.json") + if first_key else "") + receipt = None + if receipt_path and os.path.isfile(receipt_path): + with open(receipt_path) as handle: + receipt = json.load(handle) + check("A-receipt-counts-utf8-bytes", + receipt is not None and receipt.get("artifact_bytes") + == os.path.getsize(c1), receipt) + + code, c2, wall, rss, events = build(fx, cache, "b.c") + runs.append({"step": "B-rebuild", "exit": code, "wall_s": wall, + "rss_kib": rss, + "event": events[-1]["event"] if events else None, + "event_ms": events[-1]["ms"] if events else None}) + check("B-hit-exit-0", code == 0 + and events[-1]["event"] == "hit", events[-1]) + check("B-byte-identical", sha(c1) == sha(c2), + "%s vs %s" % (sha(c1)[:12], sha(c2)[:12])) + compile_ms = runs[0]["event_ms"] + hit_ms = runs[1]["event_ms"] + check("B-hit-skips-generation", + hit_ms is not None and compile_ms is not None + and hit_ms < compile_ms, + "hit %.1fms < compile %.1fms (tool log)" % (hit_ms or -1, + compile_ms or -1)) + + with open(os.path.join(fx, "fx.bend"), "a") as handle: + handle.write("# fixture comment change\n") + code, c3, wall, rss, events = build(fx, cache, "c.c") + runs.append({"step": "C-bend-mutation", "exit": code, "wall_s": wall, + "rss_kib": rss, + "event": events[-1]["event"] if events else None, + "event_ms": events[-1]["ms"] if events else None}) + check("C-bend-mutation-miss", code == 0 + and events[-1]["event"] == "compile", events[-1]) + + with open(os.path.join(fx, "fx.bend"), "w") as handle: + handle.write(FX_BEND) + diag_env = {"BEND_COMPILER_DIAGNOSTICS": "1"} + code, cdiag1, _, _, events = build( + fx, cache, "d.c", extra_env=diag_env) + check("F-diagnostics-env-miss", code == 0 + and events[-1]["event"] == "rebuild-uncertain", events[-1]) + code, cdiag2, _, _, events = build( + fx, cache, "d2.c", extra_env=diag_env) + check("G-diagnostics-bypass-same-key", code == 0 + and events[-1]["event"] == "rebuild-uncertain", events[-1]) + check("G-diagnostics-byte-identical", code == 0 + and sha(cdiag1) == sha(cdiag2), + "%s vs %s" % (sha(cdiag1)[:12], sha(cdiag2)[:12])) + + with open(os.path.join(fx, "host.c"), "w") as handle: + handle.write(HOST_C % 8) + code, c4, wall, rss, events = build(fx, cache, "e.c") + runs.append({"step": "D-foreign-refresh", "exit": code, + "wall_s": wall, "rss_kib": rss, + "event": events[-1]["event"] if events else None, + "event_ms": events[-1]["ms"] if events else None}) + check("D-foreign-refresh", code == 0 + and events[-1]["event"] == "refresh", events[-1]) + + fresh_cache = os.path.join(tmp, "fresh-cache") + code, c5, wall, rss, events = build(fx, fresh_cache, "f.c") + runs.append({"step": "E-fresh-of-changed-tree", "exit": code, + "wall_s": wall, "rss_kib": rss, + "event": events[-1]["event"] if events else None, + "event_ms": events[-1]["ms"] if events else None}) + check("E-fresh-compile", code == 0 + and events[-1]["event"] == "compile", events[-1]) + check("E-refresh-byte-identical-to-fresh", sha(c4) == sha(c5), + "%s vs %s" % (sha(c4)[:12], sha(c5)[:12])) + + # A damaged cache entry must never become the source of a foreign + # refresh. Exercise artifact and receipt integrity independently. + for label in ("artifact", "receipt"): + with open(os.path.join(fx, "fx.bend"), "w") as handle: + handle.write(FX_BEND) + with open(os.path.join(fx, "host.c"), "w") as handle: + handle.write(HOST_C % 7) + corrupt_cache = os.path.join(tmp, "corrupt-" + label) + code, origin, _, _, events = build( + fx, corrupt_cache, label + "-origin.c") + key = events[-1]["key"] if events else None + check(label + "-origin-compiled", code == 0 and key is not None + and events[-1]["event"] == "compile", events[-1]) + if key is None: + continue + entry = os.path.join(corrupt_cache, key) + if label == "artifact": + with open(os.path.join(entry, "artifact.c"), "ab") as handle: + handle.write(b"damaged") + else: + with open(os.path.join(entry, "receipt.sha256"), "w") as handle: + handle.write("0" * 64 + "\n") + with open(os.path.join(fx, "host.c"), "w") as handle: + handle.write(HOST_C % 8) + code, repaired, _, _, events = build( + fx, corrupt_cache, label + "-repaired.c") + check(label + "-origin-rebuilds", code == 0 + and events[-1]["event"] == "compile", events[-1]) + fresh_cache = os.path.join(tmp, "fresh-" + label) + code, fresh, _, _, _ = build( + fx, fresh_cache, label + "-fresh.c") + check(label + "-repair-equals-fresh", + code == 0 and sha(repaired) == sha(fresh), + "%s vs %s" % (sha(repaired)[:12], sha(fresh)[:12])) + + # Well-formed JSON can still contain an unusable origin receipt. + # Refresh scans must skip each malformed shape and run ordinary codegen. + malformed_cases = { + "null-manifest": lambda receipt: receipt.update(manifest=None), + "missing-manifest-key": lambda receipt: receipt["manifest"].pop( + "k3_entry"), + "null-foreign-rows": lambda receipt: receipt["manifest"].update( + k5_effs=None), + "malformed-foreign-row": lambda receipt: receipt["manifest"].update( + k5_effs=[None]), + } + for label, corrupt in malformed_cases.items(): + with open(os.path.join(fx, "fx.bend"), "w") as handle: + handle.write(FX_BEND) + with open(os.path.join(fx, "host.c"), "w") as handle: + handle.write(HOST_C % 7) + corrupt_cache = os.path.join(tmp, "malformed-" + label) + code, _, _, _, events = build( + fx, corrupt_cache, label + "-origin.c") + key = events[-1]["key"] if events else None + check(label + "-origin-compiled", code == 0 and key is not None + and events[-1]["event"] == "compile", events[-1]) + if key is None: + continue + entry = os.path.join(corrupt_cache, key) + receipt_path = os.path.join(entry, "receipt.json") + with open(receipt_path) as handle: + receipt = json.load(handle) + corrupt(receipt) + raw = json.dumps(receipt, indent=2).encode() + b"\n" + with open(receipt_path, "wb") as handle: + handle.write(raw) + with open(os.path.join(entry, "receipt.sha256"), "w") as handle: + handle.write(hashlib.sha256(raw).hexdigest() + "\n") + with open(os.path.join(fx, "host.c"), "w") as handle: + handle.write(HOST_C % 8) + code, repaired, _, _, events = build( + fx, corrupt_cache, label + "-repaired.c") + check(label + "-malformed-origin-rebuilds", code == 0 + and events[-1]["event"] == "compile", events[-1]) + fresh_cache = os.path.join(tmp, "fresh-malformed-" + label) + code, fresh, _, _, _ = build( + fx, fresh_cache, label + "-fresh.c") + check(label + "-repair-equals-fresh", + code == 0 and sha(repaired) == sha(fresh), + "%s vs %s" % (sha(repaired)[:12], sha(fresh)[:12])) + + i_cache = os.path.join(tmp, "i-cache") + code, i1, _, _, events = build(fx, i_cache, "i1.c") + check("I-setup-compile", code == 0 + and events[-1]["event"] == "compile", events[-1]) + with open(os.path.join(fx, "host.c"), "w") as handle: + handle.write(HOST_CID % 8) + code, i2, _, _, events = build(fx, i_cache, "i2.c") + check("I-cid-decline-compiles", code == 0 + and events[-1]["event"] == "compile", events[-1]) + i_fresh = os.path.join(tmp, "i-fresh") + code, i3, _, _, events = build(fx, i_fresh, "i3.c") + check("I-fresh-compile", code == 0 + and events[-1]["event"] == "compile", events[-1]) + check("I-decline-parity", code == 0 + and sha(i2) == sha(i3), + "%s vs %s" % (sha(i2)[:12], sha(i3)[:12])) + + # Refused directories get zero artifact I/O: event logs stay empty + # and the directory tree is byte-unchanged. Outputs are anchored to + # a separately built trusted-cache binary, not to each other. + # Writable ancestors are trusted by design (ownership is enforced + # at the leaf only); a hostile rename there remains a documented + # residual, still gated per served byte by digests. + def snapshot(d): + return {os.path.relpath(os.path.join(r, f), d): sha( + os.path.join(r, f)) for r, _, fs in os.walk( + d, followlinks=False) for f in fs} + + j_fresh = os.path.join(tmp, "j-fresh") + code, jf, _, _, events = build(fx, j_fresh, "jf.c") + check("J-fresh-compile", code == 0 + and events[-1]["event"] == "compile", events[-1]) + for jlabel in ("J-open-mode", "J-symlink-dir", + "J-ancestor-symlink"): + j_cache = os.path.join(tmp, "j-" + jlabel) + if jlabel == "J-open-mode": + os.makedirs(j_cache) + os.chmod(j_cache, 0o777) + elif jlabel == "J-symlink-dir": + real = os.path.join(tmp, "j-" + jlabel + "-real") + os.makedirs(real) + os.symlink(real, j_cache) + else: + areal = os.path.join(tmp, "j-" + jlabel + "-areal") + os.makedirs(os.path.join(areal, "sub")) + os.symlink(areal, os.path.join(tmp, "j-" + jlabel)) + j_cache = os.path.join(tmp, "j-" + jlabel, "sub") + before = snapshot(j_cache) + code, j1, _, _, events1 = build(fx, j_cache, "j1.c") + code2, j2, _, _, events2 = build(fx, j_cache, "j2.c") + check(jlabel + "-empty-events", code == 0 and code2 == 0 + and events1 == [] and events2 == [], + [e.get("event") for e in events1 + events2]) + check(jlabel + "-dir-unchanged", + snapshot(j_cache) == before, + "%d entries" % len(before)) + check(jlabel + "-fresh-parity", code == 0 and code2 == 0 + and sha(j1) == sha(jf) and sha(j2) == sha(jf), + "%s vs %s" % (sha(j1)[:12], sha(jf)[:12])) + + + + failed = [c for c in checks if not c["pass"]] + result = {"tool": "compiler/cache.ts via compiler/main.ts default path", + "bun": "pinned toolchain-home/bin/bun", + "checks": checks, "runs": runs, + "passed": len(checks) - len(failed), "failed": len(failed), + "compiler_invoked": True, "clang_invoked": False, + "network_used": False, "main_artifacts_used": False, + "note": "compiler_invoked=true ONLY for bounded synthetic " + "fixtures through the instrumented default path; no " + "Main build, no release impact. Event log proves " + "compile() skipped on hit. No generation-time saving " + "claimed beyond observed per-event ms."} + os.makedirs(os.path.dirname(RESULTS), exist_ok=True) + with open(RESULTS, "w") as handle: + json.dump(result, handle, indent=2, sort_keys=True) + print("%d/%d checks passed" % (result["passed"], len(checks))) + return 1 if failed else 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/cli_report_refs/check.sh b/tests/cli_report_refs/check.sh new file mode 100755 index 0000000000..860989052f --- /dev/null +++ b/tests/cli_report_refs/check.sh @@ -0,0 +1,19 @@ +#!/bin/sh +set -eu +root=$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd -P) +bun=${BUN:-bun} +out=$(mktemp -d) +trap 'rm -rf "$out"' EXIT +set +e +BEND_NO_TELEMETRY=1 "$bun" "$root/bend2/main.ts" "$root/tests/cli_report_refs/dependent_type.bend" --check-only >"$out/stdout" 2>"$out/stderr" +status=$? +set -e +[ "$status" -eq 1 ] +[ ! -s "$out/stdout" ] +cat >"$out/expected" <<'EOF' +SOME PROOFS FAIL +Error: 2 defs rely on unsafe or foreign code: +defs that depend on @unsafe: Rp.BadType, Rp.dependent +EOF +cmp "$out/expected" "$out/stderr" +printf 'dependent type reference report passed\n' diff --git a/tests/cli_report_refs/dependent_type.bend b/tests/cli_report_refs/dependent_type.bend new file mode 100644 index 0000000000..8a453d5a79 --- /dev/null +++ b/tests/cli_report_refs/dependent_type.bend @@ -0,0 +1,13 @@ +import Base + +@unsafe +def Rp.BadType() -> Type: + Nat + +def Rp.dependent(next: @+n: Nat -> Rp.BadType()) -> Nat: + 0n + +def main() -> IO(Unit): + IO.print("ok") + +#|ok