Add full codebase audit with prioritized remediation plan

c91327d60c52 · AtlantisPleb · · parent 46e86d51f709

Add full codebase audit with prioritized remediation plan

Audit the entire repository at abda46a: security findings (gzip bomb
on anonymous git-upload-pack, comment authorship gap, unthrottled
insert paths), code quality debt (triplicated tool-step ledger,
oversized ChatLive and Conversations), testing gaps (ungated coverage
floor), and infrastructure weaknesses. Findings cite file paths and
line numbers; a ranked plan orders the fixes.

Deploy story

What this commit did to the running system — joined from the forge receipt chain, the part a commit page elsewhere cannot show.

Not deployed through the forge lane

No push, promotion, build, or deploy receipt references this commit (receipts are scanned over a bounded recent window). Changes shipped by full node replacement carry their proof in the release gate receipt instead.

Changed files

  • added docs/2026-08-21-full-codebase-audit.md

Diff

1 file changed, +260 -0

docs/2026-08-21-full-codebase-audit.md added +260

@@ -0,0 +1,260 @@

1
# Full codebase audit
2
3
**Date:** 2026-08-21
4
**Commit measured:** `abda46ab664e` (origin/main)
5
**Scope:** the entire repository: `lib/`, `test/`, `config/`, `assets/`, `priv/repo/migrations/`, `ops/`, `infra/`, `rel/`, and `docs/`
6
**Method:** manual review of representative and highest-risk modules in each layer, cross-checked with targeted searches. Every finding cites file paths and line numbers current at the measured commit.
7
8
---
9
10
## 0. Verdict
11
12
This is a high-discipline codebase with unusually strong documentation, OTP practice, and operational tooling for a pre-production project. The structural debt is real but concentrated in exactly three places, all sharing one root cause: text chat, voice, and delegated work grew parallel implementations of the same turn and tool-step machinery instead of sharing one.
13
14
The most important security gap is an unauthenticated denial-of-service vector in the forge git HTTP endpoint. The most important process gap is that nothing enforces the 83% coverage threshold or runs verification remotely; every guarantee depends on a developer's local machine running `ops/ci/gate.sh`.
15
16
| Metric | Value |
17
|---|---|
18
| Application modules (`lib/`) | 502 |
19
| Application LOC | ~90,000 |
20
| Test files | 233 (~44,700 LOC) |
21
| Migrations | 77 (all with rollback paths) |
22
| Measured line coverage (2026-08-20 audit) | 83.14% overall |
23
| TODO / FIXME / `IO.inspect` in `lib/` | 0 |
24
| Committed secrets found by sweep | none |
25
26
---
27
28
## 1. What this codebase is
29
30
OpenAgents is a Phoenix 1.8 application (~70 bounded contexts) that combines:
31
32
- A public website with GitHub-authenticated accounts, an issues/projects surface mirroring the GitHub REST API shape under `/api/v3`.
33
- A chat surface with inference turns, tool steps, and provenance capture.
34
- Voice sessions, delegated work jobs, and a SCV worker role.
35
- A forge: canonical git hosting over smart HTTP with scoped personal access tokens.
36
- A distributed runtime: Horde registries and supervisors across nodes, Raft (`:ra`) for cluster session ownership, hot-upgrade releases with appup generation.
37
- Staging infrastructure as Terraform with tests asserting its own security posture.
38
39
The web role and the SCV worker role boot different supervision trees from one codebase (`lib/openagents/application.ex`).
40
41
---
42
43
## 2. What is strong
44
45
These are verified strengths worth protecting as the codebase evolves.
46
47
### 2.1 Authorization design
48
49
- Visibility is enforced in SQL, not after loading. Public reads filter `repository.visibility == "public"` inside queries (`lib/openagents/repositories.ex:45`), so a forgotten filter fails closed rather than leaking rows.
50
- Every `/api/v3` write route sits behind a bearer-token pipeline requiring scope `forge:write` (`lib/openagents_web/router.ex:235`). Session-cookie routes keep CSRF protection without exemptions.
51
- Admin status re-verifies on every LiveView event, not just at mount (`lib/openagents_web/user_auth.ex:107`), closing the stale-socket elevation hole. Admin identity anchors on immutable numeric GitHub IDs.
52
- `RouteAuthority` (`lib/openagents_web/route_authority.ex`) is an executable inventory of every route's auth classification, and a test fails when a new route ships unclassified. This is a rare structural guardrail; keep it current.
53
54
### 2.2 Token and secret hygiene
55
56
- API tokens are 256-bit random secrets stored as SHA-256 digests with `redact: true`, returned to the caller exactly once, capped at 90-day lifetimes, compared with `Plug.Crypto.secure_compare` under `FOR UPDATE` row locks (`lib/openagents/api_tokens.ex:52`).
57
- The OAuth flow uses PKCE S256 with single-use server-side state digests and TTL pruning (`lib/openagents/github_oauth.ex:18`).
58
- Device-flow and machine-pairing flows store only digests, enforce single-claim semantics under row locks, and hand secrets over once.
59
- A repo-wide sweep for AWS keys, private key blocks, GitHub tokens, OpenAI-style keys, and credential-bearing URLs found nothing committed. Detection regexes exist where such strings appear, which is exactly right.
60
- Parameter filtering goes well beyond the Phoenix default: `authorization`, `verifier`, `poll_secret`, `transcript`, and more are scrubbed (`config/config.exs:10`). `OpenAgents.LogSafety` plus `ops/ci/private-log-scan.exs` scan logs in CI with the same detection rules the runtime uses.
61
62
### 2.3 OTP architecture
63
64
- Horde registry/supervisor pairs host cluster-wide singletons with `process_redistribution: :passive`; work jobs register under `{:work_job, job_id}` and treat `{:already_started, pid}` as success (`lib/openagents/work.ex:126`).
65
- `lib/openagents/cluster/session_registry.ex` is a pure `:ra_machine` holding ownership generations with fencing, so zombie writes are rejected structurally rather than by convention.
66
- Concurrency correctness leans on explicit row locks (`get_for_update!`) inside transactions — 105 `Repo.transaction` calls and 55 lock sites — instead of optimistic hope. Idempotent terminal paths handle races explicitly (for example `{:error, {:already_terminal, job}} -> {:ok, job}` at `lib/openagents/work.ex:525`).
67
- Feature-gated children enter the tree through clean `maybe_*` functions (`lib/openagents/runtime_supervisor.ex:54`); recovery workers run once at boot as `restart: :temporary`.
68
69
### 2.4 Testing culture
70
71
- Assertions target values and database state, not markup. LiveView tests click by DOM id or role, then assert on context state. Of roughly 400 selector-based interactions reviewed, only two page-level smoke checks assert raw HTML.
72
- No mock library anywhere. External services sit behind behaviour boundaries with configured test fakes; the forge integration test runs real git over real local HTTP against a temporary directory. The suite is network-free.
73
- Infra safety is pinned by ExUnit: `test/openagents/staging_candidate_contract_test.exs` reads the Terraform templates and asserts immutability, digest-pinned builder images, and absence of privileged flags. If someone loosens infra posture, tests fail before deploys do.
74
- The repository audited itself: [docs/2026-08-20-test-coverage-audit.md](2026-08-20-test-coverage-audit.md) measured 79.14% coverage, found the issues/projects layer at 33%, mapped the gap to exact TDD plan steps, added 279 tests, and published the receipts — including five defects the new tests caught.
75
76
### 2.5 Operations pipeline
77
78
- `ops/ci/gate.sh` runs thirteen stages (compile, production compile, precommit, cluster tests, JavaScript tests, relup proof, contracts, staging infra validation, release smoke) and writes a schema-versioned receipt keyed by SHA into `.git/openagents/release-gate-receipts/`. Downstream scripts verify receipts before building or promoting anything.
79
- `ops/deploy/build-image.sh` refuses dirty worktrees, requires the gate receipt, sets reproducible timestamps, then runs Erlang inside the built image to assert the embedded revision matches the git SHA.
80
- Container builds are digest-pinned, run as a non-root user, and install npm dependencies with scripts disabled. `.dockerignore` excludes `.env*`, `*.pem`, and `*.key`, so secret-shaped files cannot enter the build context by accident.
81
- Terraform uses write-only ephemeral database passwords, least-privilege custom IAM roles, private-only Cloud SQL, immutable tags, and bucket public-access prevention — asserted by `tftest` files under `infra/staging/tests/`.
82
83
### 2.6 Code hygiene
84
85
Zero `TODO`/`FIXME` markers, zero debug prints, effectively no commented-out code, shallow nesting throughout even in long functions, and moduledocs that explain rationale and cite invariants from [INVARIANTS.md](../INVARIANTS.md). All 77 migrations have rollback paths; none disable DDL transactions or migration locks.
86
87
---
88
89
## 3. Security findings
90
91
Ordered by severity. Each item names the fix owner area and concrete remediation.
92
93
### 3.1 Summary
94
95
| ID | Severity | Finding |
96
|---|---|---|
97
| S1 | High | Unauthenticated gzip decompression bomb on anonymous `git-upload-pack` |
98
| S2 | Medium | Comment update/delete ignores comment authorship |
99
| S3 | Medium | No rate limiting on unauthenticated table-inserting endpoints |
100
| S4 | Medium-low | Fleet container disables seccomp and AppArmor |
101
| S5 | Low | Client-controllable bookkeeping fields survive issue/comment creation |
102
| S6 | Low | Malformed numeric path segments return 500 instead of 404 |
103
| S7 | Low | CSP allows any websocket origin and inline styles; no `form-action` |
104
| S8 | Low | `OPENAGENTS_SECURE_COOKIES` evaluated at compile time |
105
| S9 | Low | Origin allowlist validator exists but is dead code |
106
| S10 | Low | Machine bearer token travels in websocket query string |
107
| S11 | Low | One-year session lifetime without step-up on sensitive actions |
108
| S12 | Informational | Health endpoints disclose build revision publicly |
109
110
### 3.2 Details
111
112
**S1 — gzip bomb on anonymous `git-upload-pack` (high)**
113
114
`lib/openagents/forge/git_http.ex` caps compressed input at 512 MiB (`@max_body_bytes`, line 30) but authorization runs before the body is read, so anonymous clients can POST gzip-encoded bodies to any public repo's `git-upload-pack`. `safe_gunzip/1` (line 354) calls `:zlib.gunzip/1`, materializing the full decompressed output in the request heap with no output cap. DEFLATE ratios near 1000:1 allow multi-hundred-GiB logical expansion, ending in an OOM kill of the BEAM. Remote, unauthenticated, trivially scriptable.
115
116
Remediation: stream-decompress with a hard byte cap using incremental inflation (`:zlib.safeInflate` chunked loops), reject beyond a fixed decompressed limit, and apply the same bound to `receive-pack`. Alternatively drop `content-encoding` handling entirely and require authentication for `upload-pack`.
117
118
**S2 — comment authorship ignored on update/delete (medium)**
119
120
`CommentController.update/2` and `delete/2` (`lib/openagents_web/controllers/comment_controller.ex:62`) resolve a writable repo membership and then call `Issues.update_comment/2` with no check that the acting principal wrote the comment (`lib/openagents/issues.ex:304`). Any member with `contributor` or above can rewrite or delete anyone's comment while the rendered JSON keeps the original author snapshot — silent impersonation within a repository, diverging from the mirrored GitHub contract.
121
122
Remediation: compare `comment.author_user_id` against the acting principal; allow only the author or elevated roles (`owner`/`maintainer`); return 404 otherwise to avoid enumeration.
123
124
**S3 — unthrottled unauthenticated insert paths (medium)**
125
126
The only rate limits in the tree cover chat turns, voice admission, and device-flow slow-down polling. Nothing throttles `POST /api/v3/device/authorizations` (one row per call, 600-second TTL), controller pairings, or `POST /auth/github` (rows retained up to 24 hours, `lib/openagents/accounts.ex:194`). Anonymous clients can inflate these tables and amplify database load.
127
128
Remediation: add an IP-scoped throttling plug in front of pairing, device authorization, and OAuth start; cap pending rows per source; add modest quotas on public `/api/v3` reads.
129
130
**S4 — container isolation disabled (medium-low)**
131
132
`infra/staging/templates/fleet-startup.sh.tftpl:200` passes `--security-opt seccomp=unconfined --security-opt apparmor=unconfined` plus host networking, apparently as a Bubblewrap workaround. Remediation: author a tailored seccomp profile permitting only the namespace syscalls Bubblewrap needs, restoring layered defense.
133
134
**S5 — mass assignment on create paths (low)**
135
136
`Issue.changeset/2` casts `state`, `locked`, `closed_at`, and `comments`; `Comment.changeset/2` casts `created_at` (`lib/openagents/issues/issue.ex:30`, `comment.ex:20`). Writers can create pre-closed issues with fabricated timestamps or backdated comment provenance. Update paths correctly drop identity fields. Remediation: whitelist client-settable fields on create; derive counters and closure timestamps server-side.
137
138
**S6 — malformed identifiers produce 500s (low)**
139
140
Routes carry no numeric constraints, and handlers call bare `String.to_integer/1` (`issue_controller.ex:40`, `comment_controller.ex:13`, four more files). `GET /api/v3/repos/o/r/issues/abc` raises `ArgumentError`, escaping the `Ecto.NoResultsError` rescues. Remediation: use `Integer.parse/1` plus explicit 404, as `ProjectController.cast_issue_number/1` already does, or add route segment constraints.
141
142
**S7 — CSP looseness (low)**
143
144
`connect-src ws: wss:` permits page-context connections to any websocket host (`lib/openagents_web/plugs/content_security_policy.ex:25`); `'unsafe-inline'` remains in `style-src`; `form-action` is absent. Everything else is strong: nonce'd scripts, `frame-ancestors 'none'`, `object-src 'none'`. Remediation: restrict websockets to same-origin or named dev hosts behind an environment conditional; add `form-action 'self'`.
145
146
**S8–S12 (low/informational)**
147
148
- `secure: Application.compile_env(...)` bakes cookie security at compile time (`lib/openagents_web/endpoint.ex:13`), making the runtime env var misleading. Read it at request time or document it as build-time.
149
- `OpenAgentsWeb.AllowedOrigins.for_production/2` validates https-only origins precisely for `check_origin`, but nothing calls it; raw CSV flows straight from env to config (`config/runtime.exs:410`). Wire it up and fail boot on invalid entries.
150
- Machine websocket tokens arrive as connect params, landing in proxy access logs (`controller_socket.ex:18`). Move to a subprotocol header or first-message handshake.
151
- Sessions last 365 days (`endpoint.ex:15`) with no recent-auth requirement for token issuance or data deletion. Shorten the window or add step-up.
152
- `/health` returns the build revision publicly (`health_controller.ex:7`); gate it behind operator auth.
153
154
### 3.3 Explicit non-findings
155
156
Checked and found sound: no missing auth on sensitive routes; no IDOR on the GitHub-style surface (public reads filtered in SQL; writes resolve writable membership per request); no CORS emissions anywhere; no CSRF exemptions; production `secret_key_base` required at boot; dev-only routes compiled out of other environments; OAuth redirect URIs pinned to https and `PHX_HOST` in production.
157
158
---
159
160
## 4. Code quality and smells
161
162
### 4.1 The root cause: three parallel turn/tool implementations
163
164
The same algorithm — validate arguments, digest, lock the parent row, dedupe by provider call id, sequence-check, insert a `"requested"` step — exists three times:
165
166
- `Conversations.request_tool_step/3` (`lib/openagents/conversations.ex:515`)
167
- `Voice.request_tool_step/2` (`lib/openagents/voice.ex:662`)
168
- `Work.request_job_step/2` (`lib/openagents/work.ex:298`)
169
170
Helpers are copy-pasted between them (`validate_raw_tool_arguments`, `tool_argument_digest`, `merge_refs` — the last byte-identical between conversations and voice). Drift has already happened:
171
172
- Argument size caps differ silently: 262,144 bytes for text turns versus 16,384 for voice, with no recorded decision.
173
- `Conversations.ToolStep` carries artifact and executor digests that `Voice.ToolStep` lacks, so voice outcomes skip digest verification that text turns enforce.
174
175
This is the highest-value refactor available: extract one shared ledger module or behaviour, make the tier differences explicit parameters, and force the divergence decisions into writing. Estimated effort: two to four days.
176
177
### 4.2 Oversized modules
178
179
| Module | Lines | Assessment |
180
|---|---|---|
181
| `ComponentsLive` | 2,639 | ~83 homogeneous demo clauses plus fixture data. Inert gallery content; splitting buys little. Lowest priority. |
182
| `ChatLive` | 1,897 | Genuine problem. One LiveView owns transcript, voice-session state, delegation-rail state machine, computer-live streaming (four dedicated `handle_info` clauses), and sidebar projections. `render/1` alone spans 502 lines. Split the delegation rail and computer-live panel into LiveComponents or extracted function-component modules. Two to three days. |
183
| `Voice` | 1,702 | Context plus admission control, rate limiting, its own tool ledger, and broadcast plumbing. Shrinks naturally if the shared ledger lands. |
184
| `Conversations` | 1,687 | Fat context carrying six responsibilities. `begin_inference/5` runs 131 lines; `request_tool_step/3` 112; `finish_turn/8` 100. The long functions are coherent but should shed the ~300 lines of provenance validators (`conversations.ex:1410`) into a capture module, and rate limiting into a helper. One to two days. |
185
186
### 4.3 Orphaned subsystem
187
188
`OpenAgents.Compensation` (`compensation.ex`, 554 lines, plus seven schemas under `compensation/`) has zero references outside itself and its own test file. Either wire it into a supervisor and product flow or delete it. Half a day including the migration cleanup decision.
189
190
### 4.4 Minor smells
191
192
- The recovery trio (`turn_recovery.ex`, `voice_recovery.ex`, `work_recovery.ex`) is structurally identical; one parameterized module would replace all three in under an hour. Defensible as-is given their size, but consolidation removes drift risk.
193
- `Issues.repository_stub/1` (`issues.ex:517`) fabricates `%Repository{}` structs as query tokens to satisfy pattern-matched helpers — a smell suggesting those helpers should take ids directly.
194
- Issue labels/assignees/milestones live both as denormalized JSON snapshots and join-table rows, with the dual-write hidden inside `Issues`. Pragmatic for API mirroring; document the invariant or centralize it.
195
- The `eventually/2` polling helper is duplicated across at least twelve test files; move it to `test/support`.
196
197
### 4.5 What is NOT wrong
198
199
No dead code beyond Compensation (checked `data_rights`, `diff`, `markdown` — all alive). No business logic leaks into controllers; they delegate to contexts and map results to statuses. Nesting stays shallow everywhere because transaction pipelines stay flat. Error handling converts exceptions to tuples or logs them at all but seven sites, each of which swallows deliberately with a comment explaining why.
200
201
---
202
203
## 5. Testing
204
205
### 5.1 State of play
206
207
233 test files against 502 modules, with measured coverage at 83.14% after the 2026-08-20 remediation wave. Domain schemas for the issues/projects surface sit at 100%. Recovery workers flagged in the earlier audit now have direct tests.
208
209
### 5.2 Gaps
210
211
1. **The coverage floor gates nothing.** `mix.exs` declares `threshold: 83.0`, and `ops/ci/coverage.sh` correctly unions default and cluster-only passes, but neither `mix precommit` nor `gate.sh` invokes the script. Add a coverage stage to the gate.
212
2. **No hosted CI.** Verification depends entirely on developers running the pre-push hook locally; nothing remote proves a push passed the gate. This is documented as deliberate, and the receipt system makes external attestation easy to add later — but today the guarantee is trust-based.
213
3. **Contexts without same-name tests.** Verified list includes `Audit`, `Mailer`, `BuildInfo`, `ComputerAgentJobs`, `ReleaseState`, `TurnRecovery` (covered indirectly), among others. `Audit` appears in only one indirect test; `Mailer` has zero test references.
214
4. **A known cross-user authorization gap is pinned as expected behavior.** Per the coverage audit, `ProjectController` ignores the `:username` segment, so a project owned by alice is readable and writable at `/users/bob/projectsV2/:n`. Tests deliberately document the divergence instead of fixing it. Decide: either implement username scoping or remove the segment from the contract.
215
5. **Test-rule violations.** Seven bare `Process.sleep/1` call sites contradict the repository's own testing rules (`coding_job_test.exs:118`, `computer_tools_test.exs:43`, `registry_and_runner_test.exs:39`, `builder_test.exs:266`, `ra_cluster_test.exs:160` and neighbors). Replace with monitors or the shared `eventually` helper.
216
6. **Async tagging is inconsistent**: 76 files tagged `async: true`, 109 tagged false, 48 untagged. Some serialization is likely habitual rather than required; audit and tag deliberately.
217
218
---
219
220
## 6. Infrastructure and deployment
221
222
### 6.1 Strengths
223
224
Reproducible, digest-pinned builds verified from the inside out; a receipt chain from gate to build to production preflight; fail-fast runtime configuration where every secret is required-or-explicitly-optional and integers are range-validated; staging evidence artifacts scanned for credentials before acceptance (`ops/staging/scan-evidence.sh`); complete migration rollback coverage including a heavyweight tenant-scoping backfill that fails closed via SQL exception if any row lacks a match (`priv/repo/migrations/20260820082100_add_repository_tenant_scoping.exs`).
225
226
### 6.2 Weaknesses
227
228
1. **Container isolation disabled** (see S4).
229
2. **Instance service accounts carry `cloud-platform` scopes**, broader than the IAM grants require. Narrow to storage, secret manager, and Cloud SQL admin scopes actually used.
230
3. **Transactional whole-table backfills are a future hazard.** The tenant-scoping migration updates six tables and rebuilds indexes inside one transaction. Fine at current scale; replaying this shape against large production tables risks lock storms. For post-cutover migrations, prefer batched backfills with `RETURNING BATCHES`-style loops.
231
4. **No container `HEALTHCHECK`**, and `curl` and `git` ship in the final image without documented justification. Add a healthcheck and prune or justify extra binaries.
232
5. **Cosmetics**: the LiveView signing salt is a committed generator value (`config/config.exs:260`); rotate per environment for defense-in-depth. Dev falls back to a checked-in token-vault encryption key (`config/dev.exs:36`) — documented as intentional and prod-safe, but a developer pointing dev at production GitHub apps encrypts real tokens under a public key; consider refusing the fallback when the configured OAuth client looks production-shaped. `config/dev.exs:19` embeds a personal username fallback.
233
234
---
235
236
## 7. Documentation
237
238
Exceptional by any standard. Every doc carries a status header enforced by script; `docs-check.exs` validates every markdown link, bans retired terms, and checks the invariant ledger for duplicate ids, missing proofs, and stale module references. `INVARIANTS.md` holds 74 numbered invariants spanning identity, memory, turns, tools, work, voice, and release, and moduledocs cite them by id. Seven ADRs record the load-bearing decisions. The dated audit/runbook trail shows a team publishing its own defects with receipts.
239
240
One gap: documentation describes intent better than operations-by-numbers. There is no capacity/planning doc stating expected scale (concurrent sessions, repository counts, message volumes) — which matters because several of the hazards above (backfill locking, rate-limit absence, gunzip exposure) change severity with scale. Adding an expectations section to [docs/architecture.md](architecture.md) would let reviewers weigh these tradeoffs correctly.
241
242
---
243
244
## 8. Prioritized remediation plan
245
246
Do these in order. Items 1–3 are small and contained; items 4–6 are the strategic debt paydown.
247
248
1. **Cap git-body decompression (S1).** Stream-decompress with hard output bounds in `read_git_body`/`safe_gunzip`. Hours, closes the only remotely exploitable DoS.
249
2. **Enforce comment authorship (S2).** Author-or-elevated check in both comment write actions. An hour.
250
3. **Wire the orphaned guardrails.** Call `AllowedOrigins.for_production/2` from `runtime.exs`; add the coverage stage to `gate.sh`; read `secure_cookies` at runtime. Half a day total, converts existing good intentions into enforcement.
251
4. **Extract the shared tool-step ledger** across conversations/voice/work (section 4.1). Two to four days. Highest refactor value: eliminates the triplication, forces the size-cap and digest-tier decisions to be explicit, and shrinks both fat contexts.
252
5. **Split `ChatLive`** — delegation rail and computer-live panel into LiveComponents; extract render sections. Two to three days.
253
6. **Throttle unauthenticated inserts (S3) and decide the ProjectController username question (section 5.2 item 4).** Both are product decisions with small implementations.
254
7. **Hygiene pass:** integer parsing (S6), CSP tightening (S7), create-path whitelisting (S5), Compensation fate (4.3), recovery-trio consolidation, bare-sleep removal, async tagging, narrow IAM scopes, tailored seccomp profile.
255
256
---
257
258
## 9. Closing assessment
259
260
Measured against its own stated standards — the invariants ledger, the gate receipts, the TDD workflow — this codebase mostly keeps its promises, and where it does not, it says so in writing. The weaknesses found here are concentration problems, not culture problems: one unshared algorithm replicated three ways, verification power trapped on developer laptops, and isolation shortcuts taken for staging convenience. Each has a contained fix, and the repository's own machinery (receipts, contract tests, docs linting) is exactly the right toolkit to land them.

This page updates live while a promote is in flight · changelog