diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 818c72f25..75ef61bf2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -212,6 +212,11 @@ jobs: NODE_OPTIONS: "--no-experimental-webstorage --max-old-space-size=4096" CI: "true" + # QNBS-v3: node:test tooling scripts (.mjs) fall outside Vitest's include glob (.ts/.tsx only) -- + # this is the one authoritative step admitting them to routine CI, not ad-hoc scattered invocation. + - name: Unit tests (node:test, tooling scripts) + run: pnpm run test:node + - name: Coverage ratchet check (informational, non-blocking) if: always() continue-on-error: true diff --git a/.gitignore b/.gitignore index 2f9167382..057f61fa3 100644 --- a/.gitignore +++ b/.gitignore @@ -109,6 +109,9 @@ voice-spike/ *.crt *.der .storycraft-* +# Test fixture temp roots created under cwd by dependency-state/signing/verify-exact-tree tests -- +# normally cleaned up in afterEach, but an interrupted run can leave these visible to git status. +.worldscript-* ~/ .playwright-mcp/ diff --git a/CLAUDE.md b/CLAUDE.md index aa420ca0a..5f5418791 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -45,6 +45,8 @@ pnpm run token:audit # audit-tokens.mjs — design-token usage gate (CI b **Quality gate (local pre-push subset):** `pnpm run ci:prepush` runs dependency-state/docs/CSP/native-readiness checks unconditionally (never full-repository lint — see the pre-push gate note above for what runs lint locally), and the single-checker local typecheck and i18n/content-guard checks only for changes the classifier marks as potentially impacting them (fail-closed to "run everything conditional" when evidence is incomplete); CI additionally runs full-repository lint, the 4-checker typecheck, full-suite coverage, and heavy jobs regardless of what the local gate ran. Locally use only the targeted form `pnpm exec vitest run --coverage` when debugging coverage. Full pipeline graph: [`docs/CI.md`](docs/CI.md). Coverage thresholds: lines 74, branches 60, functions 67, statements 72 (see `vitest.config.ts`). +**Exact-tree typecheck verification (opt-in, not part of `ci:prepush`):** `pnpm run verify:exact-tree [ref...]` (default `HEAD`) proves the *exact committed tree* of a ref typechecks in full isolation from the live checkout. Before touching disk, it lists the target commit's own git tree (`dependency-state.mjs`'s `listTreeFiles`, reused rather than a second parser) and refuses (`UNKNOWN`) any commit force-tracking a `node_modules` path anywhere (root, nested, or a tracked `node_modules` symlink itself) — an arbitrary ref could otherwise smuggle in an attacker-controlled `node_modules/.bin/tsgo` for the tool to trust and execute. It then creates an isolated `git worktree` at the exact commit with Git hooks disabled for that one invocation (`git -c core.hooksPath= worktree add --detach`, cleaned up immediately after) — this repo's `graphify:hooks` `post-checkout` integration, or any other configured hook, must never fire during materialization. Inside that hook-free worktree it runs a real `pnpm install --frozen-lockfile --offline --ignore-scripts --ignore-pnpmfile` (`COREPACK_ENABLE_NETWORK=0` too, since `pnpm` here is a Corepack shim that could otherwise reach the network before pnpm's own `--offline` applies; `--ignore-pnpmfile` blocks pnpm's own hook-file mechanism, a separate arbitrary-code path from `--ignore-scripts`), reconstructing pnpm's own real dependency-resolution graph (root, package-local, and transitive workspace links) rather than symlinking the live checkout's `node_modules` — an earlier symlink-based design was found, in review, to leak uncommitted/live workspace-package source back into the "isolated" result. Finally it runs the same single-checker `tsgo --noEmit` inside that tree with an explicit 6-minute timeout (measured the single-checker run alone at ~56s here; the repo separately documents ~300s for the full multi-checker `pnpm run typecheck`, so 6 minutes clears both figures with real margin, not a bare 300s cutoff). A missing package in the local offline store, an unreadable tree, a hook-dir failure, or a signal/timeout always fails the install/typecheck step and reports `UNKNOWN`, never a silent wrong answer — this tool never falls back to the network and never treats an unprovable state as a pass. Closes the gap where `ci-prepush-lowend.mjs`'s normal typecheck runs against whatever is currently on disk, not necessarily the exact tree about to be pushed. Deliberately **not** wired into the default `ci:prepush` path — the real install alone measures over a minute on this hardware, unacceptable as an always-on default; run it manually before a risky push or when investigating a CI/local typecheck mismatch. Diagnostic-only: reports `PASS | FAIL | NOT_APPLICABLE | UNKNOWN` and never blocks a push on its own; required CI remains the sole merge-safety authority regardless of its result. Its `node:test` suite (`scripts/verify-exact-tree.mjs`'s own tooling test, alongside `scripts/dependency-state.mjs`'s) runs serially (`--test-concurrency=1`, since these fixtures interleave git/pnpm/worktree operations that must not overlap on this hardware) via `pnpm run test:node`, wired into CI's quality job separately from Vitest (whose include glob is `.ts`/`.tsx`-only). + **CI pipeline order:** `security` → `quality` (Biome + tsgo + Vitest matrix) → `build` / `e2e` / `storybook` (parallel) → `lighthouse` (after build) → `deploy` on `main`. `ci-success` is a required-status aggregator (`needs: [security, quality, build]`) so branch protection can require one context instead of three/four individual ones — see `docs/CI.md`. Two additional jobs run in parallel with `quality`, both path-scoped via the `changes` job (legitimately `skipping` on PRs that don't touch their directory, which `ci-success` treats as a pass for that job only): `rust-tauri` (`src-tauri/**` — fmt/check/clippy/test, needs the GTK/WebKit apt-get steps) and `core-rust` (`crates/**` — same fmt/check/clippy/test for the renderer-neutral Rust Core, no GUI deps so no apt-get steps needed). **CI-cloud-first workflow (constrained local hardware only):** On low-end hardware, run only `ci:prepush` locally before pushing. Coverage, E2E, Lighthouse, and Stryker are CI-gate jobs. After each push, update README.md badges and AUDIT.md quality-gate line with CI-reported numbers. Local CI simulation: `act pull_request --job quality` (Docker + `act`; see `infra/low-end-ci/DAILY-DRIVER.md`). diff --git a/biome.json b/biome.json index 66be3be5a..2c1dd1fef 100644 --- a/biome.json +++ b/biome.json @@ -135,7 +135,7 @@ } }, { - "includes": ["tests/**/*.ts", "tests/**/*.tsx"], + "includes": ["tests/**/*.ts", "tests/**/*.tsx", "tests/**/*.mjs"], "linter": { "rules": { "suspicious": { diff --git a/package.json b/package.json index ddbc47b7b..85ef383db 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "deps:verify": "node scripts/dependency-state.mjs verify", "deps:reconcile": "node scripts/dependency-state.mjs reconcile", "ci:prepush": "node scripts/ci-prepush-lowend.mjs", + "verify:exact-tree": "node scripts/verify-exact-tree.mjs", "ci:local:full": "pnpm run lint && pnpm run typecheck && pnpm run i18n:check && pnpm run guardrail:desktop-imports && pnpm run native-readiness:check", "predev": "node scripts/sync-csp.mjs && node scripts/sync-sw-version.mjs && node scripts/sync-tauri-version.mjs && node scripts/sync-readme-metrics.mjs && node scripts/build-i18n.mjs && node scripts/copy-duckdb-assets.mjs", "dev": "vite", @@ -86,6 +87,7 @@ "test:turbo": "turbo run test", "test:run": "vitest run", "test:coverage": "vitest run --coverage", + "test:node": "node --test --test-concurrency=1 tests/unit/tooling/dependency-state.test.mjs tests/unit/tooling/verify-exact-tree.test.mjs", "bench": "vitest bench --run tests/bench", "test:e2e": "node -e \"if (process.env.CI !== 'true') { console.error('E2E tests are CI-only. Set CI=true or run via GitHub Actions.'); process.exit(1); }\" && playwright test", "test:e2e:ui": "node -e \"if (process.env.CI !== 'true') { console.error('E2E tests are CI-only. Set CI=true or run via GitHub Actions.'); process.exit(1); }\" && playwright test --ui", diff --git a/scripts/dependency-state.d.mts b/scripts/dependency-state.d.mts index ae227695a..a8fab5f7e 100644 --- a/scripts/dependency-state.d.mts +++ b/scripts/dependency-state.d.mts @@ -1,6 +1,15 @@ // QNBS-v3: diagnostic-only dimension, independent of resolvePushEvidence's canonical evidence validity. export type DependencyState = 'MATCHES' | 'DIVERGED' | 'NOT_APPLICABLE' | 'UNKNOWN'; +export interface TreeEntry { + mode: string; + type: string; + hash: string; + path: string; +} +export function listTreeEntries(sha: string, cwd?: string): TreeEntry[] | null; +export function listTreeFiles(sha: string, cwd?: string): string[] | null; +export function readFileAtRef(sha: string, relativePath: string, cwd?: string): Buffer | null; export function dependencyFiles(root?: string): string[]; export function calculateDependencyFingerprint(root?: string): string; export function fingerprintPath(root?: string): string; diff --git a/scripts/dependency-state.mjs b/scripts/dependency-state.mjs index 26df2c6e3..df7183a3d 100644 --- a/scripts/dependency-state.mjs +++ b/scripts/dependency-state.mjs @@ -76,20 +76,33 @@ export function calculateDependencyFingerprint(root = projectRoot) { return hashManifests(entries); } -// QNBS-v3: --full-tree ignores cwd-subdirectory scoping; -z disables git's default path C-quoting. -function defaultListTreeFiles(sha, cwd) { - const result = spawnSync('git', ['ls-tree', '-r', '--full-tree', '--name-only', '-z', sha], { +// QNBS-v3: -z avoids path C-quoting; includes mode so callers (e.g. symlink detection) don't need a second parser. +export function listTreeEntries(sha, cwd) { + const result = spawnSync('git', ['ls-tree', '-r', '--full-tree', '-z', sha], { cwd, encoding: 'utf8', timeout: 5000, }); if (result.error || result.status !== 0) return null; - return result.stdout.split('\0').filter(Boolean); + return result.stdout + .split('\0') + .filter(Boolean) + .map((entry) => { + const tabIndex = entry.indexOf('\t'); + const [mode, type, hash] = entry.slice(0, tabIndex).split(' '); + return { mode, type, hash, path: entry.slice(tabIndex + 1) }; + }); +} + +// QNBS-v3: exported so verify-exact-tree.mjs reuses this authority instead of a second parser. +export function listTreeFiles(sha, cwd) { + const entries = listTreeEntries(sha, cwd); + return entries === null ? null : entries.map((entry) => entry.path); } // QNBS-v3: diagnostic-only; mirrors dependencyFiles' inclusion rules against a commit, not disk. export function dependencyFilesFromRef(sha, root = projectRoot, dependencies = {}) { - const listTree = dependencies.listTree ?? ((ref) => defaultListTreeFiles(ref, root)); + const listTree = dependencies.listTree ?? ((ref) => listTreeFiles(ref, root)); const allPaths = listTree(sha); if (allPaths === null) return null; const rootFiles = new Set(['package.json', 'pnpm-lock.yaml', 'pnpm-workspace.yaml']); @@ -101,8 +114,8 @@ export function dependencyFilesFromRef(sha, root = projectRoot, dependencies = { .sort(); } -// QNBS-v3: no encoding -- raw Buffer stdout, matching readFileSync's raw bytes for invalid UTF-8 safety. -function defaultReadFileAtRef(sha, relativePath, cwd) { +// QNBS-v3: raw Buffer stdout for UTF-8 safety; exported so verify-exact-tree.mjs reuses this, not a second reader. +export function readFileAtRef(sha, relativePath, cwd) { const result = spawnSync('git', ['show', `${sha}:${relativePath}`], { cwd, timeout: 5000, @@ -117,7 +130,7 @@ export function calculateDependencyFingerprintFromRef(sha, root = projectRoot, d const listFiles = dependencies.dependencyFilesFromRef ?? (() => dependencyFilesFromRef(sha, root, dependencies)); const files = listFiles(sha); if (files === null) return null; - const readContent = dependencies.readFileAtRef ?? ((path) => defaultReadFileAtRef(sha, path, root)); + const readContent = dependencies.readFileAtRef ?? ((path) => readFileAtRef(sha, path, root)); const entries = []; for (const relativePath of files) { const content = readContent(relativePath); diff --git a/scripts/verify-exact-tree.d.mts b/scripts/verify-exact-tree.d.mts new file mode 100644 index 000000000..1e395a0ba --- /dev/null +++ b/scripts/verify-exact-tree.d.mts @@ -0,0 +1,87 @@ +import type { DependencyState, TreeEntry } from './dependency-state.d.mts'; +import type { BoundedResult } from './hooks/shared.d.mts'; +import type { GitOptions, GitResult } from './signing/signing-core.d.mts'; + +// QNBS-v3: absolute correctness check, not a comparison -- distinct vocabulary from WorkingTreeState. +export type ExactTreeState = 'PASS' | 'FAIL' | 'NOT_APPLICABLE' | 'UNKNOWN'; + +export interface VerifyExactTreeDependencies { + runBounded?: ( + command: string, + args: string[], + options?: { + timeoutMs?: number; + cwd?: string; + env?: NodeJS.ProcessEnv; + input?: string; + shell?: boolean; + root?: string; + detached?: boolean; + }, + ) => Promise; + runLocalBinaryDetailed?: ( + binary: string, + args: string[], + options?: { root?: string; cwd?: string; timeoutMs?: number }, + ) => Promise; + // QNBS-v3: a distinct, pre-existing (#494) synchronous/output-capturing wrapper -- not BoundedResult. + runGit?: (args: string[], options?: GitOptions) => GitResult; + mkdtempFn?: () => Promise; + // QNBS-v3: a separate temp dir authority from mkdtempFn -- distinct lifecycle (hooks dir vs. worktree dir). + mkdtempHooksFn?: () => Promise; + rmFn?: (path: string) => Promise; + installTimeoutMs?: number; + tsgoArgs?: string[]; + tsgoTimeoutMs?: number; + repoRoot?: string; + // QNBS-v3: reuses dependency-state.mjs's mode-aware git-tree enumeration authority -- not a second parser. + listTreeEntries?: (sha: string, cwd: string) => TreeEntry[] | null; + // QNBS-v3: reuses dependency-state.mjs's git-object blob reader for symlink/.gitattributes content. + readBlobAtRef?: (sha: string, relativePath: string, cwd: string) => Buffer | null; + // QNBS-v3: checks any scope (local/global/system) for a tracked .gitattributes filter name. + isFilterConfigured?: (name: string, repoRoot: string) => boolean; + // QNBS-v3: reuses #502's manifest-compatibility authority to gate which tsgo binary may be trusted. + computeDependencyState?: (sha: string, root: string) => DependencyState; + // QNBS-v3: the trusted checkout installDependencies resolves the pinned store-dir from -- never the worktree. + trustedRepoRoot?: string; + // QNBS-v3: bypasses the real `pnpm store path` query in tests; undefined means "resolve it for real". + storeDir?: string | null; + resolveStoreDir?: (trustedRepoRoot: string | undefined) => Promise | string | null; +} + +export function createIsolatedWorktree( + sha: string, + repoRoot: string, + dependencies?: VerifyExactTreeDependencies, +): Promise<{ ok: boolean; path: string | undefined; interrupted?: boolean }>; + +export function removeIsolatedWorktree( + worktreePath: string | undefined, + repoRoot: string, + dependencies?: VerifyExactTreeDependencies, +): Promise; + +export function installDependencies( + worktreePath: string, + dependencies?: VerifyExactTreeDependencies, +): Promise; + +export function verifyExactTreeTypecheck( + sha: string, + repoRoot?: string, + dependencies?: VerifyExactTreeDependencies, +): Promise; + +export function verifyExactTreeForShas( + shas: string[], + repoRoot?: string, + dependencies?: VerifyExactTreeDependencies, +): Promise; + +export function resolveRef( + ref: string, + repoRoot: string, + dependencies?: VerifyExactTreeDependencies, +): string | null; + +export function main(argv?: string[], dependencies?: VerifyExactTreeDependencies): Promise; diff --git a/scripts/verify-exact-tree.mjs b/scripts/verify-exact-tree.mjs new file mode 100644 index 000000000..ffef5f20e --- /dev/null +++ b/scripts/verify-exact-tree.mjs @@ -0,0 +1,421 @@ +import { spawnSync } from 'node:child_process'; +import { mkdtemp as mkdtempAsync, rm as rmAsync } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join, posix, resolve } from 'node:path'; +import process from 'node:process'; +import { isMainModule } from './ci-prepush-range-resolver.mjs'; +import { computeDependencyState, listTreeEntries, readFileAtRef } from './dependency-state.mjs'; +import { runBounded, runLocalBinaryDetailed } from './hooks/shared.mjs'; +import { runGit as defaultRunGit } from './signing/signing-core.mjs'; + +// QNBS-v3: distinct from every other UNKNOWN cause -- explicit user intent must stop the whole run. +class ExactTreeInterrupted extends Error { + constructor() { + super('verify-exact-tree: interrupted'); + this.name = 'ExactTreeInterrupted'; + } +} + +// QNBS-v3: refs/replace/ would substitute a different tree; set globally so every git call in this process inherits it. +process.env.GIT_NO_REPLACE_OBJECTS = '1'; + +const DEFAULT_TSGO_ARGS = [ + '--project', + 'tsconfig.tsgo.json', + '--noEmit', + '--checkers', + '1', + // QNBS-v3: CLI flags outrank the checked ref's own tsconfig -- must not disable checking or redirect build-info writes. + '--noCheck', + 'false', + '--tsBuildInfoFile', + '.tsbuildinfo', +]; +// QNBS-v3: measured ~2m20s for the full project on this hardware with a warm store; 5min gives margin. +const DEFAULT_INSTALL_TIMEOUT_MS = 300_000; +// QNBS-v3: single-checker measured ~56s; docs cite ~300s for full typecheck -- 6min clears both with margin. +const DEFAULT_TSGO_TIMEOUT_MS = 360_000; + +// QNBS-v3: lifecycle commands (worktree/install) -- any non-zero or unreadable exit fails outright. +function boundedCommandFailed(result) { + return Boolean( + result.error || result.timedOut || result.interrupted || result.signal || result.status !== 0, + ); +} + +// QNBS-v3: tsgo only -- a genuine numeric non-zero status is a real FAIL; a signal/timeout/OOM is UNKNOWN. +function tsgoResultUnknown(result) { + return Boolean( + result.error || result.timedOut || result.interrupted || result.signal || typeof result.status !== 'number', + ); +} + +// QNBS-v3: sweeps entries orphaned by a prior crashed/killed run before creating a new one. +async function pruneStaleWorktrees(repoRoot, dependencies = {}) { + const runGit = dependencies.runBounded ?? runBounded; + const result = await runGit('git', ['worktree', 'prune'], { cwd: repoRoot }); + if (result.interrupted) throw new ExactTreeInterrupted(); +} + +// QNBS-v3: an arbitrary ref can force-track a node_modules path anywhere; could execute/redirect. Fail closed. +function hasTrackedNodeModules(entries) { + // QNBS-v3: case-insensitive -- NODE_MODULES aliases node_modules on Windows/macOS checkouts. + return entries.some((entry) => + entry.path.split('/').some((segment) => segment.toLowerCase() === 'node_modules'), + ); +} + +// QNBS-v3: a Windows checkout recreates backslash/drive-letter/UNC targets literally -- posix alone misses them. +function looksLikeWindowsEscapingTarget(target) { + return /^[a-zA-Z]:[\\/]/.test(target) || target.startsWith('\\\\') || target.includes('\\'); +} + +// QNBS-v3: a tracked symlink whose target escapes the tree root would let tsgo read live/external content. +function hasEscapingSymlink(entries, sha, repoRoot, dependencies) { + const readBlob = dependencies.readBlobAtRef ?? readFileAtRef; + for (const entry of entries) { + if (entry.mode !== '120000') continue; + const targetBuffer = readBlob(sha, entry.path, repoRoot); + if (targetBuffer === null) return true; // unreadable target -- fail closed, treat as escaping. + const target = targetBuffer.toString('utf8').trim(); + if (looksLikeWindowsEscapingTarget(target)) return true; + if (posix.isAbsolute(target)) return true; + const resolved = posix.normalize(posix.join(posix.dirname(entry.path), target)); + if (resolved.startsWith('..') || posix.isAbsolute(resolved)) return true; + } + return false; +} + +// QNBS-v3: an uninitialized submodule leaves its path silently empty -- tsgo could exit 0 while omitting it. +function hasUnmaterializedGitlink(entries) { + return entries.some((entry) => entry.type === 'commit'); +} + +// QNBS-v3: git has no clean way to force a filter driver to a no-op (smudge vs. process differ); refuse instead. +function defaultIsFilterConfigured(name, repoRoot) { + for (const key of ['smudge', 'clean', 'process']) { + const result = spawnSync('git', ['config', '--get', `filter.${name}.${key}`], { + cwd: repoRoot, + encoding: 'utf8', + timeout: 5000, + }); + if (!result.error && result.status === 0) return true; + } + return false; +} + +// QNBS-v3: a tracked .gitattributes selecting a filter configured in ANY scope (local/global/system) would run it. +function hasActiveTrackedFilter(entries, sha, repoRoot, dependencies) { + const readBlob = dependencies.readBlobAtRef ?? readFileAtRef; + const isFilterConfigured = dependencies.isFilterConfigured ?? defaultIsFilterConfigured; + const filterNames = new Set(); + for (const entry of entries) { + if (entry.path !== '.gitattributes' && !entry.path.endsWith('/.gitattributes')) continue; + const contentBuffer = readBlob(sha, entry.path, repoRoot); + if (contentBuffer === null) return true; // unreadable .gitattributes -- fail closed. + for (const line of contentBuffer.toString('utf8').split('\n')) { + const match = line.match(/(?:^|\s)filter=(\S+)/); + if (match) filterNames.add(match[1]); + } + } + for (const name of filterNames) { + if (isFilterConfigured(name, repoRoot)) return true; + } + return false; +} + +// QNBS-v3: partial mitigation for the tsconfig-level vector; an escaping source-level import is a documented residual (needs OS sandboxing). +function hasEscapingTsconfigScope(entries, sha, repoRoot, dependencies) { + const readBlob = dependencies.readBlobAtRef ?? readFileAtRef; + const tsconfigEntry = entries.find((entry) => entry.path === 'tsconfig.tsgo.json'); + if (!tsconfigEntry) return false; // a missing tsconfig fails the install/tsgo steps on their own terms. + const contentBuffer = readBlob(sha, 'tsconfig.tsgo.json', repoRoot); + if (contentBuffer === null) return true; // unreadable -- fail closed. + let parsed; + try { + parsed = JSON.parse(contentBuffer.toString('utf8')); + } catch { + return true; // unparseable -- refuse rather than let tsgo interpret it unexamined. + } + const values = [ + ...(Array.isArray(parsed.include) ? parsed.include : []), + ...(Array.isArray(parsed.exclude) ? parsed.exclude : []), + ...(Array.isArray(parsed.files) ? parsed.files : []), + ...(typeof parsed.extends === 'string' ? [parsed.extends] : []), + ].filter((value) => typeof value === 'string'); + return values.some( + (value) => + looksLikeWindowsEscapingTarget(value) || + posix.isAbsolute(value) || + posix.normalize(value).startsWith('..'), + ); +} + +// QNBS-v3: checked against the exact commit's own git objects, before any worktree/pnpm step touches disk. +function verifyExactTreePreflight(sha, repoRoot, dependencies = {}) { + const listEntries = dependencies.listTreeEntries ?? listTreeEntries; + const entries = listEntries(sha, repoRoot); + if (entries === null) return false; // an unreadable tree can never be proven clean. + if (hasTrackedNodeModules(entries)) return false; + if (hasUnmaterializedGitlink(entries)) return false; + if (hasEscapingSymlink(entries, sha, repoRoot, dependencies)) return false; + if (hasActiveTrackedFilter(entries, sha, repoRoot, dependencies)) return false; + if (hasEscapingTsconfigScope(entries, sha, repoRoot, dependencies)) return false; + return true; +} + +// QNBS-v3: hook-free dir so 'git worktree add' can't run repo/user hooks -- scoped via -c, not global config. +async function createEmptyHooksDir(dependencies = {}) { + const makeHooksDir = + dependencies.mkdtempHooksFn ?? + (() => mkdtempAsync(join(tmpdir(), 'worldscript-exact-tree-hooks-'))); + return makeHooksDir(); +} + +// QNBS-v3: resolved from the trusted repoRoot -- picks up its pinned packageManager, and a .pnpmfile.cjs there is the developer's own code. +function defaultResolveStoreDir(trustedRepoRoot) { + if (!trustedRepoRoot) return null; // no trusted root to resolve the pinned toolchain from -- fail closed. + const result = spawnSync('pnpm', ['store', 'path'], { + cwd: trustedRepoRoot, + encoding: 'utf8', + timeout: 10_000, + shell: process.platform === 'win32', + env: { ...process.env, COREPACK_ENABLE_NETWORK: '0' }, + }); + if (result.error || result.status !== 0) return null; + const storePath = result.stdout.trim(); + // QNBS-v3: an empty store path would resolve --store-dir '' relative to the untrusted worktree cwd. + return storePath === '' ? null : storePath; +} + +export async function createIsolatedWorktree(sha, repoRoot, dependencies = {}) { + const runGit = dependencies.runBounded ?? runBounded; + const makeTempDir = + dependencies.mkdtempFn ?? (() => mkdtempAsync(join(tmpdir(), 'worldscript-exact-tree-'))); + const removeDir = dependencies.rmFn ?? ((p) => rmAsync(p, { recursive: true, force: true })); + await pruneStaleWorktrees(repoRoot, dependencies); + let path; + try { + path = await makeTempDir(); + } catch { + return { ok: false, path: undefined }; + } + + let hooksDir; + try { + hooksDir = await createEmptyHooksDir(dependencies); + } catch { + return { ok: false, path }; + } + try { + const result = await runGit( + 'git', + ['-c', `core.hooksPath=${hooksDir}`, 'worktree', 'add', '--detach', path, sha], + { + cwd: repoRoot, + // QNBS-v3: core.hooksPath disables hooks only, not smudge/clean filters (e.g. LFS) -- nonexistent global/system config makes any referenced filter a no-op. + env: { + GIT_CONFIG_GLOBAL: join(hooksDir, 'no-global-config'), + GIT_CONFIG_SYSTEM: join(hooksDir, 'no-system-config'), + }, + }, + ); + // QNBS-v3: reported via the return shape, not a throw -- the caller must still clean up this path. + if (result.interrupted) return { ok: false, path, interrupted: true }; + if (boundedCommandFailed(result)) return { ok: false, path }; + return { ok: true, path }; + } finally { + try { + await removeDir(hooksDir); + } catch { + // Best-effort: nothing more can be done from here; the directory is under os.tmpdir(). + } + } +} + +// QNBS-v3: fail-closed removal fallback; cleanup always finishes even when interrupted, then re-raises it. +export async function removeIsolatedWorktree(worktreePath, repoRoot, dependencies = {}) { + if (!worktreePath) return; + const runGit = dependencies.runBounded ?? runBounded; + const removeDir = dependencies.rmFn ?? ((path) => rmAsync(path, { recursive: true, force: true })); + const result = await runGit('git', ['worktree', 'remove', '--force', worktreePath], { + cwd: repoRoot, + }); + let interrupted = Boolean(result.interrupted); + if (boundedCommandFailed(result)) { + try { + await removeDir(worktreePath); + } catch { + // Best-effort: nothing more can be done from here; the directory is under os.tmpdir(). + } + const pruneResult = await runGit('git', ['worktree', 'prune'], { cwd: repoRoot }); + interrupted ||= Boolean(pruneResult.interrupted); + if (interrupted) throw new ExactTreeInterrupted(); + return; + } + // QNBS-v3: git leaves the now-empty mkdtemp-created dir in place after remove -- sweep it too. + try { + await removeDir(worktreePath); + } catch { + // Best-effort: nothing more can be done from here; the directory is under os.tmpdir(). + } + if (interrupted) throw new ExactTreeInterrupted(); +} + +// QNBS-v3: real pnpm install, not a hand-reconstructed symlink graph -- offline, fails to UNKNOWN below. +export async function installDependencies(worktreePath, dependencies = {}) { + const runPnpm = dependencies.runBounded ?? runBounded; + const timeoutMs = dependencies.installTimeoutMs ?? DEFAULT_INSTALL_TIMEOUT_MS; + const resolveStoreDir = dependencies.resolveStoreDir ?? defaultResolveStoreDir; + const storeDir = + dependencies.storeDir !== undefined + ? dependencies.storeDir + : await resolveStoreDir(dependencies.trustedRepoRoot); + if (storeDir === null) return false; // could not establish a trusted store-dir -- fail closed. + + const result = await runPnpm( + 'pnpm', + [ + 'install', + '--frozen-lockfile', + '--offline', + // QNBS-v3: verifying an arbitrary ref must never run that ref's scripts or .pnpmfile.cjs hooks. + '--ignore-scripts', + '--ignore-pnpmfile', + // QNBS-v3: pins every pnpm output path -- CLI flags outrank a target-controlled .npmrc, containing writes. + '--modules-dir', + 'node_modules', + '--virtual-store-dir', + 'node_modules/.pnpm', + '--lockfile-dir', + worktreePath, + '--store-dir', + storeDir, + ], + { + cwd: worktreePath, + timeoutMs, + shell: process.platform === 'win32', + // QNBS-v3: pnpm here is a Corepack shim, which can itself reach the network before --offline applies. + env: { COREPACK_ENABLE_NETWORK: '0' }, + }, + ); + if (result.interrupted) throw new ExactTreeInterrupted(); + return !boundedCommandFailed(result); +} + +// QNBS-v3: precedence shape mirrors aggregateDiagnosticState, kept separate -- different vocabulary. +function aggregateExactTreeState(states) { + const relevant = states.filter((state) => state !== 'NOT_APPLICABLE'); + if (relevant.length === 0) return 'NOT_APPLICABLE'; + if (relevant.includes('FAIL')) return 'FAIL'; + if (relevant.includes('UNKNOWN')) return 'UNKNOWN'; + return 'PASS'; +} + +// QNBS-v3: never throws UNKNOWN-worthy failures -- only ExactTreeInterrupted escapes, to stop the whole run. +export async function verifyExactTreeTypecheck(sha, repoRoot = process.cwd(), dependencies = {}) { + // QNBS-v3: a relative repoRoot would produce ambiguous git-cwd and install-cwd semantics. + const absoluteRepoRoot = resolve(repoRoot); + try { + // QNBS-v3: refuse before any materialization -- tracked node_modules or an escaping symlink must never reach pnpm/tsgo. + if (!verifyExactTreePreflight(sha, absoluteRepoRoot, dependencies)) return 'UNKNOWN'; + + // QNBS-v3: the checked ref may supply source/types/deps but never the compiler that certifies it. + const computeState = dependencies.computeDependencyState ?? computeDependencyState; + if (computeState(sha, absoluteRepoRoot, dependencies) !== 'MATCHES') return 'UNKNOWN'; + + const created = await createIsolatedWorktree(sha, absoluteRepoRoot, dependencies); + if (!created.ok) { + await removeIsolatedWorktree(created.path, absoluteRepoRoot, dependencies); + if (created.interrupted) throw new ExactTreeInterrupted(); + return 'UNKNOWN'; + } + + try { + const installed = await installDependencies(created.path, { + ...dependencies, + trustedRepoRoot: absoluteRepoRoot, + }); + if (!installed) return 'UNKNOWN'; + + const runDetailed = dependencies.runLocalBinaryDetailed ?? runLocalBinaryDetailed; + const tsgoArgs = dependencies.tsgoArgs ?? DEFAULT_TSGO_ARGS; + const tsgoTimeoutMs = dependencies.tsgoTimeoutMs ?? DEFAULT_TSGO_TIMEOUT_MS; + const result = await runDetailed('tsgo', tsgoArgs, { + // QNBS-v3: root is the TRUSTED checkout's own tsgo -- cwd stays the isolated tree being analyzed. + root: absoluteRepoRoot, + cwd: created.path, + timeoutMs: tsgoTimeoutMs, + }); + if (result.interrupted) throw new ExactTreeInterrupted(); + // QNBS-v3: a signal (including an external OOM kill) must yield UNKNOWN, never a false FAIL. + if (tsgoResultUnknown(result)) return 'UNKNOWN'; + return result.status === 0 ? 'PASS' : 'FAIL'; + } finally { + await removeIsolatedWorktree(created.path, absoluteRepoRoot, dependencies); + } + } catch (error) { + if (error instanceof ExactTreeInterrupted) throw error; + return 'UNKNOWN'; + } +} + +export async function verifyExactTreeForShas(shas, repoRoot = process.cwd(), dependencies = {}) { + const unique = [...new Set(shas)]; + if (unique.length === 0) return 'NOT_APPLICABLE'; + const states = []; + for (const sha of unique) { + // QNBS-v3: sequential, never parallel -- bounded resource use on constrained developer hardware. + states.push(await verifyExactTreeTypecheck(sha, repoRoot, dependencies)); + } + return aggregateExactTreeState(states); +} + +// QNBS-v3: reuses signing-core's bounded, output-capturing runGit -- runBounded can't capture stdout. +export function resolveRef(ref, repoRoot, dependencies = {}) { + const runGit = dependencies.runGit ?? defaultRunGit; + const result = runGit(['rev-parse', '--verify', `${ref}^{commit}`], { cwd: repoRoot }); + if (result.error || result.status !== 0) return null; + return result.stdout.trim(); +} + +export async function main(argv = process.argv.slice(2), dependencies = {}) { + const repoRoot = resolve(dependencies.repoRoot ?? process.cwd()); + const refs = argv.length > 0 ? argv : ['HEAD']; + const shas = []; + for (const ref of refs) { + const sha = resolveRef(ref, repoRoot, dependencies); + if (!sha) { + console.error(`[verify-exact-tree] could not resolve ref: ${ref}`); + process.exitCode = 1; + return; + } + shas.push(sha); + } + console.log(`[verify-exact-tree] verifying ${shas.length} commit(s) in isolated worktree(s)...`); + let state; + try { + state = await verifyExactTreeForShas(shas, repoRoot, dependencies); + } catch (error) { + // QNBS-v3: explicit user intent -- stop here, never continue to another multi-minute verification. + if (error instanceof ExactTreeInterrupted) { + console.log('[verify-exact-tree] interrupted; stopping without completing verification.'); + process.exitCode = 130; + return; + } + throw error; + } + console.log(`[verify-exact-tree] result: ${state}`); + if (state === 'UNKNOWN') { + console.log( + '[verify-exact-tree] could not be established (offline dependency materialization failed, or a worktree/tsgo step failed); required CI remains authoritative.', + ); + } else if (state === 'FAIL') { + console.log('[verify-exact-tree] the exact committed tree does not typecheck in isolation.'); + process.exitCode = 1; + } +} + +// QNBS-v3: guard execution so this module can be imported for testing without running the CLI. +if (isMainModule(process.argv[1], import.meta.url)) await main(); diff --git a/tests/unit/tooling/verify-exact-tree.test.mjs b/tests/unit/tooling/verify-exact-tree.test.mjs new file mode 100644 index 000000000..77aeb62e9 --- /dev/null +++ b/tests/unit/tooling/verify-exact-tree.test.mjs @@ -0,0 +1,1283 @@ +import { strict as assert } from 'node:assert'; +import { execFileSync } from 'node:child_process'; +import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { join, resolve } from 'node:path'; +import { afterEach, describe, it } from 'node:test'; +import { + createIsolatedWorktree, + installDependencies, + main, + removeIsolatedWorktree, + resolveRef, + verifyExactTreeForShas, + verifyExactTreeTypecheck, +} from '../../../scripts/verify-exact-tree.mjs'; + +const temporaryRoots = []; + +afterEach(() => { + for (const root of temporaryRoots.splice(0)) rmSync(root, { recursive: true, force: true }); +}); + +function git(cwd, args) { + return execFileSync('git', args, { cwd, encoding: 'utf8' }); +} + +function makeTinyTsRepo(source) { + const root = mkdtempSync(join(process.cwd(), '.worldscript-exact-tree-')); + temporaryRoots.push(root); + git(root, ['init', '--quiet', '--initial-branch=main']); + git(root, ['config', 'user.email', 'test@example.com']); + git(root, ['config', 'user.name', 'test']); + writeFileSync( + join(root, 'tsconfig.json'), + JSON.stringify({ + compilerOptions: { strict: true, noEmit: true, module: 'esnext', target: 'es2022' }, + include: ['index.ts'], + }), + ); + writeFileSync(join(root, 'index.ts'), source); + git(root, ['add', '-A']); + git(root, ['-c', 'commit.gpgsign=false', 'commit', '--quiet', '-m', 'test']); + const sha = git(root, ['rev-parse', 'HEAD']).trim(); + return { root, sha }; +} + +// QNBS-v3: root/package-local/transitive links, zero external deps (offline metadata can't resolve fresh). +function makeWorkspaceFixture(innerContent) { + const root = mkdtempSync(join(process.cwd(), '.worldscript-exact-tree-ws-')); + temporaryRoots.push(root); + mkdirSync(join(root, 'packages', 'demo-pkg'), { recursive: true }); + mkdirSync(join(root, 'packages', 'inner-pkg'), { recursive: true }); + writeFileSync( + join(root, 'package.json'), + '{"name":"fixture-root","private":true,"dependencies":{"@fixture/demo-pkg":"workspace:*"}}\n', + ); + writeFileSync(join(root, 'pnpm-workspace.yaml'), 'packages:\n - packages/*\n'); + writeFileSync( + join(root, 'packages', 'demo-pkg', 'package.json'), + '{"name":"@fixture/demo-pkg","version":"1.0.0","dependencies":{"@fixture/inner-pkg":"workspace:*"}}\n', + ); + writeFileSync(join(root, 'packages', 'demo-pkg', 'index.js'), "export { INNER } from '@fixture/inner-pkg';\n"); + writeFileSync( + join(root, 'packages', 'inner-pkg', 'package.json'), + '{"name":"@fixture/inner-pkg","version":"1.0.0"}\n', + ); + writeFileSync(join(root, 'packages', 'inner-pkg', 'index.js'), `export const INNER = '${innerContent}';\n`); + // QNBS-v3: load-bearing -- without this, git add -A commits the symlinks and a bare checkout alone would pass. + writeFileSync(join(root, '.gitignore'), 'node_modules\n'); + // QNBS-v3: generates a real, valid lockfile for this fixture -- workspace-only, no network needed. + execFileSync('pnpm', ['install', '--offline'], { cwd: root, stdio: 'ignore' }); + git(root, ['init', '--quiet', '--initial-branch=main']); + git(root, ['config', 'user.email', 'test@example.com']); + git(root, ['config', 'user.name', 'test']); + git(root, ['add', '-A']); + git(root, ['-c', 'commit.gpgsign=false', 'commit', '--quiet', '-m', 'test']); + const sha = git(root, ['rev-parse', 'HEAD']).trim(); + return { root, sha }; +} + +// QNBS-v3: .npmrc added AFTER the setup install, so fixture setup itself never escapes -- only the tested install can. +function makeEscapingNpmrcFixture(npmrcContent) { + const root = mkdtempSync(join(process.cwd(), '.worldscript-exact-tree-npmrc-')); + temporaryRoots.push(root); + mkdirSync(join(root, 'packages', 'demo-pkg'), { recursive: true }); + writeFileSync( + join(root, 'package.json'), + '{"name":"fixture-root","private":true,"dependencies":{"@fixture/demo-pkg":"workspace:*"}}\n', + ); + writeFileSync(join(root, 'pnpm-workspace.yaml'), 'packages:\n - packages/*\n'); + writeFileSync(join(root, 'packages', 'demo-pkg', 'package.json'), '{"name":"@fixture/demo-pkg","version":"1.0.0"}\n'); + writeFileSync(join(root, 'packages', 'demo-pkg', 'index.js'), "export const DEMO = 'ok';\n"); + writeFileSync(join(root, '.gitignore'), 'node_modules\n'); + execFileSync('pnpm', ['install', '--offline'], { cwd: root, stdio: 'ignore' }); + writeFileSync(join(root, '.npmrc'), npmrcContent); + git(root, ['init', '--quiet', '--initial-branch=main']); + git(root, ['config', 'user.email', 'test@example.com']); + git(root, ['config', 'user.name', 'test']); + git(root, ['add', '-A']); + git(root, ['-c', 'commit.gpgsign=false', 'commit', '--quiet', '-m', 'escaping npmrc']); + const sha = git(root, ['rev-parse', 'HEAD']).trim(); + return { root, sha }; +} + +// QNBS-v3: a workspace package legitimately providing a "tsgo" bin -- bin-linking isn't a lifecycle script. +function makeMaliciousTsgoFixture() { + const root = mkdtempSync(join(process.cwd(), '.worldscript-exact-tree-eviltsgo-')); + temporaryRoots.push(root); + const markerRoot = mkdtempSync(join(process.cwd(), '.worldscript-exact-tree-eviltsgomark-')); + temporaryRoots.push(markerRoot); + const markerFile = join(markerRoot, 'evil-tsgo-ran.txt'); + + mkdirSync(join(root, 'packages', 'evil-tsgo'), { recursive: true }); + writeFileSync( + join(root, 'package.json'), + '{"name":"fixture-root","private":true,"dependencies":{"@fixture/evil-tsgo":"workspace:*"}}\n', + ); + writeFileSync(join(root, 'pnpm-workspace.yaml'), 'packages:\n - packages/*\n'); + writeFileSync( + join(root, 'packages', 'evil-tsgo', 'package.json'), + '{"name":"@fixture/evil-tsgo","version":"1.0.0","bin":{"tsgo":"./evil.js"}}\n', + ); + writeFileSync( + join(root, 'packages', 'evil-tsgo', 'evil.js'), + `require('fs').writeFileSync(${JSON.stringify(markerFile)}, 'executed');\n`, + ); + writeFileSync(join(root, '.gitignore'), 'node_modules\n'); + execFileSync('pnpm', ['install', '--offline'], { cwd: root, stdio: 'ignore' }); + git(root, ['init', '--quiet', '--initial-branch=main']); + git(root, ['config', 'user.email', 'test@example.com']); + git(root, ['config', 'user.name', 'test']); + git(root, ['add', '-A']); + git(root, [ + '-c', + 'commit.gpgsign=false', + 'commit', + '--quiet', + '-m', + 'malicious: workspace package provides a fake tsgo bin', + ]); + const maliciousSha = git(root, ['rev-parse', 'HEAD']).trim(); + // QNBS-v3: the setup install above also installs at the fixture root -- delete it so it stays untainted. + rmSync(join(root, 'node_modules'), { recursive: true, force: true }); + return { root, maliciousSha, markerFile }; +} + +describe('createIsolatedWorktree / removeIsolatedWorktree (real git, no pnpm)', () => { + it('creates a worktree at the exact commit and leaves nothing registered after cleanup', async () => { + const { root, sha } = makeTinyTsRepo('const x: number = 1;\n'); + const created = await createIsolatedWorktree(sha, root, {}); + assert.equal(created.ok, true); + assert.equal(readFileSync(join(created.path, 'index.ts'), 'utf8'), 'const x: number = 1;\n'); + await removeIsolatedWorktree(created.path, root, {}); + const list = git(root, ['worktree', 'list', '--porcelain']); + assert.equal(list.trim().split('\n\n').length, 1, `expected only the main worktree: ${list}`); + }); + + it('falls back to a raw directory sweep plus prune when git worktree remove fails', async () => { + let removeCalled = false; + let pruneCallCount = 0; + let rmCalled = false; + await removeIsolatedWorktree('/tmp/worldscript-exact-tree-fake', '/repo', { + runBounded: (_command, args) => { + if (args.includes('prune')) { + pruneCallCount += 1; + return { status: 0, error: null, signal: null, timedOut: false, interrupted: false }; + } + if (args.includes('remove')) { + removeCalled = true; + return { status: 1, error: null, signal: null, timedOut: false, interrupted: false }; + } + return { status: 0, error: null, signal: null, timedOut: false, interrupted: false }; + }, + rmFn: async () => { + rmCalled = true; + }, + }); + assert.equal(removeCalled, true); + assert.equal(rmCalled, true); + assert.equal(pruneCallCount, 1); + }); +}); + +describe('bare-call regressions (dependencies argument omitted, matching resolveRef)', () => { + it('createIsolatedWorktree does not throw when called without a dependencies argument', async () => { + const { root, sha } = makeTinyTsRepo('const x: number = 1;\n'); + const created = await createIsolatedWorktree(sha, root); + assert.equal(created.ok, true); + await removeIsolatedWorktree(created.path, root); + }); + + it('removeIsolatedWorktree does not throw when called without a dependencies argument', async () => { + const { root, sha } = makeTinyTsRepo('const x: number = 1;\n'); + const created = await createIsolatedWorktree(sha, root, {}); + await assert.doesNotReject(() => removeIsolatedWorktree(created.path, root)); + }); + + it('installDependencies does not throw when called without a dependencies argument', async () => { + // QNBS-v3: an unreachable repoRoot fails fast, but the call itself must not throw on undefined deps. + await assert.doesNotReject(() => installDependencies('/tmp/worldscript-exact-tree-nonexistent')); + }); + + it('pruneStaleWorktrees (via createIsolatedWorktree) does not throw with dependencies omitted', async () => { + const { root, sha } = makeTinyTsRepo('const x: number = 1;\n'); + // QNBS-v3: createIsolatedWorktree forwards dependencies to pruneStaleWorktrees internally. + const created = await createIsolatedWorktree(sha, root); + assert.equal(created.ok, true); + await removeIsolatedWorktree(created.path, root); + }); +}); + +describe('installDependencies (workspace-package soundness -- the core regression)', () => { + it('resolves workspace packages to the isolated worktree\'s committed source, never the live checkout, including a transitive package-local link', async () => { + const { root, sha } = makeWorkspaceFixture('committed'); + + // QNBS-v3: mutate the LIVE checkout AFTER committing -- a leak would read this instead of committed. + writeFileSync( + join(root, 'packages', 'inner-pkg', 'index.js'), + "export const INNER = 'LEAKED-live-checkout-value';\n", + ); + + const created = await createIsolatedWorktree(sha, root, {}); + assert.equal(created.ok, true); + try { + const installed = await installDependencies(created.path, { trustedRepoRoot: root }); + assert.equal(installed, true); + + // QNBS-v3: root workspace link (node_modules/@fixture/demo-pkg) resolving into the isolated tree. + const demoPkgContent = readFileSync( + join(created.path, 'node_modules', '@fixture', 'demo-pkg', 'index.js'), + 'utf8', + ); + assert.match(demoPkgContent, /@fixture\/inner-pkg/); + + // QNBS-v3: the critical assertion -- package-local, transitive workspace link resolves committed. + const innerContent = readFileSync( + join(created.path, 'packages', 'demo-pkg', 'node_modules', '@fixture', 'inner-pkg', 'index.js'), + 'utf8', + ); + assert.match(innerContent, /'committed'/); + assert.doesNotMatch(innerContent, /LEAKED/); + + // QNBS-v3: also verify via Node's own resolution, wherever pnpm actually placed the hoisted link. + const resolved = execFileSync('node', ['-e', "process.stdout.write(require('fs').readFileSync(require.resolve('@fixture/inner-pkg'), 'utf8'))"], { + cwd: join(created.path, 'packages', 'demo-pkg'), + encoding: 'utf8', + }); + assert.match(resolved, /'committed'/); + assert.doesNotMatch(resolved, /LEAKED/); + } finally { + await removeIsolatedWorktree(created.path, root, {}); + } + }); + + it('never executes a side-effecting .pnpmfile.cjs, even its top-level (require-time) code', async () => { + const { root } = makeWorkspaceFixture('committed'); + const markerRoot = mkdtempSync(join(process.cwd(), '.worldscript-exact-tree-pnpmfilemark-')); + temporaryRoots.push(markerRoot); + const markerFile = join(markerRoot, 'pnpmfile-ran.txt'); + // QNBS-v3: top-level (require-time) side effect -- proves --ignore-pnpmfile stops it before any hook fires. + writeFileSync( + join(root, '.pnpmfile.cjs'), + `require('fs').writeFileSync(${JSON.stringify(markerFile)}, 'executed');\nmodule.exports = { hooks: {} };\n`, + ); + git(root, ['add', '-A']); + git(root, ['-c', 'commit.gpgsign=false', 'commit', '--quiet', '-m', 'add pnpmfile']); + const pnpmfileSha = git(root, ['rev-parse', 'HEAD']).trim(); + // QNBS-v3: store-dir resolution runs in repoRoot's own dir; the commit object is unaffected either way. + rmSync(join(root, '.pnpmfile.cjs'), { force: true }); + + const created = await createIsolatedWorktree(pnpmfileSha, root, {}); + assert.equal(created.ok, true); + try { + const installed = await installDependencies(created.path, { trustedRepoRoot: root }); + assert.equal(installed, true); + assert.equal(existsSync(markerFile), false, '.pnpmfile.cjs must not have executed'); + } finally { + await removeIsolatedWorktree(created.path, root, {}); + } + }); + + it('returns false (mapped to UNKNOWN by callers) when the frozen-lockfile install fails', async () => { + const installed = await installDependencies('/tmp/worldscript-exact-tree-fake', { + storeDir: '/fake-store', + runBounded: async () => ({ + status: 1, + error: null, + signal: null, + timedOut: false, + interrupted: false, + }), + }); + assert.equal(installed, false); + }); + + it('returns false (mapped to UNKNOWN) when the trusted store-dir cannot be resolved', async () => { + const installed = await installDependencies('/tmp/worldscript-exact-tree-fake', { + resolveStoreDir: () => null, + runBounded: async () => { + throw new Error('must not be called -- an unresolved store-dir must fail closed first'); + }, + }); + assert.equal(installed, false); + }); + + it('resolves store-dir from trustedRepoRoot, never the untrusted worktree, so the pinned pnpm version is used', async () => { + let seenTrustedRoot; + await installDependencies('/tmp/worldscript-exact-tree-fake-worktree', { + trustedRepoRoot: '/repo', + resolveStoreDir: (trustedRepoRoot) => { + seenTrustedRoot = trustedRepoRoot; + return '/fake-store'; + }, + runBounded: async () => ({ status: 0, error: null, signal: null, timedOut: false, interrupted: false }), + }); + assert.equal(seenTrustedRoot, '/repo', 'must resolve store-dir from the trusted repoRoot, not the worktree'); + }); +}); + +describe('installDependencies (P1: contain pnpm output paths against an escaping .npmrc)', () => { + it('does not let modules-dir escape the isolated worktree', async () => { + const { root, sha } = makeEscapingNpmrcFixture('modules-dir=../ESCAPE-modules\n'); + const created = await createIsolatedWorktree(sha, root, {}); + assert.equal(created.ok, true); + try { + const installed = await installDependencies(created.path, { trustedRepoRoot: root }); + assert.equal(installed, true); + assert.equal( + existsSync(join(created.path, '..', 'ESCAPE-modules')), + false, + 'modules-dir must not escape the isolated worktree', + ); + } finally { + await removeIsolatedWorktree(created.path, root, {}); + } + }); + + it('does not let virtual-store-dir escape the isolated worktree', async () => { + const { root, sha } = makeEscapingNpmrcFixture('virtual-store-dir=../ESCAPE-virtualstore\n'); + const created = await createIsolatedWorktree(sha, root, {}); + assert.equal(created.ok, true); + try { + const installed = await installDependencies(created.path, { trustedRepoRoot: root }); + assert.equal(installed, true); + assert.equal( + existsSync(join(created.path, '..', 'ESCAPE-virtualstore')), + false, + 'virtual-store-dir must not escape the isolated worktree', + ); + } finally { + await removeIsolatedWorktree(created.path, root, {}); + } + }); +}); + +describe('verifyExactTreeTypecheck (fail-closed lifecycle, signal/status semantics -- DI only)', () => { + it('reports UNKNOWN when git worktree add fails', async () => { + const state = await verifyExactTreeTypecheck('a'.repeat(40), '/repo', { + listTreeEntries: () => [], + computeDependencyState: () => 'MATCHES', + runBounded: (_command, args) => { + if (args.includes('add')) return { status: 1, error: null, signal: null, timedOut: false, interrupted: false }; + return { status: 0, error: null, signal: null, timedOut: false, interrupted: false }; + }, + mkdtempFn: async () => '/tmp/worldscript-exact-tree-fake', + }); + assert.equal(state, 'UNKNOWN'); + }); + + it('reports UNKNOWN, never a false PASS/FAIL, when the install fails', async () => { + const state = await verifyExactTreeTypecheck('a'.repeat(40), '/repo', { + listTreeEntries: () => [], + computeDependencyState: () => 'MATCHES', + storeDir: '/fake-store', + mkdtempFn: async () => '/tmp/worldscript-exact-tree-fake', + runBounded: (_command, args) => { + if (args.includes('worktree')) return { status: 0, error: null, signal: null, timedOut: false, interrupted: false }; + // QNBS-v3: the pnpm install call -- fails, must map to UNKNOWN, and tsgo must never run. + return { status: 1, error: null, signal: null, timedOut: false, interrupted: false }; + }, + runLocalBinaryDetailed: async () => { + throw new Error('must not be called -- install already failed'); + }, + }); + assert.equal(state, 'UNKNOWN'); + }); + + it('reports PASS/FAIL correctly from a genuine tsgo exit status once install succeeds (DI)', async () => { + const runBounded = () => ({ status: 0, error: null, signal: null, timedOut: false, interrupted: false }); + const pass = await verifyExactTreeTypecheck('a'.repeat(40), '/repo', { + listTreeEntries: () => [], + computeDependencyState: () => 'MATCHES', + storeDir: '/fake-store', + mkdtempFn: async () => '/tmp/worldscript-exact-tree-fake', + runBounded, + runLocalBinaryDetailed: async () => ({ + status: 0, + error: null, + signal: null, + timedOut: false, + interrupted: false, + }), + }); + assert.equal(pass, 'PASS'); + + const fail = await verifyExactTreeTypecheck('a'.repeat(40), '/repo', { + listTreeEntries: () => [], + computeDependencyState: () => 'MATCHES', + storeDir: '/fake-store', + mkdtempFn: async () => '/tmp/worldscript-exact-tree-fake', + runBounded, + runLocalBinaryDetailed: async () => ({ + status: 2, + error: null, + signal: null, + timedOut: false, + interrupted: false, + }), + }); + assert.equal(fail, 'FAIL'); + }); + + it('reports UNKNOWN, not FAIL, when tsgo is terminated by a signal (e.g. external OOM kill)', async () => { + const state = await verifyExactTreeTypecheck('a'.repeat(40), '/repo', { + listTreeEntries: () => [], + computeDependencyState: () => 'MATCHES', + storeDir: '/fake-store', + mkdtempFn: async () => '/tmp/worldscript-exact-tree-fake', + runBounded: () => ({ status: 0, error: null, signal: null, timedOut: false, interrupted: false }), + runLocalBinaryDetailed: async () => ({ + status: null, + signal: 'SIGKILL', + error: null, + timedOut: false, + interrupted: false, + }), + }); + assert.equal(state, 'UNKNOWN'); + }); + + it('reports UNKNOWN, not FAIL, on a null status with no signal (defensive)', async () => { + const state = await verifyExactTreeTypecheck('a'.repeat(40), '/repo', { + listTreeEntries: () => [], + computeDependencyState: () => 'MATCHES', + storeDir: '/fake-store', + mkdtempFn: async () => '/tmp/worldscript-exact-tree-fake', + runBounded: () => ({ status: 0, error: null, signal: null, timedOut: false, interrupted: false }), + runLocalBinaryDetailed: async () => ({ + status: null, + signal: null, + error: null, + timedOut: false, + interrupted: false, + }), + }); + assert.equal(state, 'UNKNOWN'); + }); + + it('passes a generous default timeoutMs to the tsgo call, overridable via dependencies', async () => { + const runBounded = () => ({ status: 0, error: null, signal: null, timedOut: false, interrupted: false }); + let seenTimeoutMs; + await verifyExactTreeTypecheck('a'.repeat(40), '/repo', { + listTreeEntries: () => [], + computeDependencyState: () => 'MATCHES', + storeDir: '/fake-store', + mkdtempFn: async () => '/tmp/worldscript-exact-tree-fake', + runBounded, + runLocalBinaryDetailed: async (_binary, _args, options) => { + seenTimeoutMs = options?.timeoutMs; + return { status: 0, error: null, signal: null, timedOut: false, interrupted: false }; + }, + }); + // QNBS-v3: repo docs cite ~300s for the full multi-checker typecheck; the default must clear that too. + assert.ok(seenTimeoutMs >= 300_000, `expected margin above the documented ~300s figure, got ${seenTimeoutMs}`); + + let overriddenTimeoutMs; + await verifyExactTreeTypecheck('a'.repeat(40), '/repo', { + listTreeEntries: () => [], + computeDependencyState: () => 'MATCHES', + storeDir: '/fake-store', + mkdtempFn: async () => '/tmp/worldscript-exact-tree-fake', + runBounded, + tsgoTimeoutMs: 42_000, + runLocalBinaryDetailed: async (_binary, _args, options) => { + overriddenTimeoutMs = options?.timeoutMs; + return { status: 0, error: null, signal: null, timedOut: false, interrupted: false }; + }, + }); + assert.equal(overriddenTimeoutMs, 42_000); + }); + + it('canonicalizes a relative repoRoot to an absolute path before any git/install/dependencyState call', async () => { + const seenCwds = []; + await verifyExactTreeTypecheck('a'.repeat(40), '.', { + listTreeEntries: (_sha, cwd) => { + seenCwds.push(cwd); + return []; + }, + computeDependencyState: (_sha, root) => { + seenCwds.push(root); + return 'MATCHES'; + }, + mkdtempFn: async () => '/tmp/worldscript-exact-tree-fake', + runBounded: (_command, _args, options) => { + seenCwds.push(options?.cwd); + return { status: 1, error: null, signal: null, timedOut: false, interrupted: false }; + }, + }); + assert.ok(seenCwds.length > 0); + for (const cwd of seenCwds) assert.equal(cwd, resolve('.'), `expected absolute cwd, got ${cwd}`); + }); + + it('forces --noCheck false and a worktree-local --tsBuildInfoFile on the tsgo invocation, outranking the checked ref\'s own tsconfig', async () => { + let seenArgs; + await verifyExactTreeTypecheck('a'.repeat(40), '/repo', { + listTreeEntries: () => [], + computeDependencyState: () => 'MATCHES', + storeDir: '/fake-store', + mkdtempFn: async () => '/tmp/worldscript-exact-tree-fake', + runBounded: () => ({ status: 0, error: null, signal: null, timedOut: false, interrupted: false }), + runLocalBinaryDetailed: async (_binary, args) => { + seenArgs = args; + return { status: 0, error: null, signal: null, timedOut: false, interrupted: false }; + }, + }); + assert.deepEqual(seenArgs.slice(seenArgs.indexOf('--noCheck')), ['--noCheck', 'false', '--tsBuildInfoFile', '.tsbuildinfo']); + }); +}); + +describe('verifyExactTreeTypecheck (P1: the checked ref must never supply the compiler that certifies it)', () => { + it('invokes tsgo using the trusted repoRoot, never the isolated worktree, once dependencyState proves MATCHES', async () => { + let seenRoot; + let seenCwd; + const state = await verifyExactTreeTypecheck('a'.repeat(40), '/repo', { + listTreeEntries: () => [], + computeDependencyState: () => 'MATCHES', + storeDir: '/fake-store', + mkdtempFn: async () => '/tmp/worldscript-exact-tree-fake', + runBounded: () => ({ status: 0, error: null, signal: null, timedOut: false, interrupted: false }), + runLocalBinaryDetailed: async (_binary, _args, options) => { + seenRoot = options?.root; + seenCwd = options?.cwd; + return { status: 0, error: null, signal: null, timedOut: false, interrupted: false }; + }, + }); + assert.equal(state, 'PASS'); + assert.equal(seenRoot, '/repo', 'the compiler binary must resolve from the trusted repoRoot'); + assert.equal(seenCwd, '/tmp/worldscript-exact-tree-fake', 'the compile must still analyze the isolated tree'); + }); + + it('refuses (UNKNOWN) without creating any worktree when dependencyState is not MATCHES', async () => { + for (const dependencyState of ['DIVERGED', 'UNKNOWN', 'NOT_APPLICABLE']) { + let worktreeCalls = 0; + const state = await verifyExactTreeTypecheck('a'.repeat(40), '/repo', { + listTreeEntries: () => [], + computeDependencyState: () => dependencyState, + runBounded: () => { + worktreeCalls += 1; + return { status: 0, error: null, signal: null, timedOut: false, interrupted: false }; + }, + }); + assert.equal(state, 'UNKNOWN', `expected UNKNOWN for dependencyState=${dependencyState}`); + assert.equal(worktreeCalls, 0, `expected no worktree materialization for dependencyState=${dependencyState}`); + } + }); + + it('refuses (UNKNOWN) a workspace-supplied tsgo binary via the default dependencyState gate, before any install', async () => { + const { root, maliciousSha, markerFile } = makeMaliciousTsgoFixture(); + const state = await verifyExactTreeTypecheck(maliciousSha, root, {}); + assert.equal(state, 'UNKNOWN'); + assert.equal(existsSync(markerFile), false, 'the ref-supplied tsgo bin must never execute'); + }); + + it('never executes a workspace-supplied tsgo binary even via a real, legitimate frozen install (dependencyState mocked to MATCHES)', async () => { + const { root, maliciousSha, markerFile } = makeMaliciousTsgoFixture(); + // QNBS-v3: bypasses the gate above -- root's own tsgo lookup finds nothing, so the isolated tree's real, freshly-installed evil bin is still never touched. + const state = await verifyExactTreeTypecheck(maliciousSha, root, { computeDependencyState: () => 'MATCHES' }); + assert.notEqual(state, 'PASS', 'a ref-supplied compiler must never certify itself'); + assert.equal(existsSync(markerFile), false, 'the ref-supplied tsgo bin must never execute'); + }); +}); + +describe('verifyExactTreePreflight (P1: refuse before any materialization touches disk)', () => { + function makeRepoWithTrackedPath(relativePath, { symlink } = {}) { + const root = mkdtempSync(join(process.cwd(), '.worldscript-exact-tree-nm-')); + temporaryRoots.push(root); + git(root, ['init', '--quiet', '--initial-branch=main']); + git(root, ['config', 'user.email', 'test@example.com']); + git(root, ['config', 'user.name', 'test']); + writeFileSync(join(root, 'README.md'), 'fixture\n'); + const fullPath = join(root, relativePath); + mkdirSync(join(fullPath, '..'), { recursive: true }); + if (symlink) { + execFileSync('ln', ['-s', '/nonexistent-target', fullPath]); + } else { + writeFileSync(fullPath, '#!/bin/sh\necho attacker-controlled\n'); + } + git(root, ['add', '-A']); + git(root, ['-c', 'commit.gpgsign=false', 'commit', '--quiet', '-m', 'test']); + const sha = git(root, ['rev-parse', 'HEAD']).trim(); + return { root, sha }; + } + + // QNBS-v3: counts calls, not just final state -- a missing precondition check could also catch-to-UNKNOWN. + function refusingMaterializationSpy() { + const calls = { runBounded: 0, runLocalBinaryDetailed: 0 }; + return { + calls, + runBounded: async () => { + calls.runBounded += 1; + throw new Error('must not be called -- a tracked node_modules must be refused before materialization'); + }, + runLocalBinaryDetailed: async () => { + calls.runLocalBinaryDetailed += 1; + throw new Error('must not be called -- a tracked node_modules must be refused before materialization'); + }, + }; + } + + it('refuses a commit with a tracked root-level node_modules/.bin/tsgo (UNKNOWN, no materialization)', async () => { + const { root, sha } = makeRepoWithTrackedPath('node_modules/.bin/tsgo'); + const spy = refusingMaterializationSpy(); + const state = await verifyExactTreeTypecheck(sha, root, spy); + assert.equal(state, 'UNKNOWN'); + assert.equal(spy.calls.runBounded, 0, 'materialization must never start for a tracked node_modules commit'); + }); + + it('refuses a commit with a tracked nested packages/foo/node_modules/x (UNKNOWN, no materialization)', async () => { + const { root, sha } = makeRepoWithTrackedPath('packages/foo/node_modules/x/index.js'); + const spy = refusingMaterializationSpy(); + const state = await verifyExactTreeTypecheck(sha, root, spy); + assert.equal(state, 'UNKNOWN'); + assert.equal(spy.calls.runBounded, 0, 'materialization must never start for a tracked node_modules commit'); + }); + + it('refuses a commit with a tracked node_modules symlink itself (UNKNOWN, no materialization)', async () => { + const { root, sha } = makeRepoWithTrackedPath('node_modules', { symlink: true }); + const spy = refusingMaterializationSpy(); + const state = await verifyExactTreeTypecheck(sha, root, spy); + assert.equal(state, 'UNKNOWN'); + assert.equal(spy.calls.runBounded, 0, 'materialization must never start for a tracked node_modules commit'); + }); + + it('refuses a case-variant NODE_MODULES/.bin/tsgo (aliases node_modules on case-insensitive filesystems)', async () => { + const { root, sha } = makeRepoWithTrackedPath('NODE_MODULES/.bin/tsgo'); + const spy = refusingMaterializationSpy(); + const state = await verifyExactTreeTypecheck(sha, root, spy); + assert.equal(state, 'UNKNOWN'); + assert.equal(spy.calls.runBounded, 0, 'materialization must never start for a tracked node_modules commit'); + }); + + it('leaves an ordinary commit with no tracked node_modules eligible for materialization', async () => { + const { root, sha } = makeTinyTsRepo('const x: number = 1;\n'); + let worktreeAddCalled = false; + const state = await verifyExactTreeTypecheck(sha, root, { + computeDependencyState: () => 'MATCHES', + mkdtempFn: async () => '/tmp/worldscript-exact-tree-fake', + runBounded: (_command, args) => { + if (args.includes('add')) worktreeAddCalled = true; + return { status: 1, error: null, signal: null, timedOut: false, interrupted: false }; + }, + }); + assert.equal(worktreeAddCalled, true); + assert.equal(state, 'UNKNOWN'); // fake path -- worktree add itself is mocked to fail, but it was reached. + }); + + function makeRepoWithTrackedSymlink(linkPath, target) { + const root = mkdtempSync(join(process.cwd(), '.worldscript-exact-tree-symlink-')); + temporaryRoots.push(root); + git(root, ['init', '--quiet', '--initial-branch=main']); + git(root, ['config', 'user.email', 'test@example.com']); + git(root, ['config', 'user.name', 'test']); + writeFileSync(join(root, 'real.txt'), 'inside the tree\n'); + const fullPath = join(root, linkPath); + mkdirSync(join(fullPath, '..'), { recursive: true }); + execFileSync('ln', ['-s', target, fullPath]); + git(root, ['add', '-A']); + git(root, ['-c', 'commit.gpgsign=false', 'commit', '--quiet', '-m', 'test']); + const sha = git(root, ['rev-parse', 'HEAD']).trim(); + return { root, sha }; + } + + it('refuses a tracked symlink whose relative target escapes the isolated tree (UNKNOWN, no materialization)', async () => { + const { root, sha } = makeRepoWithTrackedSymlink('sub/escaping-link', '../../../outside-target'); + const spy = refusingMaterializationSpy(); + const state = await verifyExactTreeTypecheck(sha, root, spy); + assert.equal(state, 'UNKNOWN'); + assert.equal(spy.calls.runBounded, 0, 'materialization must never start for an escaping symlink commit'); + }); + + it('refuses a tracked symlink with an absolute target (UNKNOWN, no materialization)', async () => { + const { root, sha } = makeRepoWithTrackedSymlink('escaping-abs-link', '/etc/passwd'); + const spy = refusingMaterializationSpy(); + const state = await verifyExactTreeTypecheck(sha, root, spy); + assert.equal(state, 'UNKNOWN'); + assert.equal(spy.calls.runBounded, 0, 'materialization must never start for an escaping symlink commit'); + }); + + it('leaves a commit with an ordinary, non-escaping tracked symlink eligible for materialization', async () => { + const { root, sha } = makeRepoWithTrackedSymlink('safe-link', 'real.txt'); + let worktreeAddCalled = false; + const state = await verifyExactTreeTypecheck(sha, root, { + computeDependencyState: () => 'MATCHES', + mkdtempFn: async () => '/tmp/worldscript-exact-tree-fake', + runBounded: (_command, args) => { + if (args.includes('add')) worktreeAddCalled = true; + return { status: 1, error: null, signal: null, timedOut: false, interrupted: false }; + }, + }); + assert.equal(worktreeAddCalled, true); + assert.equal(state, 'UNKNOWN'); // fake path -- worktree add itself is mocked to fail, but it was reached. + }); + + function makeRepoWithGitattributesFilter(filterName, attributesPath = '.gitattributes') { + const root = mkdtempSync(join(process.cwd(), '.worldscript-exact-tree-filterattr-')); + temporaryRoots.push(root); + git(root, ['init', '--quiet', '--initial-branch=main']); + git(root, ['config', 'user.email', 'test@example.com']); + git(root, ['config', 'user.name', 'test']); + const fullAttrPath = join(root, attributesPath); + mkdirSync(join(fullAttrPath, '..'), { recursive: true }); + writeFileSync(fullAttrPath, `tracked.bin filter=${filterName}\n`); + writeFileSync(join(root, 'tracked.bin'), 'secret content\n'); + git(root, ['add', '-A']); + git(root, ['-c', 'commit.gpgsign=false', 'commit', '--quiet', '-m', 'test']); + const sha = git(root, ['rev-parse', 'HEAD']).trim(); + return { root, sha }; + } + + it('refuses (UNKNOWN, no materialization) when a tracked .gitattributes selects a LOCALLY-configured filter', async () => { + const { root, sha } = makeRepoWithGitattributesFilter('evilfilter'); + // QNBS-v3: --local specifically, not --global -- proves this closes the local-scope gap too. + git(root, ['config', '--local', 'filter.evilfilter.smudge', 'cat']); + const spy = refusingMaterializationSpy(); + const state = await verifyExactTreeTypecheck(sha, root, spy); + assert.equal(state, 'UNKNOWN'); + assert.equal(spy.calls.runBounded, 0, 'materialization must never start when a configured filter is selected'); + }); + + it('refuses (UNKNOWN, no materialization) when a NESTED tracked .gitattributes selects a configured filter', async () => { + const { root, sha } = makeRepoWithGitattributesFilter('nestedfilter', 'sub/.gitattributes'); + git(root, ['config', '--local', 'filter.nestedfilter.process', 'cat']); + const spy = refusingMaterializationSpy(); + const state = await verifyExactTreeTypecheck(sha, root, spy); + assert.equal(state, 'UNKNOWN'); + assert.equal(spy.calls.runBounded, 0, 'materialization must never start when a configured filter is selected'); + }); + + it('leaves a commit eligible when its .gitattributes selects a filter name that is not configured anywhere', async () => { + const { root, sha } = makeRepoWithGitattributesFilter('totally-undefined-filter-name'); + let worktreeAddCalled = false; + const state = await verifyExactTreeTypecheck(sha, root, { + computeDependencyState: () => 'MATCHES', + mkdtempFn: async () => '/tmp/worldscript-exact-tree-fake', + runBounded: (_command, args) => { + if (args.includes('add')) worktreeAddCalled = true; + return { status: 1, error: null, signal: null, timedOut: false, interrupted: false }; + }, + }); + assert.equal(worktreeAddCalled, true); + assert.equal(state, 'UNKNOWN'); // fake path -- worktree add itself is mocked to fail, but it was reached. + }); + + it('refuses (UNKNOWN) a commit containing an unmaterialized gitlink (submodule)', async () => { + const root = mkdtempSync(join(process.cwd(), '.worldscript-exact-tree-gitlink-')); + temporaryRoots.push(root); + git(root, ['init', '--quiet', '--initial-branch=main']); + git(root, ['config', 'user.email', 'test@example.com']); + git(root, ['config', 'user.name', 'test']); + writeFileSync(join(root, 'real.ts'), 'const x: number = 1;\n'); + // QNBS-v3: a real submodule commit hash object need not exist locally -- ls-tree/mode is what matters. + git(root, ['update-index', '--add', '--cacheinfo', '160000', 'a'.repeat(40), 'sub-module']); + git(root, ['add', 'real.ts']); + git(root, ['-c', 'commit.gpgsign=false', 'commit', '--quiet', '-m', 'test']); + const sha = git(root, ['rev-parse', 'HEAD']).trim(); + const spy = refusingMaterializationSpy(); + const state = await verifyExactTreeTypecheck(sha, root, spy); + assert.equal(state, 'UNKNOWN'); + assert.equal(spy.calls.runBounded, 0, 'materialization must never start when a gitlink is present'); + }); + + it('refuses (UNKNOWN) a tracked symlink with a Windows-style backslash-relative escaping target', async () => { + const { root, sha } = makeRepoWithTrackedSymlink('sub/escaping-link', '..\\..\\..\\outside-target'); + const spy = refusingMaterializationSpy(); + const state = await verifyExactTreeTypecheck(sha, root, spy); + assert.equal(state, 'UNKNOWN'); + assert.equal(spy.calls.runBounded, 0, 'materialization must never start for an escaping symlink commit'); + }); + + it('refuses (UNKNOWN) a tracked symlink with a Windows drive-letter absolute target', async () => { + const { root, sha } = makeRepoWithTrackedSymlink('escaping-drive-link', 'C:\\Windows\\System32\\drivers\\etc\\hosts'); + const spy = refusingMaterializationSpy(); + const state = await verifyExactTreeTypecheck(sha, root, spy); + assert.equal(state, 'UNKNOWN'); + assert.equal(spy.calls.runBounded, 0, 'materialization must never start for an escaping symlink commit'); + }); + + it('refuses (UNKNOWN) a tracked symlink with a UNC path target', async () => { + const { root, sha } = makeRepoWithTrackedSymlink('escaping-unc-link', '\\\\attacker-host\\share\\payload'); + const spy = refusingMaterializationSpy(); + const state = await verifyExactTreeTypecheck(sha, root, spy); + assert.equal(state, 'UNKNOWN'); + assert.equal(spy.calls.runBounded, 0, 'materialization must never start for an escaping symlink commit'); + }); + + function makeRepoWithTsconfigScope(tsconfigContent) { + const root = mkdtempSync(join(process.cwd(), '.worldscript-exact-tree-tsconfigscope-')); + temporaryRoots.push(root); + git(root, ['init', '--quiet', '--initial-branch=main']); + git(root, ['config', 'user.email', 'test@example.com']); + git(root, ['config', 'user.name', 'test']); + writeFileSync(join(root, 'tsconfig.tsgo.json'), tsconfigContent); + writeFileSync(join(root, 'index.ts'), 'const x: number = 1;\n'); + git(root, ['add', '-A']); + git(root, ['-c', 'commit.gpgsign=false', 'commit', '--quiet', '-m', 'test']); + const sha = git(root, ['rev-parse', 'HEAD']).trim(); + return { root, sha }; + } + + it('refuses (UNKNOWN) when tsconfig.tsgo.json include escapes the tree via ../..', async () => { + const { root, sha } = makeRepoWithTsconfigScope( + JSON.stringify({ include: ['../../../etc/**/*.ts'] }), + ); + const spy = refusingMaterializationSpy(); + const state = await verifyExactTreeTypecheck(sha, root, spy); + assert.equal(state, 'UNKNOWN'); + assert.equal(spy.calls.runBounded, 0, 'materialization must never start for an escaping tsconfig scope'); + }); + + it('refuses (UNKNOWN) when tsconfig.tsgo.json extends an absolute path', async () => { + const { root, sha } = makeRepoWithTsconfigScope(JSON.stringify({ extends: '/etc/tsconfig-base.json' })); + const spy = refusingMaterializationSpy(); + const state = await verifyExactTreeTypecheck(sha, root, spy); + assert.equal(state, 'UNKNOWN'); + assert.equal(spy.calls.runBounded, 0, 'materialization must never start for an escaping tsconfig scope'); + }); + + it('refuses (UNKNOWN) when tsconfig.tsgo.json is not valid JSON', async () => { + const { root, sha } = makeRepoWithTsconfigScope('{ not valid json'); + const spy = refusingMaterializationSpy(); + const state = await verifyExactTreeTypecheck(sha, root, spy); + assert.equal(state, 'UNKNOWN'); + assert.equal(spy.calls.runBounded, 0, 'materialization must never start for an unparseable tsconfig'); + }); + + it('leaves a commit eligible when tsconfig.tsgo.json only uses ordinary in-tree relative paths', async () => { + const { root, sha } = makeRepoWithTsconfigScope(JSON.stringify({ include: ['index.ts'] })); + let worktreeAddCalled = false; + const state = await verifyExactTreeTypecheck(sha, root, { + computeDependencyState: () => 'MATCHES', + mkdtempFn: async () => '/tmp/worldscript-exact-tree-fake', + runBounded: (_command, args) => { + if (args.includes('add')) worktreeAddCalled = true; + return { status: 1, error: null, signal: null, timedOut: false, interrupted: false }; + }, + }); + assert.equal(worktreeAddCalled, true); + assert.equal(state, 'UNKNOWN'); // fake path -- worktree add itself is mocked to fail, but it was reached. + }); +}); + +describe('createIsolatedWorktree (P1/P2: post-checkout hooks and checkout filters must not execute)', () => { + it('does not execute a configured post-checkout hook while materializing the isolated worktree', async () => { + const { root, sha } = makeTinyTsRepo('const x: number = 1;\n'); + const markerRoot = mkdtempSync(join(process.cwd(), '.worldscript-exact-tree-hookmark-')); + temporaryRoots.push(markerRoot); + const markerFile = join(markerRoot, 'hook-ran.txt'); + const hooksDir = join(root, '.git', 'hooks'); + mkdirSync(hooksDir, { recursive: true }); + writeFileSync(join(hooksDir, 'post-checkout'), `#!/bin/sh\necho ran > "${markerFile}"\n`, { mode: 0o755 }); + + const created = await createIsolatedWorktree(sha, root, {}); + try { + assert.equal(created.ok, true); + assert.equal(existsSync(markerFile), false, 'post-checkout hook must not have run'); + } finally { + await removeIsolatedWorktree(created.path, root, {}); + } + }); + + it('does not execute a configured smudge/checkout filter (e.g. Git LFS-style) while materializing the isolated worktree', async () => { + const markerRoot = mkdtempSync(join(process.cwd(), '.worldscript-exact-tree-filtermark-')); + temporaryRoots.push(markerRoot); + const markerFile = join(markerRoot, 'filter-ran.txt'); + + const root = mkdtempSync(join(process.cwd(), '.worldscript-exact-tree-filter-')); + temporaryRoots.push(root); + git(root, ['init', '--quiet', '--initial-branch=main']); + git(root, ['config', 'user.email', 'test@example.com']); + git(root, ['config', 'user.name', 'test']); + writeFileSync(join(root, '.gitattributes'), 'tracked.bin filter=evilfilter\n'); + writeFileSync(join(root, 'tracked.bin'), 'secret content\n'); + git(root, ['add', '-A']); + git(root, ['-c', 'commit.gpgsign=false', 'commit', '--quiet', '-m', 'test']); + const sha = git(root, ['rev-parse', 'HEAD']).trim(); + + // QNBS-v3: simulates a developer with an LFS-style filter registered globally (as `git lfs install` would). + const fakeHome = mkdtempSync(join(process.cwd(), '.worldscript-exact-tree-fakehome-')); + temporaryRoots.push(fakeHome); + const filterCommand = `sh -c "echo ran > '${markerFile}'; cat"`; + git(root, ['config', '--file', join(fakeHome, '.gitconfig'), 'filter.evilfilter.smudge', filterCommand]); + git(root, ['config', '--file', join(fakeHome, '.gitconfig'), 'filter.evilfilter.required', 'true']); + + const originalHome = process.env.HOME; + process.env.HOME = fakeHome; + let created; + try { + created = await createIsolatedWorktree(sha, root, {}); + assert.equal(created.ok, true); + assert.equal(existsSync(markerFile), false, 'the configured smudge filter must not have run'); + } finally { + process.env.HOME = originalHome; + if (created) await removeIsolatedWorktree(created.path, root, {}); + } + }); +}); + +describe('resolveRef (bounded, output-capturing ref resolution)', () => { + it('resolves a valid ref to its full commit SHA via the injected runGit', () => { + const sha = resolveRef('HEAD', '/repo', { + runGit: (args) => { + assert.deepEqual(args, ['rev-parse', '--verify', 'HEAD^{commit}']); + return { status: 0, stdout: `${'a'.repeat(40)}\n`, stderr: '', error: undefined }; + }, + }); + assert.equal(sha, 'a'.repeat(40)); + }); + + it('returns null for a ref that does not resolve', () => { + const sha = resolveRef('not-a-ref', '/repo', { + runGit: () => ({ status: 128, stdout: '', stderr: 'fatal: bad revision', error: undefined }), + }); + assert.equal(sha, null); + }); + + // QNBS-v3: regression -- the CLI path must not crash when no dependencies argument is passed. + it('does not throw when called with only (ref, repoRoot), matching a bare call site', () => { + assert.doesNotThrow(() => resolveRef('HEAD', process.cwd())); + }); +}); + +describe('verifyExactTreeForShas (dedup, sequential, aggregation)', () => { + it('deduplicates identical SHAs so the underlying check runs once', async () => { + let calls = 0; + const state = await verifyExactTreeForShas(['a'.repeat(40), 'a'.repeat(40)], '/repo', { + listTreeEntries: () => [], + computeDependencyState: () => 'MATCHES', + mkdtempFn: async () => { + calls += 1; + return '/tmp/worldscript-exact-tree-fake'; + }, + runBounded: (_command, args) => { + if (args.includes('add')) return { status: 1, error: null, signal: null, timedOut: false, interrupted: false }; + return { status: 0, error: null, signal: null, timedOut: false, interrupted: false }; + }, + }); + assert.equal(calls, 1); + assert.equal(state, 'UNKNOWN'); + }); + + it('returns NOT_APPLICABLE for an empty list', async () => { + assert.equal(await verifyExactTreeForShas([], '/repo'), 'NOT_APPLICABLE'); + }); + + it('aggregates with FAIL outranking UNKNOWN', async () => { + // QNBS-v3: sequential processing -- alternate by call order for one genuine FAIL, one UNKNOWN. + let tsgoCallCount = 0; + const state = await verifyExactTreeForShas(['a'.repeat(40), 'b'.repeat(40)], '/repo', { + listTreeEntries: () => [], + computeDependencyState: () => 'MATCHES', + storeDir: '/fake-store', + mkdtempFn: async () => '/tmp/worldscript-exact-tree-fake', + runBounded: () => ({ status: 0, error: null, signal: null, timedOut: false, interrupted: false }), + runLocalBinaryDetailed: async () => { + tsgoCallCount += 1; + // QNBS-v3: first sha -> genuine FAIL (numeric non-zero exit); second sha -> UNKNOWN (signaled). + return tsgoCallCount === 1 + ? { status: 2, error: null, signal: null, timedOut: false, interrupted: false } + : { status: null, error: null, signal: 'SIGKILL', timedOut: false, interrupted: false }; + }, + }); + assert.equal(tsgoCallCount, 2); + assert.equal(state, 'FAIL'); + }); +}); + +describe('interruption handling (P2: explicit user intent must stop the whole run)', () => { + it('rejects when the initial stale-worktree prune is interrupted, before any worktree is created', async () => { + let addCalled = false; + await assert.rejects( + verifyExactTreeTypecheck('a'.repeat(40), '/repo', { + listTreeEntries: () => [], + computeDependencyState: () => 'MATCHES', + mkdtempFn: async () => '/tmp/worldscript-exact-tree-fake', + runBounded: (_command, args) => { + if (args.includes('prune')) return { status: null, error: null, signal: null, timedOut: false, interrupted: true }; + if (args.includes('add')) addCalled = true; + return { status: 0, error: null, signal: null, timedOut: false, interrupted: false }; + }, + }), + { name: 'ExactTreeInterrupted' }, + ); + assert.equal(addCalled, false, 'worktree add must never start after the prune step is interrupted'); + }); + + it('createIsolatedWorktree reports interrupted:true in its return shape, not a generic failure', async () => { + const created = await createIsolatedWorktree('a'.repeat(40), '/repo', { + mkdtempFn: async () => '/tmp/worldscript-exact-tree-fake', + runBounded: (_command, args) => { + if (args.includes('add')) return { status: null, error: null, signal: null, timedOut: false, interrupted: true }; + return { status: 0, error: null, signal: null, timedOut: false, interrupted: false }; + }, + }); + assert.equal(created.ok, false); + assert.equal(created.interrupted, true); + }); + + it('removeIsolatedWorktree still completes cleanup, then rejects, when git worktree remove is interrupted', async () => { + let removeCalled = false; + let pruneCalled = false; + let rmCalled = false; + await assert.rejects( + removeIsolatedWorktree('/tmp/worldscript-exact-tree-fake', '/repo', { + runBounded: (_command, args) => { + if (args.includes('remove')) { + removeCalled = true; + return { status: null, error: null, signal: null, timedOut: false, interrupted: true }; + } + if (args.includes('prune')) { + pruneCalled = true; + return { status: 0, error: null, signal: null, timedOut: false, interrupted: false }; + } + return { status: 0, error: null, signal: null, timedOut: false, interrupted: false }; + }, + rmFn: async () => { + rmCalled = true; + }, + }), + { name: 'ExactTreeInterrupted' }, + ); + assert.equal(removeCalled, true); + assert.equal(rmCalled, true, 'the fallback sweep must still run even though remove was interrupted'); + assert.equal(pruneCalled, true, 'the fallback prune must still run even though remove was interrupted'); + }); + + it('verifyExactTreeTypecheck rejects (does not return UNKNOWN) when worktree creation is interrupted', async () => { + await assert.rejects( + verifyExactTreeTypecheck('a'.repeat(40), '/repo', { + listTreeEntries: () => [], + computeDependencyState: () => 'MATCHES', + mkdtempFn: async () => '/tmp/worldscript-exact-tree-fake', + runBounded: (_command, args) => { + if (args.includes('add')) return { status: null, error: null, signal: null, timedOut: false, interrupted: true }; + return { status: 0, error: null, signal: null, timedOut: false, interrupted: false }; + }, + }), + { name: 'ExactTreeInterrupted' }, + ); + }); + + it('verifyExactTreeTypecheck rejects when the pnpm install is interrupted, and still cleans up the worktree', async () => { + let removeCalled = false; + await assert.rejects( + verifyExactTreeTypecheck('a'.repeat(40), '/repo', { + listTreeEntries: () => [], + computeDependencyState: () => 'MATCHES', + storeDir: '/fake-store', + mkdtempFn: async () => '/tmp/worldscript-exact-tree-fake', + runBounded: (_command, args) => { + if (args.includes('remove')) removeCalled = true; + if (args.includes('worktree')) return { status: 0, error: null, signal: null, timedOut: false, interrupted: false }; + // QNBS-v3: the pnpm install call. + return { status: null, error: null, signal: null, timedOut: false, interrupted: true }; + }, + }), + { name: 'ExactTreeInterrupted' }, + ); + assert.equal(removeCalled, true, 'cleanup must still run even when interrupted mid-install'); + }); + + it('verifyExactTreeTypecheck rejects when tsgo itself is interrupted, and still cleans up the worktree', async () => { + let removeCalled = false; + await assert.rejects( + verifyExactTreeTypecheck('a'.repeat(40), '/repo', { + listTreeEntries: () => [], + computeDependencyState: () => 'MATCHES', + storeDir: '/fake-store', + mkdtempFn: async () => '/tmp/worldscript-exact-tree-fake', + runBounded: (_command, args) => { + if (args.includes('remove')) removeCalled = true; + return { status: 0, error: null, signal: null, timedOut: false, interrupted: false }; + }, + runLocalBinaryDetailed: async () => ({ + status: null, + error: null, + signal: null, + timedOut: false, + interrupted: true, + }), + }), + { name: 'ExactTreeInterrupted' }, + ); + assert.equal(removeCalled, true, 'cleanup must still run even when interrupted mid-typecheck'); + }); + + it('verifyExactTreeForShas stops processing remaining SHAs immediately when one is interrupted', async () => { + let worktreeAddCalls = 0; + await assert.rejects( + verifyExactTreeForShas(['a'.repeat(40), 'b'.repeat(40)], '/repo', { + listTreeEntries: () => [], + computeDependencyState: () => 'MATCHES', + mkdtempFn: async () => '/tmp/worldscript-exact-tree-fake', + runBounded: (_command, args) => { + if (args.includes('add')) { + worktreeAddCalls += 1; + return { status: null, error: null, signal: null, timedOut: false, interrupted: true }; + } + return { status: 0, error: null, signal: null, timedOut: false, interrupted: false }; + }, + }), + { name: 'ExactTreeInterrupted' }, + ); + assert.equal(worktreeAddCalls, 1, 'the second SHA must never start a worktree add after the first is interrupted'); + }); + + it('main() reports an interrupted outcome and exits 130 instead of continuing or printing a normal result', async () => { + const logs = []; + const originalLog = console.log; + console.log = (message) => logs.push(message); + const originalExitCode = process.exitCode; + process.exitCode = 0; + try { + await main(['HEAD'], { + repoRoot: '/repo', + listTreeEntries: () => [], + computeDependencyState: () => 'MATCHES', + runGit: () => ({ status: 0, stdout: `${'a'.repeat(40)}\n`, stderr: '', error: undefined }), + mkdtempFn: async () => '/tmp/worldscript-exact-tree-fake', + runBounded: (_command, args) => { + if (args.includes('add')) return { status: null, error: null, signal: null, timedOut: false, interrupted: true }; + return { status: 0, error: null, signal: null, timedOut: false, interrupted: false }; + }, + }); + assert.equal(process.exitCode, 130); + } finally { + console.log = originalLog; + process.exitCode = originalExitCode; + } + assert.ok(logs.some((line) => line.includes('interrupted')), logs.join('\n')); + assert.ok(!logs.some((line) => line.includes('result:')), 'must not print a normal PASS/FAIL/UNKNOWN result line'); + }); +}); + +describe('git replace refs (P1: exact-tree checks must not be fooled by refs/replace)', () => { + it('operates on the original tree, not a git-replace substitute, when the preflight lists tracked entries', async () => { + const root = mkdtempSync(join(process.cwd(), '.worldscript-exact-tree-replace-')); + temporaryRoots.push(root); + git(root, ['init', '--quiet', '--initial-branch=main']); + git(root, ['config', 'user.email', 'test@example.com']); + git(root, ['config', 'user.name', 'test']); + writeFileSync(join(root, 'README.md'), 'original\n'); + git(root, ['add', '-A']); + git(root, ['-c', 'commit.gpgsign=false', 'commit', '--quiet', '-m', 'original']); + const originalSha = git(root, ['rev-parse', 'HEAD']).trim(); + + // QNBS-v3: if refs/replace were honored, the preflight would see the replacement's node_modules and refuse. + mkdirSync(join(root, 'node_modules', '.bin'), { recursive: true }); + writeFileSync(join(root, 'node_modules', '.bin', 'tsgo'), '#!/bin/sh\necho attacker\n'); + git(root, ['add', '-A']); + git(root, ['-c', 'commit.gpgsign=false', 'commit', '--quiet', '-m', 'replacement']); + const replacedSha = git(root, ['rev-parse', 'HEAD']).trim(); + git(root, ['reset', '--hard', '--quiet', originalSha]); + git(root, ['replace', originalSha, replacedSha]); + + let worktreeAddCalled = false; + const state = await verifyExactTreeTypecheck(originalSha, root, { + computeDependencyState: () => 'MATCHES', + mkdtempFn: async () => '/tmp/worldscript-exact-tree-fake', + runBounded: (_command, args) => { + if (args.includes('add')) worktreeAddCalled = true; + return { status: 1, error: null, signal: null, timedOut: false, interrupted: false }; + }, + }); + assert.equal(worktreeAddCalled, true, 'must operate on the original tree, not the git-replace substitute'); + assert.equal(state, 'UNKNOWN'); // fake path -- worktree add itself is mocked to fail, but it was reached. + }); +}); + +describe('main (real CLI entry path, realistic DI)', () => { + it('resolves HEAD by default, verifies it, and prints the result without crashing', async () => { + const logs = []; + const originalLog = console.log; + console.log = (message) => logs.push(message); + try { + await main([], { + repoRoot: '/repo', + listTreeEntries: () => [], + computeDependencyState: () => 'MATCHES', + runGit: () => ({ status: 0, stdout: `${'a'.repeat(40)}\n`, stderr: '', error: undefined }), + runBounded: (_command, args) => { + if (args.includes('add')) return { status: 1, error: null, signal: null, timedOut: false, interrupted: false }; + return { status: 0, error: null, signal: null, timedOut: false, interrupted: false }; + }, + mkdtempFn: async () => '/tmp/worldscript-exact-tree-fake', + }); + } finally { + console.log = originalLog; + } + assert.ok(logs.some((line) => line.includes('result: UNKNOWN')), logs.join('\n')); + }); + + it('resolves and verifies multiple explicit refs', async () => { + const resolvedRefs = []; + const logs = []; + const originalLog = console.log; + console.log = (message) => logs.push(message); + try { + await main(['main', 'feature-branch'], { + repoRoot: '/repo', + listTreeEntries: () => [], + computeDependencyState: () => 'MATCHES', + runGit: (args) => { + resolvedRefs.push(args[2]); + return { status: 0, stdout: `${'a'.repeat(40)}\n`, stderr: '', error: undefined }; + }, + runBounded: (_command, args) => { + if (args.includes('add')) return { status: 1, error: null, signal: null, timedOut: false, interrupted: false }; + return { status: 0, error: null, signal: null, timedOut: false, interrupted: false }; + }, + mkdtempFn: async () => '/tmp/worldscript-exact-tree-fake', + }); + } finally { + console.log = originalLog; + } + assert.deepEqual(resolvedRefs, ['main^{commit}', 'feature-branch^{commit}']); + assert.ok(logs.some((line) => line.includes('verifying 2 commit(s)')), logs.join('\n')); + }); + + // QNBS-v3: regression -- the real resolveRef(ref) call-site crash; helper-only tests missed main(). + it('reports a clear error and a non-zero exit code for an unresolvable ref, without crashing', async () => { + const errors = []; + const originalError = console.error; + console.error = (message) => errors.push(message); + const originalExitCode = process.exitCode; + process.exitCode = 0; + try { + await main(['not-a-real-ref'], { + repoRoot: '/repo', + runGit: () => ({ status: 128, stdout: '', stderr: 'fatal: bad revision', error: undefined }), + }); + assert.equal(process.exitCode, 1); + } finally { + console.error = originalError; + process.exitCode = originalExitCode; + } + assert.ok(errors.some((line) => line.includes('could not resolve ref: not-a-real-ref')), errors.join('\n')); + }); + + it('sets a non-zero exit code when the exact tree fails to typecheck', async () => { + const originalExitCode = process.exitCode; + process.exitCode = 0; + try { + await main(['HEAD'], { + repoRoot: '/repo', + listTreeEntries: () => [], + computeDependencyState: () => 'MATCHES', + storeDir: '/fake-store', + runGit: () => ({ status: 0, stdout: `${'a'.repeat(40)}\n`, stderr: '', error: undefined }), + runBounded: () => ({ status: 0, error: null, signal: null, timedOut: false, interrupted: false }), + mkdtempFn: async () => '/tmp/worldscript-exact-tree-fake', + runLocalBinaryDetailed: async () => ({ + status: 2, + error: null, + signal: null, + timedOut: false, + interrupted: false, + }), + }); + assert.equal(process.exitCode, 1); + } finally { + process.exitCode = originalExitCode; + } + }); +});