Task — engineering-spec@1

"OKR quarterly retro CUO draft — auto-generated retro with face-saving Vietnamese framing for honest reflection"

draftTASK-OKR-007
module okr · class product · priority p1 · created 2026-05-17 · shipped null
depends on TASK-OKR-001, TASK-CUO-101 · blocks none

§1 — Description (BCP-14 normative)

The OKR service MUST ship quarterly retro at services/okr/src/retro/ auto-drafted Q-end, framing-aware, CEO-reviewed, memory-persisted on approval, 4 memory audit kinds.

  1. MUST trigger on Q-end per DEC-2020 via TASK-MCP-007 cron (Mar 31, Jun 30, Sep 30, Dec 31).
  1. MUST validate retro_framing against closed enum per DEC-2021.
  1. MUST pick framing at framing_picker.rs::pick(tenant):
  1. MUST draft at draft_generator.rs::draft(tenant, quarter, framing) per DEC-2023:
  1. MUST queue for CEO review per DEC-2024 — never auto-persist.
  1. MUST persist to memory on approval at memory_persister.rs::persist(retro) per TASK-CRM-007 pattern — searchable kind=lessons.
  1. MUST define table at migration 0007: ``sql CREATE TABLE okr_quarterly_retros ( retro_id UUID PRIMARY KEY, tenant_id UUID NOT NULL, quarter CHAR(7) NOT NULL, -- 'YYYY-Q1' format framing TEXT NOT NULL CHECK (framing IN ('vn_face_saving','direct_western','balanced')), draft_jsonb JSONB NOT NULL, status TEXT NOT NULL DEFAULT 'pending_review' CHECK (status IN ('pending_review','approved','dismissed','persisted')), reviewed_by UUID, reviewed_at TIMESTAMPTZ, memory_memory_path TEXT, trace_id CHAR(32), created_at TIMESTAMPTZ NOT NULL DEFAULT now(), UNIQUE (tenant_id, quarter) ); ALTER TABLE okr_quarterly_retros ENABLE ROW LEVEL SECURITY; CREATE POLICY retros_rls ON okr_quarterly_retros USING (tenant_id = current_setting('auth.tenant_id')::uuid) WITH CHECK (tenant_id = current_setting('auth.tenant_id')::uuid); REVOKE UPDATE, DELETE ON okr_quarterly_retros FROM cyberos_app; GRANT UPDATE (framing, draft_jsonb, status, reviewed_by, reviewed_at, memory_memory_path) ON okr_quarterly_retros TO cyberos_app; ``
  1. MUST expose endpoints: ``text GET /v1/okr/retros (list) POST /v1/okr/retros/{id}/approve (CEO; persists to memory) POST /v1/okr/retros/{id}/dismiss POST /v1/okr/retros/{id}/regenerate body: {framing} (re-draft with different framing) ``
  1. MUST emit 4 memory audit kinds per DEC-2025. PII per TASK-MEMORY-111: draft text SHA-256 hashed.
  1. MUST thread trace_id from cron → draft → CEO review → memory persist → audit.
  1. MUST NOT auto-persist per DEC-2024.
  1. MUST NOT use blame-heavy framing for vn-1 tenants per DEC-2022 (default to vn_face_saving).

§2 — Why this design

Why VN face-saving (DEC-2022)? VN business culture values harmony; blame-heavy retros undermine team trust + future honesty.

Why 3 framings (DEC-2021)? Cultural fit varies; CEO picks per audience.

Why CEO review (DEC-2024)? Retro narrative becomes institutional memory; mistakes here persist for years.

Why memory persist (DEC-2025)? Future retros reference past lessons; without persist, learning evaporates.


§3 — API contract

Sample retro draft:

{
  "retro_id": "uuid",
  "quarter": "2026-Q2",
  "framing": "vn_face_saving",
  "draft_jsonb": {
    "results_summary": "Q2 đã đạt 8/10 KRs (80%)...",
    "hits_and_misses": [...],
    "lessons_learned": ["Quy trình tuyển dụng cần được cải thiện..."],
    "next_quarter_recommendations": [...]
  },
  "status": "pending_review"
}

§4 — Acceptance criteria

  1. Auto-trigger Q-end cron. 2. retro_framing enum cardinality 3. 3. VN tenant defaults to vn_face_saving. 4. Other tenant defaults to balanced. 5. 4 sections drafted. 6. CEO can override framing. 7. Regenerate endpoint with new framing. 8. Never auto-persist to memory. 9. Approve writes memory memory. 10. 4 memory audit kinds emitted. 11. PII scrubbed (draft text SHA256). 12. RLS denies cross-tenant. 13. CEO-only review. 14. Trace_id preserved. 15. UNIQUE(tenant_id, quarter) idempotency. 16. Append-only via REVOKE except status cols. 17. memory_memory_path populated post-approve. 18. Dismiss → status=dismissed (audit retained). 19. AI failure → status=failed + sev-2 + retry. 20. Status workflow: pending_review → approved → persisted | dismissed.

§5 — Verification

#[tokio::test]
async fn vn_tenant_defaults_vn_framing() {
    let ctx = TestContext::vn_tenant().await;
    ctx.run_quarter_end_cron("2026-Q2").await;
    let retro = ctx.fetch_retro_draft("2026-Q2").await;
    assert_eq!(retro.framing, "vn_face_saving");
}

#[tokio::test]
async fn never_auto_persists() {
    let ctx = TestContext::with_retro_draft().await;
    tokio::time::sleep(Duration::from_secs(2)).await;
    let mem_count = ctx.memory_memory_count_for_retro(ctx.retro_id).await;
    assert_eq!(mem_count, 0);
    let retro = ctx.fetch_retro(ctx.retro_id).await;
    assert_eq!(retro.status, "pending_review");
}

#[tokio::test]
async fn approve_writes_memory() {
    let ctx = TestContext::with_pending_retro().await;
    ctx.approve_retro_as_ceo(ctx.retro_id).await;
    let retro = ctx.fetch_retro(ctx.retro_id).await;
    assert_eq!(retro.status, "persisted");
    assert!(retro.memory_memory_path.is_some());
}

// 5.4..5.10

§7 — Dependencies

Upstream: TASK-OKR-001, TASK-CUO-101. Cross-module: TASK-OKR-005 (check-in history), TASK-AI-003 (draft + framing), TASK-MEMORY-111 (PII + persist), TASK-AUTH-101 (CEO role), TASK-MCP-007 (cron).

§10 — Failure modes

FailureDetectionOutcomeRecovery
AI timeoutretry 1xsev-2; minimal draft fallbackmanual fill
Duplicate quarterUNIQUEskipinherent
0 KRs in quarterinherentempty draft + sev-3inherent
memory write failsev-1retryinherent
User dismissesstatus=dismissedretained for auditinherent
Regenerate while reviewinglast-winsinherentinherent
Wrong framing for tenantCEO overrideinherentregenerate
Cross-tenant approveRLS403inherent
Concurrent approveUPDATE WHERE pendingfirst winsinherent
Q-end on weekendcron runs Mondayinherentinherent

§11 — Implementation notes


End of TASK-OKR-007 spec.