hub-core/docs/statehub-inbox-read-pilot.md
tegwick 6fb5ce285c
Some checks failed
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / pytest-smoke (push) Failing after 2s
feat: add guarded State Hub inbox read projection pilot
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a06ed7-828d-7ca0-a8d4-0c3e5a0c4102
2026-09-05 10:37:09 +02:00

3.9 KiB
Raw Blame History

State Hub inbox read pilot

GET /ports/projections/statehub-inbox is an opt-in, authenticated read-only projection over the existing agent_messages model. It is distinct from /ports/messaging/messages, which accepts new message envelopes and does not implement State Hub's read/archive lifecycle.

Enable only after validating and importing a consistent source snapshot: HUB_CORE_STATEHUB_INBOX_READS=1, HUB_CORE_STATEHUB_INBOX_AGENT=state-hub. PostgreSQL and the configured operator bearer token are required. Disabled by default. No POST/PATCH/DELETE exists on the pilot endpoint. Imported history never emits messages or notifications.

The response is the State Hub MessageRead list shape. Parameters are to_agent (must match the configured pilot agent), exact from_agent, unread_only and limit (11000, default 50). Broadcast messages are included; archived messages are excluded. Results order by created_at descending, then ID for ties. Query transactions are read-only. Responses carry Cache-Control: no-store and X-Hub-Core-Read-Mode: snapshot-pilot.

This proves one literal agent inbox. It does not claim repository rename/alias resolution, unscoped fleet reads, thread lookup, continuous freshness, or writer cutover. The importer preserves thread roots so later history work need not reconstruct dangling identities.

Snapshot and import

Capture messages with one source PostgreSQL read-only, repeatable-read transaction. Preserve id, sender, recipient, subject, body, thread_id, read_at, archived_at and created_at, including archived/thread-root records. Keep the raw snapshot in private temporary storage; commit only hashes and counts.

The bundle has schema hub-core.statehub-inbox-snapshot.v1, source, captured_at, messages, count, and content_hash. Use snapshot_hash from hub_core.runtime.inbox_projection for canonical UTC/UUID normalization. import_snapshot(session, bundle, expected_hash=..., apply=False) validates; apply=True imports under a caller-owned transaction. The caller must commit only on success and roll back any exception. PostgreSQL apply locks the target message table before inspecting it. Different existing content refuses all changes; exact repeats are no-ops. Thread roots insert before replies. Duplicate IDs, malformed rows, hash/count mismatch, absent provenance, missing roots and cycles are rejected.

Use the existing workload database credential delivery inside the receiving pod; never export database credentials into evidence. No schema migration or new secret is required. This is a one-time migration helper, not a message sending API or an automated synchronization loop.

Pilot sequence and rollback

  1. Keep State Hub as sole message writer; capture source revision, instance, repeatable-read snapshot, data hash and expected filtered inbox results.
  2. Import only into an empty receiver, verify all row hashes/counts and replay as a no-op. Deploy the disabled reader image, then enable the private pilot.
  3. Compare the same frozen source rows with authenticated receiver responses: all/unread/sender/limit cases, broadcast inclusion and archive exclusion. Check unauthenticated rejection and absence of writes. A later live State Hub read is a freshness observation, not the original snapshot oracle.
  4. Disable the reader flag and verify the endpoint returns 404; re-enable and recheck the same hash. Retain the imported rows for review; do not delete history as a rollback technique. Runtime rollback uses the previous image.

Transport from State Hub must be explicitly admitted by rapp-core-hub's NetworkPolicy. Scope any pilot exception to State Hub API pod labels and the candidate port. No public Ingress or production message-writer change is part of this pilot. The operator bearer stays in its existing credential lane; production caller credentials and ongoing freshness require their own cutover.