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)¶
- RecruitCRM plan tier — Business or Enterprise required for API access. Confirm WIN's current plan or budget for upgrade.
- Stage taxonomy — ED provides draft pipeline stages (even rough). Populate
integration_stage_mappingsfor the WIN program. All scaffold stages are labeled MOCK until this is done. - Webhook vs polling — Determine whether webhooks are available on WIN's plan. If polling, respect RecruitCRM's 60 req/min rate limit.
- 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:
- Login as
ryan@warriorsinneed.org→ Dashboard confidence visuals (verification mix, outcome state, Radio Check response). - Open
Warriors in Need (MOCK Pipeline)→ program page and/programs/:id/report(funnel ~12 enroll → ~8 placed). - Open a DEMO participant progress page under Beneficiaries.
- Login as
curryrg@gmail.com→/member/progressand 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_confirmedboth exist (append-only). - Disputes —
participant_disputedis 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 laterorg_attestedororg_correctionsupersedes 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).