# Runbook: Bench Import (WS9) This runbook is for an operator loading a real cleared workforce bench into one tenant with the `import-bench` command. The command reads a directory of resumes and a structured CSV or JSONL roster, maps each entry onto the existing sourced person path, dedups, writes the candidate plus structured skills, certs, and agencies through the migrator role, composes a name free profile and embeds it before the write so the inference round trip never holds a row lock, and tags the candidate's missions through the application role under RLS. It writes one `bench_import_log` compliance row per committed record and one append friendly audit summary attributed to the target org. The frozen scoring engine is never touched. The command is server side only. Run it on the host that holds `/etc/missionmatch/sam-sync.env` and the database DSNs, never from a workstation, and never against production until a dry run on staging is clean. ## Environment All of these are server side only and never reach the browser bundle. - `MM_DATABASE_URL`: the application DSN, the NOBYPASSRLS `missionmatch_app` role. Required. - `MM_MIGRATOR_DATABASE_URL`: the migrator DSN, the BYPASSRLS role. Falls back to `MM_DATABASE_URL` when unset, but set it explicitly in any real run so the candidate writes go through the migrator role. - `MM_MEGAMIND_URL`: the inference plane base url. Required only when `-embed` is set. - `MM_MEGAMIND_EMBED_MODEL`: the 384 dim embed model, defaults to `qllama/bge-small-en-v1.5`. Build and run FIPS validated: ``` CGO_ENABLED=0 GOFIPS140=v1.0.0 go build -o bin/import-bench ./cmd/import-bench GODEBUG=fips140=on ./bin/import-bench ``` The binary runs a PBKDF2 self test at boot and refuses to start if the validated crypto module is missing, so a build that dropped the module is caught before any record is read. ## Posture: dry run first, always The first run against any new bench is a dry run. A dry run extracts, resolves identity, and reports the dedup verdict for every record, then rolls the transaction back so nothing is written. It lets the operator preview how many records are new versus how many will enrich an existing person, and catch a malformed roster, before committing anything. ``` GODEBUG=fips140=on ./bin/import-bench \ -org \ -profiles /bench/roster.csv \ -resumes /bench/resumes \ -legal-basis "client provided cleared bench, client is the data controller" \ -dry-run ``` Read the summary line. Confirm the read, mapped, new, and enriched counts match what you expect for this bench. A surprising skipped count means malformed rows or unreadable files; investigate the source before a real run. ## Posture: small batch first After a clean dry run, do a small bounded real run before the full bench. Cap the record count with `-size` so the first committed batch is small and reviewable in the Command Center and the `bench_import_log`. ``` GODEBUG=fips140=on ./bin/import-bench \ -org \ -profiles /bench/roster.csv \ -legal-basis "client provided cleared bench, client is the data controller" \ -size 25 ``` Verify in the tenant: the new candidates appear, the skills resolved to ontology rows, the mission tags landed, and one `bench_import_log` row exists per committed record. Then remove `-size` for the full run. ## Required: the explicit legal basis `-legal-basis` is required. The command refuses to run if it is empty. It is NEVER defaulted to the PDL legitimate interest string, and the recorded `source` is `bench_csv` for a roster or `bench_file` for a resume directory, never `PDL`. The basis is an owner provided controller basis. The standard wording is "client provided cleared bench, client is the data controller": the client is the data controller for their own bench, and the platform processes it on their instruction. Confirm the exact wording with the founder before the first real Galapagos run, and pass that exact string. ## Invariant: clearance is self reported and stays unknown on import A clearance value on a resume or a roster is a self reported indicator, not a verified credential. The import path writes every candidate with `clearance_level` `unknown`, `clearance_poly` `none`, and `clearance_status` `unknown`, exactly like the sourced lead path. A clearance code extracted from a resume or supplied in a roster column is carried for triage only and is NEVER written to a clearance column, so a resume regex can never score as a held clearance under the clearance weight. Clearance becomes known only later, through an explicit org FSO action that records the provenance. The real check is the hiring client FSO through DISS at onboarding. Do not treat an imported clearance value as verified. ## The real embed probe gate When `-embed` is set, the command runs the REAL vector model probe at startup before processing any record. The probe re embeds stored vectors and compares the cosine against the configured model, the same gate the API runs at boot. It is NOT the bare liveness ping. If the inference plane is serving a different model than the stored vectors were built with, the probe fails and the command refuses to embed and exits non zero, so a mismatched plane can never poison the resume embeddings. If the probe fails, do not force the run. Fix the plane so it serves `qllama/bge-small-en-v1.5` at 384 dim (or whatever model the stored vectors match), confirm with `cmd/verify-vector-model`, and only then re run with `-embed`. ## Fallback: backfill a skipped embed The embed call happens before the write transaction, so a slow or failed embed never holds a row lock. When an embed call fails for a record, the candidate still commits without a vector, and the run reports an `embed_skipped` count. You can also import without `-embed` at all, leaving every new candidate's `resume_embedding` NULL on purpose. Either way, fill the missing vectors afterward with the embed backfill, scoped to the org and to only the rows whose embedding is currently NULL: ``` GODEBUG=fips140=on ./bin/embed-backfill \ -what candidates \ -org \ -only-missing ``` `-only-missing` touches only the candidates whose `resume_embedding` is NULL, so it is safe to re run and it never re embeds a row that already has a vector. ## Target a fresh Galapagos org so the demo gap fillers fall away Import into a fresh Galapagos org, not the demo org. The demo org's candidate pool is synthetic generated data, labeled "Demo data" everywhere candidates appear, with deliberately shaped gap fillers so the sales demo tells one connected story. Once the customer's own bench loads into their own org, the seeded fillers do not apply: their org is populated by the real import, and the Command Center reflects the real bench coverage. Never import a real bench into the demo org and never let a real demo imply the seeded fillers are real cleared people.