partial under Contract Graph audit — shipped backend/web/native source surfaces are

Spec: F31 — Follow-up Missions

Status: partial under Contract Graph audit — shipped backend/web/native source surfaces are present, while browser, named native, live Telegram, and production scheduler evidence remain explicit gaps. Contract: contracts/features/F31.cue · Audit: F31 implementation audit · Test plan: F31 test plan

1. Problem & CEO Job

Decide fast + trust the signal. Brief can surface an issue and F25/F26 can delegate it, but neither chases the answer. If the CEO still pings “đến đâu rồi?” in Zalo, Lumi diagnoses work instead of operating it. F31 closes that loop: send the follow-up, collect the reply, attach it to the source issue, and return the updated state to the CEO.

The wedge is removing executive chase work without becoming a spam engine.

2. User Stories & Acceptance Criteria

  • As a CEO or manager, I want to send a targeted follow-up from an existing issue, so that I do not leave Lumi to chase updates manually.
    • Given a Task / Commitment / Risk / DecisionRequest / ApprovalRequest / BriefItem, when I tap “Nhắc cập nhật”, then a prefilled one-question draft opens with owner, context, and due-by prefilled.
    • Given I edit and send, then the follow-up is delivered by the chosen channel (telegram first, web inbox fallback) and the request becomes sent.
    • Given a manager asks Lumi to send an existing draft by topic, then Lumi proposes exactly one send_followup action, shows the resolved assignee, question, channel, and due time, and sends only after explicit confirmation.
  • As an assignee, I want to answer the follow-up in one reply, so that the update costs less than being chased twice.
    • Given a Telegram follow-up, when I reply in-thread, then Lumi records the answer and marks the request replied.
    • Given a web follow-up, when I answer from the app, then the same request is closed with the new answer attached.
  • As a CEO, I want the reply linked back to the original issue, so that Lumi’s queue updates instead of creating another orphan message.
    • Given a reply, then the reply text is stored as an EvidenceSource, visible from the original source object, and summarized on the next Brief/Today load.
    • Given multiple follow-ups on the same source item, then the latest visible state is obvious; older requests remain in history.
  • As an admin, I want stale issues to produce send-ready chase drafts, so that the org’s operating rhythm does not depend on the CEO remembering every loose end.
    • Given an overdue commitment, stale approval, or critical open risk, then Lumi may create a draft follow-up suggestion once per cooldown window.
    • Drafts never auto-send in v1. A human reviews and sends them; Lumi proposes, a human dispatches.
  • As the product, I want restraint, so that follow-ups improve trust rather than feeling like surveillance.
    • Only one open sent follow-up may exist per source item + assignee at a time.
    • Every sent follow-up is visible in Lumi; no hidden nags, no autonomous escalation ladders, no “AI silently messaged your team.”

3. Scope Boundaries

  • In: one-question follow-up drafts, send/edit flow, Telegram reply handling, web reply flow, reply evidence capture, source-item linkage, stale-item draft generation, Today/Brief visibility of outstanding follow-ups.
  • Out: multi-step campaigns, automatic repeated nagging, voice calls, follow-up analytics dashboards, manager scorecards, sentiment scoring, silent escalations, hidden autonomy.

4. Data Model

  • New app followups.
  • FollowUpRequest — org FK, created_by (OrganizationMember), assignee (OrganizationMember), source_type (brief_item | task | commitment | risk | decision | approval | deal), source_id (UUID), source_snapshot_json, question_text, delivery_channel (telegram | web), status (draft → sent → replied | expired | cancelled), due_at, sent_at, replied_at, latest_reply_text, reply_evidence_source FK nullable, auto_generated bool.
  • FollowUpTemplate is not a model in v1. Auto-draft question text is deterministic from the source object and lives in service code; avoid inventing a template-builder product.

State machine belongs in backend/DIAGRAMS.md. GLOSSARY entries: Follow-up Mission, auto-draft.

5. API Contract

