First End-to-End PowerFill Preflight Against PS_DemoData
Date: 2026-04-16 evening
Agent: Collaborator (with PO active in Azure Portal for two infrastructure changes)
Outcome: PSSaaS pointed at a real PowerSeller client database (PS_DemoData), executed a full 9-check PowerFill preflight, returned structured diagnostic findings.
This is the first concrete proof-of-concept artifact: code we built actually runs against production-shape customer data, and the responses are interpretable diagnostics rather than implementation errors. Five PS_DemoData live-system findings surfaced and were corrected in lockstep.
Why This Run Mattered
Up to today PowerFill Phase 1-4 had only been validated against a synthetic local SQL Server container (pssaas-db) seeded by infra/sql/init/seed-schema.sql. The seed schema was inferred from the legacy NVO during Phase 2 reverse-engineering. Tests passed against that schema; nothing exercised the assumption that the schema was actually correct.
PoC framing (PO instruction, this evening): "Everything we are doing here is Proof of Concept to help [Tom and Greg] believe that the PowerSeller Desktop App can be migrated to SaaS and likely a piece at a time (i.e., move chunks of core business logic out of the Desktop App and into PSSaaS and have the Desktop App make API calls accordingly)." Reframed priority: not parity validation; demonstrability against real customer data.
What Was Done — Steps A through E
Step A: A30 unblocked (committed earlier as d2bab7b)
PO granted kevin_pssaas_dev the db_ddladmin role on PS_DemoData. Verified empirically (sys.database_role_members + actual CREATE/DROP TABLE + CREATE/DROP PROCEDURE). Backlog items #23 / #25 unblocked.
Step B: Schema validation, pfill_* tables (no commit — read-only)
Compared information_schema.columns for all 17 pfill_* tables in PS_DemoData vs local pssaas-db.
- 17/17 tables present in both
- 255/255 columns identical except for one drift cause repeated 92 times: every string column is
CHAR(N)in our001schema andVARCHAR(N)in PS_DemoData (assumption A29 confirmed at full scope; no other types/lengths/nullability/precision differences) - Operationally non-blocking for PoC — SQL Server implicit conversion handles this
Step C: Deploy 004 + 005 to PS_DemoData (no separate commit — DDL applied to PS_DemoData, not to repo)
Per A32 Deploy Verification Gate (PRINT-in-guard refinement):
PowerFill 004: created table dbo.pfill_preflight_settings
PowerFill 004: inserted default preflight row (id=1)
PowerFill 005: added column dbo.pfill_constraints.rv_rowversion
Three PRINT lines = three DDL operations actually executed (not skipped). Both arms of the gate satisfied (sys catalog presence + DDL execution proof).
Post-deploy checks: pfill_preflight_settings exists with seed row (id=1, min_status='Closed', 2, 60), rv_rowversion ROWVERSION column present.
Step D: PS_DemoData as a tenant in the local API (in this commit)
appsettings.Development.json— addedps-demodatatenant with empty placeholder connection stringdocker-compose.override.yml.example— committed; documents the override pattern for future developersdocker-compose.override.yml— gitignored; per-developer file with the actual SQL MI connection string injected via env var (Tenants__ps-demodata__ConnectionString).gitignore— addeddocker-compose.override.ymlline
The multi-tenant infrastructure built in earlier phases (TenantRegistry, TenantMiddleware, TenantContext) handled this with zero code changes. X-Tenant-Id: ps-demodata header dispatches the entire request to PS_DemoData.
Step E: The truth moment
Three iterations on POST /api/powerfill/preflight with X-Tenant-Id: ps-demodata and an empty body:
Iteration 1 — Invalid column name 'loan_status'. Invalid column name 'stage_order'.
pscat_loan_stages actual columns are status_code and stage_rank in both PS_DemoData and PS608. Phase 2 reverse-engineering invented the column names. Surfaced as Truth-Rot finding T1.
Iteration 2 — Incorrect syntax near '$'
PS_DemoData was at SQL Server compatibility level 120 (SQL 2014). EF Core 8 generates OPENJSON(@param) WITH (... '$') for Contains(parameterList) queries, requiring compat level ≥130 (SQL 2016). Compat level mismatch is a Tier-1 Ghost Deploy adjacent failure mode — code that works in dev (SQL 2022, compat 160) silently fails against an older customer DB. Surfaced as live-system finding T5.
PO bumped PS_DemoData to compat level 150 in Azure Portal.
Iteration 3 — Success.
{
"tenant_id": "ps-demodata",
"checked_at": "2026-04-17T01:15:13Z",
"is_ready": false,
"summary": {"error_count": 2, "warning_count": 1, "info_count": 0},
"checks": [
{"code": "NO_CONSTRAINTS", "severity": 2, "message": "pfill_constraints is empty; PowerFill requires at least one constraint before a run."},
{"code": "PRICES_STALE", "severity": 1, "message": "Latest BestEx prices are older than 2 day(s).", "context": {"most_recent_market_date": "2023-04-26", "threshold_days": 2}},
{"code": "PIPELINE_EMPTY", "severity": 2, "message": "No loans in pipeline at or above min_status 'Closed'.", "context": {"min_status": "Closed"}}
]
}
6 of 9 checks ran without error. The 3 failing checks returned structured findings about real properties of the demo client's data:
- The client never configured any PowerFill constraints —
pfill_constraintsis empty - Prices are 3 years stale (most recent market date
2023-04-26); confirms PS_DemoData is a 2023-vintage snapshot - No loans in any "Closed" status — the client used
In Process / In Underwriting / Approved / Docs Out(perpscat_loan_stagesdata), with no "Closed" stage at all
These are not bugs in PSSaaS. They are findings about the customer's data that PSSaaS surfaced via the preflight system. That's exactly what the system is designed to do.
Truth-Rot Findings (Primary-Source Verification Gate, live-DB arm)
Five distinct findings; all corrected in this commit. Comparison was against both PS_DemoData (4-yr-old client) AND PS608 (current WTPO) — schemas agree on all five points.
T1 — pscat_loan_stages column names
Phase 2 entity used loan_status and stage_order. Live schema: status_code and stage_rank. Same semantics, different names. Disposition: corrected in LoanStage.cs and infra/sql/init/seed-schema.sql. C# property names retained for code readability; only [Column] attributes changed.
T2 — pscat_pools.pool_status does not exist
Phase 2 entity declared a pool_status field. Neither client DB has this column on pscat_pools (verified). The pfillv_existng_pool_disposition view computes a pool_status column inside its definition, which is unaffected. Disposition: removed from Pool.cs and seed-schema. No code consumed Pool.PoolStatus.
T3 — pscat_pair_offs shape
Phase 2 entity modeled this as an event table with pair_off_id PK, trade_id, pair_off_date, pair_off_amount. Live schema: relationship table, composite PK (trade_id, designated_trade_id), columns designated_amount, designated_percent. Phase 2 invented all 4 of its column names. Disposition: rewrote PairOff.cs to match live schema; added composite-key configuration in UpstreamEntityConfiguration.cs; updated seed-schema. PairOff was registered in DbContext but never queried, so no consumer changes required.
T4 — rmcat_ra_history_trades PK shape
Phase 2 used composite (analysis_id, trade_id). Live schema: (analysis_id, profile_name, trade_id). Also analysis_id is int in both client DBs, not numeric(10,0). Disposition: added ProfileName to TradeAnalysisHistory.cs; updated UpstreamEntityConfigurationTradeAnalysisHistoryConfiguration to the 3-column key; changed analysis_id type. Seed-schema updated.
T5 — PS_DemoData compatibility level was 120
Independent of entity drift. EF Core 8 generates OPENJSON for parameterized Contains queries, requiring compat ≥130. Local pssaas-db is at compat level 160 (SQL 2022 default), so tests passed. PS_DemoData was at 120 — same query failed. Process discipline observation: integration tests against a local container at higher compat level cannot detect this class of issue. Phase 9 (parity validation) plan should explicitly include compatibility-level matching against the customer DB. Disposition: PO bumped PS_DemoData to 150 in Azure Portal.
PS608 Cross-Check Confirms PS_DemoData Is Canonical
Mid-investigation question: PS_DemoData is a 4-year-old client snapshot; does it accurately reflect current customer schema?
Probed PS608 (current WTPO production database) for the 5 tables involved in the drift findings. Result: PS608 and PS_DemoData are identical on pscat_loan_stages, pscat_pools, pscat_pair_offs, and rmcat_ra_history_trades. PS608 has 2 trailing columns missing on pscat_trades that PS_DemoData has (payup_actual, payup_instrument) — neither referenced by our entities.
Verdict: schema is remarkably stable across 4 years. PS_DemoData IS the canonical PoC reference for these tables. Aligning entities to PS_DemoData is also aligning to current customer reality. ADR-006 (schema preservation) holds — the schema we're preserving against is the right one.
Verification
dotnet build: 0 warnings, 0 errorsdotnet test: 87/87 pass (32 BestEx + 1 Api + 50 PowerFill unit + 4 integration whenPFILL_TEST_SQLSERVERset; 4 skipped in dev container without env var)- Local
pssaas-dbrebuilt with new seed-schema; tests still pass - PS_DemoData preflight returns structured 200 OK with 3 contextual findings about real demo data
Counterfactual Retro
If I were starting over knowing what I know now, what would I do differently?
-
Do live-DB column probing as part of Phase 2 reverse-engineering itself, not as a Phase-N+ gate. The Primary-Source Verification Gate as defined in v3 says "verify against live systems where accessible." Phase 2 was blocked from PS_DemoData by the perms issue (A30, then unresolved). The lesson is blocked is not done. When the live arm is unavailable, the work item should be tagged "incomplete pending live-DB probe" and held visible — not silently progressed past. Process discipline candidate: add an "incomplete-but-progressed" disposition to the Gate Output Action format, or a separate "blocked-arm-of-gate" tag. Worth surfacing if a similar pattern recurs.
-
Compatibility-level matching should be explicit in test design. Local containers default to the latest SQL Server compat level. Customer DBs frequently run on older levels. Integration tests at compat 160 give false confidence about what works at compat 120. Practice candidate (or A26 amendment): the test container should match the lowest customer-supported compat level, not the highest. Worth a Phase 9 hardening spec line.
-
A live-DB probe in 30 minutes today produced more value than 2-3 days of Phase 2-4 reverse-engineering for these specific findings. The PoC framing makes "do less reverse-engineering, do more live probing" an obvious shift in approach. Future phases (5+) should default-probe before assuming.
What's Live Now
- Local API at
http://pssaas.powerseller.local/api/powerfill/preflightacceptsX-Tenant-Id: ps-demodataand returns structured diagnostic JSON pfill_preflight_settingsexists in PS_DemoData with default valuespfill_constraints.rv_rowversioncolumn added to PS_DemoData (concurrency token will work when Phase 4 CRUD is exercised against PS_DemoData)- 4 entity drifts corrected
- Seed-schema aligned with PS_DemoData / PS608 reality
- Multi-tenant API tested end-to-end with two tenants (default →
pssaas-db,ps-demodata→ SQL MI)
What's Next (Tomorrow's Decision)
Demo working against PS_DemoData. Three legitimate next priorities:
- Configure pfill_constraints in PS_DemoData so preflight can return
is_ready: true— that's an even stronger demo - Phase 5 (carry-cost calculator) dispatch — original next-phase work
- Connector Plugin spec — the architecture story for "Desktop App calls PSSaaS API"
PO call.