From dbd2f585ede1179432c2fbf7849874d06cd32779 Mon Sep 17 00:00:00 2001 From: 404SecNotFound <46477113+404SecNotFound@users.noreply.github.com> Date: Fri, 25 Sep 2026 23:08:31 +0000 Subject: [PATCH] docs(release): bring the roadmap up to date and prepare v2.3.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The roadmap's sequencing table stopped at 4.5. Everything #210 landed is now a row: the end-to-end review fixes, the FORMAT-V2-DESIGN §4.8 password-and-shares slot, the paper vault's second pass (set code, presets, version-25 symbols, self-contained strips, the printout check), photo and live camera scanning, and RECOVERY.md being executed by recovery_test.py. Phase 8 now says what it waits on: the owner, after the next tag. The Cut table's steganography row records why the audio carrier stays: it is documented as a carrier, not steganography, and claims nothing that row cuts. TEN-X-PLAN.md said Bet 1 (camera scanning) was on hold in three places. It shipped in 22c7cdf and 161e919, and the decoder decision that held it was taken as BarcodeDetector where present and pinned jsqr where not. The plan now says so, and says what from the sketch did not ship. Release prep: package.json and the lockfile go to 2.3.0, a minor version because the changelog carries an additive format change (slot type 0x03), and the changelog's Unreleased section becomes the v2.3.0 heading. SECURITY.md's supported-versions table names only "latest release" and needs no change. Gates run on this tree: test:release-notes, test:release-gate and test:release-recipe all pass. The tag itself is the owner's to push: git tag -a v2.3.0 and git push origin v2.3.0. --- CHANGELOG.md | 2 +- docs/ROADMAP.md | 9 +++++-- docs/design/TEN-X-PLAN.md | 52 +++++++++++++++++++++++++++------------ package-lock.json | 4 +-- package.json | 2 +- 5 files changed, 47 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b51cfb8..22a0392 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## Keymaker v2.3.0 One additive format change: a new slot type, `0x03` (FORMAT-V2-DESIGN §4.8), which a container carries only when its owner chooses it. Every container diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 05250c7..0fede04 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -997,7 +997,7 @@ Not "later". Cut, with reasons. |---|---| | **Plausible-deniability container** and **duress/decoy password** | The blueprint concedes it: "deniability fails if the UI announces it." This is open-source software with a public spec — an adversary who knows Keymaker exists knows the decoy mode exists, and the presence of the feature is itself evidence. Shipping it invites users to bet their physical safety on a property the design cannot deliver. Worse than absent. | | **PAKE / croc-style transfer** | Needs a rendezvous server. The zero-server property is the product. | -| **Steganography (KEYM-in-PNG)** | The blueprint frames it honestly as "obscurity, not security" — which is the argument for not shipping it. | +| **Steganography (KEYM-in-PNG)** | The blueprint frames it honestly as "obscurity, not security" — which is the argument for not shipping it. The audio carrier that ships under *Audio* (`KAUD1`, `docs/FORMAT-AUDIO-STEGO.md`) is kept for the opposite reason: it is documented as a carrier, not steganography. The magic sits in the first sample LSBs, nothing is hidden from steganalysis, and all confidentiality is the container's, so it makes no claim this row would cut. It is a transport for a container, like paper, and stays only as long as it claims nothing more. | | **TOTP vault** | Scope creep into password-manager territory, against incumbents with sync. Dilutes focus for no differentiation. | | **OPFS vault / File System Access workspace** | Chromium-only, large surface, and it puts plaintext-adjacent state into durable storage — directly against "nothing is stored", which is the claim people choose this tool for. | | **Importers (Hat.sh, age, OpenPGP)** | Low value, ongoing maintenance, and OpenPGP is a key-model mismatch with a huge dependency tree. | @@ -1047,7 +1047,12 @@ Phase 4.2 Paper vault print kit ─ done ─ §7.1 parts · the sheet · Phase 4.3 Self-extracting page ─ done ─ §7.2 subset · the page · three readers, one file Phase 4.4 Passkey / WebAuthn PRF slot ─ done ─ §4.7 · reference · parity · UI Phase 4.5 Inheritance wizard ─ done ─ a plan over shares · paper vault · self-extract · recovery kit -Phase 8 Outreach ────── gated on 6, and on a tag existing +Review End-to-end review fixes ─ done ─ strips scan back in · SVG symbols · secret hygiene · CI signs only reproduced bytes +§4.8 Password-and-shares slot ─ done ─ spec · reference · parity · UI · three fixtures +Paper Paper vault, second pass ─ done ─ set code · presets · version-25 symbols · self-contained strips · printout check +Scanning Photo and live camera ─ done ─ every code in one photo · live camera · TEN-X Bet 1 un-held +Docs RECOVERY.md executed ─ done ─ recovery_test.py runs every bash block · v1, v2, v3 · with shares +Phase 8 Outreach ────── owner-only · drafts in docs/OUTREACH.md · after the next tag ``` **Phase 6 goes before the rest of Phase 4, and that reverses the usual order.** diff --git a/docs/design/TEN-X-PLAN.md b/docs/design/TEN-X-PLAN.md index e2920fc..b1c534f 100644 --- a/docs/design/TEN-X-PLAN.md +++ b/docs/design/TEN-X-PLAN.md @@ -10,14 +10,17 @@ rule it amends. The organising fact: Keymaker *displays* QR codes everywhere and cannot *read* one. The paper vault prints symbols the app cannot scan back. The heir — the person the product exists for — types. The bets follow from taking that person -seriously, even though the bet that fixes it directly (Heir Mode) is on hold. +seriously. The bet that fixes it directly (Heir Mode) was on hold when this +was written and has since shipped; see the end of this file. ## Decisions already made -- **Bet 1 — Heir Mode (in-app camera scanning) is ON HOLD.** It adds a camera - permission surface and, because `BarcodeDetector` is not available in every - engine, almost certainly a QR-decoding dependency. That is a supply-chain - decision the owner has not taken. Do not start it; do not add a decoder. +- **Bet 1 — Heir Mode (in-app camera scanning) was ON HOLD, and has since + shipped** (`22c7cdf`, every QR code in one photo; `161e919`, live camera + scanning). The hold was the decoder: `BarcodeDetector` is not in every + engine, so a QR-decoding dependency was a supply-chain decision the owner + had not taken. It was taken as `BarcodeDetector` where the engine has it and + `jsqr`, pinned, where it does not. - **Bet 6's motion amendment is approved** by the instruction to execute every bet except Bet 1. The house order still applies: amend `DESIGN-SYSTEM.md § Motion` *first*, in the same PR, then write the code. @@ -143,8 +146,8 @@ walks the owner through the *heir's* path. **Scope.** - "Rehearse now" on the issued-shares dialog: choose any K of the N shares - just issued, enter them as an heir would (paste; scanning is Bet 1 and on - hold), run the identical decryption via the verify-only path, and report + just issued, enter them as an heir would (paste; Bet 1 was on hold when + this was written), run the identical decryption via the verify-only path, and report "opened in N s — contents kept hidden". A wrong share fails loudly. - On success, the next print carries the rehearsal stamp filled in (date, which strips). The app stores nothing: the stamp is ink on paper and @@ -252,12 +255,29 @@ the whole plan; say so in the PR body. stored anywhere; nothing new touches the clipboard. - `connect-src 'none'`. The build fails on purpose if it changes. -## Bet 1, for when it is un-held - -Heir Mode: in-app scanning of paper parts and shares inside Decrypt, a -viewfinder that requests the camera only on tap, "2 of 3 shares scanned" -progress, arrival detection when someone lands from the printed URL, and -zero-jargon copy. The decision it needs is the decoder dependency -(`BarcodeDetector` where present; a small, licence-vetted fallback where not). -Everything else in this plan is designed so that Heir Mode drops into flows -that already speak the heir's language. +## Bet 1, shipped + +As sketched when it was on hold: in-app scanning of paper parts and shares +inside Decrypt, a viewfinder that requests the camera only on tap, "2 of 3 +shares scanned" progress, arrival detection when someone lands from the +printed URL, and zero-jargon copy. The decision it needed was the decoder +dependency (`BarcodeDetector` where present; a small, licence-vetted fallback +where not). + +What shipped, in `22c7cdf` and `161e919`. The decoder decision was taken as +`BarcodeDetector` where the engine has it and `jsqr` where it does not. On the +Decrypt tab, *Use the camera* and *Scan strips with the camera* open the +device camera from a button and read strips and container symbols held up one +after another; the dialog says what is in and what is still needed ("1 of the +2 needed. Show the next strip."), leaves out a strip from a different set, +stops by itself once there is enough, hands the codes to the same boxes a +scanned photo fills, and releases the camera when it closes. Frames never +leave the page. A photo of a whole sheet is read in one go, on the Decrypt +tab and in the printout check. Code in `src/components/camera-scan.tsx`, +covered by `tests/browser/camera-scan.spec.ts`; the progress logic is gated +by `scripts/camera-progress-test.mjs`. + +Not shipped from the sketch: arrival detection when someone lands from the +printed URL, and a separate zero-jargon Heir Mode. The scanner drops into the +existing Decrypt flow instead, which is what the rest of this plan prepared +for. diff --git a/package-lock.json b/package-lock.json index 8258f80..a5e7ca5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "keymaker", - "version": "2.2.0", + "version": "2.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "keymaker", - "version": "2.2.0", + "version": "2.3.0", "dependencies": { "@fontsource-variable/jetbrains-mono": "5.3.0", "@fontsource-variable/plus-jakarta-sans": "^5.3.0", diff --git a/package.json b/package.json index 2d6e074..b2bbad9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "keymaker", - "version": "2.2.0", + "version": "2.3.0", "private": true, "scripts": { "dev": "node scripts/build-crypto-worker.mjs && next dev -p 9002",