Skip to content

RecruitCRM × Warriors in Need — Integration Checklist

last1.app mirrors WIN's RecruitCRM pipeline (read-only). RecruitCRM remains the ED's system of record for recruiting; last1.app is the system of record for outcomes + participant confirmation.

Live adapter activation (blocked until complete)

  1. RecruitCRM plan tier — Business or Enterprise required for API access. Confirm WIN's current plan or budget for upgrade.
  2. Stage taxonomy — ED provides draft pipeline stages (even rough). Populate integration_stage_mappings for the WIN program. All scaffold stages are labeled MOCK until this is done.
  3. Webhook vs polling — Determine whether webhooks are available on WIN's plan. If polling, respect RecruitCRM's 60 req/min rate limit.
  4. Sandbox pipeline — Test/sandbox pipeline in ED's RecruitCRM account for integration testing before production binding.

What exists today (scaffold)

Component Path Status
Append-only attestation events milestone_attestation_events Implemented
Stage mappings integration_stage_mappings MOCK seed
Mock adapter server/integrations/recruitCrm/mockAdapter.ts Fixture-driven
Live client server/integrations/recruitCrm/client.ts Preview only — not wired to ingest
Org attestation API POST .../milestones/:id/attest Implemented
Member progress GET /api/member/progress Implemented
Nudge hook server/nudgeHooks/milestoneConfirmationNudge.ts Stub only

ED walkthrough (production last1.app)

Populate a full MOCK cohort for the Warriors In Need Accountability Dashboard and funder report:

# Requires DATABASE_URL in .env (direct Postgres; bypasses disabled legacy API keys)
# Optional: ORG_ADMIN_EMAIL, VETERAN_EMAIL, RECRUIT_CRM_WIN_ORG_ID
npm run demo:win-ed
# Preview without writes:
npx tsx --env-file=.env scripts/seed-win-ed-demo.ts --dry-run

Then:

  1. Login as ryan@warriorsinneed.org → Dashboard confidence visuals (verification mix, outcome state, Radio Check response).
  2. Open Warriors in Need (MOCK Pipeline) → program page and /programs/:id/report (funnel ~12 enroll → ~8 placed).
  3. Open a DEMO participant progress page under Beneficiaries.
  4. Login as curryrg@gmail.com → /member/progress and confirm the pending Enrolled (MOCK) milestone live (requires Last1 ID linked).

All seeded names include DEMO or MOCK. Re-run is idempotent. No RecruitCRM writes.

Dev E2E (local)

# After auth + org exists:
POST /api/dev/seed/win-mock-program
POST /api/dev/seed/win-walkthrough

# Simulate RecruitCRM stage advance:
POST /api/integrations/recruit-crm/mock/advance
{ "programId": "...", "candidateId": "mock-candidate-001", "currentStage": "enrolled" }

# Veteran confirms (requires Last1 ID):
POST /api/member/milestones/:milestoneId/confirm

# Funder report:
GET /api/programs/:id/reports

Design invariants

  • THE RETURN PATH — veterans see their own progress; unconfirmed milestones are reported honestly.
  • Two-source loop — milestone closed only when org_attested + participant_confirmed both exist (append-only).
  • Disputes — participant_disputed is an append-only event. It does not edit or reopen prior events. There is no in-app resolution workflow; resolution is a human conversation. The org sees disputed counts on reports/backlog. A later org_attested or org_correction supersedes for closure; the participant must confirm again.
  • No writes to RecruitCRM — v1 adapter is receive/read-only.
  • No health data — workforce/placement milestones only.

Attestation event payload hygiene

Same rule as the Last1.id trust ledger: structural ids/stages only in metadata; no free-text PII.

Column / field Content
milestone_id, beneficiary_id, program_beneficiary_id UUIDs
event_type org_attested | participant_confirmed | participant_disputed | org_correction
source manual | recruit_crm_mock | …
actor_user_id, actor_identity_id Actor refs (confirm/dispute)
external_ref Opaque adapter ref (e.g. rcrm:candidateId:stage)
occurred_at, created_at Timestamps
metadata (org_attested only) from_stage, to_stage, recruit_crm_candidate_id — all other keys stripped at insert

Dev / mock route gating

All /api/dev/* and /api/integrations/recruit-crm/mock/* routes call rejectIfProduction() (server/middleware/developmentOnly.ts). When NODE_ENV === "production", they return 403 with code: DEVELOPMENT_ONLY — not merely hidden from the UI.

Production start script sets NODE_ENV=production (package.json start command).