test: observe audited semantic adapter in managed candidate path

This commit is contained in:
Forge
2026-09-22 17:07:05 +00:00
parent 415291abea
commit fa2c839833
4 changed files with 513 additions and 0 deletions
+143
View File
@@ -0,0 +1,143 @@
# Internal dependency shadow recorder
This implements the bounded follow-on to the [contract review](semantic-dependency-contract.md)
as an **explicitly enabled validation host**, not a shipping engine option. The three
modules live under [docs/validation/dependency-shadow](../validation/dependency-shadow/).
No production source, schema, worker, public SDK or Drupal package is changed.
## Observation point and admission
The harness packs a small `shadow-binding` package whose entry points to the audited
host adapter. The real `ExtensionManager` installs/trusts its bytes; the normal loader
registers its pass and the existing candidate/synthesis pipeline runs it. The pass
receives the real `ResolutionContext`, reads the real candidate nodes and returns
ordinary validated edges through the real registry merge into SQLite. No pipeline or
context monkey patch is used. The validation package's absolute host-module path is
test-owned, local and nonportable; it is never a registry artifact or public opt-in.
`recorder.enable(projectRoot, { expectedPackageDigest, owners })` explicitly creates
one process-local validation session. The session is not inferred from a plugin option
or manifest. The loader's actual resolved package digest must match the host's approval.
The host adapter/recorder/compiled engine digests are bound at module load and a later
disk change disables recording. This is an audited allowlist, **not arbitrary-plugin
containment**. Existing v1 code can access Node; a proxy cannot stop that. Without a
session, the adapter executes normally with no shadow callback. Other v1 hooks are
never double-invoked by the observer and normally make the generation ineligible.
Two additional exact-digest fixture producers are admitted only in isolated tests:
a source-only node producer for stable-ID query-order changes, and a colliding edge
producer for ownership order. They always evaluate normally, once; their output is
never cached or shadow-executed. They are not a second reusable adapter or an API for
arbitrary upstream declarations. The legacy filesystem hook remains ineligible.
The adapter is grounded in the generic binding pass in
[`__tests__/extension-sync.test.ts`](../../__tests__/extension-sync.test.ts): resolve
`dispatch` to the unique named Python function, with option override and a real failure
sentinel. `binding.txt` deliberately moves metadata outside the indexed source set;
`.events.yaml` registration membership supplies a fallback. Fixed optional probes
exercise an unsupported derived query and a caught missing operation. No option can
select an arbitrary function or arbitrary context operation.
## Exact views, reads and invocation identity
The audited evaluator has no filesystem, network, clock, environment or module reads.
Only the surrounding host observer performs bookkeeping. Its four captured operations
delegate to the actual context: `getAllFiles`, `fileExists`, `readFile`, `getNodesByName`.
Unknown operations are forwarded with sticky taint set **before** execution or an
exception, including when the audited callback catches that exception. The observer
does not reinterpret errors as an empty successful capture.
Node results use a declared internal JSON projection: remove `updatedAt` and undefined
properties, preserve all remaining values and array order. This is a new internal view,
not transparent interception of arbitrary v1 callbacks. The evaluator cannot read the
omitted fields. Returned arrays/objects are detached copies. All empty, false and null
results are recorded, not just successful symbols. Each observation stores operation,
arguments, complete result SHA256, serialized value byte count and empty-result marker.
No previous returned member list substitutes for reexecuting a query predicate.
Identity binds canonical project path, loaded adapter/observer/engine bytes, engine
version, view contract, post-resolution stage, options, full project config, ignore
rules, CODEGRAPH environment settings, all loaded package digests/options/replacements,
and the actual ordered synthesis registry (including builtins). The config includes
scan policies, enabled state and replacements. Read-set replay uses the new candidate's
context. A trace from another committed graph marker or identity is cold/ineligible.
The first implementation is deliberately conservative and expensive: it replays complete
queries and hashes their results, reads identity files and inspects SQLite. No efficient
bucket index, missing-path watcher or general derived-query implementation is claimed.
## Authoritative execution and commit discipline
1. Always run the full audited callback on the real context. Its result is authoritative.
An actual callback error follows the existing loader/candidate failure path.
2. If explicitly admitted, replay the previous committed trace to predict `wouldReuse`.
Always run the separate capture execution of **only this audited callback**, and
compare its exact premerge output with the authoritative output. For a predicted hit,
also compare the previous owned output exactly with the newly evaluated result.
3. Return the original authoritative result regardless of observer error, mismatch,
budget or taint. Observer failure/mismatch disables the session's trace; unsupported
reads/legacy input discard that generation. No observed result controls the graph.
4. The validation owner wraps a real engine/manager operation in `session.operation`.
A pending trace is not retained as a valid generation unless the operation returns
successfully, the existing `codegraph.semantic.update` **committed** event occurred,
the actual SQLite `semantic_update` marker changed, observed metadata/config still
matches, and exact fixture-owner SQLite output matches the expected ordered merge.
5. Store the full owned output **before pair merge**, including an output shadowed by
another owner. The ownership test reverses registry order and removes the winner;
the previously shadowed contribution must appear. The private fixture oracle supplies
the known competing owner's output. This is not a general replacement for observing
all builtin/extension merge outputs in a production recorder.
Failure, partial work, unknown commit path or missing observation drops the trace. The
existing transaction and recovery code is untouched; no new process-death mechanism
exists to validate. A fresh child process proves that observation state is absent while
the real graph remains correct. The validation owner closes the recorder with its graph.
Normal `CodeGraph.close()` has no new observer hook; lifecycle association is private
to this owner and would need an explicit internal hook before product integration.
One trace is retained per explicitly registered project, bounded to 128 observations
and a 64 KiB serialized `{identity, trace, owned}` payload by default. Oversized captures
are declined without affecting the callback. Reporting history is capped at 256 rows
per session; it is diagnostic overhead, not an engine cache. The limits are serialized
payload bounds, not promises about V8 heap allocation. Closing removes the session,
trace and listener. No trace is written to the project or restored from disk.
## Integrated boundaries discovered
**Normal config-triggered sync can bypass candidate events.** It still runs the normal
engine and applies the changed options, but this observer cannot certify that path.
It discards observations. The options/scan identity cases therefore use the real explicit
candidate refresh, and a separate normal-sync case asserts this conservative refusal.
This is a finding about observation coverage, not a change to sync behavior.
**Non-indexed metadata is not a certified filesystem snapshot.** The existing engine
source stamp does not cover arbitrary `binding.txt` reads. A deliberately timed edit
after candidate evaluation can leave the old observed binding committed. The observer
detects the changed direct-file observation and refuses to certify a trace; it cannot
retroactively undo or repair the authoritative graph. Explicit refresh converges. Even
auth/shadow equality can compare the same cached old file content, so equality alone is
not a freshness guarantee. Indexed-source mutation rejects the actual candidate; config
mutation during copy remains pending for the next sync and cannot certify the old trace.
Only the main-thread resolver is instrumented in this validation. Worker dispatch lacks
the private session and must remain ineligible; no shared-process state or worker-channel
integration was added. Native worker and transaction evidence remains the unchanged
accepted evidence, not a new native shadow claim.
## Decision
**Pass for this bounded shadow experiment; no-go for production skipping yet.** It
predicts repeat/irrelevant-input reuse while preserving actual full evaluation and
detects the specified changes and failures. This cheap binding callback does not establish
an economic case: small-fixture observation overhead must be compared with the much
smaller callback time, not with the whole indexing operation. Predicted work avoided
is hypothetical; actual callback skips are zero.
Before a selective-reuse implementation: provide real candidate generation and close/
worker lifecycle hooks; close the non-indexed/negative-input observation and revalidation
gap; cover every eligible commit path; generalize premerge ownership comparison without
assuming the fixture schedule; measure a useful audited callback where replay costs less
than reevaluation. Retain legacy/global fallback and the current atomic graph transaction.
Do not roll this into public API-v1 admission, persistent cache, Drupal partitioning,
cyclic stage evaluation or graph storage deltas. Global extraction/resolution/candidate/
database costs persist. See the [exact evidence report](../validation/extensions-dependency-shadow-20260922.md).
@@ -0,0 +1,26 @@
'use strict';
// Audited validation adapter, grounded in extension-sync.test.ts's bindings pass.
// No ambient I/O inside evaluate(). This is NOT a public v1 purity declaration.
function evaluate(view, options) {
// Enumerate registrations even when absent; predicate membership is observable.
const registrations = view.getAllFiles().filter(f => f.endsWith('.events.yaml'));
let target = options.target;
if (!target) {
const exists = view.fileExists('binding.txt');
target = exists ? (view.readFile('binding.txt') || '').trim() : '';
if (!target && registrations.length === 1) target = (view.readFile(registrations[0]) || '').trim();
}
// Fixed validation probes; arbitrary method names cannot be passed as options.
if (options.probe === 'aliases') view.getProjectAliases();
if (options.probe === 'caught') { try { view.unknownReviewOperation(); } catch { /* test sticky taint */ } }
if (target === 'FAIL') throw new Error('audited binding failed');
if (!target) return [];
const from = view.getNodesByName('dispatch').filter(n => n.kind === 'function' && n.language === 'python');
const targets = view.getNodesByName(target).filter(n => n.kind === 'function' && n.language === 'python');
if (from.length !== 1 || (!options.first && targets.length !== 1) || !targets.length) return [];
return [{ source: from[0].id, target: targets[0].id, kind: 'calls', line: 1,
metadata: { label: 'Audited binding', targetSignature: targets[0].signature || '' } }];
}
exports.evaluate = evaluate;
exports.factory = ({ projectRoot, options }) => ({ synthPasses: [{ name: 'binding', languages: ['python'],
run: ctx => require('./recorder.cjs').invoke(projectRoot, ctx, options) }] });
@@ -0,0 +1,170 @@
'use strict';
// Internal opt-in experiment. Never imported by production. No callback skips.
const fs = require('node:fs'), path = require('node:path'), crypto = require('node:crypto');
const { channel } = require('node:diagnostics_channel');
const { DatabaseSync } = require('node:sqlite');
const { currentPlugins } = require('../../../dist/plugins/registry');
const { getSynthPasses } = require('../../../dist/resolution/callback-synthesizer');
const { getCodeGraphDir } = require('../../../dist/directory');
const { evaluate } = require('./adapter.cjs');
const { hash } = require('../dependency-review/model.cjs');
const sessions = new Map(), events = channel('codegraph.semantic.update');
const copy = value => structuredClone(value);
const clock = () => performance.now();
const raw = file => { try { return fs.readFileSync(file, 'utf8'); } catch (e) { if (e.code === 'ENOENT') return null; throw e; } };
const byteHash = file => crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
const loadedCode = { adapter: byteHash(path.join(__dirname, 'adapter.cjs')), observer: byteHash(__filename),
engineSource: byteHash(path.join(__dirname, '../../../dist/index.js')) };
const normalize = edges => edges.map(e => ({ source: e.source, target: e.target, kind: e.kind,
line: e.line ?? null, metadata: e.metadata, provenance: e.provenance }));
const guardedOutput = edges => edges.map(e => ({ ...e, provenance: 'heuristic', metadata: { ...e.metadata, synthesizedBy: 'shadow-binding' } }));
const methods = new Set(['readFile', 'fileExists', 'getAllFiles', 'getNodesByName']);
function projected(value, method) {
// New internal view explicitly excludes wall-clock updatedAt. Every other
// observable Node field and query order is kept. No claim of transparent v1 capture.
return method === 'getNodesByName' ? JSON.parse(JSON.stringify(value.map(({ updatedAt, ...node }) => node))) : value;
}
function view(ctx, observer) {
return new Proxy({}, { get(_, method) {
return (...args) => {
// Unknown/caught operations taint before calling or throwing; authoritative
// semantics are forwarded unchanged. A proxy is not an I/O sandbox.
if (!methods.has(method)) observer?.taint('unsupported:' + String(method));
const value = projected(ctx[method](...args), method);
observer?.read(method, args, value);
return copy(value);
};
} });
}
function dbState(root) {
const file = path.join(getCodeGraphDir(root), 'codegraph.db');
if (!fs.existsSync(file)) return { marker: null, edges: [] };
const db = new DatabaseSync(file, { readOnly: true });
try {
const marker = db.prepare("SELECT value FROM project_metadata WHERE key='semantic_update'").get()?.value || null;
const edges = db.prepare('SELECT source,target,kind,line,metadata,provenance FROM edges').all()
.map(e => ({ ...e, metadata: e.metadata ? JSON.parse(e.metadata) : undefined }));
return { marker, edges };
} finally { db.close(); }
}
function sourceIdentity(root, registry, options) {
if (loadedCode.adapter !== byteHash(path.join(__dirname, 'adapter.cjs')) || loadedCode.observer !== byteHash(__filename) ||
loadedCode.engineSource !== byteHash(path.join(__dirname, '../../../dist/index.js'))) throw Error('loaded host code changed; restart required');
return { project: fs.realpathSync(root), ...loadedCode, engine: require('../../../package.json').version,
queryContract: 'shadow-view-1-without-updatedAt',
stage: 'independent-post-resolution', options: copy(options),
config: raw(path.join(root, 'codegraph.json')), ignore: raw(path.join(root, '.gitignore')),
// Whole codegraph config includes scan policy, replacements and enabled order.
environment: Object.fromEntries(Object.entries(process.env).filter(([k]) => k.startsWith('CODEGRAPH_')).sort()),
registry: registry.resolved.map(p => [p.manifest.id, p.version, p.digest, p.options, p.replaces]),
mergeOrder: getSynthPasses().map(p => p.name) };
}
function currentFileMatches(root, read) {
if (read.method === 'readFile') return hash(raw(path.join(root, read.args[0]))) === read.hash;
if (read.method === 'fileExists') return hash(fs.existsSync(path.join(root, read.args[0]))) === read.hash;
return true;
}
class Session {
constructor(root, options) {
this.root = fs.realpathSync(root); this.options = options; this.committed = null; this.active = null;
this.records = []; this.closed = false; this.disabled = false;
this.listener = e => { if (e.projectRoot === this.root && this.active) {
this.active.phases.push(e.phase); if (e.phase === 'committed') this.active.graphCommitted = true;
} };
events.subscribe(this.listener);
}
async operation(name, fn, { fault } = {}) {
if (this.closed || this.active) throw Error('shadow session closed or operation active');
const start = clock(); let previousMarker;
try { previousMarker = dbState(this.root).marker; } catch { this.disabled = true; this.committed = null; }
const op = this.active = { name, phases: [], pending: null, graphCommitted: false, fault, invocations: 0 };
let error, result;
try { result = await fn(); } catch (e) { error = e; }
const finalizeStart = clock();
// Never throw an observer failure into the successful engine operation.
const record = { name, success: !error, certified: false, phases: op.phases, ...op.pending?.metrics,
authoritativeInvocations: op.invocations, graphMarker: null, trace: op.pending?.trace || [],
owned: op.pending?.owned || [], reason: error ? 'operation-failed' : op.reason || 'not-observed' };
try {
const state = dbState(this.root); record.graphMarker = state.marker;
if (!error && op.pending && !this.disabled && !op.reason && op.graphCommitted && state.marker !== previousMarker) {
const p = op.pending;
if (raw(path.join(this.root, 'codegraph.json')) !== p.identity.config || raw(path.join(this.root, '.gitignore')) !== p.identity.ignore ||
!p.trace.every(r => currentFileMatches(this.root, r))) throw Error('observed source/config changed before certification');
// Owned premerge output can be shadowed by another earlier pass. Compare
// the complete registered fixture-owner merge, never delete shadowed ownership.
const expected = this.options.expectedMerge ? this.options.expectedMerge(p.owned) : guardedOutput(p.owned);
const actual = state.edges.filter(e => this.options.owners.includes(e.metadata?.synthesizedBy));
const sort = es => normalize(es).sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
if (hash(sort(actual)) !== hash(sort(expected))) throw Error('committed SQLite/owned merge mismatch');
this.committed = { identity: p.identity, trace: p.trace, owned: copy(p.owned), marker: state.marker, bytes: p.metrics.traceBytes };
record.certified = true; record.reason = 'committed'; record.sqliteHash = hash(sort(actual));
} else if (error || op.reason || !op.graphCommitted) this.committed = null;
} catch (e) { record.reason = 'observer:' + e.message; this.committed = null; this.disabled = true; }
record.finalizeMs = clock() - finalizeStart;
record.observerMs = (record.identityMs || 0) + (record.replayMs || 0) + (record.shadowMs || 0) + (record.compareMs || 0) + record.finalizeMs;
record.totalOperationMs = clock() - start;
this.records.push(record); if (this.records.length > 256) this.records.shift();
this.active = null;
if (error) throw error;
return result;
}
close() { this.committed = null; this.active = null; this.closed = true; sessions.delete(this.root); events.unsubscribe(this.listener); }
}
function enable(root, options) {
const real = fs.realpathSync(root);
if (sessions.has(real) || !options.expectedPackageDigest || !options.owners?.includes('shadow-binding')) throw Error('explicit unique project/package admission required');
const session = new Session(real, { maxReads: 128, maxBytes: 65536, ...options }); sessions.set(real, session); return session;
}
function invoke(root, ctx, options) {
// The full authoritative callback is unconditional. Observer code runs only
// afterward and never changes this value, including mismatches/over-budget.
const session = sessions.get(root), op = session?.active;
if (op) op.invocations++;
const authStart = clock(); const authoritative = evaluate(view(ctx), copy(options)); const authoritativeMs = clock() - authStart;
if (!session || !op || session.disabled) return authoritative;
try {
const identityStart = clock(), registry = currentPlugins();
const own = registry?.resolved.find(p => p.manifest.id === 'shadow-binding');
if (!own || own.digest !== session.options.expectedPackageDigest) { op.reason = 'not-admitted-package'; return authoritative; }
const identity = sourceIdentity(root, registry, options), identityMs = clock() - identityStart;
const legacy = registry.resolved.some(p => !session.options.auditedUpstreamDigests?.includes(p.digest) && p !== own);
let reason = legacy ? 'legacy-untracked-global-fallback' : null;
const old = session.committed, replayStart = clock();
let wouldReuse = !reason && !!old && old.marker === dbState(root).marker && hash(old.identity) === hash(identity);
if (wouldReuse) for (const r of old.trace) {
if (hash(projected(ctx[r.method](...r.args), r.method)) !== r.hash) { wouldReuse = false; break; }
}
const replayMs = clock() - replayStart;
const trace = []; let traceBytes = 0, captureMs = 0;
const observer = { taint: why => { reason ||= why; }, read(method, args, value) {
const t = clock();
if (op.fault === 'capture-error') throw Error('injected observer failure');
if (methods.has(method) && !reason) {
const entry = { method, args: copy(args), hash: hash(value), valueBytes: Buffer.byteLength(JSON.stringify(value)),
empty: value === null || value === false || (Array.isArray(value) && value.length === 0) };
traceBytes += Buffer.byteLength(JSON.stringify(entry));
if (trace.length >= session.options.maxReads || traceBytes > session.options.maxBytes) reason = 'observation-budget';
else trace.push(entry);
}
captureMs += clock() - t;
} };
const shadowStart = clock(); const shadow = evaluate(view(ctx, observer), copy(options)); const shadowMs = clock() - shadowStart;
const compareStart = clock();
if (op.fault === 'mismatch') shadow.push({ source: 'invalid', target: 'invalid', kind: 'calls' });
if (hash(shadow) !== hash(authoritative) || (wouldReuse && hash(old.owned) !== hash(authoritative))) {
reason = 'prediction-or-shadow-mismatch'; session.disabled = true;
}
const compareMs = clock() - compareStart;
if (reason) { op.reason = reason; session.committed = null; }
op.pending = { identity, trace: reason ? [] : trace, owned: copy(authoritative), metrics: {
wouldReuse: wouldReuse && !reason, predictedCallbackMsAvoided: wouldReuse && !reason ? authoritativeMs : 0,
authoritativeMs, identityMs, captureMs, replayMs, compareMs, shadowMs,
traceBytes: reason ? 0 : Buffer.byteLength(JSON.stringify({ identity, trace, owned: authoritative })),
readCount: trace.length, negativeReads: trace.filter(r => r.empty).length,
actualCallbacksSkipped: 0, shadowExecutions: 1 } };
if (op.pending.metrics.traceBytes > session.options.maxBytes) { op.reason = 'total-observation-budget'; op.pending.trace = []; session.committed = null; }
} catch (error) { session.committed = null; session.disabled = true; op.reason = 'observer:' + error.message; }
return authoritative;
}
module.exports = { enable, invoke, dbState, normalize, guardedOutput, hash };
+174
View File
@@ -0,0 +1,174 @@
'use strict';
const fs = require('node:fs'), path = require('node:path'), os = require('node:os');
const assert = require('node:assert/strict'), cp = require('node:child_process');
const { channel } = require('node:diagnostics_channel');
Object.assign(process.env, { CODEGRAPH_TELEMETRY: '0', CODEGRAPH_PARSE_WORKERS: '0', CODEGRAPH_RESOLVE_WORKERS: '0' });
const { CodeGraph, packExtension } = require('../../../dist');
const { ExtensionManager } = require('../../../dist/plugins/manager');
const { packageDigest } = require('../../../dist/plugins/package');
const { enable, dbState, hash, normalize, guardedOutput } = require('./recorder.cjs');
const adapter = path.join(__dirname, 'adapter.cjs');
const projectName = 'shadow-binding';
const handlers = 'def dispatch():\n pass\ndef receipt():\n pass\ndef cancel():\n pass\n';
const checks = [], commands = [];
let lab, out;
function write(root, file, value) { const p = path.join(root, file); if (value === null) { if (fs.existsSync(p)) fs.unlinkSync(p); }
else { fs.mkdirSync(path.dirname(p), { recursive: true }); fs.writeFileSync(p, value); } }
function config(root, mutate) { const p = path.join(root, 'codegraph.json'), c = JSON.parse(fs.readFileSync(p)); mutate(c); fs.writeFileSync(p, JSON.stringify(c, null, 2)); }
function nodeState(root) {
const { DatabaseSync } = require('node:sqlite'); const db = new DatabaseSync(path.join(root, '.codegraph/codegraph.db'), { readOnly: true });
try { return db.prepare('SELECT id,name,file_path,start_line,signature FROM nodes ORDER BY id').all(); } finally { db.close(); }
}
function graphHash(root) { const s = dbState(root); return hash(normalize(s.edges).sort((a,b)=>JSON.stringify(a).localeCompare(JSON.stringify(b)))); }
function bindingEdges(root) { return dbState(root).edges.filter(e => ['shadow-binding', 'overlap'].includes(e.metadata?.synthesizedBy)); }
function targetName(root) { const ns = nodeState(root); return bindingEdges(root).map(e => ns.find(n => n.id === e.target)?.name); }
function makePackage(id, source, capabilities = ['synthPasses'], version = '0.1.0') {
const dir = path.join(lab, `author-${id}-${version}`); fs.mkdirSync(dir);
fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({ name: id, version, main: 'index.cjs', codegraph: { id, apiVersion: 1, capabilities } }, null, 2));
fs.writeFileSync(path.join(dir, 'index.cjs'), source);
return { bytes: packExtension(dir), digest: packageDigest(dir), dir };
}
function save() { if (out) fs.writeFileSync(path.join(out, 'progress.json'), JSON.stringify({ checks, commands }, null, 2)); }
function pass(name, record, extra = {}) { checks.push({ name, scope: 'real managed candidate/synthesis/SQLite', record, ...extra }); console.log('PASS', name); save(); }
async function main() {
out = path.resolve(process.argv[2] || '.qa/dependency-shadow'); fs.mkdirSync(out, { recursive: true });
for (const name of ['adapter.cjs','recorder.cjs','run.cjs']) fs.copyFileSync(path.join(__dirname,name),path.join(out,'source-'+name));
lab = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-shadow-'));
const pkg = makePackage(projectName, `module.exports=require(${JSON.stringify(adapter)}).factory;`);
const root = path.join(lab,'project');fs.mkdirSync(root);write(root,'handlers.py',handlers);write(root,'unrelated.py','def unrelated():\n pass\n');
const manager = new ExtensionManager(root); let graph, s;
const settings = { expectedPackageDigest: pkg.digest, owners: ['shadow-binding'] };
function start(overrides = {}) { s?.close(); s = enable(root, { ...settings, ...overrides }); return s; }
async function run(name, fn, expected, prediction, extras = {}) {
await s.operation(name, fn); const r = s.records.at(-1);
assert.deepEqual(targetName(root), expected, name);
if (prediction !== undefined) assert.equal(r.wouldReuse, prediction, name);
if (!extras.uncertified) assert.equal(r.certified,true, JSON.stringify(r));
assert.equal(r.actualCallbacksSkipped ?? 0, 0); pass(name,r,extras); return r;
}
const refresh = () => graph.refreshPluginIndex();
try {
// Ordinary installation before opt-in produces a real graph but no recorder.
await manager.install({bytes:pkg.bytes});graph=await CodeGraph.open(root);
assert.deepEqual(targetName(root),[]);pass('disabled by default: ordinary full install works',null);
start();
const cold=await run('explicitly enabled cold trace with missing metadata',refresh,[],false);
assert.ok(cold.negativeReads>=1);assert.ok(cold.trace.some(r=>r.method==='fileExists'&&r.empty));
await run('unchanged full evaluation predicts reuse',refresh,[],true);
write(root,'binding.txt','missing');await run('non-indexed metadata appears with missing symbol',refresh,[],false);
assert.ok(s.records.at(-1).trace.some(r=>r.method==='getNodesByName'&&r.empty));
write(root,'missing.py','def missing():\n pass\n');await run('missing symbol appears in candidate query membership',()=>graph.sync(),['missing'],false);
write(root,'duplicate.py','def missing():\n pass\n');await run('duplicate matching symbol removes ambiguous edge',()=>graph.sync(),[],false);
write(root,'duplicate.py',null);await run('duplicate deletion restores unique target',()=>graph.sync(),['missing'],false);
fs.renameSync(path.join(root,'missing.py'),path.join(root,'renamed.py'));await run('target rename replaces endpoint identity',()=>graph.sync(),['missing'],false);
write(root,'renamed.py',null);await run('target deletion removes owned edge',()=>graph.sync(),[],false);
write(root,'binding.txt','receipt');await run('metadata retarget selects unchanged receipt endpoint',refresh,['receipt'],false);
write(root,'binding.txt','cancel');await run('metadata retarget selects unchanged cancel endpoint',refresh,['cancel'],false);
const beforeNodes=nodeState(root);write(root,'handlers.py',handlers.replace('cancel():','cancel(value=1):'));
await run('same-ID node signature field invalidates captured query',()=>graph.sync(),['cancel'],false);
const a=beforeNodes.find(n=>n.name==='cancel'),b=nodeState(root).find(n=>n.name==='cancel');assert.equal(a.id,b.id);assert.notEqual(a.signature,b.signature);
write(root,'unrelated.py','def unrelated():\n return 7\n');await run('irrelevant existing-file edit predicts reuse but still evaluates',()=>graph.sync(),['cancel'],true);
write(root,'binding.txt',null);await run('metadata deletion removes binding',refresh,[],false);
write(root,'registration.events.yaml','receipt');await run('registration enumeration membership enables fallback binding',()=>graph.sync(),['receipt'],false);
write(root,'registration.events.yaml',null);await run('registration deletion clears output',()=>graph.sync(),[],false);
write(root,'binding.txt','receipt');await run('restore metadata and rebuild trace',refresh,['receipt'],false);
config(root,c=>c.plugins[0].options={target:'cancel'});await run('loaded options identity invalidates',refresh,['cancel'],false);
config(root,c=>c.exclude=['unrelated.py']);await run('scan policy identity invalidates',refresh,['cancel'],false);
write(root,'.gitignore','unrelated.py\n');await run('ignore policy identity invalidates',refresh,['cancel'],false);
// Independent project with identical fixture package/options never shares traces.
const other=path.join(lab,'other');fs.mkdirSync(other);write(other,'handlers.py',handlers);write(other,'binding.txt','receipt');
const osession=enable(other,settings);try{await osession.operation('project identity',()=>new ExtensionManager(other).install({bytes:pkg.bytes}));
assert.equal(osession.records.at(-1).wouldReuse,false);assert.equal(osession.records.at(-1).certified,true);pass('project identity isolates independent managed project',osession.records.at(-1));
}finally{osession.close();}
const pkg2=makePackage(projectName,`// changed reviewed package bytes\nmodule.exports=require(${JSON.stringify(adapter)}).factory;`,['synthPasses'],'0.1.1');
settings.expectedPackageDigest=pkg2.digest;s.options.expectedPackageDigest=pkg2.digest;
await run('adapter package content/version identity invalidates',()=>manager.install({bytes:pkg2.bytes}),['cancel'],false);
// A forged v1 declaration has no admission effect. Exact content is required.
const admitted=s.options.expectedPackageDigest;s.options.expectedPackageDigest='forged-purity-flag';
await run('wrong package admission cannot opt into recording',refresh,['cancel'],undefined,{uncertified:true});assert.equal(s.records.at(-1).reason,'not-admitted-package');
s.options.expectedPackageDigest=admitted;
// Real legacy hook reads another file without using ResolutionContext.
const legacy=makePackage('legacy-hook',`module.exports=({projectRoot})=>({frameworks:[{name:'legacy',languages:['python'],detect:()=>true,resolve:()=>null,extract(file){if(file!=='handlers.py')return {nodes:[],references:[]};const name=require('fs').readFileSync(require('path').join(projectRoot,'binding.txt'),'utf8');return {nodes:[{id:'plugin:legacy-hook:route',name,qualifiedName:'legacy:route',kind:'route',language:'python',filePath:file,startLine:1,endLine:1,startColumn:0,endColumn:0}],references:[]}}}],synthPasses:[{name:'count',run(){require('fs').appendFileSync(require('path').join(projectRoot,'legacy-calls.txt'),'1');return []}}]});`,['frameworks','synthPasses']);
await run('legacy fs hook is ineligible and still evaluated once',()=>manager.install({bytes:legacy.bytes}),['cancel'],false,{uncertified:true});
assert.equal(s.records.at(-1).reason,'legacy-untracked-global-fallback');const count=fs.readFileSync(path.join(root,'legacy-calls.txt'),'utf8').length;
write(root,'binding.txt','cancel');await run('legacy cross-file edit changes actual node without capture admission',refresh,['cancel'],false,{uncertified:true});
assert.equal(fs.readFileSync(path.join(root,'legacy-calls.txt'),'utf8').length,count+1);assert.ok(nodeState(root).some(n=>n.id==='plugin:legacy-hook:route'&&n.name==='cancel'));
await run('legacy removal returns to cold eligible trace',()=>manager.remove('legacy-hook'),['cancel'],false);
// Unsupported operation is performed only by the fixed audited probe branch.
for(const probe of ['aliases','caught']){
config(root,c=>c.plugins.find(p=>p.name==='managed:shadow-binding').options={target:'receipt',probe});
await run('unsupported '+probe+' read taints even when caught',refresh,['receipt'],false,{uncertified:true});assert.match(s.records.at(-1).reason,/unsupported/);
}
config(root,c=>c.plugins[0].options={});write(root,'binding.txt','receipt');start();await run('cold reset after unsupported traces',refresh,['receipt'],false);
// Failure originates in actual callback and existing loader/candidate guard.
write(root,'binding.txt','FAIL');const working=graphHash(root);
await assert.rejects(()=>s.operation('real callback failure',refresh),/audited binding failed/);assert.equal(graphHash(root),working);assert.equal(s.committed,null);pass('real callback failure preserves SQLite and certifies no partial trace',s.records.at(-1));
write(root,'binding.txt','receipt');await run('actual failed callback retries cold',refresh,['receipt'],false);
for(const fault of ['capture-error','mismatch']){
await s.operation('observer '+fault,refresh,{fault});assert.deepEqual(targetName(root),['receipt']);assert.equal(s.committed,null);assert.equal(s.disabled,true);
pass('observer '+fault+' disables trace and preserves authoritative SQLite',s.records.at(-1));start();await run('reset after '+fault,refresh,['receipt'],false);
}
s.committed.owned[0].metadata.label='injected stale prediction';await s.operation('prediction mismatch',refresh);assert.equal(s.disabled,true);assert.deepEqual(targetName(root),['receipt']);assert.equal(s.records.at(-1).reason,'prediction-or-shadow-mismatch');pass('incorrect predicted output disables trace, never replaces graph',s.records.at(-1));start();
await run('reset before race tests',refresh,['receipt'],false);
let listener=e=>{if(e.projectRoot===root&&e.phase==='candidate_ready')write(root,'handlers.py',handlers+'# changed during candidate\n');};
channel('codegraph.semantic.update').subscribe(listener);const beforeRace=graphHash(root);
try{await assert.rejects(()=>s.operation('indexed source race',refresh),/changed during semantic indexing/);}finally{channel('codegraph.semantic.update').unsubscribe(listener);}
assert.equal(graphHash(root),beforeRace);assert.equal(s.committed,null);pass('indexed source mutation rejects actual candidate and trace',s.records.at(-1));
await run('indexed source race retries cold',refresh,['receipt'],false);
listener=e=>{if(e.projectRoot===root&&e.phase==='candidate_ready')write(root,'binding.txt','cancel');};
channel('codegraph.semantic.update').subscribe(listener);
try{await s.operation('non-indexed source race',refresh);}finally{channel('codegraph.semantic.update').unsubscribe(listener);}
// The existing engine does NOT stamp arbitrary non-indexed metadata. The
// graph reflects its observed old value, but recorder refuses certification.
assert.deepEqual(targetName(root),['receipt']);assert.equal(s.committed,null);assert.match(s.records.at(-1).reason,/source\/config changed/);
pass('non-indexed race exposes existing snapshot limit and disables trace',s.records.at(-1),{limitation:'Engine commits old observed metadata; explicit refresh required'});start();
await run('explicit refresh converges after metadata race',refresh,['cancel'],false);
listener=e=>{if(e.projectRoot===root&&e.phase==='copy_started')config(root,c=>c.plugins[0].options={target:'receipt'});};
channel('codegraph.semantic.update').subscribe(listener);
try{await s.operation('config race',refresh);}finally{channel('codegraph.semantic.update').unsubscribe(listener);}
assert.deepEqual(targetName(root),['cancel']);assert.equal(s.committed,null);assert.equal(s.disabled,true);pass('configuration changed during commit cannot certify old trace',s.records.at(-1));start();
await run('next normal config sync applies options but has no candidate certificate',()=>graph.sync(),['receipt'],false,{uncertified:true});
assert.equal(s.records.at(-1).certified,false);assert.equal(s.committed,null);
// Source-only framework fixture supplies fixed node IDs so query order can
// change independently of identity. It is a pinned test producer, never cached.
const provider=makePackage('target-provider',`module.exports=()=>({frameworks:[{name:'targets',languages:['yaml'],detect:()=>true,resolve:()=>null,extract(file,source){if(file!=='targets.yaml')return {nodes:[],references:[]};return {nodes:JSON.parse(source).map(n=>({id:'plugin:target-provider:'+n.id,name:'ordered',qualifiedName:'fixture:'+n.id,kind:'function',language:'python',filePath:file,startLine:n.line,endLine:n.line,startColumn:0,endColumn:1,signature:n.sig})),references:[]}}}]});`,['frameworks']);
settings.auditedUpstreamDigests=[provider.digest];start();
write(root,'targets.yaml',JSON.stringify([{id:'a',line:1,sig:'a'},{id:'b',line:2,sig:'b'}]));
await s.operation('provider install',()=>manager.install({bytes:provider.bytes}));
config(root,c=>c.plugins.find(p=>p.name==='managed:shadow-binding').options={target:'ordered',first:true});
await run('ordered membership baseline with pinned upstream producer',refresh,['ordered'],false);assert.equal(bindingEdges(root)[0].target,'plugin:target-provider:a');
const ids=nodeState(root).filter(n=>n.name==='ordered').map(n=>n.id);
write(root,'targets.yaml',JSON.stringify([{id:'a',line:2,sig:'a'},{id:'b',line:1,sig:'b'}]));
await run('same-ID query order change selects correct first result',()=>graph.sync(),['ordered'],false);
assert.deepEqual(nodeState(root).filter(n=>n.name==='ordered').map(n=>n.id),ids);assert.equal(bindingEdges(root)[0].target,'plugin:target-provider:b');
await s.operation('provider remove',()=>manager.remove('target-provider'));settings.auditedUpstreamDigests=[];
config(root,c=>c.plugins[0].options={target:'receipt'});start();await run('restore ordinary audited binding',refresh,['receipt'],false);
const overlap=makePackage('overlap',`module.exports=()=>({synthPasses:[{name:'overlap',run(ctx){const a=ctx.getNodesByName('dispatch'),b=ctx.getNodesByName('receipt');return a.length===1&&b.length===1?[{source:a[0].id,target:b[0].id,kind:'calls',line:1,metadata:{label:'Overlap'}}]:[]}}]});`);
settings.owners=['shadow-binding','overlap'];settings.auditedUpstreamDigests=[overlap.digest];
settings.expectedMerge=owned=>{const ordered=JSON.parse(fs.readFileSync(path.join(root,'codegraph.json'))).plugins.filter(p=>p.enabled!==false);const first=ordered[0].name;
return first==='managed:overlap'?owned.map(e=>({...e,provenance:'heuristic',metadata:{label:'Overlap',synthesizedBy:'overlap'}})):guardedOutput(owned);};start();
await run('overlap keeps full premerge owned outputs',()=>manager.install({bytes:overlap.bytes}),['receipt'],false);assert.equal(s.committed.owned.length,1);assert.equal(bindingEdges(root)[0].metadata.synthesizedBy,'shadow-binding');
config(root,c=>c.plugins.reverse());await run('ordered registry identity changes collision winner',refresh,['receipt'],false);assert.equal(bindingEdges(root)[0].metadata.synthesizedBy,'overlap');assert.equal(s.committed.owned.length,1);
await run('removing winning owner reveals shadowed output',()=>manager.remove('overlap'),['receipt'],false);assert.equal(bindingEdges(root)[0].metadata.synthesizedBy,'shadow-binding');
settings.owners=['shadow-binding'];settings.auditedUpstreamDigests=[];delete settings.expectedMerge;
config(root,c=>delete c.exclude);write(root,'.gitignore',null); // keep repeat edits in the indexed set
start({maxBytes:64});await s.operation('bounded recorder',refresh);assert.deepEqual(targetName(root),['receipt']);assert.equal(s.committed,null);assert.match(s.records.at(-1).reason,/budget/);pass('bounded observations decline caching without changing graph',s.records.at(-1));
start();await run('fresh session starts cold',refresh,['receipt'],false);
// Store exact observation breakdown, not an estimate of engine speedup.
for(let i=0;i<5;i++)await run('unchanged overhead sample '+(i+1),refresh,['receipt'],true,{sample:'unchanged'});
for(let i=0;i<5;i++){write(root,'unrelated.py','def unrelated():\n return '+i+'\n');await run('irrelevant content overhead sample '+(i+1),refresh,['receipt'],true,{sample:'irrelevant-content-explicit-refresh'});}
const stable=graphHash(root);s.close();graph.close();graph=null;
const child=cp.spawnSync(process.execPath,[__filename,'--cold-child',root,settings.expectedPackageDigest],{encoding:'utf8',timeout:60000});
fs.writeFileSync(path.join(out,'cold-child.log'),child.stdout+child.stderr);commands.push({command:[process.execPath,__filename,'--cold-child',root,settings.expectedPackageDigest],exit:child.status,signal:child.signal});assert.equal(child.status,0,child.stderr);
const last=JSON.parse(child.stdout.trim().split('\n').at(-1));assert.equal(last.wouldReuse,false);assert.equal(last.certified,true);assert.equal(graphHash(root),stable);pass('fresh OS process has no prior trace and preserves full graph',last);
// Cold full reference with recorder absent has exactly the same graph.
graph=await CodeGraph.open(root);await graph.refreshPluginIndex();assert.equal(graphHash(root),stable);pass('recorder-disabled full reference equals final SQLite graph',null);
fs.writeFileSync(path.join(out,'result.json'),JSON.stringify({success:true,revision:cp.execFileSync('git',['rev-parse','HEAD'],{encoding:'utf8'}).trim(),platform:process.platform,node:process.version,
checks,commands,productionChanges:false,callbackSkipping:false,workerCoverage:'main-thread resolver; production worker code unchanged',limits:['No arbitrary-plugin containment','Non-indexed metadata races are detected for trace but existing engine requires explicit refresh','Process-local validation owner must close the recorder with graph','No full suite/native rerun: no production source change']},null,2));
}finally{s?.close();graph?.close();fs.rmSync(lab,{recursive:true,force:true});}
}
if(process.argv[2]==='--cold-child'){
(async()=>{const root=process.argv[3],s=enable(root,{expectedPackageDigest:process.argv[4],owners:['shadow-binding']});const g=await CodeGraph.open(root);
try{await s.operation('cold process',()=>g.refreshPluginIndex());console.log(JSON.stringify(s.records.at(-1)));}finally{s.close();g.close();}})().catch(e=>{console.error(e.stack);process.exitCode=1});
}else main().catch(e=>{save();fs.writeFileSync(path.join(out,'failure.json'),JSON.stringify({checks,commands,error:String(e.stack)},null,2));console.error(e.stack);process.exitCode=1;});