STATE-WP-0093: per-recipient broadcast receipts and standing notices (T01-T06).
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:
parent
740068fcf1
commit
ef541f58cf
18 changed files with 1256 additions and 52 deletions
|
|
@ -9,6 +9,7 @@ from sqlalchemy.orm import noload, selectinload
|
|||
|
||||
from api.config import settings
|
||||
from api.database import get_session
|
||||
from api.services.message_receipts import standing_notice_digests
|
||||
from api.services.schema_state import schema_state
|
||||
from api.flow_defs import assertion_result_to_dict, load_flow
|
||||
from api.models.capability_request import CapabilityRequest
|
||||
|
|
@ -136,6 +137,18 @@ def _apply_summary_flavor_view(
|
|||
)
|
||||
|
||||
|
||||
async def _live_summary_sections(
|
||||
session: AsyncSession, *, refresh: bool = False
|
||||
) -> dict[str, object]:
|
||||
"""Sections computed per request, outside the revision-keyed cache."""
|
||||
return {
|
||||
"ops_runs": await get_ops_run_projection(refresh=refresh),
|
||||
# STATE-WP-0093 D5: receipts are written on inbox reads, which do not
|
||||
# bump the summary revision, so standing-notice counts are live.
|
||||
"standing_notices": await standing_notice_digests(session),
|
||||
}
|
||||
|
||||
|
||||
@router.get("/summary", response_model=StateSummary)
|
||||
async def get_summary(
|
||||
request: Request,
|
||||
|
|
@ -155,7 +168,7 @@ async def get_summary(
|
|||
if cache_status == "hit-revision" and cached is not None:
|
||||
_summary_cache_headers(response, cache_status="hit-revision", revision=revision_token)
|
||||
return _apply_summary_flavor_view(
|
||||
cached.model_copy(update={"ops_runs": await get_ops_run_projection()}),
|
||||
cached.model_copy(update=await _live_summary_sections(session)),
|
||||
include_residuals=include_residuals,
|
||||
flavor=flavor,
|
||||
)
|
||||
|
|
@ -164,7 +177,7 @@ async def get_summary(
|
|||
result = await apply_progress_section(session, cached, revision)
|
||||
_summary_cache_headers(response, cache_status="hit-revision", revision=revision_token)
|
||||
return _apply_summary_flavor_view(
|
||||
result.model_copy(update={"ops_runs": await get_ops_run_projection()}),
|
||||
result.model_copy(update=await _live_summary_sections(session)),
|
||||
include_residuals=include_residuals,
|
||||
flavor=flavor,
|
||||
)
|
||||
|
|
@ -173,7 +186,7 @@ async def get_summary(
|
|||
cache.schedule_refresh(revision)
|
||||
_summary_cache_headers(response, cache_status="stale", revision=revision_token)
|
||||
return _apply_summary_flavor_view(
|
||||
cached.model_copy(update={"ops_runs": await get_ops_run_projection()}),
|
||||
cached.model_copy(update=await _live_summary_sections(session)),
|
||||
include_residuals=include_residuals,
|
||||
flavor=flavor,
|
||||
)
|
||||
|
|
@ -182,7 +195,7 @@ async def get_summary(
|
|||
cache.store(result, revision)
|
||||
_summary_cache_headers(response, cache_status="miss", revision=revision_token)
|
||||
return _apply_summary_flavor_view(
|
||||
result.model_copy(update={"ops_runs": await get_ops_run_projection(refresh=force_refresh)}),
|
||||
result.model_copy(update=await _live_summary_sections(session, refresh=force_refresh)),
|
||||
include_residuals=include_residuals,
|
||||
flavor=flavor,
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue