diff --git a/ROADMAP.md b/ROADMAP.md index 934b0c6..6b691dc 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -13,14 +13,17 @@ A size given as `M or L` is conditional on a decision named in that slice's file |---|---|---| | **M0 Stands** | Repo, gate, packaging path proven | 000, 001 approved | | **M1 Talks** | Streaming chat with any provider, persisted, crash-safe | 010–013 approved | -| **M2 Acts** | Tools with permission gate, one side-effect membrane, local receipts, context compaction | 020–024 approved | +| **M2 Acts** | Tools with permission gate, one side-effect membrane, local receipts, context compaction; the FIPS build leg that proves 024's properties | 003, 020–024 approved | | **M3 Remembers** | Persona, always-on memory, FTS + semantic recall, project context, and data you can take with you | 030–034 approved | | **M4 Learns** | Skills system with agent self-management + approval | 040–041 approved | | **M5a Automates** | Cron tasks and MCP, client and server, with authorization | 050, 059–062 approved | | **M5b Reaches** | Gateways over PubSub, subagents | 070–072, 080 approved (081 optional, outside the milestone) | | **M6 Ships** | Observability, native desktop shell, signed releases | 090–101 approved | | **M7 Sandboxed** | Executable skills in an in-VM sandbox | 110 approved | -| **M9 Donatable** | OSS hygiene audited, supply chain signed, AAIF Sandbox package complete, shared libraries extracted | 120–123 approved (122 filing is an owner action) | +| **M9 Donatable** | OSS hygiene audited, supply chain signed, AAIF Sandbox package complete, shared libraries extracted | 002, 120–123 approved (122 filing is an owner action) | + +Slices 025 and 026 carry no milestone: they serve regulated deployments after M2 and are assigned when the +standards register names the rows that ask for them. ## Slices @@ -28,6 +31,8 @@ A size given as `M or L` is conditional on a decision named in that slice's file |---|---|---|---|---|---| | 000 | Toolchain, repo bootstrap, quality gate | 0 Foundation | L | none | approved | | 001 | Packaging spike: Burrito + ex_tauri smoke build | 0 Foundation | M | 000 | approved | +| 002 | Supply chain, early: SBOM, build provenance, the TLS floor | 0 Foundation | S | 000 | planned | +| 003 | FIPS build leg in CI, from source | 0 Foundation | M | 000 | planned | | 010 | Core domain + persistence (Ecto/SQLite, schemas, Repo owner) | 1 Core loop | M | 000 | planned | | 011 | LLM provider layer (req_llm behind `Trinity.LLM` behaviour) | 1 Core loop | M | 010 | planned | | 012 | Session process + agent loop (gen_statem, DynamicSupervisor, rehydration) | 1 Core loop | L | 010, 011 | planned | @@ -37,6 +42,8 @@ A size given as `M or L` is conditional on a decision named in that slice's file | 022 | Core tools: filesystem, web fetch/search, shell (MuonTrap) | 2 Tools | L | 021 | planned | | 023 | Context compaction + session lineage | 2 Tools | M | 012 | planned | | 024 | Effect catalog, authority selection (`TRINITY_AUTHORITY`), local receipts | 2 Tools | L | 021, 022 | planned | +| 025 | Encryption at rest, and the key-custody seam | 2 Tools | M | 010, 024 | planned | +| 026 | Store-and-forward receipts for disconnected operation | 2 Tools | L | 024 | planned | | 030 | Persona (SOUL) + always-on memory tier | 3 Memory | M | 012 | planned | | 031 | Session search (SQLite FTS5) | 3 Memory | S | 010 | planned | | 032 | Embeddings + semantic memory + hybrid retrieval | 3 Memory | L | 031 | planned | @@ -81,8 +88,12 @@ for planning, not deadlines; the point here is the cut line, not a duration. ``` 000 ─┬─ 001 ─────────────────────────────────────────── 100 ── 101 + ├─ 002 (supply chain, early) + ├─ 003 (FIPS build leg; runs 024's FIPS tests) └─ 010 ─┬─ 011 ─┬─ 090 - │ └─ 012 ─┬─ 013 ── 021 ── 022 ── 024 ── 061 ── 062 + │ └─ 012 ─┬─ 013 ── 021 ── 022 ── 024 ─┬─ 061 ── 062 + │ │ ├─ 025 (also needs 010) + │ │ └─ 026 │ ├─ 020 ─┬─ 040 ── 041 ── 110 │ │ ├─ 059 ── 060 ── 061 │ │ └─ 080 ── (081 optional, also needs 061) @@ -110,4 +121,5 @@ After 012, multiple branches are independent. If running more than one coding ag | 2026-09-05 | Authority made an adapter behind a behaviour rather than a mode (ADR-0008, ADR-0010). ADR-0009 opened for the Jido question, decided at the 012 checkpoint. Slice 024 added: effect catalog, `TRINITY_AUTHORITY`, local receipts. Alignment appendices on 012, 020, 021, 022, 023, 030, 032, 040, 041. | | 2026-09-05 | Review pass before commit 1. Counts in the three entries above were typed, not derived, and none matches the tree; a decreasing count is impossible under insert-never-renumber. Derived this date: `find slices -name SLICE.md | wc -l` → **38**. Milestones are derived from the Milestones table, not from memory. From here, any count in this log names the command that produced it. Entries above are not rewritten. | | 2026-09-06 | Plan corrections, round 2. **Supersedes the slice count in the entry above:** that entry derived **38** on 2026-09-05, before slices 082, 083 and 084 were withdrawn and slice 034 was added. Re-derived this date, not adjusted by arithmetic: `find slices -name SLICE.md | wc -l` → **36**. The entry above is not rewritten. `scripts/plan_check.sh` now enforces this count, the acceptance-criteria numbering, the Definition-of-Done ranges, ROADMAP/SLICE.md agreement, and the absence of references to paths not in `git ls-files`. | -| 2026-09-20 | The MCP phase replanned under owner decisions of 2026-09-08, recorded in ADR-0007 decisions 5 to 8: beam_mcp 0.8.0 is the server core; 059 measures the capability gap and probes the `:server` seam (S/M); 060 is Trinity's thin driver (L, the M/L condition decided); the OAuth client role moves from 060 to 062; 061 carries a named blocker on the MRTR wrapper; the four `M5 Always-on` headers on 059 to 062 read M5a Automates, as this file has since 2026-09-08. R14 re-scoped, R15 closed. Re-derived this date: `find slices -name SLICE.md \| wc -l` → **36**. | +| 2026-09-20 | The MCP phase replanned under owner decisions of 2026-09-08, recorded in ADR-0007 decisions 5 to 8: beam_mcp 0.8.0 is the server core; 059 measures the capability gap and probes the `:server` seam (S/M); 060 is Trinity's thin driver (L, the M/L condition decided); the OAuth client role moves from 060 to 062; 061 carries a named blocker on the MRTR wrapper; the four `M5 Always-on` headers on 059 to 062 read M5a Automates, as this file has since 2026-09-08. R14 re-scoped, R15 closed. Re-derived this date: `find slices -name SLICE.md | wc -l` → **36**. | +| 2026-09-20 | Four slices added under the accepted 2026-09-20 plan: 002 (supply chain, early: SBOM, provenance, TLS floor; counted in M9), 003 (FIPS build leg from source; counted in M2 because it runs 024's FIPS properties), 025 (encryption at rest and the key-custody seam) and 026 (store-and-forward receipts; blocked on the external plane's answer). Re-derived this date: `find slices -name SLICE.md | wc -l` → **40**. | diff --git a/slices/002-supply-chain-early/SLICE.md b/slices/002-supply-chain-early/SLICE.md new file mode 100644 index 0000000..fd18765 --- /dev/null +++ b/slices/002-supply-chain-early/SLICE.md @@ -0,0 +1,72 @@ +# Slice 002: Supply chain, early: SBOM, build provenance, the TLS floor + +| Field | Value | +|---|---| +| Phase | 0 Foundation | +| Milestone | M9 Donatable | +| Size | S | +| Depends on | 000 | +| Status | see ROADMAP.md | + +Added 2026-09-20. Slice 121 keeps Scorecard, Sigstore signing of release artifacts, SLSA and the registry entry, +and depends on the release pipeline; the three items here need none of that and are cheap enough to carry from +now on. + +## Goal +A CycloneDX software bill of materials generated by the gate and attached to every release; build provenance +attested on release artifacts through GitHub's attestation action; one README line stating the TLS floor and +that any MAC in an envelope Trinity mints is Trinity's and an approved one in FIPS mode. + +## Why +Vision goal 10. A reviewer of a public tree expects to find what is in the binary and where the binary came +from without asking; a regulated deployment requires both on request. Deferring them to slice 121 means every +release before it ships without them. + +## Scope +**In:** +- `sbom` (hex.pm, `mix sbom.cyclonedx`, CycloneDX) as a dev and test dependency; a gate step that generates + `sbom.cdx.json` and fails on a generation error; the file attached to every release by the package workflow. +- `actions/attest-build-provenance`, pinned to a commit SHA with its release in a comment like the other actions, + attesting each artifact the package workflow uploads. The attestation is verified in the same workflow with + `gh attestation verify` and the output kept in the run summary. +- One README line under Running from source: the TLS floor is 1.2, and any MAC over an envelope Trinity mints is + Trinity's own and is an approved algorithm in FIPS mode. +- `VERSIONS.md` row for `sbom`. +**Out:** +- Signing the SBOM or the artifacts with a Trinity-held key; Scorecard; SLSA levels; the registry entry. All + slice 121. +- A cryptography bill of materials. It follows once the minimum elements for one are published. + +## Design notes +The SBOM is generated from `mix.lock`, so it is exact for Hex dependencies and blind to the Rust crates of the +desktop shell; the file says so in a top-level property and slice 121 adds the crate half. Provenance is +GitHub's, over the artifact's digest, and proves where the bytes were built, not what they do. + +## Deliverables +- `mix.exs` dependency and gate step; `.github/workflows/package.yml` upload and attest steps; `README.md` line; + `lib/trinity/versions.ex` row and regenerated `VERSIONS.md`. + +## Acceptance criteria +1. [auto] `mix gate` generates `sbom.cdx.json` and the file validates as CycloneDX (the validator's command and + exit code pasted). +2. [auto] A package run attaches the SBOM to its artifacts and attests each artifact; `gh attestation verify` + exits 0 for each, output in the run summary (run id cited). +3. [auto] The README carries the TLS floor line, and a test pins it so the line cannot vanish silently. +4. [auto] Gate green; coverage line reported. + +## Proof required +- For each criterion: the command and its output, or a test name and its result. A sentence is not proof. + +## Manual verification queue +None. Every acceptance criterion in this slice is `[auto]` and is proven by a command or a test. +If that changes during the slice, the criterion is retagged and this section is filled at G1. + +## Definition of Done +- [ ] `mix gate` green · [ ] AC1–4 proven · [ ] docs/ADR/VERSIONS updated if affected · [ ] ROADMAP status → done · [ ] final commit + tag + +## Commit & tag +`feat(s002): complete slice 002 (supply chain, early)` · tag `slice/002` + +## Risks / open questions +- The SBOM's blindness to the desktop shell's crates is stated in the file itself; a reader who misses the + property misreads the bill. Slice 121 closes it. diff --git a/slices/003-fips-build-leg/SLICE.md b/slices/003-fips-build-leg/SLICE.md new file mode 100644 index 0000000..eef6649 --- /dev/null +++ b/slices/003-fips-build-leg/SLICE.md @@ -0,0 +1,79 @@ +# Slice 003: A FIPS build leg in CI, from source, so FIPS claims are proven rather than asserted + +| Field | Value | +|---|---| +| Phase | 0 Foundation | +| Milestone | M2 Acts | +| Size | M | +| Depends on | 000 | +| Status | see ROADMAP.md | + +Added 2026-09-20. Counted toward M2 Acts because its reason to exist is slice 024's FIPS property tests, which +cannot run anywhere else: the developer machine's OTP reports `crypto:info_fips()` as `not_supported`, and the +hosted BEAM images the gate uses are not FIPS builds. + +## Goal +A second entry in the gate workflow's matrix that runs the whole suite on an OTP 28.5.0.5 built from source with +`--enable-fips` against the validated OpenSSL of a UBI9 container, with FIPS mode entered before the crypto +application loads, and a smoke that proves the mode is on. The default leg stays exactly as it is. + +## Why +Slice 024's amendment says FIPS mode selects P-384 and never denies for want of a signer. Until a build runs +in FIPS mode, that is a sentence. This leg is the measurement. It also answers, once, what else in the tree +reaches an algorithm FIPS mode removes. + +## Scope +**In:** +- A container image built by its own workflow, rebuilt when `.tool-versions` changes: UBI9, the distribution's + OpenSSL and its FIPS provider, OTP 28.5.0.5 from source with `--enable-fips`, Elixir 1.20.4. Published to the + repository's container registry so the gate leg pulls rather than builds. +- The gate leg: `fips_mode: true` for `:crypto` in the release's `sys.config` equivalent for tests, set before + crypto loads; the smoke `crypto:info_fips() =:= enabled` and the `fips_provider_buildinfo` key present in + `crypto:info/0`; then `mix test` and the rest of the gate as the default leg runs it. +- A committed record of `crypto:supports/1` on the FIPS leg diffed against the default leg, so the removed + algorithms are a list in the tree and not a memory. +- Runner minutes measured over the first ten runs and written into NOTES.md. +**Out:** +- Making FIPS mode the default anywhere; changing the OTP pin; ML-DSA (the UBI9 OpenSSL is a 3.0 line and does + not carry it; that measurement waits for a build against OpenSSL 3.5 or later and is `:unknown` until then). +- A FIPS-mode desktop bundle. Whether Burrito can wrap a `--enable-fips` ERTS is slice 100's question. + +## Design notes +Entering or leaving FIPS mode on a running node is unsupported by OTP, so the leg sets the mode in configuration +and never toggles it in a test. A test that needs a removed algorithm on this leg is a finding, not a skip: it is +listed in the committed diff with the slice that owns it. + +## Deliverables +- `.github/workflows/fips-image.yml` and the Containerfile; the second matrix entry in `.github/workflows/gate.yml`; + `docs/fips-leg.md` with the `crypto:supports/1` diff and how to reproduce the image; NOTES.md with the minutes. + +## Acceptance criteria +1. [auto] On the FIPS leg, `crypto:info_fips()` returns `enabled` and `crypto:info/0` carries + `fips_provider_buildinfo` (log excerpt with the run id). +2. [auto] On the FIPS leg, `crypto:sign/4` with `eddsa` returns `notsup` and with `ecdsa` on `secp384r1` and + `sha384` returns a signature that verifies (test). +3. [auto] The committed `crypto:supports/1` diff between the two legs matches what the leg prints (test). +4. [auto] The whole gate runs on the leg; every red is either fixed or listed by test name with its owning slice + in `docs/fips-leg.md`; there is no skip tag. +5. [auto] Gate green on both legs; coverage line reported. + +## Proof required +- For each criterion: the command and its output, or a test name and its result, with the run id. A sentence is + not proof. + +## Manual verification queue +None. Every acceptance criterion in this slice is `[auto]` and is proven by a command or a test. +If that changes during the slice, the criterion is retagged and this section is filled at G1. + +## Definition of Done +- [ ] `mix gate` green · [ ] AC1–5 proven · [ ] docs/ADR/VERSIONS updated if affected · [ ] ROADMAP status → done · [ ] final commit + tag + +## Commit & tag +`feat(s003): complete slice 003 (FIPS build leg)` · tag `slice/003` + +## Risks / open questions +- Building OTP from source is slow; the image workflow exists so the gate leg does not pay for it on every push. + If the image cannot be cached in the registry for a reason found at G1, the leg runs on a schedule and on + demand rather than on every push, and the slice says so. +- The validated provider a container ships is the distribution's claim; the slice cites the certificate number + the distribution publishes and does not restate it as Trinity's. diff --git a/slices/025-encryption-at-rest/SLICE.md b/slices/025-encryption-at-rest/SLICE.md new file mode 100644 index 0000000..79ee7f2 --- /dev/null +++ b/slices/025-encryption-at-rest/SLICE.md @@ -0,0 +1,81 @@ +# Slice 025: Encryption at rest, and the key-custody seam + +| Field | Value | +|---|---| +| Phase | 2 Tools | +| Milestone | none (regulated deployment, after M2) | +| Size | M | +| Depends on | 010, 024 | +| Status | see ROADMAP.md | + +Added 2026-09-20 under two accepted recommendations: keys live where the deployment controls them, with a local +adapter first; and encryption at rest splits by whether SQLite indexes the data. + +## Goal +A key-custody behaviour (`Trinity.Keys`) with a local adapter (passphrase-derived, TPM-sealed or +systemd-credential-supplied, whichever the machine offers, chosen at boot and named in the boot receipt) through +which the 024 signer and this slice's envelope encryption obtain keys; envelope encryption in code, AES-256-GCM +through `:crypto`, for the blobs nothing indexes: skill files, staged skill changes, exported artifacts; and the +documented, measured baseline for everything SQLite indexes, which is volume or page level encryption below the +database file and zero code in Trinity. + +## Why +Field-level ciphertext in the memory store would break slice 032's full-text and vector search, which index +plaintext tokens and raw vectors. The split keeps the indexes working and still leaves no plaintext blob on disk +that Trinity could have encrypted. The custody seam is what lets a later deployment put keys in a KMS or an HSM +without touching either consumer. + +## Scope +**In:** +- `Trinity.Keys` behaviour: `fetch/2`, `wrap/2`, `unwrap/2`, `rotate/1`; the `Local` adapter with its three + sources; selection at boot; the 024 signer and the 100 keychain path retrofitted to read through it. +- Envelope encryption for the blob classes above: a data key per blob, wrapped by the custody adapter, the + wrapped key stored beside the ciphertext; decryption on read; a receipt on every key wrap and unwrap. +- `docs/encryption-at-rest.md`: the split, the reasons, the dm-crypt baseline with the commands to set it up + and the measured overhead on this machine from the 010 insert bench run on an encrypted and a plain volume. +**Out:** +- Encrypting the SQLite files in code (page-level libraries): only as a later option for a deployment that + already licenses a validated one, and not in this tree. +- KMS and PKCS#11 adapters: their own slices when a deployment asks; the behaviour is shaped so they fit. +- Key escrow, recovery and multi-party custody. + +## Design notes +The behaviour is shaped around a local round trip, on purpose; a network adapter later has to fit a callback +that was proven without one, rather than the other way round. Keys never appear in receipts, logs or the +database; the receipt names the key id and the operation. + +## Deliverables +- `lib/trinity/keys/` with the behaviour and the `Local` adapter; envelope encryption in the skill and export + paths; migrations for wrapped keys beside blobs; `docs/encryption-at-rest.md`; the bench results in PROOF.md. + +## Acceptance criteria +1. [auto] The boot receipt names the custody adapter and source; each of the three local sources works when + present and is refused with a named reason when absent (tests, one per source, with the absent case). +2. [auto] A skill file, a staged change and an export are ciphertext on disk, readable through the seam, and + unreadable with the wrapped key removed (tests). +3. [auto] The 024 signer obtains its key through the seam and no other path; a census finds no key read outside + `Trinity.Keys` (test with a planted read). +4. [auto] Rotation: after `rotate/1` new blobs use the new key, old blobs still open, and the registry records + both (test). +5. [auto] The insert bench from slice 010 run on a dm-crypt volume and on a plain volume, same machine, same + day, the two numbers in PROOF.md with the commands. +6. [auto] Gate green; coverage line reported. + +## Proof required +- For each criterion: the command and its output, or a test name and its result. A sentence is not proof. + +## Manual verification queue +None. Every acceptance criterion in this slice is `[auto]` and is proven by a command or a test. +If that changes during the slice, the criterion is retagged and this section is filled at G1. + +## Definition of Done +- [ ] `mix gate` green · [ ] AC1–6 proven · [ ] docs/ADR/VERSIONS updated if affected · [ ] ROADMAP status → done · [ ] final commit + tag + +## Commit & tag +`feat(s025): complete slice 025 (encryption at rest and the key-custody seam)` · tag `slice/025` + +## Risks / open questions +- A TPM is absent on many developer machines; the passphrase source is the one every test can exercise, and the + TPM and systemd-credential sources are proven where present and recorded as not measured where not. +- Retrofitting 024's signer to the seam is a change to an approved slice's code if 024 lands first; it is a + fix commit referencing 024, never a rewrite of its record. diff --git a/slices/026-store-and-forward-receipts/SLICE.md b/slices/026-store-and-forward-receipts/SLICE.md new file mode 100644 index 0000000..c631fd2 --- /dev/null +++ b/slices/026-store-and-forward-receipts/SLICE.md @@ -0,0 +1,72 @@ +# Slice 026: Store-and-forward receipts for disconnected operation + +| Field | Value | +|---|---| +| Phase | 2 Tools | +| Milestone | none (regulated deployment, after M2) | +| Size | L | +| Depends on | 024 | +| Status | see ROADMAP.md | + +Added 2026-09-20. Blocked until the external authority plane's maintainers answer whether a queued-then-acknowledged +`receipt/2` fits their adapter; that question is routed through the owner and this slice does not open before the +answer is recorded in NOTES.md. Opening it changes ADR-0008's contract and needs an appended decision there. + +## Goal +`receipt/2` gains a queued-then-acknowledged mode so that the local authority and an external adapter behave +identically when the machine is offline: receipts are appended locally with a hybrid logical clock on every row, +queued for the adapter, acknowledged when it confirms, and merged on reconnect by Merkle comparison of the two +chains rather than by replay. + +## Why +A field or clinical site loses its link and keeps working. Today `receipt/2` is synchronous: offline, the local +chain continues and the adapter's view stops, and nothing reconciles them. A chain that cannot survive a +partition is evidence only while the network is up. + +## Scope +**In:** +- Hybrid logical clock on every receipt row; the clock's rules and its trust limits written into the security + model (a signed receipt with an untrusted clock is weaker evidence, and the model says so). +- A durable outbound queue per chain scope; acknowledgement tracking; back-pressure when the queue is bounded. +- Merkle merge on reconnect: per-device chains, a comparison of tree heads, and a merge that never rewrites + either side; conflicts are recorded as receipts of their own. +- The `Local` authority exercising the same path, so standalone Trinity proves the mode without an adapter. +**Out:** +- Multi-master conflict resolution beyond recording; a witness or gossip network; the external plane's side of the + merge, which is theirs. + +## Design notes +Nothing here changes what a receipt says, only when it is acknowledged and how two chains are compared. The +signer seam and chain scopes from 024 are used unchanged. + +## Deliverables +- Queue and clock in `Trinity.Receipts`; the merge; the ADR-0008 appended decision; docs/07 clock section; tests + that partition and reconnect. + +## Acceptance criteria +1. [auto] With the adapter unreachable, effects proceed under the local chain, every receipt carries a clock, and + the queue grows; with the adapter back, every queued receipt is acknowledged in order (test). +2. [auto] Two chains diverged during a partition merge on reconnect with neither rewritten and a conflict receipt + for each divergence (test). +3. [auto] A receipt whose clock is behind the previous row's is refused and the refusal receipted (test). +4. [auto] Bounded queue: past the bound, effects are denied, not written unacknowledged (test). +5. [auto] Gate green; coverage line reported. + +## Proof required +- For each criterion: the command and its output, or a test name and its result. A sentence is not proof. + +## Manual verification queue +None. Every acceptance criterion in this slice is `[auto]` and is proven by a command or a test. +If that changes during the slice, the criterion is retagged and this section is filled at G1. + +## Definition of Done +- [ ] `mix gate` green · [ ] AC1–5 proven · [ ] docs/ADR/VERSIONS updated if affected · [ ] ROADMAP status → done · [ ] final commit + tag + +## Commit & tag +`feat(s026): complete slice 026 (store-and-forward receipts)` · tag `slice/026` + +## Risks / open questions +- The external plane may answer that its adapter cannot take a queued mode; then this slice narrows to the local + authority only and says so. +- Clock trust under disconnection has no software fix; a trusted time source is a deployment matter and is + named in the register, not solved here.