Phase 1 Implementation Plan

Date: 2026-08-08 · Status: Draft 1 · Purpose: the engineering plan for Phase 1 (“prove the format,” PRD §8): what gets built, in what order, gated by what, defined-done how. This is the umbrella document for the tech-spec suite:

DocCovers
docs/08-test-vector-spec.mdVector suite: formats, schemas, harness contract, injection, cross protocol
docs/09-core-architecture.mdLanguage-agnostic core design all implementations follow
docs/10-core-python.md / docs/11-core-typescript.mdPhase 1 core bindings
docs/12-adapter-django.md / docs/13-adapter-prisma.mdPhase 1 adapters
docs/14-conformance-ci.mdConformance claims, CI, the cross job
docs/15-tooling.mdBackfill and leakage estimator (P1; constraints known now)
docs/adr/Decision records for the Phase-1-blocking choices (spec §13.1, §13.2)

Standing constraint: none of this authorizes writing code before the Phase 0 exit gate. The gate (PRD §8) is: at least two people with real cryptographic credentials have read the spec and their objections are addressed or documented. Independent review is a release gate, not a nice-to-have (PRD §9, top risk). This plan exists so that Phase 1 starts fast when the gate opens — not instead of the gate.


1. Gate 0 — what must close before code

In dependency order:

  1. ADR-0001 (envelope: profile AWS or define fresh) — everything serializes the envelope; spec §13.1 calls it the highest-leverage open decision. The expressibility-mapping task in the ADR can start immediately; it needs no reviewer.
  2. ADR-0002 (suite 0x0001 AEAD) — determines envelope arithmetic and whether gap G1 applies to the mandatory suite.
  3. The spec-gap issues G1–G13 (§5 below) — each is a spec issue per CONTRIBUTING.md (justification + citation, breakage statement, vectors); ready-to-post drafts live in docs/issues/. Scope within the gate: G1–G8 (the vector-blocking set) must merge before M0; G9–G13 must be filed before M0 but may close during Phase 1 alongside the workstreams they touch (G8/G12 before adapter DDL ships, G13 before the Prisma conformance claim).
  4. Cryptographic review covering, at minimum: the per-write-key nonce argument (§4.4/§5.3), the commitment construction chosen for G1, the blind-index construction (§7), and the envelope-format novelty question posed in ADR-0001.

Items 1–3 produce artifacts reviewers can react to; run them concurrently with reviewer recruitment, not before it.

2. Workstreams and dependency graph

Gate 0 ──► WS-A vectors & harness ──► WS-B Python core ──► WS-E Django adapter ──► WS-G example app
                    │                      │                                            ▲
                    │                      ▼                                            │
                    └────────────► WS-C TypeScript core ──► WS-F Prisma adapter ────────┘
                                           │
              WS-D cross CI ◄──────────────┘   (needs both cores)
              WS-H docs & certification page   (parallel, continuous)
  • WS-A — vector generator (tools/vector-gen/, Python), JSON Schemas, MANIFEST.json, errors/format+policy families (authorable now), remaining families as G-issues close. Output: vector suite v0.1.0-provisional.
  • WS-B — Python core per docs/10. Also hosts the generator’s primitive checks (docs/08 §7).
  • WS-C — TypeScript core per docs/11, under the independence rule (docs/11 §6): built from spec + docs only, no reading Python source, until first freeze. Divergences it finds are recorded in §7 below and are the cheap substitute for a second review pass.
  • WS-D — CI per docs/14: per-core workflows first, then the N×N cross job. The cross job green is the Phase 1 exit criterion (PRD §8).
  • WS-E / WS-F — adapters per docs/12/docs/13, each shipping its coverage matrix and refusal tests.
  • WS-G — one end-to-end demonstration app (PRD Phase 1 deliverable). Proposal: a small patient-directory service with both a Django and a Prisma frontend over one shared Postgres schema — making the demo itself a live cross-language proof (a row written by either stack reads from the other), which is the project’s pitch in runnable form.
  • WS-H — “Certifying an implementation” page (docs/14 §6), operational docs skeletons (threat-model-as-deployed, KMS degradation modes — PRD DO-1/DO-6), and keeping docs/06-verification-log.md current as library-fact [VERIFY] flags in docs/10–13 get resolved.

