diff --git a/Cargo.lock b/Cargo.lock index 302b562..685b066 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1156,7 +1156,7 @@ dependencies = [ [[package]] name = "model-connector" version = "0.1.0" -source = "git+https://eureka.gamecult.org/forge/GameCult/ModelConnector.git?rev=9db62027eb1e6a2f7a47fdfb3cb5f118c5b96da7#9db62027eb1e6a2f7a47fdfb3cb5f118c5b96da7" +source = "git+https://eureka.gamecult.org/forge/GameCult/ModelConnector.git?rev=c1802d85bee3ae04b0c5737563c8343c1fef515d#c1802d85bee3ae04b0c5737563c8343c1fef515d" dependencies = [ "aes-gcm", "base64", diff --git a/crates/mind-body/Cargo.toml b/crates/mind-body/Cargo.toml index 31b4d90..4f37524 100644 --- a/crates/mind-body/Cargo.toml +++ b/crates/mind-body/Cargo.toml @@ -33,7 +33,7 @@ cultnet-rs.workspace = true eureka-pipeline = { path = "../eureka-pipeline" } eureka-state = { path = "../eureka-state", default-features = false } huginn-mind = { path = "../huginn-mind" } -model-connector = { git = "https://eureka.gamecult.org/forge/GameCult/ModelConnector.git", rev = "9db62027eb1e6a2f7a47fdfb3cb5f118c5b96da7", package = "model-connector" } +model-connector = { git = "https://eureka.gamecult.org/forge/GameCult/ModelConnector.git", rev = "c1802d85bee3ae04b0c5737563c8343c1fef515d", package = "model-connector" } rmp-serde = "1" serde = { version = "1", features = ["derive"] } serde_json = "1" @@ -53,7 +53,7 @@ rustix = { version = "1", default-features = false, features = ["std", "net"] } [dev-dependencies] # The kit's serve loop and service, for the test that runs the connector client # against the real server with a fake provider behind it. -model-connector = { git = "https://eureka.gamecult.org/forge/GameCult/ModelConnector.git", rev = "9db62027eb1e6a2f7a47fdfb3cb5f118c5b96da7", package = "model-connector", features = ["daemon"] } +model-connector = { git = "https://eureka.gamecult.org/forge/GameCult/ModelConnector.git", rev = "c1802d85bee3ae04b0c5737563c8343c1fef515d", package = "model-connector", features = ["daemon"] } proc-macro2 = "1" quote = "1" syn = { version = "2", features = ["full"] } diff --git a/crates/mind-body/src/lease_client/lease_v2/README.md b/crates/mind-body/src/lease_client/lease_v2/README.md new file mode 100644 index 0000000..e62045e --- /dev/null +++ b/crates/mind-body/src/lease_client/lease_v2/README.md @@ -0,0 +1,16 @@ +# lease_v2 fixtures + +The exact bytes of the claude lease protocol v2: each file is one whole frame (a u32 +big-endian length, then the named-MessagePack map `{kind, value}`). + +- `request.bin`: `claude_lease_request`. +- `refusal_unknown_caller.bin`, `refusal_expired.bin`, `refusal_busy.bin`, + `refusal_provider_rejected.bin`: `claude_lease_refusal`; `kind` is the variant name as a + PascalCase string. +- `grant.bin`: `claude_lease_grant` with `lease_id` and `plan_quota` carrying `five_hour` and + `seven_day` windows (utilization_permille, resets_at_unix_ms, status, observed_at_unix_ms). + On the wire the grant travels with one SCM_RIGHTS fd; the file holds the frame only. + +Huginn's lease client vendors these files from this repository and tests against the same +bytes. Generator: `tests/lease_wire_golden.rs`; run it with `LEASE_V2_REGENERATE=1`, then +review the diff: a changed byte is a wire change. diff --git a/crates/mind-body/src/lease_client/lease_v2/grant.bin b/crates/mind-body/src/lease_client/lease_v2/grant.bin new file mode 100644 index 0000000..bc0a78d Binary files /dev/null and b/crates/mind-body/src/lease_client/lease_v2/grant.bin differ diff --git a/crates/mind-body/src/lease_client/lease_v2/refusal_busy.bin b/crates/mind-body/src/lease_client/lease_v2/refusal_busy.bin new file mode 100644 index 0000000..f6a0887 Binary files /dev/null and b/crates/mind-body/src/lease_client/lease_v2/refusal_busy.bin differ diff --git a/crates/mind-body/src/lease_client/lease_v2/refusal_expired.bin b/crates/mind-body/src/lease_client/lease_v2/refusal_expired.bin new file mode 100644 index 0000000..b058450 Binary files /dev/null and b/crates/mind-body/src/lease_client/lease_v2/refusal_expired.bin differ diff --git a/crates/mind-body/src/lease_client/lease_v2/refusal_provider_rejected.bin b/crates/mind-body/src/lease_client/lease_v2/refusal_provider_rejected.bin new file mode 100644 index 0000000..54bfcad Binary files /dev/null and b/crates/mind-body/src/lease_client/lease_v2/refusal_provider_rejected.bin differ diff --git a/crates/mind-body/src/lease_client/lease_v2/refusal_unknown_caller.bin b/crates/mind-body/src/lease_client/lease_v2/refusal_unknown_caller.bin new file mode 100644 index 0000000..1172467 Binary files /dev/null and b/crates/mind-body/src/lease_client/lease_v2/refusal_unknown_caller.bin differ diff --git a/crates/mind-body/src/lease_client/lease_v2/request.bin b/crates/mind-body/src/lease_client/lease_v2/request.bin new file mode 100644 index 0000000..fddaa37 Binary files /dev/null and b/crates/mind-body/src/lease_client/lease_v2/request.bin differ diff --git a/crates/mind-body/src/lease_client/tests.rs b/crates/mind-body/src/lease_client/tests.rs index aac8063..dc865b9 100644 --- a/crates/mind-body/src/lease_client/tests.rs +++ b/crates/mind-body/src/lease_client/tests.rs @@ -259,3 +259,46 @@ mod socket_wire { served.join().unwrap(); } } + +/// The connector published bytes of lease v2, vendored under `lease_v2/` from +/// ModelConnector `c1802d8` (`crates/claude-connector/tests/fixtures/lease_v2`). +/// The client decodes every one of them and encodes each back to the same bytes. +mod connector_goldens { + use super::*; + + const REQUEST: &[u8] = include_bytes!("lease_v2/request.bin"); + const GRANT: &[u8] = include_bytes!("lease_v2/grant.bin"); + const REFUSALS: [(&[u8], LeaseRefusalKind); 4] = [ + (include_bytes!("lease_v2/refusal_unknown_caller.bin"), LeaseRefusalKind::UnknownCaller), + (include_bytes!("lease_v2/refusal_expired.bin"), LeaseRefusalKind::Expired), + (include_bytes!("lease_v2/refusal_busy.bin"), LeaseRefusalKind::Busy), + (include_bytes!("lease_v2/refusal_provider_rejected.bin"), LeaseRefusalKind::ProviderRejected), + ]; + + #[test] + fn the_request_encodes_to_exactly_the_published_bytes() { + let Some(LeaseMessage::Request(request)) = decode_frame(REQUEST) else { panic!("request.bin is a request") }; + assert_eq!(request.expires_at_unix_ms, 0x0000_01a1_896d_3cc0); + assert_eq!(encode_frame(&LeaseMessage::Request(LeaseRequest { expires_at_unix_ms: 0x0000_01a1_896d_3cc0 })).unwrap(), REQUEST); + } + + #[test] + fn every_published_refusal_decodes_to_its_kind_and_encodes_back_to_its_bytes() { + for (bytes, kind) in REFUSALS { + let message = decode_frame(bytes).unwrap_or_else(|| panic!("{kind:?} decodes")); + let LeaseMessage::Refusal(refused) = &message else { panic!("{kind:?}: a refusal") }; + assert_eq!(refused.kind, kind); + assert_eq!(encode_frame(&message).unwrap(), bytes, "{kind:?} re-encodes to the connector bytes"); + } + } + + #[test] + fn the_published_grant_decodes_with_both_plan_windows_and_encodes_back_to_its_bytes() { + let message = decode_frame(GRANT).expect("grant.bin decodes"); + let LeaseMessage::Grant(grant) = &message else { panic!("a grant") }; + assert!(!grant.lease_id.is_empty()); + let windows: Vec<&str> = grant.plan_quota.iter().map(|plan| plan.window.as_str()).collect(); + assert_eq!(windows, ["five_hour", "seven_day"]); + assert_eq!(encode_frame(&message).unwrap(), GRANT); + } +}