Loop #1 — Coordination Loop Build Plan (Implementation Map)¶
Status: Scope only — awaiting Ryan approval before any code
Date: 2026-09-02
Prerequisites: L0 verification closed · L1 entry (Last1 ID login + gate) closed
Authorizes: LOOP1_FIRST_BUILD_SPEC.md §8 (+ §3.1 governance)
Maps: LOOP1_COORDINATION_ARCHITECTURE.md → last1.app codebase
Companion: LOOP1_BUILD_PLAN.md §4 (sequenced phases); this doc is the under-the-hood map Ryan asked for.
0. One sentence (keystone)¶
Close one real WIN job placement as a three-party Verified Outcome (WIN + veteran via last1.id + hiring manager via Path A magic link), on a hash-chained PHI-free ledger in last1.app, with denominator tracked and a minimal read surface in last1.app + LastVet hub card.
Not tonight: no migrations, no routes, no UI. Review this plan first.
1. What exists today vs net-new¶
1.1 Three different "loop" systems (do not conflate)¶
| System | Repo / DB | Purpose | Relation to Loop #1 |
|---|---|---|---|
| Gate 0 ROI loop | last.vet-api · coordination_loops + loop_events (037_coordination_loop_ledger.sql) |
Mental-health consent → ROI delivered; PHI-free event emitter (loopEvents.ts) |
Separate product plane. Same English name, different schema/state enum. Do not extend for WIN placement. |
| Legacy WIN milestone attestation | last1.app · milestone_attestation_events + milestoneAttestationService.ts |
Two-party org + member confirm on program milestones (POST /api/member/milestones/:id/confirm) |
Legacy mock / demo path. Stays unchanged. Loop #1 is a new SoR. |
| Loop #1 coordination engine | last1.app · coordination_* tables (net-new) |
Three-party Λ state machine, hash chain, Path A HM close | This build. |
1.2 last1.app — extend vs net-new¶
| Area | Path | Today | Loop #1 action |
|---|---|---|---|
| Verification gate consumer | server/last1VerificationService.ts |
Reads veteran_verified from last1.id via internal API |
Extend — reject veteran attestation if not verified |
| Identity link | server/last1IdentityService.ts |
Maps beneficiary ↔ last1.id subject | Reuse — last1id_subject on loop row |
| Legacy attestation | server/services/milestoneAttestationService.ts |
WIN two-party milestone events | Do not touch |
| Accountability / outcomes | server/accountabilityLoopService.ts |
Org outcome coverage, delivery metrics | Parallel — may read denominator later; not SoR for Λ |
| Member progress UI | client/src/pages/member/progress.tsx |
Milestone rows + confirm button (legacy) | Extend — add placement-close panel from new API |
| Member routes | server/routes.ts (~4782 milestone confirm) |
Legacy endpoints | Add new /api/coordination-loops/* block; keep legacy |
| Schema | shared/schema.ts |
milestoneAttestationEvents Drizzle |
Add coordination_* tables (migration + Drizzle) |
| Cross-org coordination | migrations/20260217_cross_org_coordination.sql |
Beneficiary multi-org relationships | Unrelated — do not overload |
| WIN ED demo | scripts/seed-win-ed-demo.ts, win-walkthrough-help.mjs |
Seeds milestone attestation | Replace walkthrough with coordination-loop E2E script (L1-7) |
1.3 LastVet — read surface only (minimal)¶
| Area | Path | Today | Loop #1 action |
|---|---|---|---|
| L1 entry / verification | OperatingSystemShellView.swift, AuthManager |
Last1 ID status card | Done — not loop card |
| ROI Gate 0 loop | ROIViewModel.swift, loopEvents API |
Opens MH coordination loop on consent | Unchanged — different loop type |
| Loop #1 hub card | none | — | Net-new (L1-6b) — thin client: GET last1.app member/coordination API or LastVet proxy; show Λ state + attest CTA |
Pragmatic sequencing: close loop in last1.app first (WIN operates there); LastVet card is minimum beneficiary read + deep link to attest — not full dashboard.
1.4 last1.id — attestation gate (proven at L1 entry)¶
| Capability | Status |
|---|---|
| Sovereign session (Google OAuth, partner redirect) | Proven — L1 entry E2E |
veteran_verified credential |
Proven — L0 |
| Veteran recipient attestation gate | Reuse — session subject must match verifier_binding on token |
No new last1.id product features for Loop #1 close beyond session validation on attest POST (handled in last1.app verifying JWT / internal identity lookup).
2. Module boundaries — coordination_* in last1.app¶
Decision (locked): New module namespace under server/services/coordination/ — separate system of record from milestoneAttestationService.
last1.app/
├── migrations/
│ └── YYYYMMDD_coordination_loop1.sql # coordination_* tables
├── shared/schema.ts # Drizzle: coordinationLoops, ledger, tokens, stageSignals
├── server/services/coordination/
│ ├── coordinationLoopService.ts # Λ transitions, loop CRUD, denominator
│ ├── coordinationLedgerService.ts # Append-only hash chain, payload allowlists
│ ├── attestationTokenService.ts # Mint/consume/revoke tokens; §3.1 HM gate
│ ├── coordinationAttestationService.ts # Record attestations → ledger → applyTransition
│ ├── coordinationPayloadAllowlist.ts # Zod per event_type (PHI-free)
│ └── __tests__/
│ ├── lambdaTransition.test.ts
│ └── hashChain.test.ts
├── server/routes/coordinationLoops.ts # WIN + public HM routes (mount in routes.ts)
└── scripts/
└── coordination-loop-e2e-staging.mjs # ED walkthrough (L1-7)
Private record store (existing tables): beneficiaries, organizations, programs — hold veteran name, employer, role details. Ledger references them by opaque UUID only in allowlisted payload keys (beneficiary_ref, org_ref), never names/PII.
3. Data model — four tables¶
Naming: coordination_* prefix in last1.app Postgres (Supabase). Distinct from last.vet-api.coordination_loops.
3.1 coordination_loops¶
| Column | Type | Notes |
|---|---|---|
id |
uuid PK | Opaque loop id (also ledger key) |
organization_id |
uuid FK | WIN org |
program_id |
uuid FK | WIN program (optional but useful for reporting) |
beneficiary_id |
uuid FK | Veteran in WIN pipeline |
last1id_subject |
text | Identity binding for recipient gate |
outcome_class |
enum | job_placement (Loop #1 only) |
lambda_state |
enum | open | partial | verified | contradicted | lapsed |
hiring_manager_email |
text | Domain-bound; reject public mail providers |
hiring_manager_domain |
text | Derived + WIN-vouched; used for Path A binding |
private_refs |
jsonb | Opaque refs to role/employer rows in private store (not in ledger payload) |
opened_at |
timestamptz | Λ_open |
verified_at |
timestamptz | Set on Λ_verified |
terminal_at |
timestamptz | Set on verified | contradicted | lapsed |
attestation_bitmap |
smallint | Optional cache: bits for |
Denominator: every row with opened_at counts as attempted; terminal state from lambda_state.
3.2 coordination_ledger_events¶
| Column | Type | Notes |
|---|---|---|
id |
uuid | Event id |
loop_id |
uuid FK | |
seq |
bigint | Monotonic per loop (unique with loop_id) |
event_type |
enum | loop_created | stage_signal | attestation | state_transition |
payload |
jsonb | PHI-free — zod allowlist per type |
resulting_lambda |
enum | Λ after this event |
prev_event_hash |
text | SHA-256 hex; genesis = zero hash |
event_hash |
text | SHA256(canonical_json(payload) ‖ prev_event_hash) |
created_at |
timestamptz |
Append-only: no UPDATE/DELETE on ledger rows; corrections = new events + optional revoked token status.
Payload examples (allowlisted keys only):
| event_type | Allowed payload keys |
|---|---|
loop_created |
outcome_class, org_ref, beneficiary_ref |
attestation |
verifier_role, expected_attestation, attestation_value, token_id_ref |
state_transition |
from_lambda, to_lambda, reason |
stage_signal |
stage, attested_by_role, occurred_at |
3.3 attestation_tokens¶
| Column | Type | Notes |
|---|---|---|
id |
uuid | |
token_hash |
text | Store hash only; raw token shown once at mint |
loop_id |
uuid FK | |
verifier_role |
enum | deliverer | recipient | independent |
verifier_binding |
text | win_org:{orgId} | last1id:{subject} | hm_email:{domain} |
gate |
enum | session | domain_verified_magic_link |
expected_attestation |
enum | placement_delivered | placement_confirmed | hire_confirmed |
issued_at, expires_at |
timestamptz | Governance-set windows |
status |
enum | issued | consumed | expired | revoked |
single_use |
bool | always true |
3.4 coordination_stage_signals¶
| Column | Type | Notes |
|---|---|---|
id |
uuid | |
loop_id |
uuid FK | |
stage |
enum | Loop #1: placement only (seven-stage enum reserved) |
signal |
text | e.g. stage_complete |
attested_by_role |
enum | deliverer | system |
occurred_at |
timestamptz | |
ledger_event_id |
uuid FK | Back-link to ledger row |
Also emit matching stage_signal ledger event when placement stage recorded (parallel to attestation close).
4. Λ state machine¶
4.1 Committed vocabulary¶
| Spec symbol | DB value | Meaning |
|---|---|---|
| Λ_open | open |
Loop created; 0–3 attestations |
| Λ_partial | partial |
1–2 of {WIN, veteran, HM} recorded, no contradiction |
| Λ_verified | verified |
All three present, no contradiction |
| Λ_contradicted | contradicted |
Conflicting attestations → human review queue |
| Λ_lapsed | lapsed |
Token/window expired before close |
Do not invent: Λ_pending_*, Λ_draft, ordered sub-states.
4.2 Transition function (deterministic)¶
Pure function applyTransition(loop, attestationSet, now) — recompute from recorded attestations, not from order:
INPUT: set of recorded attestations {placement_delivered?, placement_confirmed?, hire_confirmed?}
+ any contradiction flags + expiry checks
IF any contradiction among recorded values → contradicted
ELSE IF all three attestations present → verified
ELSE IF any attestation present → partial
ELSE IF open and any required token expired → lapsed
ELSE → open
Contradiction examples (first-class, not errors):
- WIN
placement_delivered+ veteranplacement_denied(if modeled) → contradicted - WIN says placed + HM
hire_confirmed: false→ contradicted
4.3 Token-issuance governance (spec §3.1) — veteran-first for HM contact¶
Attestation order: not fixed (WIN and veteran may attest in any order).
Token mint order (load-bearing business rule):
issue-hm-tokenMUST return 403 untilplacement_confirmedis on the ledger.
- WIN may still record
placement_deliveredbefore or after veteran confirms. - Path B HM session (deferred) exempt from this mint rule only if session already exists — not built in Loop #1.
Implementation: attestationTokenService.canIssueHmToken(loopId) checks ledger for recipient attestation event before minting Path A link.
5. Three-party attestation — gates and paths¶
| Verifier | Role | Gate | Expected attestation | Loop #1 path |
|---|---|---|---|---|
| WIN | Deliverer | last1.app org session (WIN admin) | placement_delivered |
Session |
| Veteran | Recipient | last1.id session + L0 verified | placement_confirmed |
Session (LastVet → last1.app API with identity) |
| Hiring manager | Independent | Path A: domain magic link | hire_confirmed |
GET/POST /public/hm-attest/:token |
| HM Path B | Independent | last1.app hiring-org session | hire_confirmed |
Deferred |
5.1 Path A HM flow (DoD path)¶
- WIN creates loop with
hiring_manager_emailat verified employer domain (not gmail/yahoo/etc.). - WIN vouches employer relationship (stored in private refs; ledger gets domain code only).
- After veteran
placement_confirmed, WIN callsissue-hm-token. - Email magic link →
/public/hm-attest/:token(no account). - Server validates: token unexpired, single-use, email domain matches
verifier_binding. - HM submits hire confirmation → ledger
attestation→ recompute Λ →verifiedif all three agree.
Trust chain (spec §1.1): WIN vouch + domain inbox control = independent third party, not "anyone with company email."
5.2 Veteran attestation in LastVet (L1-6b)¶
Options (pick at L1-1 design review — smallest path wins):
| Option | Flow |
|---|---|
| A (recommended) | LastVet iOS opens last1.app member URL in ASWebAuthenticationSession; last1.app validates last1.id session |
| B | LastVet API proxy to last1.app with veteran JWT + last1.id subject claim |
Gate in all cases: last1VerificationService confirms veteran_verified for beneficiary's linked subject.
6. Hash-chained PHI-free ledger¶
6.1 Write path (single front door)¶
All ledger writes go through coordinationLedgerService.appendEvent():
- Load last event for
loop_id→prev_event_hash - Validate payload against allowlist
- Compute
event_hash - INSERT in transaction with attestation / loop state update
- Return chain head for optional future anchor
6.2 Two-layer boundary¶
| Layer | Location | Contents | In ledger? |
|---|---|---|---|
| Private store | last1.app beneficiaries, org records, WIN CRM notes | Names, DD-214 refs, employer, role title | Never |
| Ledger | coordination_ledger_events |
Opaque ids, enums, timestamps, verifier roles, attestation values | Yes |
Reference by opaque id only: loop_id, beneficiary_id as UUID in DB but payload uses beneficiary_ref key without name fields.
6.3 Verification tooling (L1-2)¶
Script scripts/verify-coordination-hash-chain.ts:
- For each loop, walk
seqascending - Recompute hashes; fail on break
- CI test on fixture loop
Public anchor (RFC 3161, last1.org log): explicitly deferred until first Λ_verified exists.
7. Denominator tracking¶
From day one (architecture §2.3, spec §6):
| Metric | Source |
|---|---|
| Attempted | COUNT(*) FROM coordination_loops WHERE opened_at IS NOT NULL |
| Verified | lambda_state = 'verified' |
| Lapsed | lambda_state = 'lapsed' |
| Contradicted | lambda_state = 'contradicted' |
| Rate | verified / attempted (never placements-only) |
L1-5 deliverable: GET /api/coordination-loops/denominator?organization_id= for WIN admin (minimal table; no fancy dashboard).
Ledger audit: sum of terminal state_transition events must match loop row counts.
8. Read surfaces — minimum for one close¶
8.1 last1.app (engine + detail)¶
| Surface | Path | Loop #1 scope |
|---|---|---|
| WIN ops | Org admin — create loop, deliverer attest, issue HM token | In scope — minimal forms |
| Member progress | /member/progress |
Extend — "Placement" panel: Λ badge, three checkmarks, link to confirm if pending |
| Member API | GET /api/member/coordination-loop |
New — loop state + attestation status for logged-in beneficiary |
| HM public | /public/hm-attest/:token |
In scope — single-page confirm |
| Full timeline / seven stages | /member/timeline |
Out of scope — placement stage signal only |
8.2 LastVet (hub — beneficiary-first)¶
| Surface | Loop #1 scope |
|---|---|
| Full coordination dashboard | Out |
| Loop status card | In (minimal) — Λ state, "Confirm placement" if veteran action pending, deep link |
| Gate 0 ROI card | Unchanged — separate loop type |
Card reads same API as member progress (proxy or direct last1.app fetch with veteran auth).
9. ED staging test path (real accounts, no bypass)¶
| Step | Actor | Action | Expected Λ |
|---|---|---|---|
| 0 | ED | L0 + L1 entry already proven (verified last1.id) | — |
| 1 | ED (WIN org admin) | Enroll beneficiary linked to veteran's last1.id | — |
| 2 | ED (WIN) | POST /api/coordination-loops — veteran + employer domain email |
open |
| 3 | ED (WIN) | POST .../attest/deliverer — placement_delivered |
partial |
| 4 | Veteran (real last1.id) | POST .../attest/recipient — placement_confirmed |
partial |
| 5 | ED (WIN) | POST .../issue-hm-token — must succeed after step 4 |
— |
| 6 | ED (HM inbox) | Open magic link on employer domain test mailbox | — |
| 7 | HM | POST /public/hm-attest/:token — hire_confirmed |
verified |
| 8 | Any | verify-coordination-hash-chain.ts PASS |
— |
| 9 | Any | Denominator: 1 open, 1 verified | — |
| 10 | Veteran | /member/progress + LastVet card show closed placement |
read surface |
Reject tests:
issue-hm-tokenbefore step 4 → 403- HM token to
@gmail.com→ 400 - Expired token → 410 + loop →
lapsed(after expiry job)
No LOOP1_ED_TEST_MODE. ED may use second real last1.id account for veteran step.
10. In scope vs deferred (§10.5 discipline)¶
10.1 IN first loop-close increment¶
- [ ]
coordination_*migrations + services + hash chain - [ ] Λ state machine (five states only)
- [ ] WIN deliverer + veteran recipient attestations (session gates)
- [ ] Path A HM magic link + §3.1 veteran-first token rule
- [ ] Denominator query (attempted / verified / lapsed / contradicted)
- [ ] Extend
/member/progressplacement panel - [ ] Minimal LastVet loop status card
- [ ] Staging E2E script + ED sign-off
10.2 DEFERRED (named, not built)¶
| Item | Why deferred |
|---|---|
| RecruitCRM integration | Scale after first manual close |
| Seven-stage timeline UI | Enrich backward after keystone |
veteran_verification, dd214_mos_validated, training_complete, etc. stage signals |
Only placement stage in Loop #1 |
| Path B HM last1.app session | Fast-follow after first Λ_verified |
| Hiring-org verified-hiring product | Downstream SaaS on same attestation |
| Public anchor + open-source verifier + last1.org dashboard | After first close |
| last1.org veteran-hiring standard (published) | With showcase product |
| BH/MH care-coordination fork (V_req=2) | Second fork |
| Meridian as matcher | Fed by closed loops, not driver |
| Full LastVet coordination dashboard | Hub card minimum only |
Merge legacy outcome_events / milestone timeline |
Optional later |
last.vet-api Gate 0 loop changes |
Unrelated plane |
11. Build sequence (L1-1 → L1-7)¶
| Phase | Deliverable | Repo | Depends on |
|---|---|---|---|
| L1-1 | Migration + Drizzle schema for four tables | last1.app | Plan approval |
| L1-2 | coordinationLoopService + coordinationLedgerService + unit tests (Λ + hash chain) |
last1.app | L1-1 |
| L1-3 | WIN routes: create loop, deliverer attest; veteran attest + L0 gate | last1.app | L1-2 |
| L1-4 | attestationTokenService + Path A public HM routes + §3.1 enforcement |
last1.app | L1-3 |
| L1-5 | Denominator endpoint + WIN admin read (single table) | last1.app | L1-2 |
| L1-6a | GET /api/member/coordination-loop + /member/progress placement panel |
last1.app | L1-3 |
| L1-6b | LastVet hub loop card (read + attest deep link) | last.vet-ios (+ optional API proxy) | L1-6a |
| L1-7 | coordination-loop-e2e-staging.mjs + hash verifier in CI + ED checklist |
last1.app, lastvet-gates doc update | L1-4, L1-6 |
Estimated effort: 5–7 dev days after approval.
L1 Definition of Done: spec §8 — one placement, three-party verified, hash chain valid, denominator correct, Path A only, real last1.id sessions.
12. API sketch (mount in routes.ts)¶
WIN (authenticated org)¶
POST /api/coordination-loops
POST /api/coordination-loops/:id/attest/deliverer
POST /api/coordination-loops/:id/issue-hm-token # 403 until veteran confirmed
POST /api/coordination-loops/:id/revoke # append-only correction
GET /api/coordination-loops/:id # ops read
GET /api/coordination-loops/denominator # org scoped
Veteran (authenticated member + last1.id link)¶
GET /api/member/coordination-loop
POST /api/coordination-loops/:id/attest/recipient # 403 if not veteran_verified
Hiring manager (public Path A)¶
Not in Loop #1: POST .../attest/independent (Path B).
13. Open items for Ryan (before build)¶
- Which veteran is loop #1 in production? WIN picks one at/near placement after staging proves the path.
- Veteran attest surface: last1.app web session vs LastVet-native form (§5.2 option A vs B).
- HM test mailbox: staging employer domain + inbox for Path A proof.
- WSGR — placement loop vs payment structure (unchanged from spec §9).
- Email delivery for HM magic link — confirm SendGrid path in last1.app prod (existing
sendEmail).
14. Citation chain¶
| Document | Role |
|---|---|
LOOP1_COORDINATION_ARCHITECTURE.md |
Layer model, Λ vocabulary, ledger integrity |
LOOP1_FIRST_BUILD_SPEC.md |
Authorized increment, §3.1 HM token rule, §8 DoD |
LOOP1_BUILD_PLAN.md |
Sequenced L0/L1 sub-plans |
L1_ENTRY_CLOSEOUT_2026-09-02.md |
Login + gate closed |
LOOP1_L0_DOD_REPORT.md |
L0 + L1-entry validation |
| This plan | Implementation map onto last1.app |
STOP — Ryan reviews this plan ("see what's under the hood"). No coordination-loop code until explicit approval for L1-1.