ThatAIguyA world of ideas

THE CONSCIOUSNESS ATLAS
Read. Experiment. Follow the evidence.

ConsciousnessLearn · Explore · Joseph Anady

Janady13/missionmatch / docs/superpowers/specs/2026-07-20-recruitmatch-design.md

Source identity, revision, and recovered contents.

GitHub sourceSource text recovered

Source identity

FieldRecorded value
Source typeGitHub source
Original recordJanady13/missionmatch / docs/superpowers/specs/2026-07-20-recruitmatch-design.md
DateNot specified in source
RepositoryJanady13/missionmatch
Revision720d47fae476b0d38477ae4401697d372b1ea2bc
Branchfeature/opportunity-to-outcome
Path or archive memberdocs/superpowers/specs/2026-07-20-recruitmatch-design.md
Original SHA-256f0d1317e48d1d5415b9b53b3318be7642d4e4bbd4ca34635417c049875e30e2e
Git blob SHAde1fa4e1fe5d822ce0d4abb8f058f895f1c89d02
Reading-copy SHA-256f0d1317e48d1d5415b9b53b3318be7642d4e4bbd4ca34635417c049875e30e2e

Original source ↗

Every recorded occurrence

These 2 records contain identical original source bytes. Their paths and revisions remain part of the archive.

Original recordBranch or archiveRevision / identifierOriginal location
Janady13/missionmatch / docs/superpowers/specs/2026-07-20-recruitmatch-design.mdfeature/opportunity-to-outcome720d47fae476b0d38477ae4401697d372b1ea2bcOpen original
Janady13/missionmatch / docs/superpowers/specs/2026-07-20-recruitmatch-design.mdfeature/aioe-upgrade2178e7298d0e5d9af83afa12402fd587f0ba6ed8Open original

Recovered source transcript

