/* * **재기동 정책** — 개명(ADR-0029 §2)과 그 규율 둘. * * ── 무엇을 바꿨나 ─────────────────────────────────────────────────────────── * `realityMode`(`mirror`·`sim-world`·`sim-experiment`) → `restartPolicy`(`resync`·`resume`·`reset`). * 「현실이 어디서 오나」는 ADR-0029 가 **원본의 종류**로 옮긴 물음이고, 코드가 이 축으로 실제로 가른 것은 * **재기동 거동 하나**였다 — 그것은 원본의 성질이 아니라 트윈과 자기 과거의 관계다. 그래서 축은 남고 * 이름이 바뀌었다. * * ── 지키는 규율 둘 (계획서 §6) ────────────────────────────────────────────── * ① **모르는 값은 오류를 낸다.** 예전에는 미선언이 알리지 않고 `sim-experiment`(=저널 초기화)로 떨어졌다 — 값이 * 하나 어긋나는 순간 이력을 지우는 길이다. 기본값을 없앴다. * ② **옛 어휘를 번역하지 않는다.** 번역을 두면 옛 값이 살아남고 다시 저장된다. 저장된 값은 한 번 * 옮기고(데이터 이관), 코드는 새 어휘만 안다 — 다만 무엇으로 옮겨야 하는지는 오류 문장이 말한다. */ import { test } from 'node:test' import assert from 'node:assert/strict' import { readdirSync, readFileSync, statSync } from 'node:fs' import { fileURLToPath } from 'node:url' import { join } from 'node:path' import { readRestartPolicy, isRestartPolicy, RESTART_POLICIES } from '../server/engine/restart-policy.ts' test('셋뿐이다 — 그리고 그 셋을 읽는다', () => { assert.deepEqual([...RESTART_POLICIES], ['resync', 'resume', 'reset']) for (const p of RESTART_POLICIES) assert.equal(readRestartPolicy(p, 'test'), p) assert.equal(readRestartPolicy(' resume ', 'test'), 'resume', '공백은 다듬는다') }) test('미선언은 오류를 낸다 — 기본값으로 메우면 저널이 지워지는 길이 열린다', () => { for (const raw of [undefined, null, '', ' ', 42, {}]) { assert.throws(() => readRestartPolicy(raw, 'twin "x"'), /twin "x": restart policy/) } /* 무엇을 해야 하는지 문장이 말한다 — 코드를 그대로 오류를 내지 않는다. */ try { readRestartPolicy(undefined, 'twin "x"') assert.fail('오류를 내어야 한다') } catch (e: any) { assert.match(e.message, /resync \| resume \| reset/) assert.match(e.message, /no default/) } }) test('옛 어휘도 오류를 낸다 — 다만 무엇으로 옮길지 말한다(번역해 주지 않는다)', () => { const cases: [string, string][] = [ ['mirror', 'resync'], ['sim-world', 'resume'], ['sim-experiment', 'reset'] ] for (const [old, next] of cases) { try { readRestartPolicy(old, 'twin "x"') assert.fail(`${old} 은 오류를 내어야 한다`) } catch (e: any) { assert.match(e.message, /old vocabulary/) assert.match(e.message, new RegExp(`"${next}"`), '옮길 값을 말한다') } } }) test('묻는 자리도 있다 — 화면·검사는 오류를 내지 않고 판별한다', () => { assert.equal(isRestartPolicy('resume'), true) assert.equal(isRestartPolicy('sim-world'), false) assert.equal(isRestartPolicy(undefined), false) }) /* * ── 옛 어휘가 코드로 되돌아오지 않게 (계획서 §6 의 검사) ──────────────────── * ADR-0034 의 `local-declarations.test.ts` 가 `site*` 에 대해 하는 것과 같은 방식이다. 개명은 저장값·타입· * 화면에 걸쳐 있어서, 한 곳만 되돌아와도 그 자리가 알리지 않고 다르게 재기동한다. */ const OLD_TOKENS = [/\brealityMode\b/, /\bRealityMode\b/, /'sim-world'/, /'sim-experiment'/, /DEFAULT_REALITY_MODE/] function sources(root: string): { file: string; src: string }[] { const base = fileURLToPath(new URL(root, import.meta.url)) const out: { file: string; src: string }[] = [] const walk = (dir: string) => { for (const name of readdirSync(dir)) { if (name === 'node_modules' || name.startsWith('dist')) continue const path = join(dir, name) if (statSync(path).isDirectory()) { walk(path); continue } if (name.endsWith('.ts')) out.push({ file: path.slice(base.length + 1), src: readFileSync(path, 'utf-8') }) } } walk(base) return out } test('옛 어휘가 코드에 없다 — 이력을 설명하는 주석만 예외다', () => { const offenders: string[] = [] for (const { file, src } of [...sources('../server'), ...sources('../test')]) { /* 이력 설명(「예전에는 …」)과 이관 안내(restart-policy.ts)는 남아야 한다 — 그것이 개명의 근거다. */ if (file.endsWith('restart-policy.ts') || file.endsWith('restart-policy.test.ts')) continue for (const line of src.split('\n')) { const isComment = /^\s*(\*|\/\/|\/\*)/.test(line) if (isComment) continue if (OLD_TOKENS.some(re => re.test(line))) offenders.push(`${file}: ${line.trim().slice(0, 80)}`) } } assert.deepEqual(offenders, [], `옛 어휘가 코드로 돌아왔다:\n${offenders.join('\n')}`) }) test('기본값 상수가 없다 — 미선언을 우리가 고르지 않는다', () => { const engine = readFileSync(fileURLToPath(new URL('../server/engine/twin-engine.ts', import.meta.url)), 'utf-8') const code = engine .split('\n') .filter(line => !/^\s*(\*|\/\/|\/\*)/.test(line)) .join('\n') assert.equal(/DEFAULT_RESTART_POLICY/.test(code), false, '기본값 상수가 되살아나면 미선언이 다시 들어온 것이 없어진다') assert.match(code, /readRestartPolicy\(/, '읽는 자리는 엄격한 리더를 지난다') })