MethodPathMin roleRequestResponseErrors
GET/api/follow-ups/member?status=&mine=&source_type=&source_id=FollowUpRequest list401
POST/api/follow-ups/manager{source_type, source_id, assignee_id, question_text?, due_at?, delivery_channel}created FollowUpRequest400 invalid source/assignee
POST/api/follow-ups/<id>/send/manageroptional edited payloadsent FollowUpRequest409 already sent/replied
POST/api/follow-ups/<id>/reply/member*{answer_text}replied FollowUpRequest403 non-assignee
POST/api/follow-ups/<id>/cancel/manager—cancelled FollowUpRequest409 already replied
POST/api/follow-ups/drafts/generate/adminoptional org-scope triggercreated draft count403

Conversational twin for POST /api/follow-ups/<id>/send/:

  • Assistant action: send_followup with {followup_query}. The query resolves one draft request by question_text within request.organization only.
  • Missing, zero-match, or multi-match queries ask for clarification and send nothing. Sent/replied/cancelled requests are not eligible targets.
  • The action requires manager authority, has autonomy_ceiling=0, and remains confirm-only forever because it sends to another person.
  • Confirmation calls the same send_follow_up() service as the REST form.

* Telegram replies do not use this endpoint directly; the bot maps the chat reply to the same service path.

Tenant scoping: every source lookup and every assignee lookup is scoped to request.tenant. Reply evidence is stored as a new EvidenceSource row (source_type="manual_note" in v1, with metadata noting follow-up origin) linked to the follow-up and referenced from the originating source item.

6. UI / UX

  • New route: /intel/follow-ups — two tabs max: Chờ trả lời and Bản nháp.
  • Inline entry points live where the CEO already works:
    • Brief item action: Nhắc cập nhật
    • Approval / Task / Commitment / Risk detail actions: same label, same interaction
  • Composer: one textarea, one assignee, one due date, one send button. No workflow-builder UI.
  • Outstanding follow-up chips surface in /work/today and /intel/brief only when materially useful: “Đã nhắc Tuấn, chờ trả lời đến 14:00”.
  • States: empty queue is positive, drafts explain why Lumi proposed them, failure keeps edited text, reply detail is visible from the source object.
  • i18n: new followups namespace, en + vi.
  • The direct send dialog remains available. Chat is an optional second path, surfaced as an action card that previews the target, assignee, question, channel, and due time before its confirmation button.

7. Test Plan

#CaseTypeWhere
1Create/send/reply/cancel flow; cross-tenant source blockedbackendnew backend/tests/test_followups.py
2Only assignee can reply; only manager+ can create/send/cancelbackendsame
3Duplicate open follow-up on same source+assignee rejectedbackendsame
4Telegram reply maps back to correct follow-up and creates EvidenceSourcebackendsame + backend/tests/test_telegram.py
5Draft-generation respects cooldown and never auto-sendsbackendsame
6Follow-up visibility from Brief/Today/source detaile2enew describe("Follow-ups") + brief regression cases
7Conversational send resolves one tenant-scoped draft and calls the shared send servicebackendassistant executor tests
8Missing/ambiguous/non-draft target sends nothing; member/viewer cannot confirmbackendassistant executor + permission tests
9send_followup remains ceiling 0 and requires confirmation at every trust levelbackendassistant parity + ceiling guards
10Chat → action preview → confirm → request becomes sent; direct dialog remainse2efollow-ups.spec.ts

8. Current Behavior

The dedicated follow-up system is shipped. apps/followups provides create/send/cancel/reply flows, Telegram replies map back into the same service path, replies create EvidenceSource(manual_note) provenance, auto-drafts generate on a cooldown, and /intel/follow-ups exposes the queue in the web UI.

The focused web unit surface also proves that a failed direct send preserves the open dialog and edited question for retry, while the Replied tab renders the latest answer. Browser execution, named native, live Telegram, and production scheduler evidence remain separate gaps.

