# HUMANABLE — Claude Code Working Document
## Read this on every Claude Code session

---

## Identity

You are working on **HUMANABLE** — the HCMS Sovereign Recruitment Platform.
- Owner: HCMS N.V. (subsidiary of K&K Heritage Group N.V.)
- Position: Pillar I of the HCMS App Suite
- Companion apps: ATLAS (Pillar II), HCMS Academy (III), HCMS Digi Coaches (IV)
- Consumes: Compliance Shield microservice (separate FO)
- Authoritative spec: `docs/specs/HCMS-FO-HUMANABLE-Final-v1.3-Selfcontained-2026.md`
- Companion spec: `docs/specs/HCMS-FO-ComplianceShield-v1.0-2026.md`

## Critical Rules (Never Violate)

1. NO Docker. NO docker-compose. NO Kubernetes. NO containers.
2. NO Redis. Use Laravel database drivers for cache/queue/session.
3. NO Meilisearch. Use PostgreSQL FTS + pgvector.
4. NO AWS managed services. NO Supabase. NO Firebase.
5. NO Laravel Horizon. Use Laravel Pulse.
6. NO external services beyond the §29.7 approved list:
   - Claude API (primary AI)
   - Google Gemini API (Claude fallback)
   - Anthropic embedding service (semantic matching, search)
   - WhatsApp Business Cloud API (Meta)
   - Gmail / Google Workspace SMTP
   - Twilio SMS (critical-only escalation)
   - Google Drive API (offsite backup)
   - EAS Build (mobile CI/CD — v1.2 phase only)
   - Self-hosted Sentry
   - GitHub Actions (CI/CD)
   - Google Calendar / Outlook OAuth
   - Apple Developer + Google Play (mobile distribution, v1.2 phase)
7. NEVER hardcode credentials. Use `.env` + `config()`.
8. NEVER expand scope without explicit IQT Lead + CEO approval.
9. ALWAYS verify against the FO before architectural decisions.

## Tech Stack (Locked)

