Diagnosis
Seam-audit 2026-06-01 surfaced a hard contract mismatch on the echidna ↔ echidnabot GraphQL seam. In EchidnaApiMode::Graphql the client is completely broken; in EchidnaApiMode::Auto the failures are silently swallowed and every call degrades to REST after a wasted round-trip + warn-log.
Client expects (echidnabot/src/dispatcher/echidna_client.rs)
| Operation |
Type |
Signature |
verifyProof(prover, content) |
mutation |
returns { status, message, proverOutput, durationMs, artifacts } |
suggestTactics(prover, context, goalState) |
mutation |
returns [{ tactic, confidence, explanation }] |
proverStatus(prover) |
query |
returns { available, message } |
Server exposes (echidna/src/interfaces/graphql/schema.rs)
| Operation |
Type |
Signature |
submitProof(goal, prover) |
mutation |
returns ProofState |
applyTactic(...) |
mutation |
|
cancelProof(proofId) |
mutation |
|
provers |
query |
[ProverInfo] |
proofState(id) |
query |
ProofState? |
listProofs(limit, status) |
query |
[ProofState] |
suggestTactics(proofId, limit) |
query |
[Tactic] — note: query not mutation, takes proofId not (prover, context, goalState) |
health |
query |
String |
Zero of the client's three operations exist on the server. suggestTactics is the closest match by name only — different kind (query vs mutation), different arguments (proof-id vs prover/context/goal), different return shape (no confidence/explanation).
REST seam (Seam 1 sub-finding)
Tighter, but also drift: server's /api/verify REST response now carries a typed outcome: String (PROVED / NO_PROOF_FOUND / TIMEOUT / INVALID_INPUT / etc.) plus optional mode and smt_status. Client's RestVerifyResponse deserializes only valid: bool and discards everything else. Recoverable (serde ignores unknown fields), but the client cannot distinguish TIMEOUT from FAILED via REST, and rich diagnostics are dropped.
Suggested resolution paths
Two plausible directions; owner picks:
A. Document one side as canonical, port the other. Either the GraphQL schema in echidna is the SoT (then echidnabot client needs rewriting against submitProof/proofState polling, or we extend the schema with a synchronous verifyProof(prover, content) mutation) or the echidnabot client's contract is the SoT (then echidna's MutationRoot gets the three missing operations).
B. Sunset GraphQL on the echidnabot path. Per the memory entry on echidnabot Phase 6 (BoJ-routed MCP), it's possible the GraphQL client is legacy. Make EchidnaApiMode::Rest the only supported mode and gate / remove GraphQL paths in EchidnaConfig.
Either way, add an integration test that round-trips a contract assertion (e.g. a cargo test in echidnabot CI spawns echidna's GraphQL playground and asserts the three operations exist with the expected argument shapes — would have caught this in either repo).
Don't-touch boundaries respected
src/abi/ in echidnabot is owner-intentional broken namespace — not touched.
- This is a pure diagnosis issue; no code change proposed.
Filed by seam-audit per estate directive (cross-repo Closes doesn't auto-fire; if a follow-up PR lands in echidnabot rather than here, please close this manually).
Diagnosis
Seam-audit 2026-06-01 surfaced a hard contract mismatch on the echidna ↔ echidnabot GraphQL seam. In
EchidnaApiMode::Graphqlthe client is completely broken; inEchidnaApiMode::Autothe failures are silently swallowed and every call degrades to REST after a wasted round-trip + warn-log.Client expects (echidnabot/src/dispatcher/echidna_client.rs)
verifyProof(prover, content){ status, message, proverOutput, durationMs, artifacts }suggestTactics(prover, context, goalState)[{ tactic, confidence, explanation }]proverStatus(prover){ available, message }Server exposes (echidna/src/interfaces/graphql/schema.rs)
submitProof(goal, prover)ProofStateapplyTactic(...)cancelProof(proofId)provers[ProverInfo]proofState(id)ProofState?listProofs(limit, status)[ProofState]suggestTactics(proofId, limit)[Tactic]— note: query not mutation, takes proofId not (prover, context, goalState)healthStringZero of the client's three operations exist on the server.
suggestTacticsis the closest match by name only — different kind (query vs mutation), different arguments (proof-id vs prover/context/goal), different return shape (no confidence/explanation).REST seam (Seam 1 sub-finding)
Tighter, but also drift: server's
/api/verifyREST response now carries a typedoutcome: String(PROVED / NO_PROOF_FOUND / TIMEOUT / INVALID_INPUT / etc.) plus optionalmodeandsmt_status. Client'sRestVerifyResponsedeserializes onlyvalid: booland discards everything else. Recoverable (serde ignores unknown fields), but the client cannot distinguish TIMEOUT from FAILED via REST, and rich diagnostics are dropped.Suggested resolution paths
Two plausible directions; owner picks:
A. Document one side as canonical, port the other. Either the GraphQL schema in echidna is the SoT (then echidnabot client needs rewriting against
submitProof/proofStatepolling, or we extend the schema with a synchronousverifyProof(prover, content)mutation) or the echidnabot client's contract is the SoT (then echidna'sMutationRootgets the three missing operations).B. Sunset GraphQL on the echidnabot path. Per the memory entry on echidnabot Phase 6 (BoJ-routed MCP), it's possible the GraphQL client is legacy. Make
EchidnaApiMode::Restthe only supported mode and gate / remove GraphQL paths inEchidnaConfig.Either way, add an integration test that round-trips a contract assertion (e.g. a
cargo testin echidnabot CI spawns echidna's GraphQL playground and asserts the three operations exist with the expected argument shapes — would have caught this in either repo).Don't-touch boundaries respected
src/abi/in echidnabot is owner-intentional broken namespace — not touched.Filed by seam-audit per estate directive (
cross-repo Closes doesn't auto-fire; if a follow-up PR lands in echidnabot rather than here, please close this manually).