Before the 2026-07-14 form-parity slice, sending still requires the web dialog: the assistant can create a draft and cancel a follow-up but cannot send an existing F31 draft. The slice adds that missing conversational twin without removing or changing the direct form.

9. Error States

  • followups.error_invalid_source — source item missing or not in org; no request created.
  • followups.error_invalid_assignee — assignee not an active member of the same org.
  • followups.error_already_open — an open sent follow-up already exists for this source item + assignee.
  • followups.error_reply_forbidden — someone other than the assignee tried to reply.
  • Delivery failure:
    • user message: followups.error_send_failed
    • internal: request stays draft, log delivery error, allow retry
  • Telegram reply from an unpaired or unknown chat:
    • user sees a safe fallback message in Telegram
    • Lumi records no reply and logs the event
  • Conversational send target missing or blank: missing_followup_query / no_followup_found; ask for a topic and send nothing.
  • More than one draft matches: ambiguous_followup; ask for a narrower topic and never pick the newest silently.
  • Target is no longer a draft at confirmation time: shared service raises invalid_status; action fails without a second delivery.
  • Member/viewer confirmation: 403-equivalent permission failure; manager/owner confirmation is permitted, matching the direct endpoint.

10. Diagrams & Docs Touched

  • DIAGRAMS.md signal loop update: issue → follow-up → reply → brief
  • backend/DIAGRAMS.md FollowUpRequest state machine
  • frontend/DIAGRAMS.md /intel/follow-ups route
  • F12 / F21 / F25 / F26 specs for glue references

11. Implementation Audit (2026-06-11)

#ClaimStatusEvidence
1FollowUpRequest model + state machine (draft → sent → replied|expired|cancelled)✅ matchapps/followups/models.py; backend/tests/test_followups.py (48 test methods); GLOSSARY + backend/DIAGRAMS.md state machine
2POST /api/follow-ups/, /send/, /cancel/ with IsOrgManager gate✅ matchFollowUpRequestViewSet; TestCreateFollowUpAPI, TestSendCancelFlow, TestDuplicateOpenFollowUp; dedup enforced (one sent per source+assignee)
3POST /api/follow-ups/<id>/reply/ — assignee-only, sent-only✅ matchPR-2; TestReplyFlow (assignee success, non-assignee 403, draft-status 409)
4Reply creates EvidenceSource(manual_note) linked to source item⚠️ false until 2026-07-10 (reply evidence was only linked via reply_evidence_source FK on the FollowUpRequest, never added to the source item’s evidence_sources M2M — so Brief/Today reads missed it)reply_to_follow_up service; reply_evidence_source FK only; test_followups.py:TestReplyFlow.test_reply_evidence_linked_to_source_item proves the gap
4aReply evidence now linked to source item via its evidence_sources M2M✅ match from 2026-07-10fix in apps/followups/services.py:reply_to_follow_up — after creating EvidenceSource, resolves source and calls source_obj.evidence_sources.add(evidence) for models that support it; TestReplyFlow.test_reply_evidence_linked_to_source_item, test_reply_evidence_linked_to_each_source_type, test_reply_evidence_skipped_for_models_without_m2m
5Telegram reply path: bot reply → handle_follow_up_reply → reply service✅ matchPR-2; FollowUpReplyHandlerTests; test_webhook_follow_up_reply
6Auto-draft generation (POST /api/follow-ups/drafts/generate/), 7-day cooldown✅ matchPR-4; generate_follow_up_drafts_for_all_orgs Celery beat task (daily 07:00)
7/intel/follow-ups route, followups i18n namespace (en + vi + 9), sidebar entry✅ matchPR-3; FollowUpsPage.tsx; frontend/DIAGRAMS.md route tree updated; 29 i18n namespaces green
8Only one open sent follow-up per source+assignee (deduplication)✅ matchPR-1; dedup check in FollowUpRequestViewSet.create
9E2E test describe("Follow-ups")⚠️ partialDedicated Playwright suite exists at frontend/tests/e2e/follow-ups.spec.ts and proves route/nav/API smoke; explicit create/send/reply and Brief/Today source-loop proof is still not covered end-to-end
10Departed assignee: sent follow-up auto-transitions to cancelled✅ match2026-07-01: apps/organizations/signals.py cancels outstanding sent follow-ups when an assignee leaves active status or is removed; covered by TestAssigneeLifecycle in backend/tests/test_followups.py
11iOS + Android: reply action available on status=sent items in shared KMP FollowUpsScreen✅ match2026-06-22 PR mobile/f31-followup-reply-kmp; FollowUpsScreenModel.{startReply,confirmReply}; ReplyDialog AlertDialog in FollowUpsScreen.kt; 3 new unit tests; dead native FollowUpDetailScreen deleted

