Applicant Portal Seed Credentials
On this page
Overview
The default generative seed (cargo xtask seed) mints a small cast of
login-capable applicant-portal households so the applicant-side walk and the
applicant-portal / portal-recover Playwright projects can sign in
deterministically. The cast is built by phase14_cast in
tools/canopy-seed/src/datagen.rs; each member carries a byte-stable, fixed
Application ID code + passcode (the household names + case UUIDs are RNG-drawn per
seed, but the credentials are constants), so a human demo and the e2e specs use the
same login every run regardless of the seed number.
The demo seed profile that these credentials once lived under was retired in #716 (the demo dataset folded into the default seed); the cast is its successor — one seed, one login surface.
These passcodes are seed fixtures — fake applicants in a throwaway devstack
database, already public in datagen.rs. They are not secrets (Kerckhoffs: the
system is secure with its source public). Production credentials are minted by the
applicant-portal lookup/recovery flow (canopy_common::credentials), never seeded.
The cast
| Role (manifest key) | Application ID code | Passcode | Case state (Home hero) |
|---|---|---|---|
|
|
|
Approved — active SNAP benefits + issuances + 2 letters; the "Your year" recap |
|
|
|
Submitted — under review with pending verifications + an IEVS discrepancy;
|
|
|
|
Confidential — self-service recovery routes to the helpline
( |
|
|
|
Submitted — SNAP+TANF, adult + one minor child, with an ELE consent + a gating
identity verification (the cross-portal worker walk; full stack only — the ELE
consent lands in canopy_medicaid). Backs
|
Specs never hardcode these constants — they discover a member structurally through
the manifest finders in tests/e2e/lib/fixtures.ts (findApprovedWithIssuances,
findSubmittedWithVerifications, findConfidential, findEleConsented), which read
SEED.cast and return the member’s code / passcode / householdId + published
state facts. A human tester uses the fixed codes above directly.
How the cast is seeded
Each member is built into SeedData and rendered by the seal-aware render_*
writers (so its PII is crypto-shred-sealed like every other seeded person):
-
Household + person(s) + membership + address →
canopy_persons.sql. -
Application + program row(s) + Application ID code + active passcode hash →
canopy_applications.sql. -
Pending verifications + any IEVS hit →
canopy_verification.sql. -
The
elemember’s Express Lane consent →canopy_medicaid.sql(full-stack only).
The argon2id passcode_hash for each member is a precomputed constant
(canopy_common::credentials::hash_passcode draws a random salt, so hashing at
generation time would break the seed’s byte-stable-output contract). A unit test
(cast_passcode_hashes_verify) re-verifies every embedded hash against its
documented passcode, so a copy-paste error fails the build; the cleartext passcodes
never appear in the emitted SQL (passcodes_never_appear_in_sql), only their hashes.
Verifying the seed
cargo xtask dev refresh
cargo xtask seed
# The non-program services share one Postgres instance with a database per
# service. The host port lands in .ports.env as CANOPY_PORT_POSTGRES_5432.
PG=$(grep '^CANOPY_PORT_POSTGRES_5432=' .ports.env | cut -d= -f2)
# Every cast code is present and matches ^HH-[a-f0-9]{8}$:
psql "postgres://canopy:canopy@localhost:${PG}/canopy_applications" \
-c "SELECT code FROM application_id_codes WHERE code LIKE 'HH-ca5700%' ORDER BY code;"
# HH-ca570001 HH-ca570002 HH-ca570003 HH-ca570004
Sign in through the portal (native lookup form):
PORTAL_PORT=$(grep '^CANOPY_PORT_CANOPY_PORTAL_8090=' .ports.env | cut -d= -f2)
curl -i -X POST "http://localhost:${PORTAL_PORT}/lookup/submit" \
--data-urlencode "code=HH-ca570002" \
--data-urlencode "passcode=4821-0073-9156"
# 303 → Set-Cookie: canopy_portal_session=… (then GET /home, /verifications, …)
See also
-
tools/canopy-seed/src/datagen.rs—phase14_cast+ theCASTconstant + tests. -
tests/e2e/lib/fixtures.ts— the role finders specs discover the cast through. -
Secret Management & Rotation — how real (non-demo) credentials are handled.