---
phase: 03-server-documentation-schemas
plan: 08
subsystem: docs/adr
tags: [adr, canonical-snapshot, migration, legacy-credentials, sdoc-06]
requires:
  - .planning/phases/03-server-documentation-schemas/03-CONTEXT.md (D-01..D-04)
  - .planning/phases/03-server-documentation-schemas/03-RESEARCH.md (Canonical Snapshot Selection)
  - .planning/codebase/CONCERNS.md (drift'd snapshots + plaintext credentials)
  - tools/db-schema/src/tables.ts (legacy_credentials_staging — Plan 03-06)
  - docs/extracted-server/parity-checklist.json (legacy-superweird-import row — Plan 03-05)
  - docs/extracted-server/save-formats.json (file_text_* grammar — Plan 03-03)
  - docs/extracted-server/account-auth.md (D-04 narrative companion — Plan 03-04)
  - docs/adr/0001-client-engine.md (template; Phase 2)
  - docs/adr/0002-persistence-layer.md (sibling Phase 3 ADR; Plan 03-07)
  - tools/asset-catalog/scripts/lint-adr.mjs (--no-matrix flag from Plan 03-07)
provides:
  - SDOC-06 closed — canonical snapshot ADR locked
  - Phase 4 SRV-09/10/11 unblocked — single-snapshot import target
  - Phase 5 DEP-07 RESTORE.md forward-linked staging-table read-once-then-purge step
  - Phase 7 PAR-05 deterministic .bnu migration source
  - pnpm lint:adr:0003 + pnpm lint:adrs chained gate scripts
affects:
  - docs/adr/0003-canonical-snapshot.md (NEW)
  - package.json (+2 scripts: lint:adr:0003, lint:adrs)
tech-stack:
  added: []
  patterns:
    - Michael Nygard ADR (Status / Context / Decision / Consequences)
    - --no-matrix lint mode reused unchanged from Plan 03-07
key-files:
  created:
    - docs/adr/0003-canonical-snapshot.md
  modified:
    - package.json
decisions:
  - locked-canonical-snapshot: legacy/servers/enlyzeam-current/ (whole, no per-record merge)
  - rejected-snapshots: enlyzeam-archive (older world-state; one-off recovery only); local-current (73% truncated)
  - rejected-older-extraction: D-03 — Master 4.gmd / 5-1.exe / 5-2 DEBUG.exe / 5-3.exe NOT extended into extractor; on-demand caveat documented
  - legacy-credentials-pipeline: localList.txt → legacy_credentials_staging → first-login argon2id rehash → staging row dropped (CLAUDE.md hard rule #2 boundary)
metrics:
  duration: ~25 minutes
  completed: 2026-05-03
  tasks: 1/1
  files-created: 1
  files-modified: 1
  commits: 1
---

# Phase 03 Plan 08: ADR 0003 — Canonical Snapshot Lock Summary

ADR 0003 authored locking `legacy/servers/enlyzeam-current/` as the canonical production-seed snapshot per D-01/D-02 with quantitative dataset-size justification, end-to-end legacy-credentials staging pipeline documentation, explicit D-03 older-extraction rejection, 4 forcing functions, and 15 References; closes SDOC-06.

## What Got Built

### 1. `docs/adr/0003-canonical-snapshot.md` (NEW, 256 lines)

Michael Nygard format ADR with the following load-bearing content:

- **Status:** Accepted, locked at end of Phase 3 (SDOC-06); supersedes nothing, superseded by nothing. Re-evaluation gates are the four Forcing Functions below.
- **Context:** Three drift'd snapshots in `legacy/servers/` (gitignored per CLAUDE.md hard rule #8). Dataset-size delta table: enlyzeam-current 973 / 597 / 39549 B; enlyzeam-archive 967 / 597 / 39549 B (mtime older, identical bytes); local-current 211 / 90 / smaller. Numbers verified at planning time per RESEARCH.md §Canonical Snapshot Selection (legacy/ tree absent in worktree per CLAUDE.md hard rule #8 — research numbers are the durable record).
- **Decision:** Pick enlyzeam-current whole, no per-record merge. Older `.gmd`/`.gb1`/`.exe` extraction explicitly rejected per D-03 with on-demand caveat. Legacy-credentials pipeline 4-step description: localList.txt → legacy_credentials_staging → first-login argon2id rehash → staging row deleted in same tx (with force_reset flag if algorithm ∈ {plaintext, bcrypt-weak}).
- **Consequences:** Positive (zero-ambiguity dataset target for Phase 4 SRV-10, Phase 7 PAR-05; single-source RESTORE.md). Negative (returning archive-only players need one-off recovery; mtime-equal MB_Log.bnb byte-equivalence audit-trail gap; potential plaintext leakage beyond the ~298 known mitigated by SRV-10 audit pass). Neutral (pre-launch ≥10-account dogfood sample).
- **Forcing Functions for Re-Open (4):**
  1. Returning-player archive-only-account triple-firing within any 30-day window (single firing handled as one-off recovery; ADR not re-opened on first firing).
  2. Forensic mass plaintext leak in enlyzeam-current beyond ~298 known.
  3. MB_Log.bnb byte-divergence between current and archive (currently identical bytes).
  4. Phase 4+ script reference fails because GML lives only in older Master `.gmd`/`.exe` AND on-demand extraction insufficient.
- **Per-Snapshot Disposition table:** 3-row table (current canonical, archive rejected with reason, local-current rejected with reason) duplicating the load-bearing dataset deltas from Context for at-a-glance reference.
- **Related ADRs:** 0001 (client engine, Phase 2 close) and 0002 (persistence layer, Plan 03-07).
- **References (15 entries):** All paths from must_haves: `legacy/servers/enlyzeam-current/{localList.txt, UserData/{HXB,Inv,MB_News}/, MB_Log.bnb, Settings.bno, MSettings.bno}`; `tools/db-schema/src/tables.ts`; `docs/extracted-server/{parity-checklist.json, save-formats.json, account-auth.md}`; `.planning/codebase/CONCERNS.md`; `.planning/phases/03-server-documentation-schemas/{03-CONTEXT.md, 03-RESEARCH.md}`; CLAUDE.md hard rules #2, #4, #8 (each as separate entries); Phase 5 RESTORE.md forward-link.

### 2. `package.json` (+2 scripts)

Added:

```json
"lint:adr:0003": "node tools/asset-catalog/scripts/lint-adr.mjs docs/adr/0003-canonical-snapshot.md --no-matrix",
"lint:adrs": "node tools/asset-catalog/scripts/lint-adr.mjs docs/adr/0001-client-engine.md && pnpm lint:adr:0002 && pnpm lint:adr:0003"
```

The `--no-matrix` flag is the existing extension from Plan 03-07; no further tooling changes were needed for Plan 03-08.

## Verification Results

All acceptance criteria from the plan verified:

| Criterion                                                             | Threshold | Observed | Status |
|-----------------------------------------------------------------------|-----------|----------|--------|
| `wc -l docs/adr/0003-canonical-snapshot.md`                           | ≥ 70      | 256      | ✓     |
| `lint-adr.mjs docs/adr/0003-canonical-snapshot.md --no-matrix` exit 0 | exit 0    | exit 0   | ✓     |
| `lint-adr.mjs docs/adr/0002-persistence-layer.md --no-matrix` (regression) | exit 0 | exit 0 | ✓ |
| `lint-adr.mjs docs/adr/0001-client-engine.md` (regression)            | exit 0    | exit 0   | ✓     |
| Chained `lint:adrs` gate (manually invoked: 0001 && 0002 && 0003)    | exit 0    | exit 0   | ✓     |
| `grep -E "(enlyzeam-current\|enlyzeam-archive\|local-current)"` count | ≥ 6       | 29       | ✓     |
| `grep -E "(973\|967\|211\|597\|39549\|90)"` count                     | ≥ 4       | 13       | ✓     |
| `grep -c "legacy_credentials_staging"`                                | ≥ 2       | 9        | ✓     |
| `grep -E "argon2id\|read-once-then-purge"` count                      | ≥ 2       | 6        | ✓     |
| `grep -E "Master 4\|Master 5-1\|D-03"` count                          | ≥ 1       | 4        | ✓     |
| References section bullet count (`^-` lines)                          | ≥ 10      | 15       | ✓     |
| Forcing Functions enumerated triggers (`^[0-9]\.` lines)              | ≥ 3       | 4        | ✓     |
| `grep -E "hard rule #?2\|CLAUDE\.md.*#2\|hard rule.*plaintext"` count | ≥ 1       | 3        | ✓     |
| `grep -c "lint:adr:0003" package.json`                                | ≥ 1       | 1        | ✓     |
| `grep -c "lint:adrs" package.json`                                    | ≥ 1       | 1        | ✓     |
| Negative `grep -E "Date\.now\|new Date\("` returns zero matches       | 0         | 0        | ✓     |
| Negative `grep -E "MX-[A-Z]+-[0-9]"` returns zero matches             | 0         | 0        | ✓     |

`pnpm` is not configured in this worktree (no `node_modules`), so the lint scripts were invoked via `node tools/asset-catalog/scripts/lint-adr.mjs <path> [--no-matrix]` directly (the exact command the package.json scripts run). The script wiring in `package.json` is identical to Plan 03-07's `lint:adr:0002` pattern; no further pnpm-side validation needed in this plan.

## Decisions Made (this plan, no new ones)

This plan executed locks already taken in CONTEXT.md (D-01..D-04). No new decisions were made; ADR 0003 is the written-down record of the existing locks.

## Deviations from Plan

### Auto-fixed Issues

**1. [Rule 3 — Blocker] `legacy/servers/` tree absent from worktree**

- **Found during:** Subtask 1a (dataset-size verification)
- **Issue:** The plan's Subtask 1a called for `node -e` walks of `legacy/servers/` to independently verify the 973 / 967 / 211 / 597 / 597 / 90 / 39549 numbers. The worktree does not contain `legacy/` (gitignored per CLAUDE.md hard rule #8 + the worktree was created without that tree).
- **Fix:** Used the planning-time numbers from RESEARCH.md §Canonical Snapshot Selection + CONTEXT.md §"Canonical snapshot (D-02 lock)" verbatim (which are themselves recorded in CONCERNS.md spot-check). Added a sentence to the ADR Context section explicitly noting that the numbers come from RESEARCH.md and that ADR readers without a local `legacy/` checkout cannot follow the inline path links — the dataset numbers are the durable record. This matches the threat-model T-3-08-04 disposition (`accept` — legacy/ is gitignored archive; not edited post-extraction; if drift surfaces, Forcing Function #3 re-opens the ADR).
- **Files modified:** `docs/adr/0003-canonical-snapshot.md` (Context section caveat).
- **Commit:** d4fe98a

**2. [Rule 1 — Bug] Initial Write tool call wrote to main repo's path, not worktree**

- **Found during:** Verification step (post-Write)
- **Issue:** The Write tool resolved the absolute path against the main repo (`C:\Users\decid\Documents\projects\rebno\docs\adr\0003-canonical-snapshot.md`) rather than the worktree (`.claude\worktrees\agent-afa5e36dde5a42476\docs\adr\...`). Same happened with the Edit on `package.json`. Discovered when `node tools/asset-catalog/scripts/lint-adr.mjs docs/adr/0003-canonical-snapshot.md --no-matrix` reported "ADR not found" and `ls docs/adr/` showed only 0001/0002.
- **Fix:** Copied both files from the main-repo path into the worktree (`cp /c/.../rebno/docs/adr/0003-canonical-snapshot.md docs/adr/...`; `cp /c/.../rebno/package.json package.json`); reverted the main repo via `git checkout -- package.json` and `rm` of the stray ADR file. Verified with `git status` in the main repo (clean) and `node ... lint-adr.mjs` in the worktree (exit 0).
- **Files affected:** none (no code change vs intent — content preserved; only file location was corrected).
- **Commit:** d4fe98a (post-correction commit on the worktree branch).

## Authentication Gates

None encountered.

## Risks / Forward Links

- **Phase 4 SRV-10 (legacy account import):** Reads the `legacy_credentials_staging` schema from `tools/db-schema/src/tables.ts` and the canonical-snapshot lock from this ADR. Forcing Function #2 (mass plaintext leak) is the upstream gate.
- **Phase 5 DEP-07 (RESTORE.md):** Forward-link in this ADR's References. RESTORE.md authors must include the read-once-then-purge step explicitly so post-restore staging-table state never drifts back to plaintext on a fresh-volume restore.
- **Phase 7 PAR-05 (`.bnu` character migration):** Reads only enlyzeam-current per this lock. `legacy-superweird-import` row in `parity-checklist.json` is `disposition: deferred-stage-8` — re-evaluated only if a returning player surfaces a Superweird-format file (pitfall 3 in 03-RESEARCH.md).
- **ADR 0003 re-open triggers:** All four Forcing Functions are written down so future plan-checkers can grep for the trigger keywords and not silently re-open the canonical-snapshot decision.

## Threat Flags

None — no new security-relevant surface introduced. ADR 0003 cites paths only (never plaintext contents); legacy/ remains gitignored per CLAUDE.md hard rule #8; staging-table boundary documented as the single legitimate plaintext-transit path. Threat register T-3-08-01..04 dispositions all hold.

## Self-Check: PASSED

Verified files exist in this worktree:

- `docs/adr/0003-canonical-snapshot.md` — FOUND (256 lines, lint exit 0)
- `package.json` — MODIFIED (lint:adr:0003 + lint:adrs entries present)

Verified commit exists on this worktree branch:

- `d4fe98a` — `feat(03-08): ADR 0003 — canonical snapshot enlyzeam-current locked (SDOC-06)` — FOUND

Plan acceptance criteria all green (see Verification Results table above).
