STATE-WP-0093: per-recipient broadcast receipts and standing notices (T01-T06).
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Build and Publish Multi-Context Image / build-and-push (push) Successful in 53s

Founder approved T01 on 2026-09-22 (D2, D3, D6 as recommended).
- T02: message_receipts table; kind/expires_at/supersedes_id on
  agent_messages; migration d7e8f9a0b1c2 archives existing broadcasts,
  leaves direct messages untouched, reversible.
- T03: reader-aware mark-read (unattributed broadcast mark-read is a
  metered, deprecated no-op), delivery receipts on the scoped unread inbox,
  POST /messages/{id}/ack, news/standing kinds, expiry, supersede, broadcast
  archive no longer stamps read_at, reply writes the replier's receipt.
- T04 (state-hub part): Codex MCP reader param and acknowledge_notice;
  hub-core part handed off (message 69fc387c).
- T05: GET /messages/notices, standing_notices in /state/summary,
  dashboard standing-notices panel.
- T06: 17 new tests; full suite green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 63291@bnt-lap001
Assistant-Session: 8bd77868-ca68-4f49-bb1e-d539ecc0d703
This commit is contained in:
tegwick 2026-09-22 00:26:33 +02:00
parent 740068fcf1
commit ef541f58cf
18 changed files with 1256 additions and 52 deletions

View file

