Skip to content

STORY-F-015: finnest_compliance schemas + ~100 credential_types seed

Epic: Compliance Seed Priority: Must Have Story Points: 3 Status: Not Started Assigned To: Unassigned Created: 2026-04-17 Sprint: 3


User Story

As a Recruitment Consultant (post-launch) and as a developer (now), I want the compliance.credential_types table populated with ~100 real Australian credential types across industries, so that downstream compliance checks (roster assignment, placement) have real data to reason over and multi-industry profiles (F-019) have targets to reference.


Description

Background

ADR-011-F makes FinnestCompliance.check/2 the single synchronous cross-domain exception — it gates writes in 5 domains (roster, timekeep, payroll, clients, assets). Before that check function is useful, the credential registry needs real data. Brainstorm-05 (multi-industry design) + brainstorm-11 (Traffio feature map) + brainstorm-12 (competitor audit) enumerate the required credential types.

This story builds the finnest_compliance schemas + seeds ~100 credential types — hierarchical, industry-tagged, with expiry rules. Industry profiles (labour_hire + construction) that reference these credentials land in F-019.

Scope

In scope:

  • Migration creating compliance schema:
  • compliance.credential_types — id UUID, parent_id UUID (FK self-reference, nullable — hierarchy), code VARCHAR UNIQUE, name VARCHAR, category VARCHAR (high_risk_work | construction | mining | logistics | defence | retail | traffic | general | ...), nationally_recognized BOOLEAN, issuing_jurisdictions JSONB (["NSW","VIC",...]), mutual_recognition BOOLEAN, renewal_period_months INTEGER nullable, prerequisites JSONB DEFAULT '[]' (credential_type_id refs), industries JSONB DEFAULT '[]', verification_method VARCHAR (document|api|manual|self_declared), document_templates JSONB, metadata JSONB, inserted_at, updated_at, deleted_at — note NO org_id (platform-wide reference data per data.md)
  • Add FinnestCompliance.CredentialType schema; allowlist for architecture tests (reference-data exception, no org_id)
  • Add FinnestCompliance.CredentialCache ETS GenServer — loads credential types at boot + 6-hourly refresh; invalidated by credential_type_updated event
  • Seed file priv/repo/seeds/compliance_credential_types.exs — ~100 credentials per brainstorm-05 seed list:
  • Construction: white_card (General Construction Induction), EWP (Elevated Work Platform), telehandler, crane_licence (CB, CV, C0, C1, C2, C6, CN, CT), rigging_basic, rigging_intermediate, rigging_advanced, dogging, scaffolding_basic/intermediate/advanced, asbestos_awareness, asbestos_removal_class_A/B, confined_space, height_safety, site_induction_generic
  • Mining: standard_11, coal_board_medical, drug_alcohol_clear, underground_induction_coal/metal, surface_induction, shot_firer, coal_mine_manager
  • Logistics: driver_licence_C, driver_licence_LR, driver_licence_MR, driver_licence_HR, driver_licence_HC, driver_licence_MC, fatigue_management_bfm, fatigue_management_afm, dangerous_goods, forklift, reach_truck
  • Defence: baseline_clearance, nv1, nv2, pv, disp_registration
  • Retail: rsa, rcg, food_safety_level_½, barista_cert, cash_handling
  • Traffic: traffic_controller_nsw/vic/qld, traffic_management_implementer, traffic_management_designer, road_safety_audit
  • General (cross-industry): first_aid_provide, first_aid_hltaid011, cpr_hltaid009, manual_handling, working_at_heights, wwcc (Working With Children Check, per state), police_check_national, right_to_work_citizen/permanent_resident/visa
  • Healthcare (future industry): ahpra_nursing, ahpra_midwifery, mandatory_reporter_training
  • Hierarchical entries: e.g. hrwl_forklift has parent_id = hrwl_license (High Risk Work License top-level); rigging_advanced has prerequisites = [rigging_intermediate]; rigging_intermediate has prerequisites = [rigging_basic]
  • FinnestCompliance.CredentialTypes context module: get_by_code/1, list_by_industry/1, list_required_for/1, prerequisites_of/1 (recursive)
  • Compliance.check/2 skeleton — the function signature + placeholder implementation returning :compliant always for now; real impl lands in Scout+Verify sprints when roster/timekeep gates are built
  • Architecture test reference_data_no_org_id_test.exs: CredentialType is on allowlist (no org_id by design)

