Skip to content

Commit 36e9c3e

Browse files
Merge branch 'main' into feat/gossamer-android-migration-83
2 parents 34d402e + 71d2d99 commit 36e9c3e

18 files changed

Lines changed: 1471 additions & 46 deletions

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,10 @@ dist/
9797

9898
# TLA+ model-checker jar cache (fetched by `just proof-tla`)
9999
.tlacache/
100+
101+
# Dafny release cache (fetched by `just proof-dafny`)
102+
.dafnycache/
103+
104+
# Lean/Lake build cache and vendored Mathlib checkout (fetched/built by
105+
# `just proof-lean`); see proofs/lean/.gitignore.
106+
proofs/lean/.lake/

.machine_readable/contractiles/trust/Trustfile.a2ml

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -225,22 +225,23 @@ artifacts:
225225
- id: "0.1" ; name: "Panic-freedom (operational paths)" ; system: "proptest" ; file: "crates/*/tests/proptest_*.rs, neurophone-core/tests/proptest_core.rs" ; status: "TESTED"
226226
- id: "0.2" ; name: "Numeric containment (no NaN/Inf)" ; system: "proptest" ; file: "crates/*/tests/proptest_numeric.rs, proptest_core.rs" ; status: "TESTED"
227227
- id: "0.3" ; name: "unsafe discipline" ; system: "rustc" ; file: "forbid/deny(unsafe_code) across crates" ; status: "PROVEN"
228-
- id: "1.1" ; name: "Echo State Property" ; system: "Lean/TLA" ; file: "proofs/lean/, proofs/tla/" ; status: "DESIGNED"
229-
- id: "1.2" ; name: "LSM bounded dynamics" ; system: "proptest/Dafny" ; file: "crates/lsm/tests/proptest_bounds.rs" ; status: "TESTED"
228+
- id: "1.1" ; name: "Echo State Property (contraction, ‖W‖∞ hypothesis)" ; system: "Lean 4 + Mathlib" ; file: "proofs/lean/EsnEcho.lean" ; status: "PROVEN"
229+
- id: "1.2" ; name: "LSM bounded dynamics (membrane-potential ceiling)" ; system: "proptest + Dafny" ; file: "crates/lsm/tests/proptest_bounds.rs, proofs/dafny/LsmBoundedDynamics.dfy" ; status: "PROVEN"
230230
- id: "1.3" ; name: "Bridge soundness" ; system: "proptest" ; file: "crates/bridge/tests/proptest_soundness.rs" ; status: "TESTED"
231231
- id: "2.1" ; name: "Lifecycle state-machine safety" ; system: "TLA+ (TLC)" ; file: "proofs/tla/Lifecycle.{tla,cfg}" ; status: "PROVEN"
232232
- id: "2.2" ; name: "Concurrency safety" ; system: "TLA+" ; file: "(deferred: single-owner design, no shared concurrency)" ; status: "DESIGNED"
233233
- id: "2.3" ; name: "Resource/affine lifecycle" ; system: "rustc (compile-fail)" ; file: "neurophone-core compile-fail doc-tests + android reset" ; status: "PROVEN"
234-
- id: "3.1" ; name: "Data-egress / privacy" ; system: "arch + conative-gating" ; file: "claude-client (egress confined); veto external #103" ; status: "DESIGNED"
234+
- id: "3.1" ; name: "Data-egress / privacy" ; system: "proptest + conative-gating" ; file: "crates/claude-client/src/egress_gate.rs (wired into create_message), egress_integration_tests" ; status: "TESTED"
235235
- id: "3.2" ; name: "Bounded external interaction" ; system: "proptest/contract" ; file: "claude-client: capped backoff + bounded retries (backoff_is_bounded_and_monotonic, total_retry_budget_is_finite)" ; status: "TESTED"
236236
open_obligations:
237-
- "1.1 formal contraction proof (precondition holds; theorem pending)"
238-
- "1.2 formal Dafny bound (property evidence exists)"
237+
- "1.1 bridge lemma: spectral_radius(W) < 1 (what esn::scale_to_spectral_radius actually enforces) does NOT in general imply ‖W‖∞ < 1 (the Lean theorem's hypothesis) for non-normal matrices — the contraction theorem itself is proven (proofs/lean/EsnEcho.lean), but this connecting lemma is a separate, harder, unattempted result (would need a Gelfand's-formula-style construction)."
238+
- "1.2 the two-sided [reset, threshold] membrane-potential bound is FALSE in general (see the LowerBoundFails counterexample in proofs/dafny/LsmBoundedDynamics.dfy) — only the one-sided ceiling bound is provable and is now proven; the exact-real-vs-f32 gap and the spike-history-length bound (crates/lsm/tests/proptest_bounds.rs's third invariant) remain open, not attempted in Dafny this pass."
239239
- "2.2 concurrency spec (N/A until shared concurrency exists)"
240-
- "3.1 GO/NO-GO egress veto (blocked on conative-gating, #103)"
240+
- "3.1 caller-declared classification honesty (EgressClass is asserted by the caller, not derived from an independent sensor-provenance tracker); no formal (non-property) proof yet"
241241
non_claims:
242-
- "neurophone does NOT yet claim a machine-checked Echo State Property (1.1)."
243-
- "neurophone does NOT yet enforce a cryptographic egress veto (3.1)."
242+
- "neurophone's 1.1 Lean proof (proofs/lean/EsnEcho.lean) establishes the classical reservoir contraction / echo-state theorem under a ‖W‖∞ < 1 hypothesis; it does NOT claim — and this is false in general for non-normal matrices — that the code's actual spectral-radius scaling implies that hypothesis. The code-to-theorem bridge is a separate, still-open lemma."
243+
- "neurophone's 1.2 Dafny proof (proofs/dafny/LsmBoundedDynamics.dfy) establishes only a one-sided (ceiling) bound on membrane potential; it does NOT claim a lower (floor) bound (a Dafny counterexample refutes one), and it does not model f32 floating-point rounding, subnormals, or overflow."
244+
- "neurophone's egress veto (3.1) enforces policy on a caller-declared payload classification; it does NOT independently verify that classification against real sensor provenance, and it is NOT a formally-proven (only property-tested) guarantee."
244245

