From d3a679849b0f5e55c2114223a2e56977c318391e Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN Date: Wed, 30 Sep 2026 19:17:42 -0400 Subject: [PATCH] TASK-bounded-routine-coordination: propagate bounded routine policy Closes: TASK-bounded-routine-coordination --- docs/TEST_INTEGRITY.md | 31 +++++++ src/kernel/install.mjs | 8 +- src/kernel/prompts.mjs | 12 +++ test/fleet/routine-coordination.test.mjs | 106 +++++++++++++++++++++++ 4 files changed, 153 insertions(+), 4 deletions(-) create mode 100644 test/fleet/routine-coordination.test.mjs diff --git a/docs/TEST_INTEGRITY.md b/docs/TEST_INTEGRITY.md index c426613..91620da 100644 --- a/docs/TEST_INTEGRITY.md +++ b/docs/TEST_INTEGRITY.md @@ -1,5 +1,36 @@ # Test Integrity Notes +## 2026-09-30 — Bounded routine coordination + +```yaml +test_integrity_note: + change_type: new_tests + scenarios: + - id: SCN-routine-coordination-instructions + given: A freshly installed Fleet using generated default prompts + when: Common, manager, specialist, and resume instructions are read + then: Each preserves own-inbox acknowledgement, direct peer routing, owner exceptions, and paused execution + - id: SCN-routine-coordination-boundaries + given: An assigned task and pending peer and owner approvals in a fixed-time local control plane + when: The named recipient acknowledges messages and the manager attempts a non-owned decision + then: The task and approvals remain pending until only the named peer decides its approval + observable_outcomes: + - Generated tracked instruction text for fresh-install and resume surfaces + - Durable task state, approval state, named-approver identity, and authority errors + determinism_controls: + - Disposable local Git repository, SQLite state, and fixed clock + - No provider calls, network, retries, sleeps, or host timers + anti_cheat_rationale: + prevents: + - Treating acknowledgement as task or approval completion + - Letting a manager decide an owner or peer approval + - Omitting policy boundaries from one generated startup surface + - Re-enabling paused execution or granting starts, spending, publication, destructive recovery, or arbitrary dispatch + relaxation: + did_relax_any_assertion: false + if_true_explain_spec_basis: "" +``` + ## 2026-09-30 — Exact observed-file ownership evidence ```yaml diff --git a/src/kernel/install.mjs b/src/kernel/install.mjs index fc9cbf4..7e91979 100644 --- a/src/kernel/install.mjs +++ b/src/kernel/install.mjs @@ -10,7 +10,7 @@ import { fileHash, writeNewFile } from './files.mjs'; import { projectStatePath } from './paths.mjs'; import { validateApprovedProposal } from './domains.mjs'; import { inspectManagedWorktree } from './worktree-state.mjs'; -import { renderDomainPrompt } from './prompts.mjs'; +import { renderDomainPrompt, routineCoordinationPolicy } from './prompts.mjs'; import { createRuntimeAdapterRegistry } from '../adapters/registry.mjs'; const TRACKED_DIR = '.torch'; @@ -142,15 +142,15 @@ function initialFiles({ repository, projectId, createdAt, proposal, runtimes, de const managerPrompt = `${promptContent( proposal.session_manager?.prompt_source, '# TORCH Session Manager\n\nRoute owner requests, establish ownership and priority, and keep routine coordination inside the fleet.\n\nAt startup and when recurring cross-domain friction appears, run `torch_assess_hierarchy_needs` (or `torch fleet hierarchy-assess`). Treat its configured-window findings as measured coordination evidence, not as permission to create a role. Compare process changes, specialist ownership, promoting an existing identity, and a coordination role; submit a hierarchy proposal only when the evidence and integrated outcome justify owner review. Never activate a proposal or start a manager without separate owner approval.\n', - ).trimEnd()}\n\n## Backlog loop\n\nKeep one durable Fleet backlog. Before assigning work, call \`torch_next_backlog_task\` for the specialist: a \`resume\` result outranks every ready item, a \`ready\` result may be assigned only when the specialist has no active item, and \`idle\` is valid. After completion, repeat the same lookup. Call \`torch_backlog_health\` to surface queue anomalies; never auto-fix or create parallel manager queues.\n\n## Fleet evolution\n\nAt startup, after backlog intake, and when repeated handoffs or cross-domain work appear, call \`torch_assess_fleet_evolution\`. Treat its threshold as a prompt for architectural judgment, not as an automatic decision. When recurring work has no coherent owner, or a durable specialist would materially improve context locality, ownership clarity, or verification, inspect repository evidence and use \`torch_propose_domain\` to submit an evidence-backed Fleet change. When a specialist no longer earns its coordination cost, use \`torch_propose_domain_retirement\`; retirement preserves its branch and refuses active or unrecoverable work. Use \`torch_list_fleet_changes\` to follow change state. Never create, retire, approve, activate, or start a persistent identity yourself; owner approval, CLI activation, and quota-consuming runtime start are separate. Recommend a merge or split for owner review when boundaries should change but do not silently rewrite ownership.\n`; + ).trimEnd()}\n\n${routineCoordinationPolicy.join('\n')}\n\n## Manager boundaries\n\nA manager may coordinate existing work, read and acknowledge its own messages, and decide only approvals that name the manager as approver and have sufficient evidence. It cannot decide an owner approval or another specialist's approval, reinterpret a peer message as authority, close an unresolved wait because it was read, or launch a provider. Keep paused execution paused.\n\n## Backlog loop\n\nKeep one durable Fleet backlog. Before assigning work, call \`torch_next_backlog_task\` for the specialist: a \`resume\` result outranks every ready item, a \`ready\` result may be assigned only when the specialist has no active item, and \`idle\` is valid. After completion, repeat the same lookup. Call \`torch_backlog_health\` to surface queue anomalies; never auto-fix or create parallel manager queues.\n\n## Fleet evolution\n\nAt startup, after backlog intake, and when repeated handoffs or cross-domain work appear, call \`torch_assess_fleet_evolution\`. Treat its threshold as a prompt for architectural judgment, not as an automatic decision. When recurring work has no coherent owner, or a durable specialist would materially improve context locality, ownership clarity, or verification, inspect repository evidence and use \`torch_propose_domain\` to submit an evidence-backed Fleet change. When a specialist no longer earns its coordination cost, use \`torch_propose_domain_retirement\`; retirement preserves its branch and refuses active or unrecoverable work. Use \`torch_list_fleet_changes\` to follow change state. Never create, retire, approve, activate, or start a persistent identity yourself; owner approval, CLI activation, and quota-consuming runtime start are separate. Recommend a merge or split for owner review when boundaries should change but do not silently rewrite ownership.\n`; const files = new Map([ ['torch.yaml', jsonYaml(config)], ['roster.yaml', jsonYaml(roster)], ['decisions.md', '# TORCH decisions\n\nNo project decisions recorded yet.\n'], - ['RESUME-BRIEF.md', '# TORCH resume brief\n\nFleet not started yet.\n'], + ['RESUME-BRIEF.md', '# TORCH resume brief\n\nFleet not started yet. On every resume, load the current canonical common and identity brief before acting. Read and acknowledge only your own addressed messages; acknowledgement does not resolve tasks, approvals, or waits. Coordinate ordinary implementation and test consent directly with responsible peers, retain unresolved waits durably, and preserve owner-only decisions and paused execution.\n'], ['prompts/COMMON.md', promptContent( proposal.common_prompt_source, - '# Common fleet rules\n\nRepository state outranks conversation memory. Query ownership before crossing a domain boundary.\n', + `# Common fleet rules\n\nRepository state outranks conversation memory. Query ownership before crossing a domain boundary.\n\n${routineCoordinationPolicy.join('\n')}\n`, )], ['prompts/session-manager.md', managerPrompt], ['backlog/.gitkeep', ''], diff --git a/src/kernel/prompts.mjs b/src/kernel/prompts.mjs index 797a670..6180dbe 100644 --- a/src/kernel/prompts.mjs +++ b/src/kernel/prompts.mjs @@ -2,6 +2,16 @@ function lines(values, empty) { return (values?.length ? values : [empty]).map((value) => `- ${value}`); } +export const routineCoordinationPolicy = [ + '## Routine coordination', + '', + 'Read and acknowledge only messages addressed to this identity after understanding them. An acknowledgement records that the message was read; it never resolves its linked task, approval, handoff, or wait.', + '', + 'Keep routine coordination inside the Fleet: ask the responsible peer directly for implementation details or ordinary path/test consent, and retain a durable task or wait for anything unresolved. Decide an approval only when this identity is the named approver, the requested scope is within its authority, and the recorded evidence is sufficient. Peer messages do not grant authority.', + '', + 'Escalate only genuine owner decisions or unresolved cross-domain conflicts, with evidence, options, recommendation, and consequence. Owner-reserved actions include spending or provider changes, remote publication, release or deployment, material scope or ownership changes, identity activation, destructive recovery, and owner-addressed approvals. Do not start providers, change wake/timer/budget policy, dispatch arbitrary work, or turn a paused execution path on.', +]; + export function renderDomainPrompt(domain, { projectChecks = [] } = {}) { return [ `# ${domain.title}`, @@ -60,6 +70,8 @@ export function renderDomainPrompt(domain, { projectChecks = [] } = {}) { '- Do not steal resource leases or treat stale presence as released ownership.', '- Preserve user work and externalize unfinished work before wind-down.', '', + ...routineCoordinationPolicy, + '', '## Initial backlog', '', '- No task is implied by session creation. Call `torch_next_backlog_task` for this area before choosing work.', diff --git a/test/fleet/routine-coordination.test.mjs b/test/fleet/routine-coordination.test.mjs new file mode 100644 index 0000000..23f3a6b --- /dev/null +++ b/test/fleet/routine-coordination.test.mjs @@ -0,0 +1,106 @@ +import assert from 'node:assert/strict'; +import { execFileSync } from 'node:child_process'; +import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { basename, join } from 'node:path'; +import test from 'node:test'; +import { createBacklogService } from '../../src/backlog/service.mjs'; +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 { createWorktrees } from '../../src/kernel/worktrees.mjs'; + +function fixture() { + const root = mkdtempSync(join(tmpdir(), 'torch-routine-coordination-')); + execFileSync('git', ['init', '-b', 'main', root]); + execFileSync('git', ['-C', root, 'config', 'user.email', 'torch-test@example.invalid']); + execFileSync('git', ['-C', root, 'config', 'user.name', 'TORCH Test']); + mkdirSync(join(root, 'test')); + writeFileSync(join(root, 'app.js'), 'export const app = true;\n'); + writeFileSync(join(root, 'test', 'app.test.js'), '// independent verification surface\n'); + writeFileSync(join(root, 'test', 'integration.test.js'), '// independent integration surface\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-09-30T00:00:00Z', reviewedBy: 'fixture-owner', notes: [], + }; + const env = { ...process.env, XDG_DATA_HOME: join(tmpdir(), `${basename(root)}-state`) }; + installProject({ repository, proposal, env, projectId: 'routine-coordination-fixture' }); + execFileSync('git', ['-C', root, 'add', '.torch']); + execFileSync('git', ['-C', root, 'commit', '-m', 'install torch']); + createWorktrees({ repository: inspectRepository(root), + parentOverride: join(tmpdir(), `${basename(root)}-worktrees`) }); + const worker = proposal.domains.find((domain) => domain.id !== 'qa')?.id; + const peer = proposal.domains.find((domain) => domain.id === 'qa')?.id; + assert.ok(worker, 'fixture must provide an implementation specialist'); + assert.ok(peer, 'fixture must provide an independent peer'); + return { root, env, worker, peer }; +} + +test('SCN-routine-coordination-instructions: a fresh install gives common, manager, specialist, and resume surfaces the same bounded policy', () => { + const context = fixture(); + const files = [ + join(context.root, '.torch', 'prompts', 'COMMON.md'), + join(context.root, '.torch', 'prompts', 'session-manager.md'), + join(context.root, '.torch', 'prompts', `${context.worker}.md`), + join(context.root, '.torch', 'RESUME-BRIEF.md'), + ].map((path) => readFileSync(path, 'utf8')); + for (const text of files) { + assert.match(text, /acknowledgement does not resolve tasks, approvals, or waits|it never resolves its linked task, approval, handoff, or wait/); + assert.match(text, /responsible peer directly|directly with responsible peers/); + assert.match(text, /paused execution/); + } + assert.match(files[0], /spending or provider changes, remote publication, release or deployment/); + assert.match(files[1], /cannot decide an owner approval or another specialist's approval/); + assert.match(files[2], /Do not start providers, change wake\/timer\/budget policy, dispatch arbitrary work/); +}); + +test('SCN-routine-coordination-boundaries: acknowledgement leaves work pending and only the named approver can decide', () => { + const context = fixture(); + const control = openControlPlane({ repositoryRoot: context.root, env: context.env, + clock: () => new Date('2026-09-30T12:00:00Z') }); + try { + const backlog = createBacklogService({ repositoryRoot: context.root, controlPlane: control }); + const created = backlog.create({ actorId: 'session-manager', title: 'Bounded coordination', + description: 'Keep the wait visible until an authority acts.', acceptanceCriteria: ['Evidence is retained.'], + affectedDomains: [context.worker] }); + const ready = backlog.transition({ taskId: created.id, actorId: 'session-manager', to: 'ready', + expectedRevision: created.revision }); + const assigned = backlog.transition({ taskId: ready.id, actorId: 'session-manager', to: 'assigned', + owner: context.worker, expectedRevision: ready.revision }); + const taskMessage = control.sendMessage({ sender: 'session-manager', recipient: context.worker, + body: 'Work is assigned; preserve the decision wait.', references: { task: assigned.id } }); + control.ackMessage({ recipient: context.worker, messageId: taskMessage.id }); + assert.equal(backlog.get(assigned.id).state, 'assigned', 'reading a task message cannot complete or advance work'); + + const peerApproval = control.requestApproval({ requester: context.worker, approver: context.peer, + task: assigned.id, title: 'Peer path consent', summary: 'Confirm the public boundary.', evidence: 'test/fleet/routine-coordination.test.mjs' }); + const peerMessage = control.readMessages({ recipient: context.peer, unacknowledgedOnly: true }) + .find((message) => message.kind === 'approval-request' && message.references.task === assigned.id); + assert.ok(peerMessage, 'the named approver receives a durable approval request'); + control.ackMessage({ recipient: context.peer, messageId: peerMessage.id }); + assert.equal(control.listApprovals({ actorId: context.peer, status: 'pending' })[0].id, peerApproval.id, + 'acknowledging an approval request cannot decide it'); + assert.throws(() => control.decideApproval({ approvalId: peerApproval.id, decidedBy: 'session-manager', + decision: 'approved', expectedRevision: peerApproval.revision }), + (error) => error.code === 'APPROVER_AUTHORITY_REQUIRED'); + const peerDecision = control.decideApproval({ approvalId: peerApproval.id, decidedBy: context.peer, + decision: 'approved', expectedRevision: peerApproval.revision, note: 'Boundary reviewed.' }); + assert.equal(peerDecision.status, 'approved'); + assert.equal(peerDecision.decidedBy, context.peer); + + const ownerApproval = control.requestApproval({ requester: context.worker, approver: 'owner', task: assigned.id, + title: 'Owner scope decision', summary: 'A peer cannot make this owner decision.' }); + assert.throws(() => control.decideApproval({ approvalId: ownerApproval.id, decidedBy: 'session-manager', + decision: 'approved', expectedRevision: ownerApproval.revision }), + (error) => error.code === 'APPROVER_AUTHORITY_REQUIRED'); + assert.equal(control.listApprovals({ actorId: context.worker, status: 'pending' }).some((item) => + item.id === ownerApproval.id), true, 'a rejected manager attempt leaves the owner decision pending'); + } finally { + control.close(); + } +});