| Layer | Choice |
| :---- | :---- |
| Framework | Laravel 13.x |
| Language | PHP 8.3+ |
| Local DB | SQLite |
| Production DB | PostgreSQL 16 + pgvector |
| Web Frontend | React 18 + Inertia.js 2 + Tailwind 4 + Vite 8 |
| Mobile (v1.2 phase) | React Expo + EAS Build |
| Admin | Filament 4 (IQT-approved deviation from FO §29.12 — Filament 3 doesn't support Laravel 13) |
| Auth | Laravel Fortify (session) + Sanctum (API tokens) + spatie/laravel-permission (RBAC) |
| MFA | Fortify TOTP, mandatory for staff/employer/vendor/auditor roles per FO §17.2 |
| Cache/Queue/Session | Laravel database driver |
| Search | PostgreSQL FTS + pgvector |
| Background Jobs | Laravel queue worker (systemd-managed) |
| Monitoring | Laravel Pulse + self-hosted Sentry |
| Web Server | Nginx |
| TLS | Let's Encrypt |
| Hosting | Shared LiquidNet US VPS (with ATLAS + Compliance Shield) |
| Port | 8001 |

## Roles & RBAC (per FO §3)

Nine canonical roles. Constants live on `App\Models\User` (e.g. `User::ROLE_ADMIN`).

| Role constant | Role name (spatie) | MFA | Filament panel |
| :---- | :---- | :---- | :---- |
| `ROLE_CANDIDATE` | `candidate` | Optional | No (candidate portal) |
| `ROLE_EMPLOYER` | `employer` | **Required** | No (employer portal) |
| `ROLE_RECRUITER` | `hcms_recruiter` | **Required** | Yes |
| `ROLE_ADMIN` | `hcms_admin` | **Required** | Yes |
| `ROLE_OPS_MANAGER` | `hcms_ops_manager` | **Required** | Yes |
| `ROLE_INTERVIEWER` | `interviewer` | Optional | No (limited Inertia view) |
| `ROLE_VENDOR` | `external_vendor` | **Required** | No (vendor portal) |
| `ROLE_AUDITOR` | `auditor` | **Required** | Yes (read-only) |

`User::BACK_OFFICE_ROLES` and `User::MFA_MANDATORY_ROLES` are the source of truth
for panel + MFA gating. Public visitors are unauthenticated (no row).

Permissions seeded in Phase 1 (`RolesAndPermissionsSeeder`):
`view_admin_panel`, `view_employer_portal`, `view_candidate_portal`,
`view_vendor_portal`, `view_pulse`, `perform_bulk_operations`, `manage_users`,
`manage_roles`, `view_audit_log`. Granular per-resource permissions arrive in
later phases.

## Candidate Authentication (Phase 1 → Phase 13 Path)

Per FO §9.2 + Decision #10, candidates authenticate via **WhatsApp number +
email**. Phase 1 ships **email + password** with the WhatsApp number captured
at registration. Actual WhatsApp OTP wires in when the Meta WhatsApp Business
Cloud API account is provisioned (Phase 13). The `whatsapp_number` column on
`users` is the storage anchor — DO NOT rename it.

## Important Limitation: SQLite vs PostgreSQL

- Local dev uses SQLite.
- pgvector is PostgreSQL-only — the `2022_08_03_000000_create_vector_extension`
  migration is a no-op on SQLite.
- AI matching, embeddings, and pgvector-based search CANNOT be tested locally
  on SQLite. Use the staging PostgreSQL VPS to test these features.
- When writing migrations, ensure they work in both SQLite and PostgreSQL —
  prefer `json` over `jsonb`, use `DB::getDriverName()` guards for any
  driver-specific DDL.

## Shared VPS Topology

HUMANABLE shares a LiquidNet US VPS with ATLAS (port 8002) and Compliance
Shield (port 8003). All three apps share PostgreSQL (separate schemas) and the
self-hosted Sentry instance.

```
LiquidNet US VPS (16 GB RAM · 6 vCPU · 400 GB SSD)
├── humanable.hcmsnv.com → :8001 (this app)
├── atlas.hcmsnv.com → :8002
└── compliance-shield.internal → :8003

PostgreSQL 16 + pgvector
├── humanable schema (this app)
├── atlas schema
└── compliance_shield schema
```

## Compliance Shield Consumption

HUMANABLE calls the Compliance Shield microservice for:
- Tax pre-flight (Module 1) — offer drafting for expat hires
- Article 1639 flag (Module 2) — vacancy creation
- Work permit pre-screen (Module 3) — shortlist for expats
- Local Content reports (Module 4) — quarterly for Tier-1 Operators
- EU AI Act logging (Module 5) — every AI agent decision
- Bias detection (Module 6) — JD + scorecard publish
- GDPR subject portal (Module 7) — candidate data requests
- Right-to-Work verification (Module 8) — pre-offer for expats

Service-token authentication via Laravel Sanctum. Client lives at
`app/Services/ComplianceShield/ComplianceShieldClient.php` and is bound as a
singleton in `AppServiceProvider`.

## Phase 14 — Hardening, Zoho Cutover & Launch

Phase 14 is the final pre-launch substrate: pre-deploy audit
commands, the real backup runner, and the three operator
runbooks (production deploy, Zoho cutover, launch checklist).

- **`harden:audit [--strict]`** — FO §17.2 + §29 pre-deploy
  audit. APP_KEY / debug / cache-queue-session driver / MFA roles
  / mandatory env (Compliance Shield, Claude, Sentry, mail,
  Google Drive) / storage writable / forbidden artifacts
  (Docker / Horizon / Redis / Meilisearch / AWS SDK). Exits 1
  on any red; `--strict` also fails on yellow. Wire as a CI gate
  on the production deploy workflow.
- **`launch:readiness [--phase=soft|full]`** — go/no-go check
  against user-visible state (admin user exists, all 8 roles
  seeded, Zoho cutover ran, employer link, webhook subscribers).
  `--phase=full` adds Claude/Gemini key + Compliance Shield URL
  + token checks.
- **`backup:run [--skip-drive]`** — FO §17.7 daily backup.
  pg_dump (PostgreSQL) or sqlite copy (local), tars `storage/app`,
  uploads both artefacts to Google Drive when configured. Wire
  into the scheduler at 02:00 local.
- **`App\Services\Backup\GoogleDriveBackupClient`** — Google
  OAuth refresh-token → multipart upload to Drive v3. Throws on
  missing creds so the backup command surfaces it cleanly.

Runbooks (all in `docs/runbooks/`):
- `production-deploy.md` — 8-section LiquidNet cutover with
  pre-deploy gates, deps, migrate, smoke, rollback, comms.
- `zoho-cutover.md` — Zoho export → dry-run on staging →
  prod import → DNS switch → rollback recipe.
- `launch-checklist.md` — Soft + Full launch gates organised by
  infrastructure / application / external services / pilot
  employers / compliance / DNS / comms.

## Phase 13 — Integrations & Search

Phase 13 turns the Phase 6 / 8 stubs into real outbound calls and
adds the FO §18.7 PostgreSQL FTS.

- **`App\Services\Integrations\WhatsAppCloudClient`** — Meta Graph
  `/{version}/{phone_number_id}/messages` for text + approved
  templates. `WhatsAppChannel` now calls it; missing creds still
  returns `deferred` so the FO §24.3 dispatcher falls through.
- **`App\Services\Integrations\TwilioSmsClient`** — real
  `Messages.json` POST with HTTP basic auth. `SmsChannel` wired.
- **`App\Services\Integrations\AsanaClient`** + `asana:sync`
  artisan command — mirrors open vacancies as Asana tasks (CG
  POWER Method mandate, FO §15).
- **`App\Services\Integrations\WebhookDispatcher`** — outbound
  subscriber broadcasts. HMAC-SHA256 signed envelope under
  `X-HUMANABLE-Signature: sha256=…` + event under
  `X-HUMANABLE-Event:`. Five consecutive failures auto-pauses the
  subscription. Wired into VacancyObserver (`vacancy.opened`) +
  PlacementObserver (`placement.created`).
- **`App\Http\Controllers\Webhooks\WhatsAppWebhookController`** —
  Meta verification GET + inbound POST. Every message + status
  row writes a `communications` row so the back-office can see
  inbound replies + delivery receipts.
- **`App\Services\Integrations\HrisExporter`** +
  `hris:export-employer {id|slug}` — per-employer JSON export
  (placements / offers / latest QoH). v2 connectors plug into
  this canonical shape.
- **FO §18.7 PostgreSQL FTS** — migration
  `2026_05_24_100000_add_postgres_fts_to_search_targets` adds a
  generated `search_vector tsvector` + GIN index on `vacancies`
  and `candidates` on PostgreSQL only. SQLite is a no-op.
  `App\Services\Search\VacancySearch` switches between FTS (PG)
  and LIKE (SQLite) based on `DB::getDriverName()`. Wired into
  `CareersController::index`.

## Phase 12 — Employer Portal

Phase 12 stands up the FO §10 employer-facing surface at `/employer`.
Every employer user is scoped to a single Employer row via
`employers.primary_contact_user_id` (multi-user employers wait on
a pivot table).

- **`App\Services\Employer\EmployerScope::forUser($user)`** — the
  single source of truth for resolving an employer user → their
  Employer row. Returns null when no link exists; controllers
  abort 422.
- **`App\Services\Employer\SalaryVisibility::canEmployerSee($application)`**
  enforces FO §10.2: salary blanked until `application.stage` is
  in `shortlisted / interview / offer / hired` or
  `shortlisted_at` is set. Recruiters bypass this via
  `view_candidate_salary` permission.
- **Routes** (all gated by `can:view_employer_portal`):
  - `/employer` — dashboard with vacancy / application / offer /
    placement counters
  - `/employer/vacancies` + `/employer/vacancies/{id}` — read of
    own pipeline + salary-gated application list
  - `/employer/vacancies/create` + `POST` — self-serve intake that
    lands in `pending_publish` for HCMS review
  - `/employer/offers` + `POST /employer/offers/{id}/approve` —
    employer half of FO §10.1 dual approval; decision recorded
    on `offers.tax_pre_flight_snapshot.employer_approval`
- `EmployerLayout` (Inertia) + pages under
  `resources/js/Pages/Employer/`.

## Phase 11 — QoH Engine (Pillar E)

Phase 11 ships the FO §5.5 + §14.1 apex KPI: composite Quality of
Hire calculator + daily rollup + Filament dashboard.

- **`App\Services\Qoh\QohCalculator`** computes a 0-100 composite
  from six weighted components per FO §5.5:
  retention 25% · employer-rated performance 25% · mobility 15% ·
  counter-offer inverse 15% · candidate NPS 5% · time-to-fill
  inverse 15%. Null components are dropped from the denominator
  (so a recent hire doesn't look catastrophically bad before NPS
  is captured). `breakdown` JSON stores per-component value +
  weight for audit.
- **`qoh:rollup [--date=YYYY-MM-DD]`** artisan command recomputes
  every active / guarantee_window / completed / terminated
  placement. Idempotent on `(placement_id, measured_at)`. Wire
  daily via the scheduler.
- **Per-recruiter attribution**: each rollup writes
  `recruiter_metrics.qoh_attributable` for the placement's
  `owner_recruiter_id` on the same measurement date.
- **Filament QoH Dashboard** at `/admin/qoh-dashboard` —
  portfolio composite, top employers, per-service-line breakdown,
  per-recruiter ranking, component averages. Window selector
  (30 / 90 / 180 / 365 days).
- `QohScore` + `RecruiterMetric` model casts now use
  `'date:Y-m-d'` so `updateOrCreate` lookups match what's
  persisted (the default 'date' cast was serializing as
  `YYYY-MM-DD 00:00:00`, breaking idempotency).

## Phase 10 — Service Line Specialization (Pillar D)

Phase 10 wires the FO §16.1 App Suite bridges (ATLAS / Academy /
Digi Coaches / EOR) and adds the FO §6.2 Tech Fit Scorer.

- **`App\Services\AppSuite\AppSuiteBridgeDispatcher`** sends every
  outbound bridge envelope through the same path:
  1. write a `bridge_events` row (`status=pending`)
  2. POST to `services.app_suite.{bridge}.url` if configured,
     else mark the row `deferred`
  3. on 2xx → mark `success` + update the corresponding
     placement timestamp (`atlas_bridge_sent_at` etc.)
  4. on failure → mark `failed` + notify the first
     `hcms_ops_manager` via the FO §24 dispatcher.
- **`App\Observers\PlacementObserver`** (extended): every
  Placement creation fires Academy + Digi Coaches; expat hires
  also fire ATLAS; `service_line=HCMS-PRO-002` also fires the EOR
  bridge. The 30 / 60 / 90 PostPlacement scheduling from Phase 7
  is preserved.
- **Digi Coach selection**: BRIAN by default, AXIS when the
  vacancy seniority is `executive` or `lead` (FO §16.1).
- **`App\Services\AiTalent\TechFitScorer`** is the FO §6.2 gate
  for HCMS-PRO-005 vacancies: deterministic 0-100 score over
  AI-talent flags, sector focus, public footprint, years of
  experience, and CV-parsed-skill overlap with vacancy.requirements.
  `gatesPlacement($score)` returns true when score < 85.
- **`services.app_suite.*`** config block holds URL + token per
  bridge. Empty values short-circuit to `deferred` — safe for
  local dev before the peer apps exist.

## Phase 9 — Compliance Shield Integration (Pillar C)

Phase 9 wires the 8 Compliance Shield modules into the trigger
points that already produce their inputs. Module 5 (EU AI Act) was
done at Phase 0 via `AiFailureChain::logToShield`; Phase 9 adds the
other seven plus a candidate-facing GDPR portal.

- **`App\Services\Compliance\ComplianceShieldGateway`** wraps
  `ComplianceShieldClient` (Phase 0). Per-module behaviour:
  - Module 1 (tax pre-flight) — `OfferObserver::created` calls when
    `expat_flag=true`. Result writes `offers.tax_pre_flight_snapshot`
    + `offers.total_employment_cost`.
  - Module 2 (Article 1639) — `VacancyObserver` on first open. Writes
    `vacancies.article_1639_flag` + `vacancies.compliance.article_1639`.
  - Module 3 (work permit) — `ApplicationStageTransitioner` on
    `shortlisted` for expat candidates. Writes
    `applications.compliance_snapshot.work_permit`.
  - Module 4 (Local Content) — `compliance:local-content-quarterly`
    artisan command for Tier-1 employers (`local_content_required=true`).
  - Module 5 (EU AI Act) — already routed via `AiFailureChain`.
  - Module 6 (bias detection) — `VacancyObserver` on JD publish +
    `ScorecardObserver::saved` on submit. Result rows into
    `bias_scans_local`.
  - Module 7 (GDPR subject) — `POST /candidate/gdpr-request`.
    Six request types (access / export / rectification / erasure /
    objection / restriction). Always writes a `consent_records` row,
    regardless of CS outcome.
  - Module 8 (right-to-work) — `ApplicationStageTransitioner` on
    `offer` for expat candidates. Writes
    `applications.compliance_snapshot.right_to_work`.
- Gateway returns a `ComplianceShieldResult` (`success` / `failed` /
  `deferred`). Failures + deferred-on-misconfig are surfaced via
  `NotificationDispatcher` to the responsible recruiter — never
  silently masked (CS FO §4.3).
- `deferred` status when `COMPLIANCE_SHIELD_URL` or
  `COMPLIANCE_SHIELD_TOKEN` is empty means the call skipped without
  an HTTP attempt. Safe for staging / local dev before the CS
  service is provisioned.

## Phase 8 — Voice & Multi-Modal (Pillar B)

Phase 8 extends the LLM clients with multimodal support and adds
the agents that consume images, PDFs, and audio per FO §5.2.

- **`MediaAttachment`** DTO carries a `disk + path + mime` and
  exposes `kind()` (image / document / audio) + `base64()`.
- **`ClaudeClient::completeWithAttachments()`** builds image +
  document content blocks per the Anthropic Messages API. Audio
  attachments raise immediately so `AiFailureChain` falls through
  to Gemini (FO §5.2 — "Claude voice" is forward-looking; today
  Claude has no audio modality, this is the conservative posture).
- **`GeminiClient::completeWithAttachments()`** ships image / PDF /
  audio via `inline_data` parts (Gemini 1.5+ accepts audio).
- **`CvParseAgent`** runs on PDF / image CV uploads — asks Claude
  for STRICT JSON, parses it into `cv_versions.parsed_payload`,
  enriches `candidates.current_role / current_employer /
  years_of_experience` when null, and stores
  `candidates.profile.cv_parsed`.
- **`VoiceIntroTranscriptionAgent`** runs on audio CV uploads via
  the new `/candidate/profile/voice-intro` endpoint. Claude leg
  throws on audio → chain falls to Gemini.
- **`InterviewTranscriptionAgent`** writes the transcript to
  `storage/app/interviews/{id}/transcript-…txt` and sets
  `interviews.transcript_path`.
- **`LinkedInImportAgent`** fires from
  `/candidate/profile/linkedin` — captures the URL and asks the
  LLM to extract handle / geo / priority into
  `candidates.profile.linkedin_import`. Full scraping waits on the
  FO §29.7 change request.
- **`CvVersionObserver`** routes uploads: PDF / image / DOC →
  CvParseAgent; audio → VoiceIntroTranscriptionAgent.
- The candidate Profile page gains LinkedIn-import + voice-intro
  upload sections; Apply form now accepts photo CVs (PNG / JPG /
  WEBP up to 8 MB).

## Phase 7 — AI Agent Orchestration (Pillar A)

Phase 7 stands up the eight FO §5.1 named agents on top of
`AiFailureChain` (Phase 0), the database queue (FO §29.5), and the
Phase 6 notification dispatcher.

- **LLM clients** (`app/Support/AI/Clients/`): `LlmClient` interface,
  `ClaudeClient` (Anthropic Messages API), `GeminiClient` (Google
  `generateContent` v1beta), `LlmResponse` DTO. Each throws
  `MissingApiKeyException` when the env key is empty so the chain
  falls through cleanly without a network call. Bound as singletons
  from `services.anthropic.*` / `services.gemini.*`.
- **`App\Agents\AgentJob`** is the base for every agent: `ShouldQueue`
  with `tries=3`, `timeout=300` (FO §18.6 — 5-min cap), exponential
  backoff `[30, 120, 300]` seconds. `handle()` builds prompts, calls
  the failure chain, reloads the latest `AgentRun` for the
  success-vs-manual fork, and uses `NotificationDispatcher` to
  surface either the AI draft or a manual-required nudge to the owner.
- **Eight concrete agents** (`app/Agents/`): `SourcingAgent`,
  `TriageAgent`, `SchedulingAgent`, `ReferenceCheckAgent`,
  `FollowUpAgent`, `OfferAgent`, `PostPlacementAgent`,
  `ResurfacingAgent`. TriageAgent parses `Match score: X` into
  `Application.match_score`. OfferAgent stores the draft letter on
  `Offer.tax_pre_flight_snapshot.offer_letter_draft`.
- **Triggers** (`app/Observers/`): VacancyObserver dispatches
  Sourcing + Resurfacing on open; ApplicationObserver dispatches
  Triage on create, Scheduling on `→interview`, ReferenceCheck on
  `→offer`; PlacementObserver schedules three delayed
  PostPlacement jobs (+30/+60/+90 days). `agents:follow-up-stale`
  artisan command sweeps stale apps for FollowUpAgent — wire into
  the Laravel scheduler hourly via `deploy/cron/`.
- **Agent Orchestrator** Filament page at
  `/admin/agent-orchestrator`: per-agent last run + counters, queue
  health, provider configured flags. Lifecycle nav group.

## Phase 6 — Lifecycle & Notification Service

Phase 6 layers the FO §4 Phases 1-4 lifecycle on top of the Phase 0
funnel and stands up the FO §24 multi-channel notification dispatcher.

- `App\Services\Notifications\NotificationDispatcher` routes every
  Notification: primary channel → fallback → (critical only) SMS
  escalation, plus always an in-app inbox row. Quiet-hours (§24.4)
  block non-critical notifications when the user has opted in;
  critical urgency bypasses the window. Failure on every channel for
  a critical notification escalates a new notification to the first
  `hcms_ops_manager`.
- Channel drivers under `app/Services/Notifications/Channels/`:
  `EmailChannel` (functional via Gmail SMTP), `WhatsAppChannel` (Phase
  13 stub — returns `deferred`), `SmsChannel` (Twilio stub —
  `deferred` until creds land), `InAppChannel` (always succeeds).
- `App\Services\Lifecycle\ApplicationStageTransitioner` enforces the
  FO §4 stage rules (applied → screening → shortlisted → interview →
  offer → hired, plus rejected/withdrawn from anywhere), captures an
  audit trail in `applications.compliance_snapshot.transitions` +
  spatie/activitylog `lifecycle` log, and dispatches workflow
  notifications to the owner recruiter + the candidate (milestone-
  collapsed per FO §9.4).
- Filament resources under a new **Lifecycle** nav group:
  `InterviewResource`, `ScorecardResource`, `OfferResource`,
  `PlacementResource`. ApplicationResource gains an **Advance stage**
  action that uses the transitioner.
- OfferResource ships a **Create Placement** action that emits a
  Placement row and queues the ATLAS bridge envelope when the offer
  is `expat_flag=true`.
- Phase 6 permissions: `view_interviews`, `manage_interviews`,
  `view_scorecards`, `manage_scorecards`, `submit_scorecard`,
  `view_offers`, `manage_offers`, `approve_offers`, `view_placements`,
  `manage_placements`. Offer approval (FO §11.4 spirit) gated on
  `approve_offers` — recruiters can draft but not approve.

## Phase 5 — Phase 0 Pre-Approval Workflow

Phase 5 builds the FO §4 Phase 0 workflow on top of the Phase 2
schema. Trigger phrase **"Initiate HCMS Recruitment Project"**.

- Public `/get-started` form (throttled 6/min/IP) creates an Inquiry +
  finds-or-creates an Employer, then auto-routes to the first
  `hcms_ops_manager`.
- Filament resources under a new **Phase 0 — Pre-Approval** nav group:
  Inquiries → Needs Analyses → Proposals → Agreements. Each carries a
  workflow action (Convert / Mark complete + Generate Proposal /
  Mark sent + Mark accepted + Generate Agreement / Mark signed +
  Mark effective).
- `App\Services\PreApproval\ServiceLineCatalog` is the source of truth
  for FO §6 pricing — 12-15% PRO-001 / 90d guarantee · 18-20% PRO-005 /
  180d / HCMS-F-025-AI · 22-25% PRO-003 / 180d / engages CS Modules
  1+3+8. `inferFromFlags(aiTalent, expat)` picks the line.
- `ProposalGenerator` emits a draft `Proposal` with 65/35 fee tranches
  (offer acceptance / guarantee close) per FO §6.1.
- `AgreementFinaliser` flips the source proposal to `accepted` if
  necessary and snapshots all terms into `terms_snapshot` so later
  proposal edits don't mutate the agreement.
- References are year-scoped sequential: `PROP-YYYY-NNNN`,
  `AGR-YYYY-NNNN` (via `ReferenceGenerator`).
- Phase 5 permissions: `view_inquiries`, `manage_inquiries`,
  `view_needs_analyses`, `manage_needs_analyses`, `view_proposals`,
  `manage_proposals`, `view_agreements`, `manage_agreements`.
  Recruiters can run intake + discovery; proposal/agreement
  management stays with Ops Manager + Admin.

## Phase 4 — Public Careers & Candidate Portal

Phase 4 stood up the FO §9 candidate-facing surfaces on top of the
Phase 1 auth + Phase 2 schema + Phase 3 admin.

- Public careers at `/jobs` and `/jobs/{slug}` (FO §4 Phase 1, §9.1):
  partial browse — titles + summary public; full description + salary +
  apply require login. Confidential-search vacancies anonymise the
  employer + salary band per FO §9.1.
- Public CV Optimizer at `/cv-optimizer` (FO §9.3): implicit-consent
  intake that writes a Candidate row (`source=cv_optimizer`,
  `talent_graph_opt_in=true`) and a `consent_records` audit row with
  IP + UA + notice version. Actual Claude-driven optimisation lands in
  Phase 7-8 via `AiFailureChain`.
- Candidate portal `/candidate`, `/candidate/profile`,
  `/candidate/applications` (FO §9.7): profile edit (User + Candidate
  rows kept in sync), application list with milestone-only status
  (FO §9.4 — never expose internal stages), and `/jobs/{slug}/apply`
  for the application flow.
- Application flow (FO §4 Phase 2): CV upload to local disk
  (`storage/app/candidates/{id}/cv/`), Application + CvVersion creation
  in a transaction, idempotent re-applies (existing application
  redirects with a status notice), explicit consent required.

## Phase 3 — Filament Back-Office

Phase 3 stood up the FO §11 admin surfaces on top of the Phase 2 schema.
Resources live at `app/Filament/Admin/Resources/{Employers,Vacancies,
Candidates,Applications,Users}/` and custom pages at
`app/Filament/Admin/Pages/{MasterPipeline,ComplianceDashboard}.php`.

- AdminPanelProvider is now the `default()` Filament panel and groups
  navigation into **Pipeline** (Master Pipeline, Vacancies,
  Applications), **People** (Candidates, Employers), **Compliance**
  (Compliance Dashboard), **Settings** (Users).
- Every resource gates `canViewAny / canCreate / canEdit / canDelete`
  on a spatie permission. Bulk actions hide behind
  `perform_bulk_operations`.
- EmployerResource splits the FO §10.3 per-employer config into
  identity / configuration / tax + approvals sections, with sane
  defaults (USD currency, 5-year retention, good-behaviour required).
- VacancyResource exposes AI Talent / Expat / confidential search /
  blind screening / Article 1639 flag in a single section so recruiters
  see the compliance posture at a glance.
- Master Pipeline page is a read-only six-column Kanban board across
  open vacancies. Drag-and-drop stage transitions are deliberately
  Phase 6 work.
- Compliance Dashboard pings the Compliance Shield `/v1/health`
  endpoint and surfaces local mirror counters per module. Service
  unreachability is shown to the recruiter, never silently masked
  (FO §4.3 of the CS FO).
- UserResource closes the Phase 1 deferral — admins create
  staff/employer/vendor/auditor accounts with role assignment +
  optional password set. MFA enrolment stays self-service.
- Phase 3 permissions added by `RolesAndPermissionsSeeder`:
  `view_employers`, `manage_employers`, `view_vacancies`,
  `manage_vacancies`, `publish_vacancies`, `view_candidates`,
  `manage_candidates`, `view_candidate_salary`, `view_applications`,
  `manage_applications`.

## Phase 2 — Core Data Model + Zoho Migration

Phase 2 added the FO §19 schema (≈30 net-new tables) + Eloquent models +
factories + the FO §22 Zoho import framework.

- All tables additive — Phase 0 `employers`, `candidates`, `vacancies`,
  `applications` were enriched (per-employer config, lifecycle flags,
  recruiter ownership) rather than recreated.
- Migrations grouped into 11 review-friendly files under
  `database/migrations/2026_05_24_0000{0..110}_*.php`.
- Embeddings table uses pgvector on PostgreSQL and a JSON fallback on
  SQLite — guarded with `DB::getDriverName()`. AI matching is staging-only.
- Soft deletes on every business-record table where retention matters
  (FO §17.3): employers, candidates, vacancies, applications, offers,
  placements, agreements, proposals, scorecards, interviews, references,
  candidate_documents, vendors, vendor_assignments, invoice_requests,
  webhook_subscriptions.
- Zoho importer lives at `App\Services\ZohoMigration\` (extractor +
  abstract importer + per-resource implementations) and is driven by the
  artisan command `php artisan humanable:zoho-import {resource} --file=…
  [--dry-run]`. Idempotent: re-runs skip already-imported rows via the
  `zoho_migration_records` audit table.

## Project Structure (Phase 2 Result)

```
humanable/
├── app/
│   ├── Actions/Fortify/        (CreateNewUser, ResetUserPassword, …)
│   ├── Filament/Admin/         (Filament resources — Phase 3+)
│   ├── Http/Middleware/
│   │   ├── EnsureTwoFactorEnrolled.php
│   │   └── HandleInertiaRequests.php
│   ├── Console/Commands/ZohoImportCommand.php
│   ├── Listeners/RecordAuthenticationEvent.php
│   ├── Models/                 (35 lifecycle models — see Phase 2 handoff)
│   ├── Providers/
│   │   ├── AppServiceProvider.php
│   │   ├── FortifyServiceProvider.php
│   │   └── Filament/AdminPanelProvider.php
│   ├── Services/
│   │   ├── ComplianceShield/ComplianceShieldClient.php
│   │   └── ZohoMigration/       (Contracts, Extractors, Importers)
│   └── Support/AI/AiFailureChain.php
├── bootstrap/
├── config/                     (fortify.php publishes here)
├── database/
│   ├── migrations/             (Phase 0 + 1 + 11 Phase 2 cluster files)
│   ├── seeders/
│   │   ├── AdminUserSeeder.php
│   │   ├── DatabaseSeeder.php
│   │   ├── DemoUsersSeeder.php   (gated on HUMANABLE_SEED_DEMO_USERS)
│   │   └── RolesAndPermissionsSeeder.php
│   └── factories/              (User + Employer + Candidate + Vacancy +
│                                Application + Interview + Offer)
├── deploy/                     (DevOps artifacts — systemd, nginx, cron)
├── docs/
│   ├── handoffs/               (per-phase handoff reports)
│   └── specs/                  (FO documents committed for offline ref)
├── public/
├── resources/
│   ├── js/
│   │   ├── Components/         (FormInput, PrimaryButton, …)
│   │   ├── Layouts/            (AuthLayout, AppLayout)
│   │   ├── Pages/
│   │   │   ├── Auth/           (Login, Register, Forgot/Reset Password,
│   │   │   │                    VerifyEmail, ConfirmPassword,
│   │   │   │                    TwoFactorChallenge, TwoFactorSetup)
│   │   │   ├── Portals/        (Candidate, Employer, Vendor, Interviewer)
│   │   │   ├── Dashboard.jsx
│   │   │   └── Welcome.jsx
│   │   └── app.jsx
│   ├── css/
│   └── views/app.blade.php
├── routes/
│   ├── web.php                 (Phase 1 — auth + role-gated portals)
│   ├── api.php
│   └── console.php
├── storage/
├── tests/
│   ├── Feature/Auth/           (Phase 1 — Registration, Authentication,
│   │                            TwoFactorEnforcement, FilamentAccess)
│   ├── Feature/Data/           (Phase 2 — CoreSchemaTest)
│   ├── Feature/ZohoMigration/  (Phase 2 — JsonImporterTest)
│   └── Fixtures/ZohoMigration/ (candidates.json, vacancies.json,
│                                employers.json — fixture data)
├── .env.example
├── .gitignore
├── composer.json
├── package.json
├── vite.config.js
├── CLAUDE.md                   (this file)
└── README.md
```

## AI Failure Chain (Mandatory)

Every AI agent + AI call MUST implement:
1. Try Claude API (primary)
2. On Claude failure → Try Gemini API (fallback)
3. On Gemini failure → Graceful degrade: mark step as `manual_required` +
   notify recruiter
4. ALL attempts logged in `agent_runs` + sent to Compliance Shield Module 5
   (EU AI Act audit)

No "Claude-only" implementations are permitted. Use
`App\Support\AI\AiFailureChain` as the entry point.

## Languages

v1 = English only. Build the Laravel i18n framework but populate only `en_US`
locale. DO NOT delay launch for additional languages (NL/FR/PT come in v1.x,
Spanish in v2).

## Behavioral Defaults

- Read the FO before architectural decisions
- Atomic commits with descriptive messages
- Run tests before commits
- Update this CLAUDE.md if the team learns something Claude Code needs to know
- Never assume scope expansion — if uncertain, stop and ask
- Reference FO sections explicitly in commit messages where relevant
- Never bypass the Compliance Shield client. Even when the service is down,
  the failure must be logged + the operation queued for retry.

---
*Last updated: 2026-05-23 · Maintained by: IQT Lead · Authoritative source:
docs/specs/HCMS-FO-HUMANABLE-Final-v1.3-Selfcontained-2026.md*
