Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 20 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,26 @@ jobs:
if: always()
run: supabase stop || true

business-contracts:
name: Business contracts
runs-on: ubuntu-latest
continue-on-error: true
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 advisory business contract lane
run: npm run verify:business

all:
name: all
runs-on: ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions docs/operations/BRANCH_PROTECTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ 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`
- `Course picker screenshots`
- CircleCI weekly radar jobs
- Lighthouse preview jobs
Expand Down
93 changes: 93 additions & 0 deletions docs/operations/BUSINESS_CONTRACT_ISSUE_DRAFTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Business Contract Issue Drafts

These are ready-to-file follow-up issues created from `docs/operations/BUSINESS_CONTRACT_MATRIX.md`. They are drafts rather than opened issues because the repo has not yet decided whether the advisory Business Contracts lane should auto-create GitHub issues.

## Issue Draft: Decide canonical SG baseline surface

Labels: `area:golfhelm`, `type:product-truth`, `needs-decision`

### Problem

`src/lib/golf/sg-benchmarks.ts` exposes legacy `BenchmarkLevel` metadata for NCAA/scratch/break scales while the runtime `SG_BASELINE_OPTIONS` contract exposes only `pga_tour` and `womens`.

### Decision Needed

Decide whether legacy benchmark metadata is still supported UI/reference metadata, or whether it should be removed/renamed so the runtime baseline contract is unambiguous.

### Risk

Strokes-gained comparisons can become mathematically meaningless if a player/team view mixes incompatible baselines or implies support for a baseline the cache does not use.

### Acceptance Criteria

- The product decision is recorded in `docs/operations/BUSINESS_CONTRACT_MATRIX.md`.
- If legacy metadata remains, comments/tests explain it is not a runtime SG baseline choice.
- If legacy metadata is removed, affected UI/actions/tests are updated in a focused PR.

## Issue Draft: Decide canonical CoachHelm metric alias policy

Labels: `area:coachhelm`, `type:product-truth`, `needs-decision`

### Problem

`src/lib/coachhelm/v3/metrics/registry.ts` has canonical v3 metric IDs and a legacy alias map. The business contracts can verify direction parity for canonical metrics today, but cannot decide whether all generators must emit canonical IDs.

### Decision Needed

Decide whether v2 legacy metric aliases are permanently supported product inputs or a migration bridge that should shrink over time.

### Risk

CoachHelm may learn or explain trends backward if an alias lacks the right lower-is-better/higher-is-better direction.

### Acceptance Criteria

- Alias policy is documented.
- Business contracts either require all emitted metrics to be canonical or verify every allowed alias has direction coverage.
- Any generator emitting an unknown metric fails the advisory lane or records a decision item.

## Issue Draft: Decide advisory issue automation

Labels: `repo-hygiene`, `type:automation`, `needs-decision`

### Problem

The Business Contracts lane is advisory. The mission asks for a radar that reveals issues and creates high-quality follow-up issues, but the repo has not decided whether CI should open GitHub issues automatically.

### Decision Needed

Choose one:

- Keep in-repo issue drafts only.
- Add a manual script that files selected drafts.
- Add CI automation that opens/updates issues with labels and duplicate detection.

### Risk

Without a clear policy, advisory failures may either spam the tracker or never become actionable work.

### Acceptance Criteria

- The chosen policy is recorded in `docs/operations/BUSINESS_CONTRACT_MATRIX.md`.
- If automation is chosen, labels, duplicate keys, and permissions are defined before enabling it in CI.

## Issue Draft: Decide missing-putts aggregate semantics

Labels: `area:golfhelm`, `type:product-truth`, `needs-decision`

### Problem

When a hole row exists with `putts = null`, the shot aggregate preserves scoring null-honesty but currently reports `puttsPerRound` as `0` in at least one no-putt-data fixture. The product contract needs to decide whether that is a valid "zero recorded putts" value or should be `null` to mean "putting data missing."

### Decision Needed

Decide whether missing putting data should remain `null` across per-round aggregates, or whether `0` is acceptable when no putts are recorded.

### Risk

Missing putting data can look like a healthy zero-value stat, which conflicts with the no fake healthy empty-state goal.

### Acceptance Criteria

- The chosen behavior is recorded in `docs/operations/BUSINESS_CONTRACT_MATRIX.md`.
- A business contract is added for `puttsPerRound` once the product rule is settled.
54 changes: 54 additions & 0 deletions docs/operations/BUSINESS_CONTRACT_MATRIX.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Business Contract Matrix

Living map of business rules protected by the advisory Business Contracts lane.

Status values: `missing`, `starter`, `covered`, `needs decision`.

## GolfHelm Stats Contracts

| Contract ID | Product area | Rule | Why it matters | Source of truth | Test file | Status | Gate | Risk if broken |
|---|---|---|---|---|---|---|---|---|
| GH-STATS-001 | GolfHelm stats | Round gross score must equal the sum of hole scores when hole-level scores are present. | Coach/player dashboards show wrong scoring. | Existing pure function: `calculateStatsFromShots`; explicit rule in prompt | `src/contracts/golf/stats.contract.test.ts` | starter | advisory | Coach/player dashboards show wrong scoring. |
| GH-STATS-002 | GolfHelm stats | Score-to-par must equal gross score minus course par when course par is known. | Leaderboards and summaries become untrustworthy. | Existing pure function: `calculateStatsFromShots`; obvious golf invariant; explicit rule in prompt | `src/contracts/golf/stats.contract.test.ts` | starter | advisory | Leaderboards and summaries become untrustworthy. |
| GH-STATS-003 | GolfHelm putting | A 3-putt is counted when a hole has 3 or more putts. | Putting stats and CoachHelm putting insights stay truthful. | Existing pure function: `calculateHoleStatsFromShots`; explicit rule in prompt | `src/contracts/golf/stats.contract.test.ts` | covered | advisory | Putting stats and CoachHelm putting insights become wrong. |
| GH-STATS-004 | GolfHelm short game | Scrambling opportunity exists only when GIR is missed and the player can still save par or better. | Short-game stats should reflect real up-and-down chances. | Existing pure function: `calculateHoleStatsFromShots`; explicit rule in prompt | `src/contracts/golf/stats.contract.test.ts` | starter | advisory | Short-game stats become inflated or meaningless. |
| GH-STATS-005 | GolfHelm short game | Scrambling success cannot be counted on GIR holes. | GIR pars should not inflate short-game performance. | Existing pure function: `calculateHoleStatsFromShots`; explicit rule in prompt | `src/contracts/golf/stats.contract.test.ts` | covered | advisory | Short-game performance is overstated. |
| GH-STATS-006 | GolfHelm data honesty | Missing/null shot data cannot be treated as zero performance. | Missing facts must stay visibly missing. | Existing pure functions: `stat-formulas`, `calculateStrokesGainedForShot`; feature doc: `memory/features/golf-round-lifecycle.md`; explicit rule in prompt | `src/contracts/golf/stats.contract.test.ts` | covered | advisory | Dashboards look healthy while data is missing. |
| GH-STATS-007 | GolfHelm stats | Round summary stats must not contradict shot-level facts. | Raw shot facts and summaries must agree. | Existing pure function: `calculateStatsFromShots`; feature doc: `memory/features/golf-round-lifecycle.md`; explicit rule in prompt | `src/contracts/golf/stats.contract.test.ts` | starter | advisory | Coaches lose trust in stats. |
| GH-STATS-008 | GolfHelm strokes gained | Strokes-gained calculations must not mix incompatible baselines. | SG values are only meaningful against one baseline at a time. | Existing product constants: `SG_BASELINE_OPTIONS`, `sgBaselineScale`; explicit rule in prompt | `src/contracts/golf/stats.contract.test.ts` | starter | advisory | SG values become mathematically meaningless. |
| GH-STATS-009 | GolfHelm baselines | PGA/LPGA/team baseline choice must be explicit, not accidental fallback. | Player comparisons need clear context. | Existing product constants: `defaultSgBaseline`, `effectiveSgBaseline`; existing tests: `sg-baseline-options.test.ts`; explicit rule in prompt | `src/contracts/golf/stats.contract.test.ts` | starter | advisory | Player comparisons become wrong. |
| GH-STATS-010 | GolfHelm stat cache | Stat cache output must match raw round/shot facts for the same player/team/time period. | Cache drift undermines coach dashboards. | DB schema/RLS policies and cache functions: `src/lib/cache/golf-stats-calculator.ts`; explicit rule in prompt | Not yet added | missing | future hard gate | Coach dashboard displays stale or incorrect stats. |

## CoachHelm Truth Contracts

| Contract ID | Product area | Rule | Why it matters | Source of truth | Test file | Status | Gate | Risk if broken |
|---|---|---|---|---|---|---|---|---|
| CH-AI-001 | CoachHelm metric direction | Lower-is-better metrics improve when value decreases. | Polarity must not invert. | Existing product constant: `METRIC_DIRECTION`; explicit rule in prompt | `src/contracts/coachhelm/truth.contract.test.ts` | covered | advisory | CoachHelm tells coaches the opposite of reality. |
| CH-AI-002 | CoachHelm metric direction | Higher-is-better metrics improve when value increases. | Strengths/weaknesses must classify correctly. | Existing product constant: `METRIC_DIRECTION`; explicit rule in prompt | `src/contracts/coachhelm/truth.contract.test.ts` | covered | advisory | Strength/weakness classification flips. |
| CH-AI-003 | CoachHelm insight truth | CoachHelm cannot label a worsening metric as an improvement. | Improvement signs must respect metric polarity. | Existing pure function: `improvementSign`; explicit rule in prompt | `src/contracts/coachhelm/truth.contract.test.ts` | starter | advisory | AI becomes confidently wrong. |
| CH-AI-004 | CoachHelm evidence | Every insight must include supporting evidence or explicitly say insufficient data. | Evidence-backed claims are the trust contract. | Feature doc: `memory/features/coachhelm-ai.md`; existing pure function: `verifyCitations`; explicit rule in prompt | `src/contracts/coachhelm/truth.contract.test.ts` | starter | advisory | AI invents unsupported claims. |
| CH-AI-005 | CoachHelm confidence | Prediction/confidence requires a minimum sample size or must be marked low confidence. | Thin samples should not look authoritative. | Existing pure functions: `calcConfidence`, `sampleDamping`; explicit rule in prompt | `src/contracts/coachhelm/truth.contract.test.ts` | starter | advisory | CoachHelm overstates weak evidence. |
| CH-AI-006 | CoachHelm ranking | Composite/score outputs must remain within legal bounds. | Ranking values must stay finite and sane. | Existing pure functions: `scoreInsight`, `cappedStrokesImpact`; explicit rule in prompt | `src/contracts/coachhelm/truth.contract.test.ts` | covered | advisory | UI and rankings become nonsensical. |
| CH-AI-007 | CoachHelm NLG | LLM/NLG output cannot add factual claims not present in the evidence payload. | Generated prose must remain grounded. | Existing pure function: `verifyCitations`; feature doc: `memory/features/coachhelm-ai.md`; explicit rule in prompt | `src/contracts/coachhelm/truth.contract.test.ts` | starter | advisory | Hallucinated coach advice. |
| CH-AI-008 | CoachHelm UI state | If CoachHelm generation fails, UI must show a failure state, not fake insight. | Failed AI should be visible. | UI behavior: route error boundaries; server action meta result; explicit rule in prompt | `src/contracts/product-trust/states.contract.test.ts` | starter | advisory | Silent AI failure. |
| CH-AI-009 | CoachHelm budget | Budget/usage limits must prevent generation when exceeded. | Cost controls must be enforced server-side. | Existing pure constants and server helper: `FALLBACK_PRIORITY`, `checkBudget`; explicit rule in prompt | `src/contracts/coachhelm/truth.contract.test.ts` | starter | advisory | Runaway AI cost. |
| CH-AI-010 | CoachHelm access | Coach/player/team scope must be preserved in every CoachHelm insight. | Insight data is tenant scoped. | DB schema/RLS policy; feature doc: `memory/features/coachhelm-ai.md`; explicit rule in prompt | `src/contracts/access/golf-access.contract.test.ts` | starter | advisory | One team's data leaks into another team's analysis. |

## Product Trust Contracts

| Contract ID | Product area | Rule | Why it matters | Source of truth | Test file | Status | Gate | Risk if broken |
|---|---|---|---|---|---|---|---|---|
| TRUST-001 | Stats UI | Failed stat load cannot render a healthy empty state. | Users need to know the system failed. | UI behavior: stats client error branch; explicit rule in prompt | `src/contracts/product-trust/states.contract.test.ts` | starter | advisory | Users think there is no data instead of a system error. |
| TRUST-002 | Save flows | Failed save cannot show "Saved." | Save confirmation must reflect persistence. | Explicit rule in prompt; current source needs adapter | Not yet added | missing | advisory | User trust breaks. |
| TRUST-003 | Imports | Partial import failure must surface failed rows. | Imports must not hide corrupt/missing data. | Explicit rule in prompt; normalization helpers | `src/contracts/imports/normalization.contract.test.ts` | starter | advisory | Data silently corrupts. |
| TRUST-004 | Empty states | Empty success and failed load are different states. | Empty data and outage are different outcomes. | UI behavior: stats client error branch before empty branch; explicit rule in prompt | `src/contracts/product-trust/states.contract.test.ts` | starter | advisory | UI hides outages. |
| TRUST-005 | CoachHelm availability | CoachHelm unavailable must not show stale/generated-looking new advice. | AI failures must not masquerade as current insight. | UI behavior: CoachHelm route error boundaries; explicit rule in prompt | `src/contracts/product-trust/states.contract.test.ts` | starter | advisory | False AI trust. |

## Needs Decision

| Contract ID | Product area | Rule | Why it matters | Source of truth | Test file | Status | Gate | Risk if broken |
|---|---|---|---|---|---|---|---|---|
| DECISION-001 | GolfHelm SG baselines | Decide whether legacy `BenchmarkLevel` metadata for NCAA/scratch/break levels remains valid while runtime SG options are PGA/LPGA only. | Prevents accidental mixed-scale SG comparisons. | Existing constants disagree in `src/lib/golf/sg-benchmarks.ts` comments and exports | Not yet added | needs decision | advisory | SG UI or comparisons may imply unsupported baselines. |
| DECISION-002 | CoachHelm metric registry | Decide whether v2 legacy metric aliases are permanently allowed or must migrate to canonical v3 metric IDs. | Determines how strict direction/evidence contracts can be. | `METRIC_DIRECTION_ALIASES`, `memory/features/coachhelm-ai.md` | Not yet added | needs decision | advisory | Direction bugs hide behind alias drift. |
| DECISION-003 | Issue automation | Decide whether advisory failures should create real GitHub issues automatically or only generate issue drafts. | Avoids spam while still turning radar signals into work. | Explicit rule in prompt | Not yet added | needs decision | advisory | Radar signals may not become actionable follow-up. |
| DECISION-004 | GolfHelm putting | Decide whether `puttsPerRound` should be `null` or `0` when holes exist but putt values are all null. | Determines whether missing putting data can look like zero putting volume. | Existing aggregate behavior in `calculateStatsFromShots`; explicit no-fake-empty rule in prompt | Not yet added | needs decision | advisory | Missing putting data may appear healthy or complete. |
1 change: 1 addition & 0 deletions docs/operations/GATE_MATRIX.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ This file is the current map of hard merge blockers versus 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 |
| Course picker screenshots | visual evidence artifact, not a merge blocker |
| Lighthouse | performance and accessibility visibility |
| Knip | dead-code discovery |
Expand Down
Loading
Loading