Skip to content

Agentic engineering: Tier 2 / Tier 3 backlog (lints, test runner, supply-chain, errors) #7

Open

Description

@hellno

Context

Tier 1 of the agentic-engineering hardening has landed (lint policy in the manifest, clippy.toml,
rustfmt.toml, deny.toml + cargo-deny CI, #![forbid(unsafe_code)] on deckard-core, closed CI
gaps, CLAUDE.md/AGENTS.md). The full rationale and the deliberately-rejected rules are in
docs/AGENTIC-ENGINEERING.md.

This issue tracks the Tier 2 / Tier 3 backlog. Each item was researched against paradigm Rust
repos (reth, alloy, Zed) and adversarially challenged by a cross-model (codex GPT-5.5) review —
that verdict is recorded per item below.


⭐ Promoted to "do now" (highest leverage)

Both models rank these above the rest because they harden the wallet engine and make the Tier-1
unused_must_use = "deny" actually bite.

1. Restriction lints in deckard-core

Clippy rules that make the security engine's production code unable to .unwrap() / panic! on
bad input (tests still unwrap freely, via the allow-*-in-tests keys already in clippy.toml).

Two paths — pick one:

  • Safe (zero code change, lands green):
    # crates/deckard-core — via lib.rs inner attrs or a non-workspace [lints] block
    unwrap_used = "deny"   # clean: all real unwraps are in #[cfg(test)]
    get_unwrap  = "deny"
    mem_forget  = "deny"
    expect_used = "allow"  # 2 non-test expects in eth.rs
    panic       = "allow"
    indexing_slicing = "allow"
  • Hardened (codex-preferred, ~3 small edits): also deny expect_used / panic /
    indexing_slicing, and fix the real sites rather than blanket-allow — those sites (the keystore
    byte-parser + RPC decode) are exactly where future agent-written indexing should be reviewed:
    • eth.rs:48 / eth.rs:97 — startup expect()s → documented fatal boundary or fallible spawn.
    • keystore.rs Reader (~492–505) — raw slice indexing → bounds-checked .get() + explicit error.
    • balances.rs:80 — results[0] → .first().ok_or(...)?.

codex verdict: MODIFY → PROMOTE. "Promote the corrected core lint set now, with expect_used,
panic, and indexing_slicing denied and the current indexing/expect sites fixed rather than exempted."

2. #[must_use] audit on secret types

Tag the secret-bearing types/methods so the compiler errors if a caller creates one and ignores it:
Vault, UnlockedVault, Vault::unlock, account_signer, account_address, primary_address,
reveal_phrase.

codex verdict: PROMOTE. Low churn, directly useful; unused_must_use = deny only fully bites
once these are annotated.


Tier 2 — high value, some effort

3. cargo-nextest

A faster, nicer test runner than cargo test (parallel, clear per-test output, per-test timeouts).
Add .config/nextest.toml, install via a pinned taiki-e/install-action, run
cargo nextest run --locked --workspace; keep a separate cargo test --locked --doc (nextest skips
doctests).

codex verdict: MODIFY — use retries = 0, NOT 2. Retries normalize flaky security tests,
which is dangerous for a wallet. fail-fast = false. Pin the install action version.

4. profile.dev tuning (faster debug crypto)

Make debug builds optimize the slow crypto so wallet create/unlock and tests aren't painful at
opt-level = 0.

codex verdict: MODIFY — do NOT optimize all deps (opt-level=2 for * slows the first
gpui/alloy compile and the agent feedback loop). Target just the crypto crates:

[profile.dev.package.argon2]
opt-level = 3
[profile.dev.package.blake2]
opt-level = 3

Drop [profile.dev.build-override] unless measured.

