Skip to content

fix(csv): honor an explicit --coverage 80 on --input-csv runs - #2247

Draft
IAmJSD wants to merge 1 commit into
LeanerCloud:mainfrom
IAmJSD:fix/issue-2236
Draft

IAmJSD wants to merge 1 commit into
LeanerCloud:mainfrom
IAmJSD:fix/issue-2236

Conversation

@IAmJSD

@IAmJSD IAmJSD commented Oct 16, 2026

Copy link
Copy Markdown
Collaborator

Root cause

determineCSVCoverage (cmd/multi_service_csv.go) decided whether the user had asked for a coverage by comparing cfg.Coverage with the flag's default, 80.0. An explicit --coverage 80 matched that value, so it was treated as unset: the function returned 100, filterAndAdjustRecommendations skipped sizing (csvModeCoverage < 100 was false), and the run bought 100% of every CSV row's Count.

Fix

  • runTool (cmd/main.go) copies toolCfg and sets a new field, Config.CoverageFromDefault, to !cmd.Flags().Changed("coverage"). This is the same Changed() check validateTargetCoverage in cmd/validators.go already uses.
  • determineCSVCoverage returns 100 only when CoverageFromDefault is set. Otherwise it returns cfg.Coverage.
  • The field's zero value means "explicit". A Config built in code (all callers outside runTool are tests) has its Coverage honored as given. If a future entry point forgets to set the field, the run sizes to the given coverage and does not silently buy 100%.
  • docs/cli/README.md: the --coverage row now documents the CSV default.

There is no config file or environment variable for coverage. No viper is used, and the only os.Getenv calls are for Archera and DISABLE_PURCHASE_DELAY. So the precedence stays as before: an explicit flag wins, and with no flag a CSV run buys 100% and a non-CSV run uses the flag default of 80. determineCSVCoverage was the only code that relied on the 80 sentinel (grep 'Coverage == 80'). The non-CSV path never reads CoverageFromDefault.

Behavior matrix (--input-csv, dry run, rows with Count 10 and 20)

Invocation Before After
no --coverage 10, 20 (100%) 10, 20 (100%), unchanged
--coverage 80 / -c 80 / --coverage=80 10, 20 (100%) 8, 16 (80%)
--coverage 100 10, 20 10, 20
--coverage 50 5, 10 5, 10
--target-coverage N target sizing target sizing, unchanged
non-CSV runs cfg.Coverage cfg.Coverage, unchanged

Closes #2236

Verification

All of this ran locally on macOS against the worktree at 7579572b, with AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null. No real AWS calls were made and nothing was purchased.

  • New TestCSVCoverageFlagThroughRootCommand (cmd/csv_coverage_flag_test.go). Each case runs rootCmd.Execute() in a child test process, covering cobra flag parsing, validateFlags, runTool, runToolMultiService and the --input-csv dry run. It uses isolateAWSEnv's local 403 endpoint and asserts the per-row Count values in the written purchase report. Cases: no flag, --coverage 80, -c 80, --coverage=80, 100 and 50. A child process is used so the flag state cobra keeps on rootCmd cannot leak between cases.
  • TestDetermineCSVCoverage now has two cases: defaulted 80 returns 100, and explicit 80 returns 80.
  • Fail-before / mutation probes. Each mutant was applied in turn, then reverted:
    • Original Coverage == 80.0 sentinel: the three explicit-80 cases fail, plus the unit case. This is the fail-before.
    • runTool never sets the field: the no-flag case fails.
    • Inverted Changed(): the no-flag, explicit-80 and explicit-50 cases fail.
    • CSV 100% default removed: the no-flag case and the unit case fail.
  • go build ./cmd, go vet ./... and go test ./cmd/ -count=1 all pass.
  • golangci-lint v2.10.1 run --allow-parallel-runners ./... reports 0 issues.
  • gocyclo -over 10 on cmd/main.go and cmd/multi_service_csv.go: clean.
  • scripts/gofmt-hook.sh, make tidy-check and the trailing-whitespace/EOF checks on the changed files: clean.
  • git-secrets --scan on a temp copy of the tree with an isolated HOME and --register-aws: clean.
  • Not run locally: markdownlint and gosec (not installed here) and the repo-wide go test -short -race ./... (skipped for disk space). CI covers these.

