"RES hiring memo CUO draft — skill-gap × CRM pipeline trigger → CEO+CFO review queue with cost-benefit projection"
§1 — Description (BCP-14 normative)
The RES service MUST ship hiring memo at services/res/src/hiring/ triggered by skill-gap + pipeline + cost projection + CEO+CFO dual-sign, 4 memory audit kinds.
- MUST validate
hire_recommendationagainst closed enum per DEC-2071.
- MUST detect trigger at
gap_detector.rs::detect(tenant)per DEC-2070:
- SELECT members from TASK-RES-001 matrix where
allocation_flag='over_allocated'for ≥4 consecutive weeks - SELECT TASK-CRM-001 deals advancing stage in same period
- If both conditions met: enqueue memo draft
- MUST project cost at
cost_projector.rs::project(role, region, contract_type)per DEC-2072:
- Base salary band per role (TASK-RES-007 future or hardcoded table for v1)
- TASK-REW-004 statutory deductions per region (BHXH + BHYT + BHTN + PIT)
- Total fully-loaded 6-month cost
- Pipeline-derived revenue offset
- MUST generate memo at
memo_generator.rs::generate(tenant, gap, costs):
- Skill gap summary
- Pipeline correlation
- Cost projection table
- 6-month ROI estimate
- AI recommendation (TASK-AI-003) with reasoning
- MUST require CEO + CFO dual-sign per DEC-2073 — same-person rejected.
- MUST define table at migration
0004: ``sql CREATE TABLE res_hiring_memos ( memo_id UUID PRIMARY KEY, tenant_id UUID NOT NULL, trigger_kind TEXT NOT NULL, -- 'auto_gap_pipeline' | 'manual_ceo' | 'manual_chro' role_title TEXT NOT NULL, region TEXT NOT NULL, proposed_contract_type TEXT, memo_body_jsonb JSONB NOT NULL, recommendation TEXT NOT NULL CHECK (recommendation IN ('hire_immediate','hire_q_plus_1','defer_pipeline_uncertain','reject_no_business_case')), status TEXT NOT NULL DEFAULT 'pending_review' CHECK (status IN ('pending_review','ceo_signed','cfo_signed','approved_for_action','dismissed')), ceo_signed_by UUID, ceo_signed_at TIMESTAMPTZ, cfo_signed_by UUID, cfo_signed_at TIMESTAMPTZ, hire_slot_reserved BOOLEAN NOT NULL DEFAULT false, trace_id CHAR(32), created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); ALTER TABLE res_hiring_memos ENABLE ROW LEVEL SECURITY; CREATE POLICY hiring_memos_rls ON res_hiring_memos USING (tenant_id = current_setting('auth.tenant_id')::uuid) WITH CHECK (tenant_id = current_setting('auth.tenant_id')::uuid); REVOKE UPDATE, DELETE ON res_hiring_memos FROM cyberos_app; GRANT UPDATE (status, ceo_signed_by, ceo_signed_at, cfo_signed_by, cfo_signed_at, hire_slot_reserved) ON res_hiring_memos TO cyberos_app;``
- MUST expose endpoints: ``
text POST /v1/res/hiring-memos (CEO/CHRO manual trigger) POST /v1/res/hiring-memos/{id}/ceo-sign POST /v1/res/hiring-memos/{id}/cfo-sign POST /v1/res/hiring-memos/{id}/dismiss GET /v1/res/hiring-memos (list)``
- MUST emit 4 memory audit kinds per DEC-2074. PII per TASK-MEMORY-111: cost projections SHA-256 hashed.
- MUST thread trace_id from trigger → projector → memo → sign → audit.
- MUST NOT auto-action without dual-sign per DEC-2073.
- MUST NOT allow same person to sign both roles per DEC-2073.
§2 — Why this design
Why dual-sign (DEC-2073)? Headcount = major financial commitment; CFO budget perspective + CEO strategic both required.
Why cost projection (DEC-2072)? Without numbers, debate is opinion; projection grounds discussion.
Why pipeline correlation (DEC-2070)? Over-allocation alone may be temporary; pairing with deal velocity confirms sustained demand.
Why 4 recommendations (DEC-2071)? Captures real decisions — immediate, next quarter, defer, reject — bounded prevents waffling.
§3 — API contract
Sample memo:
{
"memo_id": "uuid",
"role_title": "Senior Backend Engineer",
"region": "vn-1",
"memo_body_jsonb": {
"skill_gap": "Backend capacity at 115% for 6 weeks",
"pipeline_correlation": "$300k in committed deals requiring backend work in next quarter",
"cost_projection": {
"annual_base_vnd": 600000000,
"annual_si_employer_vnd": 144000000,
"annual_total_vnd": 744000000,
"6mo_total_vnd": 372000000
},
"roi_estimate": "Break-even at month 2 if pipeline holds",
"ai_recommendation": "hire_immediate"
},
"recommendation": "hire_immediate",
"status": "pending_review"
}
§4 — Acceptance criteria
- hire_recommendation enum cardinality 4. 2. Auto-trigger at 4w over-allocation + pipeline velocity. 3. Manual trigger CEO/CHRO. 4. Cost projection includes statutory. 5. CEO + CFO dual-sign required. 6. Same-person rejected. 7. approved_for_action requires both signs. 8. hire_slot_reserved set on approval (capacity matrix marker). 9. 4 memory audit kinds emitted. 10. PII scrubbed (cost SHA256). 11. RLS denies cross-tenant. 12. Trace_id preserved. 13. Append-only via REVOKE except status cols. 14. AI failure → minimal memo + sev-2. 15. Dismiss allowed at any pre-action stage. 16. Status workflow enforced. 17. Auto-trigger idempotent per tenant per week. 18. Memo body JSONB schema validated. 19. Region required (drives cost projection). 20. List endpoint paginated.
§5 — Verification
#[tokio::test]
async fn auto_trigger_at_4w_over_allocation_plus_pipeline() {
let ctx = TestContext::with_4w_over_allocation_and_new_deals().await;
ctx.run_hiring_detector(ctx.tenant_id).await;
let memos = ctx.fetch_hiring_memos(ctx.tenant_id).await;
assert!(!memos.is_empty());
}
#[tokio::test]
async fn dual_sign_required() {
let ctx = TestContext::with_pending_memo().await;
ctx.ceo_sign(ctx.memo_id).await;
let memo = ctx.fetch_memo(ctx.memo_id).await;
assert_eq!(memo.status, "ceo_signed");
ctx.cfo_sign(ctx.memo_id).await;
let memo2 = ctx.fetch_memo(ctx.memo_id).await;
assert_eq!(memo2.status, "approved_for_action");
assert!(memo2.hire_slot_reserved);
}
#[tokio::test]
async fn same_person_both_roles_rejected() {
let ctx = TestContext::with_pending_memo().await;
ctx.ceo_sign_as(ctx.user_a, ctx.memo_id).await;
let r = ctx.try_cfo_sign_as(ctx.user_a, ctx.memo_id).await;
assert!(r.is_err());
}
// 5.4..5.10
§7 — Dependencies
Upstream: TASK-CUO-101, TASK-CRM-001. Cross-module: TASK-RES-001 (over-allocation signal), TASK-RES-002 (slot reserved), TASK-HR-002 (contract type), TASK-REW-004 (statutory deductions), TASK-AI-003 (LLM), TASK-AUTH-101 (CEO/CFO roles), TASK-MEMORY-111 (PII).
§10 — Failure modes
| Failure | Detection | Outcome | Recovery |
|---|---|---|---|
| AI timeout | retry 1x | minimal memo + sev-2 | CEO completes manually |
| Cost projection fail | catch | sev-2; use rough estimate | data fix |
| One signer missing | gate | inherent | wait for second |
| Same-person dual-sign | validate | 403 | different signer |
| Auto-trigger noise | rate limit per tenant/week | inherent | inherent |
| Pipeline data stale | warn | proceed with sev-3 | data refresh |
| Cross-tenant memo | RLS | 403 | inherent |
| hire_slot reservation race | UPDATE WHERE | first wins | inherent |
| Stage revert post-approval | manual CEO action | inherent | reject memo |
| Region invalid | validate | 400 | use valid |
§11 — Implementation notes
- §11.1 Detector cron via TASK-MCP-007
kind: 'res.hiring_gap_detection', weekly. - §11.2 Cost projector uses TASK-REW-004 SI rate formulas + TASK-HR-005 minimum_wage policy.
- §11.3 AI prompt: structured "Given gap + pipeline + costs, recommend one of: hire_immediate / hire_q+1 / defer / reject. Explain reasoning."
- §11.4 hire_slot_reserved column drives TASK-RES-002 Gantt to show pending hire row.
- §11.5 memory audit body: memo_id, role, region, recommendation; cost SHA256.
End of TASK-RES-004 spec.