mirror of
https://github.com/PR0M3TH3AN/TORCH.git
synced 2026-11-05 20:31:54 +00:00
Merge canonical for TASK-pilot-cli-suite-timeout
This commit is contained in:
commit
0c8db11bcc
41 files changed
+4544
-24
No files matched your search
@@ -6,6 +6,18 @@ import '../../site/live-refresh.js';
|
||||
|
||||
const project = (snapshot) => globalThis.TorchAttentionProjection.groups(snapshot);
|
||||
|
||||
test('SCN-console-continuation-stop: exhausted budget is an owner policy decision; no-progress remains manager-owned', () => {
|
||||
const groups = project({ continuation: { available: true, stopReason: 'daily-turn-cap', attempts: 12,
|
||||
maxTurnsPerDay: 12, day: '2026-10-03', held: [{ areaId: 'owner-console' }] } });
|
||||
assert.equal(groups.owner.length, 1);
|
||||
assert.match(groups.owner[0].detail, /explicitly change the limit/);
|
||||
assert.equal(groups.owner[0].decisionApprovalId, null, 'No fake approval or automatic budget expansion');
|
||||
assert.equal(groups.fleet.length, 1);
|
||||
assert.equal(groups.fleet[0].owner, 'session-manager');
|
||||
assert.equal(groups.fleet[0].requestOwner, 'session-manager');
|
||||
assert.equal(project({ continuation: { available: true, stopReason: 'paused', held: [] } }).owner.length, 0);
|
||||
});
|
||||
|
||||
test('SCN-console-attention-ownership: only owner-addressed pending decisions enter Waiting on you', () => {
|
||||
const groups = project({
|
||||
agents: [
|
||||
|
||||
@@ -0,0 +1,420 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { execFileSync, spawn } from 'node:child_process';
|
||||
import { mkdtempSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { basename, join } from 'node:path';
|
||||
import test from 'node:test';
|
||||
import { openControlPlane } from '../../src/control-plane/service.mjs';
|
||||
import { analyzeRepository } from '../../src/kernel/analyze.mjs';
|
||||
import { proposeDomains } from '../../src/kernel/domains.mjs';
|
||||
import { inspectRepository } from '../../src/kernel/git.mjs';
|
||||
import { installProject } from '../../src/kernel/install.mjs';
|
||||
import { IntegrationService } from '../../src/integration/service.mjs';
|
||||
|
||||
const sha = value => execFileSync('git', ['-C', value, 'rev-parse', 'HEAD'], { encoding: 'utf8' }).trim();
|
||||
const digest = value => createHash('sha256').update(JSON.stringify(value)).digest('hex');
|
||||
|
||||
function fixture(name) {
|
||||
const root = mkdtempSync(join(tmpdir(), `${name}-`));
|
||||
execFileSync('git', ['init', '-b', 'main', root]);
|
||||
execFileSync('git', ['-C', root, 'config', 'user.email', '[email protected]']);
|
||||
execFileSync('git', ['-C', root, 'config', 'user.name', 'TORCH Test']);
|
||||
writeFileSync(join(root, 'package.json'), `${JSON.stringify({ name, scripts: {} })}\n`);
|
||||
writeFileSync(join(root, 'app.mjs'), 'export const value = 1;\n');
|
||||
execFileSync('git', ['-C', root, 'add', '.']);
|
||||
execFileSync('git', ['-C', root, 'commit', '-m', 'fixture base']);
|
||||
const repository = inspectRepository(root);
|
||||
const proposal = proposeDomains({ repository, analysis: analyzeRepository(repository) });
|
||||
proposal.domains.push({
|
||||
id: 'review-area', title: 'Review Area', kind: 'development',
|
||||
scope: ['review fixtures'], not_scope: ['application source'], owned_paths: ['review/**'],
|
||||
shared_paths: [], neighbours: [], required_checks: [], resources: [], runtime: 'claude', evidence: ['app.mjs'],
|
||||
});
|
||||
proposal.checks = [];
|
||||
proposal.review = { status: 'approved', reviewedAt: '2026-10-03T00:00:00.000Z', reviewedBy: 'fixture-owner', notes: [] };
|
||||
const env = { ...process.env, XDG_DATA_HOME: join(tmpdir(), `${basename(root)}-state`) };
|
||||
installProject({ repository, proposal, env, projectId: name });
|
||||
execFileSync('git', ['-C', root, 'add', '.torch']);
|
||||
execFileSync('git', ['-C', root, 'commit', '-m', 'install fixture control plane']);
|
||||
const base = sha(root);
|
||||
execFileSync('git', ['-C', root, 'checkout', '-b', 'candidate']);
|
||||
writeFileSync(join(root, 'candidate.mjs'), 'export const candidate = true;\n');
|
||||
execFileSync('git', ['-C', root, 'add', 'candidate.mjs']);
|
||||
execFileSync('git', ['-C', root, 'commit', '-m', 'candidate contribution']);
|
||||
const candidate = sha(root);
|
||||
execFileSync('git', ['-C', root, 'checkout', 'main']);
|
||||
const control = openControlPlane({ repositoryRoot: root, env });
|
||||
const service = new IntegrationService({ repositoryRoot: root, controlPlane: control, checkService: { exactPasses: () => false } });
|
||||
const contributor = proposal.domains[0].id;
|
||||
return { root, env, control, service, contributor, destination: 'review-area', base, candidate };
|
||||
}
|
||||
|
||||
function concurrentAdoptedRequest(context, requestInput) {
|
||||
const childSource = `
|
||||
import { pathToFileURL } from 'node:url';
|
||||
import { resolve } from 'node:path';
|
||||
const { repositoryRoot, stateRoot, requestInput } = JSON.parse(process.argv[1]);
|
||||
const sourceRoot = process.cwd();
|
||||
const { openControlPlane } = await import(pathToFileURL(resolve(sourceRoot, 'src/control-plane/service.mjs')).href);
|
||||
const { IntegrationService } = await import(pathToFileURL(resolve(sourceRoot, 'src/integration/service.mjs')).href);
|
||||
const control = openControlPlane({ repositoryRoot, env: { ...process.env, XDG_DATA_HOME: stateRoot } });
|
||||
const service = new IntegrationService({ repositoryRoot, controlPlane: control, checkService: { exactPasses: () => false } });
|
||||
try {
|
||||
const result = service.requestAdoptedCandidate(requestInput);
|
||||
console.log(JSON.stringify({ outcome: 'requested', integrationRequestId: result.integrationRequest.id }));
|
||||
} catch (error) {
|
||||
console.log(JSON.stringify({ outcome: 'refused', code: error.code ?? null }));
|
||||
} finally {
|
||||
control.close();
|
||||
}
|
||||
`;
|
||||
const encoded = JSON.stringify({ repositoryRoot: context.root, stateRoot: context.env.XDG_DATA_HOME, requestInput });
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn(process.execPath, ['--input-type=module', '-e', childSource, encoded], {
|
||||
cwd: process.cwd(), env: process.env, stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
let stdout = ''; let stderr = '';
|
||||
child.stdout.on('data', chunk => { stdout += chunk; });
|
||||
child.stderr.on('data', chunk => { stderr += chunk; });
|
||||
child.on('error', reject);
|
||||
child.on('close', (code) => {
|
||||
if (code !== 0) return reject(new Error(`concurrent adopted request child failed: ${stderr}`));
|
||||
try { resolve(JSON.parse(stdout.trim().split('\n').at(-1))); }
|
||||
catch (error) { reject(new Error(`invalid concurrent adopted request output: ${stdout}\n${stderr}\n${error.message}`)); }
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function authority(context, { action, adoptionId }) {
|
||||
const provenance = ['fixture:owner-approved', `candidate:${context.candidate}`];
|
||||
const binding = {
|
||||
schema: 'torch.dev/integration-adoption-authority/v1alpha1', action, adoptionId,
|
||||
candidateSha: context.candidate, baseTargetSha: context.base,
|
||||
provenanceManifestDigest: digest(provenance), contributorArea: context.contributor,
|
||||
destinationArea: context.destination, targetBranch: 'main',
|
||||
candidateRef: `refs/torch/integration-candidates/${adoptionId}`,
|
||||
expectedOldRef: 'refs/heads/main', adapter: {
|
||||
name: context.service.adoption.refAdapter.name, id: context.service.adoption.refAdapter.id,
|
||||
},
|
||||
};
|
||||
const pending = context.control.requestApproval({
|
||||
requester: context.contributor, approver: 'owner', task: 'TASK-f40126ee',
|
||||
title: `Fixture ${action} authority`, summary: 'Hermetic exact-source adoption scenario.',
|
||||
});
|
||||
const approved = context.control.decideApproval({
|
||||
approvalId: pending.id, decidedBy: 'owner', decision: 'approved', expectedRevision: pending.revision,
|
||||
note: JSON.stringify(binding),
|
||||
});
|
||||
return {
|
||||
approvalId: approved.id, approvalRevision: approved.revision, provenance,
|
||||
provenanceManifestDigest: binding.provenanceManifestDigest, adapter: binding.adapter,
|
||||
};
|
||||
}
|
||||
|
||||
function input(context, action, adoptionId, extra = {}) {
|
||||
return {
|
||||
actorId: 'owner', adoptionId, candidateSha: context.candidate, baseTargetSha: context.base,
|
||||
contributorArea: context.contributor, destinationArea: context.destination,
|
||||
targetBranch: 'main', candidateRef: `refs/torch/integration-candidates/${adoptionId}`,
|
||||
expectedOldRef: 'refs/heads/main', ...authority(context, { action, adoptionId }), ...extra,
|
||||
};
|
||||
}
|
||||
|
||||
function prepare(context, adoptionId = 'fixture-adoption') {
|
||||
const preparedInput = input(context, 'prepare', adoptionId);
|
||||
const plan = context.service.planAdoption({ ...preparedInput, action: 'prepare' });
|
||||
assert.deepEqual(plan.blockers, [], `preparation must begin from an exact clean fixture: ${JSON.stringify(plan.blockers)}`);
|
||||
return context.service.prepareAdoption(preparedInput);
|
||||
}
|
||||
|
||||
function lifecycleInput(context, action, adoptionId, extra = {}) {
|
||||
const { approvalId, approvalRevision, adapter } = authority(context, { action, adoptionId });
|
||||
const record = context.service.getPreparedAdoption(adoptionId);
|
||||
return { actorId: 'owner', adoptionId, expectedRecordRevision: record.revision, approvalId, approvalRevision, adapter, ...extra };
|
||||
}
|
||||
|
||||
function blockedCodes(error) {
|
||||
assert.equal(error.code.endsWith('_BLOCKED'), true, `expected a blocked adoption transition, got ${error.code}`);
|
||||
return error.details.map((blocker) => blocker.code);
|
||||
}
|
||||
|
||||
function lifecycleBoundary(context, adoptionId) {
|
||||
const record = context.service.getPreparedAdoption(adoptionId);
|
||||
return {
|
||||
record,
|
||||
refs: context.service.adoption.refAdapter.readRefs([
|
||||
record.expectedOldRef, record.candidateRef, record.archiveRef,
|
||||
]),
|
||||
queue: context.service.list(),
|
||||
auditCount: context.control.database.prepare(`SELECT COUNT(*) AS count FROM audit_events
|
||||
WHERE project_id = ? AND entity_type = 'integration-adoption' AND entity_id = ?`)
|
||||
.get(context.control.projectId, adoptionId).count,
|
||||
};
|
||||
}
|
||||
|
||||
function assertLifecycleBoundaryUnchanged(context, adoptionId, before) {
|
||||
assert.deepEqual(lifecycleBoundary(context, adoptionId), before);
|
||||
}
|
||||
|
||||
test('SCN-integration-adoption-private-ref-cas: a real temporary Git transaction atomically creates private archive and candidate refs, and stale expected refs refuse', () => {
|
||||
const context = fixture('torch-adoption-cas');
|
||||
const adoptionId = 'fixture-adoption';
|
||||
prepare(context, adoptionId);
|
||||
const finalized = context.service.finalizeAdoption(lifecycleInput(context, 'finalize', adoptionId));
|
||||
const refs = context.service.adoption.refAdapter.readRefs([
|
||||
'refs/heads/main', finalized.candidateRef, finalized.archiveRef,
|
||||
]);
|
||||
assert.deepEqual(refs, {
|
||||
'refs/heads/main': context.base,
|
||||
[finalized.candidateRef]: context.candidate,
|
||||
[finalized.archiveRef]: context.base,
|
||||
});
|
||||
assert.throws(() => context.service.adoption.refAdapter.compareAndSwapRefs({
|
||||
expected: [{ ref: finalized.candidateRef, sha: context.base }],
|
||||
writes: [{ ref: finalized.candidateRef, sha: null }],
|
||||
}), (error) => error.code === 'ADOPTION_REF_CAS_FAILED');
|
||||
assert.equal(context.service.adoption.getPrepared(adoptionId).state, 'finalized');
|
||||
context.control.close();
|
||||
});
|
||||
|
||||
test('SCN-integration-adoption-journal-and-binding: a duplicate preparation cannot overwrite the journal and lifecycle input cannot alter its immutable candidate binding', () => {
|
||||
const context = fixture('torch-adoption-journal');
|
||||
const adoptionId = 'fixture-adoption';
|
||||
const prepared = prepare(context, adoptionId);
|
||||
assert.throws(() => context.service.prepareAdoption(input(context, 'prepare', adoptionId)),
|
||||
(error) => error.code === 'ADOPTION_RECORD_REVISION_STALE');
|
||||
assert.throws(() => context.service.finalizeAdoption(lifecycleInput(context, 'finalize', adoptionId, { candidateSha: context.base })),
|
||||
(error) => error.code === 'ADOPTION_IMMUTABLE_BINDING_STALE');
|
||||
assert.throws(() => context.service.finalizeAdoption(lifecycleInput(context, 'finalize', adoptionId, {
|
||||
expectedRecordRevision: prepared.revision + 1,
|
||||
})), (error) => error.code === 'ADOPTION_RECORD_REVISION_STALE');
|
||||
const refs = context.service.adoption.refAdapter.readRefs([
|
||||
prepared.expectedOldRef, prepared.candidateRef, prepared.archiveRef,
|
||||
]);
|
||||
assert.deepEqual(refs, {
|
||||
[prepared.expectedOldRef]: context.base,
|
||||
[prepared.candidateRef]: null,
|
||||
[prepared.archiveRef]: null,
|
||||
});
|
||||
const { mutationPerformed, ...durablePrepared } = prepared;
|
||||
assert.equal(mutationPerformed, true);
|
||||
assert.deepEqual(context.service.adoption.getPrepared(adoptionId), durablePrepared);
|
||||
context.control.close();
|
||||
});
|
||||
|
||||
test('SCN-integration-adoption-lifecycle-revision-cas: stale or omitted revisions for every lifecycle action refuse before refs, journal, queue, or audit effects', () => {
|
||||
const cases = [
|
||||
{
|
||||
action: 'finalize',
|
||||
setup: (context, adoptionId) => prepare(context, adoptionId),
|
||||
invoke: (context, input) => context.service.finalizeAdoption(input),
|
||||
},
|
||||
{
|
||||
action: 'reconcile',
|
||||
setup: (context, adoptionId) => prepare(context, adoptionId),
|
||||
invoke: (context, input) => context.service.reconcileAdoption(input),
|
||||
},
|
||||
{
|
||||
action: 'rollback',
|
||||
setup: (context, adoptionId) => {
|
||||
prepare(context, adoptionId);
|
||||
return context.service.finalizeAdoption(lifecycleInput(context, 'finalize', adoptionId));
|
||||
},
|
||||
invoke: (context, input) => context.service.rollbackAdoption(input),
|
||||
},
|
||||
{
|
||||
action: 'request',
|
||||
setup: (context, adoptionId) => {
|
||||
prepare(context, adoptionId);
|
||||
return context.service.finalizeAdoption(lifecycleInput(context, 'finalize', adoptionId));
|
||||
},
|
||||
invoke: (context, input) => context.service.requestAdoptedCandidate(input),
|
||||
},
|
||||
];
|
||||
|
||||
for (const { action, setup, invoke } of cases) {
|
||||
const context = fixture(`torch-adoption-${action}-revision-boundary`);
|
||||
const adoptionId = 'fixture-adoption';
|
||||
setup(context, adoptionId);
|
||||
for (const variant of ['omitted', 'stale']) {
|
||||
const current = lifecycleInput(context, action, adoptionId);
|
||||
const input = variant === 'omitted'
|
||||
? (({ expectedRecordRevision: _expectedRecordRevision, ...withoutRevision }) => withoutRevision)(current)
|
||||
: { ...current, expectedRecordRevision: current.expectedRecordRevision + 1 };
|
||||
const before = lifecycleBoundary(context, adoptionId);
|
||||
assert.throws(() => invoke(context, input), (error) => error.code === 'ADOPTION_RECORD_REVISION_STALE',
|
||||
`${action} must reject a ${variant} lifecycle revision`);
|
||||
assertLifecycleBoundaryUnchanged(context, adoptionId, before);
|
||||
}
|
||||
context.control.close();
|
||||
}
|
||||
});
|
||||
|
||||
test('SCN-integration-adoption-finalize-recovery: a crash after real ref CAS but before the journal transition reconciles once and never duplicates refs', () => {
|
||||
const context = fixture('torch-adoption-finalize-recovery');
|
||||
const adoptionId = 'fixture-adoption';
|
||||
const prepared = prepare(context, adoptionId);
|
||||
context.service.adoption.refAdapter.compareAndSwapRefs({
|
||||
expected: [
|
||||
{ ref: prepared.expectedOldRef, sha: prepared.baseTargetSha },
|
||||
{ ref: prepared.candidateRef, sha: null },
|
||||
{ ref: prepared.archiveRef, sha: null },
|
||||
],
|
||||
writes: [
|
||||
{ ref: prepared.archiveRef, sha: prepared.baseTargetSha },
|
||||
{ ref: prepared.candidateRef, sha: prepared.candidateSha },
|
||||
],
|
||||
});
|
||||
const recovered = context.service.reconcileAdoption(lifecycleInput(context, 'reconcile', adoptionId));
|
||||
assert.equal(recovered.state, 'recovered-finalized');
|
||||
assert.equal(recovered.record.recovery, 'private-ref-cas-completed-before-journal');
|
||||
const replay = context.service.reconcileAdoption(lifecycleInput(context, 'reconcile', adoptionId));
|
||||
assert.equal(replay.state, 'consistent');
|
||||
assert.equal(replay.mutationPerformed, false);
|
||||
context.control.close();
|
||||
});
|
||||
|
||||
test('SCN-integration-adoption-rollback-recovery: a journal-missing rollback completes without a second deletion, while divergent candidate bytes refuse', () => {
|
||||
const context = fixture('torch-adoption-rollback-recovery');
|
||||
const adoptionId = 'fixture-adoption';
|
||||
prepare(context, adoptionId);
|
||||
const finalized = context.service.finalizeAdoption(lifecycleInput(context, 'finalize', adoptionId));
|
||||
assert.equal(finalized.state, 'finalized');
|
||||
context.service.adoption.refAdapter.compareAndSwapRefs({
|
||||
expected: [{ ref: finalized.candidateRef, sha: finalized.candidateSha }],
|
||||
writes: [{ ref: finalized.candidateRef, sha: null }],
|
||||
});
|
||||
const recovered = context.service.rollbackAdoption(lifecycleInput(context, 'rollback', adoptionId));
|
||||
assert.equal(recovered.state, 'rolled-back');
|
||||
assert.equal(recovered.recovery, 'private-ref-rollback-completed-before-journal');
|
||||
|
||||
const divergent = fixture('torch-adoption-rollback-divergence');
|
||||
const divergentId = 'fixture-adoption';
|
||||
prepare(divergent, divergentId);
|
||||
const divergentFinal = divergent.service.finalizeAdoption(lifecycleInput(divergent, 'finalize', divergentId));
|
||||
divergent.service.adoption.refAdapter.compareAndSwapRefs({
|
||||
expected: [{ ref: divergentFinal.candidateRef, sha: divergentFinal.candidateSha }],
|
||||
writes: [{ ref: divergentFinal.candidateRef, sha: divergent.base }],
|
||||
});
|
||||
assert.throws(() => divergent.service.rollbackAdoption(lifecycleInput(divergent, 'rollback', divergentId)),
|
||||
(error) => error.code === 'ADOPTION_ROLLBACK_REF_DIVERGED');
|
||||
context.control.close();
|
||||
divergent.control.close();
|
||||
});
|
||||
|
||||
test('SCN-integration-adoption-private-ref-intake: finalized private refs enter the ordinary native integration queue, not a specialist branch shortcut', () => {
|
||||
const unfinalized = fixture('torch-adoption-unfinalized-intake');
|
||||
const unfinalizedId = 'fixture-adoption';
|
||||
const unfinalizedRef = `refs/torch/integration-candidates/${unfinalizedId}`;
|
||||
unfinalized.service.adoption.refAdapter.compareAndSwapRefs({
|
||||
expected: [{ ref: unfinalizedRef, sha: null }],
|
||||
writes: [{ ref: unfinalizedRef, sha: unfinalized.candidate }],
|
||||
});
|
||||
assert.throws(() => unfinalized.service.requestCandidate({
|
||||
areaId: unfinalized.contributor, commit: unfinalized.candidate, candidateRef: unfinalizedRef,
|
||||
}), (error) => error.code === 'INTEGRATION_ADOPTED_INTAKE_INTERNAL_ONLY');
|
||||
assert.deepEqual(unfinalized.service.list(), []);
|
||||
unfinalized.control.close();
|
||||
|
||||
const context = fixture('torch-adoption-native-intake');
|
||||
const adoptionId = 'fixture-adoption';
|
||||
prepare(context, adoptionId);
|
||||
const finalized = context.service.finalizeAdoption(lifecycleInput(context, 'finalize', adoptionId));
|
||||
assert.throws(() => context.service.requestAdoptedCandidate(lifecycleInput(context, 'request', adoptionId, {
|
||||
expectedRecordRevision: finalized.revision + 1,
|
||||
})), (error) => error.code === 'ADOPTION_RECORD_REVISION_STALE');
|
||||
assert.deepEqual(context.service.list(), []);
|
||||
const requested = context.service.requestAdoptedCandidate(lifecycleInput(context, 'request', adoptionId));
|
||||
assert.equal(requested.state, 'requested');
|
||||
assert.equal(requested.integrationRequest.sourceBranch, finalized.candidateRef);
|
||||
assert.equal(requested.integrationRequest.sourceCommit, context.candidate);
|
||||
assert.equal(requested.integrationRequest.state, 'testing');
|
||||
assert.equal(requested.integrationRequest.targetBranch, 'main');
|
||||
context.control.close();
|
||||
});
|
||||
|
||||
test('SCN-integration-adoption-approval-boundaries: unrelated or source-only approval records cannot authorize an owner adoption, and actor, action, revision, and provenance mismatches each refuse', () => {
|
||||
const unrelated = fixture('torch-adoption-unrelated-approval');
|
||||
const unrelatedId = 'fixture-adoption';
|
||||
const unrelatedApproval = unrelated.control.requestApproval({
|
||||
requester: unrelated.contributor, approver: 'owner', task: 'TASK-unrelated',
|
||||
title: 'Ordinary source branch review', summary: 'Not an integration-adoption authority.',
|
||||
});
|
||||
const unrelatedApproved = unrelated.control.decideApproval({
|
||||
approvalId: unrelatedApproval.id, decidedBy: 'owner', decision: 'approved', expectedRevision: unrelatedApproval.revision,
|
||||
note: JSON.stringify({ schema: 'torch.dev/ordinary-source-review/v1alpha1', sourceArea: unrelated.contributor }),
|
||||
});
|
||||
const unrelatedInput = {
|
||||
actorId: 'owner', adoptionId: unrelatedId, candidateSha: unrelated.candidate, baseTargetSha: unrelated.base,
|
||||
contributorArea: unrelated.contributor, destinationArea: unrelated.destination, targetBranch: 'main',
|
||||
candidateRef: `refs/torch/integration-candidates/${unrelatedId}`, expectedOldRef: 'refs/heads/main',
|
||||
approvalId: unrelatedApproved.id, approvalRevision: unrelatedApproved.revision,
|
||||
provenance: ['fixture:owner-approved', `candidate:${unrelated.candidate}`],
|
||||
provenanceManifestDigest: digest(['fixture:owner-approved', `candidate:${unrelated.candidate}`]),
|
||||
adapter: { name: unrelated.service.adoption.refAdapter.name, id: unrelated.service.adoption.refAdapter.id },
|
||||
};
|
||||
assert.throws(() => unrelated.service.prepareAdoption(unrelatedInput), (error) => blockedCodes(error).includes('ADOPTION_APPROVAL_BINDING_INVALID'));
|
||||
assert.deepEqual(unrelated.service.list(), []);
|
||||
unrelated.control.close();
|
||||
|
||||
const wrongActor = fixture('torch-adoption-wrong-actor');
|
||||
assert.throws(() => wrongActor.service.prepareAdoption({
|
||||
...input(wrongActor, 'prepare', 'fixture-adoption'), actorId: wrongActor.contributor,
|
||||
}), (error) => blockedCodes(error).includes('OWNER_ACTOR_REQUIRED'));
|
||||
wrongActor.control.close();
|
||||
|
||||
const wrongAction = fixture('torch-adoption-wrong-action');
|
||||
prepare(wrongAction, 'fixture-adoption');
|
||||
assert.throws(() => wrongAction.service.finalizeAdoption(lifecycleInput(wrongAction, 'rollback', 'fixture-adoption')),
|
||||
(error) => blockedCodes(error).includes('ADOPTION_APPROVAL_BINDING_INVALID'));
|
||||
wrongAction.control.close();
|
||||
|
||||
const staleRevision = fixture('torch-adoption-stale-revision');
|
||||
prepare(staleRevision, 'fixture-adoption');
|
||||
const staleInput = lifecycleInput(staleRevision, 'finalize', 'fixture-adoption');
|
||||
assert.throws(() => staleRevision.service.finalizeAdoption({ ...staleInput, approvalRevision: staleInput.approvalRevision - 1 }),
|
||||
(error) => blockedCodes(error).includes('ADOPTION_APPROVAL_REVISION_STALE'));
|
||||
staleRevision.control.close();
|
||||
|
||||
const wrongProvenance = fixture('torch-adoption-wrong-provenance');
|
||||
assert.throws(() => wrongProvenance.service.prepareAdoption({
|
||||
...input(wrongProvenance, 'prepare', 'fixture-adoption'), provenance: ['fixture:owner-approved', 'unapproved:alternate-provenance'],
|
||||
}), (error) => blockedCodes(error).includes('ADOPTION_PROVENANCE_DIGEST_MISMATCH'));
|
||||
wrongProvenance.control.close();
|
||||
});
|
||||
|
||||
test('SCN-integration-adoption-request-replay: concurrent authenticated API callers serialize on the adoption journal and create exactly one private-ref queue request', async () => {
|
||||
const context = fixture('torch-adoption-request-replay');
|
||||
const adoptionId = 'fixture-adoption';
|
||||
prepare(context, adoptionId);
|
||||
context.service.finalizeAdoption(lifecycleInput(context, 'finalize', adoptionId));
|
||||
const requestInput = lifecycleInput(context, 'request', adoptionId);
|
||||
const outcomes = await Promise.all([
|
||||
concurrentAdoptedRequest(context, requestInput),
|
||||
concurrentAdoptedRequest(context, requestInput),
|
||||
]);
|
||||
assert.deepEqual(outcomes.map((entry) => entry.outcome).sort(), ['refused', 'requested']);
|
||||
assert.equal(outcomes.find((entry) => entry.outcome === 'refused').code, 'ADOPTION_RECORD_REVISION_STALE');
|
||||
const requests = context.service.list();
|
||||
assert.equal(requests.length, 1);
|
||||
assert.equal(context.service.getPreparedAdoption(adoptionId).state, 'requested');
|
||||
context.control.close();
|
||||
});
|
||||
|
||||
test('SCN-integration-adoption-legacy-retries: historical ordinary-request retries remain readable when integration service starts', () => {
|
||||
const context = fixture('torch-adoption-legacy-retries');
|
||||
const insert = context.control.database.prepare(`INSERT INTO integration_requests (
|
||||
id, project_id, source_area, source_branch, source_commit, target_branch,
|
||||
base_target_commit, state, reason, required_checks, created_at, updated_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
||||
for (const [id, state] of [['historical-failed', 'blocked'], ['historical-retry', 'superseded']]) {
|
||||
insert.run(id, context.control.projectId, context.contributor, 'refs/heads/candidate', context.candidate,
|
||||
'main', context.base, state, 'historical-retry', '[]', '2026-10-03T00:00:00.000Z', '2026-10-03T00:00:00.000Z');
|
||||
}
|
||||
assert.doesNotThrow(() => new IntegrationService({
|
||||
repositoryRoot: context.root, controlPlane: context.control, checkService: { exactPasses: () => false },
|
||||
}));
|
||||
assert.equal(context.service.list().length, 2);
|
||||
context.control.close();
|
||||
});
|
||||
@@ -0,0 +1,433 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import test from 'node:test';
|
||||
import { analyzeRepository } from '../../src/kernel/analyze.mjs';
|
||||
import { inspectRepository } from '../../src/kernel/git.mjs';
|
||||
import { proposeDomains } from '../../src/kernel/domains.mjs';
|
||||
import { installProject } from '../../src/kernel/install.mjs';
|
||||
import { openControlPlane } from '../../src/control-plane/service.mjs';
|
||||
import { issueStoppedExecutorEvidence } from '../../src/runtime/stopped-executor-evidence.mjs';
|
||||
import { observeRuntimeTurn, withRuntimeTurnGuard } from '../../src/runtime/turn-guard.mjs';
|
||||
import { observeProject } from '../../src/observability/snapshot.mjs';
|
||||
import { configureContinuation, observeContinuation, planContinuation, runContinuation } from '../../src/runtime/continuation.mjs';
|
||||
|
||||
function fixture({ recoveryWorktree = true, extraWorker = false, workerCount = 0 } = {}) {
|
||||
const root = mkdtempSync(join(tmpdir(), 'torch-legacy-recovery-'));
|
||||
for (const args of [['init', '-b', 'main'], ['config', 'user.email', '[email protected]'],
|
||||
['config', 'user.name', 'Recovery Fixture']]) execFileSync('git', args, { cwd: root, stdio: 'pipe' });
|
||||
writeFileSync(join(root, 'app.js'), 'export const ready = true;\n');
|
||||
execFileSync('git', ['add', '.'], { cwd: root });
|
||||
execFileSync('git', ['commit', '-m', 'fixture'], { cwd: root, stdio: 'pipe' });
|
||||
const repository = inspectRepository(root);
|
||||
const proposal = proposeDomains({ repository, analysis: analyzeRepository(repository) });
|
||||
if (extraWorker) proposal.domains.push({ ...proposal.domains[0], id: 'extra-worker', title: 'Extra worker', owned_paths: ['extra/**'] });
|
||||
for (let i = 0; i < workerCount; i++) proposal.domains.push({ ...proposal.domains[0], id: `worker-${i}`, title: `Worker ${i}`, owned_paths: [`worker-${i}/**`] });
|
||||
proposal.review = { status: 'approved', reviewedAt: '2026-10-03T00:00:00Z', reviewedBy: 'owner', notes: [] };
|
||||
const env = { ...process.env, XDG_DATA_HOME: join(root, '.data') };
|
||||
installProject({ repository, proposal, env, projectId: 'recovery-fixture' });
|
||||
const actualPath = join(root, '.torch', 'install-manifest.json');
|
||||
const manifest = JSON.parse(readFileSync(actualPath, 'utf8'));
|
||||
const areaId = proposal.domains[0].id;
|
||||
if (recoveryWorktree) manifest.external.push({ type: 'worktree', area: areaId, path: join(root, 'worker') });
|
||||
writeFileSync(actualPath, JSON.stringify(manifest));
|
||||
let time = Date.parse('2026-10-03T00:00:00Z');
|
||||
const control = openControlPlane({ repositoryRoot: root, env, clock: () => new Date(time) });
|
||||
control.reportStatus({ areaId, state: 'working', runtime: 'codex', runtimeSessionId: 'fixture-session',
|
||||
task: 'TASK-preserved', summary: 'Runtime executor completion is unknown; duplicate launch is blocked pending reconciliation.' });
|
||||
const identity = control.getAgent(areaId);
|
||||
const unit = `torch-${control.projectId}-worker.service`;
|
||||
const properties = { Id: unit, ActiveState: 'failed', SubState: 'failed', MainPID: '0', ControlPID: '0',
|
||||
ControlGroup: '', InvocationID: 'a'.repeat(32), ExecStart: `node /tmp/probe.mjs ${areaId} ;`,
|
||||
WorkingDirectory: root, ExecMainExitTimestamp: identity.updatedAt };
|
||||
let processes = [];
|
||||
const issue = () => issueStoppedExecutorEvidence({ controlPlane: control, actorId: 'owner', areaId, unit,
|
||||
invocationId: 'a'.repeat(32), readUnit: () => ({ ...properties }), readProcesses: () => processes, now: () => time });
|
||||
const apply = issued => control.recoverOwnerStoppedUnknownIdentity({ actorId: 'owner', areaId,
|
||||
expectedIdentity: issued.plan.expectedIdentity, evidence: issued.evidence });
|
||||
return { root, env, control, identity, areaId, properties, issue, apply,
|
||||
advance: ms => { time += ms; }, processes: value => { processes = value; } };
|
||||
}
|
||||
|
||||
test('SCN-legacy-runtime-recovery: owner recovery preserves task/session/unknown and records exactly one owner audit', () => {
|
||||
const f = fixture();
|
||||
try {
|
||||
const issued = f.issue();
|
||||
assert.equal(f.control.getAgent(f.areaId).state, 'working');
|
||||
const result = f.apply(issued);
|
||||
assert.equal(result.identity.state, 'offline');
|
||||
assert.equal(result.identity.currentTask, 'TASK-preserved');
|
||||
assert.equal(result.identity.runtimeSessionId, 'fixture-session');
|
||||
assert.equal(result.priorOutcome, 'unknown');
|
||||
assert.equal(result.sessionsStarted, false);
|
||||
const audits = f.control.readAudit({ limit: 100 }).filter(x => x.operation === 'runtime.legacy-unknown.recover-stopped');
|
||||
assert.equal(audits.length, 1);
|
||||
assert.equal(audits[0].actorId, 'owner');
|
||||
assert.equal(audits[0].details.outcome, 'unknown');
|
||||
assert.throws(() => f.apply(issued), { code: 'RUNTIME_RECOVERY_SNAPSHOT_CONFLICT' });
|
||||
} finally { f.control.close(); }
|
||||
});
|
||||
|
||||
test('SCN-legacy-runtime-recovery-refusal: live, foreign invocation, area, time and process mismatches cannot issue proof', () => {
|
||||
for (const patch of [{ MainPID: '99' }, { ControlPID: '99' }, { ActiveState: 'active' },
|
||||
{ InvocationID: 'b'.repeat(32) }, { ExecStart: 'node other-area' },
|
||||
{ ExecMainExitTimestamp: '2026-10-02T00:00:00Z' }, { ControlGroup: '/live' }]) {
|
||||
const f = fixture();
|
||||
try { Object.assign(f.properties, patch); assert.throws(f.issue, { code: 'RUNTIME_RECOVERY_UNVERIFIED' });
|
||||
assert.equal(f.control.getAgent(f.areaId).state, 'working'); } finally { f.control.close(); }
|
||||
}
|
||||
const f = fixture();
|
||||
try { f.processes([{ pid: 999, argv: ['codex', 'resume', 'fixture-session'], cwd: '/tmp' }]);
|
||||
assert.throws(f.issue, { code: 'RUNTIME_RECOVERY_UNVERIFIED' }); } finally { f.control.close(); }
|
||||
});
|
||||
|
||||
test('SCN-legacy-runtime-recovery-cas: changed snapshot, live recheck, expiry and caller-forged proof refuse without mutation', () => {
|
||||
for (const scenario of ['changed', 'live', 'expired', 'forged']) {
|
||||
const f = fixture();
|
||||
try {
|
||||
const issued = f.issue();
|
||||
if (scenario === 'changed') { f.advance(1); f.control.reportStatus({ areaId: f.areaId, state: 'working', runtimeSessionId: 'different', summary: 'Runtime executor completion is unknown' }); }
|
||||
if (scenario === 'live') f.properties.MainPID = '99';
|
||||
if (scenario === 'expired') f.advance(60_001);
|
||||
if (scenario === 'forged') issued.evidence = { stopped: true };
|
||||
assert.throws(() => f.apply(issued));
|
||||
assert.equal(f.control.getAgent(f.areaId).state, 'working');
|
||||
assert.equal(f.control.readAudit({ limit: 100 }).filter(x => x.operation === 'runtime.legacy-unknown.recover-stopped').length, 0);
|
||||
} finally { f.control.close(); }
|
||||
}
|
||||
});
|
||||
|
||||
test('SCN-legacy-runtime-recovery-authority: specialist and incomplete snapshot cannot use owner recovery', () => {
|
||||
const f = fixture();
|
||||
try {
|
||||
const issued = f.issue();
|
||||
assert.throws(() => f.control.recoverOwnerStoppedUnknownIdentity({ actorId: f.areaId, areaId: f.areaId,
|
||||
expectedIdentity: issued.plan.expectedIdentity, evidence: issued.evidence }), { code: 'OWNER_AUTHORITY_REQUIRED' });
|
||||
assert.throws(() => f.control.recoverOwnerStoppedUnknownIdentity({ actorId: 'owner', areaId: f.areaId,
|
||||
expectedIdentity: {}, evidence: issued.evidence }), { code: 'RUNTIME_RECOVERY_SNAPSHOT_REQUIRED' });
|
||||
assert.equal(f.control.getAgent(f.areaId).state, 'working');
|
||||
} finally { f.control.close(); }
|
||||
});
|
||||
|
||||
test('SCN-runtime-turn-guard: overlapping executors refuse even if an agent reports idle; terminal turns release their own guard', () => {
|
||||
const f = fixture();
|
||||
try {
|
||||
let launches = 0;
|
||||
withRuntimeTurnGuard(f.control, f.areaId, () => {
|
||||
launches++;
|
||||
f.control.reportStatus({ areaId: f.areaId, state: 'idle', summary: 'Agent reported idle before physical exit' });
|
||||
assert.throws(() => withRuntimeTurnGuard(f.control, f.areaId, () => { launches++; }), { code: 'RUNTIME_TURN_ACTIVE' });
|
||||
});
|
||||
withRuntimeTurnGuard(f.control, f.areaId, () => { launches++; });
|
||||
assert.equal(launches, 2);
|
||||
} finally { f.control.close(); }
|
||||
});
|
||||
|
||||
test('SCN-runtime-live-presence: live guard phases override stale idle display without changing durable reports or waits', { skip: process.platform !== 'linux' }, () => {
|
||||
const f = fixture({ recoveryWorktree: false });
|
||||
try {
|
||||
f.control.reportStatus({ areaId: f.areaId, state: 'idle', summary: 'Previous turn completed' });
|
||||
const observe = () => observeProject({ repositoryRoot: f.root, env: f.env }).agents.find(a => a.areaId === f.areaId);
|
||||
withRuntimeTurnGuard(f.control, f.areaId, phase => {
|
||||
let agent = observe();
|
||||
assert.equal(agent.state, 'starting');
|
||||
assert.equal(agent.reportedState, 'idle');
|
||||
assert.equal(agent.executor.source, 'live-process-and-turn-guard');
|
||||
phase('working');
|
||||
agent = observe();
|
||||
assert.equal(agent.state, 'working');
|
||||
assert.equal(f.control.getAgent(f.areaId).state, 'idle', 'Observation is not an identity mutation');
|
||||
f.control.reportStatus({ areaId: f.areaId, state: 'waiting', summary: 'Waiting for named approval' });
|
||||
assert.equal(observe().state, 'waiting', 'Live execution must not conceal semantic approval waits');
|
||||
});
|
||||
assert.equal(observe().executor.state, 'inactive');
|
||||
assert.equal(observe().state, 'waiting');
|
||||
f.control.reportStatus({ areaId: f.areaId, state: 'idle', summary: 'Turn completed' });
|
||||
assert.equal(observe().state, 'idle');
|
||||
} finally { f.control.close(); }
|
||||
});
|
||||
|
||||
test('SCN-runtime-live-presence-refusal: dead, replaced, foreign-user and malformed guards never claim active execution', () => {
|
||||
const f = fixture();
|
||||
try {
|
||||
withRuntimeTurnGuard(f.control, f.areaId, () => {
|
||||
const path = join(f.control.stateRoot, 'sessions', 'turn-guards', f.areaId, 'owner.json');
|
||||
const original = JSON.parse(readFileSync(path, 'utf8'));
|
||||
writeFileSync(path, JSON.stringify({ ...original, startTicks: 'fixture-start' }));
|
||||
const read = live => observeRuntimeTurn({ stateRoot: f.control.stateRoot, repositoryRoot: f.root,
|
||||
projectId: f.control.projectId, areaId: f.areaId, uid: 1000, inspectProcess: () => live });
|
||||
const live = { uid: 1000, startTicks: 'fixture-start', state: 'S' };
|
||||
assert.equal(read(live).state, 'active');
|
||||
for (const mismatch of [{ ...live, uid: 2000 }, { ...live, startTicks: 'reused-pid' }, { ...live, state: 'Z' }]) {
|
||||
assert.equal(read(mismatch).state, 'unknown');
|
||||
}
|
||||
const absent = observeRuntimeTurn({ stateRoot: f.control.stateRoot, repositoryRoot: f.root,
|
||||
projectId: f.control.projectId, areaId: f.areaId, inspectProcess: () => { throw new Error('Process gone'); } });
|
||||
assert.equal(absent.state, 'unknown');
|
||||
writeFileSync(path, JSON.stringify({ ...original, projectId: 'foreign' }));
|
||||
assert.equal(read(live).reason, 'guard-invalid');
|
||||
writeFileSync(path, JSON.stringify(original));
|
||||
});
|
||||
} finally { f.control.close(); }
|
||||
});
|
||||
|
||||
test('SCN-runtime-live-presence-legacy: pre-observation guards require exact TORCH identity and project cwd', () => {
|
||||
const f = fixture();
|
||||
try {
|
||||
withRuntimeTurnGuard(f.control, f.areaId, () => {
|
||||
const path = join(f.control.stateRoot, 'sessions', 'turn-guards', f.areaId, 'owner.json');
|
||||
const original = JSON.parse(readFileSync(path, 'utf8'));
|
||||
const legacy = { ...original };
|
||||
delete legacy.startTicks;
|
||||
delete legacy.phase;
|
||||
writeFileSync(path, JSON.stringify(legacy));
|
||||
const inspect = live => observeRuntimeTurn({ stateRoot: f.control.stateRoot, repositoryRoot: f.root,
|
||||
projectId: f.control.projectId, areaId: f.areaId, uid: 1000, inspectProcess: () => live });
|
||||
const live = { uid: 1000, state: 'S', cwd: f.root, argv: ['node', '/runtime/bin/torch.mjs', 'up', '--only', f.areaId] };
|
||||
assert.equal(inspect(live).phase, 'working');
|
||||
assert.equal(inspect({ ...live, cwd: '/foreign' }).state, 'unknown');
|
||||
assert.equal(inspect({ ...live, argv: ['node', 'torch.mjs', 'up', '--only', 'someone-else'] }).state, 'unknown');
|
||||
writeFileSync(path, JSON.stringify(original));
|
||||
});
|
||||
} finally { f.control.close(); }
|
||||
});
|
||||
|
||||
test('SCN-runtime-continuation: explicit enable, event deduplication, daily cap and pause govern authorized turns', async () => {
|
||||
const f = fixture();
|
||||
const backlog = { list: () => [] };
|
||||
const at = new Date('2026-10-03T00:00:00Z');
|
||||
try {
|
||||
f.control.reportStatus({ areaId: f.areaId, state: 'idle', summary: 'Physical turn completed' });
|
||||
f.control.sendOwnerRequest({ actorId: 'owner', recipient: f.areaId, body: 'Continue assigned work' });
|
||||
assert.equal(planContinuation(f.control, backlog, { at }).reason, 'paused');
|
||||
assert.throws(() => configureContinuation(f.control, { actorId: f.areaId, enabled: true }), { code: 'OWNER_AUTHORITY_REQUIRED' });
|
||||
configureContinuation(f.control, { actorId: 'owner', enabled: true, maxTurnsPerDay: 1 });
|
||||
assert.equal(planContinuation(f.control, backlog, { at }).candidates.length, 1);
|
||||
let calls = 0;
|
||||
const run = () => runContinuation(f.control, backlog, { actorId: 'owner', at, launch: async area => { assert.equal(area, f.areaId); calls++; return 0; } });
|
||||
assert.equal((await run()).launched.length, 1);
|
||||
assert.equal((await run()).launched.length, 0);
|
||||
f.control.sendOwnerRequest({ actorId: 'owner', recipient: f.areaId, body: 'New work' });
|
||||
assert.equal(planContinuation(f.control, backlog, { at }).reason, 'daily-turn-cap');
|
||||
configureContinuation(f.control, { actorId: 'owner', enabled: true, maxTurnsPerDay: 2 });
|
||||
assert.equal(planContinuation(f.control, backlog, { at }).candidates.length, 1);
|
||||
configureContinuation(f.control, { actorId: 'owner', enabled: false, maxTurnsPerDay: 2 });
|
||||
assert.equal((await run()).launched.length, 0);
|
||||
assert.equal(calls, 1);
|
||||
} finally { f.control.close(); }
|
||||
});
|
||||
|
||||
test('SCN-runtime-continuation-day-override: owner extra allowance preserves charges and expires at UTC reset', async () => {
|
||||
const f = fixture();
|
||||
const backlog = { list: () => [] };
|
||||
const at = new Date('2026-10-03T00:00:00Z');
|
||||
try {
|
||||
f.control.reportStatus({ areaId: f.areaId, state: 'idle', summary: 'Ready' });
|
||||
configureContinuation(f.control, { actorId: 'owner', enabled: true, maxTurnsPerDay: 1 });
|
||||
f.control.sendOwnerRequest({ actorId: 'owner', recipient: f.areaId, body: 'First work' });
|
||||
const run = () => runContinuation(f.control, backlog, { actorId: 'owner', at, launch: async () => 0 });
|
||||
assert.equal((await run()).launched.length, 1);
|
||||
assert.equal(planContinuation(f.control, backlog, { at }).reason, 'daily-turn-cap');
|
||||
assert.throws(() => configureContinuation(f.control, { actorId: f.areaId, enabled: true, todayMaxTurns: 3 }), { code: 'OWNER_AUTHORITY_REQUIRED' });
|
||||
for (const todayMaxTurns of [0, 97, 1.5]) assert.throws(() => configureContinuation(f.control,
|
||||
{ actorId: 'owner', enabled: true, todayMaxTurns }), { code: 'CONTINUATION_POLICY_INVALID' });
|
||||
configureContinuation(f.control, { actorId: 'owner', enabled: true, todayMaxTurns: 3 });
|
||||
assert.equal(planContinuation(f.control, backlog, { at }).attempts, 1, 'Previously charged turn is retained');
|
||||
assert.equal(planContinuation(f.control, backlog, { at }).remainingTurns, 2);
|
||||
f.control.sendOwnerRequest({ actorId: 'owner', recipient: f.areaId, body: 'Additional work' });
|
||||
assert.equal((await run()).launched.length, 1);
|
||||
configureContinuation(f.control, { actorId: 'owner', enabled: false });
|
||||
const sameDay = observeContinuation({ stateRoot: f.control.stateRoot, now: () => at });
|
||||
assert.equal(sameDay.attempts, 2);
|
||||
assert.equal(sameDay.maxTurnsPerDay, 3);
|
||||
assert.equal(sameDay.stopReason, 'paused');
|
||||
const nextDay = observeContinuation({ stateRoot: f.control.stateRoot, now: () => new Date('2026-10-04T00:00:00Z') });
|
||||
assert.equal(nextDay.maxTurnsPerDay, 1, 'Override cannot widen future daily allowances');
|
||||
assert.equal(nextDay.attempts, 0);
|
||||
assert.ok(f.control.readAudit({ limit: 100 }).some(a => a.operation === 'runtime.continuation.configure'
|
||||
&& a.details?.dailyOverride?.maxTurns === 3));
|
||||
} finally { f.control.close(); }
|
||||
});
|
||||
|
||||
test('SCN-runtime-continuation-concurrency: seven authorized starts preserve budget and obey tighter limits', async () => {
|
||||
const f = fixture({ workerCount: 5 });
|
||||
const backlog = { list: () => [] };
|
||||
const at = new Date('2026-10-03T00:00:00Z');
|
||||
try {
|
||||
for (const agent of f.control.listAgents()) {
|
||||
f.control.reportStatus({ areaId: agent.areaId, state: 'idle', summary: 'Ready' });
|
||||
f.control.sendOwnerRequest({ actorId: 'owner', recipient: agent.areaId, body: 'Review work' });
|
||||
}
|
||||
configureContinuation(f.control, { actorId: 'owner', enabled: true, maxTurnsPerDay: 48 });
|
||||
assert.equal(planContinuation(f.control, backlog, { at }).maxConcurrency, 3);
|
||||
assert.equal(planContinuation(f.control, backlog, { at, limit: 7 }).candidates.length, 3, 'Tick cannot exceed approved capacity');
|
||||
configureContinuation(f.control, { actorId: 'owner', enabled: true, maxConcurrency: 7 });
|
||||
assert.equal(observeContinuation({ stateRoot: f.control.stateRoot }).maxTurnsPerDay, 48);
|
||||
assert.equal(planContinuation(f.control, backlog, { at, limit: 2 }).candidates.length, 2);
|
||||
for (const value of [0, 8, 1.5]) assert.throws(() => configureContinuation(f.control,
|
||||
{ actorId: 'owner', enabled: true, maxConcurrency: value }), { code: 'CONTINUATION_POLICY_INVALID' });
|
||||
const completions = [];
|
||||
const running = runContinuation(f.control, backlog, { actorId: 'owner', at,
|
||||
launch: () => new Promise(resolve => completions.push(resolve)) });
|
||||
assert.equal(completions.length, 7, 'All seven start before any finishes');
|
||||
assert.equal(planContinuation(f.control, backlog, { at }).attempts, 7);
|
||||
configureContinuation(f.control, { actorId: 'owner', enabled: false });
|
||||
assert.equal(observeContinuation({ stateRoot: f.control.stateRoot, now: () => at }).attempts, 7);
|
||||
assert.equal(observeContinuation({ stateRoot: f.control.stateRoot }).maxConcurrency, 7);
|
||||
completions.forEach(resolve => resolve(0));
|
||||
assert.equal((await running).launched.length, 7);
|
||||
} finally { f.control.close(); }
|
||||
});
|
||||
|
||||
test('SCN-runtime-continuation-signals: acknowledgements do not wake; eligible turns start concurrently within the limit', async () => {
|
||||
const f = fixture();
|
||||
try {
|
||||
f.control.reportStatus({ areaId: f.areaId, state: 'idle', summary: 'Idle' });
|
||||
const messages = [];
|
||||
for (const recipient of [f.areaId, 'session-manager']) {
|
||||
messages.push(f.control.sendOwnerRequest({ actorId: 'owner', recipient, body: 'Review current work' }));
|
||||
f.control.sendOwnerRequest({ actorId: 'owner', recipient, body: 'Second handoff' });
|
||||
}
|
||||
configureContinuation(f.control, { actorId: 'owner', enabled: true, maxTurnsPerDay: 4 });
|
||||
const completions = [];
|
||||
const running = runContinuation(f.control, { list: () => [] }, { actorId: 'owner', limit: 2,
|
||||
launch: () => new Promise(resolve => completions.push(resolve)) });
|
||||
assert.equal(completions.length, 2, 'Both starts occur without waiting for the first provider to finish');
|
||||
assert.equal((await runContinuation(f.control, { list: () => [] }, { actorId: 'owner', launch: () => 0 })).reason,
|
||||
'controller-active-or-unreconciled');
|
||||
completions.forEach(resolve => resolve(0));
|
||||
assert.equal((await running).launched.length, 2);
|
||||
for (const message of messages) f.control.ackMessage({ recipient: message.recipient, messageId: message.id });
|
||||
assert.equal(planContinuation(f.control, { list: () => [] }).candidates.length, 0);
|
||||
} finally { f.control.close(); }
|
||||
});
|
||||
|
||||
test('SCN-runtime-continuation-pause: a pause during one turn prevents another queued turn', async () => {
|
||||
const f = fixture();
|
||||
try {
|
||||
f.control.reportStatus({ areaId: f.areaId, state: 'idle', summary: 'Idle' });
|
||||
for (const recipient of [f.areaId, 'session-manager']) f.control.sendOwnerRequest({ actorId: 'owner', recipient, body: 'Review current work' });
|
||||
configureContinuation(f.control, { actorId: 'owner', enabled: true, maxTurnsPerDay: 3 });
|
||||
const result = await runContinuation(f.control, { list: () => [] }, { actorId: 'owner',
|
||||
launch: async () => { configureContinuation(f.control, { actorId: 'owner', enabled: false, maxTurnsPerDay: 3 }); return 0; } });
|
||||
assert.equal(result.launched.length, 1);
|
||||
assert.equal(planContinuation(f.control, { list: () => [] }).reason, 'paused');
|
||||
} finally { f.control.close(); }
|
||||
});
|
||||
|
||||
test('SCN-runtime-continuation-refill: a short turn frees capacity while the manager remains running', async () => {
|
||||
const f = fixture({ extraWorker: true });
|
||||
try {
|
||||
f.control.reportStatus({ areaId: f.areaId, state: 'idle', summary: 'Idle' });
|
||||
for (const recipient of ['session-manager', f.areaId, 'extra-worker']) {
|
||||
f.control.sendOwnerRequest({ actorId: 'owner', recipient, body: 'Eligible handoff' });
|
||||
}
|
||||
configureContinuation(f.control, { actorId: 'owner', enabled: true, maxTurnsPerDay: 3 });
|
||||
const starts = [];
|
||||
const finishes = new Map();
|
||||
let thirdStarted;
|
||||
const third = new Promise(resolve => { thirdStarted = resolve; });
|
||||
const run = runContinuation(f.control, { list: () => [] }, { actorId: 'owner', limit: 2,
|
||||
launch: area => { starts.push(area); if (starts.length === 3) thirdStarted();
|
||||
return new Promise(resolve => finishes.set(area, resolve)); } });
|
||||
assert.deepEqual(starts, ['session-manager', f.areaId]);
|
||||
finishes.get(f.areaId)(0);
|
||||
await third;
|
||||
assert.deepEqual(starts, ['session-manager', f.areaId, 'extra-worker']);
|
||||
assert.equal(finishes.has('session-manager'), true, 'Manager completion was not required for refill');
|
||||
finishes.get('extra-worker')(0);
|
||||
finishes.get('session-manager')(0);
|
||||
assert.equal((await run).launched.length, 3);
|
||||
assert.equal(planContinuation(f.control, { list: () => [] }).reason, 'daily-turn-cap');
|
||||
} finally { f.control.close(); }
|
||||
});
|
||||
|
||||
test('SCN-runtime-continuation-no-progress: unfinished assignment continues once then escalates rather than busy-looping', async () => {
|
||||
const f = fixture();
|
||||
try {
|
||||
f.control.reportStatus({ areaId: f.areaId, state: 'idle', summary: 'Idle' });
|
||||
configureContinuation(f.control, { actorId: 'owner', enabled: true, maxTurnsPerDay: 8 });
|
||||
const task = { id: 'TASK-continue', owner: f.areaId, state: 'assigned', revision: 1, dependencies: [] };
|
||||
let specialistTurns = 0;
|
||||
const result = await runContinuation(f.control, { list: () => [task] }, { actorId: 'owner', limit: 1,
|
||||
launch: area => { if (area === f.areaId) specialistTurns++; return 0; } });
|
||||
assert.equal(specialistTurns, 2);
|
||||
assert.equal(result.launched.filter(t => t.areaId === f.areaId).length, 2);
|
||||
const messages = f.control.readMessages({ recipient: 'session-manager', unacknowledgedOnly: true, limit: 1000 });
|
||||
assert.equal(messages.filter(m => m.body.includes('two turns without advancing')).length, 1);
|
||||
assert.equal(planContinuation(f.control, { list: () => [task] }).candidates.some(c => c.areaId === f.areaId), false);
|
||||
task.revision++;
|
||||
assert.equal(planContinuation(f.control, { list: () => [task] }).candidates.some(c => c.areaId === f.areaId), true);
|
||||
task.dependencies = ['TASK-unfinished'];
|
||||
assert.equal(planContinuation(f.control, { list: () => [task] }).candidates.some(c => c.areaId === f.areaId), false);
|
||||
} finally { f.control.close(); }
|
||||
});
|
||||
|
||||
test('SCN-runtime-continuation-capacity: a manually held executor counts against automatic fleet capacity', async () => {
|
||||
const f = fixture();
|
||||
try {
|
||||
f.control.reportStatus({ areaId: f.areaId, state: 'idle', summary: 'Idle' });
|
||||
f.control.sendOwnerRequest({ actorId: 'owner', recipient: f.areaId, body: 'Eligible handoff' });
|
||||
configureContinuation(f.control, { actorId: 'owner', enabled: true });
|
||||
let running;
|
||||
withRuntimeTurnGuard(f.control, 'session-manager', () => {
|
||||
running = runContinuation(f.control, { list: () => [] }, { actorId: 'owner', limit: 1,
|
||||
launch: () => { throw new Error('Capacity must not be exceeded'); } });
|
||||
});
|
||||
assert.deepEqual((await running).launched, []);
|
||||
assert.equal(planContinuation(f.control, { list: () => [] }).attempts, 0);
|
||||
} finally { f.control.close(); }
|
||||
});
|
||||
|
||||
test('SCN-runtime-continuation-fairness: a fresh task revision does not let one specialist starve an unstarted peer', async () => {
|
||||
const f = fixture({ extraWorker: true });
|
||||
try {
|
||||
f.control.reportStatus({ areaId: f.areaId, state: 'idle', summary: 'Idle' });
|
||||
configureContinuation(f.control, { actorId: 'owner', enabled: true, maxTurnsPerDay: 3 });
|
||||
const task = { id: 'TASK-changing', owner: f.areaId, state: 'assigned', revision: 1, dependencies: [] };
|
||||
f.control.sendOwnerRequest({ actorId: 'owner', recipient: 'extra-worker', body: 'Peer work' });
|
||||
const starts = [];
|
||||
await runContinuation(f.control, { list: () => [task] }, { actorId: 'owner', limit: 1,
|
||||
launch: area => { starts.push(area); if (area === f.areaId) task.revision++; return 0; } });
|
||||
assert.deepEqual(starts.slice(0, 2), [f.areaId, 'extra-worker']);
|
||||
} finally { f.control.close(); }
|
||||
});
|
||||
|
||||
test('SCN-runtime-continuation-health: budget stops, day reset, pause and unreadable ledgers are visible without mutation', async () => {
|
||||
const f = fixture({ recoveryWorktree: false });
|
||||
const at = new Date('2026-10-03T00:00:00Z');
|
||||
try {
|
||||
f.control.reportStatus({ areaId: f.areaId, state: 'idle', summary: 'Idle' });
|
||||
f.control.sendOwnerRequest({ actorId: 'owner', recipient: f.areaId, body: 'Work' });
|
||||
configureContinuation(f.control, { actorId: 'owner', enabled: true, maxTurnsPerDay: 1 });
|
||||
await runContinuation(f.control, { list: () => [] }, { actorId: 'owner', at, launch: () => 0 });
|
||||
const path = join(f.control.stateRoot, 'continuation.json');
|
||||
const before = readFileSync(path, 'utf8');
|
||||
const health = observeContinuation({ stateRoot: f.control.stateRoot, now: () => at });
|
||||
assert.equal(health.stopReason, 'daily-turn-cap');
|
||||
assert.equal(health.remainingTurns, 0);
|
||||
assert.equal(health.manualTurnsIncluded, false);
|
||||
assert.equal(health.timerInstallationVerified, false);
|
||||
const snapshot = observeProject({ repositoryRoot: f.root, env: f.env, now: () => at });
|
||||
assert.equal(snapshot.continuation.stopReason, 'daily-turn-cap');
|
||||
assert.equal(readFileSync(path, 'utf8'), before);
|
||||
assert.equal(observeContinuation({ stateRoot: f.control.stateRoot, now: () => new Date('2026-10-04T00:00:00Z') }).remainingTurns, 1);
|
||||
configureContinuation(f.control, { actorId: 'owner', enabled: false, maxTurnsPerDay: 1 });
|
||||
assert.equal(observeContinuation({ stateRoot: f.control.stateRoot, now: () => at }).stopReason, 'paused');
|
||||
writeFileSync(path, 'not-json');
|
||||
assert.equal(observeContinuation({ stateRoot: f.control.stateRoot, now: () => at }).stopReason, 'policy-or-ledger-unreadable');
|
||||
assert.equal(readFileSync(path, 'utf8'), 'not-json', 'Observation must not repair or reset the attempt ledger');
|
||||
} finally { f.control.close(); }
|
||||
});
|
||||
|
||||
test('SCN-legacy-runtime-recovery-project: evidence cannot be applied to a different checkout with cloned project/session fields', () => {
|
||||
const first = fixture();
|
||||
const second = fixture();
|
||||
try {
|
||||
assert.throws(() => second.control.recoverOwnerStoppedUnknownIdentity({ actorId: 'owner', areaId: second.areaId,
|
||||
expectedIdentity: second.identity, evidence: first.issue().evidence }), { code: 'RUNTIME_RECOVERY_UNVERIFIED' });
|
||||
assert.equal(second.control.getAgent(second.areaId).state, 'working');
|
||||
} finally { first.control.close(); second.control.close(); }
|
||||
});
|
||||
@@ -14,6 +14,7 @@ import { installProject } from '../../src/kernel/install.mjs';
|
||||
import { defaultManagerCheckInSchedule, organizationGraphFromConfig } from '../../src/kernel/organization.mjs';
|
||||
import { createWorktrees } from '../../src/kernel/worktrees.mjs';
|
||||
import { ResourceService } from '../../src/resources/service.mjs';
|
||||
import { createTorchToolset } from '../../src/mcp/tools.mjs';
|
||||
import {
|
||||
createFleetBrief, detachFleet, planAreaUp, planFleetDetach, planFleetDown, planFleetUp, startFleet, stopFleet,
|
||||
} from '../../src/runtime/lifecycle.mjs';
|
||||
@@ -83,6 +84,43 @@ function fleetFixture({ workerRuntime = 'claude', hierarchy = false } = {}) {
|
||||
return { root, env, worker: proposal.domains[0].id, middleManager: hierarchy ? 'runtime-lead' : null };
|
||||
}
|
||||
|
||||
test('SCN-fleet-startup-inbox: fresh and resumed launch instructions retrieve newer unread handoffs beyond the default history page', () => {
|
||||
const fixture = fleetFixture();
|
||||
const control = openControlPlane({ repositoryRoot: fixture.root, env: fixture.env });
|
||||
const adapters = new Map([['claude', createClaudeAdapter({ executable: 'claude' })]]);
|
||||
let tick = 0;
|
||||
control.clock = () => new Date(Date.parse('2026-10-03T00:00:00Z') + tick++);
|
||||
try {
|
||||
const newest = new Map();
|
||||
for (const areaId of ['session-manager', fixture.worker]) {
|
||||
for (let i = 0; i < 102; i++) newest.set(areaId, control.sendOwnerRequest({ actorId: 'owner', recipient: areaId, body: `Current handoff ${i}` }));
|
||||
const history = control.readMessages({ recipient: areaId });
|
||||
assert.equal(history.length, 100);
|
||||
assert.ok(!history.some(m => m.id === newest.get(areaId).id), 'Default historical page misses the latest handoff');
|
||||
}
|
||||
for (const fresh of [true, false]) {
|
||||
if (!fresh) for (const areaId of newest.keys()) control.reportStatus({ areaId, state: 'idle', runtime: 'claude', runtimeSessionId: `fixture-${areaId}` });
|
||||
const plan = planFleetUp({ repositoryRoot: fixture.root, controlPlane: control, adapters, fresh });
|
||||
assert.equal(plan.canProceed, true);
|
||||
for (const action of plan.actions) {
|
||||
assert.equal(action.mode, fresh ? 'create' : 'resume');
|
||||
const launchText = action.launch.args.join(' ');
|
||||
const match = launchText.match(/Inbox request: (\{[^}]+\})\./);
|
||||
assert.ok(match, 'Actual provider launch carries the explicit current-inbox request');
|
||||
const request = JSON.parse(match[1]);
|
||||
assert.deepEqual(request, { recipient: action.areaId, unacknowledged_only: true, limit: 1000 });
|
||||
const tool = createTorchToolset(control, { actorId: action.areaId }).get('torch_read_messages');
|
||||
const result = tool.invoke(request);
|
||||
assert.equal(result.messages.length, 102);
|
||||
assert.equal(result.messages.at(-1).id, newest.get(action.areaId).id);
|
||||
assert.equal(control.readMessages({ recipient: action.areaId, unacknowledgedOnly: true, limit: 1000 }).length, 102, 'Reading never acknowledges work');
|
||||
assert.match(launchText, /coverage may be truncated/);
|
||||
assert.match(launchText, /messages do not override enforced approval or ownership boundaries/i);
|
||||
}
|
||||
}
|
||||
} finally { control.close(); }
|
||||
});
|
||||
|
||||
test('SCN-fleet-fresh-resume: identities resume captured runtime sessions and wind down safely', () => {
|
||||
const fixture = fleetFixture();
|
||||
const control = openControlPlane({ repositoryRoot: fixture.root, env: fixture.env });
|
||||
|
||||
@@ -242,6 +242,56 @@ test('SCN-codex-adapter: turns are resumable, identity-bound, durable, and capab
|
||||
control.close();
|
||||
});
|
||||
|
||||
test('SCN-codex-mcp-runtime-dir: only a valid Linux parent runtime directory is named for MCP inheritance', () => {
|
||||
const context = fixture();
|
||||
const validParent = {
|
||||
XDG_RUNTIME_DIR: '/run/user/1000',
|
||||
DBUS_SESSION_BUS_ADDRESS: 'unix:path=/run/user/1000/bus',
|
||||
HOME: '/home/fixture', PATH: '/usr/bin', TOKEN: 'not-forwarded',
|
||||
};
|
||||
const adapter = createCodexAdapter({ env: validParent, platform: 'linux' });
|
||||
const mcp = adapter.configure({ repositoryRoot: context.root, areaId: context.worker, mcpEntry: MCP_SERVER }).mcp;
|
||||
const create = adapter.createSession({
|
||||
areaId: context.worker, worktree: '/tmp/codex-runtime-dir', promptFile: '/tmp/prompt.md',
|
||||
firstMessage: 'Start.', mcp,
|
||||
});
|
||||
const resume = adapter.resumeSession({
|
||||
areaId: context.worker, runtimeSessionId: 'thread-runtime-dir', worktree: '/tmp/codex-runtime-dir',
|
||||
message: 'Resume.', mcp,
|
||||
});
|
||||
const expected = `mcp_servers.${mcp.name}.env_vars=["XDG_RUNTIME_DIR"]`;
|
||||
for (const launch of [create, resume]) {
|
||||
assert.equal(launch.launch.args.includes(expected), true);
|
||||
assert.equal(launch.launch.args.some((arg) => /env_vars=/.test(arg) && arg !== expected), false);
|
||||
assert.equal(launch.launch.args.some((arg) => arg.includes(validParent.XDG_RUNTIME_DIR)
|
||||
|| arg.includes(validParent.DBUS_SESSION_BUS_ADDRESS) || arg.includes(validParent.HOME)
|
||||
|| arg.includes(validParent.PATH) || arg.includes(validParent.TOKEN)), false);
|
||||
}
|
||||
|
||||
for (const invalid of [
|
||||
{ env: {}, platform: 'linux' },
|
||||
{ env: { XDG_RUNTIME_DIR: '' }, platform: 'linux' },
|
||||
{ env: { XDG_RUNTIME_DIR: 'relative/runtime' }, platform: 'linux' },
|
||||
{ env: validParent, platform: 'darwin' },
|
||||
]) {
|
||||
const unsupported = createCodexAdapter(invalid);
|
||||
const unsupportedMcp = unsupported.configure({
|
||||
repositoryRoot: context.root, areaId: context.worker, mcpEntry: MCP_SERVER,
|
||||
}).mcp;
|
||||
const createPlan = unsupported.createSession({
|
||||
areaId: context.worker, worktree: '/tmp/codex-runtime-dir', promptFile: '/tmp/prompt.md',
|
||||
firstMessage: 'Start.', mcp: unsupportedMcp,
|
||||
});
|
||||
const resumePlan = unsupported.resumeSession({
|
||||
areaId: context.worker, runtimeSessionId: 'thread-runtime-dir', worktree: '/tmp/codex-runtime-dir',
|
||||
message: 'Resume.', mcp: unsupportedMcp,
|
||||
});
|
||||
for (const plan of [createPlan, resumePlan]) {
|
||||
assert.equal(plan.launch.args.some((arg) => /env_vars=/.test(arg)), false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('SCN-mcp-stdio: an MCP host can initialize and list the TORCH tool surface over stdio', async () => {
|
||||
const context = fixture();
|
||||
const child = spawn(process.execPath, [MCP_SERVER, '--root', context.root, '--area', context.worker], {
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { execFileSync, spawn } from 'node:child_process';
|
||||
import { mkdtempSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import test from 'node:test';
|
||||
import { openControlPlane } from '../../src/control-plane/service.mjs';
|
||||
import { analyzeRepository } from '../../src/kernel/analyze.mjs';
|
||||
import { proposeDomains } from '../../src/kernel/domains.mjs';
|
||||
import { inspectRepository } from '../../src/kernel/git.mjs';
|
||||
import { installProject } from '../../src/kernel/install.mjs';
|
||||
|
||||
const MCP_SERVER = new URL('../../src/mcp/server.mjs', import.meta.url).pathname;
|
||||
|
||||
function installedFixture() {
|
||||
const root = mkdtempSync(join(tmpdir(), 'torch-mcp-owner-reply-'));
|
||||
execFileSync('git', ['init', '-b', 'main', root]);
|
||||
execFileSync('git', ['-C', root, 'config', 'user.email', '[email protected]']);
|
||||
execFileSync('git', ['-C', root, 'config', 'user.name', 'TORCH Test']);
|
||||
writeFileSync(join(root, 'index.js'), 'export const fixture = true;\n');
|
||||
execFileSync('git', ['-C', root, 'add', '.']);
|
||||
execFileSync('git', ['-C', root, 'commit', '-m', 'fixture']);
|
||||
const repository = inspectRepository(root);
|
||||
const proposal = proposeDomains({ repository, analysis: analyzeRepository(repository) });
|
||||
proposal.review = { status: 'approved', reviewedAt: '2026-10-03T00:00:00Z', reviewedBy: 'fixture-owner', notes: [] };
|
||||
const env = { ...process.env, XDG_DATA_HOME: join(root, '.data') };
|
||||
installProject({ repository, proposal, env, projectId: 'mcp-owner-reply-fixture' });
|
||||
const control = openControlPlane({ repositoryRoot: root, env });
|
||||
const identities = control.listAgents().map((agent) => agent.areaId);
|
||||
control.close();
|
||||
const worker = identities.find((areaId) => areaId !== 'qa' && areaId !== 'session-manager')
|
||||
?? identities.find((areaId) => areaId !== 'qa')
|
||||
?? identities[0];
|
||||
const other = identities.find((areaId) => areaId !== worker);
|
||||
assert.ok(other, 'fixture requires a second registered Fleet identity');
|
||||
return { root, env, worker, other };
|
||||
}
|
||||
|
||||
async function connectMcp({ root, env, actorId }) {
|
||||
const child = spawn(process.execPath, [MCP_SERVER, '--root', root, '--area', actorId], {
|
||||
cwd: root, env, stdio: ['pipe', 'pipe', 'pipe'],
|
||||
});
|
||||
const responses = new Map();
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
child.stderr.on('data', (chunk) => { stderr += chunk; });
|
||||
child.stdout.on('data', (chunk) => {
|
||||
stdout += chunk;
|
||||
const lines = stdout.split('\n');
|
||||
stdout = lines.pop();
|
||||
for (const line of lines.filter(Boolean)) {
|
||||
const message = JSON.parse(line);
|
||||
if (message.id !== undefined) responses.get(message.id)?.(message);
|
||||
}
|
||||
});
|
||||
let nextId = 0;
|
||||
const request = (method, params) => new Promise((resolve, reject) => {
|
||||
const id = ++nextId;
|
||||
const timeout = setTimeout(() => reject(new Error(`MCP response timeout. stderr: ${stderr}`)), 5000);
|
||||
responses.set(id, (response) => { clearTimeout(timeout); resolve(response); });
|
||||
child.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', id, method, params })}\n`);
|
||||
});
|
||||
const initialized = await request('initialize', {
|
||||
protocolVersion: '2025-06-18', capabilities: {}, clientInfo: { name: 'torch-test', version: '1.0.0' },
|
||||
});
|
||||
assert.equal(initialized.result.serverInfo.name, 'torch-agent-fleet');
|
||||
child.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', method: 'notifications/initialized' })}\n`);
|
||||
return {
|
||||
request,
|
||||
async close() {
|
||||
child.stdin.end();
|
||||
await new Promise((resolve) => {
|
||||
const timeout = setTimeout(() => { child.kill('SIGTERM'); resolve(); }, 1000);
|
||||
child.once('exit', () => { clearTimeout(timeout); resolve(); });
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test('SCN-mcp-owner-reply-public-boundary: a bound MCP identity can reply only to its own owner request', async () => {
|
||||
const fixture = installedFixture();
|
||||
let control = openControlPlane({ repositoryRoot: fixture.root, env: fixture.env });
|
||||
const request = control.sendOwnerRequest({ actorId: 'owner', recipient: fixture.worker, body: 'Report the durable outcome.' });
|
||||
control.close();
|
||||
|
||||
const workerMcp = await connectMcp({ ...fixture, actorId: fixture.worker });
|
||||
try {
|
||||
const listed = await workerMcp.request('tools/list', {});
|
||||
const replyTool = listed.result.tools.find((tool) => tool.name === 'torch_reply_to_owner');
|
||||
assert.ok(replyTool);
|
||||
assert.deepEqual(Object.keys(replyTool.inputSchema.properties).sort(), ['body', 'request_id']);
|
||||
assert.equal(replyTool.inputSchema.additionalProperties, false);
|
||||
assert.equal(listed.result.tools.some((tool) => /owner.*conversation|history/i.test(tool.name)), false);
|
||||
|
||||
const replied = await workerMcp.request('tools/call', {
|
||||
name: 'torch_reply_to_owner', arguments: { request_id: request.id, body: 'The durable outcome is ready.' },
|
||||
});
|
||||
assert.equal(replied.result.isError, undefined);
|
||||
const reply = JSON.parse(replied.result.content[0].text);
|
||||
|
||||
control = openControlPlane({ repositoryRoot: fixture.root, env: fixture.env });
|
||||
const conversation = control.readOwnerConversation({ actorId: 'owner', requestId: request.id, limit: 10 });
|
||||
assert.deepEqual(conversation.replies.map((message) => message.id), [reply.id]);
|
||||
assert.equal(conversation.replies[0].references.replyTo, request.id);
|
||||
const audit = control.readAudit().find((event) => event.operation === 'message.reply-owner' && event.entityId === reply.id);
|
||||
assert.deepEqual(audit && { actorId: audit.actorId, details: audit.details }, { actorId: fixture.worker, details: { requestId: request.id } });
|
||||
const before = { audit: control.readAudit().length, replies: conversation.replies.length };
|
||||
control.close();
|
||||
|
||||
const schemaRefusal = await workerMcp.request('tools/call', {
|
||||
name: 'torch_reply_to_owner',
|
||||
arguments: { request_id: request.id, body: 'Attempt to select a sender.', sender: fixture.other },
|
||||
});
|
||||
assert.equal(schemaRefusal.result.isError, true);
|
||||
control = openControlPlane({ repositoryRoot: fixture.root, env: fixture.env });
|
||||
assert.equal(control.readAudit().length, before.audit);
|
||||
assert.equal(control.readOwnerConversation({ actorId: 'owner', requestId: request.id, limit: 10 }).replies.length, before.replies);
|
||||
control.close();
|
||||
} finally {
|
||||
await workerMcp.close();
|
||||
}
|
||||
|
||||
control = openControlPlane({ repositoryRoot: fixture.root, env: fixture.env });
|
||||
const beforeWrongActor = {
|
||||
audit: control.readAudit().length,
|
||||
replies: control.readOwnerConversation({ actorId: 'owner', requestId: request.id, limit: 10 }).replies.length,
|
||||
};
|
||||
control.close();
|
||||
|
||||
const otherMcp = await connectMcp({ ...fixture, actorId: fixture.other });
|
||||
try {
|
||||
const wrongActor = await otherMcp.request('tools/call', {
|
||||
name: 'torch_reply_to_owner', arguments: { request_id: request.id, body: 'A valid identity cannot reply for another agent.' },
|
||||
});
|
||||
assert.equal(wrongActor.result.isError, true);
|
||||
assert.match(wrongActor.result.content[0].text, /owner request addressed to the sender/i);
|
||||
} finally {
|
||||
await otherMcp.close();
|
||||
}
|
||||
|
||||
control = openControlPlane({ repositoryRoot: fixture.root, env: fixture.env });
|
||||
assert.equal(control.readAudit().length, beforeWrongActor.audit);
|
||||
assert.equal(control.readOwnerConversation({ actorId: 'owner', requestId: request.id, limit: 10 }).replies.length, beforeWrongActor.replies);
|
||||
control.close();
|
||||
});
|
||||
@@ -0,0 +1,91 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { mkdtempSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import test from 'node:test';
|
||||
import { openControlPlane } from '../../src/control-plane/service.mjs';
|
||||
import { analyzeRepository } from '../../src/kernel/analyze.mjs';
|
||||
import { proposeDomains } from '../../src/kernel/domains.mjs';
|
||||
import { inspectRepository } from '../../src/kernel/git.mjs';
|
||||
import { installProject } from '../../src/kernel/install.mjs';
|
||||
|
||||
function installedFixture({ projectId = 'owner-reply-fixture', sequencePrefix = 'main' } = {}) {
|
||||
const root = mkdtempSync(join(tmpdir(), 'torch-owner-reply-'));
|
||||
execFileSync('git', ['init', '-b', 'main', root]);
|
||||
execFileSync('git', ['-C', root, 'config', 'user.email', '[email protected]']);
|
||||
execFileSync('git', ['-C', root, 'config', 'user.name', 'TORCH Test']);
|
||||
writeFileSync(join(root, 'index.js'), 'export const fixture = true;\n');
|
||||
execFileSync('git', ['-C', root, 'add', '.']);
|
||||
execFileSync('git', ['-C', root, 'commit', '-m', 'fixture']);
|
||||
const repository = inspectRepository(root);
|
||||
const proposal = proposeDomains({ repository, analysis: analyzeRepository(repository) });
|
||||
proposal.review = { status: 'approved', reviewedAt: '2026-10-03T00:00:00Z', reviewedBy: 'fixture-owner', notes: [] };
|
||||
const env = { ...process.env, XDG_DATA_HOME: join(root, '.data') };
|
||||
installProject({ repository, proposal, env, projectId });
|
||||
let sequence = 0;
|
||||
let seconds = 0;
|
||||
return {
|
||||
root, env,
|
||||
worker: proposal.domains.find((domain) => domain.id !== 'qa')?.id ?? proposal.domains[0].id,
|
||||
options: {
|
||||
repositoryRoot: root, env,
|
||||
idFactory: () => `${sequencePrefix}-${++sequence}`,
|
||||
clock: () => new Date(Date.UTC(2026, 9, 3, 12, 0, seconds++)),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test('SCN-owner-addressed-reply-durability: a registered agent replies to its owner request with durable attribution', () => {
|
||||
const fixture = installedFixture();
|
||||
let control = openControlPlane(fixture.options);
|
||||
const request = control.sendOwnerRequest({ actorId: 'owner', recipient: fixture.worker, body: 'Report the persisted outcome.' });
|
||||
const first = control.sendOwnerReply({ sender: fixture.worker, requestId: request.id, body: 'The durable outcome is ready.' });
|
||||
const second = control.sendOwnerReply({ sender: fixture.worker, requestId: request.id, body: 'The evidence remains bounded.' });
|
||||
control.close();
|
||||
|
||||
control = openControlPlane(fixture.options);
|
||||
const firstPage = control.readOwnerConversation({ actorId: 'owner', requestId: request.id, limit: 1 });
|
||||
assert.equal(firstPage.request.id, request.id);
|
||||
assert.deepEqual(firstPage.replies.map((message) => message.id), [first.id]);
|
||||
assert.equal(firstPage.replies[0].recipient, 'owner');
|
||||
assert.equal(firstPage.replies[0].references.replyTo, request.id);
|
||||
assert.equal(firstPage.page.truncated, true);
|
||||
const secondPage = control.readOwnerConversation({ actorId: 'owner', requestId: request.id, cursor: firstPage.page.nextCursor, limit: 1 });
|
||||
assert.deepEqual(secondPage.replies.map((message) => message.id), [second.id]);
|
||||
assert.equal(secondPage.page.complete, true);
|
||||
assert.equal(secondPage.page.nextCursor, null);
|
||||
const audit = control.readAudit().find((event) => event.operation === 'message.reply-owner' && event.entityId === first.id);
|
||||
assert.deepEqual(audit && { actorId: audit.actorId, details: audit.details }, { actorId: fixture.worker, details: { requestId: request.id } });
|
||||
control.close();
|
||||
});
|
||||
|
||||
test('SCN-owner-addressed-reply-refusal: forged owner recipients, senders, and references leave durable state untouched', () => {
|
||||
const fixture = installedFixture();
|
||||
const foreign = installedFixture({ projectId: 'foreign-owner-reply-fixture', sequencePrefix: 'foreign' });
|
||||
const control = openControlPlane(fixture.options);
|
||||
const request = control.sendOwnerRequest({ actorId: 'owner', recipient: fixture.worker, body: 'Respond only from your registered identity.' });
|
||||
const foreignControl = openControlPlane(foreign.options);
|
||||
const foreignRequest = foreignControl.sendOwnerRequest({ actorId: 'owner', recipient: foreign.worker, body: 'Foreign request.' });
|
||||
foreignControl.close();
|
||||
const beforeAudit = control.readAudit().length;
|
||||
const beforeMessages = control.readMessages({ recipient: fixture.worker }).length;
|
||||
const beforeConversation = control.readOwnerConversation({ actorId: 'owner', requestId: request.id, limit: 10 });
|
||||
assert.deepEqual(beforeConversation.replies, []);
|
||||
|
||||
assert.throws(() => control.sendMessage({ sender: fixture.worker, recipient: 'owner', body: 'Bypass the reply boundary.' }), (error) => error.code === 'UNKNOWN_FLEET_IDENTITY');
|
||||
assert.throws(() => control.sendOwnerReply({ sender: 'owner', requestId: request.id, body: 'Spoofed sender.' }), (error) => error.code === 'UNKNOWN_FLEET_IDENTITY');
|
||||
assert.throws(() => control.sendOwnerReply({ sender: 'session-manager', requestId: request.id, body: 'Valid identity, wrong addressee.' }), (error) => error.code === 'OWNER_REPLY_REFERENCE_INVALID');
|
||||
assert.throws(() => control.sendOwnerReply({ sender: fixture.worker, requestId: foreignRequest.id, body: 'Foreign reference.' }), (error) => error.code === 'OWNER_REPLY_REFERENCE_INVALID');
|
||||
assert.throws(() => control.sendOwnerReply({ sender: fixture.worker, requestId: 'forged-message-id', body: 'Forged reference.' }), (error) => error.code === 'OWNER_REPLY_REFERENCE_INVALID');
|
||||
assert.throws(() => control.readOwnerConversation({ actorId: fixture.worker, requestId: request.id, limit: 1 }), (error) => error.code === 'OWNER_AUTHORITY_REQUIRED');
|
||||
assert.equal(control.readAudit().length, beforeAudit);
|
||||
assert.equal(control.readMessages({ recipient: fixture.worker }).length, beforeMessages);
|
||||
const afterConversation = control.readOwnerConversation({ actorId: 'owner', requestId: request.id, limit: 10 });
|
||||
assert.deepEqual(afterConversation.replies, []);
|
||||
assert.deepEqual(afterConversation.page, beforeConversation.page);
|
||||
const direct = control.sendMessage({ sender: 'session-manager', recipient: fixture.worker, body: 'Existing direct delivery.' });
|
||||
const broadcast = control.sendMessage({ sender: fixture.worker, recipient: 'all', body: 'Existing broadcast delivery.' });
|
||||
assert.deepEqual(control.readMessages({ recipient: fixture.worker }).slice(-2).map((message) => message.id), [direct.id, broadcast.id]);
|
||||
control.close();
|
||||
});
|
||||
@@ -0,0 +1,163 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { chmodSync, existsSync, mkdtempSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { basename, join } from 'node:path';
|
||||
import { DatabaseSync } from 'node:sqlite';
|
||||
import test from 'node:test';
|
||||
import { openControlPlane, openControlPlaneReadOnly } from '../../src/control-plane/service.mjs';
|
||||
import { analyzeRepository } from '../../src/kernel/analyze.mjs';
|
||||
import { proposeDomains } from '../../src/kernel/domains.mjs';
|
||||
import { inspectRepository } from '../../src/kernel/git.mjs';
|
||||
import { installProject } from '../../src/kernel/install.mjs';
|
||||
import { projectStatePath } from '../../src/kernel/paths.mjs';
|
||||
import { setupProject } from '../../src/kernel/setup.mjs';
|
||||
|
||||
const now = () => new Date('2026-10-03T10:00:00Z');
|
||||
|
||||
function fixture() {
|
||||
const root = mkdtempSync(join(tmpdir(), 'torch-read-only-control-plane-'));
|
||||
const git = (args) => execFileSync('git', ['-C', root, ...args], {
|
||||
env: { ...process.env, GIT_AUTHOR_DATE: '2026-10-03T10:00:00Z', GIT_COMMITTER_DATE: '2026-10-03T10:00:00Z' },
|
||||
});
|
||||
git(['init', '-b', 'main']);
|
||||
git(['config', 'user.email', '[email protected]']);
|
||||
git(['config', 'user.name', 'TORCH Test']);
|
||||
writeFileSync(join(root, 'app.js'), 'export const value = 1;\n');
|
||||
git(['add', '.']);
|
||||
git(['commit', '-m', 'fixture']);
|
||||
const repository = inspectRepository(root);
|
||||
const proposal = proposeDomains({ repository, analysis: analyzeRepository(repository) });
|
||||
proposal.review = { status: 'approved', reviewedAt: now().toISOString(), reviewedBy: 'owner', notes: [] };
|
||||
const env = { ...process.env, XDG_DATA_HOME: mkdtempSync(join(tmpdir(), 'torch-read-only-state-')) };
|
||||
const projectId = 'read-only-fixture';
|
||||
installProject({ repository, proposal, env, projectId });
|
||||
setupProject({ repository: inspectRepository(root), env, parentOverride: join(tmpdir(), `${basename(root)}-trees`), authorized: true });
|
||||
const writer = openControlPlane({ repositoryRoot: root, env, clock: now });
|
||||
const worker = proposal.domains[0].id;
|
||||
writer.reportStatus({ areaId: worker, state: 'idle', summary: 'registered v2 snapshot' });
|
||||
writer.close();
|
||||
const stateRoot = projectStatePath(projectId, env);
|
||||
const stable = new DatabaseSync(join(stateRoot, 'state.db'));
|
||||
stable.exec('PRAGMA journal_mode = DELETE');
|
||||
stable.close();
|
||||
return { root, env, worker, stateRoot };
|
||||
}
|
||||
|
||||
function sidecars(stateRoot) {
|
||||
return readdirSync(stateRoot).filter((name) => name === 'state.db-wal' || name === 'state.db-shm').sort();
|
||||
}
|
||||
|
||||
test('SCN-read-only-v2-control-plane: a write-refusing state directory yields a stable v2 snapshot and rejects mutation', (t) => {
|
||||
const context = fixture();
|
||||
const statePath = join(context.stateRoot, 'state.db');
|
||||
const before = readFileSync(statePath);
|
||||
const beforeSidecars = sidecars(context.stateRoot);
|
||||
assert.deepEqual(beforeSidecars, [], 'fixture must prove sidecar-free reader behavior');
|
||||
chmodSync(statePath, 0o444);
|
||||
chmodSync(context.stateRoot, 0o555);
|
||||
t.after(() => {
|
||||
chmodSync(context.stateRoot, 0o755);
|
||||
chmodSync(statePath, 0o644);
|
||||
});
|
||||
const reader = openControlPlaneReadOnly({ repositoryRoot: context.root, env: context.env, clock: now });
|
||||
assert.equal(reader.identity(context.worker).summary, 'registered v2 snapshot');
|
||||
assert.deepEqual(reader.observeMessages({ recipient: context.worker, selection: 'unread', limit: 1 }), {
|
||||
recipient: context.worker,
|
||||
messages: [],
|
||||
observation: { selection: 'unread', requestedLimit: 1, returnedCount: 0, complete: true, truncated: false, pendingUnreadCount: 0 },
|
||||
});
|
||||
assert.throws(
|
||||
() => reader.reportStatus({ areaId: context.worker, state: 'working', summary: 'must not persist' }),
|
||||
(error) => error.code === 'CONTROL_PLANE_READ_ONLY',
|
||||
);
|
||||
reader.close();
|
||||
assert.deepEqual(readFileSync(statePath), before, 'the database bytes remain unchanged');
|
||||
assert.deepEqual(sidecars(context.stateRoot), beforeSidecars, 'the reader creates no WAL or SHM sidecar');
|
||||
});
|
||||
|
||||
test('SCN-read-only-v2-control-plane-race: a concurrent v2 writer produces one coherent snapshot or a typed refusal', () => {
|
||||
const context = fixture();
|
||||
const statePath = join(context.stateRoot, 'state.db');
|
||||
const wal = new DatabaseSync(statePath);
|
||||
wal.exec('PRAGMA journal_mode = WAL');
|
||||
wal.close();
|
||||
const writer = new DatabaseSync(statePath);
|
||||
writer.exec('BEGIN IMMEDIATE');
|
||||
writer.prepare('UPDATE identities SET summary = ?, updated_at = ? WHERE area_id = ?')
|
||||
.run('writer committed after reader snapshot', '2026-10-03T10:01:00Z', context.worker);
|
||||
let reader;
|
||||
let refusal = null;
|
||||
try { reader = openControlPlaneReadOnly({ repositoryRoot: context.root, env: context.env, clock: now }); } catch (error) { refusal = error; }
|
||||
writer.exec('COMMIT');
|
||||
writer.close();
|
||||
if (refusal) {
|
||||
assert.equal(refusal.code, 'CONTROL_PLANE_READ_ONLY_UNAVAILABLE');
|
||||
return;
|
||||
}
|
||||
const observed = reader.identity(context.worker).summary;
|
||||
assert.ok(observed === 'registered v2 snapshot' || observed === 'writer committed after reader snapshot', `reader must expose one whole SQLite snapshot, received ${observed}`);
|
||||
reader.close();
|
||||
});
|
||||
|
||||
test('SCN-read-only-pinned-correlated-snapshot: one reader cannot combine generations across an atomic writer commit', () => {
|
||||
const context = fixture();
|
||||
const statePath = join(context.stateRoot, 'state.db');
|
||||
const generationA = '2026-10-03T10:00:00.000Z';
|
||||
const generationB = '2026-10-03T10:01:00.000Z';
|
||||
const correlated = [context.worker, 'session-manager'];
|
||||
const seed = new DatabaseSync(statePath);
|
||||
seed.exec('PRAGMA journal_mode = WAL');
|
||||
const setGeneration = seed.prepare('UPDATE identities SET summary = ?, updated_at = ? WHERE area_id = ?');
|
||||
for (const areaId of correlated) setGeneration.run('generation A', generationA, areaId);
|
||||
seed.close();
|
||||
|
||||
const reader = openControlPlaneReadOnly({ repositoryRoot: context.root, env: context.env, clock: now });
|
||||
const first = reader.identity(context.worker);
|
||||
assert.deepEqual({ summary: first.summary, updatedAt: first.updatedAt }, { summary: 'generation A', updatedAt: generationA });
|
||||
const writer = new DatabaseSync(statePath);
|
||||
writer.exec('BEGIN IMMEDIATE');
|
||||
const update = writer.prepare('UPDATE identities SET summary = ?, updated_at = ? WHERE area_id = ?');
|
||||
for (const areaId of correlated) update.run('generation B', generationB, areaId);
|
||||
writer.exec('COMMIT');
|
||||
writer.close();
|
||||
let second;
|
||||
try { second = reader.identity('session-manager'); } catch (error) { assert.equal(error.code, 'CONTROL_PLANE_READ_ONLY_UNAVAILABLE'); }
|
||||
if (second) {
|
||||
assert.deepEqual({ summary: second.summary, updatedAt: second.updatedAt }, { summary: 'generation A', updatedAt: generationA }, 'the same reader must not combine generations');
|
||||
}
|
||||
reader.close();
|
||||
const fresh = openControlPlaneReadOnly({ repositoryRoot: context.root, env: context.env, clock: now });
|
||||
assert.deepEqual(
|
||||
Object.fromEntries(correlated.map((areaId) => {
|
||||
const identity = fresh.identity(areaId);
|
||||
return [areaId, { summary: identity.summary, updatedAt: identity.updatedAt }];
|
||||
})),
|
||||
Object.fromEntries(correlated.map((areaId) => [areaId, { summary: 'generation B', updatedAt: generationB }])),
|
||||
);
|
||||
fresh.close();
|
||||
});
|
||||
|
||||
test('SCN-read-only-v2-control-plane-schema: unknown schema revisions are refused instead of guessed', () => {
|
||||
const context = fixture();
|
||||
const statePath = join(context.stateRoot, 'state.db');
|
||||
const writer = new DatabaseSync(statePath);
|
||||
writer.exec('PRAGMA user_version = 3');
|
||||
writer.close();
|
||||
assert.ok(existsSync(statePath));
|
||||
assert.throws(
|
||||
() => openControlPlaneReadOnly({ repositoryRoot: context.root, env: context.env }),
|
||||
(error) => error.code === 'CONTROL_PLANE_READ_ONLY_UNAVAILABLE',
|
||||
);
|
||||
});
|
||||
|
||||
test('SCN-read-only-v2-control-plane-input-drift: tracked identity inputs changing after snapshot make reads unavailable', () => {
|
||||
const context = fixture();
|
||||
const rosterPath = join(context.root, '.torch', 'roster.yaml');
|
||||
const original = readFileSync(rosterPath, 'utf8');
|
||||
const reader = openControlPlaneReadOnly({ repositoryRoot: context.root, env: context.env });
|
||||
writeFileSync(rosterPath, `${original}\n`);
|
||||
assert.throws(() => reader.identity(context.worker), (error) => error.code === 'CONTROL_PLANE_READ_ONLY_UNAVAILABLE');
|
||||
writeFileSync(rosterPath, original);
|
||||
reader.close();
|
||||
});
|
||||
Reference in new issue
Block a user