diff --git a/src/interfaces/v-adapter/boj.v b/src/interfaces/v-adapter/boj.v deleted file mode 100644 index e83ab77f..00000000 --- a/src/interfaces/v-adapter/boj.v +++ /dev/null @@ -1,293 +0,0 @@ -// SPDX-License-Identifier: PMPL-1.0-or-later -// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -// -// ECHIDNA BoJ Client REST API Gateway — Port 7700 -// -// Exposes BoJ cartridge management operations via REST: -// -// GET /api/v1/health — BoJ server health -// GET /api/v1/status — connection status -// GET /api/v1/version — BoJ version -// POST /api/v1/connect — connect to BoJ server -// POST /api/v1/disconnect — disconnect from BoJ -// GET /api/v1/cartridges — list all cartridges -// GET /api/v1/cartridges/:name — get cartridge details -// POST /api/v1/cartridges/:name/load — load (mount) cartridge -// POST /api/v1/cartridges/:name/unload — unload (unmount) cartridge -// POST /api/v1/cartridges/:name/invoke — invoke a cartridge tool -// GET /api/v1/topology — get service topology matrix -// GET /api/v1/umoja/status — Umoja federation status -// -// Links against libechidna_boj.so (Zig FFI layer) when available. -// Falls back to stub responses with X-Boj-Mode: stub header when FFI is absent. - -module main - -import net.http -import time - -// --- Zig FFI extern declarations (libechidna_boj.so) --- - -#flag -L @VMODROOT/../../ffi/zig/zig-out/lib -#flag -lechidna_boj - -fn C.echidna_boj_connect(config_ptr &u8, config_len usize) int -fn C.echidna_boj_disconnect() -fn C.echidna_boj_status() int -fn C.echidna_boj_health(out_ptr &u8, out_len &usize) int -fn C.echidna_boj_list_cartridges(out_ptr &u8, out_len &usize) int -fn C.echidna_boj_get_cartridge(name_ptr &u8, name_len usize, out_ptr &u8, out_len &usize) int -fn C.echidna_boj_load_cartridge(name_ptr &u8, name_len usize) int -fn C.echidna_boj_unload_cartridge(name_ptr &u8, name_len usize) int -fn C.echidna_boj_invoke(name_ptr &u8, name_len usize, tool_ptr &u8, tool_len usize, args_ptr &u8, args_len usize, out_ptr &u8, out_len &usize) int -fn C.echidna_boj_topology(out_ptr &u8, out_len &usize) int -fn C.echidna_boj_umoja_status(out_ptr &u8, out_len &usize) int -fn C.echidna_boj_cartridge_count() int -fn C.echidna_boj_version() &u8 -fn C.echidna_boj_last_error() &u8 - -// --- FFI availability detection --- - -mut ffi_available := false - -fn init_boj_ffi() { - unsafe { - ver := C.echidna_boj_version() - if ver != nil { - ffi_available = true - } - } -} - -// --- Response helpers --- - -fn boj_response(mut res http.Response, status int, body string) { - res.set_status(http.Status.from_int(status)) - res.header.set(.content_type, 'application/json') - if ffi_available { - res.header.set_custom('X-Boj-Mode', 'live') or {} - } else { - res.header.set_custom('X-Boj-Mode', 'stub') or {} - } - res.body = body -} - -fn ffi_buf_call(f fn (&u8, &usize) int) string { - mut buf := [65536]u8{} - mut buf_len := usize(65536) - rc := f(&buf[0], &buf_len) - if rc == 0 && buf_len > 0 { - unsafe { return tos(&buf[0], int(buf_len)) } - } - return '' -} - -// --- Request handler --- - -struct BojHandler {} - -struct BojServer { - port int = 7700 -} - -fn (mut handler BojHandler) handle(req http.Request) http.Response { - mut res := http.Response{ - header: http.new_header_from_map({ - http.CommonHeader.content_type: 'application/json' - }) - } - - path := req.url.trim_right('/') - - match true { - path == '/api/v1/health' { - if ffi_available { - result := ffi_buf_call(C.echidna_boj_health) - if result.len > 0 { - boj_response(mut res, 200, result) - } else { - boj_response(mut res, 503, '{"status":"error","message":"BoJ health check failed"}') - } - } else { - boj_response(mut res, 200, '{"status":"ok","version":"0.2.0","cartridges":14,"mode":"stub"}') - } - } - path == '/api/v1/status' { - if ffi_available { - status := C.echidna_boj_status() - status_name := match status { - 0 { 'disconnected' } - 1 { 'connecting' } - 2 { 'connected' } - else { 'error' } - } - boj_response(mut res, 200, '{"status":"${status_name}","status_code":${status}}') - } else { - boj_response(mut res, 200, '{"status":"stub","status_code":0}') - } - } - path == '/api/v1/version' { - if ffi_available { - unsafe { - ver := C.echidna_boj_version() - if ver != nil { - boj_response(mut res, 200, '{"version":"${cstring_to_vstring(ver)}"}') - } else { - boj_response(mut res, 200, '{"version":"unknown"}') - } - } - } else { - boj_response(mut res, 200, '{"version":"0.2.0","mode":"stub"}') - } - } - path == '/api/v1/connect' && req.method == .post { - if ffi_available { - config := req.data - if config.len > 0 { - rc := C.echidna_boj_connect(config.str, usize(config.len)) - if rc == 0 { - boj_response(mut res, 200, '{"connected":true}') - } else { - boj_response(mut res, 500, '{"connected":false,"error_code":${rc}}') - } - } else { - boj_response(mut res, 400, '{"error":"Empty configuration"}') - } - } else { - boj_response(mut res, 200, '{"connected":true,"mode":"stub"}') - } - } - path == '/api/v1/disconnect' && req.method == .post { - if ffi_available { - C.echidna_boj_disconnect() - } - boj_response(mut res, 200, '{"disconnected":true}') - } - path == '/api/v1/cartridges' && req.method == .get { - if ffi_available { - result := ffi_buf_call(C.echidna_boj_list_cartridges) - if result.len > 0 { - boj_response(mut res, 200, result) - } else { - boj_response(mut res, 500, '{"error":"Failed to list cartridges"}') - } - } else { - boj_response(mut res, 200, '[{"name":"proof-mcp","status":"Ready","domain":"Proof","mounted":false}]') - } - } - path == '/api/v1/topology' { - if ffi_available { - result := ffi_buf_call(C.echidna_boj_topology) - if result.len > 0 { - boj_response(mut res, 200, result) - } else { - boj_response(mut res, 500, '{"error":"Failed to get topology"}') - } - } else { - boj_response(mut res, 200, '{"protocols":9,"domains":14,"cartridges":14,"mode":"stub"}') - } - } - path == '/api/v1/umoja/status' { - if ffi_available { - result := ffi_buf_call(C.echidna_boj_umoja_status) - if result.len > 0 { - boj_response(mut res, 200, result) - } else { - boj_response(mut res, 500, '{"error":"Failed to get Umoja status"}') - } - } else { - boj_response(mut res, 200, '{"federation":"idle","peers":0,"mode":"stub"}') - } - } - else { - // Cartridge-specific routes: /api/v1/cartridges/:name/... - if path.starts_with('/api/v1/cartridges/') { - parts := path.replace('/api/v1/cartridges/', '').split('/') - if parts.len >= 1 { - name := parts[0] - if parts.len == 1 { - // GET /cartridges/:name - if ffi_available { - mut buf := [65536]u8{} - mut buf_len := usize(65536) - rc := C.echidna_boj_get_cartridge(name.str, usize(name.len), &buf[0], &buf_len) - if rc == 0 && buf_len > 0 { - unsafe { boj_response(mut res, 200, tos(&buf[0], int(buf_len))) } - } else { - boj_response(mut res, 404, '{"error":"Cartridge not found","name":"${name}"}') - } - } else { - boj_response(mut res, 200, '{"name":"${name}","status":"Ready","mode":"stub"}') - } - } else if parts.len == 2 { - action := parts[1] - match action { - 'load' { - if ffi_available { - rc := C.echidna_boj_load_cartridge(name.str, usize(name.len)) - if rc == 0 { - boj_response(mut res, 200, '{"loaded":true,"name":"${name}"}') - } else { - boj_response(mut res, 500, '{"loaded":false,"error_code":${rc}}') - } - } else { - boj_response(mut res, 200, '{"loaded":true,"name":"${name}","mode":"stub"}') - } - } - 'unload' { - if ffi_available { - rc := C.echidna_boj_unload_cartridge(name.str, usize(name.len)) - if rc == 0 { - boj_response(mut res, 200, '{"unloaded":true,"name":"${name}"}') - } else { - boj_response(mut res, 500, '{"unloaded":false,"error_code":${rc}}') - } - } else { - boj_response(mut res, 200, '{"unloaded":true,"name":"${name}","mode":"stub"}') - } - } - 'invoke' { - if ffi_available { - body := req.data - // Parse tool and args from JSON body - // Simplified: pass raw body as args - tool := 'default' - mut out_buf := [65536]u8{} - mut out_len := usize(65536) - rc := C.echidna_boj_invoke(name.str, usize(name.len), tool.str, usize(tool.len), body.str, usize(body.len), &out_buf[0], &out_len) - if rc == 0 && out_len > 0 { - unsafe { boj_response(mut res, 200, tos(&out_buf[0], int(out_len))) } - } else { - boj_response(mut res, 500, '{"error":"Invoke failed","error_code":${rc}}') - } - } else { - boj_response(mut res, 200, '{"status":"ok","result":{},"mode":"stub"}') - } - } - else { - boj_response(mut res, 404, '{"error":"Unknown action","action":"${action}"}') - } - } - } - } - } else { - boj_response(mut res, 404, '{"error":"Not found","path":"${path}"}') - } - } - } - - return res -} - -fn main() { - init_boj_ffi() - mode := if ffi_available { 'live (libechidna_boj.so loaded)' } else { 'stub' } - eprintln('ECHIDNA BoJ REST adapter starting on :7700 [${mode}]') - - mut handler := BojHandler{} - mut server := http.Server{ - handler: handler - addr: ':7700' - } - server.listen_and_serve() -} diff --git a/src/interfaces/v-adapter/graphql.v b/src/interfaces/v-adapter/graphql.v deleted file mode 100644 index f68ac768..00000000 --- a/src/interfaces/v-adapter/graphql.v +++ /dev/null @@ -1,657 +0,0 @@ -// SPDX-License-Identifier: PMPL-1.0-or-later -// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -// -// ECHIDNA GraphQL API Gateway -// -// Exposes ECHIDNA theorem prover dispatch via GraphQL on port 8102: -// query { provers { kind name tier description available complexity } } -// query { prover(kind: "lean") { kind name tier description } } -// query { proof(id: "prf-001") { goals context } } -// query { suggestTactics(proverId: "ses-lean-...", limit: 5) { tactic confidence source } } -// query { health { healthy service version proverCount } } -// query { __schema { types { name fields } } } -// mutation { createProver(kind: "lean") { sessionId kind status } } -// mutation { destroyProver(id: "ses-lean-...") { sessionId status } } -// mutation { parseProof(prover: "lean", content: "...") { parsed goals } } -// mutation { verifyProof(prover: "lean", content: "...") { verified trustLevel message } } -// mutation { applyTactic(proverId: "prf-001", tactic: "simp") { success newState } } -// mutation { exportProof(proverId: "prf-001") { format exportedContent } } -// mutation { dispatch(config: "...", proof: "...") { verified trustLevel proversUsed } } -// GET /graphql — GraphiQL playground -// -// Links against libechidna_ffi.so (Zig FFI layer) when available. -// Falls back to stub responses with X-Echidna-Mode: stub header when FFI is absent. - -module main - -import net.http -import time -import rand - -// --- Zig FFI extern declarations (libechidna_ffi.so) --- - -#flag -L @VMODROOT/../../ffi/zig/zig-out/lib -#flag -lechidna_ffi - -fn C.echidna_init() int -fn C.echidna_deinit() -fn C.echidna_create_prover(kind int) int -fn C.echidna_destroy_prover(handle int) -fn C.echidna_parse_file(handle int, path_ptr &u8, path_len usize) int -fn C.echidna_parse_string(handle int, content_ptr &u8, content_len usize) int -fn C.echidna_apply_tactic(handle int, tactic_ptr &u8, tactic_len usize) int -fn C.echidna_verify_proof(handle int) int -fn C.echidna_export_proof(handle int, out_ptr &u8, out_len &usize) int -fn C.echidna_suggest_tactics(handle int, limit int, out_ptr &u8, out_len &usize) int -fn C.echidna_version() &u8 -fn C.echidna_prover_count() int -fn C.echidna_prover_name(kind int) &u8 -fn C.echidna_last_error() &u8 -fn C.echidna_build_info() &u8 - -__global ffi_available = false -__global ffi_initialised = false - -fn init_ffi() { - rc := C.echidna_init() - if rc == 0 { - ffi_available = true - ffi_initialised = true - println(' FFI: linked to libechidna_ffi.so (live mode)') - } else { - ffi_available = false - println(' FFI: not available (stub mode)') - } -} - -fn ffi_last_error() string { - ptr := C.echidna_last_error() - if ptr == unsafe { nil } { - return 'unknown error' - } - return unsafe { cstring_to_vstring(ptr) } -} - -fn ffi_version() string { - ptr := C.echidna_version() - return unsafe { cstring_to_vstring(ptr) } -} - -// --- Prover Data --- - -struct ProverInfo { - kind string - name string - tier int - ordinal int // FFI prover kind ordinal (0-29) - description string - available bool - complexity int -} - -fn all_provers() []ProverInfo { - return [ - // Tier 1: Core + SMT (ordinals 0-5 match Zig ProverKind) - ProverInfo{kind: 'agda', name: 'Agda', tier: 1, ordinal: 0, description: 'Dependently-typed proof assistant with Curry-Howard correspondence', available: true, complexity: 3}, - ProverInfo{kind: 'coq', name: 'Coq/Rocq', tier: 1, ordinal: 1, description: 'Calculus of Inductive Constructions proof assistant', available: true, complexity: 3}, - ProverInfo{kind: 'lean', name: 'Lean 4', tier: 1, ordinal: 2, description: 'Dependent type theory with powerful automation (mathlib)', available: true, complexity: 3}, - ProverInfo{kind: 'isabelle', name: 'Isabelle/HOL', tier: 1, ordinal: 3, description: 'Higher-order logic proof assistant with Sledgehammer', available: true, complexity: 4}, - ProverInfo{kind: 'z3', name: 'Z3', tier: 1, ordinal: 4, description: 'Microsoft SMT solver (SAT modulo theories)', available: true, complexity: 2}, - ProverInfo{kind: 'cvc5', name: 'CVC5', tier: 1, ordinal: 5, description: 'SMT solver with quantifier reasoning', available: true, complexity: 2}, - // Tier 2: Big Six completion (ordinals 6-8) - ProverInfo{kind: 'metamath', name: 'Metamath', tier: 2, ordinal: 6, description: 'Minimalist proof language with tiny trusted kernel', available: true, complexity: 2}, - ProverInfo{kind: 'hollight', name: 'HOL Light', tier: 2, ordinal: 7, description: 'Simple higher-order logic theorem prover', available: true, complexity: 3}, - ProverInfo{kind: 'mizar', name: 'Mizar', tier: 2, ordinal: 8, description: 'Set-theoretic proof assistant (MML library)', available: true, complexity: 3}, - // Tier 3: Additional coverage (ordinals 9-10) - ProverInfo{kind: 'pvs', name: 'PVS', tier: 3, ordinal: 9, description: 'Prototype Verification System with rich type system', available: true, complexity: 4}, - ProverInfo{kind: 'acl2', name: 'ACL2', tier: 3, ordinal: 10, description: 'A Computational Logic for Applicative Common Lisp', available: true, complexity: 4}, - // Tier 4: Advanced (ordinal 11) - ProverInfo{kind: 'hol4', name: 'HOL4', tier: 4, ordinal: 11, description: 'Higher-order logic (LCF-style, SML-based)', available: true, complexity: 5}, - // Extended: Dependent types (ordinals 12, 17) - ProverInfo{kind: 'idris2', name: 'Idris 2', tier: 1, ordinal: 12, description: 'Quantitative type theory with dependent types and linear types', available: true, complexity: 3}, - ProverInfo{kind: 'fstar', name: 'F*', tier: 1, ordinal: 17, description: 'Dependent types with effects for program verification', available: true, complexity: 3}, - // Tier 5: First-Order ATPs (ordinals 13-16) - ProverInfo{kind: 'vampire', name: 'Vampire', tier: 5, ordinal: 13, description: 'First-order automated theorem prover (TPTP format)', available: true, complexity: 2}, - ProverInfo{kind: 'eprover', name: 'E Prover', tier: 5, ordinal: 14, description: 'Equational first-order theorem prover', available: true, complexity: 2}, - ProverInfo{kind: 'spass', name: 'SPASS', tier: 5, ordinal: 15, description: 'First-order logic with equality (DFG/TPTP)', available: true, complexity: 2}, - ProverInfo{kind: 'altergo', name: 'Alt-Ergo', tier: 5, ordinal: 16, description: 'SMT solver with polymorphism and AC reasoning', available: true, complexity: 2}, - // Tier 6: Auto-active / orchestration (ordinals 18-19) - ProverInfo{kind: 'dafny', name: 'Dafny', tier: 2, ordinal: 18, description: 'Auto-active verification language (Boogie/Z3 backend)', available: true, complexity: 2}, - ProverInfo{kind: 'why3', name: 'Why3', tier: 2, ordinal: 19, description: 'Multi-prover orchestration platform', available: true, complexity: 3}, - // Tier 7: Specialised / niche (ordinals 20-24) - ProverInfo{kind: 'tlaps', name: 'TLAPS', tier: 2, ordinal: 20, description: 'TLA+ Proof System for distributed systems', available: true, complexity: 4}, - ProverInfo{kind: 'twelf', name: 'Twelf', tier: 4, ordinal: 21, description: 'Logical framework based on LF type theory', available: true, complexity: 4}, - ProverInfo{kind: 'nuprl', name: 'Nuprl', tier: 4, ordinal: 22, description: 'Constructive type theory prover (Cornell)', available: true, complexity: 4}, - ProverInfo{kind: 'minlog', name: 'Minlog', tier: 4, ordinal: 23, description: 'Minimal logic proof assistant with program extraction', available: true, complexity: 4}, - ProverInfo{kind: 'imandra', name: 'Imandra', tier: 2, ordinal: 24, description: 'ML-based automated reasoning engine', available: true, complexity: 3}, - // Tier 8: Constraint solvers (ordinals 25-29) - ProverInfo{kind: 'glpk', name: 'GLPK', tier: 5, ordinal: 25, description: 'GNU Linear Programming Kit (LP/MIP solver)', available: true, complexity: 2}, - ProverInfo{kind: 'scip', name: 'SCIP', tier: 5, ordinal: 26, description: 'Solving Constraint Integer Programs (MINLP)', available: true, complexity: 3}, - ProverInfo{kind: 'minizinc', name: 'MiniZinc', tier: 5, ordinal: 27, description: 'Constraint modelling language', available: true, complexity: 2}, - ProverInfo{kind: 'chuffed', name: 'Chuffed', tier: 5, ordinal: 28, description: 'Lazy clause generation constraint solver', available: true, complexity: 2}, - ProverInfo{kind: 'ortools', name: 'OR-Tools', tier: 5, ordinal: 29, description: 'Google constraint and optimization solver', available: true, complexity: 2}, - ] -} - -fn find_prover(kind string) ?ProverInfo { - for p in all_provers() { - if p.kind == kind { - return p - } - } - return none -} - -fn prover_to_graphql_json(p ProverInfo) string { - return '{"kind":"${p.kind}","name":"${esc(p.name)}","tier":${p.tier},"description":"${esc(p.description)}","available":${p.available},"complexity":${p.complexity}}' -} - -// --- GraphQL Handler --- - -struct EchidnaGraphQLHandler { - port int -} - -pub fn (mut h EchidnaGraphQLHandler) handle(req http.Request) http.Response { - path := req.url.all_before('?') - if path != '/graphql' { - return json_response(404, '{"error":"Use /graphql endpoint"}') - } - - if req.method == .get { - return graphiql_page() - } - if req.method != .post { - return json_response(405, '{"error":"POST or GET required"}') - } - - query := json_field(req.data, 'query') - if query.len == 0 { - return json_response(400, '{"errors":[{"message":"Missing query field"}]}') - } - - return resolve(query, req.data) -} - -// --- Server --- - -pub struct Server { -pub mut: - port int -} - -pub fn new_server(port int) &Server { - return &Server{ - port: port - } -} - -pub fn (s Server) start() { - println('ECHIDNA GraphQL API Gateway starting on port ${s.port}...') - init_ffi() - println(' POST /graphql — execute GraphQL queries/mutations') - println(' GET /graphql — GraphiQL playground') - println(' Queries: provers, prover(kind), proof(id), suggestTactics, health, __schema') - println(' Mutations: createProver, destroyProver, parseProof, verifyProof,') - println(' applyTactic, exportProof, dispatch') - mut server := http.Server{ - addr: ':${s.port}' - handler: &EchidnaGraphQLHandler{port: s.port} - } - server.listen_and_serve() -} - -fn main() { - mut s := new_server(8102) - s.start() -} - -// --- Resolver Dispatch --- - -fn resolve(query string, data string) http.Response { - // Mutations (check first since they modify state) - if query.contains('createProver') { - return resolve_create_prover(query) - } - if query.contains('destroyProver') { - return resolve_destroy_prover(query) - } - if query.contains('parseProof') { - return resolve_parse_proof(query) - } - if query.contains('verifyProof') { - return resolve_verify_proof(query) - } - if query.contains('applyTactic') { - return resolve_apply_tactic(query) - } - if query.contains('exportProof') { - return resolve_export_proof(query) - } - if query.contains('dispatch') { - return resolve_dispatch(query, data) - } - - // Queries — check specific before general - if query.contains('__schema') { - return resolve_schema() - } - if query.contains('health') { - return resolve_health() - } - if query.contains('suggestTactics') { - return resolve_suggest_tactics(query) - } - // proof (singular, by ID) — must check before provers (plural) - if query.contains('proof(') && !query.contains('provers') { - id := extract_arg(query, 'id') - return resolve_proof(id) - } - // prover (singular, by kind) — must check before provers (plural) - if query.contains('prover(') && !query.contains('provers') { - kind := extract_arg(query, 'kind') - return resolve_prover(kind) - } - if query.contains('provers') { - return resolve_provers() - } - - return ffi_response(200, '{"errors":[{"message":"Unknown query. Available queries: provers, prover(kind), proof(id), suggestTactics(proverId, limit), health, __schema. Available mutations: createProver(kind), destroyProver(id), parseProof(prover, content), verifyProof(prover, content), applyTactic(proverId, tactic), exportProof(proverId), dispatch(config, proof)"}]}') -} - -// --- Mutation Resolvers --- - -fn resolve_create_prover(query string) http.Response { - kind := extract_arg(query, 'kind') - if kind.len == 0 { - return ffi_response(200, '{"errors":[{"message":"kind argument required for createProver mutation"}]}') - } - prover := find_prover(kind) or { - return ffi_response(200, '{"errors":[{"message":"Unknown prover kind: ${esc(kind)}"}]}') - } - if ffi_available { - handle := C.echidna_create_prover(prover.ordinal) - if handle >= 0 { - session_id := 'ses-${kind}-${handle}' - return ffi_response(200, '{"data":{"createProver":{"sessionId":"${session_id}","kind":"${esc(kind)}","status":"active","handle":${handle},"createdAt":"${time.now().format_rfc3339()}","timeoutSeconds":300}}}') - } - err := ffi_last_error() - return ffi_response(200, '{"errors":[{"message":"FFI prover creation failed: ${esc(err)}"}]}') - } - session_id := 'ses-${kind}-${rand.u32()}' - return ffi_response(200, '{"data":{"createProver":{"sessionId":"${session_id}","kind":"${esc(kind)}","status":"active","createdAt":"${time.now().format_rfc3339()}","timeoutSeconds":300}}}') -} - -fn resolve_destroy_prover(query string) http.Response { - id := extract_arg(query, 'id') - if id.len == 0 { - return ffi_response(200, '{"errors":[{"message":"id argument required for destroyProver mutation"}]}') - } - if ffi_available { - handle := extract_handle_from_session(id) - if handle >= 0 { - C.echidna_destroy_prover(handle) - } - } - return ffi_response(200, '{"data":{"destroyProver":{"sessionId":"${esc(id)}","status":"destroyed","destroyedAt":"${time.now().format_rfc3339()}"}}}') -} - -fn resolve_parse_proof(query string) http.Response { - prover := extract_arg(query, 'prover') - content := extract_arg(query, 'content') - if prover.len == 0 || content.len == 0 { - return ffi_response(200, '{"errors":[{"message":"prover and content arguments required for parseProof mutation"}]}') - } - p := find_prover(prover) or { - return ffi_response(200, '{"errors":[{"message":"Unknown prover kind: ${esc(prover)}"}]}') - } - if ffi_available { - handle := C.echidna_create_prover(p.ordinal) - if handle >= 0 { - rc := C.echidna_parse_string(handle, content.str, usize(content.len)) - if rc == 0 { - C.echidna_destroy_prover(handle) - return ffi_response(200, '{"data":{"parseProof":{"parsed":true,"prover":"${esc(prover)}","goals":[],"context":{"theorems":[],"axioms":[],"definitions":[]},"proofScript":[],"contentLength":${content.len},"ffiHandle":${handle}}}}') - } - err := ffi_last_error() - C.echidna_destroy_prover(handle) - return ffi_response(200, '{"errors":[{"message":"Parse failed: ${esc(err)}"}]}') - } - } - return ffi_response(200, '{"data":{"parseProof":{"parsed":true,"prover":"${esc(prover)}","goals":[{"id":"goal_0","target":"forall (n : nat), n + 0 = n","hypotheses":[{"name":"n","type":"nat"}]}],"context":{"theorems":["Nat.add_zero","Nat.add_succ","Nat.rec"],"axioms":[],"definitions":[]},"proofScript":[]}}}') -} - -fn resolve_verify_proof(query string) http.Response { - prover := extract_arg(query, 'prover') - content := extract_arg(query, 'content') - if prover.len == 0 || content.len == 0 { - return ffi_response(200, '{"errors":[{"message":"prover and content arguments required for verifyProof mutation"}]}') - } - p := find_prover(prover) or { - return ffi_response(200, '{"errors":[{"message":"Unknown prover kind: ${esc(prover)}"}]}') - } - if ffi_available { - handle := C.echidna_create_prover(p.ordinal) - if handle >= 0 { - parse_rc := C.echidna_parse_string(handle, content.str, usize(content.len)) - if parse_rc == 0 { - verify_rc := C.echidna_verify_proof(handle) - C.echidna_destroy_prover(handle) - verified := verify_rc == 1 - trust := if verified { 'Level4' } else { 'Level1' } - return ffi_response(200, '{"data":{"verifyProof":{"verified":${verified},"trustLevel":"${trust}","message":"Proof ${if verified { 'verified' } else { 'failed' }} with ${trust} trust","prover":"${esc(prover)}","axiomReport":{"axiom":"none","dangerLevel":"Safe","occurrences":0,"sourceLocations":[]},"goalsRemaining":0,"proofTimeMs":0}}}') - } - err := ffi_last_error() - C.echidna_destroy_prover(handle) - return ffi_response(200, '{"errors":[{"message":"Parse failed: ${esc(err)}"}]}') - } - } - return ffi_response(200, '{"data":{"verifyProof":{"verified":true,"trustLevel":"Level4","message":"Proof verified with Level4 trust","prover":"${esc(prover)}","axiomReport":{"axiom":"none","dangerLevel":"Safe","occurrences":0,"sourceLocations":[]},"certificateHash":"sha3-256:a1b2c3d4e5f6789012345678abcdef0123456789abcdef0123456789abcdef01","goalsRemaining":0,"proofTimeMs":47}}}') -} - -fn resolve_apply_tactic(query string) http.Response { - prover_id := extract_arg(query, 'proverId') - tactic := extract_arg(query, 'tactic') - if prover_id.len == 0 || tactic.len == 0 { - return ffi_response(200, '{"errors":[{"message":"proverId and tactic arguments required for applyTactic mutation"}]}') - } - if ffi_available { - handle := extract_handle_from_session(prover_id) - if handle >= 0 { - rc := C.echidna_apply_tactic(handle, tactic.str, usize(tactic.len)) - if rc == 0 { - return ffi_response(200, '{"data":{"applyTactic":{"success":true,"proverId":"${esc(prover_id)}","tacticApplied":"${esc(tactic)}","newState":{"goals":[],"proofScript":["${esc(tactic)}"],"goalsRemaining":0},"errorMessage":null}}}') - } - err := ffi_last_error() - return ffi_response(200, '{"data":{"applyTactic":{"success":false,"proverId":"${esc(prover_id)}","tacticApplied":"${esc(tactic)}","newState":null,"errorMessage":"${esc(err)}"}}}') - } - } - return ffi_response(200, '{"data":{"applyTactic":{"success":true,"proverId":"${esc(prover_id)}","tacticApplied":"${esc(tactic)}","newState":{"goals":[{"id":"goal_1","target":"0 = 0","hypotheses":[{"name":"n","type":"nat"},{"name":"IH","type":"n + 0 = n"}]}],"proofScript":["${esc(tactic)}"],"goalsRemaining":1},"errorMessage":null}}}') -} - -fn resolve_export_proof(query string) http.Response { - prover_id := extract_arg(query, 'proverId') - if prover_id.len == 0 { - return ffi_response(200, '{"errors":[{"message":"proverId argument required for exportProof mutation"}]}') - } - if ffi_available { - handle := extract_handle_from_session(prover_id) - if handle >= 0 { - mut buf := [4096]u8{} - mut buf_len := usize(4096) - rc := C.echidna_export_proof(handle, &buf[0], &buf_len) - if rc == 0 && buf_len > 0 { - exported := unsafe { tos(&buf[0], int(buf_len)) } - return ffi_response(200, '{"data":{"exportProof":{"proverId":"${esc(prover_id)}","format":"lean4","exportedContent":"${esc(exported)}","contentLength":${buf_len},"exportTimeMs":0}}}') - } - } - } - return ffi_response(200, '{"data":{"exportProof":{"proverId":"${esc(prover_id)}","format":"lean4","exportedContent":"theorem add_zero (n : Nat) : n + 0 = n := by\\n induction n with\\n | zero => rfl\\n | succ n ih => simp [Nat.add_succ, ih]","contentLength":112,"exportTimeMs":3}}}') -} - -fn resolve_dispatch(query string, data string) http.Response { - proof := extract_arg(query, 'proof') - if proof.len == 0 { - // Try variables - variables_proof := json_field(data, 'proof') - if variables_proof.len == 0 { - return ffi_response(200, '{"errors":[{"message":"proof argument required for dispatch mutation"}]}') - } - } - config_str := extract_arg(query, 'config') - cross_check := config_str.contains('cross_check') - - provers_used := if cross_check { - '"lean","z3","cvc5"' - } else { - '"lean"' - } - trust := if cross_check { 'Level5' } else { 'Level4' } - - if ffi_available { - prover_kind := 'lean' - p := find_prover(prover_kind) or { - return ffi_response(200, '{"errors":[{"message":"Internal error: default prover not found"}]}') - } - handle := C.echidna_create_prover(p.ordinal) - if handle >= 0 { - actual_proof := if proof.len > 0 { proof } else { json_field(data, 'proof') } - parse_rc := C.echidna_parse_string(handle, actual_proof.str, usize(actual_proof.len)) - if parse_rc == 0 { - verify_rc := C.echidna_verify_proof(handle) - C.echidna_destroy_prover(handle) - verified := verify_rc == 1 - actual_trust := if verified && cross_check { - 'Level5' - } else if verified { - 'Level4' - } else { - 'Level1' - } - return ffi_response(200, '{"data":{"dispatch":{"verified":${verified},"trustLevel":"${actual_trust}","proversUsed":[${provers_used}],"proofTimeMs":0,"goalsRemaining":0,"axiomReport":{"axiom":"none","dangerLevel":"Safe","occurrences":0,"sourceLocations":[]},"message":"Proof dispatch complete with ${actual_trust} trust","crossChecked":${cross_check}}}}') - } - err := ffi_last_error() - C.echidna_destroy_prover(handle) - return ffi_response(200, '{"errors":[{"message":"Parse failed: ${esc(err)}"}]}') - } - } - - return ffi_response(200, '{"data":{"dispatch":{"verified":true,"trustLevel":"${trust}","proversUsed":[${provers_used}],"proofTimeMs":142,"goalsRemaining":0,"axiomReport":{"axiom":"none","dangerLevel":"Safe","occurrences":0,"sourceLocations":[]},"certificateHash":"sha3-256:b4d7e2a1c9f80356124abc789def0123456789abcdef0123456789abcdef0142","message":"Proof verified with ${trust} trust","crossChecked":${cross_check}}}}') -} - -// --- Query Resolvers --- - -fn resolve_provers() http.Response { - if ffi_available { - count := C.echidna_prover_count() - mut items := []string{} - for i in 0 .. count { - name_ptr := C.echidna_prover_name(i) - name := if name_ptr != unsafe { nil } { - unsafe { cstring_to_vstring(name_ptr) } - } else { - 'Unknown' - } - p := find_prover_by_index(i) or { - items << '{"kind":"prover_${i}","name":"${esc(name)}","tier":0,"description":"","available":true,"complexity":0}' - continue - } - items << prover_to_graphql_json(p) - } - return ffi_response(200, '{"data":{"provers":[${items.join(",")}]}}') - } - provers := all_provers() - mut items := []string{} - for p in provers { - items << prover_to_graphql_json(p) - } - return ffi_response(200, '{"data":{"provers":[${items.join(",")}]}}') -} - -fn resolve_prover(kind string) http.Response { - if kind.len == 0 { - return ffi_response(200, '{"errors":[{"message":"kind argument required"}]}') - } - prover := find_prover(kind) or { - return ffi_response(200, '{"errors":[{"message":"Unknown prover kind: ${esc(kind)}"}]}') - } - return ffi_response(200, '{"data":{"prover":${prover_to_graphql_json(prover)}}}') -} - -fn resolve_proof(id string) http.Response { - if id.len == 0 { - return ffi_response(200, '{"errors":[{"message":"id argument required"}]}') - } - return ffi_response(200, '{"data":{"proof":{"id":"${esc(id)}","goals":[{"id":"goal_0","target":"forall (n : nat), n + 0 = n","hypotheses":[{"name":"n","type":"nat"}]}],"context":{"theorems":["Nat.add_zero","Nat.add_succ"],"axioms":[],"definitions":[]},"proofScript":[],"isComplete":false}}}') -} - -fn resolve_suggest_tactics(query string) http.Response { - prover_id := extract_arg(query, 'proverId') - if prover_id.len == 0 { - return ffi_response(200, '{"errors":[{"message":"proverId argument required"}]}') - } - if ffi_available { - handle := extract_handle_from_session(prover_id) - if handle >= 0 { - mut buf := [1024]u8{} - mut buf_len := usize(1024) - rc := C.echidna_suggest_tactics(handle, 5, &buf[0], &buf_len) - if rc == 0 && buf_len > 0 { - raw := unsafe { tos(&buf[0], int(buf_len)) } - return ffi_response(200, '{"data":{"suggestTactics":{"proverId":"${esc(prover_id)}","suggestionsRaw":${raw},"model":"echidna-tactic-v1.5","inferenceTimeMs":0}}}') - } - } - } - return ffi_response(200, '{"data":{"suggestTactics":{"proverId":"${esc(prover_id)}","suggestions":[{"tactic":"simp [Nat.add_zero]","confidence":0.94,"source":"neural_premise_selection"},{"tactic":"rfl","confidence":0.87,"source":"heuristic"},{"tactic":"induction n","confidence":0.72,"source":"neural_premise_selection"},{"tactic":"apply Nat.rec","confidence":0.65,"source":"library_search"},{"tactic":"omega","confidence":0.58,"source":"heuristic"}],"model":"echidna-tactic-v1.5","inferenceTimeMs":12}}}') -} - -fn resolve_health() http.Response { - now := time.now() - if ffi_available { - ver := ffi_version() - count := C.echidna_prover_count() - return ffi_response(200, '{"data":{"health":{"healthy":true,"service":"echidna-graphql","version":"${ver}","proverCount":${count},"activeSessions":0,"uptimeSeconds":${now.unix()},"trustPipeline":"operational","integrityChecker":"enabled","axiomTracker":"enabled","sandboxMode":"bubblewrap","ffiMode":"live"}}}') - } - return ffi_response(200, '{"data":{"health":{"healthy":true,"service":"echidna-graphql","version":"1.5.0","proverCount":30,"activeSessions":0,"uptimeSeconds":${now.unix()},"trustPipeline":"operational","integrityChecker":"enabled","axiomTracker":"enabled","sandboxMode":"bubblewrap","ffiMode":"stub"}}}') -} - -fn resolve_schema() http.Response { - return ffi_response(200, '{"data":{"__schema":{"types":[' + - '{"name":"Query","fields":["provers","prover","proof","suggestTactics","health"]},' + - '{"name":"Mutation","fields":["createProver","destroyProver","parseProof","verifyProof","applyTactic","exportProof","dispatch"]},' + - '{"name":"Prover","fields":["kind","name","tier","description","available","complexity"]},' + - '{"name":"ProverKind","fields":["AGDA","COQ","LEAN","ISABELLE","Z3","CVC5","METAMATH","HOL_LIGHT","MIZAR","PVS","ACL2","HOL4","IDRIS2","FSTAR","VAMPIRE","EPROVER","SPASS","ALT_ERGO","DAFNY","WHY3","TLAPS","TWELF","NUPRL","MINLOG","IMANDRA","GLPK","SCIP","MINIZINC","CHUFFED","ORTOOLS"]},' + - '{"name":"ProofResult","fields":["verified","trustLevel","message","axiomReport","certificateHash","goalsRemaining","proofTimeMs"]},' + - '{"name":"TacticSuggestion","fields":["tactic","confidence","source"]},' + - '{"name":"DispatchResult","fields":["verified","trustLevel","proversUsed","proofTimeMs","goalsRemaining","axiomReport","certificateHash","message","crossChecked"]},' + - '{"name":"TrustLevel","fields":["LEVEL0","LEVEL1","LEVEL2","LEVEL3","LEVEL4","LEVEL5"]},' + - '{"name":"Goal","fields":["id","target","hypotheses"]},' + - '{"name":"Hypothesis","fields":["name","type"]},' + - '{"name":"AxiomReport","fields":["axiom","dangerLevel","occurrences","sourceLocations"]},' + - '{"name":"ProofState","fields":["goals","context","proofScript","isComplete"]},' + - '{"name":"Context","fields":["theorems","axioms","definitions"]}' + - ']}}}') -} - -fn graphiql_page() http.Response { - html := ' -ECHIDNA GraphQL - -

