refactor(tools): rename rhema-tools-contract to rhema-claw-tools-contract

The crate binds the claw to its client tools, not tools at large,
so it carries the claw's name: package, lib and dep names, the
mock's binary, and the conformance suite's use paths. Specs and
README follow. The tools/contract directory keeps its name, and
no reference to the old name remains.
This commit is contained in:
vhaudiquet committed 2026-11-06 23:05:46 +01:00
1 parent 9e132fdf71
commit d067f4632f
11 files changed
+91 -76

No files matched your search

+1 -1
View File
@@ -49,7 +49,7 @@ rhema-rsd = { path = "../rhema-rsd" }
# The tool contract: the agent-token allowlist and version the auth # The tool contract: the agent-token allowlist and version the auth
# middleware enforces (rhema-tools spec). The arrow runs claw -> # middleware enforces (rhema-tools spec). The arrow runs claw ->
# contract only — never the reverse. # contract only — never the reverse.
rhema-tools-contract = { path = "../../tools/contract" } rhema-claw-tools-contract = { path = "../../tools/contract" }
# The resident tier's durable memory: its tools join the resolver chain # The resident tier's durable memory: its tools join the resolver chain
rhema-memory = { path = "../rhema-memory" } rhema-memory = { path = "../rhema-memory" }
# The shared ModelCard the backend registry's listings return # The shared ModelCard the backend registry's listings return
+11 -7
View File
@@ -42,13 +42,13 @@ use axum::routing::put;
use axum::routing::{delete, get, post}; use axum::routing::{delete, get, post};
use axum::{Router, middleware}; use axum::{Router, middleware};
use rhema_autonomous_agent::Driver; use rhema_autonomous_agent::Driver;
use rhema_claw_tools_contract::Auth;
use rhema_claw_tools_contract::CONTRACT_HEADER;
use rhema_claw_tools_contract::CONTRACT_VERSION;
use rhema_core::ImageMediaType; use rhema_core::ImageMediaType;
use rhema_rsd::{ use rhema_rsd::{
DispatchError, Dispatcher, IngestResult, KeyRecord, KeyState, ResourceKey, SqliteStore, Store, DispatchError, Dispatcher, IngestResult, KeyRecord, KeyState, ResourceKey, SqliteStore, Store,
}; };
use rhema_tools_contract::Auth;
use rhema_tools_contract::CONTRACT_HEADER;
use rhema_tools_contract::CONTRACT_VERSION;
use serde::{Deserialize, Deserializer}; use serde::{Deserialize, Deserializer};
use serde_json::{Value, json}; use serde_json::{Value, json};
use tokio::sync::broadcast::error::RecvError; use tokio::sync::broadcast::error::RecvError;
@@ -401,8 +401,10 @@ async fn require_auth(State(state): State<ClawState>, request: Request, next: Ne
) )
.into_response(); .into_response();
} }
if rhema_tools_contract::agent_allowed(request.method().as_str(), request.uri().path()) if rhema_claw_tools_contract::agent_allowed(
{ request.method().as_str(),
request.uri().path(),
) {
next.run(request).await next.run(request).await
} else { } else {
( (
@@ -427,8 +429,10 @@ async fn require_auth(State(state): State<ClawState>, request: Request, next: Ne
) )
.into_response(); .into_response();
} }
if rhema_tools_contract::ingest_allowed(request.method().as_str(), request.uri().path()) if rhema_claw_tools_contract::ingest_allowed(
{ request.method().as_str(),
request.uri().path(),
) {
next.run(request).await next.run(request).await
} else { } else {
( (
+38 -36
View File
@@ -1483,42 +1483,44 @@ async fn the_surface_speaks_the_tool_contract() {
.expect("the daemon assembles"); .expect("the daemon assembles");
let base = serve(&daemon).await; let base = serve(&daemon).await;
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let result = rhema_tools_contract::conformance(|request: rhema_tools_contract::CallRequest| { let result = rhema_claw_tools_contract::conformance(
let client = client.clone(); |request: rhema_claw_tools_contract::CallRequest| {
let base = base.clone(); let client = client.clone();
async move { let base = base.clone();
let method = reqwest::Method::from_bytes(request.method.as_bytes()) async move {
.map_err(|e| e.to_string())?; let method = reqwest::Method::from_bytes(request.method.as_bytes())
let builder = client.request(method, format!("{base}{}", request.path)); .map_err(|e| e.to_string())?;
let builder = match request.auth { let builder = client.request(method, format!("{base}{}", request.path));
rhema_tools_contract::Auth::None => builder, let builder = match request.auth {
rhema_tools_contract::Auth::Human => { rhema_claw_tools_contract::Auth::None => builder,
builder.header(reqwest::header::AUTHORIZATION, "Bearer human-token") rhema_claw_tools_contract::Auth::Human => {
} builder.header(reqwest::header::AUTHORIZATION, "Bearer human-token")
rhema_tools_contract::Auth::Agent => { }
builder.header(reqwest::header::AUTHORIZATION, "Bearer agent-token") rhema_claw_tools_contract::Auth::Agent => {
} builder.header(reqwest::header::AUTHORIZATION, "Bearer agent-token")
rhema_tools_contract::Auth::Invalid => { }
builder.header(reqwest::header::AUTHORIZATION, "Bearer a-stranger") rhema_claw_tools_contract::Auth::Invalid => {
} builder.header(reqwest::header::AUTHORIZATION, "Bearer a-stranger")
rhema_tools_contract::Auth::Ingest => { }
builder.header(reqwest::header::AUTHORIZATION, "Bearer ingest-token") rhema_claw_tools_contract::Auth::Ingest => {
} builder.header(reqwest::header::AUTHORIZATION, "Bearer ingest-token")
}; }
let builder = match request.contract { };
Some(version) => builder.header("x-rhema-contract", version.to_string()), let builder = match request.contract {
None => builder, Some(version) => builder.header("x-rhema-contract", version.to_string()),
}; None => builder,
let builder = match request.body { };
Some(body) => builder.json(&body), let builder = match request.body {
None => builder, Some(body) => builder.json(&body),
}; None => builder,
let response = builder.send().await.map_err(|e| e.to_string())?; };
let status = response.status().as_u16(); let response = builder.send().await.map_err(|e| e.to_string())?;
let body = response.json().await.unwrap_or(serde_json::json!({})); let status = response.status().as_u16();
Ok(rhema_tools_contract::CallResponse { status, body }) let body = response.json().await.unwrap_or(serde_json::json!({}));
} Ok(rhema_claw_tools_contract::CallResponse { status, body })
}) }
},
)
.await; .await;
assert_eq!(result, Ok(()), "the daemon speaks the tool contract"); assert_eq!(result, Ok(()), "the daemon speaks the tool contract");
} }
+13 -13
View File
@@ -466,8 +466,8 @@ version = "0.1.0"
dependencies = [ dependencies = [
"axum", "axum",
"clap", "clap",
"rhema-claw-tools-contract",
"rhema-tools-common", "rhema-tools-common",
"rhema-tools-contract",
"serde", "serde",
"serde_json", "serde_json",
"tokio", "tokio",
@@ -525,18 +525,7 @@ dependencies = [
] ]
[[package]] [[package]]
name = "rhema-tools-common" name = "rhema-claw-tools-contract"
version = "0.1.0"
dependencies = [
"rhema-tools-contract",
"serde",
"serde_json",
"thiserror",
"ureq",
]
[[package]]
name = "rhema-tools-contract"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"serde", "serde",
@@ -544,6 +533,17 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "rhema-tools-common"
version = "0.1.0"
dependencies = [
"rhema-claw-tools-contract",
"serde",
"serde_json",
"thiserror",
"ureq",
]
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.23" version = "1.0.23"
+1 -1
View File
@@ -9,7 +9,7 @@ repository.workspace = true
authors.workspace = true authors.workspace = true
[dependencies] [dependencies]
rhema-tools-contract = { path = "../contract" } rhema-claw-tools-contract = { path = "../contract" }
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
thiserror.workspace = true thiserror.workspace = true
+1 -1
View File
@@ -7,7 +7,7 @@
use std::io::Write; use std::io::Write;
use rhema_tools_contract::{ApiError, CONTRACT_HEADER, CONTRACT_VERSION}; use rhema_claw_tools_contract::{ApiError, CONTRACT_HEADER, CONTRACT_VERSION};
use serde::Serialize; use serde::Serialize;
use serde_json::Value; use serde_json::Value;
+1 -1
View File
@@ -1,5 +1,5 @@
[package] [package]
name = "rhema-tools-contract" name = "rhema-claw-tools-contract"
description = "The wire contract between the rhema claw daemon and its thin client tools: routes, shapes, the agent-token allowlist, and the conformance suite" description = "The wire contract between the rhema claw daemon and its thin client tools: routes, shapes, the agent-token allowlist, and the conformance suite"
version.workspace = true version.workspace = true
edition.workspace = true edition.workspace = true
+19 -10
View File
@@ -2,7 +2,7 @@
//! The daemon's real router runs the same suite in its test tree — //! The daemon's real router runs the same suite in its test tree —
//! same results is the definition of implementing the contract. //! same results is the definition of implementing the contract.
use rhema_tools_contract::{CallRequest, MockClaw, conformance}; use rhema_claw_tools_contract::{CallRequest, MockClaw, conformance};
#[tokio::test] #[tokio::test]
async fn the_suite_passes_against_the_mock_router() { async fn the_suite_passes_against_the_mock_router() {
@@ -28,31 +28,40 @@ async fn an_open_mock_fails_the_suite() {
#[tokio::test] #[tokio::test]
async fn the_agent_allowlist_reads_the_documented_routes() { async fn the_agent_allowlist_reads_the_documented_routes() {
assert!(rhema_tools_contract::agent_allowed("GET", "/v1/watches")); assert!(rhema_claw_tools_contract::agent_allowed(
assert!(rhema_tools_contract::agent_allowed( "GET",
"/v1/watches"
));
assert!(rhema_claw_tools_contract::agent_allowed(
"DELETE", "DELETE",
"/v1/watches/github/acme/widgets" "/v1/watches/github/acme/widgets"
)); ));
assert!(rhema_tools_contract::agent_allowed( assert!(rhema_claw_tools_contract::agent_allowed(
"POST", "POST",
"/v1/notifications" "/v1/notifications"
)); ));
assert!(rhema_tools_contract::agent_allowed( assert!(rhema_claw_tools_contract::agent_allowed(
"DELETE", "DELETE",
"/v1/tasks/12" "/v1/tasks/12"
)); ));
assert!(!rhema_tools_contract::agent_allowed("POST", "/v1/ingest")); assert!(!rhema_claw_tools_contract::agent_allowed(
assert!(!rhema_tools_contract::agent_allowed( "POST",
"/v1/ingest"
));
assert!(!rhema_claw_tools_contract::agent_allowed(
"POST", "POST",
"/v1/consents/c-1" "/v1/consents/c-1"
)); ));
assert!(!rhema_tools_contract::agent_allowed("GET", "/v1/consents")); assert!(!rhema_claw_tools_contract::agent_allowed(
assert!(!rhema_tools_contract::agent_allowed( "GET",
"/v1/consents"
));
assert!(!rhema_claw_tools_contract::agent_allowed(
"DELETE", "DELETE",
"/v1/phones/p1" "/v1/phones/p1"
)); ));
assert!( assert!(
!rhema_tools_contract::agent_allowed("GET", "/v1/watches/github/acme/widgets"), !rhema_claw_tools_contract::agent_allowed("GET", "/v1/watches/github/acme/widgets"),
"only DELETE reaches subpaths of /v1/watches" "only DELETE reaches subpaths of /v1/watches"
); );
} }
+1 -1
View File
@@ -14,7 +14,7 @@ path = "src/main.rs"
[dependencies] [dependencies]
rhema-tools-common = { path = "../common" } rhema-tools-common = { path = "../common" }
rhema-tools-contract = { path = "../contract" } rhema-claw-tools-contract = { path = "../contract" }
clap.workspace = true clap.workspace = true
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
+2 -2
View File
@@ -5,7 +5,7 @@
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use rhema_tools_common::{Client, Config, fail, print}; use rhema_tools_common::{Client, Config, fail, print};
use rhema_tools_contract::NotificationPush; use rhema_claw_tools_contract::NotificationPush;
/// Push a notification to the user's devices. /// Push a notification to the user's devices.
#[derive(Parser)] #[derive(Parser)]
@@ -49,7 +49,7 @@ fn main() {
body, body,
device, device,
}; };
match client.call("POST", rhema_tools_contract::NOTIFICATIONS, Some(&push)) { match client.call("POST", rhema_claw_tools_contract::NOTIFICATIONS, Some(&push)) {
Ok(record) => print(&record), Ok(record) => print(&record),
Err(error) => fail(error), Err(error) => fail(error),
} }
+3 -3
View File
@@ -10,7 +10,7 @@ use axum::extract::State;
use axum::http::{HeaderMap, StatusCode}; use axum::http::{HeaderMap, StatusCode};
use axum::routing::get; use axum::routing::get;
use rhema_tools_common::{Client, Config, ToolError}; use rhema_tools_common::{Client, Config, ToolError};
use rhema_tools_contract::NotificationPush; use rhema_claw_tools_contract::NotificationPush;
use serde_json::{Value, json}; use serde_json::{Value, json};
/// What the fake daemon recorded, for the test to assert on. /// What the fake daemon recorded, for the test to assert on.
@@ -82,7 +82,7 @@ async fn a_push_rides_the_contract_and_prints_the_row() {
let row = client let row = client
.call( .call(
"POST", "POST",
rhema_tools_contract::NOTIFICATIONS, rhema_claw_tools_contract::NOTIFICATIONS,
Some(&NotificationPush { Some(&NotificationPush {
title: "CI is red".into(), title: "CI is red".into(),
body: "main failed".into(), body: "main failed".into(),
@@ -111,7 +111,7 @@ async fn a_refused_call_maps_to_denied() {
let error = client let error = client
.call( .call(
"POST", "POST",
rhema_tools_contract::NOTIFICATIONS, rhema_claw_tools_contract::NOTIFICATIONS,
Some(&NotificationPush { Some(&NotificationPush {
title: "x".into(), title: "x".into(),
body: "y".into(), body: "y".into(),