/* * 지난 기록 채우기의 판단 — **무엇을 받고 무엇을 거절하나.** * * ── 왜 저널에만 쓰나 (2026-08-30) ────────────────────────────────────────── * 커널의 상태는 「지금」이다. 늦게 온 옛 표본은 가드가 막는다(그렇게 만들었다). 지난 기록을 라이브 * 문으로 넣으면 가드에 막히거나 트윈의 시계가 과거로 끌린다. 그래서 이 길은 저널에만 쓰고, * **상태를 바꾸는 사실은 아예 받지 않는다.** * * ── 보존 안에서만 (사용자 결정) ──────────────────────────────────────────── * 보존보다 오래된 것을 채우면 다음 정리에서 지워진다. 조용히 넣고 지워지면 아무도 모르므로 * 거절하고 이유를 말한다. */ import { test } from 'node:test' import assert from 'node:assert/strict' import { judgeBackfill, splitAlreadyThere, PERIOD_FACTS } from '../server/service/twin-backfill/backfill-shape.ts' const NOW = Date.parse('2026-08-30T00:00:00.000Z') const env = (eventType: string, eventTime: string, eventId = `${eventType}|${eventTime}`) => ({ eventType, eventTime, eventId }) test('★ 마감된 구간 사실을 받는다', () => { const r = judgeBackfill([env('energy.usage.period', '2026-08-29T00:00:00.000Z')], { nowMs: NOW, retentionDays: 7 }) assert.equal(r.keep.length, 1) assert.equal(r.refused.length, 0) }) test('★ 상태를 바꾸는 사실은 받지 않는다 — 저널에만 넣으면 상태와 저널이 갈라진다', () => { const r = judgeBackfill( [env('energy.measured', '2026-08-29T00:00:00.000Z'), env('energy.generated', '2026-08-29T00:00:00.000Z')], { nowMs: NOW, retentionDays: 7 } ) assert.equal(r.keep.length, 0) assert.equal(r.refused.length, 2) assert.match(r.refused[0].reason, /라이브 문으로 보내라/) }) test('★ 보존보다 오래된 것은 거절하고 이유를 말한다 — 넣어도 지워진다', () => { const r = judgeBackfill([env('energy.usage.period', '2026-08-01T00:00:00.000Z')], { nowMs: NOW, retentionDays: 7 }) assert.equal(r.keep.length, 0) assert.match(r.refused[0].reason, /보존 기간\(7일\)보다 오래됐다/) assert.match(r.refused[0].reason, /보존 기간을 늘려야 한다/, '무엇을 해야 하는지 함께 말한다') }) test('보존 안쪽 경계는 받는다', () => { const justInside = new Date(NOW - 7 * 86_400_000 + 1000).toISOString() const r = judgeBackfill([env('energy.usage.period', justInside)], { nowMs: NOW, retentionDays: 7 }) assert.equal(r.keep.length, 1) }) test('보존 정책이 없으면 오래됨을 판정하지 않는다 — 없는 기준으로 거절하지 않는다', () => { const r = judgeBackfill([env('energy.usage.period', '2020-01-01T00:00:00.000Z')], { nowMs: NOW }) assert.equal(r.keep.length, 1) }) test('★ 아직 오지 않은 시각은 거절한다 — 지난 기록이 아니다', () => { const r = judgeBackfill([env('energy.usage.period', '2026-09-01T00:00:00.000Z')], { nowMs: NOW, retentionDays: 7 }) assert.equal(r.keep.length, 0) assert.match(r.refused[0].reason, /아직 오지 않은 시각/) }) test('시각을 읽을 수 없으면 거절한다', () => { const r = judgeBackfill([env('energy.usage.period', 'n/a')], { nowMs: NOW, retentionDays: 7 }) assert.equal(r.keep.length, 0) assert.match(r.refused[0].reason, /읽을 수 없다/) }) test('받는 종류 다섯이 모두 마감된 구간 사실이다', () => { for (const t of PERIOD_FACTS) { const r = judgeBackfill([env(t, '2026-08-29T00:00:00.000Z')], { nowMs: NOW, retentionDays: 7 }) assert.equal(r.keep.length, 1, `${t} 를 받아야 한다`) } }) /* ── 같은 기간을 두 번 넣지 않는다 ────────────────────────────────────────── */ test('★ 이미 있는 것은 건너뛴다 — 다시 읽어도 중복이 쌓이지 않는다', () => { const a = env('energy.usage.period', '2026-08-29T00:00:00.000Z') const b = env('energy.usage.period', '2026-08-29T01:00:00.000Z') const r = splitAlreadyThere([a, b], new Set([a.eventId])) assert.equal(r.fresh.length, 1) assert.equal(r.fresh[0].eventId, b.eventId) assert.equal(r.skipped, 1) }) test('id 가 없으면 넣지 않는다 — 같은 사실인지 판단할 근거가 없다', () => { const r = splitAlreadyThere([{ eventType: 'energy.usage.period', eventTime: '2026-08-29T00:00:00.000Z' }], new Set()) assert.equal(r.fresh.length, 0) assert.equal(r.skipped, 1) })