"OKR Monday-morning CUO digest — auto-progress + check-ins → founder summary delivered via email/chat at 08:00 Monday"
§1 — Description (BCP-14 normative)
The OKR service MUST ship Monday digest at services/okr/src/digest/ triggered 08:00 Monday tenant_tz, assembled from TASK-OKR-004/005, summarized via TASK-CUO-101, delivered via email + chat, 4 memory audit kinds.
- MUST schedule cron Monday 08:00 tenant_tz per DEC-2010 via TASK-MCP-007.
- MUST validate
digest_deliveryagainst closed enum per DEC-2011.
- MUST assemble at
assembler.rs::assemble(tenant, week)per DEC-2012:
- Top-3 KRs at risk (low confidence + declining trend)
- Top-3 KRs making progress (high confidence + improving)
- Missing check-ins from last week
- Drift alerts from TASK-OKR-004 last batch run
- MUST summarize via TASK-CUO-101 + TASK-AI-003 per DEC-2013 — structured prompt with sections.
- MUST deliver per recipient preference per DEC-2010 at
deliverer.rs::deliver:
- email: TASK-EMAIL-009 send
- chat: TASK-CHAT-005 message
- both: both
- none_skipped: log only
- MUST define tables at migration
0006: ```sql CREATE TABLE okr_digest_recipients ( tenant_id UUID NOT NULL, user_id UUID NOT NULL, delivery_pref TEXT NOT NULL DEFAULT 'both' CHECK (delivery_pref IN ('email','chat','both','none_skipped')), enabled BOOLEAN NOT NULL DEFAULT true, updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), PRIMARY KEY (tenant_id, user_id) ); ALTER TABLE okr_digest_recipients ENABLE ROW LEVEL SECURITY; CREATE POLICY recipients_rls ON okr_digest_recipients USING (tenant_id = current_setting('auth.tenant_id')::uuid) WITH CHECK (tenant_id = current_setting('auth.tenant_id')::uuid); GRANT UPDATE (delivery_pref, enabled, updated_at) ON okr_digest_recipients TO cyberos_app;
CREATE TABLE okr_digest_runs ( run_id UUID PRIMARY KEY, tenant_id UUID NOT NULL, iso_week CHAR(8) NOT NULL, content_jsonb JSONB NOT NULL, recipients_count INT NOT NULL, delivered_count INT NOT NULL, trace_id CHAR(32), created_at TIMESTAMPTZ NOT NULL DEFAULT now(), UNIQUE (tenant_id, iso_week) ); ALTER TABLE okr_digest_runs ENABLE ROW LEVEL SECURITY; CREATE POLICY digest_runs_rls ON okr_digest_runs USING (tenant_id = current_setting('auth.tenant_id')::uuid) WITH CHECK (tenant_id = current_setting('auth.tenant_id')::uuid); REVOKE UPDATE, DELETE ON okr_digest_runs FROM cyberos_app; ```
- MUST emit 4 memory audit kinds per DEC-2014. PII per TASK-MEMORY-111: content text SHA-256 hashed.
- MUST thread trace_id from cron → assembler → CUO → deliverer → audit.
- MUST NOT send to non-opted-in users per DEC-2010 (recipient table is opt-in).
- MUST NOT skip TASK-CUO-101 per DEC-2013 (raw assembled data not delivered).
- MUST be idempotent per week (UNIQUE on iso_week).
§2 — Why this design
Why Monday 08:00 (DEC-2010)? Founders open inbox first; digest sets the week's priorities.
Why 4 sections (DEC-2012)? Bounded; addresses CEO's typical questions (risk + win + gaps).
Why CUO tone (DEC-2013)? Direct AI output feels robotic; CUO adds context + framing.
Why opt-in (DEC-2010)? Not every founder wants email noise; let them choose chat-only or skip.
§3 — API contract
PUT /v1/okr/digest/recipients/{user_id} body: {delivery_pref, enabled}
GET /v1/okr/digest/runs (list past digests)
POST /v1/okr/digest/trigger (CEO manual trigger)
Sample digest content:
{
"iso_week": "2026-W20",
"sections": {
"at_risk": [
{"kr_id": "uuid", "title": "Q2 revenue $500k", "confidence": 4, "trend": "declining"}
],
"making_progress": [...],
"missing_checkins": ["KR-A", "KR-B"],
"drift_alerts": [{"kr_id": "uuid", "drift_pct": 25.5}]
},
"cuo_summary": "We're tracking well on engineering velocity but Q2 revenue confidence dropped 3 points..."
}
§4 — Acceptance criteria
- digest_delivery enum cardinality 4. 2. Monday 08:00 cron. 3. Recipient opt-in via table. 4. 4 sections assembled. 5. CUO summary generated. 6. Email delivery via TASK-EMAIL-009. 7. Chat delivery via TASK-CHAT-005. 8. 'both' sends both. 9. 'none_skipped' logs only. 10. 4 memory audit kinds emitted. 11. PII scrubbed (content SHA256). 12. RLS denies cross-tenant. 13. CEO-only manual trigger. 14. Trace_id preserved. 15. UNIQUE(tenant_id, iso_week). 16. Append-only runs via REVOKE. 17. Empty sections degrade gracefully. 18. CUO failure → degrade to raw content + sev-2. 19. Delivery failure per recipient isolated. 20. Recipient pref updates effective next run.
§5 — Verification
#[tokio::test]
async fn assembles_4_sections() {
let ctx = TestContext::with_krs_at_risk_and_progress().await;
let digest = ctx.assemble_digest(this_week()).await;
assert!(digest.sections.at_risk.len() <= 3);
assert!(digest.sections.making_progress.len() <= 3);
}
#[tokio::test]
async fn delivers_per_recipient_pref() {
let ctx = TestContext::with_3_recipients_diff_prefs().await;
ctx.run_monday_digest().await;
let email_sent = ctx.email_send_count().await;
let chat_sent = ctx.chat_send_count().await;
assert_eq!(email_sent, 2); // email + both
assert_eq!(chat_sent, 2); // chat + both
}
#[tokio::test]
async fn idempotent_per_week() {
let ctx = TestContext::with_recipients().await;
ctx.run_monday_digest().await;
ctx.run_monday_digest().await;
let runs = ctx.fetch_digest_runs(this_iso_week()).await;
assert_eq!(runs.len(), 1);
}
// 5.4..5.10
§7 — Dependencies
Upstream: TASK-OKR-005, TASK-CUO-101. Cross-module: TASK-OKR-004 (drift alerts), TASK-EMAIL-009 (email), TASK-CHAT-005 (chat), TASK-MCP-007 (cron), TASK-AI-003 (LLM via CUO), TASK-AUTH-101 (CEO role), TASK-MEMORY-111 (PII).
§10 — Failure modes
| Failure | Detection | Outcome | Recovery |
|---|---|---|---|
| Cron skipped | catch-up | sev-3 | inherent |
| Duplicate run | UNIQUE | skip | inherent |
| 0 KRs in tenant | inherent | skip digest | inherent |
| CUO summarize fails | retry | degrade to raw content + sev-2 | inherent |
| Email send fails | per-recipient isolation | sev-2 | inherent |
| Chat send fails | per-recipient isolation | sev-2 | inherent |
| Empty section | inherent | omit section | inherent |
| Recipient disabled | filter | skip | inherent |
| Cross-tenant recipient | RLS | 0 rows | inherent |
| Content > 50k chars | truncate | sev-3 | inherent |
§11 — Implementation notes
- §11.1 Cron via TASK-MCP-007
kind: 'okr.monday_digest', weekly Monday 08:00. - §11.2 Assembler queries TASK-OKR-004 last batch + TASK-OKR-005 last-week check-ins.
- §11.3 CUO prompt: "Generate a founder-friendly Monday digest from this data..."; 200-word target.
- §11.4 memory audit body: tenant_id, iso_week, recipients_count, delivered_count; content SHA256.
- §11.5 Recipients opt-in via TASK-PORTAL-006 settings page.
End of TASK-OKR-006 spec.