Skip to content

Latest commit

 

History

History
36 lines (35 loc) · 20.7 KB

06-risk-register.md

File metadata and controls

36 lines (35 loc) · 20.7 KB

06: Risk register

# Risk Likelihood Impact Trigger / early warning Mitigation Owner slice
R1 Desktop packaging chain (Burrito/ex_tauri) lags OTP or lacks Windows High High Slice 001 spike fails on a target OS Spike first; fallback matrix: ex_tauri → plain Tauri sidecar with hand-rolled Rust → elixir-desktop → local-server+tray 001
R2 macOS notarization / Windows HSM signing friction High Med 101 blocked on certs Start cert procurement at M0; Windows 460-day cert cap (from Mar 2026) noted; unsigned dev builds allowed until 101 101
R3 EXLA/Bumblebee binary size and RAM on user machines Med Med 032 measurement > 300 MB or embed > 500 ms Behaviour lets us switch to hosted embeddings; or Ortex despite stall 032
R4 sqlite_vec extension not loadable inside Burrito bundle Med Med 032 on packaged build Vendor the extension in priv/; fallback Nx brute-force over an ETS cache 032
R5 req_llm breaking changes / provider drift Med Med Live tests fail after bump Behaviour isolates it; LangChain-Elixir fallback 011
R6 LiveView streaming performance (token spam) Med Low UI lag at > 20 msg/s Coalescing broadcaster; phoenix_streamdown 013
R6 Closed 2026-09-20 at slice 013: the Session coalesces to at most 20 broadcasts a second (012) and the page renders once per broadcast, measured at 6 renders for 1,000 deltas (013 PROOF.md, AC7). The renderer is mdex, not phoenix_streamdown, which never left beta (013 NOTES.md). The row above stands as written none none none none 013
R7 Context compaction loses critical information Med High Eval set regression Always-on tier preserved verbatim; compaction eval harness in 023 023
R8 Prompt injection via tool output / skills / web High High none Untrusted-content framing; scanner on skills; permission gate on side effects; see 07-security-model 021, 041
R9 Oban on SQLite limitations (no Pro workflows) Low Med Need for multi-step durable graphs Postgres path kept alive in CI matrix 050
R10 Scope creep beyond a change's stated specification Med Med The engineering record shows work outside the specification The conventions in docs/03; recorded follow-ups; maintainer review all
R11 Stale or single-maintainer libs (boundary, hnswlib, ex_tauri, sqlite_vec, nostrum, telegex) High Med–High No release in 6 months. Measured 2026-09-05: boundary 2024-09-25, sqlite_vec 2024-11-19, telegex 1.9.0-rc.0 2024-09-18, nostrum 2025-03-02, the trigger already fires for four of them All behind behaviours; vendor if needed. boundary is the highest-consequence one: ADR-0001, docs/01 and the engineering rules in docs/03 all rest on it, and it is unverified on Elixir 1.20. Probe it before anything is built on it 000, 032, 072
R12 Secrets leak into logs/DB/commits Low High grep hits in CI mix gate includes a secret scan (gitleaks-style regex) from 000; Secrets module from 100 000, 100
R13 Scope drifts toward matching other agents' breadth instead of shipping depth Med Low Slice scope grows during a phase Non-goals in docs/00 are binding; breadth is a later decision, not a default none
R14 beam_mcp's will-not-implement page keeps MRTR, the client role and OAuth out of the core; each is Trinity's to build above it (re-scoped 2026-09-20, was library lag) High Med 059 FINDINGS names a gap 060 to 062 did not plan for The layering rule in ADR-0007 decision 6; the :server seam asked for, with the fork as recorded fallback; boundaries around Trinity.MCP.* 059
R15 Closed 2026-09-20: the server core is beam_mcp, Apache-2.0 (ADR-0007 decision 5); no LGPL candidate remains none none none none 059
R20 Foundation donation may require transferring assets or marks the project intends to keep Med Med Proposal drafting (122) Unverified: the requirement is asserted from an announcement, not from the charter text. Read the charter, then decide what is offered and what is retained. Legal review before any proposal leaves the tree 122
R21 The tree carries IP that is not this project's to publish Med High Any design in Trinity that reproduces a third party's protected mechanism Trinity's receipt and policy design is its own. Policy identifiers stay out of signed_payload until legal review clears them; 024's field set is reviewed before that slice starts 024, 120, 122
R21 Answered 2026-09-24 for the receipt field set, which was the open half. Slice 024 proposed the signed_payload field set for review and built "the default until cleared"; nothing cleared it until now, and slice 120's AC3 could not be met while that was true. Two questions, both now answered from published standards rather than from instinct. Does the field set reproduce a third party's protected mechanism? No, because it contains no mechanism anyone owns: prev_hash with seq is a forward-integrity hash chain (Bellare and Yee 1997; Schneier and Kelsey 1998), key_id is COSE kid, scheme occupies COSE alg's role, at is RFC 3339, subject is in-toto's Statement subject, chain_scope is Certificate Transparency's per-log scoping, fingerprint is an RFC 8785 canonical-digest commitment and decision is XACML's PDP outcome. IETF RFC 9943 (Standards Track, June 2026) standardises this whole object class as COSE_Sign1 Signed Statements and Receipts, which settles it: an open standard for the thing means the mechanism is nobody's to protect. May policy identifiers enter signed_payload? No, and the existing placement in unsigned meta is confirmed rather than merely tolerated. SLSA's attestation model separates externalParameters (untrusted, MUST be included and verified downstream) from internalParameters (platform-set, trusted, OPTIONAL, not verified downstream); a policy identifier is the second kind. Field-tier minimisation (arXiv 2608.25474) gives the governing property: the ledger commits to a canonical digest of the full parameters computed before minimisation, so "reclassifying a field changes what is disclosed without invalidating a historical entry, reopening a hash, or altering what an offline verifier checks" - Trinity already holds that commitment as the fingerprint, and moving policy identifiers into the signed bytes would convert a revisable disclosure decision into an immutable one for no gain, since a downstream verifier cannot evaluate Trinity's policy anyway and decision.basis already names the layer that decided inside the signed bytes. The original hazard also holds: if policy identifiers ever come from the external authority plane, embedding another party's namespace in immutable signed bytes is exactly what this row warns about, and in meta a namespace change is a metadata change rather than a scheme bump. The rule, so this is not re-litigated: the signed payload carries the commitment and the decision facts; anything platform-set, revisable or another party's namespace goes in meta. The row above stands for everything other than the receipt field set Low (was Med: the named open question is closed) High The lift condition slice 024 named, met docs/06-risk-register.md R21; slice 120 AC3 024, 120
R21 Amended 2026-09-26, appended on the owner's instruction after slice 026. Chain-integrity fields generated by this node (seq, prev_hash, clock) are commitments. They stay in the signed payload so a merge cannot be reordered by rewriting unsigned meta. "Platform-set" in the original sentence means revisable transport and operator annotations, not the values that make two chains one. Low High The lift condition slice 024 named, met docs/06-risk-register.md R21; slice 026 NOTES 024, 026, 120
R22 Single maintainer; Growth needs two unaffiliated production users and commits from two orgs High Med 6-month Sandbox checkpoint Sandbox tolerates it; GOVERNANCE.md documents intent to grow; recruit co-maintainers via the Elixir community once public 120, 122
R22 Partly mitigated 2026-09-23: two further maintainers, Zach Mead and Mike Hostetler, joined with review, merge, tag and release rights (MAINTAINERS.md; GOVERNANCE.md "Continuity"). What that closes is availability, which is what the OpenSSF criterion access_continuity asks for: the project can accept a change and publish a release without any one person. What it does not close is knowledge or independence. Every commit in the tree to date has one author, so a "bus factor" of two or more in the sense the criteria mean it (the number who would have to disappear before the work stalls for lack of competent personnel) is being established rather than claimed, and the badge answer says so; and organisation administration, the domain and the security mailbox still rest with the author alone, which GOVERNANCE.md names as the first thing a reader should ask about. Lift condition, checkable by a stranger: reviews and merged changes from a maintainer other than the author in the git history, and the administrative access documented as recoverable. The row above stands as written Med Med The lift condition above, unmet GOVERNANCE.md "Continuity"; MAINTAINERS.md 120, 122
R24 A native NIF in the desktop bundle: mdex_native (the markdown renderer's Rust core, added at 013) is the first, and slice 001 proved the packaging chain with a pure-BEAM release Med Med The package workflow fails on one of its three operating systems after 013, or 100's cross-target build finds no artifact for a target rustler_precompiled ships artifacts for every target the chain builds for (013 NOTES.md, measured against the checksum file); the package run on 013's mix.lock change is the first proof on three runners; lift condition: that run green on all three, cited in 013's PROOF.md; a fallback is MDEX_NATIVE_BUILD=1 with the pinned Rust toolchain already in the tree 013, 100
R24 Fired 2026-09-20, in slice 013: neither precompiled mdex_native artifact loads in Burrito's musl ERTS (both need glibc's libgcc_s; _dl_find_object: symbol not found). Lifted the same day by the owner's decision: the linux package builds the NIF from source for musl with Zig as the linker (scripts/zig-cc-musl, config/config.exs, rustler at build time), measured on a fresh install of the packaged binary rendering the real provider's answer; the --smoke path now prints TRINITY_SMOKE_MARKDOWN=ok and exits 3 otherwise, and the package workflow reads that line on every target, so a dead NIF cannot pass again. Residual: macOS and Windows use the precompiled artifact, and the package workflow has yet to run green there since the slice/011 tag (an older failure at "Serves HTTP 200", now diagnosed by the step's log). The row above stands as written none none none none 013
R24 Residual closed 2026-09-24. The row above left one open: macOS and Windows use the precompiled mdex_native artifact and the package workflow had not run green there since the slice/011 tag. It has now, six times consecutively, on the runs for slice/090.1, 027, 028, 029, 120 and main, each with linux x86_64, macOS aarch64 and windows x86_64 all passing (gh run list --workflow=package.yml). The lift condition the row named is met. What is not closed by this, and is worth saying so nobody reads more into it than it carries: these runs package the tree, they do not install the artifact on a mac or a Windows machine and watch it render, which is what finally caught the musl defect on linux. The rows above stand as written none none none none 013, 100
R23 EMA / ID-JAG is beta everywhere (vendors label it so); IETF draft still moving Low (re-scoped 2026-09-22) Med Spec revision breaks the exchange at the external authorization server, which is where it happens: Trinity redeems no assertion since the owner's decision of 2026-09-22 (slice 062 NOTES) The resource server validates whatever audience-bound token that server issues, so a draft revision is that server's upgrade, not Trinity's; the row stays until the deferral lifts 062
R25 glib 0.18.5 carries GHSA-wrw7-89jp-8q8g (medium, unsoundness in Iterator and DoubleEndedIterator impls); first patched in 0.20.0 Low Med The advisory is open against src-tauri/Cargo.lock; measured 2026-09-23 It is transitive through the GTK 0.18 stack (gtk 0.18.2, webkit2gtk 2.0.2) that Tauri 2.11.5 requires on Linux, so it is fixed by a Tauri release built on gtk-rs 0.20 and not by a direct bump. Scope: the desktop shell only. The headless release is steps: [:assemble] with no Burrito and no src-tauri, so a server deployment does not ship this dependency. Owner: the maintainer. Lift condition: a Tauri release whose Linux stack resolves glib >= 0.20, adopted by the desktop shell work 100
R26 An intermittent Exqlite.Error: Database busy fails the test suite on loaded CI runners, in a different test each time, always on a write to the main repository Med Low (test-only; no production path) Failed the gate on pull requests 67, 70 and again on the commit that attempted to fix it; the same commit passed on its other run, so it is seed- and load-dependent. Not once reproduced locally across repeated runs Ruled out: busy_timeout, raised from the shipped 5 s to 30 s in the suite, did not stop it. SQLite returns BUSY immediately, without the busy handler, when a read transaction upgrades to a write while another connection holds the write lock; no timeout affects that path. The suite runs two connections (one for the sandbox owner, one for Oban's boot check) and deliberately queues fifty concurrent writers at slice 070 AC7, which is the load that provokes it. Candidate fixes, not yet taken: drain the stress test's sessions before the suite continues; give that test its own tag and CI step; or remove the second connection's need. Owner: the maintainer. Lift condition: ten consecutive green CI runs of the full suite with the stress test in the default run 070
R26 Occurrence recorded 2026-09-23: the same class of failure appeared on the FIPS leg rather than the ordinary gate, which the row above does not cover. Trinity.Gateways.ApprovalsTest AC4 failed in its setup, with the crash in Trinity.Receipts.ChainWriter.terminate/2 calling insert_checkpoint/2 after the sandbox owner had gone: an ownership error at termination rather than a Database busy on a write. Re-running the identical commit passed all four checks, which is the evidence that matters: the code did not change between the red run and the green one, so the failure is in the environment or the ordering and not in the change under test. What this adds to the row above: the mechanism is not only write contention, it also reaches process termination racing sandbox teardown, and it is not confined to the ordinary gate's runners. A candidate fix specific to this shape: ChainWriter.terminate/2 writes a checkpoint on the way out, and a terminate callback that touches the database after the test that owns the connection has finished is a race by construction; it should skip the write when the repo is in sandbox mode and has no owner, rather than attempting it and crashing. Second occurrence the same day, and it changes the severity. The very next pull request, which changed only this file, failed the FIPS leg again: Trinity.Gateways.RouterTest AC6, Exqlite.Error: Database busy on an INSERT INTO gateway_identities - the row's original signature, on the leg the row did not cover. A change that cannot affect behaviour failing a required check twice in a row means this is no longer an intermittent annoyance: it blocks work, and the honest reading is that the FIPS leg provokes it more reliably than the ordinary gate does, most likely because that leg is slower (163 s against about 110 s locally) and every contention window is correspondingly wider. The row above stands as written High (was Med: it now blocks merges) Low (test-only; no production path) The lift condition in the row above, still unmet; two failures on the FIPS leg on 2026-09-23, one of them on a documentation-only change docs/06-risk-register.md R26 070
R26 Cause removed 2026-09-24, slice 005; the row stays open until its lift condition is met. The mechanism this row diagnosed on 2026-09-23 is the documented behaviour of a DEFERRED transaction: no lock at BEGIN, a reader until its first write, and at that upgrade SQLite returns SQLITE_BUSY without invoking the busy handler because blocking there risks deadlock. The documented fix is BEGIN IMMEDIATE, which takes the write lock at BEGIN where the handler is allowed to wait, and ecto_sqlite3 0.24.1 exposes it as :default_transaction_mode. Both SQLite repos are now configured :immediate, guarded on the adapter so it cannot reach the Postgres build. The three candidate fixes this row recorded were all workarounds, and saying so is the useful part: draining the stress test's sessions, giving it its own CI step and removing the second connection's need each reduce how often two connections contend and leave the upgrade path exactly where it is. The row had the mechanism right and then proposed three ways around it. Reproduced deterministically for the first time, against this row's own words that it was "not once reproduced locally": test/trinity/repo/sqlite_transaction_mode_test.exs drives two connections through both sequences and asserts that under DEFERRED the upgrade is refused in under a second despite a five-second busy_timeout, and that under IMMEDIATE the same sequence waits and succeeds, in a tenth of a second. Not closed: the lift condition is ten consecutive green runs of the full suite with the stress test in the default run, which this can begin earning and cannot assert on the day it lands. A fix for an intermittent fault that declares victory on one green suite is how the fault comes back. The rows above stand as written Low (was High: the upgrade path is gone, pending the ten runs) Low (test-only; no production path) Ten consecutive green runs from 2026-09-24 docs/05-data-model.md, "Transactions begin IMMEDIATE on SQLite" 005, 070
R26 Occurrence recorded 2026-09-24, and this one is R26 rather than something wearing its signature. Trinity.Telemetry.CostsTest, "an unconfigured budget is not a budget of zero", failed with ** (Exqlite.Error) Database busy on the ordinary gate leg of pull request 96, while the sibling run of the identical commit passed. The test is untouched by that change, the error is the row's original wording, and the failure is on a write: three of the three things the row names. Recorded separately from the counter-example above so that the register shows both kinds, because the counter-example's whole point is that one cannot be assumed from the signature and this one was checked before being filed. What it adds: the row above says it blocks work, and it blocked a merge again here, costing a full CI cycle on a branch whose own tests were green. The three candidate fixes in the original row remain untaken, which is now a decision rather than an oversight: it has cost four cycles across 2026-09-23 and 2026-09-24. Recommend taking it as a slice of its own rather than continuing to re-run. The rows above stand as written High, unchanged Low (test-only; no production path) The lift condition in the original row, still unmet; a fourth blocking occurrence docs/06-risk-register.md R26 070
R26 Counter-example recorded 2026-09-24, in slice 090, and it corrects a heuristic the rows above state. At commit b1bca1f the pull_request run passed all four checks and the push run of the same commit failed postgres (runs 36001064067 and 36001057879). By the reading the 2026-09-23 entry offers - the code did not change between the green run and the red one, so the fault is the environment - this was R26 again. It was not. It was a real defect in slice 080's code, on a genuinely racy path: every call into a child session is a gen_statem.call, and a call into a process that dies exits the caller, so killing a child killed whoever called delegate/3. Whether the kill landed before or after the call returned decided the outcome, which is why the same commit could go either way. Fixed at 1f33f12, after which both runs of the same commit passed. What this changes: "the same commit passed on its other run" is evidence that the failure is timing-dependent, which is not the same as evidence that it is environmental. A real race in library code produces exactly the signature R26 is recognised by, so the signature alone cannot be the diagnosis - the stack trace has to be read before a failure is filed here. This slice nearly filed one. The rows above stand as written none (not an occurrence) none none none 080, 090