Checkpoint portable TORCH fleet, provider adapters and owner dashboard

This commit is contained in:
thePR0M3TH3AN committed 2026-09-30 13:28:40 -04:00
1 parent d31437a4fb
commit 03fcbdd23f
140 files changed
+26152 -757

No files matched your search

+7 -3
View File
@@ -1,6 +1,6 @@
import { existsSync, readFileSync } from 'node:fs';
import { extname } from 'node:path';
import { trackedFiles } from './git.mjs';
import { workingTreeFiles, workingTreeFingerprint } from './git.mjs';
import { buildArchitectureGraph } from './domains.mjs';
const LANGUAGE_BY_EXTENSION = new Map([
@@ -20,7 +20,8 @@ const MANIFESTS = [
];
export function analyzeRepository(repository, { specifications = [] } = {}) {
const files = trackedFiles(repository.root);
const workingTree = workingTreeFiles(repository.root);
const files = workingTree.files;
const languageCounts = new Map();
const topLevel = new Set();
@@ -70,9 +71,12 @@ export function analyzeRepository(repository, { specifications = [] } = {}) {
branch: repository.branch,
head: repository.head,
hasRemote: repository.remotes.length > 0,
workingTreeFingerprint: workingTreeFingerprint(repository.root, files),
},
inventory: {
trackedFileCount: files.length,
trackedFileCount: workingTree.trackedFileCount,
untrackedFileCount: workingTree.untrackedFileCount,
analyzedFileCount: files.length,
languages,
manifests,
topLevelDirectories: [...topLevel].sort(),
+195 -3
View File
@@ -2,12 +2,14 @@ import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { z } from 'zod';
import { TorchError } from './errors.mjs';
import { organizationGraphFromConfig, organizationGraphSchema } from './organization.mjs';
export const PROJECT_CONFIG_SCHEMA = 'torch.dev/v1alpha1';
const text = z.string().trim().min(1);
const id = text.regex(/^[a-z0-9][a-z0-9-]*$/);
const textList = z.array(text);
const launchPolicy = z.record(z.string().regex(/^[a-z][A-Za-z0-9]*$/), text).optional();
const domain = z.object({
id,
@@ -22,6 +24,8 @@ const domain = z.object({
resources: textList,
runtime: text,
model: text.nullable().optional(),
reasoning: text.nullable().optional(),
launchPolicy,
branch: text.nullable().optional(),
worktree_name: text.regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/).nullable().optional(),
}).strict();
@@ -33,6 +37,21 @@ const check = z.object({
args: textList.optional(),
source: z.record(z.string(), z.unknown()).optional(),
resources: textList.optional(),
snapshot: z.object({
paths: textList.min(1),
source_commit_file: text,
}).strict().optional(),
conditions: z.object({
command: text,
args: textList.optional(),
timeout_seconds: z.number().int().positive().max(300).optional(),
required: z.array(z.object({
id,
equals: z.union([z.string(), z.boolean(), z.number().finite(), z.null()]),
}).strict()).min(1).refine((requirements) =>
new Set(requirements.map((requirement) => requirement.id)).size === requirements.length,
'Condition IDs must be unique'),
}).strict().optional(),
}).strict();
const resource = z.object({
@@ -53,8 +72,28 @@ const schedule = z.object({
z.object({ type: z.literal('interval'), seconds: z.number().int().positive() }).strict(),
z.object({ type: z.literal('cron'), expression: text }).strict(),
]),
behavior: z.enum(['read-only', 'mutating']),
action: z.object({ type: z.literal('command'), command: text, args: textList }).strict(),
behavior: z.enum(['read-only', 'coordination', 'mutating']),
action: z.discriminatedUnion('type', [
z.object({ type: z.literal('command'), command: text, args: textList }).strict(),
z.object({
type: z.literal('manager-check-in'), manager_id: id,
stale_work: z.object({
stale_days: z.number().int().min(1).max(365).optional(),
max_commits: z.number().int().min(1).max(10_000).optional(),
max_items: z.number().int().min(1).max(100).optional(),
}).strict().optional(),
wake: z.object({
enabled: z.boolean(),
budget_mode: z.enum(['invocation-count', 'usd-hard-cap']).optional(),
max_usd_per_invocation: z.number().finite().positive().optional(),
}).strict().optional(),
}).strict(),
z.object({
type: z.literal('integration-drain'), landing_authority_id: id,
limit: z.number().int().min(1).max(500).optional(),
}).strict(),
z.object({ type: z.literal('owner-digest') }).strict(),
]),
required_authority: textList.min(1),
retry: z.object({ max_attempts: z.number().int().min(1).max(10) }).strict().optional(),
failure_recipient: text.optional(),
@@ -89,8 +128,19 @@ export const projectConfigSchema = z.object({
message: 'runtimes.default must name a configured runtime',
}),
checks: z.array(check),
backlog: z.object({
self_claim: z.object({ enabled: z.boolean(), areas: textList }).strict().optional(),
activity: z.object({
stale_days: z.number().int().min(1).max(365).optional(),
max_commits: z.number().int().min(1).max(10_000).optional(),
auto_close_after_landing: z.boolean().optional(),
}).strict().optional(),
}).strict().optional(),
resources: z.array(resource),
schedules: z.array(schedule),
runtime_wake_budget: z.object({
max_invocations_per_day: z.number().int().min(1).max(24),
}).strict().optional(),
integration: z.object({
provider: text,
target: text,
@@ -99,6 +149,7 @@ export const projectConfigSchema = z.object({
landing_authority: textList.min(1),
}).strict(),
delivery: z.object({
retry: z.object({ max_attempts: z.number().int().min(1).max(5) }).strict().optional(),
authority: z.object({
implemented: textList.min(1),
verified: textList.min(1),
@@ -113,14 +164,26 @@ export const projectConfigSchema = z.object({
deployment: z.object({ provider: text }).passthrough(),
}).strict(),
}).strict(),
owner_digest: z.object({ enabled: z.boolean(), window_hours: z.number().int().min(1).max(168).optional(),
max_items: z.number().int().min(1).max(100).optional() }).strict().optional(),
session_manager: z.object({
id: z.literal('session-manager'),
runtime: text,
start_last: z.literal(true),
model: text.nullable().optional(),
reasoning: text.nullable().optional(),
launchPolicy,
// Older installations carried this launch-order hint. Current lifecycle
// ordering is derived from the organization graph; retain only for reads.
start_last: z.literal(true).optional(),
branch: text.nullable().optional(),
worktree_name: text.regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/).nullable().optional(),
}).strict(),
domains: z.array(domain).min(1),
organization: organizationGraphSchema.optional(),
organization_assessment: z.object({
observation_window_days: z.number().int().min(7).max(365),
minimum_recurrences: z.number().int().min(3).max(10),
}).strict().optional(),
retired_domains: z.array(z.object({
id,
title: text,
@@ -130,6 +193,124 @@ export const projectConfigSchema = z.object({
}).strict()).optional(),
}).strict().superRefine((config, context) => {
const runtimeNames = new Set(Object.keys(config.runtimes).filter((name) => name !== 'default'));
const scheduleIds = new Set();
const identityIds = new Set(['session-manager', ...config.domains.map((entry) => entry.id)]);
for (const [index, area] of (config.backlog?.self_claim?.areas ?? []).entries()) {
if (area === 'session-manager' || !identityIds.has(area)) context.addIssue({
code: 'custom', path: ['backlog', 'self_claim', 'areas', index],
message: 'self-claim allowlist must name a defined specialist identity',
});
}
for (const [index, entry] of config.schedules.entries()) {
if (scheduleIds.has(entry.id)) {
context.addIssue({ code: 'custom', path: ['schedules', index, 'id'], message: `duplicate schedule id: ${entry.id}` });
}
scheduleIds.add(entry.id);
if (entry.action.type === 'manager-check-in') {
if (entry.behavior !== 'coordination' || entry.lifetime !== 'system'
|| !entry.required_authority.includes('owner')) {
context.addIssue({
code: 'custom', path: ['schedules', index],
message: 'manager check-in must be an owner-authorized system coordination schedule',
});
}
if (entry.trigger.type === 'interval' && entry.trigger.seconds < 60) {
context.addIssue({
code: 'custom', path: ['schedules', index, 'trigger', 'seconds'],
message: 'manager check-in intervals must be at least 60 seconds',
});
}
if (!identityIds.has(entry.action.manager_id)) {
context.addIssue({
code: 'custom', path: ['schedules', index, 'action', 'manager_id'],
message: `manager check-in references unknown identity: ${entry.action.manager_id}`,
});
}
const wake = entry.action.wake;
const mode = wake?.budget_mode ?? (wake?.max_usd_per_invocation !== undefined ? 'usd-hard-cap' : null);
if (wake?.enabled === true && mode === null) {
context.addIssue({
code: 'custom', path: ['schedules', index, 'action', 'wake', 'budget_mode'],
message: 'enabled manager runtime wakes require an explicit invocation-count policy or a positive per-invocation USD ceiling',
});
}
if (mode === 'usd-hard-cap' && !(Number.isFinite(wake.max_usd_per_invocation)
&& wake.max_usd_per_invocation > 0)) {
context.addIssue({
code: 'custom', path: ['schedules', index, 'action', 'wake', 'max_usd_per_invocation'],
message: 'enabled manager runtime wakes require a positive per-invocation USD ceiling',
});
}
if (mode === 'invocation-count' && wake?.max_usd_per_invocation !== undefined) {
context.addIssue({
code: 'custom', path: ['schedules', index, 'action', 'wake', 'max_usd_per_invocation'],
message: 'invocation-count mode cannot promise a USD ceiling; select usd-hard-cap instead',
});
}
} else if (entry.action.type === 'integration-drain') {
if (entry.behavior !== 'mutating' || entry.lifetime !== 'system'
|| !['interval', 'cron'].includes(entry.trigger.type)
|| !entry.required_authority.includes('owner')) {
context.addIssue({
code: 'custom', path: ['schedules', index],
message: 'integration drain must be an owner-authorized periodic system mutation schedule',
});
}
if (!identityIds.has(entry.action.landing_authority_id)) {
context.addIssue({
code: 'custom', path: ['schedules', index, 'action', 'landing_authority_id'],
message: `integration drain references unknown identity: ${entry.action.landing_authority_id}`,
});
}
if (!config.integration.landing_authority.includes(entry.action.landing_authority_id)) {
context.addIssue({
code: 'custom', path: ['schedules', index, 'action', 'landing_authority_id'],
message: `integration drain identity lacks configured landing authority: ${entry.action.landing_authority_id}`,
});
}
if (entry.trigger.type === 'interval' && entry.trigger.seconds < 60) {
context.addIssue({
code: 'custom', path: ['schedules', index, 'trigger', 'seconds'],
message: 'integration-drain intervals must be at least 60 seconds',
});
}
} else if (entry.action.type === 'owner-digest') {
if (entry.behavior !== 'coordination' || entry.lifetime !== 'system'
|| !entry.required_authority.includes('owner') || (entry.retry?.max_attempts ?? 1) !== 1) {
context.addIssue({ code: 'custom', path: ['schedules', index],
message: 'owner digest must be an owner-authorized system coordination schedule without automatic retry' });
}
} else if (entry.behavior === 'coordination') {
context.addIssue({
code: 'custom', path: ['schedules', index, 'behavior'],
message: 'coordination behavior is reserved for the typed manager-check-in action',
});
}
}
const managerRoleIds = new Set(config.organization
? config.organization.roles.flatMap((role) => role.reports_to)
: []);
const scheduledManagers = new Set(config.schedules
.filter((entry) => entry.action.type === 'manager-check-in')
.map((entry) => entry.action.manager_id));
for (const managerIdentity of new Set(organizationGraphFromConfig(config).roles
.filter((role) => managerRoleIds.has(role.id) && role.kind !== 'owner')
.map((role) => role.identity_id))) {
if (!scheduledManagers.has(managerIdentity)) {
context.addIssue({
code: 'custom', path: ['schedules'],
message: `manager identity ${managerIdentity} has direct reports but no manager-check-in schedule`,
});
}
}
const wakeEnabled = config.schedules.some((entry) =>
entry.action.type === 'manager-check-in' && entry.action.wake?.enabled === true);
if (wakeEnabled && !config.runtime_wake_budget) {
context.addIssue({
code: 'custom', path: ['runtime_wake_budget'],
message: 'enabled manager runtime wakes require a project-wide daily invocation budget',
});
}
if (!runtimeNames.has(config.runtimes.default)) {
context.addIssue({ code: 'custom', path: ['runtimes', 'default'], message: 'default runtime is not configured' });
}
@@ -138,6 +319,17 @@ export const projectConfigSchema = z.object({
context.addIssue({ code: 'custom', path: ['domains', index, 'runtime'], message: 'domain runtime is not configured' });
}
}
for (const [runtime, profile] of Object.entries(config.runtimes)) {
if (runtime === 'default' || !profile || typeof profile !== 'object' || Array.isArray(profile)) continue;
if (profile.launchPolicy !== undefined) {
const result = launchPolicy.safeParse(profile.launchPolicy);
if (!result.success) {
for (const issue of result.error.issues) context.addIssue({
...issue, path: ['runtimes', runtime, 'launchPolicy', ...issue.path],
});
}
}
}
});
function formatIssues(issues) {
+65 -7
View File
@@ -7,15 +7,19 @@ import { inspectManagedWorktree } from './worktree-state.mjs';
import { validateProjectConfig } from './config.mjs';
import { forgeStatus } from '../forge/service.mjs';
import { classifyRecoverability } from '../canonical/local.mjs';
import { createClaudeAdapter } from '../adapters/claude.mjs';
import { createCodexAdapter } from '../adapters/codex.mjs';
import { createRuntimeAdapterRegistry } from '../adapters/registry.mjs';
import { profileCapabilityIssues, resolveLaunchPolicy } from '../adapters/runtime.mjs';
import { assessBacklogHealth, backlogObservedCommitDistance } from '../backlog/service.mjs';
import { organizationGraphFromConfig } from './organization.mjs';
function parseJsonYaml(path) {
return JSON.parse(readFileSync(path, 'utf8'));
}
export function diagnoseProject({ repository, env = process.env, now = () => new Date() }) {
export function diagnoseProject({
repository, env = process.env, now = () => new Date(),
runtimeRegistry = createRuntimeAdapterRegistry({ env }),
}) {
const trackedRoot = join(repository.root, '.torch');
const configPath = join(trackedRoot, 'torch.yaml');
const manifestPath = join(trackedRoot, 'install-manifest.json');
@@ -39,6 +43,26 @@ export function diagnoseProject({ repository, env = process.env, now = () => new
}
if (manifest) {
if (config) {
try {
const graph = organizationGraphFromConfig(config);
const activeIdentities = new Set([
'owner', 'session-manager', ...(config.domains ?? []).map((domain) => domain.id),
]);
const missing = [...new Set(graph.roles
.map((role) => role.identity_id)
.filter((identity) => !activeIdentities.has(identity)))];
if (missing.length) findings.push({
severity: 'error', code: 'ORGANIZATION_IDENTITY_MISSING', identities: missing,
recommendation: 'Review the approved organization graph against the active Fleet roster; role references do not create identities.',
});
} catch (error) {
findings.push({
severity: 'error', code: 'ORGANIZATION_GRAPH_INVALID',
message: error.message, details: error.details,
});
}
}
for (const record of manifest.created ?? []) {
const path = join(repository.root, record.path);
if (!existsSync(path)) findings.push({ severity: 'error', code: 'OWNED_FILE_MISSING', path: record.path });
@@ -220,13 +244,24 @@ export function diagnoseProject({ repository, env = process.env, now = () => new
findings.push({ severity: 'error', code: 'RECOVERABILITY_INVALID', message: error.message });
}
if (config?.runtimes) {
const adapters = new Map([
['claude', createClaudeAdapter({ env })],
['codex', createCodexAdapter({ env })],
]);
const adapters = runtimeRegistry;
for (const pluginError of adapters.pluginErrors ?? []) findings.push({
severity: 'warning', code: pluginError.code, runtime: pluginError.name,
message: pluginError.message, details: pluginError.details,
recommendation: 'Review the user-local runtime plugin trust record and the exact adapter module before retrying.',
});
for (const runtime of Object.keys(config.runtimes).filter((name) => name !== 'default')) {
const adapter = adapters.get(runtime);
if (!adapter) {
const trustedPlugin = adapters.trustedPlugins?.find((plugin) => plugin.name === runtime);
if (trustedPlugin?.status === 'trusted') {
findings.push({
severity: 'info', code: 'RUNTIME_ADAPTER_TRUSTED_NOT_LOADED', runtime,
modulePath: trustedPlugin.modulePath, sha256: trustedPlugin.sha256,
recommendation: 'The user-local entrypoint hash is trusted. Doctor did not execute it; validate the adapter only in an explicit runtime operation.',
});
continue;
}
findings.push({
severity: 'warning', code: 'RUNTIME_ADAPTER_UNKNOWN', runtime,
recommendation: 'Install or configure a TORCH runtime adapter before starting this identity.',
@@ -242,6 +277,29 @@ export function diagnoseProject({ repository, env = process.env, now = () => new
: `Install ${runtime} or change the approved runtime assignment before startup.`,
});
}
const assigned = [
{ id: 'session-manager', ...config.session_manager },
...config.domains,
];
for (const area of assigned) {
const runtime = area.runtime ?? config.runtimes.default;
const adapter = adapters.get(runtime);
if (!adapter) continue;
const profile = {
model: area.model ?? config.runtimes[runtime]?.model ?? adapter.configuration?.model ?? null,
reasoning: area.reasoning ?? config.runtimes[runtime]?.reasoning ?? adapter.configuration?.reasoning ?? null,
launchPolicy: resolveLaunchPolicy({
runtime, adapterDefaults: adapter.configuration,
runtimeConfig: config.runtimes[runtime], identity: area,
}),
};
const unsupported = profileCapabilityIssues(adapter, profile);
if (unsupported.length) findings.push({
severity: 'error', code: 'RUNTIME_PROFILE_UNSUPPORTED', areaId: area.id,
runtime, adapter: adapter.name, profile, capabilities: unsupported,
recommendation: 'Assign a model/reasoning profile supported by this identity runtime adapter.',
});
}
}
if (config?.forge?.provider && config.forge.provider !== 'none') {
try {
+98 -22
View File
@@ -1,6 +1,8 @@
import { existsSync, readFileSync } from 'node:fs';
import { posix } from 'node:path';
import { join, posix } from 'node:path';
import { TorchError } from './errors.mjs';
import { organizationGraphFromConfig, planManagerCheckInSchedules } from './organization.mjs';
import { workingTreeFingerprint } from './git.mjs';
import { validateSpecificationEvidence } from './specifications.mjs';
const SOURCE_ROOTS = new Set(['src', 'app', 'apps', 'server', 'client', 'lib', 'packages', 'services']);
@@ -138,7 +140,7 @@ function domainFromComponent(component) {
neighbours: [],
required_checks: [],
resources: [],
runtime: 'claude',
runtime: 'default',
evidence: component.evidence,
};
}
@@ -163,12 +165,24 @@ function specDomain(signal, specification) {
: [`responsibilities described by ${specification.path}:${signal.line}`],
not_scope: [],
owned_paths: signal.pathReferences,
shared_paths: [], neighbours: [], required_checks: [], resources: [], runtime: 'claude',
shared_paths: [], neighbours: [], required_checks: [], resources: [], runtime: 'default',
evidence: [`${specification.path}:${signal.line}`],
design_status: signal.pathReferences.length ? 'evidence-backed' : 'needs-owner-path-review',
};
}
function hasProjectPathEvidence(signal, repositoryRoot) {
return signal.pathReferences.some((reference) => {
const value = reference.replaceAll('\\', '/').replace(/^\.\//, '');
if (!value || value.startsWith('/') || value.startsWith('~') || value.startsWith('$')
|| /^[a-z][a-z0-9+.-]*:\/\//i.test(value)) return false;
const normalized = posix.normalize(value);
if (normalized === '..' || normalized.startsWith('../')) return false;
const prefix = normalized.replace(/[?*{\x5b].*$/, '').replace(/\/+$/, '');
return Boolean(prefix && existsSync(join(repositoryRoot, ...prefix.split('/'))));
});
}
export function proposeDomains({ repository, analysis }) {
const graph = analysis.architecture;
const sourceComponents = graph.components.filter((component) => !SHARED_PATTERN.test(component.key));
@@ -181,32 +195,22 @@ export function proposeDomains({ repository, analysis }) {
specification.domainSignals.map((signal) => ({ signal, specification })));
for (const { signal, specification } of signals) {
const grounded = hasProjectPathEvidence(signal, repository.root);
const candidate = domains
.map((domain) => ({ domain, score: overlap(`${domain.id} ${domain.title} ${domain.owned_paths.join(' ')}`, `${signal.title} ${signal.pathReferences.join(' ')}`) }))
.sort((a, b) => b.score - a.score)[0];
if (candidate?.score > 0) {
if (candidate?.score > 0 && grounded) {
for (const responsibility of signal.responsibilities) {
if (!candidate.domain.scope.includes(responsibility)) candidate.domain.scope.push(responsibility);
}
candidate.domain.evidence.push(`${specification.path}:${signal.line}`);
continue;
}
if (signal.level <= 3 && (signal.pathReferences.length || specFirst)) {
if (signal.level <= 3 && (grounded || specFirst)) {
const proposed = specDomain(signal, specification);
if (!domains.some((domain) => domain.id === proposed.id)) domains.push(proposed);
}
}
if (!domains.length) {
domains.push({
id: 'core', title: 'Core project', kind: 'development',
scope: ['initial project implementation and architecture'],
not_scope: [], owned_paths: ['**'], shared_paths: [], neighbours: [], required_checks: [],
resources: [],
runtime: 'claude',
evidence: analysis.inventory.manifests,
});
}
if (analysis.inventory.testFileCount >= 2) {
domains.push({
id: 'qa', title: 'Quality and integration', kind: 'cross-cutting',
@@ -215,7 +219,7 @@ export function proposeDomains({ repository, analysis }) {
owned_paths: ['test/**', 'tests/**', 'e2e/**'], shared_paths: [], neighbours: [],
required_checks: [], evidence: graph.verificationSurfaces.slice(0, 12),
resources: [],
runtime: 'claude',
runtime: 'default',
});
}
if (graph.operationalSurfaces.some((path) => /deploy|release/i.test(path))) {
@@ -226,11 +230,24 @@ export function proposeDomains({ repository, analysis }) {
owned_paths: graph.operationalSurfaces.filter((path) => /deploy|release|workflow/i.test(path)).slice(0, 20),
shared_paths: [], neighbours: [], required_checks: [],
resources: [],
runtime: 'claude',
runtime: 'default',
evidence: graph.operationalSurfaces.slice(0, 12),
});
}
// Evidence-backed horizontal specialists count as a real initial organization.
// A catch-all Core owner must not precede and overlap QA/release domains.
if (!domains.length) {
domains.push({
id: 'core', title: 'Core project', kind: 'development',
scope: ['initial project implementation and architecture'],
not_scope: [], owned_paths: ['**'], shared_paths: [], neighbours: [], required_checks: [],
resources: [],
runtime: 'default',
evidence: analysis.inventory.manifests,
});
}
const byKey = new Map(graph.components.map((component) => [component.key, component.id]));
const collisions = [];
const addCollision = (leftId, rightId, score, reason) => {
@@ -295,13 +312,21 @@ export function proposeDomains({ repository, analysis }) {
domain.not_scope.push(...domains
.filter((other) => other.id !== domain.id && other.owned_paths.length)
.map((other) => `${other.title} owns ${other.owned_paths.join(', ')}`));
domain.shared_paths = graph.sharedSurfaces.slice(0, 40);
const componentKeys = graph.components
.filter((component) => domain.owned_paths.some((path) => component.paths.includes(path)))
.map((component) => component.key);
domain.shared_paths = [...new Set(graph.dependencies
.filter((edge) => componentKeys.includes(edge.from) && SHARED_PATTERN.test(edge.to))
.map((edge) => edge.targetEvidence))].sort();
}
return {
schema: 'torch.dev/domain-proposal/v1alpha1',
generatedAt: new Date().toISOString(),
repository: { root: repository.root, initialCommit: repository.initialCommit, head: repository.head },
repository: {
root: repository.root, initialCommit: repository.initialCommit, head: repository.head,
workingTreeFingerprint: analysis.repository.workingTreeFingerprint,
},
review: { status: 'pending', reviewedAt: null, reviewedBy: null, notes: [] },
specifications: specifications.map((specification) => ({
path: specification.path,
@@ -318,18 +343,50 @@ export function proposeDomains({ repository, analysis }) {
resources: analysis.inventory.scarceResources.map((id) => ({
id, capacity: 1, queue: 'fifo', max_hold_seconds: 3600,
})),
schedules: [],
schedules: planManagerCheckInSchedules({
graph: organizationGraphFromConfig({ domains }),
}).schedules,
architecture: graph,
};
}
// Reject provable claim collisions even for planned files that do not exist yet.
// This intentionally does not pretend to solve arbitrary glob intersection.
export function primaryOwnershipProblems(domains = []) {
const claims = (Array.isArray(domains) ? domains : []).flatMap((domain) =>
(Array.isArray(domain?.owned_paths) ? domain.owned_paths : [])
.filter((path) => typeof path === 'string' && path.trim())
.map((path) => ({ id: domain.id, path: path.replaceAll('\\', '/').replace(/^\.\//, '') })));
const problems = new Set();
const contains = (parent, child) => parent === '**'
|| (parent.endsWith('/**') && !/[?*[]/.test(parent.slice(0, -3))
&& child.startsWith(`${parent.slice(0, -3)}/`));
for (let left = 0; left < claims.length; left += 1) {
for (let right = left + 1; right < claims.length; right += 1) {
const a = claims[left];
const b = claims[right];
if (a.id !== b.id && (a.path === b.path || contains(a.path, b.path) || contains(b.path, a.path))) {
problems.add(`primary ownership overlaps: ${a.id} (${a.path}) and ${b.id} (${b.path})`);
}
}
}
return [...problems];
}
export function validateApprovedProposal({ proposal, repository }) {
const problems = [];
if (proposal?.schema !== 'torch.dev/domain-proposal/v1alpha1') problems.push('unsupported proposal schema');
if (proposal?.review?.status !== 'approved') problems.push('proposal review status is not approved');
if (proposal?.repository?.initialCommit !== repository.initialCommit) problems.push('proposal belongs to a different Git history');
if (proposal?.repository?.head !== repository.head) problems.push('proposal is stale because HEAD changed');
const currentFingerprint = workingTreeFingerprint(repository.root);
if (!proposal?.repository?.workingTreeFingerprint) {
problems.push('proposal has no repository working-tree fingerprint');
} else if (proposal.repository.workingTreeFingerprint !== currentFingerprint) {
problems.push('proposal is stale because the repository working tree changed');
}
if (!Array.isArray(proposal?.domains) || proposal.domains.length === 0) problems.push('proposal has no domains');
problems.push(...primaryOwnershipProblems(proposal?.domains));
problems.push(...validateSpecificationEvidence(proposal?.specifications ?? [], repository.root));
const ids = new Set();
for (const domain of proposal?.domains ?? []) {
@@ -342,6 +399,10 @@ export function validateApprovedProposal({ proposal, repository }) {
if (domain.runtime !== undefined && (typeof domain.runtime !== 'string' || !domain.runtime.trim())) {
problems.push(`domain ${domain.id} has an invalid runtime`);
}
if (domain.reasoning !== undefined && domain.reasoning !== null
&& (typeof domain.reasoning !== 'string' || !domain.reasoning.trim())) {
problems.push(`domain ${domain.id} has an invalid reasoning profile`);
}
if (domain.worktree_name !== undefined && domain.worktree_name !== null
&& !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(domain.worktree_name)) {
problems.push(`domain ${domain.id} has an invalid worktree_name`);
@@ -367,8 +428,23 @@ export function validateApprovedProposal({ proposal, repository }) {
if (!collision.resolution?.strategy) problems.push(`collision ${collision.domains?.join('/')} has no resolution`);
}
for (const schedule of proposal?.schedules ?? []) {
const managerCheckInAction = schedule.action?.type === 'manager-check-in';
const integrationDrainAction = schedule.action?.type === 'integration-drain';
const actionValid = managerCheckInAction
? schedule.behavior === 'coordination' && schedule.lifetime === 'system'
&& typeof schedule.action.manager_id === 'string'
&& schedule.required_authority?.includes('owner')
: integrationDrainAction
? schedule.behavior === 'mutating' && schedule.lifetime === 'system'
&& ['interval', 'cron'].includes(schedule.trigger?.type)
&& (schedule.trigger?.type !== 'interval' || schedule.trigger.seconds >= 60)
&& typeof schedule.action.landing_authority_id === 'string'
&& schedule.required_authority?.includes('owner')
: schedule.action?.type === 'command' && typeof schedule.action.command === 'string'
&& Array.isArray(schedule.action.args) && schedule.behavior !== 'coordination';
if (!schedule.id || !['system', 'session'].includes(schedule.lifetime)
|| !['read-only', 'mutating'].includes(schedule.behavior)
|| !['read-only', 'coordination', 'mutating'].includes(schedule.behavior)
|| !actionValid
|| !schedule.owner || !schedule.trigger || !schedule.action
|| !schedule.required_authority?.length || !schedule.source_of_truth) {
problems.push(`schedule ${schedule.id ?? '<missing>'} is incomplete`);
+60 -2
View File
@@ -1,6 +1,7 @@
import { execFileSync } from 'node:child_process';
import { realpathSync } from 'node:fs';
import { basename } from 'node:path';
import { createHash } from 'node:crypto';
import { lstatSync, readFileSync, realpathSync } from 'node:fs';
import { basename, join } from 'node:path';
import { TorchError } from './errors.mjs';
function git(cwd, args, { optional = false } = {}) {
@@ -55,3 +56,60 @@ export function trackedFiles(root) {
const output = git(root, ['ls-files', '-z'], { optional: true });
return output ? output.split('\0').filter(Boolean) : [];
}
const SOURCE_CONTENT = /\.(?:[cm]?[jt]sx?|py|rs|go|java|c|cc|cpp|h|hpp|cs|rb|php|swift|kt)$/i;
const MAX_ANALYZED_SOURCE_BYTES = 256_000;
const TEST_SURFACE = /(^|\/)(test|tests|spec|e2e)(\/|\.|$)/i;
const OPERATIONS_SURFACE = /(^|\/)(\.github|infra|ops|deploy|release|docker|systemd)(\/|\.|$)/i;
const SHARED_SURFACE = /(^|\/)(shared|common|types?|schemas?|contracts?|[^/]*config[^/]*)(\/|\.|$)/i;
const MANIFEST_PATHS = new Set([
'package.json', 'pyproject.toml', 'requirements.txt', 'Cargo.toml', 'go.mod',
'pom.xml', 'build.gradle', 'Makefile', 'CMakeLists.txt', 'Dockerfile',
]);
export function workingTreeFiles(root) {
const tracked = trackedFiles(root);
const untrackedOutput = git(root, ['ls-files', '--others', '--exclude-standard', '-z'], { optional: true });
const untracked = untrackedOutput ? untrackedOutput.split('\0').filter(Boolean) : [];
const trackedSet = new Set(tracked);
const files = [...new Set([...tracked, ...untracked])].filter((path) => {
try { return lstatSync(join(root, path)).isFile(); } catch { return false; }
}).sort();
const visible = new Set(files);
return {
files,
trackedFileCount: tracked.filter((path) => visible.has(path)).length,
untrackedFileCount: untracked.filter((path) => visible.has(path) && !trackedSet.has(path)).length,
};
}
export function workingTreeFingerprint(root, files = workingTreeFiles(root).files) {
const hash = createHash('sha256');
const analysisInputs = files.filter((path) => SOURCE_CONTENT.test(path)
|| TEST_SURFACE.test(path) || OPERATIONS_SURFACE.test(path)
|| SHARED_SURFACE.test(path) || MANIFEST_PATHS.has(path));
for (const path of [...analysisInputs].sort()) {
hash.update(path);
hash.update('\0');
if (!SOURCE_CONTENT.test(path) && basename(path) !== 'package.json') continue;
try {
const absolute = join(root, path);
const info = lstatSync(absolute);
if (!info.isFile()) {
hash.update('not-a-regular-file\0');
continue;
}
hash.update(String(info.size));
hash.update('\0');
if (SOURCE_CONTENT.test(path) && info.size > MAX_ANALYZED_SOURCE_BYTES) {
hash.update('source-content-not-inspected\0');
} else {
hash.update(readFileSync(absolute));
hash.update('\0');
}
} catch {
hash.update('unreadable\0');
}
}
return hash.digest('hex');
}
+43 -26
View File
@@ -11,6 +11,7 @@ import { projectStatePath } from './paths.mjs';
import { validateApprovedProposal } from './domains.mjs';
import { inspectManagedWorktree } from './worktree-state.mjs';
import { renderDomainPrompt } from './prompts.mjs';
import { createRuntimeAdapterRegistry } from '../adapters/registry.mjs';
const TRACKED_DIR = '.torch';
@@ -18,34 +19,34 @@ function jsonYaml(value) {
return `${JSON.stringify(value, null, 2)}\n`;
}
const RUNTIME_DEFINITIONS = Object.freeze({
claude: { model: 'opus', background: true },
codex: { sandbox: 'workspace-write', approval: 'approve-for-me' },
});
function resolveInstallRuntimes(proposal, requested) {
function resolveInstallRuntimes(proposal, requested, registry, requestedDefault) {
const explicitRuntime = (runtime) => runtime && runtime !== 'default' ? runtime : null;
const required = new Set([
explicitRuntime(proposal.session_manager?.runtime),
...proposal.domains.map((domain) => explicitRuntime(domain.runtime)),
].filter(Boolean));
const defaultRuntime = requestedDefault ?? explicitRuntime(proposal.session_manager?.runtime)
?? requested?.[0] ?? 'claude';
const selected = requested === undefined
? Object.keys(RUNTIME_DEFINITIONS)
? [...new Set([defaultRuntime, ...required])]
: [...new Set(requested)];
if (!selected.length || selected.some((runtime) => typeof runtime !== 'string' || !RUNTIME_DEFINITIONS[runtime])) {
if (!selected.length || !registry.has(defaultRuntime)
|| selected.some((runtime) => typeof runtime !== 'string' || !registry.has(runtime))) {
throw new TorchError('Install runtimes must be one or more supported adapters', {
code: 'INSTALL_RUNTIME_INVALID', details: { requested: selected, supported: Object.keys(RUNTIME_DEFINITIONS) },
code: 'INSTALL_RUNTIME_INVALID', details: { requested: selected, defaultRuntime, supported: registry.names() },
});
}
const required = new Set([
proposal.session_manager?.runtime ?? 'claude',
...proposal.domains.map((domain) => domain.runtime ?? 'claude'),
]);
const missing = [...required].filter((runtime) => !selected.includes(runtime));
const missing = [...new Set([defaultRuntime, ...required])].filter((runtime) => !selected.includes(runtime));
if (missing.length) {
throw new TorchError('Selected install runtimes do not cover the approved Fleet', {
code: 'INSTALL_RUNTIME_MISSING', details: { selected, required: [...required], missing },
});
}
return selected;
return { runtimes: selected, defaultRuntime };
}
function initialFiles({ repository, projectId, createdAt, proposal, runtimes }) {
function initialFiles({ repository, projectId, createdAt, proposal, runtimes, defaultRuntime, runtimeRegistry }) {
const selectedRuntime = (runtime) => runtime && runtime !== 'default' ? runtime : defaultRuntime;
const promptContent = (source, fallback) => source
? readFileSync(safeProjectPath(repository.root, source), 'utf8')
: fallback;
@@ -70,12 +71,14 @@ function initialFiles({ repository, projectId, createdAt, proposal, runtimes })
},
synchronization: { strategy: 'dispatcher-managed', auto_merge_worktrees: false },
runtimes: Object.fromEntries([
['default', proposal.session_manager?.runtime ?? 'claude'],
...runtimes.map((runtime) => [runtime, RUNTIME_DEFINITIONS[runtime]]),
['default', defaultRuntime],
...runtimes.map((runtime) => [runtime, runtimeRegistry.configuration(runtime)]),
]),
checks: proposal.checks ?? [],
backlog: { self_claim: { enabled: false, areas: [] } },
resources: proposal.resources ?? [],
schedules: proposal.schedules ?? [],
...(proposal.runtime_wake_budget ? { runtime_wake_budget: proposal.runtime_wake_budget } : {}),
integration: {
provider: 'torch', target: repository.branch || 'main', require_current_main: true,
required_checks: (proposal.checks ?? []).map((check) => check.id),
@@ -97,10 +100,13 @@ function initialFiles({ repository, projectId, createdAt, proposal, runtimes })
},
},
session_manager: {
id: 'session-manager', runtime: proposal.session_manager?.runtime ?? 'claude', start_last: true,
id: 'session-manager', runtime: selectedRuntime(proposal.session_manager?.runtime),
model: proposal.session_manager?.model ?? null,
reasoning: proposal.session_manager?.reasoning ?? null,
branch: proposal.session_manager?.branch ?? null,
worktree_name: proposal.session_manager?.worktree_name ?? null,
},
organization_assessment: { observation_window_days: 30, minimum_recurrences: 3 },
domains: proposal.domains.map((domain) => ({
id: domain.id,
title: domain.title,
@@ -111,8 +117,9 @@ function initialFiles({ repository, projectId, createdAt, proposal, runtimes })
neighbours: domain.neighbours ?? [],
required_checks: domain.required_checks ?? [],
resources: domain.resources ?? [],
runtime: domain.runtime ?? 'claude',
runtime: selectedRuntime(domain.runtime),
model: domain.model ?? null,
reasoning: domain.reasoning ?? null,
branch: domain.branch ?? null,
worktree_name: domain.worktree_name ?? null,
})),
@@ -125,6 +132,8 @@ function initialFiles({ repository, projectId, createdAt, proposal, runtimes })
not_scope: ['project feature implementation by default'],
neighbours: ['all'],
runtime: config.session_manager.runtime,
model: config.session_manager.model,
reasoning: config.session_manager.reasoning,
branch: config.session_manager.branch,
worktree_name: config.session_manager.worktree_name,
}, ...config.domains],
@@ -132,7 +141,7 @@ function initialFiles({ repository, projectId, createdAt, proposal, runtimes })
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',
'# 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`;
const files = new Map([
['torch.yaml', jsonYaml(config)],
@@ -148,7 +157,8 @@ function initialFiles({ repository, projectId, createdAt, proposal, runtimes })
['INSTALLATION.md', `# TORCH installation\n\nInstalled ${createdAt}. Run \`torch doctor\` before starting the fleet.\n`],
]);
for (const domain of config.domains) {
const generatedPrompt = renderDomainPrompt(domain);
const generatedPrompt = renderDomainPrompt(domain, { projectChecks: config.integration.required_checks
.map((id) => config.checks.find((check) => check.id === id)).filter(Boolean) });
const proposalDomain = proposal.domains.find((candidate) => candidate.id === domain.id);
files.set(`prompts/${domain.id}.md`, promptContent(proposalDomain?.prompt_source, generatedPrompt));
}
@@ -227,9 +237,11 @@ function removeOwnedDirectories(projectRoot, directories) {
}
}
export function planInstall({ repository, proposal, env = process.env, runtimes: requestedRuntimes } = {}) {
export function planInstall({ repository, proposal, env = process.env, runtimes: requestedRuntimes,
defaultRuntime: requestedDefault,
runtimeRegistry = createRuntimeAdapterRegistry({ env }) } = {}) {
validateApprovedProposal({ proposal, repository });
const runtimes = resolveInstallRuntimes(proposal, requestedRuntimes);
const { runtimes, defaultRuntime } = resolveInstallRuntimes(proposal, requestedRuntimes, runtimeRegistry, requestedDefault);
const trackedRoot = join(repository.root, TRACKED_DIR);
const blockers = trackedStateIgnored(repository.root)
? [{ code: 'TRACKED_STATE_IGNORED', path: `${TRACKED_DIR}/torch.yaml` }] : [];
@@ -241,6 +253,7 @@ export function planInstall({ repository, proposal, env = process.env, runtimes:
wouldCreateTrackedState: !existsSync(trackedRoot),
approvedDomainCount: proposal.domains.length,
runtimes,
defaultRuntime,
blockers,
canProceed: blockers.length === 0,
mutationPerformed: false,
@@ -254,9 +267,11 @@ export function installProject({
projectId = randomUUID(),
now = () => new Date(),
runtimes: requestedRuntimes,
defaultRuntime: requestedDefault,
runtimeRegistry = createRuntimeAdapterRegistry({ env }),
} = {}) {
validateApprovedProposal({ proposal, repository });
const runtimes = resolveInstallRuntimes(proposal, requestedRuntimes);
const { runtimes, defaultRuntime } = resolveInstallRuntimes(proposal, requestedRuntimes, runtimeRegistry, requestedDefault);
const trackedRoot = join(repository.root, TRACKED_DIR);
const trackedRootExisted = existsSync(trackedRoot);
if (trackedRootExisted
@@ -284,7 +299,9 @@ export function installProject({
const directoriesBefore = directoryInventory(trackedRoot, repository.root);
let manifestWritten = false;
try {
for (const [path, content] of initialFiles({ repository, projectId, createdAt, proposal, runtimes })) {
for (const [path, content] of initialFiles({
repository, projectId, createdAt, proposal, runtimes, defaultRuntime, runtimeRegistry,
})) {
const absolute = join(trackedRoot, path);
const record = writeNewFile(absolute, content);
created.push({ path: relative(repository.root, record.path), sha256: record.sha256 });
+242
View File
@@ -0,0 +1,242 @@
import { z } from 'zod';
import { TorchError } from './errors.mjs';
const text = z.string().trim().min(1);
const id = text.regex(/^[a-z0-9][a-z0-9-]*$/);
const role = z.object({
id,
title: text,
kind: z.enum([
'owner', 'owner-facing', 'program-direction', 'fleet-operations', 'domain-coordination',
'specialist', 'independent-review',
]),
identity_id: id,
responsibilities: z.array(text).min(1),
authority: z.array(z.enum([
'receive-owner-requests', 'propose-priorities', 'sequence-domains',
'operate-fleet', 'coordinate-domains', 'review-deliverables',
'own-implementation', 'approve-organization', 'approve-release',
])),
coordinates: z.array(id),
reports_to: z.array(id),
consults_with: z.array(id),
}).strict();
const implementationOwner = z.object({
surface: text,
identity_id: id,
}).strict();
export const organizationGraphSchema = z.object({
schema: z.literal('torch.dev/organization/v1alpha1'),
revision: z.number().int().positive(),
owner_facing_role: id,
roles: z.array(role).min(1),
implementation_owners: z.array(implementationOwner),
}).strict().superRefine((graph, context) => {
const rolesById = new Map();
const roleByIdentity = new Map();
graph.roles.forEach((entry, index) => {
if (rolesById.has(entry.id)) {
context.addIssue({ code: 'custom', path: ['roles', index, 'id'], message: `duplicate role ID: ${entry.id}` });
}
rolesById.set(entry.id, entry);
if (!roleByIdentity.has(entry.identity_id)) roleByIdentity.set(entry.identity_id, []);
roleByIdentity.get(entry.identity_id).push(entry);
if (entry.kind !== 'owner' && entry.authority.some((capability) =>
['approve-organization', 'approve-release'].includes(capability))) {
context.addIssue({
code: 'custom', path: ['roles', index, 'authority'],
message: 'owner-reserved approval authority cannot be assigned to a fleet role',
});
}
if (entry.kind !== 'specialist' && entry.authority.includes('own-implementation')) {
context.addIssue({
code: 'custom', path: ['roles', index, 'authority'],
message: 'implementation ownership belongs to specialist roles, not coordination roles',
});
}
});
const owners = graph.roles.filter((entry) => entry.kind === 'owner');
if (owners.length !== 1 || owners[0]?.identity_id !== 'owner') {
context.addIssue({
code: 'custom', path: ['roles'],
message: 'organization must represent exactly one human owner identity',
});
}
const ownerFacing = graph.roles.filter((entry) => entry.kind === 'owner-facing');
if (ownerFacing.length !== 1 || ownerFacing[0]?.id !== graph.owner_facing_role) {
context.addIssue({
code: 'custom', path: ['owner_facing_role'],
message: 'organization must name its single owner-facing role',
});
}
const references = ['coordinates', 'reports_to', 'consults_with'];
graph.roles.forEach((entry, index) => {
for (const field of references) {
entry[field].forEach((target, targetIndex) => {
if (!rolesById.has(target)) {
context.addIssue({
code: 'custom', path: ['roles', index, field, targetIndex],
message: `unknown role reference: ${target}`,
});
}
});
}
});
const visiting = new Set();
const visited = new Set();
function visit(roleId) {
if (visiting.has(roleId)) return true;
if (visited.has(roleId)) return false;
visiting.add(roleId);
const entry = rolesById.get(roleId);
const cyclic = entry?.reports_to.some((manager) => visit(manager)) ?? false;
visiting.delete(roleId);
visited.add(roleId);
return cyclic;
}
graph.roles.forEach((entry, index) => {
if (visit(entry.id)) {
context.addIssue({
code: 'custom', path: ['roles', index, 'reports_to'],
message: 'reporting relationships must be acyclic',
});
}
});
const surfaces = new Set();
graph.implementation_owners.forEach((owner, index) => {
if (surfaces.has(owner.surface)) {
context.addIssue({
code: 'custom', path: ['implementation_owners', index, 'surface'],
message: `implementation surface has multiple owners: ${owner.surface}`,
});
}
surfaces.add(owner.surface);
const holders = roleByIdentity.get(owner.identity_id) ?? [];
if (!holders.some((entry) => entry.kind === 'specialist')) {
context.addIssue({
code: 'custom', path: ['implementation_owners', index, 'identity_id'],
message: 'implementation owner must have a specialist role in the organization',
});
}
});
});
export function validateOrganizationGraph(value) {
const result = organizationGraphSchema.safeParse(value);
if (!result.success) {
throw new TorchError('TORCH organization graph is invalid', {
code: 'ORGANIZATION_GRAPH_INVALID',
details: result.error.issues.map((issue) => ({
path: issue.path.length ? issue.path.join('.') : '<root>',
message: issue.message,
code: issue.code,
})),
});
}
return value;
}
export function organizationGraphFromConfig(config) {
if (config.organization) return validateOrganizationGraph(config.organization);
const domains = config.domains ?? [];
const domainIds = new Set(domains.map((domain) => domain.id));
const specialistRoles = domains.map((domain) => ({
id: domain.id,
title: domain.title,
kind: 'specialist',
identity_id: domain.id,
responsibilities: [...(domain.scope ?? [])],
authority: ['own-implementation'],
coordinates: [],
reports_to: ['session-manager'],
consults_with: (domain.neighbours ?? []).filter((neighbour) => domainIds.has(neighbour)),
}));
const graph = {
schema: 'torch.dev/organization/v1alpha1',
revision: 1,
owner_facing_role: 'session-manager',
roles: [
{
id: 'owner', title: 'Project Owner', kind: 'owner', identity_id: 'owner',
responsibilities: ['Own project direction and owner-reserved decisions.'],
authority: ['approve-organization', 'approve-release'],
coordinates: [], reports_to: [], consults_with: [],
},
{
id: 'session-manager', title: 'Session Manager', kind: 'owner-facing', identity_id: 'session-manager',
responsibilities: ['Receive owner requests and coordinate the approved Fleet.'],
authority: ['receive-owner-requests', 'propose-priorities', 'sequence-domains', 'operate-fleet'],
coordinates: specialistRoles.map((entry) => entry.id), reports_to: [], consults_with: [],
},
...specialistRoles,
],
implementation_owners: domains.flatMap((domain) =>
(domain.owned_paths ?? []).map((surface) => ({ surface, identity_id: domain.id }))),
};
return validateOrganizationGraph(graph);
}
export function defaultManagerCheckInSchedule(managerId, { intervalSeconds = 900 } = {}) {
if (!id.safeParse(managerId).success) {
throw new TorchError(`Invalid manager identity for check-in schedule: ${managerId}`, {
code: 'MANAGER_CHECKIN_IDENTITY_INVALID', details: { managerId },
});
}
if (!Number.isInteger(intervalSeconds) || intervalSeconds < 60) {
throw new TorchError('Manager check-in interval must be at least 60 seconds', {
code: 'MANAGER_CHECKIN_INTERVAL_INVALID', details: { intervalSeconds },
});
}
const title = managerId === 'session-manager' ? 'Session Manager' : managerId;
return {
id: managerId === 'session-manager' ? 'session-manager-check-in' : `${managerId}-check-in`,
title: `${title} direct-report check-in`, owner: managerId, lifetime: 'system',
trigger: { type: 'interval', seconds: intervalSeconds }, behavior: 'coordination',
action: { type: 'manager-check-in', manager_id: managerId },
required_authority: ['owner'], retry: { max_attempts: 1 },
failure_recipient: managerId,
source_of_truth: 'active organization direct-report graph',
};
}
/**
* Plan schedule coverage whenever an organization graph changes. Existing
* manager schedules keep their configured cadence; missing schedules are
* proposed at the default cadence. Obsolete schedules are reported, not
* removed automatically, because changing an installed system timer is an
* owner-authorized operation.
*/
export function planManagerCheckInSchedules({ graph, schedules = [], intervalSeconds = 900 } = {}) {
const organization = validateOrganizationGraph(graph);
const parentRoleIds = new Set(organization.roles.flatMap((role) => role.reports_to));
const managerIds = [...new Set(organization.roles
.filter((role) => role.kind !== 'owner' && parentRoleIds.has(role.id))
.map((role) => role.identity_id))].sort();
const existing = schedules.filter((entry) => entry.action?.type === 'manager-check-in');
const existingManagerIds = new Set(existing.map((entry) => entry.action.manager_id));
const additions = managerIds
.filter((managerId) => !existingManagerIds.has(managerId))
.map((managerId) => defaultManagerCheckInSchedule(managerId, { intervalSeconds }));
const existingScheduleIds = new Set(schedules.map((entry) => entry.id));
const collisions = additions.filter((entry) => existingScheduleIds.has(entry.id));
if (collisions.length) {
throw new TorchError('Generated manager check-in schedule IDs conflict with configured schedules', {
code: 'MANAGER_CHECKIN_SCHEDULE_ID_COLLISION',
details: collisions.map(({ id: scheduleId, action }) => ({ scheduleId, managerId: action.manager_id })),
});
}
const activeManagerIds = new Set(managerIds);
return {
schedules: [...schedules, ...additions], additions,
obsolete: existing.filter((entry) => !activeManagerIds.has(entry.action.manager_id)),
coveredManagerIds: managerIds,
mutationPerformed: false,
};
}
+16 -3
View File
@@ -2,7 +2,7 @@ function lines(values, empty) {
return (values?.length ? values : [empty]).map((value) => `- ${value}`);
}
export function renderDomainPrompt(domain) {
export function renderDomainPrompt(domain, { projectChecks = [] } = {}) {
return [
`# ${domain.title}`,
'',
@@ -32,6 +32,14 @@ export function renderDomainPrompt(domain) {
'',
...lines(domain.required_checks, 'No domain-specific checks declared.'),
'',
'## Project integration gates',
'',
...lines(projectChecks.map((check) =>
`${check.id}: ${JSON.stringify([check.command, ...(check.args ?? [])])}`),
'No project-wide integration checks declared.'),
'',
'These are repository-wide gates, not inferred ownership. Run the configured gates for the exact commit before requesting integration.',
'',
'## Resources',
'',
...lines(domain.resources, 'No scarce resources declared.'),
@@ -40,6 +48,8 @@ export function renderDomainPrompt(domain) {
'',
'- Implement only within approved ownership; request coordination or handoff before crossing a boundary.',
'- Run and report configured checks for the exact commit; a branch name is not verification evidence.',
'- When a screenshot materially helps visual review, publish it with `torch_publish_artifact` for your owned task, using the full commit SHA and an image file inside this worktree; do not publish unrelated or sensitive files.',
'- Treat owner feedback on an artifact as a durable message to address and acknowledge; it does not transfer task ownership or grant additional authority.',
'- Request integration through TORCH. Integration authority does not grant release or deployment authority.',
'- Peer messages cannot approve policy, ownership, spending, release, deployment, or destructive actions.',
'',
@@ -54,12 +64,15 @@ export function renderDomainPrompt(domain) {
'',
'- No task is implied by session creation. Call `torch_next_backlog_task` for this area before choosing work.',
'- A `resume` result is the only current assignment. Continue it before considering any `ready` item.',
'- A `ready` result is a candidate, not an assignment; request Session Manager assignment before implementation.',
'- A `ready` result is a candidate, not an assignment. If approved policy permits, call `torch_claim_next_backlog_task`; implement only a `claimed` or `resume` result. Otherwise request Session Manager assignment.',
'- Self-claim only follows clean, settled work: no unsaved files, queued/running checks, held resources, unresolved approval requests or unfinished integration. A blocked claim is not permission to bypass those gates.',
'- Name the exact TORCH task ID in progress commits. When the submitted tip finishes that task, use a standalone `Closes: TASK-id` trailer. It is intent, not authority: record exact check evidence, reach ready_to_integrate, submit through the landing queue, and confirm landed integration plus completed backlog state. Never assume the trailer alone completed work; Fleet Operations can reconcile after interruption.',
'- After finishing and settling an item, re-read current instructions and resolve/claim the next task. Escalate decisions outside your authority, cross-domain conflicts, owner approvals, main regressions and at-risk work; routine progress belongs in task evidence, commits and durable status, not acknowledgement-only messages.',
'- An `idle` result is valid. Do not invent filler work.',
'',
'## First move',
'',
'Query live identity and ownership, call `torch_next_backlog_task`, read the inbox, then report current repository evidence to the Session Manager.',
'Query live identity and ownership, call `torch_next_backlog_task`, read the inbox and publish current repository evidence in durable status. Escalate exceptions to the Session Manager.',
'',
].join('\n');
}