Task — engineering-spec@1

"OKR weekly check-in — 1-10 confidence + rationale per KR with rolling 4-week history + trend visualization"

draftTASK-OKR-005
module okr · class product · priority p0 · created 2026-05-17 · shipped null
depends on TASK-OKR-001 · blocks TASK-OKR-006

§1 — Description (BCP-14 normative)

The OKR service MUST ship weekly check-in at services/okr/src/checkin/ with 1-10 confidence + rationale + 4-week trend + Monday reminder, 3 memory audit kinds.

  1. MUST validate confidence in 1-10 per DEC-2000 (CHECK constraint).
  1. MUST validate confidence_trend against closed enum per DEC-2001.
  1. MUST compute trend at trend_calculator.rs::trend(kr, current_week):
  1. MUST define table at migration 0005: ``sql CREATE TABLE okr_weekly_checkins ( checkin_id UUID PRIMARY KEY, tenant_id UUID NOT NULL, kr_id UUID NOT NULL, iso_week CHAR(8) NOT NULL, -- 'YYYY-Www' e.g. '2026-W20' version INT NOT NULL DEFAULT 1, confidence INT NOT NULL CHECK (confidence >= 1 AND confidence <= 10), rationale TEXT NOT NULL, submitted_by UUID NOT NULL, submitted_at TIMESTAMPTZ NOT NULL DEFAULT now(), trace_id CHAR(32), UNIQUE (tenant_id, kr_id, iso_week, version) ); CREATE INDEX checkins_kr_week_idx ON okr_weekly_checkins(tenant_id, kr_id, iso_week DESC); ALTER TABLE okr_weekly_checkins ENABLE ROW LEVEL SECURITY; CREATE POLICY checkins_rls ON okr_weekly_checkins USING (tenant_id = current_setting('auth.tenant_id')::uuid) WITH CHECK (tenant_id = current_setting('auth.tenant_id')::uuid); REVOKE UPDATE, DELETE ON okr_weekly_checkins FROM cyberos_app; ``
  1. MUST correct via new row per DEC-2002 — version increments; original preserved.
  1. MUST run Monday reminder cron per DEC-2003 — for each KR owner missing this-week check-in, emit okr.checkin_missing_alert.
  1. MUST expose endpoints: ``text POST /v1/okr/krs/{id}/checkins body: {confidence, rationale} GET /v1/okr/krs/{id}/checkins (history desc time) GET /v1/okr/krs/{id}/trend (derived from history) ``
  1. MUST emit 3 memory audit kinds per DEC-2004. PII per TASK-MEMORY-111: rationale SHA-256 hashed.
  1. MUST thread trace_id from submit → audit.
  1. MUST NOT mutate prior check-in per DEC-2002.
  1. MUST NOT accept confidence outside 1-10 per DEC-2000.

§2 — Why this design

Why 1-10 + rationale (DEC-2000)? Number alone is noise; rationale captures the why for retro/learning.

Why immutable with version (DEC-2002)? Audit lineage; can't backdate confidence to look good in retros.

Why ISO week (DEC-2002)? Calendar weeks standard; UNIQUE constraint per week+version.

Why Monday reminder (DEC-2003)? Common cadence; gives owner full Monday to complete.


§3 — API contract

Sample check-in:

POST /v1/okr/krs/{id}/checkins
{
  "confidence": 7,
  "rationale": "On track; one risk is Q3 hire delay impacting milestone 4."
}

Response with trend:

{
  "checkin_id": "uuid",
  "confidence": 7,
  "iso_week": "2026-W20",
  "trend": "steady",
  "rolling_4w_avg": 6.8
}

§4 — Acceptance criteria

  1. Confidence 1-10 CHECK. 2. confidence_trend enum cardinality 4. 3. Rationale required. 4. Per-week immutable. 5. Correction via version+1. 6. UNIQUE(kr_id, iso_week, version). 7. Trend calc from 4-week rolling. 8. <2 prior weeks → trend=unknown. 9. Monday reminder cron. 10. 3 memory audit kinds emitted. 11. PII scrubbed (rationale SHA256). 12. RLS denies cross-tenant. 13. KR owner-only submit. 14. Trace_id preserved. 15. Append-only via REVOKE. 16. History query desc time. 17. Trend recompute on each new check-in. 18. Missing check-in alert sev-3. 19. ISO week format enforced. 20. Rationale length capped 2000 chars.

§5 — Verification

#[tokio::test]
async fn confidence_range_enforced() {
    let r = ctx.submit_checkin(ctx.kr_id, 11, "test").await;
    assert!(r.is_err());
    let r2 = ctx.submit_checkin(ctx.kr_id, 0, "test").await;
    assert!(r2.is_err());
}

#[tokio::test]
async fn correction_creates_v2() {
    let ctx = TestContext::with_checkin_v1().await;
    ctx.submit_checkin_correction(ctx.kr_id, 8, "corrected").await;
    let history = ctx.fetch_checkins(ctx.kr_id, "2026-W20").await;
    assert_eq!(history.len(), 2);
    assert_eq!(history[1].version, 2);
}

#[tokio::test]
async fn trend_improving_after_3w_climb() {
    let ctx = TestContext::with_checkin_history(vec![5, 6, 7]).await;
    ctx.submit_checkin(ctx.kr_id, 9, "great week").await;
    let trend = ctx.fetch_trend(ctx.kr_id).await;
    assert_eq!(trend, "improving");
}

// 5.4..5.10

§7 — Dependencies

Upstream: TASK-OKR-001. Downstream: TASK-OKR-006 (Monday digest uses check-ins). Cross-module: TASK-MCP-007 (reminder cron), TASK-AUTH-101 (KR owner role), TASK-MEMORY-111 (PII).

§10 — Failure modes

FailureDetectionOutcomeRecovery
Confidence out of rangeCHECK4001-10
Rationale emptyvalidate400provide text
Duplicate version raceUNIQUE409retry with v+1
Trend with <2 dataunknowninherentinherent
Cron skippedcatch-upsev-3inherent
KR owner inactiveskip reminderinherentreassign
Cross-tenant submitRLS403inherent
ISO week parse failvalidate400YYYY-Www format
Rationale > 2000 charsvalidate400shorten
Concurrent submitUNIQUEsecond 409retry

§11 — Implementation notes


End of TASK-OKR-005 spec.