245246
---
246247
### [CI_CD]

Cargo.lock

Lines changed: 64 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Justfile

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,55 @@ proof-tla:
8181
cd proofs/tla
8282
java -XX:+UseParallelGC -cp "$jar" tlc2.TLC -config Lifecycle.cfg Lifecycle.tla
8383
84+
# Type-check the Lean 4 Echo State Property proof (obligation 1.1, issue
85+
# #84, `proofs/lean/EsnEcho.lean`). Self-skips (non-fatal) if `lake` (elan)
86+
# isn't on PATH. First run needs network access to fetch the pinned Mathlib
87+
# revision (see `proofs/lean/lake-manifest.json`); later runs reuse the
88+
# local `.lake/` cache.
89+
proof-lean:
90+
#!/usr/bin/env bash
91+
set -euo pipefail
92+
if ! command -v lake >/dev/null 2>&1; then
93+
echo "proof-lean: lake not found — skipping Lean check (spec unchanged)"; exit 0
94+
fi
95+
cd proofs/lean && lake build
96+
97+
# Verify the Dafny LSM-bounded-dynamics proof (obligation 1.2, issue #84,
98+
# `proofs/dafny/LsmBoundedDynamics.dfy`). Uses a system `dafny` if present;
99+
# otherwise fetches a pinned, checksum-verified, self-contained release into
100+
# `.dafnycache/` on first run. Self-skips (non-fatal) if no system `dafny`
101+
# is found and the download isn't possible (offline / no permissions), so
102+
# it degrades gracefully in minimal/offline environments.
103+
proof-dafny:
104+
#!/usr/bin/env bash
105+
set -euo pipefail
106+
if command -v dafny >/dev/null 2>&1; then
107+
dafny verify proofs/dafny/LsmBoundedDynamics.dfy
108+
exit 0
109+
fi
110+
version=4.11.0
111+
sha256=a46a9ff7cdd720f7955854c78e95df13f4cfe6b80691b05f8654fe19e8267179
112+
dir="$(pwd)/.dafnycache/dafny-${version}"
113+
bin="${dir}/dafny/dafny"
114+
if [ ! -x "$bin" ]; then
115+
zip="$(pwd)/.dafnycache/dafny-${version}.zip"
116+
mkdir -p "$dir"
117+
if ! curl -fsSL -o "$zip" \
118+
"https://github.com/dafny-lang/dafny/releases/download/v${version}/dafny-${version}-x64-ubuntu-22.04.zip"; then
119+
echo "proof-dafny: could not download Dafny (offline?) — skipping"; exit 0
120+
fi
121+
echo "${sha256} ${zip}" | sha256sum -c - || {
122+
echo "proof-dafny: checksum mismatch on downloaded release, aborting"; exit 1
123+
}
124+
unzip -q "$zip" -d "$dir"
125+
fi
126+
"$bin" verify proofs/dafny/LsmBoundedDynamics.dfy
127+
84128
# Run the full proof surface: property tests + compile-fail typestate doc-tests
85-
# (via `cargo test`) plus the TLA+ model check.
86-
proof: test proof-tla
87-
@echo "Proof surface checked (properties, typestate compile-fails, TLC)."
129+
# (via `cargo test`) plus the TLA+ model check, the Lean type-check, and the
130+
# Dafny verification.
131+
proof: test proof-tla proof-lean proof-dafny
132+
@echo "Proof surface checked (properties, typestate compile-fails, TLC, Lean, Dafny)."
88133

