Commit graph

2 commits

Author SHA1 Message Date
44b222859e feat(scanners): the recovery path is code you can run, not prose you can read
R1+R2 as one chunk — both KRITISK rows of the Q3 severity table sit on the
restore path, and neither closes alone.

R1: rollback-engine.mjs verified every checksum before AND after each write,
resolved the legacy backup root and reported createdNotRemoved — and none of it
was reachable. Measured: 16 files under scanners/ carry a process.argv entry;
the engine was not one of them. commands/rollback.md drove the restore as model
prose: an ESM import block a template cannot execute, ad-hoc `cp` offered
underneath as the runnable path, and "(checksum verified)" pre-rendered three
times in the success output. `cp` establishes no checksum, so the verification
was a property of the template rather than of the run — on the one surface that
runs when the user is already in trouble.

R2: implement.md Step 3 hand-built its backup (mkdir, cp, a date-derived id, a
manifest typed out in the template) while parseManifest knew one frozen sample
of that format, pinned by a HAND-WRITTEN fixture instead of by the template's
own text. Rename a key and parseManifest returns zero files while rollback
reports success.

Fixing only R1 leaves the new CLI parsing a prose format; fixing only R2 leaves
a clean format with no runnable entry.

- scanners/rollback-cli.mjs — --list / --create / --restore / --delete over the
  existing engine, on the shared requireValidArgs gate. Exit 0 done, 1
  outstanding (gate refusal with nothing written, or a backup that covered fewer
  targets than given), 2 a file failed, 3 could not do the job. A gated restore
  is 1, not 3: "this write leaves your project" is a verdict about a write that
  WAS examined, and it rides in the payload where a command under 2>/dev/null
  can act on it.
- createBackup gains `created` (recorded, never copied — no backup can hold a
  file that does not exist) and `skipped`, so a backup covering fewer files than
  asked is no longer indistinguishable from a clean one.
- implement.md Step 3 and rollback.md now call the CLI. parseManifest's
  implement-format branch stays: nothing writes that shape now, but every backup
  made before this chunk is on disk in it.
- backup-restore-contract.test.mjs checks every field rollback.md renders
  against a payload produced by RUNNING the CLI. That is what replaced
  "(checksum verified)".

20 guards seen red against the original state before any production code, then
each against its own defect. Two holes that surfaced there were mine: the
implement assertion matched `--create` as a substring of `--created` and stayed
green when the call was removed; and mutating the argv gate showed
requireValidArgs sets exit 3 by itself, so a CLI can report that it could not
parse its arguments and still run the restore underneath — that case is now
asserted on the bytes.

Suite 1752 -> 1777, 0 fail. Frozen tests/snapshots/v5.0.0 untouched. Dogfooded
through the templates' own command lines against a sandboxed HOME, including the
machine-wide arm: refused with the file unchanged, then restored under
--approve-scope.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Logq8GGWKhtyDem63FTEnG
2026-08-18 21:28:15 +02:00
dc800560b7 docs(plan): the prose-invariant sweep finds the class's third instance (Q_AUDIT)
Q1 was gate-in-prose, Q2 was argv-in-prose; the sweep's answer to 'what is
the third' is data-contract-in-prose: templates hand-build files (backup
manifest, session state) that engines and hooks later parse. Rated list of
9, topped by two recovery-path findings: the rollback engine has no CLI
entry (restore runs as model prose with a pre-rendered 'checksum verified'
line), and implement's hand-built manifest format is pinned only by a
hand-written fixture — the seam that already produced one success-shaped
no-op. Every number in the doc is command-produced (Fable session, no
advisor).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BeVSUuNqSCDUgTvfLKv4fK
2026-08-12 22:14:48 +02:00