ECHIDNA GraphQL API

-

POST queries to /graphql with JSON body:

-
-// List all 30 provers
-{ "query": "{ provers { kind name tier description available complexity } }" }
-
-// Get specific prover
-{ "query": "{ prover(kind: \\"lean\\") { kind name tier description } }" }
-
-// Verify a proof
-{ "query": "mutation { verifyProof(prover: \\"lean\\", content: \\"theorem foo : True := trivial\\") { verified trustLevel message certificateHash } }" }
-
-// Full trust-pipeline dispatch
-{ "query": "mutation { dispatch(proof: \\"..\\") { verified trustLevel proversUsed proofTimeMs crossChecked } }" }
-
-// Suggest tactics
-{ "query": "{ suggestTactics(proverId: \\"ses-lean-001\\", limit: 5) { suggestions { tactic confidence source } } }" }
-
-// Health check
-{ "query": "{ health { healthy service version proverCount trustPipeline } }" }
-
-// Schema introspection
-{ "query": "{ __schema { types { name fields } } }" }
-
-

Trust Levels: Level0 (untrusted) through Level5 (cross-checked, certificate-verified)

-

X-Echidna-Mode header indicates live (FFI) or stub (simulated) responses

-' - - return http.new_response( - status: .ok - header: http.new_header(key: .content_type, value: 'text/html') - body: html - ) -} - -// --- Helpers --- - -fn ffi_response(status_code int, body string) http.Response { - mut header := http.new_header(key: .content_type, value: 'application/json') - if ffi_available { - header.add_custom('X-Echidna-Mode', 'live') or {} - } else { - header.add_custom('X-Echidna-Mode', 'stub') or {} - } - return http.new_response( - status: unsafe { http.Status(status_code) } - header: header - body: body - ) -} - -fn json_response(status_code int, body string) http.Response { - return http.new_response( - status: unsafe { http.Status(status_code) } - header: http.new_header(key: .content_type, value: 'application/json') - body: body - ) -} - -fn esc(s string) string { - return s.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n').replace('\t', '\\t') -} - -fn json_field(data string, key string) string { - needle := '"${key}":' - idx := data.index(needle) or { return '' } - tail := data[idx + needle.len..].trim_space() - if tail.len == 0 || tail[0] != `"` { - return '' - } - end := tail[1..].index('"') or { return '' } - return tail[1..end + 1] -} - -fn json_field_or(data string, key string, default_val string) string { - val := json_field(data, key) - if val.len == 0 { - return default_val - } - return val -} - -fn extract_arg(query string, arg_name string) string { - needle := '${arg_name}:' - idx := query.index(needle) or { return '' } - tail := query[idx + needle.len..].trim_space() - if tail.len == 0 { - return '' - } - if tail[0] == `"` { - end := tail[1..].index('"') or { return '' } - return tail[1..end + 1] - } - mut end := tail.len - for i, c in tail { - if c == `,` || c == `)` || c == ` ` || c == `}` { - end = i - break - } - } - return tail[..end] -} - -fn extract_handle_from_session(session_id string) int { - parts := session_id.split('-') - if parts.len >= 3 { - return parts.last().int() - } - return -1 -} - -fn find_prover_by_index(idx int) ?ProverInfo { - provers := all_provers() - if idx < 0 || idx >= provers.len { - return none - } - return provers[idx] -} diff --git a/src/interfaces/v-adapter/grpc.v b/src/interfaces/v-adapter/grpc.v deleted file mode 100644 index 42acf6d1..00000000 --- a/src/interfaces/v-adapter/grpc.v +++ /dev/null @@ -1,525 +0,0 @@ -// SPDX-License-Identifier: PMPL-1.0-or-later -// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -// -// ECHIDNA gRPC-Web API Gateway -// -// Exposes ECHIDNA theorem prover dispatch via gRPC-style RPC on port 8101: -// POST /echidna.ProverService/CreateProver — create prover session -// POST /echidna.ProverService/DestroyProver — destroy prover session -// POST /echidna.ProverService/ListProvers — list available provers -// POST /echidna.ProverService/ParseProof — parse proof content -// POST /echidna.ProverService/VerifyProof — verify proof -// POST /echidna.ProverService/ApplyTactic — apply tactic -// POST /echidna.ProverService/SuggestTactics — suggest tactics -// POST /echidna.ProverService/ExportProof — export proof -// POST /echidna.ProverService/Dispatch — full trust pipeline dispatch -// POST /echidna.ProverService/Health — health check -// -// Uses JSON-over-HTTP as transport (gRPC-Web compatible). -// Full HTTP/2 + Protobuf transport planned via Zig FFI. -// -// Links against libechidna_ffi.so (Zig FFI layer) when available. -// Falls back to stub responses with X-Echidna-Mode: stub header when FFI is absent. - -module main - -import net.http -import time -import rand - -// --- Zig FFI extern declarations (libechidna_ffi.so) --- - -#flag -L @VMODROOT/../../ffi/zig/zig-out/lib -#flag -lechidna_ffi - -fn C.echidna_init() int -fn C.echidna_deinit() -fn C.echidna_create_prover(kind int) int -fn C.echidna_destroy_prover(handle int) -fn C.echidna_parse_file(handle int, path_ptr &u8, path_len usize) int -fn C.echidna_parse_string(handle int, content_ptr &u8, content_len usize) int -fn C.echidna_apply_tactic(handle int, tactic_ptr &u8, tactic_len usize) int -fn C.echidna_verify_proof(handle int) int -fn C.echidna_export_proof(handle int, out_ptr &u8, out_len &usize) int -fn C.echidna_suggest_tactics(handle int, limit int, out_ptr &u8, out_len &usize) int -fn C.echidna_version() &u8 -fn C.echidna_prover_count() int -fn C.echidna_prover_name(kind int) &u8 -fn C.echidna_last_error() &u8 -fn C.echidna_build_info() &u8 - -__global ffi_available = false -__global ffi_initialised = false - -fn init_ffi() { - rc := C.echidna_init() - if rc == 0 { - ffi_available = true - ffi_initialised = true - println(' FFI: linked to libechidna_ffi.so (live mode)') - } else { - ffi_available = false - println(' FFI: not available (stub mode)') - } -} - -fn ffi_last_error() string { - ptr := C.echidna_last_error() - if ptr == unsafe { nil } { - return 'unknown error' - } - return unsafe { cstring_to_vstring(ptr) } -} - -fn ffi_version() string { - ptr := C.echidna_version() - return unsafe { cstring_to_vstring(ptr) } -} - -// --- Prover Data --- - -struct ProverInfo { - kind string - name string - tier int - ordinal int // FFI prover kind ordinal (0-29) - description string - available bool - complexity int -} - -fn all_provers() []ProverInfo { - return [ - // Tier 1: Core + SMT (ordinals 0-5 match Zig ProverKind) - ProverInfo{kind: 'agda', name: 'Agda', tier: 1, ordinal: 0, description: 'Dependently-typed proof assistant with Curry-Howard correspondence', available: true, complexity: 3}, - ProverInfo{kind: 'coq', name: 'Coq/Rocq', tier: 1, ordinal: 1, description: 'Calculus of Inductive Constructions proof assistant', available: true, complexity: 3}, - ProverInfo{kind: 'lean', name: 'Lean 4', tier: 1, ordinal: 2, description: 'Dependent type theory with powerful automation (mathlib)', available: true, complexity: 3}, - ProverInfo{kind: 'isabelle', name: 'Isabelle/HOL', tier: 1, ordinal: 3, description: 'Higher-order logic proof assistant with Sledgehammer', available: true, complexity: 4}, - ProverInfo{kind: 'z3', name: 'Z3', tier: 1, ordinal: 4, description: 'Microsoft SMT solver (SAT modulo theories)', available: true, complexity: 2}, - ProverInfo{kind: 'cvc5', name: 'CVC5', tier: 1, ordinal: 5, description: 'SMT solver with quantifier reasoning', available: true, complexity: 2}, - // Tier 2: Big Six completion (ordinals 6-8) - ProverInfo{kind: 'metamath', name: 'Metamath', tier: 2, ordinal: 6, description: 'Minimalist proof language with tiny trusted kernel', available: true, complexity: 2}, - ProverInfo{kind: 'hollight', name: 'HOL Light', tier: 2, ordinal: 7, description: 'Simple higher-order logic theorem prover', available: true, complexity: 3}, - ProverInfo{kind: 'mizar', name: 'Mizar', tier: 2, ordinal: 8, description: 'Set-theoretic proof assistant (MML library)', available: true, complexity: 3}, - // Tier 3: Additional coverage (ordinals 9-10) - ProverInfo{kind: 'pvs', name: 'PVS', tier: 3, ordinal: 9, description: 'Prototype Verification System with rich type system', available: true, complexity: 4}, - ProverInfo{kind: 'acl2', name: 'ACL2', tier: 3, ordinal: 10, description: 'A Computational Logic for Applicative Common Lisp', available: true, complexity: 4}, - // Tier 4: Advanced (ordinal 11) - ProverInfo{kind: 'hol4', name: 'HOL4', tier: 4, ordinal: 11, description: 'Higher-order logic (LCF-style, SML-based)', available: true, complexity: 5}, - // Extended: Dependent types (ordinals 12, 17) - ProverInfo{kind: 'idris2', name: 'Idris 2', tier: 1, ordinal: 12, description: 'Quantitative type theory with dependent types and linear types', available: true, complexity: 3}, - ProverInfo{kind: 'fstar', name: 'F*', tier: 1, ordinal: 17, description: 'Dependent types with effects for program verification', available: true, complexity: 3}, - // Tier 5: First-Order ATPs (ordinals 13-16) - ProverInfo{kind: 'vampire', name: 'Vampire', tier: 5, ordinal: 13, description: 'First-order automated theorem prover (TPTP format)', available: true, complexity: 2}, - ProverInfo{kind: 'eprover', name: 'E Prover', tier: 5, ordinal: 14, description: 'Equational first-order theorem prover', available: true, complexity: 2}, - ProverInfo{kind: 'spass', name: 'SPASS', tier: 5, ordinal: 15, description: 'First-order logic with equality (DFG/TPTP)', available: true, complexity: 2}, - ProverInfo{kind: 'altergo', name: 'Alt-Ergo', tier: 5, ordinal: 16, description: 'SMT solver with polymorphism and AC reasoning', available: true, complexity: 2}, - // Tier 6: Auto-active / orchestration (ordinals 18-19) - ProverInfo{kind: 'dafny', name: 'Dafny', tier: 2, ordinal: 18, description: 'Auto-active verification language (Boogie/Z3 backend)', available: true, complexity: 2}, - ProverInfo{kind: 'why3', name: 'Why3', tier: 2, ordinal: 19, description: 'Multi-prover orchestration platform', available: true, complexity: 3}, - // Tier 7: Specialised / niche (ordinals 20-24) - ProverInfo{kind: 'tlaps', name: 'TLAPS', tier: 2, ordinal: 20, description: 'TLA+ Proof System for distributed systems', available: true, complexity: 4}, - ProverInfo{kind: 'twelf', name: 'Twelf', tier: 4, ordinal: 21, description: 'Logical framework based on LF type theory', available: true, complexity: 4}, - ProverInfo{kind: 'nuprl', name: 'Nuprl', tier: 4, ordinal: 22, description: 'Constructive type theory prover (Cornell)', available: true, complexity: 4}, - ProverInfo{kind: 'minlog', name: 'Minlog', tier: 4, ordinal: 23, description: 'Minimal logic proof assistant with program extraction', available: true, complexity: 4}, - ProverInfo{kind: 'imandra', name: 'Imandra', tier: 2, ordinal: 24, description: 'ML-based automated reasoning engine', available: true, complexity: 3}, - // Tier 8: Constraint solvers (ordinals 25-29) - ProverInfo{kind: 'glpk', name: 'GLPK', tier: 5, ordinal: 25, description: 'GNU Linear Programming Kit (LP/MIP solver)', available: true, complexity: 2}, - ProverInfo{kind: 'scip', name: 'SCIP', tier: 5, ordinal: 26, description: 'Solving Constraint Integer Programs (MINLP)', available: true, complexity: 3}, - ProverInfo{kind: 'minizinc', name: 'MiniZinc', tier: 5, ordinal: 27, description: 'Constraint modelling language', available: true, complexity: 2}, - ProverInfo{kind: 'chuffed', name: 'Chuffed', tier: 5, ordinal: 28, description: 'Lazy clause generation constraint solver', available: true, complexity: 2}, - ProverInfo{kind: 'ortools', name: 'OR-Tools', tier: 5, ordinal: 29, description: 'Google constraint and optimization solver', available: true, complexity: 2}, - ] -} - -fn find_prover(kind string) ?ProverInfo { - for p in all_provers() { - if p.kind == kind { - return p - } - } - return none -} - -fn find_prover_by_index(idx int) ?ProverInfo { - provers := all_provers() - if idx < 0 || idx >= provers.len { - return none - } - return provers[idx] -} - -fn prover_to_json(p ProverInfo) string { - return '{"kind":"${p.kind}","name":"${esc(p.name)}","tier":${p.tier},"description":"${esc(p.description)}","available":${p.available},"complexity":${p.complexity}}' -} - -// --- gRPC Handler --- - -struct EchidnaGRPCHandler { - port int -} - -pub fn (mut h EchidnaGRPCHandler) handle(req http.Request) http.Response { - if req.method != .post { - return grpc_response(405, '{"error":"POST required for RPC calls","hint":"gRPC-Web uses POST for all service methods"}') - } - - path := req.url.all_before('?') - return match path { - '/echidna.ProverService/CreateProver' { handle_create_prover(req) } - '/echidna.ProverService/DestroyProver' { handle_destroy_prover(req) } - '/echidna.ProverService/ListProvers' { handle_list_provers() } - '/echidna.ProverService/ParseProof' { handle_parse_proof(req) } - '/echidna.ProverService/VerifyProof' { handle_verify_proof(req) } - '/echidna.ProverService/ApplyTactic' { handle_apply_tactic(req) } - '/echidna.ProverService/SuggestTactics' { handle_suggest_tactics(req) } - '/echidna.ProverService/ExportProof' { handle_export_proof(req) } - '/echidna.ProverService/Dispatch' { handle_dispatch(req) } - '/echidna.ProverService/Health' { handle_health() } - else { - grpc_response(404, '{"error":"Unknown method: ${esc(path)}","available":["/echidna.ProverService/CreateProver","/echidna.ProverService/DestroyProver","/echidna.ProverService/ListProvers","/echidna.ProverService/ParseProof","/echidna.ProverService/VerifyProof","/echidna.ProverService/ApplyTactic","/echidna.ProverService/SuggestTactics","/echidna.ProverService/ExportProof","/echidna.ProverService/Dispatch","/echidna.ProverService/Health"]}') - } - } -} - -// --- Server --- - -pub struct Server { -pub mut: - port int -} - -pub fn new_server(port int) &Server { - return &Server{ - port: port - } -} - -pub fn (s Server) start() { - println('ECHIDNA gRPC-Web API Gateway starting on port ${s.port}...') - init_ffi() - println(' POST /echidna.ProverService/CreateProver — create prover session') - println(' POST /echidna.ProverService/DestroyProver — destroy prover session') - println(' POST /echidna.ProverService/ListProvers — list available provers') - println(' POST /echidna.ProverService/ParseProof — parse proof content') - println(' POST /echidna.ProverService/VerifyProof — verify proof') - println(' POST /echidna.ProverService/ApplyTactic — apply tactic') - println(' POST /echidna.ProverService/SuggestTactics — suggest tactics') - println(' POST /echidna.ProverService/ExportProof — export proof') - println(' POST /echidna.ProverService/Dispatch — full trust pipeline dispatch') - println(' POST /echidna.ProverService/Health — health check') - println(' (JSON-over-HTTP transport, gRPC-Web compatible)') - mut server := http.Server{ - addr: ':${s.port}' - handler: &EchidnaGRPCHandler{port: s.port} - } - server.listen_and_serve() -} - -fn main() { - mut s := new_server(8101) - s.start() -} - -// --- RPC Handlers --- - -fn handle_create_prover(req http.Request) http.Response { - kind := json_field(req.data, 'kind') - if kind.len == 0 { - return grpc_response(400, '{"error":"kind field required (e.g. lean, coq, z3)"}') - } - prover := find_prover(kind) or { - return grpc_response(400, '{"error":"Unknown prover kind","kind":"${esc(kind)}"}') - } - if ffi_available { - handle := C.echidna_create_prover(prover.ordinal) - if handle >= 0 { - session_id := 'ses-${kind}-${handle}' - return grpc_response(200, '{"session_id":"${session_id}","kind":"${esc(kind)}","status":"active","handle":${handle},"created_at":"${time.now().format_rfc3339()}","timeout_seconds":300}') - } - err := ffi_last_error() - return grpc_response(500, '{"error":"FFI prover creation failed","detail":"${esc(err)}","kind":"${esc(kind)}"}') - } - session_id := 'ses-${kind}-${rand.u32()}' - return grpc_response(200, '{"session_id":"${session_id}","kind":"${esc(kind)}","status":"active","created_at":"${time.now().format_rfc3339()}","timeout_seconds":300}') -} - -fn handle_destroy_prover(req http.Request) http.Response { - session_id := json_field(req.data, 'session_id') - if session_id.len == 0 { - return grpc_response(400, '{"error":"session_id field required"}') - } - if ffi_available { - handle := extract_handle_from_session(session_id) - if handle >= 0 { - C.echidna_destroy_prover(handle) - } - } - return grpc_response(200, '{"session_id":"${esc(session_id)}","status":"destroyed","destroyed_at":"${time.now().format_rfc3339()}"}') -} - -fn handle_list_provers() http.Response { - if ffi_available { - count := C.echidna_prover_count() - mut items := []string{} - for i in 0 .. count { - name_ptr := C.echidna_prover_name(i) - name := if name_ptr != unsafe { nil } { - unsafe { cstring_to_vstring(name_ptr) } - } else { - 'Unknown' - } - p := find_prover_by_index(i) or { - items << '{"kind":"prover_${i}","name":"${esc(name)}","tier":0,"description":"","available":true,"complexity":0}' - continue - } - items << prover_to_json(p) - } - return grpc_response(200, '{"provers":[${items.join(",")}],"count":${count}}') - } - provers := all_provers() - mut items := []string{} - for p in provers { - items << prover_to_json(p) - } - return grpc_response(200, '{"provers":[${items.join(",")}],"count":${provers.len}}') -} - -fn handle_parse_proof(req http.Request) http.Response { - prover := json_field(req.data, 'prover') - content := json_field(req.data, 'content') - if prover.len == 0 || content.len == 0 { - return grpc_response(400, '{"error":"Both prover and content fields required"}') - } - p := find_prover(prover) or { - return grpc_response(400, '{"error":"Unknown prover kind","kind":"${esc(prover)}"}') - } - if ffi_available { - handle := C.echidna_create_prover(p.ordinal) - if handle >= 0 { - rc := C.echidna_parse_string(handle, content.str, usize(content.len)) - if rc == 0 { - C.echidna_destroy_prover(handle) - return grpc_response(200, '{"parsed":true,"prover":"${esc(prover)}","goals":[],"context":{"theorems":[],"axioms":[],"definitions":[]},"proof_script":[],"content_length":${content.len},"ffi_handle":${handle}}') - } - err := ffi_last_error() - C.echidna_destroy_prover(handle) - return grpc_response(422, '{"parsed":false,"prover":"${esc(prover)}","error":"${esc(err)}","content_length":${content.len}}') - } - } - return grpc_response(200, '{"parsed":true,"prover":"${esc(prover)}","goals":[{"id":"goal_0","target":"forall (n : nat), n + 0 = n","hypotheses":[{"name":"n","type":"nat"}]}],"context":{"theorems":["Nat.add_zero","Nat.add_succ","Nat.rec"],"axioms":[],"definitions":[]},"proof_script":[],"content_length":${content.len}}') -} - -fn handle_verify_proof(req http.Request) http.Response { - prover := json_field(req.data, 'prover') - content := json_field(req.data, 'content') - if prover.len == 0 || content.len == 0 { - return grpc_response(400, '{"error":"Both prover and content fields required"}') - } - p := find_prover(prover) or { - return grpc_response(400, '{"error":"Unknown prover kind","kind":"${esc(prover)}"}') - } - if ffi_available { - handle := C.echidna_create_prover(p.ordinal) - if handle >= 0 { - parse_rc := C.echidna_parse_string(handle, content.str, usize(content.len)) - if parse_rc == 0 { - verify_rc := C.echidna_verify_proof(handle) - C.echidna_destroy_prover(handle) - verified := verify_rc == 1 - trust := if verified { 'Level4' } else { 'Level1' } - return grpc_response(200, '{"verified":${verified},"trust_level":"${trust}","message":"Proof ${if verified { 'verified' } else { 'failed' }} with ${trust} trust","prover":"${esc(prover)}","axiom_report":{"axiom":"none","danger_level":"Safe","occurrences":0,"source_locations":[]},"goals_remaining":0,"proof_time_ms":0}') - } - err := ffi_last_error() - C.echidna_destroy_prover(handle) - return grpc_response(422, '{"verified":false,"trust_level":"Level0","message":"Parse failed: ${esc(err)}","prover":"${esc(prover)}"}') - } - } - return grpc_response(200, '{"verified":true,"trust_level":"Level4","message":"Proof verified with Level4 trust","prover":"${esc(prover)}","axiom_report":{"axiom":"none","danger_level":"Safe","occurrences":0,"source_locations":[]},"certificate_hash":"sha3-256:a1b2c3d4e5f6789012345678abcdef0123456789abcdef0123456789abcdef01","goals_remaining":0,"proof_time_ms":47}') -} - -fn handle_apply_tactic(req http.Request) http.Response { - proof_id := json_field(req.data, 'proof_id') - tactic := json_field(req.data, 'tactic') - if proof_id.len == 0 || tactic.len == 0 { - return grpc_response(400, '{"error":"Both proof_id and tactic fields required"}') - } - if ffi_available { - handle := extract_handle_from_session(proof_id) - if handle >= 0 { - rc := C.echidna_apply_tactic(handle, tactic.str, usize(tactic.len)) - if rc == 0 { - return grpc_response(200, '{"success":true,"proof_id":"${esc(proof_id)}","tactic_applied":"${esc(tactic)}","new_state":{"goals":[],"proof_script":["${esc(tactic)}"],"goals_remaining":0},"error_message":null}') - } - err := ffi_last_error() - return grpc_response(422, '{"success":false,"proof_id":"${esc(proof_id)}","tactic_applied":"${esc(tactic)}","error_message":"${esc(err)}"}') - } - } - return grpc_response(200, '{"success":true,"proof_id":"${esc(proof_id)}","tactic_applied":"${esc(tactic)}","new_state":{"goals":[{"id":"goal_1","target":"0 = 0","hypotheses":[{"name":"n","type":"nat"},{"name":"IH","type":"n + 0 = n"}]}],"proof_script":["${esc(tactic)}"],"goals_remaining":1},"error_message":null}') -} - -fn handle_suggest_tactics(req http.Request) http.Response { - proof_id := json_field(req.data, 'proof_id') - if proof_id.len == 0 { - return grpc_response(400, '{"error":"proof_id field required"}') - } - limit := json_field_int(req.data, 'limit') - effective_limit := if limit > 0 { limit } else { 5 } - if ffi_available { - handle := extract_handle_from_session(proof_id) - if handle >= 0 { - mut buf := [1024]u8{} - mut buf_len := usize(1024) - rc := C.echidna_suggest_tactics(handle, effective_limit, &buf[0], &buf_len) - if rc == 0 && buf_len > 0 { - raw := unsafe { tos(&buf[0], int(buf_len)) } - return grpc_response(200, '{"proof_id":"${esc(proof_id)}","suggestions_raw":${raw},"model":"echidna-tactic-v1.5","inference_time_ms":0}') - } - } - } - return grpc_response(200, '{"proof_id":"${esc(proof_id)}","suggestions":[{"tactic":"simp [Nat.add_zero]","confidence":0.94,"source":"neural_premise_selection"},{"tactic":"rfl","confidence":0.87,"source":"heuristic"},{"tactic":"induction n","confidence":0.72,"source":"neural_premise_selection"},{"tactic":"apply Nat.rec","confidence":0.65,"source":"library_search"},{"tactic":"omega","confidence":0.58,"source":"heuristic"}],"model":"echidna-tactic-v1.5","inference_time_ms":12}') -} - -fn handle_export_proof(req http.Request) http.Response { - proof_id := json_field(req.data, 'proof_id') - if proof_id.len == 0 { - return grpc_response(400, '{"error":"proof_id field required"}') - } - format := json_field_or(req.data, 'format', 'lean4') - if ffi_available { - handle := extract_handle_from_session(proof_id) - if handle >= 0 { - mut buf := [4096]u8{} - mut buf_len := usize(4096) - rc := C.echidna_export_proof(handle, &buf[0], &buf_len) - if rc == 0 && buf_len > 0 { - exported := unsafe { tos(&buf[0], int(buf_len)) } - return grpc_response(200, '{"proof_id":"${esc(proof_id)}","format":"${esc(format)}","exported_content":"${esc(exported)}","content_length":${buf_len},"export_time_ms":0}') - } - } - } - return grpc_response(200, '{"proof_id":"${esc(proof_id)}","format":"${esc(format)}","exported_content":"theorem add_zero (n : Nat) : n + 0 = n := by\\n induction n with\\n | zero => rfl\\n | succ n ih => simp [Nat.add_succ, ih]","content_length":112,"export_time_ms":3}') -} - -fn handle_dispatch(req http.Request) http.Response { - proof := json_field(req.data, 'proof') - if proof.len == 0 { - return grpc_response(400, '{"error":"proof field required"}') - } - cross_check := json_field(req.data, 'cross_check') - prover := json_field_or(req.data, 'prover', 'lean') - - is_cross := cross_check == 'true' - provers_used := if is_cross { - '"${esc(prover)}","z3","cvc5"' - } else { - '"${esc(prover)}"' - } - trust := if is_cross { 'Level5' } else { 'Level4' } - - if ffi_available { - p := find_prover(prover) or { - return grpc_response(400, '{"error":"Unknown prover kind","kind":"${esc(prover)}"}') - } - handle := C.echidna_create_prover(p.ordinal) - if handle >= 0 { - parse_rc := C.echidna_parse_string(handle, proof.str, usize(proof.len)) - if parse_rc == 0 { - verify_rc := C.echidna_verify_proof(handle) - C.echidna_destroy_prover(handle) - verified := verify_rc == 1 - actual_trust := if verified && is_cross { - 'Level5' - } else if verified { - 'Level4' - } else { - 'Level1' - } - return grpc_response(200, '{"verified":${verified},"trust_level":"${actual_trust}","provers_used":[${provers_used}],"proof_time_ms":0,"goals_remaining":0,"axiom_report":{"axiom":"none","danger_level":"Safe","occurrences":0,"source_locations":[]},"message":"Proof dispatch complete with ${actual_trust} trust","cross_checked":${is_cross}}') - } - err := ffi_last_error() - C.echidna_destroy_prover(handle) - return grpc_response(422, '{"verified":false,"trust_level":"Level0","error":"${esc(err)}","cross_checked":${is_cross}}') - } - } - return grpc_response(200, '{"verified":true,"trust_level":"${trust}","provers_used":[${provers_used}],"proof_time_ms":142,"goals_remaining":0,"axiom_report":{"axiom":"none","danger_level":"Safe","occurrences":0,"source_locations":[]},"certificate_hash":"sha3-256:b4d7e2a1c9f80356124abc789def0123456789abcdef0123456789abcdef0142","message":"Proof verified with ${trust} trust","cross_checked":${is_cross}}') -} - -fn handle_health() http.Response { - now := time.now() - if ffi_available { - ver := ffi_version() - count := C.echidna_prover_count() - return grpc_response(200, '{"status":"SERVING","service":"echidna-grpc","version":"${ver}","prover_count":${count},"active_sessions":0,"uptime_seconds":${now.unix()},"trust_pipeline":"operational","integrity_checker":"enabled","axiom_tracker":"enabled","sandbox_mode":"bubblewrap","ffi_mode":"live"}') - } - return grpc_response(200, '{"status":"SERVING","service":"echidna-grpc","version":"1.5.0","prover_count":30,"active_sessions":0,"uptime_seconds":${now.unix()},"trust_pipeline":"operational","integrity_checker":"enabled","axiom_tracker":"enabled","sandbox_mode":"bubblewrap","ffi_mode":"stub"}') -} - -// --- Helpers --- - -fn extract_handle_from_session(session_id string) int { - parts := session_id.split('-') - if parts.len >= 3 { - return parts.last().int() - } - return -1 -} - -fn grpc_response(status_code int, body string) http.Response { - mut header := http.new_header(key: .content_type, value: 'application/grpc+json') - if ffi_available { - header.add_custom('X-Echidna-Mode', 'live') or {} - } else { - header.add_custom('X-Echidna-Mode', 'stub') or {} - } - header.add_custom('grpc-status', '0') or {} - return http.new_response( - status: unsafe { http.Status(status_code) } - header: header - body: body - ) -} - -fn esc(s string) string { - return s.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n').replace('\t', '\\t') -} - -fn json_field(data string, key string) string { - needle := '"${key}":' - idx := data.index(needle) or { return '' } - tail := data[idx + needle.len..].trim_space() - if tail.len == 0 || tail[0] != `"` { - return '' - } - end := tail[1..].index('"') or { return '' } - return tail[1..end + 1] -} - -fn json_field_or(data string, key string, default_val string) string { - val := json_field(data, key) - if val.len == 0 { - return default_val - } - return val -} - -fn json_field_int(data string, key string) int { - needle := '"${key}":' - idx := data.index(needle) or { return 0 } - tail := data[idx + needle.len..].trim_space() - if tail.len == 0 { - return 0 - } - mut end := 0 - for i, c in tail { - if c >= `0` && c <= `9` { - end = i + 1 - } else if end > 0 { - break - } - } - if end == 0 { - return 0 - } - return tail[..end].int() -} diff --git a/src/interfaces/v-adapter/overlay.v b/src/interfaces/v-adapter/overlay.v deleted file mode 100644 index f94bfd7b..00000000 --- a/src/interfaces/v-adapter/overlay.v +++ /dev/null @@ -1,814 +0,0 @@ -// SPDX-License-Identifier: PMPL-1.0-or-later -// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -// -// ECHIDNA Overlay Network REST API Gateway -// -// Exposes Tor, IPFS, and Ethereum overlay network operations via REST on port 8103: -// -// Unified: -// GET /api/v1/overlay/health — overlay subsystem health -// GET /api/v1/overlay/status — combined status of all networks -// GET /api/v1/overlay/version — overlay module version -// -// Tor: -// POST /api/v1/overlay/tor/connect — connect to Tor control port -// POST /api/v1/overlay/tor/disconnect — disconnect from Tor -// GET /api/v1/overlay/tor/status — Tor connection status -// POST /api/v1/overlay/tor/hidden-service — create hidden service -// DELETE /api/v1/overlay/tor/hidden-service/:onion — destroy hidden service -// GET /api/v1/overlay/tor/circuits — list active circuits -// GET /api/v1/overlay/tor/circuits/:id — get circuit details -// POST /api/v1/overlay/tor/resolve — resolve hostname through Tor -// -// IPFS: -// POST /api/v1/overlay/ipfs/connect — connect to IPFS daemon -// POST /api/v1/overlay/ipfs/disconnect — disconnect from IPFS -// GET /api/v1/overlay/ipfs/status — IPFS connection status -// POST /api/v1/overlay/ipfs/add — add content, returns CID -// GET /api/v1/overlay/ipfs/cat/:cid — retrieve content by CID -// POST /api/v1/overlay/ipfs/pin — pin content by CID -// POST /api/v1/overlay/ipfs/unpin — unpin content by CID -// GET /api/v1/overlay/ipfs/dag/:cid — get DAG node -// -// Ethereum (stubbed — Aerie future use): -// POST /api/v1/overlay/eth/connect — connect to Ethereum RPC -// POST /api/v1/overlay/eth/disconnect — disconnect from Ethereum -// GET /api/v1/overlay/eth/status — Ethereum connection status -// POST /api/v1/overlay/eth/timestamp-proof — anchor proof hash on-chain -// POST /api/v1/overlay/eth/verify-timestamp — verify anchored timestamp -// -// Links against libechidna_overlay.so (Zig FFI layer) when available. -// Falls back to stub responses with X-Overlay-Mode: stub header when FFI is absent. - -module main - -import net.http -import time - -// --- Zig FFI extern declarations (libechidna_overlay.so) --- - -#flag -L @VMODROOT/../../ffi/zig/zig-out/lib -#flag -lechidna_overlay - -// Tor -fn C.echidna_tor_connect(config_ptr &u8, config_len usize) int -fn C.echidna_tor_disconnect() -fn C.echidna_tor_create_hidden_service(port int, target_port int, out_ptr &u8, out_len &usize) int -fn C.echidna_tor_destroy_hidden_service(onion_ptr &u8, onion_len usize) int -fn C.echidna_tor_get_circuit(circuit_id int, out_ptr &u8, out_len &usize) int -fn C.echidna_tor_list_circuits(out_ptr &u8, out_len &usize) int -fn C.echidna_tor_resolve(hostname_ptr &u8, hostname_len usize, out_ptr &u8, out_len &usize) int -fn C.echidna_tor_status() int -fn C.echidna_tor_hidden_service_count() int - -// IPFS -fn C.echidna_ipfs_connect(config_ptr &u8, config_len usize) int -fn C.echidna_ipfs_disconnect() -fn C.echidna_ipfs_add(data_ptr &u8, data_len usize, out_cid_ptr &u8, out_cid_len &usize) int -fn C.echidna_ipfs_cat(cid_ptr &u8, cid_len usize, out_ptr &u8, out_len &usize) int -fn C.echidna_ipfs_pin(cid_ptr &u8, cid_len usize) int -fn C.echidna_ipfs_unpin(cid_ptr &u8, cid_len usize) int -fn C.echidna_ipfs_dag_get(cid_ptr &u8, cid_len usize, out_ptr &u8, out_len &usize) int -fn C.echidna_ipfs_status() int -fn C.echidna_ipfs_pin_count() int - -// Ethereum (stubbed) -fn C.echidna_eth_connect(config_ptr &u8, config_len usize) int -fn C.echidna_eth_disconnect() -fn C.echidna_eth_timestamp_proof(proof_hash_ptr &u8, proof_hash_len usize, out_ptr &u8, out_len &usize) int -fn C.echidna_eth_verify_timestamp(tx_hash_ptr &u8, tx_hash_len usize, out_ptr &u8, out_len &usize) int -fn C.echidna_eth_status() int - -// Unified -fn C.echidna_overlay_version() &u8 -fn C.echidna_overlay_kind_name(kind int) &u8 -fn C.echidna_overlay_last_error() &u8 - -// Callback registration (Zig FFI bidirectional callbacks) -// Signatures must match Zig OnStatusChangeFn, OnErrorFn, OnProgressFn, OnCircuitChangeFn, OnPinChangeFn -fn C.echidna_overlay_register_on_status_change(cb fn (int, int, int) ) int -fn C.echidna_overlay_register_on_error(cb fn (int, int, &u8, usize) ) int -fn C.echidna_overlay_register_on_progress(cb fn (int, int, u64, u64) ) int -fn C.echidna_overlay_register_on_circuit_change(cb fn (int, int, int) ) int -fn C.echidna_overlay_register_on_pin_change(cb fn (&u8, usize, int, int) ) int -fn C.echidna_overlay_unregister_all_callbacks() int -fn C.echidna_overlay_callback_count() int - -// --- Overlay Event Buffer --- - -struct OverlayEvent { - kind string - timestamp string - data string -} - -__global overlay_event_buffer = []OverlayEvent{} -__global overlay_event_buffer_max = 1000 - -fn push_overlay_event(kind string, data string) { - if overlay_event_buffer.len >= overlay_event_buffer_max { - overlay_event_buffer.delete(0) - } - overlay_event_buffer << OverlayEvent{ - kind: kind - timestamp: time.now().format_rfc3339() - data: data - } -} - -fn on_overlay_status_callback(kind int, old_status int, new_status int) { - kind_name := match kind { - 0 { 'tor' } - 1 { 'ipfs' } - 2 { 'ethereum' } - else { 'unknown' } - } - push_overlay_event('status', '{"kind":"${kind_name}","old_status":${old_status},"new_status":${new_status},"new_name":"${status_name(new_status)}"}') -} - -fn on_overlay_error_callback(kind int, error_code int, msg_ptr &u8, msg_len usize) { - msg := if msg_ptr != unsafe { nil } && msg_len > 0 { - unsafe { tos(msg_ptr, int(msg_len)) } - } else { - 'unknown error' - } - push_overlay_event('error', '{"kind":${kind},"error_code":${error_code},"message":"${esc(msg)}"}') -} - -fn on_overlay_progress_callback(kind int, operation_id int, bytes_done u64, bytes_total u64) { - push_overlay_event('progress', '{"kind":${kind},"operation_id":${operation_id},"bytes_done":${bytes_done},"bytes_total":${bytes_total}}') -} - -fn on_overlay_circuit_callback(circuit_id int, old_status int, new_status int) { - push_overlay_event('circuit', '{"circuit_id":${circuit_id},"old_status":${old_status},"new_status":${new_status}}') -} - -fn on_overlay_pin_callback(cid_ptr &u8, cid_len usize, old_status int, new_status int) { - cid := if cid_ptr != unsafe { nil } && cid_len > 0 { - unsafe { tos(cid_ptr, int(cid_len)) } - } else { - 'unknown' - } - push_overlay_event('pin', '{"cid":"${esc(cid)}","old_status":${old_status},"new_status":${new_status}}') -} - -// Track whether FFI is available (set during init) -__global overlay_ffi_available = false -__global overlay_ffi_initialised = false - -fn init_overlay_ffi() { - // Test connectivity by reading version — if we can call it, the library is linked - ptr := C.echidna_overlay_version() - if ptr != unsafe { nil } { - ver := unsafe { cstring_to_vstring(ptr) } - if ver.len > 0 { - overlay_ffi_available = true - overlay_ffi_initialised = true - // Register bidirectional callbacks (signatures match Zig OnStatusChangeFn etc.) - C.echidna_overlay_register_on_status_change(on_overlay_status_callback) - C.echidna_overlay_register_on_error(on_overlay_error_callback) - C.echidna_overlay_register_on_progress(on_overlay_progress_callback) - C.echidna_overlay_register_on_circuit_change(on_overlay_circuit_callback) - C.echidna_overlay_register_on_pin_change(on_overlay_pin_callback) - println(' FFI: linked to libechidna_overlay.so v${ver} (live mode, ${C.echidna_overlay_callback_count()} callbacks registered)') - return - } - } - overlay_ffi_available = false - println(' FFI: overlay library not available (stub mode)') -} - -fn overlay_response(status_code int, body string) http.Response { - mut header := http.new_header(key: .content_type, value: 'application/json') - if !overlay_ffi_available { - header.add_custom('X-Overlay-Mode', 'stub') or {} - } else { - header.add_custom('X-Overlay-Mode', 'live') or {} - } - return http.new_response( - status: unsafe { http.Status(status_code) } - header: header - body: body - ) -} - -fn overlay_last_error() string { - ptr := C.echidna_overlay_last_error() - if ptr == unsafe { nil } { - return 'unknown error' - } - return unsafe { cstring_to_vstring(ptr) } -} - -fn overlay_version() string { - ptr := C.echidna_overlay_version() - return unsafe { cstring_to_vstring(ptr) } -} - -fn status_name(code int) string { - return match code { - 0 { 'disconnected' } - 1 { 'connecting' } - 2 { 'connected' } - 3 { 'error' } - else { 'unknown' } - } -} - -// --- Handler --- - -struct OverlayHandler { - port int -} - -pub fn (mut h OverlayHandler) handle(req http.Request) http.Response { - path := req.url.all_before('?') - prefix := '/api/v1/overlay' - - // Route: GET / — API discovery - if path == '/' && req.method == .get { - return handle_overlay_info() - } - - // === Unified === - if path == '${prefix}/events' && req.method == .get { - return handle_overlay_events() - } - if path == '${prefix}/health' && req.method == .get { - return handle_overlay_health() - } - if path == '${prefix}/status' && req.method == .get { - return handle_overlay_status() - } - if path == '${prefix}/version' && req.method == .get { - return handle_overlay_version() - } - - // === Tor === - if path == '${prefix}/tor/connect' && req.method == .post { - return handle_tor_connect(req) - } - if path == '${prefix}/tor/disconnect' && req.method == .post { - return handle_tor_disconnect() - } - if path == '${prefix}/tor/status' && req.method == .get { - return handle_tor_status() - } - if path == '${prefix}/tor/hidden-service' && req.method == .post { - return handle_tor_create_hidden_service(req) - } - if path.starts_with('${prefix}/tor/hidden-service/') && req.method == .delete { - onion := path.all_after('${prefix}/tor/hidden-service/') - return handle_tor_destroy_hidden_service(onion) - } - if path == '${prefix}/tor/circuits' && req.method == .get { - return handle_tor_list_circuits() - } - if path.starts_with('${prefix}/tor/circuits/') && req.method == .get { - circuit_id := path.all_after('${prefix}/tor/circuits/') - return handle_tor_get_circuit(circuit_id) - } - if path == '${prefix}/tor/resolve' && req.method == .post { - return handle_tor_resolve(req) - } - - // === IPFS === - if path == '${prefix}/ipfs/connect' && req.method == .post { - return handle_ipfs_connect(req) - } - if path == '${prefix}/ipfs/disconnect' && req.method == .post { - return handle_ipfs_disconnect() - } - if path == '${prefix}/ipfs/status' && req.method == .get { - return handle_ipfs_status() - } - if path == '${prefix}/ipfs/add' && req.method == .post { - return handle_ipfs_add(req) - } - if path.starts_with('${prefix}/ipfs/cat/') && req.method == .get { - cid := path.all_after('${prefix}/ipfs/cat/') - return handle_ipfs_cat(cid) - } - if path == '${prefix}/ipfs/pin' && req.method == .post { - return handle_ipfs_pin(req) - } - if path == '${prefix}/ipfs/unpin' && req.method == .post { - return handle_ipfs_unpin(req) - } - if path.starts_with('${prefix}/ipfs/dag/') && req.method == .get { - cid := path.all_after('${prefix}/ipfs/dag/') - return handle_ipfs_dag_get(cid) - } - - // === Ethereum === - if path == '${prefix}/eth/connect' && req.method == .post { - return handle_eth_connect(req) - } - if path == '${prefix}/eth/disconnect' && req.method == .post { - return handle_eth_disconnect() - } - if path == '${prefix}/eth/status' && req.method == .get { - return handle_eth_status() - } - if path == '${prefix}/eth/timestamp-proof' && req.method == .post { - return handle_eth_timestamp_proof(req) - } - if path == '${prefix}/eth/verify-timestamp' && req.method == .post { - return handle_eth_verify_timestamp(req) - } - - return overlay_response(404, '{"error":"Not found","service":"echidna-overlay","endpoints":["/api/v1/overlay/health","/api/v1/overlay/status","/api/v1/overlay/tor/*","/api/v1/overlay/ipfs/*","/api/v1/overlay/eth/*"]}') -} - -// --- Server --- - -pub struct OverlayServer { -pub mut: - port int -} - -pub fn new_overlay_server(port int) &OverlayServer { - return &OverlayServer{ - port: port - } -} - -pub fn (s OverlayServer) start() { - println('ECHIDNA Overlay REST API Gateway starting on port ${s.port}...') - init_overlay_ffi() - println(' === Unified ===') - println(' GET /api/v1/overlay/health — subsystem health') - println(' GET /api/v1/overlay/status — combined status') - println(' GET /api/v1/overlay/version — overlay version') - println(' === Tor ===') - println(' POST /api/v1/overlay/tor/connect — connect to Tor') - println(' POST /api/v1/overlay/tor/disconnect — disconnect from Tor') - println(' GET /api/v1/overlay/tor/status — connection status') - println(' POST /api/v1/overlay/tor/hidden-service — create hidden service') - println(' DELETE /api/v1/overlay/tor/hidden-service/:onion — destroy hidden service') - println(' GET /api/v1/overlay/tor/circuits — list circuits') - println(' GET /api/v1/overlay/tor/circuits/:id — circuit details') - println(' POST /api/v1/overlay/tor/resolve — resolve via Tor') - println(' === IPFS ===') - println(' POST /api/v1/overlay/ipfs/connect — connect to IPFS') - println(' POST /api/v1/overlay/ipfs/disconnect — disconnect from IPFS') - println(' GET /api/v1/overlay/ipfs/status — connection status') - println(' POST /api/v1/overlay/ipfs/add — add content') - println(' GET /api/v1/overlay/ipfs/cat/:cid — retrieve by CID') - println(' POST /api/v1/overlay/ipfs/pin — pin content') - println(' POST /api/v1/overlay/ipfs/unpin — unpin content') - println(' GET /api/v1/overlay/ipfs/dag/:cid — DAG node') - println(' === Ethereum (stubbed) ===') - println(' POST /api/v1/overlay/eth/connect — connect to Ethereum') - println(' POST /api/v1/overlay/eth/disconnect — disconnect') - println(' GET /api/v1/overlay/eth/status — connection status') - println(' POST /api/v1/overlay/eth/timestamp-proof — anchor proof on-chain') - println(' POST /api/v1/overlay/eth/verify-timestamp — verify timestamp') - mut server := http.Server{ - addr: ':${s.port}' - handler: &OverlayHandler{port: s.port} - } - server.listen_and_serve() -} - -fn main() { - mut s := new_overlay_server(8103) - s.start() -} - -// ============================================================================ -// Unified Handlers -// ============================================================================ - -fn handle_overlay_info() http.Response { - if overlay_ffi_available { - ver := overlay_version() - return overlay_response(200, '{"service":"echidna-overlay","version":"${ver}","description":"ECHIDNA overlay network gateway (Tor, IPFS, Ethereum)","networks":["tor","ipfs","ethereum"],"endpoints":["/api/v1/overlay/health","/api/v1/overlay/status","/api/v1/overlay/tor/*","/api/v1/overlay/ipfs/*","/api/v1/overlay/eth/*"],"documentation":"https://github.com/hyperpolymath/echidna"}') - } - return overlay_response(200, '{"service":"echidna-overlay","version":"1.0.0","description":"ECHIDNA overlay network gateway (Tor, IPFS, Ethereum)","networks":["tor","ipfs","ethereum"],"endpoints":["/api/v1/overlay/health","/api/v1/overlay/status","/api/v1/overlay/tor/*","/api/v1/overlay/ipfs/*","/api/v1/overlay/eth/*"],"documentation":"https://github.com/hyperpolymath/echidna"}') -} - -fn handle_overlay_events() http.Response { - if overlay_event_buffer.len == 0 { - return overlay_response(200, '{"events":[],"count":0,"message":"No pending overlay events"}') - } - mut items := []string{} - for ev in overlay_event_buffer { - items << '{"kind":"${esc(ev.kind)}","timestamp":"${esc(ev.timestamp)}","data":${ev.data}}' - } - count := overlay_event_buffer.len - overlay_event_buffer.clear() - return overlay_response(200, '{"events":[${items.join(",")}],"count":${count}}') -} - -fn handle_overlay_health() http.Response { - now := time.now() - if overlay_ffi_available { - ver := overlay_version() - tor := status_name(C.echidna_tor_status()) - ipfs := status_name(C.echidna_ipfs_status()) - eth := status_name(C.echidna_eth_status()) - return overlay_response(200, '{"healthy":true,"service":"echidna-overlay","version":"${ver}","tor":"${tor}","ipfs":"${ipfs}","ethereum":"${eth}","uptime_seconds":${now.unix()},"ffi_mode":"live"}') - } - return overlay_response(200, '{"healthy":true,"service":"echidna-overlay","version":"1.0.0","tor":"disconnected","ipfs":"disconnected","ethereum":"disconnected","uptime_seconds":${now.unix()},"ffi_mode":"stub"}') -} - -fn handle_overlay_status() http.Response { - if overlay_ffi_available { - tor := status_name(C.echidna_tor_status()) - ipfs := status_name(C.echidna_ipfs_status()) - eth := status_name(C.echidna_eth_status()) - tor_hs := C.echidna_tor_hidden_service_count() - ipfs_pins := C.echidna_ipfs_pin_count() - return overlay_response(200, '{"tor":{"status":"${tor}","hidden_services":${tor_hs}},"ipfs":{"status":"${ipfs}","pinned_items":${ipfs_pins}},"ethereum":{"status":"${eth}","note":"Stubbed — awaiting Aerie integration"}}') - } - return overlay_response(200, '{"tor":{"status":"disconnected","hidden_services":0},"ipfs":{"status":"disconnected","pinned_items":0},"ethereum":{"status":"disconnected","note":"Stubbed — awaiting Aerie integration"}}') -} - -fn handle_overlay_version() http.Response { - if overlay_ffi_available { - ver := overlay_version() - return overlay_response(200, '{"version":"${ver}"}') - } - return overlay_response(200, '{"version":"1.0.0"}') -} - -// ============================================================================ -// Tor Handlers -// ============================================================================ - -fn handle_tor_connect(req http.Request) http.Response { - config := if req.data.len > 0 { req.data } else { '{"control_port":9051}' } - if overlay_ffi_available { - rc := C.echidna_tor_connect(config.str, usize(config.len)) - if rc == 0 { - return overlay_response(200, '{"connected":true,"network":"tor","control_port":9051,"socks_port":9050,"message":"Connected to Tor control port"}') - } - err := overlay_last_error() - return overlay_response(500, '{"connected":false,"network":"tor","error":"${esc(err)}"}') - } - return overlay_response(200, '{"connected":true,"network":"tor","control_port":9051,"socks_port":9050,"message":"Connected to Tor control port (stub)"}') -} - -fn handle_tor_disconnect() http.Response { - if overlay_ffi_available { - C.echidna_tor_disconnect() - } - return overlay_response(200, '{"disconnected":true,"network":"tor","message":"Disconnected from Tor"}') -} - -fn handle_tor_status() http.Response { - if overlay_ffi_available { - status := status_name(C.echidna_tor_status()) - hs_count := C.echidna_tor_hidden_service_count() - return overlay_response(200, '{"network":"tor","status":"${status}","hidden_services":${hs_count}}') - } - return overlay_response(200, '{"network":"tor","status":"disconnected","hidden_services":0}') -} - -fn handle_tor_create_hidden_service(req http.Request) http.Response { - if req.data.len == 0 { - return overlay_response(400, '{"error":"Request body required with port and target_port fields"}') - } - port := json_field_int(req.data, 'port') - target_port := json_field_int(req.data, 'target_port') - if port <= 0 || target_port <= 0 { - return overlay_response(400, '{"error":"Both port and target_port must be positive integers"}') - } - if overlay_ffi_available { - mut buf := [128]u8{} - mut buf_len := usize(128) - rc := C.echidna_tor_create_hidden_service(port, target_port, &buf[0], &buf_len) - if rc == 0 && buf_len > 0 { - onion := unsafe { tos(&buf[0], int(buf_len)) } - hs_count := C.echidna_tor_hidden_service_count() - return overlay_response(201, '{"created":true,"onion_address":"${esc(onion)}","port":${port},"target_port":${target_port},"active_services":${hs_count},"created_at":"${time.now().format_rfc3339()}"}') - } - err := overlay_last_error() - return overlay_response(500, '{"created":false,"error":"${esc(err)}","port":${port},"target_port":${target_port}}') - } - return overlay_response(201, '{"created":true,"onion_address":"echidna2fproof7verify3trust8secure4formal5check6valid.onion","port":${port},"target_port":${target_port},"active_services":1,"created_at":"${time.now().format_rfc3339()}"}') -} - -fn handle_tor_destroy_hidden_service(onion string) http.Response { - if onion.len == 0 { - return overlay_response(400, '{"error":"Onion address required in URL path"}') - } - if overlay_ffi_available { - rc := C.echidna_tor_destroy_hidden_service(onion.str, usize(onion.len)) - if rc == 0 { - hs_count := C.echidna_tor_hidden_service_count() - return overlay_response(200, '{"destroyed":true,"onion_address":"${esc(onion)}","remaining_services":${hs_count}}') - } - err := overlay_last_error() - return overlay_response(500, '{"destroyed":false,"onion_address":"${esc(onion)}","error":"${esc(err)}"}') - } - return overlay_response(200, '{"destroyed":true,"onion_address":"${esc(onion)}","remaining_services":0}') -} - -fn handle_tor_list_circuits() http.Response { - if overlay_ffi_available { - mut buf := [8192]u8{} - mut buf_len := usize(8192) - rc := C.echidna_tor_list_circuits(&buf[0], &buf_len) - if rc == 0 && buf_len > 0 { - circuits := unsafe { tos(&buf[0], int(buf_len)) } - return overlay_response(200, '{"network":"tor","circuits":${circuits}}') - } - err := overlay_last_error() - return overlay_response(500, '{"network":"tor","error":"${esc(err)}"}') - } - return overlay_response(200, '{"network":"tor","circuits":[{"circuit_id":1,"status":"BUILT","hop_count":3,"purpose":"GENERAL"}]}') -} - -fn handle_tor_get_circuit(circuit_id_str string) http.Response { - circuit_id := circuit_id_str.int() - if overlay_ffi_available { - mut buf := [8192]u8{} - mut buf_len := usize(8192) - rc := C.echidna_tor_get_circuit(circuit_id, &buf[0], &buf_len) - if rc == 0 && buf_len > 0 { - circuit := unsafe { tos(&buf[0], int(buf_len)) } - return overlay_response(200, circuit) - } - err := overlay_last_error() - return overlay_response(500, '{"network":"tor","circuit_id":${circuit_id},"error":"${esc(err)}"}') - } - return overlay_response(200, '{"circuit_id":${circuit_id},"status":"BUILT","path":[{"fingerprint":"AAAA...","nickname":"Guard1","country":"DE","is_exit":false},{"fingerprint":"BBBB...","nickname":"Middle1","country":"NL","is_exit":false},{"fingerprint":"CCCC...","nickname":"Exit1","country":"SE","is_exit":true}],"purpose":"GENERAL","time_created":"${time.now().format_rfc3339()}"}') -} - -fn handle_tor_resolve(req http.Request) http.Response { - if req.data.len == 0 { - return overlay_response(400, '{"error":"Request body required with hostname field"}') - } - hostname := json_field(req.data, 'hostname') - if hostname.len == 0 { - return overlay_response(400, '{"error":"hostname field required"}') - } - if overlay_ffi_available { - mut buf := [256]u8{} - mut buf_len := usize(256) - rc := C.echidna_tor_resolve(hostname.str, usize(hostname.len), &buf[0], &buf_len) - if rc == 0 && buf_len > 0 { - resolved := unsafe { tos(&buf[0], int(buf_len)) } - return overlay_response(200, '{"hostname":"${esc(hostname)}","resolved":"${esc(resolved)}","via":"tor-socks5"}') - } - err := overlay_last_error() - return overlay_response(500, '{"hostname":"${esc(hostname)}","error":"${esc(err)}"}') - } - return overlay_response(200, '{"hostname":"${esc(hostname)}","resolved":"198.51.100.42","via":"tor-socks5"}') -} - -// ============================================================================ -// IPFS Handlers -// ============================================================================ - -fn handle_ipfs_connect(req http.Request) http.Response { - config := if req.data.len > 0 { req.data } else { '{"api_port":5001}' } - if overlay_ffi_available { - rc := C.echidna_ipfs_connect(config.str, usize(config.len)) - if rc == 0 { - return overlay_response(200, '{"connected":true,"network":"ipfs","api_port":5001,"gateway_port":8080,"message":"Connected to IPFS daemon"}') - } - err := overlay_last_error() - return overlay_response(500, '{"connected":false,"network":"ipfs","error":"${esc(err)}"}') - } - return overlay_response(200, '{"connected":true,"network":"ipfs","api_port":5001,"gateway_port":8080,"message":"Connected to IPFS daemon (stub)"}') -} - -fn handle_ipfs_disconnect() http.Response { - if overlay_ffi_available { - C.echidna_ipfs_disconnect() - } - return overlay_response(200, '{"disconnected":true,"network":"ipfs","message":"Disconnected from IPFS"}') -} - -fn handle_ipfs_status() http.Response { - if overlay_ffi_available { - status := status_name(C.echidna_ipfs_status()) - pin_count := C.echidna_ipfs_pin_count() - return overlay_response(200, '{"network":"ipfs","status":"${status}","pinned_items":${pin_count}}') - } - return overlay_response(200, '{"network":"ipfs","status":"disconnected","pinned_items":0}') -} - -fn handle_ipfs_add(req http.Request) http.Response { - if req.data.len == 0 { - return overlay_response(400, '{"error":"Request body required with data field"}') - } - data := json_field(req.data, 'data') - if data.len == 0 { - return overlay_response(400, '{"error":"data field required"}') - } - content_type := json_field_or(req.data, 'content_type', 'application/octet-stream') - if overlay_ffi_available { - mut cid_buf := [256]u8{} - mut cid_len := usize(256) - rc := C.echidna_ipfs_add(data.str, usize(data.len), &cid_buf[0], &cid_len) - if rc == 0 && cid_len > 0 { - cid := unsafe { tos(&cid_buf[0], int(cid_len)) } - return overlay_response(201, '{"added":true,"cid":"${esc(cid)}","size":${data.len},"content_type":"${esc(content_type)}","added_at":"${time.now().format_rfc3339()}"}') - } - err := overlay_last_error() - return overlay_response(500, '{"added":false,"error":"${esc(err)}"}') - } - return overlay_response(201, '{"added":true,"cid":"bafkreihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenrqpc","size":${data.len},"content_type":"${esc(content_type)}","added_at":"${time.now().format_rfc3339()}"}') -} - -fn handle_ipfs_cat(cid string) http.Response { - if cid.len == 0 { - return overlay_response(400, '{"error":"CID required in URL path"}') - } - if overlay_ffi_available { - mut buf := [65536]u8{} - mut buf_len := usize(65536) - rc := C.echidna_ipfs_cat(cid.str, usize(cid.len), &buf[0], &buf_len) - if rc == 0 && buf_len > 0 { - content := unsafe { tos(&buf[0], int(buf_len)) } - return overlay_response(200, '{"cid":"${esc(cid)}","content":"${esc(content)}","size":${buf_len}}') - } - err := overlay_last_error() - return overlay_response(500, '{"cid":"${esc(cid)}","error":"${esc(err)}"}') - } - return overlay_response(200, '{"cid":"${esc(cid)}","content":"(* ECHIDNA proof certificate — retrieved from IPFS *)","size":54}') -} - -fn handle_ipfs_pin(req http.Request) http.Response { - if req.data.len == 0 { - return overlay_response(400, '{"error":"Request body required with cid field"}') - } - cid := json_field(req.data, 'cid') - if cid.len == 0 { - return overlay_response(400, '{"error":"cid field required"}') - } - if overlay_ffi_available { - rc := C.echidna_ipfs_pin(cid.str, usize(cid.len)) - if rc == 0 { - pin_count := C.echidna_ipfs_pin_count() - return overlay_response(200, '{"pinned":true,"cid":"${esc(cid)}","total_pinned":${pin_count}}') - } - err := overlay_last_error() - return overlay_response(500, '{"pinned":false,"cid":"${esc(cid)}","error":"${esc(err)}"}') - } - return overlay_response(200, '{"pinned":true,"cid":"${esc(cid)}","total_pinned":1}') -} - -fn handle_ipfs_unpin(req http.Request) http.Response { - if req.data.len == 0 { - return overlay_response(400, '{"error":"Request body required with cid field"}') - } - cid := json_field(req.data, 'cid') - if cid.len == 0 { - return overlay_response(400, '{"error":"cid field required"}') - } - if overlay_ffi_available { - rc := C.echidna_ipfs_unpin(cid.str, usize(cid.len)) - if rc == 0 { - pin_count := C.echidna_ipfs_pin_count() - return overlay_response(200, '{"unpinned":true,"cid":"${esc(cid)}","total_pinned":${pin_count}}') - } - err := overlay_last_error() - return overlay_response(500, '{"unpinned":false,"cid":"${esc(cid)}","error":"${esc(err)}"}') - } - return overlay_response(200, '{"unpinned":true,"cid":"${esc(cid)}","total_pinned":0}') -} - -fn handle_ipfs_dag_get(cid string) http.Response { - if cid.len == 0 { - return overlay_response(400, '{"error":"CID required in URL path"}') - } - if overlay_ffi_available { - mut buf := [8192]u8{} - mut buf_len := usize(8192) - rc := C.echidna_ipfs_dag_get(cid.str, usize(cid.len), &buf[0], &buf_len) - if rc == 0 && buf_len > 0 { - dag := unsafe { tos(&buf[0], int(buf_len)) } - return overlay_response(200, dag) - } - err := overlay_last_error() - return overlay_response(500, '{"cid":"${esc(cid)}","error":"${esc(err)}"}') - } - return overlay_response(200, '{"cid":"${esc(cid)}","links":0,"size":256,"data_size":128}') -} - -// ============================================================================ -// Ethereum Handlers (stubbed — Aerie future use) -// ============================================================================ - -fn handle_eth_connect(req http.Request) http.Response { - config := if req.data.len > 0 { req.data } else { '{"rpc_url":"http://localhost:8545","chain_id":1337}' } - if overlay_ffi_available { - rc := C.echidna_eth_connect(config.str, usize(config.len)) - if rc == 0 { - return overlay_response(200, '{"connected":true,"network":"ethereum","rpc_url":"http://localhost:8545","message":"Connected to Ethereum RPC (stubbed — Aerie future use)"}') - } - err := overlay_last_error() - return overlay_response(500, '{"connected":false,"network":"ethereum","error":"${esc(err)}"}') - } - return overlay_response(200, '{"connected":true,"network":"ethereum","rpc_url":"http://localhost:8545","message":"Connected to Ethereum RPC (stub mode — Aerie future use)"}') -} - -fn handle_eth_disconnect() http.Response { - if overlay_ffi_available { - C.echidna_eth_disconnect() - } - return overlay_response(200, '{"disconnected":true,"network":"ethereum","message":"Disconnected from Ethereum"}') -} - -fn handle_eth_status() http.Response { - if overlay_ffi_available { - status := status_name(C.echidna_eth_status()) - return overlay_response(200, '{"network":"ethereum","status":"${status}","note":"Stubbed — awaiting Aerie integration"}') - } - return overlay_response(200, '{"network":"ethereum","status":"disconnected","note":"Stubbed — awaiting Aerie integration"}') -} - -fn handle_eth_timestamp_proof(req http.Request) http.Response { - if req.data.len == 0 { - return overlay_response(400, '{"error":"Request body required with proof_hash field"}') - } - proof_hash := json_field(req.data, 'proof_hash') - if proof_hash.len == 0 { - return overlay_response(400, '{"error":"proof_hash field required (SHA3-256 hex string)"}') - } - if overlay_ffi_available { - mut buf := [2048]u8{} - mut buf_len := usize(2048) - rc := C.echidna_eth_timestamp_proof(proof_hash.str, usize(proof_hash.len), &buf[0], &buf_len) - if rc == 0 && buf_len > 0 { - result := unsafe { tos(&buf[0], int(buf_len)) } - return overlay_response(200, result) - } - err := overlay_last_error() - return overlay_response(500, '{"error":"${esc(err)}","proof_hash":"${esc(proof_hash)}"}') - } - return overlay_response(200, '{"tx_hash":"0xdeadbeef0123456789abcdef0123456789abcdef0123456789abcdef01234567","block_number":19000000,"timestamp":${time.now().unix()},"proof_hash":"${esc(proof_hash)}","chain_id":1,"status":"STUBBED","note":"Ethereum timestamping not yet implemented — awaiting Aerie integration"}') -} - -fn handle_eth_verify_timestamp(req http.Request) http.Response { - if req.data.len == 0 { - return overlay_response(400, '{"error":"Request body required with tx_hash field"}') - } - tx_hash := json_field(req.data, 'tx_hash') - if tx_hash.len == 0 { - return overlay_response(400, '{"error":"tx_hash field required (0x-prefixed hex string)"}') - } - if overlay_ffi_available { - mut buf := [2048]u8{} - mut buf_len := usize(2048) - rc := C.echidna_eth_verify_timestamp(tx_hash.str, usize(tx_hash.len), &buf[0], &buf_len) - if rc == 0 && buf_len > 0 { - result := unsafe { tos(&buf[0], int(buf_len)) } - return overlay_response(200, result) - } - err := overlay_last_error() - return overlay_response(500, '{"error":"${esc(err)}","tx_hash":"${esc(tx_hash)}"}') - } - return overlay_response(200, '{"verified":true,"tx_hash":"${esc(tx_hash)}","proof_hash":"sha3-256:stub","block_number":19000000,"timestamp":${time.now().unix()},"confirmations":100,"status":"STUBBED"}') -} - -// --- Helpers --- - -fn esc(s string) string { - return s.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n').replace('\t', '\\t') -} - -fn json_field(data string, key string) string { - needle := '"${key}":' - idx := data.index(needle) or { return '' } - tail := data[idx + needle.len..].trim_space() - if tail.len == 0 || tail[0] != `"` { - return '' - } - end := tail[1..].index('"') or { return '' } - return tail[1..end + 1] -} - -fn json_field_or(data string, key string, default_val string) string { - val := json_field(data, key) - if val.len == 0 { - return default_val - } - return val -} - -fn json_field_int(data string, key string) int { - needle := '"${key}":' - idx := data.index(needle) or { return 0 } - tail := data[idx + needle.len..].trim_space() - if tail.len == 0 { - return 0 - } - mut end := 0 - for i, c in tail { - if c >= `0` && c <= `9` { - end = i + 1 - } else if end > 0 { - break - } - } - if end == 0 { - return 0 - } - return tail[..end].int() -} diff --git a/src/interfaces/v-adapter/overlay_graphql.v b/src/interfaces/v-adapter/overlay_graphql.v deleted file mode 100644 index a42f66ca..00000000 --- a/src/interfaces/v-adapter/overlay_graphql.v +++ /dev/null @@ -1,630 +0,0 @@ -// SPDX-License-Identifier: PMPL-1.0-or-later -// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -// -// ECHIDNA Overlay Network GraphQL API Gateway -// -// Exposes Tor, IPFS, and Ethereum overlay network operations via GraphQL on port 8105: -// -// Queries: -// overlayHealth { healthy tor ipfs ethereum version ffiMode } -// overlayStatus { tor { status hiddenServices } ipfs { status pinnedItems } ethereum { status } } -// overlayVersion { version } -// torStatus { network status hiddenServices } -// torCircuits { circuitId status hopCount purpose } -// torCircuit(id: Int) { circuitId status path { fingerprint nickname country isExit } } -// ipfsStatus { network status pinnedItems } -// ethStatus { network status note } -// -// Mutations: -// torConnect(config: JSON) { connected network } -// torDisconnect { disconnected } -// torCreateHiddenService(port: Int, targetPort: Int) { created onionAddress activeServices } -// torDestroyHiddenService(onionAddress: String) { destroyed remainingServices } -// torResolve(hostname: String) { hostname resolved via } -// ipfsConnect(config: JSON) { connected network } -// ipfsDisconnect { disconnected } -// ipfsAdd(data: String, contentType: String) { added cid size } -// ipfsCat(cid: String) { cid content size } -// ipfsPin(cid: String) { pinned totalPinned } -// ipfsUnpin(cid: String) { unpinned totalPinned } -// ipfsDagGet(cid: String) { cid links size } -// ethConnect(config: JSON) { connected note } -// ethDisconnect { disconnected } -// ethTimestampProof(proofHash: String) { txHash blockNumber timestamp status } -// ethVerifyTimestamp(txHash: String) { verified proofHash blockNumber timestamp status } -// -// GET /graphql — GraphiQL playground -// -// Links against libechidna_overlay.so (Zig FFI layer) when available. -// Falls back to stub responses with X-Overlay-Mode: stub header when FFI is absent. - -module main - -import net.http -import time - -// --- Zig FFI extern declarations (libechidna_overlay.so) --- - -#flag -L @VMODROOT/../../ffi/zig/zig-out/lib -#flag -lechidna_overlay - -// Tor -fn C.echidna_tor_connect(config_ptr &u8, config_len usize) int -fn C.echidna_tor_disconnect() -fn C.echidna_tor_create_hidden_service(port int, target_port int, out_ptr &u8, out_len &usize) int -fn C.echidna_tor_destroy_hidden_service(onion_ptr &u8, onion_len usize) int -fn C.echidna_tor_get_circuit(circuit_id int, out_ptr &u8, out_len &usize) int -fn C.echidna_tor_list_circuits(out_ptr &u8, out_len &usize) int -fn C.echidna_tor_resolve(hostname_ptr &u8, hostname_len usize, out_ptr &u8, out_len &usize) int -fn C.echidna_tor_status() int -fn C.echidna_tor_hidden_service_count() int - -// IPFS -fn C.echidna_ipfs_connect(config_ptr &u8, config_len usize) int -fn C.echidna_ipfs_disconnect() -fn C.echidna_ipfs_add(data_ptr &u8, data_len usize, out_cid_ptr &u8, out_cid_len &usize) int -fn C.echidna_ipfs_cat(cid_ptr &u8, cid_len usize, out_ptr &u8, out_len &usize) int -fn C.echidna_ipfs_pin(cid_ptr &u8, cid_len usize) int -fn C.echidna_ipfs_unpin(cid_ptr &u8, cid_len usize) int -fn C.echidna_ipfs_dag_get(cid_ptr &u8, cid_len usize, out_ptr &u8, out_len &usize) int -fn C.echidna_ipfs_status() int -fn C.echidna_ipfs_pin_count() int - -// Ethereum (stubbed) -fn C.echidna_eth_connect(config_ptr &u8, config_len usize) int -fn C.echidna_eth_disconnect() -fn C.echidna_eth_timestamp_proof(proof_hash_ptr &u8, proof_hash_len usize, out_ptr &u8, out_len &usize) int -fn C.echidna_eth_verify_timestamp(tx_hash_ptr &u8, tx_hash_len usize, out_ptr &u8, out_len &usize) int -fn C.echidna_eth_status() int - -// Unified -fn C.echidna_overlay_version() &u8 -fn C.echidna_overlay_kind_name(kind int) &u8 -fn C.echidna_overlay_last_error() &u8 - -__global overlay_ffi_available = false -__global overlay_ffi_initialised = false - -fn init_overlay_ffi() { - ptr := C.echidna_overlay_version() - if ptr != unsafe { nil } { - ver := unsafe { cstring_to_vstring(ptr) } - if ver.len > 0 { - overlay_ffi_available = true - overlay_ffi_initialised = true - println(' FFI: linked to libechidna_overlay.so v${ver} (live mode)') - return - } - } - overlay_ffi_available = false - println(' FFI: overlay library not available (stub mode)') -} - -fn overlay_last_error() string { - ptr := C.echidna_overlay_last_error() - if ptr == unsafe { nil } { - return 'unknown error' - } - return unsafe { cstring_to_vstring(ptr) } -} - -fn overlay_version() string { - ptr := C.echidna_overlay_version() - return unsafe { cstring_to_vstring(ptr) } -} - -fn status_name(code int) string { - return match code { - 0 { 'disconnected' } - 1 { 'connecting' } - 2 { 'connected' } - 3 { 'error' } - else { 'unknown' } - } -} - -fn gql_response(status_code int, body string) http.Response { - mut header := http.new_header(key: .content_type, value: 'application/json') - if overlay_ffi_available { - header.add_custom('X-Overlay-Mode', 'live') or {} - } else { - header.add_custom('X-Overlay-Mode', 'stub') or {} - } - return http.new_response( - status: unsafe { http.Status(status_code) } - header: header - body: body - ) -} - -// --- Handler --- - -struct OverlayGraphQLHandler { - port int -} - -pub fn (mut h OverlayGraphQLHandler) handle(req http.Request) http.Response { - path := req.url.all_before('?') - - // GET /graphql — serve GraphiQL playground - if path == '/graphql' && req.method == .get { - return serve_graphiql() - } - // POST /graphql — resolve query/mutation - if path == '/graphql' && req.method == .post { - query := json_field(req.data, 'query') - if query.len == 0 { - return gql_response(400, '{"errors":[{"message":"query field required in POST body"}]}') - } - return resolve(query, req.data) - } - // GET / — redirect to GraphiQL - if path == '/' && req.method == .get { - return gql_response(200, '{"service":"echidna-overlay-graphql","playground":"/graphql"}') - } - - return gql_response(404, '{"errors":[{"message":"Use POST /graphql for queries, GET /graphql for GraphiQL"}]}') -} - -// --- Server --- - -pub struct Server { -pub mut: - port int -} - -pub fn new_server(port int) &Server { - return &Server{port: port} -} - -pub fn (s Server) start() { - println('ECHIDNA Overlay GraphQL API Gateway starting on port ${s.port}...') - init_overlay_ffi() - println(' POST /graphql — query and mutation endpoint') - println(' GET /graphql — GraphiQL interactive playground') - println(' Queries: overlayHealth, overlayStatus, overlayVersion, torStatus, torCircuits, ipfsStatus, ethStatus') - println(' Mutations: tor{Connect,Disconnect,CreateHiddenService,...}, ipfs{Connect,Add,Cat,Pin,...}, eth{Connect,TimestampProof,...}') - mut server := http.Server{ - addr: ':${s.port}' - handler: &OverlayGraphQLHandler{port: s.port} - } - server.listen_and_serve() -} - -fn main() { - mut s := new_server(8105) - s.start() -} - -// ============================================================================ -// Resolver Dispatch -// ============================================================================ - -fn resolve(query string, data string) http.Response { - // Mutations (check first — they modify state) - if query.contains('torConnect') { return resolve_tor_connect(data) } - if query.contains('torDisconnect') { return resolve_tor_disconnect() } - if query.contains('torCreateHiddenService') { return resolve_tor_create_hs(query) } - if query.contains('torDestroyHiddenService') { return resolve_tor_destroy_hs(query) } - if query.contains('torResolve') { return resolve_tor_resolve(query) } - if query.contains('ipfsConnect') { return resolve_ipfs_connect(data) } - if query.contains('ipfsDisconnect') { return resolve_ipfs_disconnect() } - if query.contains('ipfsAdd') { return resolve_ipfs_add(query) } - if query.contains('ipfsCat') { return resolve_ipfs_cat(query) } - if query.contains('ipfsPin(') { return resolve_ipfs_pin(query) } - if query.contains('ipfsUnpin') { return resolve_ipfs_unpin(query) } - if query.contains('ipfsDagGet') { return resolve_ipfs_dag_get(query) } - if query.contains('ethConnect') { return resolve_eth_connect(data) } - if query.contains('ethDisconnect') { return resolve_eth_disconnect() } - if query.contains('ethTimestampProof') { return resolve_eth_timestamp(query) } - if query.contains('ethVerifyTimestamp') { return resolve_eth_verify(query) } - - // Queries - if query.contains('overlayHealth') { return resolve_health() } - if query.contains('overlayStatus') { return resolve_status() } - if query.contains('overlayVersion') { return resolve_version() } - if query.contains('torCircuit(') { return resolve_tor_circuit(query) } - if query.contains('torCircuits') { return resolve_tor_circuits() } - if query.contains('torStatus') { return resolve_tor_status() } - if query.contains('ipfsStatus') { return resolve_ipfs_status() } - if query.contains('ethStatus') { return resolve_eth_status() } - if query.contains('__schema') { return resolve_schema() } - - return gql_response(200, '{"errors":[{"message":"Unknown query/mutation. Use overlayHealth, overlayStatus, torConnect, ipfsAdd, ethTimestampProof, etc."}]}') -} - -// ============================================================================ -// Query Resolvers -// ============================================================================ - -fn resolve_health() http.Response { - now := time.now() - if overlay_ffi_available { - ver := overlay_version() - tor := status_name(C.echidna_tor_status()) - ipfs := status_name(C.echidna_ipfs_status()) - eth := status_name(C.echidna_eth_status()) - return gql_response(200, '{"data":{"overlayHealth":{"healthy":true,"service":"echidna-overlay-graphql","version":"${ver}","tor":"${tor}","ipfs":"${ipfs}","ethereum":"${eth}","uptimeSeconds":${now.unix()},"ffiMode":"live"}}}') - } - return gql_response(200, '{"data":{"overlayHealth":{"healthy":true,"service":"echidna-overlay-graphql","version":"1.0.0","tor":"disconnected","ipfs":"disconnected","ethereum":"disconnected","uptimeSeconds":${now.unix()},"ffiMode":"stub"}}}') -} - -fn resolve_status() http.Response { - if overlay_ffi_available { - tor := status_name(C.echidna_tor_status()) - ipfs := status_name(C.echidna_ipfs_status()) - eth := status_name(C.echidna_eth_status()) - return gql_response(200, '{"data":{"overlayStatus":{"tor":{"status":"${tor}","hiddenServices":${C.echidna_tor_hidden_service_count()}},"ipfs":{"status":"${ipfs}","pinnedItems":${C.echidna_ipfs_pin_count()}},"ethereum":{"status":"${eth}","note":"Stubbed"}}}}') - } - return gql_response(200, '{"data":{"overlayStatus":{"tor":{"status":"disconnected","hiddenServices":0},"ipfs":{"status":"disconnected","pinnedItems":0},"ethereum":{"status":"disconnected","note":"Stubbed"}}}}') -} - -fn resolve_version() http.Response { - if overlay_ffi_available { - return gql_response(200, '{"data":{"overlayVersion":{"version":"${overlay_version()}"}}}') - } - return gql_response(200, '{"data":{"overlayVersion":{"version":"1.0.0"}}}') -} - -fn resolve_tor_status() http.Response { - if overlay_ffi_available { - return gql_response(200, '{"data":{"torStatus":{"network":"tor","status":"${status_name(C.echidna_tor_status())}","hiddenServices":${C.echidna_tor_hidden_service_count()}}}}') - } - return gql_response(200, '{"data":{"torStatus":{"network":"tor","status":"disconnected","hiddenServices":0}}}') -} - -fn resolve_tor_circuits() http.Response { - if overlay_ffi_available { - mut buf := [8192]u8{} - mut buf_len := usize(8192) - rc := C.echidna_tor_list_circuits(&buf[0], &buf_len) - if rc == 0 && buf_len > 0 { - return gql_response(200, '{"data":{"torCircuits":${unsafe { tos(&buf[0], int(buf_len)) }}}}') - } - } - return gql_response(200, '{"data":{"torCircuits":[{"circuitId":1,"status":"BUILT","hopCount":3,"purpose":"GENERAL"}]}}') -} - -fn resolve_tor_circuit(query string) http.Response { - id := extract_int_arg(query, 'id') - if overlay_ffi_available { - mut buf := [8192]u8{} - mut buf_len := usize(8192) - rc := C.echidna_tor_get_circuit(id, &buf[0], &buf_len) - if rc == 0 && buf_len > 0 { - return gql_response(200, '{"data":{"torCircuit":${unsafe { tos(&buf[0], int(buf_len)) }}}}') - } - } - return gql_response(200, '{"data":{"torCircuit":{"circuitId":${id},"status":"BUILT","path":[{"fingerprint":"AAAA...","nickname":"Guard1","country":"DE","isExit":false}],"purpose":"GENERAL"}}}') -} - -fn resolve_ipfs_status() http.Response { - if overlay_ffi_available { - return gql_response(200, '{"data":{"ipfsStatus":{"network":"ipfs","status":"${status_name(C.echidna_ipfs_status())}","pinnedItems":${C.echidna_ipfs_pin_count()}}}}') - } - return gql_response(200, '{"data":{"ipfsStatus":{"network":"ipfs","status":"disconnected","pinnedItems":0}}}') -} - -fn resolve_eth_status() http.Response { - if overlay_ffi_available { - return gql_response(200, '{"data":{"ethStatus":{"network":"ethereum","status":"${status_name(C.echidna_eth_status())}","note":"Stubbed — Aerie future use"}}}') - } - return gql_response(200, '{"data":{"ethStatus":{"network":"ethereum","status":"disconnected","note":"Stubbed — Aerie future use"}}}') -} - -fn resolve_schema() http.Response { - return gql_response(200, '{"data":{"__schema":{"queryType":{"name":"Query"},"mutationType":{"name":"Mutation"},"types":[{"name":"Query","fields":["overlayHealth","overlayStatus","overlayVersion","torStatus","torCircuits","torCircuit","ipfsStatus","ethStatus"]},{"name":"Mutation","fields":["torConnect","torDisconnect","torCreateHiddenService","torDestroyHiddenService","torResolve","ipfsConnect","ipfsDisconnect","ipfsAdd","ipfsCat","ipfsPin","ipfsUnpin","ipfsDagGet","ethConnect","ethDisconnect","ethTimestampProof","ethVerifyTimestamp"]}]}}}') -} - -// ============================================================================ -// Mutation Resolvers — Tor -// ============================================================================ - -fn resolve_tor_connect(data string) http.Response { - config := if data.len > 0 { data } else { '{"control_port":9051}' } - if overlay_ffi_available { - rc := C.echidna_tor_connect(config.str, usize(config.len)) - if rc == 0 { - return gql_response(200, '{"data":{"torConnect":{"connected":true,"network":"tor","controlPort":9051,"socksPort":9050}}}') - } - return gql_response(200, '{"errors":[{"message":"Tor connect failed: ${esc(overlay_last_error())}"}]}') - } - return gql_response(200, '{"data":{"torConnect":{"connected":true,"network":"tor","controlPort":9051,"socksPort":9050}}}') -} - -fn resolve_tor_disconnect() http.Response { - if overlay_ffi_available { - C.echidna_tor_disconnect() - } - return gql_response(200, '{"data":{"torDisconnect":{"disconnected":true,"network":"tor"}}}') -} - -fn resolve_tor_create_hs(query string) http.Response { - port := extract_int_arg(query, 'port') - target_port := extract_int_arg(query, 'targetPort') - if port <= 0 || target_port <= 0 { - return gql_response(200, '{"errors":[{"message":"port and targetPort required for torCreateHiddenService"}]}') - } - if overlay_ffi_available { - mut buf := [128]u8{} - mut buf_len := usize(128) - rc := C.echidna_tor_create_hidden_service(port, target_port, &buf[0], &buf_len) - if rc == 0 && buf_len > 0 { - onion := unsafe { tos(&buf[0], int(buf_len)) } - return gql_response(200, '{"data":{"torCreateHiddenService":{"created":true,"onionAddress":"${esc(onion)}","port":${port},"targetPort":${target_port},"activeServices":${C.echidna_tor_hidden_service_count()}}}}') - } - return gql_response(200, '{"errors":[{"message":"${esc(overlay_last_error())}"}]}') - } - return gql_response(200, '{"data":{"torCreateHiddenService":{"created":true,"onionAddress":"echidna2fproof7verify3trust8secure4formal5check6valid.onion","port":${port},"targetPort":${target_port},"activeServices":1}}}') -} - -fn resolve_tor_destroy_hs(query string) http.Response { - onion := extract_arg(query, 'onionAddress') - if onion.len == 0 { - return gql_response(200, '{"errors":[{"message":"onionAddress required"}]}') - } - if overlay_ffi_available { - rc := C.echidna_tor_destroy_hidden_service(onion.str, usize(onion.len)) - if rc == 0 { - return gql_response(200, '{"data":{"torDestroyHiddenService":{"destroyed":true,"onionAddress":"${esc(onion)}","remainingServices":${C.echidna_tor_hidden_service_count()}}}}') - } - return gql_response(200, '{"errors":[{"message":"${esc(overlay_last_error())}"}]}') - } - return gql_response(200, '{"data":{"torDestroyHiddenService":{"destroyed":true,"onionAddress":"${esc(onion)}","remainingServices":0}}}') -} - -fn resolve_tor_resolve(query string) http.Response { - hostname := extract_arg(query, 'hostname') - if hostname.len == 0 { - return gql_response(200, '{"errors":[{"message":"hostname required"}]}') - } - if overlay_ffi_available { - mut buf := [256]u8{} - mut buf_len := usize(256) - rc := C.echidna_tor_resolve(hostname.str, usize(hostname.len), &buf[0], &buf_len) - if rc == 0 && buf_len > 0 { - return gql_response(200, '{"data":{"torResolve":{"hostname":"${esc(hostname)}","resolved":"${unsafe { tos(&buf[0], int(buf_len)) }}","via":"tor-socks5"}}}') - } - return gql_response(200, '{"errors":[{"message":"${esc(overlay_last_error())}"}]}') - } - return gql_response(200, '{"data":{"torResolve":{"hostname":"${esc(hostname)}","resolved":"198.51.100.42","via":"tor-socks5"}}}') -} - -// ============================================================================ -// Mutation Resolvers — IPFS -// ============================================================================ - -fn resolve_ipfs_connect(data string) http.Response { - config := if data.len > 0 { data } else { '{"api_port":5001}' } - if overlay_ffi_available { - rc := C.echidna_ipfs_connect(config.str, usize(config.len)) - if rc == 0 { - return gql_response(200, '{"data":{"ipfsConnect":{"connected":true,"network":"ipfs","apiPort":5001,"gatewayPort":8080}}}') - } - return gql_response(200, '{"errors":[{"message":"${esc(overlay_last_error())}"}]}') - } - return gql_response(200, '{"data":{"ipfsConnect":{"connected":true,"network":"ipfs","apiPort":5001,"gatewayPort":8080}}}') -} - -fn resolve_ipfs_disconnect() http.Response { - if overlay_ffi_available { - C.echidna_ipfs_disconnect() - } - return gql_response(200, '{"data":{"ipfsDisconnect":{"disconnected":true,"network":"ipfs"}}}') -} - -fn resolve_ipfs_add(query string) http.Response { - data := extract_arg(query, 'data') - if data.len == 0 { - return gql_response(200, '{"errors":[{"message":"data required for ipfsAdd"}]}') - } - if overlay_ffi_available { - mut cid_buf := [256]u8{} - mut cid_len := usize(256) - rc := C.echidna_ipfs_add(data.str, usize(data.len), &cid_buf[0], &cid_len) - if rc == 0 && cid_len > 0 { - cid := unsafe { tos(&cid_buf[0], int(cid_len)) } - return gql_response(200, '{"data":{"ipfsAdd":{"added":true,"cid":"${esc(cid)}","size":${data.len}}}}') - } - return gql_response(200, '{"errors":[{"message":"${esc(overlay_last_error())}"}]}') - } - return gql_response(200, '{"data":{"ipfsAdd":{"added":true,"cid":"bafkreihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenrqpc","size":${data.len}}}}') -} - -fn resolve_ipfs_cat(query string) http.Response { - cid := extract_arg(query, 'cid') - if cid.len == 0 { - return gql_response(200, '{"errors":[{"message":"cid required"}]}') - } - if overlay_ffi_available { - mut buf := [65536]u8{} - mut buf_len := usize(65536) - rc := C.echidna_ipfs_cat(cid.str, usize(cid.len), &buf[0], &buf_len) - if rc == 0 && buf_len > 0 { - content := unsafe { tos(&buf[0], int(buf_len)) } - return gql_response(200, '{"data":{"ipfsCat":{"cid":"${esc(cid)}","content":"${esc(content)}","size":${buf_len}}}}') - } - return gql_response(200, '{"errors":[{"message":"${esc(overlay_last_error())}"}]}') - } - return gql_response(200, '{"data":{"ipfsCat":{"cid":"${esc(cid)}","content":"(* ECHIDNA proof certificate *)","size":31}}}') -} - -fn resolve_ipfs_pin(query string) http.Response { - cid := extract_arg(query, 'cid') - if cid.len == 0 { - return gql_response(200, '{"errors":[{"message":"cid required"}]}') - } - if overlay_ffi_available { - rc := C.echidna_ipfs_pin(cid.str, usize(cid.len)) - if rc == 0 { - return gql_response(200, '{"data":{"ipfsPin":{"pinned":true,"cid":"${esc(cid)}","totalPinned":${C.echidna_ipfs_pin_count()}}}}') - } - return gql_response(200, '{"errors":[{"message":"${esc(overlay_last_error())}"}]}') - } - return gql_response(200, '{"data":{"ipfsPin":{"pinned":true,"cid":"${esc(cid)}","totalPinned":1}}}') -} - -fn resolve_ipfs_unpin(query string) http.Response { - cid := extract_arg(query, 'cid') - if cid.len == 0 { - return gql_response(200, '{"errors":[{"message":"cid required"}]}') - } - if overlay_ffi_available { - rc := C.echidna_ipfs_unpin(cid.str, usize(cid.len)) - if rc == 0 { - return gql_response(200, '{"data":{"ipfsUnpin":{"unpinned":true,"cid":"${esc(cid)}","totalPinned":${C.echidna_ipfs_pin_count()}}}}') - } - return gql_response(200, '{"errors":[{"message":"${esc(overlay_last_error())}"}]}') - } - return gql_response(200, '{"data":{"ipfsUnpin":{"unpinned":true,"cid":"${esc(cid)}","totalPinned":0}}}') -} - -fn resolve_ipfs_dag_get(query string) http.Response { - cid := extract_arg(query, 'cid') - if cid.len == 0 { - return gql_response(200, '{"errors":[{"message":"cid required"}]}') - } - if overlay_ffi_available { - mut buf := [8192]u8{} - mut buf_len := usize(8192) - rc := C.echidna_ipfs_dag_get(cid.str, usize(cid.len), &buf[0], &buf_len) - if rc == 0 && buf_len > 0 { - return gql_response(200, '{"data":{"ipfsDagGet":${unsafe { tos(&buf[0], int(buf_len)) }}}}') - } - return gql_response(200, '{"errors":[{"message":"${esc(overlay_last_error())}"}]}') - } - return gql_response(200, '{"data":{"ipfsDagGet":{"cid":"${esc(cid)}","links":0,"size":256,"dataSize":128}}}') -} - -// ============================================================================ -// Mutation Resolvers — Ethereum (stubbed) -// ============================================================================ - -fn resolve_eth_connect(data string) http.Response { - config := if data.len > 0 { data } else { '{"rpc_url":"http://localhost:8545"}' } - if overlay_ffi_available { - rc := C.echidna_eth_connect(config.str, usize(config.len)) - if rc == 0 { - return gql_response(200, '{"data":{"ethConnect":{"connected":true,"network":"ethereum","note":"Stubbed — Aerie future use"}}}') - } - return gql_response(200, '{"errors":[{"message":"${esc(overlay_last_error())}"}]}') - } - return gql_response(200, '{"data":{"ethConnect":{"connected":true,"network":"ethereum","note":"Stubbed — Aerie future use"}}}') -} - -fn resolve_eth_disconnect() http.Response { - if overlay_ffi_available { - C.echidna_eth_disconnect() - } - return gql_response(200, '{"data":{"ethDisconnect":{"disconnected":true,"network":"ethereum"}}}') -} - -fn resolve_eth_timestamp(query string) http.Response { - proof_hash := extract_arg(query, 'proofHash') - if proof_hash.len == 0 { - return gql_response(200, '{"errors":[{"message":"proofHash required"}]}') - } - if overlay_ffi_available { - mut buf := [2048]u8{} - mut buf_len := usize(2048) - rc := C.echidna_eth_timestamp_proof(proof_hash.str, usize(proof_hash.len), &buf[0], &buf_len) - if rc == 0 && buf_len > 0 { - return gql_response(200, '{"data":{"ethTimestampProof":${unsafe { tos(&buf[0], int(buf_len)) }}}}') - } - return gql_response(200, '{"errors":[{"message":"${esc(overlay_last_error())}"}]}') - } - return gql_response(200, '{"data":{"ethTimestampProof":{"txHash":"0xdeadbeef...","blockNumber":19000000,"timestamp":${time.now().unix()},"proofHash":"${esc(proof_hash)}","status":"STUBBED"}}}') -} - -fn resolve_eth_verify(query string) http.Response { - tx_hash := extract_arg(query, 'txHash') - if tx_hash.len == 0 { - return gql_response(200, '{"errors":[{"message":"txHash required"}]}') - } - if overlay_ffi_available { - mut buf := [2048]u8{} - mut buf_len := usize(2048) - rc := C.echidna_eth_verify_timestamp(tx_hash.str, usize(tx_hash.len), &buf[0], &buf_len) - if rc == 0 && buf_len > 0 { - return gql_response(200, '{"data":{"ethVerifyTimestamp":${unsafe { tos(&buf[0], int(buf_len)) }}}}') - } - return gql_response(200, '{"errors":[{"message":"${esc(overlay_last_error())}"}]}') - } - return gql_response(200, '{"data":{"ethVerifyTimestamp":{"verified":true,"txHash":"${esc(tx_hash)}","proofHash":"sha3-256:stub","blockNumber":19000000,"timestamp":${time.now().unix()},"status":"STUBBED"}}}') -} - -// ============================================================================ -// GraphiQL Playground -// ============================================================================ - -fn serve_graphiql() http.Response { - html := 'ECHIDNA Overlay GraphQL - - - - -
-' - mut header := http.new_header(key: .content_type, value: 'text/html; charset=utf-8') - return http.new_response( - status: .ok - header: header - body: html - ) -} - -// --- Helpers --- - -fn esc(s string) string { - return s.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n').replace('\t', '\\t') -} - -fn extract_arg(query string, key string) string { - // Match GraphQL argument syntax: key: "value" or key: \"value\" - patterns := ['${key}: "', '${key}:\\"', '${key}: \\"'] - for pattern in patterns { - idx := query.index(pattern) or { continue } - start := idx + pattern.len - rest := query[start..] - end := rest.index_any('"\\"') or { continue } - if end > 0 { - return rest[..end] - } - } - return '' -} - -fn extract_int_arg(query string, key string) int { - patterns := ['${key}: ', '${key}:'] - for pattern in patterns { - idx := query.index(pattern) or { continue } - start := idx + pattern.len - rest := query[start..].trim_space() - mut end := 0 - for i, c in rest { - if c >= `0` && c <= `9` { - end = i + 1 - } else if end > 0 { - break - } - } - if end > 0 { - return rest[..end].int() - } - } - return 0 -} - -fn json_field(data string, key string) string { - needle := '"${key}":' - idx := data.index(needle) or { return '' } - tail := data[idx + needle.len..].trim_space() - if tail.len == 0 || tail[0] != `"` { - return '' - } - end := tail[1..].index('"') or { return '' } - return tail[1..end + 1] -} diff --git a/src/interfaces/v-adapter/overlay_grpc.v b/src/interfaces/v-adapter/overlay_grpc.v deleted file mode 100644 index 2788c551..00000000 --- a/src/interfaces/v-adapter/overlay_grpc.v +++ /dev/null @@ -1,584 +0,0 @@ -// SPDX-License-Identifier: PMPL-1.0-or-later -// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -// -// ECHIDNA Overlay Network gRPC-Web API Gateway -// -// Exposes Tor, IPFS, and Ethereum overlay network operations via gRPC-style -// RPC (JSON-over-HTTP transport, gRPC-Web compatible) on port 8104: -// -// Unified: -// POST /echidna.OverlayService/Health — overlay subsystem health -// POST /echidna.OverlayService/Status — combined status of all networks -// POST /echidna.OverlayService/Version — overlay module version -// -// Tor: -// POST /echidna.OverlayService/TorConnect — connect to Tor -// POST /echidna.OverlayService/TorDisconnect — disconnect from Tor -// POST /echidna.OverlayService/TorStatus — Tor connection status -// POST /echidna.OverlayService/TorCreateHiddenService — create hidden service -// POST /echidna.OverlayService/TorDestroyHiddenService — destroy hidden service -// POST /echidna.OverlayService/TorListCircuits — list active circuits -// POST /echidna.OverlayService/TorGetCircuit — get circuit details -// POST /echidna.OverlayService/TorResolve — resolve via Tor -// -// IPFS: -// POST /echidna.OverlayService/IpfsConnect — connect to IPFS -// POST /echidna.OverlayService/IpfsDisconnect — disconnect from IPFS -// POST /echidna.OverlayService/IpfsStatus — IPFS connection status -// POST /echidna.OverlayService/IpfsAdd — add content, returns CID -// POST /echidna.OverlayService/IpfsCat — retrieve content by CID -// POST /echidna.OverlayService/IpfsPin — pin content by CID -// POST /echidna.OverlayService/IpfsUnpin — unpin content by CID -// POST /echidna.OverlayService/IpfsDagGet — get DAG node -// -// Ethereum (stubbed): -// POST /echidna.OverlayService/EthConnect — connect to Ethereum -// POST /echidna.OverlayService/EthDisconnect — disconnect -// POST /echidna.OverlayService/EthStatus — connection status -// POST /echidna.OverlayService/EthTimestampProof — anchor proof on-chain -// POST /echidna.OverlayService/EthVerifyTimestamp — verify anchored timestamp -// -// Links against libechidna_overlay.so (Zig FFI layer) when available. -// Falls back to stub responses with X-Overlay-Mode: stub header when FFI is absent. - -module main - -import net.http -import time - -// --- Zig FFI extern declarations (libechidna_overlay.so) --- - -#flag -L @VMODROOT/../../ffi/zig/zig-out/lib -#flag -lechidna_overlay - -// Tor -fn C.echidna_tor_connect(config_ptr &u8, config_len usize) int -fn C.echidna_tor_disconnect() -fn C.echidna_tor_create_hidden_service(port int, target_port int, out_ptr &u8, out_len &usize) int -fn C.echidna_tor_destroy_hidden_service(onion_ptr &u8, onion_len usize) int -fn C.echidna_tor_get_circuit(circuit_id int, out_ptr &u8, out_len &usize) int -fn C.echidna_tor_list_circuits(out_ptr &u8, out_len &usize) int -fn C.echidna_tor_resolve(hostname_ptr &u8, hostname_len usize, out_ptr &u8, out_len &usize) int -fn C.echidna_tor_status() int -fn C.echidna_tor_hidden_service_count() int - -// IPFS -fn C.echidna_ipfs_connect(config_ptr &u8, config_len usize) int -fn C.echidna_ipfs_disconnect() -fn C.echidna_ipfs_add(data_ptr &u8, data_len usize, out_cid_ptr &u8, out_cid_len &usize) int -fn C.echidna_ipfs_cat(cid_ptr &u8, cid_len usize, out_ptr &u8, out_len &usize) int -fn C.echidna_ipfs_pin(cid_ptr &u8, cid_len usize) int -fn C.echidna_ipfs_unpin(cid_ptr &u8, cid_len usize) int -fn C.echidna_ipfs_dag_get(cid_ptr &u8, cid_len usize, out_ptr &u8, out_len &usize) int -fn C.echidna_ipfs_status() int -fn C.echidna_ipfs_pin_count() int - -// Ethereum (stubbed) -fn C.echidna_eth_connect(config_ptr &u8, config_len usize) int -fn C.echidna_eth_disconnect() -fn C.echidna_eth_timestamp_proof(proof_hash_ptr &u8, proof_hash_len usize, out_ptr &u8, out_len &usize) int -fn C.echidna_eth_verify_timestamp(tx_hash_ptr &u8, tx_hash_len usize, out_ptr &u8, out_len &usize) int -fn C.echidna_eth_status() int - -// Unified -fn C.echidna_overlay_version() &u8 -fn C.echidna_overlay_kind_name(kind int) &u8 -fn C.echidna_overlay_last_error() &u8 - -__global overlay_ffi_available = false -__global overlay_ffi_initialised = false - -fn init_overlay_ffi() { - ptr := C.echidna_overlay_version() - if ptr != unsafe { nil } { - ver := unsafe { cstring_to_vstring(ptr) } - if ver.len > 0 { - overlay_ffi_available = true - overlay_ffi_initialised = true - println(' FFI: linked to libechidna_overlay.so v${ver} (live mode)') - return - } - } - overlay_ffi_available = false - println(' FFI: overlay library not available (stub mode)') -} - -fn overlay_last_error() string { - ptr := C.echidna_overlay_last_error() - if ptr == unsafe { nil } { - return 'unknown error' - } - return unsafe { cstring_to_vstring(ptr) } -} - -fn overlay_version() string { - ptr := C.echidna_overlay_version() - return unsafe { cstring_to_vstring(ptr) } -} - -fn status_name(code int) string { - return match code { - 0 { 'disconnected' } - 1 { 'connecting' } - 2 { 'connected' } - 3 { 'error' } - else { 'unknown' } - } -} - -fn grpc_response(status_code int, body string) http.Response { - mut header := http.new_header(key: .content_type, value: 'application/grpc+json') - if overlay_ffi_available { - header.add_custom('X-Overlay-Mode', 'live') or {} - } else { - header.add_custom('X-Overlay-Mode', 'stub') or {} - } - header.add_custom('grpc-status', '0') or {} - return http.new_response( - status: unsafe { http.Status(status_code) } - header: header - body: body - ) -} - -// --- Handler --- - -struct OverlayGRPCHandler { - port int -} - -pub fn (mut h OverlayGRPCHandler) handle(req http.Request) http.Response { - if req.method != .post { - return grpc_response(405, '{"error":"POST required for RPC calls"}') - } - - path := req.url.all_before('?') - return match path { - // Unified - '/echidna.OverlayService/Health' { rpc_health() } - '/echidna.OverlayService/Status' { rpc_status() } - '/echidna.OverlayService/Version' { rpc_version() } - // Tor - '/echidna.OverlayService/TorConnect' { rpc_tor_connect(req) } - '/echidna.OverlayService/TorDisconnect' { rpc_tor_disconnect() } - '/echidna.OverlayService/TorStatus' { rpc_tor_status() } - '/echidna.OverlayService/TorCreateHiddenService' { rpc_tor_create_hs(req) } - '/echidna.OverlayService/TorDestroyHiddenService' { rpc_tor_destroy_hs(req) } - '/echidna.OverlayService/TorListCircuits' { rpc_tor_list_circuits() } - '/echidna.OverlayService/TorGetCircuit' { rpc_tor_get_circuit(req) } - '/echidna.OverlayService/TorResolve' { rpc_tor_resolve(req) } - // IPFS - '/echidna.OverlayService/IpfsConnect' { rpc_ipfs_connect(req) } - '/echidna.OverlayService/IpfsDisconnect' { rpc_ipfs_disconnect() } - '/echidna.OverlayService/IpfsStatus' { rpc_ipfs_status() } - '/echidna.OverlayService/IpfsAdd' { rpc_ipfs_add(req) } - '/echidna.OverlayService/IpfsCat' { rpc_ipfs_cat(req) } - '/echidna.OverlayService/IpfsPin' { rpc_ipfs_pin(req) } - '/echidna.OverlayService/IpfsUnpin' { rpc_ipfs_unpin(req) } - '/echidna.OverlayService/IpfsDagGet' { rpc_ipfs_dag_get(req) } - // Ethereum - '/echidna.OverlayService/EthConnect' { rpc_eth_connect(req) } - '/echidna.OverlayService/EthDisconnect' { rpc_eth_disconnect() } - '/echidna.OverlayService/EthStatus' { rpc_eth_status() } - '/echidna.OverlayService/EthTimestampProof' { rpc_eth_timestamp(req) } - '/echidna.OverlayService/EthVerifyTimestamp' { rpc_eth_verify(req) } - else { - grpc_response(404, '{"error":"Unknown method: ${esc(path)}","service":"echidna.OverlayService"}') - } - } -} - -// --- Server --- - -pub struct Server { -pub mut: - port int -} - -pub fn new_server(port int) &Server { - return &Server{port: port} -} - -pub fn (s Server) start() { - println('ECHIDNA Overlay gRPC-Web API Gateway starting on port ${s.port}...') - init_overlay_ffi() - println(' === Unified ===') - println(' POST /echidna.OverlayService/Health') - println(' POST /echidna.OverlayService/Status') - println(' POST /echidna.OverlayService/Version') - println(' === Tor (8 RPCs) ===') - println(' POST /echidna.OverlayService/Tor{Connect,Disconnect,Status,CreateHiddenService,...}') - println(' === IPFS (8 RPCs) ===') - println(' POST /echidna.OverlayService/Ipfs{Connect,Disconnect,Status,Add,Cat,Pin,Unpin,DagGet}') - println(' === Ethereum (5 RPCs, stubbed) ===') - println(' POST /echidna.OverlayService/Eth{Connect,Disconnect,Status,TimestampProof,VerifyTimestamp}') - println(' (JSON-over-HTTP transport, gRPC-Web compatible)') - mut server := http.Server{ - addr: ':${s.port}' - handler: &OverlayGRPCHandler{port: s.port} - } - server.listen_and_serve() -} - -fn main() { - mut s := new_server(8104) - s.start() -} - -// ============================================================================ -// Unified RPCs -// ============================================================================ - -fn rpc_health() http.Response { - now := time.now() - if overlay_ffi_available { - ver := overlay_version() - tor := status_name(C.echidna_tor_status()) - ipfs := status_name(C.echidna_ipfs_status()) - eth := status_name(C.echidna_eth_status()) - return grpc_response(200, '{"status":"SERVING","service":"echidna-overlay-grpc","version":"${ver}","tor":"${tor}","ipfs":"${ipfs}","ethereum":"${eth}","uptime_seconds":${now.unix()},"ffi_mode":"live"}') - } - return grpc_response(200, '{"status":"SERVING","service":"echidna-overlay-grpc","version":"1.0.0","tor":"disconnected","ipfs":"disconnected","ethereum":"disconnected","uptime_seconds":${now.unix()},"ffi_mode":"stub"}') -} - -fn rpc_status() http.Response { - if overlay_ffi_available { - tor := status_name(C.echidna_tor_status()) - ipfs := status_name(C.echidna_ipfs_status()) - eth := status_name(C.echidna_eth_status()) - tor_hs := C.echidna_tor_hidden_service_count() - ipfs_pins := C.echidna_ipfs_pin_count() - return grpc_response(200, '{"tor":{"status":"${tor}","hidden_services":${tor_hs}},"ipfs":{"status":"${ipfs}","pinned_items":${ipfs_pins}},"ethereum":{"status":"${eth}","note":"Stubbed"}}') - } - return grpc_response(200, '{"tor":{"status":"disconnected","hidden_services":0},"ipfs":{"status":"disconnected","pinned_items":0},"ethereum":{"status":"disconnected","note":"Stubbed"}}') -} - -fn rpc_version() http.Response { - if overlay_ffi_available { - return grpc_response(200, '{"version":"${overlay_version()}"}') - } - return grpc_response(200, '{"version":"1.0.0"}') -} - -// ============================================================================ -// Tor RPCs -// ============================================================================ - -fn rpc_tor_connect(req http.Request) http.Response { - config := if req.data.len > 0 { req.data } else { '{"control_port":9051}' } - if overlay_ffi_available { - rc := C.echidna_tor_connect(config.str, usize(config.len)) - if rc == 0 { - return grpc_response(200, '{"connected":true,"network":"tor","control_port":9051,"socks_port":9050}') - } - return grpc_response(500, '{"connected":false,"error":"${esc(overlay_last_error())}"}') - } - return grpc_response(200, '{"connected":true,"network":"tor","control_port":9051,"socks_port":9050}') -} - -fn rpc_tor_disconnect() http.Response { - if overlay_ffi_available { - C.echidna_tor_disconnect() - } - return grpc_response(200, '{"disconnected":true,"network":"tor"}') -} - -fn rpc_tor_status() http.Response { - if overlay_ffi_available { - return grpc_response(200, '{"network":"tor","status":"${status_name(C.echidna_tor_status())}","hidden_services":${C.echidna_tor_hidden_service_count()}}') - } - return grpc_response(200, '{"network":"tor","status":"disconnected","hidden_services":0}') -} - -fn rpc_tor_create_hs(req http.Request) http.Response { - port := json_field_int(req.data, 'port') - target_port := json_field_int(req.data, 'target_port') - if port <= 0 || target_port <= 0 { - return grpc_response(400, '{"error":"port and target_port required"}') - } - if overlay_ffi_available { - mut buf := [128]u8{} - mut buf_len := usize(128) - rc := C.echidna_tor_create_hidden_service(port, target_port, &buf[0], &buf_len) - if rc == 0 && buf_len > 0 { - onion := unsafe { tos(&buf[0], int(buf_len)) } - return grpc_response(200, '{"created":true,"onion_address":"${esc(onion)}","port":${port},"target_port":${target_port},"active_services":${C.echidna_tor_hidden_service_count()}}') - } - return grpc_response(500, '{"created":false,"error":"${esc(overlay_last_error())}"}') - } - return grpc_response(200, '{"created":true,"onion_address":"echidna2fproof7verify3trust8secure4formal5check6valid.onion","port":${port},"target_port":${target_port},"active_services":1}') -} - -fn rpc_tor_destroy_hs(req http.Request) http.Response { - onion := json_field(req.data, 'onion_address') - if onion.len == 0 { - return grpc_response(400, '{"error":"onion_address required"}') - } - if overlay_ffi_available { - rc := C.echidna_tor_destroy_hidden_service(onion.str, usize(onion.len)) - if rc == 0 { - return grpc_response(200, '{"destroyed":true,"onion_address":"${esc(onion)}","remaining_services":${C.echidna_tor_hidden_service_count()}}') - } - return grpc_response(500, '{"destroyed":false,"error":"${esc(overlay_last_error())}"}') - } - return grpc_response(200, '{"destroyed":true,"onion_address":"${esc(onion)}","remaining_services":0}') -} - -fn rpc_tor_list_circuits() http.Response { - if overlay_ffi_available { - mut buf := [8192]u8{} - mut buf_len := usize(8192) - rc := C.echidna_tor_list_circuits(&buf[0], &buf_len) - if rc == 0 && buf_len > 0 { - return grpc_response(200, '{"network":"tor","circuits":${unsafe { tos(&buf[0], int(buf_len)) }}}') - } - return grpc_response(500, '{"error":"${esc(overlay_last_error())}"}') - } - return grpc_response(200, '{"network":"tor","circuits":[{"circuit_id":1,"status":"BUILT","hop_count":3,"purpose":"GENERAL"}]}') -} - -fn rpc_tor_get_circuit(req http.Request) http.Response { - circuit_id := json_field_int(req.data, 'circuit_id') - if overlay_ffi_available { - mut buf := [8192]u8{} - mut buf_len := usize(8192) - rc := C.echidna_tor_get_circuit(circuit_id, &buf[0], &buf_len) - if rc == 0 && buf_len > 0 { - return grpc_response(200, unsafe { tos(&buf[0], int(buf_len)) }) - } - return grpc_response(500, '{"error":"${esc(overlay_last_error())}"}') - } - return grpc_response(200, '{"circuit_id":${circuit_id},"status":"BUILT","path":[{"fingerprint":"AAAA...","nickname":"Guard1","country":"DE","is_exit":false},{"fingerprint":"BBBB...","nickname":"Middle1","country":"NL","is_exit":false},{"fingerprint":"CCCC...","nickname":"Exit1","country":"SE","is_exit":true}],"purpose":"GENERAL","time_created":"${time.now().format_rfc3339()}"}') -} - -fn rpc_tor_resolve(req http.Request) http.Response { - hostname := json_field(req.data, 'hostname') - if hostname.len == 0 { - return grpc_response(400, '{"error":"hostname required"}') - } - if overlay_ffi_available { - mut buf := [256]u8{} - mut buf_len := usize(256) - rc := C.echidna_tor_resolve(hostname.str, usize(hostname.len), &buf[0], &buf_len) - if rc == 0 && buf_len > 0 { - return grpc_response(200, '{"hostname":"${esc(hostname)}","resolved":"${unsafe { tos(&buf[0], int(buf_len)) }}","via":"tor-socks5"}') - } - return grpc_response(500, '{"hostname":"${esc(hostname)}","error":"${esc(overlay_last_error())}"}') - } - return grpc_response(200, '{"hostname":"${esc(hostname)}","resolved":"198.51.100.42","via":"tor-socks5"}') -} - -// ============================================================================ -// IPFS RPCs -// ============================================================================ - -fn rpc_ipfs_connect(req http.Request) http.Response { - config := if req.data.len > 0 { req.data } else { '{"api_port":5001}' } - if overlay_ffi_available { - rc := C.echidna_ipfs_connect(config.str, usize(config.len)) - if rc == 0 { - return grpc_response(200, '{"connected":true,"network":"ipfs","api_port":5001,"gateway_port":8080}') - } - return grpc_response(500, '{"connected":false,"error":"${esc(overlay_last_error())}"}') - } - return grpc_response(200, '{"connected":true,"network":"ipfs","api_port":5001,"gateway_port":8080}') -} - -fn rpc_ipfs_disconnect() http.Response { - if overlay_ffi_available { - C.echidna_ipfs_disconnect() - } - return grpc_response(200, '{"disconnected":true,"network":"ipfs"}') -} - -fn rpc_ipfs_status() http.Response { - if overlay_ffi_available { - return grpc_response(200, '{"network":"ipfs","status":"${status_name(C.echidna_ipfs_status())}","pinned_items":${C.echidna_ipfs_pin_count()}}') - } - return grpc_response(200, '{"network":"ipfs","status":"disconnected","pinned_items":0}') -} - -fn rpc_ipfs_add(req http.Request) http.Response { - data := json_field(req.data, 'data') - if data.len == 0 { - return grpc_response(400, '{"error":"data required"}') - } - if overlay_ffi_available { - mut cid_buf := [256]u8{} - mut cid_len := usize(256) - rc := C.echidna_ipfs_add(data.str, usize(data.len), &cid_buf[0], &cid_len) - if rc == 0 && cid_len > 0 { - cid := unsafe { tos(&cid_buf[0], int(cid_len)) } - return grpc_response(200, '{"added":true,"cid":"${esc(cid)}","size":${data.len}}') - } - return grpc_response(500, '{"added":false,"error":"${esc(overlay_last_error())}"}') - } - return grpc_response(200, '{"added":true,"cid":"bafkreihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenrqpc","size":${data.len}}') -} - -fn rpc_ipfs_cat(req http.Request) http.Response { - cid := json_field(req.data, 'cid') - if cid.len == 0 { - return grpc_response(400, '{"error":"cid required"}') - } - if overlay_ffi_available { - mut buf := [65536]u8{} - mut buf_len := usize(65536) - rc := C.echidna_ipfs_cat(cid.str, usize(cid.len), &buf[0], &buf_len) - if rc == 0 && buf_len > 0 { - content := unsafe { tos(&buf[0], int(buf_len)) } - return grpc_response(200, '{"cid":"${esc(cid)}","content":"${esc(content)}","size":${buf_len}}') - } - return grpc_response(500, '{"cid":"${esc(cid)}","error":"${esc(overlay_last_error())}"}') - } - return grpc_response(200, '{"cid":"${esc(cid)}","content":"(* ECHIDNA proof certificate — retrieved from IPFS *)","size":54}') -} - -fn rpc_ipfs_pin(req http.Request) http.Response { - cid := json_field(req.data, 'cid') - if cid.len == 0 { - return grpc_response(400, '{"error":"cid required"}') - } - if overlay_ffi_available { - rc := C.echidna_ipfs_pin(cid.str, usize(cid.len)) - if rc == 0 { - return grpc_response(200, '{"pinned":true,"cid":"${esc(cid)}","total_pinned":${C.echidna_ipfs_pin_count()}}') - } - return grpc_response(500, '{"pinned":false,"error":"${esc(overlay_last_error())}"}') - } - return grpc_response(200, '{"pinned":true,"cid":"${esc(cid)}","total_pinned":1}') -} - -fn rpc_ipfs_unpin(req http.Request) http.Response { - cid := json_field(req.data, 'cid') - if cid.len == 0 { - return grpc_response(400, '{"error":"cid required"}') - } - if overlay_ffi_available { - rc := C.echidna_ipfs_unpin(cid.str, usize(cid.len)) - if rc == 0 { - return grpc_response(200, '{"unpinned":true,"cid":"${esc(cid)}","total_pinned":${C.echidna_ipfs_pin_count()}}') - } - return grpc_response(500, '{"unpinned":false,"error":"${esc(overlay_last_error())}"}') - } - return grpc_response(200, '{"unpinned":true,"cid":"${esc(cid)}","total_pinned":0}') -} - -fn rpc_ipfs_dag_get(req http.Request) http.Response { - cid := json_field(req.data, 'cid') - if cid.len == 0 { - return grpc_response(400, '{"error":"cid required"}') - } - if overlay_ffi_available { - mut buf := [8192]u8{} - mut buf_len := usize(8192) - rc := C.echidna_ipfs_dag_get(cid.str, usize(cid.len), &buf[0], &buf_len) - if rc == 0 && buf_len > 0 { - return grpc_response(200, unsafe { tos(&buf[0], int(buf_len)) }) - } - return grpc_response(500, '{"cid":"${esc(cid)}","error":"${esc(overlay_last_error())}"}') - } - return grpc_response(200, '{"cid":"${esc(cid)}","links":0,"size":256,"data_size":128}') -} - -// ============================================================================ -// Ethereum RPCs (stubbed) -// ============================================================================ - -fn rpc_eth_connect(req http.Request) http.Response { - config := if req.data.len > 0 { req.data } else { '{"rpc_url":"http://localhost:8545","chain_id":1337}' } - if overlay_ffi_available { - rc := C.echidna_eth_connect(config.str, usize(config.len)) - if rc == 0 { - return grpc_response(200, '{"connected":true,"network":"ethereum","note":"Stubbed — Aerie future use"}') - } - return grpc_response(500, '{"connected":false,"error":"${esc(overlay_last_error())}"}') - } - return grpc_response(200, '{"connected":true,"network":"ethereum","note":"Stubbed — Aerie future use"}') -} - -fn rpc_eth_disconnect() http.Response { - if overlay_ffi_available { - C.echidna_eth_disconnect() - } - return grpc_response(200, '{"disconnected":true,"network":"ethereum"}') -} - -fn rpc_eth_status() http.Response { - if overlay_ffi_available { - return grpc_response(200, '{"network":"ethereum","status":"${status_name(C.echidna_eth_status())}","note":"Stubbed"}') - } - return grpc_response(200, '{"network":"ethereum","status":"disconnected","note":"Stubbed"}') -} - -fn rpc_eth_timestamp(req http.Request) http.Response { - proof_hash := json_field(req.data, 'proof_hash') - if proof_hash.len == 0 { - return grpc_response(400, '{"error":"proof_hash required"}') - } - if overlay_ffi_available { - mut buf := [2048]u8{} - mut buf_len := usize(2048) - rc := C.echidna_eth_timestamp_proof(proof_hash.str, usize(proof_hash.len), &buf[0], &buf_len) - if rc == 0 && buf_len > 0 { - return grpc_response(200, unsafe { tos(&buf[0], int(buf_len)) }) - } - return grpc_response(500, '{"error":"${esc(overlay_last_error())}"}') - } - return grpc_response(200, '{"tx_hash":"0xdeadbeef0123456789abcdef0123456789abcdef0123456789abcdef01234567","block_number":19000000,"timestamp":${time.now().unix()},"proof_hash":"${esc(proof_hash)}","status":"STUBBED"}') -} - -fn rpc_eth_verify(req http.Request) http.Response { - tx_hash := json_field(req.data, 'tx_hash') - if tx_hash.len == 0 { - return grpc_response(400, '{"error":"tx_hash required"}') - } - if overlay_ffi_available { - mut buf := [2048]u8{} - mut buf_len := usize(2048) - rc := C.echidna_eth_verify_timestamp(tx_hash.str, usize(tx_hash.len), &buf[0], &buf_len) - if rc == 0 && buf_len > 0 { - return grpc_response(200, unsafe { tos(&buf[0], int(buf_len)) }) - } - return grpc_response(500, '{"error":"${esc(overlay_last_error())}"}') - } - return grpc_response(200, '{"verified":true,"tx_hash":"${esc(tx_hash)}","proof_hash":"sha3-256:stub","block_number":19000000,"timestamp":${time.now().unix()},"status":"STUBBED"}') -} - -// --- Helpers --- - -fn esc(s string) string { - return s.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n').replace('\t', '\\t') -} - -fn json_field(data string, key string) string { - needle := '"${key}":' - idx := data.index(needle) or { return '' } - tail := data[idx + needle.len..].trim_space() - if tail.len == 0 || tail[0] != `"` { - return '' - } - end := tail[1..].index('"') or { return '' } - return tail[1..end + 1] -} - -fn json_field_int(data string, key string) int { - needle := '"${key}":' - idx := data.index(needle) or { return 0 } - tail := data[idx + needle.len..].trim_space() - if tail.len == 0 { - return 0 - } - mut end := 0 - for i, c in tail { - if c >= `0` && c <= `9` { - end = i + 1 - } else if end > 0 { - break - } - } - if end == 0 { - return 0 - } - return tail[..end].int() -} diff --git a/src/interfaces/v-adapter/rest.v b/src/interfaces/v-adapter/rest.v deleted file mode 100644 index 562821ed..00000000 --- a/src/interfaces/v-adapter/rest.v +++ /dev/null @@ -1,683 +0,0 @@ -// SPDX-License-Identifier: PMPL-1.0-or-later -// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -// -// ECHIDNA REST API Gateway -// -// Exposes ECHIDNA theorem prover dispatch via REST endpoints on port 8100: -// POST /api/v1/provers — create prover session -// DELETE /api/v1/provers/:id — destroy prover session -// GET /api/v1/provers — list available provers (30 provers) -// GET /api/v1/provers/:kind — get prover info -// POST /api/v1/proofs/parse — parse proof content -// POST /api/v1/proofs/verify — verify proof -// POST /api/v1/proofs/:id/tactics — apply tactic -// GET /api/v1/proofs/:id/tactics/suggest — suggest tactics -// POST /api/v1/proofs/:id/export — export proof -// POST /api/v1/dispatch — full trust pipeline dispatch -// GET /api/v1/health — health check -// GET / — API discovery -// -// Links against libechidna_ffi.so (Zig FFI layer) when available. -// Falls back to stub responses with X-Echidna-Mode: stub header when FFI is absent. - -module main - -import net.http -import time -import rand - -// --- Zig FFI extern declarations (libechidna_ffi.so) --- - -#flag -L @VMODROOT/../../ffi/zig/zig-out/lib -#flag -lechidna_ffi - -fn C.echidna_init() int -fn C.echidna_deinit() -fn C.echidna_create_prover(kind int) int -fn C.echidna_destroy_prover(handle int) -fn C.echidna_parse_file(handle int, path_ptr &u8, path_len usize) int -fn C.echidna_parse_string(handle int, content_ptr &u8, content_len usize) int -fn C.echidna_apply_tactic(handle int, tactic_ptr &u8, tactic_len usize) int -fn C.echidna_verify_proof(handle int) int -fn C.echidna_export_proof(handle int, out_ptr &u8, out_len &usize) int -fn C.echidna_suggest_tactics(handle int, limit int, out_ptr &u8, out_len &usize) int -fn C.echidna_version() &u8 -fn C.echidna_prover_count() int -fn C.echidna_prover_name(kind int) &u8 -fn C.echidna_last_error() &u8 -fn C.echidna_build_info() &u8 - -// Callback registration (Zig FFI bidirectional callbacks) -// Signatures must match Zig OnInitChangeFn, OnProverChangeFn, OnFfiErrorFn, OnVerifyCompleteFn -fn C.echidna_register_on_init_change(cb fn (int, int) ) int -fn C.echidna_register_on_prover_change(cb fn (int, int, int) ) int -fn C.echidna_register_on_error(cb fn (int, &u8, usize) ) int -fn C.echidna_register_on_verify_complete(cb fn (int, int, int) ) int -fn C.echidna_unregister_all_callbacks() int -fn C.echidna_callback_count() int - -// --- Event Buffer --- -// Callbacks push events here; GET /api/v1/events drains the buffer. - -struct Event { - kind string - timestamp string - data string -} - -__global event_buffer = []Event{} -__global event_buffer_max = 1000 - -fn push_event(kind string, data string) { - if event_buffer.len >= event_buffer_max { - event_buffer.delete(0) - } - event_buffer << Event{ - kind: kind - timestamp: time.now().format_rfc3339() - data: data - } -} - -fn on_init_callback(old_state int, new_state int) { - push_event('init', '{"old_state":${old_state},"new_state":${new_state}}') -} - -fn on_prover_change_callback(handle_id int, prover_kind int, created int) { - push_event('prover_change', '{"handle_id":${handle_id},"prover_kind":${prover_kind},"created":${created == 1}}') -} - -fn on_error_callback(error_code int, msg_ptr &u8, msg_len usize) { - msg := if msg_ptr != unsafe { nil } && msg_len > 0 { - unsafe { tos(msg_ptr, int(msg_len)) } - } else { - 'unknown error' - } - push_event('error', '{"error_code":${error_code},"message":"${esc(msg)}"}') -} - -fn on_verify_complete_callback(handle_id int, prover_kind int, verified int) { - push_event('verify_complete', '{"handle_id":${handle_id},"prover_kind":${prover_kind},"verified":${verified == 1}}') -} - -// Track whether FFI is available (set during init) -__global ffi_available = false -__global ffi_initialised = false - -fn init_ffi() { - rc := C.echidna_init() - if rc == 0 { - ffi_available = true - ffi_initialised = true - // Register bidirectional callbacks (signatures match Zig OnInitChangeFn etc.) - C.echidna_register_on_init_change(on_init_callback) - C.echidna_register_on_prover_change(on_prover_change_callback) - C.echidna_register_on_error(on_error_callback) - C.echidna_register_on_verify_complete(on_verify_complete_callback) - println(' FFI: linked to libechidna_ffi.so (live mode, ${C.echidna_callback_count()} callbacks registered)') - } else { - ffi_available = false - println(' FFI: not available (stub mode)') - } -} - -fn ffi_response(status_code int, body string) http.Response { - mut header := http.new_header(key: .content_type, value: 'application/json') - if !ffi_available { - header.add_custom('X-Echidna-Mode', 'stub') or {} - } else { - header.add_custom('X-Echidna-Mode', 'live') or {} - } - return http.new_response( - status: unsafe { http.Status(status_code) } - header: header - body: body - ) -} - -fn ffi_last_error() string { - ptr := C.echidna_last_error() - if ptr == unsafe { nil } { - return 'unknown error' - } - return unsafe { cstring_to_vstring(ptr) } -} - -fn ffi_version() string { - ptr := C.echidna_version() - return unsafe { cstring_to_vstring(ptr) } -} - -// --- Prover Data --- - -struct ProverInfo { - kind string - name string - tier int - ordinal int // FFI prover kind ordinal (0-29) - description string - available bool - complexity int -} - -fn all_provers() []ProverInfo { - return [ - // Tier 1: Core + SMT (ordinals 0-5 match Zig ProverKind) - ProverInfo{kind: 'agda', name: 'Agda', tier: 1, ordinal: 0, description: 'Dependently-typed proof assistant with Curry-Howard correspondence', available: true, complexity: 3}, - ProverInfo{kind: 'coq', name: 'Coq/Rocq', tier: 1, ordinal: 1, description: 'Calculus of Inductive Constructions proof assistant', available: true, complexity: 3}, - ProverInfo{kind: 'lean', name: 'Lean 4', tier: 1, ordinal: 2, description: 'Dependent type theory with powerful automation (mathlib)', available: true, complexity: 3}, - ProverInfo{kind: 'isabelle', name: 'Isabelle/HOL', tier: 1, ordinal: 3, description: 'Higher-order logic proof assistant with Sledgehammer', available: true, complexity: 4}, - ProverInfo{kind: 'z3', name: 'Z3', tier: 1, ordinal: 4, description: 'Microsoft SMT solver (SAT modulo theories)', available: true, complexity: 2}, - ProverInfo{kind: 'cvc5', name: 'CVC5', tier: 1, ordinal: 5, description: 'SMT solver with quantifier reasoning', available: true, complexity: 2}, - // Tier 2: Big Six completion (ordinals 6-8) - ProverInfo{kind: 'metamath', name: 'Metamath', tier: 2, ordinal: 6, description: 'Minimalist proof language with tiny trusted kernel', available: true, complexity: 2}, - ProverInfo{kind: 'hollight', name: 'HOL Light', tier: 2, ordinal: 7, description: 'Simple higher-order logic theorem prover', available: true, complexity: 3}, - ProverInfo{kind: 'mizar', name: 'Mizar', tier: 2, ordinal: 8, description: 'Set-theoretic proof assistant (MML library)', available: true, complexity: 3}, - // Tier 3: Additional coverage (ordinals 9-10) - ProverInfo{kind: 'pvs', name: 'PVS', tier: 3, ordinal: 9, description: 'Prototype Verification System with rich type system', available: true, complexity: 4}, - ProverInfo{kind: 'acl2', name: 'ACL2', tier: 3, ordinal: 10, description: 'A Computational Logic for Applicative Common Lisp', available: true, complexity: 4}, - // Tier 4: Advanced (ordinal 11) - ProverInfo{kind: 'hol4', name: 'HOL4', tier: 4, ordinal: 11, description: 'Higher-order logic (LCF-style, SML-based)', available: true, complexity: 5}, - // Extended: Dependent types (ordinals 12, 17) - ProverInfo{kind: 'idris2', name: 'Idris 2', tier: 1, ordinal: 12, description: 'Quantitative type theory with dependent types and linear types', available: true, complexity: 3}, - ProverInfo{kind: 'fstar', name: 'F*', tier: 1, ordinal: 17, description: 'Dependent types with effects for program verification', available: true, complexity: 3}, - // Tier 5: First-Order ATPs (ordinals 13-16) - ProverInfo{kind: 'vampire', name: 'Vampire', tier: 5, ordinal: 13, description: 'First-order automated theorem prover (TPTP format)', available: true, complexity: 2}, - ProverInfo{kind: 'eprover', name: 'E Prover', tier: 5, ordinal: 14, description: 'Equational first-order theorem prover', available: true, complexity: 2}, - ProverInfo{kind: 'spass', name: 'SPASS', tier: 5, ordinal: 15, description: 'First-order logic with equality (DFG/TPTP)', available: true, complexity: 2}, - ProverInfo{kind: 'altergo', name: 'Alt-Ergo', tier: 5, ordinal: 16, description: 'SMT solver with polymorphism and AC reasoning', available: true, complexity: 2}, - // Tier 6: Auto-active / orchestration (ordinals 18-19) - ProverInfo{kind: 'dafny', name: 'Dafny', tier: 2, ordinal: 18, description: 'Auto-active verification language (Boogie/Z3 backend)', available: true, complexity: 2}, - ProverInfo{kind: 'why3', name: 'Why3', tier: 2, ordinal: 19, description: 'Multi-prover orchestration platform', available: true, complexity: 3}, - // Tier 7: Specialised / niche (ordinals 20-24) - ProverInfo{kind: 'tlaps', name: 'TLAPS', tier: 2, ordinal: 20, description: 'TLA+ Proof System for distributed systems', available: true, complexity: 4}, - ProverInfo{kind: 'twelf', name: 'Twelf', tier: 4, ordinal: 21, description: 'Logical framework based on LF type theory', available: true, complexity: 4}, - ProverInfo{kind: 'nuprl', name: 'Nuprl', tier: 4, ordinal: 22, description: 'Constructive type theory prover (Cornell)', available: true, complexity: 4}, - ProverInfo{kind: 'minlog', name: 'Minlog', tier: 4, ordinal: 23, description: 'Minimal logic proof assistant with program extraction', available: true, complexity: 4}, - ProverInfo{kind: 'imandra', name: 'Imandra', tier: 2, ordinal: 24, description: 'ML-based automated reasoning engine', available: true, complexity: 3}, - // Tier 8: Constraint solvers (ordinals 25-29) - ProverInfo{kind: 'glpk', name: 'GLPK', tier: 5, ordinal: 25, description: 'GNU Linear Programming Kit (LP/MIP solver)', available: true, complexity: 2}, - ProverInfo{kind: 'scip', name: 'SCIP', tier: 5, ordinal: 26, description: 'Solving Constraint Integer Programs (MINLP)', available: true, complexity: 3}, - ProverInfo{kind: 'minizinc', name: 'MiniZinc', tier: 5, ordinal: 27, description: 'Constraint modelling language', available: true, complexity: 2}, - ProverInfo{kind: 'chuffed', name: 'Chuffed', tier: 5, ordinal: 28, description: 'Lazy clause generation constraint solver', available: true, complexity: 2}, - ProverInfo{kind: 'ortools', name: 'OR-Tools', tier: 5, ordinal: 29, description: 'Google constraint and optimization solver', available: true, complexity: 2}, - ] -} - -fn find_prover(kind string) ?ProverInfo { - for p in all_provers() { - if p.kind == kind { - return p - } - } - return none -} - -fn prover_to_json(p ProverInfo) string { - return '{"kind":"${p.kind}","name":"${esc(p.name)}","tier":${p.tier},"description":"${esc(p.description)}","available":${p.available},"complexity":${p.complexity}}' -} - -// --- Handler --- - -struct EchidnaRESTHandler { - port int -} - -pub fn (mut h EchidnaRESTHandler) handle(req http.Request) http.Response { - path := req.url.all_before('?') - - // Route: GET / — API discovery - if path == '/' && req.method == .get { - return handle_info() - } - // Route: GET /api/v1/health - if path == '/api/v1/health' && req.method == .get { - return handle_health() - } - // Route: GET /api/v1/events — drain event buffer (callback events) - if path == '/api/v1/events' && req.method == .get { - return handle_events() - } - // Route: POST /api/v1/dispatch - if path == '/api/v1/dispatch' && req.method == .post { - return handle_dispatch(req) - } - // Route: POST /api/v1/proofs/parse - if path == '/api/v1/proofs/parse' && req.method == .post { - return handle_parse(req) - } - // Route: POST /api/v1/proofs/verify - if path == '/api/v1/proofs/verify' && req.method == .post { - return handle_verify(req) - } - // Route: POST /api/v1/proofs/:id/export - if path.starts_with('/api/v1/proofs/') && path.ends_with('/export') && req.method == .post { - proof_id := path.all_after('/api/v1/proofs/').all_before('/export') - return handle_export(proof_id) - } - // Route: GET /api/v1/proofs/:id/tactics/suggest - if path.starts_with('/api/v1/proofs/') && path.ends_with('/tactics/suggest') && req.method == .get { - proof_id := path.all_after('/api/v1/proofs/').all_before('/tactics') - return handle_suggest_tactics(proof_id) - } - // Route: POST /api/v1/proofs/:id/tactics - if path.starts_with('/api/v1/proofs/') && path.ends_with('/tactics') && req.method == .post { - proof_id := path.all_after('/api/v1/proofs/').all_before('/tactics') - return handle_apply_tactic(req, proof_id) - } - // Route: POST /api/v1/provers — create session - if path == '/api/v1/provers' && req.method == .post { - return handle_create_session(req) - } - // Route: GET /api/v1/provers — list provers - if path == '/api/v1/provers' && req.method == .get { - return handle_list_provers() - } - // Route: DELETE /api/v1/provers/:id — destroy session - if path.starts_with('/api/v1/provers/') && req.method == .delete { - session_id := path.all_after('/api/v1/provers/') - return handle_destroy_session(session_id) - } - // Route: GET /api/v1/provers/:kind — get prover info - if path.starts_with('/api/v1/provers/') && req.method == .get { - kind := path.all_after('/api/v1/provers/') - return handle_get_prover(kind) - } - - return stub_response(404, '{"error":"Not found","endpoints":["/api/v1/provers","/api/v1/proofs/parse","/api/v1/proofs/verify","/api/v1/dispatch","/api/v1/health"]}') -} - -// --- Server --- - -pub struct Server { -pub mut: - port int -} - -pub fn new_server(port int) &Server { - return &Server{ - port: port - } -} - -pub fn (s Server) start() { - println('ECHIDNA REST API Gateway starting on port ${s.port}...') - init_ffi() - println(' POST /api/v1/provers — create prover session') - println(' DELETE /api/v1/provers/:id — destroy prover session') - println(' GET /api/v1/provers — list available provers') - println(' GET /api/v1/provers/:kind — get prover info') - println(' POST /api/v1/proofs/parse — parse proof content') - println(' POST /api/v1/proofs/verify — verify proof') - println(' POST /api/v1/proofs/:id/tactics — apply tactic') - println(' GET /api/v1/proofs/:id/tactics/suggest — suggest tactics') - println(' POST /api/v1/proofs/:id/export — export proof') - println(' POST /api/v1/dispatch — full trust pipeline dispatch') - println(' GET /api/v1/events — drain callback event buffer') - println(' GET /api/v1/health — health check') - println(' GET / — API discovery') - mut server := http.Server{ - addr: ':${s.port}' - handler: &EchidnaRESTHandler{port: s.port} - } - server.listen_and_serve() -} - -fn main() { - mut s := new_server(8100) - s.start() -} - -// --- Route Handlers --- - -fn handle_info() http.Response { - if ffi_available { - ver := ffi_version() - count := C.echidna_prover_count() - return ffi_response(200, '{"service":"echidna-rest","version":"${ver}","description":"ECHIDNA trust-hardened theorem prover REST gateway","prover_count":${count},"endpoints":["/api/v1/provers","/api/v1/proofs/parse","/api/v1/proofs/verify","/api/v1/proofs/:id/tactics","/api/v1/proofs/:id/tactics/suggest","/api/v1/proofs/:id/export","/api/v1/dispatch","/api/v1/health"],"trust_levels":["Level0","Level1","Level2","Level3","Level4","Level5"],"documentation":"https://github.com/hyperpolymath/echidna"}') - } - return ffi_response(200, '{"service":"echidna-rest","version":"1.5.0","description":"ECHIDNA trust-hardened theorem prover REST gateway","prover_count":30,"endpoints":["/api/v1/provers","/api/v1/proofs/parse","/api/v1/proofs/verify","/api/v1/proofs/:id/tactics","/api/v1/proofs/:id/tactics/suggest","/api/v1/proofs/:id/export","/api/v1/dispatch","/api/v1/health"],"trust_levels":["Level0","Level1","Level2","Level3","Level4","Level5"],"documentation":"https://github.com/hyperpolymath/echidna"}') -} - -fn handle_health() http.Response { - now := time.now() - if ffi_available { - ver := ffi_version() - count := C.echidna_prover_count() - return ffi_response(200, '{"healthy":true,"service":"echidna-rest","version":"${ver}","prover_count":${count},"active_sessions":0,"uptime_seconds":${now.unix()},"trust_pipeline":"operational","integrity_checker":"enabled","axiom_tracker":"enabled","sandbox_mode":"bubblewrap","ffi_mode":"live"}') - } - return ffi_response(200, '{"healthy":true,"service":"echidna-rest","version":"1.5.0","prover_count":30,"active_sessions":0,"uptime_seconds":${now.unix()},"trust_pipeline":"operational","integrity_checker":"enabled","axiom_tracker":"enabled","sandbox_mode":"bubblewrap","ffi_mode":"stub"}') -} - -fn handle_events() http.Response { - if event_buffer.len == 0 { - return ffi_response(200, '{"events":[],"count":0,"message":"No pending events"}') - } - mut items := []string{} - for ev in event_buffer { - items << '{"kind":"${esc(ev.kind)}","timestamp":"${esc(ev.timestamp)}","data":${ev.data}}' - } - count := event_buffer.len - event_buffer.clear() - return ffi_response(200, '{"events":[${items.join(",")}],"count":${count}}') -} - -fn handle_list_provers() http.Response { - if ffi_available { - count := C.echidna_prover_count() - mut items := []string{} - for i in 0 .. count { - name_ptr := C.echidna_prover_name(i) - name := if name_ptr != unsafe { nil } { - unsafe { cstring_to_vstring(name_ptr) } - } else { - 'Unknown' - } - // Augment FFI name with local prover metadata for tier/description - p := find_prover_by_index(i) or { - items << '{"kind":"prover_${i}","name":"${esc(name)}","tier":0,"description":"","available":true,"complexity":0}' - continue - } - items << prover_to_json(p) - } - return ffi_response(200, '{"provers":[${items.join(",")}],"count":${count}}') - } - provers := all_provers() - mut items := []string{} - for p in provers { - items << prover_to_json(p) - } - return ffi_response(200, '{"provers":[${items.join(",")}],"count":${provers.len}}') -} - -fn handle_get_prover(kind string) http.Response { - prover := find_prover(kind) or { - return ffi_response(404, '{"error":"Unknown prover kind","kind":"${esc(kind)}","available_kinds":["agda","coq","lean","isabelle","z3","cvc5","metamath","hollight","mizar","pvs","acl2","hol4","idris2","fstar","vampire","eprover","spass","altergo","dafny","why3","tlaps","twelf","nuprl","minlog","imandra","glpk","scip","minizinc","chuffed","ortools"]}') - } - return ffi_response(200, prover_to_json(prover)) -} - -fn handle_create_session(req http.Request) http.Response { - if req.data.len == 0 { - return ffi_response(400, '{"error":"Request body required with kind field"}') - } - kind := json_field(req.data, 'kind') - if kind.len == 0 { - return ffi_response(400, '{"error":"kind field required (e.g. lean, coq, z3)"}') - } - prover := find_prover(kind) or { - return ffi_response(400, '{"error":"Unknown prover kind","kind":"${esc(kind)}"}') - } - if ffi_available { - handle := C.echidna_create_prover(prover.ordinal) - if handle >= 0 { - session_id := 'ses-${kind}-${handle}' - return ffi_response(201, '{"session_id":"${session_id}","kind":"${esc(kind)}","status":"active","handle":${handle},"created_at":"${time.now().format_rfc3339()}","timeout_seconds":300}') - } - err := ffi_last_error() - return ffi_response(500, '{"error":"FFI prover creation failed","detail":"${esc(err)}","kind":"${esc(kind)}"}') - } - session_id := 'ses-${kind}-${rand.u32()}' - return ffi_response(201, '{"session_id":"${session_id}","kind":"${esc(kind)}","status":"active","created_at":"${time.now().format_rfc3339()}","timeout_seconds":300}') -} - -fn handle_destroy_session(session_id string) http.Response { - if session_id.len == 0 { - return ffi_response(400, '{"error":"Session ID required"}') - } - if ffi_available { - handle := extract_handle_from_session(session_id) - if handle >= 0 { - C.echidna_destroy_prover(handle) - } - } - return ffi_response(200, '{"session_id":"${esc(session_id)}","status":"destroyed","destroyed_at":"${time.now().format_rfc3339()}"}') -} - -fn handle_parse(req http.Request) http.Response { - if req.data.len == 0 { - return ffi_response(400, '{"error":"Request body required with prover and content fields"}') - } - prover := json_field(req.data, 'prover') - content := json_field(req.data, 'content') - if prover.len == 0 || content.len == 0 { - return ffi_response(400, '{"error":"Both prover and content fields required"}') - } - p := find_prover(prover) or { - return ffi_response(400, '{"error":"Unknown prover kind","kind":"${esc(prover)}"}') - } - if ffi_available { - handle := C.echidna_create_prover(p.ordinal) - if handle >= 0 { - rc := C.echidna_parse_string(handle, content.str, usize(content.len)) - if rc == 0 { - C.echidna_destroy_prover(handle) - return ffi_response(200, '{"parsed":true,"prover":"${esc(prover)}","goals":[],"context":{"theorems":[],"axioms":[],"definitions":[]},"proof_script":[],"content_length":${content.len},"ffi_handle":${handle}}') - } - err := ffi_last_error() - C.echidna_destroy_prover(handle) - return ffi_response(422, '{"parsed":false,"prover":"${esc(prover)}","error":"${esc(err)}","content_length":${content.len}}') - } - } - return ffi_response(200, '{"parsed":true,"prover":"${esc(prover)}","goals":[{"id":"goal_0","target":"forall (n : nat), n + 0 = n","hypotheses":[{"name":"n","type":"nat"}]}],"context":{"theorems":["Nat.add_zero","Nat.add_succ","Nat.rec"],"axioms":[],"definitions":[]},"proof_script":[],"content_length":${content.len}}') -} - -fn handle_verify(req http.Request) http.Response { - if req.data.len == 0 { - return ffi_response(400, '{"error":"Request body required with prover and content fields"}') - } - prover := json_field(req.data, 'prover') - content := json_field(req.data, 'content') - if prover.len == 0 || content.len == 0 { - return ffi_response(400, '{"error":"Both prover and content fields required"}') - } - p := find_prover(prover) or { - return ffi_response(400, '{"error":"Unknown prover kind","kind":"${esc(prover)}"}') - } - if ffi_available { - handle := C.echidna_create_prover(p.ordinal) - if handle >= 0 { - // Parse first, then verify - parse_rc := C.echidna_parse_string(handle, content.str, usize(content.len)) - if parse_rc == 0 { - verify_rc := C.echidna_verify_proof(handle) - C.echidna_destroy_prover(handle) - verified := verify_rc == 1 - trust := if verified { 'Level4' } else { 'Level1' } - return ffi_response(200, '{"verified":${verified},"trust_level":"${trust}","message":"Proof ${if verified { 'verified' } else { 'failed' }} with ${trust} trust","prover":"${esc(prover)}","axiom_report":{"axiom":"none","danger_level":"Safe","occurrences":0,"source_locations":[]},"goals_remaining":0,"proof_time_ms":0}') - } - err := ffi_last_error() - C.echidna_destroy_prover(handle) - return ffi_response(422, '{"verified":false,"trust_level":"Level0","message":"Parse failed: ${esc(err)}","prover":"${esc(prover)}"}') - } - } - return ffi_response(200, '{"verified":true,"trust_level":"Level4","message":"Proof verified with Level4 trust","prover":"${esc(prover)}","axiom_report":{"axiom":"none","danger_level":"Safe","occurrences":0,"source_locations":[]},"certificate_hash":"sha3-256:a1b2c3d4e5f6789012345678abcdef0123456789abcdef0123456789abcdef01","goals_remaining":0,"proof_time_ms":47}') -} - -fn handle_apply_tactic(req http.Request, proof_id string) http.Response { - if req.data.len == 0 { - return ffi_response(400, '{"error":"Request body required with tactic field"}') - } - tactic := json_field(req.data, 'tactic') - if tactic.len == 0 { - return ffi_response(400, '{"error":"tactic field required (e.g. apply, intro, cases, rewrite, simp)"}') - } - if ffi_available { - handle := extract_handle_from_session(proof_id) - if handle >= 0 { - rc := C.echidna_apply_tactic(handle, tactic.str, usize(tactic.len)) - if rc == 0 { - return ffi_response(200, '{"success":true,"proof_id":"${esc(proof_id)}","tactic_applied":"${esc(tactic)}","new_state":{"goals":[],"proof_script":["${esc(tactic)}"],"goals_remaining":0},"error_message":null}') - } - err := ffi_last_error() - return ffi_response(422, '{"success":false,"proof_id":"${esc(proof_id)}","tactic_applied":"${esc(tactic)}","error_message":"${esc(err)}"}') - } - } - return ffi_response(200, '{"success":true,"proof_id":"${esc(proof_id)}","tactic_applied":"${esc(tactic)}","new_state":{"goals":[{"id":"goal_1","target":"0 = 0","hypotheses":[{"name":"n","type":"nat"},{"name":"IH","type":"n + 0 = n"}]}],"proof_script":["${esc(tactic)}"],"goals_remaining":1},"error_message":null}') -} - -fn handle_suggest_tactics(proof_id string) http.Response { - if ffi_available { - handle := extract_handle_from_session(proof_id) - if handle >= 0 { - mut buf := [1024]u8{} - mut buf_len := usize(1024) - rc := C.echidna_suggest_tactics(handle, 5, &buf[0], &buf_len) - if rc == 0 && buf_len > 0 { - raw := unsafe { tos(&buf[0], int(buf_len)) } - return ffi_response(200, '{"proof_id":"${esc(proof_id)}","suggestions_raw":${raw},"model":"echidna-tactic-v1.5","inference_time_ms":0}') - } - } - } - return ffi_response(200, '{"proof_id":"${esc(proof_id)}","suggestions":[{"tactic":"simp [Nat.add_zero]","confidence":0.94,"source":"neural_premise_selection"},{"tactic":"rfl","confidence":0.87,"source":"heuristic"},{"tactic":"induction n","confidence":0.72,"source":"neural_premise_selection"},{"tactic":"apply Nat.rec","confidence":0.65,"source":"library_search"},{"tactic":"omega","confidence":0.58,"source":"heuristic"}],"model":"echidna-tactic-v1.5","inference_time_ms":12}') -} - -fn handle_export(proof_id string) http.Response { - if ffi_available { - handle := extract_handle_from_session(proof_id) - if handle >= 0 { - mut buf := [4096]u8{} - mut buf_len := usize(4096) - rc := C.echidna_export_proof(handle, &buf[0], &buf_len) - if rc == 0 && buf_len > 0 { - exported := unsafe { tos(&buf[0], int(buf_len)) } - return ffi_response(200, '{"proof_id":"${esc(proof_id)}","format":"lean4","exported_content":"${esc(exported)}","content_length":${buf_len},"export_time_ms":0}') - } - } - } - return ffi_response(200, '{"proof_id":"${esc(proof_id)}","format":"lean4","exported_content":"theorem add_zero (n : Nat) : n + 0 = n := by\\n induction n with\\n | zero => rfl\\n | succ n ih => simp [Nat.add_succ, ih]","content_length":112,"export_time_ms":3}') -} - -fn handle_dispatch(req http.Request) http.Response { - if req.data.len == 0 { - return ffi_response(400, '{"error":"Request body required with config and proof fields"}') - } - proof := json_field(req.data, 'proof') - if proof.len == 0 { - return ffi_response(400, '{"error":"proof field required"}') - } - - // Extract config options if present - cross_check := json_field(req.data, 'cross_check') - prover := json_field_or(req.data, 'prover', 'lean') - - is_cross := cross_check == 'true' - provers_used := if is_cross { - '"${esc(prover)}","z3","cvc5"' - } else { - '"${esc(prover)}"' - } - trust := if is_cross { 'Level5' } else { 'Level4' } - - if ffi_available { - p := find_prover(prover) or { - return ffi_response(400, '{"error":"Unknown prover kind","kind":"${esc(prover)}"}') - } - handle := C.echidna_create_prover(p.ordinal) - if handle >= 0 { - parse_rc := C.echidna_parse_string(handle, proof.str, usize(proof.len)) - if parse_rc == 0 { - verify_rc := C.echidna_verify_proof(handle) - C.echidna_destroy_prover(handle) - verified := verify_rc == 1 - actual_trust := if verified && is_cross { - 'Level5' - } else if verified { - 'Level4' - } else { - 'Level1' - } - return ffi_response(200, '{"verified":${verified},"trust_level":"${actual_trust}","provers_used":[${provers_used}],"proof_time_ms":0,"goals_remaining":0,"axiom_report":{"axiom":"none","danger_level":"Safe","occurrences":0,"source_locations":[]},"message":"Proof dispatch complete with ${actual_trust} trust","cross_checked":${is_cross}}') - } - err := ffi_last_error() - C.echidna_destroy_prover(handle) - return ffi_response(422, '{"verified":false,"trust_level":"Level0","error":"${esc(err)}","cross_checked":${is_cross}}') - } - } - return ffi_response(200, '{"verified":true,"trust_level":"${trust}","provers_used":[${provers_used}],"proof_time_ms":142,"goals_remaining":0,"axiom_report":{"axiom":"none","danger_level":"Safe","occurrences":0,"source_locations":[]},"certificate_hash":"sha3-256:b4d7e2a1c9f80356124abc789def0123456789abcdef0123456789abcdef0142","message":"Proof verified with ${trust} trust","cross_checked":${is_cross}}') -} - -// --- Helpers --- - -fn stub_response(status_code int, body string) http.Response { - mut header := http.new_header(key: .content_type, value: 'application/json') - header.add_custom('X-Echidna-Mode', 'stub') or {} - return http.new_response( - status: unsafe { http.Status(status_code) } - header: header - body: body - ) -} - -fn esc(s string) string { - return s.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n').replace('\t', '\\t') -} - -fn json_field(data string, key string) string { - needle := '"${key}":' - idx := data.index(needle) or { return '' } - tail := data[idx + needle.len..].trim_space() - if tail.len == 0 || tail[0] != `"` { - return '' - } - end := tail[1..].index('"') or { return '' } - return tail[1..end + 1] -} - -fn json_field_or(data string, key string, default_val string) string { - val := json_field(data, key) - if val.len == 0 { - return default_val - } - return val -} - -fn json_field_int(data string, key string) int { - needle := '"${key}":' - idx := data.index(needle) or { return 0 } - tail := data[idx + needle.len..].trim_space() - if tail.len == 0 { - return 0 - } - mut end := 0 - for i, c in tail { - if c >= `0` && c <= `9` { - end = i + 1 - } else if end > 0 { - break - } - } - if end == 0 { - return 0 - } - return tail[..end].int() -} - -fn find_prover_by_index(idx int) ?ProverInfo { - provers := all_provers() - if idx < 0 || idx >= provers.len { - return none - } - return provers[idx] -} - -fn extract_handle_from_session(session_id string) int { - // Session IDs follow pattern: ses-- - parts := session_id.split('-') - if parts.len >= 3 { - return parts.last().int() - } - return -1 -} - -fn query_param(url string, key string) string { - qmark := url.index('?') or { return '' } - query := url[qmark + 1..] - for part in query.split('&') { - eq := part.index('=') or { continue } - if part[..eq] == key { - return part[eq + 1..] - } - } - return '' -} diff --git a/src/interfaces/v-adapter/tentacles.v b/src/interfaces/v-adapter/tentacles.v deleted file mode 100644 index fbf04b0e..00000000 --- a/src/interfaces/v-adapter/tentacles.v +++ /dev/null @@ -1,417 +0,0 @@ -// SPDX-License-Identifier: PMPL-1.0-or-later -// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -// -// ECHIDNA Tentacles REST API Gateway — Port 8300 -// -// Exposes 7-Tentacles agent operations via REST: -// -// GET /api/v1/health — tentacles system health -// GET /api/v1/version — tentacles version -// POST /api/v1/init — initialise agent system -// POST /api/v1/shutdown — shut down agent system -// GET /api/v1/agents — list all 7 agents with status -// GET /api/v1/agents/:id/status — agent status -// GET /api/v1/agents/:id/phase — agent OODA phase -// GET /api/v1/agents/:id/stage — agent reveal stage -// POST /api/v1/agents/:id/task — dispatch task to agent -// POST /api/v1/agents/:id/cancel — cancel agent task -// GET /api/v1/stage — get global stage -// PUT /api/v1/stage — set global stage -// GET /api/v1/events — poll agent events (JSON array) -// POST /api/v1/broadcast — broadcast from an agent -// -// Links against libechidna_tentacles.so (Zig FFI layer) when available. -// Falls back to stub responses with X-Tentacles-Mode: stub header when FFI is absent. - -module main - -import net.http -import time - -// --- Zig FFI extern declarations (libechidna_tentacles.so) --- - -#flag -L @VMODROOT/../../ffi/zig/zig-out/lib -#flag -lechidna_tentacles - -fn C.echidna_tentacles_init() int -fn C.echidna_tentacles_shutdown() -fn C.echidna_tentacles_agent_status(agent_id int) int -fn C.echidna_tentacles_agent_phase(agent_id int) int -fn C.echidna_tentacles_agent_stage(agent_id int) int -fn C.echidna_tentacles_dispatch_task(agent_id int, task_ptr &u8, task_len usize) int -fn C.echidna_tentacles_cancel_task(agent_id int) int -fn C.echidna_tentacles_set_global_stage(stage_id int) int -fn C.echidna_tentacles_get_global_stage() int -fn C.echidna_tentacles_poll_events(out_ptr &u8, out_len &usize) int -fn C.echidna_tentacles_event_count() int -fn C.echidna_tentacles_broadcast(source_id int, payload_ptr &u8, payload_len usize) int -fn C.echidna_tentacles_version() &u8 -fn C.echidna_tentacles_last_error() &u8 -fn C.echidna_tentacles_agent_count() int - -// Callback registration -fn C.echidna_tentacles_register_on_phase_change(cb fn (int, int, int)) int -fn C.echidna_tentacles_register_on_task_complete(cb fn (int, int)) int -fn C.echidna_tentacles_register_on_error(cb fn (int, int, &u8, usize)) int - -// --- FFI availability detection --- - -mut ffi_available := false - -fn init_tentacles_ffi() { - unsafe { - ver := C.echidna_tentacles_version() - if ver != nil { - ffi_available = true - // Register event callbacks - C.echidna_tentacles_register_on_phase_change(on_phase_change) - C.echidna_tentacles_register_on_task_complete(on_task_complete) - C.echidna_tentacles_register_on_error(on_tentacles_error) - } - } -} - -// --- Event buffer --- - -struct TentaclesEvent { - kind string - agent_id int - detail string - timestamp i64 -} - -mut event_buffer := []TentaclesEvent{} -const max_events = 1000 - -fn push_tentacles_event(kind string, agent_id int, detail string) { - if event_buffer.len >= max_events { - event_buffer.delete(0) - } - event_buffer << TentaclesEvent{ - kind: kind - agent_id: agent_id - detail: detail - timestamp: time.now().unix() - } -} - -// --- Callback handlers --- - -fn on_phase_change(agent_id int, old_phase int, new_phase int) { - push_tentacles_event('phase_change', agent_id, '${old_phase}->${new_phase}') -} - -fn on_task_complete(agent_id int, result_code int) { - push_tentacles_event('task_complete', agent_id, 'rc=${result_code}') -} - -fn on_tentacles_error(agent_id int, error_code int, msg_ptr &u8, msg_len usize) { - msg := unsafe { tos(msg_ptr, int(msg_len)) } - push_tentacles_event('error', agent_id, 'code=${error_code}: ${msg}') -} - -// --- Agent name helper --- - -fn agent_name(id int) string { - return match id { - 0 { 'Red (Parser)' } - 1 { 'Orange (Concurrency)' } - 2 { 'Yellow (Type System)' } - 3 { 'Green (AST Architect)' } - 4 { 'Blue (Auditor)' } - 5 { 'Indigo (Metaprogrammer)' } - 6 { 'Violet (Governance)' } - else { 'Unknown' } - } -} - -fn stage_name(id int) string { - return match id { - 0 { 'Cuttle' } - 1 { 'Squidlet' } - 2 { 'Duet' } - 3 { 'Octopus' } - else { 'Unknown' } - } -} - -fn phase_name(id int) string { - return match id { - 0 { 'Observe' } - 1 { 'Orient' } - 2 { 'Decide' } - 3 { 'Act' } - else { 'Unknown' } - } -} - -fn status_name(id int) string { - return match id { - 0 { 'idle' } - 1 { 'busy' } - 2 { 'error' } - 3 { 'disabled' } - else { 'unknown' } - } -} - -// --- Response helpers --- - -fn tentacles_response(mut res http.Response, status int, body string) { - res.set_status(http.Status.from_int(status)) - res.header.set(.content_type, 'application/json') - if ffi_available { - res.header.set_custom('X-Tentacles-Mode', 'live') or {} - } else { - res.header.set_custom('X-Tentacles-Mode', 'stub') or {} - } - res.body = body -} - -fn ffi_buf_call(f fn (&u8, &usize) int) string { - mut buf := [65536]u8{} - mut buf_len := usize(65536) - rc := f(&buf[0], &buf_len) - if rc == 0 && buf_len > 0 { - unsafe { return tos(&buf[0], int(buf_len)) } - } - return '{"error":"ffi call failed","code":${rc}}' -} - -// --- Route handlers --- - -fn handle_health(mut res http.Response) { - if ffi_available { - ver := unsafe { cstring_to_vstring(C.echidna_tentacles_version()) } - count := C.echidna_tentacles_agent_count() - active := C.echidna_tentacles_event_count() - tentacles_response(mut res, 200, '{"status":"ok","version":"${ver}","agentCount":${count},"activeAgents":${active}}') - } else { - tentacles_response(mut res, 200, '{"status":"stub","version":"stub","agentCount":7,"activeAgents":0}') - } -} - -fn handle_version(mut res http.Response) { - if ffi_available { - ver := unsafe { cstring_to_vstring(C.echidna_tentacles_version()) } - tentacles_response(mut res, 200, '{"version":"${ver}"}') - } else { - tentacles_response(mut res, 200, '{"version":"stub"}') - } -} - -fn handle_init(mut res http.Response) { - if ffi_available { - rc := C.echidna_tentacles_init() - if rc == 0 { - tentacles_response(mut res, 200, '{"status":"initialised"}') - } else { - tentacles_response(mut res, 500, '{"error":"init failed","code":${rc}}') - } - } else { - tentacles_response(mut res, 200, '{"status":"stub_initialised"}') - } -} - -fn handle_shutdown(mut res http.Response) { - if ffi_available { - C.echidna_tentacles_shutdown() - } - tentacles_response(mut res, 200, '{"status":"shutdown"}') -} - -fn handle_agents(mut res http.Response) { - if ffi_available { - json := ffi_buf_call(C.echidna_tentacles_poll_events) - tentacles_response(mut res, 200, json) - } else { - mut agents := '[' - for i in 0 .. 7 { - if i > 0 { agents += ',' } - agents += '{"id":${i},"name":"${agent_name(i)}","status":"idle","phase":"observe","stage":"cuttle"}' - } - agents += ']' - tentacles_response(mut res, 200, agents) - } -} - -fn handle_agent_status(agent_id int, mut res http.Response) { - if ffi_available { - status := C.echidna_tentacles_agent_status(agent_id) - if status < 0 { - tentacles_response(mut res, 400, '{"error":"invalid agent","code":${status}}') - } else { - tentacles_response(mut res, 200, '{"id":${agent_id},"name":"${agent_name(agent_id)}","status":"${status_name(status)}"}') - } - } else { - tentacles_response(mut res, 200, '{"id":${agent_id},"name":"${agent_name(agent_id)}","status":"idle"}') - } -} - -fn handle_agent_phase(agent_id int, mut res http.Response) { - if ffi_available { - phase := C.echidna_tentacles_agent_phase(agent_id) - if phase < 0 { - tentacles_response(mut res, 400, '{"error":"invalid agent","code":${phase}}') - } else { - tentacles_response(mut res, 200, '{"id":${agent_id},"phase":"${phase_name(phase)}"}') - } - } else { - tentacles_response(mut res, 200, '{"id":${agent_id},"phase":"observe"}') - } -} - -fn handle_dispatch_task(agent_id int, body string, mut res http.Response) { - if ffi_available { - rc := unsafe { C.echidna_tentacles_dispatch_task(agent_id, body.str, usize(body.len)) } - if rc == 0 { - tentacles_response(mut res, 200, '{"status":"dispatched","agent":${agent_id}}') - } else { - tentacles_response(mut res, 400, '{"error":"dispatch failed","code":${rc}}') - } - } else { - tentacles_response(mut res, 200, '{"status":"stub_dispatched","agent":${agent_id}}') - } -} - -fn handle_cancel_task(agent_id int, mut res http.Response) { - if ffi_available { - rc := C.echidna_tentacles_cancel_task(agent_id) - if rc == 0 { - tentacles_response(mut res, 200, '{"status":"cancelled","agent":${agent_id}}') - } else { - tentacles_response(mut res, 400, '{"error":"cancel failed","code":${rc}}') - } - } else { - tentacles_response(mut res, 200, '{"status":"stub_cancelled","agent":${agent_id}}') - } -} - -fn handle_get_stage(mut res http.Response) { - if ffi_available { - stage := C.echidna_tentacles_get_global_stage() - tentacles_response(mut res, 200, '{"stage":"${stage_name(stage)}","stageId":${stage}}') - } else { - tentacles_response(mut res, 200, '{"stage":"cuttle","stageId":0}') - } -} - -fn handle_set_stage(body string, mut res http.Response) { - // Expect body like {"stageId": 2} - stage_id := body.find_between('"stageId":', '}').trim_space().int() - if ffi_available { - rc := C.echidna_tentacles_set_global_stage(stage_id) - if rc == 0 { - tentacles_response(mut res, 200, '{"status":"stage_set","stage":"${stage_name(stage_id)}"}') - } else { - tentacles_response(mut res, 400, '{"error":"invalid stage","code":${rc}}') - } - } else { - tentacles_response(mut res, 200, '{"status":"stub_stage_set","stage":"${stage_name(stage_id)}"}') - } -} - -fn handle_events(mut res http.Response) { - mut json := '[' - for i, evt in event_buffer { - if i > 0 { json += ',' } - json += '{"kind":"${evt.kind}","agentId":${evt.agent_id},"detail":"${evt.detail}","timestamp":${evt.timestamp}}' - } - json += ']' - tentacles_response(mut res, 200, json) -} - -fn handle_broadcast(body string, mut res http.Response) { - // Expect body like {"sourceId": 0, "payload": "..."} - source_id := body.find_between('"sourceId":', ',').trim_space().int() - if ffi_available { - rc := unsafe { C.echidna_tentacles_broadcast(source_id, body.str, usize(body.len)) } - if rc == 0 { - tentacles_response(mut res, 200, '{"status":"broadcast_sent","source":${source_id}}') - } else { - tentacles_response(mut res, 400, '{"error":"broadcast failed","code":${rc}}') - } - } else { - push_tentacles_event('broadcast', source_id, body) - tentacles_response(mut res, 200, '{"status":"stub_broadcast","source":${source_id}}') - } -} - -// --- Main server --- - -fn main() { - init_tentacles_ffi() - - mut server := http.Server.init(.{ - .addr = ':8300' - .handler = fn (req http.Request, mut res http.Response) { - path := req.url.path - method := req.method - - // CORS headers - res.header.set(.access_control_allow_origin, '*') - res.header.set(.access_control_allow_methods, 'GET, POST, PUT, OPTIONS') - res.header.set(.access_control_allow_headers, 'Content-Type') - - if method == .options { - res.set_status(.no_content) - return - } - - match true { - path == '/api/v1/health' && method == .get { - handle_health(mut res) - } - path == '/api/v1/version' && method == .get { - handle_version(mut res) - } - path == '/api/v1/init' && method == .post { - handle_init(mut res) - } - path == '/api/v1/shutdown' && method == .post { - handle_shutdown(mut res) - } - path == '/api/v1/agents' && method == .get { - handle_agents(mut res) - } - path == '/api/v1/stage' && method == .get { - handle_get_stage(mut res) - } - path == '/api/v1/stage' && method == .put { - handle_set_stage(req.body, mut res) - } - path == '/api/v1/events' && method == .get { - handle_events(mut res) - } - path == '/api/v1/broadcast' && method == .post { - handle_broadcast(req.body, mut res) - } - path.starts_with('/api/v1/agents/') { - parts := path.split('/') - if parts.len >= 5 { - agent_id := parts[4].int() - if parts.len >= 6 { - match parts[5] { - 'status' { handle_agent_status(agent_id, mut res) } - 'phase' { handle_agent_phase(agent_id, mut res) } - 'task' { handle_dispatch_task(agent_id, req.body, mut res) } - 'cancel' { handle_cancel_task(agent_id, mut res) } - else { tentacles_response(mut res, 404, '{"error":"unknown endpoint"}') } - } - } else { - handle_agent_status(agent_id, mut res) - } - } else { - tentacles_response(mut res, 400, '{"error":"missing agent id"}') - } - } - else { - tentacles_response(mut res, 404, '{"error":"not found","path":"${path}"}') - } - } - } - }) - - println('ECHIDNA Tentacles REST adapter listening on :8300') - server.listen_and_serve() -} diff --git a/src/interfaces/v-adapter/typell.v b/src/interfaces/v-adapter/typell.v deleted file mode 100644 index e32799f6..00000000 --- a/src/interfaces/v-adapter/typell.v +++ /dev/null @@ -1,264 +0,0 @@ -// SPDX-License-Identifier: PMPL-1.0-or-later -// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -// -// ECHIDNA TypeLL REST API Gateway — Port 7800 -// -// Exposes TypeLL type-level computation operations via REST: -// -// GET /api/v1/health — TypeLL server health -// GET /api/v1/status — connection status -// GET /api/v1/version — TypeLL version -// POST /api/v1/connect — connect to TypeLL server -// POST /api/v1/disconnect — disconnect from TypeLL -// POST /api/v1/check — type-check an expression -// POST /api/v1/infer — infer the type of an expression -// POST /api/v1/refine — apply refinement types -// POST /api/v1/compute — evaluate a type-level computation -// GET /api/v1/signatures — list available type signatures -// GET /api/v1/universes — get type universe hierarchy -// -// Links against libechidna_typell.so (Zig FFI layer) when available. -// Falls back to stub responses with X-TypeLL-Mode: stub header when FFI is absent. - -module main - -import net.http -import time - -// --- Zig FFI extern declarations (libechidna_typell.so) --- - -#flag -L @VMODROOT/../../ffi/zig/zig-out/lib -#flag -lechidna_typell - -fn C.echidna_typell_connect(config_ptr &u8, config_len usize) int -fn C.echidna_typell_disconnect() -fn C.echidna_typell_status() int -fn C.echidna_typell_health(out_ptr &u8, out_len &usize) int -fn C.echidna_typell_check(expr_ptr &u8, expr_len usize, ctx_ptr &u8, ctx_len usize, out_ptr &u8, out_len &usize) int -fn C.echidna_typell_infer(expr_ptr &u8, expr_len usize, out_ptr &u8, out_len &usize) int -fn C.echidna_typell_refine(spec_ptr &u8, spec_len usize, cons_ptr &u8, cons_len usize, out_ptr &u8, out_len &usize) int -fn C.echidna_typell_compute(term_ptr &u8, term_len usize, out_ptr &u8, out_len &usize) int -fn C.echidna_typell_list_signatures(out_ptr &u8, out_len &usize) int -fn C.echidna_typell_universes(out_ptr &u8, out_len &usize) int -fn C.echidna_typell_version() &u8 -fn C.echidna_typell_last_error() &u8 - -// --- FFI availability detection --- - -mut ffi_available := false - -fn init_typell_ffi() { - unsafe { - ver := C.echidna_typell_version() - if ver != nil { - ffi_available = true - } - } -} - -// --- Response helpers --- - -fn typell_response(mut res http.Response, status int, body string) { - res.set_status(http.Status.from_int(status)) - res.header.set(.content_type, 'application/json') - if ffi_available { - res.header.set_custom('X-TypeLL-Mode', 'live') or {} - } else { - res.header.set_custom('X-TypeLL-Mode', 'stub') or {} - } - res.body = body -} - -fn ffi_buf_call(f fn (&u8, &usize) int) string { - mut buf := [65536]u8{} - mut buf_len := usize(65536) - rc := f(&buf[0], &buf_len) - if rc == 0 && buf_len > 0 { - unsafe { return tos(&buf[0], int(buf_len)) } - } - return '' -} - -// --- Request handler --- - -struct TypeLLHandler {} - -fn (mut handler TypeLLHandler) handle(req http.Request) http.Response { - mut res := http.Response{ - header: http.new_header_from_map({ - http.CommonHeader.content_type: 'application/json' - }) - } - - path := req.url.trim_right('/') - - match true { - path == '/api/v1/health' { - if ffi_available { - result := ffi_buf_call(C.echidna_typell_health) - if result.len > 0 { - typell_response(mut res, 200, result) - } else { - typell_response(mut res, 503, '{"status":"error","message":"TypeLL health check failed"}') - } - } else { - typell_response(mut res, 200, '{"status":"ok","version":"0.1.0","universes":4,"mode":"stub"}') - } - } - path == '/api/v1/status' { - if ffi_available { - status := C.echidna_typell_status() - status_name := match status { - 0 { 'disconnected' } - 1 { 'connecting' } - 2 { 'connected' } - else { 'error' } - } - typell_response(mut res, 200, '{"status":"${status_name}","status_code":${status}}') - } else { - typell_response(mut res, 200, '{"status":"stub","status_code":0}') - } - } - path == '/api/v1/version' { - if ffi_available { - unsafe { - ver := C.echidna_typell_version() - if ver != nil { - typell_response(mut res, 200, '{"version":"${cstring_to_vstring(ver)}"}') - } else { - typell_response(mut res, 200, '{"version":"unknown"}') - } - } - } else { - typell_response(mut res, 200, '{"version":"0.1.0","mode":"stub"}') - } - } - path == '/api/v1/connect' && req.method == .post { - if ffi_available { - config := req.data - if config.len > 0 { - rc := C.echidna_typell_connect(config.str, usize(config.len)) - if rc == 0 { - typell_response(mut res, 200, '{"connected":true}') - } else { - typell_response(mut res, 500, '{"connected":false,"error_code":${rc}}') - } - } else { - typell_response(mut res, 400, '{"error":"Empty configuration"}') - } - } else { - typell_response(mut res, 200, '{"connected":true,"mode":"stub"}') - } - } - path == '/api/v1/disconnect' && req.method == .post { - if ffi_available { - C.echidna_typell_disconnect() - } - typell_response(mut res, 200, '{"disconnected":true}') - } - path == '/api/v1/check' && req.method == .post { - body := req.data - if ffi_available && body.len > 0 { - ctx := '{}' - mut out_buf := [65536]u8{} - mut out_len := usize(65536) - rc := C.echidna_typell_check(body.str, usize(body.len), ctx.str, usize(ctx.len), &out_buf[0], &out_len) - if rc == 0 && out_len > 0 { - unsafe { typell_response(mut res, 200, tos(&out_buf[0], int(out_len))) } - } else { - typell_response(mut res, 422, '{"error":"Type check failed","error_code":${rc}}') - } - } else { - typell_response(mut res, 200, '{"well_typed":true,"type":"Nat -> Nat","mode":"stub"}') - } - } - path == '/api/v1/infer' && req.method == .post { - body := req.data - if ffi_available && body.len > 0 { - mut out_buf := [65536]u8{} - mut out_len := usize(65536) - rc := C.echidna_typell_infer(body.str, usize(body.len), &out_buf[0], &out_len) - if rc == 0 && out_len > 0 { - unsafe { typell_response(mut res, 200, tos(&out_buf[0], int(out_len))) } - } else { - typell_response(mut res, 422, '{"error":"Type inference failed","error_code":${rc}}') - } - } else { - typell_response(mut res, 200, '{"inferred_type":"Nat -> Nat","mode":"stub"}') - } - } - path == '/api/v1/refine' && req.method == .post { - body := req.data - if ffi_available && body.len > 0 { - cons := '[]' - mut out_buf := [65536]u8{} - mut out_len := usize(65536) - rc := C.echidna_typell_refine(body.str, usize(body.len), cons.str, usize(cons.len), &out_buf[0], &out_len) - if rc == 0 && out_len > 0 { - unsafe { typell_response(mut res, 200, tos(&out_buf[0], int(out_len))) } - } else { - typell_response(mut res, 422, '{"error":"Refinement failed","error_code":${rc}}') - } - } else { - typell_response(mut res, 200, '{"refined_type":"{n : Nat | n > 0}","mode":"stub"}') - } - } - path == '/api/v1/compute' && req.method == .post { - body := req.data - if ffi_available && body.len > 0 { - mut out_buf := [65536]u8{} - mut out_len := usize(65536) - rc := C.echidna_typell_compute(body.str, usize(body.len), &out_buf[0], &out_len) - if rc == 0 && out_len > 0 { - unsafe { typell_response(mut res, 200, tos(&out_buf[0], int(out_len))) } - } else { - typell_response(mut res, 422, '{"error":"Computation failed","error_code":${rc}}') - } - } else { - typell_response(mut res, 200, '{"result":"S (S (S Z))","type":"Nat","mode":"stub"}') - } - } - path == '/api/v1/signatures' { - if ffi_available { - result := ffi_buf_call(C.echidna_typell_list_signatures) - if result.len > 0 { - typell_response(mut res, 200, result) - } else { - typell_response(mut res, 500, '{"error":"Failed to list signatures"}') - } - } else { - typell_response(mut res, 200, '[{"name":"id","type":"forall A, A -> A"}]') - } - } - path == '/api/v1/universes' { - if ffi_available { - result := ffi_buf_call(C.echidna_typell_universes) - if result.len > 0 { - typell_response(mut res, 200, result) - } else { - typell_response(mut res, 500, '{"error":"Failed to get universes"}') - } - } else { - typell_response(mut res, 200, '{"levels":["Type","Type1","Type2","Typeω"],"mode":"stub"}') - } - } - else { - typell_response(mut res, 404, '{"error":"Not found","path":"${path}"}') - } - } - - return res -} - -fn main() { - init_typell_ffi() - mode := if ffi_available { 'live (libechidna_typell.so loaded)' } else { 'stub' } - eprintln('ECHIDNA TypeLL REST adapter starting on :7800 [${mode}]') - - mut handler := TypeLLHandler{} - mut server := http.Server{ - handler: handler - addr: ':7800' - } - server.listen_and_serve() -}