PowerFill A54 Fix — Greg-Demo Readiness
Date: 2026-04-19
Agent: PSSaaS Systems Architect (Opus 4.7 High Thinking)
Scope: Mid-stream targeted fix between Phase 8 W1 (complete) and Phase 8 W2 / Phase 9 (queued). Closes the long-deferred A54 PRIMARY KEY violation in psp_powerfill_pool_guide so PSSaaS can demonstrate end-to-end Complete PowerFill runs against PS_DemoData. Two surgical fixes inside the legacy proc body, both within ADR-021's amended new §Narrow Bug-Fix Carve-Out. Sentinel bumped to phase-8-superset-ready-a54-fixed.
Why
A54 has been deferred since Phase 6c (2026-04-19 morning). Every PSSaaS PowerFill run against PS_DemoData since 6c terminated as Failed at Step 5 (pool_guide) due to a latent legacy proc bug (SqlException 2627: Violation of PRIMARY KEY constraint 'PK__##cte_po__...'). 6 of the 8 Phase 8 W1 Superset dashboards were therefore empty.
The PO has decided that Greg himself is the next consumer of PSSaaS PowerFill output, in a Greg-demo context. Greg is the canonical domain authority who would normally consult on this kind of legacy-proc bug. Per ADR-021 §Narrow Bug-Fix Carve-Out clause (d) (added in this commit), the demo IS the consultation. The empirical evidence + this fix itself become the consultation material Greg reviews live.
Three forcing functions:
- Greg needs to see PowerFill produce real allocation output end-to-end, not empty dashboards
- The "Bug as Feature" demo narrative builds credibility with Greg (a domain expert who values rigor)
- A54 closure is the prerequisite for downstream W2 (React UI) + Phase 9 (Parallel Validation) — both compound in value when there's real allocation output to surface / validate
What Was Done
Code (1 file edit + 1 sentinel bump)
src/backend/PowerSeller.SaaS.Modules.PowerFill/Sql/009_CreatePoolGuideProcedure.sql(+30 LOC + 16 comment LOC) — Two surgical fixes insidepsp_powerfill_pool_guideproc body:##cte_posting_set_1300PK extension — addedpa_key NUMERIC(1, 0) NOT NULLcolumn + extended PK from(trade_id, loan_id)to(trade_id, loan_id, pa_key); updated INSERT column list; mirrored_1400's author-intendedpa_keyderivation in the SELECT (identical CASE expression onpool_action's first letter).pt13INNER JOIN qualifier extension — addedAND ps1200_13.settlement_date = pt13.settlement_date. The inline subquery already selected(trade_id, settlement_date)and GROUPed by both — but the legacy JOIN qualified ontrade_idalone, producing fan-out when a trade has multiple distinct settlement_dates across its loans.
src/backend/PowerSeller.SaaS.Modules.PowerFill/PowerFillModule.cs— sentinel bumped fromphase-8-superset-readytophase-8-superset-ready-a54-fixed.
Documentation (architecturally load-bearing)
docs-site/docs/adr/adr-021-powerfill-port-strategy.md— Status amended to "Proposed (2026-04-16) — Amended 2026-04-19"; new §"Narrow Bug-Fix Carve-Out" section with 4 acceptance criteria + canonical first instance (A54) write-up + "What this is NOT" guardrails + "Forward-only" note. Revision Triggers section gains 2 new bullets.docs-site/docs/specs/powerfill-assumptions-log.md— A54 marked RESOLVED with full empirical-resolution arc (5-run dup-key signature evolution table); A56 marked RESOLVED as A54-side-effect closure; A65 added (multi-pa_key Switching + settlement-date variance are two distinct triggers; both fire on PS_DemoData; PS608 customer DB behavior unknown — Phase 9 probe needed); A66 added (UE clear-and-rebuild-empty on syn-trade-empty datasets — surfaced behavior, not a bug; A58 framing carries a sub-clarification).docs-site/docs/specs/powerfill-engine.md— Phase 6 carry-over checklist item marked DONE; Phase 7 PoC validation note appended with A54 fix update; Phased Implementation table Phase 8 row updated with A54 fix sub-entry; BR-9 amended with A66 note about UE's end-to-end-rebuild behavior.docs-site/docs/superset/powerfill-dashboards.md— A54/A56 carry-over rendering section completely rewritten with pre-fix vs post-fix row count table; new canonical proof-of-life identified (Hub Dashboard 1 instead of Cash Trade Slotting Dashboard 8).docs-site/docs/handoffs/powerfill-a54-fix-greg-demo-readiness.md(NEW) — Greg-demo readiness completion report with full empirical-resolution arc, "Bug as Feature" demo narrative for PO copy-paste, post-fix dashboard row counts, and Counterfactual Retro.docs-site/docs/handoffs/pssaas-session-handoff.md(amended) — sentinel + backlog row updates.- This devlog entry.
PoC verification (DV Gate all 3 arms)
- Arm (a) sentinel:
curl /api/powerfill/status→{"module":"PowerFill","status":"phase-8-superset-ready-a54-fixed"}✓ - Arm (c) DB probe:
psp_powerfill_pool_guidedeployed cleanly to PS_DemoData (modify_date = 2026-04-19 20:00:56); 8/8 dashboard SQL queries execute clean (no SqlException); empirical fan-out source confirmed via mid-run probe ofpfill_powerfill_guidefor trade36177868(3 distinct settlement_dates) - Arm (b) live API run: Run
43e8f148-3d1f-4c40-9f76-a43f84efdfb9completed end-to-end in 30 seconds withStatus=Complete,pool_guide_count: 515,uestep succeeded, 12 events inpfill_powerfill_log. Reproducible (run7c9dfe50-...31s).
Tests
- 233/233 pass (32 BestEx + 200 PowerFill + 1 Api + 6 skipped) — Phase 7/8 W1 baseline preserved exactly (no test regressions; no new tests needed since this is a SQL-side fix to an existing proc body).
Why Specifically (Reasoning)
The fix shape evolution (Diagnostic-First Rule application)
The kickoff hypothesized a single fix: extend _1300's 2-col PK to 3 cols (mirror _1400). I planned around that hypothesis — wrong. Empirical PoC after deploying the PK-only fix produced a NEW dup-key shape: (36177868, 3385000026, 3) — three identical rows with the same pa_key=3 (Remaining), not different pool_action values.
Diagnostic-First Rule kicked in. Mid-run probe of pfill_powerfill_guide revealed trade 36177868 had 4 rows with 3 distinct settlement_dates across its loans. The actual fan-out source was the pt13 INNER JOIN qualifying on trade_id only despite the inline subquery selecting (trade_id, settlement_date).
Second surgical fix added (1-line JOIN qualifier extension) — also mirroring legacy author intent (the settlement_date column was selected for pt13 with no other downstream use; clear oversight). Re-deployed and re-ran. Run completed end-to-end.
Why the carve-out (ADR-021 amendment)
The kickoff explicitly directed: "Modifying a legacy proc body deviates from ADR-021's verbatim-port discipline; it needs a documented exception." I chose to formalize the exception as a §Narrow Bug-Fix Carve-Out within ADR-021 (not a sibling ADR-026), with 4 acceptance criteria:
- (a) Empirically reproducible
- (b) Mirrors legacy author intent (as evidenced by adjacent code)
- (c) Documented in assumption log with a Phase 9 parallel-validation hook
- (d) Tom/Greg consultation recorded (PO+recipient signature OR demo-as-consultation note)
The A54 fix satisfies all 4. Demo-as-consultation per (d) is PO-confirmed.
Why two ANDON-cord pulls
This session had three Reviewable Chunks checkpoints, all PO-confirmed:
- Plan-presentation (start of session) — Three-layer Gate findings + Alternatives-First framing + sentinel decision. PO confirmed Option A + sentinel bump.
- ANDON #1: Diagnostic-First reveal — Post-PK-only-fix PoC failure prompted STOP-and-report. Presented the empirical fan-out finding + refined fix shape. PO confirmed Option A' (PK + JOIN qualifier).
- ANDON #2: A66 surfaced finding — Post-both-fix Complete-but-empty-tables outcome prompted STOP-and-report. Presented A66 framing + 2 paths forward (demo as-is vs investigate UE preconditions). PO confirmed Path (i).
Pattern observation: Reviewable Chunks at sub-checkpoint boundaries (not just workstream boundaries) IS the right granularity for empirical-discovery sessions. Without these 3 ANDON-cord pulls I would have shipped the wrong fix shape OR continued with an in-scope-expansion the PO didn't authorize.
Sentinel bump rationale
phase-8-superset-ready → phase-8-superset-ready-a54-fixed.
- Distinguishes pre-fix vs post-fix Phase 8 dashboard state
- Makes the fix observable from
/api/powerfill/statuswithout re-deriving from commit log - Doesn't reset Phase 8 progress (W1 deliverable unchanged; this is runtime block remediation)
- Architect-chosen suffix-style preserves phase tracking AND surfaces the fix
- PO confirmed at planning checkpoint
What's Next
| Next | Notes |
|---|---|
| Pre-push docs-build check | docker build -f docs-site/Dockerfile.prod docs-site — mandatory per Phase 6e/7/8-W1 lesson before any commit involving new docs-site/docs/** files |
| Atomic commits | Architect commits; PO pushes per .cursorrules |
| Phase 8 W2 (React UI) | Ready to start as planned per Backlog row #21d / W2 kickoff. Runtime block is closed; W2 can build on a working end-to-end pipeline. |
| Phase 9 (Parallel Validation Harness) | Can now use the pre-fix vs post-fix dup-key signature evolution as a known-good test fixture. Harness's "data-shape compatibility" pre-flight (per A65) is now well-specified. A66's UE syn-trade question becomes a Phase 9 / Greg-consultation followup. |
| Greg-demo readiness | The completion report at docs-site/docs/handoffs/powerfill-a54-fix-greg-demo-readiness.md is the consultation material Greg reviews live. Includes a copy-paste-ready "Bug as Feature" demo narrative for the PO. |
Process Discipline Banking
-
Backlog re-read pass at planning time is now 2-session corroborated (F-8-BR-1 in Phase 8 W1; F-A54-6 in this session) — eligible for canonical promotion at next process-discipline revision per the existing Phase 7 CR #1 nomination.
-
Diagnostic-First Rule for carve-out fix sizing. Banking for future kickoffs: include an empirical fan-out-source verification step BEFORE sizing the fix. Cost ~10 min; upside avoids a wrong-fix iteration. The kickoff's "STOP and report" pre-emptive instruction was load-bearing even though the specific predicted scenario didn't match.
-
Alternatives-First Gate option-space framing. Banking: when planning a carve-out, the Alternatives-First options should include the diagnosis hypotheses as well as the fix shape options. The empirical evidence may change the option space itself, not just the chosen option.
-
Suffix-style sentinel bumps for runtime-block remediation fixes.
phase-8-superset-ready-a54-fixedis the canonical pattern: phase progress preserved, fix observable from/status, no commit-log re-derivation needed. -
A58-style "preservation across BR-9" framings need explicit "if this side-effect goes away" sections. A58's preservation was a de-facto property of A56 short-circuiting UE; with A54+A56 closed and UE running end-to-end, UE itself supersedes those tables. Banking: framings that depend on side-effects of other open issues should state their fragility upfront.
-
Demo-as-consultation IS a viable ADR-021 §Narrow Bug-Fix Carve-Out clause (d) disposition when the immediate downstream consumer of the fix is the would-be-consulted domain authority. Reversibility is the safety net: if Greg rejects the carve-out, the fix is
git revert-able. -
Sub-phase calendar time: ~1 Architect-session. Consistent with 6a-6e + Phase 7/8 W1 velocity at ~2x complexity (2 fixes + 4 assumption-log entries + ADR amendment + new completion report). Empirical Diagnostic-First detour + ANDON-cord protocol added ~40 min total but produced a measurably better fix.
Files Touched (Summary)
src/backend/PowerSeller.SaaS.Modules.PowerFill/Sql/009_CreatePoolGuideProcedure.sql— +30 LOC + 16 comment LOC; 2 surgical fixes insidepsp_powerfill_pool_guidesrc/backend/PowerSeller.SaaS.Modules.PowerFill/PowerFillModule.cs— sentinel bump (1 line)docs-site/docs/adr/adr-021-powerfill-port-strategy.md— Status note + new §Narrow Bug-Fix Carve-Out + Revision Triggersdocs-site/docs/specs/powerfill-assumptions-log.md— A54 RESOLVED + A56 RESOLVED + A65 NEW + A66 NEWdocs-site/docs/specs/powerfill-engine.md— Phase 6 carry-over checklist + Phase 7 PoC note + Phased Implementation Phase 8 row + BR-9docs-site/docs/superset/powerfill-dashboards.md— A54/A56 carry-over rendering section rewritedocs-site/docs/handoffs/powerfill-a54-fix-greg-demo-readiness.md— NEW completion reportdocs-site/docs/handoffs/pssaas-session-handoff.md— sentinel + backlog row updatesdocs-site/docs/devlog/2026-04-19f-a54-fix.md— this entry