Skip to content
Draft
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
14 changes: 6 additions & 8 deletions scripts/execution-gate.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,28 +111,26 @@ class ExecutionGate {
if (classification.type === 'LEGACY_MESSAGE_ID') {
const msgId = msg.completion_message_id;
if (!msgId) return { verified: false, wouldVerify: false, reason: 'completion_message_id is empty' };

if (this.dryRun) {
return { verified: false, wouldVerify: true, reason: 'DRY_RUN_SKIP_REF_CHECK' };
}
const found = this._findReferencedMessage(msgId, msg);
if (found) {
return { verified: true, wouldVerify: true, reason: 'Referenced message exists on disk' };
}
if (this.dryRun) {
return { verified: false, wouldVerify: true, reason: 'DRY_RUN_SKIP_REF_CHECK' };
}
return { verified: false, wouldVerify: false, reason: `Referenced message not found: ${msgId}` };
}

if (classification.type === 'LEGACY_TASK_ID') {
const taskId = msg.resolved_by_task_id;
if (!taskId) return { verified: false, wouldVerify: false, reason: 'resolved_by_task_id is empty' };

if (this.dryRun) {
return { verified: false, wouldVerify: true, reason: 'DRY_RUN_SKIP_REF_CHECK' };
}
const found = this._findReferencedTask(taskId, msg);
if (found) {
return { verified: true, wouldVerify: true, reason: 'Referenced task exists on disk' };
}
if (this.dryRun) {
return { verified: false, wouldVerify: true, reason: 'DRY_RUN_SKIP_REF_CHECK' };
}
return { verified: false, wouldVerify: false, reason: `Referenced task not found: ${taskId}` };
}

Expand Down
40 changes: 37 additions & 3 deletions scripts/lane-worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -711,7 +711,7 @@ if (msg.confidence !== undefined && msg.confidence >= 7) {
task_id: msg.task_id || 'unknown',
confidence: msg.confidence,
};
const cpsPath = path.join(repoRoot, 'context-buffer', 'cps_log.jsonl');
const cpsPath = path.join(this.repoRoot, 'context-buffer', 'cps_log.jsonl');
try {
fs.appendFileSync(cpsPath, JSON.stringify(cpsEntry) + '\n');
} catch (e) {
Expand Down Expand Up @@ -991,7 +991,13 @@ processFile(filePath) {
target_path: nrPath, reason: 'FOREIGN_INSTANCE_ACTIONABLE',
detail: `Non-owner session ${SESSION_ID.slice(0,12)}: actionable message from different instance deferred`,
schema_valid: false, signature_valid: false, actionable: true,
has_completion_proof: false, dry_run: this.dryRun,
has_completion_proof: false, execution_verified: false,
would_verify: false, enforce_ownership: this.enforceOwnership,
ownership_enforcement_enabled: this.enforceOwnership,
ownership: { present: false }, ownership_notes: [],
verification_outcome: null, domain_validation: null,
domain_gate_executed: false, verification_path: null,
schema_remediation: null, dry_run: this.dryRun,
};
}

Expand All @@ -1015,7 +1021,13 @@ processFile(filePath) {
target_path: sfPath, reason: 'STALE_FOREIGN_INSTANCE',
detail: `Message from foreign session ${msg._lane_worker.session_identity.session_id.slice(0,12)}, cross-instance not allowed`,
schema_valid: false, signature_valid: false, actionable: true,
has_completion_proof: false, dry_run: this.dryRun,
has_completion_proof: false, execution_verified: false,
would_verify: false, enforce_ownership: this.enforceOwnership,
ownership_enforcement_enabled: this.enforceOwnership,
ownership: { present: false }, ownership_notes: [],
verification_outcome: null, domain_validation: null,
domain_gate_executed: false, verification_path: null,
schema_remediation: null, dry_run: this.dryRun,
};
}
}
Expand Down Expand Up @@ -1101,6 +1113,17 @@ _routeRaw(filePath, queueKey, meta) {
signature_valid: false,
actionable: false,
has_completion_proof: false,
execution_verified: false,
would_verify: false,
enforce_ownership: this.enforceOwnership,
ownership_enforcement_enabled: this.enforceOwnership,
ownership: { present: false },
ownership_notes: [],
verification_outcome: null,
domain_validation: null,
domain_gate_executed: false,
verification_path: null,
schema_remediation: null,
dry_run: this.dryRun,
};