89134
# Quality gates (RSR golden path `just test && just quality`).
90135
quality: fmt-check lint audit

audits/assail-classifications.a2ml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
; SPDX-License-Identifier: MPL-2.0
2+
; assail-classifications.a2ml — user-classification registry for panic-attack.
3+
;
4+
; Flips audited-sound findings to `suppressed = true` AFTER the kanren
5+
; structural-suppression pass (see panic-attack src/assail/mod.rs ::
6+
; apply_user_classifications). Matching is exact on (file, category). This
7+
; registry lives apart from the source under scan, so a finding cannot
8+
; self-suppress — each entry is a reviewable companion change with a rationale
9+
; cross-referenced to audits/audit-install-scripts.adoc.
10+
;
11+
; All entries below are audited false positives; neurophone has 0 real
12+
; vulnerabilities (production: 0 unsafe, 0 panic sites, 0 injection, 0 crypto
13+
; misuse). The PanicPath findings are auto-suppressed (test/bench) and need no
14+
; classification here.
15+
;
16+
; The top-level identity/version fields below satisfy the estate a2ml-validate
17+
; gate (matching a2ml-ecosystem conformance/valid/sectioned-manifest.a2ml);
18+
; panic-attack's loader ignores everything outside `(classification …)` blocks,
19+
; so they do not affect suppression.
20+
21+
project = "neurophone"
22+
schema_version = "1.0.0"
23+
24+
[metadata]
25+
name = "assail-classifications"
26+
version = "1.0.0"
27+
28+
(assail-classifications
29+
(classification
30+
(file "scripts/setup.sh")
31+
(category "PathTraversal")
32+
(audit "audits/audit-install-scripts.adoc §1")
33+
(rationale "Android /data/local/tmp device-staging path (adb), not host /tmp"))
34+
(classification
35+
(file "scripts/install-on-phone.sh")
36+
(category "PathTraversal")
37+
(audit "audits/audit-install-scripts.adoc §1")
38+
(rationale "adb push target on the device: /data/local/tmp/<model>, not host /tmp"))
39+
(classification
40+
(file "scripts/uninstall.sh")
41+
(category "PathTraversal")
42+
(audit "audits/audit-install-scripts.adoc §1")
43+
(rationale "adb shell rm of the on-device model at /data/local/tmp, not host /tmp"))
44+
(classification
45+
(file "scripts/install-termux.sh")
46+
(category "HardcodedSecret")
47+
(audit "audits/audit-install-scripts.adoc §2")
48+
(rationale "public HuggingFace model URL + commented sk-ant placeholder in a config template; no secret")))

