From c11754ab18fa690355ead3f6f87b3accb2975153 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN Date: Wed, 30 Sep 2026 15:14:10 -0400 Subject: [PATCH 1/3] Fix canonical worktree briefs and add guided setup and safe reinstall --- .torch/prompts/COMMON.md | 5 +- README.md | 18 +++- docs/PORTABLE_AGENT_FLEET_SPEC.md | 12 +++ docs/TEST_INTEGRITY.md | 31 ++++++ docs/TODO.md | 4 +- package-lock.json | 4 +- package.json | 2 +- reports/audit/setup-reinstall-2026-09-30.md | 29 ++++++ src/cli.mjs | 23 ++++- src/kernel/project-root.mjs | 38 ++++++++ src/kernel/setup.mjs | 55 +++++++++++ test/fleet/setup.test.mjs | 101 ++++++++++++++++++++ torch-release.json | 2 +- 13 files changed, 311 insertions(+), 13 deletions(-) create mode 100644 reports/audit/setup-reinstall-2026-09-30.md create mode 100644 src/kernel/project-root.mjs create mode 100644 src/kernel/setup.mjs create mode 100644 test/fleet/setup.test.mjs diff --git a/.torch/prompts/COMMON.md b/.torch/prompts/COMMON.md index 886536b..3fb0de4 100644 --- a/.torch/prompts/COMMON.md +++ b/.torch/prompts/COMMON.md @@ -6,7 +6,10 @@ Repository state outranks conversation memory. Query ownership before crossing a This initial launch is a bounded installation and communications smoke test. Use the installed `torch` launcher and identity-bound TORCH MCP tools. Query -identity, ownership, inbox and next backlog item. Read current canonical +identity, ownership and inbox. Specialists query their next backlog item; +the Session Manager inspects fleet backlog rather than claiming specialist work. +Run `torch brief --area --json` at startup and before each new item. +Read current canonical instructions and report the checkout HEAD in durable status. Do not implement code, create or assign development tasks, enable timers, diff --git a/README.md b/README.md index 7c580df..ae8641f 100644 --- a/README.md +++ b/README.md @@ -292,14 +292,28 @@ Review the evidence in `fleet-proposal.json`, edit its `review.status` to node bin/torch.mjs install --proposal fleet-proposal.json --runtime codex --dry-run --json node bin/torch.mjs install --proposal fleet-proposal.json --runtime codex --yes --json node bin/torch.mjs doctor --json -node bin/torch.mjs worktrees --dry-run --json -node bin/torch.mjs worktrees --yes --json +node bin/torch.mjs setup --dry-run --json +node bin/torch.mjs setup --yes --json node bin/torch.mjs up --fresh --dry-run --json ``` Starting or stopping configured runtime sessions is always a separate explicit step: +`setup` commits only installation-owned configuration, provisions worktrees, +and checkpoints their registration. An existing staging area is a blocker; +unrelated files are never staged. Alternatively, append `--setup` to the +approved install command. Neither path launches agents. + +Normal uninstall retains configuration and history. Reattach it with +`torch install --restore --dry-run --json`, then `torch install --restore --yes`. +Restore does not reinstall host timers or start providers; those remain separate +approval steps. A purge is a different, destructive operation. + +From a registered agent worktree, `torch brief --area ` reads the latest +canonical project instructions. Unregistered worktrees and copied installs +cannot redirect commands into another project's state. + ```bash node bin/torch.mjs up --fresh --yes --json node bin/torch.mjs up --only backend,qa --dry-run --json diff --git a/docs/PORTABLE_AGENT_FLEET_SPEC.md b/docs/PORTABLE_AGENT_FLEET_SPEC.md index e8d325d..1279547 100644 --- a/docs/PORTABLE_AGENT_FLEET_SPEC.md +++ b/docs/PORTABLE_AGENT_FLEET_SPEC.md @@ -451,6 +451,18 @@ or installed persistent launchers. Historical messages and receipts therefore do not make reversal permanently impossible, while active or unowned state still blocks deletion. +Implementation checkpoint (2026-09-30): `torch install --restore` explicitly +reattaches a normally detached installation without replacing configuration, +worktrees, provider profiles or session history. It never launches a provider +or recreates host timers. `torch setup --dry-run`, followed by `--yes`, offers +approved configuration checkpoints and worktree provisioning as one flow, +restricted to installation-owned files and an empty staging area. Install may +opt into the same flow with `--setup`. Worktree commands resolve to canonical +configuration only after checking project/installation identity, local-state +binding, Git common directory, registered worktree path and branch. Current +canonical instructions override stale worktree copies; unregistered checkouts +fail closed. + Ordinary CLI uninstall is also review-first and explicitly confirmed. It uses the same safe Fleet wind-down contract as detach, removes only manifest-owned persistent schedule launchers after verifying their content, and preserves the diff --git a/docs/TEST_INTEGRITY.md b/docs/TEST_INTEGRITY.md index f10b4f2..e6e5832 100644 --- a/docs/TEST_INTEGRITY.md +++ b/docs/TEST_INTEGRITY.md @@ -2237,3 +2237,34 @@ test_integrity_note: did_relax_any_assertion: false if_true_explain_spec_basis: "" ``` +# Setup and canonical worktree briefs (2026-09-30) + +```yaml +test_integrity_note: + change_type: new_tests + scenarios: + - id: SCN-guided-setup + given: "An approved installed project with uncommitted TORCH files" + when: "Owner reviews and authorizes setup" + then: "Only owned files are committed; registered worktrees exist; no providers start; staged user work blocks setup" + - id: SCN-worktree-canonical-brief + given: "A registered worktree has older instructions" + when: "Its installation root is resolved and a brief requested" + then: "Canonical instructions appear; unregistered worktrees, copied installs and stale installation IDs fail closed" + - id: SCN-install-restore + given: "A normally detached project retains configuration and worktree commits" + when: "Owner explicitly restores the install" + then: "Binding is reattached without changing profiles, worktree history or launching providers" + observable_outcomes: + - "Git commits, clean canonical checkout, worktree registrations, brief text and local detach metadata" + determinism_controls: + - "Isolated temporary Git repositories and XDG state; no provider or network calls" + anti_cheat_rationale: + prevents: + - "Hard-coded success without Git worktrees or commits" + - "Accepting copied or unregistered installation state" + - "Staging unrelated owner work" + relaxation: + did_relax_any_assertion: false + if_true_explain_spec_basis: "" +``` diff --git a/docs/TODO.md b/docs/TODO.md index 7ec4258..a357380 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -37,7 +37,7 @@ updates, retained previous installs, offline previews and fail-closed errors. Four deterministic scenarios added; live download qualification recorded in the first-run audit, not implied by mocked installer tests. -- [ ] Fix `torch brief` invoked from a managed worktree: both live sessions hit +- [x] Fix `torch brief` invoked from a managed worktree: both live sessions hit `LOCAL_STATE_METADATA_MISMATCH`. Resolve canonical project binding with checked worktree membership, never weaken root/installation identity checks. - [ ] Surface Codex JSON stdout failure events: first Sol launch hid the actual @@ -45,7 +45,7 @@ - [ ] Add exact executable/version/model/auth preflight to owner Console and setup flow. PATH selected CLI 0.158.0 while the current session used 0.159.2; the older child rejected Sol 6.1 and required manual investigation/retry. -- [ ] Make configuration checkpoint/worktree/manifest handoffs explicit or +- [x] Make configuration checkpoint/worktree/manifest handoffs explicit or guided: generated tracked files and provisioning manifest updates require deliberate commits; a healthy doctor does not prove startup readiness. - [ ] Isolate irrelevant inherited global MCP servers without removing user diff --git a/package-lock.json b/package-lock.json index b4afe31..13acd7f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "torch-agent-fleet", - "version": "0.1.0-alpha.1", + "version": "0.1.0-alpha.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "torch-agent-fleet", - "version": "0.1.0-alpha.1", + "version": "0.1.0-alpha.2", "bundleDependencies": [ "@modelcontextprotocol/server", "typebox", diff --git a/package.json b/package.json index f2b1ad6..b3352f1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "torch-agent-fleet", - "version": "0.1.0-alpha.1", + "version": "0.1.0-alpha.2", "type": "module", "description": "TORCH — a portable, provider-independent AI development fleet for Git repositories", "main": "src/cli.mjs", diff --git a/reports/audit/setup-reinstall-2026-09-30.md b/reports/audit/setup-reinstall-2026-09-30.md new file mode 100644 index 0000000..eecd18a --- /dev/null +++ b/reports/audit/setup-reinstall-2026-09-30.md @@ -0,0 +1,29 @@ +# Guided setup and reinstall verification + +## Changes + +- `torch brief` and normal installed-project CLI commands resolve registered + worktrees to the canonical project. The resolver checks project and installation + IDs, XDG state binding, Git common directory, worktree membership and branch. + Current canonical instructions win over an old worktree copy. +- `torch setup --dry-run`, then `--yes`, checkpoints owned configuration, + provisions worktrees and checkpoints registration. Existing staged work is + rejected; unrelated files are not staged. Install can opt in with `--setup`. +- `torch install --restore` reattaches the retained state from normal uninstall. + It does not overwrite profiles, erase history, recreate timers or start agents. +- The self-host smoke brief distinguishes manager backlog inspection from + specialist next-task lookup and explicitly requires current briefs. + +## Evidence before installation + +All 39 source acceptance groups, lint and syntax: PASS. +Receipt: `/tmp/torch-alpha2-source-acceptance.json`. +New scenarios: guided setup, canonical worktree briefs, safe detach/restore. +The actual Owner Console worktree returned its current brief successfully +through the patched source CLI, including its preserved offline runtime ID. + +## Installation trial + +Pending accepted alpha.2 build and normal uninstall/restore verification. +This is a retained-state reinstall, not a purge or deletion of the tool's +historical versions. Provider launches remain deliberately disabled. diff --git a/src/cli.mjs b/src/cli.mjs index 361ea7d..8c8cc6b 100644 --- a/src/cli.mjs +++ b/src/cli.mjs @@ -24,6 +24,8 @@ import { diagnoseProject } from './kernel/doctor.mjs'; import { loadProjectConfig, validateProjectConfig } from './kernel/config.mjs'; import { asErrorRecord, TorchError } from './kernel/errors.mjs'; import { inspectRepository } from './kernel/git.mjs'; +import { resolveInstalledProjectRoot } from './kernel/project-root.mjs'; +import { setupProject, restoreProject } from './kernel/setup.mjs'; import { inspectSpecifications } from './kernel/specifications.mjs'; import { installProject, planInstall, uninstallProject } from './kernel/install.mjs'; import { IntegrationService } from './integration/service.mjs'; @@ -72,6 +74,8 @@ Usage: torch domains [--repo ] [--spec ] [--output ] [--json] torch install --proposal [--runtime ] [--default-runtime ] [--update-runtimes] [--dry-run] [--yes] [--json] torch worktrees [--parent ] [--dry-run] [--yes] [--json] + torch install --restore [--dry-run] [--yes] [--json] + torch setup [--parent ] [--dry-run] [--yes] [--json] torch up [--fresh] [--only ] [--dry-run] [--yes] [--json] torch down [--dry-run] [--yes] [--json] torch capture [--json] @@ -414,7 +418,7 @@ export const CANDIDATE_ACCEPTANCE_EVIDENCE = Object.freeze({ 'forge-migration': ['SCN-forge-migration'], 'delivery-lifecycle': ['SCN-delivery-lifecycle', 'SCN-delivery-attempts', 'SCN-delivery-retry-boundaries', 'SCN-delivery-succeeded-recovery', 'SCN-owner-digest', 'SCN-owner-digest-schedule', 'SCN-owner-digest-receipts'], - 'review-install-roster': ['SCN-cli-domain-review', 'SCN-cli-install-provider-default', 'SCN-install-provider-inheritance'], + 'review-install-roster': ['SCN-cli-domain-review', 'SCN-cli-install-provider-default', 'SCN-install-provider-inheritance', 'SCN-guided-setup'], 'branches-worktrees': ['SCN-worktree-bootstrap'], 'runtime-identities': ['SCN-mixed-runtime'], 'runtime-profiles': ['SCN-runtime-profiles', 'SCN-runtime-cost-ceiling', 'SCN-runtime-profile-cli'], @@ -471,7 +475,7 @@ export const CANDIDATE_ACCEPTANCE_EVIDENCE = Object.freeze({ 'capture-stop-resume': [ 'SCN-fleet-fresh-resume', 'SCN-hierarchy-aware-start-stop', 'SCN-hierarchy-startup-cycle', ], - 'detach-uninstall': ['SCN-install-doctor-purge'], + 'detach-uninstall': ['SCN-install-doctor-purge', 'SCN-install-restore', 'SCN-worktree-canonical-brief'], 'combatrig-compatibility': ['SCN-combatrig-import', 'SCN-cli-combatrig-import'], 'product-surface': ['SCN-product-site', 'SCN-dashboard-demo', 'SCN-console-readonly', 'SCN-console-lifecycle-blockers', 'SCN-console-owner-digest', 'SCN-demo-owner-digest', 'SCN-console-check-evidence', 'SCN-console-task-activity', 'SCN-console-operation-outcomes', 'SCN-console-live-refresh'], @@ -796,7 +800,9 @@ export async function runCli(argv = process.argv.slice(2), { } const designCommand = ['init', 'analyze', 'domains', 'design', 'bootstrap'].includes(command); const repositoryRoot = designCommand ? resolve(cwd, optionValue(argv, '--repo') ?? '.') : cwd; - const repository = inspectRepository(repositoryRoot); + const inspectedRepository = inspectRepository(repositoryRoot); + const repository = designCommand ? inspectedRepository + : inspectRepository(resolveInstalledProjectRoot(inspectedRepository.root, env)); if (command === 'profile') { const operation = argv[1] ?? 'show'; const areaId = optionValue(argv, '--area'); @@ -997,6 +1003,11 @@ export async function runCli(argv = process.argv.slice(2), { return 0; } if (command === 'install') { + if (argv.includes('--restore')) { + const result = restoreProject({ repository, env, dryRun: argv.includes('--dry-run'), authorized: argv.includes('--yes') }); + print(result, { json }); + return 0; + } const proposalPath = optionValue(argv, '--proposal'); const runtimeOption = optionValue(argv, '--runtime'); const defaultRuntime = optionValue(argv, '--default-runtime'); @@ -1038,7 +1049,11 @@ export async function runCli(argv = process.argv.slice(2), { } persistProfileConfig(repository.root, config); } - print(installed, { json }); + print(argv.includes('--setup') ? { ...installed, setup: setupProject({ repository: inspectRepository(repository.root), env, authorized: true, parentOverride: optionValue(argv, '--parent') }) } : installed, { json }); + return 0; + } + if (command === 'setup') { + print(setupProject({ repository, env, authorized: argv.includes('--yes'), dryRun: argv.includes('--dry-run'), parentOverride: optionValue(argv, '--parent') }), { json }); return 0; } if (command === 'doctor') { diff --git a/src/kernel/project-root.mjs b/src/kernel/project-root.mjs new file mode 100644 index 0000000..eeea65b --- /dev/null +++ b/src/kernel/project-root.mjs @@ -0,0 +1,38 @@ +import { execFileSync } from 'node:child_process'; +import { existsSync, readFileSync, realpathSync } from 'node:fs'; +import { join, resolve } from 'node:path'; +import { projectStatePath } from './paths.mjs'; +import { TorchError } from './errors.mjs'; + +const read = path => JSON.parse(readFileSync(path, 'utf8')); +const git = (root, args) => execFileSync('git', ['-C', root, ...args], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim(); + +// A worktree's copied configuration is not authoritative. Only registered, +// same-installation worktrees may resolve to the current canonical definition. +export function resolveInstalledProjectRoot(root, env = process.env) { + root = realpathSync(root); + const path = join(root, '.torch', 'install-manifest.json'); + if (!existsSync(path)) return root; + const manifest = read(path); + const stateRoot = projectStatePath(manifest.projectId, env); + const local = manifest.external?.find(entry => entry.type === 'local-state'); + if (!local || resolve(local.path) !== resolve(stateRoot)) { + throw new TorchError('Installation state path does not match this project', { code: 'LOCAL_STATE_PATH_MISMATCH' }); + } + const metadata = read(join(stateRoot, 'project.json')); + if (metadata.projectId !== manifest.projectId) { + throw new TorchError('Installation metadata belongs to another project', { code: 'LOCAL_STATE_METADATA_MISMATCH' }); + } + const canonical = realpathSync(metadata.root); + if (canonical === root) return root; + const current = read(join(canonical, '.torch', 'install-manifest.json')); + const registered = current.external?.find(entry => entry.type === 'worktree' && existsSync(entry.path) && realpathSync(entry.path) === root); + const common = directory => realpathSync(resolve(directory, git(directory, ['rev-parse', '--git-common-dir']))); + const members = git(canonical, ['worktree', 'list', '--porcelain']).split('\n').filter(line => line.startsWith('worktree ')).map(line => realpathSync(line.slice(9))); + if (current.projectId !== manifest.projectId || current.installationId !== manifest.installationId + || !registered || common(root) !== common(canonical) || !members.includes(root) + || git(root, ['branch', '--show-current']) !== registered.branch) { + throw new TorchError('This checkout is not a registered worktree of the installation', { code: 'UNREGISTERED_PROJECT_WORKTREE' }); + } + return canonical; +} diff --git a/src/kernel/setup.mjs b/src/kernel/setup.mjs new file mode 100644 index 0000000..b926a56 --- /dev/null +++ b/src/kernel/setup.mjs @@ -0,0 +1,55 @@ +import { execFileSync } from 'node:child_process'; +import { readFileSync, renameSync, writeFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { inspectRepository } from './git.mjs'; +import { readInstallManifest } from './install.mjs'; +import { projectStatePath } from './paths.mjs'; +import { resolveInstalledProjectRoot } from './project-root.mjs'; +import { createWorktrees, planWorktrees } from './worktrees.mjs'; +import { TorchError } from './errors.mjs'; + +const git = (root, args) => execFileSync('git', ['-C', root, ...args], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim(); + +export function setupProject({ repository, env = process.env, authorized = false, dryRun = false, parentOverride }) { + const root = resolveInstalledProjectRoot(repository.root, env); + repository = inspectRepository(root); + const manifest = readInstallManifest(root); + const owned = [...new Set([...manifest.created.map(record => record.path), '.torch/install-manifest.json'])]; + const changed = owned.filter(path => git(root, ['status', '--porcelain=v1', '--', path])); + const staged = git(root, ['diff', '--cached', '--name-only']); + if (staged) throw new TorchError('Setup requires an empty staging area; preserve and finish your existing staged work first', { code: 'SETUP_STAGED_WORK', details: staged }); + const unrelated = repository.dirtyEntries.filter(entry => !owned.includes(entry.slice(3))); + const worktrees = planWorktrees({ repository: { ...repository, dirtyEntries: unrelated }, parentOverride }); + const plan = { action: 'setup', projectId: manifest.projectId, checkpointPaths: changed, worktrees, canProceed: worktrees.canProceed, sessionsStarted: false, mutationPerformed: false }; + if (dryRun) return plan; + if (!authorized) throw new TorchError('Setup commits TORCH-owned configuration and provisions worktrees; review --dry-run then use --yes', { code: 'APPROVAL_REQUIRED' }); + if (!plan.canProceed) throw new TorchError('Setup has blockers', { code: 'SETUP_BLOCKED', details: worktrees.conflicts }); + const checkpoint = (paths, message) => { + const pending = paths.filter(path => git(root, ['status', '--porcelain=v1', '--', path])); + if (!pending.length) return null; + git(root, ['add', '--', ...pending]); + git(root, ['commit', '--only', '-m', message, '--', ...pending]); + return git(root, ['rev-parse', 'HEAD']); + }; + const configurationCommit = checkpoint(changed, 'Checkpoint approved TORCH configuration'); + const provisioned = createWorktrees({ repository: inspectRepository(root), parentOverride }); + const registrationCommit = checkpoint(['.torch/install-manifest.json'], 'Checkpoint TORCH worktree registration'); + return { ...plan, configurationCommit, registrationCommit, provisioned, mutationPerformed: Boolean(configurationCommit || registrationCommit || provisioned.mutationPerformed) }; +} + +export function restoreProject({ repository, env = process.env, authorized = false, dryRun = false }) { + const root = resolveInstalledProjectRoot(repository.root, env); + const manifest = readInstallManifest(root); + const path = join(projectStatePath(manifest.projectId, env), 'project.json'); + const metadata = JSON.parse(readFileSync(path, 'utf8')); + const result = { action: 'restore-installation', projectId: manifest.projectId, installationId: manifest.installationId, wasDetached: Boolean(metadata.detachedAt), preserved: ['configuration', 'worktrees', 'branches', 'session history', 'provider profiles'], sessionsStarted: false, mutationPerformed: false }; + if (dryRun) return result; + if (!authorized) throw new TorchError('Restoring a retained installation requires --yes', { code: 'APPROVAL_REQUIRED' }); + if (metadata.detachedAt) { + const temporary = `${path}.restore-${process.pid}`; + writeFileSync(temporary, `${JSON.stringify({ ...metadata, detachedAt: null, attachedAt: new Date().toISOString() }, null, 2)}\n`, { flag: 'wx', mode: 0o600 }); + renameSync(temporary, path); + result.mutationPerformed = true; + } + return result; +} diff --git a/test/fleet/setup.test.mjs b/test/fleet/setup.test.mjs new file mode 100644 index 0000000..b051f36 --- /dev/null +++ b/test/fleet/setup.test.mjs @@ -0,0 +1,101 @@ +import assert from 'node:assert/strict'; +import { execFileSync } from 'node:child_process'; +import { mkdtempSync, readFileSync, writeFileSync, cpSync } 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 { proposeDomains } from '../../src/kernel/domains.mjs'; +import { inspectRepository } from '../../src/kernel/git.mjs'; +import { installProject, readInstallManifest } from '../../src/kernel/install.mjs'; +import { resolveInstalledProjectRoot } from '../../src/kernel/project-root.mjs'; +import { setupProject, restoreProject } from '../../src/kernel/setup.mjs'; +import { openControlPlane } from '../../src/control-plane/service.mjs'; +import { createFleetBrief, detachFleet, planFleetDetach } from '../../src/runtime/lifecycle.mjs'; + +const git = (root, args) => execFileSync('git', ['-C', root, ...args], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim(); +function fixture() { + const root = mkdtempSync(join(tmpdir(), 'torch-setup-')); + const env = { ...process.env, XDG_DATA_HOME: join(root, 'external-state') }; + git(root, ['init', '-b', 'main']); + git(root, ['config', 'user.name', 'Setup Test']); + git(root, ['config', 'user.email', 'test@example.invalid']); + writeFileSync(join(root, '.gitignore'), 'external-state/\nworktrees/\n'); + writeFileSync(join(root, 'app.js'), 'export const app = true;\n'); + git(root, ['add', '.']); + git(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: 'owner', notes: [] }; + installProject({ repository, proposal, env }); + return { root, env, parentOverride: join(root, 'worktrees') }; +} + +test('SCN-guided-setup: approved setup checkpoints owned files and provisions without agents', () => { + const f = fixture(); + const before = git(f.root, ['rev-parse', 'HEAD']); + assert.equal(setupProject({ ...f, repository: inspectRepository(f.root), dryRun: true }).canProceed, true); + assert.equal(git(f.root, ['rev-parse', 'HEAD']), before); + assert.throws(() => setupProject({ ...f, repository: inspectRepository(f.root) }), { code: 'APPROVAL_REQUIRED' }); + writeFileSync(join(f.root, 'app.js'), 'export const app = false;\n'); + git(f.root, ['add', 'app.js']); + assert.throws(() => setupProject({ ...f, repository: inspectRepository(f.root), authorized: true }), { code: 'SETUP_STAGED_WORK' }); + assert.equal(git(f.root, ['diff', '--cached', '--name-only']), 'app.js'); + git(f.root, ['restore', '--staged', 'app.js']); + writeFileSync(join(f.root, 'app.js'), 'export const app = true;\n'); + const result = setupProject({ ...f, repository: inspectRepository(f.root), authorized: true }); + assert.equal(result.sessionsStarted, false); + assert.ok(result.configurationCommit); + assert.ok(result.registrationCommit); + assert.equal(git(f.root, ['status', '--porcelain']), ''); + assert.ok(git(f.root, ['diff', '--name-only', before, 'HEAD']).split('\n').every(path => path.startsWith('.torch/'))); + assert.ok(readInstallManifest(f.root).external.some(entry => entry.type === 'worktree')); + assert.equal(setupProject({ ...f, repository: inspectRepository(f.root), authorized: true }).mutationPerformed, false); +}); + +test('SCN-worktree-canonical-brief: current instructions win and unregistered or copied worktrees fail closed', () => { + const f = fixture(); + setupProject({ ...f, repository: inspectRepository(f.root), authorized: true }); + const entry = readInstallManifest(f.root).external.find(item => item.type === 'worktree'); + const marker = 'Current canonical instructions, not the old worktree copy.'; + writeFileSync(join(f.root, '.torch/prompts/COMMON.md'), marker); + const canonical = resolveInstalledProjectRoot(entry.path, f.env); + assert.equal(canonical, f.root); + const control = openControlPlane({ repositoryRoot: canonical, env: f.env }); + try { + const brief = createFleetBrief({ repositoryRoot: canonical, controlPlane: control, areaId: entry.area }); + assert.ok(brief.areas[0].prompt.includes(marker)); + } finally { control.close(); } + const stray = join(f.root, 'worktrees', 'stray'); + git(f.root, ['worktree', 'add', '-b', 'stray', stray]); + assert.throws(() => resolveInstalledProjectRoot(stray, f.env), { code: 'UNREGISTERED_PROJECT_WORKTREE' }); + const copied = mkdtempSync(join(tmpdir(), 'torch-copy-')); + cpSync(join(entry.path, '.torch'), join(copied, '.torch'), { recursive: true }); + git(copied, ['init', '-b', entry.branch]); + assert.throws(() => resolveInstalledProjectRoot(copied, f.env), { code: 'UNREGISTERED_PROJECT_WORKTREE' }); + const localManifest = JSON.parse(readFileSync(join(entry.path, '.torch/install-manifest.json'))); + localManifest.installationId = 'stale-installation'; + writeFileSync(join(entry.path, '.torch/install-manifest.json'), JSON.stringify(localManifest)); + assert.throws(() => resolveInstalledProjectRoot(entry.path, f.env), { code: 'UNREGISTERED_PROJECT_WORKTREE' }); +}); + +test('SCN-install-restore: detach and explicit restore preserve configuration and worktree history without starting sessions', () => { + const f = fixture(); + setupProject({ ...f, repository: inspectRepository(f.root), authorized: true }); + const manifest = readInstallManifest(f.root); + const metadataPath = join(manifest.external.find(entry => entry.type === 'local-state').path, 'project.json'); + const config = readFileSync(join(f.root, '.torch/torch.yaml'), 'utf8'); + const heads = manifest.external.filter(entry => entry.type === 'worktree').map(entry => git(entry.path, ['rev-parse', 'HEAD'])); + const control = openControlPlane({ repositoryRoot: f.root, env: f.env }); + try { + detachFleet({ plan: planFleetDetach({ repositoryRoot: f.root, controlPlane: control, adapters: {} }), controlPlane: control, stopRuntime: () => { throw new Error('Offline setup must not call a provider'); } }); + } finally { control.close(); } + const before = readFileSync(metadataPath, 'utf8'); + assert.equal(restoreProject({ repository: inspectRepository(f.root), env: f.env, dryRun: true }).wasDetached, true); + assert.equal(readFileSync(metadataPath, 'utf8'), before); + assert.throws(() => restoreProject({ repository: inspectRepository(f.root), env: f.env }), { code: 'APPROVAL_REQUIRED' }); + assert.equal(restoreProject({ repository: inspectRepository(f.root), env: f.env, authorized: true }).sessionsStarted, false); + assert.equal(JSON.parse(readFileSync(metadataPath)).detachedAt, null); + assert.equal(readFileSync(join(f.root, '.torch/torch.yaml'), 'utf8'), config); + assert.deepEqual(manifest.external.filter(entry => entry.type === 'worktree').map(entry => git(entry.path, ['rev-parse', 'HEAD'])), heads); +}); diff --git a/torch-release.json b/torch-release.json index 00b9ad6..98595d5 100644 --- a/torch-release.json +++ b/torch-release.json @@ -1,6 +1,6 @@ { "schema": "torch.dev/release/v1alpha1", - "version": "0.1.0-alpha.1", + "version": "0.1.0-alpha.2", "state": { "reads": ["torch.dev/state/v1alpha1"], "writes": "torch.dev/state/v1alpha1", From ff0712534829047142ff7b5ae9c60ae9e2292699 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN Date: Wed, 30 Sep 2026 15:20:11 -0400 Subject: [PATCH 2/3] Record successful alpha.2 uninstall and restore trial --- reports/audit/setup-reinstall-2026-09-30.md | 36 +++++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/reports/audit/setup-reinstall-2026-09-30.md b/reports/audit/setup-reinstall-2026-09-30.md index eecd18a..b96e5f8 100644 --- a/reports/audit/setup-reinstall-2026-09-30.md +++ b/reports/audit/setup-reinstall-2026-09-30.md @@ -24,6 +24,36 @@ through the patched source CLI, including its preserved offline runtime ID. ## Installation trial -Pending accepted alpha.2 build and normal uninstall/restore verification. -This is a retained-state reinstall, not a purge or deletion of the tool's -historical versions. Provider launches remain deliberately disabled. +Accepted alpha.2 source: `c11754ab18fa690355ead3f6f87b3accb2975153`. +Candidate digest: `d4b6ba38a51cc1804047b175c963806850f017a92d88863d187043314414dd90`. +All 39 acceptance groups passed again from the copied candidate. Active version +is `0.1.0-alpha.2`; rollback retains alpha.1. + +Normal uninstall: PASS. Retained-state restore: PASS. All seven actual agent +worktrees returned briefs through `/home/user/.local/bin/torch`: PASS. Their +briefs include the updated canonical smoke instructions despite older worktree +copies. Model configuration hash, project/installation IDs, offline states, +saved Codex runtime IDs and all seven branch tips were unchanged across +uninstall/restore. No provider was launched. + +The restored project's `torch setup --yes --json` was a no-op: no commits, +no mutations, no sessions started. Final doctor reports healthy. Modified owned +configuration and unique manager history remain legitimate safety warnings, +not authorization to purge. Uninstall refreshed the manager resume brief; +that generated file was separately checkpointed on its existing branch, without +rewriting prior history or landing anything. + +Receipts: + +- `/tmp/torch-alpha2-candidate-build.json` +- `/tmp/torch-alpha2-uninstall-plan.json` +- `/tmp/torch-alpha2-uninstall-result.json` +- `/tmp/torch-alpha2-restore-result.json` +- `/tmp/torch-alpha2-reinstall-verification.json` +- `/tmp/torch-alpha2-real-setup-result.json` +- `/tmp/torch-alpha2-real-doctor.json` + +This tests retained-state reinstall, not destructive purge or a fresh installation +with discarded configuration. New-install setup is covered by isolated real-Git +regression tests. Provider-authentication and new agent launches were deliberately +not repeated. Host timers remain uninstalled. From 28ee61bf62c3c99a845c97540570401a47bd6aa3 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN Date: Wed, 30 Sep 2026 15:41:35 -0400 Subject: [PATCH 3/3] Document first-work intake and persistent init install operations --- .torch/prompts/session-manager.md | 8 ++ docs/PORTABLE_AGENT_FLEET_SPEC.md | 67 ++++++++++++++ docs/TODO.md | 63 ++++++++++++- .../audit/conversation-handoff-2026-09-30.md | 90 +++++++++++++++++++ 4 files changed, 226 insertions(+), 2 deletions(-) create mode 100644 reports/audit/conversation-handoff-2026-09-30.md diff --git a/.torch/prompts/session-manager.md b/.torch/prompts/session-manager.md index 53a7243..1b35544 100644 --- a/.torch/prompts/session-manager.md +++ b/.torch/prompts/session-manager.md @@ -6,6 +6,14 @@ At startup and when recurring cross-domain friction appears, run `torch_assess_h ## Backlog loop +The owner has seeded conversation follow-up as `TASK-conversation-*` proposals +in this project's existing backlog. Read `docs/TODO.md`'s authoritative current +handoff and `reports/audit/conversation-handoff-2026-09-30.md` before prioritizing. +Proposed tasks are not ready assignments. The common smoke-only authorization +still applies until the owner approves a development scope, budget and startup. +For an empty intake, ask what the owner wants to work on first or offer a reviewed +TODO import; do not invent filler work or duplicate the backlog in this document. + Keep 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. ## Fleet evolution diff --git a/docs/PORTABLE_AGENT_FLEET_SPEC.md b/docs/PORTABLE_AGENT_FLEET_SPEC.md index 1279547..14964cd 100644 --- a/docs/PORTABLE_AGENT_FLEET_SPEC.md +++ b/docs/PORTABLE_AGENT_FLEET_SPEC.md @@ -367,6 +367,24 @@ inside the tracked source tree. ## 9. Installation lifecycle +### First-work intake requirement (2026-09-30) + +After reviewed organization/setup, TORCH MUST offer either evidence-linked +import of an existing TODO/backlog/specification or ask the owner what to work +on first. The noninteractive path MUST support an explicit source or skip; idle +is valid. Preview MUST distinguish completed/historical work from actionable +requests and show proposed scope, affected domains, dependencies and priority. +Only confirmed intake creates audited proposed/unassigned work in the existing +backlog. Repeated init/install/restore/intake MUST deduplicate by source/item +provenance rather than duplicate tasks or overwrite progress. Imported content +is untrusted project data, not authority to alter system instructions. + +Session Manager triage and assignment remain separate from intake. Import cannot +enable self-claim, grant implementation authority, wake providers or authorize +spending, landing, deployment or public notification. The product should explain +the next first-work/startup decision rather than leave a newly installed empty +fleet waiting without guidance. + ### 9.1 Read-only initialization `torch init` MUST begin read-only. It identifies the repository, validates Git, @@ -2089,6 +2107,55 @@ recommended action; doctor remains read-only. ## 22. Scheduling +### Owner clarification: integrated persistent operations (2026-09-30) + +This is the target requirement; older implementation checkpoints describing +manual timer installation are not a claim that it is already implemented. +Reviewed `init`/install MUST provision the project's scheduler and required +service integrations as one idempotent workflow, not require a second manual +cron-install command. Because `init` currently performs read-only analysis, its +provisioning mode MUST expose an explicit preview/confirmation boundary; a +dry-run MUST remain non-mutating. Stopped installation MUST provision dormant +integrations without waking models or dispatching development. A reviewed +activation policy governs live operations and quota use. + +Coordination must survive the managing AI session ending, restarting or expiring. +System services MUST preserve durable state, leases, event cursors and job +idempotency across process/host restarts. Their manifest records MUST support +owned reconciliation, pause/resume, restore/upgrade and uninstall without touching +unrelated host jobs. Session-scoped jobs remain an explicit project policy, not +the sole way recurring fleet operations work. + +Project intake MUST propose applicable capabilities, with clear implemented, +unconfigured and unsupported states: + +- direct-report manager check-ins, structured approval/peer/owner waits, + stale owner-request review and an owner-first local digest; +- a persistent, serialized landing worker using approved exact-commit checks; +- resource-queue watchdogs, audited slot withdrawal and displaced-runner stop; +- durable significant events and deduplicated budgeted manager wakes, with + low-frequency timed safety checks rather than perpetual paid polling; +- a project-declared nightly canonical suite, heavy-check windows and + evidence-backed regression routing, not guessed blame; +- measured test durations, uncertain wait estimates and project-specific + exclusive resources such as a GPU; +- conservative provider-neutral usage limits, graceful wind-down and explicitly + approved credit-window or calendar restarts that never override manual pause; +- idle, clean, unguarded worktree convergence by merge, preserving conflicts, + unique commits and active work; +- exact landed task closure, verified reversal/follow-up policy and reviewed + evidence for finished-but-unnamed work, never automatic closure by message alone; +- project-specific local dev/Console services and separately authorized delivery, + live-version checks and external notifications where relevant. + +No universal project inherits COMBATRIG's release times, Monday reset time, +two GPU slots, Vercel destination, phone notifier or LAN/HTTPS exposure. Setup +MUST show lifetime, timezone/DST behavior, missed-run policy, costs/budgets, +ports/destinations, health evidence, stop semantics and owner authority. Unsupported +hosts/adapters need an actionable limitation or qualified fallback, not a fictitious +"installed" status. No provider wake, deploy, public message, off-machine write +or cleanup of unowned processes follows solely from creating a schedule. + TORCH recognizes two schedule classes. **System schedules** survive AI sessions and may perform approved release, diff --git a/docs/TODO.md b/docs/TODO.md index a357380..e33adf3 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -1,5 +1,64 @@ # TORCH remaining work +## Authoritative current handoff (2026-09-30, after alpha.2 reinstall) + +TORCH is installed and healthy at `0.1.0-alpha.2`. Setup/restore and all seven +worktree briefs have been verified. The active organization is Session Manager +plus six specialists; all providers are offline. The default Git branch remains +legacy TORCH; the configured development/integration boundary is +`rewrite/portable-agent-fleet`. No publication or default-branch promotion is +authorized. Later sections retain historical audit snapshots, including old +"uncommitted/no launcher" statements; those are superseded by this handoff. + +The conversation's unfinished work is now owner-proposed in the **existing +authoritative backlog in the Session Manager worktree**, not a second TODO queue. +Use `torch backlog list --json` to see it from any registered checkout. Tasks use +the `TASK-conversation-` prefix, have acceptance criteria, affected domains and +dependencies, and remain unassigned for manager triage. See +`reports/audit/conversation-handoff-2026-09-30.md` for the domain review and index. + +### New owner requirements: first work and automatic operations + +- [ ] `TASK-conversation-first-work-intake`: init/install/setup offers reviewed + TODO/backlog import or asks what the owner wants to begin with; previews and + confirms proposed tasks; deduplicates resumes; never activates stale/completed + TODOs or launches agents silently. +- [ ] `TASK-conversation-persistent-operations`: reviewed init/install provisions + machine-level scheduler/services automatically, not merely schedule JSON or a + separate manual cron-install command. Paused installs remain dormant. Services + declare system/session lifetimes, budgets, timezone, restart/missed-run policy, + pause/resume and manifest-owned uninstall/restore behavior. This extends the + earlier `TASK-conversation-automatic-scheduler` requirement. +- [ ] `TASK-conversation-event-driven-wakes`: durable deduplicated significant + events wake the appropriate manager under budget; periodic checks are the quiet + safety net, not repeated usage-consuming busywork. +- [ ] `TASK-conversation-queue-watchdog`: detect stuck resource queues and + disappearing slots, audit removals and stop displaced waiters; never infer + browser ownership/termination from a process name or runner death. +- [ ] `TASK-conversation-nightly-main-tests`: project-declared full canonical + suite on an exact frozen commit, resource-aware idle window and evidence-backed + regression routing. Do not guess the offending change. +- [ ] `TASK-conversation-usage-governor`: provider-neutral usage/credit evidence, + conservative fallback budgets, graceful wind-down and reviewed reset/restart + policy that cannot override manual pause or detach. +- [ ] `TASK-conversation-idle-worktree-refresh`: scheduled merges into clean, + idle, unguarded branches; skip active/dirty/unknown work and preserve conflicts. +- [ ] `TASK-conversation-queue-planning`: measured test durations, uncertain wait + estimates, slowdown visibility, GPU-exclusive resources and approved heavy-run + windows; preserve the resource queue's authority. +- [ ] `TASK-conversation-closure-revert-review`: verified revert/follow-up policy + and evidence-backed review of unnamed finished work; never mass-close or reopen + tasks based only on untrusted commit trailers. +- [ ] `TASK-conversation-project-services`: reviewed project-specific landing + worker, local dev/Console server, local summary and optional release jobs. + No COMBATRIG schedules, Vercel deployment, credentials, LAN exposure or automatic + phone notifications are assumed. + +These are proposed development tasks, **not implemented automatic setup**. The +existing 15-minute manager schedule is configured, but no TORCH host timer is +installed. Common instructions still authorize only the smoke test; development +activation, self-claim and recurring provider wakes remain separately reviewed. + ## First real self-host run (2026-09-30) ### Portable production pipelines (new roadmap element) @@ -349,7 +408,7 @@ in `reports/audit/self-host-readiness-2026-09-30.md`. No agent or timer was star the collapsed panel; observation never releases a launch lock. - [ ] Qualify interrupted-wake recovery against live runtime lifecycle evidence; owner attestation is explicit but is not independent proof of termination. -- [ ] Qualify the updated source as a clean commit before self-host activation; +- [x] Qualify the updated source as a clean commit before self-host activation; older artifact receipts predate these changes. No fleet or timer has started. Review verification (2026-09-30): full regression suite through isolated @@ -961,7 +1020,7 @@ COMBATRIG's proposed hierarchy as the default. multi-domain coordination/resume scenario with a fixed provider budget. - [ ] Install and reverse the user-systemd scheduler, verifying one real dispatch and its receipts. -- [ ] Install an accepted TORCH version in user-local versioned state and use +- [x] Install an accepted TORCH version in user-local versioned state and use its stable launcher to manage this source checkout. - [ ] Resolve COMBATRIG's 56 ownership/exclusion blockers, then separately authorize an import/cutover trial. Do not mutate COMBATRIG beforehand. diff --git a/reports/audit/conversation-handoff-2026-09-30.md b/reports/audit/conversation-handoff-2026-09-30.md new file mode 100644 index 0000000..d3755ad --- /dev/null +++ b/reports/audit/conversation-handoff-2026-09-30.md @@ -0,0 +1,90 @@ +# TORCH conversation-to-fleet handoff + +## Installed domains + +The installed organization is appropriately scoped to this repository: one +Session Manager and six specialists. A current read-only ownership query over +129 tracked files under `src`, `bin`, `site`, `schemas`, `scripts` and `test` +found zero unowned files and zero duplicate primary owners. This claim does not +cover every historical report or future new directory. + +| Domain | Responsibility | Effective Codex model | +| --- | --- | --- | +| Session Manager | Routing, ownership, priority and fleet health | GPT-6.1 Sol high | +| Project Kernel | Bootstrap, identity, canonical instructions and organization | GPT-5.6 Terra high | +| Provider Runtime | Harnesses, MCP, lifecycle, schedules and telemetry | GPT-5.6 Terra high | +| Work and Integration | Backlog, checks, resources, landing and recovery | GPT-5.6 Terra high | +| Owner Console | Dashboard, artifacts, feedback and observability | GPT-6 Luna high | +| Release and Self-host | CLI, package, candidates, upgrades and removal | GPT-6 Luna high | +| Independent QA | Scenario integrity and real qualification | GPT-5.6 Terra high | + +The six-role grouping was a reviewed consolidation of repository evidence, +not a copy of COMBATRIG's specialists and not proof of a fully autonomous AI +Architect first-run. Proposed extra domain leads are inactive. Headcount alone +does not justify adding managers. Significant pipeline work may justify different +model effort or a new domain later, through the existing review path. + +## Authoritative backlog + +Owner-request intake created 34 proposed, unassigned items in the existing +Session Manager worktree backlog. There are 33 current follow-ups plus the +earlier narrower `automatic-scheduler` request, retained for manager triage +against the newer `persistent-operations` requirement rather than silently +deleted. All have acceptance criteria, affected domains and observed commit +binding; dependent slices reference existing task IDs. No assignment, provider +start, self-claim enabling or host service installation occurred. + +Run `torch backlog list --json` from any registered checkout. The canonical +backlog service resolves the manager's tracked ledger; do not create a second +copy in the source checkout. Task IDs below are prefixed `TASK-conversation-`. + +| Group | Task suffixes | +| --- | --- | +| First work / readiness | `first-work-intake`, `development-activation`, `selfhost-development-trial` | +| Persistent operations | `persistent-operations`, `automatic-scheduler`, `event-driven-wakes`, `queue-watchdog`, `nightly-main-tests`, `usage-governor`, `idle-worktree-refresh`, `queue-planning`, `project-services` | +| Provider reliability | `runtime-diagnostics`, `startup-preflight`, `mcp-isolation`, `update-startpaths`, `mixed-provider-qualification` | +| Pipelines | `pipeline-definitions`, `pipeline-provenance`, `pipeline-flow`, `pipeline-stalls`, `pipeline-console`, `pipeline-metrics`, `pipeline-pilot` | +| Live qualification | `hierarchy-pilot`, `scheduler-qualification`, `browser-portability`, `landing-closure`, `closure-revert-review`, `delivery-qualification` | +| Owner/product/release | `owner-digest`, `console-workflow`, `documentation-current`, `release-qualification` | + +## What is not yet automatic + +Setup and retained-state restore have been qualified; installed alpha.2 is +healthy. All agents remain offline. The current manager schedule declares a +15-minute interval, but no TORCH-owned systemd units are installed or active. +Read-only host timer enumeration confirmed no TORCH timer; unrelated Archive +timers were untouched. The owner's latest request is that init/install provision +these integrations automatically. That is now an explicit target in specification +section 22 and the backlog, not an already delivered feature. + +The shared common prompt still authorizes only an installation smoke test and +the project self-claim policy is disabled. Starting agents under it does not +authorize development. The manager should read and triage proposed work, recommend +a first bounded slice and budget, and obtain the development/startup approval +before delegation. New installs should offer reviewed TODO import or ask the +owner what they want first; repeated intake must deduplicate rather than queue +every historical checkbox. + +## Suggested first implementation sequence + +1. First-work intake and persistent operations planning/provisioning: make + the product's installation genuinely complete while stopped until ready. +2. Runtime diagnostics and exact launch preflight: make startup failures legible. +3. Separately approved bounded self-host development, landing/closure and + scheduler qualification, followed by persistent event/watchdog/nightly work. +4. Pipeline definitions, immutable provenance, checked transitions and Console. +5. Finish mixed-provider, organization-growth and release qualification. + +There is no authorization here to promote the rewrite to default `main`, deploy +the public website, send notifications, alter COMBATRIG, spend on asset generation +or install active recurring AI wakes. Those boundaries remain in the task criteria. + +## Validation + +Installed `torch backlog list` sees all 34 items. Acceptance criteria and domains +are populated; every dependency exists and the dependency graph is acyclic. +Installed `torch backlog health` reports healthy with no anomalous assignments. +Its bounded historical activity coverage remains incomplete, correctly labelled +unknown rather than proof of inactivity. Existing backlog/scheduling regression +suite: 23/23 PASS. All seven identities remain offline. These are backlog/spec +changes, not implementation of the newly requested onboarding or automation.