Task — engineering-spec@1
"RES over/under-allocation flags — 110% warning / 60% under-utilization threshold with weekly digest to CHRO"
draftTASK-RES-003
§1 — Description (BCP-14 normative)
The RES service MUST ship flag computation + weekly digest at services/res/src/flags/ with 110/60 thresholds + Friday CHRO digest, 2 memory audit kinds.
- MUST validate
allocation_flagagainst closed enum per DEC-2051.
- MUST compute flag at
computer.rs::compute(matrix_row)per DEC-2050:
- utilization_pct = allocated_hours / capacity_hours
- >= 110% → over_allocated
- >= 60% AND < 110% → healthy
- < 60% → under_utilized
- MUST cache flag on TASK-RES-001 matrix row: ``
sql ALTER TABLE res_capacity_matrix ADD COLUMN allocation_flag TEXT CHECK (allocation_flag IS NULL OR allocation_flag IN ('over_allocated','healthy','under_utilized')); ALTER TABLE res_capacity_matrix ADD COLUMN flag_computed_at TIMESTAMPTZ; GRANT UPDATE (allocation_flag, flag_computed_at) ON res_capacity_matrix TO cyberos_app;``
- MUST schedule weekly digest Friday 16:00 tenant_tz per DEC-2053 via TASK-MCP-007 — count flagged members + send to CHRO via TASK-EMAIL-009 + TASK-CHAT-005.
- MUST define digest table at migration
0003: ``sql CREATE TABLE res_weekly_digests ( digest_id UUID PRIMARY KEY, tenant_id UUID NOT NULL, iso_week CHAR(8) NOT NULL, over_count INT NOT NULL, under_count INT NOT NULL, healthy_count INT NOT NULL, flagged_members_jsonb JSONB NOT NULL, sent_at TIMESTAMPTZ, trace_id CHAR(32), created_at TIMESTAMPTZ NOT NULL DEFAULT now(), UNIQUE (tenant_id, iso_week) ); ALTER TABLE res_weekly_digests ENABLE ROW LEVEL SECURITY; CREATE POLICY digests_rls ON res_weekly_digests USING (tenant_id = current_setting('auth.tenant_id')::uuid) WITH CHECK (tenant_id = current_setting('auth.tenant_id')::uuid); REVOKE UPDATE, DELETE ON res_weekly_digests FROM cyberos_app; GRANT UPDATE (sent_at) ON res_weekly_digests TO cyberos_app;``
- MUST expose endpoints: ``
text GET /v1/res/flags/summary (current week counts) GET /v1/res/weekly-digests (list)``
- MUST emit 2 memory audit kinds per DEC-2054. PII per TASK-MEMORY-111: flag enums (public) ok; counts ok.
- MUST thread trace_id from compute / cron → digest → audit.
- MUST NOT hardcode thresholds outside DEC-2050 (consider future per-tenant override).
§2 — Why this design
Why 110%/60% (DEC-2050)? Industry standard; balance burnout prevention + bench cost.
Why cache on matrix row (DEC-2052)? Avoids recomputing on every query; UI shows flag with row.
Why weekly digest (DEC-2053)? Daily would be noise; monthly too late.
§3 — API contract
Sample summary:
{
"iso_week": "2026-W20",
"over_count": 3,
"healthy_count": 12,
"under_count": 2,
"over_members": [
{"member_id": "uuid", "name": "Alice", "utilization_pct": 115}
]
}
§4 — Acceptance criteria
- allocation_flag enum cardinality 3. 2. >=110% → over_allocated. 3. <60% → under_utilized. 4. Threshold boundaries exact (110 and 60 inclusive of healthy edge). 5. Flag cached on matrix row. 6. Weekly digest Friday 16:00. 7. Digest sent via email + chat. 8. UNIQUE(tenant_id, iso_week) idempotency. 9. 2 memory audit kinds emitted. 10. PII: flag enums + counts ok. 11. RLS denies cross-tenant. 12. Trace_id preserved. 13. Append-only via REVOKE except sent_at. 14. 0-capacity member → flag=null + sev-3. 15. Recompute on TASK-RES-002 commit. 16. Recompute on TASK-RES-001 batch. 17. Digest skip if 0 members. 18. rust_decimal precision for utilization. 19. CHRO-only digest config. 20. flag_computed_at populated.
§5 — Verification
#[tokio::test]
async fn over_allocated_at_115pct() {
let row = matrix_row_with(capacity: 40, allocated: 46);
assert_eq!(flags::compute(&row), Flag::OverAllocated);
}
#[tokio::test]
async fn under_at_50pct() {
let row = matrix_row_with(capacity: 40, allocated: 20);
assert_eq!(flags::compute(&row), Flag::UnderUtilized);
}
#[tokio::test]
async fn weekly_digest_sent_friday() {
let ctx = TestContext::with_3_over_2_under_members().await;
ctx.run_friday_digest_cron().await;
let digest = ctx.fetch_latest_digest().await;
assert_eq!(digest.over_count, 3);
assert_eq!(digest.under_count, 2);
let email_sent = ctx.email_send_count().await;
assert_eq!(email_sent, 1);
}
// 5.4..5.10
§7 — Dependencies
Upstream: TASK-RES-001. Cross-module: TASK-RES-002 (UI hook to recompute), TASK-EMAIL-009, TASK-CHAT-005, TASK-MCP-007, TASK-AUTH-101, TASK-MEMORY-111.
§10 — Failure modes
| Failure | Detection | Outcome | Recovery |
|---|---|---|---|
| 0 capacity | null flag + sev-3 | inherent | data fix |
| Cron skipped | catch-up | inherent | inherent |
| Duplicate digest | UNIQUE | skip | inherent |
| Email send fail | sev-2 | retry | inherent |
| Chat send fail | sev-2 | retry | inherent |
| Negative allocation | flag=under (clamp 0) | sev-3 | data fix |
| Decimal precision | rust_decimal | inherent | inherent |
| Cross-tenant query | RLS | 0 rows | inherent |
| Threshold mid-batch change | inherent | inherent | future tenant override |
| Bench member (contractor) | inherent flag computed | inherent | inherent |
§11 — Implementation notes
- §11.1 Flag computer pure function:
(capacity, allocated) → Flag. - §11.2 Digest cron via TASK-MCP-007
kind: 'res.weekly_flags_digest', Friday 16:00. - §11.3 memory audit body: tenant_id, week, counts; member uuids included for flagged.
- §11.4 Future: per-tenant threshold override table for industries with different norms.
- §11.5 Digest excludes contractors (per TASK-HR-002 type override).
End of TASK-RES-003 spec.