Skip to content

Commit 403e760

Browse files
hyperpolymathclaude
andcommitted
feat(verisim-api): POST /proof_attempts auth guard + pagination offset
- Add optional token auth for POST /api/v1/proof_attempts via VERISIM_PROOF_ATTEMPTS_TOKEN (X-Proof-Attempts-Token or Bearer). - Add offset param to GET /proof_attempts for cursor-style pagination. - Document both env vars in DEPLOYMENT.adoc. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 27debe9 commit 403e760

2 files changed

Lines changed: 57 additions & 3 deletions

File tree

verisimdb/DEPLOYMENT.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,8 @@ max_size_mb = 10240
237237

238238
|`VERISIM_MODE` |Deployment mode: standalone, federation, hybrid |standalone
239239
|`VERISIM_DATA_DIR` |Data directory path |/var/lib/verisimdb
240+
|`VERISIM_CLICKHOUSE_URL` |ClickHouse HTTP endpoint used by `/api/v1/proof_attempts*` |http://localhost:8123
241+
|`VERISIM_PROOF_ATTEMPTS_TOKEN` |If set, required token for `POST /api/v1/proof_attempts` via `X-Proof-Attempts-Token` or `Authorization: Bearer` |(unset)
240242
|`VERISIM_LOG_LEVEL` |Log level: debug, info, warn, error |info
241243
|`VERISIM_HTTP_PORT` |Rust API HTTP port |8080
242244
|`VERISIM_ELIXIR_PORT` |Elixir orchestration port |4000

verisimdb/rust-core/verisim-api/src/proof_attempts.rs

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//!
77
//! | Method | Path | Purpose |
88
//! |--------|-------------------------------------------|-----------------------------------|
9+
//! | GET | /proof_attempts?limit=N&offset=M | List attempt rows (paged) |
910
//! | POST | /proof_attempts | Insert a single attempt row |
1011
//! | GET | /proof_attempts/strategy?class=X&limit=N | Recommend best provers for class |
1112
//! | GET | /proof_attempts/certificates?class=X | PROVEN/pending cert status |
@@ -15,10 +16,13 @@
1516
//! parsing only (inbound from ClickHouse, never emitted to callers).
1617
//!
1718
//! The ClickHouse URL is read from `VERISIM_CLICKHOUSE_URL` at request time.
19+
//! Optional write auth: if `VERISIM_PROOF_ATTEMPTS_TOKEN` is set, callers must
20+
//! provide either `X-Proof-Attempts-Token: <token>` or
21+
//! `Authorization: Bearer <token>` on `POST /proof_attempts`.
1822
1923
use axum::{
2024
extract::Query,
21-
http::StatusCode,
25+
http::{HeaderMap, StatusCode},
2226
response::Response,
2327
};
2428
use reqwest::Client;
@@ -51,6 +55,28 @@ fn ch_url() -> String {
5155
.unwrap_or_else(|_| "http://localhost:8123".to_string())
5256
}
5357

58+
fn required_insert_token() -> Option<String> {
59+
std::env::var("VERISIM_PROOF_ATTEMPTS_TOKEN")
60+
.ok()
61+
.map(|s| s.trim().to_string())
62+
.filter(|s| !s.is_empty())
63+
}
64+
65+
fn provided_insert_token(headers: &HeaderMap) -> Option<String> {
66+
if let Some(v) = headers
67+
.get("x-proof-attempts-token")
68+
.and_then(|v| v.to_str().ok())
69+
{
70+
return Some(v.trim().to_string());
71+
}
72+
73+
headers
74+
.get("authorization")
75+
.and_then(|v| v.to_str().ok())
76+
.and_then(|v| v.strip_prefix("Bearer "))
77+
.map(|v| v.trim().to_string())
78+
}
79+
5480
// ── Inbound proof-attempt row (matches echidnabot VeriSimWriter schema) ───────
5581

5682
/// A single proof attempt submitted by echidnabot.
@@ -79,6 +105,7 @@ pub struct ProofAttemptRow {
79105
#[derive(Debug, Deserialize)]
80106
pub struct ListParams {
81107
pub limit: Option<usize>,
108+
pub offset: Option<usize>,
82109
}
83110

84111
#[derive(Debug, Deserialize)]
@@ -94,14 +121,15 @@ pub struct ClassParam {
94121

95122
// ── Handlers ─────────────────────────────────────────────────────────────────
96123

97-
/// GET /proof_attempts?limit=N
124+
/// GET /proof_attempts?limit=N&offset=M
98125
///
99126
/// Returns up to `limit` (default 1000, max 20000) recent proof-attempt rows
100127
/// from ClickHouse as an A2ML document for retraining the Julia ML models.
101128
pub async fn list_proof_attempts(
102129
Query(params): Query<ListParams>,
103130
) -> Response {
104-
let limit = params.limit.unwrap_or(1000).min(20000);
131+
let limit = params.limit.unwrap_or(1000).clamp(1, 20000);
132+
let offset = params.offset.unwrap_or(0);
105133

106134
let sql = format!(
107135
"SELECT attempt_id, obligation_id, repo, file, claim, obligation_class, \
@@ -110,6 +138,7 @@ pub async fn list_proof_attempts(
110138
FROM verisim.proof_attempts \
111139
ORDER BY started_at DESC \
112140
LIMIT {limit} \
141+
OFFSET {offset} \
113142
FORMAT JSONEachRow"
114143
);
115144

@@ -148,8 +177,31 @@ pub async fn list_proof_attempts(
148177
/// Inserts a single attempt row into `verisim.proof_attempts` via the
149178
/// ClickHouse HTTP INSERT … FORMAT JSONEachRow endpoint.
150179
pub async fn insert_proof_attempt(
180+
headers: HeaderMap,
151181
axum::Json(row): axum::Json<ProofAttemptRow>,
152182
) -> Response {
183+
if let Some(expected) = required_insert_token() {
184+
match provided_insert_token(&headers) {
185+
Some(provided) if provided == expected => {}
186+
Some(_) => {
187+
return a2ml_response(
188+
StatusCode::UNAUTHORIZED,
189+
a2ml_error_detail("invalid_proof_attempts_token", 401, "Token mismatch"),
190+
);
191+
}
192+
None => {
193+
return a2ml_response(
194+
StatusCode::UNAUTHORIZED,
195+
a2ml_error_detail(
196+
"proof_attempts_auth_required",
197+
401,
198+
"Set X-Proof-Attempts-Token or Authorization: Bearer",
199+
),
200+
);
201+
}
202+
}
203+
}
204+
153205
let url = format!(
154206
"{}/?query=INSERT+INTO+verisim.proof_attempts+FORMAT+JSONEachRow",
155207
ch_url()

0 commit comments

Comments
 (0)