From d93de9364141397cb417ec61295cf08d70be6c3e Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN Date: Thu, 1 Oct 2026 11:52:53 -0400 Subject: [PATCH 01/31] TASK-console-attention-actions: group stale evidence in advisory cohort --- docs/TEST_INTEGRITY.md | 47 ++++ site/attention-projection.js | 61 ++++- site/console.js | 17 +- test/fleet/console-attention-cohorts.test.mjs | 228 ++++++++++++++++++ 4 files changed, 343 insertions(+), 10 deletions(-) create mode 100644 test/fleet/console-attention-cohorts.test.mjs diff --git a/docs/TEST_INTEGRITY.md b/docs/TEST_INTEGRITY.md index 44c659d..bbe5fb8 100644 --- a/docs/TEST_INTEGRITY.md +++ b/docs/TEST_INTEGRITY.md @@ -2715,3 +2715,50 @@ test_integrity_note: did_relax_any_assertion: false if_true_explain_spec_basis: "" ``` + +# Console attention advisory cohorts (2026-10-01) + +```yaml +test_integrity_note: + change_type: new_tests + scenarios: + - id: SCN-attention-advisory-cohort-cardinality-and-exact-references + given: "A fixed 49-task snapshot with equivalent stale or missing observed-commit findings in doctor and backlogHealth" + when: "The read-only attention projection groups those findings" + then: "Exactly one separately labelled advisory/recheck cohort contains the exact 49 task references and retains each owner, observedAt, current observed commit, and UNKNOWN reproduction status" + - id: SCN-attention-advisory-provenance-and-semantic-conflicts + given: "Doctor and backlogHealth findings that either match or differ in task, code, owner, observedAt, current observed commit, or reproduction status" + when: "The cohort uses the complete semantic fingerprint" + then: "Equal findings retain both provenance sources once; any differing fingerprint field remains a separate visible observation" + - id: SCN-attention-advisory-does-not-hide-owner-or-fleet-risks + given: "An advisory warning alongside an unsafe error, schema error, unknown wake, delivery operation, and owner-addressed pending decision" + when: "Attention is projected" + then: "Only the intended stale/missing warnings enter Advisory; the owner decision remains Owner action and all unsafe, schema, wake, and delivery findings remain Fleet action" + - id: SCN-attention-advisory-projection-is-read-only + given: "A frozen snapshot containing an advisory finding and a revisioned owner decision preview boundary" + when: "The attention projection runs" + then: "Snapshot, decision evidence, preview token, revision, and draft remain unchanged; projection invokes no acknowledgement, close, recovery, or decision action" + - id: SCN-attention-advisory-unknown-fields-stay-unknown + given: "A stale/missing observation whose owner, observedAt, and current commit provenance are absent" + when: "The advisory drilldown is formed" + then: "The task reference remains visible while missing owner and provenance remain null and reproduction is labelled UNKNOWN" + observable_outcomes: + - "One 49-reference advisory cohort replaces duplicated stale/missing attention cards while preserving exact drilldown references" + - "Each deduplicated observation retains doctor and backlogHealth provenance; conflicting records are never merged by code alone" + - "Owner decisions, unsafe recovery, schema errors, unknown wakes, delivery operations, and same-code errors remain in their existing action groups" + - "Projection does not mutate snapshot data or invoke state-changing actions, and existing owner quick-action preview/revision/draft assertions remain unchanged" + - "Source/scenario qualification is not actual live desktop/mobile visual acceptance" + determinism_controls: + - "Fixed hermetic task IDs, owner values, observedAt values, observed commit, finding codes, and provenance inputs" + - "Deep-frozen projection snapshots and an isolated demo serving one fixed in-memory snapshot; no existing fixture source, network, live project mutation, retries, or sleeps" + anti_cheat_rationale: + prevents: + - "Hard-coded cohort counts that omit or invent task references" + - "Deduplication by code alone that merges conflicting observations" + - "Discarding one source while presenting a deduplicated observation" + - "Hiding unsafe errors or owner decisions inside advisory summaries" + - "Changing preview, revision, draft, or acknowledgement behavior while rendering evidence" + relaxation: + did_relax_any_assertion: false + if_true_explain_spec_basis: "" +``` diff --git a/site/attention-projection.js b/site/attention-projection.js index 588f913..7e9c3fb 100644 --- a/site/attention-projection.js +++ b/site/attention-projection.js @@ -12,10 +12,40 @@ function worktreeProblemLabel(problem) { function attentionGroups(snapshot) { const groups = { owner: [], fleet: [], arbiter: [] }; const agents = new Map((snapshot.agents ?? []).map((agent) => [agent.areaId, agent])); + const tasks = new Map((snapshot.backlog ?? []).map((task) => [task.id, task])); const agentName = (id) => agents.get(id)?.title ?? id ?? 'Owner not recorded'; const add = (group, item) => groups[group].push(item); const approvals = (snapshot.approvalRequests?.items ?? []).filter((approval) => approval.status === 'pending'); + const advisory = []; + Object.defineProperty(groups, 'advisory', { value: advisory }); + const recheckCodes = new Set(['BACKLOG_OBSERVED_COMMIT_STALE', 'BACKLOG_OBSERVED_COMMIT_MISSING']); + const rechecks = new Map(); + const recheckValue = (finding, key, fallback) => Object.hasOwn(finding, key) && finding[key] !== undefined + ? finding[key] : fallback; + const addRecheck = (finding, source) => { + if (finding.severity !== 'warning' || !recheckCodes.has(finding.code)) return; + const taskId = recheckValue(finding, 'taskId', null); + const task = tasks.get(taskId); + const observation = { + taskId, + code: finding.code, + owner: recheckValue(finding, 'owner', task?.owner ?? null), + observedAt: recheckValue(finding, 'observedAt', task?.observedAt ?? null), + currentObservedCommit: recheckValue(finding, 'currentObservedCommit', + snapshot.repository?.head ?? snapshot.project?.head ?? null), + reproductionStatus: finding.reproductionStatus ?? finding.reproductionState ?? 'UNKNOWN', + sources: [], + }; + const fingerprint = JSON.stringify([ + observation.taskId, observation.code, observation.owner, observation.observedAt, + observation.currentObservedCommit, observation.reproductionStatus, + ]); + const retained = rechecks.get(fingerprint) ?? observation; + if (!retained.sources.includes(source)) retained.sources.push(source); + rechecks.set(fingerprint, retained); + }; + for (const approval of approvals) { const isOwner = approval.approver === 'owner'; add(isOwner ? 'owner' : 'fleet', { @@ -76,7 +106,8 @@ function attentionGroups(snapshot) { } for (const finding of findings.filter((entry) => ['error', 'warning'].includes(entry.severity) - && !['MESSAGE_BACKLOG', 'WORKTREE_PROBLEM'].includes(entry.code))) { + && !['MESSAGE_BACKLOG', 'WORKTREE_PROBLEM'].includes(entry.code) + && !(entry.severity === 'warning' && recheckCodes.has(entry.code)))) { const recoveryGap = finding.code === 'RECOVERABILITY' && !finding.offMachine; const humanTitle = recoveryGap ? 'Off-machine recovery copy is not verified' @@ -136,14 +167,28 @@ function attentionGroups(snapshot) { evidence: 'Review the recorded task activity before asking for a change.', href: '#activity-review', action: 'Open activity review', }); - const staleEvidence = (snapshot.backlogHealth?.findings ?? []).filter((finding) => - ['BACKLOG_OBSERVED_COMMIT_STALE', 'BACKLOG_OBSERVED_COMMIT_MISSING'].includes(finding.code)); - if (staleEvidence.length) add('fleet', { - tone: 'review', title: `${staleEvidence.length} task evidence record${staleEvidence.length === 1 ? '' : 's'} need refresh`, - owner: 'Task owner not recorded', detail: 'Some backlog evidence refers to an old or unreachable commit. Re-check it before dispatch.', - evidence: staleEvidence.map((finding) => `${finding.taskId ?? 'task not recorded'} · ${finding.code}`).join('; '), - href: '#backlog-board', action: 'Inspect backlog evidence', + for (const finding of findings) addRecheck(finding, 'doctor'); + for (const finding of snapshot.backlogHealth?.findings ?? []) addRecheck(finding, 'backlogHealth'); + const observations = [...rechecks.values()].sort((left, right) => { + const leftKey = JSON.stringify([left.taskId, left.code, left.owner, left.observedAt, + left.currentObservedCommit, left.reproductionStatus]); + const rightKey = JSON.stringify([right.taskId, right.code, right.owner, right.observedAt, + right.currentObservedCommit, right.reproductionStatus]); + return leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0; }); + if (observations.length) { + const taskReferences = [...new Set(observations.map((item) => item.taskId).filter((id) => id != null))]; + const countLabel = taskReferences.length === observations.length + ? `${taskReferences.length} task evidence recheck${taskReferences.length === 1 ? '' : 's'}` + : `${observations.length} observed evidence record${observations.length === 1 ? '' : 's'} to recheck`; + advisory.push({ + tone: 'info', title: countLabel, owner: 'Advisory recheck', + detail: 'Observed backlog evidence may refer to an old or unreachable commit. Recheck each observation before dispatch; reproduction remains UNKNOWN until independently verified.', + evidence: `${observations.length} distinct observation${observations.length === 1 ? '' : 's'} · ${taskReferences.length} recorded task reference${taskReferences.length === 1 ? '' : 's'}`, + href: '#backlog-board', action: 'Inspect backlog evidence', + observations, taskReferences, + }); + } return groups; } diff --git a/site/console.js b/site/console.js index 94102e5..1e58de2 100644 --- a/site/console.js +++ b/site/console.js @@ -954,13 +954,18 @@ function renderFlowWatch(snapshot) { function attentionGroups(snapshot) { return globalThis.TorchAttentionProjection.groups(snapshot); } +function attentionItemCount(groups) { + return ['owner', 'fleet', 'advisory', 'arbiter'] + .reduce((count, key) => count + (groups[key]?.length ?? 0), 0); +} function renderAttention(groups) { const definitions = [ ['owner', 'Waiting on you', 'Only requests that name the project owner as decision-maker.'], ['fleet', 'Fleet handling', 'A named domain owns the next step; unknown ownership stays explicit.'], + ['advisory', 'Advisory / recheck cohorts', 'Observed evidence grouped for rechecking; these findings do not assign an owner action.'], ['arbiter', 'Arbiter handling', 'No observed item has structured evidence assigning its next step exclusively to an arbiter.'], ]; - const total = Object.values(groups).reduce((sum, items) => sum + items.length, 0); + const total = attentionItemCount(groups); setText('#attention-count', `${total} ${total === 1 ? 'item' : 'items'}`); setHtml('#attention-list', definitions.map(([key, title, description]) => { const items = groups[key]; @@ -973,6 +978,14 @@ function renderAttention(groups) { ${item.evidence.length > 120 ? `
Evidence and references

${escapeHtml(item.evidence)}

