diff --git a/.github/workflows/gate.yml b/.github/workflows/gate.yml index 912ef29..01bcca7 100644 --- a/.github/workflows/gate.yml +++ b/.github/workflows/gate.yml @@ -35,6 +35,19 @@ jobs: with: version-file: .tool-versions version-type: strict + install-hex: false + + # Hex is pinned to the archive the image pins (ci/ironbank/hardening_manifest.yaml), by the + # SHA-512 builds.hex.pm publishes for it in installs/hex.csv. setup-beam's own install takes + # the newest Hex, and the bill of materials names the running Hex, so a Hex release turned + # `trinity.third_party_licenses --check` red on every branch (2.5.3, 2026-10-14) while + # nothing in the tree had changed. + - name: Hex 2.5.1, pinned as the image pins it + run: | + curl -fsSL -o /tmp/hex-2.5.1-otp-28.ez https://builds.hex.pm/installs/1.20.0/hex-2.5.1-otp-28.ez + echo "690f12f6933088bb32f1d7125485fb13426318c03cfeabcbd3cce55824cddf2a701104b09b7f4cd7092fe7f76d5b669e391a072428ec4526879b36f62820ae5d /tmp/hex-2.5.1-otp-28.ez" | sha512sum -c - + mix archive.install /tmp/hex-2.5.1-otp-28.ez --force + mix hex.info | head -1 - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: @@ -138,6 +151,19 @@ jobs: with: version-file: .tool-versions version-type: strict + install-hex: false + + # Hex is pinned to the archive the image pins (ci/ironbank/hardening_manifest.yaml), by the + # SHA-512 builds.hex.pm publishes for it in installs/hex.csv. setup-beam's own install takes + # the newest Hex, and the bill of materials names the running Hex, so a Hex release turned + # `trinity.third_party_licenses --check` red on every branch (2.5.3, 2026-10-14) while + # nothing in the tree had changed. + - name: Hex 2.5.1, pinned as the image pins it + run: | + curl -fsSL -o /tmp/hex-2.5.1-otp-28.ez https://builds.hex.pm/installs/1.20.0/hex-2.5.1-otp-28.ez + echo "690f12f6933088bb32f1d7125485fb13426318c03cfeabcbd3cce55824cddf2a701104b09b7f4cd7092fe7f76d5b669e391a072428ec4526879b36f62820ae5d /tmp/hex-2.5.1-otp-28.ez" | sha512sum -c - + mix archive.install /tmp/hex-2.5.1-otp-28.ez --force + mix hex.info | head -1 - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: diff --git a/.github/workflows/otp-canary.yml b/.github/workflows/otp-canary.yml index 999bb8c..326e3e5 100644 --- a/.github/workflows/otp-canary.yml +++ b/.github/workflows/otp-canary.yml @@ -75,6 +75,20 @@ jobs: with: version-file: .tool-versions version-type: strict + install-hex: false + + # Hex is pinned to the archive the image pins (ci/ironbank/hardening_manifest.yaml), by the + # SHA-512 builds.hex.pm publishes for it in installs/hex.csv. setup-beam's own install takes + # the newest Hex, and the bill of materials names the running Hex, so a Hex release turned + # `trinity.third_party_licenses --check` red on every branch (2.5.3, 2026-10-14) while + # nothing in the tree had changed. + - name: Hex 2.5.1, pinned as the image pins it + if: steps.seen.outputs.seen == 'false' + run: | + curl -fsSL -o /tmp/hex-2.5.1-otp-28.ez https://builds.hex.pm/installs/1.20.0/hex-2.5.1-otp-28.ez + echo "690f12f6933088bb32f1d7125485fb13426318c03cfeabcbd3cce55824cddf2a701104b09b7f4cd7092fe7f76d5b669e391a072428ec4526879b36f62820ae5d /tmp/hex-2.5.1-otp-28.ez" | sha512sum -c - + mix archive.install /tmp/hex-2.5.1-otp-28.ez --force + mix hex.info | head -1 - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 if: steps.seen.outputs.seen == 'false' diff --git a/THIRD_PARTY_LICENSES.md b/THIRD_PARTY_LICENSES.md index c9ad401..3e41a8b 100644 --- a/THIRD_PARTY_LICENSES.md +++ b/THIRD_PARTY_LICENSES.md @@ -22,7 +22,7 @@ it, and a reader should not infer from an entry here that the component is in a | `abnf_parsec` | `2.1.0` | MIT | bom | | `asn1` | `5.4.3` | Apache-2.0 | bom | | `axon` | `0.8.1` | Apache-2.0 | bom | -| `bandit` | `1.12.5` | MIT | bom | +| `bandit` | `1.13.1` | MIT | bom | | `beam_mcp` | `0.10.1` | Apache-2.0 | bom | | `boundary` | `0.10.4` | MIT | bom | | `bumblebee` | `0.7.1` | Apache-2.0 | bom | @@ -36,7 +36,7 @@ it, and a reader should not infer from an entry here that the component is in a | `crypto` | `5.8.3.3` | Apache-2.0 | bom | | `daisyui` | `22ecff57f2c3` | MIT | declared | | `db_connection` | `2.10.2` | Apache-2.0 | bom | -| `decimal` | `3.1.1` | Apache-2.0 | bom | +| `decimal` | `3.2.0` | Apache-2.0 | bom | | `dns_cluster` | `0.2.0` | MIT | bom | | `dotenvy` | `1.2.1` | Apache-2.0 | bom | | `earmark_parser` | `1.4.46` | Apache-2.0 | bom | @@ -62,7 +62,7 @@ it, and a reader should not infer from an entry here that the component is in a | `glob_ex` | `0.1.12` | MIT | bom | | `heroicons` | `0435d4ca364a` | MIT | declared | | `hex` | `2.5.1` | Apache-2.0 | bom | -| `hex_core` | `0.19.0` | Apache-2.0 | bom | +| `hex_core` | `0.20.1` | Apache-2.0 | bom | | `hpax` | `1.1.0` | Apache-2.0 | bom | | `idna` | `7.1.0` | MIT | bom | | `igniter` | `0.8.4` | MIT | bom | @@ -114,7 +114,7 @@ it, and a reader should not infer from an entry here that the component is in a | `plug_crypto` | `2.2.0` | Apache-2.0 | bom | | `polaris` | `0.1.0` | Apache-2.0 | bom | | `postgrex` | `0.22.4` | Apache-2.0 | bom | -| `protobuf` | `0.17.0` | MIT | bom | +| `protobuf` | `0.17.1` | MIT | bom | | `public_key` | `1.20.3.4` | Apache-2.0 | bom | | `purl` | `0.5.0` | Apache-2.0 | bom | | `req` | `0.7.4` | Apache-2.0 | bom | @@ -125,7 +125,7 @@ it, and a reader should not infer from an entry here that the component is in a | `rustler_precompiled` | `0.9.0` | Apache-2.0 | bom | | `safetensors` | `0.1.3` | Apache-2.0 | bom | | `sasl` | `4.3.2` | Apache-2.0 | bom | -| `sbom` | `0.11.0` | BSD-3-Clause AND CC0-1.0 AND MIT AND Apache-2.0 | bom | +| `sbom` | `0.12.0` | BSD-3-Clause AND CC0-1.0 AND MIT AND Apache-2.0 | bom | | `server_sent_events` | `1.1.0` | MIT | bom | | `sobelow` | `0.15.0` | Apache-2.0 | bom | | `sourceror` | `1.12.3` | Apache-2.0 | bom | @@ -142,7 +142,7 @@ it, and a reader should not infer from an entry here that the component is in a | `telemetry_registry` | `0.3.2` | Apache-2.0 | bom | | `text_diff` | `0.1.0` | MIT | bom | | `texture` | `1.2.1` | Apache-2.0 | bom | -| `thousand_island` | `1.5.0` | MIT | bom | +| `thousand_island` | `1.6.0` | MIT | bom | | `tokenizers` | `0.5.1` | Apache-2.0 | bom | | `toml` | `0.7.0` | Apache-2.0 | bom | | `typed_struct` | `0.3.0` | MIT | bom | diff --git a/ci/headless/justifications.yaml b/ci/headless/justifications.yaml index e99da5b..2dca36d 100644 --- a/ci/headless/justifications.yaml +++ b/ci/headless/justifications.yaml @@ -21,71 +21,16 @@ # include no pcre2_jit_*.c and no pcre2_dfa_match.c (`tar -tzf otp_src_28.5.0.7.tar.gz`). --- justifications: - # pcre2: inherited from ubi9-micro 9.8, whose own scan reports the same three CVEs on the same - # two packages. Red Hat lists no fix (grype fix state `not-fixed`, Red Hat's namespace). - - finding: "CVE-2026-86145" - package: "pcre2-10.40-6.el9" - packagePath: null - severity: "high" - scanSource: "grype" - justification: >- - Inherited from the ubi9-micro 9.8 base image, which reports the same finding; Red Hat lists - no fix. The flaw is in pcre2_jit_match. In this image libpcre2-8 is linked only by the base's - libselinux and libpcre2-posix (readelf -d over every executable and shared object); the - Erlang runtime does not link it, and the PCRE2 copy ERTS compiles in (erts/emulator/pcre, - 10.47) contains no JIT source (no pcre2_jit_*.c in OTP 28.5.0.7's tree). - - finding: "CVE-2026-86145" - package: "pcre2-syntax-10.40-6.el9" - packagePath: null - severity: "high" - scanSource: "grype" - justification: >- - The syntax-data subpackage of the pcre2 finding of the same identifier: inherited from the - ubi9-micro 9.8 base image, Red Hat lists no fix, and the JIT code the flaw is in is not - loaded by anything Trinity runs (see the pcre2 row). - - finding: "CVE-2026-89161" - package: "pcre2-10.40-6.el9" - packagePath: null - severity: "high" - scanSource: "grype" - justification: >- - Inherited from the ubi9-micro 9.8 base image, which reports the same finding; Red Hat lists - no fix. The flaw needs an attacker-controlled pattern and the JIT API. libpcre2-8 is linked - only by the base's libselinux and libpcre2-posix; the Erlang runtime does not link it, and - ERTS's own PCRE2 10.47 copy contains no JIT source. - - finding: "CVE-2026-89161" - package: "pcre2-syntax-10.40-6.el9" - packagePath: null - severity: "high" - scanSource: "grype" - justification: >- - The syntax-data subpackage of the pcre2 finding of the same identifier: inherited from the - ubi9-micro 9.8 base image, Red Hat lists no fix, and the JIT code the flaw is in is not - loaded by anything Trinity runs (see the pcre2 row). - - finding: "CVE-2026-103111" - package: "pcre2-10.40-6.el9" - packagePath: null - severity: "high" - scanSource: "grype" - justification: >- - Inherited from the ubi9-micro 9.8 base image, which reports the same finding; Red Hat lists - no fix. The flaw is in pcre2_dfa_match. libpcre2-8 is linked only by the base's libselinux - and libpcre2-posix; the Erlang runtime does not link it, and ERTS's own PCRE2 10.47 copy has - no pcre2_dfa_match.c (erts/emulator/pcre in OTP 28.5.0.7). - - finding: "CVE-2026-103111" - package: "pcre2-syntax-10.40-6.el9" - packagePath: null - severity: "high" - scanSource: "grype" - justification: >- - The syntax-data subpackage of the pcre2 finding of the same identifier: inherited from the - ubi9-micro 9.8 base image, Red Hat lists no fix, and the DFA matcher the flaw is in is not - loaded by anything Trinity runs (see the pcre2 row). - - # openssl-libs: added by this image (the crypto NIF links libcrypto). Red Hat lists no fix for - # either; `dnf upgrade` ran against the installroot at build time, so this is the newest build - # UBI's repositories carried. Re-pinned 2026-10-08 from 3.5.8-1 to Red Hat's 3.5.8-2 rebuild, which - # the scan still reports as not fixed for both. + # Retired 2026-10-19, from the scan of run 39257269071 (grype database built 2026-10-19T06:39Z): + # the six pcre2 and pcre2-syntax rows, because the base now carries 10.40-7.el9_8, against which + # none of CVE-2026-86145, CVE-2026-89161 or CVE-2026-103111 is reported; and the + # CVE-2026-84782 row, because Red Hat's security data names openssl-1:3.5.8-2.el9_8, the build in + # this image, as the fixed release (RHSA-2026:77396). + # + # openssl-libs: added by this image (the crypto NIF links libcrypto). Red Hat lists no fix for the + # finding below; `dnf upgrade` ran against the installroot at build time, so this is the newest + # build UBI's repositories carried. Re-pinned 2026-10-08 from 3.5.8-1 to Red Hat's 3.5.8-2 + # rebuild. - finding: "CVE-2026-75804" package: "openssl-libs-1:3.5.8-2.el9_8" packagePath: null @@ -97,14 +42,3 @@ justifications: (readelf -d over all of them); Erlang's crypto NIF links libcrypto alone, and OTP's ssl application implements TLS in Erlang without libssl. Red Hat lists no fix; the build ran dnf upgrade, so this is the newest openssl-libs UBI carried. - - finding: "CVE-2026-84782" - package: "openssl-libs-1:3.5.8-2.el9_8" - packagePath: null - severity: "high" - scanSource: "grype" - justification: >- - The flaw is in OpenSSL's DTLS retransmission, which lives in libssl. No executable or shared - object in this image declares libssl.so.3 as a dependency (readelf -d over all of them); - Erlang's crypto NIF links libcrypto alone, and OTP's ssl application implements DTLS in - Erlang without libssl. Red Hat lists no fix; the build ran dnf upgrade, so this is the - newest openssl-libs UBI carried. diff --git a/docs/regulated/control-mapping.md b/docs/regulated/control-mapping.md index dd8f898..2ce316c 100644 --- a/docs/regulated/control-mapping.md +++ b/docs/regulated/control-mapping.md @@ -102,7 +102,7 @@ register does not claim is never Trinity's. | `IA-5(7)` | The gate fails on a credential-shaped string anywhere in the tracked tree | `lib/mix/tasks/trinity.secrets.scan.ex` | `mix trinity.secrets.scan`; `test/secrets_scan_test.exs` "RED: a planted fake key of each shape is found" | Trinity's | none | none | | `IA-11` | An expired token is refused, and with introspection enabled the issuer is asked about each token; a client then goes back to the identity provider | `lib/trinity/mcp/auth/token.ex`, `config/runtime.exs` | `test/trinity/mcp/auth/token_test.exs` "introspection: an opaque token is asked about at the issuer (RFC 7662), the answer's claims checked as a JWT's are" | shared | Token lifetime, revocation and re-authentication policy, at the identity provider | CRM "Token revocation and session lifetime" | | `IR-6` | Trinity reports nothing to anyone; `incident.md` sets out the clocks that may apply and what the record gives an investigator | `docs/regulated/incident.md` | none | the deployment's | Reporting, within whatever clock applies | CRM "Reporting to a government customer within a clock" | -| `RA-5` | The gate audits every locked dependency on every push; the built image is scanned with grype, and a High or Critical finding without a justification fails. **The image's scan fails today**: OTP findings fixed in a later OTP release have no justification until the toolchain moves | `lib/mix/tasks/trinity.image.findings.ex`, `ci/headless/justifications.yaml` | `mix deps.audit`; `mix trinity.image.findings`; `test/mix/tasks/trinity_image_findings_test.exs` "AC3: a planted High finding with no row fails, and passes once justified" | shared | Scanning what is actually deployed, on the deployment's schedule | CRM "Scanning for known vulnerabilities" | +| `RA-5` | The gate audits every locked dependency on every push; the built image is scanned with grype, and a High or Critical finding without a justification fails. A finding with a fix available is fixed, never justified: the OTP findings fixed in 28.5.0.7 were closed by moving the image to it (ADR-0014) | `lib/mix/tasks/trinity.image.findings.ex`, `ci/headless/justifications.yaml` | `mix deps.audit`; `mix trinity.image.findings`; `test/mix/tasks/trinity_image_findings_test.exs` "AC3: a planted High finding with no row fails, and passes once justified" | shared | Scanning what is actually deployed, on the deployment's schedule | CRM "Scanning for known vulnerabilities" | | `RA-5(11)` | A published process for reporting a vulnerability in this software. It is a document, and no check in the tree holds it | `SECURITY.md` | none | shared | Reporting to it | CRM "A published vulnerability disclosure process for this software" | | `SA-4` | Nothing in the tree is a contract with a model provider | `config/llm.exs` | none | the deployment's | Whether the provider may train on or keep the data, settled in the contract | CRM "Whether the model provider trains on your data" | | `SA-9` | The model in force is configuration: Trinity calls the endpoint it is pointed at, and under the regulated profile only one on the allow-list (the `AC-4` row above) | `config/llm.exs` | none | shared | Choosing the provider and the endpoint | CRM "Which model is called" | diff --git a/mix.lock b/mix.lock index 54b6b65..3f36753 100644 --- a/mix.lock +++ b/mix.lock @@ -1,7 +1,7 @@ %{ "abnf_parsec": {:hex, :abnf_parsec, "2.1.0", "c4e88d5d089f1698297c0daced12be1fb404e6e577ecf261313ebba5477941f9", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e0ed6290c7cc7e5020c006d1003520390c9bdd20f7c3f776bd49bfe3c5cd362a"}, "axon": {:hex, :axon, "0.8.1", "c4a975e62a14ab6c374997b77367ec3a4c2740952ac474d3b0f202c91b7f75c4", [:mix], [{:kino, "~> 0.7", [hex: :kino, repo: "hexpm", optional: true]}, {:kino_vega_lite, "~> 0.1.7", [hex: :kino_vega_lite, repo: "hexpm", optional: true]}, {:nx, "~> 0.10", [hex: :nx, repo: "hexpm", optional: false]}, {:polaris, "~> 0.1", [hex: :polaris, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1 or ~> 4.1", [hex: :table_rex, repo: "hexpm", optional: true]}], "hexpm", "682a3517489300507ac9345f28341e7fa95bc5b4960d645816074ce551795d37"}, - "bandit": {:hex, :bandit, "1.12.5", "af205a8e550f304caae09a97d29fd3c79a7f337526ea7cd772d2ff11d2f7c800", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.5", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "c5684ca062fa407cac115aec3256383f3e2ec9fdced7904d59cf5a7bb7ed6181"}, + "bandit": {:hex, :bandit, "1.13.1", "1ce25a7beb750ed3fa2e905962f1f9a679cb585f1da0ae45637d06e385ce2eae", [:mix], [{:hpax, "~> 1.1", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.6", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "39028f0b5a21404661410a6c19275060a9822c9dcca70b7c3e4e4fa50b11da58"}, "beam_mcp": {:hex, :beam_mcp, "0.10.1", "7345827f19db43b07c32707374a813bf781687eaf6ee1297beb89e113f86a0e9", [:mix], [{:bandit, "~> 1.5", [hex: :bandit, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:plug, "~> 1.16", [hex: :plug, repo: "hexpm", optional: true]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d59b6e75b8c5d06a9d5c71711b1cab4ad7fb84f96495e7907822aa0e3966da20"}, "boundary": {:hex, :boundary, "0.10.4", "5fec5d2736c12f9bfe1720c3a2bd8c48c3547c24d6002ebf8e087570afd5bd2f", [:mix], [], "hexpm", "8baf6f23987afdb1483033ed0bde75c9c703613c22ed58d5f23bf948f203247c"}, "bumblebee": {:hex, :bumblebee, "0.7.1", "0d6a757f955671a379ebcf4ed07565513fa767b1869169b989f65ba7958a80f4", [:mix], [{:axon, "~> 0.8.0", [hex: :axon, repo: "hexpm", optional: false]}, {:jason, "~> 1.4.0", [hex: :jason, repo: "hexpm", optional: false]}, {:nx, "~> 0.12.0 or ~> 0.13.0", [hex: :nx, repo: "hexpm", optional: false]}, {:nx_image, "~> 0.1.0", [hex: :nx_image, repo: "hexpm", optional: false]}, {:nx_signal, "~> 0.2.0", [hex: :nx_signal, repo: "hexpm", optional: false]}, {:safetensors, "~> 0.1.3", [hex: :safetensors, repo: "hexpm", optional: false]}, {:tokenizers, "~> 0.4", [hex: :tokenizers, repo: "hexpm", optional: false]}, {:unpickler, "~> 0.1.0", [hex: :unpickler, repo: "hexpm", optional: false]}, {:unzip, "~> 0.12.0 or ~> 0.13.0", [hex: :unzip, repo: "hexpm", optional: false]}], "hexpm", "f26b3355700e3c7580014d41ce477d87d9fd71a60b76d5b64701dacb07830a58"}, @@ -13,7 +13,7 @@ "credo": {:hex, :credo, "1.7.19", "cc52129665fc7c15143d47838fda0f9cd6dac9ceced7bf4da6f85fcbfe64b12a", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "2d8bc95d5a7bb99dd2613621d4f08c6a3575c3fd4b62e6a2b48a100352a557b8"}, "daisyui": {:git, "https://github.com/saadeghi/daisyui.git", "22ecff57f2c391b80a75617325748cf4d13fdf47", [tag: "v5.5.20", sparse: "packages/bundle", depth: 1]}, "db_connection": {:hex, :db_connection, "2.10.2", "ae391e803a5adff104da913c2fc1c0c14a37f8b10001dcef568796e1fb7bf95c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "510b14482330f1af6490a2fa0efd8d4f1435d1529b165647df22ac0f2df0fa93"}, - "decimal": {:hex, :decimal, "3.1.1", "430d87b04011ce6cbd4fd205be758311a81f87d552d40904abd00f015935b1d0", [:mix], [], "hexpm", "c5f25f2ced74a0587d03e6023f595db8e924c9d3922c8c8ffd9edfc4498cf1f6"}, + "decimal": {:hex, :decimal, "3.2.0", "f1235f05d841a211fda78a08fdb9777f2ff51a71f18455233fae186ff76187c6", [:mix], [], "hexpm", "190bca80580ce61aa69bb0eacadd7677710205c770225c2c2c07e32a5c31b234"}, "dns_cluster": {:hex, :dns_cluster, "0.2.0", "aa8eb46e3bd0326bd67b84790c561733b25c5ba2fe3c7e36f28e88f384ebcb33", [:mix], [], "hexpm", "ba6f1893411c69c01b9e8e8f772062535a4cf70f3f35bcc964a324078d8c8240"}, "dotenvy": {:hex, :dotenvy, "1.2.1", "43b28d17c996d70ac09f6a6dfd2245378b346ed7290c36452b46cdca77cd96bc", [:mix], [], "hexpm", "20cf780119be89a7cae7808543ebc8b7e56f0993b6aa058260fab686afe9073d"}, "earmark_parser": {:hex, :earmark_parser, "1.4.46", "67607a0532e810c6f630a515c548d0b24949643f168cc556303bee4cf96105c7", [:mix], [], "hexpm", "9c44636e8a1c68c62f526b2dcd85d941dbbcee7ab82cf64ba06ce28bef8e89f5"}, @@ -35,7 +35,7 @@ "gettext": {:hex, :gettext, "1.0.2", "5457e1fd3f4abe47b0e13ff85086aabae760497a3497909b8473e0acee57673b", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "eab805501886802071ad290714515c8c4a17196ea76e5afc9d06ca85fb1bfeb3"}, "glob_ex": {:hex, :glob_ex, "0.1.12", "7b2d9369c20e2697efcfd185d13d6e84c94cd3bfd2730fbde613141c2e015c00", [:mix], [], "hexpm", "2e2fac83f113514434c7eaf267b4c38af2f91766f1cab2c5db7053b7fc1ee0bb"}, "heroicons": {:git, "https://github.com/tailwindlabs/heroicons.git", "0435d4ca364a608cc75e2f8683d374e55abbae26", [tag: "v2.2.0", sparse: "optimized", depth: 1]}, - "hex_core": {:hex, :hex_core, "0.19.0", "b9b5d3cde2d5227b85c62c4ab9f9db586e6b38cf65a9bf5b683757cae9ced5eb", [:rebar3], [], "hexpm", "c2cc414f3893a7edbcab28207cadca13829407abe41f689c82f379fe22f026a5"}, + "hex_core": {:hex, :hex_core, "0.20.1", "5c277d24b954be44c311286fc93367bb9f2d5d16c77553961c3076bc2084cfe9", [:rebar3], [], "hexpm", "d74aef5a0f8718e408ef8590df4bf8a7225403b1e134360a4e7dd421e0a2d7b4"}, "hpax": {:hex, :hpax, "1.1.0", "782931867cc23217c68fb5f68fe1a11f5e7544c7fda82c8a7019a5df5a4a1cdf", [:mix], [], "hexpm", "0b8d0f05832f55571d65ac720f79bf8994138ffbb133209dc4685eae0ad456a8"}, "idna": {:hex, :idna, "7.1.0", "1067a13043538129602d2f2ce6899d8713125c7d19734aa557ce2e3ea55bd4f1", [:rebar3], [], "hexpm", "6ae959a025bf36df61a8cab8508d9654891b5426a84c44d82deaffd6ddf8c71f"}, "igniter": {:hex, :igniter, "0.8.4", "f79f1bbdc2fb7b9ca030a22d12a585b060cbf5b94b9d3f23b1148578a9e05d11", [:mix], [{:ex_ast, "~> 0.5", [hex: :ex_ast, repo: "hexpm", optional: false]}, {:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4.5", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "a9b1cbec996ccb100b4f7d8130129b2dd3f18eb4224ac9a0e907e428ca90dbd7"}, @@ -83,7 +83,7 @@ "plug_crypto": {:hex, :plug_crypto, "2.2.0", "144014737daaf485407f5ed77daeaad74d651b216a28c87543f8cc7043f8efc8", [:mix], [], "hexpm", "83a95744ab1c75876542b6fab135fcc176280e0f301a111c1f757fddcec95d2c"}, "polaris": {:hex, :polaris, "0.1.0", "dca61b18e3e801ecdae6ac9f0eca5f19792b44a5cb4b8d63db50fc40fc038d22", [:mix], [{:nx, "~> 0.5", [hex: :nx, repo: "hexpm", optional: false]}], "hexpm", "13ef2b166650e533cb24b10e2f3b8ab4f2f449ba4d63156e8c569527f206e2c2"}, "postgrex": {:hex, :postgrex, "0.22.4", "d271f595dfd25230b6398354e19d17bb5e2d20130fd2d9bdca7e15f125d43552", [:mix], [{:db_connection, "~> 2.9", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "4aae45a2d60e35b04eea2602440be152fae332901f1fc7a60fc7cb7f0f9a9c5a"}, - "protobuf": {:hex, :protobuf, "0.17.0", "39e24e43c9648e148feba16ed51100b5b2028ea900b55460377b0476f6e10613", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "ca6c91f6f63e2c147b47f03eefd10b80538aa6fc55ff4b12b795efb786b0152f"}, + "protobuf": {:hex, :protobuf, "0.17.1", "5c058a4abbdf3c094bbdab142022b22950dd25214fcfdc624ae9bf0c60f9346f", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "76770ecda0ef057e540932f15b5a143ba8877875e88727f53429a7dc4dbf8b6c"}, "purl": {:hex, :purl, "0.5.0", "67cc2a57142236ad4a93184e8db5b83e8285b4b7cf7ad4a67eeefcb61e3134f3", [:mix, :rebar3], [{:stream_data, "~> 1.1", [hex: :stream_data, repo: "hexpm", optional: true]}], "hexpm", "658057133f7698b250821828185997279dba75f3991f5b570357cf3d73f64b4d"}, "req": {:hex, :req, "0.7.4", "23e9ffec17de032a46a4b15ed65c09793893bf4a7c680f4bbf6227fce6bdf74d", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:finch, "~> 0.21", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "4b192d63253e8dcc6221ef992ea9ebef7d3555166e8423aa5b553e86bc3c69a2"}, "req_llm": {:hex, :req_llm, "1.24.0", "cdc5c5cd7f38c0e17cf7a949ced99b6760dcba0d860cd4d066fea1aeba422929", [:mix], [{:dotenvy, "~> 1.1", [hex: :dotenvy, repo: "hexpm", optional: false]}, {:ex_aws_auth, "~> 1.4", [hex: :ex_aws_auth, repo: "hexpm", optional: true]}, {:goth, "~> 1.4", [hex: :goth, repo: "hexpm", optional: true]}, {:igniter, "~> 0.7", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:jsv, "~> 0.11", [hex: :jsv, repo: "hexpm", optional: false]}, {:llm_db, ">= 2026.9.3 and < 2027.0.0", [hex: :llm_db, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.1", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:server_sent_events, "~> 1.1.0", [hex: :server_sent_events, repo: "hexpm", optional: false]}, {:splode, "~> 0.3.0", [hex: :splode, repo: "hexpm", optional: false]}, {:websockex, "~> 0.5.1", [hex: :websockex, repo: "hexpm", optional: false]}, {:zoi, "~> 0.14", [hex: :zoi, repo: "hexpm", optional: false]}], "hexpm", "8ab6bda68e28afc3adf070cc6616d51831b42b8ff144116a9f104999af4c763f"}, @@ -91,7 +91,7 @@ "rustler": {:hex, :rustler, "0.38.0", "7a8906998ff0d28e3021c0a73264abcda719bda344b2e58307c6805b0f87c9b4", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "704c03c1bf66be12b031c5a389347b91c81c5cb819a24b068b0de36fe4a5652a"}, "rustler_precompiled": {:hex, :rustler_precompiled, "0.9.0", "3a052eda09f3d2436364645cc1f13279cf95db310eb0c17b0d8f25484b233aa0", [:mix], [{:rustler, "~> 0.23", [hex: :rustler, repo: "hexpm", optional: true]}], "hexpm", "471d97315bd3bf7b64623418b3693eedd8e47de3d1cb79a0ac8f9da7d770d94c"}, "safetensors": {:hex, :safetensors, "0.1.3", "7ff3c22391e213289c713898481d492c9c28a49ab1d0705b72630fb8360426b2", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nx, "~> 0.5", [hex: :nx, repo: "hexpm", optional: false]}], "hexpm", "fe50b53ea59fde4e723dd1a2e31cfdc6013e69343afac84c6be86d6d7c562c14"}, - "sbom": {:hex, :sbom, "0.11.0", "b36348696dc1a0a0a350ebf26de12b7508fb774e565e213cba95751fc73a5447", [:mix], [{:hex_core, "~> 0.19.0", [hex: :hex_core, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:optimus, "~> 0.6.1", [hex: :optimus, repo: "hexpm", optional: false]}, {:protobuf, "~> 0.17.0", [hex: :protobuf, repo: "hexpm", optional: false]}, {:purl, "~> 0.5.0", [hex: :purl, repo: "hexpm", optional: false]}], "hexpm", "edbd95e75155fed8fe61a19f21ea590c58178fe237c816afa534bc514ae53eb9"}, + "sbom": {:hex, :sbom, "0.12.0", "7d90bc70e5e83173df55620fb693226c4c88aca2eac1849dc0de01b5f746f5e9", [:mix], [{:hex_core, "~> 0.19.0 or ~> 0.20.0", [hex: :hex_core, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:optimus, "~> 0.6.1", [hex: :optimus, repo: "hexpm", optional: false]}, {:protobuf, "~> 0.17.0", [hex: :protobuf, repo: "hexpm", optional: false]}, {:purl, "~> 0.5.0", [hex: :purl, repo: "hexpm", optional: false]}], "hexpm", "8a5474f6492fd54efdf84428f04be753ddde8c50f9f8ae301eb6fbaddfa2234b"}, "server_sent_events": {:hex, :server_sent_events, "1.1.0", "54606238b9182ba673a10ce90bd95f81a0939c1baa91cdf86b66acc04edd1832", [:mix], [], "hexpm", "8e164db8e295a2d869a8faafbf4a1eeaa749b62fe93f66271adff6394d93ce15"}, "sobelow": {:hex, :sobelow, "0.15.0", "b067d7f8522a9d758fa89cb2bfcbab7ad72c45a0993cb958c989c6fd956fdd56", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "24a800e2d7fa8c3bd21561b6ad8ad4745ed726a09fd606598981d9048708da98"}, "sourceror": {:hex, :sourceror, "1.12.3", "f58eebef0765c7a369a49a755ab2a5ee88d92777f403ea5a08b722cefcc37f51", [:mix], [], "hexpm", "d5f2f37099de794840f08c54ae546d7f6e4ea015e397be64aebe4996fa9f7da7"}, @@ -105,7 +105,7 @@ "telemetry_registry": {:hex, :telemetry_registry, "0.3.2", "701576890320be6428189bff963e865e8f23e0ff3615eade8f78662be0fc003c", [:mix, :rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7ed191eb1d115a3034af8e1e35e4e63d5348851d556646d46ca3d1b4e16bab9"}, "text_diff": {:hex, :text_diff, "0.1.0", "1caf3175e11a53a9a139bc9339bd607c47b9e376b073d4571c031913317fecaa", [:mix], [], "hexpm", "d1ffaaecab338e49357b6daa82e435f877e0649041ace7755583a0ea3362dbd7"}, "texture": {:hex, :texture, "1.2.1", "ae8d875fb099bcc1ccb0269296bf6c4590116eff9a44895049379359a14d5fca", [:mix], [{:abnf_parsec, "~> 2.0", [hex: :abnf_parsec, repo: "hexpm", optional: false]}], "hexpm", "925b1938891ce5c1d589df408faa7ed57dd872e4aac9521b32495a29b265cb17"}, - "thousand_island": {:hex, :thousand_island, "1.5.0", "f50a213cac97262b6d5ebb85745aa2c00fec1413191e6e66834788d45425cecb", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "708923d40523e43cf99041ab37a0d4b0ec426ac6438fa3716ab23d919eaeb412"}, + "thousand_island": {:hex, :thousand_island, "1.6.0", "d6ad7c2220c96a270cc8ca1586ec4d8bde648a4a3a53f003ce093f41beec0288", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4d4720470f091fc21a1311777c2ac66916c43621bffc068590c9f9bb3cd057a3"}, "tokenizers": {:hex, :tokenizers, "0.5.1", "b0975d92b4ee5b18e8f47b5d65b9d5f1e583d9130189b1a2620401af4e7d4b35", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:rustler, ">= 0.0.0", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.6", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}], "hexpm", "5f08d97cc7f2ed3d71d370d68120da6d3de010948ccf676c9c0eb591ba4bacc9"}, "toml": {:hex, :toml, "0.7.0", "fbcd773caa937d0c7a02c301a1feea25612720ac3fa1ccb8bfd9d30d822911de", [:mix], [], "hexpm", "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70"}, "typed_struct": {:hex, :typed_struct, "0.3.0", "939789e3c1dca39d7170c87f729127469d1315dcf99fee8e152bb774b17e7ff7", [:mix], [], "hexpm", "c50bd5c3a61fe4e198a8504f939be3d3c85903b382bde4865579bc23111d1b6d"}, diff --git a/test/trinity/mcp/client_test.exs b/test/trinity/mcp/client_test.exs index 5aebeec..744534a 100644 --- a/test/trinity/mcp/client_test.exs +++ b/test/trinity/mcp/client_test.exs @@ -78,6 +78,14 @@ defmodule Trinity.MCP.ClientTest do end test "AC6: the server dies: the tools are unregistered; it is restarted with backoff; the tools are registered again" do + # The client is :down only for the backoff before its reconnect, and `await/3` polls every + # 50 ms. With the suite's 50 ms backoff (config/test.exs) the window was one poll wide, so a + # quick reconnect passed through :down and :connecting unseen and every later poll read + # :ready: the postgres job failed this way on 2026-10-19, and a 1 ms backoff reproduces it + # three times in three. A 1 s backoff holds :down for twenty polls; what is asserted is the same. + prev = Application.get_env(:trinity, :mcp_client, []) + Application.put_env(:trinity, :mcp_client, Keyword.put(prev, :backoff_ms, 1_000)) + on_exit(fn -> Application.put_env(:trinity, :mcp_client, prev) end) start!("r", :modern) %{transport_os_pid: os_pid} = await("r", :ready) assert {:ok, _} = Tools.lookup("mcp:r:echo") diff --git a/test/trinity/secrets_boot_node_test.exs b/test/trinity/secrets_boot_node_test.exs index 722c571..72430fa 100644 --- a/test/trinity/secrets_boot_node_test.exs +++ b/test/trinity/secrets_boot_node_test.exs @@ -172,7 +172,14 @@ defmodule Trinity.SecretsBootNodeTest do %{key_id: kid, key_path: path} = Trinity.Receipts.KeyCustody.selected() IO.puts("NEW_KEY " <> kid) IO.puts("NEW_PATH " <> path) - boot = Trinity.Receipts.boot_receipt() + # The boot receipt is written by `Trinity.Effects.Boot`, a transient task the application + # starts, so `ensure_all_started/1` can return before it is written. Polled for, as + # `TrinityWeb.Auth.BootRefusalNodeTest` does; reading it at once failed a gate run on 2026-10-19 + # (`BadMapError ... nil` after NEW_PATH). + boot = + Enum.find_value(1..50, fn _ -> + Trinity.Receipts.boot_receipt() || (Process.sleep(100) && nil) + end) IO.puts("MOVE " <> JSON.encode!(JSON.decode!(boot.signed_payload)["subject"]["secrets_migration"])) {:ok, export} = Trinity.Receipts.export("boot") IO.puts("COUNT " <> Integer.to_string(length(export["receipts"])))