Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/ci-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,10 @@ jobs:
env:
GITNEXUS_REQUIRE_FTS: '1'
GITNEXUS_E2E_CLI: dist
# Ubuntu coverage proves all four durable recovery boundaries. The OS
# matrix keeps the most destructive native filesystem boundary inside
# its 15-minute per-shard watchdog.
GITNEXUS_RECOVERY_BOUNDARIES: during-delete
Comment thread
100yenadmin marked this conversation as resolved.
steps:
# persist-credentials: false — runs tests only, never pushes (zizmor
# credential-persistence / artipacked audit).
Expand Down
8 changes: 7 additions & 1 deletion gitnexus-shared/src/scope-resolution/finalize-algorithm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export interface FinalizeHooks {
targetRaw: string,
fromFile: string,
workspaceIndex: WorkspaceIndex,
parsedImport?: ParsedImport,
): string | readonly string[] | null;

/**
Expand Down Expand Up @@ -348,7 +349,12 @@ function makeEdgeDrafts(
];
}

const targetFile = hooks.resolveImportTarget(parsed.targetRaw ?? '', file.filePath, workspace);
const targetFile = hooks.resolveImportTarget(
parsed.targetRaw ?? '',
file.filePath,
workspace,
parsed,
);

// Edge is unresolvable at the file level — mark unresolved now.
if (targetFile === null) {
Expand Down
5 changes: 5 additions & 0 deletions gitnexus-shared/src/scope-resolution/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ export type ParsedImport =
readonly localName: string;
readonly importedName: string;
readonly targetRaw: string;
/** Provider-specific imported symbol category when module and symbol
* namespaces have distinct resolution rules (for example PHP). */
readonly importedSymbolKind?: 'type' | 'function' | 'const';
/**
* Set by providers when `targetRaw` already names the imported symbol
* rather than only its containing module. Consumers that compose
Expand All @@ -127,6 +130,8 @@ export type ParsedImport =
readonly alias: string;
readonly targetRaw: string;
/** See the same field on the `named` variant. */
readonly importedSymbolKind?: 'type' | 'function' | 'const';
/** See the same field on the `named` variant. */
readonly targetIncludesImportedName?: boolean;
}
/**
Expand Down
4 changes: 2 additions & 2 deletions gitnexus/bench/scope-capture/baselines.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@
"_note": "PR #1934: F66/F68 let-binding pattern narrowing; F71 union (Struct-labeled, now materialized via legacy @definition.struct + resolvable); F72 macro FULLY WIRED \u2014 @declaration.macro/@reference.macro + MacroRegistry \u2192 USES edges to Macro nodes (never a same-named fn). + rust-macro / rust-union fixtures and merged with origin/main #1975 rust-scoped-impl; fingerprint re-baselined (scaling ~0.99, fixture_count 126). #1992: + rust-nested-tail-collision-generic and rust-generic-impl-same-method-name (F3) fixtures \u2014 pure fixture-corpus drift, no scope-extractor change; fixture_count 127->129, fingerprint 56ffc1c0->b00aea0f."
},
"php": {
"fingerprint": "bc2c27c5ba26d5aea61142a2a99fb772222f5b969205260eb7a71b4c0bd73cdb",
"fingerprint": "31c9e3f3cb7094a2bf9021cf9db859036e002f8b44605cd993b470fc600e97cb",
"scaling_budget": 1.5,
"_rebaselined": "#1956: heritage-bearing scale source (class extends Base + use trait); both forms gated at scale; linear (~1.04).",
"_rebaselined": "#1956: heritage-bearing scale source (class extends Base + use trait); both forms gated at scale; linear (~1.04). | #95: PHP imports carry a symbol-kind capture so function/constant imports resolve by declaring file; capture shape changes, scaling remains linear (~1.04).",
"_note": "PR #1931: F53 import multi-clause, F54 enum_case, F55 anonymous_class \u2014 fixture count 138\u2192140, fingerprint drift expected."
},
"ruby": {
Expand Down
33 changes: 33 additions & 0 deletions gitnexus/scripts/reconciliation/canary-environment.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const PORTABLE_KEYS = [
'PATH',
'SHELL',
'TMPDIR',
'TMP',
'TEMP',
'LANG',
'LC_ALL',
'LC_CTYPE',
'TZ',
];

const WINDOWS_KEYS = ['SystemRoot', 'WINDIR', 'COMSPEC', 'PATHEXT'];

export const createSanitizedEnvironment = (source, { home, platform = process.platform } = {}) => {
if (!home) throw new Error('sanitized environment requires an isolated home');

const result = {};
const allowedKeys = platform === 'win32' ? [...PORTABLE_KEYS, ...WINDOWS_KEYS] : PORTABLE_KEYS;
for (const key of allowedKeys) {
if (typeof source[key] === 'string') result[key] = source[key];
}

return {
...result,
HOME: home,
USERPROFILE: home,
CI: '1',
GIT_TERMINAL_PROMPT: '0',
GIT_CONFIG_NOSYSTEM: '1',
GIT_CONFIG_GLOBAL: platform === 'win32' ? 'NUL' : '/dev/null',
};
};
297 changes: 297 additions & 0 deletions gitnexus/scripts/reconciliation/canary-safety.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
import { spawn, spawnSync } from 'node:child_process';
import crypto from 'node:crypto';
import fs from 'node:fs';
import path from 'node:path';

const SIGNAL_EXIT_CODES = { SIGINT: 130, SIGTERM: 143 };

const codePointCompare = (left, right) => (left < right ? -1 : left > right ? 1 : 0);

const assertDirectory = (directory) => {
const stat = fs.lstatSync(directory);
if (stat.isSymbolicLink()) {
throw new Error(`refusing symbolic link in canary evidence path: ${directory}`);
}
if (!stat.isDirectory()) {
throw new Error(`expected canary evidence directory: ${directory}`);
}
};

const assertTreeHasNoSymlinks = (directory) => {
assertDirectory(directory);
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
const entryPath = path.join(directory, entry.name);
if (entry.isSymbolicLink()) {
throw new Error(`refusing symbolic link in canary evidence path: ${entryPath}`);
}
if (entry.isDirectory()) assertTreeHasNoSymlinks(entryPath);
}
};

export const prepareEvidenceLayout = ({ evidence, commandDir, snapshotDir, home }) => {
fs.mkdirSync(evidence, { recursive: true });
assertDirectory(evidence);
for (const directory of [commandDir, snapshotDir, home]) {
fs.mkdirSync(directory, { recursive: true });
assertDirectory(directory);
}
assertTreeHasNoSymlinks(evidence);
};

export const writeJsonAtomic = (filePath, value) => {
assertDirectory(path.dirname(filePath));
const temporaryPath = path.join(
path.dirname(filePath),
`.${path.basename(filePath)}.${process.pid}.${crypto.randomUUID()}.tmp`,
);
try {
fs.writeFileSync(temporaryPath, `${JSON.stringify(value, null, 2)}\n`, {
encoding: 'utf8',
flag: 'wx',
mode: 0o600,
});
fs.renameSync(temporaryPath, filePath);
} finally {
fs.rmSync(temporaryPath, { force: true });
}
};

const artifactStem = (name, attempt) => (attempt === 1 ? name : `${name}-attempt-${attempt}`);

export const openArtifactLogs = (directory, name) => {
assertDirectory(directory);
for (let attempt = 1; attempt < 10_000; attempt += 1) {
const stem = artifactStem(name, attempt);
const stdoutPath = path.join(directory, `${stem}.stdout.log`);
const stderrPath = path.join(directory, `${stem}.stderr.log`);
let stdout;
try {
stdout = fs.openSync(stdoutPath, 'wx', 0o600);
const stderr = fs.openSync(stderrPath, 'wx', 0o600);
return { stdoutPath, stderrPath, stdout, stderr };
} catch (error) {
if (stdout !== undefined) {
fs.closeSync(stdout);
fs.rmSync(stdoutPath, { force: true });
}
if (error?.code !== 'EEXIST') throw error;
}
}
throw new Error(`could not allocate unique command artifacts for ${name}`);
};

export const writeJsonArtifact = (directory, name, value) => {
assertDirectory(directory);
for (let attempt = 1; attempt < 10_000; attempt += 1) {
const filePath = path.join(directory, `${artifactStem(name, attempt)}.json`);
try {
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, {
encoding: 'utf8',
flag: 'wx',
mode: 0o600,
});
return filePath;
} catch (error) {
if (error?.code !== 'EEXIST') throw error;
}
}
throw new Error(`could not allocate unique JSON artifact for ${name}`);
};

const isContained = (root, candidate) => {
const relative = path.relative(root, candidate);
return (
relative === '' ||
(!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative))
);
};

const assertNoSymlinkComponents = (root, candidate) => {
const relative = path.relative(root, candidate);
let cursor = root;
for (const component of relative.split(path.sep).filter(Boolean)) {
cursor = path.join(cursor, component);
if (fs.lstatSync(cursor).isSymbolicLink()) {
throw new Error(`refusing symbolic link in canary worktree path: ${cursor}`);
}
}
};

export const createSafeContainedDirectory = (root, relativePath) => {
const resolvedRoot = path.resolve(root);
const candidate = path.resolve(resolvedRoot, relativePath);
if (!isContained(resolvedRoot, candidate) || candidate === resolvedRoot) {
throw new Error(`refusing directory outside canary worktree: ${relativePath}`);
}
if (fs.existsSync(candidate)) {
throw new Error(`refusing existing canary fixture directory: ${candidate}`);
}
let cursor = resolvedRoot;
for (const component of path.relative(resolvedRoot, candidate).split(path.sep)) {
cursor = path.join(cursor, component);
if (fs.existsSync(cursor)) {
const stat = fs.lstatSync(cursor);
if (stat.isSymbolicLink()) {
throw new Error(`refusing symbolic link in canary worktree path: ${cursor}`);
}
if (!stat.isDirectory()) {
throw new Error(`refusing non-directory in canary worktree path: ${cursor}`);
}
} else {
fs.mkdirSync(cursor, { mode: 0o700 });
}
}
const realRoot = fs.realpathSync(resolvedRoot);
const realCandidate = fs.realpathSync(candidate);
if (!isContained(realRoot, realCandidate)) {
throw new Error(`refusing directory outside real canary worktree: ${relativePath}`);
}
return candidate;
};

export const selectSafeTrackedFiles = (worktree, files, count) => {
const realWorktree = fs.realpathSync(worktree);
const candidates = files.map((file) => {
if (!file || path.isAbsolute(file)) {
throw new Error(`refusing unsafe tracked path: ${JSON.stringify(file)}`);
}
const candidate = path.resolve(worktree, file);
if (!isContained(path.resolve(worktree), candidate)) {
throw new Error(`refusing tracked path outside canary worktree: ${file}`);
}
assertNoSymlinkComponents(path.resolve(worktree), candidate);
const stat = fs.lstatSync(candidate);
if (stat.isSymbolicLink()) {
throw new Error(`refusing tracked symbolic link in canary mutation set: ${file}`);
}
if (!stat.isFile()) {
throw new Error(`refusing non-file in canary mutation set: ${file}`);
}
const realCandidate = fs.realpathSync(candidate);
if (!isContained(realWorktree, realCandidate)) {
throw new Error(`refusing tracked path outside real canary worktree: ${file}`);
}
return { file, bytes: stat.size };
});
return candidates
.sort((left, right) => left.bytes - right.bytes || codePointCompare(left.file, right.file))
.slice(0, count);
};

export class ChildSupervisor {
constructor({
platform = process.platform,
killTimeoutMs = 5_000,
terminationEnv,
spawnSyncImpl = spawnSync,
} = {}) {
this.platform = platform;
this.killTimeoutMs = killTimeoutMs;
this.terminationEnv = terminationEnv ?? {
SystemRoot: process.env.SystemRoot,
WINDIR: process.env.WINDIR,
COMSPEC: process.env.COMSPEC,
PATHEXT: process.env.PATHEXT,
TEMP: process.env.TEMP,
TMP: process.env.TMP,
};
this.spawnSyncImpl = spawnSyncImpl;
this.children = new Set();
this.receivedSignal = undefined;
this.signalHandlers = new Map();
}

spawn(command, args, options = {}) {
if (this.receivedSignal) {
throw new Error(`refusing child spawn after ${this.receivedSignal}`);
}
const child = spawn(command, args, {
...options,
detached: this.platform !== 'win32',
});
this.children.add(child);
child.once('close', () => this.children.delete(child));
return child;
}

terminate(child, signal = 'SIGTERM') {
if (!child?.pid || child.exitCode !== null || child.signalCode !== null) return;
this.terminateProcessGroup(child.pid, signal, child);
}

terminateProcessGroup(pid, signal, directChild) {
if (this.platform === 'win32') {
const systemRoot = this.terminationEnv.SystemRoot ?? this.terminationEnv.WINDIR;
let result;
if (systemRoot) {
try {
result = this.spawnSyncImpl(
path.win32.join(systemRoot, 'System32', 'taskkill.exe'),
['/pid', String(pid), '/t', '/f'],
{
env: this.terminationEnv,
stdio: 'ignore',
windowsHide: true,
},
);
} catch (error) {
result = { error };
}
}
if (
(!result || result.error || result.status !== 0) &&
directChild &&
directChild.exitCode === null &&
directChild.signalCode === null
) {
directChild.kill(signal);
}
return;
}
try {
process.kill(-pid, signal);
} catch (error) {
if (
error?.code !== 'ESRCH' &&
directChild &&
directChild.exitCode === null &&
directChild.signalCode === null
) {
directChild.kill(signal);
}
}
}

terminateAll(signal = 'SIGTERM') {
const children = [...this.children];
for (const child of children) this.terminate(child, signal);
if (children.length > 0) {
setTimeout(() => {
for (const child of children) {
if (child.pid) this.terminateProcessGroup(child.pid, 'SIGKILL');
}
}, this.killTimeoutMs);
}
}

installSignalHandlers(targetProcess = process, onSignal = () => {}) {
for (const signal of ['SIGINT', 'SIGTERM']) {
const handler = () => {
if (this.receivedSignal) return;
this.receivedSignal = signal;
targetProcess.exitCode = SIGNAL_EXIT_CODES[signal];
this.terminateAll(signal);
onSignal(signal);
};
this.signalHandlers.set(signal, handler);
targetProcess.on(signal, handler);
}
}

disposeSignalHandlers(targetProcess = process) {
for (const [signal, handler] of this.signalHandlers) {
targetProcess.off(signal, handler);
}
this.signalHandlers.clear();
}
}
Loading
Loading