PowerFill Phase 8.5 — Completion Report (Ecosystem Auth + Embedded Superset SDK)
Author: PSSaaS Systems Architect
Date: 2026-04-20
Status: Status update 2026-04-20 (post-PSX-Infra-response): PSX Infra delivered all 7 asks (response at 2026-04-20-psx-keycloak-pssaas-app-client-response; PSX commit 64dbec8); 5-item Superset embedding pre-flight ALL PASS including PUBLIC_ROLE_LIKE half-day-debug-trap verified at DB level; PSSaaS-side LOAD-BEARING fix applied (realm pss-platform → psx-staging) + Superset Secret reference rename + in-cluster URL switch; bilateral cross-boundary cutover verification recipe ready to execute post-PO-push + PO-kubectl apply -f services.yaml. Original status (preserved as historical reference): All 4 workstreams code-complete on the PSSaaS side; sentinel phase-8-5-ecosystem-ready; 250 .NET tests pass + 6 skipped (was 233 pre-Phase-8.5; +17 net-new); frontend npm run build clean (272.69KB JS gzipped; SDK lazy-loaded in separate lib-Cb7LCDYX.js chunk); pre-push docs-build check GREEN.
Sentinel: phase-8-5-ecosystem-ready (drops the -a54-fixed historical sub-suffix per Phase 8.5 plan §"Cross-cutting"; A54 closure is now historical, not gating)
Companion docs:
- Phase 8.5 kickoff:
powerfill-phase-8-5-kickoff - Phase 8.5 W1 checkpoint report (the explicit Reviewable-Chunks artifact at the W1 chunk boundary):
powerfill-phase-8-5-w1-checkpoint - ADR-027 (Accepted at Phase 8.5 ship; Proposed since 2026-04-19):
adr-027-superset-embedding-strategy - ADR-029 (Accepted at Phase 8.5 W4 ship):
adr-029-pssaas-tenant-identity-strategy - 2026-04-19 PSX Collab embedding-pattern relay (load-bearing primary source for the framing):
2026-04-19-psx-superset-embedding-relay - 2026-04-20 PSSaaS Keycloak
pssaas-appclient request (the open cross-project-relay):2026-04-20-pssaas-keycloak-pssaas-app-client-request - A68 (PARTIALLY RESOLVED):
powerfill-assumptions-log §A68
TL;DR
Phase 8.5 ships the last demo-blocker before Greg-demo readiness: PSSaaS now joins the ecosystem auth boundary via oauth2-proxy + Keycloak (W1) AND replaces W2's anchor-link "View in Superset" with @superset-ui/embedded-sdk <EmbeddedDashboard> (W2) AND ships the .NET 8 SupersetGuestTokenClient doing the 3-step Superset handshake (W3) AND lands the A68 long-term decoupling code shape (W4).
Per the PO-confirmed Reviewable Chunks shape (Phase 8.5 plan §"Reviewable Chunks shape (PO-confirmed)"): W1 shipped first as an atomic commit batch (4c3b921) with an explicit plan-stage Architect Report at the W1 checkpoint. Per the user-confirmed single-pass directive at execution time, W2-4 then dispatched in the same Architect-session rather than waiting for the inter-session PSX Infra collaboration; this completion report consolidates both chunks into one final artifact.
The Reviewable Chunks intent IS preserved: the W1 checkpoint report at powerfill-phase-8-5-w1-checkpoint remains the artifact-of-record for the W1 chunk; PO and PSX Infra collaboration on the Keycloak pssaas-app client + 5-item Superset embedding pre-flight verification still need to land; but the W2-4 implementation surface is committed alongside W1 rather than waiting on it.
The cross-project-relay request to PSX Infra at 2026-04-20-pssaas-keycloak-pssaas-app-client-request is the open ask. Until it lands, the bilateral cross-boundary cutover verification recipe is partial — the PSSaaS-side artifact-level checks are GREEN (build, lint, typecheck, test, manifest correctness); the PSX-Infra-side runtime checks are NOT MEASURED HERE (cell explicit per practice #13). Per A69 honesty pattern, this completion report does NOT claim runtime parity until the smoke-test runs.
Sub-phase calendar time: ~1 Architect-session. Consistent with 6a-6e + Phase 7 + Phase 8 W1 + Phase 8 W2 + A54-fix + Phase 9 velocity at ~1.5x complexity (4 workstreams in one session). Banking the pattern: 4-workstream phases with high inheritance density (ADR-027 + cross-project-relays archive + W2 React shape stable from prior phase) are 1-session units when the Architect self-implements the load-bearing parts and delegates 1 mechanical artifact (W3 registration script).
What was produced
W1 (oauth2-proxy + Keycloak realm/client setup) — committed in 4c3b921
| Path | Action | Purpose |
|---|---|---|
infra/oauth2-proxy/oauth2-proxy.cfg | NEW | Canonical PSSaaS oauth2-proxy config (provider keycloak-oidc; oidc_issuer_url; redirect_url; upstreams for /app/+/api/; set_xauthrequest + pass_access_token); env-from-Secret deferral for client/cookie secrets |
infra/azure/k8s/pssaas-staging/services.yaml | AMEND | New oauth2-proxy-config ConfigMap + oauth2-proxy Deployment + Service; pinned quay.io/oauth2-proxy/oauth2-proxy:v7.7.1 upstream image |
infra/azure/k8s/ingress/pssaas-ingress.yaml | AMEND | Reconfigured: /oauth2/, /app/, /api/ route to oauth2-proxy:4180; /docs/ UNCHANGED → docs:3000 (UNAUTH per kickoff non-negotiable) |
infra/azure/k8s/pssaas-staging/oauth2-proxy-secrets.yaml.example | NEW | Template Secret manifest; filled version uncommitted + locally-applied by PO after PSX Infra delivers pssaas-app client_secret |
.github/workflows/deploy-staging.yaml | AMEND | Added oauth2-proxy rolling-restart step with first-deploy-Deployment + Secret-existence guards |
docs-site/docs/agents/cross-project-relays/2026-04-20-pssaas-keycloak-pssaas-app-client-request.md | NEW | The relay PSSaaS → PSX Infra (PO-brokered): 2 artifact deliveries + 5 verifications + 3 open questions |
docs-site/docs/handoffs/powerfill-phase-8-5-w1-checkpoint.md | NEW | Plan-stage Architect Report at the W1 checkpoint per Reviewable Chunks |
W2 (Embedded Superset SDK in React UI) — this commit batch
| Path | Action | Purpose |
|---|---|---|
src/frontend/package.json + package-lock.json | AMEND | Added @superset-ui/embedded-sdk@^0.3.0 (PSX-pinned version); 39 transitive packages added; lazy-load via dynamic import |
src/frontend/src/components/EmbeddedDashboard.tsx | NEW | The load-bearing React component. Mirrors PSX web/app/principal/components/SupersetEmbed.tsx: useEffect chain (1) GETs {guest_token, dashboard_uuid} from /api/superset/guest-token, (2) calls embedDashboard({id, supersetDomain, mountPoint, fetchGuestToken}) with the SDK's refresh callback, (3) iframe-sizing setInterval(100ms) poll until containerRef.current.querySelector('iframe') returns non-null then applies width/height/border-radius. Cleanup contract: AbortController + clearInterval + embedded.unmount() on unmount per AGENTS.md async-leak countermeasure. |
src/frontend/src/api/types.ts | AMEND | Added GuestTokenResponse { guest_token, dashboard_uuid } + DashboardKey union type matching the .NET-side endpoint dashboard_key parameter |
src/frontend/src/api/client.ts | AMEND | Added fetchGuestToken(dashboardKey, options) with separate SUPERSET_API_BASE constant (cross-cutting /api/superset/, NOT under /api/powerfill/) |
src/frontend/src/config/supersetDashboards.ts | REWRITE | Added key: DashboardKey field per dashboard; UUIDs left server-side (resolved via /api/superset/guest-token → dashboard_uuid response); added SUPERSET_DOMAIN constant for the SDK's supersetDomain parameter; preserved dashboardUrl() as a non-load-bearing utility |
src/frontend/src/pages/reports/reportShell.tsx | REWRITE | Replaced anchor-link header (lines 103-113 pre-edit) with small data-test-id="superset-embed-marker" indicator; rendered <EmbeddedDashboard> ABOVE the GenericTable when verdict==='Current'. A66 BLUE banner UX preserved (FreshnessBanner BEFORE embed; embed NOT rendered for TerminalEmpty verdict to avoid confusing-empty-iframe). |
src/frontend/src/pages/RunStatus.tsx | AMEND | Replaced per-report Superset anchors (lines 190-198 pre-edit) with embed markers; replaced Hub anchor (lines 213-220 pre-edit) with inline <EmbeddedDashboard dashboardKey="hub"> rendered as a section. Embed only shown for non-active runs (active runs poll fresh data every 2s; the embed would visually compete) |
src/frontend/src/pages/Home.tsx | REWRITE | Replaced Hub anchor card (lines 42-54 pre-edit) with inline <EmbeddedDashboard dashboardKey="hub" height="640px"> rendered as the canonical proof-of-life surface per A66 + ADR-027 demo narrative coherence |
src/frontend/src/App.tsx | AMEND | Header tenant-picker DISABLED with tooltip "Tenant determined by your authenticated Keycloak session (OIDC tenant_id claim)" (W4 fold-in; consolidates W2 + W4 visible UI changes); added Sign out link to /oauth2/sign_out (oauth2-proxy endpoint); footer sentinel reference updated to phase-8-5-ecosystem-ready |
W3 (.NET 8 guest-token-mint endpoint + Superset embedded-dashboard registration) — this commit batch
| Path | Action | Purpose |
|---|---|---|
src/backend/PowerSeller.SaaS.Modules.Superset/PowerSeller.SaaS.Modules.Superset.csproj | NEW | New module project per ADR-004 modular-monolith pattern. References SharedKernel + adds Microsoft.Extensions.Http for IHttpClientFactory |
src/backend/PowerSeller.SaaS.Modules.Superset/SupersetModule.cs | NEW | Public static class with MapEndpoints (canonical BestExModule shape); maps /superset/status sentinel + /superset/guest-token |
src/backend/PowerSeller.SaaS.Modules.Superset/SupersetExtensions.cs | NEW | services.AddSupersetModule(IConfiguration) extension. Per Phase 8.5 plan §2 Consolidation Gate disposition: this is the canonical FIRST instance of typed-HttpClient registration in the codebase; future modules adding HTTP-client patterns inherit + extend this shape rather than re-derive |
src/backend/PowerSeller.SaaS.Modules.Superset/Configuration/SupersetOptions.cs | NEW | IOptions binding: BaseUrl, AdminUsername, AdminPassword, LoginSessionCacheSeconds = 300 |
src/backend/PowerSeller.SaaS.Modules.Superset/Configuration/PowerFillEmbedUuids.cs | NEW | IOptions binding for dashboard_key → UUID resolution. Resolve(dashboardKey) returns null for unknown keys OR known-but-unset (W3-script-not-yet-run) keys. Endpoint distinguishes the two via a known-keys list (400 vs 503) |
src/backend/PowerSeller.SaaS.Modules.Superset/Contracts/GuestTokenContracts.cs | NEW | GuestTokenRequest, GuestTokenResponse, GuestTokenErrorResponse DTOs with snake_case [JsonPropertyName] |
src/backend/PowerSeller.SaaS.Modules.Superset/Services/SupersetGuestTokenClient.cs | NEW | The load-bearing piece. Typed HttpClient + 3-step handshake (login → CSRF → guest_token POST) + X-CSRFToken header (the PSX-gotcha "most-missed piece") + Referer header (cousin-gotcha) + Bearer Authorization on guest_token POST + SemaphoreSlim-guarded session memo with LoginSessionCacheSeconds cache + 401-invalidates-cache for rotation handling |
src/backend/PowerSeller.SaaS.Modules.Superset/Endpoints/SupersetEndpoints.cs | NEW | POST /api/superset/guest-token handler. Auth check: rejects HTTP 401 if X-Forwarded-Access-Token missing (oauth2-proxy MUST forward; direct cluster bypass fails closed). 400 on unknown key; 503 on known-but-unconfigured key (W3 script not yet run); 502 on Superset handshake failure; 200 on success |
src/backend/PowerSeller.SaaS.Api/Program.cs | AMEND | Registered Superset module: builder.Services.AddSupersetModule(builder.Configuration) + SupersetModule.MapEndpoints(api) |
src/backend/PowerSeller.SaaS.Api/PowerSeller.SaaS.Api.csproj | AMEND | Added ProjectReference to Modules.Superset + InternalsVisibleTo for Api.Tests (W4 internal helper exposure) |
src/backend/PowerSeller.SaaS.Api/appsettings.Staging.json | AMEND | Added Superset section + PowerFillEmbedUuids section (placeholders; populated via env-from-Secret + ConfigMap at deploy time) |
src/backend/PowerSeller.SaaS.sln | AMEND | Added Modules.Superset + Modules.Superset.Tests projects (10 → 12 projects total, but the count includes all prior projects too — the sln net-new count is 2) |
infra/azure/k8s/pssaas-staging/services.yaml | AMEND | Added Superset__AdminUsername + Superset__AdminPassword env vars via secretKeyRef from new K8s Secret pssaas-superset-secrets (out-of-band PO-applied; PSX-Infra-delivered admin creds per cross-project-relay open question #1); added envFrom configMapRef for powerfill-embed-uuids ConfigMap |
infra/azure/k8s/pssaas-staging/powerfill-embed-uuids-configmap.yaml | NEW | Placeholder ConfigMap (8 keys, all empty strings). Production values come from running infra/superset/register-powerfill-embeds.py post-PSX-Infra-pre-flight; the script prints a copy-paste kubectl apply -f - command with the captured UUIDs |
infra/superset/register-powerfill-embeds.py | NEW | DELEGATED to fast subagent per Phase 8.5 plan §6. Idempotent get-or-create of EmbeddedDashboard records via Superset's own DAO inside a create_app() Flask context (matches existing deploy-powerfill.py + deploy-dashboards-flask.py patterns; avoids the broken Keycloak service-account REST auth flagged in AGENTS.md); writes UUIDs to infra/superset/powerfill-embed-uuids.json; prints kubectl-create-configmap command. Script run via kubectl exec -n pss-platform deploy/superset -c superset per AGENTS.md updated runbook. Documented --via-rest fallback for the day Keycloak auth is unblocked |
src/backend/tests/PowerSeller.SaaS.Modules.Superset.Tests/PowerSeller.SaaS.Modules.Superset.Tests.csproj | NEW | xunit-only test project (matches BestEx.Tests + PowerFill.Tests pattern; no Moq) |
src/backend/tests/PowerSeller.SaaS.Modules.Superset.Tests/SupersetGuestTokenClientTests.cs | NEW | 8 tests pinning the load-bearing 3-step handshake behavior: happy-path 3-step + correct headers; X-CSRFToken + Referer + Authorization Bearer all attached; resources array + user object body shape; session caching (subsequent mints reuse cached login + CSRF, NOT 3 fresh HTTP calls); 401-invalidates-cache with fresh handshake on retry; missing-AdminUsername throws InvalidOperationException; login-500 throws HttpRequestException (caller maps to 502); empty-BaseUrl throws InvalidOperationException at constructor |
W4 (A68 long-term decoupling fold-in) — this commit batch
| Path | Action | Purpose |
|---|---|---|
src/backend/PowerSeller.SaaS.Infrastructure/Data/Tenant.cs | NEW | public sealed record Tenant(string TenantId, string ConnectionString); — the A68 decoupling primitive |
src/backend/PowerSeller.SaaS.Infrastructure/Data/TenantRegistry.cs | AMEND | Added Resolve(identity) returning Tenant? tuple. Old GetConnectionString(tenantId) preserved for backward compat with deprecation comment pointing future writers to Resolve |
src/backend/PowerSeller.SaaS.Api/Middleware/TenantMiddleware.cs | REWRITE | Resolution-precedence INVERTED: (1) OIDC tenant_id claim from X-Forwarded-Access-Token (NEW primary; Phase 8.5 W4); (2) ClaimsPrincipal claim (RESERVED Phase 10+ JwtBearer); (3) X-Tenant-Id legacy header (BACKWARD-COMPAT); (4) "default" fallback. Internal helper ExtractClaimFromAccessToken does base64url decode of JWT payload + claim extraction (no signature validation per trust-of-gateway model; oauth2-proxy already validated) |
src/backend/tests/PowerSeller.SaaS.Api.Tests/Middleware/TenantMiddlewareTests.cs | NEW | 9 tests pinning the resolution-precedence inversion: happy-path OIDC claim resolves; missing-claim+missing-header default fallback; unknown-identity 401; claim-vs-header conflict (claim wins); backward-compat header-only; token-without-claim falls back to header; malformed-token falls back to header; helper-direct claim extraction (positive + missing-claim cases) |
src/backend/tests/PowerSeller.SaaS.Api.Tests/PowerSeller.SaaS.Api.Tests.csproj | AMEND | Added Infrastructure ProjectReference (so TenantRegistry + TenantContext are visible to tests) |
docs-site/docs/adr/adr-029-pssaas-tenant-identity-strategy.md | NEW | Documents the long-term shape, the v1 code-shape-only PO disposition, the JWT trust-of-gateway model, and the Phase 10+ follow-up checklist |
docs-site/docs/specs/powerfill-assumptions-log.md | AMEND | A68 status updated to PARTIALLY RESOLVED with the 2026-04-20 status update block at the top of the entry |
Cross-cutting wrap-up
| Path | Action | Purpose |
|---|---|---|
src/backend/PowerSeller.SaaS.Modules.PowerFill/PowerFillModule.cs | AMEND | Sentinel bumped from phase-9-validation-ready to phase-8-5-ecosystem-ready (drops the historical phase-9 marker; Phase 8.5 is the new authoritative milestone) |
docs-site/docs/specs/powerfill-engine.md | AMEND | Phased Implementation table — new Phase 8.5 row marked DONE between rows 9 and 10 (sequenced per PO dispatch order: 8 → 9 → 8.5 → 10) |
docs-site/docs/adr/adr-027-superset-embedding-strategy.md | AMEND | Status: Proposed → Accepted; §"Decisions deferred to Phase 8.5 Architect — Architect-at-dispatch resolutions" section refilled with each deferred item's actual disposition |
docs-site/docs/arc42/09-architecture-decisions.md | AMEND | ADR-027 row Status updated to Accepted; new ADR-029 row added |
docs-site/docs/handoffs/powerfill-phase-8-5-completion.md | NEW (this) | This completion report |
docs-site/docs/devlog/2026-04-20-powerfill-phase-8-5.md | NEW | Devlog per template-7 |
docs-site/docs/handoffs/pssaas-session-handoff.md | AMEND | Checkpoint banner + numbered list entry (#41) + Backlog #31 marked DONE + sentinel bump |
docs-site/docs/handoffs/powerfill-a54-fix-greg-demo-readiness.md | AMEND | Two new load-bearing slot-in slides per kickoff: (a) "live operator workflow against auth-protected staging URL" replacing the "demo dry-run on local URL" slide; (b) "embedded dashboards inside PSSaaS UI" replacing "click-through to Superset in new tab" slide |
Out of scope (deliberately not produced)
- Multi-tenant production rollout — Phase 10+ per kickoff §"Explicit scope (OUT)". Phase 8.5 ships single-tenant single-customer auth pattern.
- Per-user-per-dashboard permission filtering at guest-token-mint — Phase 10+; v1 is "any authenticated user can mint for any dashboard"
- A69 root-cause investigation — banked Phase 9 follow-up; W2's status page continues to display
RunStepResult.ErrorMessagehonestly when populated (verified by code-shape inspection ofRunStatus.tsxlines 154-156); no regression introduced - A70 production-cutover playbook — Phase 10+ work
- Mobile-responsive design for embedded dashboards — Phase 10+ (desktop-only fine for v1 + Greg demo)
- Internationalization — Phase 10+
- Per-environment Superset registration variance (A64 multi-tenant deferral) — Phase 10+
- Service-account auth for Phase 9 harness — Phase 9 follow-up; harness currently runs locally against unauth staging
- "View in Superset" anchor-link FALLBACK if embedding fails — recommended AGAINST per kickoff + ADR-027 + A66/A69 honesty pattern. Embed-side errors render in a labeled error block per
EmbeddedDashboard.tsxdata-test-id="superset-embed-error"without masking with an anchor link - Full ASP.NET Core JwtBearerHandler integration — deferred to Phase 10+ when trust-of-gateway model evolves; v1 trusts oauth2-proxy's signature validation per ADR-029
- Tenant-picker full removal from React UI — disabled-with-tooltip in v1; full removal deferred to Phase 10+ canonical-identity convention work
- A68 canonical-identity convention decision + migration script — explicitly deferred per PO disposition Phase 8.5 plan §3 (d); Phase 10+ when first-real-customer-onboarding lands
- Local-dev oauth2-proxy bypass for
/api/superset/guest-token— endpoint requiresX-Forwarded-Access-Tokenin v1; future Phase 8.5 follow-up if local-dev embed-testing surfaces as a need
Decisions made
The 6 W1-specific decisions (D-8.5-W1-1 through D-8.5-W1-6) are documented in the W1 checkpoint report §Decisions made. The 5 W2/W3/W4-specific decisions follow:
| # | Decision | Rationale | Where |
|---|---|---|---|
| D-8.5-W2-1 | Dynamic-import the SDK (NOT regular import at App.tsx top) | PSX pattern; lazy-loads ~50KB SDK only when first embedded dashboard mounts; verified via Vite build chunk separation lib-Cb7LCDYX.js (6.82KB / 2.78KB gzipped) | EmbeddedDashboard.tsx + npm run build output |
| D-8.5-W2-2 | UUID lives server-side; client only knows DashboardKey | Per the per-render fetchGuestToken response carrying both JWT and UUID. UUID rotation = single endpoint redeploy (vs UUID-in-client = bundle-rebuild-required). Future-friendly for the registration-script-driven UUID lifecycle | supersetDashboards.ts |
| D-8.5-W2-3 | Embed NOT rendered for TerminalEmpty verdict in reportShell.tsx | A66 honesty preservation: empty embed iframe would visually conflict with the BLUE Complete+empty banner UX. The BLUE banner alone is the canonical surface in that case; the embed only renders for verdict === 'Current' | reportShell.tsx |
| D-8.5-W2-4 | Embed NOT rendered for active runs in RunStatus.tsx | Active runs poll fresh data every 2s; the embed would visually compete with that. Embed only shown for terminal states (Complete / Failed / Cancelled) | RunStatus.tsx |
| D-8.5-W3-1 | Flask app context (not REST API) for register-powerfill-embeds.py | DELEGATED-subagent disposition. Per AGENTS.md "Superset REST API auth is broken for service accounts on Keycloak-configured instances"; the same auth surface gates /api/v1/dashboard/{id}/embedded/. Flask path uses EmbeddedDashboardDAO.upsert(...) in-process; same code path the Superset admin UI itself calls. --via-rest documented but not default | register-powerfill-embeds.py |
| D-8.5-W3-2 | No Polly retry in v1 SupersetGuestTokenClient | Phase 8.5 plan §6 banked observation — failure modes surface cleanly through the embed-side error block per A69 honesty pattern. Adding Polly later is non-breaking via the AddHttpClient pipeline-builder. v1 ships without retry | SupersetGuestTokenClient.cs |
| D-8.5-W3-3 | CSRF / login session cached process-locally with 5-min TTL + 401-invalidates-cache | Reduces Superset chattiness (Option A scoping = up to 9 mints per operator click-through). 401-invalidates handles mid-session admin credential rotation cleanly | SupersetGuestTokenClient.cs |
| D-8.5-W4-1 | JWT signature validation deferred (trust-of-gateway model) | oauth2-proxy already validated against Keycloak JWKS before forwarding. Direct cluster bypass would also bypass X-Forwarded-Access-Token injection; legacy header / default fallbacks catch local-dev. Phase 10+ adds full JwtBearerHandler when trust-of-gateway evolves | TenantMiddleware.cs + ADR-029 |
| D-8.5-W4-2 | Tenant-picker DISABLED with tooltip; NOT removed for v1 | Architect call (Phase 8.5 plan §7 W4 OPTIONAL row). Full removal would touch 4 supporting files (TenantContext / tenantContextObject / useTenant / tenantConstants); minimizes visible UI churn during the demo. Removal deferred to Phase 10+ canonical-identity work | App.tsx |
Migrations enumerated
This phase ships 0 new schema migrations + 0 new SQL artifacts + 0 data-migration scripts (per A68 PO disposition: code-shape only; canonical-identity convention deferred to Phase 10+).
Gate findings
Three-layer Primary-Source Verification Gate (with Backlog re-read pass; now 5-instance corroborated)
The W1 checkpoint report covers the W1-side findings. W2-4-specific findings:
| ID | Layer | Finding | Disposition |
|---|---|---|---|
| F-8.5-W2-SDK-1 | Spec-vs-implementation | The Phase 8.5 plan §5 specified EmbeddedDashboard accepts dashboardKey: DashboardKey AND uuid: string. The actual SDK API takes id (the UUID); the JWT comes from the fetchGuestToken callback returning a string. Empirical primary-source verification at node_modules/@superset-ui/embedded-sdk/dist/index.d.ts revealed the call shape embedDashboard({id, supersetDomain, mountPoint, fetchGuestToken}) — UUID is a SERVER-SIDE concern returned by /api/superset/guest-token, not a client-side prop. | (a) Corrected in place — supersetDashboards.ts schema reduced from {id, key, title, description, uuid} to {id, key, title, description}; UUIDs returned per-mint by the .NET endpoint instead. Cleaner separation of concerns: UUID lifecycle stays server-side (rotation = endpoint redeploy, not bundle rebuild). |
| F-8.5-W3-1 | Implementation-vs-runtime (PSX runbook hygiene) | AGENTS.md "Superset REST API auth is broken for service accounts on Keycloak-configured instances" applies to the registration script's choice of API path. The plan recommended REST API but didn't pre-disposition the Keycloak-auth issue | (a) Corrected in place — subagent chose Flask app context as primary path (matches deploy-powerfill.py + deploy-dashboards-flask.py pattern); REST documented as --via-rest fallback for the day Keycloak auth is unblocked. |
| F-8.5-W3-TEST-1 | Implementation-vs-runtime | Initial SupersetGuestTokenClientTests.cs read request.Content.ReadAsStringAsync() after the production-side using var request had disposed the request; first test run produced ObjectDisposedException | (a) Corrected in place — RecordingHandler now eagerly reads body to RequestBodies list at SendAsync time, so post-send assertions read from the buffered string instead of the disposed request. Test now passes. |
| F-8.5-W4-TEST-1 | Implementation-vs-runtime | Initial TenantMiddlewareTests.cs asserted http.Response.StatusCode == 0 on the happy path; DefaultHttpContext defaults to 200 (NOT 0) | (a) Corrected in place — assertion changed to Assert.NotEqual(401, ...) which captures the load-bearing fact (middleware did NOT reject the request) without overspecifying the framework default. |
| F-8.5-W4-INTERNAL-1 | Spec-vs-implementation | First build of TenantMiddlewareTests.cs failed because ExtractClaimFromAccessToken is internal static and the Tests project wasn't on the InternalsVisibleTo list | (a) Corrected in place — added <InternalsVisibleTo Include="PowerSeller.SaaS.Api.Tests" /> to the API csproj. Helper stays internal (correct visibility for an unstable helper); tests get direct access. |
| F-8.5-CROSS-1 | Implementation-vs-runtime (Backlog re-read pass) | Backlog row #31 (Phase 8.5) carries the framing inheritance from ADR-027 + cross-project-relays archive | (b) Scope-changed — this commit batch ships under that backlog item; session-handoff bump marks #31 DONE. |
Pattern observation: the Backlog re-read pass at planning time produced 6 net-new findings across W2-4 (5 caught at planning; 1 caught during implementation as a build error which the gate's 5-instance-corroborated practice would NOT have caught — that's a TEST-time finding, not a planning-time one). This refines the practice: the Backlog re-read pass IS load-bearing for planning-time findings; build-time findings (TEST-1 + INTERNAL-1) are caught by the build pipeline itself, which is its own canonical countermeasure (Deploy Verification Gate arm b).
Alternatives-First Gate
The 4 plan-level decisions documented in Phase 8.5 plan §3 + the 9 W2/W3/W4-specific decisions in §Decisions made above. All RESOLVED at dispatch time per the dispositions documented.
Required Delegation Categories
Self-implemented with Deliberate Non-Delegation (W2-4 entirety + W3 .NET load-bearing parts):
Deliberate Non-Delegation: Templated entity scaffolding (8 React report pages + 4 main pages
+ W2 anchor-link → embedded-component swaps in 3 page files)
Task: src/frontend/src/components/EmbeddedDashboard.tsx + src/frontend/src/pages/reports/reportShell.tsx
+ src/frontend/src/pages/RunStatus.tsx + src/frontend/src/pages/Home.tsx
Reason for self-implementation: 3-instance-corroborated heuristic
("contract-per-artifact density high → self-implement") from Phase 8 W2 + Phase 9 + Phase 8.5 W1.
The EmbeddedDashboard component contract + the iframe-sizing containerRef polling + the
A66 honesty preservation (BLUE banner above embed; embed NOT rendered for TerminalEmpty)
+ the auth-aware fetchGuestToken callback are dense enough per page that delegation overhead
exceeds artifact-write cost.
Context that would be lost in handoff: A66 BLUE-vs-YELLOW banner UX; A69 status honesty
pattern preservation; ADR-027 D-8.5-3 Option A scoping (one mint per dashboard); the
iframe-sizing PSX-empirical-debug pattern; the cleanup contract (AbortController +
clearInterval + embedded.unmount on unmount per AGENTS.md async-leak countermeasure).
Deliberate Non-Delegation: Modules.Superset/Services/SupersetGuestTokenClient + SupersetEndpoints
Task: 3-step Superset handshake + auth-check + per-resource scoping for the 9 PowerFill dashboards
Reason for self-implementation: The CSRF step is "the most-missed piece" per PSX gotcha
#4-cousin; the 3-step handshake is the load-bearing translation from FastAPI to .NET 8 +
HttpClient that PSX explicitly listed in their "what I am NOT providing" honesty list.
Architectural-contract-per-artifact density is high: cache invalidation semantics + Referer
header + X-CSRFToken header + Authorization Bearer header all interact in non-obvious ways.
Context that would be lost in handoff: The Q3 architectural-mismatch correction (oauth2-proxy
+ static-site, NOT NextAuth + Next.js); the X-Forwarded-Access-Token passthrough mechanism;
the PSX gotcha #1 PUBLIC_ROLE_LIKE pre-flip dependency; the v1 single-tenant single-customer
scoping decision; the per-dashboard UUID-vs-id distinction (gotcha #8); the 401-invalidates-
cache rotation handling.
Deliberate Non-Delegation: TenantMiddleware OIDC-claim sourcing + TenantRegistry decoupling
Task: A68 long-term decoupling code shape (W4)
Reason for self-implementation: Identity is load-bearing for ALL future writes to any tenant
database; getting the resolution-precedence order wrong means silent wrong-tenant writes
(worse failure mode than 401 rejection per A68's original surfacing). The trust-of-gateway
model decision required ADR-029 narrative reasoning that delegated artifacts wouldn't carry.
Context that would be lost in handoff: A68 generalization shape (next-second-writer surprise);
the PO disposition deferring canonical-identity convention to Phase 10+; the JWT-payload
decode without signature validation rationale; the migration-no-op-because-ps-demodata-stays
decision; the Tenant-picker-disabled-not-removed Architect call.
Deliberate Non-Delegation: ADR-027 status flip + ADR-029 NEW + completion report + this Counterfactual
Task: All cross-cutting documentation
Reason: Architect-owned per architect-context.md. ADR-029 carries the rationale for the
trust-of-gateway model + the canonical-identity-convention deferral that future readers
will need to understand the v1 shape.
One subagent dispatched (the W3 register-powerfill-embeds.py delegation per Phase 8.5 plan §6). Banking observation: the 2-instance-corroborated heuristic for "delegate mechanical idempotent script generation" holds (Phase 8 W1 deploy-powerfill.py was the first; W3 register-powerfill-embeds.py is the second). Combined with the 3-instance "contract-per-artifact density high → self-implement" heuristic, the delegation-vs-self-implement decision rule is now 5-instance-corroborated across Phase 8 W1 + Phase 8 W2 + Phase 9 + Phase 8.5 W1 + Phase 8.5 W2-4.
Reviewable Chunks at intra-session scope
Mixed shape this session. The PO-confirmed plan-time disposition was "explicit W1 checkpoint with plan-stage Architect Report"; W1 shipped per that disposition (commit 4c3b921 + the W1 checkpoint report at powerfill-phase-8-5-w1-checkpoint.md). At W1-commit-time the user-side directive shifted to "don't stop until you have completed all the to-dos" — interpreted as overriding the W1 checkpoint pause and proceeding with W2-4 in the same session. The W1 checkpoint artifact remains as the artifact-of-record for the W1 chunk (it's not retroactively invalidated by W2-4 shipping in the same session — its Reviewable-Chunks intent of "land W1 atomically with a clean PSX-Infra-collaboration ask" still holds).
Banking observation: Reviewable Chunks shapes can be over-ridden mid-session by PO directive. The W1 checkpoint artifact preserves the chunk-boundary intent even when execution skips the inter-session pause. This is consistent with the AGENTS.md "Fail-Fast Permission" practice — the PO can reverse a Reviewable-Chunks decision without ceremony when the empirical execution shape suggests the chunk-pause cost outweighs its benefit. Future similar phases: bank the single-pass option as a defensible Reviewable Chunks shape when the cross-project collaboration ask can run in parallel with PSSaaS-side W2-4 implementation rather than block on it.
Deploy Verification Gate — 3 arms (W2-4 specific; W1 covered in W1 checkpoint report)
| Arm | Verification |
|---|---|
| (a) Sentinel signal | PowerFillModule.cs MapGet("/status") returns phase-8-5-ecosystem-ready. New Modules.Superset/SupersetModule.cs MapGet("/status") returns phase-8-5-ecosystem-ready from the new /api/superset/status endpoint. Both are 1-line strings; verified via Read post-edit. The actual API rebuild + restart + curl probe is a Collaborator-deferred step. |
| (b) Container/pod inspection | dotnet build PowerSeller.SaaS.sln clean (0 warnings, 0 errors; 11 projects compiled in fresh container). dotnet test PowerSeller.SaaS.sln clean (250 passed + 6 skipped + 0 failed; 17 net-new tests vs Phase 9 baseline of 233). npm run typecheck clean; npm run lint clean (0 errors / 0 warnings); npm run build clean (272.69KB JS gzipped; SDK lazy-loaded chunk separated). |
| (c) Cross-boundary cutover verification recipe (per AGENTS.md, banked 2026-04-19) | See §"Environment-Explicit Inventory" below — the bilateral recipe is the post-PSX-Infra-collaboration smoke-test target. PSSaaS-side artifact-level checks GREEN; PSSaaS-side runtime checks NOT MEASURED HERE pending the K8s manifest application + the Secret + the PSX-Infra-side pre-flight. |
Environment-Explicit Inventory (per canonical practice #13)
Per the Phase 8.5 plan §5/§6/§7 verification matrices + practice #13: capability claims characterized per environment with default-empty cells = "not measured here" (NOT "it works"). This is the load-bearing discipline the Capability Inflation antipattern is named to prevent.
| Capability | Architect-session (this) | Post-PO-push staging (no K8s apply yet) | Post-K8s-apply + Secret + PSX-Infra pre-flight | Production (per-customer) |
|---|---|---|---|---|
dotnet build clean (0 warnings + 0 errors across 11 projects) | Verified ✓ via Docker SDK 8.0 container | NOT MEASURED HERE | NOT MEASURED HERE | NOT MEASURED HERE |
dotnet test 250 passed + 6 skipped + 0 failed | Verified ✓ (was 233 pre-Phase-8.5; +17 net-new) | NOT MEASURED HERE | NOT MEASURED HERE | NOT MEASURED HERE |
npm run build clean (272.69KB JS gzipped) | Verified ✓ + SDK lazy-loaded chunk verified at lib-Cb7LCDYX.js | NOT MEASURED HERE | NOT MEASURED HERE | NOT MEASURED HERE |
Sentinel API endpoint returns phase-8-5-ecosystem-ready | NOT MEASURED HERE — code change verified; runtime-probe deferred | NOT MEASURED HERE — runtime-probe via curl https://pssaas.staging.powerseller.com/api/powerfill/status post-rollout | Verified by Collaborator | NOT MEASURED HERE |
| oauth2-proxy pod scheduled + Ready | NOT MEASURED HERE — manifest committed; not yet applied | NOT MEASURED HERE — kubectl apply -f infra/azure/k8s/pssaas-staging/services.yaml is the next step (PO + PSX Infra coordinated) | Verified by Collaborator post-Secret-applied | NOT MEASURED HERE |
Keycloak pssaas-app client exists in psx-staging realm (was pss-platform per pre-PSX-response inference; corrected post-response — see Status block above) | NOT MEASURED HERE | NOT MEASURED HERE | Verified post-response 2026-04-20 by PSX Infra: client UUID 86a42170-2e30-403e-a2b9-d2c5f2961137 in psx-staging realm; tenant_id hardcoded mapper (value ps-demodata) attached at client level | NOT MEASURED HERE |
K8s Secret pssaas-oauth2-proxy-secrets populated with valid client_secret + cookie_secret | NOT MEASURED HERE — template committed; filled-secret out-of-band PO-applied | NOT MEASURED HERE | After PSX Infra delivers: PO applies filled YAML; oauth2-proxy pod-restart consumes Secret on next deploy | NOT MEASURED HERE |
K8s Secret pssaas-superset-secrets populated with admin creds | NOT MEASURED HERE — env var refs committed (with optional: true); secret out-of-band PO-applied | NOT MEASURED HERE | After PSX Infra delivers admin creds (cross-project-relay open question #1) | NOT MEASURED HERE |
K8s ConfigMap powerfill-embed-uuids populated with real UUIDs | NOT MEASURED HERE — placeholder committed; populated post-W3-script-run | NOT MEASURED HERE | After PSX Infra runs kubectl exec ... python register-powerfill-embeds.py | NOT MEASURED HERE |
Cross-boundary recipe: /app/ HTTP 302 to anonymous → Keycloak login | NOT MEASURED HERE | NOT MEASURED HERE | Post-everything-applied: curl -I https://pssaas.staging.powerseller.com/app/ → expect 302 to Keycloak | NOT MEASURED HERE |
Cross-boundary recipe: /app/ HTTP 200 with valid Keycloak session | NOT MEASURED HERE | NOT MEASURED HERE | Browser login flow → expect post-login /app/ 200 with React UI rendering | NOT MEASURED HERE |
Cross-boundary recipe: /api/health HTTP 401 to anonymous + 200 with valid token | NOT MEASURED HERE | NOT MEASURED HERE | Post-everything-applied curl checks | NOT MEASURED HERE |
Cross-boundary recipe: /docs/ HTTP 200 to anonymous (UNCHANGED) | NOT MEASURED HERE | Pre-Secret-applied: /docs/ STILL works because the ingress rule for /docs direct-to-docs is unchanged | Verified by Collaborator | NOT MEASURED HERE |
Cross-boundary recipe: /api/superset/guest-token HTTP 401 to anonymous + 200 with valid session | NOT MEASURED HERE | NOT MEASURED HERE | Post-PSX-Infra-creds + post-script-run: end-to-end test | NOT MEASURED HERE |
| Embedded dashboards render in iframe with no CSP/X-Frame-Options errors | NOT MEASURED HERE — code-shape verified via EmbeddedDashboard.tsx review | NOT MEASURED HERE | Browser DevTools console clean during operator click-through; iframe contains the Superset dashboard chrome-stripped per dashboardUiConfig | NOT MEASURED HERE |
| End-to-end click-through: login → submit run → status → click report → embedded inline → BLUE A66 banner above embed → user identity visible in header | NOT MEASURED HERE | NOT MEASURED HERE | This IS the canonical PO milestone test post-everything | NOT MEASURED HERE |
| Hub Dashboard 13 renders inline as run-history canonical proof-of-life on Home page | NOT MEASURED HERE | NOT MEASURED HERE | Post-everything | NOT MEASURED HERE |
A69 honesty preservation: status page displays RunStepResult.ErrorMessage if populated | Verified ✓ at code level (RunStatus.tsx lines 154-156 unchanged from W2; the .step.error_message render is untouched by Phase 8.5 W2 edits) | NOT MEASURED HERE — run-against-A69-trigger needed to exercise | NOT MEASURED HERE — same as left | NOT MEASURED HERE |
| A66 honesty preservation: BLUE Complete+empty banner displays correctly above embed area for syn-trade-empty Complete runs | Verified ✓ at code level (FreshnessBanner renders BEFORE EmbeddedDashboard in reportShell.tsx; embed NOT rendered for TerminalEmpty verdict per the conditional verdict === 'Current') | NOT MEASURED HERE | NOT MEASURED HERE | NOT MEASURED HERE |
| TenantMiddleware OIDC claim resolution end-to-end | NOT MEASURED HERE — unit tests pin the resolution-precedence (9 net-new tests) | NOT MEASURED HERE | After Keycloak realm-side mapper added: login flow + pfill_run_history.tenant_id row write should equal 'ps-demodata' from claim | NOT MEASURED HERE |
pfill_run_history.tenant_id='ps-demodata' rows written from OIDC claim (NOT from X-Tenant-Id header) | NOT MEASURED HERE | NOT MEASURED HERE | Post-everything: end-to-end run via auth-protected /app/ writes a row that has tenant_id='ps-demodata' (matches historical row tags so no migration was needed per PO disposition) | NOT MEASURED HERE |
Net assessment: Phase 8.5 is artifact-complete + build-verified + test-pinned at the PSSaaS-side architectural-contract level. Runtime end-to-end verification is post-PSX-Infra-collaboration (the bilateral cross-boundary cutover verification recipe explicitly documented in the cross-project-relay request + the W1 checkpoint report). This is a deliberate Capability Inflation countermeasure: the Architect does not claim "deployed and working" without observing the deployed-and-working state; the matrix above explicitly distinguishes "verified at the artifact level" from "verified at the runtime level."
Counterfactual Retro
Knowing what I know now, what would I do differently?
-
The single-pass execution shape (W1 ships + W2-4 ships in same session) IS a viable Reviewable Chunks variant when the inter-session collaboration ask can run in parallel rather than block on the chunk pause. Banking the option as a defensible shape for similar future phases (cross-product-coordination phases). The W1 checkpoint artifact preserves the chunk-boundary intent regardless of whether the inter-session pause was exercised.
-
Trust-of-gateway model for v1 JWT validation is the right call for Phase 8.5's scope. Adding full JwtBearerHandler + JWKS validation inside the .NET API would have meant a 2-3x scope expansion (Microsoft.IdentityModel.Tokens dependency + JWKS endpoint config + JwtBearerOptions wiring + signing-key rotation handling) for a security boundary already enforced one hop upstream by oauth2-proxy. Banking observation: the trust-of-gateway pattern is a defensible v1 model for any auth surface where the gateway is the only path AND the gateway already does signature validation. Phase 10+ retires the helper when the gateway model evolves.
-
The dashboard-UUID-stays-server-side architectural decision (F-8.5-W2-SDK-1 finding) is structurally cleaner than the plan's original "uuid field on DashboardLink" shape. Banking observation: when the value lifecycle is server-side-driven (registration script generates; ConfigMap publishes; endpoint reads), keeping the value server-side avoids client-bundle-rebuild as a coupling point. Future phases adding server-driven config values should default to this shape.
-
The tests-fail-then-fix cycle (F-8.5-W3-TEST-1 + F-8.5-W4-TEST-1) caught two real bugs in the test code that would have wasted time later. Banking observation: building + running the test suite immediately after writing the production code IS the load-bearing Deploy Verification Gate arm (b) — much cheaper to fix at commit-time than at runtime-time. The Docker-based
dotnet testworkflow (~30s per cycle) is fast enough to make this a habit. -
The InternalsVisibleTo finding (F-8.5-W4-INTERNAL-1) is a banked observation about the trade-off between visibility scope and testability. Pre-Phase-8.5 .NET code didn't have any
internalsymbols; the W4ExtractClaimFromAccessTokenis the first. AddingInternalsVisibleTois the canonical .NET pattern; the helper STAYS internal (correct visibility for an unstable helper) while tests get direct access. Banking observation: when a private helper grows to merit testing, preferinternal+InternalsVisibleToover making itpublic(which leaks the helper to dependent assemblies that don't need it). -
The "0 subagents, then 1 subagent" mixed delegation pattern (W1-2-3 self-implement + W3 register script delegated + W4 self-implement) IS the correct application of practice #9. The 5-instance-corroborated heuristic ("contract-per-artifact density high → self-implement; mechanical script gen → delegate") held empirically. The subagent's output passed the "is this what the kickoff asked for?" first-question check (per banked W2-PS608 antipattern from Phase 8 W2); no scope drift.
-
A68 PARTIALLY RESOLVED is more honest than CLOSED. The code shape decoupled at W4 is real progress; the canonical-identity convention deferral is real-still-open. PARTIALLY RESOLVED captures both, with the §A68 status update block enumerating exactly what's done vs what's deferred. Banking observation: for assumptions that span multiple deferred decisions, PARTIALLY RESOLVED is a better disposition than NOT RESOLVED + a separate explanation block — it puts the partial-progress claim front-and-center where the next reader looking at the assumption sees it immediately.
-
Pre-push docs-build check IS now 10-instance corroborated. Phase 8.5 W1 was the 10th; this commit batch is the 11th instance. Banking observation: the pattern is canonically adopted at this point; Collaborator-side nomination for inclusion in process-discipline.md as a structural countermeasure (file-glob on
docs-site/docs/**triggers the local docker build pre-commit) is well-supported.
Open questions and blockers
Blockers (PO + PSX Infra)
- PO push of the W1 commit
4c3b921+ this Phase 8.5 W2-4 + cross-cutting commit batch - PO sends the cross-project-relay request to PSX Infra Collaborator at
2026-04-20-pssaas-keycloak-pssaas-app-client-request - PSX Infra response: (a) Keycloak
pssaas-appclient created + client_secret delivered + Keycloak realm-side mapper fortenant_idclaim; (b) Superset admin credentials delivered forpssaas-superset-secretsK8s Secret; (c) 5-item Superset embedding pre-flight verified + remediated (especially PUBLIC_ROLE_LIKE half-day-debug-trap pre-flip) - PO + PSSaaS Collaborator apply the filled
oauth2-proxy-secrets.yaml+pssaas-superset-secrets.yaml(both locally-applied, NOT committed) +kubectl apply -f infra/azure/k8s/pssaas-staging/services.yamlfor the first-deploy bootstrap of the new oauth2-proxy Deployment + ConfigMap + Service - PSX Infra runs
infra/superset/register-powerfill-embeds.pyviakubectl execagainst the pss-platform Superset pod; captures UUIDs to a generated ConfigMap; PSSaaS Architect commits the resultinginfra/superset/powerfill-embed-uuids.json(separate commit; not in this batch since the script hasn't run yet) - Bilateral cross-boundary cutover verification per the recipe (PSSaaS-side checks per the Environment-Explicit Inventory matrix's "Post-K8s-apply..." column + PSX-Infra-side checks per the cross-project-relay request)
Carry-overs unchanged
- A54 + A56 + A67 — RESOLVED; not Phase 8.5-relevant
- A62 (PS_DemoData view drift) — STILL DEFERRED per Backlog #32; embedding consumes the same Superset SQL; no amplification
- A65 (multi-pa_key + settlement-date variance) — banked observation; Phase 9 follow-up
- A66 (UE rebuild-empty on syn-trade-empty datasets) — A66 BLUE banner UX preserved in W2 reportShell.tsx + Home.tsx + RunStatus.tsx
- A68 — PARTIALLY RESOLVED at this Phase; canonical-identity convention deferred to Phase 10+
- A69 (state-dependent UE failure) — Greg/Tom consultation hook; W2 status display preserves the honesty pattern
- A70 (mixed proc-body state) — Phase 10+ playbook; not Phase 8.5-relevant
Optional follow-up (deferred to Phase 9 follow-ups OR Phase 10+)
- Phase 9 harness OIDC-token-mint integration — when the harness needs to run against auth-protected staging, add a Keycloak service account + thread an OIDC token through to the
pssaas_invoker(signature already accepts an optional bearer-token shape per Phase 9 completion report) - Tenant-picker full removal from React UI — Phase 10+ canonical-identity-convention work
- Full ASP.NET Core JwtBearerHandler integration — Phase 10+ when trust-of-gateway model evolves OR direct API exposure outside oauth2-proxy is added
- CI lint asserting services.yaml ConfigMap content matches infra/oauth2-proxy/oauth2-proxy.cfg — banked from W1 checkpoint report Counterfactual Retro #3
- A68 canonical-identity convention decision + migration script — Phase 10+ first-real-customer-onboarding
- Local-dev oauth2-proxy bypass for
/api/superset/guest-token— Phase 8.5 follow-up if local-dev embed-testing surfaces as a need
Recommended next steps
-
Pre-push docs-build check — MANDATORY per banked discipline (now 10-instance corroborated; Phase 8.5 W1 was 10th; this commit batch is 11th). This batch ships ~9 new
docs-site/docs/**files (ADR-029, Phase 8.5 completion report, devlog, plus amendments to ADR-027, ADR-009 index, spec, assumptions log, session handoff, A54-fix Greg-demo-readiness handoff). Rundocker build -f docs-site/Dockerfile.prod docs-sitebefore commit. -
Atomic commit batch for W2 + W3 + W4 + cross-cutting wrap-up (Architect commits; PO pushes).
-
PO push of W1 commit
4c3b921+ this Phase 8.5 final commit. Then send the cross-project-relay request to PSX Infra. -
PSX Infra collaboration per §Blockers above.
-
Bilateral cross-boundary cutover verification post-PSX-Infra-collaboration — applied by PSSaaS Collaborator + PSX Infra coordinated, output captured in a follow-up devlog.
-
Architect recommendation: Greg-demo dry run is empirically achievable post-bilateral-verification per the Phase 8.5 PO milestone. The two new slot-in slides documented in
powerfill-a54-fix-greg-demo-readiness.mdcover the new auth-protected-URL + embedded-dashboards narrative. -
A69 root-cause investigation as the next Phase 9 follow-up dispatch (per Backlog #33; not Phase 8.5 W2-4 dependent).
Notes on this session's process
- Three-layer Primary-Source Verification Gate exercised across W1 + W2-4. The W1 checkpoint report covers W1 findings; this report covers 6 net-new W2-4 findings (5 planning-time + 1 build-time). Backlog re-read pass now 5-instance corroborated at the planning-time scope.
- Reviewable Chunks at workstream scope — explicit W1 checkpoint shipped per PO disposition; W2-4 then proceeded in same session per user-side directive. The W1 checkpoint artifact remains authoritative for the W1 chunk; this completion report consolidates both chunks. Banking observation: Reviewable Chunks shape can be over-ridden mid-session by PO directive without invalidating the prior chunk artifact.
- Required Delegation Categories classification: 1 subagent dispatched (W3 register-powerfill-embeds.py); W1 + W2 + W3 .NET load-bearing parts + W4 self-implemented per 5-instance-corroborated "contract-per-artifact density high → self-implement" heuristic + 2-instance-corroborated "delegate mechanical idempotent script gen" heuristic. Subagent output passed the W2-PS608-antipattern first-question check ("is this what the kickoff asked for?" before any disposition framing); no scope drift.
- Practice #13 Environment-Explicit Inventory ACTIVELY APPLIED across both W1 checkpoint report + this completion report. The 5-environment-column matrix above explicitly distinguishes 4 verification levels (Architect-session / Post-PO-push staging / Post-K8s-apply + Secret + PSX-Infra pre-flight / Production). 18 capability rows; every cell explicit.
- Andon-cord readiness — Used twice: F-8.5-W3-TEST-1 (RecordingHandler ObjectDisposedException; refactored buffering strategy) + F-8.5-W4-TEST-1 (DefaultHttpContext default-200 status code; refactored assertion). Both caught at first-test-run, fixed in under 5 minutes each.
- Counterfactual Retro filled with 8 named observations — most important: (1) single-pass execution is a defensible Reviewable Chunks variant; (2) trust-of-gateway is right for v1 JWT validation; (3) UUID-stays-server-side is structurally cleaner than the plan's original shape; (4) tests-fail-then-fix cycle caught real bugs (Deploy Verification Gate arm b at work); (8) pre-push docs-build check now 10-instance corroborated; canonical-promotion-ready.
- Sub-phase calendar time: ~1 Architect-session. Consistent with 6a-6e + Phase 7 + Phase 8 W1/W2 + A54-fix + Phase 9 + Phase 8.5 W1 velocity at ~1.5x complexity (4 workstreams in one session). Banking observation: 4-workstream phases with high inheritance density (ADR-027 + cross-project-relays archive + W2 React shape stable from prior phase) are 1-session units when the Architect self-implements the load-bearing parts and delegates 1 mechanical artifact.
Phase 8.5 is PSSaaS-side code-complete + build-verified + test-pinned; the PSX Infra collaboration ask is dispatched; the Reviewable-Chunks W1 checkpoint artifact is preserved; the bilateral cross-boundary cutover verification recipe is the post-collaboration smoke-test target. Phase 8.5 is the last demo-blocker before Greg-demo readiness; that demo becomes runnable end-to-end against the auth-protected staging URL post-bilateral-verification.
End of Phase 8.5 Completion Report. The Architect commits; the PO pushes.