` : `Evidence: ${escapeHtml(item.evidence)}`} + ${item.observations ? `
Recheck observations (${item.observations.length})
` : ''} ${escapeHtml(item.action)} ${item.decisionApprovalId ? `
@@ -1012,7 +1025,7 @@ function render(snapshot) { const parts = [`${agents.length} persistent ${agents.length === 1 ? 'identity' : 'identities'}`, `${working} ${working === 1 ? 'task' : 'tasks'} in progress`]; if (blocked) parts.push(`${blocked} blocked`); - const attentionCount = Object.values(attention).reduce((sum, items) => sum + items.length, 0); + const attentionCount = attentionItemCount(attention); setText('#project-summary', snapshot.mode === 'installed' ? `${parts.join(' · ')}. ${attentionCount ? `${attentionCount} item${attentionCount === 1 ? '' : 's'} need a named next step.` : 'No owner decision is currently waiting in the observed queues.'}` : 'This repository has not been installed as a TORCH Fleet. The view is showing repository intake only.'); diff --git a/test/fleet/console-attention-cohorts.test.mjs b/test/fleet/console-attention-cohorts.test.mjs new file mode 100644 index 0000000..856de68 --- /dev/null +++ b/test/fleet/console-attention-cohorts.test.mjs @@ -0,0 +1,228 @@ +import assert from 'node:assert/strict'; +import { once } from 'node:events'; +import test from 'node:test'; +import { chromium } from '@playwright/test'; +import { createConsoleServer } from '../../src/console/server.mjs'; +import '../../site/attention-projection.js'; +import '../../site/attention-actions.js'; + +const project = (snapshot) => globalThis.TorchAttentionProjection.groups(snapshot); +const recheckCodes = ['BACKLOG_OBSERVED_COMMIT_STALE', 'BACKLOG_OBSERVED_COMMIT_MISSING']; +const taskId = (index) => `TASK-COHORT-${String(index).padStart(2, '0')}`; + +function deepFreeze(value) { + if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value; + for (const child of Object.values(value)) deepFreeze(child); + return Object.freeze(value); +} + +function finding(task, source = {}) { + return { + severity: 'warning', code: 'BACKLOG_OBSERVED_COMMIT_STALE', taskId: task.id, + observedAt: task.observedAt, owner: task.owner, ...source, + }; +} + +function cohortFixture(count = 49) { + const tasks = Array.from({ length: count }, (_, index) => ({ + id: taskId(index + 1), owner: `domain-${String(index + 1).padStart(2, '0')}`, + observedAt: `2026-09-${String((index % 28) + 1).padStart(2, '0')}T12:00:00.000Z`, + })); + const findings = tasks.map((task, index) => finding(task, { + code: index % 2 ? 'BACKLOG_OBSERVED_COMMIT_MISSING' : 'BACKLOG_OBSERVED_COMMIT_STALE', + })); + return { + repository: { head: '316d8b7480c2e10925ebdff8310fe26ef5a881a8' }, + backlog: tasks, + doctor: { findings: structuredClone(findings) }, + backlogHealth: { findings: structuredClone(findings), mutationPerformed: false }, + }; +} + +test('SCN-attention-advisory-cohort-cardinality-and-exact-references: 49 equivalent observations render as one recheck cohort', async (t) => { + const groups = project(cohortFixture()); + const expectedReferences = Array.from({ length: 49 }, (_, index) => taskId(index + 1)); + + assert.equal(groups.advisory.length, 1); + assert.equal(groups.advisory[0].title, '49 task evidence rechecks'); + assert.equal(groups.fleet.filter((item) => /BACKLOG_OBSERVED_COMMIT_(STALE|MISSING)/.test(item.title)).length, 0); + assert.deepEqual(groups.advisory[0].taskReferences, expectedReferences); + assert.equal(groups.advisory[0].observations.length, 49); + const fixture = cohortFixture(); + for (const observation of groups.advisory[0].observations) { + const task = fixture.backlog.find((entry) => entry.id === observation.taskId); + assert.ok(task, `retained reference ${observation.taskId} belongs to the fixed 49-task scenario`); + assert.equal(observation.owner, task.owner); + assert.equal(observation.observedAt, task.observedAt); + assert.equal(observation.currentObservedCommit, '316d8b7480c2e10925ebdff8310fe26ef5a881a8'); + assert.equal(observation.reproductionStatus, 'UNKNOWN'); + assert.deepEqual(observation.sources, ['doctor', 'backlogHealth']); + } + + const server = createConsoleServer(); + server.listen(0, '127.0.0.1'); + await once(server, 'listening'); + let browser; + t.after(async () => { + await browser?.close(); + await new Promise((resolve) => server.close(resolve)); + }); + browser = await chromium.launch({ headless: true }); + const page = await browser.newPage({ viewport: { width: 1280, height: 900 } }); + await page.clock.install({ time: new Date('2026-10-01T12:00:00.000Z') }); + await page.goto(`http://127.0.0.1:${server.address().port}/console?demo=1#overview`); + await page.locator('#live-refresh-status[data-generation="1"]').waitFor(); + await page.evaluate((fixedSnapshot) => { + const demo = globalThis.TorchConsoleDemo; + const originalSnapshot = demo.snapshot; + const originalRequest = demo.request; + let actionCalls = 0; + demo.snapshot = () => structuredClone(fixedSnapshot); + demo.request = (...args) => { actionCalls += 1; return originalRequest(...args); }; + globalThis.__attentionCohortReadOnlyProbe = { + originalSnapshot, get actionCalls() { return actionCalls; }, + }; + }, fixture); + await page.locator('#refresh-console').click(); + await page.locator('#live-refresh-status[data-generation="2"]').waitFor(); + const advisory = page.locator('#attention-list .attention-group-advisory'); + assert.equal(await advisory.count(), 1); + assert.match(await advisory.locator('h3').innerText(), /Advisory \/ recheck cohorts/); + assert.equal(await advisory.locator('.attention-item').count(), 1); + assert.equal(await advisory.locator('.attention-cohort-details li').count(), 49); + assert.deepEqual(await advisory.locator('.attention-cohort-details li code').allTextContents(), expectedReferences); + assert.equal(await page.locator('#attention-list .attention-group-fleet .attention-item').filter({ + hasText: /BACKLOG_OBSERVED_COMMIT_(STALE|MISSING)|task evidence record.*need refresh/, + }).count(), 0); + assert.equal(await page.evaluate(() => globalThis.__attentionCohortReadOnlyProbe.actionCalls), 0); + assert.deepEqual(await page.evaluate(() => { + const current = globalThis.__attentionCohortReadOnlyProbe.originalSnapshot(); + return [current.doctor.findings.length, current.backlogHealth.findings.length]; + }), [0, 0], 'rendering the synthetic snapshot did not mutate isolated demo state'); +}); + +test('SCN-attention-advisory-provenance-and-semantic-conflicts: equal sources merge and any fingerprint difference stays distinct', () => { + const conflictCases = [ + { field: 'taskId', firstId: 'TASK-ID-A', secondId: 'TASK-ID-B' }, + { field: 'code', change: { code: 'BACKLOG_OBSERVED_COMMIT_MISSING' } }, + { field: 'owner', change: { owner: 'domain-conflict' } }, + { field: 'observedAt', change: { observedAt: '2026-10-01T12:01:00.000Z' } }, + { field: 'currentObservedCommit', change: { currentObservedCommit: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' } }, + { field: 'reproductionStatus', change: { reproductionStatus: 'REPRODUCED' } }, + ]; + const doctor = []; + const backlogHealth = []; + const tasks = []; + for (const [index, conflict] of conflictCases.entries()) { + const id = `TASK-CONFLICT-${conflict.field}`; + const firstTask = { id: conflict.firstId ?? id, owner: 'domain-a', observedAt: `2026-10-01T12:0${index}:00.000Z` }; + const secondTask = conflict.secondId + ? { ...firstTask, id: conflict.secondId } + : firstTask; + tasks.push(firstTask, secondTask); + doctor.push(finding(firstTask, { + currentObservedCommit: '316d8b7480c2e10925ebdff8310fe26ef5a881a8', reproductionStatus: 'UNKNOWN', + })); + backlogHealth.push(finding(secondTask, { + currentObservedCommit: '316d8b7480c2e10925ebdff8310fe26ef5a881a8', reproductionStatus: 'UNKNOWN', + ...(conflict.change ?? {}), + })); + } + const groups = project({ repository: { head: '316d8b7480c2e10925ebdff8310fe26ef5a881a8' }, backlog: tasks, + doctor: { findings: doctor }, backlogHealth: { findings: backlogHealth } }); + + assert.equal(groups.advisory.length, 1, 'the advisory label remains one cohort across conflicting records'); + assert.equal(groups.advisory[0].observations.length, 12); + assert.equal(groups.advisory[0].observations.filter((entry) => entry.sources.length === 2).length, 0, + 'no mismatched fingerprint may inherit both provenance labels'); + for (const observation of groups.advisory[0].observations) { + assert.equal(observation.sources.length, 1); + assert.ok(['doctor', 'backlogHealth'].includes(observation.sources[0])); + } + + const equalTask = { id: 'TASK-EQUAL', owner: 'domain-equal', observedAt: '2026-10-01T13:00:00.000Z' }; + const equalGroups = project({ repository: { head: '316d8b7480c2e10925ebdff8310fe26ef5a881a8' }, backlog: [equalTask], + doctor: { findings: [finding(equalTask)] }, backlogHealth: { findings: [finding(equalTask)] } }); + assert.equal(equalGroups.advisory[0].observations.length, 1); + assert.deepEqual(equalGroups.advisory[0].observations[0].sources, ['doctor', 'backlogHealth']); +}); + +test('SCN-attention-advisory-does-not-hide-owner-or-fleet-risks: unsafe and unknown conditions keep their existing classifications', () => { + const groups = project({ + agents: [{ areaId: 'provider-runtime', title: 'Provider Runtime' }], + repository: { head: '316d8b7480c2e10925ebdff8310fe26ef5a881a8' }, + approvalRequests: { items: [{ id: 'owner-decision', status: 'pending', approver: 'owner', requester: 'provider-runtime', + title: 'Review a real owner decision' }] }, + doctor: { findings: [ + { severity: 'warning', code: 'BACKLOG_OBSERVED_COMMIT_STALE', taskId: 'TASK-ADVISORY' }, + { severity: 'error', code: 'BACKLOG_OBSERVED_COMMIT_STALE', taskId: 'TASK-ERROR', message: 'The source reports an unsafe error.' }, + { severity: 'error', code: 'BACKLOG_HEALTH_INVALID', message: 'Backlog schema validation failed.' }, + { severity: 'warning', code: 'RECOVERABILITY', offMachine: false, recommendation: 'Verify the recovery copy.' }, + ] }, + backlog: [{ id: 'TASK-ADVISORY', owner: 'provider-runtime', observedAt: '2026-10-01T10:00:00.000Z' }], + managerWakes: { blockingCount: 1, reservations: [{ managerId: 'provider-runtime', scheduleId: 'wake-a', outcome: 'unknown', blocksManagerWake: true }] }, + deliveryOperations: [{ operation: 'publish', state: 'unknown', actor: 'provider-runtime', commit: '316d8b7480c2e10925ebdff8310fe26ef5a881a8' }], + }); + + assert.equal(groups.owner.length, 1); + assert.equal(groups.owner[0].decisionApprovalId, 'owner-decision'); + assert.equal(groups.advisory.length, 1); + assert.ok(groups.fleet.some((item) => item.title === 'Backlog observed commit stale' + && /unsafe error/.test(item.detail)), 'an error with the advisory code remains a Fleet finding'); + assert.ok(groups.fleet.some((item) => item.title === 'Backlog health invalid')); + assert.ok(groups.fleet.some((item) => item.title === 'Off-machine recovery copy is not verified')); + assert.ok(groups.fleet.some((item) => /scheduled manager launch/.test(item.title))); + assert.ok(groups.fleet.some((item) => /delivery operation/.test(item.title))); + assert.equal(groups.fleet.some((item) => item.decisionApprovalId === 'owner-decision'), false); +}); + +test('SCN-attention-advisory-projection-is-read-only: projection preserves snapshot and existing owner decision shape', () => { + const snapshot = { + repository: { head: '316d8b7480c2e10925ebdff8310fe26ef5a881a8' }, + backlog: [{ id: 'TASK-READONLY', owner: 'provider-runtime', observedAt: '2026-10-01T12:00:00.000Z' }], + doctor: { findings: [{ severity: 'warning', code: 'BACKLOG_OBSERVED_COMMIT_STALE', taskId: 'TASK-READONLY' }] }, + backlogHealth: { findings: [{ severity: 'warning', code: 'BACKLOG_OBSERVED_COMMIT_STALE', taskId: 'TASK-READONLY' }] }, + approvalRequests: { items: [{ id: 'owner-confirm', revision: 7, status: 'pending', approver: 'owner', requester: 'provider-runtime', + title: 'Confirm reviewed scope', evidence: 'receipt:exact', summary: 'Inspect before deciding.' }] }, + }; + const before = structuredClone(snapshot); + deepFreeze(snapshot); + const groups = project(snapshot); + + assert.deepEqual(snapshot, before); + assert.equal(groups.owner[0].decisionApprovalId, 'owner-confirm'); + assert.equal(groups.owner[0].evidence, 'receipt:exact'); + assert.equal(groups.owner[0].href, '#approval-owner-confirm'); + assert.deepEqual(groups.advisory[0].observations[0].sources, ['doctor', 'backlogHealth']); + assert.equal(groups.advisory[0].observations[0].reproductionStatus, 'UNKNOWN'); + + const selected = { value: 'approved', disabled: true }; + const note = { value: 'Keep this unsent decision note.' }; + const previewForm = { dataset: { previewToken: 'current-token', revision: '7' }, + elements: { namedItem: () => selected }, querySelector: () => note }; + let previewCalls = 0; + const shortcut = globalThis.TorchAttentionActions.prepareOwnerDecisionShortcut(previewForm, 'rejected', () => { previewCalls += 1; }); + assert.equal(shortcut.started, false); + assert.equal(previewCalls, 0); + assert.equal(selected.value, 'approved'); + assert.equal(note.value, 'Keep this unsent decision note.'); + assert.equal(previewForm.dataset.previewToken, 'current-token'); + assert.equal(previewForm.dataset.revision, '7'); +}); + +test('SCN-attention-advisory-unknown-fields-stay-unknown: absent owner and observation provenance are not manufactured', () => { + const groups = project({ + doctor: { findings: [{ severity: 'warning', code: 'BACKLOG_OBSERVED_COMMIT_MISSING', taskId: 'TASK-UNKNOWN' }] }, + backlogHealth: { findings: [] }, + backlog: [{ id: 'TASK-UNKNOWN' }], + }); + const [observation] = groups.advisory[0].observations; + + assert.equal(groups.advisory.length, 1); + assert.deepEqual(groups.advisory[0].taskReferences, ['TASK-UNKNOWN']); + assert.equal(observation.owner, null); + assert.equal(observation.observedAt, null); + assert.equal(observation.currentObservedCommit, null); + assert.equal(observation.reproductionStatus, 'UNKNOWN'); + assert.deepEqual(observation.sources, ['doctor']); +}); From da9f85fc187304341114e93625ed872f299a3641 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN Date: Thu, 1 Oct 2026 11:55:47 -0400 Subject: [PATCH 02/31] TASK-console-attention-actions: remove unused cohort test constant --- test/fleet/console-attention-cohorts.test.mjs | 1 - 1 file changed, 1 deletion(-) diff --git a/test/fleet/console-attention-cohorts.test.mjs b/test/fleet/console-attention-cohorts.test.mjs index 856de68..c7bcf5d 100644 --- a/test/fleet/console-attention-cohorts.test.mjs +++ b/test/fleet/console-attention-cohorts.test.mjs @@ -7,7 +7,6 @@ import '../../site/attention-projection.js'; import '../../site/attention-actions.js'; const project = (snapshot) => globalThis.TorchAttentionProjection.groups(snapshot); -const recheckCodes = ['BACKLOG_OBSERVED_COMMIT_STALE', 'BACKLOG_OBSERVED_COMMIT_MISSING']; const taskId = (index) => `TASK-COHORT-${String(index).padStart(2, '0')}`; function deepFreeze(value) { From 4fa9bd7d490fb740eb1d0bdf3dc9cafa2f309ba1 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN Date: Thu, 1 Oct 2026 12:00:44 -0400 Subject: [PATCH 03/31] TASK-console-attention-actions: keep cohort evidence enumerable --- docs/TEST_INTEGRITY.md | 2 ++ site/attention-projection.js | 2 +- site/console.js | 4 ++-- test/fleet/console-attention-cohorts.test.mjs | 24 ++++++++++++++----- 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/docs/TEST_INTEGRITY.md b/docs/TEST_INTEGRITY.md index bbe5fb8..a2aef47 100644 --- a/docs/TEST_INTEGRITY.md +++ b/docs/TEST_INTEGRITY.md @@ -2744,8 +2744,10 @@ test_integrity_note: then: "The task reference remains visible while missing owner and provenance remain null and reproduction is labelled UNKNOWN" observable_outcomes: - "One 49-reference advisory cohort replaces duplicated stale/missing attention cards while preserving exact drilldown references" + - "A populated advisory group remains enumerable and JSON-serializable; an empty projection preserves its original owner/fleet/arbiter object shape" - "Each deduplicated observation retains doctor and backlogHealth provenance; conflicting records are never merged by code alone" - "Owner decisions, unsafe recovery, schema errors, unknown wakes, delivery operations, and same-code errors remain in their existing action groups" + - "Observed source/revision displays the exact observedAt SHA without presenting it as a clock timestamp" - "Projection does not mutate snapshot data or invoke state-changing actions, and existing owner quick-action preview/revision/draft assertions remain unchanged" - "Source/scenario qualification is not actual live desktop/mobile visual acceptance" determinism_controls: diff --git a/site/attention-projection.js b/site/attention-projection.js index 7e9c3fb..ddf4d3e 100644 --- a/site/attention-projection.js +++ b/site/attention-projection.js @@ -18,7 +18,6 @@ function attentionGroups(snapshot) { const approvals = (snapshot.approvalRequests?.items ?? []).filter((approval) => approval.status === 'pending'); const advisory = []; - Object.defineProperty(groups, 'advisory', { value: advisory }); const recheckCodes = new Set(['BACKLOG_OBSERVED_COMMIT_STALE', 'BACKLOG_OBSERVED_COMMIT_MISSING']); const rechecks = new Map(); const recheckValue = (finding, key, fallback) => Object.hasOwn(finding, key) && finding[key] !== undefined @@ -188,6 +187,7 @@ function attentionGroups(snapshot) { href: '#backlog-board', action: 'Inspect backlog evidence', observations, taskReferences, }); + groups.advisory = advisory; } return groups; } diff --git a/site/console.js b/site/console.js index 1e58de2..ecc0d88 100644 --- a/site/console.js +++ b/site/console.js @@ -968,7 +968,7 @@ function renderAttention(groups) { const total = attentionItemCount(groups); setText('#attention-count', `${total} ${total === 1 ? 'item' : 'items'}`); setHtml('#attention-list', definitions.map(([key, title, description]) => { - const items = groups[key]; + const items = groups[key] ?? []; return `

${title}

${items.length}
${items.length ? items.map((item) => `
@@ -981,7 +981,7 @@ function renderAttention(groups) { ${item.observations ? `
Recheck observations (${item.observations.length})
    ${item.observations.map((observation) => `
  • ${escapeHtml(observation.taskId ?? 'Task reference not recorded')} · ${escapeHtml(observation.code)} · Owner: ${escapeHtml(observation.owner ?? 'Owner not recorded')} - · Observed at: ${escapeHtml(observation.observedAt ?? 'Not recorded')} + · Observed source/revision: ${escapeHtml(observation.observedAt ?? 'Not recorded')} · Current observed commit: ${escapeHtml(observation.currentObservedCommit ?? 'Not recorded')} · Reproduction: ${escapeHtml(observation.reproductionStatus ?? 'UNKNOWN')} · Sources: ${escapeHtml(observation.sources.join(', ') || 'Not recorded')} diff --git a/test/fleet/console-attention-cohorts.test.mjs b/test/fleet/console-attention-cohorts.test.mjs index c7bcf5d..731479b 100644 --- a/test/fleet/console-attention-cohorts.test.mjs +++ b/test/fleet/console-attention-cohorts.test.mjs @@ -25,7 +25,7 @@ function finding(task, source = {}) { function cohortFixture(count = 49) { const tasks = Array.from({ length: count }, (_, index) => ({ id: taskId(index + 1), owner: `domain-${String(index + 1).padStart(2, '0')}`, - observedAt: `2026-09-${String((index % 28) + 1).padStart(2, '0')}T12:00:00.000Z`, + observedAt: String(index + 1).padStart(40, '0'), })); const findings = tasks.map((task, index) => finding(task, { code: index % 2 ? 'BACKLOG_OBSERVED_COMMIT_MISSING' : 'BACKLOG_OBSERVED_COMMIT_STALE', @@ -47,6 +47,13 @@ test('SCN-attention-advisory-cohort-cardinality-and-exact-references: 49 equival assert.equal(groups.fleet.filter((item) => /BACKLOG_OBSERVED_COMMIT_(STALE|MISSING)/.test(item.title)).length, 0); assert.deepEqual(groups.advisory[0].taskReferences, expectedReferences); assert.equal(groups.advisory[0].observations.length, 49); + assert.ok(Object.keys(groups).includes('advisory')); + assert.equal(Object.values(groups).reduce((count, group) => count + group.length, 0), 1, + 'the enumerable cohort participates in the existing group-count boundary'); + const serialized = JSON.parse(JSON.stringify(groups)); + assert.equal(serialized.advisory.length, 1); + assert.deepEqual(serialized.advisory[0].taskReferences, expectedReferences); + assert.equal(serialized.advisory[0].observations.length, 49); const fixture = cohortFixture(); for (const observation of groups.advisory[0].observations) { const task = fixture.backlog.find((entry) => entry.id === observation.taskId); @@ -90,6 +97,7 @@ test('SCN-attention-advisory-cohort-cardinality-and-exact-references: 49 equival assert.equal(await advisory.locator('.attention-item').count(), 1); assert.equal(await advisory.locator('.attention-cohort-details li').count(), 49); assert.deepEqual(await advisory.locator('.attention-cohort-details li code').allTextContents(), expectedReferences); + assert.match(await advisory.locator('.attention-cohort-details li').first().textContent(), /Observed source\/revision: 0{39}1/); assert.equal(await page.locator('#attention-list .attention-group-fleet .attention-item').filter({ hasText: /BACKLOG_OBSERVED_COMMIT_(STALE|MISSING)|task evidence record.*need refresh/, }).count(), 0); @@ -105,7 +113,7 @@ test('SCN-attention-advisory-provenance-and-semantic-conflicts: equal sources me { field: 'taskId', firstId: 'TASK-ID-A', secondId: 'TASK-ID-B' }, { field: 'code', change: { code: 'BACKLOG_OBSERVED_COMMIT_MISSING' } }, { field: 'owner', change: { owner: 'domain-conflict' } }, - { field: 'observedAt', change: { observedAt: '2026-10-01T12:01:00.000Z' } }, + { field: 'observedAt', change: { observedAt: '1'.repeat(40) } }, { field: 'currentObservedCommit', change: { currentObservedCommit: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' } }, { field: 'reproductionStatus', change: { reproductionStatus: 'REPRODUCED' } }, ]; @@ -114,7 +122,7 @@ test('SCN-attention-advisory-provenance-and-semantic-conflicts: equal sources me const tasks = []; for (const [index, conflict] of conflictCases.entries()) { const id = `TASK-CONFLICT-${conflict.field}`; - const firstTask = { id: conflict.firstId ?? id, owner: 'domain-a', observedAt: `2026-10-01T12:0${index}:00.000Z` }; + const firstTask = { id: conflict.firstId ?? id, owner: 'domain-a', observedAt: String(index + 1).padStart(40, '0') }; const secondTask = conflict.secondId ? { ...firstTask, id: conflict.secondId } : firstTask; @@ -139,7 +147,7 @@ test('SCN-attention-advisory-provenance-and-semantic-conflicts: equal sources me assert.ok(['doctor', 'backlogHealth'].includes(observation.sources[0])); } - const equalTask = { id: 'TASK-EQUAL', owner: 'domain-equal', observedAt: '2026-10-01T13:00:00.000Z' }; + const equalTask = { id: 'TASK-EQUAL', owner: 'domain-equal', observedAt: 'b'.repeat(40) }; const equalGroups = project({ repository: { head: '316d8b7480c2e10925ebdff8310fe26ef5a881a8' }, backlog: [equalTask], doctor: { findings: [finding(equalTask)] }, backlogHealth: { findings: [finding(equalTask)] } }); assert.equal(equalGroups.advisory[0].observations.length, 1); @@ -158,7 +166,7 @@ test('SCN-attention-advisory-does-not-hide-owner-or-fleet-risks: unsafe and unkn { severity: 'error', code: 'BACKLOG_HEALTH_INVALID', message: 'Backlog schema validation failed.' }, { severity: 'warning', code: 'RECOVERABILITY', offMachine: false, recommendation: 'Verify the recovery copy.' }, ] }, - backlog: [{ id: 'TASK-ADVISORY', owner: 'provider-runtime', observedAt: '2026-10-01T10:00:00.000Z' }], + backlog: [{ id: 'TASK-ADVISORY', owner: 'provider-runtime', observedAt: 'f'.repeat(40) }], managerWakes: { blockingCount: 1, reservations: [{ managerId: 'provider-runtime', scheduleId: 'wake-a', outcome: 'unknown', blocksManagerWake: true }] }, deliveryOperations: [{ operation: 'publish', state: 'unknown', actor: 'provider-runtime', commit: '316d8b7480c2e10925ebdff8310fe26ef5a881a8' }], }); @@ -178,7 +186,7 @@ test('SCN-attention-advisory-does-not-hide-owner-or-fleet-risks: unsafe and unkn test('SCN-attention-advisory-projection-is-read-only: projection preserves snapshot and existing owner decision shape', () => { const snapshot = { repository: { head: '316d8b7480c2e10925ebdff8310fe26ef5a881a8' }, - backlog: [{ id: 'TASK-READONLY', owner: 'provider-runtime', observedAt: '2026-10-01T12:00:00.000Z' }], + backlog: [{ id: 'TASK-READONLY', owner: 'provider-runtime', observedAt: '0'.repeat(40) }], doctor: { findings: [{ severity: 'warning', code: 'BACKLOG_OBSERVED_COMMIT_STALE', taskId: 'TASK-READONLY' }] }, backlogHealth: { findings: [{ severity: 'warning', code: 'BACKLOG_OBSERVED_COMMIT_STALE', taskId: 'TASK-READONLY' }] }, approvalRequests: { items: [{ id: 'owner-confirm', revision: 7, status: 'pending', approver: 'owner', requester: 'provider-runtime', @@ -224,4 +232,8 @@ test('SCN-attention-advisory-unknown-fields-stay-unknown: absent owner and obser assert.equal(observation.currentObservedCommit, null); assert.equal(observation.reproductionStatus, 'UNKNOWN'); assert.deepEqual(observation.sources, ['doctor']); + assert.equal(Object.hasOwn(groups, 'advisory'), true); + const emptyGroups = project({ doctor: { findings: [] }, backlogHealth: { findings: [] } }); + assert.deepEqual(emptyGroups, { owner: [], fleet: [], arbiter: [] }); + assert.equal(Object.hasOwn(emptyGroups, 'advisory'), false); }); From fcd9b049e8b28886015189351bf4b8ae7fafb15e Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN Date: Thu, 1 Oct 2026 13:37:43 -0400 Subject: [PATCH 04/31] TASK-conversation-console-workflow: compact Overview --- docs/TEST_INTEGRITY.md | 47 ++++ site/attention-projection.js | 36 ++- site/console.html | 19 +- site/console.js | 113 +++++--- site/styles.css | 36 ++- test/fleet/console-overview-density.test.mjs | 261 +++++++++++++++++++ 6 files changed, 466 insertions(+), 46 deletions(-) create mode 100644 test/fleet/console-overview-density.test.mjs diff --git a/docs/TEST_INTEGRITY.md b/docs/TEST_INTEGRITY.md index a2aef47..a35e033 100644 --- a/docs/TEST_INTEGRITY.md +++ b/docs/TEST_INTEGRITY.md @@ -2764,3 +2764,50 @@ test_integrity_note: did_relax_any_assertion: false if_true_explain_spec_basis: "" ``` + +# Compact Console Overview density (2026-10-01) + +```yaml +test_integrity_note: + change_type: new_tests + scenarios: + - id: SCN-console-overview-owner-decisions-and-urgent-hazards-lead-with-distinct-counts + given: "An owner-addressed pending decision, unsafe Fleet findings, a manager-wake reservation, and advisory evidence" + when: "The Console renders its Overview attention summary" + then: "Owner decisions and urgent hazards lead, exact owner/action/advisory counts stay distinct, and only the guarded owner decision shortcuts appear" + - id: SCN-console-overview-clean-unique-commits-stay-informational-and-unsafe-workstays-reviewable + given: "One clean branch-ahead worktree and a separate dirty worktree" + when: "The Overview projects worktree evidence" + then: "Unique commits remain informational and inspectable while dirty work remains a named review finding" + - id: SCN-console-overview-crowded-preview-preserves-every-fleet-and-advisory-record-at-desktop-and-mobile + given: "A fixed 49-reference advisory cohort with one conflicting observation and 17 Fleet findings including unknown ownership and long evidence" + when: "The Overview is measured at 1440x1000 and 390x844 and View all Fleet opens the full queue" + then: "Shown/total/remaining counts are exact, urgent findings stay visible, every Fleet record and all advisory provenance/conflicts/unknowns remain reachable, and no horizontal overflow or clipped record occurs" + - id: SCN-console-overview-refresh-and-navigation-preserve-owner-drafts-without-adding-unsafe-actions + given: "An unsent task draft and a pending owner decision in the isolated demo" + when: "The owner navigates away and refreshes the Console" + then: "The draft remains intact, the owner shortcut boundary remains explicit, and no acknowledgement, close, recovery, or non-owner decision action is introduced" + - id: SCN-console-overview-retains-ten-destinations-keyboard-routing-and-manager-wake-deep-links + given: "The ten existing Console view destinations and an urgent manager-wake finding on a mobile viewport" + when: "The owner uses keyboard navigation and opens the manager-wakes route directly and through browser history" + then: "Each route selects its owning view, nested targets receive focus, Back/Forward remain coherent, and the urgent manager-wakes target is reachable" + observable_outcomes: + - "Owner and urgent counts are separate from advisory counts and clean unique commits do not inflate hazards" + - "A bounded Fleet preview states exact shown, total, and remaining values and links to every original Fleet record" + - "The full queue retains long evidence, conflicting advisory observations, exact task references, source provenance, and unknown ownership" + - "The ten existing destinations, guarded owner action, drafts, mobile keyboard focus, and manager-wakes deep link remain observable" + - "Fixed crowded data has no horizontal overflow or clipped urgent/evidence content at 1440x1000 and 390x844" + determinism_controls: + - "Hermetic loopback Console server with the isolated in-memory demo snapshot replaced by fixed task, owner, finding, evidence, and commit values" + - "Fixed Playwright viewport dimensions and deterministic route/history actions; no live project mutation, external service, retry, or sleep" + anti_cheat_rationale: + prevents: + - "Hard-coded preview/count labels that omit original findings" + - "Hiding urgent or unknown-owner evidence behind advisory totals" + - "Classifying clean unique commits as unsafe work" + - "Losing conflicting source observations or inventing provenance" + - "Adding owner actions that bypass the existing decision preview boundary" + relaxation: + did_relax_any_assertion: false + if_true_explain_spec_basis: "" +``` diff --git a/site/attention-projection.js b/site/attention-projection.js index ddf4d3e..1808af1 100644 --- a/site/attention-projection.js +++ b/site/attention-projection.js @@ -192,5 +192,39 @@ function attentionGroups(snapshot) { return groups; } - global.TorchAttentionProjection = Object.freeze({ groups: attentionGroups }); +function attentionSummary(groups) { + const fleet = groups.fleet ?? []; + const owner = groups.owner ?? []; + const arbiter = groups.arbiter ?? []; + const advisory = groups.advisory ?? []; + const actionableFleet = fleet.filter((item) => item.tone !== 'info'); + const advisoryReferences = advisory.reduce((count, item) => count + + (Array.isArray(item.taskReferences) ? item.taskReferences.length : item.observations?.length ?? 0), 0); + return { + ownerDecisions: owner.length, + urgentHazards: fleet.filter((item) => item.tone === 'urgent').length, + actionable: owner.length + actionableFleet.length + arbiter.filter((item) => item.tone !== 'info').length, + fleetTotal: fleet.length, + fleetInformational: fleet.length - actionableFleet.length, + advisoryCohorts: advisory.length, + advisoryReferences, + }; +} + +function fleetPreview(groups, additionalLimit = 3) { + const fleet = groups.fleet ?? []; + const urgent = fleet.filter((item) => item.tone === 'urgent'); + const remaining = fleet.filter((item) => item.tone !== 'urgent') + .sort((left, right) => { + const rank = { decision: 0, review: 1, info: 2 }; + return (rank[left.tone] ?? 3) - (rank[right.tone] ?? 3); + }); + return [...urgent, ...remaining.slice(0, Math.max(0, additionalLimit))]; +} + + global.TorchAttentionProjection = Object.freeze({ + groups: attentionGroups, + summary: attentionSummary, + fleetPreview, + }); })(globalThis); diff --git a/site/console.html b/site/console.html index 3ecc697..4af0f6e 100644 --- a/site/console.html +++ b/site/console.html @@ -67,8 +67,17 @@
@@ -199,6 +208,14 @@

Ownership

Implementation stays with specialists
+
+

Every finding remains inspectable

Fleet review queue

Full evidence and provenance
+ +

Loading findings…

+
+
diff --git a/site/console.js b/site/console.js index ecc0d88..2cd75cb 100644 --- a/site/console.js +++ b/site/console.js @@ -954,9 +954,59 @@ function renderFlowWatch(snapshot) { function attentionGroups(snapshot) { return globalThis.TorchAttentionProjection.groups(snapshot); } -function attentionItemCount(groups) { - return ['owner', 'fleet', 'advisory', 'arbiter'] - .reduce((count, key) => count + (groups[key]?.length ?? 0), 0); +function attentionSummary(groups) { + return globalThis.TorchAttentionProjection.summary(groups); +} +function renderAttentionItem(item) { + const severity = ({ urgent: 'Urgent', review: 'Review', decision: 'Decision required', info: 'Progress' })[item.tone] ?? 'Unknown'; + return `
+
+ ${escapeHtml(item.title)}

${escapeHtml(item.detail)}

+ Severity: ${escapeHtml(severity)} · Owner: ${escapeHtml(item.owner)}${item.waitSince ? ` · Waiting ${escapeHtml(ageLabel(item.waitSince))}` : ''} + ${item.evidence.length > 120 + ? `
Evidence and references

${escapeHtml(item.evidence)}

` + : `Evidence: ${escapeHtml(item.evidence)}`} + ${item.observations ? `
Recheck observations (${item.observations.length})
    ${item.observations.map((observation) => `
  • + ${escapeHtml(observation.taskId ?? 'Task reference not recorded')} · ${escapeHtml(observation.code)} + · Owner: ${escapeHtml(observation.owner ?? 'Owner not recorded')} + · Observed source/revision: ${escapeHtml(observation.observedAt ?? 'Not recorded')} + · Current observed commit: ${escapeHtml(observation.currentObservedCommit ?? 'Not recorded')} + · Reproduction: ${escapeHtml(observation.reproductionStatus ?? 'UNKNOWN')} + · Sources: ${escapeHtml(observation.sources.join(', ') || 'Not recorded')} +
  • `).join('')}
` : ''} + ${escapeHtml(item.action)} + ${item.decisionApprovalId ? `
+ + + +
` : ''} + ${item.requestOwner ? `Request review from ${escapeHtml(item.owner)}` : ''} +
`; +} +function renderAttentionGroup(key, title, description, items, idPrefix) { + const headingId = `${idPrefix}-${key}-title`; + return `
+

${title}

${items.length}
+ ${items.length ? items.map(renderAttentionItem).join('') : `

${escapeHtml(description)}

`} +
`; +} +function applyFleetAttentionFilter(query = '') { + const list = $('#fleet-attention-list'); + if (!list) return; + const normalized = query.trim().toLocaleLowerCase(); + const records = [...list.querySelectorAll('[data-attention-record]')]; + let visible = 0; + for (const record of records) { + const matches = !normalized || record.textContent.toLocaleLowerCase().includes(normalized); + record.hidden = !matches; + if (matches) visible += 1; + } + for (const group of list.querySelectorAll('.attention-group')) { + group.hidden = ![...group.querySelectorAll('[data-attention-record]')].some((record) => !record.hidden); + } + setText('#fleet-attention-result-count', normalized + ? `${visible} of ${records.length} findings match.` + : `${records.length} Fleet and advisory findings available.`); } function renderAttention(groups) { const definitions = [ @@ -965,37 +1015,27 @@ function renderAttention(groups) { ['advisory', 'Advisory / recheck cohorts', 'Observed evidence grouped for rechecking; these findings do not assign an owner action.'], ['arbiter', 'Arbiter handling', 'No observed item has structured evidence assigning its next step exclusively to an arbiter.'], ]; - const total = attentionItemCount(groups); - setText('#attention-count', `${total} ${total === 1 ? 'item' : 'items'}`); - setHtml('#attention-list', definitions.map(([key, title, description]) => { - const items = groups[key] ?? []; - return `
-

${title}

${items.length}
- ${items.length ? items.map((item) => `
-
- ${escapeHtml(item.title)}

${escapeHtml(item.detail)}

- Severity: ${escapeHtml(({ urgent: 'Urgent', review: 'Review', decision: 'Decision required', info: 'Progress' })[item.tone] ?? 'Unknown')} · Owner: ${escapeHtml(item.owner)}${item.waitSince ? ` · Waiting ${escapeHtml(ageLabel(item.waitSince))}` : ''} - ${item.evidence.length > 120 - ? `
Evidence and references

${escapeHtml(item.evidence)}

` - : `Evidence: ${escapeHtml(item.evidence)}`} - ${item.observations ? `
Recheck observations (${item.observations.length})
    ${item.observations.map((observation) => `
  • - ${escapeHtml(observation.taskId ?? 'Task reference not recorded')} · ${escapeHtml(observation.code)} - · Owner: ${escapeHtml(observation.owner ?? 'Owner not recorded')} - · Observed source/revision: ${escapeHtml(observation.observedAt ?? 'Not recorded')} - · Current observed commit: ${escapeHtml(observation.currentObservedCommit ?? 'Not recorded')} - · Reproduction: ${escapeHtml(observation.reproductionStatus ?? 'UNKNOWN')} - · Sources: ${escapeHtml(observation.sources.join(', ') || 'Not recorded')} -
  • `).join('')}
` : ''} - ${escapeHtml(item.action)} - ${item.decisionApprovalId ? `
- - - -
` : ''} - ${item.requestOwner ? `Request review from ${escapeHtml(item.owner)}` : ''} -
`).join('') : `

${escapeHtml(description)}

`} -
`; - }).join('')); + const summary = attentionSummary(groups); + const fleetPreview = globalThis.TorchAttentionProjection.fleetPreview(groups); + const remaining = Math.max(0, summary.fleetTotal - fleetPreview.length); + const priorQuery = $('#fleet-attention-filter')?.value ?? ''; + setText('#attention-count', `${summary.actionable} actionable`); + setText('#owner-decision-count', `${summary.ownerDecisions} owner decision${summary.ownerDecisions === 1 ? '' : 's'}`); + setText('#urgent-hazard-count', `${summary.urgentHazards} urgent hazard${summary.urgentHazards === 1 ? '' : 's'}`); + setText('#advisory-count', summary.advisoryReferences + ? `${summary.advisoryCohorts} cohort${summary.advisoryCohorts === 1 ? '' : 's'} · ${summary.advisoryReferences} task references` + : `${summary.advisoryCohorts} advisory cohort${summary.advisoryCohorts === 1 ? '' : 's'}`); + setText('#fleet-preview-count', `Showing ${fleetPreview.length} of ${summary.fleetTotal} Fleet findings; ${remaining} remaining`); + setHtml('#attention-list', definitions.map(([key, title, description]) => renderAttentionGroup( + key, title, description, key === 'fleet' ? fleetPreview : groups[key] ?? [], 'overview-attention', + )).join('')); + setHtml('#fleet-attention-list', [ + renderAttentionGroup('fleet', 'Fleet handling', 'No Fleet-owned findings are recorded.', groups.fleet ?? [], 'fleet-queue'), + renderAttentionGroup('advisory', 'Advisory / recheck cohorts', 'No advisory cohorts are recorded.', groups.advisory ?? [], 'fleet-queue'), + ].join('')); + const filter = $('#fleet-attention-filter'); + if (filter) filter.value = priorQuery; + applyFleetAttentionFilter(priorQuery); } function render(snapshot) { @@ -1025,9 +1065,9 @@ function render(snapshot) { const parts = [`${agents.length} persistent ${agents.length === 1 ? 'identity' : 'identities'}`, `${working} ${working === 1 ? 'task' : 'tasks'} in progress`]; if (blocked) parts.push(`${blocked} blocked`); - const attentionCount = attentionItemCount(attention); + const attentionCounts = attentionSummary(attention); setText('#project-summary', snapshot.mode === 'installed' - ? `${parts.join(' · ')}. ${attentionCount ? `${attentionCount} item${attentionCount === 1 ? '' : 's'} need a named next step.` : 'No owner decision is currently waiting in the observed queues.'}` + ? `${parts.join(' · ')}. ${attentionCounts.ownerDecisions} owner decision${attentionCounts.ownerDecisions === 1 ? '' : 's'} · ${attentionCounts.urgentHazards} urgent hazard${attentionCounts.urgentHazards === 1 ? '' : 's'} · ${attentionCounts.actionable} actionable across the observed queues.` : 'This repository has not been installed as a TORCH Fleet. The view is showing repository intake only.'); renderTaskCreateOptions(agents, tasks, snapshot.mode === 'installed'); renderOwnerRequestOptions(agents, tasks, snapshot.mode === 'installed'); @@ -2136,6 +2176,7 @@ $('#attention-list')?.addEventListener('click', (event) => { }); $('#refresh-console')?.addEventListener('click', () => refresh()); +$('#fleet-attention-filter')?.addEventListener('input', (event) => applyFleetAttentionFilter(event.currentTarget.value)); $('#toggle-live-refresh')?.addEventListener('click', () => { liveUpdatesPaused = !liveUpdatesPaused; $('#toggle-live-refresh').textContent = liveUpdatesPaused ? 'Resume updates' : 'Pause updates'; diff --git a/site/styles.css b/site/styles.css index b6799b4..144369a 100644 --- a/site/styles.css +++ b/site/styles.css @@ -166,30 +166,30 @@ footer div { display: flex; gap: 1.5rem; } .task-reference { margin-top: .55rem; color: var(--muted); font-size: .75rem; overflow-wrap: anywhere; } .task-reference code { display: inline-block; margin-top: .3rem; color: var(--ink); } .console-error { margin: 1.2rem 0 0; background: var(--warning-surface); color: var(--warning-ink); border-left: 4px solid var(--signal); padding: 1rem; } -.pulse-zone { display: grid; grid-template-columns: minmax(0, 1.45fr) minmax(270px, .8fr); border-bottom: 1px solid var(--ink); } -.pulse-main { min-width: 0; padding: 2rem 2rem 1.6rem 0; } +.pulse-zone { display: grid; grid-template-columns: minmax(0, .82fr) minmax(0, 1.18fr); gap: clamp(1.25rem, 2.8vw, 2.6rem); border-bottom: 1px solid var(--ink); } +.pulse-main { min-width: 0; align-self: start; padding: 1.55rem 0 1.25rem; } .pulse-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 1.25rem; } .section-kicker { margin: 0 0 .35rem; color: var(--muted); font-size: .78rem; font-weight: 700; } .pulse-heading h1, .section-heading h2 { max-width: none; font-size: clamp(1.75rem, 2.8vw, 2.5rem); line-height: 1.05; letter-spacing: -.035em; } .health-flag { display: inline-flex; align-items: center; border: 1px solid var(--field); color: var(--field); padding: .25rem .55rem; font-size: .78rem; font-weight: 700; } -.pulse-summary { max-width: 60ch; margin: 1rem 0 1.65rem; color: var(--muted); font-size: 1.05rem; } +.pulse-summary { max-width: 50ch; margin: .75rem 0 1.15rem; color: var(--muted); font-size: .9rem; line-height: 1.5; } .pulse-readouts { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); border-top: 1px solid var(--line); } .pulse-readouts div { min-width: 0; display: grid; align-content: start; gap: .3rem; padding: .8rem .8rem .2rem 0; } .pulse-readouts div + div { padding-left: .8rem; border-left: 1px solid var(--line); } .pulse-readouts span { color: var(--muted); font-size: .74rem; } .pulse-readouts strong { overflow-wrap: anywhere; font-family: var(--display); font-size: .95rem; } -.attention-panel { padding: 1.45rem 0 1rem 1.35rem; border-left: 1px solid var(--line); } +.attention-panel { min-width: 0; align-self: start; padding: 1.15rem 0 1rem 1.2rem; border-left: 1px solid var(--line); } .panel-heading, .section-heading { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; } .panel-heading { padding-bottom: .65rem; border-bottom: 1px solid var(--line); } .panel-heading h2, .panel-heading h3 { margin: 0; font: 650 1rem/1.2 var(--display); } .panel-heading span, .section-heading > span { color: var(--muted); font-size: .76rem; text-align: right; } -.attention-list { display: grid; gap: 1rem; } -.attention-group { min-width: 0; padding-top: .6rem; border-top: 1px solid var(--line); } +.attention-list { display: grid; gap: .6rem; } +.attention-group { min-width: 0; padding-top: .4rem; border-top: 1px solid var(--line); } .attention-group-heading { display: flex; align-items: baseline; justify-content: space-between; gap: .8rem; } .attention-group-heading h3 { margin: 0; font: 650 .9rem/1.25 var(--display); } .attention-group-heading span, .attention-empty { color: var(--muted); font-size: .72rem; } .attention-empty { margin: .35rem 0 0; line-height: 1.4; } -.attention-item { display: grid; grid-template-columns: 9px minmax(0, 1fr); gap: .7rem; padding: .75rem 0; border-bottom: 1px solid var(--line); } +.attention-item { display: grid; grid-template-columns: 8px minmax(0, 1fr); gap: .55rem; padding: .5rem 0; border-bottom: 1px solid var(--line); } .attention-mark { width: 7px; height: 7px; margin-top: .45rem; background: var(--field); } .tone-urgent .attention-mark { background: var(--signal); } .tone-review .attention-mark { background: var(--route); } @@ -197,7 +197,7 @@ footer div { display: flex; gap: 1.5rem; } .tone-info .attention-mark { background: var(--field); } .attention-copy { min-width: 0; } .attention-item strong { display: block; font-size: .86rem; overflow-wrap: anywhere; } -.attention-item p { margin: .18rem 0 .35rem; color: var(--muted); font-size: .78rem; line-height: 1.4; } +.attention-item p { margin: .18rem 0 .35rem; color: var(--muted); font-size: .78rem; line-height: 1.4; overflow-wrap: anywhere; } .attention-item small { display: block; color: var(--muted); font-size: .69rem; line-height: 1.35; overflow-wrap: anywhere; } .attention-item a { display: inline-block; margin: .35rem .8rem 0 0; color: var(--route); font-size: .76rem; font-weight: 650; } .attention-evidence { margin-top: .28rem; color: var(--muted); font-size: .69rem; } @@ -207,6 +207,25 @@ footer div { display: flex; gap: 1.5rem; } .attention-quick-actions button { min-height: 2rem; padding: .3rem .55rem; } .attention-action-status { flex-basis: 100%; margin: .1rem 0 0; color: var(--muted); font-size: .7rem; line-height: 1.4; } .attention-action-status[hidden] { display: none; } +.attention-counts { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .45rem; padding: .6rem 0 .25rem; } +.attention-counts p { min-width: 0; margin: 0; padding-left: .55rem; border-left: 2px solid var(--line); } +.attention-counts p:first-child { border-left-color: #f3bd55; } +.attention-counts p:nth-child(2) { border-left-color: var(--signal); } +.attention-counts p:last-child { border-left-color: var(--field); } +.attention-counts strong, .attention-counts span { display: block; overflow-wrap: anywhere; } +.attention-counts strong { font-size: .74rem; line-height: 1.25; } +.attention-counts span { margin-top: .12rem; color: var(--muted); font-size: .65rem; line-height: 1.3; } +.attention-footer { display: flex; justify-content: space-between; align-items: baseline; gap: .75rem; padding-top: .6rem; color: var(--muted); font-size: .7rem; } +.attention-footer a { color: var(--route); font-weight: 700; white-space: nowrap; } +.fleet-attention-queue { min-width: 0; margin-top: 1.2rem; padding-top: 1.2rem; border-top: 1px solid var(--ink); scroll-margin-top: 1rem; } +.fleet-attention-filter { display: grid; gap: .3rem; max-width: 34rem; color: var(--muted); font-size: .75rem; font-weight: 650; } +.fleet-attention-filter input { width: 100%; min-width: 0; min-height: 2.5rem; padding: .45rem .6rem; border: 1px solid var(--line); border-radius: 0; background: var(--deep); color: var(--ink); font: 500 .82rem/1.3 var(--body); } +.fleet-attention-result-count { color: var(--muted); font-size: .75rem; } +.fleet-attention-list { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1.1rem; } +.fleet-attention-list > .attention-group { padding: .7rem; border: 1px solid var(--line); background: var(--paper); } +.fleet-attention-list .attention-evidence[open] p { max-width: 80ch; } +.attention-cohort-details li, .attention-cohort-details code { overflow-wrap: anywhere; } +.fleet-attention-queue [hidden] { display: none !important; } .board-section, .console-section { padding-block: 1.65rem; border-bottom: 1px solid var(--line); scroll-margin-top: 1rem; } .section-heading { margin-bottom: 1rem; } .section-heading h2 { margin: 0; } @@ -629,6 +648,7 @@ footer div { display: flex; gap: 1.5rem; } .console-workspace { padding-inline: .8rem; } .console-rail { grid-template-columns: repeat(2, minmax(0, 1fr)); } .pulse-zone, .fleet-columns, .evidence-columns, .delivery-columns, .flow-watch-grid { grid-template-columns: 1fr; } + .fleet-attention-list { grid-template-columns: 1fr; } .owner-request-fields, .runtime-profile-fields { grid-template-columns: 1fr; } .schedule-launcher-actions { align-items: stretch; flex-direction: column; } .lifecycle-heading { flex-direction: column; gap: .25rem; } diff --git a/test/fleet/console-overview-density.test.mjs b/test/fleet/console-overview-density.test.mjs new file mode 100644 index 0000000..614e612 --- /dev/null +++ b/test/fleet/console-overview-density.test.mjs @@ -0,0 +1,261 @@ +import assert from 'node:assert/strict'; +import { once } from 'node:events'; +import test, { after, before } from 'node:test'; +import { chromium } from '@playwright/test'; +import { createConsoleServer } from '../../src/console/server.mjs'; +import '../../site/attention-projection.js'; + +let server; +let browser; +let origin; + +before(async () => { + server = createConsoleServer(); + server.listen(0, '127.0.0.1'); + await once(server, 'listening'); + origin = `http://127.0.0.1:${server.address().port}`; + browser = await chromium.launch({ headless: true }); +}); + +after(async () => { + await browser?.close(); + if (server?.listening) await new Promise((resolve) => server.close(resolve)); +}); + +async function openOverview(viewport = { width: 1440, height: 1000 }) { + const page = await browser.newPage({ viewport }); + await page.goto(`${origin}/console?demo=1#overview`); + await page.locator('#live-refresh-status[data-generation="1"]').waitFor(); + const snapshot = await page.evaluate(() => structuredClone(globalThis.TorchConsoleDemo.snapshot())); + return { page, snapshot }; +} + +async function renderSnapshot(page, snapshot) { + await page.evaluate((fixed) => { globalThis.TorchConsoleDemo.snapshot = () => structuredClone(fixed); }, snapshot); + await page.locator('#refresh-console').click(); + await page.locator('#live-refresh-status[data-generation="2"]').waitFor(); +} + +function unsafeSnapshot(snapshot) { + snapshot.mode = 'installed'; + snapshot.approvalRequests = { available: true, pendingCount: 1, items: [{ + id: 'owner-review-1', revision: 4, status: 'pending', approver: 'owner', requester: 'qa', + title: 'Review the exact owner decision', task: 'TASK-CONSOLE-OVERVIEW', evidence: 'same-origin preview and current revision', + }] }; + snapshot.fleetChanges = []; + snapshot.hierarchyProposals = []; + snapshot.doctor = { ...(snapshot.doctor ?? {}), findings: [ + { severity: 'error', code: 'BACKLOG_HEALTH_INVALID', message: 'Current schema validation failed; inspect the evidence before action.' }, + { severity: 'error', code: 'WORKTREE_PROBLEM', area: 'qa', problem: 'git-operation:merge', path: '/work/qa' }, + { severity: 'warning', code: 'BACKLOG_OBSERVED_COMMIT_STALE', taskId: 'TASK-ADVISORY-1', owner: 'provider-runtime', observedAt: 'a'.repeat(40) }, + ] }; + snapshot.backlogHealth = { findings: [] }; + snapshot.worktrees = [{ area: 'qa', branch: 'qa/review', path: '/work/qa', ahead: 2, behind: 0 }]; + snapshot.agents = [...(snapshot.agents ?? []), { areaId: 'qa', title: 'QA' }]; + snapshot.managerWakes = { blockingCount: 1, reservations: [{ managerId: 'qa', scheduleId: 'wake-1', reservedAt: '2026-10-01T00:00:00Z', outcome: 'unknown', blocksManagerWake: true }] }; + snapshot.deliveryOperations = [{ operation: 'apply', state: 'unknown', actor: 'qa', commit: 'c'.repeat(40) }]; + snapshot.backlog = [...(snapshot.backlog ?? []), { id: 'TASK-ADVISORY-1', owner: 'provider-runtime', observedAt: 'a'.repeat(40), state: 'in_progress' }]; + return snapshot; +} + +test('SCN-console-overview-owner-decisions-and-urgent-hazards-lead-with-distinct-counts', async (t) => { + const { page, snapshot } = await openOverview(); + t.after(() => page.close()); + await renderSnapshot(page, unsafeSnapshot(snapshot)); + const projected = globalThis.TorchAttentionProjection.summary(globalThis.TorchAttentionProjection.groups(unsafeSnapshot(snapshot))); + + const groups = page.locator('#attention-list > .attention-group'); + assert.match(await groups.nth(0).locator('h3').innerText(), /Waiting on you/); + assert.match(await groups.nth(1).locator('h3').innerText(), /Fleet handling/); + assert.equal(await page.locator('#owner-decision-count').innerText(), `${projected.ownerDecisions} owner decision${projected.ownerDecisions === 1 ? '' : 's'}`); + assert.equal(await page.locator('#urgent-hazard-count').innerText(), `${projected.urgentHazards} urgent hazard${projected.urgentHazards === 1 ? '' : 's'}`); + assert.equal(await page.locator('#attention-count').innerText(), `${projected.actionable} actionable`); + assert.match(await page.locator('#advisory-count').innerText(), /1 cohort/); + assert.equal(await page.locator('#attention-list [data-attention-decision]').count(), 2); + assert.equal(await page.locator('#attention-list button').filter({ hasText: /Acknowledge|Close|Recover/ }).count(), 0); + assert.ok(await page.locator('#attention-list .tone-urgent').count() >= 3); + assert.equal(await page.locator('#attention-list a[href="#manager-wakes"]').count(), 1); +}); + +test('SCN-console-overview-clean-unique-commits-stay-informational-and-unsafe-workstays-reviewable', async (t) => { + const { page, snapshot } = await openOverview(); + t.after(() => page.close()); + snapshot.doctor = { ...(snapshot.doctor ?? {}), findings: [ + { severity: 'warning', code: 'WORKTREE_PROBLEM', area: 'project-kernel', problem: 'unique-commits:3', path: '/work/kernel' }, + { severity: 'warning', code: 'WORKTREE_PROBLEM', area: 'provider-runtime', problem: 'worktree-dirty', path: '/work/provider' }, + ] }; + snapshot.worktrees = [ + { area: 'project-kernel', branch: 'kernel/progress', path: '/work/kernel', ahead: 3, behind: 0 }, + { area: 'provider-runtime', branch: 'provider/changes', path: '/work/provider', ahead: 0, behind: 0 }, + ]; + snapshot.agents = [...(snapshot.agents ?? []), { areaId: 'project-kernel', title: 'Project Kernel' }, { areaId: 'provider-runtime', title: 'Provider Runtime' }]; + await renderSnapshot(page, snapshot); + + const records = page.locator('#fleet-attention-list [data-attention-record]'); + const clean = records.filter({ hasText: 'has work ahead of main' }); + const dirty = records.filter({ hasText: 'worktree needs review' }); + assert.equal(await clean.count(), 1); + assert.ok(await clean.evaluate((item) => item.classList.contains('tone-info'))); + assert.match(await clean.innerText(), /3 unique commit\(s\) are ahead/); + assert.equal(await dirty.count(), 1); + assert.ok(await dirty.evaluate((item) => item.classList.contains('tone-review'))); + assert.match(await dirty.innerText(), /Uncommitted changes are present/); +}); + +function crowdedSnapshot(snapshot) { + const references = Array.from({ length: 49 }, (_, index) => `TASK-DENSITY-${String(index + 1).padStart(2, '0')}`); + const tasks = references.map((id, index) => ({ + id, owner: index === 47 ? null : `domain-${String(index + 1).padStart(2, '0')}`, + observedAt: index === 47 ? null : String(index + 1).padStart(40, '0'), state: 'in_progress', + })); + const doctor = tasks.map((task, index) => ({ severity: 'warning', code: 'BACKLOG_OBSERVED_COMMIT_STALE', + taskId: task.id, owner: task.owner, observedAt: task.observedAt, + ...(index === 47 ? { currentObservedCommit: null } : {}), + })); + const backlogHealth = structuredClone(doctor); + backlogHealth[48].owner = 'conflicting-owner'; + snapshot.backlog = tasks; + snapshot.fleetChanges = []; + snapshot.hierarchyProposals = []; + snapshot.approvalRequests = { available: true, pendingCount: 0, items: [] }; + snapshot.messages = { ...(snapshot.messages ?? {}), unacknowledged: 0, recent: [] }; + snapshot.integration = []; + snapshot.worktrees = []; + snapshot.backlogActivity = { tasks: [] }; + snapshot.managerWakes = { blockingCount: 0, reservations: [] }; + snapshot.deliveryOperations = []; + snapshot.repository = { ...(snapshot.repository ?? {}), head: 'd'.repeat(40) }; + snapshot.doctor = { ...(snapshot.doctor ?? {}), findings: [ + ...doctor, + ...Array.from({ length: 3 }, (_, index) => ({ severity: 'error', code: `FIXED_UNSAFE_${index + 1}`, + area: index === 0 ? null : `domain-${index + 1}`, message: `Urgent evidence ${index + 1}: ${'e'.repeat(180)}` })), + ...Array.from({ length: 14 }, (_, index) => ({ severity: 'warning', code: `FIXED_REVIEW_${index + 1}`, + area: index === 0 ? null : `domain-${index + 1}`, message: `Review evidence ${index + 1}: ${'f'.repeat(140)}` })), + ] }; + snapshot.backlogHealth = { findings: backlogHealth }; + return { snapshot, references }; +} + +test('SCN-console-overview-crowded-preview-preserves-every-fleet-and-advisory-record-at-desktop-and-mobile', async (t) => { + const desktop = await openOverview(); + t.after(() => desktop.page.close()); + const { snapshot, references } = crowdedSnapshot(desktop.snapshot); + await renderSnapshot(desktop.page, snapshot); + const projected = globalThis.TorchAttentionProjection.summary(globalThis.TorchAttentionProjection.groups(snapshot)); + assert.equal(await desktop.page.locator('#advisory-count').innerText(), '1 cohort · 49 task references'); + const previewCount = globalThis.TorchAttentionProjection.fleetPreview(globalThis.TorchAttentionProjection.groups(snapshot)).length; + assert.equal(await desktop.page.locator('#fleet-preview-count').innerText(), `Showing ${previewCount} of ${projected.fleetTotal} Fleet findings; ${projected.fleetTotal - previewCount} remaining`); + assert.equal(projected.fleetTotal, 17, 'the crowded data has exactly 17 original Fleet findings'); + assert.equal(await desktop.page.locator('#attention-list .tone-urgent').count(), 3); + const desktopWidth = await desktop.page.evaluate(() => ({ viewport: document.documentElement.clientWidth, + document: document.documentElement.scrollWidth, + layout: ['body', '.console-layout', '.console-workspace', '#overview', '.attention-panel', '#attention-list'].map((selector) => { + const item = document.querySelector(selector); const rect = item.getBoundingClientRect(); + return `${selector}: ${Math.round(rect.left)}..${Math.round(rect.right)} scroll=${item.scrollWidth}/${item.clientWidth}`; + }), + offenders: [...document.querySelectorAll('body *')].map((item) => ({ item, rect: item.getBoundingClientRect() })) + .filter(({ item, rect }) => !item.closest('[hidden]') && rect.width > 0 && (rect.right > document.documentElement.clientWidth + 1 || rect.left < -1 || item.scrollWidth > item.clientWidth + 2)) + .slice(0, 12).map(({ item, rect }) => `${item.tagName.toLowerCase()}${item.id ? `#${item.id}` : ''}.${String(item.className?.baseVal ?? item.className ?? '').split(' ').filter(Boolean).join('.')} ${Math.round(rect.left)}..${Math.round(rect.right)} scroll=${item.scrollWidth}/${item.clientWidth}`) })); + assert.ok(desktopWidth.document <= desktopWidth.viewport, JSON.stringify(desktopWidth)); + const overviewUrgentBounds = await desktop.page.locator('#attention-list .tone-urgent').evaluateAll((items) => items.map((item) => { + const rect = item.getBoundingClientRect(); return [rect.left, rect.right]; + })); + assert.ok(overviewUrgentBounds.every(([left, right]) => left >= 0 && right <= 1440)); + + await desktop.page.locator('.attention-footer a[href="#fleet-attention-queue"]').click(); + await desktop.page.waitForFunction(() => location.hash === '#fleet-attention-queue' && document.querySelector('.console-workspace').dataset.currentView === 'fleet'); + assert.equal(await desktop.page.locator('#fleet-attention-list .attention-group-fleet [data-attention-record]').count(), 17); + assert.equal(await desktop.page.locator('#fleet-attention-list .attention-group-advisory .attention-cohort-details li').count(), 50, + 'the conflicting owner remains a separate observation instead of overwriting provenance'); + assert.deepEqual(await desktop.page.locator('#fleet-attention-list .attention-group-advisory .attention-cohort-details li code').allTextContents(), + [...references, references[48]]); + assert.equal(await desktop.page.evaluate(() => document.activeElement.id), 'fleet-attention-queue'); + assert.match(await desktop.page.locator('#fleet-attention-list').innerText(), /Owner: Responsible domain not recorded/); + await desktop.page.locator('#fleet-attention-list .attention-group-advisory .attention-cohort-details summary').click(); + assert.match(await desktop.page.locator('#fleet-attention-list').innerText(), /Owner: Owner not recorded/); + assert.match(await desktop.page.locator('#fleet-attention-list').innerText(), /Sources: doctor, backlogHealth/); + assert.match(await desktop.page.locator('#fleet-attention-list').innerText(), /Urgent evidence 1/); + const queueFilter = desktop.page.locator('#fleet-attention-filter'); + await queueFilter.fill(references[0]); + assert.equal(await desktop.page.locator('#fleet-attention-result-count').innerText(), '1 of 18 findings match.'); + await queueFilter.fill(''); + assert.equal(await desktop.page.locator('#fleet-attention-list [data-attention-record]:visible').count(), 18); + await desktop.page.locator('#fleet-attention-list details').evaluateAll((details) => details.forEach((detail) => { detail.open = true; })); + assert.ok(await desktop.page.evaluate(() => document.documentElement.scrollWidth <= document.documentElement.clientWidth), + 'expanded full evidence and cohort references remain horizontally contained'); + const queueBounds = await desktop.page.locator('#fleet-attention-list [data-attention-record]').evaluateAll((items) => items.map((item) => { + const rect = item.getBoundingClientRect(); return [rect.left, rect.right]; + })); + assert.ok(queueBounds.every(([left, right]) => left >= 0 && right <= 1440)); + + const mobile = await openOverview({ width: 390, height: 844 }); + t.after(() => mobile.page.close()); + const mobileFixture = crowdedSnapshot(mobile.snapshot); + await renderSnapshot(mobile.page, mobileFixture.snapshot); + const mobileWidth = await mobile.page.evaluate(() => ({ viewport: document.documentElement.clientWidth, + document: document.documentElement.scrollWidth, + offenders: [...document.querySelectorAll('body *')].map((item) => ({ item, rect: item.getBoundingClientRect() })) + .filter(({ item, rect }) => !item.closest('[hidden]') && rect.width > 0 && rect.right > document.documentElement.clientWidth + 1) + .slice(0, 8).map(({ item, rect }) => `${item.tagName.toLowerCase()}${item.id ? `#${item.id}` : ''}.${String(item.className?.baseVal ?? item.className ?? '').split(' ').filter(Boolean).join('.')} right=${Math.round(rect.right)}`) })); + assert.ok(mobileWidth.document <= mobileWidth.viewport, JSON.stringify(mobileWidth)); + await mobile.page.locator('#attention-list .attention-group-advisory summary').click(); + const lastReference = mobile.page.locator('#attention-list .attention-group-advisory .attention-cohort-details li').last(); + const mobileBounds = await lastReference.boundingBox(); + assert.ok(mobileBounds && mobileBounds.x >= 0 && mobileBounds.x + mobileBounds.width <= 390, + 'expanded exact references remain within the mobile viewport'); + assert.ok(await mobile.page.evaluate(() => document.documentElement.scrollWidth <= document.documentElement.clientWidth), + 'expanded advisory provenance does not overflow the mobile viewport'); + assert.equal(await mobile.page.locator('#fleet-preview-count').innerText(), `Showing ${previewCount} of ${projected.fleetTotal} Fleet findings; ${projected.fleetTotal - previewCount} remaining`); +}); + +test('SCN-console-overview-refresh-and-navigation-preserve-owner-drafts-without-adding-unsafe-actions', async (t) => { + const { page, snapshot } = await openOverview(); + t.after(() => page.close()); + const unreadBefore = snapshot.messages?.unacknowledged ?? 0; + await renderSnapshot(page, unsafeSnapshot(snapshot)); + await page.locator('.console-rail a[href="#work"]').click(); + await page.locator('.task-create-panel > summary').click(); + const title = page.locator('#task-create-form [name="title"]'); + await title.fill('Unsent route-change draft for the current revision.'); + await page.locator('.console-rail a[href="#overview"]').click(); + await page.locator('#refresh-console').click(); + await page.locator('#live-refresh-status[data-generation="3"]').waitFor(); + await page.locator('.console-rail a[href="#work"]').click(); + assert.equal(await title.inputValue(), 'Unsent route-change draft for the current revision.'); + assert.equal(await page.locator('#attention-list [data-attention-decision]').count(), 2); + assert.equal(await page.locator('#attention-list button').filter({ hasText: /Acknowledge|Close|Recover/ }).count(), 0); + assert.equal(await page.evaluate(() => globalThis.TorchConsoleDemo.snapshot().messages?.unacknowledged ?? 0), unreadBefore, + 'rendering and refresh do not acknowledge durable messages'); +}); + +test('SCN-console-overview-retains-ten-destinations-keyboard-routing-and-manager-wake-deep-links', async (t) => { + const { page, snapshot } = await openOverview({ width: 390, height: 844 }); + t.after(() => page.close()); + await renderSnapshot(page, unsafeSnapshot(snapshot)); + const links = page.locator('.console-rail a[href^="#"]'); + assert.equal(await links.count(), 10); + const destinations = ['overview', 'owner-briefing', 'flow-watch', 'work', 'initiative-progress', 'fleet', + 'communications', 'organization', 'evidence', 'delivery']; + for (const destination of destinations) { + const link = page.locator(`.console-rail a[href="#${destination}"]`); + if (destination === 'fleet') { + await link.focus(); + await page.keyboard.press('Enter'); + } else await link.click(); + await page.waitForFunction((view) => location.hash === `#${view}` + && document.querySelector('.console-workspace').dataset.currentView === view, destination); + } + await page.locator('.console-rail a[href="#fleet"]').click(); + await page.waitForFunction(() => location.hash === '#fleet' + && document.querySelector('.console-workspace').dataset.currentView === 'fleet'); + await page.goto(`${origin}/console?demo=1#manager-wakes`); + await page.waitForFunction(() => document.querySelector('.console-workspace').dataset.currentView === 'fleet'); + await page.waitForFunction(() => document.activeElement.id === 'manager-wakes'); + assert.equal(await page.locator('#manager-wakes').isVisible(), true); + await page.goBack(); + await page.waitForFunction(() => document.querySelector('.console-workspace').dataset.currentView === 'fleet'); + await page.goForward(); + await page.waitForFunction(() => document.querySelector('.console-workspace').dataset.currentView === 'fleet'); + await page.waitForFunction(() => document.activeElement.id === 'manager-wakes'); + assert.ok(await page.evaluate(() => document.documentElement.scrollWidth <= document.documentElement.clientWidth)); +}); From 7adf36c1c0ffae09b117ea8d4ee767f98f7fedb2 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN Date: Thu, 1 Oct 2026 13:41:46 -0400 Subject: [PATCH 05/31] TASK-conversation-console-workflow: pin Overview counts --- test/fleet/console-overview-density.test.mjs | 21 ++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/test/fleet/console-overview-density.test.mjs b/test/fleet/console-overview-density.test.mjs index 614e612..f766781 100644 --- a/test/fleet/console-overview-density.test.mjs +++ b/test/fleet/console-overview-density.test.mjs @@ -62,14 +62,22 @@ test('SCN-console-overview-owner-decisions-and-urgent-hazards-lead-with-distinct const { page, snapshot } = await openOverview(); t.after(() => page.close()); await renderSnapshot(page, unsafeSnapshot(snapshot)); - const projected = globalThis.TorchAttentionProjection.summary(globalThis.TorchAttentionProjection.groups(unsafeSnapshot(snapshot))); + const groupsForCounts = globalThis.TorchAttentionProjection.groups(unsafeSnapshot(snapshot)); + const ownerDecisions = groupsForCounts.owner.length; + const urgentHazards = groupsForCounts.fleet.filter((item) => item.tone === 'urgent').length; + const actionable = ownerDecisions + groupsForCounts.fleet.filter((item) => item.tone !== 'info').length + + groupsForCounts.arbiter.filter((item) => item.tone !== 'info').length; + const summarized = globalThis.TorchAttentionProjection.summary(groupsForCounts); + assert.equal(summarized.ownerDecisions, ownerDecisions); + assert.equal(summarized.urgentHazards, urgentHazards); + assert.equal(summarized.actionable, actionable); const groups = page.locator('#attention-list > .attention-group'); assert.match(await groups.nth(0).locator('h3').innerText(), /Waiting on you/); assert.match(await groups.nth(1).locator('h3').innerText(), /Fleet handling/); - assert.equal(await page.locator('#owner-decision-count').innerText(), `${projected.ownerDecisions} owner decision${projected.ownerDecisions === 1 ? '' : 's'}`); - assert.equal(await page.locator('#urgent-hazard-count').innerText(), `${projected.urgentHazards} urgent hazard${projected.urgentHazards === 1 ? '' : 's'}`); - assert.equal(await page.locator('#attention-count').innerText(), `${projected.actionable} actionable`); + assert.equal(await page.locator('#owner-decision-count').innerText(), `${ownerDecisions} owner decision${ownerDecisions === 1 ? '' : 's'}`); + assert.equal(await page.locator('#urgent-hazard-count').innerText(), `${urgentHazards} urgent hazard${urgentHazards === 1 ? '' : 's'}`); + assert.equal(await page.locator('#attention-count').innerText(), `${actionable} actionable`); assert.match(await page.locator('#advisory-count').innerText(), /1 cohort/); assert.equal(await page.locator('#attention-list [data-attention-decision]').count(), 2); assert.equal(await page.locator('#attention-list button').filter({ hasText: /Acknowledge|Close|Recover/ }).count(), 0); @@ -144,8 +152,9 @@ test('SCN-console-overview-crowded-preview-preserves-every-fleet-and-advisory-re const projected = globalThis.TorchAttentionProjection.summary(globalThis.TorchAttentionProjection.groups(snapshot)); assert.equal(await desktop.page.locator('#advisory-count').innerText(), '1 cohort · 49 task references'); const previewCount = globalThis.TorchAttentionProjection.fleetPreview(globalThis.TorchAttentionProjection.groups(snapshot)).length; - assert.equal(await desktop.page.locator('#fleet-preview-count').innerText(), `Showing ${previewCount} of ${projected.fleetTotal} Fleet findings; ${projected.fleetTotal - previewCount} remaining`); + assert.equal(previewCount, 6, 'all three urgent records and only three additional preview records are shown'); assert.equal(projected.fleetTotal, 17, 'the crowded data has exactly 17 original Fleet findings'); + assert.equal(await desktop.page.locator('#fleet-preview-count').innerText(), 'Showing 6 of 17 Fleet findings; 11 remaining'); assert.equal(await desktop.page.locator('#attention-list .tone-urgent').count(), 3); const desktopWidth = await desktop.page.evaluate(() => ({ viewport: document.documentElement.clientWidth, document: document.documentElement.scrollWidth, @@ -205,7 +214,7 @@ test('SCN-console-overview-crowded-preview-preserves-every-fleet-and-advisory-re 'expanded exact references remain within the mobile viewport'); assert.ok(await mobile.page.evaluate(() => document.documentElement.scrollWidth <= document.documentElement.clientWidth), 'expanded advisory provenance does not overflow the mobile viewport'); - assert.equal(await mobile.page.locator('#fleet-preview-count').innerText(), `Showing ${previewCount} of ${projected.fleetTotal} Fleet findings; ${projected.fleetTotal - previewCount} remaining`); + assert.equal(await mobile.page.locator('#fleet-preview-count').innerText(), 'Showing 6 of 17 Fleet findings; 11 remaining'); }); test('SCN-console-overview-refresh-and-navigation-preserve-owner-drafts-without-adding-unsafe-actions', async (t) => { From 0c2aeed1db670d1c7ffd68d55ee129871362afe4 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN Date: Thu, 1 Oct 2026 13:46:56 -0400 Subject: [PATCH 06/31] TASK-conversation-console-workflow: use fixed Overview oracle --- test/fleet/console-overview-density.test.mjs | 25 ++++++-------------- 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/test/fleet/console-overview-density.test.mjs b/test/fleet/console-overview-density.test.mjs index f766781..7b31cc4 100644 --- a/test/fleet/console-overview-density.test.mjs +++ b/test/fleet/console-overview-density.test.mjs @@ -3,7 +3,6 @@ import { once } from 'node:events'; import test, { after, before } from 'node:test'; import { chromium } from '@playwright/test'; import { createConsoleServer } from '../../src/console/server.mjs'; -import '../../site/attention-projection.js'; let server; let browser; @@ -54,30 +53,24 @@ function unsafeSnapshot(snapshot) { snapshot.agents = [...(snapshot.agents ?? []), { areaId: 'qa', title: 'QA' }]; snapshot.managerWakes = { blockingCount: 1, reservations: [{ managerId: 'qa', scheduleId: 'wake-1', reservedAt: '2026-10-01T00:00:00Z', outcome: 'unknown', blocksManagerWake: true }] }; snapshot.deliveryOperations = [{ operation: 'apply', state: 'unknown', actor: 'qa', commit: 'c'.repeat(40) }]; - snapshot.backlog = [...(snapshot.backlog ?? []), { id: 'TASK-ADVISORY-1', owner: 'provider-runtime', observedAt: 'a'.repeat(40), state: 'in_progress' }]; + snapshot.backlog = [{ id: 'TASK-ADVISORY-1', owner: 'provider-runtime', observedAt: 'a'.repeat(40), state: 'in_progress' }]; + snapshot.integration = []; + snapshot.backlogActivity = { tasks: [] }; return snapshot; } test('SCN-console-overview-owner-decisions-and-urgent-hazards-lead-with-distinct-counts', async (t) => { const { page, snapshot } = await openOverview(); t.after(() => page.close()); + snapshot.messages = { ...(snapshot.messages ?? {}), unacknowledged: 0, recent: [] }; await renderSnapshot(page, unsafeSnapshot(snapshot)); - const groupsForCounts = globalThis.TorchAttentionProjection.groups(unsafeSnapshot(snapshot)); - const ownerDecisions = groupsForCounts.owner.length; - const urgentHazards = groupsForCounts.fleet.filter((item) => item.tone === 'urgent').length; - const actionable = ownerDecisions + groupsForCounts.fleet.filter((item) => item.tone !== 'info').length - + groupsForCounts.arbiter.filter((item) => item.tone !== 'info').length; - const summarized = globalThis.TorchAttentionProjection.summary(groupsForCounts); - assert.equal(summarized.ownerDecisions, ownerDecisions); - assert.equal(summarized.urgentHazards, urgentHazards); - assert.equal(summarized.actionable, actionable); const groups = page.locator('#attention-list > .attention-group'); assert.match(await groups.nth(0).locator('h3').innerText(), /Waiting on you/); assert.match(await groups.nth(1).locator('h3').innerText(), /Fleet handling/); - assert.equal(await page.locator('#owner-decision-count').innerText(), `${ownerDecisions} owner decision${ownerDecisions === 1 ? '' : 's'}`); - assert.equal(await page.locator('#urgent-hazard-count').innerText(), `${urgentHazards} urgent hazard${urgentHazards === 1 ? '' : 's'}`); - assert.equal(await page.locator('#attention-count').innerText(), `${actionable} actionable`); + assert.equal(await page.locator('#owner-decision-count').innerText(), '1 owner decision'); + assert.equal(await page.locator('#urgent-hazard-count').innerText(), '4 urgent hazards'); + assert.equal(await page.locator('#attention-count').innerText(), '5 actionable'); assert.match(await page.locator('#advisory-count').innerText(), /1 cohort/); assert.equal(await page.locator('#attention-list [data-attention-decision]').count(), 2); assert.equal(await page.locator('#attention-list button').filter({ hasText: /Acknowledge|Close|Recover/ }).count(), 0); @@ -149,11 +142,7 @@ test('SCN-console-overview-crowded-preview-preserves-every-fleet-and-advisory-re t.after(() => desktop.page.close()); const { snapshot, references } = crowdedSnapshot(desktop.snapshot); await renderSnapshot(desktop.page, snapshot); - const projected = globalThis.TorchAttentionProjection.summary(globalThis.TorchAttentionProjection.groups(snapshot)); assert.equal(await desktop.page.locator('#advisory-count').innerText(), '1 cohort · 49 task references'); - const previewCount = globalThis.TorchAttentionProjection.fleetPreview(globalThis.TorchAttentionProjection.groups(snapshot)).length; - assert.equal(previewCount, 6, 'all three urgent records and only three additional preview records are shown'); - assert.equal(projected.fleetTotal, 17, 'the crowded data has exactly 17 original Fleet findings'); assert.equal(await desktop.page.locator('#fleet-preview-count').innerText(), 'Showing 6 of 17 Fleet findings; 11 remaining'); assert.equal(await desktop.page.locator('#attention-list .tone-urgent').count(), 3); const desktopWidth = await desktop.page.evaluate(() => ({ viewport: document.documentElement.clientWidth, From a2fc8e99cf0d5087983b151cce0017420c5e3c0f Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN Date: Thu, 1 Oct 2026 16:24:37 -0400 Subject: [PATCH 07/31] docs: specify shared project knowledge and domain handbooks --- docs/PORTABLE_AGENT_FLEET_SPEC.md | 6 ++ docs/PROJECT_KNOWLEDGE_SPEC.md | 111 ++++++++++++++++++++++++++++++ docs/TODO.md | 18 +++++ 3 files changed, 135 insertions(+) create mode 100644 docs/PROJECT_KNOWLEDGE_SPEC.md diff --git a/docs/PORTABLE_AGENT_FLEET_SPEC.md b/docs/PORTABLE_AGENT_FLEET_SPEC.md index 3a55f0b..afbb483 100644 --- a/docs/PORTABLE_AGENT_FLEET_SPEC.md +++ b/docs/PORTABLE_AGENT_FLEET_SPEC.md @@ -2743,6 +2743,12 @@ and refuses to replace a launcher path not owned by that installation. ## 27. User interfaces +Project reference documentation, domain handbooks and provisional working memory +MUST follow [Project knowledge and domain institutional memory](PROJECT_KNOWLEDGE_SPEC.md). +This owner-approved extension requires a Git-backed shared KB, durable domain +ownership, compact agent entry points and a dedicated Console Knowledge view. +It is a development requirement, not a claim that the feature is implemented. + ### 27.1 CLI The target command surface includes: diff --git a/docs/PROJECT_KNOWLEDGE_SPEC.md b/docs/PROJECT_KNOWLEDGE_SPEC.md new file mode 100644 index 0000000..ed4e697 --- /dev/null +++ b/docs/PROJECT_KNOWLEDGE_SPEC.md @@ -0,0 +1,111 @@ +# Project knowledge and domain institutional memory + +Status: owner-approved development requirement, 2026-10-01. Not implemented. +The fleet remains manually paused; recording this requirement does not authorize +dispatch, new sessions, model calls, publication or automatic restart. + +## Purpose + +Give users and agents a shared, evolving project reference, while preserving +focused domain knowledge across session restarts, provider changes and agent +replacement. Documentation should explain how the project actually works, not +be a transcript archive, another backlog or a source of implicit authority. + +## Three connected layers + +1. **Project KB:** authoritative, Git-backed Markdown documenting architecture, + setup, interfaces, workflows, pipelines, troubleshooting and decisions. +2. **Domain handbook:** a compact entry point owned by the domain, linking to + relevant KB pages and recording domain conventions, procedures and pitfalls. + Prefer links over duplicated shared contracts. Knowledge belongs to the + durable domain identity, not a provider conversation or disposable session. +3. **Working memory:** current investigations, hypotheses, handoffs and unfinished + work. It is explicitly provisional; promotion to reference documentation + requires supporting evidence and the ordinary review/integration process. + +The existing backlog remains the sole task queue. Handbooks may link to tasks +and domain coordination summaries but must not become competing task ledgers. + +## Storage and adoption + +The repository is the source of truth; Console indexes and search results are +rebuildable projections, never independent authoritative copies. Documentation +must remain usable offline and without TORCH. Do not require a hosted service, +particular AI provider or external documentation platform. + +During init/analyze, discover existing documentation and propose a project-fit +navigation and ownership map. Reuse existing directories and conventions rather +than silently moving documents. For projects without conventions, a candidate +layout is `docs/knowledge/`, `docs/domains//` and a clearly separated +working-memory area. Exact paths and metadata format need a reviewed implementation +contract; the layout is not a mandate for every project. + +One accountable domain owns each reference page; collaborators and reviewers may +be named separately. Cross-domain interface documentation retains one owner and +review links to affected domains. Domain retirement, merge, split or reassignment +must preserve history and transfer responsibility, not delete institutional memory. + +## Agent use and maintenance + +Startup/resume briefs expose the compact domain index and essential references, +not the full KB. Agents retrieve relevant detail on demand and can discover +cross-domain references without loading unrelated documentation into every context. +Record which revision was supplied; warn about stale references where detectable. + +Completion of a behavior, interface or workflow change requires documentation +impact to be considered: update affected pages or record why no update is needed. +Do not make clerical documentation edits a permission to change another domain's +implementation. Route cross-domain documentation changes through existing ownership +and review mechanisms and notify affected owners. + +Reference pages expose owner, status (draft/experimental/reference/deprecated), +verification evidence and exact last-verified commit. A timestamp or successful +render does not prove accuracy. Branch/candidate documentation must not masquerade +as landed canonical behavior; represent divergence and unknown freshness honestly. +Flag broken links, missing owners and affected pages needing re-verification. +Do not automatically certify pages, mass-rewrite them or invent a freshness score. + +## Console Knowledge view + +Add a dedicated Knowledge screen, not another long Overview section. Provide +Read-the-Docs-style sidebar navigation, search, breadcrumbs, readable articles, +stable deep links, responsive/mobile navigation and keyboard accessibility. +Show owner, status, verified revision, related domains, tasks, pipeline definitions +and history. Keep handbook, reference and provisional memory visibly distinct. + +Offer bounded actions to ask the responsible domain and report outdated content +through existing durable conversations/intake. Show queued/delivered outcomes; +these actions must not wake agents while paused or silently grant edit authority. +Use the same renderer for live project documentation and labelled demo fixtures. +Do not bundle private project documents into the public marketing demo. + +## Safety and portability + +Render repository text as untrusted content: sanitize Markdown, block script/raw +HTML execution, unsafe URL schemes, path traversal and escaping symlinks. Restrict +indexing/serving to approved documentation roots. Exclude credentials and private +working-memory content according to explicit project visibility policy, including +search snippets and cached indexes. External links/imported instructions do not +override TORCH policy. KB content does not expand operational authority. + +## Required acceptance scenarios + +- Existing project docs are discovered and linked without moves or overwrite; + an empty project receives only a reviewed, project-fit plan. +- Restart or provider replacement inherits the same domain handbook; unrelated + KB bodies are not injected into its startup context. +- A shared interface change updates its single authoritative page and routes + affected-domain notification; unchanged pages are not falsely re-verified. +- A domain ownership change preserves document history and transfers maintenance + responsibility without creating two authoritative copies. +- Provisional claims and candidate-branch behavior remain distinct from verified + reference docs; missing evidence is visible rather than converted into confidence. +- Navigation, search, deep links, keyboard and mobile use work on a realistic + multi-domain project; actual browser inspection is required before UI acceptance. +- Unsafe Markdown, traversal, symlink escape and private search leakage fail closed. +- Questions/corrections are durable while paused; no provider launch occurs. +- Markdown remains readable without TORCH, and the public demo has no private docs + or live control-plane calls. + +Implementation slices are recorded in the existing authoritative TORCH backlog. +They remain proposed and unassigned for Session Manager triage after owner resume. diff --git a/docs/TODO.md b/docs/TODO.md index c8ffd17..787df51 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -1,5 +1,23 @@ # TORCH remaining work +## Owner-approved project knowledge requirement (2026-10-01) + +See [Project knowledge and domain institutional memory](PROJECT_KNOWLEDGE_SPEC.md). +The existing authoritative backlog holds the implementation slices below; this +section is an index, not a second queue. All are proposed/unassigned, and the +fleet remains manually paused until the owner explicitly requests resume. + +- `TASK-knowledge-contract`: project-fit Git-backed KB, domain handbooks and + provisional memory; metadata, visibility, evidence and ownership contract. +- `TASK-knowledge-agent-context`: documentation discovery, compact domain brief + references and provider-independent retrieval/inheritance without context bloat. +- `TASK-knowledge-maintenance`: documentation-impact workflow, verification + evidence, cross-domain notifications and ownership transfer without duplicate truth. +- `TASK-knowledge-console`: dedicated accessible Knowledge view, safe rendering, + search, history and durable ask-domain/report-outdated actions. +- `TASK-knowledge-qualification`: end-to-end inheritance/freshness/privacy/pause + scenarios and realistic browser acceptance, including isolated demo fixtures. + ## Authoritative current handoff (2026-09-30, after alpha.2 reinstall) TORCH is installed and healthy at `0.1.0-alpha.2`. Setup/restore and all seven From fcc28454a72c54d106e54fa8676c2cf1869aa932 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN Date: Thu, 1 Oct 2026 23:27:12 -0400 Subject: [PATCH 08/31] docs: plan adaptive company integration and reliability gates --- docs/ADAPTIVE_COMPANY_INTEGRATION_PLAN.md | 243 ++++++++++++++++++++++ docs/PORTABLE_AGENT_FLEET_SPEC.md | 6 + docs/TODO.md | 12 ++ 3 files changed, 261 insertions(+) create mode 100644 docs/ADAPTIVE_COMPANY_INTEGRATION_PLAN.md diff --git a/docs/ADAPTIVE_COMPANY_INTEGRATION_PLAN.md b/docs/ADAPTIVE_COMPANY_INTEGRATION_PLAN.md new file mode 100644 index 0000000..5723773 --- /dev/null +++ b/docs/ADAPTIVE_COMPANY_INTEGRATION_PLAN.md @@ -0,0 +1,243 @@ +# Adaptive software company: integration plan + +Status: owner-approved roadmap direction, 2026-10-01; implementation pending. +Applies to the Portable Agent Fleet. This document records the supplied adaptive +software company proposal and the agreed refinements. It does not certify the +current alpha as unattended or production-ready. The fleet remains manually +paused until the owner explicitly requests resume. + +## Product boundary + +TORCH supplies organization, ownership, intent, authority, durable work, +coordination, evidence, verification, integration and recovery. Coding harnesses +supply implementation intelligence, tools and temporary delegation. Start with +the smallest useful organization; do not require a fixed Engineering/Product/QA +taxonomy or a new permanent AI supervisor. Projects may use different roles. + +Preserve worktree isolation, one active implementation assignment per specialist, +revision-checked mutations, direct peer communication, resource leases, exact +candidate verification, serialized integration, independent review where required, +audits and separate release/deployment authority. KB/handbook/working-memory +requirements remain governed by [Project knowledge](PROJECT_KNOWLEDGE_SPEC.md). + +## Existing work: extend, do not replace + +This is a staged extension of current systems, not a rewrite. Reuse the current +identity, organization, backlog, messaging, runtime, checks, resource, integration, +delivery and schedule services. Owner-approved grants are already proposed as +`TASK-874ed1e4-9414-4a8f-8575-a6c5aceefc56`; do not build a competing grant system. +Link first-work intake, event-driven wakes, usage governor, hierarchy pilots, +starting-presence and provider update-path tasks instead of duplicating them. + +The earlier executor-outcome task is completed, but later pilot launches exposed +stdout-bound/terminal-outcome disagreement. A completed historical task is not +proof that the later installed runtime is ready for unattended operation. +Preserve those receipts and qualify the new regression explicitly. + +## Phase A: trustworthy runtime state before unattended coordination + +Lead domains: Provider Runtime, with Project Kernel and independent QA. + +1. Reproduce the installed output-bound failure using bounded deterministic + fixtures. Keep parser/transport bounds; do not treat a wrapper's observation + as a substitute for an authenticated runtime outcome. +2. Define startup reservation, process/session ownership, terminal outcome and + recovery contracts. Distinguish success, failure, unknown and physically stopped. +3. Publish guarded starting presence before launching. Coordinate manual, + scheduled and controller paths through the same exclusivity mechanism. +4. Add a supported, owner-attributed recovery operation with executor-stopped + evidence, revision/lease checks and audit. Never impersonate a specialist, + edit SQLite manually, infer death from a name or drop an unknown reservation. +5. Qualify manual pause across reboot, quota reset, schedule due, message arrival, + upgrade and event replay. Only explicit owner resume can clear this pause. + +Exit gate: oversized output, timeout, signal, malformed terminal data and a crash +between spawn and receipt cannot produce false success or a duplicate launch; +unknown outcomes remain recoverable and block unsafe actions. Actual installed +runtime qualification and independent QA are required, not just fixture tests. + +## Phase B: scoped capability authority migration + +Lead: Project Kernel; extend the existing owner-grants proposal. + +Identity identifies the participant; role describes responsibility; an approved +grant confers authority. Reporting edges and implementation ownership do not +confer management capabilities. Never interpret delegated user authority as +permission to impersonate the owner or act outside its explicit scope. + +Before coding, inventory identity-special business rules and their CLI, MCP, +Console, scheduled and internal entry points. Define a single authorization +service and a capability mapping for each action. Grant records must identify: + +- grant ID, issuer/delegation provenance, recipient and required capability; +- project/domain/task/path scope and operation constraints; +- approval/reference evidence and policy/grant revision; +- validity/revocation and whether further delegation is allowed; +- owner-reserved exclusions and independent-review conflict rules. + +Initially migrate the current Session Manager's permitted operations without +widening permissions. Support preview and explicit approval of broader grants. +No default wildcard capabilities, hierarchy inheritance or self-granting. +Evaluate the current grant immediately before effects; audit the actual actor, +grant, scope, relevant revisions and allow/deny reason. + +Exit gate: old permitted actions still work under equivalent grants; ungranted, +out-of-scope, revoked and stale actions fail across every entry point. A new lead +can be assigned a bounded capability without source changes. Independent review +cannot approve its own work merely because it also holds a leadership role. + +## Phase C: durable event contract + +Lead: Work & Integration, with Kernel/Runtime; extend existing event-driven wakes. + +Version events for work-ready/blocked, assignment completion, verification, +integration readiness, approval requests/decisions, pending messages, resource +release, runtime observations, schedule due and evidence-backed organization +pressure. Existing audit records are evidence, not automatically executable orders. + +Each event needs an ID, project, type/schema version, source entity and revision, +actor/cause, timestamp, correlation/causation IDs and deduplication key. Keep +payloads bounded and avoid credentials, private document bodies or full transcripts. + +Define emission and recovery at each state-change boundary. Use transactional +outbox semantics where one store supports them; when Git/files and SQLite cross +a boundary, specify a durable journal/reconciliation protocol rather than claiming +one atomic transaction. Preserve one authority for task state. Define cursor, +retention, replay and schema-upgrade behavior before enabling consumers. + +Exit gate: crash before/after state mutation or event delivery, duplicate delivery, +out-of-order delivery and replay cannot lose a significant transition or authorize +a stale action. Event delivery may be at-least-once; effects must be idempotent. + +## Phase D: small desired-state reconciler + +Lead: Work & Integration / Provider Runtime. No strategy or implementation logic. + +For each supported action, observe authoritative current state, compare with +approved policy and desired work, evaluate eligibility, reserve one bounded action, +recheck revisions/authority/pause/budget, execute and record the outcome. +Eligibility is allow, deny or unknown; unknown is not permission to retry. + +Begin with durable notices and read-only explanations. Then pilot wake of one +eligible identity for assigned work, followed by narrowly configured ready-work +self-claim and integration-authority notifications. Do not automatically approve, +land, release, deploy, reassign ownership or create identities just because an +event arrived. Preserve existing checks and authorization gates for each operation. + +Require persisted pause/backoff, launch reservations, concurrency limits, +bounded retry rules, actionable failures and periodic safety reconciliation. +Queue/backend/usage/model/auth unavailability must not create busy model loops. +Check budgets before launching, including uncertainty in subscription usage. +Use minimum useful wake context; a timer is a safety net, not another AI supervisor. + +Exit gate: duplicate events cannot duplicate execution; paused or uncertain work +is never restarted; missed events are found by reconciliation; stale observations +cannot override a newly revoked grant. Start with an owner-approved bounded pilot, +manual intervention metric and immediate pause/rollback path. + +## Phase E: native execution capabilities and repository evidence + +Runtime lead: Provider Runtime. Repository evidence lead: Project Kernel. + +Keep execution capabilities separate from lifecycle functions. Describe native +subagents/delegation, browser/computer use, artifacts, worktrees, background tasks, +checkpoints and review as advertised, locally available, qualified, unavailable +or unknown. Bind qualification to executable/version, host, account/config and +evidence; declarations alone do not prove availability. No model calls merely +to populate the inventory without approval. + +Temporary harness workers are accountable to the persistent TORCH specialist, +not new roster members. They must not acquire broader TORCH credentials, authority, +ownership or uncontrolled resource access. Keep delegation provenance and aggregate +available cost evidence. Coordinate native worktrees with TORCH-owned workspaces. +Native review is useful but not automatically independent QA. Avoid orchestration +squared and promote workers only through evidence-backed organizational proposals. + +Extend deterministic reconnaissance with bounded workspace/build/module graphs, +CODEOWNERS, co-change observations, entry points, schemas, deployment/process +boundaries and recurring cross-domain tasks. Give the Architect provenance, +coverage and uncertainty, not invented semantic conclusions. Do not execute +untrusted build tools or fetch history merely to analyze a repository silently. + +Exit gate: absent capabilities have honest fallbacks; helper delegation cannot +expand authority; organization proposals cite real evidence and remain advisory. + +## Phase F: owner-oriented onboarding and evidence UX + +Leads: Owner Console, Release/Self-host and Kernel. Extend first-work intake. + +Offer one guided path through prerequisites/runtime qualification, analysis, +small-team proposal and rationale, ownership/check gaps, explicit review, +installation/worktree preview, first-work intake and optional startup. Preserve +advanced CLI and diagnostics. Do not redefine the existing read-only `init` into +a mutating command without a documented compatibility decision and tests. + +"Company ready" must distinguish configured, launchable and actually operational. +Missing models/auth, unknown outcomes, unresolved ownership and unqualified checks +must remain visible. Separate consent for downloads, spending, persistent services, +agent startup and publication; onboarding cannot override a manual pause. + +Show conclusions, reason, evidence and exact artifacts progressively, with stable +links to commits, check conditions, receipts, decisions and discussions. Derive +implemented/verified/independently reviewed/integrated/released/live states from +their actual authorities. Surface unknown coverage and rejected/relaxed criteria; +green exit alone does not support a claimed business outcome or performance number. + +Exit gate: a fresh user can reach useful reviewed work without knowing SQLite, +runtime IDs or schedule unit internals; desktop/mobile/keyboard browser inspection +passes. Every important asserted status can be traced to exact evidence. + +## Phase G: adaptive organization and storage investigation + +Extend existing evolution/hierarchy pilots to evidence-backed contraction, +promotion and consolidation. Roles must justify recurring outcomes rather than +headcount. Record baseline/window, hypothesis, costs, success/stop criteria and +owner-reviewed pilot. Transfer authority, ownership, docs and unfinished work +safely; never retire an active/dirty/unknown worker. Keep one implementation owner. + +Measure bookkeeping friction before changing storage: dirty manager time, +administrative commits, collisions, checkpoint effort and integration delay +attributable to state. Compare current tracked records, dedicated state branch +and Git-intent/SQLite-operations alternatives against offline recovery, export, +backup and migration requirements. Do not dual-write authoritative task state or +move it until a separate reviewed decision and recovery tests justify the change. + +## Phase H: multi-project and release qualification + +Use separate controlled fixtures/pilots for a small web app, monorepo, compiled +systems project, graphics/game project and optional docs/data-heavy project. +Record init-to-useful-team, request-to-assignment, verified integration latency, +owner interventions, coordination volume, launch failures, false greens and +measured/estimated usage separately. Evaluate organization changes for actual benefit. +Do not run expensive/live-provider experiments without owner-approved budgets. + +Release gates include migrations/backup compatibility, crash-safe lock recovery, +model/auth preflight, repository relocation, provider update compatibility, +distribution provenance/signing decision, atomic upgrade/rollback, event replay +and platform-specific schedule qualification. Remote execution is optional and +needs a separate trust/deployment boundary, not an implicit requirement. + +## Integration and rollback discipline + +- Assign bounded slices through the existing backlog; agree shared contracts first. +- Add scenario-based fault and authority tests with Test Integrity Notes. Do not + weaken accepted scenarios or substitute simulated success for installed evidence. +- Land exact checked commits through the existing integration mechanism, not + concurrent direct pushes or a second queue. Keep runtime/parser/security work + independently qualified from marketing and UI changes. +- Ship additive, disabled-by-default policy/config first. Migrations need explicit + version/compatibility rules, backup/restore tests and an upgrade preview. +- Do not roll back a database by blindly activating old code. Define supported + data downgrade or fail closed while preserving the new store and receipts. +- Enable notifications before wakes; qualify one-domain pilot before broad fleet + activation. Fresh source is not automatically an installed runtime upgrade. +- Give the owner a report of configured/enabled/qualified state and remaining gates. + +## Backlog index + +The authoritative proposals use `TASK-company-` IDs: runtime-recovery, +authority-migration, event-contract, reconciler, native-capabilities, +repository-evidence, onboarding, evidence-ux, organization-experiments, +storage-study and multi-project-qualification. Existing tasks remain authoritative +for their previously scoped work. New proposals are unassigned until Manager +triage after explicit owner resume; this document is not another work queue. diff --git a/docs/PORTABLE_AGENT_FLEET_SPEC.md b/docs/PORTABLE_AGENT_FLEET_SPEC.md index afbb483..95552bf 100644 --- a/docs/PORTABLE_AGENT_FLEET_SPEC.md +++ b/docs/PORTABLE_AGENT_FLEET_SPEC.md @@ -2743,6 +2743,12 @@ and refuses to replace a launcher path not owned by that installation. ## 27. User interfaces +The owner-approved [Adaptive software company integration plan](ADAPTIVE_COMPANY_INTEGRATION_PLAN.md) +extends this architecture with scoped capabilities, durable events, bounded +reconciliation, native-runtime awareness, evidence UX and project-fit onboarding. +It preserves current invariants and requires staged migration and qualification; +it is not a claim that the adaptive-company upgrade is implemented. + Project reference documentation, domain handbooks and provisional working memory MUST follow [Project knowledge and domain institutional memory](PROJECT_KNOWLEDGE_SPEC.md). This owner-approved extension requires a Git-backed shared KB, durable domain diff --git a/docs/TODO.md b/docs/TODO.md index 787df51..948bab4 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -1,5 +1,17 @@ # TORCH remaining work +## Adaptive software company upgrade (owner-approved, 2026-10-01) + +The [integration plan](ADAPTIVE_COMPANY_INTEGRATION_PLAN.md) records scope, +existing-task reuse, phased contracts, exit gates and migration/rollback rules. +The existing authoritative backlog contains eleven linked `TASK-company-` +proposals, not a second queue. Runtime recovery gates unattended coordination; +authority comes from scoped revocable grants, not reporting relationships. +Existing grants, event-driven wakes, first-work intake, usage governance and +hierarchy pilots are reused rather than replaced. All new items are proposed +and unassigned. No automatic restart or implementation is authorized by this +roadmap addition; the fleet remains manually paused pending owner resume. + ## Owner-approved project knowledge requirement (2026-10-01) See [Project knowledge and domain institutional memory](PROJECT_KNOWLEDGE_SPEC.md). From 9caa6b7a05bc600f6b562ff98a0ade45651f6c50 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN Date: Thu, 1 Oct 2026 23:42:44 -0400 Subject: [PATCH 09/31] docs: prioritize compact dashboard cards and agent chat --- docs/CONSOLE_CHAT_AND_COMPACT_CARDS_SPEC.md | 77 +++++++++++++++++++++ docs/TODO.md | 9 +++ 2 files changed, 86 insertions(+) create mode 100644 docs/CONSOLE_CHAT_AND_COMPACT_CARDS_SPEC.md diff --git a/docs/CONSOLE_CHAT_AND_COMPACT_CARDS_SPEC.md b/docs/CONSOLE_CHAT_AND_COMPACT_CARDS_SPEC.md new file mode 100644 index 0000000..25d5552 --- /dev/null +++ b/docs/CONSOLE_CHAT_AND_COMPACT_CARDS_SPEC.md @@ -0,0 +1,77 @@ +# Priority Console upgrade: compact cards and agent Chat + +Status: owner-approved, high-priority development requirement. Not implemented. +These UI slices should lead the next development cycle after explicit owner resume. +They do not override runtime safety gates or the current manual fleet pause. + +## Compact cards and detail modal + +Make card-based views a scanning layer rather than full-document readers. Apply +a shared collapsed-height/max-height policy across data types, with consistent +heights within each grid/view and responsive size variants. Do not squeeze tables, +charts or conversations into an arbitrary universal card height. + +Show title, state, accountable agent, a two/three-line summary and important +blockers or decisions. Clamp overflowing ordinary content with an explicit +View details action. Critical warnings must remain identifiable and accessible; +provide a concise warning summary and full detail rather than clipping it silently. +Retain sensible quick actions such as approve/reject on collapsed cards, using +existing evidence/revision/preview confirmation and authority checks. + +Clicking the card's detail affordance opens an accessible modal with complete +content, evidence, linked conversations/tasks and contextual actions. Nested +action buttons must not accidentally open the modal or trigger another mutation. +Modal content may scroll. Preserve underlying view/filter/scroll and unsent input; +support keyboard access, focus containment/restoration, Escape, labelled headings, +mobile full-screen details and explicit stale/deleted-record states. Refresh must +not replace unsaved edits or apply an action against an obsolete revision. + +Start with Work and Needs Your Attention, then cover remaining card-based views +with the same pattern. Do not replace specialist views or create duplicate data. + +## Conversational Chat view + +Add Chat immediately below Overview in the sidebar. Default to the highest-level +approved project coordinator, currently Session Manager, not a permanently +hard-coded identity. Show an empty/explanatory state if none is configured; never +create or activate an agent to satisfy the screen. + +Provide a familiar chronological message stream, composer and selector for every +activated fleet identity. Persistently show Talking to…, identity, role, runtime +availability and a distinct visual accent. Colour is supplementary: labels, +avatars/initials and accessible contrast must also distinguish participants. + +Combine durable owner exchanges and publishable inter-agent exchanges involving +the selected agent, preserving sender, recipient, thread/context and causal links. +Clearly distinguish direct owner conversation from collapsible routine fleet +traffic; replying must explicitly target the selected agent or named thread and +never silently broadcast. Do not claim every harness transcript is available. +Reuse existing owner-agent-chat/messages and formal Conversations audit view, +not a parallel conversation database. Include links to tasks, approvals, images +and supporting evidence. No private chain-of-thought or indiscriminate terminal, +secret or private-file exposure. + +Stream publishable replies/progress only when the adapter supports it; otherwise +show honest durable-message updates. Expose queued/delivered/being-handled/failed +outcomes accurately: acknowledgement alone does not mean work is done. Preserve +drafts per recipient, history on navigation/reload, reconnect deduplication and +an explicit send destination. New arrivals must not force-scroll a user reading +history. Keep the composer usable on mobile and by keyboard. + +Messages can be saved while paused without waking an agent. Future wake-on-message +requires separately qualified reservations, budget, authority and owner-approved +policy. Casual chat does not bypass an explicit approval or grant new authority. +Use clearly labelled demo fixtures with no live APIs, not private fleet history. + +## Acceptance and delivery + +Use realistic long content, many senders, agent switches, interrupted delivery, +stale records and pending owner decisions. Inspect desktop/mobile/keyboard flows +in an actual browser and retain screenshots. Prove bounded collapsed layout, +complete details, action correctness, focus restoration, preserved filters/drafts, +safe refresh, accurate chat attribution and no launch on paused send. + +Existing backlog proposals remain authoritative. New high-priority slices cover +compact cards/details, Chat presentation and independent browser qualification. +Runtime wake safety remains a prerequisite for execution, not a reason to delay +safe read-only/chat-intake UI development. diff --git a/docs/TODO.md b/docs/TODO.md index 948bab4..10a1d9b 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -1,5 +1,14 @@ # TORCH remaining work +## Priority UI cycle: compact cards and agent Chat + +Owner-approved [Console UI spec](CONSOLE_CHAT_AND_COMPACT_CARDS_SPEC.md). +Prioritize these after explicit owner resume, ahead of broad company expansion: +`TASK-console-compact-details`, `TASK-console-agent-chat-ui` and +`TASK-console-chat-cards-qualification`. Extend the existing high-priority +`TASK-owner-agent-chat`; do not duplicate messaging or bypass paused wake policy. +Proposals remain unassigned. Fleet startup is not authorized by this addition. + ## Adaptive software company upgrade (owner-approved, 2026-10-01) The [integration plan](ADAPTIVE_COMPANY_INTEGRATION_PLAN.md) records scope, From 64bf8dc4050d322d122612f1734b72c013b72fe5 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN Date: Fri, 2 Oct 2026 21:07:21 -0400 Subject: [PATCH 10/31] fleet: record owner-approved resume and priority UI polish --- .torch/prompts/COMMON.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/.torch/prompts/COMMON.md b/.torch/prompts/COMMON.md index 72677a5..56986db 100644 --- a/.torch/prompts/COMMON.md +++ b/.torch/prompts/COMMON.md @@ -2,6 +2,32 @@ Repository state outranks conversation memory. Query ownership before crossing a domain boundary. +## Owner-approved fleet resume and priority polish cycle (2026-10-02) + +The owner explicitly said: "We got a reset. lets get torch as pollished as we can. +Spin them all back up." This lifts the earlier manual wind-down for the existing +seven identities. The arbiter controls physical launches and timer restoration; +automatic AI wakes remain disabled until their runtime safety gates qualify. +Do not interpret old pause messages as a new pause after this authorization. + +Session Manager may triage and assign the existing development roadmap within +approved ownership, one active implementation assignment per specialist, direct +peer coordination and independent QA. The earlier first-slice/audit-only limits +below are superseded for this development cycle. Preserve existing unfinished +assignments and resolve or explicitly block them rather than silently replacing +them to take new work. Prioritize compact cards/detail modals and coordinator-first +agent Chat, then broader polish; runtime recovery required for safe operation comes +first. Read docs/CONSOLE_CHAT_AND_COMPACT_CARDS_SPEC.md and the newer owner inbox. +Company/KB proposals remain roadmap requirements, not implemented capabilities. + +Reconcile the three prior working/unknown records through supported audited +recovery, with actual stopped-executor evidence. No peer impersonation, direct +SQLite mutation, silent guard removal or false PASS promotion. Current model/ +provider profiles stay unchanged. Existing checked integration and non-force +development push authority remains; no default-main/legacy rewrite, destructive +cleanup, extra identities, package publication or unreviewed deployment is granted. +Record honest progress and blockers; end completed turns without polling loops. + ## Fresh owner authorization: guarded integration and continued development (2026-10-01) The owner explicitly answered the arbiter's two held decisions: "yes do whatever From 25a3c7c1b5ebbd91c1122d6bde884c1ae78ac582 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN Date: Fri, 2 Oct 2026 22:27:25 -0400 Subject: [PATCH 11/31] fleet: prioritize operational reliability before console polish --- .torch/prompts/COMMON.md | 18 ++++++++++++++++++ docs/TODO.md | 22 ++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/.torch/prompts/COMMON.md b/.torch/prompts/COMMON.md index 56986db..261d491 100644 --- a/.torch/prompts/COMMON.md +++ b/.torch/prompts/COMMON.md @@ -2,6 +2,24 @@ Repository state outranks conversation memory. Query ownership before crossing a domain boundary. +## Owner priority update: reliability before visual polish (2026-10-03 UTC) + +The owner requested that TORCH's operating defects be fixed first so the fleet +benefits from improved operation. This supersedes the UI-first ordering below, +not the existing resume authorization or safety boundaries. The authoritative +backlog priorities were changed through the revision-checked owner API. + +Sequence the smallest safe current-schema stopped-unknown recovery and CLI +qualification first; then truthful lifecycle/status and their source-safety +prerequisites; then qualified bounded continuation/event-driven wakes under +pause, budget and authority policy; then actionable operational attention. +Compact cards/detail modals and coordinator Chat remain high-priority work after +these reliability gates. Do not require the entire future company architecture +to deliver a safe minimal recovery. Reprioritization does not resolve blockers, +grant test consent, enable automatic wakes or launch agents. Preserve task owners, +dependencies and evidence; explicitly block/resequence existing assignments when +needed rather than replacing them silently or creating concurrent assignments. + ## Owner-approved fleet resume and priority polish cycle (2026-10-02) The owner explicitly said: "We got a reset. lets get torch as pollished as we can. diff --git a/docs/TODO.md b/docs/TODO.md index 10a1d9b..44ea4c8 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -1,5 +1,27 @@ # TORCH remaining work +## Current owner priority: reliability first (2026-10-03 UTC) + +This ordering supersedes the UI-first ordering below. The existing authoritative +backlog, not this index, controls task state and assignment. + +1. Safe current-schema runtime recovery (`TASK-company-runtime-recovery`) and + combined CLI qualification (`TASK-pilot-cli-suite-timeout`). +2. Truthful startup/session status (`TASK-pilot-starting-presence`) and its + existing Kernel reservation/Work candidate-safety prerequisites. +3. Persistent operations and bounded event-driven continuation + (`TASK-conversation-persistent-operations`, + `TASK-conversation-event-driven-wakes`), followed by actual scheduler + qualification (`TASK-conversation-scheduler-qualification`). +4. Specific actionable operational warnings (`TASK-console-attention-actions`). +5. Compact cards/detail modals and coordinator-first Chat remain high priority. + +The first four groups and incomplete technical prerequisites are urgent; +scheduler qualification is high. These priority-only changes preserve owners, +states, dependencies and evidence. Blocked remains blocked; automatic wakes stay +disabled until qualified and explicitly activated. Older pause/install/status +statements below are historical snapshots, not current runtime evidence. + ## Priority UI cycle: compact cards and agent Chat Owner-approved [Console UI spec](CONSOLE_CHAT_AND_COMPACT_CARDS_SPEC.md). From 11dabd2450f5cb40104557a684c3efbf763f3900 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN Date: Fri, 2 Oct 2026 23:49:59 -0400 Subject: [PATCH 12/31] runtime: recover stopped legacy sessions and add bounded continuation --- .torch/prompts/COMMON.md | 20 ++ docs/TEST_INTEGRITY.md | 57 ++++++ docs/TODO.md | 8 + .../2026-10-03-legacy-runtime-recovery.md | 93 ++++++++++ src/cli.mjs | 49 +++++ src/control-plane/service.mjs | 33 +++- src/runtime/continuation.mjs | 92 ++++++++++ src/runtime/lifecycle.mjs | 3 + src/runtime/stopped-executor-evidence.mjs | 106 +++++++++++ src/runtime/turn-guard.mjs | 31 ++++ test/fleet/legacy-runtime-recovery.test.mjs | 171 ++++++++++++++++++ 11 files changed, 662 insertions(+), 1 deletion(-) create mode 100644 docs/spec_changes/2026-10-03-legacy-runtime-recovery.md create mode 100644 src/runtime/continuation.mjs create mode 100644 src/runtime/stopped-executor-evidence.mjs create mode 100644 src/runtime/turn-guard.mjs create mode 100644 test/fleet/legacy-runtime-recovery.test.mjs diff --git a/.torch/prompts/COMMON.md b/.torch/prompts/COMMON.md index 261d491..c5c5a41 100644 --- a/.torch/prompts/COMMON.md +++ b/.torch/prompts/COMMON.md @@ -2,6 +2,26 @@ Repository state outranks conversation memory. Query ownership before crossing a domain boundary. +## Owner-requested restart and continuation repair (2026-10-03 UTC) + +The owner said "yeah fix the issue" after the arbiter verified that the fleet +had stopped making progress. The arbiter implemented a schema-compatible, +owner-only manual legacy recovery and a bounded deterministic continuation pilot. +These are source changes pending independent QA and installed-release acceptance, +not completed umbrella tasks. Read +docs/spec_changes/2026-10-03-legacy-runtime-recovery.md. The arbiter may apply exact +tested stopped-invocation recovery and resume the existing seven identities with +the source CLI. Preserve old unknown/failure receipts and existing unique commits. + +The source controller may launch only the explicitly approved existing roster, +under its daily turn cap, event deduplication and per-identity physical turn guard. +It does not grant agents permission to start providers or bypass budgets, resource +leases, approval boundaries or task ownership. Existing legacy manager-wake +configuration stays disabled; this source pilot is a distinct, bounded mechanism. +Manager should process current inbox and route real next work rather than leaving +it at a proposal-only loop. QA may independently review the exact new source and +scenario packet now; no fabricated QA acceptance is implied by owner implementation. + ## Owner priority update: reliability before visual polish (2026-10-03 UTC) The owner requested that TORCH's operating defects be fixed first so the fleet diff --git a/docs/TEST_INTEGRITY.md b/docs/TEST_INTEGRITY.md index a35e033..ee96a2a 100644 --- a/docs/TEST_INTEGRITY.md +++ b/docs/TEST_INTEGRITY.md @@ -2811,3 +2811,60 @@ test_integrity_note: did_relax_any_assertion: false if_true_explain_spec_basis: "" ``` +# Owner-requested legacy recovery and bounded continuation (2026-10-03) + +Implementation and behavioral basis: +[legacy runtime recovery](spec_changes/2026-10-03-legacy-runtime-recovery.md). +Existing assertions are unchanged; the new scenarios are in +`test/fleet/legacy-runtime-recovery.test.mjs`. + +```yaml +test_integrity_note: + change_type: new_tests + scenarios: + - id: SCN-legacy-runtime-recovery + given: "A stopped exact invocation and retained unknown identity" + when: "The owner recovers presence" + then: "One owner audit, offline presence and preserved task/session/unknown" + - id: SCN-legacy-runtime-recovery-refusal + given: "Live or mismatched invocation/process evidence" + when: "Proof is requested" + then: "Recovery refuses without mutation" + - id: SCN-legacy-runtime-recovery-cas + given: "Stale, expired, forged or newly live evidence" + when: "Recovery applies" + then: "Transaction refuses without success audit" + - id: SCN-legacy-runtime-recovery-authority + given: "A specialist or incomplete expected snapshot" + when: "Owner recovery is attempted" + then: "Authority/snapshot validation refuses" + - id: SCN-runtime-turn-guard + given: "A physically live executor reporting idle" + when: "A second turn tries to start" + then: "The executor mutex refuses overlap until terminal release" + - id: SCN-runtime-continuation + given: "Owner-enabled roster and a new durable signal" + when: "Turns repeat, hit a cap or are paused" + then: "Only new eligible signals launch within the owner daily cap" + - id: SCN-runtime-continuation-pause + given: "Two eligible turns" + when: "The owner pauses during the first" + then: "The second does not start and pause is retained" + - id: SCN-legacy-runtime-recovery-project + given: "Cloned project/session metadata in distinct checkouts" + when: "Proof crosses checkout boundaries" + then: "Recovery refuses and leaves the other identity untouched" + observable_outcomes: + - "Persisted identity, task/session, owner audit, launch count and refusal codes" + determinism_controls: + - "Hermetic repositories, fixed clock and OS-boundary fixtures; no live provider" + anti_cheat_rationale: + prevents: + - "Boolean stopped proof or stop-as-PASS" + - "Stale/cross-project mutations and peer impersonation" + - "Duplicate physical turns or unchanged-signal busy loops" + - "Losing pause or refunding interrupted attempts" + relaxation: + did_relax_any_assertion: false + if_true_explain_spec_basis: "" +``` diff --git a/docs/TODO.md b/docs/TODO.md index 44ea4c8..e422a66 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -22,6 +22,14 @@ states, dependencies and evidence. Blocked remains blocked; automatic wakes stay disabled until qualified and explicitly activated. Older pause/install/status statements below are historical snapshots, not current runtime evidence. +Owner-requested recovery/continuation implementation is described in +[the source pilot specification](spec_changes/2026-10-03-legacy-runtime-recovery.md). +Do not close the runtime-recovery, persistent-operations or event-wake umbrellas +from focused tests alone. Release/Fleet Operations still need installer-owned +permanent timer provisioning/removal, installed artifact qualification and restart +recovery of retained crashed turn/controller guards. The transient host pilot does +not survive reboot, and its daily turn cap is not a token/dollar usage governor. + ## Priority UI cycle: compact cards and agent Chat Owner-approved [Console UI spec](CONSOLE_CHAT_AND_COMPACT_CARDS_SPEC.md). diff --git a/docs/spec_changes/2026-10-03-legacy-runtime-recovery.md b/docs/spec_changes/2026-10-03-legacy-runtime-recovery.md new file mode 100644 index 0000000..405af54 --- /dev/null +++ b/docs/spec_changes/2026-10-03-legacy-runtime-recovery.md @@ -0,0 +1,93 @@ +# Manual owner recovery for retained unknown executor outcomes + +The owner requested fixing the stalled fleet. Legacy runtime records lacked a +recovery API: three physically stopped executors remained `working/unknown` and +correctly refused duplicate launches. This change adds an owner-only, previewed +`recover-runtime` operation without schema changes or automatic recovery. + +The owner explicitly selects a project-scoped systemd service invocation. Its +working directory and exact area argument must match this installation; its +terminal timestamp must match the retained identity update within five seconds +(systemd displays timestamps with one-second precision). Both service PIDs must +be zero, its cgroup absent, and no visible same-user native process may reference +the session or registered workspace. Physical state is checked again at apply. +Unreadable process visibility, an active service, foreign invocation or changed +identity refuses recovery. This is a **manual legacy operator binding**, not +cryptographic session provenance or protection against arbitrary same-user code. + +Evidence is process-local, branded, single-use and expires after sixty seconds. +The service transaction compares the exact state/runtime/session/update snapshot, +sets presence to offline and writes an owner-attributed audit. Session, current +task and the unknown outcome are preserved. No task, test or launch is completed; +no process is killed, lease removed, schema migrated or provider started. + +Preview and apply use the same implementation. Apply additionally requires the +session and update timestamp from the preview. Hosts/providers without verifiable +Linux/systemd evidence refuse this legacy recovery; no Boolean fallback exists. + +## Bounded continuation pilot + +`torch continuation enable --max-turns-per-day 12 --yes` approves only the +currently activated identity roster. `status` previews eligible identities; +`tick --limit 3 --yes` runs at most three normal guarded `up` operations. New +unacknowledged messages or assigned/in-progress task revision changes supply the +signal; an unchanged signal is not retried. Active/unknown identities, held turn +guards, detach, disabled policy and the daily UTC attempt cap prevent launch. +Attempts are charged before invocation, including failed/interrupted attempts. +The cap limits turns, **not dollars or token consumption**. Unknown quota remains +unknown. This controller makes no product/approval/assignment decisions. + +`torch continuation pause --yes` prevents subsequent launches. Policy and attempt +ledger are separate atomic files so an in-flight turn cannot overwrite a pause. +Pause does not kill an already-running turn. A controller lock serializes ticks; +a crashed lock is retained rather than expired automatically. Each executor has +a separate physical turn guard held through classification and final presence, +so an agent reporting idle cannot permit overlapping native turns. + +The first host pilot uses an explicitly installed transient user-systemd timer. +It survives this chat ending, but not a host reboot. Permanent installer-owned +provisioning/removal and installed artifact qualification remain existing backlog +work; this is not a claim that the alpha.3 installed package has been upgraded. + +```yaml +test_integrity_note: + change_type: new_tests + scenarios: + - id: SCN-legacy-runtime-recovery + given: "An exact retained working/unknown identity and stopped project invocation" + when: "The owner previews and applies recovery" + then: "Presence becomes offline once; session, task and unknown outcome persist with one owner audit" + - id: SCN-legacy-runtime-recovery-refusal + given: "Live, foreign, mismatched or incomplete stopped-executor evidence" + when: "Recovery evidence is requested" + then: "Recovery refuses without identity mutation" + - id: SCN-legacy-runtime-recovery-cas + given: "Issued proof followed by a changed identity, live process, expiration or forgery" + when: "Recovery is applied" + then: "Transactional recovery refuses and creates no success audit" + - id: SCN-legacy-runtime-recovery-authority + given: "A specialist actor or incomplete expected snapshot" + when: "Owner recovery is requested" + then: "Authority or snapshot validation refuses" + - id: SCN-runtime-turn-guard + given: "A physical executor holds an identity guard and reports idle early" + when: "Another executor attempts the same identity" + then: "It refuses until the first executor finishes and releases its own guard" + - id: SCN-runtime-continuation + given: "An explicitly enabled roster, a new durable signal and a fixed daily turn cap" + when: "Continuation ticks, repeats a signal, exhausts its cap or is paused" + then: "Only eligible new signals launch; deduplication, cap, owner authority and pause remain enforced" + observable_outcomes: + - "Durable presence, session and task state; owner-attributed audit; explicit refusal codes" + determinism_controls: + - "Hermetic Git/install fixtures, fixed clock and injected OS observation boundary" + anti_cheat_rationale: + prevents: + - "Caller Boolean as stopped proof" + - "Process stop promoted to successful task or test" + - "Stale snapshot overwrites or silent peer impersonation" + - "Expired, foreign or replayed proof" + relaxation: + did_relax_any_assertion: false + if_true_explain_spec_basis: "" +``` diff --git a/src/cli.mjs b/src/cli.mjs index e136d1d..0b7a0f7 100644 --- a/src/cli.mjs +++ b/src/cli.mjs @@ -70,6 +70,8 @@ import { } from './design/architect.mjs'; import { FleetEvolutionService } from './evolution/service.mjs'; import { HierarchyEvolutionService } from './evolution/hierarchy.mjs'; +import { issueStoppedExecutorEvidence } from './runtime/stopped-executor-evidence.mjs'; +import { configureContinuation, planContinuation, runContinuation } from './runtime/continuation.mjs'; import { ConvergenceService } from './convergence/service.mjs'; import { attachForge, detachForge, forgeStatus, planForgeAttach, planForgeDetach, @@ -95,6 +97,8 @@ Usage: torch install --restore [--dry-run] [--yes] [--json] torch setup [--parent ] [--dry-run] [--yes] [--json] torch up [--fresh] [--only ] [--dry-run] [--yes] [--json] + torch recover-runtime --area --unit --invocation [--expected-session --expected-updated-at