Expand Down Expand Up @@ -1138,6 +1161,17 @@ _routeRaw(filePath, queueKey, meta) {
reason: 'PROCESSING_EXCEPTION',
detail: err.message,
dry_run: this.dryRun,
execution_verified: false,
would_verify: false,
enforce_ownership: this.enforceOwnership,
ownership_enforcement_enabled: this.enforceOwnership,
ownership: { present: false },
ownership_notes: [],
verification_outcome: null,
domain_validation: null,
domain_gate_executed: false,
verification_path: null,
schema_remediation: null,
});
}
}
Expand Down
168 changes: 167 additions & 1 deletion scripts/test-execution-gate.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ test('lane-worker blocks fake artifact with EXECUTION_NOT_VERIFIED', function(tm
priority: 'P1',
timestamp: new Date().toISOString(),
requires_action: true,
confidence: 8,
subject: 'Fake artifact via worker',
body: 'Worker should block this',
evidence: { required: true },
Expand Down Expand Up @@ -183,6 +184,7 @@ test('lane-worker stamps execution_verified=true on valid artifact', function(tm
priority: 'P1',
timestamp: new Date().toISOString(),
requires_action: true,
confidence: 8,
subject: 'Real artifact via worker',
body: 'Worker should accept this',
evidence: { required: true },
Expand Down Expand Up @@ -317,10 +319,18 @@ test('execution_verified=false default when no proof present', function(tmpRoot)
});

// TEST 9: dry-run reference check must not set execution_verified=true
// Must use from: 'library' to prove lane discovery is never called
test('dry-run reference skip reports would_verify=true but execution_verified=false', function(tmpRoot) {
var resolver = new ArtifactResolver({ allowedRoots: [tmpRoot], dryRun: true });
var gate = new ExecutionGate({ lane: 'archivist', dryRun: true, resolver: resolver });

// Wrap _findReferencedMessage to prove it is never called during dry-run
var lookupCalled = false;
gate._findReferencedMessage = function() {
lookupCalled = true;
throw new Error('LOOKUP_CALLED_DURING_DRY_RUN');
};

var msg = {
id: 'dry-run-ref-check',
from: 'library',
Expand All @@ -333,12 +343,66 @@ test('dry-run reference skip reports would_verify=true but execution_verified=fa
};

var result = gate.verify(msg);
assert.strictEqual(lookupCalled, false, '_findReferencedMessage must not be called during dry-run');
assert.strictEqual(result.execution_verified, false, 'dry-run ref skip must not verify execution');
assert.strictEqual(result.would_verify, true, 'dry-run ref skip should report would_verify=true');
assert.strictEqual(result.reason, 'DRY_RUN_SKIP_REF_CHECK');
});

// TEST 10: dry-run path check must not set execution_verified=true
// TEST 10: dry-run task reference check must not set execution_verified=true
// Proves resolved_by_task_id lookup is skipped in dry-run mode
test('dry-run task reference skip reports would_verify=true but execution_verified=false', function(tmpRoot) {
var resolver = new ArtifactResolver({ allowedRoots: [tmpRoot], dryRun: true });
var gate = new ExecutionGate({ lane: 'archivist', dryRun: true, resolver: resolver });

// Wrap _findReferencedTask to prove it is never called during dry-run
var lookupCalled = false;
gate._findReferencedTask = function() {
lookupCalled = true;
throw new Error('LOOKUP_CALLED_DURING_DRY_RUN');
};

var msg = {
id: 'dry-run-task-check',
from: 'library',
to: 'archivist',
type: 'task',
priority: 'P1',
timestamp: new Date().toISOString(),
requires_action: true,
resolved_by_task_id: 'missing-task-id',
};

var result = gate.verify(msg);
assert.strictEqual(lookupCalled, false, '_findReferencedTask must not be called during dry-run');
assert.strictEqual(result.execution_verified, false, 'dry-run task ref skip must not verify execution');
assert.strictEqual(result.would_verify, true, 'dry-run task ref skip should report would_verify=true');
assert.strictEqual(result.reason, 'DRY_RUN_SKIP_REF_CHECK');
});

// TEST 11: non-dry-run cross-lane path security remains fail-closed
// Source lane outside allowed roots must trigger SECURITY error
test('non-dry-run cross-lane path security is fail-closed', function(tmpRoot) {
var resolver = new ArtifactResolver({ allowedRoots: [tmpRoot], dryRun: false });
var gate = new ExecutionGate({ lane: 'archivist', dryRun: false, resolver: resolver });

var msg = {
id: 'security-test',
from: 'swarmmind',
to: 'archivist',
type: 'task',
priority: 'P1',
timestamp: new Date().toISOString(),
requires_action: true,
completion_message_id: 'some-message-id',
};

assert.throws(function() {
gate.verify(msg);
}, /outside allowed roots|Invalid lane identifier/i, 'non-dry-run cross-lane must throw security error');
});

// TEST 12: dry-run fs check must not set execution_verified=true
test('dry-run fs skip reports would_verify=true but execution_verified=false', function(tmpRoot) {
var resolver = new ArtifactResolver({ allowedRoots: [tmpRoot], dryRun: true });
var gate = new ExecutionGate({ lane: 'archivist', dryRun: true, resolver: resolver });
Expand All @@ -365,6 +429,108 @@ test('dry-run fs skip reports would_verify=true but execution_verified=false', f
assert.strictEqual(result.reason, 'DRY_RUN_SKIP_FS_CHECK');
});

// TEST 13: INVALID_JSON route includes normalized metadata defaults
test('INVALID_JSON route includes normalized metadata defaults', function(tmpRoot) {
var inbox = path.join(tmpRoot, 'lanes', 'archivist', 'inbox');
mkDir(inbox);
['action-required', 'in-progress', 'processed', 'blocked', 'quarantine'].forEach(function(d) {
mkDir(path.join(inbox, d));
});

// Write a message file that will trigger INVALID_JSON
var msgPath = path.join(inbox, '2026-01-01_trigger_exception.json');
fs.writeFileSync(msgPath, 'NOT VALID JSON', 'utf8');

var worker = new LaneWorker({
repoRoot: tmpRoot,
lane: 'archivist',
dryRun: false,
config: {
repoRoot: tmpRoot,
lane: 'archivist',
queues: {
inbox: inbox,
actionRequired: path.join(inbox, 'action-required'),
inProgress: path.join(inbox, 'in-progress'),
processed: path.join(inbox, 'processed'),
blocked: path.join(inbox, 'blocked'),
quarantine: path.join(inbox, 'quarantine'),
},
},
schemaValidator: function() { return { valid: true, errors: [] }; },
signatureValidator: function() { return { valid: true, reason: null, details: null }; },
});

var summary = worker.processOnce();
assert.strictEqual(summary.routed.quarantine, 1, 'Must route to quarantine');

var route = summary.routes[0];
assert.strictEqual(route.reason, 'INVALID_JSON');
assert.strictEqual(route.execution_verified, false, 'INVALID_JSON must have execution_verified=false');
assert.strictEqual(route.would_verify, false, 'INVALID_JSON must have would_verify=false');
assert.deepStrictEqual(route.ownership_notes, [], 'INVALID_JSON must have ownership_notes=[]');
assert.strictEqual(route.schema_remediation, null, 'INVALID_JSON must have schema_remediation=null');
});

// TEST 14: PROCESSING_EXCEPTION route includes normalized metadata defaults
// Exercises the processOnce() catch block by replacing processFile with a throwing stub
test('PROCESSING_EXCEPTION route includes normalized metadata defaults', function(tmpRoot) {
var inbox = path.join(tmpRoot, 'lanes', 'archivist', 'inbox');
mkDir(inbox);
['action-required', 'in-progress', 'processed', 'blocked', 'quarantine'].forEach(function(d) {
mkDir(path.join(inbox, d));
});

// Write a valid message file (will be processed by the stubbed processFile)
var msgPath = path.join(inbox, '2026-01-01_process_exception.json');
fs.writeFileSync(msgPath, JSON.stringify({
id: 'process-exception-test',
from: 'library',
to: 'archivist',
type: 'task',
priority: 'P1',
timestamp: new Date().toISOString(),
requires_action: true,
subject: 'Test',
body: 'Test',
}, null, 2), 'utf8');

var worker = new LaneWorker({
repoRoot: tmpRoot,
lane: 'archivist',
dryRun: false,
config: {
repoRoot: tmpRoot,
lane: 'archivist',
queues: {
inbox: inbox,
actionRequired: path.join(inbox, 'action-required'),
inProgress: path.join(inbox, 'in-progress'),
processed: path.join(inbox, 'processed'),
blocked: path.join(inbox, 'blocked'),
quarantine: path.join(inbox, 'quarantine'),
},
},
schemaValidator: function() { return { valid: true, errors: [] }; },
signatureValidator: function() { return { valid: true, reason: null, details: null }; },
});

// Replace processFile with a function that throws to trigger the catch block
worker.processFile = function() {
throw new Error('TEST_PROCESSING_EXCEPTION');
};

var summary = worker.processOnce();
assert.strictEqual(summary.routed.quarantine, 1, 'Must route to quarantine');

var route = summary.routes[0];
assert.strictEqual(route.reason, 'PROCESSING_EXCEPTION');
assert.strictEqual(route.execution_verified, false, 'PROCESSING_EXCEPTION must have execution_verified=false');
assert.strictEqual(route.would_verify, false, 'PROCESSING_EXCEPTION must have would_verify=false');
assert.deepStrictEqual(route.ownership_notes, [], 'PROCESSING_EXCEPTION must have ownership_notes=[]');
assert.strictEqual(route.schema_remediation, null, 'PROCESSING_EXCEPTION must have schema_remediation=null');
});

// SUMMARY
console.log('\n========================================');
console.log('Execution Gate Tests');
Expand Down
Loading