3. Milestones

#MilestoneExit test
M0Gate 0 closedADRs 0001/0002 ACCEPTED; G1–G8 spec PRs merged with vector obligations attached; two credentialed reviewers’ objections addressed or logged as open questions
M1Python core passes provisional vectorsconformance-report.json with fail: 0 at vectors v0.1.0-provisional; property/fuzz suites green
M2Independent reproductionTypeScript core reproduces every expected value from spec+docs alone; divergence log triaged (each = impl bug or spec ambiguity → new G-issue); vector suite frozen at v0.1.0
M3Cross CI greenN×N produce/consume matrix green including self-pairs, on merge and nightly — the PRD Phase 1 exit criterion, permanent gate thereafter
M4Adapters at levelDjango L1+L2(a)(b), Prisma L1+L2(b)-with-throws; coverage matrices generated from green test suites; refusal tests complete
M5Phase 1 doneDemo app runs the two-frontend cross-language scenario; docs current; Phase 1 retro decides Phase 2 entry

Sequencing within the ~12-week PRD window is deliberately not calendarized per-week here: there is no calibration data for this team on this kind of work, and invented week numbers would be false precision (the PRD’s own DO-5 makes measured effort a deliverable). Relative sizing instead: WS-A M · WS-B L · WS-C L · WS-D M · WS-E L · WS-F L · WS-G S · WS-H S — with WS-B/WS-C not parallelizable with each other under a single implementer by design (M2’s independence value assumes different authors or at minimum a hard sequencing with the vectors frozen in between; if one person writes both cores, say so in M2’s report — it weakens the independence claim and the honest move is stating it).

4. Definition of done (applies to every workstream)

  • Tests green in CI including the vector harness at a named suite version; for adapters, the path matrix and refusal tests.
  • Every [VERIFY] flag in the workstream’s tech spec resolved: confirmed (flag removed, fact dated) or corrected (spec doc updated + note in docs/06-verification-log.md style).
  • Docs shipped with the artifact: README, coverage matrix (adapters), honest-limitations section (per spec’s MUST-document items — §3.3 overhead, §5.5 cache exposure, §7.3 Argon2id cost, §8.1 KMS dependency).
  • No overclaim audit: someone other than the author reads the README against CONTRIBUTING.md’s two standing rules.

5. Spec gaps found while writing the tech-spec suite

Each becomes a spec issue (label: the section it touches, per CLAUDE.md); full ready-to-post drafts live in docs/issues/. “Proposed direction” is a starting point for the issue, not a decision — several need a cryptographer’s sign-off (noted). Consolidates docs/08 §9 (G1–G8), G9–G11 found in the core/adapter specs, and G12–G13 found in the suite’s consistency review. The table is the as-found record; closure status lives in the §7 decision log and docs/issues/README.md.

