Commit Graph
4 Commits
Author SHA1 Message Date
vhaudiquet d067f4632f 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.
2026-11-06 23:05:46 +01:00
vhaudiquet aa9a68ab6d feat(contract): /v1/calendars joins the tool contract, one allowlist for both doors
The calendar table gets the same contract treatment as every other
data domain: a named route constant, three allowlist rows (GET, POST,
DELETE /v1/calendars/{name}), mock-router arms that enforce the
door's loudest rules — a named calendar, an ics with a URL — and
conformance cases (add, the invalid refused, listed, removed) that
run against the mock and the real daemon alike, so the two cannot
drift. The claw's auth middleware reads the same list, which is how
the rows land there with no claw-side code.

The spec names the doors and the pattern they establish: domains the
daemon reconfigures at runtime live in tables behind their routes,
validation runs at the door, and claw.yaml carries only what boot
needs.
2026-10-24 01:58:09 +02:00
vhaudiquet f96f8c4c1d 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.
2026-10-24 01:17:43 +02:00
vhaudiquet 75585f2dd3 feat(tools): the contract crate — allowlist, header, conformance suite
The client tools need one shared definition of what a tool may do,
and the daemon needs to enforce exactly that definition, not a
hand-copied copy of it. This crate is that definition, pure: no HTTP
client, no process — the allowlist as data, the version header as
constants, the request/response and error types, and the conformance
suite that drives any transport through the checks the contract
promises (the bearer gate, the version handshake, the allowlist's
denials, round-trips through every allowed route, one error shape).

`agent_allowed` is the whole security surface in one function:
method and path segments with `*` and `**`, exact otherwise — a
daemon's middleware asks it and nothing else, so the claw's scoping
and the tools' expectations cannot drift apart. The suite runs
against a mock router in this crate's tests and against the daemon's
real axum router in the claw's; same results is the definition of
implementing the contract.
2026-10-24 01:02:28 +02:00