Skip to content

fix(verify): skipped results must not demote; add --no-demote raise-only mode#129

Merged
rhino11 merged 3 commits into
mainfrom
fix/verify-results-skip-and-partial
Jul 5, 2026
Merged

fix(verify): skipped results must not demote; add --no-demote raise-only mode#129
rhino11 merged 3 commits into
mainfrom
fix/verify-results-skip-and-partial

Conversation

@rhino11

@rhino11 rhino11 commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Summary

rtmx verify --results silently corrupted requirement status on the cross-language path: a skipped test demoted a COMPLETE requirement to PARTIAL and made verify exit non-zero. This PR fixes that, adds a raise-only --no-demote mode for sharded/partial CI, and fixes a related git-worktree env-leak bug that made the repo's own pre-commit hook flaky.

Found while wiring results-based promotion in a downstream consumer (the Phoenix radar RTM, which uses completeness: {policy: combinations, min_combinations: 3}): requirements with three passing (scope,technique) combos were being demoted. rtmx's own dogfood RTM doesn't use the combinations policy, so the demotion direction was never exercised.

The bug (REQ-VERIFY-012)

The results Result type had no representation for skip: the decoder folded status:"skip" — and any record supplying neither passed nor status — into Passed=false. Then:

  • determineStatusWithPolicy counted every non-passing record as failed, so under require_all_pass (default) branch (A) demoted COMPLETE→PARTIAL on a single skip;
  • the shared count loop put skips in TestsFailed, so rtmx verify --results exited 1 even without --update.

This is asymmetric with the native go-test path (determineNewStatus), which keeps current status on a skip, and with from-pytest, which omits skips. Sharded/matrix CI that skips a test in one leg (e.g. a config variant) therefore corrupted status on every run.

Fix: add a Skipped tri-state to Result; decode status:"skip"/"skipped", explicit "skipped":true, and absent-outcome records as skipped (non-evidence) — not a hard decode error, so one odd record can't abort a best-effort file. Both verify count loops treat skip as non-evidence (TestsSkipped, never TestsFailed). A genuine failure still downgrades.

New: --no-demote raise-only mode (REQ-VERIFY-013)

Even with skips ignored, a partial result set legitimately carries fewer combos for a requirement than its full validation (other combos ran in another leg). --no-demote treats a result set as additive evidence: it computes the new status, then keeps whichever of {current, computed} is more complete (Status.Weight) — raising freely, never lowering. Default behavior unchanged.

Also: git-worktree env leak

internal/orchestration/{Create,Remove}Worktree shelled out to git worktree with the environment inherited verbatim. When GIT_DIR/GIT_INDEX_FILE are exported (as git does for hooks), git worktree add resolved the new worktree against the parent repo and failed (fatal: .git/index: index file open failed). This made the worktree tests — and thus the repo's own pre-commit hook (go test ./...) — fail when run from a git-driven context. Fixed by stripping the per-invocation GIT_* vars before invoking git.

Tests

Adds the demotion-direction cases the suite never had (all prior combos cases used current: MISSING):

  • COMPLETE + 3 combos stays COMPLETE (idempotent); COMPLETE + 3 combos + skip stays COMPLETE; COMPLETE + 3 combos + real fail → PARTIAL (regression guard); only-skips keeps current.
  • decoder: status:"skip"/"skipped"/"skipped":true/no-outcome → Skipped; status:"fail" unchanged.
  • TestClampNoDemote for the raise-only rule.

Verified end-to-end against the real downstream data: the old binary demotes REQ-VAL-015/REQ-SEC-002 and exits 1; this branch leaves them COMPLETE and exits 0.

Commits

  1. fix(orchestration): don't leak GIT_* env into git worktree subcommands
  2. fix(verify): treat skipped results as non-evidence on the --results path (REQ-VERIFY-012)
  3. feat(verify): add --no-demote raise-only mode (REQ-VERIFY-013)

🤖 Generated with Claude Code

rhino11 and others added 3 commits July 5, 2026 13:15
CreateWorktree/RemoveWorktree shelled out to `git worktree` with the process
environment inherited verbatim. Git exports GIT_DIR, GIT_INDEX_FILE, and
GIT_WORK_TREE into hook and alias subprocesses, and when they leak into a
`git worktree add` child git resolves the new worktree's git dir/index against
the PARENT repository and fails ("fatal: .git/index: index file open failed:
Not a directory"). This makes the worktree helpers — and the repo's own
pre-commit hook, which runs `go test ./...` including the worktree tests —
fail whenever rtmx is invoked from a git-driven context on a git that exports
those variables.

Strip the per-invocation GIT_* variables before invoking git so it computes
the worktree's own paths from cmd.Dir. The existing worktree tests now pass
under a hook-like environment.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A skipped test result carried no representation in the cross-language results
schema: Result exposed only Passed bool, and the decoder folded status:"skip"
(and any record supplying neither passed nor status) into Passed=false. The
--results status engine then counted every non-passing record as a failure, so
under require_all_pass (the default) a single skipped test DEMOTED a COMPLETE
requirement to PARTIAL, and the shared count loop put the skip in TestsFailed —
making `rtmx verify --results` exit non-zero even without --update.

This was asymmetric with the native go-test path (determineNewStatus), which
keeps current status on a skip. Sharded/matrix CI that skips a test in one leg
therefore silently corrupted status on every run.

Add a Skipped tri-state to Result and decode status:"skip"/"skipped", an
explicit "skipped":true, and an absent-outcome record as skipped (non-evidence)
rather than a silent failure — deliberately not a hard decode error, so one
odd record cannot abort a whole best-effort results file. Both verify count
loops now treat skip as non-evidence: it neither promotes nor demotes and is
counted as TestsSkipped, not TestsFailed. A genuine failure still downgrades.

Adds REQ-VERIFY-012 and the demotion-direction tests that were missing
(COMPLETE + N combos + a skip stays COMPLETE; decoder skip/absent-outcome
cases), which the prior suite never covered — it only tested forward promotion
from MISSING, so the demotion regression shipped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ult sets

Even with skipped results correctly ignored (REQ-VERIFY-012), a partial or
sharded result set legitimately carries fewer passing (scope, technique)
combinations for a requirement than its full validation does — because the
other combinations ran in a different CI leg or an environment filtered out of
this run. Under the combinations policy the engine correctly returns PARTIAL
for that partial view, so `--update` would demote a requirement that is
genuinely COMPLETE across all legs.

Add `--no-demote`: it computes the new status as usual, then keeps whichever of
{current, computed} is more complete (by Status.Weight), so a result set is
treated as additive evidence — it may raise a requirement's status but never
lower it. Without the flag, behavior is unchanged. Adds REQ-VERIFY-013 and
TestClampNoDemote.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

RTM Changes

Health Status: [?] unknown

RTM Database Comparison

Statistics

Metric Baseline Current
Total 287 289
Complete 287 287
Completion 100.0% 99.3%

Added

  • REQ-VERIFY-012
  • REQ-VERIFY-013

Summary: IMPROVED

  • 0 improved
  • 0 regressed
  • 2 added
  • 0 removed

Generated by rtmx PR check

@rhino11 rhino11 merged commit eeb14d6 into main Jul 5, 2026
19 of 21 checks passed
@rhino11 rhino11 deleted the fix/verify-results-skip-and-partial branch July 5, 2026 17:36
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