#SectionGapProposed directionNeeds crypto review
G1§4.6, §3.1Commitment construction undefined (32 B reserved, no formula)Derive a commit value from the record key via the suite KDF with a dedicated label (the AWS ESDK v2 key-commitment shape its algorithm-suite docs describe; also cf. Albertini et al., How to Abuse and Fix Authenticated Encryption Without Key Commitment, USENIX ‘22); verify constant-time pre-decryptYes
G2§7.3Argon2id invocation incomplete: parallelism, output length, version, and the password/salt/secret layout unspecified (deterministic salt strategy needed — Argon2 requires a salt, but the index must be deterministic per key)Full parameter tuple in the spec, RFC 9106 as the normative reference; salt derived from the index key via HKDF with a dedicated labelYes
G3§7.2, §7.4truncate(raw, b bits) bit semantics undefinedKeep the leading ceil(b/8) bytes, zero the trailing 8·⌈b/8⌉−b bits of the final byte (MSB-first); state byte orderNo (but vectors pin it)
G4§6.2tenant_id = null encoding unspecified (omission defined only for row_id); null vs zero-length ambiguityDefine explicitly; consider a field-count or presence-bitmap discriminator so omission can never alias a crafted valueYes (canonical-encoding forgery surface)
G5§9, §3.4Error classification order undefined: format → policy → key → commitment → AEAD precedence, and how context mismatch (which surfaces as a wrong derived key under dual binding) maps to AAD_MISMATCH vs COMMITMENT_INVALIDThe decrypt state machine in docs/09 §3.2, pinned normatively; acknowledge the §6.3 ambiguity honestly in §9Partly
G6§9, §10.3No error code for mode violations (encrypt() in readonly); whether readonly may compute blind indexes for queries unstated; readonly’s non-envelope read behavior undefined (§10.3 defines the other two modes by exactly that behavior)Add a code; clarify readonly = no writes, index computation for WHERE permitted, non-envelope input passes through as permissiveNo
G7§4.2Suite 0x0002’s XChaCha20-Poly1305 has no IETF RFC; no normative definition namedName libsodium’s construction as normative (draft-irtf-cfrg-xchacha expired), or drop 0x0002 to keep the registry leanPartly
G8§7Blind-index stored representation undefined — two languages sharing one database must byte-agreeRaw bytes, length ceil(b/8), binary column; hex alternative for text-only stores, declared per columnNo
G9§11.1Sync-only blind_index blocks the Node event loop 10–100 ms per Argon2id term (docs/11 §2); L4-capable adapters (Prisma) could use an async variant the spec currently forbidsOptional async companions for L4 adapters, sync remains mandatory and primaryNo
G10§3No plaintext length bound; implementations should reject at the same limit2³¹−1 bytes at the API boundary (docs/09 §4)No
G11§6.1, §7.2purpose/index-id grammar unconstrained (charset, length) — interacts with G4’s encoding-aliasing concernConstrain to [a-z0-9-]{1,32} after the index: prefixNo
G12§7.10, §7.4§7.10 permits unique constraints “on the index column only” while §7.4 mandates collisions (P × 2^(−b) ≥ 2) — a UNIQUE truncated index rejects legitimate distinct values§7.10 row becomes “No”; application-level uniqueness fallback documented with its race honestyNo
G13§10.2, §7.10§10.2’s Prisma bullet unconditionally MUST-rejects in: while §7.10 supports membership (“N indexes OR’d”)Scope the MUST to non-rewriting adapters; a correct index rewrite + §7.5 re-verify is conformantNo

6. Risk register (delta to PRD §9 — implementation-phase risks)

RiskMitigation
A G-issue closes differently than a tech spec assumedThe specs mark every dependent section with the G-number; closure includes a sweep of those markers (grep for G<n> across docs/07–15, docs/adr, docs/issues — not repo-wide: docs/00 uses G1–G6 as market-gap numbers, a different namespace)
Sync Argon2id in Node is a product-killer for Prisma usersSurface early: benchmark in WS-C week one at spec-minimum parameters; if unacceptable, G9 escalates from nice-to-have to Phase 1 spec change
Unicode normalization drift across languages silently breaks shared indexesnfc-casefold-v1 pinned to a vendored folding table, not platform Unicode (docs/09 §7 flag); vectors carry pre- and post-normalization forms
Library-fact flags wrong (pyca XChaCha, sync argon2 APIs, DMMF access)Every [VERIFY] is a tracked task at workstream start, not discovered mid-build
Single-author cores weaken M2’s independence claimState it in the M2 report if so; recruit a second implementer for one core as the preferred fix (also feeds the OpenSSF multi-maintainer requirement, PRD §9)
Vector generator becomes a de-facto oracledocs/08 §7 rule: agreement of two independent implementations freezes a vector, never the generator alone
Windows/line-ending corruption of vector bytesGenerator writes bytes; .gitattributes already pins LF; harness verifies MANIFEST hashes before use

7. Decision & divergence log

