CRB L4 Spec — Module E: Execution

Governing note: The W module's Python implementation is hawk/. The CanaryGO package is cmd/hawk. This was confirmed during the L4 fill pass — Hawk IS the cross-domain case management system that generalizes Fox (Q.3) across all investigable domains.

W-over-Fox: Hawk extends the Fox pattern with four additions: 1. Incident type catalog (HawkIncidentType) — structured case taxonomy, not free-form type string 2. incident_class determines resolution track (internal vs external) 3. generate_card() — structured narrative card generation (versioned, invalidatable) 4. get_wizard_template() — incident type wizard (guided case opening)


E.1–E.2 — Exception Detection and Aggregation

Source: velocity_engine.py, chirp/rule_engine.py (cross-domain adaptation)
Architecture: E.1–E.2 generalize the Chirp detection pattern across all domains (not just LP). The same velocity anomaly engine that detects LP exceptions is reused for inventory, labor, and financial anomalies.

E.1 — Cross-Domain Exception Detection

L4 ID Function Behavior
E.1.1.1 compute_baseline(daily_values: list[float]) → BaselineStats Rolling window (default 30 days). Computes mean and stddev. Returns BaselineStats(mean, std_dev, sample_count). Requires ≥3 values; returns None baseline if insufficient data.
E.1.2.1 detect_anomaly(current_value, baseline, sigma_threshold=2.0) → AnomalyResult z_score = (current_value - baseline.mean) / baseline.std_dev. AnomalyType: spike (z > +threshold), drop (z < -threshold), zero (current_value == 0 AND baseline.mean > 0). Returns AnomalyResult(is_anomaly, anomaly_type, z_score, current_value, baseline).
E.1.3.1 Day-of-week pattern learning Baseline computed per (merchant_id, metric_key, day_of_week) — separate baselines for Mon/Tue/Wed etc.
E.1.4.1 Hour-of-day pattern learning Baseline computed per (merchant_id, metric_key, hour_of_day) — separate baselines for each hour slot
E.1.5.1 Cross-domain exception alert When detect_anomaly fires: publish cross-domain exception event with domain label (inventory, finance, labor, LP)

AnomalyResult fields: is_anomaly: bool, anomaly_type: str ("spike" | "drop" | "zero" | None), z_score: float, current_value: float, baseline: BaselineStats

Constants: - DEFAULT_SIGMA_THRESHOLD = 2.0 - DEFAULT_WINDOW_DAYS = 30

E.2 — Exception Aggregation

L4 ID Activity Behavior
E.2.1.1 Collect domain-scoped exceptions Aggregate exceptions from Q (LP), D (inventory), F (finance), L (labor) into unified exception queue
E.2.2.1 Dedup by subject Group exceptions by subject (employee_id, vendor_id, customer_id) across domains
E.2.3.1 Correlation scoring Score subject risk across domains: LP alert + inventory variance + off-clock = elevated composite score

E.3 — Case CRUD (HawkCaseService)

Source: hawk/case_service.pyHawkCaseService

Status FSM

open → investigating → pending_review → escalated → closed
                                      ↗            ↗
                       investigating ──────────────→ referred_to_le

VALID_TRANSITIONS:

"open":           ["investigating"]
"investigating":  ["pending_review", "escalated"]
"pending_review": ["escalated", "closed", "referred_to_le"]
"escalated":      ["closed", "referred_to_le"]
"closed":         []          # terminal
"referred_to_le": []          # terminal

Resolution Tracks

incident_class → action code vocabulary: - internal → INTERNAL_ACTION_CODES: CLOSED_UNFOUNDED, CORRECTIVE_ACTION, INTERVIEWED_NO_CASE, QUIT_BEFORE_INTERVIEW, QUIT_DURING_INTERVIEW, REPORTED_TO_ATF, TERMINATED_PROSECUTED, TERMINATED_RELEASED, UNDER_INVESTIGATION - external | critical_smart_alert | incident → EXTERNAL_ACTION_CODES: CLOSED_UNFOUNDED, PROSECUTED, RELEASED_ADULT, RELEASED_TO_GUARDIAN, RELEASED_TO_POLICE, UNDER_INVESTIGATION

