import assert from 'node:assert/strict';
import { execFileSync, spawnSync } from 'node:child_process';
import { mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { basename, join } from 'node:path';
import test from 'node:test';
import { BacklogService } from '../../src/backlog/service.mjs';
import { openControlPlane } from '../../src/control-plane/service.mjs';
import { DeliveryService } from '../../src/delivery/service.mjs';
import { OwnerDigestService } from '../../src/observability/owner-digest.mjs';
import { analyzeRepository } from '../../src/kernel/analyze.mjs';
import { proposeDomains } from '../../src/kernel/domains.mjs';
import { inspectRepository } from '../../src/kernel/git.mjs';
import { installProject } from '../../src/kernel/install.mjs';
import { createWorktrees } from '../../src/kernel/worktrees.mjs';
import { ScheduleService } from '../../src/schedules/service.mjs';
import { createConsoleServer } from '../../src/console/server.mjs';
import { CheckService } from '../../src/checks/service.mjs';
import { IntegrationService } from '../../src/integration/service.mjs';
const CLI = new URL('../../bin/torch.mjs', import.meta.url).pathname;
const now = () => new Date('2026-09-30T08:12:00Z');
function fixture() {
const root = mkdtempSync(join(tmpdir(), 'torch-owner-digest-'));
const git = (args) => execFileSync('git', ['-C', root, ...args], {
env: { ...process.env, GIT_AUTHOR_DATE: '2026-09-20T12:00:00Z', GIT_COMMITTER_DATE: '2026-09-20T12:00:00Z' },
});
git(['init', '-b', 'main']);
git(['config', 'user.email', 'torch-test@example.invalid']);
git(['config', 'user.name', 'TORCH Test']);
writeFileSync(join(root, 'app.js'), 'export const value = 1;\n');
git(['add', '.']); git(['commit', '-m', 'fixture']);
const repository = inspectRepository(root);
const proposal = proposeDomains({ repository, analysis: analyzeRepository(repository) });
proposal.review = { status: 'approved', reviewedAt: now().toISOString(), reviewedBy: 'owner', notes: [] };
const env = { ...process.env, XDG_DATA_HOME: join(tmpdir(), `${basename(root)}-state`) };
installProject({ repository, proposal, env, projectId: 'digest-fixture' });
git(['add', '.torch']); git(['commit', '-m', 'install']);
createWorktrees({ repository: inspectRepository(root), parentOverride: join(tmpdir(), `${basename(root)}-trees`) });
git(['add', '.torch/install-manifest.json']); git(['commit', '-m', 'worktrees']);
const control = openControlPlane({ repositoryRoot: root, env, clock: now });
const backlog = new BacklogService({ repositoryRoot: root, controlPlane: control, clock: () => new Date('2026-09-20T12:00:00Z') });
return { root, env, control, backlog, worker: proposal.domains[0].id };
}
test('SCN-owner-digest: read-only owner-first report distinguishes implementation from shipping, neglect from unknown coverage and escapes project text', async (t) => {
const context = fixture();
const task = context.backlog.createOwner({ actorId: 'owner', title: ' [bad](javascript:evil)',
description: 'Owner request', acceptanceCriteria: ['review'], affectedDomains: [context.worker] });
const approval = context.control.requestApproval({ requester: context.worker, approver: 'owner', task: task.id,
title: 'Approve direction', summary: 'Owner decision needed' });
context.control.requestApproval({ requester: context.worker, approver: 'session-manager', title: 'Manager approval', summary: 'Not owner approval' });
const delivery = new DeliveryService({ repositoryRoot: context.root, controlPlane: context.control, clock: now });
delivery.create({ sourceArea: context.worker, label: 'Implementation only', evidence: ['source'] });
const service = new OwnerDigestService({ repositoryRoot: context.root, controlPlane: context.control, backlogService: context.backlog, clock: now });
const before = JSON.stringify(context.backlog.list());
const auditCount = context.control.database.prepare('SELECT COUNT(*) AS count FROM audit_events').get().count;
const report = service.build();
assert.equal(report.needsOwner.items[0].id, approval.id);
assert.equal(report.needsOwner.count, 1);
assert.equal(report.shipped.count, 0);
assert.equal(report.neglectedRequests.items[0].taskId, task.id);
assert.equal(report.lastDeployment, null);
assert.equal(report.mutationPerformed, false);
assert.ok(report.markdown.indexOf('## Needs you') < report.markdown.indexOf('## Shipped'));
assert.doesNotMatch(report.markdown, /