@microtoll/mcp 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,10 @@
3
3
  Until 1.0, the API may change in any minor release and every such change is
4
4
  listed with a migration note (DECISIONS.md D-04).
5
5
 
6
+ ## 0.1.2 — 2026-09-27
7
+
8
+ The server reports package.json's version to a host (0.1.1 said 0.0.0, from a hard-coded constant). `repository` in package.json, which npm requires for a provenance publish. Published through the release workflow with provenance.
9
+
6
10
  ## 0.1.1 — 2026-09-27
7
11
 
8
12
  Metadata only, for the MCP registry listing: `mcpName` (`io.github.microtoll/mcp`) and `repository` in package.json, and `server.json` beside it. Nothing in `src/` changed. A one-off step out of lockstep with the other packages, recorded in DECISIONS.md.
File without changes
@@ -925,7 +925,7 @@
925
925
  "title": "DECISIONS.md — founder decisions log",
926
926
  "description": "The founder's decisions: every crypto, licensing and scope choice with its reasoning.",
927
927
  "section": "Project",
928
- "markdown": "# DECISIONS.md — founder decisions log\n\n**Append-only.** A decision is recorded by adding a dated entry under\n\"Recorded\"; it is never edited afterwards, only superseded by a later entry\nthat names it. Open questions sit under \"Pending\" with a recommendation until\nthe founder decides. Claude Code never resolves a crypto, licensing or IP\nquestion silently (build plan §4).\n\nStanding rules are in `microtoll-build-plan.md` §0 (the non-negotiables) and\nare not repeated here.\n\n**2026-09-27: this log was restated so that it names no other product** (the\nfounder's direction, recorded below). Each decision keeps its number, its\ndate and its substance; the earlier wording is in the git history. Two\nentries, D-10 and D-22, were not engine decisions and were withdrawn from\nthis log; their numbers are not reused.\n\n---\n\n## Recorded\n\n**2026-09-27 — D-48 decided: `@microtoll/mcp` versions on its own; the\nother five stay in lockstep (amends D-41).** The founder's choice, on the\nquestion of how to ship the one metadata line the MCP registry requires\n(`mcpName` in the published package). Lockstep versions remain good\npractice for the five packages that share frozen byte formats\n(`crypto-core`, `identity`, `access`, `mailbox`, `blind-store`): one number\nnames a set tested together. `@microtoll/mcp` ships documentation and a\nscaffold, none of the five imports it, and it changes whenever the\ndocumentation does, so it takes its own version from here. First use:\n`@microtoll/mcp` 0.1.1, metadata only (`mcpName`\n`io.github.microtoll/mcp`, `repository`, and `server.json` beside it;\n`engine` `f9e6f00`), for the listing in the official MCP registry (launch\nitem 11). The release workflow still publishes all six on a `v*` tag,\nskipping any version already on the registry.\n\n**2026-09-27 — Microtoll Engine 0.1.0 is published: six packages on npm,\nthe signed tag, the site.** The founder published each package by hand\nfrom `D:\\PROJECTS\\engine-public` in dependency order (crypto-core,\nidentity, access, mailbox, blind-store, mcp), each with a passkey\napproval; verified from the registry by a clean install of all six into an\nempty folder, and each one loads. Shasums: crypto-core\n`3d82d75f…`, identity `0fdfb9cb…`, access `7e169792…`, mailbox\n`04b3c188…`, blind-store `a5041461…`, mcp `66593e8e…`. The signed tag\n`v0.1.0` (`d84c7d4`) is on `microtoll/engine` and GitHub verifies it. The\nrelease workflow's first run failed, as expected, on \"cannot publish over\na previously published version\"; it now skips versions already on the\nregistry (`engine` `67716d9`); provenance starts with 0.1.1, once trusted\npublishing is set on each package. `microtoll.dev` answers over plain\nhttp from GitHub Pages; the certificate for https is being issued. Email\nrouting for `security@microtoll.dev` is set up on Cloudflare with a strict\nDMARC policy (`p=reject`). Launch items done: 1 to 7 and 10; 8 waits for\nthe next version; 9 for the certificate; 11 (the MCP listing) and 12 (Show\nHN, Sponsors) remain. The public repository's CI needs `npm run docs`\nbefore any commit that touches a document the docs snapshot includes; the\nfirst run failed on a stale `packages/mcp/generated/docs.json`.\n\n**2026-09-27 — The engine is public: `microtoll/engine` from a snapshot;\n0.1.0 prepared; Pages deployed.** Launch items 5, 6 and 7 (the repository\nhalf): every package is at 0.1.0 without `\"private\"`, in lockstep (D-41),\n274 tests green with the database (`engine-record` `ebd99be`); the\nvariable `PUBLISH_GATE_OPEN` is `true` on both repositories; the founder\ncreated the public repository `microtoll/engine` from the snapshot at\n`engine-record` `2237d37`, one commit `c951b2f` that says where the dated\nhistory is kept, nothing rewritten (D-01). GitHub Pages is enabled on it\nfrom `.github/workflows/pages.yml` with the custom domain `microtoll.dev`,\nand the first deployment succeeded; the domain answers once its DNS record\npoints at `microtoll.github.io` (item 9, the founder's Cloudflare step).\nStill to do: the six packages' first publish by hand (item 7's tag, item\n8), `security@microtoll.dev` (item 10), the MCP listing (item 11). From\nthis entry, development continues in the public repository; this private\none is the dated record up to the snapshot and this entry.\n\n**2026-09-27 — `pqc-scan` v0.1.0 tagged and signed; trusted publishing set.**\nThe founder made the release signing key (`~/.ssh/microtoll-release`,\nEd25519, passphrase-protected; launch item 3), registered its public half\non GitHub as a signing key, and pushed the signed tag `v0.1.0`\n(`3e7c05c`), which GitHub verifies as valid. On npm, the package trusts\n`release.yml` for **staged** publishing only, npm's recommended setting: a\ntag stages a version with provenance and the founder approves it by hand,\nso a compromised build run can never publish alone (`pqc-scan` `5414d9d`).\nPublishing access requires two-factor authentication with no bypass\ntokens. Provenance starts with the next version; 0.1.0 was published by\nhand.\n\n**2026-09-27 — `@microtoll/pqc-scan` 0.1.0 is on the npm registry.** The\nfirst public release of anything from this project. Published by the\nfounder from their own machine (`npm publish --access public`, two-factor\nauthentication by passkey), from `pqc-scan` `5955777`; shasum\n`41000bdca199ed0b115b40e5c483201538741b68`; 22 files, 73.5 kB. Verified from\nthe registry: `npx @microtoll/pqc-scan --version` prints 0.1.0 and a scan\nwrites its two reports. The first publish attempt showed that npm removes a\n`bin` path written with a leading `./`, which would have left the package\nwith no command; fixed before publishing. The npm organisation `microtoll`\nwas already owned by the founder's account (`npm org ls`). Still to do for\nthis release: trusted publishing on npmjs.com for `release.yml`, the signing\nkey, and the signed tag `v0.1.0` (the workflow skips a version already\npublished). Provenance therefore starts with the next version.\n\n**2026-09-27 — `pqc-scan` is public.** `github.com/microtoll/pqc-scan` was\nmade public at `19eec51` (version 0.1.0, no longer private, a release\nworkflow that publishes `@microtoll/pqc-scan` with provenance on a `v*`\ntag). Its history names no other product. Waiting on the founder: the\nsigning key (launch item 3), the `@microtoll` npm organisation with trusted\npublishing for this workflow (item 4), and then the signed tag `v0.1.0`,\nwhich publishes. The engine stays private until its own launch items.\n\n**2026-09-27 — D-01 passed: the publish gate is OPEN. M6 accepted.** The\nfounder confirmed, through the question tool, that both checks of D-01 have\npassed: who owns the code the engine is built from, and what the founder's\nemployment terms require. Recorded on the founder's word; the evidence and\nany approval that was needed stay in the founder's private notes, outside\nthis repository. From this entry, a public repository, a registry and a\npublic listing are allowed for both the engine and `pqc-scan`; the launch\nchecklist (`docs/LAUNCH.md`) governs the order. The history is still never\nrewritten. **M6 is accepted** on the same day: the founder ran the scanner\non their own application from a fresh clone on a Windows PC and read the\nreport, which completes the last acceptance item (the founder's reading);\nthat run also led to the terminal default, `--test-files` and the Windows\nlauncher (`pqc-scan` `6fac933`). The founder's choice for the first public\nstep: `pqc-scan` goes public and to npm together, as `@microtoll/pqc-scan`\n0.1.0.\n\n**2026-09-27 — M6: the three public repositories reviewed by hand; the\nscanner corrected.** The founder asked for three to be proposed and run.\nChosen for three shapes, each a shallow clone read once: a JSON Web Token\nlibrary where every finding should be genuine (`panva/jose` at `55c959f`), a\nlarge browser application with almost no cryptography (`excalidraw/excalidraw`\nat `84e3f5a`), and a server application with a typical sign-in stack\n(`requarks/wiki` at `712a3a5`). Every reported finding was a genuine\ncryptographic use, and a hand search of jose's source and of Excalidraw\nfound nothing missed. Seven faults around the findings were found, each\nfixed in `pqc-scan` with a fixture and a test (its `DESIGN.md` §8.12):\ndependency versions printed as `\\1.0.6` (an invalid Markdown escape, in\nevery report with a lockfile); a package listed as a transitive dependency of\nitself (jose; the engine's own report had likewise listed\n`@microtoll/crypto-core`); `test-d` type tests not marked as test code; two\n\"could not be read\" pointers that led only to token decoding and header\nextraction; the RSA key pair that signs Wiki.js's tokens reported High under\nthe comment \"Generate certificates\" (a certificate is a signing artefact:\nMedium); Wiki.js's SAML sign-in, encrypted assertions and two-factor codes\nunreported because their libraries were not catalogued (eight packages\nadded, 57 in all); and the SHA-1 note, which told both applications to\nreplace a plain content identifier as if it protected something. 50 tests\npass. The engine's own report is unchanged apart from its dependency line\n(its own package is not a dependency). The reports, before and after, are in\nthe founder's private acceptance notes. Still open for M6: the founder's\nreading of the reports (the engine's, the second application's and these\nthree), which is the acceptance.\n\n**2026-09-27 — M6: the second application scanned; the public repository\nwill start from a snapshot.** Two founder's choices.\n- **The second real application** (DESIGN §6, second item) was chosen by the\n founder and scanned read-only; the report and the hand review are in the\n founder's private notes, because they describe that application. Every\n expectation of the item was met. The review found two scanner faults,\n fixed in `pqc-scan` `ab5332f` with a fixture and a test each: a WebAuthn\n key reported High (it only verifies signatures: Medium), and a source file\n skipped as binary because of a raw control character far down (binary now\n means a NUL in the first 8,000 bytes). The first item still passes on this\n repository. Still open for M6: three public repositories for the\n false-positive review, and the founder's reading of both reports.\n- **At the publish gate**, this repository stays private as the dated\n record, and the public repository starts from a snapshot of the tree, with\n a first commit that says where the private history is kept. Nothing is\n rewritten (D-01's evidence rule). `docs/LAUNCH.md` item 7 says so.\n\n**2026-09-27 — D-46 and D-47 decided: option (a) of each, version 3.** The\nfounder's choice, and built the same day.\n- **D-46:** new recovery codes are version 3. The check character is\n Σ aⁱ⁺¹·sᵢ over the 26 data characters in GF(32) = GF(2)[x]/(x⁵ + x² + 1),\n a = x, and the parser refuses a code whose two unused bits are not zero or\n that has more than 26 data characters. Tests prove every single wrong\n character and every swap of two different characters is refused.\n- **D-47:** an unlock method's sealed label is bound to that method:\n `frameContext(\"<ns>/aad/unlock-label/v3\", methodType, SHA-256(credentialId))`\n for a passkey, the method type alone for the recovery code. A test against\n the real server swaps two passkeys' labels in the database and both read\n as null.\n- **How version 2 stays readable** (the reading of both decisions, written\n down here so that it is not silent): a version-2 code has the same shape\n as a version-3 one, and a label carries no version byte, so neither can be\n recognised by looking at it. Trying version 2 whenever version 3 fails\n would give back exactly what version 3 closes (a mistyped code accepted as\n version 2 one time in 32; a label moved between methods opening as\n version 2). Version 2 is therefore read only when a caller names it:\n `{ version: 2 }` in crypto-core, `{ recoveryCodeVersion: 2 }` and\n `openMethodLabelV2` in identity. Nothing writes version 2. No version-2\n code or label existed outside tests.\n- Every earlier frozen fixture still opens; the version-3 bytes are frozen\n beside them (`crypto-core/test/fixtures/frozen-recovery-v3.json`,\n `identity/test/fixtures/frozen-v3.json`).\n\n**2026-09-27 — M6 built; its acceptance waits on the founder.** `pqc-scan`\nis built in its own repository (`D:\\PROJECTS\\pqc-scan`, commit `3680f12`,\nno remote): the detectors, the JSON report (schema version 1) and the\nMarkdown report, the command line and the composite GitHub Action; 47 tests\npass on Node 20 and 24. The details settled while building are that\nrepository's `DESIGN.md` §8.9–§8.11. The first acceptance item passes: on\nthis repository it finds AES-256-GCM, HKDF-SHA-256, PBKDF2 at 310,000\niterations, SHA-256, Ed25519 (Medium), the P-256 ECDH seal (High), the\nhybrid `MLKEM768-X25519` (post-quantum), the server's `node:crypto` verify\nand hash, and `deploy/nginx.sample.conf` as hybrid-enabled. The other items\nneed the founder's choices: a second real application, and three public\nrepositories for the false-positive review. Also since the entries below:\n`identity`, `access` and `mailbox` carry frozen version-2 fixtures (commit\n`e7a4d59`, 265 tests); no format changed.\n\n**2026-09-27 — D-42, D-43, D-44, D-45 decided: `pqc-scan` as designed\n(`docs/DESIGN-M6-pqc-scan.md`).** The founder directed that the build be\ncompleted as far as possible without further questions, which takes each\nrecommended option: D-42 (a) its own repository `D:\\PROJECTS\\pqc-scan`, npm\n`@microtoll/pqc-scan`, binary `pqc-scan`, Apache-2.0, Node 20 or later,\nzero runtime dependencies; D-43 (a) a tokenizer with call-site patterns;\nD-44 (a) JSON schema v1 and NCSC-shaped Markdown; D-45 (a) a composite\nGitHub Action, the JSON schema as the only seam for a hosted report, nothing\npaid built. Crypto decisions are not covered by that direction and stay\npending (D-46, D-47).\n\n**2026-09-27 — The repository is self-contained; a private GitHub remote is\nallowed.** The founder's direction: nothing in the engine's code, tests or\ndocumentation names another product, its files, documents or versions.\nNotes that need them are kept outside the repository. The git history is\nnot rewritten (it is part of the ownership record, D-01). The repository is\npushed to a **private** GitHub repository; the publish gate (D-01) still\ncloses every public repository, registry and listing. The frozen fixtures\nare now written by the engine itself under the test namespace `example`\n(`packages/crypto-core/test/fixtures/frozen-v1.json`); no format changed.\n\n**2026-09-25 — M5 approved; M6 starts.** The distribution kit accepted at\ncommit `216574f`: `@microtoll/mailbox` (M3b) and `examples/invite-app`, the\ndocs site source with `llms.txt`, `@microtoll/mcp`, `SECURITY.md`,\n`CONTRIBUTING.md`, the inert release workflow and `docs/LAUNCH.md`; 242\ntests green. Nothing pushed, published or listed (D-01). M6 (`pqc-scan`, a\nseparate repository) begins with a written design for decision.\n\n**2026-09-25 — D-38, D-39, D-40, D-41 decided: the distribution kit as\ndesigned (`docs/DESIGN-M5.md`).** D-38: the docs site from Markdown in\n`docs/` with a zero-dependency renderer, `llms.txt` from the same build,\nGitHub Pages for microtoll.dev once public, no analytics. D-39:\n`@microtoll/mcp` with the stdio JSON-RPC subset written in (zero\ndependencies), docs search, read-doc and an empty-directory scaffold that\nruns nothing and fetches nothing. D-40: M3b (`@microtoll/mailbox`: the\npairwise labels, bundles version 2 with the recipient and mailbox bound into\nthe signature, withdrawal as the server does it) built inside M5, and\n`examples/invite-app` as the direct-invite demo. D-41: lockstep versions\nfrom `0.1.0`; a release workflow on a signed tag with npm provenance, inert\nuntil the founder sets `PUBLISH_GATE_OPEN` after D-01; signed tags;\n`SECURITY.md` with coordinated disclosure to `security@microtoll.dev` and no\nbounty; `CONTRIBUTING.md` with DCO. D-06, D-07, D-08, D-09, D-11, D-12 and\nD-18 are closed as adopted in M0–M2.\n\n**2026-09-25 — M4 approved; M5 starts.** `@microtoll/blind-store` accepted\nat commit `1efc156`: the library, schema, reference server, deployment kit\nand `examples/notes-app`; 221 tests green including the database-backed\nsuites; the example built and ran end to end through Docker Compose on the\nfounder's machine. Carried forward: M3b (the mailbox client, on the server\nhalf M4 ships); the founder's own browser run of the example; D-01 still\ngates publishing. M5 (the distribution kit) begins.\n\n**2026-09-25 — D-37 decided: the pointer's binding is the account, not the\nobject id (amends D-31).** The server returns an account's pointers without\nan id, by design, so the M3 binding could never be opened on a fresh device\n(found by the notes example). The pointer's additional authenticated data is\nnow `frameContext(\"<ns>/aad/pointer/v2\", routingPublicKey)`; the object id is\nread from inside the sealed pointer. Still AES-GCM with additional data; no\nprimitive, label or KDF change; no pointer had been written outside tests.\n`packages/access/FORMATS.md` §3.2 and THREATMODEL §5 updated.\n\n**2026-09-25 — D-33, D-34, D-35, D-36 decided: the server as designed\n(`packages/blind-store/DESIGN.md`).**\nD-33: an events-and-members model becomes `objects` and `object_members`\nwith a `collection` column, a fixed-length selector and an optional date\nwindow; the query answers everything matching with no identity filter,\nrefuses past `maxQueryRows` (5,000), and carries a host's extra fields; live\nwatches route by the same selector, the imminent watch by a per-collection\nserver-owned window; the message names are fixed (D-27), the selector fields\ngeneric, and `adminCapabilityHash` leaves the query and fetch replies (it\nwould give everyone who receives cover traffic a stable per-object token);\nthe mailbox's server half is included. This settles the server half of D-13\nand confirms D-14 (what stays out).\nD-34: the handshake's server half verifies `\"<ns>/auth/v2\" ‖ 0x00 ‖\nSHA-256(origin) ‖ nonce` with `node:crypto` against the connection's\n`Origin`, or each allowed origin when none was sent; the server imports no\n`@microtoll` package and a cross-implementation test proves the framing\nagainst identity's `authMessage`; the transport limits plus the query-rows\nbackstop; the three daily counters fail open. D-20's lookup limit (3 per\nsocket) is in it.\nD-35: the M0 schema rules run as a test against a live database;\n`blind_store_sweep()` runs hourly from the library and never sweeps objects\n(resolves D-19); the schema creates the least-privilege role\n`blind_store_app` that the server and the tests connect as, so a server\ncompromise reaches no more than the server can already read. D-17 is\nresolved by the registration hook.\nD-36: `createBlindStore` (with `createCore` as an alias); the thin\n`bin/blind-store.mjs`; dependencies exactly `ws` and `pg`, pinned, with the\nLISTEN client written in and `pg-listen` dropped; `deploy/` with the\nhardened Compose file and the Nginx sample; `examples/notes-app` on the\nunchanged reference server with a random-shelf selector and import-map\nloading; Postgres for tests from a throwaway container locally and a\nservice container in CI.\n\n**2026-09-25 — M3 approved; M4 starts.** `@microtoll/access` accepted at\ncommit `0ba99bf`: formats v2 (D-31), the adversarial suite green with every\ncase mapped in THREATMODEL §5, 160 tests across the three packages. M4 begins\nwith a written design of the collection and coarse-selector abstraction and\nthe server-side hardening for decision, then the server core as a library\nwith a thin reference server (D-23), and `examples/notes-app`.\n\n**2026-09-25 — D-30, D-31, D-32 decided: the access layer as designed.**\nD-30: an events-and-members model is generalised by renaming and widening\n(event → object, `K_event` → `K_object`, participation row → member row,\norganiser → owner); the content split and the grant rule are supplied by the\napp; the pointer has an extension table; wire message names are fixed\n(D-27). D-31: formats version 2 as in `packages/access/FORMATS.md` §3 —\npurpose labels on the member-row and share-link signatures (and, in M3b, the\nrecipient inside the invite signature), additional authenticated data on\ncontent, second tier, member rows, pointers and link payloads; the\nper-member sealed `K_object` stays an ECIES seal without extra data, stated\nin the threat model. D-32: M3 ships objects, members, pointers, admin seats,\nrotation, two-tier disclosure, share links, the display rule, the wire\nbuilders and the adversarial suite; direct invites wait for M3b.\n\n**2026-09-25 — M2 approved; M3 starts.** `@microtoll/identity` accepted at\ncommit `cf78168`: formats v2 (D-28, D-29), 53 tests including every gap the\naudit listed, the acceptance demo page, THREATMODEL §4 complete. Carried\nforward: the server half of the bound handshake (M4); the founder's own run\nof the demo on a real authenticator. M3 begins with the written design of the\naccess-layer hardening and the object model (D-13) for decision.\n\n**2026-09-25 — D-28 decided: additional authenticated data on all four identity-layer seals.**\nAs designed in `packages/identity/FORMATS.md` §2.1–2.4: the wrapped root key\nis bound to its method type and identifier (SHA-256 of the credential id, or\nthe recovery lookup hash); the identity blob and the unlock-method label are\nbound to the routing public key; the trusted-device session record (version 2)\nis bound to the routing public key, its expiry and its session generation, and\na restored record whose stored routing key differs from the derived one is\nrefused. The blob gains a cooperative `revision` counter so a rolled-back blob\nis refused on a device that saw a later one. Contexts are\n`frameContext(\"<ns>/aad/<purpose>/v2\", …)`; no primitive, mode or KDF changes.\n\n**2026-09-25 — D-29 decided: the handshake signature covers purpose, origin and nonce.**\nThe routing key signs `frameContext(\"<ns>/auth/v2\", SHA-256(UTF-8(origin)), nonce)`.\nThe client half ships in M2; the server half in M4 (`blind-store`), verifying\nagainst its allowed origins, with a cross-implementation test.\n\n**2026-09-25 — M1 approved; M2 starts.** `@microtoll/crypto-core` accepted at\ncommit `095824c`: vectors green through the public API, fixtures proved in\nboth directions, zero runtime dependencies, README quickstart and threat\nmodel complete. Carried forward: the browser cross-check of the hybrid seal\n(release gate, D-07) and the non-extractable signing key (identity, D-24). M2\nbegins with a written design of the identity-layer format hardening for the\nfounder's decision (D-25).\n\n**2026-09-25 — D-26 decided: recovery-code check character, version 2.**\nThe check character is the Crockford digit of the low five bits of the first\nbyte of SHA-256(secret bytes). Entropy (128 bits), length (27 characters) and\ngrouping are unchanged from version 1; only the check rule changes, so a\nrandom transcription error of any kind is caught with probability 31/32.\nThe engine writes version 2 only; no version-1 data exists to read.\n`formatRecoveryCode` and `parseRecoveryCode` become asynchronous (SHA-256 is\nasynchronous in Web Crypto). Implemented in crypto-core `src/recovery.js`.\n(Revisited by pending D-46.)\n\n**2026-09-25 — D-27 decided: crypto-core keeps its v0 function names.**\n`sealToRecipient`, `openWithPrivateKey`, `pqSealAvailable` and the rest keep\ntheir names for v0, as do the wire message names. Any renames for outside\nusers come with aliases.\n\n**2026-09-25 — D-24 decided (amends D-21): format hardening happens in the\nengine, package by package.** The signature purpose labels, recipient\nbinding, additional authenticated data, non-extractable keys, recovery\nchecksum and handshake binding are designed and built as each package is\nbuilt (M1 to M4). Each change is written up before code and recorded here;\nthe engine freezes its own fixtures. The crypto-core formats (AEAD v1, ECIES\nv3 and v2) are unchanged by the pass; the recovery-code checksum is the one\nM1 format decision.\n\n**2026-09-25 — D-25 decided: identity and access take their screens as\ncallbacks.** `@microtoll/identity` and `@microtoll/access` hold no DOM and no\npage state; the app supplies its UI through callbacks and hooks, as D-11\nrecommends.\n\n**2026-09-25 — M0 signed off.** The founder accepted the audit,\n`THREATMODEL.md` and the decisions list. M1 groundwork starts: the\nrepository, the monorepo layout, licences, CI, the standard RFC and NIST\nvectors, and the crypto-core API design. D-01 still gates publishing.\n\n**2026-09-25 — D-04 decided: the v0.x stability promise, as drafted.**\nTwo promises, stated separately. Formats are frozen from the first publish: no\nversion byte, label, KDF parameter or signed-byte layout ever changes meaning,\nand readers for every published format stay supported. The API may change in\nany 0.x minor release, always listed in the package `CHANGELOG.md` with a\nmigration note; patch releases never break. Public wording: *\"Microtoll Engine\nis pre-1.0. Function names and options may change between minor versions; the\nbytes it writes never will. Anything you encrypt with any published version\nwill decrypt with every later one.\"*\n\n**2026-09-25 — D-23 decided: `blind-store` is a library first, with a thin reference server.**\n`blind-store` exports its core handlers, dispatcher and schema. The Docker\nreference server is a thin wrapper around them. A host application mounts\nthe library and registers its own handlers beside it, so there is one server\ncore for every consumer. Item and membership handlers stay the host's until\nthe generic collection model (D-13) is settled (it was, by D-30 and D-33).\n**Licence consequence:** a host server that embeds AGPL-3.0 `blind-store`\nmust be distributed under AGPL-compatible terms.\n\n**2026-09-24 — D-21 decided: fix format-level weaknesses before any format is frozen.**\nNothing is published, so no format is frozen yet. The format-level\nweaknesses are fixed in one deliberate pass before the first publish:\n- purpose (domain-separation) labels on signatures;\n- recipient and mailbox binding in invitation and acknowledgement\n signatures;\n- additional authenticated data (AAD) on object-layer and identity-layer\n seals;\n- non-extractable Ed25519 private keys (this absorbs D-15);\n- a stronger recovery-code checksum;\n- binding for the handshake signature.\n\nThis adds binding to existing constructions; no primitive, mode or KDF is\nadded or substituted. Each change and its reason is recorded here. (Where the\npass happens: D-24.)\n\n**2026-09-24 — D-05 decided: label namespace profile.**\nThe constructions are fixed and only the label prefix varies:\n`createProfile({ namespace })`. A namespace is required, with no silent\ndefault. Retired labels stay reserved in every namespace.\n\n**2026-09-24 — D-02 decided: licensing as recommended.**\n- Apache-2.0: `crypto-core`, `identity`, `access`, `mailbox` and the examples.\n- AGPL-3.0-only: `blind-store`.\n- CC-BY-4.0: the docs.\n- Outside contributions: DCO sign-off.\n\nThis decision does not open the publish gate; D-01 still governs that.\n\n**2026-09-24 — D-03 decided: the product name is \"Microtoll Engine\".**\nPackages are named by function under the `@microtoll` scope.\n\n**State of the publish gate (non-negotiable 8): OPEN since 2026-09-27.** The\nentry of that date records that both checks of D-01 passed. Publishing\nfollows `docs/LAUNCH.md`, in order.\n\n---\n\n## Pending\n\nEach entry gives the question, the options, a recommendation, and the\nmilestone it blocks.\n\n### Blocks the first publish\n\n(Nothing: D-01 passed on 2026-09-27, see Recorded. The entry is kept below\nfor the reasoning.)\n\n**D-01 — IP and employment clearance (the publish gate).** *(Passed 2026-09-27 — see Recorded.)*\nTwo checks must both pass before anything is published: who owns the code\nthe engine is built from, and what the founder's employment terms require.\nThe details, the evidence being kept and the options are in the founder's\nprivate notes, outside this repository.\n\nRules that follow from it here:\n- Never rewrite git history (rebase, amend, force-push or date changes) on\n this repository: the dated history is part of the evidence.\n- Local and private work may continue meanwhile.\n\n**Record here when done:** both checks passed, the date, who confirmed, and\nany approval that was needed (with its date).\n\n### Closed questions (kept for the reasoning)\n\n**D-46 — Recovery-code check character, version 3: a weighted check over GF(32).** *(Decided 2026-09-27, option (a) — see Recorded.)*\nVersion 2 (D-26) takes the check character from SHA-256 of the secret. A\nrandom error is caught with probability 31/32, but no class of error is\ncaught for certain: one mistyped character, or two swapped characters, slips\nthrough one time in 32 and is then refused by lookup as \"no such account\", a\nconfusing failure (never a wrong account).\nOptions, all keeping 128 bits of entropy, 27 characters and the grouping:\n- (a) **Version 3:** the check is Σ aⁱ⁺¹·sᵢ over the 26 data characters in\n GF(32), with a = x under x⁵ + x² + 1 (the arithmetic bech32 uses). The 26\n weights are distinct and non-zero, so **every** single wrong character and\n **every** swap of two characters, adjacent or not, is caught; random\n errors are still caught 31/32. Synchronous again (no hash). The parser\n also refuses a code whose two unused final bits are not zero (26\n characters carry 130 bits for 128), so one string names one secret; today\n four strings parse to the same bytes. **Recommended.**\n- (b) Keep version 2.\n- (c) Crockford's mod-37 check symbol (catches single errors and adjacent\n swaps; the check position may show `*~$=U`).\nError detection, not cryptography: the code's 128 random bits protect the\naccount either way. No version-2 code exists outside tests.\n**Needed before:** the first publish.\n\n**D-47 — Bind an unlock method's name to the method, not only the account.** *(Decided 2026-09-27, option (a) — see Recorded.)*\nVersion 2 (D-28) binds the sealed name of an unlock method (\"Alice's phone\")\nto the account's routing key. That stops nothing the account's own key does\nnot already stop (another account's name would not open), and it does **not**\nstop the server showing one passkey's name against another of the same\naccount's passkeys — the case that misleads a person removing a method.\nOptions:\n- (a) **Version 3 of the label context:** `frameContext(\"<ns>/aad/unlock-label/v3\",\n methodType, SHA-256(credentialId))` for a passkey, the method type alone\n for the recovery code, matching the wrapped root key's binding (D-28).\n **Recommended.**\n- (b) Keep version 2.\n- (c) Bind both (routing key and method): no gain over (a), since the key is\n per account.\n**Needed before:** the first publish.\n\n**D-02 — Licensing.** *(Decided 2026-09-24 — see Recorded.)*\nApache-2.0 client packages can be used inside an AGPL application; AGPL on\n`blind-store` means anyone running a modified server as a service must\npublish their changes.\n**Recommendation:** Apache-2.0 for `crypto-core`, `identity`, `access` and\n`mailbox`; AGPL-3.0-only for `blind-store`; Apache-2.0 for the examples and\nCC-BY-4.0 for the docs; a DCO sign-off (not a CLA) for outside contributions.\n\n**D-03 — Engine product name under the Microtoll brand.** *(Decided 2026-09-24 — see Recorded.)*\n- (a) \"Microtoll Engine\", descriptive, with packages named by function\n (`@microtoll/identity`, …).\n- (b) A distinct product name, which needs a trade-mark search.\n\n**Recommendation:** (a) for v0. It can be revisited at the eight-week review.\n\n**D-04 — API stability promise for v0.x.** *(Decided 2026-09-25 — see Recorded.)*\n\n**D-05 — KDF label namespace.** *(Decided 2026-09-24 — see Recorded.)*\nA public library hard-wired to one product's label prefix is confusing, and\nchanging a label counts as substituting one (non-negotiable 1).\n- (a) One fixed label set for everyone.\n- (b) Labels become a *profile*: the construction is fixed, and only the\n namespace prefix varies; apps pass their own namespace, for example\n `\"myapp\"` → `\"myapp/routing/v1\"`.\n- (c) A new fixed `microtoll/...` label set.\n\n**Recommendation:** (b). Nothing in any construction changes. A namespace is\nrequired (no silent default), so two apps never share a derivation by\naccident. Retired labels stay reserved in every namespace.\n\n**D-06 — Correct the build plan's primitive list.** *(Adopted in M1 (2026-09-25): P-256, RFC 5903 §8.1, RFC 7914 §11, X-Wing vectors — closed by the M5 design.)*\nThe plan listed \"Ed25519/X25519 seed derivation\" and RFC 7748 and RFC 6070\nvectors. In fact the sealing curve is **P-256** (X25519 was retired because\nSafari lacks it) and PBKDF2 is **SHA-256** (RFC 6070 is SHA-1 only).\n**Recommendation:** P-256; RFC 5903 §8.1 plus a known-answer test of the v3\nseal key derivation instead of RFC 7748; RFC 7914 §11 (PBKDF2-HMAC-SHA-256)\ninstead of RFC 6070; X25519 only inside the X-Wing hybrid, tested through the\nX-Wing vectors. Adding test vectors is not new cryptography.\n\n**D-07 — Post-quantum hybrid in crypto-core.** *(Adopted in M1: hybrid off by default; the Chrome cross-check stays a release gate — closed by the M5 design.)*\nThe X-Wing seal (ECIES v2, `MLKEM768-X25519`) needs native browser support\n(Chrome 154 has it), Node has no native X-Wing, and no cross-check on a real\nbrowser has been done.\n**Recommendation:** off by default, behind an explicit opt-in; documented as\n\"hybrid mode (experimental; requires a browser with native\nMLKEM768-X25519)\"; tested through a test-only shim, never in a published\nruntime path; the Chrome seal/open cross-check a release gate before the\nopt-in is documented as usable.\n\n**D-08 — Source language.** *(Adopted in M1: JavaScript with hand-written declarations, no bundler — closed by the M5 design.)*\nJavaScript source with JSDoc; hand-written `.d.ts` declarations checked by\n`tsc --noEmit` in CI (TypeScript as a development dependency only); no\nbundler; zero runtime dependencies in the client packages.\n\n**D-09 — Supported runtimes.** *(Adopted in M1: Node ≥ 24; current Chrome, Firefox, Safari, Edge; the post-quantum path as stated — closed by the M5 design.)*\nNode ≥ 24; current Chrome, Firefox, Safari and Edge for the classical path;\nthe post-quantum path needs Node ≥ 24.7 on OpenSSL ≥ 3.5, or Chrome ≥ 154.\n\n**D-10 — Withdrawn from this log** (2026-09-27): not an engine decision.\n\n**D-11 — Identity package boundary.** *(Adopted in M2 as built (createIdentitySession with the UI as callbacks) — closed by the M5 design.)*\nThe package ends at \"authenticated connection, `auth-ok` fields, identity\nblob opened, sealing key adopted\". All UI (asking for a code, showing a code,\nconfirming a deletion) comes in through injected callbacks. A deterministic\navatar and handle stay out of v0; the passkey's user name is a\ncaller-supplied string.\n\n**D-12 — Where the sealing key lives.** *(Adopted in M2 as built (operations in crypto-core, storage and adoption in identity) — closed by the M5 design.)*\n\n**D-13 — Generic collection model for access and blind-store.** *(Decided 2026-09-25 by D-30 and D-33 — see Recorded.)*\nGeneralise an events-and-members model, taking the stronger mechanics of a\nseat-based model: an `expectedEpoch` refusal and a rotation completeness\ncheck; role-scoped capability replacement on rotation; hash-length and\nbyte-cap `CHECK`s; `timingSafeEqual` comparisons; then the coarse selector\nand the cover-traffic query.\n\n**D-14 — What stays out of v0.** *(Confirmed 2026-09-25 by D-33 — see Recorded.)*\nReporting and moderation; a public layer; operator disclosure keys; repeat\ngrants; live signals; Web Push. Guest (ephemeral) identities and live\nwatches over the selector stay in. Push can follow as an optional package\nonce its documented join is written into THREATMODEL.md.\n\n**D-15 — Non-extractable Ed25519 private keys.** *(Absorbed into D-21, 2026-09-24; built in identity, M2.)*\nDerive the public key once with an extractable import, then re-import the\nprivate key non-extractable: byte-identical outputs.\n\n**D-16 — Milestone numbering.** *(Adopted: the mailbox is M3b.)*\n`pqc-scan` stays M6; `mailbox` is M3b, built inside M5 (D-40).\n\n**D-17 — Terms and 18+ columns.** *(Decided 2026-09-25 by D-35 — see Recorded.)*\nThe core schema carries no policy columns; a registration hook lets an app\nenforce its own policy and store its own flag.\n\n**D-18 — Repository location and version control.** *(Adopted at M0: `D:\\PROJECTS\\microtoll`; a private GitHub remote from 2026-09-27; public only when the gate opens.)*\n`pqc-scan` has its own repository (D-42).\n\n**D-19 — Retention sweeps.** *(Decided 2026-09-25 by D-35 — see Recorded.)*\n`blind-store` sweeps expired and fully used link tokens, consumed and\nexpired mailbox rows, and rate counters older than two days — all of which\nwould otherwise keep ciphertext that carries keys, or activity records,\nindefinitely. Its effect on what a database copy reveals is in\nTHREATMODEL.md.\n\n**D-20 — Keep the passkey-as-PRF-only model and the open unlock lookup.** *(Decided 2026-09-25 by D-34 — see Recorded.)*\nThe server never verifies a WebAuthn assertion; the PRF output is the\nsecret; the unauthenticated lookup returns only wrapped material; the lookup\nis rate-limited in `blind-store`.\n\n**D-22 — Withdrawn from this log** (2026-09-27): not an engine decision.\n\n**D-26 — The recovery-code check character.** *(Decided 2026-09-25 — see Recorded; revisited by D-46.)*\nVersion 1 was the sum of the 16 bytes mod 32, which misses a mistyped\ncharacter whose error falls only in a byte's top three bits, and misses\nswapped neighbours.\n- (a) Keep version 1.\n- (b) Crockford's mod-37 check symbol.\n- (c) One character from SHA-256 of the 16 bytes (chosen).\n\n**D-30 — The object model (resolves D-13).** *(Decided 2026-09-25 — see Recorded.)*\n- (a) As designed. **Recommended.**\n- (b) Keep an event vocabulary in the package API (no renames).\n- (c) A wider redesign around a seat model for members.\n\n**D-31 — Access-layer hardening, version 2 (FORMATS.md §3).** *(Decided 2026-09-25 — see Recorded.)*\n- (a) All of it. **Recommended.** Every context is known before the open, no\n schema change, no new primitive.\n- (b) Signature labels only.\n- (c) Keep version 1.\n\n**D-32 — What M3 ships (FORMATS.md §4).** *(Decided 2026-09-25 — see Recorded.)*\n\n**D-33 — The collection model on the server (DESIGN.md §2).** *(Decided 2026-09-25 — see Recorded.)*\n- (a) As in DESIGN.md §2. **Recommended.**\n- (b) Keep domain-specific field names (`geoBucket`, `dateStart`,\n `dateEnd`) and the admin hash on the wire.\n- (c) One table per configured collection instead of a `collection` column.\n\n**D-34 — The bound handshake, server half, and the transport limits (DESIGN.md §3).** *(Decided 2026-09-25 — see Recorded.)*\n- (a) As in DESIGN.md §3. **Recommended.**\n- (b) Verify against the `Origin` header only, refusing a connection that\n sends none (breaks non-browser clients and every test client).\n- (c) Import `@microtoll/crypto-core` on the server for the framing (one\n implementation, but the server package then contains code that can\n decrypt).\n\n**D-35 — Schema rules as tests, the sweep, the database role (DESIGN.md §4).** *(Decided 2026-09-25 — see Recorded.)*\n- (a) As in DESIGN.md §4. **Recommended.**\n- (b) Also sweep objects a configurable time after their window ends.\n\n**D-36 — Library, reference server, deployment kit, example (DESIGN.md §5).** *(Decided 2026-09-25 — see Recorded.)*\n- (a) As in DESIGN.md §5. **Recommended.**\n- (b) Keep `pg-listen` as a third dependency.\n- (c) Make the example a calendar rather than notes.\n\n**D-37 — Correct the pointer's binding (amends D-31).** *(Decided 2026-09-25 — see Recorded.)*\n- (a) Bind the routing public key; the id inside. **Recommended; done.**\n- (b) Keep the object-id binding and add a plaintext object-id column to the\n pointer table (breaks the M0 rule: the server would hold every account's\n object list).\n- (c) No binding beyond the label.\n\n**D-38, D-39, D-40, D-41** *(Decided 2026-09-25 — see Recorded; the options\nare in `docs/DESIGN-M5.md`.)*\n\n**D-42, D-43, D-44, D-45** *(Decided 2026-09-27 — see Recorded; the options\nare in `docs/DESIGN-M6-pqc-scan.md`.)*\n\n**D-28 — Additional authenticated data on the four identity-layer seals (FORMATS.md §2.1–2.4).** *(Decided 2026-09-25 — see Recorded; the label binding revisited by D-47.)*\n- (a) All four, as designed. **Recommended.**\n- (b) Only the session record and the wrapped root key.\n- (c) None.\n\n**D-29 — The handshake signature is bound to its purpose and origin (FORMATS.md §2.5).** *(Decided 2026-09-25 — see Recorded.)*\n- (a) Label and origin, as designed. **Recommended.**\n- (b) Label only.\n- (c) Keep the bare nonce.\n",
928
+ "markdown": "# DECISIONS.md — founder decisions log\n\n**Append-only.** A decision is recorded by adding a dated entry under\n\"Recorded\"; it is never edited afterwards, only superseded by a later entry\nthat names it. Open questions sit under \"Pending\" with a recommendation until\nthe founder decides. Claude Code never resolves a crypto, licensing or IP\nquestion silently (build plan §4).\n\nStanding rules are in `microtoll-build-plan.md` §0 (the non-negotiables) and\nare not repeated here.\n\n**2026-09-27: this log was restated so that it names no other product** (the\nfounder's direction, recorded below). Each decision keeps its number, its\ndate and its substance; the earlier wording is in the git history. Two\nentries, D-10 and D-22, were not engine decisions and were withdrawn from\nthis log; their numbers are not reused.\n\n---\n\n## Recorded\n\n**2026-09-27 — The tag `v0.1.1` was moved once, on the founder's\ninstruction (an exception to the no-rewrite rule, recorded).** The first\nrelease through the workflow (0.1.1 for the five lockstep packages, mcp\n0.1.2) failed before anything was staged: npm's provenance check requires\n`repository.url` in each package.json to name the repository the build\nran in, and the five original packages had no `repository` field. The\nfounder chose to delete the tag and re-create it on the fixed commit\n(`engine` `713da65` and the correction after it) rather than issue 0.1.2\nfor a metadata field. No commit was rewritten; nothing had been published\nunder the tag; the private record is untouched. The rule stands for\neverything else.\n\n**2026-09-27 — `io.github.microtoll/mcp` is listed in the official MCP\nregistry (launch item 11).** `@microtoll/mcp` 0.1.1 published by hand; the\nlisting made with `mcp-publisher` 1.8.1 from `packages/mcp/server.json`;\nverified from the registry (status active, package `@microtoll/mcp@0.1.1`,\nstdio) and by running the published package as a host would: it answers\n`initialize` and lists its three tools. What it took, for the record: the\nregistry grants an organisation namespace only to an organisation Owner,\nand only when the sign-in token can read organisation membership. The\ndevice-flow sign-in cannot, so the founder made a classic personal access\ntoken with the single scope `read:org` (seven-day expiry, to be deleted)\nand signed in with `login github --token`. The organisation membership was\nalso made public and the profile un-hidden along the way; neither turned\nout to be the cause. Found meanwhile: the published server reports its\nversion as 0.0.0 (a hard-coded constant); fixed in the repository to read\n`package.json`, to go out with the next version.\n\n**2026-09-27 — D-48 decided: `@microtoll/mcp` versions on its own; the\nother five stay in lockstep (amends D-41).** The founder's choice, on the\nquestion of how to ship the one metadata line the MCP registry requires\n(`mcpName` in the published package). Lockstep versions remain good\npractice for the five packages that share frozen byte formats\n(`crypto-core`, `identity`, `access`, `mailbox`, `blind-store`): one number\nnames a set tested together. `@microtoll/mcp` ships documentation and a\nscaffold, none of the five imports it, and it changes whenever the\ndocumentation does, so it takes its own version from here. First use:\n`@microtoll/mcp` 0.1.1, metadata only (`mcpName`\n`io.github.microtoll/mcp`, `repository`, and `server.json` beside it;\n`engine` `f9e6f00`), for the listing in the official MCP registry (launch\nitem 11). The release workflow still publishes all six on a `v*` tag,\nskipping any version already on the registry.\n\n**2026-09-27 — Microtoll Engine 0.1.0 is published: six packages on npm,\nthe signed tag, the site.** The founder published each package by hand\nfrom `D:\\PROJECTS\\engine-public` in dependency order (crypto-core,\nidentity, access, mailbox, blind-store, mcp), each with a passkey\napproval; verified from the registry by a clean install of all six into an\nempty folder, and each one loads. Shasums: crypto-core\n`3d82d75f…`, identity `0fdfb9cb…`, access `7e169792…`, mailbox\n`04b3c188…`, blind-store `a5041461…`, mcp `66593e8e…`. The signed tag\n`v0.1.0` (`d84c7d4`) is on `microtoll/engine` and GitHub verifies it. The\nrelease workflow's first run failed, as expected, on \"cannot publish over\na previously published version\"; it now skips versions already on the\nregistry (`engine` `67716d9`); provenance starts with 0.1.1, once trusted\npublishing is set on each package. `microtoll.dev` answers over plain\nhttp from GitHub Pages; the certificate for https is being issued. Email\nrouting for `security@microtoll.dev` is set up on Cloudflare with a strict\nDMARC policy (`p=reject`). Launch items done: 1 to 7 and 10; 8 waits for\nthe next version; 9 for the certificate; 11 (the MCP listing) and 12 (Show\nHN, Sponsors) remain. The public repository's CI needs `npm run docs`\nbefore any commit that touches a document the docs snapshot includes; the\nfirst run failed on a stale `packages/mcp/generated/docs.json`.\n\n**2026-09-27 — The engine is public: `microtoll/engine` from a snapshot;\n0.1.0 prepared; Pages deployed.** Launch items 5, 6 and 7 (the repository\nhalf): every package is at 0.1.0 without `\"private\"`, in lockstep (D-41),\n274 tests green with the database (`engine-record` `ebd99be`); the\nvariable `PUBLISH_GATE_OPEN` is `true` on both repositories; the founder\ncreated the public repository `microtoll/engine` from the snapshot at\n`engine-record` `2237d37`, one commit `c951b2f` that says where the dated\nhistory is kept, nothing rewritten (D-01). GitHub Pages is enabled on it\nfrom `.github/workflows/pages.yml` with the custom domain `microtoll.dev`,\nand the first deployment succeeded; the domain answers once its DNS record\npoints at `microtoll.github.io` (item 9, the founder's Cloudflare step).\nStill to do: the six packages' first publish by hand (item 7's tag, item\n8), `security@microtoll.dev` (item 10), the MCP listing (item 11). From\nthis entry, development continues in the public repository; this private\none is the dated record up to the snapshot and this entry.\n\n**2026-09-27 — `pqc-scan` v0.1.0 tagged and signed; trusted publishing set.**\nThe founder made the release signing key (`~/.ssh/microtoll-release`,\nEd25519, passphrase-protected; launch item 3), registered its public half\non GitHub as a signing key, and pushed the signed tag `v0.1.0`\n(`3e7c05c`), which GitHub verifies as valid. On npm, the package trusts\n`release.yml` for **staged** publishing only, npm's recommended setting: a\ntag stages a version with provenance and the founder approves it by hand,\nso a compromised build run can never publish alone (`pqc-scan` `5414d9d`).\nPublishing access requires two-factor authentication with no bypass\ntokens. Provenance starts with the next version; 0.1.0 was published by\nhand.\n\n**2026-09-27 — `@microtoll/pqc-scan` 0.1.0 is on the npm registry.** The\nfirst public release of anything from this project. Published by the\nfounder from their own machine (`npm publish --access public`, two-factor\nauthentication by passkey), from `pqc-scan` `5955777`; shasum\n`41000bdca199ed0b115b40e5c483201538741b68`; 22 files, 73.5 kB. Verified from\nthe registry: `npx @microtoll/pqc-scan --version` prints 0.1.0 and a scan\nwrites its two reports. The first publish attempt showed that npm removes a\n`bin` path written with a leading `./`, which would have left the package\nwith no command; fixed before publishing. The npm organisation `microtoll`\nwas already owned by the founder's account (`npm org ls`). Still to do for\nthis release: trusted publishing on npmjs.com for `release.yml`, the signing\nkey, and the signed tag `v0.1.0` (the workflow skips a version already\npublished). Provenance therefore starts with the next version.\n\n**2026-09-27 — `pqc-scan` is public.** `github.com/microtoll/pqc-scan` was\nmade public at `19eec51` (version 0.1.0, no longer private, a release\nworkflow that publishes `@microtoll/pqc-scan` with provenance on a `v*`\ntag). Its history names no other product. Waiting on the founder: the\nsigning key (launch item 3), the `@microtoll` npm organisation with trusted\npublishing for this workflow (item 4), and then the signed tag `v0.1.0`,\nwhich publishes. The engine stays private until its own launch items.\n\n**2026-09-27 — D-01 passed: the publish gate is OPEN. M6 accepted.** The\nfounder confirmed, through the question tool, that both checks of D-01 have\npassed: who owns the code the engine is built from, and what the founder's\nemployment terms require. Recorded on the founder's word; the evidence and\nany approval that was needed stay in the founder's private notes, outside\nthis repository. From this entry, a public repository, a registry and a\npublic listing are allowed for both the engine and `pqc-scan`; the launch\nchecklist (`docs/LAUNCH.md`) governs the order. The history is still never\nrewritten. **M6 is accepted** on the same day: the founder ran the scanner\non their own application from a fresh clone on a Windows PC and read the\nreport, which completes the last acceptance item (the founder's reading);\nthat run also led to the terminal default, `--test-files` and the Windows\nlauncher (`pqc-scan` `6fac933`). The founder's choice for the first public\nstep: `pqc-scan` goes public and to npm together, as `@microtoll/pqc-scan`\n0.1.0.\n\n**2026-09-27 — M6: the three public repositories reviewed by hand; the\nscanner corrected.** The founder asked for three to be proposed and run.\nChosen for three shapes, each a shallow clone read once: a JSON Web Token\nlibrary where every finding should be genuine (`panva/jose` at `55c959f`), a\nlarge browser application with almost no cryptography (`excalidraw/excalidraw`\nat `84e3f5a`), and a server application with a typical sign-in stack\n(`requarks/wiki` at `712a3a5`). Every reported finding was a genuine\ncryptographic use, and a hand search of jose's source and of Excalidraw\nfound nothing missed. Seven faults around the findings were found, each\nfixed in `pqc-scan` with a fixture and a test (its `DESIGN.md` §8.12):\ndependency versions printed as `\\1.0.6` (an invalid Markdown escape, in\nevery report with a lockfile); a package listed as a transitive dependency of\nitself (jose; the engine's own report had likewise listed\n`@microtoll/crypto-core`); `test-d` type tests not marked as test code; two\n\"could not be read\" pointers that led only to token decoding and header\nextraction; the RSA key pair that signs Wiki.js's tokens reported High under\nthe comment \"Generate certificates\" (a certificate is a signing artefact:\nMedium); Wiki.js's SAML sign-in, encrypted assertions and two-factor codes\nunreported because their libraries were not catalogued (eight packages\nadded, 57 in all); and the SHA-1 note, which told both applications to\nreplace a plain content identifier as if it protected something. 50 tests\npass. The engine's own report is unchanged apart from its dependency line\n(its own package is not a dependency). The reports, before and after, are in\nthe founder's private acceptance notes. Still open for M6: the founder's\nreading of the reports (the engine's, the second application's and these\nthree), which is the acceptance.\n\n**2026-09-27 — M6: the second application scanned; the public repository\nwill start from a snapshot.** Two founder's choices.\n- **The second real application** (DESIGN §6, second item) was chosen by the\n founder and scanned read-only; the report and the hand review are in the\n founder's private notes, because they describe that application. Every\n expectation of the item was met. The review found two scanner faults,\n fixed in `pqc-scan` `ab5332f` with a fixture and a test each: a WebAuthn\n key reported High (it only verifies signatures: Medium), and a source file\n skipped as binary because of a raw control character far down (binary now\n means a NUL in the first 8,000 bytes). The first item still passes on this\n repository. Still open for M6: three public repositories for the\n false-positive review, and the founder's reading of both reports.\n- **At the publish gate**, this repository stays private as the dated\n record, and the public repository starts from a snapshot of the tree, with\n a first commit that says where the private history is kept. Nothing is\n rewritten (D-01's evidence rule). `docs/LAUNCH.md` item 7 says so.\n\n**2026-09-27 — D-46 and D-47 decided: option (a) of each, version 3.** The\nfounder's choice, and built the same day.\n- **D-46:** new recovery codes are version 3. The check character is\n Σ aⁱ⁺¹·sᵢ over the 26 data characters in GF(32) = GF(2)[x]/(x⁵ + x² + 1),\n a = x, and the parser refuses a code whose two unused bits are not zero or\n that has more than 26 data characters. Tests prove every single wrong\n character and every swap of two different characters is refused.\n- **D-47:** an unlock method's sealed label is bound to that method:\n `frameContext(\"<ns>/aad/unlock-label/v3\", methodType, SHA-256(credentialId))`\n for a passkey, the method type alone for the recovery code. A test against\n the real server swaps two passkeys' labels in the database and both read\n as null.\n- **How version 2 stays readable** (the reading of both decisions, written\n down here so that it is not silent): a version-2 code has the same shape\n as a version-3 one, and a label carries no version byte, so neither can be\n recognised by looking at it. Trying version 2 whenever version 3 fails\n would give back exactly what version 3 closes (a mistyped code accepted as\n version 2 one time in 32; a label moved between methods opening as\n version 2). Version 2 is therefore read only when a caller names it:\n `{ version: 2 }` in crypto-core, `{ recoveryCodeVersion: 2 }` and\n `openMethodLabelV2` in identity. Nothing writes version 2. No version-2\n code or label existed outside tests.\n- Every earlier frozen fixture still opens; the version-3 bytes are frozen\n beside them (`crypto-core/test/fixtures/frozen-recovery-v3.json`,\n `identity/test/fixtures/frozen-v3.json`).\n\n**2026-09-27 — M6 built; its acceptance waits on the founder.** `pqc-scan`\nis built in its own repository (`D:\\PROJECTS\\pqc-scan`, commit `3680f12`,\nno remote): the detectors, the JSON report (schema version 1) and the\nMarkdown report, the command line and the composite GitHub Action; 47 tests\npass on Node 20 and 24. The details settled while building are that\nrepository's `DESIGN.md` §8.9–§8.11. The first acceptance item passes: on\nthis repository it finds AES-256-GCM, HKDF-SHA-256, PBKDF2 at 310,000\niterations, SHA-256, Ed25519 (Medium), the P-256 ECDH seal (High), the\nhybrid `MLKEM768-X25519` (post-quantum), the server's `node:crypto` verify\nand hash, and `deploy/nginx.sample.conf` as hybrid-enabled. The other items\nneed the founder's choices: a second real application, and three public\nrepositories for the false-positive review. Also since the entries below:\n`identity`, `access` and `mailbox` carry frozen version-2 fixtures (commit\n`e7a4d59`, 265 tests); no format changed.\n\n**2026-09-27 — D-42, D-43, D-44, D-45 decided: `pqc-scan` as designed\n(`docs/DESIGN-M6-pqc-scan.md`).** The founder directed that the build be\ncompleted as far as possible without further questions, which takes each\nrecommended option: D-42 (a) its own repository `D:\\PROJECTS\\pqc-scan`, npm\n`@microtoll/pqc-scan`, binary `pqc-scan`, Apache-2.0, Node 20 or later,\nzero runtime dependencies; D-43 (a) a tokenizer with call-site patterns;\nD-44 (a) JSON schema v1 and NCSC-shaped Markdown; D-45 (a) a composite\nGitHub Action, the JSON schema as the only seam for a hosted report, nothing\npaid built. Crypto decisions are not covered by that direction and stay\npending (D-46, D-47).\n\n**2026-09-27 — The repository is self-contained; a private GitHub remote is\nallowed.** The founder's direction: nothing in the engine's code, tests or\ndocumentation names another product, its files, documents or versions.\nNotes that need them are kept outside the repository. The git history is\nnot rewritten (it is part of the ownership record, D-01). The repository is\npushed to a **private** GitHub repository; the publish gate (D-01) still\ncloses every public repository, registry and listing. The frozen fixtures\nare now written by the engine itself under the test namespace `example`\n(`packages/crypto-core/test/fixtures/frozen-v1.json`); no format changed.\n\n**2026-09-25 — M5 approved; M6 starts.** The distribution kit accepted at\ncommit `216574f`: `@microtoll/mailbox` (M3b) and `examples/invite-app`, the\ndocs site source with `llms.txt`, `@microtoll/mcp`, `SECURITY.md`,\n`CONTRIBUTING.md`, the inert release workflow and `docs/LAUNCH.md`; 242\ntests green. Nothing pushed, published or listed (D-01). M6 (`pqc-scan`, a\nseparate repository) begins with a written design for decision.\n\n**2026-09-25 — D-38, D-39, D-40, D-41 decided: the distribution kit as\ndesigned (`docs/DESIGN-M5.md`).** D-38: the docs site from Markdown in\n`docs/` with a zero-dependency renderer, `llms.txt` from the same build,\nGitHub Pages for microtoll.dev once public, no analytics. D-39:\n`@microtoll/mcp` with the stdio JSON-RPC subset written in (zero\ndependencies), docs search, read-doc and an empty-directory scaffold that\nruns nothing and fetches nothing. D-40: M3b (`@microtoll/mailbox`: the\npairwise labels, bundles version 2 with the recipient and mailbox bound into\nthe signature, withdrawal as the server does it) built inside M5, and\n`examples/invite-app` as the direct-invite demo. D-41: lockstep versions\nfrom `0.1.0`; a release workflow on a signed tag with npm provenance, inert\nuntil the founder sets `PUBLISH_GATE_OPEN` after D-01; signed tags;\n`SECURITY.md` with coordinated disclosure to `security@microtoll.dev` and no\nbounty; `CONTRIBUTING.md` with DCO. D-06, D-07, D-08, D-09, D-11, D-12 and\nD-18 are closed as adopted in M0–M2.\n\n**2026-09-25 — M4 approved; M5 starts.** `@microtoll/blind-store` accepted\nat commit `1efc156`: the library, schema, reference server, deployment kit\nand `examples/notes-app`; 221 tests green including the database-backed\nsuites; the example built and ran end to end through Docker Compose on the\nfounder's machine. Carried forward: M3b (the mailbox client, on the server\nhalf M4 ships); the founder's own browser run of the example; D-01 still\ngates publishing. M5 (the distribution kit) begins.\n\n**2026-09-25 — D-37 decided: the pointer's binding is the account, not the\nobject id (amends D-31).** The server returns an account's pointers without\nan id, by design, so the M3 binding could never be opened on a fresh device\n(found by the notes example). The pointer's additional authenticated data is\nnow `frameContext(\"<ns>/aad/pointer/v2\", routingPublicKey)`; the object id is\nread from inside the sealed pointer. Still AES-GCM with additional data; no\nprimitive, label or KDF change; no pointer had been written outside tests.\n`packages/access/FORMATS.md` §3.2 and THREATMODEL §5 updated.\n\n**2026-09-25 — D-33, D-34, D-35, D-36 decided: the server as designed\n(`packages/blind-store/DESIGN.md`).**\nD-33: an events-and-members model becomes `objects` and `object_members`\nwith a `collection` column, a fixed-length selector and an optional date\nwindow; the query answers everything matching with no identity filter,\nrefuses past `maxQueryRows` (5,000), and carries a host's extra fields; live\nwatches route by the same selector, the imminent watch by a per-collection\nserver-owned window; the message names are fixed (D-27), the selector fields\ngeneric, and `adminCapabilityHash` leaves the query and fetch replies (it\nwould give everyone who receives cover traffic a stable per-object token);\nthe mailbox's server half is included. This settles the server half of D-13\nand confirms D-14 (what stays out).\nD-34: the handshake's server half verifies `\"<ns>/auth/v2\" ‖ 0x00 ‖\nSHA-256(origin) ‖ nonce` with `node:crypto` against the connection's\n`Origin`, or each allowed origin when none was sent; the server imports no\n`@microtoll` package and a cross-implementation test proves the framing\nagainst identity's `authMessage`; the transport limits plus the query-rows\nbackstop; the three daily counters fail open. D-20's lookup limit (3 per\nsocket) is in it.\nD-35: the M0 schema rules run as a test against a live database;\n`blind_store_sweep()` runs hourly from the library and never sweeps objects\n(resolves D-19); the schema creates the least-privilege role\n`blind_store_app` that the server and the tests connect as, so a server\ncompromise reaches no more than the server can already read. D-17 is\nresolved by the registration hook.\nD-36: `createBlindStore` (with `createCore` as an alias); the thin\n`bin/blind-store.mjs`; dependencies exactly `ws` and `pg`, pinned, with the\nLISTEN client written in and `pg-listen` dropped; `deploy/` with the\nhardened Compose file and the Nginx sample; `examples/notes-app` on the\nunchanged reference server with a random-shelf selector and import-map\nloading; Postgres for tests from a throwaway container locally and a\nservice container in CI.\n\n**2026-09-25 — M3 approved; M4 starts.** `@microtoll/access` accepted at\ncommit `0ba99bf`: formats v2 (D-31), the adversarial suite green with every\ncase mapped in THREATMODEL §5, 160 tests across the three packages. M4 begins\nwith a written design of the collection and coarse-selector abstraction and\nthe server-side hardening for decision, then the server core as a library\nwith a thin reference server (D-23), and `examples/notes-app`.\n\n**2026-09-25 — D-30, D-31, D-32 decided: the access layer as designed.**\nD-30: an events-and-members model is generalised by renaming and widening\n(event → object, `K_event` → `K_object`, participation row → member row,\norganiser → owner); the content split and the grant rule are supplied by the\napp; the pointer has an extension table; wire message names are fixed\n(D-27). D-31: formats version 2 as in `packages/access/FORMATS.md` §3 —\npurpose labels on the member-row and share-link signatures (and, in M3b, the\nrecipient inside the invite signature), additional authenticated data on\ncontent, second tier, member rows, pointers and link payloads; the\nper-member sealed `K_object` stays an ECIES seal without extra data, stated\nin the threat model. D-32: M3 ships objects, members, pointers, admin seats,\nrotation, two-tier disclosure, share links, the display rule, the wire\nbuilders and the adversarial suite; direct invites wait for M3b.\n\n**2026-09-25 — M2 approved; M3 starts.** `@microtoll/identity` accepted at\ncommit `cf78168`: formats v2 (D-28, D-29), 53 tests including every gap the\naudit listed, the acceptance demo page, THREATMODEL §4 complete. Carried\nforward: the server half of the bound handshake (M4); the founder's own run\nof the demo on a real authenticator. M3 begins with the written design of the\naccess-layer hardening and the object model (D-13) for decision.\n\n**2026-09-25 — D-28 decided: additional authenticated data on all four identity-layer seals.**\nAs designed in `packages/identity/FORMATS.md` §2.1–2.4: the wrapped root key\nis bound to its method type and identifier (SHA-256 of the credential id, or\nthe recovery lookup hash); the identity blob and the unlock-method label are\nbound to the routing public key; the trusted-device session record (version 2)\nis bound to the routing public key, its expiry and its session generation, and\na restored record whose stored routing key differs from the derived one is\nrefused. The blob gains a cooperative `revision` counter so a rolled-back blob\nis refused on a device that saw a later one. Contexts are\n`frameContext(\"<ns>/aad/<purpose>/v2\", …)`; no primitive, mode or KDF changes.\n\n**2026-09-25 — D-29 decided: the handshake signature covers purpose, origin and nonce.**\nThe routing key signs `frameContext(\"<ns>/auth/v2\", SHA-256(UTF-8(origin)), nonce)`.\nThe client half ships in M2; the server half in M4 (`blind-store`), verifying\nagainst its allowed origins, with a cross-implementation test.\n\n**2026-09-25 — M1 approved; M2 starts.** `@microtoll/crypto-core` accepted at\ncommit `095824c`: vectors green through the public API, fixtures proved in\nboth directions, zero runtime dependencies, README quickstart and threat\nmodel complete. Carried forward: the browser cross-check of the hybrid seal\n(release gate, D-07) and the non-extractable signing key (identity, D-24). M2\nbegins with a written design of the identity-layer format hardening for the\nfounder's decision (D-25).\n\n**2026-09-25 — D-26 decided: recovery-code check character, version 2.**\nThe check character is the Crockford digit of the low five bits of the first\nbyte of SHA-256(secret bytes). Entropy (128 bits), length (27 characters) and\ngrouping are unchanged from version 1; only the check rule changes, so a\nrandom transcription error of any kind is caught with probability 31/32.\nThe engine writes version 2 only; no version-1 data exists to read.\n`formatRecoveryCode` and `parseRecoveryCode` become asynchronous (SHA-256 is\nasynchronous in Web Crypto). Implemented in crypto-core `src/recovery.js`.\n(Revisited by pending D-46.)\n\n**2026-09-25 — D-27 decided: crypto-core keeps its v0 function names.**\n`sealToRecipient`, `openWithPrivateKey`, `pqSealAvailable` and the rest keep\ntheir names for v0, as do the wire message names. Any renames for outside\nusers come with aliases.\n\n**2026-09-25 — D-24 decided (amends D-21): format hardening happens in the\nengine, package by package.** The signature purpose labels, recipient\nbinding, additional authenticated data, non-extractable keys, recovery\nchecksum and handshake binding are designed and built as each package is\nbuilt (M1 to M4). Each change is written up before code and recorded here;\nthe engine freezes its own fixtures. The crypto-core formats (AEAD v1, ECIES\nv3 and v2) are unchanged by the pass; the recovery-code checksum is the one\nM1 format decision.\n\n**2026-09-25 — D-25 decided: identity and access take their screens as\ncallbacks.** `@microtoll/identity` and `@microtoll/access` hold no DOM and no\npage state; the app supplies its UI through callbacks and hooks, as D-11\nrecommends.\n\n**2026-09-25 — M0 signed off.** The founder accepted the audit,\n`THREATMODEL.md` and the decisions list. M1 groundwork starts: the\nrepository, the monorepo layout, licences, CI, the standard RFC and NIST\nvectors, and the crypto-core API design. D-01 still gates publishing.\n\n**2026-09-25 — D-04 decided: the v0.x stability promise, as drafted.**\nTwo promises, stated separately. Formats are frozen from the first publish: no\nversion byte, label, KDF parameter or signed-byte layout ever changes meaning,\nand readers for every published format stay supported. The API may change in\nany 0.x minor release, always listed in the package `CHANGELOG.md` with a\nmigration note; patch releases never break. Public wording: *\"Microtoll Engine\nis pre-1.0. Function names and options may change between minor versions; the\nbytes it writes never will. Anything you encrypt with any published version\nwill decrypt with every later one.\"*\n\n**2026-09-25 — D-23 decided: `blind-store` is a library first, with a thin reference server.**\n`blind-store` exports its core handlers, dispatcher and schema. The Docker\nreference server is a thin wrapper around them. A host application mounts\nthe library and registers its own handlers beside it, so there is one server\ncore for every consumer. Item and membership handlers stay the host's until\nthe generic collection model (D-13) is settled (it was, by D-30 and D-33).\n**Licence consequence:** a host server that embeds AGPL-3.0 `blind-store`\nmust be distributed under AGPL-compatible terms.\n\n**2026-09-24 — D-21 decided: fix format-level weaknesses before any format is frozen.**\nNothing is published, so no format is frozen yet. The format-level\nweaknesses are fixed in one deliberate pass before the first publish:\n- purpose (domain-separation) labels on signatures;\n- recipient and mailbox binding in invitation and acknowledgement\n signatures;\n- additional authenticated data (AAD) on object-layer and identity-layer\n seals;\n- non-extractable Ed25519 private keys (this absorbs D-15);\n- a stronger recovery-code checksum;\n- binding for the handshake signature.\n\nThis adds binding to existing constructions; no primitive, mode or KDF is\nadded or substituted. Each change and its reason is recorded here. (Where the\npass happens: D-24.)\n\n**2026-09-24 — D-05 decided: label namespace profile.**\nThe constructions are fixed and only the label prefix varies:\n`createProfile({ namespace })`. A namespace is required, with no silent\ndefault. Retired labels stay reserved in every namespace.\n\n**2026-09-24 — D-02 decided: licensing as recommended.**\n- Apache-2.0: `crypto-core`, `identity`, `access`, `mailbox` and the examples.\n- AGPL-3.0-only: `blind-store`.\n- CC-BY-4.0: the docs.\n- Outside contributions: DCO sign-off.\n\nThis decision does not open the publish gate; D-01 still governs that.\n\n**2026-09-24 — D-03 decided: the product name is \"Microtoll Engine\".**\nPackages are named by function under the `@microtoll` scope.\n\n**State of the publish gate (non-negotiable 8): OPEN since 2026-09-27.** The\nentry of that date records that both checks of D-01 passed. Publishing\nfollows `docs/LAUNCH.md`, in order.\n\n---\n\n## Pending\n\nEach entry gives the question, the options, a recommendation, and the\nmilestone it blocks.\n\n### Blocks the first publish\n\n(Nothing: D-01 passed on 2026-09-27, see Recorded. The entry is kept below\nfor the reasoning.)\n\n**D-01 — IP and employment clearance (the publish gate).** *(Passed 2026-09-27 — see Recorded.)*\nTwo checks must both pass before anything is published: who owns the code\nthe engine is built from, and what the founder's employment terms require.\nThe details, the evidence being kept and the options are in the founder's\nprivate notes, outside this repository.\n\nRules that follow from it here:\n- Never rewrite git history (rebase, amend, force-push or date changes) on\n this repository: the dated history is part of the evidence.\n- Local and private work may continue meanwhile.\n\n**Record here when done:** both checks passed, the date, who confirmed, and\nany approval that was needed (with its date).\n\n### Closed questions (kept for the reasoning)\n\n**D-46 — Recovery-code check character, version 3: a weighted check over GF(32).** *(Decided 2026-09-27, option (a) — see Recorded.)*\nVersion 2 (D-26) takes the check character from SHA-256 of the secret. A\nrandom error is caught with probability 31/32, but no class of error is\ncaught for certain: one mistyped character, or two swapped characters, slips\nthrough one time in 32 and is then refused by lookup as \"no such account\", a\nconfusing failure (never a wrong account).\nOptions, all keeping 128 bits of entropy, 27 characters and the grouping:\n- (a) **Version 3:** the check is Σ aⁱ⁺¹·sᵢ over the 26 data characters in\n GF(32), with a = x under x⁵ + x² + 1 (the arithmetic bech32 uses). The 26\n weights are distinct and non-zero, so **every** single wrong character and\n **every** swap of two characters, adjacent or not, is caught; random\n errors are still caught 31/32. Synchronous again (no hash). The parser\n also refuses a code whose two unused final bits are not zero (26\n characters carry 130 bits for 128), so one string names one secret; today\n four strings parse to the same bytes. **Recommended.**\n- (b) Keep version 2.\n- (c) Crockford's mod-37 check symbol (catches single errors and adjacent\n swaps; the check position may show `*~$=U`).\nError detection, not cryptography: the code's 128 random bits protect the\naccount either way. No version-2 code exists outside tests.\n**Needed before:** the first publish.\n\n**D-47 — Bind an unlock method's name to the method, not only the account.** *(Decided 2026-09-27, option (a) — see Recorded.)*\nVersion 2 (D-28) binds the sealed name of an unlock method (\"Alice's phone\")\nto the account's routing key. That stops nothing the account's own key does\nnot already stop (another account's name would not open), and it does **not**\nstop the server showing one passkey's name against another of the same\naccount's passkeys — the case that misleads a person removing a method.\nOptions:\n- (a) **Version 3 of the label context:** `frameContext(\"<ns>/aad/unlock-label/v3\",\n methodType, SHA-256(credentialId))` for a passkey, the method type alone\n for the recovery code, matching the wrapped root key's binding (D-28).\n **Recommended.**\n- (b) Keep version 2.\n- (c) Bind both (routing key and method): no gain over (a), since the key is\n per account.\n**Needed before:** the first publish.\n\n**D-02 — Licensing.** *(Decided 2026-09-24 — see Recorded.)*\nApache-2.0 client packages can be used inside an AGPL application; AGPL on\n`blind-store` means anyone running a modified server as a service must\npublish their changes.\n**Recommendation:** Apache-2.0 for `crypto-core`, `identity`, `access` and\n`mailbox`; AGPL-3.0-only for `blind-store`; Apache-2.0 for the examples and\nCC-BY-4.0 for the docs; a DCO sign-off (not a CLA) for outside contributions.\n\n**D-03 — Engine product name under the Microtoll brand.** *(Decided 2026-09-24 — see Recorded.)*\n- (a) \"Microtoll Engine\", descriptive, with packages named by function\n (`@microtoll/identity`, …).\n- (b) A distinct product name, which needs a trade-mark search.\n\n**Recommendation:** (a) for v0. It can be revisited at the eight-week review.\n\n**D-04 — API stability promise for v0.x.** *(Decided 2026-09-25 — see Recorded.)*\n\n**D-05 — KDF label namespace.** *(Decided 2026-09-24 — see Recorded.)*\nA public library hard-wired to one product's label prefix is confusing, and\nchanging a label counts as substituting one (non-negotiable 1).\n- (a) One fixed label set for everyone.\n- (b) Labels become a *profile*: the construction is fixed, and only the\n namespace prefix varies; apps pass their own namespace, for example\n `\"myapp\"` → `\"myapp/routing/v1\"`.\n- (c) A new fixed `microtoll/...` label set.\n\n**Recommendation:** (b). Nothing in any construction changes. A namespace is\nrequired (no silent default), so two apps never share a derivation by\naccident. Retired labels stay reserved in every namespace.\n\n**D-06 — Correct the build plan's primitive list.** *(Adopted in M1 (2026-09-25): P-256, RFC 5903 §8.1, RFC 7914 §11, X-Wing vectors — closed by the M5 design.)*\nThe plan listed \"Ed25519/X25519 seed derivation\" and RFC 7748 and RFC 6070\nvectors. In fact the sealing curve is **P-256** (X25519 was retired because\nSafari lacks it) and PBKDF2 is **SHA-256** (RFC 6070 is SHA-1 only).\n**Recommendation:** P-256; RFC 5903 §8.1 plus a known-answer test of the v3\nseal key derivation instead of RFC 7748; RFC 7914 §11 (PBKDF2-HMAC-SHA-256)\ninstead of RFC 6070; X25519 only inside the X-Wing hybrid, tested through the\nX-Wing vectors. Adding test vectors is not new cryptography.\n\n**D-07 — Post-quantum hybrid in crypto-core.** *(Adopted in M1: hybrid off by default; the Chrome cross-check stays a release gate — closed by the M5 design.)*\nThe X-Wing seal (ECIES v2, `MLKEM768-X25519`) needs native browser support\n(Chrome 154 has it), Node has no native X-Wing, and no cross-check on a real\nbrowser has been done.\n**Recommendation:** off by default, behind an explicit opt-in; documented as\n\"hybrid mode (experimental; requires a browser with native\nMLKEM768-X25519)\"; tested through a test-only shim, never in a published\nruntime path; the Chrome seal/open cross-check a release gate before the\nopt-in is documented as usable.\n\n**D-08 — Source language.** *(Adopted in M1: JavaScript with hand-written declarations, no bundler — closed by the M5 design.)*\nJavaScript source with JSDoc; hand-written `.d.ts` declarations checked by\n`tsc --noEmit` in CI (TypeScript as a development dependency only); no\nbundler; zero runtime dependencies in the client packages.\n\n**D-09 — Supported runtimes.** *(Adopted in M1: Node ≥ 24; current Chrome, Firefox, Safari, Edge; the post-quantum path as stated — closed by the M5 design.)*\nNode ≥ 24; current Chrome, Firefox, Safari and Edge for the classical path;\nthe post-quantum path needs Node ≥ 24.7 on OpenSSL ≥ 3.5, or Chrome ≥ 154.\n\n**D-10 — Withdrawn from this log** (2026-09-27): not an engine decision.\n\n**D-11 — Identity package boundary.** *(Adopted in M2 as built (createIdentitySession with the UI as callbacks) — closed by the M5 design.)*\nThe package ends at \"authenticated connection, `auth-ok` fields, identity\nblob opened, sealing key adopted\". All UI (asking for a code, showing a code,\nconfirming a deletion) comes in through injected callbacks. A deterministic\navatar and handle stay out of v0; the passkey's user name is a\ncaller-supplied string.\n\n**D-12 — Where the sealing key lives.** *(Adopted in M2 as built (operations in crypto-core, storage and adoption in identity) — closed by the M5 design.)*\n\n**D-13 — Generic collection model for access and blind-store.** *(Decided 2026-09-25 by D-30 and D-33 — see Recorded.)*\nGeneralise an events-and-members model, taking the stronger mechanics of a\nseat-based model: an `expectedEpoch` refusal and a rotation completeness\ncheck; role-scoped capability replacement on rotation; hash-length and\nbyte-cap `CHECK`s; `timingSafeEqual` comparisons; then the coarse selector\nand the cover-traffic query.\n\n**D-14 — What stays out of v0.** *(Confirmed 2026-09-25 by D-33 — see Recorded.)*\nReporting and moderation; a public layer; operator disclosure keys; repeat\ngrants; live signals; Web Push. Guest (ephemeral) identities and live\nwatches over the selector stay in. Push can follow as an optional package\nonce its documented join is written into THREATMODEL.md.\n\n**D-15 — Non-extractable Ed25519 private keys.** *(Absorbed into D-21, 2026-09-24; built in identity, M2.)*\nDerive the public key once with an extractable import, then re-import the\nprivate key non-extractable: byte-identical outputs.\n\n**D-16 — Milestone numbering.** *(Adopted: the mailbox is M3b.)*\n`pqc-scan` stays M6; `mailbox` is M3b, built inside M5 (D-40).\n\n**D-17 — Terms and 18+ columns.** *(Decided 2026-09-25 by D-35 — see Recorded.)*\nThe core schema carries no policy columns; a registration hook lets an app\nenforce its own policy and store its own flag.\n\n**D-18 — Repository location and version control.** *(Adopted at M0: `D:\\PROJECTS\\microtoll`; a private GitHub remote from 2026-09-27; public only when the gate opens.)*\n`pqc-scan` has its own repository (D-42).\n\n**D-19 — Retention sweeps.** *(Decided 2026-09-25 by D-35 — see Recorded.)*\n`blind-store` sweeps expired and fully used link tokens, consumed and\nexpired mailbox rows, and rate counters older than two days — all of which\nwould otherwise keep ciphertext that carries keys, or activity records,\nindefinitely. Its effect on what a database copy reveals is in\nTHREATMODEL.md.\n\n**D-20 — Keep the passkey-as-PRF-only model and the open unlock lookup.** *(Decided 2026-09-25 by D-34 — see Recorded.)*\nThe server never verifies a WebAuthn assertion; the PRF output is the\nsecret; the unauthenticated lookup returns only wrapped material; the lookup\nis rate-limited in `blind-store`.\n\n**D-22 — Withdrawn from this log** (2026-09-27): not an engine decision.\n\n**D-26 — The recovery-code check character.** *(Decided 2026-09-25 — see Recorded; revisited by D-46.)*\nVersion 1 was the sum of the 16 bytes mod 32, which misses a mistyped\ncharacter whose error falls only in a byte's top three bits, and misses\nswapped neighbours.\n- (a) Keep version 1.\n- (b) Crockford's mod-37 check symbol.\n- (c) One character from SHA-256 of the 16 bytes (chosen).\n\n**D-30 — The object model (resolves D-13).** *(Decided 2026-09-25 — see Recorded.)*\n- (a) As designed. **Recommended.**\n- (b) Keep an event vocabulary in the package API (no renames).\n- (c) A wider redesign around a seat model for members.\n\n**D-31 — Access-layer hardening, version 2 (FORMATS.md §3).** *(Decided 2026-09-25 — see Recorded.)*\n- (a) All of it. **Recommended.** Every context is known before the open, no\n schema change, no new primitive.\n- (b) Signature labels only.\n- (c) Keep version 1.\n\n**D-32 — What M3 ships (FORMATS.md §4).** *(Decided 2026-09-25 — see Recorded.)*\n\n**D-33 — The collection model on the server (DESIGN.md §2).** *(Decided 2026-09-25 — see Recorded.)*\n- (a) As in DESIGN.md §2. **Recommended.**\n- (b) Keep domain-specific field names (`geoBucket`, `dateStart`,\n `dateEnd`) and the admin hash on the wire.\n- (c) One table per configured collection instead of a `collection` column.\n\n**D-34 — The bound handshake, server half, and the transport limits (DESIGN.md §3).** *(Decided 2026-09-25 — see Recorded.)*\n- (a) As in DESIGN.md §3. **Recommended.**\n- (b) Verify against the `Origin` header only, refusing a connection that\n sends none (breaks non-browser clients and every test client).\n- (c) Import `@microtoll/crypto-core` on the server for the framing (one\n implementation, but the server package then contains code that can\n decrypt).\n\n**D-35 — Schema rules as tests, the sweep, the database role (DESIGN.md §4).** *(Decided 2026-09-25 — see Recorded.)*\n- (a) As in DESIGN.md §4. **Recommended.**\n- (b) Also sweep objects a configurable time after their window ends.\n\n**D-36 — Library, reference server, deployment kit, example (DESIGN.md §5).** *(Decided 2026-09-25 — see Recorded.)*\n- (a) As in DESIGN.md §5. **Recommended.**\n- (b) Keep `pg-listen` as a third dependency.\n- (c) Make the example a calendar rather than notes.\n\n**D-37 — Correct the pointer's binding (amends D-31).** *(Decided 2026-09-25 — see Recorded.)*\n- (a) Bind the routing public key; the id inside. **Recommended; done.**\n- (b) Keep the object-id binding and add a plaintext object-id column to the\n pointer table (breaks the M0 rule: the server would hold every account's\n object list).\n- (c) No binding beyond the label.\n\n**D-38, D-39, D-40, D-41** *(Decided 2026-09-25 — see Recorded; the options\nare in `docs/DESIGN-M5.md`.)*\n\n**D-42, D-43, D-44, D-45** *(Decided 2026-09-27 — see Recorded; the options\nare in `docs/DESIGN-M6-pqc-scan.md`.)*\n\n**D-28 — Additional authenticated data on the four identity-layer seals (FORMATS.md §2.1–2.4).** *(Decided 2026-09-25 — see Recorded; the label binding revisited by D-47.)*\n- (a) All four, as designed. **Recommended.**\n- (b) Only the session record and the wrapped root key.\n- (c) None.\n\n**D-29 — The handshake signature is bound to its purpose and origin (FORMATS.md §2.5).** *(Decided 2026-09-25 — see Recorded.)*\n- (a) Label and origin, as designed. **Recommended.**\n- (b) Label only.\n- (c) Keep the bare nonce.\n",
929
929
  "sections": [
930
930
  {
931
931
  "heading": "DECISIONS.md — founder decisions log",
@@ -935,7 +935,7 @@
935
935
  {
936
936
  "heading": "Recorded",
937
937
  "level": 2,
938
- "text": "**2026-09-27 — D-48 decided: `@microtoll/mcp` versions on its own; the\nother five stay in lockstep (amends D-41).** The founder's choice, on the\nquestion of how to ship the one metadata line the MCP registry requires\n(`mcpName` in the published package). Lockstep versions remain good\npractice for the five packages that share frozen byte formats\n(`crypto-core`, `identity`, `access`, `mailbox`, `blind-store`): one number\nnames a set tested together. `@microtoll/mcp` ships documentation and a\nscaffold, none of the five imports it, and it changes whenever the\ndocumentation does, so it takes its own version from here. First use:\n`@microtoll/mcp` 0.1.1, metadata only (`mcpName`\n`io.github.microtoll/mcp`, `repository`, and `server.json` beside it;\n`engine` `f9e6f00`), for the listing in the official MCP registry (launch\nitem 11). The release workflow still publishes all six on a `v*` tag,\nskipping any version already on the registry.\n\n**2026-09-27 — Microtoll Engine 0.1.0 is published: six packages on npm,\nthe signed tag, the site.** The founder published each package by hand\nfrom `D:\\PROJECTS\\engine-public` in dependency order (crypto-core,\nidentity, access, mailbox, blind-store, mcp), each with a passkey\napproval; verified from the registry by a clean install of all six into an\nempty folder, and each one loads. Shasums: crypto-core\n`3d82d75f…`, identity `0fdfb9cb…`, access `7e169792…`, mailbox\n`04b3c188…`, blind-store `a5041461…`, mcp `66593e8e…`. The signed tag\n`v0.1.0` (`d84c7d4`) is on `microtoll/engine` and GitHub verifies it. The\nrelease workflow's first run failed, as expected, on \"cannot publish over\na previously published version\"; it now skips versions already on the\nregistry (`engine` `67716d9`); provenance starts with 0.1.1, once trusted\npublishing is set on each package. `microtoll.dev` answers over plain\nhttp from GitHub Pages; the certificate for https is being issued. Email\nrouting for `security@microtoll.dev` is set up on Cloudflare with a strict\nDMARC policy (`p=reject`). Launch items done: 1 to 7 and 10; 8 waits for\nthe next version; 9 for the certificate; 11 (the MCP listing) and 12 (Show\nHN, Sponsors) remain. The public repository's CI needs `npm run docs`\nbefore any commit that touches a document the docs snapshot includes; the\nfirst run failed on a stale `packages/mcp/generated/docs.json`.\n\n**2026-09-27 — The engine is public: `microtoll/engine` from a snapshot;\n0.1.0 prepared; Pages deployed.** Launch items 5, 6 and 7 (the repository\nhalf): every package is at 0.1.0 without `\"private\"`, in lockstep (D-41),\n274 tests green with the database (`engine-record` `ebd99be`); the\nvariable `PUBLISH_GATE_OPEN` is `true` on both repositories; the founder\ncreated the public repository `microtoll/engine` from the snapshot at\n`engine-record` `2237d37`, one commit `c951b2f` that says where the dated\nhistory is kept, nothing rewritten (D-01). GitHub Pages is enabled on it\nfrom `.github/workflows/pages.yml` with the custom domain `microtoll.dev`,\nand the first deployment succeeded; the domain answers once its DNS record\npoints at `microtoll.github.io` (item 9, the founder's Cloudflare step).\nStill to do: the six packages' first publish by hand (item 7's tag, item\n8), `security@microtoll.dev` (item 10), the MCP listing (item 11). From\nthis entry, development continues in the public repository; this private\none is the dated record up to the snapshot and this entry.\n\n**2026-09-27 — `pqc-scan` v0.1.0 tagged and signed; trusted publishing set.**\nThe founder made the release signing key (`~/.ssh/microtoll-release`,\nEd25519, passphrase-protected; launch item 3), registered its public half\non GitHub as a signing key, and pushed the signed tag `v0.1.0`\n(`3e7c05c`), which GitHub verifies as valid. On npm, the package trusts\n`release.yml` for **staged** publishing only, npm's recommended setting: a\ntag stages a version with provenance and the founder approves it by hand,\nso a compromised build run can never publish alone (`pqc-scan` `5414d9d`).\nPublishing access requires two-factor authentication with no bypass\ntokens. Provenance starts with the next version; 0.1.0 was published by\nhand.\n\n**2026-09-27 — `@microtoll/pqc-scan` 0.1.0 is on the npm registry.** The\nfirst public release of anything from this project. Published by the\nfounder from their own machine (`npm publish --access public`, two-factor\nauthentication by passkey), from `pqc-scan` `5955777`; shasum\n`41000bdca199ed0b115b40e5c483201538741b68`; 22 files, 73.5 kB. Verified from\nthe registry: `npx @microtoll/pqc-scan --version` prints 0.1.0 and a scan\nwrites its two reports. The first publish attempt showed that npm removes a\n`bin` path written with a leading `./`, which would have left the package\nwith no command; fixed before publishing. The npm organisation `microtoll`\nwas already owned by the founder's account (`npm org ls`). Still to do for\nthis release: trusted publishing on npmjs.com for `release.yml`, the signing\nkey, and the signed tag `v0.1.0` (the workflow skips a version already\npublished). Provenance therefore starts with the next version.\n\n**2026-09-27 — `pqc-scan` is public.** `github.com/microtoll/pqc-scan` was\nmade public at `19eec51` (version 0.1.0, no longer private, a release\nworkflow that publishes `@microtoll/pqc-scan` with provenance on a `v*`\ntag). Its history names no other product. Waiting on the founder: the\nsigning key (launch item 3), the `@microtoll` npm organisation with trusted\npublishing for this workflow (item 4), and then the signed tag `v0.1.0`,\nwhich publishes. The engine stays private until its own launch items.\n\n**2026-09-27 — D-01 passed: the publish gate is OPEN. M6 accepted.** The\nfounder confirmed, through the question tool, that both checks of D-01 have\npassed: who owns the code the engine is built from, and what the founder's\nemployment terms require. Recorded on the founder's word; the evidence and\nany approval that was needed stay in the founder's private notes, outside\nthis repository. From this entry, a public repository, a registry and a\npublic listing are allowed for both the engine and `pqc-scan`; the launch\nchecklist (`docs/LAUNCH.md`) governs the order. The history is still never\nrewritten. **M6 is accepted** on the same day: the founder ran the scanner\non their own application from a fresh clone on a Windows PC and read the\nreport, which completes the last acceptance item (the founder's reading);\nthat run also led to the terminal default, `--test-files` and the Windows\nlauncher (`pqc-scan` `6fac933`). The founder's choice for the first public\nstep: `pqc-scan` goes public and to npm together, as `@microtoll/pqc-scan`\n0.1.0.\n\n**2026-09-27 — M6: the three public repositories reviewed by hand; the\nscanner corrected.** The founder asked for three to be proposed and run.\nChosen for three shapes, each a shallow clone read once: a JSON Web Token\nlibrary where every finding should be genuine (`panva/jose` at `55c959f`), a\nlarge browser application with almost no cryptography (`excalidraw/excalidraw`\nat `84e3f5a`), and a server application with a typical sign-in stack\n(`requarks/wiki` at `712a3a5`). Every reported finding was a genuine\ncryptographic use, and a hand search of jose's source and of Excalidraw\nfound nothing missed. Seven faults around the findings were found, each\nfixed in `pqc-scan` with a fixture and a test (its `DESIGN.md` §8.12):\ndependency versions printed as `\\1.0.6` (an invalid Markdown escape, in\nevery report with a lockfile); a package listed as a transitive dependency of\nitself (jose; the engine's own report had likewise listed\n`@microtoll/crypto-core`); `test-d` type tests not marked as test code; two\n\"could not be read\" pointers that led only to token decoding and header\nextraction; the RSA key pair that signs Wiki.js's tokens reported High under\nthe comment \"Generate certificates\" (a certificate is a signing artefact:\nMedium); Wiki.js's SAML sign-in, encrypted assertions and two-factor codes\nunreported because their libraries were not catalogued (eight packages\nadded, 57 in all); and the SHA-1 note, which told both applications to\nreplace a plain content identifier as if it protected something. 50 tests\npass. The engine's own report is unchanged apart from its dependency line\n(its own package is not a dependency). The reports, before and after, are in\nthe founder's private acceptance notes. Still open for M6: the founder's\nreading of the reports (the engine's, the second application's and these\nthree), which is the acceptance.\n\n**2026-09-27 — M6: the second application scanned; the public repository\nwill start from a snapshot.** Two founder's choices.\n- **The second real application** (DESIGN §6, second item) was chosen by the\n founder and scanned read-only; the report and the hand review are in the\n founder's private notes, because they describe that application. Every\n expectation of the item was met. The review found two scanner faults,\n fixed in `pqc-scan` `ab5332f` with a fixture and a test each: a WebAuthn\n key reported High (it only verifies signatures: Medium), and a source file\n skipped as binary because of a raw control character far down (binary now\n means a NUL in the first 8,000 bytes). The first item still passes on this\n repository. Still open for M6: three public repositories for the\n false-positive review, and the founder's reading of both reports.\n- **At the publish gate**, this repository stays private as the dated\n record, and the public repository starts from a snapshot of the tree, with\n a first commit that says where the private history is kept. Nothing is\n rewritten (D-01's evidence rule). `docs/LAUNCH.md` item 7 says so.\n\n**2026-09-27 — D-46 and D-47 decided: option (a) of each, version 3.** The\nfounder's choice, and built the same day.\n- **D-46:** new recovery codes are version 3. The check character is\n Σ aⁱ⁺¹·sᵢ over the 26 data characters in GF(32) = GF(2)[x]/(x⁵ + x² + 1),\n a = x, and the parser refuses a code whose two unused bits are not zero or\n that has more than 26 data characters. Tests prove every single wrong\n character and every swap of two different characters is refused.\n- **D-47:** an unlock method's sealed label is bound to that method:\n `frameContext(\"<ns>/aad/unlock-label/v3\", methodType, SHA-256(credentialId))`\n for a passkey, the method type alone for the recovery code. A test against\n the real server swaps two passkeys' labels in the database and both read\n as null.\n- **How version 2 stays readable** (the reading of both decisions, written\n down here so that it is not silent): a version-2 code has the same shape\n as a version-3 one, and a label carries no version byte, so neither can be\n recognised by looking at it. Trying version 2 whenever version 3 fails\n would give back exactly what version 3 closes (a mistyped code accepted as\n version 2 one time in 32; a label moved between methods opening as\n version 2). Version 2 is therefore read only when a caller names it:\n `{ version: 2 }` in crypto-core, `{ recoveryCodeVersion: 2 }` and\n `openMethodLabelV2` in identity. Nothing writes version 2. No version-2\n code or label existed outside tests.\n- Every earlier frozen fixture still opens; the version-3 bytes are frozen\n beside them (`crypto-core/test/fixtures/frozen-recovery-v3.json`,\n `identity/test/fixtures/frozen-v3.json`).\n\n**2026-09-27 — M6 built; its acceptance waits on the founder.** `pqc-scan`\nis built in its own repository (`D:\\PROJECTS\\pqc-scan`, commit `3680f12`,\nno remote): the detectors, the JSON report (schema version 1) and the\nMarkdown report, the command line and the composite GitHub Action; 47 tests\npass on Node 20 and 24. The details settled while building are that\nrepository's `DESIGN.md` §8.9–§8.11. The first acceptance item passes: on\nthis repository it finds AES-256-GCM, HKDF-SHA-256, PBKDF2 at 310,000\niterations, SHA-256, Ed25519 (Medium), the P-256 ECDH seal (High), the\nhybrid `MLKEM768-X25519` (post-quantum), the server's `node:crypto` verify\nand hash, and `deploy/nginx.sample.conf` as hybrid-enabled. The other items\nneed the founder's choices: a second real application, and three public\nrepositories for the false-positive review. Also since the entries below:\n`identity`, `access` and `mailbox` carry frozen version-2 fixtures (commit\n`e7a4d59`, 265 tests); no format changed.\n\n**2026-09-27 — D-42, D-43, D-44, D-45 decided: `pqc-scan` as designed\n(`docs/DESIGN-M6-pqc-scan.md`).** The founder directed that the build be\ncompleted as far as possible without further questions, which takes each\nrecommended option: D-42 (a) its own repository `D:\\PROJECTS\\pqc-scan`, npm\n`@microtoll/pqc-scan`, binary `pqc-scan`, Apache-2.0, Node 20 or later,\nzero runtime dependencies; D-43 (a) a tokenizer with call-site patterns;\nD-44 (a) JSON schema v1 and NCSC-shaped Markdown; D-45 (a) a composite\nGitHub Action, the JSON schema as the only seam for a hosted report, nothing\npaid built. Crypto decisions are not covered by that direction and stay\npending (D-46, D-47).\n\n**2026-09-27 — The repository is self-contained; a private GitHub remote is\nallowed.** The founder's direction: nothing in the engine's code, tests or\ndocumentation names another product, its files, documents or versions.\nNotes that need them are kept outside the repository. The git history is\nnot rewritten (it is part of the ownership record, D-01). The repository is\npushed to a **private** GitHub repository; the publish gate (D-01) still\ncloses every public repository, registry and listing. The frozen fixtures\nare now written by the engine itself under the test namespace `example`\n(`packages/crypto-core/test/fixtures/frozen-v1.json`); no format changed.\n\n**2026-09-25 — M5 approved; M6 starts.** The distribution kit accepted at\ncommit `216574f`: `@microtoll/mailbox` (M3b) and `examples/invite-app`, the\ndocs site source with `llms.txt`, `@microtoll/mcp`, `SECURITY.md`,\n`CONTRIBUTING.md`, the inert release workflow and `docs/LAUNCH.md`; 242\ntests green. Nothing pushed, published or listed (D-01). M6 (`pqc-scan`, a\nseparate repository) begins with a written design for decision.\n\n**2026-09-25 — D-38, D-39, D-40, D-41 decided: the distribution kit as\ndesigned (`docs/DESIGN-M5.md`).** D-38: the docs site from Markdown in\n`docs/` with a zero-dependency renderer, `llms.txt` from the same build,\nGitHub Pages for microtoll.dev once public, no analytics. D-39:\n`@microtoll/mcp` with the stdio JSON-RPC subset written in (zero\ndependencies), docs search, read-doc and an empty-directory scaffold that\nruns nothing and fetches nothing. D-40: M3b (`@microtoll/mailbox`: the\npairwise labels, bundles version 2 with the recipient and mailbox bound into\nthe signature, withdrawal as the server does it) built inside M5, and\n`examples/invite-app` as the direct-invite demo. D-41: lockstep versions\nfrom `0.1.0`; a release workflow on a signed tag with npm provenance, inert\nuntil the founder sets `PUBLISH_GATE_OPEN` after D-01; signed tags;\n`SECURITY.md` with coordinated disclosure to `security@microtoll.dev` and no\nbounty; `CONTRIBUTING.md` with DCO. D-06, D-07, D-08, D-09, D-11, D-12 and\nD-18 are closed as adopted in M0–M2.\n\n**2026-09-25 — M4 approved; M5 starts.** `@microtoll/blind-store` accepted\nat commit `1efc156`: the library, schema, reference server, deployment kit\nand `examples/notes-app`; 221 tests green including the database-backed\nsuites; the example built and ran end to end through Docker Compose on the\nfounder's machine. Carried forward: M3b (the mailbox client, on the server\nhalf M4 ships); the founder's own browser run of the example; D-01 still\ngates publishing. M5 (the distribution kit) begins.\n\n**2026-09-25 — D-37 decided: the pointer's binding is the account, not the\nobject id (amends D-31).** The server returns an account's pointers without\nan id, by design, so the M3 binding could never be opened on a fresh device\n(found by the notes example). The pointer's additional authenticated data is\nnow `frameContext(\"<ns>/aad/pointer/v2\", routingPublicKey)`; the object id is\nread from inside the sealed pointer. Still AES-GCM with additional data; no\nprimitive, label or KDF change; no pointer had been written outside tests.\n`packages/access/FORMATS.md` §3.2 and THREATMODEL §5 updated.\n\n**2026-09-25 — D-33, D-34, D-35, D-36 decided: the server as designed\n(`packages/blind-store/DESIGN.md`).**\nD-33: an events-and-members model becomes `objects` and `object_members`\nwith a `collection` column, a fixed-length selector and an optional date\nwindow; the query answers everything matching with no identity filter,\nrefuses past `maxQueryRows` (5,000), and carries a host's extra fields; live\nwatches route by the same selector, the imminent watch by a per-collection\nserver-owned window; the message names are fixed (D-27), the selector fields\ngeneric, and `adminCapabilityHash` leaves the query and fetch replies (it\nwould give everyone who receives cover traffic a stable per-object token);\nthe mailbox's server half is included. This settles the server half of D-13\nand confirms D-14 (what stays out).\nD-34: the handshake's server half verifies `\"<ns>/auth/v2\" ‖ 0x00 ‖\nSHA-256(origin) ‖ nonce` with `node:crypto` against the connection's\n`Origin`, or each allowed origin when none was sent; the server imports no\n`@microtoll` package and a cross-implementation test proves the framing\nagainst identity's `authMessage`; the transport limits plus the query-rows\nbackstop; the three daily counters fail open. D-20's lookup limit (3 per\nsocket) is in it.\nD-35: the M0 schema rules run as a test against a live database;\n`blind_store_sweep()` runs hourly from the library and never sweeps objects\n(resolves D-19); the schema creates the least-privilege role\n`blind_store_app` that the server and the tests connect as, so a server\ncompromise reaches no more than the server can already read. D-17 is\nresolved by the registration hook.\nD-36: `createBlindStore` (with `createCore` as an alias); the thin\n`bin/blind-store.mjs`; dependencies exactly `ws` and `pg`, pinned, with the\nLISTEN client written in and `pg-listen` dropped; `deploy/` with the\nhardened Compose file and the Nginx sample; `examples/notes-app` on the\nunchanged reference server with a random-shelf selector and import-map\nloading; Postgres for tests from a throwaway container locally and a\nservice container in CI.\n\n**2026-09-25 — M3 approved; M4 starts.** `@microtoll/access` accepted at\ncommit `0ba99bf`: formats v2 (D-31), the adversarial suite green with every\ncase mapped in THREATMODEL §5, 160 tests across the three packages. M4 begins\nwith a written design of the collection and coarse-selector abstraction and\nthe server-side hardening for decision, then the server core as a library\nwith a thin reference server (D-23), and `examples/notes-app`.\n\n**2026-09-25 — D-30, D-31, D-32 decided: the access layer as designed.**\nD-30: an events-and-members model is generalised by renaming and widening\n(event → object, `K_event` → `K_object`, participation row → member row,\norganiser → owner); the content split and the grant rule are supplied by the\napp; the pointer has an extension table; wire message names are fixed\n(D-27). D-31: formats version 2 as in `packages/access/FORMATS.md` §3 —\npurpose labels on the member-row and share-link signatures (and, in M3b, the\nrecipient inside the invite signature), additional authenticated data on\ncontent, second tier, member rows, pointers and link payloads; the\nper-member sealed `K_object` stays an ECIES seal without extra data, stated\nin the threat model. D-32: M3 ships objects, members, pointers, admin seats,\nrotation, two-tier disclosure, share links, the display rule, the wire\nbuilders and the adversarial suite; direct invites wait for M3b.\n\n**2026-09-25 — M2 approved; M3 starts.** `@microtoll/identity` accepted at\ncommit `cf78168`: formats v2 (D-28, D-29), 53 tests including every gap the\naudit listed, the acceptance demo page, THREATMODEL §4 complete. Carried\nforward: the server half of the bound handshake (M4); the founder's own run\nof the demo on a real authenticator. M3 begins with the written design of the\naccess-layer hardening and the object model (D-13) for decision.\n\n**2026-09-25 — D-28 decided: additional authenticated data on all four identity-layer seals.**\nAs designed in `packages/identity/FORMATS.md` §2.1–2.4: the wrapped root key\nis bound to its method type and identifier (SHA-256 of the credential id, or\nthe recovery lookup hash); the identity blob and the unlock-method label are\nbound to the routing public key; the trusted-device session record (version 2)\nis bound to the routing public key, its expiry and its session generation, and\na restored record whose stored routing key differs from the derived one is\nrefused. The blob gains a cooperative `revision` counter so a rolled-back blob\nis refused on a device that saw a later one. Contexts are\n`frameContext(\"<ns>/aad/<purpose>/v2\", …)`; no primitive, mode or KDF changes.\n\n**2026-09-25 — D-29 decided: the handshake signature covers purpose, origin and nonce.**\nThe routing key signs `frameContext(\"<ns>/auth/v2\", SHA-256(UTF-8(origin)), nonce)`.\nThe client half ships in M2; the server half in M4 (`blind-store`), verifying\nagainst its allowed origins, with a cross-implementation test.\n\n**2026-09-25 — M1 approved; M2 starts.** `@microtoll/crypto-core` accepted at\ncommit `095824c`: vectors green through the public API, fixtures proved in\nboth directions, zero runtime dependencies, README quickstart and threat\nmodel complete. Carried forward: the browser cross-check of the hybrid seal\n(release gate, D-07) and the non-extractable signing key (identity, D-24). M2\nbegins with a written design of the identity-layer format hardening for the\nfounder's decision (D-25).\n\n**2026-09-25 — D-26 decided: recovery-code check character, version 2.**\nThe check character is the Crockford digit of the low five bits of the first\nbyte of SHA-256(secret bytes). Entropy (128 bits), length (27 characters) and\ngrouping are unchanged from version 1; only the check rule changes, so a\nrandom transcription error of any kind is caught with probability 31/32.\nThe engine writes version 2 only; no version-1 data exists to read.\n`formatRecoveryCode` and `parseRecoveryCode` become asynchronous (SHA-256 is\nasynchronous in Web Crypto). Implemented in crypto-core `src/recovery.js`.\n(Revisited by pending D-46.)\n\n**2026-09-25 — D-27 decided: crypto-core keeps its v0 function names.**\n`sealToRecipient`, `openWithPrivateKey`, `pqSealAvailable` and the rest keep\ntheir names for v0, as do the wire message names. Any renames for outside\nusers come with aliases.\n\n**2026-09-25 — D-24 decided (amends D-21): format hardening happens in the\nengine, package by package.** The signature purpose labels, recipient\nbinding, additional authenticated data, non-extractable keys, recovery\nchecksum and handshake binding are designed and built as each package is\nbuilt (M1 to M4). Each change is written up before code and recorded here;\nthe engine freezes its own fixtures. The crypto-core formats (AEAD v1, ECIES\nv3 and v2) are unchanged by the pass; the recovery-code checksum is the one\nM1 format decision.\n\n**2026-09-25 — D-25 decided: identity and access take their screens as\ncallbacks.** `@microtoll/identity` and `@microtoll/access` hold no DOM and no\npage state; the app supplies its UI through callbacks and hooks, as D-11\nrecommends.\n\n**2026-09-25 — M0 signed off.** The founder accepted the audit,\n`THREATMODEL.md` and the decisions list. M1 groundwork starts: the\nrepository, the monorepo layout, licences, CI, the standard RFC and NIST\nvectors, and the crypto-core API design. D-01 still gates publishing.\n\n**2026-09-25 — D-04 decided: the v0.x stability promise, as drafted.**\nTwo promises, stated separately. Formats are frozen from the first publish: no\nversion byte, label, KDF parameter or signed-byte layout ever changes meaning,\nand readers for every published format stay supported. The API may change in\nany 0.x minor release, always listed in the package `CHANGELOG.md` with a\nmigration note; patch releases never break. Public wording: *\"Microtoll Engine\nis pre-1.0. Function names and options may change between minor versions; the\nbytes it writes never will. Anything you encrypt with any published version\nwill decrypt with every later one.\"*\n\n**2026-09-25 — D-23 decided: `blind-store` is a library first, with a thin reference server.**\n`blind-store` exports its core handlers, dispatcher and schema. The Docker\nreference server is a thin wrapper around them. A host application mounts\nthe library and registers its own handlers beside it, so there is one server\ncore for every consumer. Item and membership handlers stay the host's until\nthe generic collection model (D-13) is settled (it was, by D-30 and D-33).\n**Licence consequence:** a host server that embeds AGPL-3.0 `blind-store`\nmust be distributed under AGPL-compatible terms.\n\n**2026-09-24 — D-21 decided: fix format-level weaknesses before any format is frozen.**\nNothing is published, so no format is frozen yet. The format-level\nweaknesses are fixed in one deliberate pass before the first publish:\n- purpose (domain-separation) labels on signatures;\n- recipient and mailbox binding in invitation and acknowledgement\n signatures;\n- additional authenticated data (AAD) on object-layer and identity-layer\n seals;\n- non-extractable Ed25519 private keys (this absorbs D-15);\n- a stronger recovery-code checksum;\n- binding for the handshake signature.\n\nThis adds binding to existing constructions; no primitive, mode or KDF is\nadded or substituted. Each change and its reason is recorded here. (Where the\npass happens: D-24.)\n\n**2026-09-24 — D-05 decided: label namespace profile.**\nThe constructions are fixed and only the label prefix varies:\n`createProfile({ namespace })`. A namespace is required, with no silent\ndefault. Retired labels stay reserved in every namespace.\n\n**2026-09-24 — D-02 decided: licensing as recommended.**\n- Apache-2.0: `crypto-core`, `identity`, `access`, `mailbox` and the examples.\n- AGPL-3.0-only: `blind-store`.\n- CC-BY-4.0: the docs.\n- Outside contributions: DCO sign-off.\n\nThis decision does not open the publish gate; D-01 still governs that.\n\n**2026-09-24 — D-03 decided: the product name is \"Microtoll Engine\".**\nPackages are named by function under the `@microtoll` scope.\n\n**State of the publish gate (non-negotiable 8): OPEN since 2026-09-27.** The\nentry of that date records that both checks of D-01 passed. Publishing\nfollows `docs/LAUNCH.md`, in order.\n\n---"
938
+ "text": "**2026-09-27 — The tag `v0.1.1` was moved once, on the founder's\ninstruction (an exception to the no-rewrite rule, recorded).** The first\nrelease through the workflow (0.1.1 for the five lockstep packages, mcp\n0.1.2) failed before anything was staged: npm's provenance check requires\n`repository.url` in each package.json to name the repository the build\nran in, and the five original packages had no `repository` field. The\nfounder chose to delete the tag and re-create it on the fixed commit\n(`engine` `713da65` and the correction after it) rather than issue 0.1.2\nfor a metadata field. No commit was rewritten; nothing had been published\nunder the tag; the private record is untouched. The rule stands for\neverything else.\n\n**2026-09-27 — `io.github.microtoll/mcp` is listed in the official MCP\nregistry (launch item 11).** `@microtoll/mcp` 0.1.1 published by hand; the\nlisting made with `mcp-publisher` 1.8.1 from `packages/mcp/server.json`;\nverified from the registry (status active, package `@microtoll/mcp@0.1.1`,\nstdio) and by running the published package as a host would: it answers\n`initialize` and lists its three tools. What it took, for the record: the\nregistry grants an organisation namespace only to an organisation Owner,\nand only when the sign-in token can read organisation membership. The\ndevice-flow sign-in cannot, so the founder made a classic personal access\ntoken with the single scope `read:org` (seven-day expiry, to be deleted)\nand signed in with `login github --token`. The organisation membership was\nalso made public and the profile un-hidden along the way; neither turned\nout to be the cause. Found meanwhile: the published server reports its\nversion as 0.0.0 (a hard-coded constant); fixed in the repository to read\n`package.json`, to go out with the next version.\n\n**2026-09-27 — D-48 decided: `@microtoll/mcp` versions on its own; the\nother five stay in lockstep (amends D-41).** The founder's choice, on the\nquestion of how to ship the one metadata line the MCP registry requires\n(`mcpName` in the published package). Lockstep versions remain good\npractice for the five packages that share frozen byte formats\n(`crypto-core`, `identity`, `access`, `mailbox`, `blind-store`): one number\nnames a set tested together. `@microtoll/mcp` ships documentation and a\nscaffold, none of the five imports it, and it changes whenever the\ndocumentation does, so it takes its own version from here. First use:\n`@microtoll/mcp` 0.1.1, metadata only (`mcpName`\n`io.github.microtoll/mcp`, `repository`, and `server.json` beside it;\n`engine` `f9e6f00`), for the listing in the official MCP registry (launch\nitem 11). The release workflow still publishes all six on a `v*` tag,\nskipping any version already on the registry.\n\n**2026-09-27 — Microtoll Engine 0.1.0 is published: six packages on npm,\nthe signed tag, the site.** The founder published each package by hand\nfrom `D:\\PROJECTS\\engine-public` in dependency order (crypto-core,\nidentity, access, mailbox, blind-store, mcp), each with a passkey\napproval; verified from the registry by a clean install of all six into an\nempty folder, and each one loads. Shasums: crypto-core\n`3d82d75f…`, identity `0fdfb9cb…`, access `7e169792…`, mailbox\n`04b3c188…`, blind-store `a5041461…`, mcp `66593e8e…`. The signed tag\n`v0.1.0` (`d84c7d4`) is on `microtoll/engine` and GitHub verifies it. The\nrelease workflow's first run failed, as expected, on \"cannot publish over\na previously published version\"; it now skips versions already on the\nregistry (`engine` `67716d9`); provenance starts with 0.1.1, once trusted\npublishing is set on each package. `microtoll.dev` answers over plain\nhttp from GitHub Pages; the certificate for https is being issued. Email\nrouting for `security@microtoll.dev` is set up on Cloudflare with a strict\nDMARC policy (`p=reject`). Launch items done: 1 to 7 and 10; 8 waits for\nthe next version; 9 for the certificate; 11 (the MCP listing) and 12 (Show\nHN, Sponsors) remain. The public repository's CI needs `npm run docs`\nbefore any commit that touches a document the docs snapshot includes; the\nfirst run failed on a stale `packages/mcp/generated/docs.json`.\n\n**2026-09-27 — The engine is public: `microtoll/engine` from a snapshot;\n0.1.0 prepared; Pages deployed.** Launch items 5, 6 and 7 (the repository\nhalf): every package is at 0.1.0 without `\"private\"`, in lockstep (D-41),\n274 tests green with the database (`engine-record` `ebd99be`); the\nvariable `PUBLISH_GATE_OPEN` is `true` on both repositories; the founder\ncreated the public repository `microtoll/engine` from the snapshot at\n`engine-record` `2237d37`, one commit `c951b2f` that says where the dated\nhistory is kept, nothing rewritten (D-01). GitHub Pages is enabled on it\nfrom `.github/workflows/pages.yml` with the custom domain `microtoll.dev`,\nand the first deployment succeeded; the domain answers once its DNS record\npoints at `microtoll.github.io` (item 9, the founder's Cloudflare step).\nStill to do: the six packages' first publish by hand (item 7's tag, item\n8), `security@microtoll.dev` (item 10), the MCP listing (item 11). From\nthis entry, development continues in the public repository; this private\none is the dated record up to the snapshot and this entry.\n\n**2026-09-27 — `pqc-scan` v0.1.0 tagged and signed; trusted publishing set.**\nThe founder made the release signing key (`~/.ssh/microtoll-release`,\nEd25519, passphrase-protected; launch item 3), registered its public half\non GitHub as a signing key, and pushed the signed tag `v0.1.0`\n(`3e7c05c`), which GitHub verifies as valid. On npm, the package trusts\n`release.yml` for **staged** publishing only, npm's recommended setting: a\ntag stages a version with provenance and the founder approves it by hand,\nso a compromised build run can never publish alone (`pqc-scan` `5414d9d`).\nPublishing access requires two-factor authentication with no bypass\ntokens. Provenance starts with the next version; 0.1.0 was published by\nhand.\n\n**2026-09-27 — `@microtoll/pqc-scan` 0.1.0 is on the npm registry.** The\nfirst public release of anything from this project. Published by the\nfounder from their own machine (`npm publish --access public`, two-factor\nauthentication by passkey), from `pqc-scan` `5955777`; shasum\n`41000bdca199ed0b115b40e5c483201538741b68`; 22 files, 73.5 kB. Verified from\nthe registry: `npx @microtoll/pqc-scan --version` prints 0.1.0 and a scan\nwrites its two reports. The first publish attempt showed that npm removes a\n`bin` path written with a leading `./`, which would have left the package\nwith no command; fixed before publishing. The npm organisation `microtoll`\nwas already owned by the founder's account (`npm org ls`). Still to do for\nthis release: trusted publishing on npmjs.com for `release.yml`, the signing\nkey, and the signed tag `v0.1.0` (the workflow skips a version already\npublished). Provenance therefore starts with the next version.\n\n**2026-09-27 — `pqc-scan` is public.** `github.com/microtoll/pqc-scan` was\nmade public at `19eec51` (version 0.1.0, no longer private, a release\nworkflow that publishes `@microtoll/pqc-scan` with provenance on a `v*`\ntag). Its history names no other product. Waiting on the founder: the\nsigning key (launch item 3), the `@microtoll` npm organisation with trusted\npublishing for this workflow (item 4), and then the signed tag `v0.1.0`,\nwhich publishes. The engine stays private until its own launch items.\n\n**2026-09-27 — D-01 passed: the publish gate is OPEN. M6 accepted.** The\nfounder confirmed, through the question tool, that both checks of D-01 have\npassed: who owns the code the engine is built from, and what the founder's\nemployment terms require. Recorded on the founder's word; the evidence and\nany approval that was needed stay in the founder's private notes, outside\nthis repository. From this entry, a public repository, a registry and a\npublic listing are allowed for both the engine and `pqc-scan`; the launch\nchecklist (`docs/LAUNCH.md`) governs the order. The history is still never\nrewritten. **M6 is accepted** on the same day: the founder ran the scanner\non their own application from a fresh clone on a Windows PC and read the\nreport, which completes the last acceptance item (the founder's reading);\nthat run also led to the terminal default, `--test-files` and the Windows\nlauncher (`pqc-scan` `6fac933`). The founder's choice for the first public\nstep: `pqc-scan` goes public and to npm together, as `@microtoll/pqc-scan`\n0.1.0.\n\n**2026-09-27 — M6: the three public repositories reviewed by hand; the\nscanner corrected.** The founder asked for three to be proposed and run.\nChosen for three shapes, each a shallow clone read once: a JSON Web Token\nlibrary where every finding should be genuine (`panva/jose` at `55c959f`), a\nlarge browser application with almost no cryptography (`excalidraw/excalidraw`\nat `84e3f5a`), and a server application with a typical sign-in stack\n(`requarks/wiki` at `712a3a5`). Every reported finding was a genuine\ncryptographic use, and a hand search of jose's source and of Excalidraw\nfound nothing missed. Seven faults around the findings were found, each\nfixed in `pqc-scan` with a fixture and a test (its `DESIGN.md` §8.12):\ndependency versions printed as `\\1.0.6` (an invalid Markdown escape, in\nevery report with a lockfile); a package listed as a transitive dependency of\nitself (jose; the engine's own report had likewise listed\n`@microtoll/crypto-core`); `test-d` type tests not marked as test code; two\n\"could not be read\" pointers that led only to token decoding and header\nextraction; the RSA key pair that signs Wiki.js's tokens reported High under\nthe comment \"Generate certificates\" (a certificate is a signing artefact:\nMedium); Wiki.js's SAML sign-in, encrypted assertions and two-factor codes\nunreported because their libraries were not catalogued (eight packages\nadded, 57 in all); and the SHA-1 note, which told both applications to\nreplace a plain content identifier as if it protected something. 50 tests\npass. The engine's own report is unchanged apart from its dependency line\n(its own package is not a dependency). The reports, before and after, are in\nthe founder's private acceptance notes. Still open for M6: the founder's\nreading of the reports (the engine's, the second application's and these\nthree), which is the acceptance.\n\n**2026-09-27 — M6: the second application scanned; the public repository\nwill start from a snapshot.** Two founder's choices.\n- **The second real application** (DESIGN §6, second item) was chosen by the\n founder and scanned read-only; the report and the hand review are in the\n founder's private notes, because they describe that application. Every\n expectation of the item was met. The review found two scanner faults,\n fixed in `pqc-scan` `ab5332f` with a fixture and a test each: a WebAuthn\n key reported High (it only verifies signatures: Medium), and a source file\n skipped as binary because of a raw control character far down (binary now\n means a NUL in the first 8,000 bytes). The first item still passes on this\n repository. Still open for M6: three public repositories for the\n false-positive review, and the founder's reading of both reports.\n- **At the publish gate**, this repository stays private as the dated\n record, and the public repository starts from a snapshot of the tree, with\n a first commit that says where the private history is kept. Nothing is\n rewritten (D-01's evidence rule). `docs/LAUNCH.md` item 7 says so.\n\n**2026-09-27 — D-46 and D-47 decided: option (a) of each, version 3.** The\nfounder's choice, and built the same day.\n- **D-46:** new recovery codes are version 3. The check character is\n Σ aⁱ⁺¹·sᵢ over the 26 data characters in GF(32) = GF(2)[x]/(x⁵ + x² + 1),\n a = x, and the parser refuses a code whose two unused bits are not zero or\n that has more than 26 data characters. Tests prove every single wrong\n character and every swap of two different characters is refused.\n- **D-47:** an unlock method's sealed label is bound to that method:\n `frameContext(\"<ns>/aad/unlock-label/v3\", methodType, SHA-256(credentialId))`\n for a passkey, the method type alone for the recovery code. A test against\n the real server swaps two passkeys' labels in the database and both read\n as null.\n- **How version 2 stays readable** (the reading of both decisions, written\n down here so that it is not silent): a version-2 code has the same shape\n as a version-3 one, and a label carries no version byte, so neither can be\n recognised by looking at it. Trying version 2 whenever version 3 fails\n would give back exactly what version 3 closes (a mistyped code accepted as\n version 2 one time in 32; a label moved between methods opening as\n version 2). Version 2 is therefore read only when a caller names it:\n `{ version: 2 }` in crypto-core, `{ recoveryCodeVersion: 2 }` and\n `openMethodLabelV2` in identity. Nothing writes version 2. No version-2\n code or label existed outside tests.\n- Every earlier frozen fixture still opens; the version-3 bytes are frozen\n beside them (`crypto-core/test/fixtures/frozen-recovery-v3.json`,\n `identity/test/fixtures/frozen-v3.json`).\n\n**2026-09-27 — M6 built; its acceptance waits on the founder.** `pqc-scan`\nis built in its own repository (`D:\\PROJECTS\\pqc-scan`, commit `3680f12`,\nno remote): the detectors, the JSON report (schema version 1) and the\nMarkdown report, the command line and the composite GitHub Action; 47 tests\npass on Node 20 and 24. The details settled while building are that\nrepository's `DESIGN.md` §8.9–§8.11. The first acceptance item passes: on\nthis repository it finds AES-256-GCM, HKDF-SHA-256, PBKDF2 at 310,000\niterations, SHA-256, Ed25519 (Medium), the P-256 ECDH seal (High), the\nhybrid `MLKEM768-X25519` (post-quantum), the server's `node:crypto` verify\nand hash, and `deploy/nginx.sample.conf` as hybrid-enabled. The other items\nneed the founder's choices: a second real application, and three public\nrepositories for the false-positive review. Also since the entries below:\n`identity`, `access` and `mailbox` carry frozen version-2 fixtures (commit\n`e7a4d59`, 265 tests); no format changed.\n\n**2026-09-27 — D-42, D-43, D-44, D-45 decided: `pqc-scan` as designed\n(`docs/DESIGN-M6-pqc-scan.md`).** The founder directed that the build be\ncompleted as far as possible without further questions, which takes each\nrecommended option: D-42 (a) its own repository `D:\\PROJECTS\\pqc-scan`, npm\n`@microtoll/pqc-scan`, binary `pqc-scan`, Apache-2.0, Node 20 or later,\nzero runtime dependencies; D-43 (a) a tokenizer with call-site patterns;\nD-44 (a) JSON schema v1 and NCSC-shaped Markdown; D-45 (a) a composite\nGitHub Action, the JSON schema as the only seam for a hosted report, nothing\npaid built. Crypto decisions are not covered by that direction and stay\npending (D-46, D-47).\n\n**2026-09-27 — The repository is self-contained; a private GitHub remote is\nallowed.** The founder's direction: nothing in the engine's code, tests or\ndocumentation names another product, its files, documents or versions.\nNotes that need them are kept outside the repository. The git history is\nnot rewritten (it is part of the ownership record, D-01). The repository is\npushed to a **private** GitHub repository; the publish gate (D-01) still\ncloses every public repository, registry and listing. The frozen fixtures\nare now written by the engine itself under the test namespace `example`\n(`packages/crypto-core/test/fixtures/frozen-v1.json`); no format changed.\n\n**2026-09-25 — M5 approved; M6 starts.** The distribution kit accepted at\ncommit `216574f`: `@microtoll/mailbox` (M3b) and `examples/invite-app`, the\ndocs site source with `llms.txt`, `@microtoll/mcp`, `SECURITY.md`,\n`CONTRIBUTING.md`, the inert release workflow and `docs/LAUNCH.md`; 242\ntests green. Nothing pushed, published or listed (D-01). M6 (`pqc-scan`, a\nseparate repository) begins with a written design for decision.\n\n**2026-09-25 — D-38, D-39, D-40, D-41 decided: the distribution kit as\ndesigned (`docs/DESIGN-M5.md`).** D-38: the docs site from Markdown in\n`docs/` with a zero-dependency renderer, `llms.txt` from the same build,\nGitHub Pages for microtoll.dev once public, no analytics. D-39:\n`@microtoll/mcp` with the stdio JSON-RPC subset written in (zero\ndependencies), docs search, read-doc and an empty-directory scaffold that\nruns nothing and fetches nothing. D-40: M3b (`@microtoll/mailbox`: the\npairwise labels, bundles version 2 with the recipient and mailbox bound into\nthe signature, withdrawal as the server does it) built inside M5, and\n`examples/invite-app` as the direct-invite demo. D-41: lockstep versions\nfrom `0.1.0`; a release workflow on a signed tag with npm provenance, inert\nuntil the founder sets `PUBLISH_GATE_OPEN` after D-01; signed tags;\n`SECURITY.md` with coordinated disclosure to `security@microtoll.dev` and no\nbounty; `CONTRIBUTING.md` with DCO. D-06, D-07, D-08, D-09, D-11, D-12 and\nD-18 are closed as adopted in M0–M2.\n\n**2026-09-25 — M4 approved; M5 starts.** `@microtoll/blind-store` accepted\nat commit `1efc156`: the library, schema, reference server, deployment kit\nand `examples/notes-app`; 221 tests green including the database-backed\nsuites; the example built and ran end to end through Docker Compose on the\nfounder's machine. Carried forward: M3b (the mailbox client, on the server\nhalf M4 ships); the founder's own browser run of the example; D-01 still\ngates publishing. M5 (the distribution kit) begins.\n\n**2026-09-25 — D-37 decided: the pointer's binding is the account, not the\nobject id (amends D-31).** The server returns an account's pointers without\nan id, by design, so the M3 binding could never be opened on a fresh device\n(found by the notes example). The pointer's additional authenticated data is\nnow `frameContext(\"<ns>/aad/pointer/v2\", routingPublicKey)`; the object id is\nread from inside the sealed pointer. Still AES-GCM with additional data; no\nprimitive, label or KDF change; no pointer had been written outside tests.\n`packages/access/FORMATS.md` §3.2 and THREATMODEL §5 updated.\n\n**2026-09-25 — D-33, D-34, D-35, D-36 decided: the server as designed\n(`packages/blind-store/DESIGN.md`).**\nD-33: an events-and-members model becomes `objects` and `object_members`\nwith a `collection` column, a fixed-length selector and an optional date\nwindow; the query answers everything matching with no identity filter,\nrefuses past `maxQueryRows` (5,000), and carries a host's extra fields; live\nwatches route by the same selector, the imminent watch by a per-collection\nserver-owned window; the message names are fixed (D-27), the selector fields\ngeneric, and `adminCapabilityHash` leaves the query and fetch replies (it\nwould give everyone who receives cover traffic a stable per-object token);\nthe mailbox's server half is included. This settles the server half of D-13\nand confirms D-14 (what stays out).\nD-34: the handshake's server half verifies `\"<ns>/auth/v2\" ‖ 0x00 ‖\nSHA-256(origin) ‖ nonce` with `node:crypto` against the connection's\n`Origin`, or each allowed origin when none was sent; the server imports no\n`@microtoll` package and a cross-implementation test proves the framing\nagainst identity's `authMessage`; the transport limits plus the query-rows\nbackstop; the three daily counters fail open. D-20's lookup limit (3 per\nsocket) is in it.\nD-35: the M0 schema rules run as a test against a live database;\n`blind_store_sweep()` runs hourly from the library and never sweeps objects\n(resolves D-19); the schema creates the least-privilege role\n`blind_store_app` that the server and the tests connect as, so a server\ncompromise reaches no more than the server can already read. D-17 is\nresolved by the registration hook.\nD-36: `createBlindStore` (with `createCore` as an alias); the thin\n`bin/blind-store.mjs`; dependencies exactly `ws` and `pg`, pinned, with the\nLISTEN client written in and `pg-listen` dropped; `deploy/` with the\nhardened Compose file and the Nginx sample; `examples/notes-app` on the\nunchanged reference server with a random-shelf selector and import-map\nloading; Postgres for tests from a throwaway container locally and a\nservice container in CI.\n\n**2026-09-25 — M3 approved; M4 starts.** `@microtoll/access` accepted at\ncommit `0ba99bf`: formats v2 (D-31), the adversarial suite green with every\ncase mapped in THREATMODEL §5, 160 tests across the three packages. M4 begins\nwith a written design of the collection and coarse-selector abstraction and\nthe server-side hardening for decision, then the server core as a library\nwith a thin reference server (D-23), and `examples/notes-app`.\n\n**2026-09-25 — D-30, D-31, D-32 decided: the access layer as designed.**\nD-30: an events-and-members model is generalised by renaming and widening\n(event → object, `K_event` → `K_object`, participation row → member row,\norganiser → owner); the content split and the grant rule are supplied by the\napp; the pointer has an extension table; wire message names are fixed\n(D-27). D-31: formats version 2 as in `packages/access/FORMATS.md` §3 —\npurpose labels on the member-row and share-link signatures (and, in M3b, the\nrecipient inside the invite signature), additional authenticated data on\ncontent, second tier, member rows, pointers and link payloads; the\nper-member sealed `K_object` stays an ECIES seal without extra data, stated\nin the threat model. D-32: M3 ships objects, members, pointers, admin seats,\nrotation, two-tier disclosure, share links, the display rule, the wire\nbuilders and the adversarial suite; direct invites wait for M3b.\n\n**2026-09-25 — M2 approved; M3 starts.** `@microtoll/identity` accepted at\ncommit `cf78168`: formats v2 (D-28, D-29), 53 tests including every gap the\naudit listed, the acceptance demo page, THREATMODEL §4 complete. Carried\nforward: the server half of the bound handshake (M4); the founder's own run\nof the demo on a real authenticator. M3 begins with the written design of the\naccess-layer hardening and the object model (D-13) for decision.\n\n**2026-09-25 — D-28 decided: additional authenticated data on all four identity-layer seals.**\nAs designed in `packages/identity/FORMATS.md` §2.1–2.4: the wrapped root key\nis bound to its method type and identifier (SHA-256 of the credential id, or\nthe recovery lookup hash); the identity blob and the unlock-method label are\nbound to the routing public key; the trusted-device session record (version 2)\nis bound to the routing public key, its expiry and its session generation, and\na restored record whose stored routing key differs from the derived one is\nrefused. The blob gains a cooperative `revision` counter so a rolled-back blob\nis refused on a device that saw a later one. Contexts are\n`frameContext(\"<ns>/aad/<purpose>/v2\", …)`; no primitive, mode or KDF changes.\n\n**2026-09-25 — D-29 decided: the handshake signature covers purpose, origin and nonce.**\nThe routing key signs `frameContext(\"<ns>/auth/v2\", SHA-256(UTF-8(origin)), nonce)`.\nThe client half ships in M2; the server half in M4 (`blind-store`), verifying\nagainst its allowed origins, with a cross-implementation test.\n\n**2026-09-25 — M1 approved; M2 starts.** `@microtoll/crypto-core` accepted at\ncommit `095824c`: vectors green through the public API, fixtures proved in\nboth directions, zero runtime dependencies, README quickstart and threat\nmodel complete. Carried forward: the browser cross-check of the hybrid seal\n(release gate, D-07) and the non-extractable signing key (identity, D-24). M2\nbegins with a written design of the identity-layer format hardening for the\nfounder's decision (D-25).\n\n**2026-09-25 — D-26 decided: recovery-code check character, version 2.**\nThe check character is the Crockford digit of the low five bits of the first\nbyte of SHA-256(secret bytes). Entropy (128 bits), length (27 characters) and\ngrouping are unchanged from version 1; only the check rule changes, so a\nrandom transcription error of any kind is caught with probability 31/32.\nThe engine writes version 2 only; no version-1 data exists to read.\n`formatRecoveryCode` and `parseRecoveryCode` become asynchronous (SHA-256 is\nasynchronous in Web Crypto). Implemented in crypto-core `src/recovery.js`.\n(Revisited by pending D-46.)\n\n**2026-09-25 — D-27 decided: crypto-core keeps its v0 function names.**\n`sealToRecipient`, `openWithPrivateKey`, `pqSealAvailable` and the rest keep\ntheir names for v0, as do the wire message names. Any renames for outside\nusers come with aliases.\n\n**2026-09-25 — D-24 decided (amends D-21): format hardening happens in the\nengine, package by package.** The signature purpose labels, recipient\nbinding, additional authenticated data, non-extractable keys, recovery\nchecksum and handshake binding are designed and built as each package is\nbuilt (M1 to M4). Each change is written up before code and recorded here;\nthe engine freezes its own fixtures. The crypto-core formats (AEAD v1, ECIES\nv3 and v2) are unchanged by the pass; the recovery-code checksum is the one\nM1 format decision.\n\n**2026-09-25 — D-25 decided: identity and access take their screens as\ncallbacks.** `@microtoll/identity` and `@microtoll/access` hold no DOM and no\npage state; the app supplies its UI through callbacks and hooks, as D-11\nrecommends.\n\n**2026-09-25 — M0 signed off.** The founder accepted the audit,\n`THREATMODEL.md` and the decisions list. M1 groundwork starts: the\nrepository, the monorepo layout, licences, CI, the standard RFC and NIST\nvectors, and the crypto-core API design. D-01 still gates publishing.\n\n**2026-09-25 — D-04 decided: the v0.x stability promise, as drafted.**\nTwo promises, stated separately. Formats are frozen from the first publish: no\nversion byte, label, KDF parameter or signed-byte layout ever changes meaning,\nand readers for every published format stay supported. The API may change in\nany 0.x minor release, always listed in the package `CHANGELOG.md` with a\nmigration note; patch releases never break. Public wording: *\"Microtoll Engine\nis pre-1.0. Function names and options may change between minor versions; the\nbytes it writes never will. Anything you encrypt with any published version\nwill decrypt with every later one.\"*\n\n**2026-09-25 — D-23 decided: `blind-store` is a library first, with a thin reference server.**\n`blind-store` exports its core handlers, dispatcher and schema. The Docker\nreference server is a thin wrapper around them. A host application mounts\nthe library and registers its own handlers beside it, so there is one server\ncore for every consumer. Item and membership handlers stay the host's until\nthe generic collection model (D-13) is settled (it was, by D-30 and D-33).\n**Licence consequence:** a host server that embeds AGPL-3.0 `blind-store`\nmust be distributed under AGPL-compatible terms.\n\n**2026-09-24 — D-21 decided: fix format-level weaknesses before any format is frozen.**\nNothing is published, so no format is frozen yet. The format-level\nweaknesses are fixed in one deliberate pass before the first publish:\n- purpose (domain-separation) labels on signatures;\n- recipient and mailbox binding in invitation and acknowledgement\n signatures;\n- additional authenticated data (AAD) on object-layer and identity-layer\n seals;\n- non-extractable Ed25519 private keys (this absorbs D-15);\n- a stronger recovery-code checksum;\n- binding for the handshake signature.\n\nThis adds binding to existing constructions; no primitive, mode or KDF is\nadded or substituted. Each change and its reason is recorded here. (Where the\npass happens: D-24.)\n\n**2026-09-24 — D-05 decided: label namespace profile.**\nThe constructions are fixed and only the label prefix varies:\n`createProfile({ namespace })`. A namespace is required, with no silent\ndefault. Retired labels stay reserved in every namespace.\n\n**2026-09-24 — D-02 decided: licensing as recommended.**\n- Apache-2.0: `crypto-core`, `identity`, `access`, `mailbox` and the examples.\n- AGPL-3.0-only: `blind-store`.\n- CC-BY-4.0: the docs.\n- Outside contributions: DCO sign-off.\n\nThis decision does not open the publish gate; D-01 still governs that.\n\n**2026-09-24 — D-03 decided: the product name is \"Microtoll Engine\".**\nPackages are named by function under the `@microtoll` scope.\n\n**State of the publish gate (non-negotiable 8): OPEN since 2026-09-27.** The\nentry of that date records that both checks of D-01 passed. Publishing\nfollows `docs/LAUNCH.md`, in order.\n\n---"
939
939
  },
940
940
  {
941
941
  "heading": "Pending",
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@microtoll/mcp",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "mcpName": "io.github.microtoll/mcp",
5
5
  "description": "Microtoll Engine inside your coding tool: docs search, page reading and a project scaffold over the Model Context Protocol (stdio). Zero dependencies, no network, nothing runs but what is in src/.",
6
6
  "license": "Apache-2.0",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "https://github.com/microtoll/engine.git",
9
+ "url": "git+https://github.com/microtoll/engine.git",
10
10
  "directory": "packages/mcp"
11
11
  },
12
12
  "type": "module",
package/src/index.js CHANGED
@@ -3,13 +3,16 @@
3
3
  * builds the three tools on the protocol in protocol.js; bin/microtoll-mcp.mjs
4
4
  * starts it on stdio.
5
5
  */
6
+ import { createRequire } from 'node:module';
6
7
  import { createServer, PROTOCOL_VERSION } from './protocol.js';
7
8
  import { loadDocs, searchDocs, readDoc, formatSearch } from './docs.js';
8
9
  import { scaffold, renderScaffold } from './scaffold.js';
9
10
 
10
11
  export { createServer, PROTOCOL_VERSION, loadDocs, searchDocs, readDoc, formatSearch, scaffold, renderScaffold };
11
12
 
12
- export const VERSION = '0.0.0';
13
+ // The version a host sees in initialize: package.json's, so it never drifts
14
+ // (the published 0.1.1 said 0.0.0 from a hard-coded constant).
15
+ export const VERSION = createRequire(import.meta.url)('../package.json').version;
13
16
 
14
17
  export const INSTRUCTIONS = [
15
18
  'Microtoll Engine: sign-in, key handling, access control and revocation for end-to-end-encrypted apps, as packages.',