Out of scope

  • On a CSV run with no --coverage, the dry-run commitment IDs still carry the 80pct label (effectiveSizingPct reads cfg.Coverage), even though 100% is bought. This label-only issue predates this PR.
  • This PR is coordinated with the open fix(cli): only print "CSV report written to" after a complete CSV write #2203, which touches cmd/multi_service.go, cmd/multi_service_csv.go and cmd/multi_service_csv_test.go. This PR does not change cmd/multi_service.go. The overlap in multi_service_csv.go is limited to determineCSVCoverage and one TestDetermineCSVCoverage case, so whichever lands second should rebase trivially.

Labels

Please mirror the labels from #2236 onto this PR: triaged, priority/p1, severity/high, urgency/this-sprint, impact/few, effort/s, type/bug. This PR was opened from a fork, so they were not applied here.

🤖 Generated with Claude Code

determineCSVCoverage compared cfg.Coverage against the flag's default
(80) to decide whether the user had asked for a coverage, so an explicit
--coverage 80 was indistinguishable from no flag and the CSV run bought
100% of every row's Count.

runTool now records cobra's Changed("coverage") in a new
Config.CoverageFromDefault field, and determineCSVCoverage returns 100
only when that is set. With no flag a CSV run still buys each row's full
Count; any explicit value, including 80, is applied. A Config built in
code leaves the field false, so its Coverage is honored as given. The
non-CSV path never reads the field.

Closes LeanerCloud#2236

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@cristim cristim added triaged Item has been triaged priority/p1 Next up; this sprint severity/high Significant harm urgency/this-sprint Within the current sprint impact/few Limited audience effort/s Hours type/bug Defect labels Oct 16, 2026
@cristim

cristim commented Oct 16, 2026

Copy link
Copy Markdown
Member

Independent review verdict: CLEAN at 7579572

Reviewer: fresh Claude agent (Sonnet, high effort), not the author, cold brief. The PR body was treated as a claim. Coordinator: claude-cc-reviewer.

Blocking findings: none.

--input-csv invocation Sizing
no flag 100% (unchanged)
--coverage 80, -c 80, --coverage=80 80% (the fix)
--coverage 100 100%
--coverage 50 50%
0, negative, >100 rejected by validateCoverageAndSavingsPct (cmd/validators.go:151-157); traced, not run

Detection:

  • cmd/main.go:401 uses cmd.Flags().Changed("coverage") on the root flag set (:116), the same pattern as validators.go:198.
  • No viper, config-file or env path sets coverage.
  • A nil cmd counts as default, and a hand-built Config fails toward sizing, not toward buying 100%.

Same sizing as the non-CSV path:

  • Rounding: both paths call applyCoverage → recfilter.ApplyCoverage (pkg/recfilter/sizing.go:28), so rounding is identical. Coverage 0 drops every row.
  • Order unchanged: coverage, then override-count, then scoreAndLimitCSVRecs (min-count, max-instances) (multi_service.go:931-944), then the replay dedupe. Coverage is applied once.
  • Non-CSV path untouched: determineCSVCoverage is the only reader of the new field.

Notes (non-blocking):

  1. docs/cli/README.md could warn that an explicit flag is applied on top of a CSV that was already coverage-adjusted when exported.
  2. Already present before this PR: purchase-ID labels say 80pct on a no-flag CSV run that buys 100% (main.go:289). Filed as fix(csv): purchase IDs say 80pct on --input-csv runs that buy 100% #2248.

What was attacked and how (AWS_CONFIG_FILE=/dev/null, no purchases):

  • go vet ./cmd/... is clean, and go test -race -count=1 ./cmd/... passes 1550.
  • TestCSVCoverageFlagThroughRootCommand (6 subtests, run through the real root command) and TestDetermineCSVCoverage pass.
  • Mutation probes, all killed:
    • The old Coverage==80 logic fails the 3 explicit-80 subtests (this is the fail-before).
    • Always applying coverage fails the no-flag case.
    • Never applying it fails the explicit-80 cases.

Gate:

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/s Hours impact/few Limited audience priority/p1 Next up; this sprint severity/high Significant harm triaged Item has been triaged type/bug Defect urgency/this-sprint Within the current sprint

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(csv): explicit --coverage 80 is treated as unset on --input-csv runs and buys 100%

2 participants