Summary: 11/12 match (row 4 was false demoted, row 4a now true); 1 gap (dedicated E2E exists but remains smoke-depth rather than full follow-up lifecycle proof).

Contract Graph audit note (2026-07-15)

The canonical contract in F31.cue now records the shipped backend, assistant, Telegram-handler, web, and shared-KMP boundaries. Local F31 proof execution passed for the registered pytest groups; the registered Playwright group failed in the unavailable web-server/browser environment and remains an explicit gap. No named native, live Telegram, or production scheduler evidence is inferred.

Edge Cases Catalog (formerly §9, 10 items): E1 departed assignee — closed 2026-07-01 by auto-cancelling outstanding sent follow-ups when the assignee leaves active membership or is removed; E2 reply-on-cancelled error code not defined (§5 only specifies sent-only guard); E3 dual-channel reply racing (no idempotency on latest_reply_text); E4 cooldown-reset semantics after manual cancellation unspecified; E5 source_snapshot_json size limit/truncation not defined; E6 spurious Telegram reply fallback text not specified in spec (falls through to handle_unknown); E7 multiple-follow-up-per-source-item rendering on Today/Brief chips (multiple pending chips unspecified); E8 cross-category draft dedup in auto-generation not described; E9 deleted-source viewing behavior (404 vs disabled link) unspecified; E10 past due_at validation rules not defined. Full catalog in git history.

12. Open Questions / Decisions

  • Should a reply optionally update the underlying source item automatically (for example, mark a Task back to in_progress)? Recommendation: no in v1; keep reply capture and source-state mutation separate.
  • Should follow-up replies be allowed from multiple channels after send? Recommendation: Telegram + web only; adding email or F29 message replies dilutes reliability too early.

13. Bug / Change Log

2026-08-15 — Avoid server-side cursors across follow-up generation

Type: Scheduled worker reliability hardening

Summary:

  • generate_follow_up_drafts_for_all_orgs_now now materialises organizations before running per-organization draft queries and writes. This keeps the daily beat loop aligned with the no-server-side-cursor rule used by the other long-running organization scans.
  • A regression test protects the loop from returning to QuerySet.iterator().

Evidence boundary:

  • The host incident directly showed this cursor failure in the Hub and mention digests; the follow-up loop had the same unsafe iteration shape but no failure was observed in the captured window. This change is preventive source/test proof, not a claim of a follow-up send.

2026-08-10 — Native Follow-ups: preserve the queue through refresh failure

Type: Mobile recovery UX

Summary:

  • Shared KMP now keeps the last committed Follow-ups visible when a populated refresh fails and shows one compact localized warning with explicit Retry instead of replacing the queue.
  • load(), refresh(), and reply reconciliation use the same stale-or-current snapshot, so repeated failures and a reply started from stale state do not discard unaffected rows.
  • .maestro/critical/156_android_followups_refresh_recovery.yaml arms exactly two allowlisted GET /api/follow-ups/ failures, proves both sent questions remain actionable, taps Retry, and requires the warning to clear without leaking raw transport detail.
  • The guarded Lumi_Pixel7_API35 lifecycle installed matching built/installed prodDebug APK SHA-256 c716874ee382e1887dd15418686284f486992374c89d6a1681bce33a45ffd3a5, preserved both server rows as sent, observed a forwarded recovery request, cleaned the disposable fixture to 0:0:0, retained failure/recovery frames plus proxy trace, and shut down emulator-5554.