# recruitMATCH and org branding: design Status: proposed, awaiting review Date: 2026 07 20 Author: design session with Joseph Branch: feature/aioe-upgrade ## 1. Purpose Add **recruitMATCH**, a fourth product tool alongside missionMATCH, screenMATCH and slateMATCH, giving recruiters an operational dashboard over work the platform already performs. Add **per organization branding** so the application white labels for every client that buys it. FG Talent Partners is the first client of missionMATCH. It is not the only client. Nothing in this design may be FG specific. FG is a row in `organizations` and nothing more. ## 2. Non goals These are explicitly out of scope. Each would be its own design. 1. No applicant tracking spine. No client account entity, job requisition entity, hiring manager, interview, offer, placement, bill rate, pay rate, invoice, recruiter activity log, or task and reminder tables. Verified absent across all 47 migrations and against the live schema. 2. No change to the scoring engine. `engine.ModelVersion` stays `v1.4.0` and the six weights are untouched. 3. No per tenant database routing. That work lives unmerged on `per-tenant-routing` and is a separate project. Tenancy here is the existing shared database plus forced RLS. 4. No orchestration layer. Parked. 5. No change to the EEO boundary. recruitMATCH must never read the `eeo` schema. ## 3. What a tool is in this codebase Verified from the existing three. A tool is a repeatable structure, not a concept. Frontend * `frontend/src/tools/<tool>/brand.ts` exporting `{key, name, tagline, accent, basePath}` * `frontend/src/tools/<tool>/api.ts` with `const NS = '/<tool>'` and a client object * `frontend/src/tools/<tool>/queries.ts` with hooks keyed `['<tool>', resource, ...params]` * `frontend/src/tools/<tool>/*.tsx` screens, named exports, no default export * one entry in the `TOOLS` array in `frontend/src/components/AppShell.tsx` * one accent token in the `@theme` block of `frontend/src/index.css` Backend * `internal/modules/<tool>/{dto,service,service_queries,handler}.go` * `r.Route("/<tool>", handler.Routes)` in `cmd/api/main.go` ## 4. How the tools commingle, and how recruitMATCH joins screenMATCH and slateMATCH are not parallel silos. They share one `*matching.Handler` instance, and `matching.ReadRoutes` is mounted under every tool prefix, so `/api/screenmatch/candidates` and `/api/slatematch/candidates` answer identically. The deeper join is through shared tables: ``` screenMATCH scores a candidate writes candidate_evaluations slateMATCH builds a slate writes slates and slate_members missionMATCH Command Center aggregates both into readiness recruitMATCH aggregates both into recruiting operations ``` recruitMATCH joins the family two ways: 1. It mounts `matching.ReadRoutes` under `/recruitmatch`, exactly as the other three do. 2. It reads the same tables the other tools write. recruitMATCH is the outcome view of work performed in screenMATCH and slateMATCH. It introduces no private tables. ## 5. Data contract, panel by panel Every source below was verified against the migrations and the live schema. Panels with no source render an explicit empty state that names the missing feed. They never render a fabricated number. ### Backed by real data | Panel | Source | |---|---| | Active jobs | `requirements` where parent `opportunities.status = 'open'` or `opportunity_id is null` | | New candidates | `candidates.created_at` inside the window | | Submissions | `slate_members.created_at` inside the window | | Screened | `candidate_evaluations.evaluated_at` inside the window | | Band mix, green yellow red | `candidate_evaluations.band` | | Approvals and rejections | `slate_members.decision` with `decided_at` | | Decision velocity | `slate_members.decided_at` minus `slate_members.created_at` | | Recruiter attribution | `slate_members.decided_by` and `candidate_evaluations.evaluated_by` joined to `users` | | Top active jobs | `requirements` left joined to a count of `slate_members` | | Clearance breakdown | `candidates.clearance_level` joined to `clearance_levels` | | Alerts feed | existing `GET /api/alerts`, populated by the scheduled `alert-sweep` timer | | Recommended actions | existing `GET /api/recommendations/proactive`, populated by `staffing-proactive-sync` | ### Not backed, rendered as an explicit empty state | Panel | Why | |---|---| | Interviews | no interview entity exists | | Offers | no offer entity exists | | Placements | no placement entity. `vendor_submissions.status = 'placed'` exists but is keyed by vendor, so a recruiter placement cannot be recorded | | Revenue | no bill rate, pay rate, margin or invoice anywhere in the schema | | Average time to fill | `requirements` has no fill date. Migration 0039 seeds a flat hazard prior and feeds from nothing | | Candidate source breakdown | `candidates.source` is `CHECK (seed, external)`. Job board attribution cannot be expressed | | Activity trend, placement series | the other three series are real. The placement series has no source | ### Two deliberate substitutions The mockup shows an Upcoming Tasks panel and an AI Insight panel. Neither has a data source. Both are replaced with feeds that are real and already accumulating behind no interface today: * Upcoming Tasks becomes the **Alerts** feed from `GET /api/alerts`. * AI Insight becomes **Recommended actions** from `GET /api/recommendations/proactive`. This trades two invented panels for two panels with live scheduled data. ## 6. Backend design New module `internal/modules/recruitmatch`, following the Command Center vertical slice, which is the proven aggregation pattern here. ``` dto.go View plus nested panel types. Every money and percentage value is a string. service.go Service{db, audit, clock}. One entry point: RecruiterDashboard(ctx context.Context, asOf time.Time) (View, error) service_queries.go buildTemps plus SQL panel builders. SQL returns counts only. derive.go pure banding, classification and copy. No ctx, no tx, DB free tests. handler.go Routes(r chi.Router), as_of parsing, error mapping. *_test.go pure tests against hand built View structs. ``` Rules carried over from Command Center because they are load bearing: 1. **One** `RunInTenantTx` for the whole request. 2. **Keep the GUC assertion.** `buildTemps` refuses to aggregate when `app.current_org_id` is unset. On a product serving many clients this is the difference between a bug and a cross tenant breach. 3. TEMP tables prefixed `rm_`, all `ON COMMIT DROP`, so they can never collide with the `cc_` temps. 4. SQL returns counts only. All banding happens in Go, importing `engine.BandGreenMin` and `engine.BandYellowMin` rather than redeclaring thresholds. 5. `as_of` is bound as `$1::date`. No `now()` and no `current_date` anywhere in the module, so a fixed `as_of` produces byte identical output. 6. All arithmetic uses `shopspring/decimal`. Percentages use a helper that guards a zero denominator. 7. Do **not** copy the `upcomingOpportunities` shape from Command Center. It is a real N plus 1. ### Routes ```go r.Route("/recruitmatch", recruitH.Routes) ``` | Method | Path | Capability | Notes | |---|---|---|---| | GET | `/api/recruitmatch/dashboard` | `CapCandidateRead` | optional `as_of` in `2006-01-02` | | GET | `/api/recruitmatch/candidates` | `CapCandidateRead` | shared `matching.ReadRoutes` | | GET | `/api/recruitmatch/requirements` | `CapCandidateRead` | shared `matching.ReadRoutes` | `CapCandidateRead` includes every role down to `viewer`, so the dashboard is visible to all members of an org. Alerts and recommendations stay at their existing flat paths. They are genuinely cross tool and aliasing them a fourth time would add registrations without adding meaning. ### Error mapping Follow the Command Center handler: `tenant.ErrNoTenant` maps to 403, `ErrNoDatabase` maps to 503, anything else maps to 500 with a fixed body that never leaks a wrapped error. ## 7. Frontend design New directory `frontend/src/tools/recruitmatch/` containing `brand.ts`, `api.ts`, `queries.ts` and `RecruiterDashboard.tsx`. * Accent: one new token in the `@theme` block of `src/index.css`, joining screenMATCH teal `#14b8a6` and slateMATCH violet `#8b5cf6`. * `TOOLS` gains a fourth entry with `basePath: '/recruit'` and a `match` covering that prefix. * Query keys follow `['recruitmatch', resource, ...params]`. * Every decimal arrives as a string and is converted with `num()` from `lib/format.ts`. * Reuse `Panel`, `Stat`, `Bar`, `Badge`, `Gauge` and `ToolHeader`. Do not invent card markup. * Dynamic colors are `var(--color-*)` inline. Static colors are Tailwind classes. * `tabular-nums` on every number. * The screen owns no outer padding. `AppShell` main already applies it. **Handle the error state.** `CommandCenter.tsx` destructures without `isError`, so a failed fetch leaves its loading panel on screen forever. Copy the pattern in `screens/Requirements.tsx` instead. Charts: recharts is already a dependency but is used in exactly one file. The dominant idiom is hand built SVG. Use `Bar` and `Gauge` for meters and reach for recharts only for the multi series activity trend, matching the styling in `components/WorkforceDNA.tsx`. ## 8. Org branding, platform scope This is platform work, not recruitMATCH work. All four tools inherit it. **Migration 0046** adds to `organizations`: `display_name text`, `logo bytea`, `logo_mime text`, `accent_color text`. RLS policy `org_self_select` already scopes reads to the GUC. The existing column grant is deliberately narrow at `UPDATE (name)`, so widen it to exactly the new columns and leave `status` locked so a tenant cannot un suspend itself. **Extend `SessionInfo`** with the org display name and a flag indicating a logo exists. `AuthProvider` already fetches `/api/auth/session` once on mount and `AppShell` already consumes the principal, so branding arrives with no extra request and no new fetch plumbing. **Serve the logo** from `GET /api/org/logo`, session gated, returning the current org bytes with the stored content type. Writes are gated by `CapOrgSettings`, which is owner only. Upload UI goes in `screens/Settings.tsx` beside the existing team panel. **`AppShell` renders** the org mark in the sidebar header, falling back to the missionMATCH mark when an org has not uploaded one, which is every org on its first day. Storage decision: keep logo bytes in Postgres for v1. Logos are tens of kilobytes, one row per tenant, and the bytes inherit RLS isolation automatically. Wiring object storage into the API would mean building the AWS adapter that does not exist yet, for a few kilobytes per client. The `Storer` interface in `internal/platform/storage` remains available if this ever outgrows a column. ## 9. Multi tenancy requirements Non negotiable, and the reason for the GUC assertion above. 1. No org identifier ever appears in a handler signature. Org is ambient in context and reaches Postgres only through `RunInTenantTx`. 2. No FG string, FG asset, or FG conditional anywhere in the codebase. 3. Every panel must render correctly for an org with zero rows. A new client sees zeros and named empty states, never a crash, a NaN, or a fabricated figure. 4. Recruiter attribution derives from `users` in the tenant, so it works for a one recruiter shop and a twenty recruiter shop without configuration. ## 10. Testing * Pure unit tests for `derive.go` against hand built `View` structs, no database, mirroring `commandcenter/executive_test.go`. * A test asserting a zero data org produces a complete `View` with zeros and no panic. This is the new client case and it is the one most likely to regress. * An integration test under the `integration` build tag proving two orgs see disjoint dashboards. Note that the integration suite exits 0 silently when `MM_APP_DSN` and `MM_MIGRATOR_DSN` are unset, so a green run does not prove it executed. Confirm the DSNs are exported. * `make build-test-gates` must pass, including gofmt, vet, the backdoor check, the FIPS check and the frontend lint and build. ## 11. Deployment notes Production is behind this branch and that must be reconciled before shipping. * Live goose version is 42. This branch is at 45. Migrations 0043, 0044 and 0045 are not applied, so `outcome_events`, `aioe_weight_profiles` and `term_expansions` do not exist in production. * The `mm-api` binary and the frontend bundle in production were both built on July 1. * Deployment is manual. CI runs gates and never deploys. Sequence: apply 0043 through 0046, rebuild and deploy the binary, rebuild and deploy the frontend. ## 12. Open questions 1. The tool name is recruitMATCH and the base path is `/recruit`. Confirm both. 2. Accent color for the fourth tool. 3. Whether `accent_color` in org branding should actually theme the shell, or only be stored for later. Storing it is cheap. Applying it interacts with the fixed tool accents and needs a rule. 4. The activity trend window. The mockup shows seven days. ## 13. Related findings, out of scope but recorded Surfaced during the scan. Each deserves its own ticket. 1. `docs/MODEL_CARD.md` documents `v1.1.0` while the engine is `v1.4.0`, missing both band caps and the compartment check. For a product whose central claim is auditability this is the highest severity item found. 2. `ontology_crosswalk` is queried by a live code path but has no writer anywhere, so `SkillGapBridge` silently under returns. 3. `internal/platform/federated` and `dp_budget_ledger` have zero importers. Dead code. 4. `/api/opportunities`, `/api/awards` and `/api/wages` have no capability gate at all. 5. `/metrics` is unauthenticated and outside the rate limiter. 6. Several buttons render as working features with no handler attached, in `CommandCenter.tsx` and `CandidateEvaluation.tsx`. 7. `users.last_login_at` is never written, though `login_attempts` records the successes.

Search equations, builds, manuscript text, and recovered sources.