F1 policy writes and allow grants are human-surface only; F2 worker trust is confined to the hook allowlist and unexpected exceptions become audited 500 error records; F4 external side effects release only for the same run and argument hash; F6 the gate honours budget exhaustion; F8 only the gateway can vouch for an empty process registry and failed receipts can be superseded; F11/F12 receipt revision and unshipped card routes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RNPhwu2bsaRNg3DETSAZoM
92 lines
5.8 KiB
Markdown
92 lines
5.8 KiB
Markdown
# ADR-0001: freeze the hux.v1 contract at 1.0.0
|
||
|
||
Status: accepted, 2026-08-24. Owners: Claude (backend), Codex (integration).
|
||
|
||
## Decision
|
||
|
||
`services/hermes/contracts/hux/` is frozen as contract version `1.0.0`
|
||
(`VERSION`, `flags.json.contract_version`). It is the only shape any HUX
|
||
surface (Chat, Worker, Telegram, voice) codes against. The Python reference
|
||
implementation lives in `dockerfiles/hermes-hux-foundation/hux/`:
|
||
`contracts.py` validates records, `rules.py` holds the governance rules the
|
||
schemas cannot express.
|
||
|
||
Records carry their own version in `schema` (`hux.event.v1`). Wire fixtures
|
||
under `services/hermes/contracts/hux/examples/` are validated by
|
||
`testing/tests/test_hermes_hux_contract_schemas.py` and are the exchange
|
||
format with Codex: a change to a fixture is a change to the contract.
|
||
|
||
## What 1.0.0 covers
|
||
|
||
| Requirement | Where |
|
||
|---|---|
|
||
| Tenant and surface identity | `common.identity` (tenant_slot, hashed subject, surface, trust: router/relay/worker) — carried on every stored record that a caller can create |
|
||
| Capability negotiation, default-off flags | `identity.capabilities` (`GET /hux/v1/capabilities`), `flags.json`; a card is enabled only when its dependency chain is enabled (`rules.flag_enabled`) |
|
||
| Redacted activity events | `event`: `seq` (monotonic per conversation), `turn`, `correlation_id`, `idempotency_key`, replay by `after_seq`, kinds for decisions, delegation, failure/completion, cancellation, budget and side-effect gates; `detail` is allowlisted per kind and scrubbed |
|
||
| Memory | `memory`: provenance, `source`, `reason`, `sensitivity`, `topic`, `ttl`, `approval_mode` incl. `no_store`, `status` incl. `no_store`, edit via `supersedes`, delete via `forgotten`, retrieval removal via `retrievable=false`, revisioned |
|
||
| Projects, lineage, artifacts, versions, diffs, authorization | `project`, `conversation.branch`, `artifact.versions[]` immutable with `content_ref` hash, `diff_from`, `lineage`, `promotion`, `access`, `revision` |
|
||
| Autonomy | `permission.policy` (levels, grants, budgets: tokens/tool calls/wall clock/spend/subagents/delegations/scope), `approval` with `request.external`, `cancellation_receipt`, `budget_state` |
|
||
| Research | `citation.source/passage/citation/notebook` with classification, support verdict, notes, assumptions, unresolved questions, `dedupe_key` |
|
||
| Additive migrations, rollback readers | `identity.manifest` (`data_layout_version`, `min_reader_contract_version`); rules in "Compatibility" below |
|
||
|
||
## Wire conventions
|
||
|
||
- Identity is asserted by the trusted hop, never by the client body: the
|
||
chat router sets `X-Hermes-Tenant-Identity: slot-N`, `X-Hux-Subject:
|
||
usr_<hash>`, `X-Hux-Surface: chat`; the Telegram relay adds the relay key
|
||
and `trust: relay`; the Worker uses `trust: worker`. Requests without a
|
||
complete, well-formed identity are `401 unauthorized`. The service must
|
||
only be reachable from those hops (NetworkPolicy is Codex's to enforce).
|
||
- Mutations of revisioned records send `If-Match: <revision>`; a mismatch is
|
||
`409 conflict` with `hux.error.v1`. Creates accept `Idempotency-Key`; a
|
||
repeat returns the original record with `200` instead of a duplicate.
|
||
- Lists return `{"items": [...], "next": <cursor|null>}`. Event pages use
|
||
`after_seq`; the SSE stream sets `id:` to `seq` so `Last-Event-ID`
|
||
resumes exactly.
|
||
- Errors are always `hux.error.v1`. A disabled card answers `404 flag_off`
|
||
so an old client and a flag-off server look identical.
|
||
- Every read and mutation writes a `common.audit_outcome`.
|
||
|
||
## Compatibility rules
|
||
|
||
1. Within `1.x`: only add optional fields, enum values or new record kinds.
|
||
Never rename, remove, change a type, or make an optional field required.
|
||
2. Readers ignore unknown fields and unknown enum values they do not need;
|
||
a reader that needs a new enum value must check `contract_version`.
|
||
3. On-disk data is append-only per family; `data_layout_version` bumps only
|
||
for additive layout changes, and `min_reader_contract_version` says the
|
||
oldest service that can still read it. Rolling the service back to any
|
||
version ≥ `min_reader_contract_version` leaves data readable.
|
||
4. Anything outside 1–3 is `hux.v2`, served beside v1 until every surface
|
||
has moved.
|
||
|
||
## Revision 1.1.0 (2026-08-24, additive)
|
||
|
||
Raised after the three Wave A lanes reported. Adds `receipt` to
|
||
`evidence_ref.kind`, `unprocessable` (422) to error codes, optional
|
||
`revision` on source/passage/citation, optional `audit_stale` on the privacy
|
||
policy, and per-route body caps (artifact uploads up to 25 MiB content). The
|
||
argument hash a `once` approval is released against travels as
|
||
`request.evidence[] = {kind: "tool_call", id, hash}`; the agent hook must
|
||
send it or the gate can never release. Memory rules skip content-free
|
||
statuses (`no_store`, `forgotten`, `rejected`).
|
||
|
||
Review follow-up (same revision, still additive): `cancellation_receipt`
|
||
gains optional `revision`. Stored documents in every revisioned family carry
|
||
the store's `revision`, and a `failed_to_cancel` receipt is the one
|
||
non-terminal outcome: a later stop that the gateway (worker trust) confirms
|
||
supersedes it with a revision bump; an identical repeat replays. Only a
|
||
human actor (router/relay trust on chat, telegram or voice) may write a
|
||
policy or hold an `allow` grant; `trust: worker` reaches only the routes in
|
||
`hux.flags.WORKER_ROUTES` (SO-08). External side effects release only
|
||
against an approval for the same run and argument hash. Cards HUX-06,
|
||
HUX-09 and HUX-12 declare no routes until they ship.
|
||
|
||
## Consequences
|
||
|
||
- Codex codes UI against the fixtures, not against the running service.
|
||
- Claude's implementation cards (HUX-01/02/03/04/05/08/10/11) may add
|
||
optional fields but must ship the fixture change in the same commit.
|
||
- If Codex needs a shared-interface change, it lands as a `1.(x+1).0`
|
||
revision of the schemas and fixtures here, never as an informal edit.
|