Skip to content

Add advisory business contract test lane - #357

Closed
njrini99-code wants to merge 1 commit into
cleanup/comprehensive-code-auditfrom
test-hardening/golf-stats-coachhelm-contracts
Closed

Add advisory business contract test lane#357
njrini99-code wants to merge 1 commit into
cleanup/comprehensive-code-auditfrom
test-hardening/golf-stats-coachhelm-contracts

Conversation

@njrini99-code

@njrini99-code njrini99-code commented Jun 29, 2026

Copy link
Copy Markdown
Owner

Summary

  • adds an advisory Vitest business-contract lane for GolfHelm stats, CoachHelm truth/evidence, product-trust states, access scope, and import normalization
  • documents the discovery map, living business contract matrix, and follow-up issue drafts for needs-decision items
  • wires Business contracts into CI as advisory only, without adding it to the hard aggregate gate

Verification

  • npm run test:business
  • npm run typecheck
  • npm run test:unit -- src/lib/golf/tests/stat-formulas.test.ts src/lib/coachhelm/v3/ranking/score.test.ts
  • npx actionlint .github/workflows/ci.yml
  • git diff --check

Greptile Summary

This PR introduces an advisory Vitest business project that runs *.contract.test.ts files covering GolfHelm stats formulas, CoachHelm metric direction/evidence/budget, product-trust UI states, access/RLS boundaries, and import normalization — wired into CI as a non-blocking continue-on-error job that is explicitly excluded from the hard all gate.

  • CI wiring is correct: business-contracts job uses continue-on-error: true, is absent from the all job's needs array, and uses the same action versions as every other job in ci.yml.
  • Contract coverage is well-scoped: The matrix honestly marks most contracts as starter or missing, four open DECISION-* items are tracked, and the discovery document clearly separates safe tests added now from tests needing adapters or product decisions.
  • Two structural concerns: The business vitest project omits extends: true (requiring manual alias duplication that won't auto-inherit future root changes), and the source-sniffing approach in states.contract.test.ts and golf-access.contract.test.ts will generate false advisory failures on harmless string-literal refactors.

Confidence Score: 4/5

Safe to merge — the advisory lane is correctly wired and cannot block merges.

The CI integration, matrix documentation, and CoachHelm/stats contract logic are all sound. The two structural concerns — the vitest project skipping extends: true and the source-sniffing approach in the states/access contracts — are real maintenance risks but don't affect production behavior. The mislabeled "null-honest" assertion on normalizeToYards(null) documents an existing coercion that may mislead future maintainers about what null-honesty guarantees for normalization utilities.

vitest.config.ts (business project config), src/contracts/imports/normalization.contract.test.ts (null-honesty label), src/contracts/product-trust/states.contract.test.ts and src/contracts/access/golf-access.contract.test.ts (source-sniffing fragility).

Important Files Changed

Filename Overview
.github/workflows/ci.yml Adds an advisory business-contracts job with continue-on-error: true; correctly omitted from the all hard-gate needs array and uses the same action versions as every other job in the file.
vitest.config.ts Adds the business project with its own plugins and resolve block instead of extends: true; future root-level alias additions won't be inherited automatically, unlike the other three projects.
src/contracts/golf/stats.contract.test.ts Solid contracts for GH-STATS-001–009: null-denominator guards, 18-hole normalization, 3-putt detection, scrambling GIR gating, and SG baseline explicitness; fixtures use raw shot types ('putt', not 'putting') which assumes the stat calculator normalizes internally.
src/contracts/coachhelm/truth.contract.test.ts Well-formed contracts for CH-AI-001–009: direction parity with METRIC_RENDER_CONFIG, improvement sign polarity, citation verification, sample damping, bounds checks, and budget metadata coverage.
src/contracts/imports/normalization.contract.test.ts Covers unit normalization and bucket stability; the TRUST-003 test is mislabeled "null-honest" but asserts normalizeToYards(null, 'feet') returns 0, documenting null-to-zero coercion that contradicts the null-honesty contract definition.
src/contracts/product-trust/states.contract.test.ts Uses readFileSync + toContain to detect product-trust state invariants; will break on harmless string-literal refactors without real regressions — acceptable as starter status but carries a maintenance burden when promoted.
src/contracts/access/golf-access.contract.test.ts Same source-sniffing pattern as states.contract.test.ts; correctly verifies golf_team_coach_staff usage and cross-tenant RLS test presence but will false-positive on code reformatting.
package.json Adds test:business and verify:business scripts; verify:business is a thin alias for test:business with no additional checks (typecheck, lint), consistent with the advisory-only intent.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    PR[PR Push] --> CI[CI Jobs]
    CI --> HARD[Hard Gate Jobs]
    CI --> ADV[Advisory Jobs]
    HARD --> typecheck[TypeScript]
    HARD --> lint[Lint / Lint Ratchet]
    HARD --> unit[Unit Tests]
    HARD --> build[Build]
    HARD --> rls[Supabase RLS]
    HARD --> dbTypes[DB Types Drift]
    HARD --> schema[Schema Invariants]
    HARD --> knowledge[Feature Knowledge]
    HARD --> ALL{all gate}
    ALL -->|required checks pass| MERGE[✓ Merge allowed]
    ALL -->|any fail| BLOCK[✗ Blocked]
    ADV --> BC[business-contracts\ncontinue-on-error: true]
    BC --> BT[npm run verify:business\nvitest run --project business]
    BT --> SC1[golf/stats.contract.test.ts\nGH-STATS-001…009]
    BT --> SC2[coachhelm/truth.contract.test.ts\nCH-AI-001…009]
    BT --> SC3[product-trust/states.contract.test.ts\nTRUST-001,004,005]
    BT --> SC4[access/golf-access.contract.test.ts\nCH-AI-010]
    BT --> SC5[imports/normalization.contract.test.ts\nTRUST-003]
    BC -->|fail| SIGNAL[Advisory signal only\nNo merge block]
    BC -->|pass| INFO[Radar green]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    PR[PR Push] --> CI[CI Jobs]
    CI --> HARD[Hard Gate Jobs]
    CI --> ADV[Advisory Jobs]
    HARD --> typecheck[TypeScript]
    HARD --> lint[Lint / Lint Ratchet]
    HARD --> unit[Unit Tests]
    HARD --> build[Build]
    HARD --> rls[Supabase RLS]
    HARD --> dbTypes[DB Types Drift]
    HARD --> schema[Schema Invariants]
    HARD --> knowledge[Feature Knowledge]
    HARD --> ALL{all gate}
    ALL -->|required checks pass| MERGE[✓ Merge allowed]
    ALL -->|any fail| BLOCK[✗ Blocked]
    ADV --> BC[business-contracts\ncontinue-on-error: true]
    BC --> BT[npm run verify:business\nvitest run --project business]
    BT --> SC1[golf/stats.contract.test.ts\nGH-STATS-001…009]
    BT --> SC2[coachhelm/truth.contract.test.ts\nCH-AI-001…009]
    BT --> SC3[product-trust/states.contract.test.ts\nTRUST-001,004,005]
    BT --> SC4[access/golf-access.contract.test.ts\nCH-AI-010]
    BT --> SC5[imports/normalization.contract.test.ts\nTRUST-003]
    BC -->|fail| SIGNAL[Advisory signal only\nNo merge block]
    BC -->|pass| INFO[Radar green]
Loading

Comments Outside Diff (2)

  1. src/contracts/imports/normalization.contract.test.ts, line 688-689 (link)

    P2 Null-honesty label contradicts the zero-return assertion

    The test is named "null-honest" but then asserts normalizeToYards(null, 'feet') returns 0. Returning 0 for a null input is the opposite of null-honesty as defined by GH-STATS-006 — it silently converts missing data to a zero value. Any downstream function that receives this 0 as a distance_to_hole_before value might produce a non-null SG result instead of propagating the missing-data signal. The assertion documents an existing behavior that should be explicitly noted as an intentional internal conversion (for mathematical purposes) rather than a general null-honesty guarantee, or the test name should be corrected so future readers aren't misled into treating normalizeToYards(null) as compliant with the null-honesty contract.

    Fix in Claude Code

  2. src/contracts/product-trust/states.contract.test.ts, line 727-758 (link)

    P2 Source-sniffing via readFileSync + toContain is brittle for contract tests

    All three tests in this file (and similarly in src/contracts/access/golf-access.contract.test.ts) detect product-trust invariants by reading raw source files and asserting that specific string literals exist. An advisory lane failure from this approach can be triggered by an entirely safe refactor — e.g., renaming "Something Went Wrong" to "An Error Occurred" or reformatting the setStatsError call — even when the underlying state separation is preserved. This creates noisy advisory failures that aren't genuine product-trust regressions, which is exactly the kind of radar noise the issue drafts warn about. The approach is noted as starter status in the matrix, so this isn't a blocker, but the maintenance cost should be tracked when these contracts are promoted toward covered.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

    Fix in Claude Code

Fix All in Claude Code

Reviews (1): Last reviewed commit: "Add advisory business contract test lane" | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

@vercel

vercel Bot commented Jun 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
helmv3 Ready Ready Preview, Comment Jun 29, 2026 8:32pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (3)
  • main
  • develop
  • release/*

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d13f7d41-5e66-4902-8664-e6d9ddf7fc5c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch test-hardening/golf-stats-coachhelm-contracts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Comment thread vitest.config.ts
Comment on lines +104 to +119
{
plugins: [react()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'server-only': path.resolve(__dirname, './src/test/stubs/server-only.ts'),
},
},
test: {
...sharedTestConfig,
name: 'business',
include: ['src/**/*.contract.test.{ts,tsx}'],
exclude: ['node_modules', '.next'],
testTimeout: 30_000,
},
},

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 Business project doesn't use extends: true — won't auto-inherit root alias changes

The unit, integration, and rls projects all specify extends: true and rely on the root-level plugins and resolve.alias config. The business project instead duplicates those entries manually. If a new alias is added at the root level (e.g., a future stub for a Next.js internal), the business project won't pick it up automatically, causing Module not found failures that appear only in the advisory contract lane. Using extends: true and omitting the manually duplicated plugins and resolve block would keep the business project consistent with the other three projects and avoid this silent maintenance trap.

Fix in Claude Code

@njrini99-code

Copy link
Copy Markdown
Owner Author

Kept open through the 2026-07-03 PR triage — the contract-test lane is worth landing, but the branch now conflicts with current main in .circleci/config.yml + CODEOWNERS. Needs a rebase onto main, then it can ride the next merge train.

@njrini99-code

Copy link
Copy Markdown
Owner Author

Superseded by the clean-slate + unification trains (#740/#762/#763/#768).

@njrini99-code
njrini99-code deleted the test-hardening/golf-stats-coachhelm-contracts branch July 20, 2026 20:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant