Skip to content

feat(trios-mesh): trios integration - crypto hardening, HELLO MAC verification, and three spec-pipeline repairs - #385

Open
gHashTag wants to merge 10 commits into
mainfrom
feat/trios-integration
Open

feat(trios-mesh): trios integration - crypto hardening, HELLO MAC verification, and three spec-pipeline repairs#385
gHashTag wants to merge 10 commits into
mainfrom
feat/trios-integration

Conversation

@gHashTag

Copy link
Copy Markdown
Owner

Integration branch for trios-mesh. 10 commits, 26 files. Only the top commit was authored in this session; the other nine were already on origin/feat/trios-integration and are carried along by the branch. Flagging that up front so the scope is not mistaken for a single-topic PR.

What is in it

Spec-pipeline repairs (3 commits)

Crypto and daemon hardening (3 commits)

  • 27a76f2 - verify HELLO MAC, freshness and src before accepting a beacon.
  • 837a563 - per-session HELLO MAC via HKDF + HMAC-SHA256.
  • 8510062 - secure StaticKey constructors, re-export x25519 types.

Build and robustness (4 commits)

  • e4caeca - cargo fmt only, no functional change.
  • e4d1cac - panic-harden crypto/discovery/router, Result-based meshd startup.
  • 2f5e397 - workspace lints and NaN-safe ETX ordering.
  • 8826316 - compile in the trios workspace.

Verification of the top commit

a08dd04 fixes three defects behind four failing assertions: an 8-bit verified field with a 1000 witness, an 8-bit-lane trust array whose callers handed it 32-bit packed words, and a >= 60 threshold on interaction counts that give 57. The spec is the only thing edited; gen/rust/trust_manager.rs was regenerated by redirecting the compiler.

Because a function count cannot catch a statement dropped inside a test block, AST node counts were checked as well:

fn declared / pub fn emitted    21 / 21
FnDecl / TestBlock / assert     21 / 17 / 23, matching the spec
rustc --crate-type lib          exit 0, 0 errors
spec test assertions            23 pass, 0 fail (was 4 fail)
cargo check --all-targets       exit 0
spec -> artifact drift          none; artifact reproducible from its spec

should_route_via_node_false was previously a false pass - it read garbage from an overlapping slot that happened to fall under 90. It now passes for the stated reason.

Two pre-existing gate contradictions this PR does not fix

Disclosed rather than left for the reviewer to trip over. Neither is caused by these commits.

  1. spec-drift-guard Zig and C steps cannot pass on any branch. The workflow diffs gen/zig/<spec>.zig and gen/c/<spec>.c for all 68 specs, but this repository contains only gen/rust/. Those two steps are red for every spec regardless of content. The Rust step is meaningful and the top commit passes it.

  2. lefthook's no-gen-edits rejects any staged file under gen/, while gen/ is tracked and build.rs rewrites it. That is build: build.rs rewrites tracked gen/ every build vs no-gen-edits hook — self-contradictory (R1) #101. Any commit that correctly regenerates an artifact violates the hook; this one did, and only landed because the hook is not installed locally.

Also unfixed and unrelated: line 2 of all 68 files under gen/rust/ carries a non-ASCII em dash in t27c's own generated header, against L3 PURITY. It is committed at main already and fixing it means editing t27c, which L0b assigns elsewhere.

phi^2 + phi^-2 = 3

🤖 Generated with Claude Code

gHashTag and others added 10 commits July 21, 2026 17:08
- Fix build.rs type error in metadata age calculation
- Disable broken auto-discovered binaries (autobins=false)
- Exclude stub gen/rust modules from lib.rs
- Inline wire spec implementation until t27c produces valid Rust

phi^2 + phi^-2 = 3
- Inherit [lints] workspace = true so unwrap_used = deny applies.
- Allow unwrap_used in test code via cfg_attr while production stays guarded.
- Fix partial_cmp unwraps in router.rs and routing.rs with NaN-as-worst ordering.
- Replace build.rs unwraps on file_stem/to_str with safe Option handling.

Closes #T27-EPIC-001
… startup Result-based

- Add MeshError::CryptoInternal and Result propagation for HKDF/ChaCha ops.
- Convert combine_dh_shares, NoiseXX::complete_*, Session::from_shared/ratchet,
  Handshake::complete, StaticKey::session_with to return Result.
- Safe u32/u64 readers replace try_into().expect() in Session::open.
- Hello::compute_mac/authenticated return Result; verify_mac returns false on failure.
- VecTransport::take recovers from mutex poison in tests.
- Rewrite trios_meshd.rs: Result-based startup, line-numbered config errors,
  mutex poison recovery, .trinity/run/mesh.drop default with TRIOS_MESH_DROP override.
