Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
6983538
fetch: keep upstream error response bodies out of provider logs
georgewhewell Sep 23, 2026
7acb2a2
fetch: add paid HTTPS and shared attested work sessions
georgewhewell Sep 23, 2026
994e1ea
refactor: simplify paid Fetch interfaces and documentation
georgewhewell Sep 24, 2026
30ff4d7
Route HTTP API clients through Fetch with archives and telemetry
georgewhewell Sep 24, 2026
79e3161
Preserve HTTP semantics and streaming backpressure through Fetch
georgewhewell Sep 24, 2026
dac1a9d
Keep serving when gateway archives fail and report failures
georgewhewell Sep 24, 2026
7643c6f
Fix native credentials, streaming usage, and pooled request traces
georgewhewell Sep 24, 2026
70a5a70
fix(fetch): reuse HTTPS connections and preserve trace and archive he…
georgewhewell Sep 24, 2026
b288a6e
feat(gateway): route models across backends with session affinity
georgewhewell Sep 24, 2026
462fa52
fix(gateway): reclaim affinity for closed client connections
georgewhewell Sep 24, 2026
6205208
refactor: simplify gateway routing and provider helpers
georgewhewell Sep 24, 2026
f2a8c63
Route HTTP proxy through funded Fetch channels
georgewhewell Sep 24, 2026
5c3c9da
Validate paid funding early and batch ready Fetch prefixes
georgewhewell Sep 24, 2026
08d4efe
Observe paid channels independently of requests
georgewhewell Sep 25, 2026
048637c
Clarify local readiness checks in HTTP Fetch documentation
georgewhewell Sep 25, 2026
f77be46
Fix CI checks and preserve early RPC refusals
georgewhewell Sep 25, 2026
d8e5b80
Exercise the production paid input reader in CLI tests
georgewhewell Sep 25, 2026
e654802
Format the paid input regression test
georgewhewell Sep 25, 2026
1ebd758
Keep provider dispatch open for the life of a paid job
georgewhewell Sep 26, 2026
08ab36f
Validate paid-work configuration before executor side effects
georgewhewell Sep 26, 2026
8900370
Settle delivered Fetch results during journal recovery
georgewhewell Sep 26, 2026
4069dbf
Read ZDR store flags from decoded request bodies
georgewhewell Sep 26, 2026
07e3c81
Tighten HTTP fetch gateway edge cases
georgewhewell Sep 26, 2026
1a78445
Keep hop-by-hop headers out of the signed HTTP head
georgewhewell Sep 26, 2026
c3e9bbe
Tighten paid HTTP vocabulary and admission coherence
georgewhewell Sep 26, 2026
78cdc88
Skip the full-state clone for redundant stream releases
georgewhewell Sep 26, 2026
bc33d4b
Cover the executor paid fetch seam with tests
georgewhewell Sep 26, 2026
603dae1
Drop SDK alias shims in paid-work commands
georgewhewell Sep 26, 2026
366a291
Harden paid Fetch provider boundaries and capacity recovery
georgewhewell Sep 27, 2026
df129ca
Share the paid gateway pool across SDK hosts
georgewhewell Sep 28, 2026
d9407a2
Sort provider dependencies
georgewhewell Sep 28, 2026
420f5e7
Check typed file errors and run CLI tests in local CI
georgewhewell Sep 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
224 changes: 221 additions & 3 deletions Cargo.lock

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,11 @@ trusted app itself requires a platform-backed assurance.

## HTTP gateway

The gateway requires the same canonical causal-LM environment and an explicit
For upstream APIs, [HTTP Fetch routes](docs/http-gateway.md) preserve the vendor's
request, response and streaming formats. CLI gateways archive payloads by default;
`--zdr` or `x-hellas-zdr: true` disables application payload persistence.

The causal-LM gateway requires the same canonical environment and an explicit
presentation tokenizer. `--model` is only an API response label; when omitted,
the manifest ID is used.

