Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
ccb1e66
Wire free production readiness stack
njrini99-code Jun 30, 2026
cea85e3
fix(readiness): close route secret and dependency findings
njrini99-code Jun 30, 2026
399d664
fix(ops): close 24h GitHub issues for CI, secrets, and tooling
njrini99-code Jun 30, 2026
5a6cc85
fix(readiness): close 24h ops issues (#351–#365)
njrini99-code Jun 30, 2026
b9cda23
feat(routes): wire Next.js route bug catcher static checks
njrini99-code Jun 30, 2026
f2deea6
feat(routes): add route bug catcher playbook and hygiene hardening
njrini99-code Jun 30, 2026
19edb0e
fix(semgrep): document push token action revalidate exceptions
njrini99-code Jun 30, 2026
a3b487d
fix(ci): restore routes:check and clear semgrep on device-token path
njrini99-code Jun 30, 2026
69e4594
fix(golf): suppress semgrep on guarded round-submit fallback
njrini99-code Jun 30, 2026
8d271d4
fix(e2e): stabilize route crawler auth redirect waits
njrini99-code Jun 30, 2026
66c14b3
fix(ci): clear Review Gate semgrep on baseball games actions
njrini99-code Jun 30, 2026
fdb57f5
feat(ops): complete bug-discovery stack install
njrini99-code Jun 30, 2026
4479cc4
fix(ci): satisfy actionlint on Meticulous advisory workflow
njrini99-code Jun 30, 2026
26958c1
fix(ci): Meticulous advisory placeholder without unresolved action
njrini99-code Jun 30, 2026
e9b5b94
feat(ops): add visual baselines and verify:bugs entry points
njrini99-code Jun 30, 2026
2cbcf99
fix(ci): ZAP workflow env indirection for semgrep gate
njrini99-code Jun 30, 2026
ed2e716
fix(ci): supply Supabase env for visual regression job
njrini99-code Jun 30, 2026
cad174e
fix(ci): supply Supabase env for route crawler job
njrini99-code Jun 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions .github/workflows/free-production-readiness.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
name: Free Production Readiness

on:
pull_request:
workflow_dispatch:

permissions:
contents: read

jobs:
business-contracts:
name: Business contracts
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup Node
uses: actions/setup-node@v6
with:
node-version: 20
cache: npm

- name: Install dependencies
run: npm ci

- name: Run low-gray business contracts
run: npm run verify:business

- name: Run advisory source-shape contracts
run: npm run test:business:advisory
continue-on-error: true

route-hygiene:
name: Route Hygiene P0/P1
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup Node
uses: actions/setup-node@v6
with:
node-version: 20
cache: npm

- name: Install dependencies
run: npm ci

- name: Check route hygiene
run: npm run routes:check

- name: Upload route hygiene reports
if: always()
uses: actions/upload-artifact@v7
with:
name: route-hygiene-reports
path: docs/operations/generated/
if-no-files-found: ignore
retention-days: 14

static-radar:
name: Static radar advisory
runs-on: ubuntu-latest
continue-on-error: true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep static radar steps running independently

In this workflow, static-radar is intended to collect Semgrep, dependency-cruiser, and jscpd advisory reports, but putting continue-on-error only on the job does not make later steps run after an earlier step fails; the default step condition remains success-only. In a fresh CI run, any nonzero exit from Semgrep prevents the dependency and duplicate-code reports from being generated or uploaded, so each radar step needs its own continue-on-error or the later report steps need if: always().

Useful? React with 👍 / 👎.

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup Node
uses: actions/setup-node@v6
with:
node-version: 20
cache: npm

- name: Install dependencies
run: npm ci

- name: Install Semgrep CE
run: python -m pip install semgrep

- name: Run Semgrep Helm rules
run: npm run analyze:semgrep

- name: Run dependency-cruiser
run: npm run analyze:deps

- name: Run duplicate-code radar
run: npm run analyze:duplicates

- name: Run Helm auditor
if: always()
run: npm run auditor:all

- name: Upload static radar reports
if: always()
uses: actions/upload-artifact@v7
with:
name: static-radar-reports
path: |
docs/operations/generated/
docs/operations/revealed-bugs/production-readiness/
if-no-files-found: ignore
retention-days: 14
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@ scripts/rotate-demo-passwords.mjs
/test-results/
/playwright-report/

# Production-readiness generated reports.
/docs/operations/generated/
/.stryker-tmp/

# Large generated showcase binaries (repo already ignores GolfHelm-Prospect-Deck.pptx).
GolfHelm-CoachHelm-Showcase.pdf
GolfHelm-Fairway-Guide.pdf
Expand Down
89 changes: 89 additions & 0 deletions .semgrep/helm-rules.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
rules:
- id: helm-service-role-in-client-code
severity: ERROR
languages: [typescript, javascript]
message: Service-role/admin Supabase client must not be imported from client-facing code.
paths:
include:
- /src/**/*.ts
- /src/**/*.tsx
exclude:
- /src/lib/supabase/admin.ts
- /src/**/*.test.ts
- /src/**/*.test.tsx
- /src/contracts/**
patterns:
- pattern-either:
- pattern: import $X from '@/lib/supabase/admin'
- pattern: import { $X } from '@/lib/supabase/admin'
- pattern: createAdminClient(...)
- pattern-not-inside: |
"use server";
...

- id: helm-golf-file-links-baseball-auth-or-dashboard
severity: ERROR
languages: [typescript]
message: Golf files must not link or redirect to Baseball auth/dashboard routes.
patterns:
- pattern-either:
- pattern: '"/baseball/$PATH"'
- pattern: "'/baseball/$PATH'"
paths:
include:
- /src/app/golf/**/*.ts
- /src/app/golf/**/*.tsx

- id: helm-baseball-file-links-golf-auth-or-dashboard
severity: ERROR
languages: [typescript]
message: Baseball files must not link or redirect to Golf auth/dashboard routes.
patterns:
- pattern-either:
- pattern: '"/golf/$PATH"'
- pattern: "'/golf/$PATH'"
- metavariable-regex:
metavariable: $PATH
regex: ^(login|signup|dashboard).*
paths:
include:
- /src/app/baseball/**/*.ts
- /src/app/baseball/**/*.tsx

- id: helm-success-true-inside-catch
severity: ERROR
languages: [typescript, javascript]
message: Catch blocks must not return success:true.
pattern: |
catch ($ERR) {
...
return { ..., success: true, ... };
}

- id: helm-llm-call-in-client-component
severity: ERROR
languages: [typescript]
message: LLM generation must stay server-side.
patterns:
- pattern-either:
- pattern: generateText(...)
- pattern: streamText(...)
- pattern-inside: |
"use client";
...

- id: helm-catch-collapses-error-to-empty
severity: WARNING
languages: [typescript, javascript]
message: Catch block collapses a failure to []/null; prefer an explicit error state.
pattern-either:
- pattern: |
catch ($ERR) {
...
return [];
}
- pattern: |
catch ($ERR) {
...
return null;
}
65 changes: 65 additions & 0 deletions dependency-cruiser.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/** @type {import('dependency-cruiser').IConfiguration} */
module.exports = {
forbidden: [
{
name: 'golf-app-must-not-import-baseball-app',
severity: 'error',
from: { path: '^src/app/golf/' },
to: { path: '^src/app/baseball/' },
},
{
name: 'baseball-app-must-not-import-golf-app',
severity: 'error',
from: { path: '^src/app/baseball/' },
to: { path: '^src/app/golf/' },
},
{
name: 'golf-lib-must-not-import-baseball-lib',
severity: 'error',
from: { path: '^src/lib/golf/' },
to: { path: '^src/lib/baseball/' },
},
{
name: 'baseball-lib-must-not-import-golf-lib',
severity: 'error',
from: { path: '^src/lib/baseball/' },
to: { path: '^src/lib/golf/' },
},
{
name: 'lib-must-not-import-app',
severity: 'error',
from: { path: '^src/lib/' },
to: { path: '^src/app/' },
},
{
name: 'coachhelm-core-must-not-import-ui-pages',
severity: 'error',
from: { path: '^src/lib/coachhelm/' },
to: { path: '^src/(app|components)/' },
},
{
name: 'stats-helpers-must-not-import-react-ui',
severity: 'error',
from: { path: '^src/lib/.*/(stats|stat|calculator|formulas)' },
to: { dependencyTypes: ['npm'], path: '^(react|react-dom|next)' },
},
{
name: 'no-circular-coachhelm-or-stats',
severity: 'error',
from: { path: '^src/lib/(coachhelm|.*stats.*|.*golf.*)' },
to: { circular: true },
}
],
options: {
doNotFollow: { path: 'node_modules' },
tsPreCompilationDeps: true,
tsConfig: { fileName: 'tsconfig.json' },
enhancedResolveOptions: {
exportsFields: ['exports'],
conditionNames: ['import', 'require', 'node', 'default'],
},
reporterOptions: {
dot: { collapsePattern: 'node_modules/[^/]+' },
},
},
};
8 changes: 7 additions & 1 deletion docs/operations/BRANCH_PROTECTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ Required checks for `main` should match the hard blockers in `docs/operations/GA
- `Unit tests`
- `Next build`
- `Supabase lint + RLS tests`
- `Business contracts`
- `Route Hygiene P0/P1`
- `Playwright Smoke / Smoke checks`
- `Review Gate / all`
- `CodeRabbit`
Expand All @@ -22,10 +24,14 @@ Required checks for `main` should match the hard blockers in `docs/operations/GA
Do not require these until they are stable enough to block everyday work:

- `Playwright E2E Advisory`
- `Business contracts`
- `Business contracts advisory`
- `Course picker screenshots`
- CircleCI weekly radar jobs
- Lighthouse preview jobs
- `Knip`
- `jscpd`
- `Stryker`
- `k6 staging load`

## Change Rule

Expand Down
59 changes: 59 additions & 0 deletions docs/operations/FREE_PRODUCTION_READINESS_STACK.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Free Production Readiness Stack

This branch wires the free/open-source production-readiness foundation for Helm without changing product behavior.

## Installed Tools

Repo dev dependencies:

- `fast-check`
- `@stryker-mutator/core`
- `@stryker-mutator/vitest-runner`
- `dependency-cruiser`
- `jscpd`
- `knip`
- `@axe-core/playwright`
- `@lhci/cli`

Machine CLIs verified on this workstation:

- `semgrep`
- `gitleaks`
- `osv-scanner`
- `k6`

## New Lanes

- `npm run test:business`: low-gray Vitest business contracts.
- `npm run test:business:advisory`: source-shape advisory contracts.
- `npm run routes:check`: route inventory plus duplicate/stale/boundary/coverage/dead-route checks.
- `npm run analyze:semgrep`: Helm Semgrep CE rules.
- `npm run analyze:deps`: dependency-cruiser architecture report.
- `npm run analyze:duplicates`: jscpd duplicate-code report.
- `npm run prod:audit:db`: SELECT-only DB audit; skips safely without `PROD_AUDIT_DATABASE_URL`.
- `npm run auditor:all`: aggregates generated reports into issue drafts.

Generated reports go under `docs/operations/generated/` and are ignored by git.

## Gate Posture

Immediate hard candidates:

- Business contracts
- Route Hygiene P0/P1
- Existing CI hard gates
- Existing Review Gate / gitleaks / semgrep / CodeQL checks

Advisory first:

- Business source-shape advisory
- Stryker mutation pilots
- Knip
- jscpd
- dependency-cruiser lower-severity findings
- k6 staging load
- Lighthouse public route budgets

## Gray-Test Cleanup

Source-sniffing contracts were moved to `*.advisory.contract.test.ts`. The low-gray path now includes pure product-trust state mappers and property contracts for GolfHelm stats.
8 changes: 7 additions & 1 deletion docs/operations/GATE_MATRIX.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,23 @@ This file is the current map of hard merge blockers versus advisory checks.
| Review Gate / ast-grep | `review-gate.yml` | project-specific banned patterns |
| Review Gate / semgrep | `review-gate.yml` | security and static-analysis risks |
| Review Gate / gitleaks | `review-gate.yml` | leaked secrets |
| Business Contracts | `npm run verify:business` / `Business contracts` | low-gray product-truth regressions in stats, CoachHelm evidence, and product trust mappers |
| Route Hygiene P0/P1 | `npm run routes:check` | missing or duplicate critical routes and Golf/Baseball route-boundary leaks |

## Advisory Checks

| Check | Why Advisory |
|---|---|
| Playwright E2E Advisory | full suite is still stabilizing |
| Business Contracts | advisory product-truth radar for GolfHelm stats, CoachHelm evidence, access boundaries, and product trust states |
| Business Contracts Advisory | source-shape radar for contracts that still need behavior-test replacements |
| Course picker screenshots | visual evidence artifact, not a merge blocker |
| Lighthouse | performance and accessibility visibility |
| Knip | dead-code discovery |
| Stryker | mutation-test signal |
| jscpd | duplicate-code radar |
| dependency-cruiser P2/P3 | architecture smell radar after P0/P1 boundaries are clean |
| OSV Scanner | vulnerability triage report until reachability policy is finalized |
| k6 staging load | staging reliability signal |
| Promptfoo | AI output drift signal |
| npm audit | dependency-risk triage |
| full sqlfluff | SQL style and risk signal |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Gitleaks reported 81 redacted secret findings

Severity: P0
Confidence: high
Source: gitleaks.json

## Evidence

- See ignored generated gitleaks report locally; do not commit raw secret output.

## Suggested Fix

Investigate and fix the production-readiness finding.
Loading
Loading