/* * ★ 실행기 — **회색 칸이 빨간 칸으로 위장되지 않게.** * * ① 시뮬 트윈에 라이브를 요구하지 않는다(해당 없음). 그 빨강은 결함이 아니다. * ② 템플릿으로 세운 트윈에는 옮겨 온 마스터가 없다 — 「못 옮긴 값」을 물을 대상이 아니다. * ③ 반출 0 의 처방이 둘로 갈린다: 스냅샷이면 방식을 바꿔야 하고, 아니면 더 기다릴 수 있다. * ④ 액추에이션은 계약에 없다 — 측정을 만들지 않고 미실행으로 남긴다. */ import { test } from 'node:test' import assert from 'node:assert/strict' import { assessSuite, type SuiteFacts } from '../server/engine/integration-runner.ts' const live = (): SuiteFacts => ({ subject: 'live', fromReference: true, adapterCapabilities: ['live'], connectOk: true, siteCount: 3, locationCount: 12, itemCount: 340, lastWarningCount: 2, journalTotal: 1200, removalsJournaled: 17, specCoverage: { declared: 6, total: 8 }, forecastRan: true, feed: 'delta-poll' }) test('라이브 전부 갖추면 예측까지 도달하고, 폐루프는 능력이 없어 막힌다', () => { const a = assessSuite(live()) assert.equal(a.path.reached, 'forecast') assert.deepEqual(a.path.stages[3].blockedBy, ['no-actuate-capability']) assert.deepEqual(a.forbiddenClaims, ['claim.automatic-control']) assert.equal(a.profile.multiSite, true) }) test('시뮬 트윈은 라이브가 해당 없음이고, 그 사유를 말한다', () => { const a = assessSuite({ ...live(), subject: 'sim', journalTotal: null, removalsJournaled: null }) assert.deepEqual(a.notApplicable.map(n => n.id), ['live-ingest', 'live-removal']) assert.deepEqual(a.path.stages[1].blockedBy, ['subject-is-simulation']) assert.deepEqual(a.path.stages[1].unmeasured, [], '해당 없음을 미측정으로 계산하지 않는다') assert.equal(a.path.reached, 'structure') assert.ok(!a.planned.includes('live-removal'), '해당 없는 프로브는 계획하지 않는다') }) test('템플릿으로 세운 트윈은 못 옮긴 값을 물을 대상이 아니다', () => { const a = assessSuite({ ...live(), fromReference: false, lastWarningCount: null }) assert.deepEqual(a.notApplicable.map(n => n.id), ['master-warnings']) assert.equal(a.path.stages[0].achieved, true, '해당 없음이 구조 달성을 막지 않는다') }) test('반출 0 — 스냅샷이면 방식을 바꿔야 한다고 말한다', () => { const a = assessSuite({ ...live(), removalsJournaled: 0, feed: 'snapshot-poll' }) assert.deepEqual(a.path.stages[1].blockedBy, ['snapshot-only-feed']) assert.ok(a.forbiddenClaims.includes('claim.realtime')) }) test('반출 0 — 스냅샷이 아니면 아직 일어나지 않은 것일 수 있다', () => { const a = assessSuite({ ...live(), removalsJournaled: 0, feed: 'delta-poll' }) assert.deepEqual(a.path.stages[1].blockedBy, ['no-removal-observed']) }) test('승격 전 저널은 반출을 셀 수 없다 — 「없다」가 아니라 「모른다」', () => { const a = assessSuite({ ...live(), removalsJournaled: null }) assert.deepEqual(a.path.stages[1].unmeasured, ['removals']) assert.deepEqual(a.path.stages[1].blockedBy, []) }) test('액추에이션 효과는 측정을 만들지 않고 미실행으로 남긴다', () => { const a = assessSuite({ ...live(), adapterCapabilities: ['live', 'actuate'] }) const eff = a.results.find(r => r.id === 'actuate-effect')! assert.equal(eff.ran, false) assert.equal(eff.detail, 'actuation-not-wired') assert.deepEqual(a.path.stages[3].unmeasured, ['correlated-effect']) }) test('쓰기 프로브는 동의 없이 계획에 들어오지 않는다', () => { const able = { ...live(), adapterCapabilities: ['live', 'actuate'] } assert.ok(!assessSuite(able).planned.includes('actuate-effect')) assert.ok(assessSuite(able, { allowWrites: true }).planned.includes('actuate-effect')) }) test('액추에이션 능력이 없으면 쓰기 동의가 있어도 계획하지 않는다 — 보낼 문이 없다', () => { assert.ok(!assessSuite(live(), { allowWrites: true }).planned.includes('actuate-effect')) }) test('접속을 안 돌렸으면 구조부터 미측정이 아니라 미실행으로 남고, 계획의 첫 칸이 된다', () => { const a = assessSuite({ ...live(), connectOk: null }) assert.equal(a.planned[0], 'connect') }) test('못 옮긴 값이 0 이어도 세었으므로 통과다', () => { assert.equal(assessSuite({ ...live(), lastWarningCount: 0 }).path.stages[0].achieved, true) })