Expand Down
2 changes: 1 addition & 1 deletion crates/chain/src/server/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ async fn bound_and_relay_transports_answer_from_one_node_state() {
.await
.expect("the second transport receives a prompt saturation result")
.expect_err("all sixteen node response permits are held by the first transport");
assert_eq!(overflow.code(), WireCode::ResourceExhausted);
assert_eq!(overflow.code(), WireCode::ResourceExhausted, "{overflow:?}");

blocker.release.add_permits(16);
for call in held {
Expand Down
8 changes: 6 additions & 2 deletions crates/chain/src/work_blocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ pub struct WorkBlocks<C>(C);
/// What one production clock step did for a mounted paid channel.
#[derive(Debug)]
pub struct PaidWorkClockAdvance {
/// The verified snapshot already read for settlement, reusable by admission.
pub snapshot: Option<WorkChannelSnapshot>,
/// The journaled start-or-response drive that ran first.
pub close: CloseProgress,
/// Submission outcome for a due adjudicated payment close.
Expand Down Expand Up @@ -158,6 +160,7 @@ where
.map_err(PaidWorkClockError::Snapshot)?
else {
return Ok(PaidWorkClockAdvance {
snapshot: None,
close,
adjudication: None,
bond_timeout: None,
Expand Down Expand Up @@ -194,6 +197,7 @@ where
};

Ok(PaidWorkClockAdvance {
snapshot: Some(snapshot),
close,
adjudication,
bond_timeout,
Expand Down Expand Up @@ -476,7 +480,7 @@ mod tests {
network: TEST_NETWORK,
policy_salt: SALT,
channel_policy: channel_policy(),
execution_policy: execution_policy(),
execution_policy: execution_policy().into(),
expected_payment_values: expected_values(),
min_omit_response_blocks: hellas_kernel::MIN_OMIT_RESPONSE_BLOCKS,
}
Expand Down Expand Up @@ -893,7 +897,7 @@ mod tests {
payment_terms: payment_terms(),
policy_salt: SALT,
channel_policy: channel_policy(),
execution_policy: execution_policy(),
execution_policy: execution_policy().into(),
expected_payment_values: expected_values(),
}) {
Ok(descriptor) => descriptor,
Expand Down
10 changes: 5 additions & 5 deletions crates/chain/src/work_e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ use hellas_rpc::{
use hellas_wire::mux::{MessagePipe, MuxConfig, MuxTransport, Role as MuxRole};
use hellas_wire::{DefaultClock, Dispatcher, StreamTransport as _, TransportContext};
use hellas_work::work::{
BackendFault, ClientEndpoint, CloseEndpoint, JobProposal, PaidEvaluateBackend, PaymentError,
BackendFault, ClientEndpoint, CloseEndpoint, JobProposal, PaidWorkBackend, PaymentError,
PreparedEvaluateInput, RunOutcome, WorkService, propose_work, run_accepted_work,
};
use hellas_work::work_close::{CloseProgress, TxSink, close_start};
Expand Down Expand Up @@ -396,7 +396,7 @@ fn provider_policy() -> ProviderChannelPolicy {
network: TEST_NETWORK,
policy_salt: SALT,
channel_policy: channel_policy(),
execution_policy: execution_policy(),
execution_policy: execution_policy().into(),
expected_payment_values: expected_values(),
min_omit_response_blocks: hellas_kernel::MIN_OMIT_RESPONSE_BLOCKS,
}
Expand All @@ -409,7 +409,7 @@ fn descriptor(allocations: &[(SettlementKey, u64)]) -> WorkChannelDescriptor {
payment_terms: payment_terms(allocations),
policy_salt: SALT,
channel_policy: channel_policy(),
execution_policy: execution_policy(),
execution_policy: execution_policy().into(),
expected_payment_values: expected_values(),
}) {
Ok(descriptor) => descriptor,
Expand Down Expand Up @@ -901,7 +901,7 @@ struct ProviderBackend {
prompt: Vec<u32>,
}

impl PaidEvaluateBackend for ProviderBackend {
impl PaidWorkBackend for ProviderBackend {
fn evaluate(
&self,
input: PreparedEvaluateInput,
Expand Down Expand Up @@ -1276,7 +1276,7 @@ async fn run_one_paid_job(devnet: &Devnet, opened: &mut Opened) -> u64 {
calls: Arc::clone(&client_calls),
};
let proposal = JobProposal {
prepared_input: prepared_input(),
prepared_input: prepared_input().into(),
deadlines: deadlines(),
};

Expand Down
6 changes: 5 additions & 1 deletion crates/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ apple-app-attest = []
# is driven from; without it the runner has a chain and no way to ask it
# anything.
node = [
"dep:hellas-sdk",
"hellas-sdk/paid-client",
"hellas-sdk/paid-provider",
"chain",
"dep:async-stream",
"dep:axum",
Expand All @@ -37,7 +40,7 @@ node = [
# This deliberately does not pull Catena into a client that only dials a node.
llm = ["hellas-client/evaluate", "dep:hellas-presentation"]
# HTTP gateway front-end (OpenAI/Anthropic/plain APIs).
gateway = ["llm", "dep:hellas-gateway"]
gateway = ["llm", "dep:hellas-gateway", "hellas-sdk?/paid-gateway"]
evaluate = [
"node",
"gateway",
Expand Down Expand Up @@ -101,6 +104,7 @@ hellas-rpc = { workspace = true, default-features = false, features = [
"work",
"host-control",
] }
hellas-sdk = { workspace = true, default-features = false, optional = true }
hellas-store = { workspace = true }
hellas-wire = { workspace = true, features = [
"unix",
Expand Down
17 changes: 13 additions & 4 deletions crates/cli/src/commands/environment/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@ fn build_rejects_oversized_settings_before_indexing_artifacts() {
.expect_err("oversized settings must be refused before artifact indexing");
let message = error.to_string();
assert!(message.contains("environment settings"), "{message}");
assert!(message.contains("byte limit"), "{message}");
assert_eq!(
error.downcast_ref::<std::io::Error>().unwrap().kind(),
std::io::ErrorKind::InvalidData
);
assert!(!output.exists());
}

Expand All @@ -103,16 +106,22 @@ fn inspect_rejects_oversized_environment_metadata() {
let error = inspect(&environment).expect_err("oversized environment must be refused");
let message = error.to_string();
assert!(message.contains("environment"), "{message}");
assert!(message.contains("byte limit"), "{message}");
assert_eq!(
error.downcast_ref::<std::io::Error>().unwrap().kind(),
std::io::ErrorKind::InvalidData
);
}

#[cfg(unix)]
#[test]
fn inspect_rejects_a_device_before_reading_from_it() {
let error =
inspect(Path::new("/dev/zero")).expect_err("environment metadata must be an ordinary file");
assert!(error.to_string().contains("failed to open environment"));
assert!(format!("{error:#}").contains("not a regular file"));
assert!(error.to_string().contains("environment"));
assert_eq!(
error.downcast_ref::<std::io::Error>().unwrap().kind(),
std::io::ErrorKind::InvalidInput
);
}

#[cfg(unix)]
Expand Down
5 changes: 4 additions & 1 deletion crates/cli/src/commands/fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,10 @@ mod tests {
std::fs::write(&path, vec![b' '; MAX_FETCH_REQUEST_BODY_BYTES + 1]).unwrap();

let error = load_payload_file(&path).expect_err("oversized payload must be refused");
assert!(error.to_string().contains("over the 1048576-byte limit"));
assert_eq!(
error.downcast_ref::<std::io::Error>().unwrap().kind(),
std::io::ErrorKind::InvalidData
);
}

#[cfg(unix)]
Expand Down
5 changes: 4 additions & 1 deletion crates/cli/src/commands/llm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,10 @@ mod tests {
let error = load_environment(&path, None)
.err()
.expect("oversize is refused");
assert!(error.to_string().contains("over the"));
assert_eq!(
error.downcast_ref::<std::io::Error>().unwrap().kind(),
std::io::ErrorKind::InvalidData
);
}

#[cfg(unix)]
Expand Down
21 changes: 6 additions & 15 deletions crates/cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
pub type CliResult<T = ()> = anyhow::Result<T>;

use anyhow::Context as _;
use std::io::Read as _;
use std::path::Path;
use std::time::Duration;

Expand Down Expand Up @@ -38,20 +37,12 @@ pub(crate) fn read_bounded_regular_file(
label: &str,
maximum: usize,
) -> CliResult<Vec<u8>> {
let file = hellas_store::open_regular_file(path)
.with_context(|| format!("failed to open {label} {}", path.display()))?;
let limit = u64::try_from(maximum).unwrap_or(u64::MAX).saturating_add(1);
let mut bytes = Vec::new();
file.take(limit)
.read_to_end(&mut bytes)
.with_context(|| format!("failed to read {label} {}", path.display()))?;
anyhow::ensure!(
bytes.len() <= maximum,
"{label} {} is {} bytes, over the {maximum}-byte limit",
path.display(),
bytes.len()
);
Ok(bytes)
hellas_private::read_bounded_regular_file(path, maximum).with_context(|| {
format!(
"failed to read {label} {} (limit {maximum} bytes)",
path.display()
)
})
}

pub(crate) fn http_client(request_timeout: Duration) -> reqwest::Client {
Expand Down
Loading
Loading