Redesign TORCH product and console

This commit is contained in:
thePR0M3TH3AN committed 2026-09-27 17:08:38 -04:00
1 parent 056fab8af4
commit d2837581d9
15 files changed
+827 -6

No files matched your search

+17 -2
View File
@@ -52,10 +52,12 @@ and the first Claude reference-fleet path:
- a read-only COMBATRIG compatibility importer that preserves legacy prompts,
branch/worktree mappings, checks, resources, schedules, release boundaries,
and backlog inventory while refusing to invent missing ownership boundaries.
- a redesigned public product site and read-only local Fleet Console backed by
the same repository and runtime state rather than a separate dashboard model.
Live mixed-provider qualification, stable self-host bootstrapping, COMBATRIG
cutover, scheduling, richer observability, and the Fleet Console remain staged
work.
cutover, scheduling, richer measured context telemetry, and public deployment
remain staged work.
The canonical architecture and delivery plan is
[docs/PORTABLE_AGENT_FLEET_SPEC.md](docs/PORTABLE_AGENT_FLEET_SPEC.md).
@@ -176,6 +178,19 @@ Claude and Codex boundaries. The tests make no paid provider calls. A live
mixed-provider, multi-domain acceptance run remains an explicit
owner-authorized qualification gate.
Run the redesigned public surface and local operational console on loopback:
```bash
npm run site:serve -- --repo /path/to/project --port 4317
```
The public explanation is at `/`; `/console` reads Fleet state without repair
or mutation. Automation can obtain the same read-only view with:
```bash
node bin/torch.mjs console snapshot --repo /path/to/project --json
```
## Product principles
- Git is required; GitHub is optional.
+16
View File
@@ -1054,6 +1054,13 @@ not treat a smaller prompt as success when verification quality declined.
Doctor output MUST distinguish observation from recommended action. Repair
commands require explicit invocation and normal authority checks.
Implementation checkpoint (2026-09-27): a read-only observation service and
`torch console snapshot` now expose project, health, identity, message,
backlog, check, resource, integration, schedule, and audit state without
initializing a missing database. Context-locality output explicitly reports
`unavailable` until runtime usage samples exist, preventing estimates from
masquerading as measurements.
## 22. Scheduling
TORCH recognizes two schedule classes.
@@ -1450,6 +1457,15 @@ COMBATRIG is safely installed and operated through portable TORCH.
Exit gate: the public surface accurately demonstrates behavior proven by the
released product.
Implementation checkpoint (2026-09-27): the Nostr-era landing page and
dashboard were removed. A new responsive public surface explains the
request-to-main flow through an interactive dispatch board, publishes the
current qualification ledger, and presents repository/spec-aware startup. A
separate loopback Fleet Console consumes the read-only observation service,
uses a restrictive content-security policy, and labels context locality as
unmeasured until evidence arrives. Local HTTP, desktop, and mobile rendering
are verified; public deployment and standalone distribution remain open.
## 29. Required acceptance configurations
### A. Laptop-only
+10 -4
View File
@@ -11,6 +11,7 @@
"./adapters/codex": "./src/adapters/codex.mjs",
"./canonical/local": "./src/canonical/local.mjs",
"./checks": "./src/checks/service.mjs",
"./console": "./src/console/server.mjs",
"./control-plane": "./src/control-plane/service.mjs",
"./kernel/analyze": "./src/kernel/analyze.mjs",
"./kernel/git": "./src/kernel/git.mjs",
@@ -20,6 +21,7 @@
"./integration": "./src/integration/service.mjs",
"./importers/combatrig": "./src/importers/combatrig.mjs",
"./mcp": "./src/mcp/tools.mjs",
"./observability": "./src/observability/snapshot.mjs",
"./resources": "./src/resources/service.mjs",
"./runtime/lifecycle": "./src/runtime/lifecycle.mjs",
"./self-host": "./src/self-host/service.mjs"
@@ -51,25 +53,29 @@
"src/backlog/",
"src/canonical/",
"src/checks/",
"src/console/",
"src/control-plane/",
"src/kernel/",
"src/integration/",
"src/importers/",
"src/mcp/",
"src/observability/",
"src/resources/",
"src/runtime/",
"src/self-host/",
"docs/PORTABLE_AGENT_FLEET_SPEC.md",
"README.md",
"LICENSE",
"torch-release.json"
"torch-release.json",
"site/"
],
"scripts": {
"test": "node --test --test-timeout=30000 test/fleet/*.test.mjs",
"torch": "node bin/torch.mjs",
"check": "node --check bin/torch.mjs && node --check src/cli.mjs && node --check src/mcp/server.mjs",
"format": "eslint src/adapters src/backlog src/canonical src/checks src/control-plane src/importers src/integration src/kernel src/mcp src/resources src/runtime src/self-host src/cli.mjs bin/torch.mjs test/fleet --fix",
"lint": "eslint src/adapters src/backlog src/canonical src/checks src/control-plane src/importers src/integration src/kernel src/mcp src/resources src/runtime src/self-host src/cli.mjs bin/torch.mjs test/fleet"
"check": "node --check bin/torch.mjs && node --check src/cli.mjs && node --check src/mcp/server.mjs && node --check src/console/server.mjs && node --check site/app.js && node --check site/console.js",
"site:serve": "node bin/torch.mjs console serve",
"format": "eslint src/adapters src/backlog src/canonical src/checks src/console src/control-plane src/importers src/integration src/kernel src/mcp src/observability src/resources src/runtime src/self-host src/cli.mjs bin/torch.mjs site/*.js test/fleet --fix",
"lint": "eslint src/adapters src/backlog src/canonical src/checks src/console src/control-plane src/importers src/integration src/kernel src/mcp src/observability src/resources src/runtime src/self-host src/cli.mjs bin/torch.mjs site/*.js test/fleet"
},
"dependencies": {
"@modelcontextprotocol/server": "^2.1.0",
@@ -142,6 +142,14 @@ test_integrity_note:
given: a caller outside a selected Git repository with an external specification
when: torch design is invoked with explicit repo and spec paths
then: a pending project-specific proposal is produced without creating TORCH state in the project
- id: SCN-product-site
given: the redesigned TORCH public surface
when: its product story and local-console boundary are inspected
then: it explains project-derived fleets, exact-commit routing, honest open gates, and measured context claims without obsolete product language
- id: SCN-console-readonly
given: an installed Fleet without an initialized control-plane database
when: the observation API and local HTTP console are requested
then: fixed assets and structured state are served with restrictive headers while Git and machine-local state remain unchanged
observable_outcomes:
- Git status before and after read-only analysis
- presence or absence of .torch and XDG-local project state
@@ -150,6 +158,8 @@ test_integrity_note:
- proposal review status, repository commit binding, and generated domain count
- specification digest, evidence-linked responsibility, and stale-spec refusal
- compatibility report counts and unchanged COMBATRIG Git status
- HTTP status, content-security policy, fixed routes, and traversal refusal
- unchanged Git status and absent database before and after console observation
- preservation of project source and modified owner configuration
- SQLite state after process restart
- CLI and MCP output over real process boundaries
@@ -176,6 +186,7 @@ test_integrity_note:
- injected post-pointer interruption with deterministic recovery timestamps
- external temporary Markdown specification with no provider or network calls
- synthetic COMBATRIG layout in an isolated temporary Git repository
- loopback-only ephemeral HTTP server and fixed static route map
anti_cheat_rationale:
prevents:
- init that silently writes files
@@ -205,6 +216,8 @@ test_integrity_note:
- specifications being executed as instructions or changing after owner review
- generic fleet templates replacing project-derived responsibilities
- COMBATRIG compatibility inventing path ownership to force a green migration
- a disconnected dashboard inventing state or creating it during observation
- public copy claiming live qualification that only virtualized tests prove
relaxation:
did_relax_any_assertion: false
if_true_explain_spec_basis: ""
+47
View File
@@ -0,0 +1,47 @@
const routeButton = document.querySelector('#route-toggle');
const steps = [...document.querySelectorAll('.route-step')];
const readout = document.querySelector('#route-readout');
const routeMessages = [
'Owner intent recorded.', 'Session Manager assigned terrain ownership.',
'Terrain and QA coordinate directly.', 'Checks passed for commit 8f6c2e1.',
'Authorized fast-forward reached canonical main.',
];
let routeTimer;
routeButton?.addEventListener('click', () => {
clearInterval(routeTimer);
steps.forEach((step) => step.classList.remove('is-active', 'is-complete'));
routeButton.setAttribute('aria-pressed', 'true');
routeButton.textContent = 'Routing…';
let index = 0;
const advance = () => {
steps.forEach((step, stepIndex) => {
step.classList.toggle('is-complete', stepIndex < index);
step.classList.toggle('is-active', stepIndex === index);
});
readout.textContent = routeMessages[index];
index += 1;
if (index === steps.length) {
clearInterval(routeTimer);
routeButton.setAttribute('aria-pressed', 'false');
routeButton.textContent = 'Run again';
}
};
advance();
routeTimer = setInterval(advance, window.matchMedia('(prefers-reduced-motion: reduce)').matches ? 80 : 850);
});
const commands = {
both: `node bin/torch.mjs design \\\n+ --repo /path/to/project \\\n+ --spec /path/to/product-spec.md \\\n+ --output fleet-proposal.json \\\n+ --json`,
repo: `node bin/torch.mjs design \\\n+ --repo /path/to/project \\\n+ --output fleet-proposal.json \\\n+ --json`,
};
const command = document.querySelector('#start-command');
document.querySelectorAll('[data-command]').forEach((button) => button.addEventListener('click', () => {
document.querySelectorAll('[data-command]').forEach((tab) => tab.setAttribute('aria-selected', String(tab === button)));
command.textContent = commands[button.dataset.command];
}));
document.querySelector('#copy-command')?.addEventListener('click', async (event) => {
await navigator.clipboard.writeText(command.textContent);
event.currentTarget.textContent = 'Copied';
setTimeout(() => { event.currentTarget.textContent = 'Copy command'; }, 1500);
});
+36
View File
@@ -0,0 +1,36 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex,nofollow">
<title>TORCH Fleet Console</title>
<link rel="stylesheet" href="/styles.css">
<script src="/console.js" defer></script>
</head>
<body class="console-body">
<a class="skip-link" href="#console-main">Skip to fleet status</a>
<header class="masthead console-nav">
<a class="wordmark" href="/"><span class="mark">T</span> TORCH</a>
<p>Local Fleet Console</p>
<button id="refresh-console" type="button">Refresh snapshot</button>
</header>
<main id="console-main" class="console-shell" aria-live="polite">
<section class="console-summary">
<div><p>Project</p><strong id="project-name">Loading…</strong></div>
<div><p>Commit</p><strong id="project-commit">—</strong></div>
<div><p>Fleet health</p><strong id="fleet-health">Observing</strong></div>
<div><p>Context locality</p><strong id="context-state">Unmeasured</strong></div>
</section>
<p class="console-error" id="console-error" hidden></p>
<section class="console-grid">
<article class="console-panel agents-panel"><div class="panel-heading"><h1>Fleet</h1><span id="agent-count">0 identities</span></div><div id="agent-list" class="agent-list"></div></article>
<article class="console-panel"><div class="panel-heading"><h2>Backlog</h2><span id="task-count">0 active</span></div><div id="task-list" class="console-list"></div></article>
<article class="console-panel"><div class="panel-heading"><h2>Integration</h2><span>Exact commits</span></div><div id="integration-list" class="console-list"></div></article>
<article class="console-panel"><div class="panel-heading"><h2>Resources</h2><span>FIFO leases</span></div><div id="resource-list" class="console-list"></div></article>
<article class="console-panel messages-panel"><div class="panel-heading"><h2>Messages</h2><span id="message-count">0 queued</span></div><div id="message-list" class="console-list"></div></article>
<article class="console-panel context-panel"><div class="panel-heading"><h2>Context locality</h2><span>Measured, never assumed</span></div><div id="context-detail" class="context-detail"></div></article>
</section>
</main>
</body>
</html>
+71
View File
@@ -0,0 +1,71 @@
const $ = (selector) => document.querySelector(selector);
function empty(message) {
return `<p class="empty-state">${message}</p>`;
}
function safe(value) {
const element = document.createElement('span');
element.textContent = value ?? '';
return element.innerHTML;
}
function render(snapshot) {
const project = snapshot.project ?? snapshot.repository;
$('#project-name').textContent = project?.name ?? project?.id ?? 'Not installed';
$('#project-commit').textContent = snapshot.repository?.head?.slice(0, 9) ?? '—';
$('#fleet-health').textContent = snapshot.mode === 'installed'
? (snapshot.doctor?.healthy ? 'Healthy' : 'Needs attention') : 'Not installed';
$('#context-state').textContent = snapshot.contextLocality?.measured ? 'Measured' : 'Unmeasured';
const agents = snapshot.agents ?? [];
$('#agent-count').textContent = `${agents.length} ${agents.length === 1 ? 'identity' : 'identities'}`;
$('#agent-list').innerHTML = agents.length ? agents.map((agent) => `
<div class="agent-row"><span class="presence ${safe(agent.state)}"></span><div><strong>${safe(agent.title ?? agent.areaId)}</strong><small>${safe(agent.summary ?? `${agent.runtime ?? 'runtime'} · ${agent.state}`)}</small></div><code>${safe(agent.areaId)}</code></div>
`).join('') : empty('Install an approved fleet to see persistent identities.');
const tasks = (snapshot.backlog ?? []).filter((task) => !['completed', 'cancelled'].includes(task.state));
$('#task-count').textContent = `${tasks.length} active`;
$('#task-list').innerHTML = tasks.length ? tasks.slice(0, 12).map((task) => `
<div class="list-row"><div><strong>${safe(task.title ?? task.id)}</strong><small>${safe(task.owner ?? 'unassigned')}</small></div><span>${safe(task.state)}</span></div>
`).join('') : empty('No active tracked work.');
const integrations = snapshot.integration ?? [];
$('#integration-list').innerHTML = integrations.length ? integrations.map((item) => `
<div class="list-row"><div><strong>${safe(item.sourceArea)}</strong><small>${safe(item.sourceCommit?.slice(0, 9))}</small></div><span>${safe(item.state)}</span></div>
`).join('') : empty('No commits waiting to land.');
const holders = snapshot.resources?.holders ?? [];
const waiters = snapshot.resources?.waiters ?? [];
$('#resource-list').innerHTML = holders.length || waiters.length
? [...holders.map((item) => `<div class="list-row"><strong>${safe(item.resourceId)}</strong><span>${safe(item.areaId)} holds</span></div>`),
...waiters.map((item) => `<div class="list-row"><strong>${safe(item.resourceId)}</strong><span>${safe(item.areaId)} waits</span></div>`)].join('')
: empty('All configured resources are available.');
const messages = snapshot.messages?.recent ?? [];
$('#message-count').textContent = `${snapshot.messages?.unacknowledged ?? 0} unacknowledged`;
$('#message-list').innerHTML = messages.length ? messages.map((message) => `
<div class="message-row"><p><strong>${safe(message.sender)}</strong> to ${safe(message.recipient)}</p><span>${safe(message.body)}</span></div>
`).join('') : empty('No durable messages yet.');
const locality = snapshot.contextLocality;
$('#context-detail').innerHTML = locality?.measured
? locality.samples.map((sample) => `<div class="locality-row"><strong>${safe(sample.areaId)}</strong><span>${safe(String(sample.cacheRead ?? 0))} cached tokens</span></div>`).join('')
: `<p>${safe(locality?.reason ?? 'Runtime usage reporting is not available.')}</p><small>TORCH will compare cache activity with verified outcomes, not token count alone.</small>`;
}
async function refresh() {
$('#console-error').hidden = true;
try {
const response = await fetch('/api/snapshot', { cache: 'no-store' });
const snapshot = await response.json();
if (!response.ok) throw new Error(snapshot.message ?? snapshot.error);
render(snapshot);
} catch (error) {
$('#console-error').textContent = `Snapshot unavailable: ${error.message}`;
$('#console-error').hidden = false;
}
}
$('#refresh-console')?.addEventListener('click', refresh);
refresh();
+133
View File
@@ -0,0 +1,133 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="TORCH turns one Git repository into a coordinated team of persistent AI engineering specialists.">
<title>TORCH — one repository, a coordinated AI engineering team</title>
<link rel="stylesheet" href="/styles.css">
<script src="/app.js" defer></script>
</head>
<body>
<a class="skip-link" href="#main">Skip to content</a>
<header class="masthead">
<a class="wordmark" href="/" aria-label="TORCH home"><span class="mark">T</span> TORCH</a>
<nav aria-label="Primary">
<a href="#system">System</a>
<a href="#evidence">Evidence</a>
<a href="#start">Start</a>
<a href="/console">Fleet Console</a>
</nav>
</header>
<main id="main">
<section class="hero" aria-labelledby="hero-title">
<div class="hero-copy">
<p class="status-line"><span></span> Portable fleet alpha</p>
<h1 id="hero-title">One repository.<br>A coordinated AI engineering team.</h1>
<p class="lede">TORCH studies your code and project specification, proposes durable areas of responsibility, and gives each specialist its own branch, worktree, context, and route back to main.</p>
<div class="hero-actions">
<a class="primary-action" href="#start">Design a fleet</a>
<a class="text-action" href="https://github.com/PR0M3TH3AN/TORCH">Read the source</a>
</div>
</div>
<div class="dispatch" aria-label="Interactive TORCH routing board">
<div class="dispatch-head">
<p>Request CR-184</p>
<button id="route-toggle" type="button" aria-pressed="false">Run the route</button>
</div>
<ol class="route" id="route">
<li class="route-step is-ready" data-step="0"><span class="node">A</span><div><strong>Owner request</strong><small>Change terrain streaming without weakening frame checks.</small></div></li>
<li class="route-step" data-step="1"><span class="node">S</span><div><strong>Session Manager</strong><small>Routes ownership and establishes acceptance.</small></div></li>
<li class="route-step split" data-step="2"><span class="node">T</span><div><strong>Terrain specialist</strong><small>Works in its persistent domain context.</small></div><span class="peer">QA peer</span></li>
<li class="route-step" data-step="3"><span class="node">✓</span><div><strong>Exact-commit checks</strong><small>Receipts bind evidence to the measured SHA.</small></div></li>
<li class="route-step" data-step="4"><span class="node">M</span><div><strong>Controlled main</strong><small>Authorized fast-forward after convergence.</small></div></li>
</ol>
<p class="route-readout" id="route-readout" aria-live="polite">Ready at owner request.</p>
</div>
</section>
<section class="principle-strip" aria-label="TORCH principles">
<p>Git required</p><p>GitHub optional</p><p>Provider independent</p><p>Local first</p><p>Human approved</p>
</section>
<section class="system-section" id="system">
<div class="section-intro">
<h2>The project decides the team.</h2>
<p>TORCH does not install a catalog of generic agents. It reads the repository and the specifications you select, then produces a pending organization for you to split, merge, rename, or reject.</p>
</div>
<div class="blueprint">
<article>
<span class="blueprint-index">Input</span>
<h3>Code and intent</h3>
<p>Architecture, tests, operations, scarce resources, and external Markdown specifications become evidence—not instructions to execute.</p>
</article>
<article>
<span class="blueprint-index">Design</span>
<h3>Explicit boundaries</h3>
<p>Every domain receives scope, exclusions, owned paths, neighbors, checks, runtime policy, and collision rules.</p>
</article>
<article>
<span class="blueprint-index">Operate</span>
<h3>Persistent specialists</h3>
<p>Stable identities survive runtime sessions. Durable messages and project state outlive model context.</p>
</article>
</div>
</section>
<section class="context-section">
<div class="context-diagram" aria-hidden="true">
<div class="wide-context">general session<div class="churn"></div><div class="churn second"></div><div class="churn third"></div></div>
<div class="versus">versus</div>
<div class="focused-context"><span>terrain</span><span>runtime</span><span>verification</span></div>
</div>
<div>
<h2>Keep useful context hot.</h2>
<p>A specialist can retain the working set for one coherent area instead of repeatedly loading unrelated project history. TORCH measures cache reads, cache creation, compactions, resumed prompt size, cost, and verified outcomes before calling that an efficiency win.</p>
<p class="truth-note">Context savings are a measured hypothesis, not a marketing claim.</p>
</div>
</section>
<section class="evidence-section" id="evidence">
<div class="section-intro">
<h2>Proof has a commit.</h2>
<p>TORCH distinguishes implementation from qualification. Green output without the right scenario, authority, and SHA is not proof.</p>
</div>
<div class="ledger" role="table" aria-label="Current TORCH qualification ledger">
<div class="ledger-row ledger-head" role="row"><span role="columnheader">Capability</span><span role="columnheader">Current evidence</span><span role="columnheader">State</span></div>
<div class="ledger-row" role="row"><span>Portable kernel and reversal</span><span>Scenario tested</span><strong class="proven">Proven locally</strong></div>
<div class="ledger-row" role="row"><span>Durable control plane and backlog</span><span>SQLite + tracked tasks</span><strong class="proven">Proven locally</strong></div>
<div class="ledger-row" role="row"><span>Checks, resources, integration</span><span>Exact-SHA lifecycle</span><strong class="proven">Proven locally</strong></div>
<div class="ledger-row" role="row"><span>Claude and Codex coordination</span><span>Virtualized adapters</span><strong class="pending">Live gate open</strong></div>
<div class="ledger-row" role="row"><span>Self-managed successor</span><span>Atomic candidate mechanics</span><strong class="pending">Stable boot open</strong></div>
<div class="ledger-row" role="row"><span>COMBATRIG migration</span><span>33 areas, 570 tasks inventoried</span><strong class="pending">Boundaries open</strong></div>
</div>
</section>
<section class="start-section" id="start">
<div>
<h2>Start with the work you actually have.</h2>
<p>Point TORCH at a Git repository, a product specification, or both. Design is read-only; installation waits for your reviewed proposal.</p>
</div>
<div class="command-panel">
<div class="command-tabs" role="tablist" aria-label="Startup input">
<button role="tab" aria-selected="true" data-command="both">Repository + spec</button>
<button role="tab" aria-selected="false" data-command="repo">Repository only</button>
</div>
<pre><code id="start-command">node bin/torch.mjs design \
--repo /path/to/project \
--spec /path/to/product-spec.md \
--output fleet-proposal.json \
--json</code></pre>
<button class="copy-button" id="copy-command" type="button">Copy command</button>
</div>
</section>
</main>
<footer>
<p><strong>TORCH</strong> is an open, local-first agent fleet for Git projects.</p>
<div><a href="https://github.com/PR0M3TH3AN/TORCH">GitHub</a><a href="/console">Local console</a><a href="https://github.com/PR0M3TH3AN/TORCH/blob/main/docs/PORTABLE_AGENT_FLEET_SPEC.md">Specification</a></div>
</footer>
</body>
</html>
+172
View File
@@ -0,0 +1,172 @@
:root {
color-scheme: light;
--surface: #e7eef2;
--paper: #f8fbfc;
--ink: #102a3a;
--muted: #557080;
--line: #a8bbc5;
--signal: #f05a28;
--route: #246bfe;
--field: #2e7d5a;
--shadow: 0 18px 50px rgba(16, 42, 58, 0.12);
--display: "Bahnschrift", "DIN Alternate", "Arial Narrow", sans-serif;
--body: "Source Sans 3", "Noto Sans", "Segoe UI", sans-serif;
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body { margin: 0; background: var(--surface); color: var(--ink); font-family: var(--body); font-size: 17px; line-height: 1.55; }
a { color: inherit; text-underline-offset: 0.22em; }
button { font: inherit; }
.skip-link { position: fixed; left: 1rem; top: -5rem; z-index: 30; background: var(--ink); color: white; padding: .65rem 1rem; }
.skip-link:focus { top: 1rem; }
:focus-visible { outline: 3px solid var(--signal); outline-offset: 3px; }
.masthead { min-height: 78px; display: flex; align-items: center; justify-content: space-between; gap: 2rem; padding: 0 clamp(1.25rem, 4vw, 5rem); border-bottom: 1px solid var(--line); }
.wordmark { display: flex; align-items: center; gap: .7rem; font-family: var(--display); font-weight: 700; letter-spacing: .04em; text-decoration: none; }
.mark { display: grid; place-items: center; width: 2rem; height: 2rem; background: var(--signal); color: white; clip-path: polygon(50% 0, 100% 28%, 82% 100%, 18% 100%, 0 28%); }
nav { display: flex; gap: clamp(1rem, 3vw, 2.4rem); font-size: .92rem; }
nav a { text-decoration: none; }
nav a:hover { text-decoration: underline; }
.hero { min-height: min(calc(100vh - 78px), 900px); display: grid; grid-template-columns: minmax(0, 1fr) minmax(440px, 1fr); align-items: center; gap: clamp(3rem, 6vw, 8rem); padding: clamp(4rem, 7vw, 7rem) clamp(1.25rem, 6vw, 7rem); }
.hero-copy { max-width: 700px; }
.status-line { display: flex; align-items: center; gap: .65rem; color: var(--muted); font-weight: 650; }
.status-line span { width: .72rem; height: .72rem; border-radius: 50%; background: var(--field); box-shadow: 0 0 0 .28rem rgba(46, 125, 90, .15); }
h1, h2, h3 { font-family: var(--display); font-weight: 650; line-height: .98; margin: 0; }
h1 { font-size: clamp(3.3rem, 6vw, 6.6rem); letter-spacing: -.055em; max-width: 12ch; }
h2 { font-size: clamp(2.5rem, 5vw, 5.2rem); letter-spacing: -.04em; max-width: 13ch; }
h3 { font-size: 1.55rem; line-height: 1.15; }
.lede { max-width: 630px; font-size: clamp(1.1rem, 1.6vw, 1.38rem); color: #2e4a5a; margin: 2rem 0; }
.hero-actions { display: flex; align-items: center; gap: 1.5rem; }
.primary-action { display: inline-flex; background: var(--ink); color: white; padding: .82rem 1.15rem; text-decoration: none; font-weight: 700; }
.primary-action:hover { background: var(--route); }
.text-action { font-weight: 650; }
.dispatch { background: var(--paper); border: 1px solid var(--ink); box-shadow: var(--shadow); }
.dispatch-head { display: flex; justify-content: space-between; align-items: center; padding: 1rem 1.2rem; border-bottom: 1px solid var(--line); }
.dispatch-head p { margin: 0; font-family: var(--display); font-weight: 650; }
.dispatch-head button, .console-nav button, .copy-button { border: 1px solid var(--ink); background: transparent; color: var(--ink); padding: .45rem .72rem; cursor: pointer; font-weight: 650; }
.dispatch-head button:hover, .console-nav button:hover, .copy-button:hover { background: var(--ink); color: white; }
.route { list-style: none; margin: 0; padding: 1.2rem 1.4rem; }
.route-step { position: relative; display: grid; grid-template-columns: 44px 1fr auto; align-items: center; gap: 1rem; min-height: 88px; opacity: .58; }
.route-step:not(:last-child)::after { content: ""; position: absolute; left: 21px; top: 65px; height: 48px; width: 2px; background: var(--line); }
.route-step.is-active, .route-step.is-complete, .route-step.is-ready { opacity: 1; }
.route-step.is-active .node { background: var(--signal); color: white; border-color: var(--signal); transform: scale(1.12); }
.route-step.is-complete .node { background: var(--field); color: white; border-color: var(--field); }
.route-step.is-complete::after { background: var(--field); }
.node { display: grid; place-items: center; width: 44px; height: 44px; border: 2px solid var(--route); border-radius: 50%; font-family: var(--display); font-weight: 700; transition: transform .2s, background .2s; }
.route-step strong, .route-step small { display: block; }
.route-step small { color: var(--muted); margin-top: .15rem; }
.peer { border: 1px solid var(--route); color: var(--route); padding: .25rem .5rem; font-size: .78rem; }
.route-readout { margin: 0; padding: .85rem 1.4rem; border-top: 1px solid var(--line); color: var(--muted); font-size: .9rem; }
.principle-strip { display: grid; grid-template-columns: repeat(5, 1fr); border-block: 1px solid var(--line); }
.principle-strip p { margin: 0; padding: 1rem; text-align: center; border-right: 1px solid var(--line); font-size: .9rem; font-weight: 650; }
.principle-strip p:last-child { border: 0; }
.system-section, .evidence-section { padding: clamp(5rem, 10vw, 10rem) clamp(1.25rem, 6vw, 7rem); }
.section-intro { display: grid; grid-template-columns: 1fr 1fr; gap: 3rem; align-items: end; margin-bottom: 4rem; }
.section-intro p { margin: 0; max-width: 650px; font-size: 1.18rem; color: #2e4a5a; }
.blueprint { display: grid; grid-template-columns: repeat(3, 1fr); background-image: linear-gradient(var(--line) 1px, transparent 1px), linear-gradient(90deg, var(--line) 1px, transparent 1px); background-size: 24px 24px; border: 1px solid var(--line); }
.blueprint article { min-height: 330px; padding: clamp(1.5rem, 3vw, 3rem); background: rgba(248, 251, 252, .93); border-right: 1px solid var(--line); }
.blueprint article:last-child { border: 0; }
.blueprint-index { display: inline-block; margin-bottom: 5rem; color: var(--route); border-bottom: 2px solid currentColor; font-weight: 700; }
.blueprint p { color: var(--muted); }
.context-section { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(3rem, 7vw, 8rem); align-items: center; padding: clamp(5rem, 9vw, 9rem) clamp(1.25rem, 6vw, 7rem); background: var(--ink); color: var(--paper); }
.context-section p { max-width: 650px; color: #c1d0d8; font-size: 1.14rem; }
.truth-note { display: inline-block; border-left: 3px solid var(--signal); padding-left: 1rem; }
.context-diagram { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 1rem; min-height: 360px; }
.wide-context { position: relative; display: grid; place-items: center; height: 250px; border: 1px solid #6e8795; overflow: hidden; font-family: var(--display); }
.churn { position: absolute; width: 180%; height: 18px; background: var(--signal); transform: rotate(23deg); opacity: .72; }
.churn.second { transform: rotate(-28deg); background: var(--route); }
.churn.third { transform: rotate(66deg); background: #a8bbc5; }
.versus { color: #8fa5b1; font-size: .82rem; writing-mode: vertical-rl; }
.focused-context { display: grid; gap: .7rem; }
.focused-context span { display: grid; place-items: center; min-height: 74px; border: 1px solid #8fa5b1; background: #18384a; font-family: var(--display); }
.evidence-section { background: var(--paper); }
.ledger { border-top: 2px solid var(--ink); }
.ledger-row { display: grid; grid-template-columns: 1.4fr 1fr .65fr; gap: 2rem; align-items: center; padding: 1rem .2rem; border-bottom: 1px solid var(--line); }
.ledger-head { font-size: .82rem; color: var(--muted); font-weight: 700; }
.ledger-row strong { justify-self: start; padding: .24rem .5rem; font-size: .8rem; }
.proven { color: var(--field); border: 1px solid currentColor; }
.pending { color: #a64a28; border: 1px solid currentColor; }
.start-section { display: grid; grid-template-columns: .8fr 1.2fr; gap: clamp(3rem, 8vw, 9rem); padding: clamp(5rem, 9vw, 9rem) clamp(1.25rem, 6vw, 7rem); }
.start-section p { color: var(--muted); max-width: 600px; }
.command-panel { background: #0d2534; color: #dbe6eb; box-shadow: var(--shadow); }
.command-tabs { display: flex; border-bottom: 1px solid #446170; }
.command-tabs button { border: 0; border-right: 1px solid #446170; background: transparent; color: #9eb2bc; padding: .75rem 1rem; cursor: pointer; }
.command-tabs button[aria-selected="true"] { background: #17394b; color: white; }
pre { margin: 0; padding: 1.6rem; min-height: 190px; overflow: auto; font-size: .88rem; line-height: 1.7; }
.copy-button { margin: 0 1.4rem 1.4rem; border-color: #9eb2bc; color: #dbe6eb; }
footer { display: flex; justify-content: space-between; gap: 2rem; padding: 2.2rem clamp(1.25rem, 6vw, 7rem); border-top: 1px solid var(--line); }
footer div { display: flex; gap: 1.5rem; }
.console-body { min-height: 100vh; background: #dce7ec; }
.console-nav p { margin: 0 auto 0 0; color: var(--muted); }
.console-shell { padding: 2rem clamp(1rem, 4vw, 4rem) 5rem; }
.console-summary { display: grid; grid-template-columns: repeat(4, 1fr); border: 1px solid var(--line); background: var(--paper); }
.console-summary div { padding: 1.1rem 1.3rem; border-right: 1px solid var(--line); }
.console-summary div:last-child { border: 0; }
.console-summary p { margin: 0 0 .3rem; color: var(--muted); font-size: .78rem; }
.console-summary strong { font-family: var(--display); font-size: 1.12rem; }
.console-error { background: #ffe4da; border-left: 4px solid var(--signal); padding: 1rem; }
.console-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1rem; margin-top: 1rem; }
.console-panel { min-height: 280px; padding: 1.2rem; border: 1px solid var(--line); background: var(--paper); }
.agents-panel, .messages-panel { grid-column: span 2; }
.panel-heading { display: flex; justify-content: space-between; align-items: baseline; border-bottom: 1px solid var(--line); padding-bottom: .8rem; }
.panel-heading h1, .panel-heading h2 { font-size: 1.45rem; letter-spacing: -.02em; }
.panel-heading span { color: var(--muted); font-size: .78rem; }
.agent-list { display: grid; grid-template-columns: repeat(2, 1fr); column-gap: 2rem; }
.agent-row { display: grid; grid-template-columns: 12px 1fr auto; gap: .75rem; align-items: center; padding: .8rem 0; border-bottom: 1px solid #d7e1e6; }
.agent-row small { display: block; color: var(--muted); }
.agent-row code { font-size: .75rem; }
.presence { width: 10px; height: 10px; border-radius: 50%; background: #8297a2; }
.presence.working, .presence.online { background: var(--field); }
.presence.blocked { background: var(--signal); }
.list-row, .message-row, .locality-row { display: flex; justify-content: space-between; gap: 1rem; padding: .72rem 0; border-bottom: 1px solid #d7e1e6; }
.list-row small { display: block; color: var(--muted); }
.list-row > span { color: var(--route); font-size: .8rem; }
.message-row { display: block; }
.message-row p { margin: 0; font-size: .8rem; color: var(--muted); }
.message-row span { display: block; margin-top: .25rem; }
.empty-state { color: var(--muted); max-width: 40ch; padding: 1rem 0; }
.context-detail p { max-width: 55ch; }
.context-detail small { color: var(--muted); }
@media (max-width: 900px) {
.hero { grid-template-columns: 1fr; min-height: auto; }
.dispatch { max-width: 650px; }
.principle-strip { grid-template-columns: 1fr 1fr; }
.principle-strip p { border-bottom: 1px solid var(--line); }
.section-intro, .context-section, .start-section { grid-template-columns: 1fr; }
.blueprint { grid-template-columns: 1fr; }
.blueprint article { min-height: auto; border-right: 0; border-bottom: 1px solid var(--line); }
.blueprint-index { margin-bottom: 2rem; }
.console-summary { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 600px) {
body { font-size: 16px; }
.masthead { padding-block: 1rem; align-items: flex-start; }
nav { display: grid; grid-template-columns: 1fr 1fr; gap: .4rem 1rem; }
.hero { padding-top: 4rem; }
h1 { font-size: clamp(3rem, 13vw, 5.2rem); }
.hero-actions, footer { align-items: flex-start; flex-direction: column; }
.peer { display: none; }
.ledger-row { grid-template-columns: 1fr; gap: .25rem; }
.ledger-head { display: none; }
.context-diagram { grid-template-columns: 1fr; }
.versus { writing-mode: initial; text-align: center; }
.console-summary, .console-grid, .agent-list { grid-template-columns: 1fr; }
.console-summary div { border-right: 0; border-bottom: 1px solid var(--line); }
.agents-panel, .messages-panel { grid-column: auto; }
.console-nav p { display: none; }
}
@media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; }
*, *::before, *::after { transition-duration: .01ms !important; animation-duration: .01ms !important; animation-iteration-count: 1 !important; }
}
+23
View File
@@ -3,6 +3,7 @@ import { BacklogService } from './backlog/service.mjs';
import { createClaudeAdapter } from './adapters/claude.mjs';
import { createCodexAdapter } from './adapters/codex.mjs';
import { CheckService } from './checks/service.mjs';
import { startConsole } from './console/server.mjs';
import { classifyRecoverability, createLocalCanonical, planLocalCanonical } from './canonical/local.mjs';
import { openControlPlane } from './control-plane/service.mjs';
import { spawnSync } from 'node:child_process';
@@ -21,6 +22,7 @@ import { createWorktrees, planWorktrees } from './kernel/worktrees.mjs';
import { planFleetDown, planFleetUp, startFleet, stopFleet } from './runtime/lifecycle.mjs';
import { ResourceService } from './resources/service.mjs';
import { CANDIDATE_ACCEPTANCE_SCENARIOS, createVersionService } from './self-host/service.mjs';
import { observeProject } from './observability/snapshot.mjs';
const HELP = `TORCH — portable agent fleet
@@ -76,6 +78,8 @@ Usage:
torch upgrade --version <version> [--dry-run] --yes [--json]
torch rollback [--dry-run] --yes [--json]
torch import combatrig --source <path> [--output <path>] [--json]
torch console snapshot [--repo <path>] [--json]
torch console serve [--repo <path>] [--host <address>] [--port <n>]
torch doctor [--json]
torch uninstall [--dry-run] [--purge] [--json]
`;
@@ -164,6 +168,7 @@ function runCandidateAcceptance(candidateRoot, spawn) {
['capture-stop-resume', ['SCN-fleet-fresh-resume']],
['detach-uninstall', ['SCN-install-doctor-purge']],
['combatrig-compatibility', ['SCN-combatrig-import', 'SCN-cli-combatrig-import']],
['product-surface', ['SCN-product-site', 'SCN-console-readonly']],
]);
const scenarios = CANDIDATE_ACCEPTANCE_SCENARIOS.filter((scenario) =>
(evidenceByScenario.get(scenario) ?? []).every((marker) => testOutput.includes(marker)));
@@ -285,6 +290,24 @@ export async function runCli(argv = process.argv.slice(2), {
} else print({ proposal, mutationPerformed: false }, { json });
return proposal.compatibility.readyForApproval ? 0 : 1;
}
if (command === 'console') {
const operation = argv[1] ?? 'serve';
const root = resolve(cwd, optionValue(argv, '--repo') ?? '.');
if (operation === 'snapshot') {
print(observeProject({ repositoryRoot: root, env }), { json });
return 0;
}
if (operation === 'serve') {
const host = optionValue(argv, '--host') ?? '127.0.0.1';
const port = Number(optionValue(argv, '--port') ?? 4317);
if (!Number.isInteger(port) || port < 0 || port > 65535) {
throw new TorchError('Console port must be an integer from 0 through 65535', { code: 'CONSOLE_PORT_INVALID' });
}
startConsole({ repositoryRoot: root, env, host, port });
return 0;
}
throw new TorchError(`Unknown console operation: ${operation}`, { code: 'UNKNOWN_COMMAND' });
}
const designCommand = ['init', 'analyze', 'domains', 'design'].includes(command);
const repositoryRoot = designCommand ? resolve(cwd, optionValue(argv, '--repo') ?? '.') : cwd;
const repository = inspectRepository(repositoryRoot);
+60
View File
@@ -0,0 +1,60 @@
import { createServer } from 'node:http';
import { readFileSync } from 'node:fs';
import { dirname, extname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { observeProject } from '../observability/snapshot.mjs';
const SITE_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'site');
const ROUTES = new Map([
['/', 'index.html'], ['/index.html', 'index.html'], ['/styles.css', 'styles.css'],
['/app.js', 'app.js'], ['/console', 'console.html'], ['/console.html', 'console.html'],
['/console.js', 'console.js'],
]);
const TYPES = new Map([
['.html', 'text/html; charset=utf-8'], ['.css', 'text/css; charset=utf-8'],
['.js', 'text/javascript; charset=utf-8'], ['.svg', 'image/svg+xml'],
]);
function respond(response, status, body, type = 'application/json; charset=utf-8') {
response.writeHead(status, {
'content-type': type,
'cache-control': 'no-store',
'x-content-type-options': 'nosniff',
'content-security-policy': "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; connect-src 'self'; base-uri 'none'; frame-ancestors 'none'",
});
response.end(body);
}
export function createConsoleServer({ repositoryRoot = process.cwd(), env = process.env } = {}) {
return createServer((request, response) => {
const url = new URL(request.url ?? '/', 'http://torch.local');
if (request.method !== 'GET') {
respond(response, 405, JSON.stringify({ error: 'METHOD_NOT_ALLOWED' }));
return;
}
if (url.pathname === '/api/snapshot') {
try {
respond(response, 200, `${JSON.stringify(observeProject({ repositoryRoot, env }))}\n`);
} catch (error) {
respond(response, 503, `${JSON.stringify({ error: error.code ?? 'OBSERVATION_FAILED', message: error.message })}\n`);
}
return;
}
const file = ROUTES.get(url.pathname);
if (!file) {
respond(response, 404, 'Not found\n', 'text/plain; charset=utf-8');
return;
}
const path = join(SITE_ROOT, file);
respond(response, 200, readFileSync(path), TYPES.get(extname(path)) ?? 'application/octet-stream');
});
}
export function startConsole({ repositoryRoot = process.cwd(), env = process.env, host = '127.0.0.1', port = 4317 } = {}) {
const server = createConsoleServer({ repositoryRoot, env });
server.listen(port, host, () => {
const address = server.address();
process.stdout.write(`TORCH console: http://${host}:${address.port}/console\n`);
});
return server;
}
+156
View File
@@ -0,0 +1,156 @@
import { existsSync, readFileSync, readdirSync } from 'node:fs';
import { join } from 'node:path';
import { DatabaseSync } from 'node:sqlite';
import { analyzeRepository } from '../kernel/analyze.mjs';
import { diagnoseProject } from '../kernel/doctor.mjs';
import { inspectRepository } from '../kernel/git.mjs';
import { readInstallManifest } from '../kernel/install.mjs';
import { projectStatePath } from '../kernel/paths.mjs';
function json(path) {
return JSON.parse(readFileSync(path, 'utf8'));
}
function hasTable(database, name) {
return Boolean(database.prepare(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?`).get(name));
}
function rows(database, table, statement) {
return hasTable(database, table) ? database.prepare(statement).all() : [];
}
function backlog(repositoryRoot, manifest) {
const manager = (manifest.external ?? []).find((entry) => entry.type === 'worktree' && entry.area === 'session-manager');
const root = join(manager?.path && existsSync(manager.path) ? manager.path : repositoryRoot, '.torch', 'backlog');
if (!existsSync(root)) return [];
const tasks = [];
for (const name of readdirSync(root).filter((entry) => entry.endsWith('.json')).sort()) {
try {
const task = json(join(root, name));
tasks.push({
id: task.id, title: task.title, state: task.state, owner: task.owner ?? null,
priority: task.priority ?? null, revision: task.revision ?? null,
});
} catch {
tasks.push({ id: name.slice(0, -5), title: 'Unreadable task', state: 'invalid', owner: null });
}
}
return tasks;
}
export function observeProject({ repositoryRoot, env = process.env, now = () => new Date() } = {}) {
const repository = inspectRepository(repositoryRoot);
const installed = existsSync(join(repository.root, '.torch', 'torch.yaml'));
if (!installed) {
const analysis = analyzeRepository(repository);
return {
schema: 'torch.dev/observation/v1alpha1', generatedAt: now().toISOString(),
mode: 'not-installed', mutationPerformed: false,
repository: analysis.repository,
inventory: analysis.inventory,
contextLocality: {
measured: false, status: 'unavailable',
reason: 'Install a Fleet and connect runtime usage reporting before comparing context locality.',
},
};
}
const config = json(join(repository.root, '.torch', 'torch.yaml'));
const roster = json(join(repository.root, '.torch', 'roster.yaml'));
const manifest = readInstallManifest(repository.root);
const stateRoot = projectStatePath(manifest.projectId, env);
const databasePath = join(stateRoot, 'state.db');
const result = {
schema: 'torch.dev/observation/v1alpha1', generatedAt: now().toISOString(),
mode: 'installed', mutationPerformed: false,
project: config.project,
repository: {
root: repository.root, branch: repository.branch, head: repository.head,
dirty: repository.dirtyEntries.length > 0,
},
doctor: diagnoseProject({ repository, env }),
agents: (roster.areas ?? []).map((area) => ({
areaId: area.id, title: area.title, state: 'offline', runtime: area.runtime ?? null,
runtimeSessionId: null, summary: null, task: null, heartbeatAt: null,
})),
messages: { total: 0, unacknowledged: 0, recent: [] },
backlog: backlog(repository.root, manifest),
checks: { definitions: config.checks ?? [], receipts: [] },
resources: { definitions: config.resources ?? [], holders: [], waiters: [] },
integration: [],
schedules: config.schedules ?? [],
audit: [],
contextLocality: {
measured: false, status: 'unavailable',
reason: 'No runtime usage samples have been reported for this Fleet.',
metrics: ['cachedInput', 'uncachedInput', 'cacheCreation', 'cacheRead', 'compactions', 'resumedPromptBytes', 'costPerVerifiedItem'],
},
};
if (!existsSync(databasePath)) return result;
const database = new DatabaseSync(databasePath, { readOnly: true });
try {
if (hasTable(database, 'identities')) {
const live = new Map(database.prepare(`
SELECT area_id, state, runtime, runtime_session_id, summary, current_task AS task, heartbeat_at
FROM identities ORDER BY area_id
`).all().map((row) => [row.area_id, row]));
result.agents = result.agents.map((agent) => {
const row = live.get(agent.areaId);
return row ? {
...agent, state: row.state, runtime: row.runtime ?? agent.runtime,
runtimeSessionId: row.runtime_session_id, summary: row.summary,
task: row.task, heartbeatAt: row.heartbeat_at,
} : agent;
});
}
if (hasTable(database, 'messages')) {
result.messages.total = database.prepare('SELECT COUNT(*) AS count FROM messages').get().count;
result.messages.recent = database.prepare(`
SELECT id, sender_id AS sender, recipient_id AS recipient, kind, body, created_at AS createdAt
FROM messages ORDER BY created_at DESC LIMIT 20
`).all();
if (hasTable(database, 'message_acks') && hasTable(database, 'identities')) {
result.messages.unacknowledged = database.prepare(`
SELECT COUNT(*) AS count FROM messages m
JOIN identities i ON m.recipient_id = i.area_id OR m.recipient_id = 'all'
LEFT JOIN message_acks a ON a.message_id = m.id AND a.area_id = i.area_id
WHERE a.message_id IS NULL
`).get().count;
}
}
result.checks.receipts = rows(database, 'check_receipts', `
SELECT id, check_id AS checkId, area_id AS areaId, commit_sha AS commit, result, finished_at AS finishedAt
FROM check_receipts ORDER BY finished_at DESC LIMIT 40
`);
result.resources.holders = rows(database, 'resource_leases', `
SELECT resource_id AS resourceId, area_id AS areaId, acquired_at AS acquiredAt, expires_at AS expiresAt
FROM resource_leases WHERE released_at IS NULL ORDER BY acquired_at
`);
result.resources.waiters = rows(database, 'resource_requests', `
SELECT resource_id AS resourceId, area_id AS areaId, requested_at AS requestedAt
FROM resource_requests WHERE state = 'waiting' ORDER BY requested_at
`);
result.integration = rows(database, 'integration_requests', `
SELECT id, source_area AS sourceArea, source_commit AS sourceCommit, target_branch AS targetBranch, state, reason, updated_at AS updatedAt
FROM integration_requests ORDER BY updated_at DESC LIMIT 40
`);
result.audit = rows(database, 'audit_events', `
SELECT id, actor_id AS actorId, operation, entity_type AS entityType, entity_id AS entityId, created_at AS createdAt
FROM audit_events ORDER BY created_at DESC LIMIT 30
`);
if (hasTable(database, 'context_usage')) {
const samples = database.prepare(`
SELECT area_id AS areaId, SUM(cached_input) AS cachedInput, SUM(uncached_input) AS uncachedInput,
SUM(cache_creation) AS cacheCreation, SUM(cache_read) AS cacheRead,
SUM(compactions) AS compactions, SUM(resumed_prompt_bytes) AS resumedPromptBytes,
SUM(cost_microusd) AS costMicrousd, SUM(verified_items) AS verifiedItems
FROM context_usage GROUP BY area_id ORDER BY area_id
`).all();
result.contextLocality = { measured: true, status: 'measured', samples };
}
} finally {
database.close();
}
return result;
}
+1
View File
@@ -21,6 +21,7 @@ export const CANDIDATE_ACCEPTANCE_SCENARIOS = Object.freeze([
'capture-stop-resume',
'detach-uninstall',
'combatrig-compatibility',
'product-surface',
]);
function json(path) {
+1
View File
@@ -37,6 +37,7 @@ function releaseCandidate(root, version) {
'SCN-backlog-lifecycle', 'SCN-native-integration', 'SCN-resource-fifo',
'SCN-fleet-fresh-resume', 'SCN-install-doctor-purge',
'SCN-combatrig-import', 'SCN-cli-combatrig-import',
'SCN-product-site', 'SCN-console-readonly',
].join('\n'))});\n`);
writeFileSync(join(candidateRoot, 'package.json'), `${JSON.stringify({
name: 'torch-agent-fleet', version, bin: { torch: 'bin/torch.mjs' },
+71
View File
@@ -0,0 +1,71 @@
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import { existsSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import test from 'node:test';
import { createConsoleServer } from '../../src/console/server.mjs';
import { analyzeRepository } from '../../src/kernel/analyze.mjs';
import { proposeDomains } from '../../src/kernel/domains.mjs';
import { inspectRepository } from '../../src/kernel/git.mjs';
import { installProject } from '../../src/kernel/install.mjs';
import { observeProject } from '../../src/observability/snapshot.mjs';
function fixture() {
const root = mkdtempSync(join(tmpdir(), 'torch-console-'));
execFileSync('git', ['init', '-b', 'main', root]);
execFileSync('git', ['-C', root, 'config', 'user.email', '[email protected]']);
execFileSync('git', ['-C', root, 'config', 'user.name', 'TORCH Test']);
writeFileSync(join(root, 'README.md'), '# Console fixture\n');
execFileSync('git', ['-C', root, 'add', '.']);
execFileSync('git', ['-C', root, 'commit', '-m', 'fixture']);
return root;
}
test('SCN-product-site: public surface explains the portable fleet and separates the local console', () => {
const html = readFileSync(new URL('../../site/index.html', import.meta.url), 'utf8');
const consoleHtml = readFileSync(new URL('../../site/console.html', import.meta.url), 'utf8');
assert.match(html, /One repository[\s\S]*coordinated AI engineering team/);
assert.match(html, /id="route-toggle"/);
assert.match(html, /Exact-commit checks/);
assert.match(html, /Live gate open/);
assert.match(html, /Context savings are a measured hypothesis/);
assert.doesNotMatch(html, /nostr|relay coordination|task lock/i);
assert.match(consoleHtml, /Local Fleet Console/);
assert.match(consoleHtml, /noindex,nofollow/);
});
test('SCN-console-readonly: HTTP console serves fixed assets and observes an installed fleet without mutation', async (context) => {
const root = fixture();
const env = { ...process.env, XDG_DATA_HOME: mkdtempSync(join(tmpdir(), 'torch-console-state-')) };
const repository = inspectRepository(root);
const proposal = proposeDomains({ repository, analysis: analyzeRepository(repository) });
proposal.review = {
status: 'approved', reviewedAt: '2026-09-27T00:00:00Z', reviewedBy: 'fixture-owner', notes: [],
};
installProject({ repository, proposal, env, projectId: 'console-fixture' });
const databasePath = join(env.XDG_DATA_HOME, 'torch', 'projects', 'console-fixture', 'state.db');
assert.equal(existsSync(databasePath), false);
const before = execFileSync('git', ['-C', root, 'status', '--porcelain'], { encoding: 'utf8' });
const observed = observeProject({ repositoryRoot: root, env, now: () => new Date('2026-09-27T00:00:00Z') });
assert.equal(observed.mode, 'installed');
assert.equal(observed.mutationPerformed, false);
assert.equal(observed.agents.length, proposal.domains.length + 1);
assert.equal(observed.contextLocality.measured, false);
assert.equal(existsSync(databasePath), false);
assert.equal(execFileSync('git', ['-C', root, 'status', '--porcelain'], { encoding: 'utf8' }), before);
const server = createConsoleServer({ repositoryRoot: root, env });
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
context.after(() => server.close());
const origin = `http://127.0.0.1:${server.address().port}`;
const page = await fetch(`${origin}/`);
assert.equal(page.status, 200);
assert.match(page.headers.get('content-security-policy'), /default-src 'self'/);
assert.match(await page.text(), /coordinated AI engineering team/);
const snapshot = await fetch(`${origin}/api/snapshot`);
assert.equal(snapshot.status, 200);
assert.equal((await snapshot.json()).mutationPerformed, false);
assert.equal((await fetch(`${origin}/../package.json`)).status, 404);
assert.equal(execFileSync('git', ['-C', root, 'status', '--porcelain'], { encoding: 'utf8' }), before);
});