@ -4,12 +4,12 @@ type: workplan
title: "Per-recipient broadcast receipts and standing notices"
domain: infotech
repo: state-hub
status: proposed
status: active
owner: claude
topic_slug: infotech
flavor: implementation
created: "2026-09-21"
updated: "2026-09-21"
updated: "2026-09-22"
related:
- STATE-WP-0091
origin: founder-direction
@ -150,7 +150,7 @@ acknowledgement needs one instruction line, changed once:
```task
id: STATE-WP-0093-T01
status: wait
status: done
priority: high
state_hub_task_id: "94eec945-b728-50cb-aefb-60eca5aeccd9"
```
@ -162,11 +162,28 @@ broadcasts (D6). Record the choices as decisions in this file.
Done when D2, D3 and D6 each have a recorded founder choice and this
workplan moves to `ready`.
**Founder decisions (Bernd Worsch, 2026-09-22) — all accepted as recommended:**
1. **D3 ACCEPTED:** `GET /messages/?to_agent=X&unread_only=true` records an
idempotent `delivered_at` receipt for X on each broadcast it returns (a
deliberate write inside the GET; no protocol change for delivery tracking).
2. **D6 ACCEPTED:** the migration ARCHIVES the 5 existing broadcasts
(reversible; nothing re-surfaces). T08 asks gate-house whether to
republish its v0.7 "start here" note as a standing notice.
3. **D2/D7 ACCEPTED:** unattributed mark-read of a broadcast is a visibility
no-op with deprecation headers; one line in
`scripts/project_rules/session-protocol.template` and
`agents-codex.template` (inbox curl gains `?reader={REPO_SLUG}` plus an
ack note), carried ONCE by `update_agent_instruction_files.py`. The fleet
propagation run itself is T08, not part of the implementation session.
T07 (release) still waits on its own founder go-ahead.
## Schema and migration
```task
id: STATE-WP-0093-T02
status: wait
status: progress
priority: high
state_hub_task_id: "6680d408-ebce-5b2c-8a97-84f0bf011754"
```
@ -182,11 +199,21 @@ Downgrade drops the table and columns (archive stamps stay).
Done when `alembic upgrade head` and `downgrade -1` both run clean on a
copy of a production dump, and the 5 broadcasts end in the D6 state.
**2026-09-22 (progress):** `MessageReceipt` model and Alembic revision
`d7e8f9a0b1c2` (on `c6f7a8b9d0e1`) implemented: `message_receipts`,
`kind`/`expires_at`/`supersedes_id`, both indexes, broadcasts backfilled to
`news` and archived (D6); direct messages untouched. Verified on a scratch
local database built from the full migration chain (upgrade head, downgrade
-1, upgrade head all clean; a read broadcast ended `news` + archived, a direct
message unchanged) and by an isolated-schema up/down test. **Remaining:** the
rehearsal on a copy of a production dump — not run from the implementation
session (no production access); do it in the T07 preflight.
## API: receipts, notices, ack
```task
id: STATE-WP-0093-T03
status: wait
status: done
priority: high
state_hub_task_id: "33010aa6-326d-5f52-9ff3-32b09c9e88c4"
```
@ -201,11 +228,21 @@ Capability-request broadcasts send `kind='news'`.
Done when direct-message behavior is byte-for-byte unchanged in the
existing test suite and T06's new tests pass.
**2026-09-22 (done):** D2-D4 in `api/routers/messages.py`,
`api/services/message_receipts.py`, `api/schemas/agent_message.py` (local
subclasses of the hub-core schemas). `PATCH /read?reader=&ack=` (also JSON
body), unattributed broadcast mark-read = no-op + `Deprecation`/`Warning`
headers + legacy-meter key `rest_api:PATCH /messages/{id}/read
broadcast-without-reader`, broadcast reply writes a receipt for the replier,
`POST /messages/{id}/ack`, news default expiry 30d, supersede archives the
predecessor, expiry filtered at query time, capability-request broadcasts are
`news`. Direct-message paths unchanged; existing suite green.
## MCP parity (state-hub and hub-core)
```task
id: STATE-WP-0093-T04
status: wait
status: progress
priority: medium
state_hub_task_id: "43529a93-6e49-51c8-a358-726f7e737fab"
```
@ -222,11 +259,21 @@ Done when both MCP servers can mark a broadcast read per reader and
acknowledge a standing notice, with tests in
`tests/test_codex_mcp_server.py` and hub-core's suite.
**2026-09-22 (progress — split):** state-hub part done:
`mcp_server/codex_server.py` `mark_message_read(message_id, reader=None)` and
new `acknowledge_notice(message_id, agent)`, tests in
`tests/test_codex_mcp_server.py`, `mcp_server/TOOLS.md` updated (the Codex
server has no `send_message`). hub-core part handed off via State Hub message
`69fc387c-5bd3-47bc-9a3f-d3d83b6dc213` (addendum
`f8eecebf-cf76-44f9-832e-20401b1b37ec`: absorb the schema fields so state-hub
can return to a pure re-export). **Remaining:** hub-core change + release,
then bump the pinned hub-core here.
## Visibility: notices view, summary, dashboard
```task
id: STATE-WP-0093-T05
status: wait
status: progress
priority: medium
state_hub_task_id: "fdb44fd9-ab44-5e26-86b3-a4f0f8a4e701"
```
@ -237,11 +284,20 @@ Implement D5: `GET /messages/notices`, `standing_notices` in
Done when a live standing notice shows acked / delivered-only /
unreached repo lists on the endpoint and the dashboard.
**2026-09-22 (progress):** `GET /messages/notices` (acknowledged /
delivered_only / unreached against active `managed_repos`),
`standing_notices` in `/state/summary` (computed per request, outside the
revision cache), dashboard inbox "Standing notices" panel + separate
"Broadcasts" section (no global mark-read for broadcasts), loader
`dashboard/src/data/notices.json.py`. Endpoint and summary covered by tests;
dashboard build passes. **Remaining:** check the panel against a live notice
after T07.
## Tests
```task
id: STATE-WP-0093-T06
status: wait
status: done
priority: high
state_hub_task_id: "d68aa1fa-0f09-5d64-a4fc-cbc24180f2c3"
```
@ -257,6 +313,12 @@ migration state of pre-existing broadcasts.
Done when the new tests pass and the full suite is green.
**2026-09-22 (done):** `tests/test_message_receipts.py` (17 tests: all
listed cases, incl. reader in JSON body, ack via mark-read flag, capability
broadcast as news, isolated-schema migration up/down). Full suite: 894 passed
(`tests/test_hub_core_imports.py` message-schema check relaxed from identity
to subclass, see T04).
## Release through the normal promotion path
```task