# Per tenant database routing (Mission A, validated on staging) Branch `per-tenant-routing` (off `ai-brain-live`). Validated on M4 staging only, NOT merged, no production change, no brain cutover. Moves the platform from one shared RLS database to a shared control plane plus one isolated database per tenant. Built and proven across phases A0 to A6; the operator cutover is authored in `docs/MIGRATE_TO_PER_TENANT.md` and the new client onboarding in `docs/RUNBOOK_ONBOARDING.md`. ## A0 research (primary sources relied on) A four facet research workflow verified current best practice before building. - **Control plane vs tenant data plane.** Keep the database per tenant silo (the resource boundary is the isolation boundary) and operate the fleet through ONE shared control plane that owns identity, the auth secret tables, and a tenant directory catalog. Sources: AWS SaaS Lens silo/pool/bridge models (https://docs.aws.amazon.com/wellarchitected/latest/saas-lens/silo-pool-and-bridge-models.html) and Tenant Isolation Strategies; Azure multitenant SaaS patterns and control plane considerations (https://learn.microsoft.com/en-us/azure/architecture/guide/multitenant/considerations/control-planes). CRITICAL FINDING: the repo today replicates identity and auth (0001 organizations and users, 0009 auth_*) into EVERY tenant database via provision-tenant, which makes the repo's own globally unique email and email first login invariants physically unenforceable. Auth must be OPERATED only against the control plane (the schema can stay identical; the per tenant copies of the auth tables go unused). - **pgxpool per tenant lifecycle.** Build each tenant pool through the existing `database.NewPool` (so the pgvector AfterConnect codec and the NOBYPASSRLS `AssertRole` run on every pool), lazy open and Ping before caching, a bounded LRU of live pools with a pin or refcount so an in flight transaction is never closed under it, singleflight to avoid duplicate opens, a nonzero `MaxConnLifetimeJitter`, and typed `ErrUnknownTenant` (no network) and `ErrTenantUnreachable`, never a fallback to another tenant. Source: pgx v5 pgxpool godoc and the jackc/pgx discussions on pool per request and RLS vs pool per tenant. Repo reconciliation: org tenancy stays RLS in one pool; the registry is only for the physically separate tenant databases, layered ABOVE `database.NewPool`, never replacing RLS. - **Cross tenant migrations.** Use the per instance `goose.NewProvider` (not the process global API), two providers (public plus eeo via `WithTableName`), a Postgres session locker for cross process mutual exclusion, a dry run via `Status` and `HasPending`, and an aggregate then fail design that names the offending database and exits non zero if any database errored or is behind. Source: pressly/goose v3 Provider API. Flagged: STACK_DECISIONS pins goose v3.27.1 but go.mod has v3.24.1 indirect; reconcile in A4. - **Per tenant secrets.** A secrets port mirroring the storage and embeddings ports; the directory stores only a reference (never a credential); an env adapter for dev and staging; cloud adapters stubbed and wired at deploy (no cloud SDK in the core build); a short TTL cache with force refresh on an auth failure. Sources: AWS Secrets Manager and Azure Key Vault multitenancy guidance, the 12 factor config principle. ## A1 control plane, tenant directory, secrets port - **config:** `MM_TENANCY` (shared default, or per_tenant), the bounded pool knobs (`MM_TENANT_MAX_POOLS`, `MM_TENANT_POOL_MAX_CONNS`, `MM_TENANT_POOL_IDLE_EVICT_SECONDS`), and `MM_SECRETS_BACKEND` plus `MM_SECRET_CACHE_TTL_SECONDS`. Production validation: per_tenant requires the control plane app DSN and a government cloud secret store, never the env backend. - **Migration 0020 tenant_directory:** the control plane catalog (organization_id, db_name, db_host, db_port, connection_secret_ref, status, cloud, region, last_migrated_version/at). It is cross tenant by design so it is NOT under the org GUC RLS; the migrator writes it, the app role reads it to route. Created in the shared migration set so the schema stays identical (harmless and empty in a tenant database). - **internal/platform/secrets:** the `Resolver` port (`ResolveDSN` by reference, `Forget` for rotation), an env adapter (`MM_TENANT_DSN_`), stubbed cloud adapters, and a TTL cache. Unit tested (resolve, key folding, unknown reference typed error, cloud stub error, cache hit and expiry and force refresh). - **internal/platform/directory:** the `Directory` accessor over a control plane pool, with `Lookup`, `List`, `Register`, `SetStatus`, `SetMigrated`, and a typed `ErrUnknownTenant`. Integration tested on staging (the app role reads the catalog for routing, the migrator writes it during provisioning). - **Validation:** FIPS build clean, full unit suite green under race and fips140=on, `TestTenantDirectoryRoundTrip` passes on M4 staging at goose v20. ## A2 tenant pool manager `internal/platform/tenantpool` is the bounded, lazy registry of per tenant pools. Each pool is built through the new `database.NewBoundedPool` (so the pgvector codec and the NOBYPASSRLS role assertion run on every tenant pool), Pinged before caching, held in a bounded LRU with a pin or refcount so an in flight transaction is never closed under it, opened once under singleflight, and evicted whole when idle. Typed errors never fall back to another tenant (`ErrUnknownTenant`, `ErrTenantUnavailable`, `ErrTenantUnreachable`), and an authentication failure forgets the cached secret and retries once. Unit tested under the race detector for lazy open, cache hit, singleflight, LRU eviction, pin protection, idle sweep, close, and every typed error. `Config` carries a role and a secret reference suffix so a second registry serves the compliance role for the eeo path with a distinct per tenant credential. ## A3 request routing `tenant.DB` carries an optional `Router`. `RunInTenantTx` (all 23 call sites unchanged) and a new `Acquire(ctx)` resolve the request's own tenant database by the validated org in context and run there; shared mode is a transparent no op over the single pool. The direct pool readers (the stored skill matcher, whose cache is now keyed by org because each tenant has its own skills, plus the reference catalog reads and sourcing) route through `Acquire`. The brain, the staffing layer, and embed-backfill all run against the tenant database. `cmd/api/main.go` builds a static control plane DB for auth and the System audit, and in per_tenant mode makes the app and compliance DBs routing DBs over the two pool managers. Routing unit tested (dispatch by org, fail closed without org, shared mode no op); the shared mode integration suite on M4 staging is unchanged. ## A4 cross tenant migrations `cmd/migrate-all-tenants` applies the migration set to the control plane and every active tenant database from the directory, both the public and the segregated eeo schema (two goose version tables), using the per instance goose Provider (not the process global API) and a Postgres session locker. It is idempotent and resumable, supports `-check` (dry run) and `-concurrency`, records the applied version back to the directory, and exits non zero if any database failed or is behind. Proven on staging: the control plane reports public v20 and eeo v1 at head, and the tool correctly fails closed when a directory row points at a database that does not exist. ## A5 auth and identity against the control plane Auth, sessions, MFA, and user lookups operate against the control plane (the auth service and the System audit bind to the static control plane pool); the crypto is unchanged. The five auth integration tests pass. For foreign key integrity, each tenant database's business tables reference its own organizations and users rows; those are migrator owned, so identity is seeded into each tenant database at onboarding by `internal/platform/identity.SeedTenant` (and `cmd/sync-tenant-identity`), re run when the org's users change. The running API stays the NOBYPASSRLS app role and never writes identity. ## A6 staging validation (two tenant databases, physical isolation) Provisioned a control plane plus two tenant databases on M4 (cp_staging, tenant_alpha, tenant_beta), all schema identical at goose v20, RLS forced, eeo segregated. `TestPerTenantPhysicalIsolation` drives the real routing stack (directory, secrets resolver, pool manager) and proves a request as tenant A reads only its own row from tenant_alpha, a request as tenant B reads only its own row from tenant_beta, neither crosses, an unknown org is a typed denial, and no org fails closed. Inside a real tenant database the app works in full: RLS forced as defense in depth, the brain end to end (RAG, TAG, CAG, the EEO refusal, the audits, the reconciliation, memory isolation and erasure, the semantic threshold), the staffing layer end to end (gap, portfolio, proactive, decision, erasure), the eeo adverse impact via the compliance role with the app role denied on the eeo schema, and embed-backfill populating the tenant's own vectors. Gates green: full unit suite under race and fips140=on, FIPS stamp present, ci-gates green (the dev X-Org-ID resolver still cannot compile into a release build), vet and gofmt clean, no Python, no cgo. Fixed a real provisioning bug surfaced here: `provision-tenant` ran the migrations as the admin superuser, so the tenant tables were owned by postgres and the migrator could neither write identity nor run future migrations. It now runs the migrations as the migrator (a new `-migrator-dsn`, with CREATE granted on the schema and the database to the migrator by the admin first), so the migrator owns every table. ## What remains for the operator (Mission A) - The production cutover is the operator's manual step in `docs/MIGRATE_TO_PER_TENANT.md` (stand up the control plane, provision each tenant database, copy each org's business data, register and activate, flip `MM_TENANCY=per_tenant`, verify, roll back). Authored, NOT executed. The onboarding flow for a new client is `docs/RUNBOOK_ONBOARDING.md`. - Production secrets must use a government cloud secret store (the cloud adapter is wired at deploy); the env backend is dev and staging only (config rejects env in production per_tenant). - A new user invited after onboarding must be re synced into the tenant database (`sync-tenant-identity`); automating that on invite accept is a sequenced follow on. - Per tenant connection budget: confirm `MaxPools` times `MaxConnsPerPool` plus the control plane and compliance pools stays under the server `max_connections` for the fleet size. --- # BUILD_REPORT.md — AI brain activation **Date:** 2026-06-29. **Branch:** `ai-brain-live` (off `w3-ai-staffing-recommendations`, which sits on the unmerged AI intelligence layer). **Build host:** M2. **Validated on:** M4 staging (`brain_staging`, goose v18, bge-small embeddings, the live MEGAMIND plane). This mission made the AI brain LIVE: the brain library in `internal/modules/ai` (orchestrator, TAG, RAG retrieval, memory, KAG SkillAdjacency, CAG rules) was built and tested but unreachable from the running app, and the vector layer was dead in the live path. It is now wired: the real embedder, backfilled vectors, the HNSW indexed skill matcher, the brain mounted as an authenticated tenant scoped route behind the faithfulness gate, the two scoring paths reconciled, proven end to end on staging. **Done means, and is proven:** a person can ask the brain a question through an authenticated route and get an explainable, faithfulness gated, tenant isolated answer. `TestBrainAnswersEndToEnd` exercises exactly that (RAG, TAG, CAG, and an EEO refusal, with the AEDT audits), and passes on M4 staging. ## Pre cutover hardening (four fixes, 2026-06-29) A close review before cutover found four things to fix. All four are now closed and re validated on M4 staging (`brain_staging`, goose v19). No feature was added; these are correctness and deploy safety. **FIX 1, the semantic threshold (the live correctness defect).** The engine's `DefaultSemanticThreshold` was 0.80, tuned for a different embedder. On the production bge-small vectors a clear synonym scores ~0.74, so true semantic matches fell under the live cutoff and were silently discarded: the brain shipped a semantic matcher the engine then ignored. A calibration harness on staging (`TestSemanticThresholdCalibration`) scored known synonym and adjacent skill pairs against known unrelated pairs on the stored bge vectors: - Genuine synonyms (11 of 12 pairs): cosine 0.678 to 0.944 (for example "Penetration Testing" to "Ethical Hacking" 0.678, "Threat Hunting" to "Threat Detection" 0.875, "Network Engineering" to "Network Infrastructure Engineering" 0.944). - Unrelated pairs (all 12): cosine 0.410 to 0.571 (mean 0.498), the highest being a noise spike "Amazon Web Services" to "Underwater Basket Weaving" 0.571. - The genuine groups separate in the gap [0.571, 0.678]. One acronym pair, "Signals Intelligence" to "SIGINT" (0.534), sits BELOW the unrelated ceiling, so no safe single threshold can capture it; acronym to expansion belongs to the skills ontology alias layer, not to a cosine cutoff. It is documented, not forced. Chosen threshold: **0.62**, in the separating gap with margin: 0.0486 above the unrelated ceiling (no invented match) and below every genuine synonym (no discarded match). At 0.62 the harness confirms 11 of 12 synonyms matched and 12 of 12 unrelated rejected. The threshold is a frozen engine contract, so one commit changed the constant, bumped the engine **ModelVersion to v1.1.0**, and updated the frozen test together; **migration 0019** registers v1.1.0 in `aedt_model_versions` (migrator only, since the app role has SELECT) so every decision stamped v1.1.0 has a registry row and a model card entry, with weights and band cuts unchanged from v1.0.0. `TestBrainSemanticSkillMatchEnginePath` proves the new threshold FIRES in the engine scoring path (not a read only compare): a candidate holding a synonym of a required skill scores Skills 100 through `engine.ScoreCandidate` over stored bge vectors, while an unrelated skill scores 0 with the required skill reported as a gap. The golden `TestSemanticBelowThresholdDoesNotMatch` moved its below threshold fixture from 0.79 to 0.55 (a deliberate shift, since 0.79 now matches). **FIX 2, the enforced re embed gate.** Production holds all-minilm vectors; bge and all-minilm are not comparable, so serving over a wrong model store silently mis ranks every retrieval with no error. A new boot self check (`ai.VerifyVectorModel`, behind `ai.VectorGate`) asserts the skills and candidates vector columns are vector(384) through the catalog (no RLS bypass, no PII), samples stored skill vectors, and re embeds them with the configured model to confirm the stored vectors were produced by that model (cosine within 0.98). It re embeds the SAME text the backfill wrote (skill name with category, via a shared `embeddings.SkillEmbedText`), so a categorized skill is not a false mismatch. On a proven mismatch, `POST /ai/query` returns a typed 503 `vector_model_mismatch` and the mismatch is logged, while the rest of the API serves normally; a boot time plane outage leaves the gate to self heal on the first query after the plane recovers. The comparison core and the gate state machine are unit tested for match, model swap, wrong width, permanent mismatch, transient retry, and the handler 503; a live staging test proves the gate OPENS on the bge store. DEPLOY_PLAN.md B2 is now a hard psql assertion (it RAISES unless every live candidate and every skill is embedded and a sampled vector is 384 wide) and states the gate in words: the brain stays dark until both the re embed is verified complete and the startup check passes. **FIX 3, the merge sequence.** DEPLOY_PLAN.md now enumerates the three branch merges as discrete, individually rollback able steps: M1 merge the AI intelligence layer (migration 0017, ai_memory, the foundation), M2 then merge W3 (0018, the two staffing tables), M3 then merge the brain (0019, the v1.1.0 registry row). Each has its own rollback (revert the merge commit, and the goose down already in the migration). The hard rule is stated: do not collapse the three, and do not run the cutover until all three are merged in order and the migrations through 0019 are applied to production. **FIX 4, the forecast test is pre existing (confirmed, not a regression).** `TestCommandCenterUpcomingIncumbentAdopt` requires a populated global forecast and contract award catalog (it does not seed them; it expects them as global reference data). On a fresh database with an empty catalog it fails by construction. Confirmed by a controlled experiment: - On the **ai-intelligence-layer** branch (neither W3 nor the brain present, head migration 0017), against a CLEAN M4 database (`fix4_ail_baseline`, goose v17, empty catalog), it FAILS at `command_center_d_integration_test.go:43` ("expected forecast records in upcoming opportunities"). - On the **ai-brain-live** branch (full stack, migrations to 0019), against a CLEAN M4 database (`fix4_brain_baseline`, goose v19, empty catalog), it FAILS at the SAME assertion, line 43. Identical failure on the foundation branch alone proves the cause is the empty reference catalog (environmental), NOT a regression introduced by W3 or the brain. The "passes on the AI layer, fails only with W3 or the brain" regression condition is NOT met. Both scratch databases were dropped after the run. **Re validation (all on M4 staging, nothing against production).** FIPS production build clean with the stamp present (`GOFIPS140=v1.0.0`), `sqlc generate` no drift, goose to v19. Full unit suite green under race and fips140=on; `go vet` and `gofmt` clean; `make ci-gates` green (backdoor and FIPS). Full integration suite on `brain_staging`: **49 pass, 1 skip, 1 fail**. The skip is `TestCrossTenantDatabaseIsolation` (per tenant pools, out of scope). The one fail is the same environmental `TestCommandCenterUpcomingIncumbentAdopt` (on `brain_staging` it now clears line 43 because two forecasts exist there, and fails one assertion later at line 62 on the empty contract award feed, the same missing reference data cause). Every mission critical brain test passes: the threshold fires in the engine path and the frozen test matches, the startup vector model check serves on a match, the brain answers end to end with the EEO refusal and audits, the reconciliation proves engine equals tag equals dashboard, and cross tenant memory isolation and erasure hold. ## Scope held - Mounted LIVE on the SHARED RLS database, exactly like matching and commandcenter. Per tenant database routing is a sequenced follow on and was NOT done; the live API was not switched to per tenant pools. - No branch merged, no production database or Bubbles host touched. Validated on M4 staging only. ## Non negotiable constraints, all held 1. **No Python, FIPS intact.** All ML runs out of process on the MEGAMIND plane over HTTP. The API binary stays `CGO_ENABLED=0 GOFIPS140=v1.0.0`; the stamp was verified after every build. No cgo, no new dependency. 2. **EEO isolation absolute.** No brain path names the eeo schema; the TAG layer runs ONLY curated, parameterized, read only intents over an allowlist that excludes eeo (never free form SQL); `safety.ForbidEEO` guards every string sent to the plane; and a new natural language guard (`MentionsProtectedAttribute`) refuses a question that asks to group people by a protected attribute even in plain words ("list candidates by race"). The EEO unreachability tripwire walks the ai package (21 ai and staffing files scanned, none reach eeo). 3. **Tenant isolation.** Every brain database read (HybridSearch, ExecuteIntent, memory Recall and Store, the audit) runs inside `RunInTenantTx`, RLS scoped, on the validated org. The skill matcher reads only the GLOBAL skills table (no org, no RLS). MEGAMIND stays stateless. `TestBrainMemoryCrossTenantIsolation` proves one tenant cannot recall another's memory. 4. **The deterministic engine is the floor.** The brain never recomputes or overrides `engine.ScoreCandidate`; the TAG intents read the frozen `candidate_evaluations` and `slate_members`, not the Command Center composite. No model is in any scoring or eligibility decision; the model only embeds a query, generates prose, and checks faithfulness. `TestBrainTAGReconciliation` proves the brain's answer equals the engine equals the dashboard read. 5. **Faithfulness gate plus deterministic fallback on every answer.** The model prose ships only when the gate confirms it; otherwise the brain returns the deterministic rendering of the same evidence (a 200, rationale_source deterministic), recording why. A plane outage degrades through the circuit breaker to a typed 503, never a hang. 6. **Every brain action audits** (`aedt.ai.answered`, `aedt.ai.refused`), carrying no PII, no names, no prose, no question text, no fallback reason. ## Research (primary sources, cited in code) A 3 agent research workflow verified each piece before wiring: - **pgvector 0.8 HNSW and RRF**: the repo is textbook correct (the cosine operator with vector_cosine_ops, RRF at k=60, SET LOCAL ef_search and iterative_scan in the tenant tx). One tightening applied: the vector arm of `SearchCandidatesHybrid` is now `AS MATERIALIZED` so the relaxed_order HNSW output is re-sorted by true distance before the LIMIT, preventing nearest neighbor mis ranking under the RLS post index filter at scale (pgvector 0.8 README; AWS pgvector 0.8 Aurora guidance). The critical rule, the SAME model and normalization for stored and query vectors, drove the embedder decision below. - **bge-small-en-v1.5 on Ollama**: it is NOT in the official Ollama library; `qllama/bge-small-en-v1.5` is the simplest stable 384 dim handle over the standard /api/embed (pulled and probed on M4). The optional query instruction prefix is query only and genuinely optional (BAAI: omitting it is a slight degradation only), so the adopted policy is NO prefix on either path, which keeps stored and query text consistent. - **NL2SQL safety and faithfulness**: a curated intent registry beats sanitizing generated SQL; the gate withholds unconfirmed prose and falls back to a deterministic answer while still returning a useful 200 (CRAG, Self RAG family). ## What was activated, by phase 1. **The real embedder.** bge-small-en-v1.5 (the `qllama` handle, 384 dim) is the configured default for the MEGAMIND plane; the gateway forwards the request model, so the API and the backfill name it. The fake deterministic embedder is GONE from the API binary (the embedder was removed from main.go and from the staffing cron) and remains only on the deterministic test path (`EmbeddingSkillMatcher`). 2. **The backfill and the live skill matcher.** `cmd/embed-backfill` (idempotent, resumable, batched) ran against `brain_staging`: **skills.embedding 32/32, candidates.resume_embedding 10/10**, all bge. The new `matching.NewStoredSkillMatcher` reads the backfilled, HNSW indexed `skills.embedding` column (a global, no RLS table) into a bounded cache and resolves a required skill against a candidate's skills by cosine over the STORED vectors, instead of embedding name strings at request time. `TestBrainSemanticSkillMatchLive` proves the engine's Skills component now resolves semantically (a synonym scores 0.74 versus an unrelated skill 0.57 on stored bge vectors). 3. **Reconciliation.** The brain consumes the one engine path: the TAG org level intents read `candidate_evaluations`, and `WhoCanSupport` reads the same frozen table, so the brain, the engine, and the dashboard agree by construction. `TestBrainTAGReconciliation` proves equality (engine = tag = support). 4. **The brain mounted live.** `POST /ai/query` is mounted under the tenant scoped group behind session, CSRF, the new `brain.query` RBAC capability (the search.run tier), rate limit, and audit. The orchestrator gained a top level dispatcher (`Answer`) and the CAG direct path (`AnswerDirect`); the faithfulness gate was upgraded to the deterministic fallback. The registry is validated at boot (an unsafe intent fails the boot). 5. **Per tenant memory live.** Memory writes are scoped by organization_id and recall is RLS scoped; RAG is memory augmented (the tenant's own recalled notes join the evidence). Memory is wiped on erasure in BOTH anonymize paths (the on demand `sourcing.Delete` and the governance retention sweep). Cross tenant non retrieval and erasure are tested. 6. **The contract** is in `docs/openapi.yaml`: the query request, the answer object (answer, path, rationale source, citations, faithful, fallback reason), and one typed error envelope with the 200/403/422/503 failure surface (a faithfulness fallback is a 200 deterministic; a demographic or unanswerable or unknown intent question is a 422; a plane outage is a 503; never a raw SQL error and never a hang). ## Live embedding counts on staging `brain_staging` after the pre cutover hardening was fully re embedded with bge (skills 38/38, live candidates 131/131), and the startup vector model check passes against it (the gate opens). (The shared production database holds all-minilm vectors today; the cutover re embeds everything with bge, now an enforced gate, see DEPLOY_PLAN.md and FIX 2, because mixing models silently corrupts ranking.) ## Test results - **Unit (race, no DB):** all pass, including the EEO tripwire over the ai package and the engine frozen and golden tests. - **Live integration on M4 staging:** the brain tests pass (semantic skill match via stored embeddings AND in the engine scoring path at the new threshold, the threshold calibration harness, the startup vector model check, the brain answering across RAG/TAG/CAG with the EEO refusal and the audits, the scoring reconciliation engine=tag=dashboard, memory cross tenant isolation with erasure). After the pre cutover hardening the full suite on `brain_staging` is **49 pass, 1 skip, 1 fail** (see the hardening section: the skip is per tenant database isolation, out of scope; the fail is the environmental `TestCommandCenterUpcomingIncumbentAdopt`, the empty global forecast and award catalog, confirmed pre existing on the AI layer branch alone in FIX 4). - **Build gates:** FIPS stamp present after every build, `make ci-gates` green (backdoor and FIPS), vet and gofmt clean, no Python, no cgo. ## What the adversarial review found and what was fixed A 5 dimension review (EEO safety, the engine floor, the live vector layer, tenancy, the failure surface), with every finding independently verified, confirmed six, all fixed: 1. **HIGH (erasure parity).** The governance retention sweep anonymized a candidate but skipped ai_memory, so a candidate de identified by the automatic sweep left recallable memory behind, unlike the on demand erasure. Fixed: the ai_memory delete was added to the sweep's purge loop, so both anonymize paths leave the same residue. 2. **MEDIUM (embed model drift).** The API defaulted to bge while the backfill, the gateway, and the env defaulted to all-minilm, the silent corruption mode (query bge versus stored all-minilm mis ranks every neighbor). Fixed: every default aligned to bge so write time and query time vectors share one model. 3. **MEDIUM (failure surface).** A transient plane failure that had not yet tripped the breaker mapped to a 4xx. Fixed: a typed `inference.ErrPlaneUnavailable` is returned on exhausted transient retries, mapped to 503. 4. **LOW.** The API now calls `ValidateRegistry` at boot so an unsafe TAG intent fails the boot. The `ForgetCandidate` comment was corrected (the transactional erasure paths inline the delete; ForgetCandidate is the standalone hook). ## What remains (genuine decisions or sequenced work) 1. **The production cutover** is the operator's manual step in DEPLOY_PLAN.md (merge the AI layer and W3, apply the migrations through 0018, pull bge on the production Ollama, switch the embedder, re embed all vectors with bge, deploy the brain binary, verify, roll back). Validated on M4 staging, NOT run against production. 2. **Per tenant database routing** for the brain is the sequenced follow on (out of scope here); the brain is born on the shared RLS database and isolation is enforced by RLS and the org GUC. 3. **The semantic threshold.** DONE in the pre cutover hardening above (FIX 1). The engine's 0.80 default was recalibrated to 0.62 on the bge-small vectors (engine ModelVersion v1.1.0, migration 0019), proven to fire in the engine scoring path; the old 0.80 silently discarded real synonym matches (cosine ~0.74). 4. **GraphRAG teaming and Foresight** stay scaffolded and degrade gracefully; the rule based router stays rule based, as directed. 5. **The Command Center SQL composite** still co exists for the org level dashboard (a different metric than the per candidate engine score); the brain consumes the engine path and agrees with both, so they cannot contradict. Merging the composite into the engine is a larger refactor, recommended but out of scope.