HawkCaseService Methods (L4 Activities)

L4 ID Method Key Behavior
E.3.1.1 create_case(merchant_id, location_id, incident_type, opened_by, narrative, source_code, assigned_to, chirp_alert_id) Lookup HawkIncidentType by incident_type; raises ValueError if not found. Derive incident_class and de_pv_flag from HawkIncidentType. Lookup HawkSource by source_code if provided. INSERT HawkCase(status="open"). Auto-create timeline entry event_type="created".
E.3.1.2 get_case(case_id) Query HawkCase by id; returns None if not found (vs Fox which raises NoResultFound — important difference)
E.3.1.3 list_cases(merchant_id, status, incident_class, limit=50, offset=0) Filter by merchant + optional status + optional incident_class; ORDER BY opened_at DESC
E.3.2.1 advance_status(case_id, new_status, actor_id) Lookup case; get current status; validate VALID_TRANSITIONS[current] contains new_status; on terminal: set closed_at=now(); create timeline entry event_type="status_changed" with event_data={from, to}; flush
E.3.3.1 add_subject(case_id, subject_type, actor_id, employee_id, vendor_entity_id, external_name, notes) Validate subject_type in VALID_SUBJECT_TYPES; lookup case; INSERT HawkSubject; timeline entry event_type="subject_added"
E.3.4.1 add_action(case_id, action_code, actor_id, notes) Lookup case; derive track from incident_class via _CLASS_TO_TRACK; validate action_code against track's valid set; INSERT HawkAction; timeline entry event_type="action_added"

E.3 vs Q.3 (Fox) Differences

Aspect Q.3 Fox E.3 Hawk
Case type input Free-form string from VALID_CASE_TYPES frozenset incident_type lookup against HawkIncidentType catalog
incident_class Not present Derived from HawkIncidentType.incident_class
de_pv_flag Not present Derived from HawkIncidentType.de_pv_flag
Action codes VALID_ACTION_TYPES frozenset (uniform) Split by resolution track (internal vs external)
Not-found behavior .one() raises NoResultFound .first() returns None, caller handles
Narrative card Not present generate_card() produces versioned structured card

E.4 — Remediation Routing

Source: hawk/case_service.pyadd_action(), advance_status()
Source: hawk/tools.pyadvance_workflow tool

Remediation in W is case action recording + status FSM advancement. There is no separate "remediation routing" service — the action code captures the resolution disposition; the status FSM gates advancement.

L4 ID Activity Behavior
E.4.1.1 Record resolution action add_action(case_id, action_code, actor_id) — validates track, creates HawkAction, timeline entry
E.4.1.2 Advance to closed/referred_to_le advance_status(case_id, "closed" or "referred_to_le", actor_id) — sets closed_at, creates timeline entry
E.4.1.3 Approval gate before close MCP tool advance_workflow with target_status="closed" — investigator confirms before status advances
E.4.1.4 Cross-domain dispatch For actions affecting other modules (e.g., labor disciplinary → HR system): E.5.5 remediation routing tool dispatches to target module's API

E.5 — MCP Investigator Tools (E.5.1–E.5.6)

Source: hawk/tools.py
CanaryGO server: cmd/hawk MCP server

Full tool manifest:

Tool Purpose Key Params
create_case Open new Hawk case merchant_id, location_id, incident_type, opened_by, narrative, source_code
get_case Case detail by ID case_id
list_cases List cases with filters merchant_id, status, incident_class, limit, offset
advance_workflow Advance case through FSM case_id, new_status, actor_id — validates VALID_TRANSITIONS
add_subject Link subject to case case_id, subject_type, actor_id, employee_id/vendor_entity_id/external_name
add_action Record resolution action case_id, action_code, actor_id, notes
generate_card Generate narrative card case_id, actor_id → versioned markdown card
get_timeline Case audit trail case_id → HawkTimeline entries ordered ASC
get_wizard_template Incident type wizard incident_type → guided card template