5. Dependency + spelling hygiene

  • cargo-machete — find unused dependencies. Start manual / non-blocking: it false-positives the
    cfg-gated objc2* + gpui-component-assets, but it correctly flags that the app's root
    Cargo.toml lists alloy-signer-local / alloy-primitives as direct deps it may not use directly

    (they're re-exported from deckard-core) — worth investigating.
  • typos (crate-ci) in CI with a crypto-jargon allow-list + extend-exclude = ["Cargo.lock"].
  • lefthook pre-commit hooks (fmt/clippy/typos) — local convenience only, NOT a CI gate (agents
    bypass with --no-verify; CI is the real gate).

codex verdict: MODIFY/SPLIT (as above).

6. Doctests + rustdoc warnings

RUSTDOCFLAGS="-D warnings" cargo doc --locked --no-deps -p deckard-core
cargo test --locked --doc -p deckard-core

Catches broken intra-doc links and ensures any future doc examples compile.

codex verdict: KEEP — sensible, not urgent (no doc examples exist yet).


Tier 3 — revisit later

  • Typed errors (thiserror) for the keystore. Structured errors instead of anyhow.

    codex verdict: MODIFY, do LAST. ⚠️ Keep one opaque UnlockFailed — distinguishing
    "wrong passphrase" vs "tampered" is an attacker oracle (surfaced via short_err in
    shell.rs). Only the keystore; keep network/provider errors as anyhow until there's real
    programmatic handling.

  • proptest round-trips for the keystore binary format (to_bytes/from_bytes, wrong-passphrase
    rejection). Reuse the existing KdfParams::FAST_TEST profile.
  • MSRV / toolchain-drift CI job — cargo +1.95.0 build --locked + rust-version = "1.95.0" in
    both manifests, to catch a bump-gpui that silently needs a newer compiler.
  • Automated monthly bump-gpui PR bot + Dependabot for the registry deps (serde, alloy,
    argon2, tray-icon); Dependabot/Renovate handle git deps poorly.
  • Scheduled cargo-audit cron (decouples the advisory time-signal from PR CI).
  • merge_group + a required aggregated check for branch protection (one-time repo-admin action).
  • cargo-vet / cargo-semver-checks / coverage gates — deferred (heavy / premature for a pre-1.0 app
    with no external consumer).

Deliberately NOT recommended

See the table in docs/AGENTIC-ENGINEERING.md:
forbid(unsafe_code) at the workspace root, clippy::pedantic/nursery/full restriction,
indexing_slicing = deny workspace-wide, edition 2024, nightly rustfmt/build flags, panic = abort,
multiple-versions = deny, mold/lld linker config (1.95 already defaults to the fast linkers).

Activity

  1. hellno commented on Jun 6, 2026

    @hellno
    OwnerAuthor

    ✅ Promoted "do-now" items (1 & 2) are implemented — path B (hardened) — and verified green.

    • deckard-core/src/lib.rs: #![deny(clippy::unwrap_used, expect_used, get_unwrap, indexing_slicing, panic, mem_forget)] on top of the existing #![forbid(unsafe_code)].
    • Refactored all 5 raw-index sites to bounds-checked forms: Reader::take→.get(range), ::u8→.first().copied(), ::u32→try_into(), parse_exact_32→iter_mut().zip(), balances results[0]→.first().ok_or(...).
    • The 2 startup-fatal eth.rs expects keep a scoped #[allow(clippy::expect_used)] + documented reason (the 'documented fatal boundary' pattern).
    • #[must_use] on Vault/UnlockedVault (not the Result-returning methods — avoids clippy::double_must_use).
    • Test code exempted via clippy.toml allow-indexing-slicing-in-tests = true.

    Verified: cargo clippy -D warnings clean on default and --features tray; cargo test --workspace 13/13; cargo fmt --check clean.

    A cross-model (codex GPT-5.5) adversarial pass found no semantics regression in the parser rewrites (checked empty/truncated buffers, exact-boundary reads, the 64-hex path). CLAUDE.md / AGENTS.md / docs now state these as enforced (no longer 'convention pending').

    Remaining scope of this issue: Tier 2 items 3–6 (nextest, profile.dev, hygiene tools, doctests) + Tier 3.

  2. hellno commented on Jun 7, 2026

    @hellno
    OwnerAuthor

    Speed-ideas adversarial review — outcome (do-later + dropped) & bonus findings

    The "go 10× faster without breaking wallet trust" brainstorm was reviewed by two independent adversarial passes (codex GPT-5.5 cross-model + a repo-grounded per-idea workflow); they converged. Full draft + reconciled verdict: .context/agentic-speed-ideas.md.

    Already done (separate from this issue, on hellno/rust-agent-lint-rules): the three do-first items — the unlock no-oracle fix + test (a real shipping leak: read→from_bytes parse errors were distinguishable from wrong-passphrase at the UI; now collapsed via Vault::open/open_bytes), the just core fast loop, and the #5b decode-compat fixtures (one per kind).

    Do-later

    Dropped

    • WASM purity proof + miri (Agentic engineering: Tier 2 / Tier 3 backlog (lints, test runner, supply-chain, errors) #7). deckard-core isn't pure (keystore fs I/O, std::thread, tokio runtime, alloy HTTP) → won't compile to wasm32; miri ≈ nil value under #![forbid(unsafe_code)]. Reconsider only if a deckard-crypto / deckard-net split happens for other reasons.
    • Injected test RNG (Deckard: Helios verified reads + walkaway, Railgun shield, signer daemon → main #9) — riskiest; dropped as drafted. The only idea that can weaken production: a seeded/weak RNG reaching release key-gen would be catastrophic, silent, and invisible to every guard (it's a logic hole, not a memory-safety one). Not even needed — fuzzers persist crash inputs and the #5b fixtures need no RNG injection. If ever required: a #[cfg(test)]-only seal_with_rng that is compile-absent from release, plus extending clippy disallowed-methods to ban seeded constructors (StdRng::from_seed, …).

    #8 capability firewalls — reshaped

    Cheap tier already done (secret types don't derive Debug — verified; the new no-oracle test even proves UnlockedVault: !Debug via .err() vs .unwrap_err()). Drop the dylint tier — custom lints track rustc/clippy internals and would rot against the ~monthly gpui toolchain bumps.

    Bonus trust findings (real, actionable — surfaced by both reviewers)

    1. account_signer is a public API returning a PrivateKeySigner (keystore.rs) — "transient signer, never stored" is a comment, not an enforced boundary. Consider sign-by-request so a raw signer never escapes the core.
    2. The app's alloy-signer-local / alloy-primitives look like unused direct deps (re-exported from deckard-core) — a cargo-machete cleanup.
    3. cargo-deny is not yet a real gate (continue-on-error: true + the licenses allow-list is still a starting superset). Don't call the supply chain "gated" until: run cargo deny check licenses locally to seed the list, confirm the AGPL crate = exception key on the action's cargo-deny version, then drop continue-on-error and mark it a required check.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions