Helm Bridge — unified /admin command center + total feature instrumentation (single deploy) - #727
Conversation
…n plan Discovery (Fable research + readers) → approved design → Sonnet reground on main → single Fable plan writer. Unified /admin command center for golf + baseball: error triage (Sentry live + admin_events), auth visibility, ops tabs, jobs/integrity, deploys, read-only impersonation, daily digest. 48 TDD tasks across 15 mergeable waves. CRM fenced off. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017q4UbStdjm4Nt9t2Ciyi9F
…uard self-escalation (P0/W0)
Closes two self-service privilege-escalation vectors before /admin ships:
1. handle_new_user() cast raw signup metadata role straight into user_role
('admin' was mintable via a raw signup API call) — restricted to player|coach.
Live function body (baseball_players seed, ON CONFLICT, sport/name vars)
preserved verbatim; only the role assignment changed.
2. users_update_own RLS (no WITH CHECK) allowed self-PATCH of role — added a
BEFORE UPDATE OF role guard blocking self-escalation to non-self-service roles
(player<->coach onboarding unaffected; service_role unaffected).
Applied to prod via Supabase MCP; ACL asserted. See EXECUTION_LOG.md for the two
plan deviations corrected against live prod.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017q4UbStdjm4Nt9t2Ciyi9F
…, delete error-monitoring.ts (P0/W0) The 10 logError/logCritical call sites in golf.ts used the weak error-monitoring.ts logger — an unawaited fire-and-forget dynamic Sentry import with no error_logs/admin_events write, so captures could be lost when the serverless function returned first. Replaced with awaited logServerException from server-error-logger.ts (dual-writes Sentry + error_logs + admin_events; never throws, so awaiting cannot break a live round save). Merged into the existing logServerError import rather than adding a duplicate. Deleted the now-dead error-monitoring.ts module and its orphaned vi.mock in golf-events.test.ts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017q4UbStdjm4Nt9t2Ciyi9F
…ion) Single-super-admin allowlist (Nick only, seeded admin@helmsportslabs.com) + is_super_admin() SECURITY DEFINER gate. RLS enable+force, deny-by-default; authenticated-only EXECUTE on the function, anon fully denied. Applied to prod via Supabase MCP with ACL + seed assertions. W2 recon: admin_events carries a latent anon SELECT grant to revoke next. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017q4UbStdjm4Nt9t2Ciyi9F
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017q4UbStdjm4Nt9t2Ciyi9F
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017q4UbStdjm4Nt9t2Ciyi9F
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017q4UbStdjm4Nt9t2Ciyi9F
…contract (W1) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017q4UbStdjm4Nt9t2Ciyi9F
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017q4UbStdjm4Nt9t2Ciyi9F
… revoke (W2 migration) Additive columns + NOT VALID CHECKs + 4 triage indexes on the 90k-row live table. Revoked the latent anon table grant entirely and authenticated INSERT (dead weight, no INSERT policy); kept authenticated SELECT/UPDATE for the still-live /golf/admin (removed in W14). Applied to prod via Supabase MCP with ACL assertions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017q4UbStdjm4Nt9t2Ciyi9F
Threads sport/teamId/dbFingerprint through RoundErrorContext into the admin_events insert (sport, team_id, fingerprint, source columns added by the W2 migration). fingerprint defaults to buildIncidentSignature() so identical failures collapse in the triage queue. Purely additive — existing callers compile and behave unchanged. Regenerates the stale admin_events block in database.ts (db:types can't run without SUPABASE_PROJECT_ID in this worktree) so the writer types check. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017q4UbStdjm4Nt9t2Ciyi9F
Adds optional sport/teamId/fingerprint/source to AdminEventInput and the admin_events insert body. logLogin/logSignup hoist a caller- supplied sport out of metadata and set source='auth'; logSecurityEvent also sets source='auth'. All three exported signatures are unchanged so the existing demo-access.test.ts mocks of logLogin keep passing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017q4UbStdjm4Nt9t2Ciyi9F
…tion) Two SECURITY DEFINER RPCs internally gated on is_super_admin(): get_active_sessions (reads auth.sessions, not PostgREST-exposed) and resolve_admin_event (the one admin mutation). anon EXECUTE revoked, authenticated granted, ACL asserted. Applied to prod. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017q4UbStdjm4Nt9t2Ciyi9F
… (W3)
db:types can't run headless in this environment; surgically add both W3
RPCs to the Functions block (get_active_sessions: Args Record<string,
never>, Returns Json; resolve_admin_event: Args { p_event_ids: string[]
}, Returns number) in alphabetical position matching the generated
format, so typed .rpc() calls compile ahead of Task 4.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017q4UbStdjm4Nt9t2Ciyi9F
AdminFetchResult<T> envelope (ok/failed/unconfigured) plus the server-only Sentry REST client: fetchSentryIssues/fetchSentryHourlyStats/ fetchSentryReleaseHealth. Reads SENTRY_READ_TOKEN/SENTRY_ORG/ SENTRY_PROJECT, never throws (missing token -> unconfigured, HTTP/network failure -> error), 60s revalidate cache, Link-header cursor pagination capped at 3 pages, honors 429 Retry-After. Deviation from wave doc: the pagination test's mockResolvedValue reused a single Response instance across all 3 simulated pages; Response.json() can only be read once (confirmed via isolated repro: "Body is unusable: Body has already been read"), so any correct implementation would fail that assertion. Switched to mockImplementation() returning a fresh Response per call - preserves the test's intent (3 fetches, cursor followed) without changing the implementation contract. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017q4UbStdjm4Nt9t2Ciyi9F
fetchVercelDeployments reuses the VERCEL_API_TOKEN/VERCEL_PROJECT_ID/ VERCEL_TEAM_ID trio admin-data.ts already reads for web analytics; same fail-soft contract as sentry-api.ts (missing trio -> unconfigured, non-2xx or network failure -> error, never throws). Always sends teamId when set to avoid the Vercel empty-results footgun on team-scoped tokens. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017q4UbStdjm4Nt9t2Ciyi9F
mergeTriage merges Sentry issues + admin_events app rows into ONE TriageItem queue, grouping app rows by fingerprint and ranking by distinct affected users first, recency second — never raw event volume (a retry-looping job must not bury a low-volume auth bug). fetchTriageQueue wraps it with the service-role admin_events read + fetchSentryIssues. resolveTriageEvents (src/app/admin/actions/triage.ts) calls requireSuperAdmin() as its first line and invokes resolve_admin_event with the user-scoped client (never service_role — the RPC's internal is_super_admin() gate needs auth.uid() to resolve). Covered by the W1 admin-gate-coverage contract test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017q4UbStdjm4Nt9t2Ciyi9F
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017q4UbStdjm4Nt9t2Ciyi9F
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017q4UbStdjm4Nt9t2Ciyi9F
… menu (W4) Adapts to the real Fairway AppShell/CommandMenu prop shapes (NavItem uses activeMatch, not isActive; CommandMenu takes a top-level onSelect and CommandItem has no per-item onSelect/hint — uses id + shortcut). Also swaps SportBadge's arbitrary text-[11px] for the canonical text-eyebrow utility (helm/no-arbitrary-text-px). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017q4UbStdjm4Nt9t2Ciyi9F
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017q4UbStdjm4Nt9t2Ciyi9F
Two fixes to the doc-pinned code to satisfy this repo's stricter compiler settings: Component subclass members need `override` (noImplicitOverride), and the STALE card label no longer echoes the panel title verbatim (it collided with the <h2> heading's own text under getByText(/title/), making both doc-pinned assertions ambiguous over the same text). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017q4UbStdjm4Nt9t2Ciyi9F
Not one of w04-design-foundation.md's 4 numbered tasks, but pinned in the Shared Interfaces / File Structure sections of the master plan as a W4/W5 component every later panel wave needs. Composes the existing useVisibilityAwareInterval hook + router.refresh() — DECISIONS #9 (no Supabase Realtime in v1, 30-60s visibility-aware polling only). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017q4UbStdjm4Nt9t2Ciyi9F
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017q4UbStdjm4Nt9t2Ciyi9F
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017q4UbStdjm4Nt9t2Ciyi9F
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017q4UbStdjm4Nt9t2Ciyi9F
…ail (W5) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017q4UbStdjm4Nt9t2Ciyi9F
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017q4UbStdjm4Nt9t2Ciyi9F
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017q4UbStdjm4Nt9t2Ciyi9F
Uses the async-function delegation form (module-level wrapped closure + exported async function delegate) rather than a const-export, since golf.ts is a 'use server' file and Next requires exported server actions to be async function declarations. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017q4UbStdjm4Nt9t2Ciyi9F
|
🤖 Mission Control — PR summary What it changes: Consolidates the two Helm Bridge PRs into a single-deploy change. Ships the unified Risk / areas: HIGH surface area — ~74 files under
What reviewers should watch:
Automated summary · not a review · CI status as of 2026-07-02 22:07 UTC. |
Code Review by Qodo
Context used✅ Compliance rules (platform):
93 rules 1. admin_allowlist has no policy
|
| CREATE TABLE IF NOT EXISTS public.admin_allowlist ( | ||
| user_id uuid PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE, | ||
| email text NOT NULL, | ||
| note text, | ||
| created_at timestamptz NOT NULL DEFAULT now() | ||
| ); | ||
|
|
||
| ALTER TABLE public.admin_allowlist ENABLE ROW LEVEL SECURITY; | ||
| ALTER TABLE public.admin_allowlist FORCE ROW LEVEL SECURITY; |
There was a problem hiding this comment.
1. admin_allowlist has no policy 📘 Rule violation ⛨ Security
The new public.admin_allowlist table enables (and forces) RLS but defines no CREATE POLICY in the same migration. This violates the requirement that every newly created table migration must include at least one RLS policy, risking audit failure and inconsistent access behavior expectations.
Agent Prompt
## Issue description
A new table (`public.admin_allowlist`) is created with RLS enabled/forced but no RLS policies are defined in the same migration.
## Issue Context
Compliance requires every new table migration to both enable RLS and define at least one policy.
## Fix Focus Areas
- supabase/migrations/20260701110000_admin_allowlist_is_super_admin.sql[6-14]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| import { LazyMotion, domAnimation } from 'framer-motion'; | ||
| import type { ReactNode } from 'react'; | ||
|
|
||
| /** | ||
| * LazyMotion(domAnimation) at the /admin route root. Without it every | ||
| * `<m.*>` renders as static DOM and animated numbers freeze at 0 — this | ||
| * bit the golf-admin Tracer KPI tiles before (see golf/admin/layout.tsx). | ||
| */ | ||
| export function AdminMotionProvider({ children }: { children: ReactNode }) { | ||
| return <LazyMotion features={domAnimation}>{children}</LazyMotion>; |
There was a problem hiding this comment.
2. Static lazymotion features import 📜 Skill insight ➹ Performance
AdminMotionProvider passes domAnimation via a static import to LazyMotion instead of using a dynamic import function. This violates the bundle-optimization requirement and can increase client bundle cost.
Agent Prompt
## Issue description
`LazyMotion` features are provided via a static import (`domAnimation`) instead of a dynamic import loader function.
## Issue Context
The compliance rule requires `features` to be a function returning a dynamic import for better bundle optimization.
## Fix Focus Areas
- src/app/admin/_motion-provider.tsx[3-12]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| export function AdminMotionProvider({ children }: { children: ReactNode }) { | ||
| return <LazyMotion features={domAnimation}>{children}</LazyMotion>; |
There was a problem hiding this comment.
3. lazymotion missing strict 📜 Skill insight ☼ Reliability
AdminMotionProvider renders LazyMotion without the strict prop. This violates the requirement to enable strict mode to prevent accidental motion imports from silently pulling the full Framer Motion bundle.
Agent Prompt
## Issue description
`LazyMotion` is used without the `strict` prop.
## Issue Context
Strict mode helps catch accidental `motion` imports that can bloat bundles.
## Fix Focus Areas
- src/app/admin/_motion-provider.tsx[11-12]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| const supabase = await createClient(); | ||
| const { data: { user } } = await supabase.auth.getUser(); | ||
|
|
||
| if (!user) { | ||
| return NextResponse.json({ success: false }, { status: 401 }); | ||
| } | ||
| // Was: 401 for unauthenticated users — which blinded us to login/signup | ||
| // flow client errors (they reached Sentry but never error_logs). | ||
| // Anonymous writes are accepted, flagged, and severity-capped. | ||
| const isAnonymous = !user; | ||
|
|
||
| const errorReport = await request.json(); | ||
| const adminClient = createAdminClient(); |
There was a problem hiding this comment.
6. Anonymous log spam risk 🐞 Bug ⛨ Security
POST /api/log-error no longer requires an authenticated user and performs privileged inserts into error_logs/admin_events via the service-role admin client, enabling unauthenticated callers to generate DB write load and telemetry noise. The only mitigation is a per-instance in-memory rate limit, which does not prevent distributed flooding across IPs/instances.
Agent Prompt
## Issue description
`POST /api/log-error` now accepts unauthenticated requests and writes into `error_logs` and `admin_events` using the service-role admin client. This expands the trust boundary and allows unauthenticated log spam / DB write amplification; the current limiter is in-memory per instance.
## Issue Context
The route intentionally allows anonymous reporting for pre-auth flows, but it still needs stronger abuse controls (request size, stricter limits, and/or narrower acceptance rules) because it writes through `SUPABASE_SERVICE_ROLE_KEY`.
## Fix Focus Areas
- src/app/api/log-error/route.ts[14-109]
- src/lib/rate-limit.ts[1-21]
- src/lib/rate-limit.ts[110-128]
- src/lib/supabase/admin.ts[4-21]
## Suggested changes
- Add a request body size guard *before* JSON parsing (e.g., read `await request.text()`, reject with 413 if > N bytes, then `JSON.parse`).
- Tighten anonymous acceptance rules:
- Use a much stricter rate limit for anonymous reports (separate bucket from `API_WRITE`).
- Consider accepting anonymous reports only when `new URL(request.url).pathname`/`referer` indicates known pre-auth pages (login/signup) and otherwise return 401.
- Optionally avoid inserting anonymous reports into `admin_events` (keep them in `error_logs` only) to reduce operational noise.
- If you need stronger guarantees, move rate limiting to a shared store (Upstash/Redis) so limits apply across instances.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| export async function recordJobRun<T>(jobType: string, fn: () => Promise<T>): Promise<T> { | ||
| const startedAt = new Date(); | ||
| try { | ||
| const result = await fn(); | ||
| await writeRow(jobType, 'completed', startedAt, null); | ||
| return result; | ||
| } catch (err) { | ||
| const message = err instanceof Error ? err.message : String(err); | ||
| await writeRow(jobType, 'failed', startedAt, message.slice(0, 2000)); | ||
| try { | ||
| void logServerEvent( | ||
| `Cron failed: ${jobType}`, | ||
| { action: `cron.${jobType}`, source: 'cron', errorDetails: message.slice(0, 2000) }, | ||
| 'error', | ||
| ).catch(() => {}); | ||
| } catch { | ||
| /* never mask the real failure */ | ||
| } | ||
| throw err; | ||
| } | ||
| } | ||
|
|
||
| async function writeRow( | ||
| jobType: string, | ||
| status: 'completed' | 'failed', | ||
| startedAt: Date, | ||
| errorMessage: string | null, | ||
| ): Promise<void> { | ||
| try { | ||
| const completedAt = new Date(); | ||
| const admin = createAdminClient(); | ||
| await admin.from('background_job_logs').insert({ | ||
| job_type: jobType, | ||
| status, | ||
| duration_ms: completedAt.getTime() - startedAt.getTime(), | ||
| error_message: errorMessage, | ||
| started_at: startedAt.toISOString(), | ||
| completed_at: completedAt.toISOString(), | ||
| }); |
There was a problem hiding this comment.
8. Job logging blocks cron 🐞 Bug ☼ Reliability
recordJobRun awaits an insert into background_job_logs on both success and failure paths, placing DB I/O on the critical path of cron handlers. If Supabase is slow/unavailable this delays cron responses despite the intended fire-and-forget contract.
Agent Prompt
## Issue description
`recordJobRun` claims fire-and-forget semantics but performs `await writeRow(...)`, and `writeRow` performs an awaited DB insert. Errors are swallowed, but latency is still added to every cron request.
## Issue Context
Cron handlers now wrap their work in `recordJobRun(...)`. Keeping the logging call awaited increases the chance that a slow DB extends job runtime unnecessarily.
## Fix Focus Areas
- src/lib/admin/job-log.ts[20-58]
- src/app/api/cron/v3/weekly-coach-email/route.ts[38-47]
## Suggested changes
- Remove the awaited DB write from the critical path:
- On success: `void writeRow(jobType, 'completed', startedAt, null);`
- On failure: `void writeRow(jobType, 'failed', startedAt, ...);` before rethrow.
- Optionally bound the logging latency even when fire-and-forget:
- Use an internal timeout/abort (if supported by your Supabase client) or wrap the insert in a `Promise.race` with a short timer.
- Keep the existing `try/catch` swallowing so logging failure never affects the cron outcome.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
W15 — Total Error-Capture Coverage (PR B: total feature instrumentation)
Stacks on PR A #718 (
feat/helm-bridge-command-center→main, LOW risk, foundationserver action in
withAdminObserved({sport, feature, …})and centralizes RLS-denialcapture in the shared pagination helper. Zero behavior change anywhere — every wrap is
an
Impl+ thin delegator, bodies byte-identical, verified per-batch by diff.Do not merge yet — owner merges PR A first, then this one (see NOT-FOR-AUTO-MERGE
note at the bottom).
Result: 424/424 non-CRM golf+coachhelm server-action exports wrapped
The count is not a manifest —
coverage-contract.foundation.test.ts's global tripwire(Task 16a) walks
src/app/golf/actions/**live on every test run, drops the spec §1.3CRM/non-action-boundary exclusions, and asserts every remaining export carries a valid
registry-matching
FeatureKey. A future PR that lands an unwrapped action file in thisdirectory fails CI automatically.
Batch table (Tasks 5–14)
admin_dashboard027e3c4ceround_tracking,stats_analytics,qualifiers,my_qualifiers24f220064calendar_events,academics_classes,notifications6af19e66dtask_management,travel,documentsa479f7d7amessaging,announcements2f5f286b7roster_management,team_info,join_team_flow,auth_onboarding,settings20a67d2a1course_library,recruiting_prospect_tracking,player_hub,coach_dashboard,my_game_profile,whats_new396cc446acoachhelm_ai_engine,alerts_system,patterns_dashboardeb4395f52insights_management,intelligence_dashboard,coachhelm_analytics,coaching_intelligence_settings83261f54d(insights remainder) +974f43675(files)player_coachhelm_dashboard,round_review_ai,development_plans_coach,my_development,drills_practice_rx,coachhelm_v3_goals83261f54d(insights remainder) +a498625fc(files)14+52+49+63+23+35+51+34+39+64 = 424.insights.tsis 26/26 wrapped across B7 (16) + theB8/B9 insights-remainder commit (10: 7
insights_management+generateRoundReview→round_review_ai+getPlayerFocusAreas→my_development+getPlayerCoachHelmDashboard→
player_coachhelm_dashboard).Task 15 — Centralize RLS-denial capture
8639262de): widenedfetchAllRows/fetchAllRowsResult'smakeQueryerror type to
{message: string; code?: string | null}, added an additive optionalthird arg
rlsCtx?: RlsCaptureCtx, and callmaybeCaptureRlsDenial(error, …)in bothif (error)branches — fire-and-forget, return/throw byte-identical. 16 new tests.chore/helm-bridge-t15b-threading, commit11be4c774,merged via
67a6598ab): threadsrlsCtx {table, action, feature, sport:'golf'}throughevery
fetchAllRows/fetchAllRowsResultcall site across the 13 named golf-actionfiles + 2 route handlers (
calendar/feeds/[token]/route.ts,cron/event-reminders/route.ts). AddsmaybeCaptureRlsDenialbeside the existingad-hoc 42501 branches in the 6 named files (
event-documents.ts,recruit-documents.ts,insights.ts,teams.ts,golf.ts,round-reviews.ts);admin-data.tscorrectlyskipped (service-role client, no RLS to capture). Pure third-argument addition —
zero behavior change.
grep -rn "maybeCaptureRlsDenial" src/app/baseball src/lib/baseball src/app/lifting src/lib/lifting= 0 (verified independently twice: once in T15b, once in this FINALIZE unit).Task 16 — Lock the invariant + verification sweep
9658a042e): flippedcoverage-contract.foundation.test.ts's global tripwirefrom
it.todoto a live test. Discovers (does not hard-code) every'use server'action file under
src/app/golf/actions/**minus the spec §1.3 exclusion manifest,plus the 10 golf exports of
actions/messages.ts+admin/actions/triage.ts, andasserts every export is wrapped with a valid registry-matching
FeatureKey. A secondtest re-derives the 424 count live. Also replaced 3 stale self-test assertions (which
assumed
alerts.tswas still unwrapped — it was wrapped in B7) with a dedicatednever-shipped fixture (
fixtures/unwrapped-actions.fixture.ts).789389be3, merge67a6598ab): merged T15b intothe instrumentation branch and ran the one full-suite gate for the whole unit (below).
Task 16 step 3 (manual forced-failure dev-environment verification — trigger one bad
table-name failure, confirm a single tagged
admin_eventsrow + fingerprint collapseat 50×, confirm
enterDemo/NEXT_REDIRECTwrites nothing) requires a dev server andwas not run in this CLI-only unit (machine rule: no dev servers/browsers) — left
for the owner or a follow-up unit with dev-server access.
Full gate results (this PR's final state, one run)
coverage-contract.*(13 batch files + foundation) +feature-registry.test.ts: allgreen, including the flipped tripwire (
every non-CRM golf action export is wrappedtotal wrapped-and-valid action count ... is exactly 424).fetch-all-rows.test.ts:13/13 green including every
rlsCtxcapture assertion.Excused pre-existing failures (6/6, proof attached)
Proof method:
git diff --stat 880994c16 HEAD -- <path>(880994c16= the tip thisunit started from, before T15b/T16a/FINALIZE) produced zero output for every file
below — i.e. the implicated files are byte-identical between the pre-unit base tip and
this PR's HEAD, so today's failure is exactly yesterday's failure.
src/lib/baseball/__tests__/program-type-nav-variants.test.tscoach nav ORDER differs between College and Showcase,absent programType falls back to declaration order,re-ordering never adds or drops entries)events/organization/teams), unrelated to golf/admin instrumentation. Out of scope per hard rule "never touch baseball/lifting/crm."git diff --stat 880994c16 HEAD -- src/lib/baseball/= empty. Re-ran in isolation — same 3/16 failures, deterministic.src/test/golf/actions/insight-celebration.test.tsstamps metadata.celebration_shown_at on first call)Invariant: static generation store missing in revalidatePath—revalidatePath()invoked outside request scope inside vitest. Documented in every prior W15 batch log entry (B6, B8/B9, T15-CORE, T16a).git diff --stat 880994c16 HEAD -- src/app/golf/actions/insight-celebration.ts src/lib/admin/observed-action.ts= empty.src/app/golf/actions/__tests__/round-recap.test.tsrevalidatePath-outside-request-scope invariant, inround-recap.ts:109.git diff --stat 880994c16 HEAD -- src/app/golf/actions/round-recap.ts src/lib/admin/observed-action.ts= empty.The third documented KNOWN class (jsdom
node_modules/node_modules/reactduplicate-moduleerrors) did not trigger this run — nothing to excuse.
Hard-rule compliance
crm-*.ts,resend-activity.ts,src/app/baseball/**,src/app/lifting/**,src/lib/baseball/**,src/lib/lifting/**, the self-referentiallog sinks,
inngest/route.ts, or the 19 already-instrumented/api/cron/**handlers.export const x = withAdminObserved(...)(which breaks Next's build in golf's
'use server'files).NEXT_REDIRECT/NEXT_NOT_FOUNDare still controlflow, never logged;
{success:false}validation envelopes are still not errors.⚠ NOT FOR AUTO-MERGE
This branch is stacked on
feat/helm-bridge-command-center(PR A, #718), notmain.Owner merges PR A first, then this PR. Nothing in this unit was merged into
mainorbatch/baseball-fixes.🤖 Generated with Claude Code