E.5.7 — generate_card() L4 Activities

Source: hawk/card_service.pyHawkCardService.generate_card()

L4 ID Activity Behavior
E.5.7.1 Load case + relationships Query HawkCase; load case.subjects and case.actions via ORM
E.5.7.2 Determine next card version If case.card_id exists: lookup prior HawkCard; next_version = prior.card_version + 1. If no prior card: next_version = 1.
E.5.7.3 Invalidate prior card If prior_card exists and prior_card.invalidated_at is None: set invalidated_at = now()
E.5.7.4 Build card body _build_card_body(case, subjects, actions) → structured markdown with case summary, subject list, action log
E.5.7.5 Build frontmatter JSONB Structured metadata: case_id, merchant_id, incident_type, incident_class, status, card_version, generated_at
E.5.7.6 INSERT HawkCard Fields: case_id, merchant_id, card_version, card_body, frontmatter, generated_by=actor_id
E.5.7.7 Link card to case case.card_id = card.id; flush
E.5.7.8 Timeline entry event_type="card_generated"; event_data={card_id, card_version}

Module E Data Model — Unified Case Schema

One schema. Fox writes into Hawk tables. The chirp_alert_id field on hawk_cases is the tell — the design always intended LP cases to live in the Hawk schema. The Python prototype implemented separate Fox tables as a build-speed shortcut; that is prototype debt. CanaryGO uses the unified schema.

Tables: - hawk_cases — ALL cases, all domains. Fields: status, incident_class, incident_type, de_pv_flag, opened_by, closed_at, chirp_alert_id (FK to LP alert — present only on Fox-created cases), card_id - hawk_incident_types — catalog of valid incident types with display_name, incident_class, de_pv_flag. LP types (theft, void_abuse, cash_variance, return_abuse, etc.) are rows in this table. - hawk_sources — source code catalog (CHIRP_ALERT, MANUAL, DEVICE_ALERT, etc.) - hawk_subjects — case subjects across all domains (employee_id, vendor_entity_id, external_name, subject_type) - hawk_actions — resolution actions with track-validated action codes - hawk_timeline — append-only audit trail (event_type, actor_id, description, event_data JSONB) - hawk_cards — versioned narrative cards (card_body, frontmatter JSONB, card_version, invalidated_at)


W Module vs Q Module: Fox is the LP Pathway INTO Hawk (Resolves OVERLAP #4 from Lint Report)

Hawk is the unified case management system. Fox is the LP alert-to-case creation pathway. Fox does not own tables — it writes into Hawk tables.

Fox (Q.3) Hawk (E.3)
What it is LP alert → case creation pathway + LP-specific MCP tools Unified case management system and investigator surface
Tables None — writes to hawk_cases via chirp_alert_id Owns all case tables
Case creation trigger LP rule fires → alert → investigator creates case Manual, cross-domain exception, device alert, any source
incident_type LP types from hawk_incident_types catalog All types across all domains
UI surface Alert notification → case-from-alert flow Full case management interface, cross-domain views
MCP tools LP-specific tools (create_case_from_alert, link_alert, etc.) operating on Hawk tables Full case CRUD, advance_workflow, generate_card, etc.

Go implementation: cmd/fox provides LP-specific case creation tools. cmd/hawk owns the unified schema and full case management surface. No fox_cases table. The distinction is in tooling and UX, not data ownership.


Pass: GRO-670 / GRO-675 | W L4 spec complete | hawk/ confirmed as W module | OVERLAP #4 resolved in notes | cmd/hawk ≠ unknown