feat(tools): the ingest credential — the event door, write-only
The agent token is deliberately barred from /v1/ingest: the events are the inbound channel, and the model must not feed it itself. But the same bar closes the door on every honest integration too — a sidecar poller with events to hand in had to hold the human token, which is wider than its job. This adds the missing class: a third bearer, the ingest token, answering the event door and nothing else. `ingest_allowed` is the whole credential in one function — one route row, `POST /v1/ingest` — symmetric with `agent_allowed` and suite-covered the same way: the mock and the daemon's real router both answer the new checks (a push succeeds with the contract header; a watch read and a consent answer are 403). The middleware classifier gains the third arm, boot refuses an ingest token equal to either other credential — one value playing two roles silently widens the scoped one — and the daemon provisions it to tool shells as `RHEMA_INGEST_TOKEN`, so a tool that only pushes events never sees the wider agent token at all. The door is the first half of the extensions story (rhema-tools spec §8): a self-timed sidecar is now a tier-1 tool with a credential narrow enough to hold.
This commit is contained in:
1 parent
98bc0fd623
commit
f96f8c4c1d
10 files changed
+210
-18
No files matched your search
@@ -6,7 +6,11 @@ use rhema_tools_contract::{CallRequest, MockClaw, conformance};
|
||||
|
||||
#[tokio::test]
|
||||
async fn the_suite_passes_against_the_mock_router() {
|
||||
let mock = MockClaw::new(Some("human-token"), Some("agent-token"));
|
||||
let mock = MockClaw::new(
|
||||
Some("human-token"),
|
||||
Some("agent-token"),
|
||||
Some("ingest-token"),
|
||||
);
|
||||
let result =
|
||||
conformance(|request: CallRequest| async { Ok(mock.call(request).await.unwrap()) }).await;
|
||||
assert_eq!(result, Ok(()), "the mock router speaks the contract");
|
||||
@@ -16,7 +20,7 @@ async fn the_suite_passes_against_the_mock_router() {
|
||||
async fn an_open_mock_fails_the_suite() {
|
||||
// No tokens: the agent-token guarantees the suite exists to check
|
||||
// are gone. The suite must say so, not sail through.
|
||||
let mock = MockClaw::new(None, None);
|
||||
let mock = MockClaw::new(None, None, None);
|
||||
let result =
|
||||
conformance(|request: CallRequest| async { Ok(mock.call(request).await.unwrap()) }).await;
|
||||
assert!(result.is_err(), "an open surface cannot pass the suite");
|
||||
|
||||
Reference in new issue
Block a user