atlas-iac/docs/hux/ADR-0001-hux-v1-contract-freeze.md
jenkins 6964a9d8c8 hermes(hux): close Wave A review findings in autonomy and the HTTP pipeline
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
2026-08-24 00:48:20 -03:00

5.8 KiB
Raw Blame History

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 13 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.