All checks were successful
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 12s
standards workspaces build + test / macOS energy backends (pull_request) Has been skipped
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 3m11s
standards workspaces build + test / mesh-rs (mesh/mesh-rs, -p mesh-engine-gguf -p mesh-openai --features mesh-engine-gguf/gguf,mesh-openai/server, mesh-rs, linux-arm64) (pull_request) Successful in 59s
standards workspaces build + test / joulehook-rs (joulehook/joulehook-rs, joulehook-rs, linux) (pull_request) Successful in 1m13s
The grant work (#294, #301, #306) was reachable only through the library. The reference HTTP server passed no grant to every request, had no way to learn which steward a grant must name, and no way to witness a receipt against the journal. Its binaries kept the journal in memory. It also read whatever request body length a client declared. - **Grants over HTTP.** A request's body carries its presentation in `jcp_presentation` (`presentation_in`). The presentation is signed for a request id, so a granted request is served as exactly the `ServeRequest` the body alone determines (`granted_serve_request`, plus a `_responses` variant). Its prompt-token figure stays the body's estimate rather than the server's tokenizer count, which a caller cannot reproduce. The body must name its own `metadata.joule_ceiling_uj`, since a server default would otherwise enter the id unseen. - **Endpoints.** `GET /v1/mesh/steward` returns the steward's did:key, the subject a grant must name. `POST /v1/mesh/witness`, with the `receipt` and `envelope_hex` a response carried, answers 200 when the journal holds the receipt and 409 when it does not. `Mesh::witness_wire` checks a receipt in that wire form: the envelope must verify under this steward and seal exactly that wire receipt. - **Statuses** (`http_status`): 401 when the caller is not the grant's issuer, 403 when the steward refuses the grant, 503 once it has stopped honouring grants, 500 for an unwritable journal, 400 otherwise. - **Journal in the binaries.** With `MESH_JOURNAL` set, the reference server example and serve_openai_falcon record to that file and replay it before serving, reporting any unresolved serve. - **Body cap.** A body declared over 4 MiB is refused (413) before it is read. - **Spec.** Mesh SPEC §2.3 describes the binding (informative). Checked on the build host: - mesh-rs: 81 tests pass. mesh-openai with the server feature passes 15, including an end-to-end socket test: learn the steward, grant to it, present over HTTP, witness the receipt, refuse a tampered one, refuse an oversize body. - The gguf and openai server features build, every Cargo.lock is current, and clippy is clean. - Mutations each fail tests: a server that drops the grant, a granted path that recounts tokens, a wire witness that skips the seal check, and no body cap. Co-Authored-By: Claude Opus 5.5 <[email protected]>
115 lines
5.3 KiB
Rust
115 lines
5.3 KiB
Rust
//! Run the Mesh OpenAI-compatible reference server — a real
|
||
//! `/v1/chat/completions` endpoint any OpenAI client (curl, the SDKs, vLLM-style
|
||
//! tooling) can drive, returning energy-metered serving + a sealed receipt.
|
||
//!
|
||
//! ```text
|
||
//! cargo run --example serve --features server -- 127.0.0.1:8088 &
|
||
//! curl -s localhost:8088/v1/chat/completions \
|
||
//! -d '{"model":"gpt-mini","messages":[{"role":"user","content":"hi"}],"max_tokens":8}'
|
||
//! ```
|
||
//!
|
||
//! **What a receipt's figure is.** Each request's `usage.energy` carries the
|
||
//! energy of the served span — the deterministic reference engine's work, in
|
||
//! this process (the HTTP handling around it is outside the span) — at the
|
||
//! grade of whatever read it, in `usage.energy.provenance` and in the sealed
|
||
//! `mesh/1` receipt. The meter is the one this build can read:
|
||
//!
|
||
//! - `--features server,metal` (macOS): `mesh_meter::ProcessMeter`, this
|
||
//! process's own CPU and GPU energy over the span. The engine runs in this
|
||
//! process and uses no GPU, so where the GPU counter is proved live its GPU
|
||
//! energy is exactly zero and the span covers the whole request: `HwShunt`,
|
||
//! the kernel's count of this process's CPU energy. That count is every
|
||
//! thread of this process, so a request served while another of its
|
||
//! threads works reads that work too. Where the GPU counter is not proved
|
||
//! live (a GPU busy with other work fails its probe) the span covers part
|
||
//! of the request and is sealed as an estimate of all of it, `Estimator`.
|
||
//! A serve too short for the CPU count to advance over it (this engine's
|
||
//! are microseconds) has no reading either, and is the estimator's figure
|
||
//! for the serve, `Estimator`.
|
||
//! - `--features server,hw-meter` (or `hw-meter-rapl`, `-nvml`, `-apple`):
|
||
//! `mesh_meter::HwMeter::detect`, the machine's counter, a reading only
|
||
//! where a live one covers the whole request (a RAPL package on Linux) and
|
||
//! `mesh_meter::EstimatorMeter` otherwise. A machine counter reads
|
||
//! everything the host does over the span, not this request alone.
|
||
//! - `--features server` alone: `mesh_meter::EstimatorMeter`, wall time × an
|
||
//! assumed draw, `Estimator`.
|
||
//!
|
||
//! No build seals a figure nothing read: a `mesh/1` receipt has no coverage
|
||
//! field, so `HwShunt` there claims a hardware reading of the whole request
|
||
//! (SPEC §3.3), and only a meter that read one may say so.
|
||
|
||
use std::net::TcpListener;
|
||
|
||
use ed25519_dalek::SigningKey;
|
||
use mesh_core::Mesh;
|
||
use mesh_engine::ReferenceEngine;
|
||
use mesh_meter::Meter;
|
||
use mesh_openai::{server, ChatBackend};
|
||
use mesh_router::BackendSpec;
|
||
use mesh_schema::Fidelity;
|
||
|
||
/// A trivial tokenizer bridge: bytes in, a deterministic note out. A real
|
||
/// deployment pairs the model's own tokenizer with the engine that serves it.
|
||
pub struct ByteBackend;
|
||
impl ChatBackend for ByteBackend {
|
||
fn encode(&self, text: &str) -> Vec<u32> {
|
||
text.bytes().map(u32::from).collect()
|
||
}
|
||
fn decode(&self, ids: &[u32]) -> String {
|
||
format!("[mesh reference engine served {} tokens]", ids.len())
|
||
}
|
||
}
|
||
|
||
/// The meter this build serves with: see the module docs for each feature's
|
||
/// meter and what its figures cover.
|
||
pub fn meter() -> Box<dyn Meter> {
|
||
#[cfg(feature = "metal")]
|
||
let meter: Box<dyn Meter> = Box::new(mesh_meter::ProcessMeter::new(
|
||
jouleclaw_backend_apple::gpu_liveness_load,
|
||
mesh_meter::SpanUses::CpuGpu,
|
||
));
|
||
#[cfg(all(feature = "hw-meter", not(feature = "metal")))]
|
||
let meter: Box<dyn Meter> = Box::new(mesh_meter::HwMeter::detect());
|
||
#[cfg(not(feature = "hw-meter"))]
|
||
let meter: Box<dyn Meter> = Box::new(mesh_meter::EstimatorMeter::default());
|
||
meter
|
||
}
|
||
|
||
/// The deterministic reference engine behind one steward-signed receipt
|
||
/// chain, metered by `meter`.
|
||
pub fn mesh(meter: &dyn Meter) -> Mesh<'_> {
|
||
let sk = SigningKey::from_bytes(&[8u8; 32]);
|
||
let mut mesh = Mesh::new(meter, sk, "did:example:mesh-demo");
|
||
mesh.register(
|
||
BackendSpec::new("tiny", "ssm-1b", Fidelity::F2, 50_000),
|
||
Box::new(ReferenceEngine::new("tiny", "ssm-1b", Fidelity::F2, 50_000, 32)),
|
||
);
|
||
mesh
|
||
}
|
||
|
||
fn main() -> std::io::Result<()> {
|
||
let addr = std::env::args().nth(1).unwrap_or_else(|| "127.0.0.1:8088".to_string());
|
||
let listener = TcpListener::bind(&addr)?;
|
||
let meter = meter();
|
||
eprintln!("mesh OpenAI server: http://{addr}/v1/chat/completions (meter: {})", meter.name());
|
||
let mut mesh = journalled(mesh(meter.as_ref()))?;
|
||
server::run(&listener, &mut mesh, &ByteBackend, 100_000_000)
|
||
}
|
||
|
||
/// With `MESH_JOURNAL` set, record grant decisions and seals in that file and
|
||
/// replay what it already holds before serving, so a restart gives no budget
|
||
/// back and the receipt chain continues (Mesh SPEC §2.2). Without it the
|
||
/// journal is in memory, which a steward that restarts must not rely on.
|
||
pub fn journalled(mesh: mesh_core::Mesh<'_>) -> std::io::Result<mesh_core::Mesh<'_>> {
|
||
let Some(path) = std::env::var_os("MESH_JOURNAL") else {
|
||
return Ok(mesh);
|
||
};
|
||
let (journal, recorded) = mesh_core::FileJournal::open(&path)?;
|
||
let mesh = mesh
|
||
.with_journal(journal, &recorded)
|
||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
|
||
for u in mesh.unresolved() {
|
||
eprintln!("unresolved: request {} on backend {}, debited {} µJ", u.request_id.to_hex(), u.backend, u.debited_uj);
|
||
}
|
||
Ok(mesh)
|
||
}
|