Skip to content

Commit d5f3476

Browse files
chore(echidnabot): bump vendored copy to faeb280 (#168, #169) (#603)
## Summary Re-pins the vendored `bots/echidnabot` copy to `hyperpolymath/echidnabot` `main` @ `faeb2808` (was `bf2c0ffc`, 2 commits behind), using the repo's own `scripts/sync-vendored-bot.sh echidnabot --sync --rev faeb2808efcf1fc8149afc5811182cb14bf79091`. It brings in: - **hyperpolymath/echidnabot#168:** echidna integration, the prove-result contract, UUID minting (`src/ids.rs`: v7 records, v8 content ids) and a CI repair. - **hyperpolymath/echidnabot#169:** the `submitProofObligation` GraphQL mutation. hypatia's FleetDispatcher and LearningScheduler send this contract (hyperpolymath/hypatia#911). Until this bump lands, a fleet-deployed echidnabot rejects every hypatia dispatch as an unknown field. Scope: - Only `bots/echidnabot/**` changes (34 files, +2065/−241). - `FLEET-SYNC.json` changes `rev` only. - 0 files deleted, so the Repo Integrity Guard needs no `[mass-delete-ok]`. - 4 files added: `migrations/20261008000001_proof_obligations.sql`, `src/dispatcher/prove_result.rs`, `src/ids.rs`, `tests/live_echidna.rs`. No issue to close. This is the follow-up to hyperpolymath/echidnabot#169. ## Type of change - [ ] 🐛 Bug fix — not a fix in this repo; it re-pins vendored upstream code. - [ ] ✨ New feature — the feature (`submitProofObligation`) was built and reviewed upstream in hyperpolymath/echidnabot#169; this PR only re-vendors it. - [ ] 💥 Breaking change — no existing fleet behaviour changes. The upstream GraphQL change is additive. - [ ] 🕳️ Soundness fix — not applicable. - [ ] 📖 Documentation — no fleet docs change. The vendored copy carries no `docs/` (not in `include`). - [ ] 🧹 Refactor / tech debt — not applicable. - [ ] ⚡ Performance — not applicable. - [x] 🔧 Build / CI / tooling — a vendored-dependency pin bump (`FLEET-SYNC.json` rev plus the synced tree). ## 📌 New pins - **PR head SHA: `cdfa0b813f44a46be8dd149edd02d32f65cbe1d8`** - **`bots/echidnabot/FLEET-SYNC.json` `rev`: `bf2c0ffc9f5faee3c2072b516855a045400ac247` → `faeb2808efcf1fc8149afc5811182cb14bf79091`** (`hyperpolymath/echidnabot` `main` head, the signed squash merge of #169). - Vendored `bots/echidnabot/Cargo.lock` records, as resolved upstream: - **added `echidna-core-spark` 0.1.0**, git `https://github.com/hyperpolymath/echidna` **rev `b761b3a832981be51d4e88076ef1b90fe5037e9c`** - **added `serde_json_canonicalizer` 0.3.2** - **added `ryu-js` 1.0.3** - **`async-trait` 0.1.89 → 0.1.92** - **`rustls` 0.23.40 → 0.23.45** - **`rustls-webpki` 0.103.13 → 0.103.15** - No action `uses:` SHAs, `actions.lock` entries or container digests change. ## How has this been verified? All commands were run in the PR worktree at the head above: - `scripts/sync-vendored-bot.sh echidnabot --check` printed "bots/echidnabot matches https://github.com/hyperpolymath/echidnabot@faeb2808… (79 entries)" and exited **0**. - `bash scripts/tests/sync-vendored-bot.sh` reported **21 passed, 0 failed**. - `jq -cS . bots/echidnabot/FLEET-SYNC.json` is byte-identical to the file, so the lock stays in canonical form. - `git diff --cached --name-only | grep -v '^bots/echidnabot/'` printed nothing. `git diff --cached --diff-filter=D` lists 0 files. - `git log -1 --show-signature` reports a good ED25519 signature, as `required_signatures` on `main` needs. - **Not built here.** Fleet CI does not compile `bots/echidnabot`: `rust.yml` builds robot-repo-automaton, shared-context, dashboard and rhodibot; CodeQL is `actions` / `build-mode: none`. The build evidence for this tree is therefore upstream CI on `faeb2808`, which is green apart from skipped deploy/automerge/coverage jobs. `squabble verify-satisfied hyperpolymath/echidnabot 169` returned `done: true`. ## Checklist - [x] My commits are **signed**: SSH ED25519 key, verified locally with `git log --show-signature`. - [x] I ran the project's own checks/tests locally and they pass: the drift `--check` and the sync script's planted-control suite, as above. - [x] New files carry the correct `SPDX-License-Identifier`: all 4 added vendored files are `MPL-2.0`, as written upstream. Nothing was relicensed. - [x] Docs are updated, and no public claim now overstates what the code does. No fleet doc describes the vendored version. Upstream's `api.adoc` says `submitProofObligation` stores an obligation and does not prove it (`status` is always `PENDING`). - [x] I have not introduced a soundness hole. This is a byte-for-byte re-vendor of reviewed upstream code, and the drift gate enforces that. ## Notes for reviewers - `.github/dependabot.yml` deliberately leaves out `/bots/echidnabot`. Dependency bumps for it land upstream and arrive here by re-pinning, as in this PR. - The new git dependency `echidna-core-spark` is pinned by full rev in both the vendored `Cargo.toml` and `Cargo.lock`. ### Deferred red checks (none required; all also red on `main` @ `72970698`) This PR touches no workflow and no `actions.lock`. Each red below fails identically on `main`: - `actions.lock is in sync with the workflow YAML`: deferred to #604. #595 bumped `smtp-notify-action` to v0.5.0 without relocking. - `governance / Actions lockfile verify`: deferred to #604, same cause. - `scorecard / Run Scorecard PR`: deferred to #604, same cause. Reconciliation fails on `push-email-notify.yml`. - `Codeac analyze results` (legacy status): deferred to #590. The service cannot analyse the repo. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_015bTuGfwCcvjrmNFejydTML Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 7297069 commit d5f3476

34 files changed

Lines changed: 2096 additions & 244 deletions

‎bots/echidnabot/Cargo.lock‎

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

‎bots/echidnabot/Cargo.toml‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,19 @@ path = "src/main.rs"
2828
# Fleet coordination
2929
gitbot-shared-context = { version = "0.1.0", git = "https://github.com/hyperpolymath/gitbot-fleet", rev = "40ef6bf1a43813948476d33c764e3405adc950c2" }
3030

31+
# ECHIDNA trust kernel. Canonical trust-level algorithm and source-level
32+
# axiom scanner, shared with ECHIDNA so echidnabot no longer re-implements
33+
# them. Pinned by full rev (immutable). At this rev the crate is still named
34+
# `echidna-core-spark`; it is being renamed `echidna-core-creusot` upstream,
35+
# and the trust types are meant to move into `echidna-core` proper. Follow-up:
36+
# re-point this line once that lands on echidna `main` (see
37+
# docs/ECHIDNA-INTEGRATION.adoc).
38+
echidna-core-spark = { git = "https://github.com/hyperpolymath/echidna", rev = "b761b3a832981be51d4e88076ef1b90fe5037e9c" }
39+
40+
# Minimum-version handshake with the ECHIDNA server.
41+
semver = "1"
42+
43+
3144
# Async runtime
3245
tokio = { version = "1", features = ["full"] }
3346

@@ -70,7 +83,12 @@ reqwest = { version = "0.12", default-features = false, features = ["json", "rus
7083

7184
# Utilities
7285
async-trait = "0.1"
73-
uuid = { version = "1", features = ["v4", "serde"] }
86+
# v7 for records, v8 for content ids; minted only in src/ids.rs.
87+
uuid = { version = "1.10", features = ["v7", "v8", "serde"] }
88+
# RFC 8785 (JCS) canonical bytes for src/ids.rs content ids. The estate
89+
# crate hyperpolymath/ijson-jcs is not public, so a public build cannot
90+
# depend on it; swap once it is (docs/ECHIDNA-INTEGRATION.adoc).
91+
serde_json_canonicalizer = "0.3"
7492
chrono = { version = "0.4", features = ["serde"] }
7593
thiserror = "2"
7694
anyhow = "1"

‎bots/echidnabot/FLEET-SYNC.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"fleet_owned":["CANONICAL_SOURCE.adoc","FLEET-SYNC.json"],"include":[".cargo","Cargo.lock","Cargo.toml","Containerfile","LICENSE","LICENSES","README.adoc","benches","config","echidnabot.example.toml","fuzz","migrations","proofs","src","tests"],"repo":"https://github.com/hyperpolymath/echidnabot","rev":"bf2c0ffc9f5faee3c2072b516855a045400ac247","schema":"gitbot-fleet.vendor-sync/1","upstream_branch":"main"}
1+
{"fleet_owned":["CANONICAL_SOURCE.adoc","FLEET-SYNC.json"],"include":[".cargo","Cargo.lock","Cargo.toml","Containerfile","LICENSE","LICENSES","README.adoc","benches","config","echidnabot.example.toml","fuzz","migrations","proofs","src","tests"],"repo":"https://github.com/hyperpolymath/echidnabot","rev":"faeb2808efcf1fc8149afc5811182cb14bf79091","schema":"gitbot-fleet.vendor-sync/1","upstream_branch":"main"}

‎bots/echidnabot/README.adoc‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ echidnabot init-db
7272
7373
# Start the webhook server
7474
export DATABASE_URL=sqlite:echidnabot.db
75-
export ECHIDNA_URL=http://localhost:8080
75+
export ECHIDNA_URL=http://127.0.0.1:8081
7676
echidnabot serve --port 8080
7777
----
7878

‎bots/echidnabot/benches/echidnabot_bench.rs‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ use sha2::Sha256;
2424
// one; CI runs clippy with `-D warnings`, so the re-export is an error here.
2525
use std::hint::black_box;
2626
use std::net::{IpAddr, Ipv4Addr};
27-
use uuid::Uuid;
2827

2928
// ──────────────────────────────────────────────────────────────────────────────
3029
// HMAC-SHA256 webhook signature verification
@@ -158,7 +157,7 @@ fn bench_goal_fingerprint(c: &mut Criterion) {
158157
// ──────────────────────────────────────────────────────────────────────────────
159158

160159
fn bench_proof_job_new(c: &mut Criterion) {
161-
let repo = Uuid::new_v4();
160+
let repo = echidnabot::ids::new_record_id();
162161
let prover = ProverKind::new("coq");
163162
let files = vec![
164163
"theories/Main.v".to_string(),

‎bots/echidnabot/config/echidnabot.ncl‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,8 @@ let Config = {
9191
max_connections = 5,
9292
},
9393
echidna = {
94-
endpoint = "http://localhost:8080/graphql",
95-
rest_endpoint = "http://localhost:8080",
94+
endpoint = "http://127.0.0.1:8081/",
95+
rest_endpoint = "http://127.0.0.1:8081",
9696
mode = 'auto,
9797
timeout_seconds = 120,
9898
},

‎bots/echidnabot/echidnabot.example.toml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ max_connections = 5
1717

1818
[echidna]
1919
# ECHIDNA Core GraphQL endpoint
20-
endpoint = "http://localhost:8080/graphql"
20+
endpoint = "http://127.0.0.1:8081/"
2121
# ECHIDNA Core REST endpoint
22-
rest_endpoint = "http://localhost:8080"
22+
rest_endpoint = "http://127.0.0.1:8081"
2323
# API mode: auto, graphql, rest
2424
mode = "auto"
2525
# Timeout for proof verification (seconds)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
-- SPDX-License-Identifier: MPL-2.0
2+
-- SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath)
3+
--
4+
-- proof_obligations — obligations received via the `submitProofObligation`
5+
-- GraphQL mutation (hypatia FleetDispatcher / LearningScheduler).
6+
-- Mirrors the SQLite DDL emitted at runtime by `SqliteStore::run_migrations`.
7+
-- `id` is a UUIDv8 content id; `repo_id` is nullable because the sender
8+
-- names a repo slug that need not be registered with echidnabot.
9+
CREATE TABLE IF NOT EXISTS proof_obligations (
10+
id TEXT PRIMARY KEY,
11+
repo_slug TEXT NOT NULL,
12+
repo_id TEXT REFERENCES repositories(id),
13+
claim TEXT NOT NULL,
14+
context TEXT NOT NULL,
15+
prover TEXT,
16+
inline_requested INTEGER NOT NULL,
17+
status TEXT NOT NULL,
18+
created_at TEXT NOT NULL
19+
);

‎bots/echidnabot/src/api/graphql.rs‎

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ use crate::dispatcher::{
1414
};
1515
use crate::scheduler::{JobPriority, JobScheduler};
1616
use crate::store::models::{
17-
goal_fingerprint, ProofJobRecord, Repository as StoreRepository, TacticOutcomeRecord,
17+
goal_fingerprint, ProofJobRecord, ProofObligationRecord, Repository as StoreRepository,
18+
TacticOutcomeRecord,
1819
};
1920
use crate::store::Store;
2021

@@ -385,6 +386,41 @@ pub struct RepoSettingsInput {
385386
pub auto_comment: Option<bool>,
386387
}
387388

389+
/// Upper bound on `claim` and `context`, in bytes. The endpoint has no auth,
390+
/// so a cap keeps one request from writing an unbounded row.
391+
pub const MAX_OBLIGATION_FIELD_BYTES: usize = 64 * 1024;
392+
393+
/// Input for submitting a proof obligation (hypatia's wire contract)
394+
#[derive(async_graphql::InputObject)]
395+
pub struct SubmitProofObligationInput {
396+
/// Repository slug `owner/name`; it need not be registered here
397+
pub repo: String,
398+
/// The claim to be proved, as prover-agnostic text
399+
pub claim: String,
400+
/// Free-text context for the claim
401+
pub context: String,
402+
/// Prover hint; absent means "echidnabot chooses"
403+
pub prover: Option<ProverKind>,
404+
/// Sender asks for inline verification. Recorded, NOT acted on yet
405+
pub inline: Option<bool>,
406+
}
407+
408+
/// Result of `submitProofObligation`
409+
#[derive(SimpleObject, Clone)]
410+
pub struct ProofObligationPayload {
411+
/// `true` means the obligation is persisted. It does NOT mean proved:
412+
/// failures are GraphQL errors, never `success: false`
413+
pub success: bool,
414+
/// UUIDv8 content id of the obligation (stable across resubmission)
415+
pub proof_id: ID,
416+
/// Always `PENDING` today: no dispatcher consumes obligations yet
417+
pub status: String,
418+
/// `false` when an identical obligation was already stored
419+
pub newly_recorded: bool,
420+
/// Whether `repo` matched a repository registered on GitHub here
421+
pub repo_registered: bool,
422+
}
423+
388424
#[Object]
389425
impl MutationRoot {
390426
/// Register a repository for monitoring
@@ -585,6 +621,69 @@ impl MutationRoot {
585621
.map_err(|e| async_graphql::Error::new(e.to_string()))?;
586622
Ok(TacticOutcome::from(record))
587623
}
624+
625+
/// Accept a proof obligation from hypatia and persist it as `PENDING`.
626+
///
627+
/// Accepted is not proved: nothing dispatches stored obligations to a
628+
/// prover yet, and `inline: true` is recorded but not honoured. Invalid
629+
/// input or a store failure is returned as a GraphQL error, because one of
630+
/// the two senders reads only `errors` and ignores `success`.
631+
async fn submit_proof_obligation(
632+
&self,
633+
ctx: &Context<'_>,
634+
input: SubmitProofObligationInput,
635+
) -> async_graphql::Result<ProofObligationPayload> {
636+
let state = ctx.data::<GraphQLState>()?;
637+
638+
let (owner, name) = match input.repo.split_once('/') {
639+
Some((o, n)) if !o.is_empty() && !n.is_empty() && !n.contains('/') => (o, n),
640+
_ => {
641+
return Err(async_graphql::Error::new(format!(
642+
"repo must be an owner/name slug, got {:?}",
643+
input.repo
644+
)))
645+
}
646+
};
647+
if input.claim.trim().is_empty() {
648+
return Err(async_graphql::Error::new("claim must not be empty"));
649+
}
650+
for (field, value) in [("claim", &input.claim), ("context", &input.context)] {
651+
if value.len() > MAX_OBLIGATION_FIELD_BYTES {
652+
return Err(async_graphql::Error::new(format!(
653+
"{field} exceeds {MAX_OBLIGATION_FIELD_BYTES} bytes"
654+
)));
655+
}
656+
}
657+
658+
let repo_id = state
659+
.store
660+
.get_repository_by_name(crate::adapters::Platform::GitHub, owner, name)
661+
.await
662+
.map_err(|e| async_graphql::Error::new(e.to_string()))?
663+
.map(|r| r.id);
664+
665+
let record = ProofObligationRecord::new(
666+
input.repo.clone(),
667+
repo_id,
668+
input.claim,
669+
input.context,
670+
input.prover.map(map_prover_kind_to_core),
671+
input.inline.unwrap_or(false),
672+
);
673+
let newly_recorded = state
674+
.store
675+
.record_proof_obligation(&record)
676+
.await
677+
.map_err(|e| async_graphql::Error::new(e.to_string()))?;
678+
679+
Ok(ProofObligationPayload {
680+
success: true,
681+
proof_id: ID::from(record.id.to_string()),
682+
status: record.status,
683+
newly_recorded,
684+
repo_registered: repo_id.is_some(),
685+
})
686+
}
588687
}
589688

590689
impl From<StoreRepository> for Repository {

0 commit comments

Comments
 (0)