# GraphRAG Phase 1: Market Plane Completion Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development. Steps use checkbox (`- [ ]`) syntax. **Goal:** Complete the public market plane so the brain is an expert at the capture targeting half of the job: G9 the recompete lifecycle and foresight graph, G7 the agency buying pattern graph, and G2 the public vendor co occurrence and teaming field, all views over the SAME global edge substrate G1 established, all public federal data, no person and no tenant table. **Architecture:** More named, parameterized QueryIntents in the existing CLOSED Registry, mostly aggregates and a small number of fixed depth or guarded recursive traversals over `usaspending_awards`, `opportunity_records`, `sam_opportunities`, `federal_mission_tags`, `award_incumbent_roles`, `mission_areas`. Routed through the Phase 0 RouteGraph surface, gated by ForbidEEO and the faithfulness gate. The frozen engine stays the only scorer. **Tech Stack:** Go 1.26, pgx/v5, sqlc, shopspring/decimal, FIPS, no Python. **Branch:** `graphrag-foundation` (continues Phase 0; the guard fix and the market table allowlist additions are already in place). Migration head 0032; Phase 1 adds NO migration (reuses existing tables). Validate on M4 `brain_staging` (it has roughly 1899 opportunity_records and 522 awards). --- ## Hard invariants (every task) 1. **Public data only; defer the tenant intersection.** Phase 1 intents read ONLY the global public tables (`usaspending_awards`, `opportunity_records`, `sam_opportunities`, `federal_mission_tags`, `award_incumbent_roles`, `mission_areas`). They do NOT read `requirement_mission_tags`, `candidate_mission_tags`, `candidates`, or any tenant table. The questions that join the tenant bench or requirements (which partner closes OUR gap, the bench readiness leg of a recompete) are explicitly DEFERRED to Phase 2 and G6, when the person plane comes online under RLS. This keeps Phase 1 with no tenant privacy surface. 2. **The honesty constraints (carry G1's discipline forward).** `usaspending_awards` self joins and segment keys use ONLY `(agency, naics)` (awards carry no psc/set_aside/pop_state); `opportunity_records` carries the full `(agency, naics, psc, set_aside, pop_state)` tuple for the finer slices and any psc/set_aside/pop_state edge is labeled opportunity_records sourced. Every incumbent or competitor edge is labeled prime-of-record (the staffing sub is a later acquisition). The agency hierarchy rollup uses a `full_parent_path` string split and GROUP BY, NOT a recursive CTE. 3. **The frozen engine is the only scorer.** No intent computes a match score or band. Market aggregates (dollar share, concentration, price bands) are computed in `shopspring/decimal` and are explicitly NOT match scores. `internal/modules/matching/engine` is untouched; golden/frozen tests stay byte identical. 4. **Closed Registry, deterministic, EEO unreachable.** Every traversal is a FIXED named QueryIntent, asserted by `ValidateRegistry`, re guarded by `GuardReadOnlyQuery`. Recursive CTEs (only where genuinely needed) carry a depth cap, a cycle guard (visited path + `UNION`), a unique id tiebreaker, and a base term `::numeric` cast so the recursive and non recursive terms share the exact type (the live 42804 lesson from G3). Every list ORDER BY ends with a unique id tiebreaker for byte stable output. No intent references the eeo schema; the `safety_test.go` EEO walk covers the new files; the audit carries counts only. 5. **Decimal not float.** No `float64` or `::float8`/`::real`/`::double precision` touches a dollar, a share, a concentration, or a price band surfaced as evidence; scan numeric into `shopspring/decimal`. 6. Standard rules: no Python; no dashes in written output except URLs, CLI flags, import paths, identifiers; both cloud core ANSI SQL plus the existing pgvector, no Neptune/Cosmos/cloud specific graph primitive; FIPS. --- ## File Structure - Create `internal/modules/ai/graph_lifecycle.go` (+ `_test.go`): G9 intents. - Create `internal/modules/ai/graph_agency.go` (+ `_test.go`): G7 intents. - Create `internal/modules/ai/graph_teaming.go` (+ `_test.go`): G2 intents. - Modify `internal/modules/ai/tag.go`: register the new intents. - Modify `internal/modules/ai/graph_route.go`: add the new intents to `graphIntents` and a few conservative NL classifier keywords. - Integration tests under `internal/integration/`. --- ## Phase 1.A: G9 Recompete Lifecycle and Foresight graph (public) ### Task A1: the lifecycle and recompete intents - [ ] Author FIXED QueryIntents over `opportunity_records` (record_kind forecast / active_solicitation / contract_award) + `usaspending_awards`, public only: - `procurement_succession`: given an active_solicitation opportunity id, walk the `dedupe_key` succession backward within the `(agency, naics, psc, pop_state, set_aside)` segment to the predecessor contract_award, returning whether it is new or a recompete, the prior award_amount (decimal) and the prior prime UEI (prime-of-record), with the cited record ids. Prefer a FIXED depth set of LEFT JOINs (forecast to solicitation to award is at most three phases) over recursion; if recursion is used, apply the full G3 discipline. - `recompete_clock`: awards/opportunities whose `pop_end_date` falls in a forward window (param months), the takeaway pipeline, ordered by pop_end_date with an id tiebreaker, the incumbent prime named (prime-of-record). - `forecast_runway`: forecasts (record_kind forecast) landing in the next N months by `estimated_award_date`, the lead time view, with the mission tag (federal_mission_tags) and pop_state, ordered by estimated_award_date with an id tiebreaker. - [ ] Register in Registry + add to `graphIntents`. All dollars decimal. Tests: succession terminates, recompete window is correct, byte stable; a live integration test on brain_staging returns a real pipeline. Commit. --- ## Phase 1.B: G7 Agency Buying Pattern graph (public) ### Task B1: the buying pattern intents - [ ] Author FIXED aggregate QueryIntents (no recursion; agency rollup via `split_part(full_parent_path, '.', 1)` string split): - `agency_buying_pattern_detail`: for an agency or department prefix and optional NAICS, the vehicle mix, set_aside mix, NAICS/PSC distribution, and award_amount band (average and median, decimal) from `opportunity_records` (the full tuple) plus the `usaspending_awards` dollar totals keyed on `(agency, naics)`, the where to position view, each row with the contributing record count as evidence. - `agency_vendor_concentration`: for an agency and NAICS, the recency weighted recipient_uei dollar share (reuse the G1 competitor pattern), the wired vs open read. - `agency_spend_trend`: an agency's obligation trend by NAICS/PSC over fiscal periods (group by year), is spend concentrating or fragmenting. - [ ] Register in Registry + `graphIntents`. Decimal for all dollar/share/band math. Tests + a live integration test on brain_staging. Commit. --- ## Phase 1.C: G2 Public Vendor Co occurrence and Teaming Field (public) ### Task C1: the co occurrence intents - [ ] Author FIXED QueryIntents over the PUBLIC vendor edges only (defer the tenant gap intersection to Phase 2 and G6): - `vendor_cooccurrence_field`: for a `(agency, naics)` segment (awards) or the fuller tuple from opportunity_records, the set of recipient/incumbent UEIs that co occur, so the likely teaming and competitor field is visible, ordered by dollar concentration with a UEI tiebreaker, each edge labeled with its source (awards keyed agency+naics, opportunity_records for the finer tuple). - `partner_or_competitor_ratio`: for a target partner UEI and the tenant self tag `is_galapagos`, the count of markets where the partner co occurs with the tenant's own past performance (same side context) versus markets where the partner holds the prime in the tenant core NAICS, expressed as a team vs compete ratio from public co occurrence alone. NO tenant table; uses only `is_galapagos` on the public awards. - `recompete_teaming_field`: opportunities with `pop_end_date` in a forward window to the incumbent UEI to that incumbent's co occurring vendor neighborhood, the likely teaming field before the RFP drops. - [ ] Honesty: awards co occurrence keys on `(agency, naics)` only; the five tuple precision is opportunity_records sourced and labeled; every vendor edge is prime-of-record. Register in Registry + `graphIntents`. Tests (static: awards reference no phantom column; the ratio uses only is_galapagos, no tenant table) + a live integration test. Commit. --- ## Phase 1.D: wire RouteGraph and review ### Task D1: routing + classifier keywords - [ ] Add the new intent names to `graphIntents` (if not already done per layer) and a few CONSERVATIVE NL classifier keywords (recompete, lifecycle, succession, agency buys, buying pattern, who teams, teaming field, co occur) that route to the matched intent only when its required params (an agency, a NAICS, an opportunity id, a window) are unambiguously extractable, else fall back to RAG. Keep the existing routes byte unchanged. - [ ] Tests: each new NL keyword routes correctly with params, falls back without; the structured path narrates gated. Commit. ### Task D2: final review and validation - [ ] Dispatch an adversarial review over the Phase 1 diff (the honesty constraints, EEO unreachable, decimal, closed registry, public only no tenant table, engine untouched, byte stable). Fix findings. - [ ] Re run golden/frozen/safety; full integration suite on brain_staging. NOT merged, NOT deployed. --- ## Self review - Phase 1 is public market data only: it adds no migration, reads no tenant table, and has no person privacy surface beyond the uniform RunInTenantTx posture. The tenant intersection questions each layer can eventually answer are deferred to Phase 2 and G6, where the bench plane comes online under RLS. - The cross cutting guard fix and the market table allowlist additions landed in Phase 0, so Phase 1 is additive intents over a proven substrate. The honesty constraints (awards key only agency+naics, prime-of-record, full_parent_path split) carry G1's discipline forward. The frozen engine is never in a modify list.