diff --git a/contrib/feature-requests/fr-2026-03-08--threephoenix--state-hub--offline-inbox-ingest.md b/contrib/feature-requests/fr-2026-03-08--threephoenix--state-hub--offline-inbox-ingest.md new file mode 100644 index 0000000..420fd74 --- /dev/null +++ b/contrib/feature-requests/fr-2026-03-08--threephoenix--state-hub--offline-inbox-ingest.md @@ -0,0 +1,88 @@ +--- +type: feature-request +id: fr-2026-03-08--threephoenix--state-hub--offline-inbox-ingest +target_org: threephoenix +target_repo: state-hub +status: draft +created: "2026-03-08" +source_repo: railiance-hosts +related_workstream_id: bf40b47e-be5b-4930-a7d2-362e76b943bb +--- + +# FR: Offline Inbox Ingest for Degraded-Mode Sessions + +## Problem + +When a Claude session runs on a remote host (e.g. HostEurope) without an active +SSH reverse tunnel, the State Hub MCP server is unreachable. Any progress events, +decision records, or task status updates that would normally be written via +`add_progress_event()` / `record_decision()` are silently lost unless the operator +manually replays them from their local machine after the session. + +This creates a gap in the audit trail and requires manual follow-up that is easy +to forget. + +## Proposed Solution + +### 1. Inbox convention in domain repos + +Domain repos write pending events to a `state-hub-inbox/` directory as YAML files: + +``` +state-hub-inbox/ + YYYY-MM-DD-.yaml +``` + +Each file is a structured event: + +```yaml +type: progress_event # or: decision, task_status_update +topic_id: +workstream_id: +event_type: milestone # matches add_progress_event() event_type values +summary: "..." +detail: {} +status: pending # state-hub sets to "ingested" after processing +recorded_at: "YYYY-MM-DD" +source_repo: +``` + +Files are committed to git so they are never lost. + +### 2. Ingest command in state-hub + +Add a `make ingest-inbox` target (or equivalent CLI command) that: + +1. Scans registered domain repos for `state-hub-inbox/*.yaml` files with `status: pending` +2. Calls the appropriate API endpoint for each (`add_progress_event`, `record_decision`, etc.) +3. Updates `status: ingested` and commits back (or opens a PR) so files are not replayed + +Alternatively, the MCP server could expose a `ingest_inbox_file(path)` tool that +Claude calls at session start when the hub is reachable, to drain any queued events +from previous degraded sessions. + +### 3. Session start behaviour + +At orientation (Step 1), after `get_domain_summary()` succeeds, Claude should: +- Glob `state-hub-inbox/*.yaml` in the current repo +- For each file with `status: pending`, call the appropriate write tool and mark it ingested + +This keeps the drain logic in Claude rather than requiring a separate make target. + +## Acceptance Criteria + +- `state-hub-inbox/` files with `status: pending` are reliably ingested on next + connected session with no manual intervention +- Ingested files are marked so they are not replayed +- Works for at minimum: `progress_event`, `decision`, `task_status_update` + +## Example + +See: `state-hub-inbox/2026-03-08-railiance01-bootstrap.yaml` in this repo — +the first real offline event that motivated this request. + +## Priority + +Medium. The workaround (manual replay) works but degrades auditability for +sessions run from remote hosts, which is the intended production workflow for +this project. diff --git a/state-hub-inbox/2026-03-08-railiance01-bootstrap.yaml b/state-hub-inbox/2026-03-08-railiance01-bootstrap.yaml new file mode 100644 index 0000000..e1e3bbb --- /dev/null +++ b/state-hub-inbox/2026-03-08-railiance01-bootstrap.yaml @@ -0,0 +1,18 @@ +type: progress_event +topic_id: ca369340-a64e-442e-98f1-a4fa7dc74a38 +workstream_id: bf40b47e-be5b-4930-a7d2-362e76b943bb +event_type: milestone +summary: "HostEurope server (Railiance01) bootstrapped and hardened" +detail: + tasks_completed: [T03, T04, T05] + server: Railiance01 + ip: 92.205.62.239 + changes: + - Extended base role with fail2ban, UFW k3s/Flannel rules, HISTCONTROL + - Fixed dynamic inventory script JSON format + - Added roles_path to ansible.cfg + - Bootstrapped server: UFW active, fail2ban running, SSH hardened + smoke_tests_passed: true +status: pending # state-hub sets to "ingested" after processing +recorded_at: "2026-03-08" +source_repo: railiance-hosts diff --git a/state-hub-inbox/README.md b/state-hub-inbox/README.md new file mode 100644 index 0000000..1e39656 --- /dev/null +++ b/state-hub-inbox/README.md @@ -0,0 +1,35 @@ +# state-hub-inbox + +Offline event queue for State Hub sessions run without an active reverse tunnel. + +When the State Hub MCP server is unreachable, Claude writes pending events here +as YAML files instead of dropping them. On the next connected session, these are +drained by calling the appropriate State Hub write tools and marking each file +`status: ingested`. + +## File format + +```yaml +type: progress_event # or: decision, task_status_update +topic_id: +workstream_id: +event_type: milestone +summary: "..." +detail: {} +status: pending # → ingested after processing +recorded_at: "YYYY-MM-DD" +source_repo: railiance-hosts +``` + +## Drain procedure (manual until automated) + +At session start, if `get_domain_summary()` succeeds, check for pending files: + +```bash +grep -l "status: pending" state-hub-inbox/*.yaml +``` + +For each, call `add_progress_event()` with the file's fields, then update +`status: pending` → `status: ingested` and commit. + +See also: `contrib/feature-requests/fr-2026-03-08--threephoenix--state-hub--offline-inbox-ingest.md`