Append-only, verification-log style (docs/06): every M2 divergence, every G-issue outcome, every ADR closure with date and consequence sweep.

  • 2026-08-08 — Suite-wide consistency review (two independent read-through passes) applied 26 corrections across docs/08–15; the substantive ones: decrypt-side suite_id sourcing pinned to the parsed header (docs/09 §3.2 step 4 — a write-suite-sourced context breaks mixed-suite reads and rotation); example blind-index declarations corrected to the §7.4 band (16 → 15 bits at P = 100,000); adapter-owned core-client construction (the index registry cannot reach construction-time validation any other way); the determinism-injection arming gate (FIELDSEAL_TEST_MODE); and two newly found spec-internal contradictions filed as G12 (§7.10 unique-constraint row vs §7.4 collision mandate) and G13 (§10.2 Prisma in: reject vs §7.10 membership support).
  • 2026-08-08docs/02 §7.1 cross-reference corrected: the prefix-index mechanism is §7.9, not §7.7 (pure pointer typo; prose-clarification bar per CONTRIBUTING.md).
  • 2026-08-08G3 resolved (issue #3), by engineering judgment per its no-crypto-review flag: truncate(raw, b) pinned normatively in spec §7.2 — keep the leading ⌈b/8⌉ bytes, zero the trailing 8·⌈b/8⌉−b bits of the final byte, bits numbered MSB-first (byte-prefix property; matches §3.1/§6.2 network byte order). §7.4 cross-references the definition; §12 adds the b mod 8 ≠ 0 vector obligation (≥3 non-aligned lengths + one aligned control per blind-index file). Marker sweep completed: docs/08 §4.4/§9, docs/09 §3.3 pipeline, docs/10/11 module maps, issue draft + README. Consequence: blind-index/hmac.json is now fully authorable; Argon2id expected values still wait on G2.
  • 2026-08-09G6 and G8 resolved (issues #6, #8), both by engineering judgment per their no-crypto-review flags, both adopted as proposed. G6: spec §9 gains MODE_VIOLATION, and §10.3 is rewritten onto two explicit axes (non-envelope read behavior; whether ciphertext-producing operations are permitted) so that all three modes are defined on the same terms — readonly inherits permissive’s pass-through, refuses encrypt/rotate, and expressly permits blind_index, since an index computed for a WHERE clause is not a write. The code is raised at the API boundary before any cryptographic processing, which deliberately keeps it clear of G5’s still-open decrypt-path ordering. Writing the two axes out exposed something the three-mode table had hidden: the modes cover three of four possible combinations, and the missing one (raise on non-envelope input and refuse writes) is a configuration a fully migrated read replica would legitimately want. §10.3 records that as a stated limitation rather than adding a fourth mode speculatively. G8: new spec §7.11 pins the stored representation (raw ⌈b/8⌉ bytes in a binary column as the MUST, declared-per-column lowercase hex as the MAY, exact equality under a binary collation), numbered as §7.11 rather than inserted mid-section because §7.9/§7.10 carry live cross-references from G12, G13, docs/12 and docs/13. Marker sweeps completed across docs/08 (§4.4 vector shape gains expected.stored, §4.6 mode rows, §9 gap table), docs/09 (§3.1, §3.2, §3.3, §3.5, §9), docs/10, docs/12, docs/13, and the G03/G10 issue drafts. Consequence: errors/policy.json is now fully authorable and the blind-index/ storage assertions are unblocked, leaving blind-index/argon2id.json waiting only on G2. Gate 0’s vector-blocking set (G1–G8) is down to G1, G2, G4, G5, G7 — three flagged Yes for cryptographic review and two flagged Partly, so every remaining member of that set now waits on the reviewers rather than on us. G9–G13 stay open as planned; they were only ever required to be filed before M0.
  • 2026-08-08 — ADR-0001’s expressibility-mapping task delivered as first-pass evidence (docs/adr/0001-appendix-a-expressibility-mapping.md); headline findings: §6.3 dual-layer binding not expressible in the AWS format; per-cell embedding costs 1.4×–2.4× the fresh envelope. ADR remains OPEN pending reviewer input and the appendix’s own §8 verification items.

8. What Phase 1 deliberately does not build

Java/.NET/Go cores and their adapters (Phase 2) · TypeORM/Sequelize adapters (deferred, PRD §8) · benchmarks beyond the demo app’s incidental numbers (bench/ methodology work is Phase 2, DO-4) · any hosted service · GUI/console tooling beyond the two CLIs in docs/15.