From d93de9364141397cb417ec61295cf08d70be6c3e Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN Date: Thu, 1 Oct 2026 11:52:53 -0400 Subject: [PATCH] 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']); +});