audits/audit-install-scripts.adoc

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// SPDX-License-Identifier: CC-BY-SA-4.0
2+
// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
3+
= Audit: install/uninstall shell scripts — panic-attack residuals
4+
:toc:
5+
:revdate: 2026-07-01
6+
7+
Companion audit for `audits/assail-classifications.a2ml`. Records why the
8+
`panic-attack assail` findings in the install/uninstall shell scripts are
9+
audited-sound false positives, so they may be classified `suppressed` in a
10+
reviewable, out-of-band way (per the panic-attack user-classification pattern —
11+
the registry lives apart from the source, so a finding cannot self-suppress).
12+
13+
Scan of record: `panic-attack assail .` — 9 weak points, 0 real vulnerabilities.
14+
The 5 `PanicPath` findings are already auto-suppressed (test/bench code). The
15+
four production-script findings below are the audited residuals.
16+
17+
== §1 PathTraversal — Android device-staging paths (not host `/tmp`)
18+
19+
Files: `scripts/setup.sh`, `scripts/install-on-phone.sh`, `scripts/uninstall.sh`.
20+
21+
The detector pattern-matched `/tmp/` inside `/data/local/tmp/`. That is **not**
22+
the host temp directory — it is the canonical Android **on-device** staging path
23+
used with `adb push` / `adb shell`, the standard location for pushing a model
24+
file to a phone:
25+
26+
* `scripts/setup.sh` — an `echo` instruction: `Place in: /data/local/tmp/ on the device`.
27+
* `scripts/install-on-phone.sh` — `DEVICE_MODEL_PATH="/data/local/tmp/${MODEL_FILE}"`, the `adb push` target on the device.
28+
* `scripts/uninstall.sh` — `adb shell rm -f /data/local/tmp/llama-…gguf`, removing the model from the device.
29+
30+
There is no host-side symlink/traversal surface here: the path is a device path
31+
addressed through `adb`, and applying `mktemp` would be **incorrect** (you cannot
32+
`mktemp` a device path, and `/data/local/tmp/` is the required Android location).
33+
Classification: audited-sound false positive.
34+
35+
== §2 HardcodedSecret — public URL + commented key template
36+
37+
File: `scripts/install-termux.sh`.
38+
39+
* Line ~19: `MODEL_URL="https://huggingface.co/…/Llama-3.2-1B-Instruct-Q4_K_M.gguf"` — a **public** model download URL, not a secret.
40+
* The `sk-ant-…` string is a **commented-out placeholder** inside a generated
41+
`config.toml` template (`# api_key = "sk-ant-..." # uncomment + set …`),
42+
showing the user where to place *their own* key. No credential is committed.
43+
44+
No secret enters source control (corroborated by the estate secret-scanner gate).
45+
Classification: audited-sound false positive.

crates/claude-client/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@ tracing = { workspace = true }
1313
tokio = { workspace = true }
1414
reqwest = { workspace = true }
1515
futures = { workspace = true }
16+
uuid = { version = "1", features = ["v4"] }
17+
# conative-gating is pre-1.0 (0.1.0) and not published to crates.io, so it is
18+
# consumed as a git dependency pinned to a specific commit SHA on the public
19+
# hyperpolymath/conative-gating repo (issue #103 / proof obligation 3.1:
20+
# egress GO/NO-GO veto). Do NOT float on a branch -- re-pin deliberately.
21+
policy-oracle = { git = "https://github.com/hyperpolymath/conative-gating", rev = "7baaf25ecb20ec2a75f138ec69c1681e7677021a" }
22+
gating-contract = { git = "https://github.com/hyperpolymath/conative-gating", rev = "7baaf25ecb20ec2a75f138ec69c1681e7677021a" }
1623

1724
[dev-dependencies]
1825
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }

0 commit comments

Comments
 (0)