Spec-gap issue drafts (G1–G16)

Date: 2026-08-08 · Status: all thirteen posted to the GitHub tracker on 2026-08-08; issue numbers align with gap numbers (Gn = issue #n) for G1–G13 only — G14, filed 2026-08-22, is tracker #43, G15, filed 2026-08-23, is tracker #48, and G16, filed 2026-08-25, is tracker #60 — the intervening numbers having gone to pull requests. These files remain the canonical drafts; edits after posting belong in the tracker.

As of 2026-08-09, eight are resolved — G3, G6, G8, G9, G10, G11, G12, G13 — which is every gap flagged no cryptographic review required. The five that remain (G1, G2, G4, G5, G7) are exactly the five flagged Yes or Partly, so nothing further in this list can close by engineering judgment: they wait on the Phase 0 reviewers (docs/16-reviewer-brief.md). G11 is a partial close — its grammar is settled, its injectivity question belongs to G4.

As of 2026-08-22, those five are provisionally adopted under Gate 0a and remain open. The Phase 0 exit gate has been split (PRD §8) so that reviewer availability no longer blocks implementation. See Provisional adoptions below for what each now says in the spec and what would close it. Nothing about the crypto-review column has been relaxed.

G14 was added on 2026-08-22, found in review of the TypeScript core rather than in the tech-spec suite: the optional context fields are unbounded, so the KDF info is unbounded, and platform HKDFs (Node at 1024 bytes, OpenSSL 3.0–3.5 at 32 KiB, .NET on Linux through OpenSSL) cap it. It needs no cryptographic review and is closable by engineering judgment, but should be seen by the Phase 0 reviewers first because it sizes the extension headroom of the §6.2 encoding Q4 asks about.

G15 was added on 2026-08-23, from the M2 report’s divergence list (docs/18 D-03, D-10, D-13, D-14) once the Python core had been brought to the same pins (PR #47): the UNKNOWN_FORMAT_VERSION set, rotate() on non-envelope input in permissive, the name of the §4.8 arming variable, and the Unicode version, folding variant, post-fold normalization and invalid-UTF-8 behavior of nfc-casefold-v1. Four parts in one issue because each is a one-paragraph normative change with the same closure shape; none needs cryptographic review. Two of its findings are new: 33 code points whose full case folding is not NFC-stable, and a bounded 28-letter / 34-mark divergence between the Unicode 16.0 table the Python core folds with and the 17.0 table the TypeScript core vendors.

G16 was added on 2026-08-25, from the consequences of closing G15 rather than from any core’s behavior: neither core is wrong under the text as it now stands. Three parts. (A) A bytes-only index API cannot enforce §7.1’s own lone-surrogate refusal — TextEncoder substitutes U+FFFD, so encode("a\uD800b") and encode("a\uDC00b") are the same five bytes, and the TypeScript core’s error message points callers at exactly that conversion while the Python core has accepted str all along. (B) encrypt does not normalize and blind_index does, so a post-pin character stores but cannot be indexed, and §7.1’s pointer to “§12 of docs/12 and docs/13” for the refuse-vs-store-unfindable decision is dangling — those sections do not exist. (C) The pin has no currency policy, and bumping it today would silently pin the draft, because Public/18.0.0/ucd/ 302-redirects to Public/draft/ucd/ over plaintext HTTP and the generator follows redirects while logging the requested URL. All four parts are decided. Part D asked whether any of this belongs in the Gate 0b packet and answers no: Part A reads like a reviewer question and is not one (it decides a parameter type, not a construction), and the refusal rule’s breadth turns on a UCD stability question rather than a cryptographic one. G16 therefore has no reviewer dependency in either direction — the first issue since G13 that is entirely the project’s own to close. It also corrects the impact table in internal/product-decision-indexing-edge-cases.md: the 11,328-point block driving the “CJK personal names” case is Seal Script, a historic calligraphic script, and CJK Unified Ideographs gained one code point.

These are the specification gaps found while writing the Phase 1 tech-spec suite (G1–G11), during its consistency review (G12–G13), in core review (G14–G15), and in the fallout of a closure (G16), consolidated in docs/07-implementation-plan.md §5. Per CONTRIBUTING.md, every specification change starts as an issue carrying: a justification with a citation, a statement of what it breaks, and test-vector obligations. Each file here is one complete issue body in that shape, titled and labeled per CLAUDE.md (“label the issue with the section it touches”).

“Proposed direction” in each issue is a starting point for discussion, not a decision — the ones marked needs cryptographic review must not be closed by engineering judgment alone. That rule is unchanged by the Gate 0a provisional adoptions below: adopting a proposed direction provisionally, in marked spec text, with the tracker issue left open, is not closing it.

#IssueFileSectionBlocksCrypto review
G1#1G01-key-commitment-construction.md§4.6, §3.1all envelope/commitment vectors; ADR-0002Yes
G2#2G02-argon2id-parameters.md§7.3Argon2id blind-index vectorsYes
G3#3G03-truncation-bit-semantics.md§7.2, §7.4all blind-index vectorsNo
G4#4G04-tenant-id-null-encoding.md§6.2context vectors; any absent-tenant envelope vectorYes
G5#5G05-error-precedence.md§9, §3.4, §6.3most of errors/crypto.jsonPartly
G6#6G06-readonly-mode-error.md§9, §10.3errors/policy.json mode casesNo
G7#7G07-xchacha-normative-source.md§4.2suite 0x0002 vector confidencePartly
G8#8G08-blind-index-stored-representation.md§7blind-index storage assertions; adapter DDLNo
G9#9G09-async-blind-index.md§11.1none (API surface; L4 ergonomics)No
G10#10G10-plaintext-length-bound.md§3one boundary behaviorNo
G11#11G11-purpose-grammar.md§6.1, §7.2context negative vectorsNo
G12#12G12-unique-constraint-contradiction.md§7.10, §7.4adapter DDL guidanceNo
G13#13G13-prisma-in-rewrite-vs-reject.md§10.2, §7.10Prisma conformance wordingNo
G14#43G14-context-field-length-bound.md§6.1, §6.2 (§5.3, §7.2)one boundary behavior; max-context vectors in context/, envelope/, kdf/No
G15 ✅#48G15-core-pins-without-spec-text.md§3.1, §3.4, §9, §10.3, §11.1, §4.8; docs/09 §7errors/ fmt_ver and rotate cases; nfc-casefold-v1 portability vectorsNo
G16#60G16-index-boundary-and-unicode-pin.mddocs/09 §7, §7.1; §7.5, §7.6, §10.2; docs/12, docs/13the adapter obligation §7.1 claims is written down; IndexDeclaration.on_unindexable; text-boundary and unindexable-value vectors in blind-index/; the pin bump that is free before freezeNo — Part D decides it carries no reviewer dependency either

G15 closed 2026-08-24. Parts A, B and C as proposed plus three additions (§3.1’s 111-byte floor on a future format version, and byte-exact comparison and the either-arms rule in §4.8); part D’s item 4 closed the other way — nfc-casefold-v1 gains a post-fold NFC, because the proposed composition was measured to leave eight assigned Greek code points failing to collide with their own uppercase spellings. Full reasoning and consequence sweep in docs/07 §7.

Provisional adoptions (Gate 0a, 2026-08-22)

These are not closures and must not be read as any. The Phase 0 exit gate is now split (PRD §8): Gate 0a permits implementation, Gate 0b — two credentialed reviewers — permits freezing. G1, G2, G4, G5 and G7 are the five gaps that cannot close by engineering judgment, and they remain open on the tracker. What changed on 2026-08-22 is only that each issue’s proposed direction is now written into the spec as normative text carrying a [PROVISIONAL] marker, so that vectors and cores have something concrete to be built against.

Every one of these is expected to be what the eventual review changes. Spec §4.8 is the containment: the constructions below are reachable only through provisional suite identifiers (0xFF01, 0xFF02), an implementation refuses to write under one without affirmative out-of-band arming, and anything written that way is identifiable afterwards from stored bytes alone.

#Provisionally adoptedSpec text carrying the markerCloses at Gate 0b via
G1commitment = HKDF-SHA-512(ikm = record_key, salt = "", info = "fieldseal-commit-v1", len = 32), verified constant-time before AEAD open. Written into §4.6 on 2026-08-23 — between 2026-08-22 and then the formula existed only in this draft and the two cores, and this row overstated what the spec said§4.6, §3.1Q2
G2Narrowed 2026-08-22. Argon2id, password = normalized plaintext, salt = HKDF-SHA-512(index_key, info fieldseal-argon2-salt-v1, 16 B), v0x13/t3/m32MiB/p1/out64. Argon2’s K and X forbiddenK was in the original proposal and is ruled out on portability (unreachable in Python), which is an engineering finding the project settled itself§7.3Q3 — now a single question: is salt-only keying sound?
G4A presence bitmap over the optional context fields, making canonical_context injective across absent vs. zero-length§6.2Q4
G5Not an order — a rule about orders. §9 requires each Gate 0a implementation to pin a decrypt-path order and declare it in its conformance report (docs/14 §4 pinned_decisions.decrypt-order); the spec itself carries no state machine. Both shipped cores declare docs/09 §3.2’s order, reporting COMMITMENT_INVALID where context mismatch and key confusion are indistinguishable (corrected 2026-08-23; the row previously read as though §9 pinned the order)§9Q5
G7Suite 0xFF02 retained naming libsodium’s crypto_aead_xchacha20poly1305_ietf_*, so the two-suite mechanics stay exercised; the normative-source question is untouched. CFRG input 2026-08-23 (see G07, option 3): the draft is citable as archived, and §5.3 already supplies the extended nonce, so RFC 8439 ChaCha20-Poly1305 under the derived key is now the proposed direction — still open, still the reviewers’ call§4.2Q6

A provisional adoption is reversed, not amended, if the review rejects it: the spec text is replaced, the affected vector family regenerates, and the provisional suite identifier retires. The [PROVISIONAL] marker in each spec section names its tracker issue and its reviewer question so the reversal path is discoverable from the spec alone.

Closure log

Closures only. Provisional adoptions are recorded in the section above and are deliberately absent here.

#ResolvedResolution
G32026-08-08Adopted as proposed: spec §7.2 pins truncate bit-exactly (leading ⌈b/8⌉ bytes, MSB-first bit numbering, trailing bits of the final byte zeroed); §7.4 cross-references it; §12 requires b mod 8 ≠ 0 vectors. Unblocks blind-index/hmac.json fully; argon2id.json still waits on G2. Tracker #3 to close on push.
G62026-08-09Adopted as proposed, all three items: spec §9 adds MODE_VIOLATION (one code for all modes, raised at the API boundary before any crypto, so it does not prejudge G5’s decrypt-path ordering); §10.3 is rewritten onto two explicit axes — non-envelope read behavior and whether ciphertext-producing operations are permitted — with readonly taking permissive’s pass-through, refusing encrypt/rotate, and explicitly permitting blind_index. The orthogonal-knob alternative is recorded as considered and rejected, and §10.3 states the cost plainly: three named modes cover three of the four combinations the two axes allow, and the omitted one (raise on non-envelope input and refuse writes) is legitimate — a fully migrated read replica would want it — so the spec flags it as a present limitation instead of pretending the combination is useless. Unblocks errors/policy.json in full. Tracker #6 to close on push.
G82026-08-09Adopted as proposed: new spec §7.11 makes the stored form the raw ⌈b/8⌉ bytes in a binary column (MUST), with declared-per-column lowercase hex as the only alternative (MAY), exact byte or string equality, and a binary-collation requirement on the column. §7.2 and §7.8 cross-reference it; §12 and docs/08 §4.4 add an expected.stored assertion. The adapters’ interim recommendation in docs/12/13 is now normative. Unblocks the blind-index/ storage assertions. Tracker #8 to close on push.
G92026-08-09Adopted with a modification: spec §11.1 now permits optional async companions under four normative constraints (sync mandatory and primary; byte-identical output and identical error codes; no sync-implemented-by-blocking-on-async; only L4 adapters may depend on one), and §10 L4 extends to async index derivation. The proposal’s names were not adopted — the spec leaves the shape to the language bindings, since a companion is consumed only by an L4 adapter written against one core in one language and no cross-implementation test can observe it. docs/08 §5 and docs/14 §4 carry the dual-path harness obligation. Tracker #9 to close on push.
G102026-08-09Adopted as proposed, plus a ceiling-not-guarantee clause: new spec §3.5 bounds plaintext at 2³¹−1 bytes on encrypt, decrypt (pre-allocation), and rotate, and §9 gains a dedicated LENGTH_EXCEEDED rather than overloading MODE_VIOLATION. A runtime failing below the bound with an allocation error stays conformant; the exact testable requirement is that 2³¹ bytes yields LENGTH_EXCEEDED. §12 exempts it from the vector rule (a 2-GiB file is not a repository artifact) and docs/14 §4’s new out_of_band block makes the substitute assertion visible in the conformance report instead of absent from it. Tracker #10 to close on push.
G112026-08-09Adopted as proposed, partial close: spec §6.1 pins index-id = [a-z0-9-]{1,32} in ABNF, refused at declaration time, echoed in §7.2. Uppercase being unrepresentable removes case drift rather than adjudicating it. This settles the grammar only — canonical_context injectivity, including the absent-tenant_id encoding, remains G4’s, and §6.1 states that rather than letting this closure imply otherwise. context/ gains four negative declarations. Tracker #11 to close on push, with the carve-out noted in the closing comment.
G122026-08-09Adopted as proposed: spec §7.10’s unique-constraint row becomes No for blind indexes as well as ciphertext, since §7.4 mandates collisions, and prose below the table carries why the constraint is incorrect rather than merely unhelpful, the application-level fallback, and the §13.6 pointer. The fallback’s race is stated concretely — READ COMMITTED lets both transactions insert, so correctness needs SERIALIZABLE or an advisory lock on the index value, which leaks nothing further because the index value is already stored in the clear. Unblocks adapter DDL guidance in docs/12 and docs/13. Tracker #12 to close on push.
G132026-08-09Adopted as proposed, plus a prefix-index guard: spec §10.2’s Prisma bullet scopes the in: MUST — reject unless rewritten to the declared blind index as §7.10 membership with §7.5 re-verification, and reject whenever the rewrite cannot be guaranteed — and states the principle behind it (never silently mis-serve the query, not never serve it correctly). Because scoping the MUST invites the same move on startsWith:, the bullet now forbids silently rewriting it onto a §7.9 prefix index, that rewrite being sound only when the operand length equals the declared prefix length. docs/13’s documented §10.2 deviation is removed. Tracker #13 to close on push.

When an issue closes, sweep the tech-spec suite for the corresponding G<n> markers (grep -rn "G<n>" docs/07* docs/08* docs/09* docs/1[0-5]* docs/adr docs/issues) and update every dependent section — that sweep is part of closing, per docs/07-implementation-plan.md §6. Namespace caveat: docs/00-research-memo.md uses G1–G6 for market gaps (and docs/06-verification-log.md cites them) — a bare repo-wide grep collides with that older namespace; the sweep scope above avoids it.