Skip to content

fix: fail-closed capture surface (iss-29) - #41

Merged
REPPL merged 3 commits into
mainfrom
fix/iss-29-fail-closed-capture-surface
Jul 12, 2026
Merged

fix: fail-closed capture surface (iss-29)#41
REPPL merged 3 commits into
mainfrom
fix/iss-29-fail-closed-capture-surface

Conversation

@REPPL

@REPPL REPPL commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Fixes ledger issue iss-29 fail-closed-capture-surface — three coupled instances behind the unrecognized-input-never-writes detector, each armed with a watched-fail acceptance test.

What was broken

  1. A typo'd subcommand became a ledger mutation. abcd capture resovle iss-1 noteresovle being a typo for resolve — fell through the capture parent's arbitrary-args RunE and was filed as a brand-new issue.
  2. --json errors emitted raw Go text, not JSON — a machine caller could not parse a failure the way it parses a success.
  3. docs lint with no config leaked the absolute config path in a raw os.ReadFile error.

The fix

  • suspectedTypoedSubcommand refuses a mistyped subverb with a did-you-mean and writes nothing. It is deliberately high-precision: it fires only when the first token is a near-miss (edit distance 1–2) to a real subverb and the shape looks like a subcommand call — a lone token, or a token followed by an iss-N id — so genuine multi-word free-text captures still file.
  • A new cli.Run(args, stdout, stderr) int seam (extracted from main.go in a separate behaviour-preserving refactor commit) renders the error diagnostic as a {"error": …} envelope under --json.
  • docs lint now reports a clean, repo-relative diagnostic for a missing or unreadable config; a *PathError's inner cause is reported without its path.

Detector-first evidence

internal/surface/cli/capture_surface_test.go — 5 acceptance tests (typo-never-writes, free-text-still-writes guard, --json error shape, missing-config clean error, unreadable-config no-path-leak) watched fail before the fix, green after. make preflight clean (build, gofmt, vet, test, -race).

Review

  • Correctness (ruthless-reviewer): PROMOTE.
  • Security (security-reviewer): initial BLOCK — the non-not-exist docs-lint branch still leaked the abspath via *PathError; fixed (strip the path-bearing wrapper) and re-reviewed PASS.

Scope split

The reviewers noted a systemic version of instance 3: cli.Run now routes all command errors through the --json envelope, so any verb returning a bare *fs.PathError widens a pre-existing stderr-text abspath leak into --json. That is out of iss-29's scope and pre-existing, so it is filed separately as iss-76 rather than fixed here.

Commits

  • refactor: extract cli.Run for testable error rendering (behaviour-preserving)
  • fix: fail-closed capture surface (iss-29), resolve folded into the branch
  • chore: capture iss-76

Do not merge / no auto-merge — maintainer merges fix: PRs.

Assisted-by: Claude:claude-opus-4-8

REPPL added 3 commits July 12, 2026 15:46
Move the error->exit-code mapping out of main and into a
cli.Run(args, stdout, stderr) int seam so the front door's error
surface is injectable and testable. Behaviour-preserving: main becomes
a one-line delegate and the raw-text diagnostic is unchanged.

Assisted-by: Claude:claude-opus-4-8
Three coupled instances behind the unrecognized-input-never-writes
detector, each with a watched-fail acceptance test:

- A mistyped capture subcommand (e.g. `capture resovle iss-1 note`) was
  swallowed as free text and filed as a new issue. suspectedTypoedSubcommand
  now refuses it with a did-you-mean and writes nothing. The guard is
  high-precision — it fires only on a near-miss token that is alone or
  followed by an iss-id, so genuine free-text prose still files.
- Errors requested with --json emitted raw Go text. cli.Run now renders the
  diagnostic as a {"error": ...} JSON envelope under --json.
- `docs lint` with a missing/unreadable config surfaced a raw os.ReadFile
  error that leaked the absolute config path. It now reports a clean,
  repo-relative diagnostic; a *PathError's inner cause is reported without
  its path.

Resolves iss-29 (folded into the branch). The systemic PathError-into-json
leak across other verbs is split out as iss-76.

Assisted-by: Claude:claude-opus-4-8
Filed during the iss-29 security review: cli.Run routes all command
errors through the --json envelope, so any verb returning a bare
*fs.PathError emits the absolute local path into machine JSON. iss-29
sanitised the docs-lint branch; the systemic boundary fix is iss-76.

Assisted-by: Claude:claude-opus-4-8
@REPPL
REPPL merged commit 4c30b4e into main Jul 12, 2026
12 checks passed
@REPPL
REPPL deleted the fix/iss-29-fail-closed-capture-surface branch July 12, 2026 15:31
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