Honest gate:

  • This proves Android emulator recovery mechanics only. Submitted-reply runtime, iOS, named-device, native screen-reader-service, signed-store, deployed, and production evidence remain open in F31.GAP002.

2026-07-16 — Android: make the populated reply dialog deterministic (INT-07)

Type: Mobile verification hardening

Summary:

  • Added stable selectors for the shared Follow-ups screen, sent card, Reply action, answer field, and dialog send control.
  • Added .maestro/critical/64_followups_reply_dialog.yaml, which asserts the seeded populated state, opens the Reply dialog, and cancels before mutating the shared fixture.
  • Fixed the canonical demo seed to create that fixture through create_follow_up() and send_follow_up(): one tenant-scoped, web-only sent request assigned to the demo CEO, backed by the existing performance-review task. The focused seed regression went red on FollowUpRequest.DoesNotExist before passing after the fix.
  • Guarded Lumi_Pixel7_API35 execution passed 1/1 in 28 seconds with APK SHA-256 f2a074ce0ccdb4600b88c8e9686c6aed9086d200fd92c11bf0ce85c6afdd23cb; command evidence: /Users/james/.maestro/tests/2026-07-16_133219.

Honest gate:

  • This is source plus one Android emulator UI proof only. The flow cancels deliberately, so it does not prove a submitted reply; named Android floor-device, iPhone 12, and PM scoring remain open.

