v1.1: harden recovery and control boundaries

WP-D and WP-E complete the planned v1.1 implementation packages.

WP-D — recovery honesty:
- backups verify the completed copy with PRAGMA integrity_check
- corruption missed by quick_check is detected by the full check
- existing good backups remain protected
- oversized exports are still delivered but declare whether this version can
  import them, while the 20 MB import limit remains unchanged
- backup was exercised through the real browser UI on both the normal campaign
  database and a campaign-shaped database over 100 MB

WP-E — control-boundary contrast:
- interactive control boundaries meet the WCAG 1.4.11 3:1 target
- the contrast audit is now a failing gate rather than an advisory
- rendered browser measurements pass for the composer, controls, tabs and nav
- text contrast and focus visibility remain intact
- owner reviewed and approved the before/after screenshots

Reports:
- planning/reports/v1.1/V1.1-WP-D-REPORT.md
- planning/reports/v1.1/V1.1-WP-E-REPORT.md

All planned v1.1 work packages A-E are now complete. Release validation has not
yet begun.
This commit is contained in:
JesseMarkowitz
2026-09-16 05:37:13 -04:00
parent 59b5ebc2d8
commit 87a40326a2
21 changed files with 2401 additions and 60 deletions
+20 -9
View File
@@ -39,8 +39,9 @@ turn is blocked.
never leaves a half-written file wearing a backup's name. `os.replace` is
atomic on the same filesystem, which is why the temporary sits in the
destination's own directory rather than in `/tmp`.
3. `PRAGMA quick_check` runs against the finished copy, opened as its own
database, before it is renamed. A backup nobody verified is a belief.
3. `PRAGMA integrity_check` runs against the finished copy, opened as its own
database, before it is renamed. A backup nobody verified is a belief. v1.1
WP-D made this the full check rather than `quick_check`; see `_verify`.
4. An existing file is never overwritten. Each run writes a new name stamped
with the time, so yesterday's backup survives today's mistake — which is most
of what a backup is for.
@@ -189,18 +190,28 @@ def _copy(source_path: Path, working: Path) -> int:
def _verify(working: Path) -> str:
"""Runs `PRAGMA quick_check` against the finished copy.
"""Runs `PRAGMA integrity_check` against the finished copy.
Opened as its own connection, so what is checked is the file on disk rather
than any page cache the copy left behind. `quick_check` rather than
`integrity_check` because it does the structural work — every page reachable,
every record readable — without the full index cross-check, which on a large
database is minutes rather than moments. A backup nobody verified is a
belief; a backup verified slowly enough that nobody takes one is worse.
than any page cache the copy left behind.
**v1.1 WP-D: the full check, not `quick_check`.** M9 chose `quick_check` for
its speed, on the argument that a backup verified slowly enough that nobody
takes one is worse than a fast one. The measurements say the trade was not
needed here: `quick_check` omits the cross-check between a table and its
indexes, and that is a real class of damage it reports as `ok`. A copy whose
index disagrees with its table restores into a database that answers queries
with rows that are not there — the failure a backup exists to prevent.
The cost is small at the sizes this application produces: on the 100-turn
evidence campaign both checks are a few milliseconds, and on a synthetic
database two orders of magnitude larger the difference is still short of a
second (WP-D report §E). A backup nobody verified is a belief; this is the
check that makes it a fact.
"""
connection = sqlite3.connect(f"file:{working}?mode=ro", uri=True)
try:
rows = connection.execute("PRAGMA quick_check").fetchall()
rows = connection.execute("PRAGMA integrity_check").fetchall()
finally:
connection.close()
result = ", ".join(str(row[0]) for row in rows) if rows else "no result"