- Leave trios-meshd unregistered due to pre-existing API drift; document in Cargo.toml.
- ASCII-clean all changed source lines.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add StaticKey::from_secret for wrapping an existing StaticSecret.
- Add StaticKey::generate() using OS CSPRNG for production identities.
- Keep from_seed test-only with explicit doc warning.
- Re-export PublicKey and StaticSecret so callers do not need a direct
  x25519-dalek dependency.

Needed by clade-meshd to drop deterministic node-id-derived seeds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Remove hardcoded global HELLO_MAC_KEY fallback

- Hello::authenticated/verify_mac now require a real 32-byte session key

- Replace AEAD-tag-as-MAC with HMAC-SHA256 over (src||seq||ts||heard[])

- Add hmac + subtle deps

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Every one of these seven specs was generating incomplete Rust, silently.
t27c's parser does `Err(_) => self.recover_to_stmt_boundary()` in
`parse_fn_body`, and the recovery skips to the next `;` at brace depth 0 - so
a statement it cannot parse takes everything up to that semicolon with it,
before any backend runs. Exit code 0, empty stderr. Reported upstream as
gHashTag/t27#2508.

The triggers are ordinary, correct t27: an `if` whose condition is not
parenthesised, `match` in any position, `let x = if ...`. And an implicit
tail return lowers to `expr;` WITH a semicolon, so the function returns ().

Measured before and after, 91 functions across the seven specs:

    declared in specs   91 -> 91
    emitted             86 -> 91     (5 were being deleted outright)
    unimplemented!()    14 ->  0
    artifact matches a fresh gen-rust: 7 of 7

Five functions were not stubs. They were absent - four in m3_multihop, one in
production_deployment - which is worse than a stub, because a stub at least
announces itself. `unimplemented!()` turns out to be the empty-body fallback,
so counting stubs understates the damage.

Each spec was rewritten into the subset the parser accepts - parenthesised
conditions, explicit returns - and its artifact REGENERATED, never hand
edited. `gen/` is generated and this repository's own law forbids touching it;
a proposed patch that edited gen/rust/adaptive_retry.rs directly was rejected
for that reason before any of this landed.

Equivalence was proven by execution, not argument. adaptive_retry was
compared against a transcription of the pre-change source over its complete
input space - 132,096 comparisons, run twice, once with overflow checks on
and once off - with a census confirming 63,521 of those inputs actually panic
under checks, so the panic-domain comparison is not vacuous. Every existing
overflow and wrap point is preserved exactly, including two latent ones in
adaptive_retry that are real bugs and were deliberately left alone: changing
them would change behaviour, which is not what this commit is for.

Two exceptions, disclosed rather than buried:

pattern_predictor is the one genuine spec bug, not a workaround. Its source
line read `return ((array >> 64) & 0xFFFFFFFFFFFFFFFF;` - unbalanced
parentheses, and a `>> 64` on a u64. Repairing it changed
get_sample_array_lower from u32 to u64, which IS a behaviour change with no
prior caller: exhaustive 8-slot pack-and-read round trips go from 1020 of
2048 failing to 0 of 2048, with no cross-talk. The old accessor was wrong.

olsr_routing still fails bare rustc, and every one of its 25 errors is on a
line this commit did not touch: the spec declares u32 while its own
expressions treat an entry as 64 bits and the table as 256. Constant shifts
past the type width are a hard error. Widening it is a real change to the
data model and is left for someone who can decide it rather than guess.
m3_multihop's remaining rustc failure is only `serde` not being passed as an
extern; the crate already declares that dependency and the file builds under
cargo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`trust_manager.t27` declares 21 functions. `t27c gen-rust` emitted 12. Nine
were gone - not stubbed, gone - and nothing anywhere reported it: exit 0,
empty stderr, and a `t27c parse` that cheerfully returns 12 FnDecl nodes.

