# missionMATCH — PLATFORM INVENTORY (read-only audit) **Scope:** Mission Workforce Technologies platform (engines screenMATCH, slateMATCH, missionMATCH). **Date:** 2026-06-26. **Method:** read-only. Findings are verified against the code, config, the live database (M4), and the running services (Bubbles). No files were changed; no secret values are printed (variable/key names only). Where a fact could not be verified it is marked **UNKNOWN**. State legend per item: **BUILT AND CONFIGURED** / **BUILT BUT NOT CONFIGURED** / **STUBBED** / **MISSING**. Verification sources: (a) direct reads of the repo at `/Users/josephanady/missionmatch`; (b) live SQL introspection of the database on M4 (`100.94.8.94`, psql `-U postgres`, read-only SELECTs); (c) live read-only inspection of the Bubbles host (systemd/nginx/cron configs, `ss`, env key NAMES only). --- ## STEP 0 — ORIENT - **Working directory / repo root:** `/Users/josephanady/missionmatch` (the Go module root; `go.mod` declares `module github.com/missionworkforce/missionmatch`, `go 1.26`). - **Git:** local repository, **no remote** (`git remote -v` empty). Branch **`master`**, last commit **`6c7c40f` "Workstream E (deploy): umbrella browser title"**. History is linear merges of workstreams A–E (the three-tool split). - **Folder layout (2 levels):** - `cmd/` — entrypoints: `api`, `sam-sync`, `awards-sync`, `rates-sync`, `import-candidates`, `opp-sync`, `provision-org`, `provision-owner`. - `internal/modules/` — domain modules (auth, matching, screenmatch, slatematch, missionmatch, commandcenter, opportunities, opportunity_intelligence, awards, wages, sourcing, eeo, governance, plus seven empty dirs — see §13). - `internal/platform/` — cross-cutting (config, database, tenant, security, audit, storage, embeddings, extraction, samgov, samawards, marketintel, bls, sourcing, forecast, ratelimit, obs). - `internal/integration/` — `//go:build integration` live-DB tests. - `db/` — `migrations/` (0001–0016), `migrations_eeo/` (segregated EEO), `bootstrap/00_roles.sql`, `queries/` (sqlc sources), `seed/`. - `frontend/` — Vite + React 19 SPA (`src/`, `public/`, `dist/`). - `docs/` — `STACK_DECISIONS.md`, `MODEL_CARD.md`, `openapi.yaml`. - `infra/systemd/` — unit + timer files. `migrations/` (top level) is **empty** (vestigial). - **Languages / frameworks (proof file):** - **Go 1.26** backend — `go.mod` (chi `v5`, pgx `v5`, sqlc-generated `dbgen`, `shopspring/decimal`, `pgvector/pgvector-go v0.4.0`). Toolchain on this machine: `go1.26.4 darwin/arm64`. - **TypeScript / React 19 frontend** — `frontend/package.json` (React `^19.2.7`, Vite `^8.1.0`, TanStack Query `^5`, react-router-dom `^7`, Tailwind `^4.3.1` via `@tailwindcss/vite`, oxlint, TypeScript `~6.0.2`). *(Note: CLAUDE.md says "React 18"; the repo is React 19.)* - **SQL** — `db/migrations/*.sql` (goose-annotated), compiled by **sqlc** (`sqlc.yaml`). - **bash** — `cmd/*` are Go; deploy/sync scripts on the host are bash (`/usr/local/bin/mm-*.sh`). - No Python anywhere in the repo (house rule). - **Build / run:** - Backend build (`Makefile:11-18`): `CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOFIPS140=v1.0.0 go build -trimpath -o mm-api ./cmd/api` (+ the sync/provision binaries). Run on the host via systemd (`infra/systemd/mm-api.service`, `GODEBUG=fips140=on`). - Frontend build (`frontend/package.json` scripts): `tsc -b && vite build` → `dist/`, served as static files by nginx. - sqlc codegen: `make sqlc`. Migrations: `goose` (`make migrate-up`). --- ## 1 — RUNTIME AND DEPLOYMENT (verified live on Bubbles, read-only) Self-hosted on **Bubbles**. **State: BUILT AND CONFIGURED and LIVE.** - **API service:** `mm-api.service` — `active` + `enabled` (systemd). `ExecStart=/home/user/missionmatch/mm-api`, `EnvironmentFile=/etc/missionmatch/api.env`, `GODEBUG=fips140=on`. Process listens **`127.0.0.1:8094`** only (loopback; reached via nginx). Unit hardening present: `NoNewPrivileges`, `ProtectSystem=strict`, `ProtectHome=read-only`, `PrivateTmp`, `ReadWritePaths=/home/user/missionmatch` (`infra/systemd/mm-api.service`). - **Process manager:** **systemd** (service + timers); **cron.d** for two jobs. No docker/pm2. - **Reverse proxy (nginx) — hostname → upstream:** - `missionmatch.thatwebhostingguy.com` → SPA root `/var/www/sites/missionmatch`; `location /api/` and `= /healthz` → `proxy_pass http://127.0.0.1:8094`; SPA fallback `try_files … /index.html`. HTTP→HTTPS 301. (`/etc/nginx/sites-available/missionmatch.thatwebhostingguy.com`.) - `screenmatch.thatwebhostingguy.com` → **`return 301 https://missionmatch.thatwebhostingguy.com/screen`** (both :80 and :443). The old host is now a redirect only. - Both vhosts are symlinked into `sites-enabled/`. - **TLS:** `/etc/letsencrypt/live/thatwebhostingguy.com/fullchain.pem`, SAN `*.thatwebhostingguy.com` + apex (wildcard covers both subdomains); `ssl_protocols TLSv1.2 TLSv1.3`. - **Frontend serving:** static `dist/` from `/var/www/sites/missionmatch`; `` confirmed live as "missionMATCH, Mission Workforce Intelligence". Live smoke (read-only `curl`): umbrella `/healthz`, `/command`, `/slate` → 200; `screenmatch` host → 301 to `/screen`; all three `/api/{screenmatch, slatematch,missionmatch}/…` → 403 (auth-gated, i.e. routes are live). - **Scheduled jobs (verified):** - systemd timers **enabled**: `mm-sam-sync.timer` (daily ~04:00), `mm-awards-sync.timer` (daily 04:30), `mm-rates-sync.timer` (weekly Sun 05:00). `list-timers` shows awards/rates with next-elapse; **`mm-sam-sync.timer` showed no next-elapse** at audit time → verify its `OnCalendar` is firing (**flag**). - cron.d: **`/etc/cron.d/mm-opp-sync`** = `30 4 * * * root /usr/local/bin/mm-opp-sync.sh`; **`/etc/cron.d/mm-pg-backup`** = `30 3 * * * root /usr/local/bin/mm-pg-backup.sh >> /mnt/storage/mm-backups/backup.log`. - **PDL import has no schedule** (no timer, no cron) → manual only. - **Listening ports:** `mm-api` on `127.0.0.1:8094`. Bubbles also runs many unrelated services on `127.0.0.1:8000–8013` (other vhosts/apps, out of scope). nginx terminates :80/:443. - **Database host:** the API connects out to Postgres on **M4** (`100.94.8.94:5432`) over Tailscale (`sslmode=disable`, transport encrypted by WireGuard). The DB is **not** on Bubbles. --- ## 2 — DATABASE (introspected live on M4, read-only) - **Engine:** **PostgreSQL 16.14** (Homebrew, native on M4, `aarch64-apple-darwin`). - **Extensions (only two):** `plpgsql 1.0`, **`vector 0.8.0` (pgvector)**. No others (no `pg_trgm`, no `uuid-ossp`; UUIDs use `gen_random_uuid()` from core). - **Schemas:** `public` (31 tables incl. 2 goose bookkeeping) + **`eeo`** (1 table). Segregation is deliberate. - **Roles (verified `pg_roles`):** `missionmatch_app` (login, **NOBYPASSRLS**), `missionmatch_compliance` (login, **NOBYPASSRLS**), `missionmatch_migrator` (login, **BYPASSRLS**). **None are superuser.** Definitions in `db/bootstrap/00_roles.sql`. - **Migration tool / state:** **goose**. `public` at **version 16** (`db/migrations/0001…0016`); EEO tracked separately in `goose_db_version_eeo` at **version 1** (`db/migrations_eeo/0001_eeo.sql`). Live schema matches migrations (no drift detected: 0015 candidate_evaluations + 0016 decision_note present live). ### Tables, RLS, and reach (RLS = enabled & forced unless noted) **Tenant tables — RLS ENABLED + FORCED, policy `org = NULLIF(current_setting('app.current_org_id',true),'')::uuid`:** `organizations` (SELECT/UPDATE policies only), `users`, `candidates`, `candidate_skills`, `candidate_certifications`, `candidate_customer_experience`, `candidate_evaluations`, `requirements`, `required_skills`, `required_certifications`, `opportunities`, `slates`, `slate_members`, `sourcing_log`, `aedt_candidate_notices`, `aedt_inventory`, and **`eeo.candidate_demographics`**. **Global / non-RLS tables (reference, append-only audit, or auth-secret reached only via SECURITY DEFINER):** `skills`, `sam_opportunities`, `usaspending_awards`, `bls_wages`, `opportunity_records`, `aedt_model_versions`, `audit_logs` (append-only, see §6), `auth_credentials`, `auth_sessions`, `mfa_totp`, `mfa_recovery_codes`, `user_invites`, `login_attempts`, `goose_db_version*`. **SECURITY DEFINER functions (19, all `public`):** `provision_org`, and the auth set: `auth_accept_invite`, `auth_create_invite`, `auth_create_session`, `auth_get_credential`, `auth_lookup_login`, `auth_mfa_activate`, `auth_mfa_advance_step`, `auth_mfa_enroll`, `auth_mfa_get`, `auth_mfa_reset`, `auth_recovery_claim`, `auth_recovery_lookup`, `auth_recovery_store`, `auth_revoke_session`, `auth_revoke_user_sessions`, `auth_rotate_session`, `auth_set_credential`, `auth_validate_session`. These are the only path the app role has to the auth-secret tables; `provision_org` is **not** granted to `missionmatch_app`. ### Columns (per table; authoritative DDL is `db/migrations/`) ``` organizations: id uuid PK, name text, slug text UNIQUE, status text, created_at, updated_at users: id uuid PK, organization_id uuid, email text, full_name text, role text, status text, last_login_at?, created_at, updated_at, mfa_enrolled bool (UNIQUE org_id+id, global lower(email)) candidates: id uuid PK, organization_id, full_name, email?, headline?, clearance_level, clearance_poly, clearance_status, clearance_granted_at? date, clearance_expires_at? date, reinvestigation_due_at? date, years_experience int, location?, remote bool, relocatable bool, available bool, resume_embedding vector(384)?, search_tsv tsvector?, created_at, updated_at, deleted_at?, source text, external_id?, anonymized_at?, clearance_provenance text, clearance_verified_at? (UNIQUE org_id+id; UNIQUE external; 6 CHECKs; HNSW + GIN(tsv) indexes) candidate_skills: organization_id, candidate_id, skill_id (composite FK to candidates+skills, RLS) candidate_certifications: organization_id, candidate_id, name candidate_customer_experience: organization_id, candidate_id, agency candidate_evaluations: organization_id, candidate_id, requirement_id (composite PK), total_score numeric, band text, clearance_score, skills_score, certs_score, customer_score, experience_score, location_score (all numeric), explanation text, matched_skills jsonb, gap_summary jsonb, model_version text, evaluated_by?, evaluated_at, updated_at (8 CHECKs, 3 FKs) requirements: id uuid PK, organization_id, opportunity_id?, title, customer_agency?, required_level, required_poly, required_years int, work_location?, remote_allowed bool, key_personnel bool, created_at, updated_at, deleted_at?, labor_category? text, quantity int (UNIQUE org_id+id, 4 CHECKs) required_skills: organization_id, requirement_id, skill_id, mandatory bool required_certifications: organization_id, requirement_id, name opportunities: id uuid PK, organization_id, name, solicitation_number?, customer_agency?, due_date?, status, created_at, updated_at, value? numeric, value_source text, federal_ref? (UNIQUE org_id+id) slates: id uuid PK, organization_id, requirement_id, name, status, created_by?, created_at, updated_at slate_members: organization_id, slate_id, candidate_id (PK slate+cand), total_score numeric, band, 6 component _score numerics, explanation, trace jsonb, gap_summary jsonb, decision text, decided_by?, decided_at?, rank? int, created_at, model_version?, decision_note? (9 CHECKs, 3 FKs) skills: id uuid PK, name text UNIQUE, category?, embedding vector(384)? (partial HNSW index) candidate (eeo).candidate_demographics: id bigint PK, organization_id, candidate_id, gender?, race_ethnicity?, veteran_status?, disability_status?, consent_given bool, collected_by?, collected_at (RLS forced; NO FK to public.candidates; compliance role only) audit_logs: id bigint PK, organization_id?, actor_user_id?, action, entity_type?, entity_id?, old_values jsonb?, new_values jsonb?, ip_address?, user_agent?, created_at (append-only triggers) auth_credentials: user_id PK, organization_id, email_normalized, password_hash, pepper_version int, failed_streak int, locked_until?, password_changed_at, created_at, updated_at (UNIQUE global email) auth_sessions: id uuid PK, token_hash bytea UNIQUE, user_id, organization_id, state text, mfa_satisfied bool, created_at, last_seen_at, idle_expires_at, absolute_expires_at, revoked_at?, rotated_from?, ip_address?, user_agent? (1 CHECK) mfa_totp: user_id PK, organization_id, secret_ciphertext bytea, secret_nonce bytea, key_version int, status text, last_used_step bigint, created_at, activated_at? (1 CHECK) mfa_recovery_codes: id uuid PK, user_id, organization_id, code_hash, pepper_version, lookup_tag bytea, used_at?, created_at user_invites: id uuid PK, organization_id, email_normalized, invited_user_id, token_hash bytea UNIQUE, role, expires_at, accepted_at?, created_by, created_at (1 CHECK) login_attempts: id bigint PK, email_normalized, organization_id?, ip_address?, succeeded bool, reason?, created_at aedt_model_versions: version PK, weights jsonb, band_cuts jsonb, notes?, effective_from, created_at aedt_inventory: id uuid PK, organization_id, tool_key, display_name, purpose, model_version?, active bool, created_at, updated_at (UNIQUE org+tool) aedt_candidate_notices: id uuid PK, organization_id, candidate_id, notice_version, data_categories jsonb, retention_days int, disclosed_by?, disclosed_at (2 FKs) sourcing_log: id uuid PK, organization_id, candidate_id?, external_id, source, provider, query, legal_basis, collected_at, created_at sam_opportunities: notice_id PK, title, solicitation_number, department, full_parent_path, sub_agency, naics_code, psc_code, set_aside_code, set_aside, notice_type, base_type, posted_date?, response_deadline?, active bool, pop_city/state/zip, ui_link, description_url, resource_links jsonb, poc jsonb, raw jsonb, source, synced_at (6 filter indexes) usaspending_awards: (recipient/uei/naics/amount/galapagos-flag; indexes idx_awards_galapagos/naics/ recipient) — keyless USASpending + SAM Contract Awards normalized bls_wages: series_id PK-part, occupation_code, occupation_title, role, area, area_name, metric, period_year, value numeric?, raw jsonb, synced_at opportunity_records: id uuid PK, source_name, source_record_id, dedupe_key, record_kind, title, agency, sub_agency, naics, psc, pop_*, set_aside*, contract_vehicle, estimated_value numeric?, value_source, incumbent_name, incumbent_uei, posted_date?, response_deadline?, award_date?, estimated_award_date?, pop_start/end_date?, ui_link, is_planning bool, raw_object_key, raw_sha256, fetched_at, synced_at (UNIQUE source+record; 6 indexes) ``` ### Indexes of note (verified) - **Vector (HNSW):** `idx_candidates_emb_hnsw` (candidates.resume_embedding), `idx_skills_embedding_hnsw` (skills.embedding, partial WHERE NOT NULL). - **Full-text:** `idx_candidates_tsv` (GIN on candidates.search_tsv). - **Tenant composites lead with `organization_id`:** `idx_candidates_org`, `idx_requirements_org`, `idx_slates_org`, `idx_slate_members_org`, `candidate_evaluations` org+req / org+cand, etc. - **Composite-FK targets:** `uq_{candidates,requirements,opportunities,slates,users,aedt_inventory}_org_id` (`UNIQUE (organization_id, id)`). ### Live row counts (introspected; tenant tables inflated by integration-test seeding) | Table | Rows | Note | |---|---|---| | organizations | 1017 | **~647 are per-test orgs** (each integration test seeds one) | | users | 206 | mostly test | | candidates | 2787 | **demo org `1111…1111` = 1511**; rest ≈ test residue (~8/org) | | candidate_skills | 1707 | | | requirements | 387 | | | slates | 165 | | | slate_members | 3424 | | | candidate_evaluations | 64 | persisted screen results (new in WS-B) | | skills | 61 | global ontology | | sam_opportunities | 1402 | **real feed load** | | usaspending_awards | 525 | **real feed load** | | bls_wages | 20 | **real feed load** (HI/NM × roles × metrics) | | opportunity_records | 2471 | **real feed load** (opportunity intelligence) | | sourcing_log | 10 | PDL imports | | audit_logs | 13123 | append-only | | eeo.candidate_demographics | 280 | segregated, compliance-only | **Embedding / FTS population (decisive for an AI layer):** `candidates`: **resume_embedding non-null = 144 / 2787**; **search_tsv non-null = 2787 / 2787** (FTS fully populated, generated column). `skills`: **embedding non-null = 0 / 61** (no skill vectors at all). The pgvector columns + HNSW indexes exist but are essentially **unpopulated**. --- ## 3 — THE DETERMINISTIC MATCHING AND SCORING ENGINE **BUILT AND CONFIGURED** - **Where computed:** the pure function `engine.ScoreCandidate(ctx, Candidate, Requirement, SkillMatcher)` in `internal/modules/matching/engine/engine.go:54`. Documented PURE — no DB/IO/clock/randomness (`engine/types.go:1-16`, `engine/engine.go:47-53`). The skill matcher is **injected**. - **Inputs:** `engine.Candidate` (`types.go:104-115`, **no demographic field** by design, `types.go:103`) and `engine.Requirement` (`types.go:126-137`). - **Output `engine.MatchResult` (`types.go:206-224`):** `Total decimal` (0–100, 2dp), `Band`, six component decimals, `Explanation`, `GapSummary []string`, `MatchedSkills []SkillMatch`, and `Trace []ComponentScore` (per-component Weight/Raw/Weighted/Rationale). - **Fixed weights (`engine.go:20-27`):** Clearance 0.25, Skills 0.25, Certifications 0.15, CustomerExperience 0.15, Experience 0.10, Location 0.10 (sum = 1.00). - **Bands (`engine.go:33-37`, `bandFor` `:301-310`):** Green ≥ 90 ("Proposal Ready"); Yellow 75–89.99 ("Potential"); Red < 75 ("Recruiting Required"). Semantic threshold `0.80` (`engine.go:31`). - **Component logic:** Clearance binary 0/100 — exact-or-higher + active + poly (`:114-138`); Skills = fraction met, exact-then-semantic (`:155-187`); Certs fraction (`:223-242`); Customer binary (`:246-254`); Experience full-or-linear (`:258-270`); Location = avg(location fit, availability) (`:274-297`). All math in `shopspring/decimal`. Rationale per component + overall `Explanation` (`buildExplanation` `:312-321`). `ModelVersion = "v1.0.0"` (`:16`). - **Single source of truth — per-candidate, YES; one org-level exception:** - The **only** non-test callers of `engine.ScoreCandidate` are inside `internal/modules/matching`: `service.go` (ScreenCandidate `:146`, GenerateSlate `:345`, Screen `:445`, RankCandidates `:485`, rankOpenOpportunities `:558`), `intel.go:215`, `missioncheck.go:96`. So the **screen view, ranked candidates / top matches, the Candidates Intelligence page, and slate generation all run the one engine.** Slate detail / proposal-readiness / export / who-can-support read the **frozen persisted** scores, never rescore (`slate_service.go`, `mission_service.go:31`). - **Exception (by design, flagged for the AI layer):** the **Command Center org-level "Mission Readiness" headline is a SEPARATE SQL composite**, NOT `engine.ScoreCandidate`. Its weights are distinct — clearance_coverage 0.30, skill_coverage 0.25, labor_category_coverage 0.20, proposal_readiness 0.15, availability_coverage 0.10 (`commandcenter/service.go:30-37`) — and its eligibility is a greedy SQL assignment that **re-encodes** the clearance/years/mandatory-skill gates in SQL rather than calling the engine (`commandcenter/service_assignment.go:30-153`). It is labeled "DISTINCT from the engine's per-candidate weights." So org-level readiness mirrors but does not reuse the engine path — a second gating implementation to keep in sync. - **Persisted write path:** `persistEvaluationOnTx` (`service.go:101-107`) → `UpsertCandidateEvaluation` keyed on `(org, candidate, requirement)`, written inside the same tenant tx as `POST /match` and `POST /screen`. Slate generation freezes a parallel `slate_members` record with `ModelVersion`. Every served/frozen score also writes an append-only AEDT audit event (`servedScoreEvent` `service.go:76-83`). - **Exposing endpoints:** `POST /screen`, `POST /match`, `GET /requirements/{id}/candidates`, `POST /slates/generate`, `GET /candidates/intelligence`, `POST /mission-check/*`, `GET /requirements/{id}/support` (reads stored). All also reachable under the three tool namespaces over the same shared `matching.Handler` (no scoring re-implemented; cross-tool equality proven by `TestSharedCoreScoresIdenticalAcrossTools`). --- ## 4 — DATA FEEDS All sync jobs read the write DSN from `MM_MIGRATOR_DATABASE_URL` (fallback `MM_DATABASE_URL`) and API keys from server-side env (`/etc/missionmatch/sam-sync.env`). Live-env key NAMES present in that file (verified, values redacted): `SAM_API_KEY`, `SAM_API_BASE`, `BLS_API_KEY`, `PDL_API_KEY`, `MM_MIGRATOR_DATABASE_URL`, `MM_COMPLIANCE_DATABASE_URL`, plus **unused** `DATA_GOV_API_KEY`, `KAGGLE_USERNAME`, `KAGGLE_KEY`, `HF_TOKEN` (present in env but not referenced by any code — dead). | Feed | Code | Auth (key NAME) | Schedule | Live? | Loaded rows | |---|---|---|---|---|---| | **SAM opportunities** | `cmd/sam-sync`, `internal/platform/samgov/*` | `SAM_API_KEY` query param (`sam-sync/main.go:33`) | systemd `mm-sam-sync.timer` daily ~04:00 *(no next-elapse at audit — verify)* | **BUILT AND CONFIGURED / LIVE** | `sam_opportunities` 1402 | | **USASpending awards + SAM Contract Awards + Galapagos past-perf by UEI** | `cmd/awards-sync`, `internal/platform/{marketintel,samawards}/*`, `internal/modules/awards` | USASpending keyless; SAM Awards reuses `SAM_API_KEY`; Galapagos UEI const `N3GDEB78RNF8` (`awards-sync/main.go:26`) | systemd `mm-awards-sync.timer` daily 04:30 | **BUILT AND CONFIGURED / LIVE** | `usaspending_awards` 525 | | **BLS OEWS wages** | `cmd/rates-sync`, `internal/platform/bls/*`, `internal/modules/wages` | `BLS_API_KEY` in body (`rates-sync/main.go:25`) | systemd `mm-rates-sync.timer` weekly Sun 05:00 | **BUILT AND CONFIGURED / LIVE** | `bls_wages` 20 (HI/NM) | | **People Data Labs sourcing** | `cmd/import-candidates`, `internal/platform/sourcing/*`, `internal/modules/sourcing` | `PDL_API_KEY` `X-Api-Key` (`import-candidates/main.go:39`) | **none — MANUAL** (`make import-candidates`) | **BUILT BUT NOT CONFIGURED** (live provider, CLI-only, no API trigger) | `sourcing_log` 10 | | **Opportunity Intelligence ingestion** | `cmd/opp-sync`, `internal/modules/opportunity_intelligence/*` | per-adapter | **cron.d `mm-opp-sync` daily 04:30** (verified on host) | mixed (see below) | `opportunity_records` 2471 | **Opportunity Intelligence adapters (`…/adapters/`):** `sam.go`, `sam_contract_awards.go`, `usaspending.go`, `fallback.go` (SAM Awards→USASpending) = **LIVE & registered** (`opp-sync/main.go:62-67`); `forecast.go` (generic GSA/agency CSV) = **BUILT BUT NOT CONFIGURED** — only appended when `MM_FORECAST_CSV` is set (`opp-sync/main.go:73-81`); `stubs.go` **GovWin IQ / GovTribe / GSA eBuy** = **STUBBED**, never registered, return `ErrRequiresLicense`/`ErrRequiresTenantCredential` (`stubs.go:25-63`). **Compliance handling wired into feeds (BUILT AND CONFIGURED):** contact-field exclusion via `--includeContact` default-false (`platform/sourcing/pdl.go`); per-import **sourcing compliance log** (source/provider/query/legal_basis, same tx as the candidate); **right-to-erasure** — `sourcing/service.go` Delete anonymizes in place (detaches the log FK so the audit survives, wipes PII + resume vector + child rows, writes `privacy.candidate.anonymized`), gated by `CapPrivacyErase` (`sourcing/handler.go:26`). --- ## 5 — SKILLS, CLEARANCE, AND LABOR CATEGORIES - **Skills ontology — BUILT, embeddings EMPTY.** Tables `skills` (global; `embedding vector(384)` + partial HNSW), `candidate_skills`, `required_skills` (`mandatory bool`) — `db/migrations/0002_matching.sql`. **No adjacency / synonym / graph table exists** (grep finds "ontology" only in comments); semantic adjacency is delegated entirely to embedding cosine. The `internal/modules/skills/` dir is **empty**; all writes upsert **by name only** (`UpsertSkill` from missioncheck/sourcing/adopt), so `skills.embedding` is never populated in the live path (live count **0/61**), and the generated `SearchSkillsByEmbedding` query has **no live caller**. - **Skill matcher — BUILT AND CONFIGURED.** `engine.matchOne` resolves exact-by-id → exact-by-name → injected `SkillMatcher` at 0.80 (`engine.go:191-219`). The semantic impl `EmbeddingSkillMatcher` (`matching/skillmatcher.go`) embeds the skill **name strings** at request time and takes best cosine in `decimal`; it does **not** use the HNSW index. Wired with `embeddings.New(cfg)` (`cmd/api/main.go:109,127`) — but the embedder default is `fake`/deterministic (see §9 and live-env note), so in practice exact matching dominates. - **Clearance / poly / status / reciprocity — BUILT AND CONFIGURED.** Ordered enums in `engine/types.go` (`ClearanceLevel` Unknown<None<Confidential<Secret<TopSecret<TSSCI`; `Polygraph` None<CI<FullScope; `ClearanceStatus`). DB code mapping `engine/codes.go` (drives the SQL CHECK sets). `scoreClearance` (`engine.go:114-138`): non-active ⇒ 0; held<required ⇒ 0 (higher satisfies lower); held poly<required ⇒ 0; else 100. **Provenance honesty layer** (`db/migrations/0012`): columns `clearance_provenance` (self_reported default / sponsor_verified / document / continuous_vetting) + `clearance_verified_at`; the intelligence layer sets `Verified` only when `sponsor_verified` — the platform never asserts it verified a clearance itself. - **Labor categories — descriptive, NOT a scoring input.** `requirements.labor_category` (nullable) + `quantity` (`db/migrations/0003`), index `idx_requirements_opp_lcat`. Coverage is computed in `matching/intel.go` (an 8-category `laborKeywords` map for unstructured leads; a candidate "covers" an LCAT at band ≥ Yellow). The pure `engine.Requirement` has **no** labor_category field — it never enters the six weighted components. --- ## 6 — AUTHENTICATION, AUTHORIZATION, AND AUDIT **BUILT AND CONFIGURED** - **FIPS crypto module:** Go-native in-tree FIPS 140-3 module, **CMVP cert #5247** (Geomys Go Cryptographic Module v1.0.0), pinned via `GOFIPS140=v1.0.0` (`docs/STACK_DECISIONS.md:73-101,167`). Build stamps it (`Makefile:12-18`); `verify-fips` asserts the stamp (`Makefile:49-51`); runtime fail-closed if `MM_REQUIRE_FIPS && !fips140.Enabled()` (`cmd/api/main.go:61-62`); systemd sets `GODEBUG=fips140=on`. The deployed binary is FIPS-stamped (verified `go version -m`). - **Password hashing:** **PBKDF2-HMAC-SHA256**, **600000 iterations** (`security/password.go:28`), 32-byte key, 16-byte salt, 64-char input cap; constant-time verify; rehash-on-login when iter/pepper behind policy. **Versioned pepper** (HMAC-SHA256 before PBKDF2, in-boundary) `security/pepper.go`. - **TOTP:** **in-house** on `crypto/hmac`+`crypto/sha256` (`security/totp.go`), **HMAC-SHA256 end-to-end** (URI `algorithm=SHA256`), **32-byte** secret, strictly-monotonic replay guard. Secrets **AES-256-GCM** encrypted at rest with rotatable KEK (`security/aesgcm.go`). - **Sessions:** opaque, **server-side**; 32 random bytes, DB stores **only the SHA-256 hash** (`security/session_token.go`). Cookie **`__Host-mm_session`** — HttpOnly, Secure, SameSite=Strict, Path=/, no Max-Age (`auth/handler.go:302-307`). Companion **`__Host-mm_csrf`** (not HttpOnly, double- submit). **Two-stage:** `state='auth_pending'` (pre-MFA, valid only on MFA routes) → `state='active' && mfa_satisfied=true`. Timeouts computed by the **DB clock** inside `auth_validate_session`: idle 900s, absolute 43200s (12h), pending 300s. - **RBAC (`security/rbac.go`):** roles owner(4)>admin(3)>recruiter(2)>member(1)>viewer(0); 14 capabilities (candidate.read/write, requirement.write, slate.write, search.run, export.run, sourcing.read, privacy.erase, user.invite, user.role.change, user.disable, org.settings.write, audit.read, compliance.read). Enforced by `RequireCapability`/`RequireRole` middleware from the **validated session in context** (never a header), 403 before the handler. `OutRanks` enforces strict separation of duties (invite/role-change/disable only someone you outrank, never self/peer/superior). - **Invite / activation / provisioning — link delivery MANUAL.** Invite (`POST /auth/invite`) and recovery reissue **return the one-time token in the JSON response** (`auth/dto.go:91-95`, `handler.go:263-269`) — there is **no email/SMS transport anywhere** (grep: zero SMTP/mailer code). Activation via SECURITY DEFINER `auth_accept_invite`. Org provisioning is **migrator-only** (`cmd/provision-org` → `provision_org` SECURITY DEFINER; not granted to the app role; no HTTP surface). - **Audit taxonomy — BUILT AND CONFIGURED.** Writer `internal/platform/audit/audit.go` (`OnTx` org-scoped + `System` NULL-org). `audit_logs` is **append-only** (BEFORE UPDATE/DELETE/TRUNCATE triggers `forbid_mutation()`; app granted INSERT/SELECT only) — `db/migrations/0001_foundation.sql:82-104`. Audit **reads are tenant-isolated** (the read query binds `organization_id = current GUC`, gated by `CapAuditRead`). ~33 distinct Action strings, e.g. `aedt.score.served`, `aedt.slate.generated`, `aedt.slate_member.decided`, `aedt.slate.exported`, `aedt.cross_tool.read`, `aedt.ephemeral.evaluated`, `retention.candidate.anonymized`, `privacy.candidate.anonymized`, `candidate.pii.view`, the full `auth.*`/`user.*`/`authz.*` set, `opportunity.ingest`, `requirement.adopted_from_forecast`. Only `aedt.score.served` is consumed by an audit READ (the EEO screening join, §10). --- ## 7 — API SURFACE (all under `/api`; tenant group = session resolver → CSRF → per-route RBAC) Wired in `cmd/api/main.go`. The flat matching paths AND the three tool namespaces share one `matching.Handler` (`main.go:243-249`): flat = backward-compat alias, namespaced = canonical. **Health/metrics (no `/api`, unauthenticated):** `GET /healthz`, `GET /readyz`, `GET /healthz/fips`, `/metrics` (`main.go:191-227`). **auth (`auth/handler.go`):** - Public (pre-tenant): `GET /api/auth/csrf`, `POST /api/auth/login`, `…/mfa/{enroll,activate,verify, recover}`, `…/invite/accept`, `…/logout`. - Session-only: `GET /api/auth/session`, `POST /api/auth/logout-all`, `POST /api/auth/password/change`. - Admin/RBAC: `GET /api/users` (RoleAdmin), `POST /api/users/invite` (user.invite), `PATCH /api/users/{id}/role` (user.role.change), `PATCH /api/users/{id}/status` (user.disable), `POST /api/users/{id}/mfa/reset` (user.disable), `POST /api/users/{id}/recovery` (user.invite). **matching core (flat + `/api/screenmatch` + `/api/slatematch` + `/api/missionmatch`):** - ScreenRoutes (cap `search.run`/`candidate.write`): `POST /match`, `POST /screen`, `GET /requirements/{id}/candidates`, `GET /candidates/{id}/opportunities`, `POST /mission-check/opportunities`, `POST /mission-check/screen`, `POST /candidates`, `PATCH /candidates/{id}/clearance-provenance`. - SlateRoutes: `POST /slates/generate` (slate.write), `PATCH /slates/{id}/members/{cid}/decision` (slate.write), `GET /slates/{id}/members/{cid}/trace` (candidate.read), `GET /requirements/{id}/slates`, `GET /slates/{id}`, `GET /slates/{id}/proposal-readiness` (candidate.read), `GET /slates/{id}/export` (**export.run**, audited). - ReadRoutes (candidate.read): `GET /candidates`, `GET /candidates/{id}`, `GET /requirements`, `GET /requirements/{id}`. - IntelRoutes/MissionRoutes (search.run): `GET /candidates/intelligence`, `GET /requirements/{id}/support`. **commandcenter (flat + `/api/missionmatch`):** `GET /command-center` (candidate.read), `POST /command-center/upcoming/{recordID}/adopt` (requirement.write). **Other modules:** `GET /opportunities`, `GET /opportunities/{id}` — **no capability gate**; `GET /opportunity-intelligence` (search.run); `GET /awards`, `GET /awards/galapagos` — **no cap**; `GET /wages` — **no cap**; `GET /sourcing/candidates` + `/log` (sourcing.read), `DELETE /sourcing/candidates/{id}` (privacy.erase); `GET /compliance/adverse-impact` + `/export` (compliance.read, **served by the compliance pool**); governance `GET /aedt/model-versions`, `GET|POST /aedt/inventory`, `POST /compliance/retention/run`, `GET /audit` (audit.read), `POST /candidates/{id}/notice` (candidate.write). **Flags:** - **Unauthenticated-but-tenant-scoped reads:** `GET /opportunities*`, `GET /awards*`, `GET /wages` carry **no `RequireCapability`** (any authenticated role; RLS still scopes by org). Every other read requires ≥ candidate.read. - **`POST /aedt/inventory`** (a write) is gated on `CapComplianceRead` (a read capability) — minor authz-granularity smell, not a bug. - **Seven empty `internal/modules/` dirs** (candidates, slates, resumes, requirements, search, dashboards, skills) expose no routes (functionality folded into `matching`/`commandcenter`). - No orphaned/unreachable route found; every `*Routes` method is wired in `main.go`. - eeo + adverse-impact routes return **503** if `MM_COMPLIANCE_DATABASE_URL` is unset; DB-backed routes 503 if `MM_DATABASE_URL` unset (`main.go:86-107`). --- ## 8 — FRONTEND **BUILT AND CONFIGURED** - **Stack:** React 19 + react-dom (`package.json:21-22`), Vite 8 (`:35`, `@vitejs/plugin-react`), TanStack Query 5, react-router-dom 7, **Tailwind v4 via `@tailwindcss/vite`**, oxlint (no ESLint), TypeScript ~6 **strict** (`tsconfig.app.json:19` + noUnusedLocals/Params, verbatimModuleSyntax). `vite.config.ts` dev server :5180 proxying `/api`+`/healthz` to `MM_BACKEND` (default :8090). Brand tokens in `src/index.css:4-32` — shared navy/gold + per-tool `--color-screen` teal / `--color-slate- accent` violet / gold umbrella. Browser resume parsing libs present: `pdfjs-dist 6.0.227` + `mammoth`. - **Routes (`src/App.tsx`; all behind the session `Gate` except `/accept`):** **default after login = index → `Navigate to="/command"`**. `/command` (CommandCenter), `/command/candidates` (Candidates intelligence), `/compliance`, `/screen` (ScreeningQueue), `/screen/evaluate/:c/:r` (CandidateEvaluation), `/requirements` (labeled "Opportunities"), `/slate`, `/settings`, `/pipeline` (Placeholder "Roadmap v2.0"), `/dashboard` (legacy), and legacy redirects `/command-center→/command`, `/candidates→/command/candidates`, `/evaluate/:c/:r`. Tool switcher in `components/AppShell.tsx` (TOOLS array, 3 tools, each its own accent + nav). - **Auth/backend calls:** `lib/AuthContext.tsx` calls `GET /api/auth/session` on mount (200→app, 403→ Login). Session is the `__Host-mm_session` cookie via `credentials:'include'`; **no X-Org-ID anywhere**. CSRF: signed double-submit token from `GET /api/auth/csrf` + `__Host-mm_csrf`, de-duplicated in-flight, refresh-once-on-403 (`lib/api.ts:42-105`). All calls hit relative `/api…`. The three tools each have a thin namespace client (`tools/*/api.ts` → `/screenmatch|/slatematch|/missionmatch/*`); a shared `api` object still serves Dashboard/Requirements/Compliance/sourcing/wages/awards/Mission-Check. Mission Check posts only **extracted resume text** (browser parses the file via `lib/resume.ts`; bytes never leave the tab). - **Served identity:** `<title>missionMATCH, Mission Workforce Intelligence` (`index.html:6`, verified live); shell brand `missionMATCH™`, header chip "FIPS 140-3 • RLS isolated". --- ## 9 — EXISTING AI, EMBEDDINGS, AND RETRIEVAL (the substrate a new layer would build on) - **Embedding generation — BUILT BUT NOT CONFIGURED.** Provider port `Embedder` in `internal/platform/embeddings/embeddings.go` with: `deterministicEmbedder` (the **`fake`** default — SHA-256+xorshift hash, offline), `httpEmbedder` (real OpenAI-compatible `/v1/embeddings`, Bearer / Azure `api-key`), and `bedrock` = **"adapter not wired into core build"** (STUBBED). Config: provider `MM_EMBED_PROVIDER` (default `fake`), `MM_EMBED_ENDPOINT`, `MM_EMBED_MODEL`, `MM_EMBED_API_KEY`, `MM_EMBED_DIMENSION` (384). Production `Validate()` **rejects `fake`** — but the **live host runs `MM_ENV=development`** so that guard is inactive (see §11). **Live-env check:** `MM_EMBED_PROVIDER` is set, but **no `MM_EMBED_ENDPOINT`/`MODEL`/`API_KEY` exist in `api.env`** → no real embedding backend is operational; the wired embedder is the deterministic/fake one. This matches the DB: `skills.embedding` 0/61 and `candidates.resume_embedding` 144/2787 populated. - **Vector store / hybrid search — BUILT BUT NOT CONFIGURED.** Schema is complete: `vector(384)` columns + HNSW on candidates & skills, `search_tsv` GENERATED + GIN. A hybrid query `SearchCandidatesHybrid` (`db/queries/candidates.sql:111-139`) fuses pgvector kNN + `ts_rank` via **Reciprocal Rank Fusion (k=60)**, sqlc-generated — but it has **no live caller** (referenced only by the integration test); no code sets `hnsw.ef_search`; `internal/modules/search/` is **empty**. The deterministic engine is what runs. - **LLM provider calls — MISSING.** No chat/completion/LLM call anywhere; no LLM SDK in `go.mod`. Every "openai/azure" reference is the **embeddings** endpoint, not chat. `config.go:61` calls an LLM extractor "a future addition." - **Extraction — deterministic only.** `internal/platform/extraction/` port supports **only** `deterministic` (rules/regex/gazetteer over the closed skill vocabulary); any other provider hard-errors ("not implemented in this build"). `MM_EXTRACT_*` config fields are reserved/unused. The CUI self-hosted- vLLM extractor named in CLAUDE.md is **MISSING** (port exists, impl does not). - **Retrieval/ranking beyond the engine — MISSING.** `matching/intelligence/` is pure presentation over `MatchResult` (no ML). The only live consumer of the embeddings provider is the semantic skill matcher, which on the `fake` embedder yields near-zero similarity for unrelated skills, so exact matching dominates. - **Libraries:** AI/vector deps limited to `pgvector/pgvector-go v0.4.0` (+ pgx adapter). No OpenAI/Anthropic/Cohere/Bedrock/LangChain SDKs. --- ## 10 — COMPLIANCE AND GOVERNANCE - **EEO storage + isolation — BUILT AND CONFIGURED; isolation PROVEN (four layers).** `eeo.candidate_demographics` (gender, race_ethnicity, veteran_status, disability_status, consent_given) lives in a **separate migration dir** `db/migrations_eeo/0001_eeo.sql` (outside `db/migrations/`, so sqlc never models it), RLS forced, **no FK to public.candidates**. Grants: SELECT/INSERT to `missionmatch_compliance` only (append-only); `missionmatch_app` gets **nothing**. It cannot reach scoring, barred at: (1) physical migration separation; (2) DB privilege (app role zero grant — proven live by `TestEEOPrivilegeBoundary`, app SELECT → 42501); (3) sqlc codegen (no eeo model in `dbgen`); (4) the pure engine input type has no demographic field (`TestCandidateHasNoDemographicFields`). Grep: zero references to `candidate_demographics`/`eeo` from matching/screenmatch/slatematch (one unrelated comment). - **Adverse-impact reporting — BUILT AND CONFIGURED (live-capable).** `internal/modules/eeo/service.go` computes the **four-fifths/80% rule** in `decimal` on the **compliance pool**, two stages: **selection** (demographics ⋈ `slate_members` decisions) and **screening** (demographics ⋈ `audit_logs WHERE action='aedt.score.served'`, green = screened-in). Returns 503 if no compliance pool. **Live env has `MM_COMPLIANCE_DATABASE_URL` set**, so this is operational on Bubbles. - **Governance docs in `docs/`:** **present & substantial** — `STACK_DECISIONS.md` (~56KB; the FIPS/RLS/ pgvector/auth law), `MODEL_CARD.md` (scoring methodology, NIST AI RMF + ISO/IEC 42001, "what the model never uses"), `openapi.yaml` (~23KB). **MISSING as standalone docs:** `DEPLOYMENT.md` (deploy guidance is inline in STACK_DECISIONS + `infra/systemd/`), `COMPLIANCE.md`, `RESPONSIBLE_AI.md` (rules live in CLAUDE.md + MODEL_CARD), **`BACKUP_DR.md`** (no RTO/RPO/runbook — though a backup cron exists, §1). --- ## 11 — CONFIG AND SECRETS (names only; never values) - **Config loader:** `internal/platform/config/config.go` — `Load()` reads env + defaults + `Validate()`. Production `Validate()` hard-requires FIPS, every auth secret, the Origin allowlist, Secure cookies, MFA, and rejects the `fake` embedder and the dev header resolver. Secret keys decode via explicit `base64:`/`hex:`/`raw:` prefix; a decode failure is fatal (no silent fallback). - **Where env lives:** `.env.example` (placeholders, gitignored real files). On Bubbles, root-owned 0600 `/etc/missionmatch/api.env` (API) and `/etc/missionmatch/sam-sync.env` (sync), loaded via systemd `EnvironmentFile=`. - **LIVE RUNNING ENVIRONMENT (verified key NAMES present in `api.env`, values redacted):** `MM_ENV` (**= development**, verified earlier — so production guards are NOT enforced live), `MM_HTTP_ADDR` (=127.0.0.1:8094), `MM_CLOUD`, `MM_REQUIRE_FIPS` (set), `MM_COOKIE_SECURE` (=true), `MM_MFA_REQUIRED` (**= false** — MFA disabled for the pilot), `MM_ALLOWED_ORIGINS`, `MM_TRUSTED_PROXY_HEADER`, `MM_DATABASE_URL`, `MM_MIGRATOR_DATABASE_URL`, `MM_COMPLIANCE_DATABASE_URL` (set), `MM_CREDENTIAL_PEPPER`, `MM_TOTP_KEK`, `MM_CSRF_KEY`, `MM_RECOVERY_LOOKUP_KEY`, `MM_EMBED_PROVIDER` (set), `GODEBUG`. **Absent from `api.env`:** `MM_EMBED_ENDPOINT`/`MODEL`/`API_KEY`/`DIMENSION`, all `MM_EXTRACT_*`, `MM_CREDENTIAL_PEPPER_PREVIOUS`/`VERSION`, `MM_TOTP_KEK_PREVIOUS`/`VERSION`, `MM_TOTP_ISSUER`, `MM_DECISION_RETENTION_DAYS`, session-timeout overrides (defaults apply). - **Secret/key inventory (NAME → purpose → where referenced; presence "live" = seen in the running env):** | Name | Purpose | Referenced | Live env | |---|---|---|---| | `MM_DATABASE_URL` | app-role (NOBYPASSRLS) DSN | config.go:131 | **present** | | `MM_MIGRATOR_DATABASE_URL` | migrator (BYPASSRLS) DSN | config.go:132 | present (api+sync) | | `MM_COMPLIANCE_DATABASE_URL` | compliance-role DSN (eeo only) | config.go:133 | **present** | | `MM_CREDENTIAL_PEPPER` (+ `_VERSION`/`_PREVIOUS`) | pre-PBKDF2 HMAC pepper, rotatable | config.go:154-156 | base present; rotation vars absent | | `MM_TOTP_KEK` (+ `_VERSION`/`_PREVIOUS`) | AES-256 KEK for TOTP secrets | config.go:157-159 | base present; rotation vars absent | | `MM_CSRF_KEY` | CSRF token HMAC | config.go:160 | present | | `MM_RECOVERY_LOOKUP_KEY` | recovery-code lookup-tag HMAC | config.go:161 | present | | `MM_EMBED_API_KEY` / `_ENDPOINT` / `_MODEL` / `_PROVIDER` / `_DIMENSION` | embedding provider | config.go:137-141 | only `_PROVIDER` present → no real backend | | `MM_EXTRACT_API_KEY` / `_ENDPOINT` / `_MODEL` / `_PROVIDER` | extraction provider (reserved) | config.go:144-147 | absent (deterministic) | | `SAM_API_KEY` (+ `SAM_API_BASE`/`SAM_AWARDS_BASE`) | SAM opps + contract awards | sam-sync/opp-sync | present (sync env) | | `BLS_API_KEY` (+ `BLS_API_BASE`) | BLS OEWS | rates-sync/main.go:25 | present (sync env) | | `PDL_API_KEY` (+ `PDL_API_BASE`) | People Data Labs | import-candidates/main.go:39 | present (sync env) | | `USASPENDING_BASE`, `MM_FORECAST_CSV`, `MM_OPP_RAW_DIR` | non-secret feed config | opp/awards-sync | base URLs default; `MM_FORECAST_CSV` UNKNOWN | | `DATA_GOV_API_KEY`, `KAGGLE_USERNAME`, `KAGGLE_KEY`, `HF_TOKEN` | declared in env/.env.example, **not referenced in code** | — | present in sync env but **dead** | Non-secret config: `MM_ENV`, `MM_HTTP_ADDR`, `MM_CLOUD`, `MM_REQUIRE_FIPS`, `MM_ALLOW_DEV_HEADER_AUTH`, `MM_ALLOWED_ORIGINS`, `MM_TRUSTED_PROXY_HEADER`, `MM_COOKIE_SECURE`, `MM_MFA_REQUIRED`, `MM_*_SECONDS` timeouts, `MM_DECISION_RETENTION_DAYS`, `MM_DEMO_ORG_ID`. No secret value was read. --- ## 12 — TESTS AND CI - **Matching engine unit tests — BUILT (strong).** `engine/engine_test.go` (421 lines): perfect-match, full clearance hierarchy (higher-satisfies-lower, expired/unknown→0, poly gate), skills exact+semantic at the 0.80 boundary, certs/customer/experience/location, **band boundaries 74.99/75/89.99/90**, **weights sum to 1**, determinism/order-independence, and an ID-only-difference no-side-channel test. `engine/frozen_test.go` pins exact weights/bands/threshold/ModelVersion. `engine/golden_test.go` snapshots Green/Yellow/Red sub-scores + trace-re-sums-to-total. `engine/codes_test.go` guards that engine enum codes equal the SQL CHECK sets. - **EEO no-field proof:** `TestCandidateHasNoDemographicFields` (`engine_test.go:361-380`) reflects over `engine.Candidate`, tokenizes each field name, and fails on an 18-prefix banned denylist (race, gender, sex, ethnic, religio, disab, national, origin, marital, dob, birth, pregnan, veteran, orientation, color, creed, age). - **Live integration suite — BUILT (`internal/integration/`, `//go:build integration`, ~24 funcs).** Proves: RLS cross-tenant read isolation; unset-GUC default-deny (0 rows); composite-FK cross-tenant injection block (23503); audit append-only (app 42501, migrator P0001); EEO privilege boundary; slate numeric round-trip; hybrid pgvector+RRF search executes; cross-tool scoring **byte-identical** 75.00; evaluation upsert-latest; slate list/detail/readiness/notes/audited-export; who-can-support reads stored (no rescore); 5-stage data-flow read-not-recompute + cross-tool audit carries no PII; auth provision/MFA/session lifecycle + app-cannot-provision + app-cannot-read-5-secret-tables; EEO four- fifths over the compliance pool; clearance provenance; command-center adopt; mission-check ephemeral; opportunity records; revenue-at-risk; sourcing dedup. **All 31 pass live on M4** (verified this audit). - **Frontend tests — MISSING.** No `*.test/*.spec`, no vitest/jest/playwright/testing-library. Zero coverage. - **CI — MISSING (no automated pipeline).** No `.github/workflows`, no GitLab/Jenkins/etc. Gates exist only as manual `make` targets: `test` (unit), `test-integration` (not in `ci`), `ci` (=test + ci-gates + sbom), `ci-gates` (`verify-backdoor` — asserts no `X-Org-ID` resolver in the release binary; `verify-fips` — asserts the GOFIPS140 stamp), `sbom` (CycloneDX). The Makefile comments describe an intended-but-absent pipeline. --- ## 13 — GAPS AND OPEN ITEMS The repo is clean of accidental `TODO`/`FIXME`/`XXX`/`HACK` in production paths; every gap below is an intentional, commented pilot deferral. **Verified suspected gaps:** - **(a) Resume parsing — BUILT (split), `resumes` module empty.** `internal/modules/resumes/` has **zero files** (vestigial). Real extraction is server-side `internal/platform/extraction/deterministic.go` (354 lines: clearance/years/USPS-location/gazetteer skills+certs); the browser does file→text only (`frontend/src/lib/resume.ts`, pdfjs+mammoth) and posts text. A **model-backed/CUI extractor is STUBBED** (hard-errors on any non-deterministic provider). - **(b) Manual invite delivery — confirmed.** No SMTP/mailer anywhere; invite + recovery links are returned in API responses for manual hand-off (MISSING transport; **needed before non-pilot rollout**). - **(c) Feeds present but not configured:** GovWin IQ / GovTribe / GSA eBuy = **STUBBED**, never registered (need a license/credential, not a flag). GSA forecast CSV = **BUILT BUT NOT CONFIGURED** (off unless `MM_FORECAST_CSV`). PDL live search = **BUILT BUT NOT CONFIGURED** (CLI-only, key-gated, no API trigger). - **(d) Placeholder UI:** only `frontend/src/screens/Placeholder.tsx`, used solely for `/pipeline` ("Talent Pipeline · Roadmap v2.0"). **Other:** - **Embeddings/semantic = scaffolding only:** default `MM_EMBED_PROVIDER=fake`; live env has no endpoint/model/key → semantic skill matching runs on hash vectors; `skills.embedding` 0/61 and `candidates.resume_embedding` 144/2787 populated; HNSW + RRF hybrid SQL exist but **nothing live calls them** (`search` module empty). **Bedrock embedding adapter STUBBED** (blocks an AWS GovCloud deploy as-is). - **Seven empty `internal/modules/` dirs:** resumes, candidates, dashboards, requirements, search, skills, slates (dead scaffolding). - **Public forgot-password** deferred to sub-phase 2.9 (MISSING). - **No skill adjacency/synonym graph** (embeddings are the only intended semantic layer, and they are unpopulated). - **No automated CI; zero frontend tests** (§12). - **No `BACKUP_DR.md`/RTO-RPO doc** despite a working `mm-pg-backup` cron (§1, §10). - **Live config posture flags:** the deployed host runs **`MM_ENV=development`** (production validations — reject-fake-embedder, require-MFA, require-secrets — are NOT enforced) and **`MM_MFA_REQUIRED=false`** (MFA off for the pilot). These are pilot settings, not code gaps, but they mean the live deployment is not in its hardened production posture. - **Command Center org-readiness is a second (SQL) gating path** distinct from the engine (§3) — to keep in sync. - Minor: `GET /opportunities`/`/awards`/`/wages` have no capability gate; `POST /aedt/inventory` (a write) is gated on a read capability; `mm-sam-sync.timer` showed no next-elapse at audit (verify it fires). --- ## STATE OF THE BUILD (plain prose) **What is solid.** The deterministic core is real, well-bounded, and proven: one pure `engine.ScoreCandidate` (frozen weights/bands, decimal math, no demographic field) drives every per-candidate number across screen, slate, ranked, and intelligence views, locked by frozen/golden/ boundary unit tests and a cross-tool byte-identical integration test. The data layer is production-grade: PostgreSQL 16 + pgvector, RLS enabled-and-forced on every tenant table with a non-bypass app role, an append-only audit table, a strictly segregated EEO schema the app role provably cannot reach (four independent barriers), and FIPS-validated in-house crypto (PBKDF2-600k, AES-GCM TOTP, opaque hashed sessions). The federal data feeds (SAM opps, SAM/USASpending awards + Galapagos UEI past-performance, BLS wages, Opportunity Intelligence) are live HTTP and scheduled, with real loaded data (1.4k opps, ~2.5k opportunity records, 525 awards). The three-tool split (screenMATCH/slateMATCH/missionMATCH) is deployed and serving on one origin with a clean redirect, a tested 24-function live integration suite, and a daily DB backup cron. **What is fragile.** The live deployment is not in production posture: it runs `MM_ENV=development` and `MM_MFA_REQUIRED=false`, so the very production guards the code enforces (reject the fake embedder, require MFA, require all secrets) are dormant. Invite and recovery links have no delivery transport (returned in API responses) — fine for a piloted handoff, blocking for real onboarding. There is no automated CI and zero frontend tests; all gates are manual `make` targets. A couple of read endpoints lack capability gates, and the Command Center's org-level readiness is a second SQL gating path that mirrors rather than reuses the engine. **What is not there yet (and matters most for an AI layer).** The semantic/AI substrate is scaffolding, not a working system: the embedding columns and HNSW indexes exist but are essentially empty (`skills.embedding` 0/61, `candidates.resume_embedding` 144/2787); the embedding provider defaults to a deterministic `fake` and the live host has no real endpoint/model/key wired; the hybrid pgvector+tsvector+ RRF retrieval SQL is generated but **no live code path calls it** and the `search` module is empty; there are **no LLM calls anywhere** and no LLM SDK; extraction is deterministic-only with the CUI/vLLM extractor unbuilt; there is no curated skill adjacency graph. In short: the database, retrieval SQL, provider ports, and brand/route shell are ready to host an AI layer, but embeddings, semantic search, and any LLM/generative capability are unbuilt or unconfigured — that is the greenfield the new layer will occupy. *End of inventory. No changes were made.*