Out of scope:

  • Industry profiles (F-019)
  • Award rates / classifications (Migration Phase 4 / Scout+Verify sprints)
  • Full Compliance.check/2 implementation with real rules (Scout+Verify sprints)
  • DVS integration (Migration Phase 2)
  • Ongoing monitoring / re-screening (Migration Phase 2)
  • Labour-hire licensing tracking (Migration Phase 3+)

Technical Notes

  • Module namespace: FinnestCompliance.* (flat), not Finnest.Compliance.* (dotted). Same Boundary constraint as F-003 (FinnestCore.*) and F-012 (FinnestAgents.*). Schema lives at FinnestCompliance.CredentialType; context at FinnestCompliance.CredentialTypes; cache at FinnestCompliance.CredentialCache; top-level check/2 at FinnestCompliance.
  • Seed format: individual %CredentialType{} maps or SQL INSERT statements; prefer Elixir seed so Cloak/validation run
  • Run seed via mix run priv/repo/seeds/compliance_credential_types.exs — idempotent (upsert by code)
  • Hierarchy encoded via parent_id — small enough to fit in memory; no need for materialized path / nested set
  • issuing_jurisdictions example: ["NSW", "VIC", "QLD", "SA", "WA", "TAS", "ACT", "NT"] for nationally recognised; single state for state-specific
  • renewal_period_months: null means no expiry (e.g. White Card); renewal_period_months: 12 for annual refresh (e.g. First Aid CPR, Working at Heights)
  • CredentialCache ETS: populated on boot from DB; refreshed every 6 hours or on credential_type_updated event; miss path falls back to DB query
  • Real Australian credential catalogue — reference Safe Work Australia + state WorkSafe publications for accuracy. Don't invent codes.

Dependencies

  • Blocked by: STORY-F-007 (architecture tests + reference-data allowlist pattern), STORY-F-008 (tenant enforcement — confirm reference-data allowlist works)
  • Unblocks: F-019 (industry profiles reference credential_type_ids)

Acceptance Criteria

  • Migration creates compliance.credential_types with all fields per spec; reversible (CQ-11)
  • FinnestCompliance.CredentialType schema compiles; Repo.all/2 works (with reference-data allowlist so prepare_query doesn't require tenant context)
  • Seed script populates ≥100 credential types across all tracked industries
  • Hierarchical structure verified: hrwl_forklift.parent_id == hrwl_license.id; rigging chain (basic → intermediate → advanced) with prerequisites set
  • CredentialTypes.get_by_code("white_card") returns the white_card record
  • CredentialTypes.list_by_industry("construction") returns all construction-tagged credentials (≥10 entries)
  • CredentialTypes.prerequisites_of("rigging_advanced") returns [rigging_intermediate, rigging_basic] transitively
  • CredentialCache loads on boot; cache hit <1ms vs DB lookup ~5-10ms
  • FinnestCompliance.check/2 callable; returns :compliant (placeholder)
  • Architecture test: CredentialType has no org_id column; allowlist confirms exception
  • Seed script idempotent: running twice produces same DB state
  • mix format, credo --strict, dialyzer, mix boundary all green

Testing Requirements

  • Unit: context functions (get_by_code, list_by_industry, prerequisites_of) happy + error paths
  • Unit: CredentialCache boot + refresh + invalidation on event
  • Integration: seed → DB → context query returns correct results
  • Data audit: count per industry matches seed file expectations; all foreign-key prerequisites resolve

References