The cause is a single character. Line 80 read

    if (index == 6) { { return ((array >> 8) & 0xFFFFFFFF) as u32; }

with one brace too many, and line 316 carried a stray `}` after the module's
own closing brace which balanced the token count. So the file looked clean.
The parser's `recover_to_stmt_boundary` then swallowed the whole remainder of
the module into that one failed statement, taking every function declared
after line 80 with it.

That is the shape of gHashTag/t27#2508: a real typo, silently amplified into
nine deletions by a recovery that reports nothing. The typo is ours. The
silence is the compiler's.

Verified per function rather than assumed: eight of the nine were extracted
VERBATIM into a probe and lower perfectly; the ninth does too. None of them
contained a rejected construct. They were collateral, all of it.

Two further defects, found while restoring them and fixed here because they
were compile blockers:
  - line 67 used `||` between two u64 shift chains where the other seven slots
    use `|`. It lowered to a Rust logical-or on u64 and produced two of the
    three E0308 errors in the old artifact.
  - line 154 called `get_tr_score_value`, a name that exists nowhere in this
    submodule. t27c lowers a call to an undefined function without complaint,
    so it only became visible once the function was no longer deleted.

Four token edits, no logic rewritten. The artifact was regenerated by
redirecting the compiler, never hand-edited, and is byte-identical across
three consecutive generations.

Equivalence proven by execution: 55.9 billion differential comparisons against
an oracle transcribed independently from the spec text, 0 mismatches. The
first run finished in 8 seconds because LLVM proved both sides equal and
deleted the loop bodies - a false pass, caught and rebuilt with black_box on
every input and every result, which took it to 225 seconds. Coverage is
exhaustive where the domain allows: all 2^32 inputs against each of the eleven
single-argument functions, all 256^4 tuples against both packers.

Independently audited: the four token edits applied mechanically to
HEAD reproduce the delivered spec exactly, comment for comment.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The parser-drop repair in 9bcbd80 restored nine deleted functions without
touching logic, which left four of the spec's own `test` assertions failing
against the artifact. All four predate that commit. Reproduced before
changing anything: 232, 57, 3, false.

Nothing in the repository consumes this layout yet - gen/rust/ is excluded
from compilation in src/lib.rs, and no file outside the spec and its
artifact names trust_manager - so each of these was cheap now and expensive
later. The three directions were confirmed with the operator, not guessed.

verified is 8 bits and stays 8 bits. create_trust_relationship masks
`verified & 0xFF` and the witness was 1000, stored as 232. The test now
passes 200. Widening the field would have made last_verified a real
timestamp at the cost of a u64 return type and the 4x8 layout every other
packed word in this module uses.

The trust array is 8-bit lanes, in both directions. create_trust_array
packs at shifts 56..0 - byte lanes - while its callers handed it 32-bit
create_trust_score words, so the score byte was shifted out before any OR
happened and only the negative-interaction byte reached the slot.
get_trust_score then extracted `& 0xFFFFFFFF` from lanes eight bits wide,
splicing neighbours together. Three edits, chosen as one:

  - create_trust_array masks each argument `& 0xFF`. Its contract is now a
    bare score value rather than a packed word, and the comment says so.
  - get_trust_score masks `& 0xFF`, matching the lane it reads.
  - find_most_trusted (8 sites) and should_route_via_node call
    get_trust_score directly. The old
    get_trust_score_value(get_trust_score(...)) shifted a value at most 8
    bits wide right by 16 and always read zero.

MAX_NODES stays 8. The alternative - two 32-bit slots - would have cut the
trust table to two nodes and deleted six of find_most_trusted's eight
branches, against the spec's own "8-node trust storage".

update_trust_score_increases asserted >= 60 from interaction counts that
give 57. Five positive and five negative start at 50; adding three and one
gives 8 of 14. The bound is now > 50, mirroring update_trust_score_decreases'
< 50. The arithmetic was right and the test was wrong.

should_route_via_node_false was passing for the wrong reason: it read
garbage from an overlapping slot that happened to fall under 90. It now
passes because 75 >= 90 is false.

Verified rather than assumed. A function count alone cannot catch a
statement dropped inside a test block, so the AST was counted too:

    fn declared / pub fn emitted    21 / 21
    FnDecl / TestBlock / assert     21 / 17 / 23, matching the spec
    rustc --crate-type lib          exit 0, 0 errors
    spec test assertions            23 pass, 0 fail (was 4 fail)
    cargo check --all-targets       exit 0

The artifact was regenerated by redirecting the compiler, never hand
edited: generated to a temporary file, validated there, then produced again
by the prescribed command and confirmed byte-identical, so a failing
generator could not have truncated it in place.

One pre-existing violation left alone. Line 2 of all 68 files under
gen/rust/ carries a non-ASCII em dash in t27c's own header, against L3
PURITY. It is the compiler's header, not this change, and it is committed
at HEAD already; fixing it means editing t27c, which L0b assigns elsewhere.

Closes #384

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

trust_manager: four spec test assertions that cannot pass against the generated code

1 participant