00 — Architecture Freeze V1
Status: FROZEN — 2026-06-12. This is the final architecture before coding begins. It supersedes conflicting statements elsewhere in the corpus. Changes from here require a new ADR that explicitly amends this freeze.
Optimized for correctness, consistency, and solo-developer feasibility. No new features were added and the platform was not redesigned — this freeze only reconciles contradictions found in the pre-development review and reduces scope to a buildable V1.
1. What this freeze does
The pre-development architecture review identified 5 blocking issues — internal contradictions on the two hardest subsystems (storage, sync) plus a security trap and data-model drift. All five are resolved below, the affected ADRs / data model / service boundaries / diagrams are rewritten to agree, and all 38 ADRs are reclassified into Accepted / Proposed / Deferred so that “Accepted” once again means “binds the V1 build.”
2. The 5 blockers — resolutions
| # | Blocker (review) | Resolution (frozen) | Artifacts changed |
|---|---|---|---|
| 1 | Two storage models — whole-blob (data model) vs chunk/pack/placement (ADRs 0017/0018/0019/0020), both “Accepted” | Whole-object content-addressed blobs (BLAKE3), per tenant. No chunking/packing/placement in V1. FR-C3 delta = whole-object transfer in V1. |
ADR-0018 (rewritten, whole-object), ADR-0017 & 0020 Deferred, data model 08 |
| 2 | Data-model I2 said “delete at refcount = 0” — the naive GC ADR-0019 calls unsafe | Safe GC: deletion authorized by orphaned state + grace period + atomic zero-reference re-confirmation (CAS); dedup hit in the window flips back to committed. Refcount is a hint, never the authority. |
ADR-0019 (rewritten, whole-object), data model I2 |
| 3 | Change journal described as both “source of truth” and “projection of at-least-once events” | Journal is source of truth, CHANGE.seq assigned inside the File commit transaction (gap-free, totally ordered per tenant by construction). File owns/writes the journal; Sync reads it. |
ADR-0008 (rewritten), ADR-0024 (aligned), data model I4, service boundaries 05, diagrams 06 |
| 4 | RLS tenant context + connection pooling unresolved (“session GUC” → cross-tenant bleed under transaction-mode pooling) | Transaction-local SET LOCAL tenant context (PgBouncer transaction-mode safe); missing context = default-deny (no rows); non-BYPASSRLS app role; new pooled-bleed CI test. |
ADR-0038 (new), ADR-0007 (fixed), data model I3 |
| 5 | Data model out of sync with ADRs 0014 / 0016 / 0037 | BLAKE3 hash; composite PK (tenant_id, content_hash); TENANT_KEY + BLOB.enc_algo/dek_version (per-tenant envelope encryption); SHARE.link_token_hash (store hash only). Materialized-path move cost stated honestly. |
data model 08, ADR-0014, ADR-0037 |
3. V1 scope boundary
V1 = roadmap P0–P2 (walking skeleton → core product → correct sync). This is the portfolio-credible deliverable; it satisfies the hard correctness criteria. Everything beyond is documented direction, not committed build.
| In V1 (built) | Out of V1 (Proposed / Deferred) |
|---|---|
bitvaultd modular monolith; in-process event bus |
NATS JetStream (P3), extracted services (P4) |
| Postgres (truth + journal + outbox), one object store (MinIO/S3) | Chunking, packing, placement/federation, multi-provider |
| Commit protocol + safe whole-object GC | Resumable multipart (Proposed), previews |
RLS multi-tenancy (SET LOCAL) |
Schema/DB-per-tenant escalation |
| Sync: journal-at-commit, cursors, conflicted-copy, safety guards | Sub-file delta sync |
| Postgres-FTS search | OpenSearch content search (P3) |
| OAuth 2.1 + argon2id + API tokens; RBAC; public links (hashed token) | MFA/passkeys/DPoP step-up (Proposed); sharing abuse program (P3) |
| Envelope encryption at rest (per-tenant DEK) | Client-side E2E |
| OpenTelemetry from commit #1 | — |
| Docker Compose (lite/standard) self-host | Helm/K8s SaaS, GitOps, IaC, supply-chain, DR, env-promotion (P4) |
Dependency tiers in V1: lite (Postgres + MinIO) and standard (+ Redis). The
full tier (OpenSearch + extracted workers) is Deferred.
4. ADR reclassification (all 38)
Legend — Accepted: binds the V1 build. Proposed: agreed direction, not committed to V1 (authored into V1 only if its phase is reached). Deferred: out until a named P3+ forcing function.
Accepted (26)
| # | Title | Note |
|---|---|---|
| 0001 | Modular monolith first | Foundational |
| 0002 | Monorepo | Foundational |
| 0003 | gRPC internal / REST external; proto contract | Contracts from day 1 |
| 0004 | Postgres = metadata source of truth | — |
| 0005 | Object storage abstraction | One adapter (MinIO/S3) in V1 |
| 0006 | Outbox + event bus | Outbox + in-proc bus V1; NATS deferred to P3 |
| 0007 | Multi-tenancy RLS | Revised: SET LOCAL (see 0038) |
| 0008 | Change-journal sync + conflicted copy | Revised: journal written at commit |
| 0009 | Search as derived index | PG-FTS V1; OpenSearch deferred to P3 |
| 0010 | AuthN/AuthZ: OAuth 2.1 + RBAC | — |
| 0011 | Direct-to-storage presigned URLs | — |
| 0012 | Tiered deployment packaging | lite/standard V1; full + Helm/K8s deferred |
| 0013 | Observability: OpenTelemetry | From commit #1 |
| 0014 | Envelope encryption at rest | Revised: schema footprint; per-tenant DEK |
| 0015 | API versioning (URI /v1) |
— |
| 0016 | Content hashing: BLAKE3 | — |
| 0018 | Deduplication scope | Revised: whole-object, per-tenant |
| 0019 | Garbage collection | Revised: whole-object grace + CAS |
| 0022 | Three-tree reconciliation (client planner) | P2 |
| 0023 | Local sync database (SQLite) | P2 |
| 0024 | Cursor delta pull | Revised: aligned to journal-at-commit |
| 0025 | File watching strategy | P2 |
| 0026 | Conflict resolution policy (conflicted copy) | P2 |
| 0027 | Sync safety guards | P2 |
| 0037 | Public sharing security | Revised: hashed token; abuse program deferred |
| 0038 | RLS connection pooling (SET LOCAL) |
New — blocker-4 |
Proposed (2)
| # | Title | Why not yet committed |
|---|---|---|
| 0021 | Resumable / multipart uploads | V1 ships single-shot presigned PUT with a size cap; multipart is the agreed next increment (P1/P2) |
| 0036 | Authentication policy (MFA/passkeys/step-up/DPoP) | Basic authn is V1 (ADR-0010); this hardening is agreed but not committed to V1 |
Deferred (10)
| # | Title | Re-opens at |
|---|---|---|
| 0017 | Content-defined chunking (FastCDC) + packing | Large-file delta/dedup efficiency (post-V1) |
| 0020 | Storage placement & federation | Multi-region/residency or 2nd provider (P5+/NG9) |
| 0028 | GitOps with ArgoCD | P4 (extraction & scale) |
| 0029 | Progressive delivery (Argo Rollouts) | P4 |
| 0030 | Secrets management (External Secrets Operator) | P4 (K8s/SaaS); V1 uses env/keyfile secrets |
| 0031 | IaC with OpenTofu | P4 |
| 0032 | CI/CD supply-chain (SBOM/cosign/SLSA) | P4; V1 CI = build/test/lint |
| 0033 | Backup & DR | P4; V1 = pg_dump + object versioning |
| 0034 | Environment promotion (build once, promote digest) | P4 (multi-env); V1 single-env |
| 0035 | Machine identity (workload identity) | P4 |
5. Reconciled correctness invariants (the test list)
| # | Invariant (data model 08 §2) | Test |
|---|---|---|
| I1 | No committed version without HEAD-verified bytes; VERSION + BLOB ref + CHANGE(seq) + OUTBOX are one transaction |
Dual-write chaos (kill between PUT and commit) |
| I2 | Deletion needs orphaned + grace + atomic zero-ref re-confirm; dedup hit flips back; refcount is a hint |
GC race (re-upload during grace → not deleted) |
| I3 | Tenant isolation via RLS + transaction-local SET LOCAL; no context ⇒ no rows |
Forged-tenant_id test and pooled-bleed test |
| I4 | CHANGE.seq assigned in the commit tx ⇒ gap-free total order per tenant; journal is source of truth |
Conflict harness; read-your-writes on next pull |
| I5 | Namespace ops are byte-free (stable NODE.id); subtree move is O(descendants) path rewrite, not O(1) |
Move/copy refcount + path-update test |
| I6 | Search/notifications/meters are rebuildable projections; journal + namespace are not derived | Rebuild search index from journal |
6. Consistency guarantee
Before this freeze the corpus claimed to be “cross-linked and consistent” but contradicted itself on storage (two models), GC (safe vs unsafe), the journal (truth vs projection), the hash algorithm, the share-token storage, and the encryption schema. After this freeze:
- There is one storage model (whole-object) across the data model and every storage ADR.
- There is one GC rule (grace + CAS) in both I2 and ADR-0019.
- There is one journal status (source of truth, written at commit) in ADR-0008, ADR-0024, data model I4, service boundaries 05, and diagrams 06.
- The data model matches ADR-0014 (encryption), ADR-0016 (BLAKE3), ADR-0018 (composite PK), and ADR-0037 (hashed token).
- The RLS pooling decision exists and is referenced (ADR-0038 ↔ ADR-0007 ↔ I3).
Affected documents carry an Architecture Freeze V1 (2026-06-12) banner pointing here.
7. Coding may begin
The five blocking conditions from the review are met. The remaining review items (scalability/operability hardening — outbox partitioning, quota reserve, key-shred runbook) are non-blocking and tracked against their phases. Build P0 next.