2026-07-14 — Conversational send parity (issue #1560)

Type: Feature

Summary:

  • Add a send_followup assistant action for sending one existing F31 draft by topic after explicit confirmation.
  • Keep the web send dialog unchanged as an optional direct path.
  • Reuse send_follow_up(), preserve tenant/manager gates, and hard-code the external-send autonomy ceiling at 0.

Reason:

  • Draft creation and cancellation already had conversational paths, but the actual send step was still required-form-only.

Impact:

  • A manager can complete the F31 chase loop inside the assistant thread while seeing exactly who will receive what before committing.

Tests:

  • Backend parity, executor, permission, tenant-isolation, ambiguity, stale-state, and ceiling tests; ActionCard unit coverage; conversational Playwright flow.

2026-07-10 — Reply evidence linked to source item (issue #1648)

Type: Bug Fix

Summary:

  • reply_to_follow_up() created an EvidenceSource(manual_note) and set FollowUpRequest.reply_evidence_source but did not add it to the source item’s evidence_sources M2M.
  • F31 §11 row 4 falsely claimed the source was linked. Demoted to ⚠️, proved by TestReplyFlow.test_reply_evidence_linked_to_source_item.
  • Fix: after creating evidence, call resolve_source() and source_obj.evidence_sources.add(evidence) when the source model supports the M2M (Task, Commitment, Risk, DecisionRequest, BriefItem). ApprovalRequest and Deal lack the field and are skipped gracefully.
  • Added 11 new backend tests: every supported source type, models without M2M (skipped), cross-tenant isolation, unchanged source status guard, multiple replies per source, web/Telegram parity.

Reason:

  • Follow-up replies were invisible to the Brief/Today pipeline because they were stored only on the FollowUpRequest itself rather than propagated to the source item’s evidence_sources.

Impact:

  • Brief/Today reads now include reply evidence for all supported source types.
  • Guard: a reply is evidence only — never auto-resolves, dismisses, or changes source status.

Tests:

  • TestReplyFlow.test_reply_evidence_linked_to_source_item (proof of gap → fix)
  • TestReplyFlow.test_reply_evidence_linked_to_each_source_type (parametrized: commitment, risk, decision, brief_item)
  • TestReplyFlow.test_reply_evidence_skipped_for_models_without_m2m (approval, deal)
  • TestReplyFlow.test_reply_does_not_change_source_status
  • TestReplyFlow.test_reply_cross_tenant_linkage_blocked
  • TestReplyFlow.test_multiple_replies_on_same_source
  • TestReplyFlow.test_telegram_and_web_reply_both_link_evidence
  • All 71 test_followups.py tests pass (was 60).
  • Backend coverage 97.01% (≥97% gate).

2026-07-02 — Correct stale page path in historical change log (batch-1 spec audit)

Type: Documentation Fix

Summary:

  • The 2026-06-11 change log entry (PR-3) records the original creation path frontend/src/pages/FollowUpsPage.tsx; the file now lives at frontend/src/pages/intel/FollowUpsPage.tsx following the app-wide IA restructure. The historical entry is preserved unchanged (per spec policy: never rewrite history). The current location: frontend/src/pages/intel/FollowUpsPage.tsx (verified by find frontend/src -name FollowUpsPage.tsx). The 2026-06-15 entry records the route rename /follow-ups → /intel/follow-ups which accompanied the directory move.

2026-07-02 — §11 test count and test-class names corrected (batch 12)

Type: Documentation Truth Fix

Summary:

  • §11 Row 1: Updated backend test count from “38 backend tests” (stale from initial PR-1 audit) to “48 test methods” reflecting the current state of backend/tests/test_followups.py (verified by grep -c "^ def test_" = 48).
  • §11 Row 2: Replaced non-existent class name TestFollowUpViewSet with the actual test classes that cover POST /api/follow-ups/, /send/, and /cancel/: TestCreateFollowUpAPI, TestSendCancelFlow, TestDuplicateOpenFollowUp (verified by grep "^class Test" backend/tests/test_followups.py).

2026-07-02 — Spec drift fix: check shipped ACs against §11 audit

Type: Documentation Truth Fix

Summary:

  • Checked 9 of 10 previously open [ ] ACs in §2 that §11 confirms as implemented:
    • AC1 (prefilled draft from source item) — rows 1, 2, 7 (model + API + UI with polymorphic templates)
    • AC2 (edit-and-send flow + sent status) — rows 2, 7
    • AC3 (Telegram reply → replied) — row 5
    • AC4 (web reply → request closed) — row 3
    • AC5 (reply stored as EvidenceSource, linked to source) — row 4
    • AC7 (auto-draft generation per 7-day cooldown) — row 6
    • AC8 (drafts never auto-send — explicit manager action required) — rows 2, 6
    • AC9 (dedup: one open sent per source+assignee) — row 8
    • AC10 (every sent follow-up visible in Lumi) — row 7
  • Left AC6 ([ ]): “Given multiple follow-ups on the same source item, then the latest visible state is obvious; older requests remain in history” — no §11 row explicitly confirms multi-follow-up history rendering in the UI.

2026-07-01 — Close departed-assignee lifecycle gap; correct E2E audit wording

Type: Backend fix + documentation truth fix

Summary:

  • Added apps/organizations/signals.py lifecycle hooks so an assignee leaving active status, or being removed entirely, auto-cancels their outstanding sent follow-ups instead of leaving the chase queue stuck on a departed person.
  • Added TestAssigneeLifecycle coverage in backend/tests/test_followups.py for both suspension and removal.
  • Corrected §11 row 9: the repo already contains a dedicated frontend/tests/e2e/follow-ups.spec.ts suite, so the real gap is E2E depth, not E2E absence.

Reason:

  • The prior audit mixed two different problems: a genuine product gap (departed assignees) and a stale wording bug (claiming the Follow-ups Playwright suite was missing when it already existed).

Impact:

  • F31 now closes sent follow-ups cleanly when the assignee is no longer active, reducing stale executive chase work and avoiding misleading queue state.
  • The spec now distinguishes “smoke coverage exists” from “full lifecycle proof still missing,” which is the sharper product truth.

Tests:

  • cd backend && DJANGO_SETTINGS_MODULE=config.settings.test ./.venv/bin/python -m pytest tests/test_followups.py -x --tb=short
  • Attempted: cd frontend && pnpm exec playwright test tests/e2e/follow-ups.spec.ts --project=chromium --reporter=list
    • blocked locally because Playwright Chromium is not installed and browser download failed with EHOSTUNREACH

2026-06-21 — CRUD-scope decision: lifecycle actions, not free delete (recorded)

Type: Decision (no code change)

During the 2026-06-21 full-CRUD audit, Follow-up requests were reviewed for edit/delete affordances. Decision: Follow-up requests are append-only by backend design (http_method_names allows GET/POST only — no PATCH/DELETE) items (each links an EvidenceSource); their UI lifecycle is send / reply / cancel, not free edit/delete. A hard delete would sever the evidence/audit trail (F16) and is intentionally not offered in the operator UI. This is a deliberate scope boundary, not a missing feature.

2026-06-15 — Frontend route namespaced

Frontend route namespaced: /follow-ups -> /intel/follow-ups (app-wide IA restructure; API paths unchanged).

2026-06-14 — Restructure to TEMPLATE.md section order; expand §11 audit (SP fleet)

Type: Documentation Change

Summary:

  • Absorbed §9 Edge Cases (10 items) into §11 note; §10 Error States → §9; §11 Diagrams → §10; §12 Implementation Audit (narrative PR-1–PR-4) → §11 (10-row table); §13 Open Questions → §12; §14 Bug/Change Log → §13.
  • §11 audit: 10 rows, 8 ✅, 2 gaps (E2E test missing, departed-assignee auto-cancel not implemented).

2026-06-12 — Added Edge Cases section (CMD fleet agent E, round 11)

Type: Documentation Change

Summary:

  • Added §9 Edge Cases (10 items covering departed assignee, reply-on-cancelled, dual-channel reply racing, cooldown semantics, snapshot size, spurious Telegram reply, multi-assignee, cross-category draft dedup, deleted source, past due-at).
  • Existing Error States renumbered from §9 to §10; Diagrams from §10 to §11; Implementation Audit from §11 to §12; Open Questions from §12 to §13; Bug/Change Log from §13 to §14.

Reason:

  • Round 11 spec-hardening: identify gaps without inventing new behavior.

Impact:

  • F31 spec is more useful as a review and implementation reference.

Tests:

  • No code changes.

Notes:

  • Several gaps flagged as explicitly unspecified rather than resolved.

2026-06-12 — Spec sync after ship audit

Type: Doc Fix

Summary:

  • Replaced the stale pre-ship Current Behavior text with the actual shipped follow-up system state.

Reason:

  • The feature was marked shipped in the registry and implementation audit, but §8 still described the pre-feature world.

Impact:

  • The spec now matches the repo’s current behavior instead of contradicting its own shipped status.

Tests:

  • No code changes. Verification comes from backend/tests/test_followups.py and frontend/tests/e2e/follow-ups.spec.ts.

Notes:

  • This is a documentation truth fix only; the shipped F31 contract is unchanged.

2026-06-11 — PR-3: FollowUpsPage UI + i18n namespace

Type: Feature

Summary:

  • frontend/src/pages/FollowUpsPage.tsx — tabbed queue (Sent / Drafts / Replied) with send dialog, reply dialog, and cancel action.
  • followUpsApi in endpoints.ts (list, create, send, cancel, reply).
  • FollowUpRequest type in types/index.ts.
  • followups i18n namespace: en/followups.json, vi/followups.json, and 9 additional language stubs.
  • Sidebar Bell entry under the Intelligence group; follow_ups key added to all sidebar locale files.
  • /follow-ups route in App.tsx.
  • frontend/DIAGRAMS.md route tree updated.

Reason:

  • PR-3 of the P14 implementation plan — gives the CEO a place to see, send, reply to, and cancel follow-ups within the product.

Impact:

  • New route /follow-ups and sidebar nav item.
  • No backend changes.

Tests:

  • Frontend unit tests pass (951/951); 29 i18n namespaces; all language key-parity checks green.

Notes:

  • No E2E test yet (F31 PR-4 or a later cleanup will add describe("Follow-ups")).

2026-06-11 — PR-2: Telegram and web reply ingestion

Type: Feature

Summary:

  • New POST /api/follow-ups/{id}/reply/ endpoint: only the assignee may reply (followups.error_reply_forbidden otherwise), only on a sent request (followups.error_invalid_status otherwise).
  • apps/followups/services.reply_to_follow_up creates an EvidenceSource(source_type="manual_note") linked via reply_evidence_source, sets latest_reply_text/replied_at, and moves the request to replied.
  • apps/followups/services.find_telegram_reply_target finds the most recent outstanding (sent, delivery_channel="telegram") follow-up for a member.
  • apps/telegram/handlers.handle_follow_up_reply + webhook wiring: a connected user’s free-text reply (when not mid-pulse) is matched to their outstanding Telegram follow-up and routed through reply_to_follow_up.

Reason:

  • PR-2 of the P14 implementation plan — closes the loop so the CEO’s chase actually produces an evidence-backed answer without the assignee leaving Telegram.

Impact:

  • New action route under /api/follow-ups/{id}/reply/.
  • Telegram webhook now has a third branch (after /start etc. and the pulse conversation) for follow-up replies.

Tests:

  • backend/tests/test_followups.py::TestReplyFlow (assignee reply success, non-assignee 403, draft-status 409, service-level invalid_status, find_telegram_reply_target).
  • backend/tests/test_telegram.py::FollowUpReplyHandlerTests and WebhookViewTests::test_webhook_follow_up_reply.

Notes:

  • An unpaired Telegram chat or a user with no outstanding follow-up falls through to handle_unknown unchanged.
  • PR-3 (UI), PR-4 (auto-draft generation) remain.

2026-06-11 — PR-1: backend model + service + manual send API

Type: Feature

Summary:

  • New apps/followups app with FollowUpRequest model (draft → sent → replied|expired|cancelled), polymorphic source resolution across Task/Commitment/Risk/DecisionRequest/ApprovalRequest/Deal/BriefItem, deterministic Vietnamese question-draft templates, and best-effort Telegram delivery.
  • FollowUpRequestViewSet with /api/follow-ups/ (list/create), /send/, /cancel/; IsOrgManager required for create/send/cancel.
  • The contract graph declares both shipped read operations explicitly: GET /api/follow-ups/ is tenant-scoped queue retrieval and GET /api/follow-ups/<id>/ is tenant-scoped detail retrieval; this ownership does not imply browser or production runtime proof.
  • The contract graph also declares the shipped create, cancel, and draft-generation operations explicitly; these remain API ownership records and do not imply live scheduler, Telegram, browser, or production evidence.
  • Deduplication: only one sent follow-up per (organization, source_type, source_id, assignee).

Reason:

  • PR-1 of the P14 implementation plan — establishes the core data model and manual send/cancel flow before reply ingestion and UI.

Impact:

  • New table follow_up_requests (migration 0001_initial).
  • New routes registered under /api/follow-ups/.

Tests:

  • backend/tests/test_followups.py — 38 tests, 100% coverage of apps/followups/*.

Notes:

  • Auto-draft, never auto-send — send_follow_up() requires an explicit manager action.
  • PR-2 (reply ingestion), PR-3 (UI), PR-4 (auto-draft generation) remain.

2026-06-11 — Initial spec

Type: Feature

Summary:

  • Added the Follow-up Missions spec to complete Lumi’s control loop after Brief assignment and approvals.

Reason:

  • Lumi currently diagnoses and delegates, but does not own the executive chase loop.

Impact:

  • Defines the v1 operating model for follow-up drafts, delivery, reply capture, and provenance.

Tests:

  • Planned in backend/tests/test_followups.py and E2E describe("Follow-ups").

Notes:

  • Auto-draft is in; auto-send is explicitly out for trust reasons.