Skip to content

feat(wire): #31 · Hello capability discovery + the five evolution rules#212

Merged
hellno merged 1 commit into
mainfrom
hellno/wire-hello-capability-discovery
Jul 7, 2026
Merged

feat(wire): #31 · Hello capability discovery + the five evolution rules#212
hellno merged 1 commit into
mainfrom
hellno/wire-hello-capability-discovery

Conversation

@hellno

@hellno hellno commented Jul 7, 2026

Copy link
Copy Markdown
Owner

What

Implements #31 — wire-contract evolution: Hello capability discovery + the five evolution rules. One additive request on the frozen deckard-contract wire, plus the in-repo rules + registry that govern how the contract may grow. This is the keystone the x402 chain (#32#34) and the ProposalOrigin::Dapp variant (#198 / #204) register their new kinds against.

SignerRequest::Hello  ->  SignerResponse::Hello(HelloInfo {
    spec_version: "YYYY-MM-DD",     // date-version; bumps only on a breaking change
    capabilities: Vec<String>,      // e.g. ["core", "mcp.v0.1"] — feature detection reads THIS only
    impl_name:    String,           // informational; no code path branches on it
})

An old daemon that predates the variant fails to decode it and returns the existing frame-decode error — that rejection is the backward-compat valve, and it already ships. No existing frame changes.

How it maps to #31's in-scope checklist

  • Hello + HelloInfo in deckard-contract; answered in every daemon state incl. Locked (the daemon arm reads no self.state); reveals capability names only — no state, policy, or key material.
  • ✅ Baseline capability registry (core, mcp.v0.1) — in code (crates/deckard-contract/src/capabilities.rs, the single source of truth) and as the name · status · since · defining doc table in the new build doc.
  • ✅ The five evolution rules written into docs/build/40-wire-evolution.md (indexed in docs/build/README.md).
  • ✅ Tests:
    • E1 Hello shape — spec_version matches ^\d{4}-\d{2}-\d{2}$, capabilities ⊇ {core, mcp.v0.1}.
    • E2 old-peer replay — existing frames encode byte-identically (golden-pinned unit frames + the Balance data frame; the round-trip fixtures were extended with Hello, every prior assertion unchanged).
    • E3 unknown enum variant → decode Err, no panic (CBOR + JSON), and at the socket the daemon replies malformed_request, keeps serving, and signs nothing.
    • E4 extra unknown struct key → decodes, key ignored (no deny_unknown_fields).
  • ✅ No code path branches on impl_name.

Single-source parity

The daemon (IMPL_SIGNERD), the MCP acceptance mock (IMPL_MOCK), and the contract MockSigner::hello() all build their answer from one capabilities::hello_info(), so spec_version + capabilities can never drift between implementations — only impl_name differs. Pinned by hello_info_is_single_sourced, hello_matches_the_single_source_registry, and the daemon-socket test.

Open question — deferred (not auto-closing #31)

#31 asks whether HelloInfo should also carry the daemon's active chain id (leaning yes). I did not include it: the normative in-scope text says "capability names only — no state," chain id is state, and a wrong-chain client is already refused at first Propose with Deny{chain_mismatch}. It can be added additively later — precisely by these rules (a new field an old client ignores per E4) — if we decide first-contact chain detection is worth the state it exposes. Flagged in 40-wire-evolution.md; easy to flip if you'd prefer it in now.

Definition of done (evidence)

  • cargo fmt --all --check — clean.
  • just check — green (clippy -D warnings on default, --features tray, and --features dev-signerd-bin).
  • TMPDIR=/tmp cargo test --workspace — green (430 passed, 2 pre-existing ignored).
  • No new or changed dependencies (Cargo.toml / Cargo.lock untouched).
  • Adversarial review: three independent lenses over the final diff (codex GPT-5 xhigh + two Claude lenses — wire-compat correctness and security/trust-core). Zero P0/P1. The P2/P3 findings (a false "MockSigner participates" doc claim, a stale test-name reference, and a tautological Balance self-comparison in E2) are all fixed in this branch.

Contract + daemon + client/test work only; no GPUI app views touched.

Add the additive `SignerRequest::Hello` -> `SignerResponse::Hello(HelloInfo)`
capability-discovery request to the frozen wire, plus the in-repo rules and
registry that govern how the contract may grow. The keystone the x402 chain
(#32/#34) and the dapp-origin variant (#198/#204) register new kinds against.

- deckard-contract: `HelloInfo { spec_version, capabilities, impl_name }` and a
  new `capabilities` module — the single source of truth for the registry
  (SPEC_VERSION 2026-07-07, `core`, `mcp.v0.1`) and the `hello_info()` builder
  every implementation answers from.
- daemon + MCP mock: answer `Hello` from that one builder (parity by
  construction). The daemon arm reads no state, so it replies identically in
  every state incl. Locked, revealing capability names only.
- MockSigner grows a matching `hello()` so the contract mock shares the parity.
- Tests: E1 shape, E2 byte-identity of existing frames (golden-pinned), E3
  unknown-variant rejected loudly (the compat valve), E4 unknown struct key
  ignored; plus a daemon-socket test proving Hello answers while Locked and the
  daemon survives a bad frame having signed nothing.
- docs/build/40-wire-evolution.md: the five rules + the capability registry.

Additive only: no existing frame changes, no dependency changes.

Implements #31
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.

1 participant