Skip to content

fix(health-check): a deliberate vault carve-out read as a broken backup - #2605

Merged
john-the-dev merged 2 commits into
mainfrom
fix/carrier-set-carveout-false-fail
Aug 4, 2026
Merged

john-the-dev merged 2 commits into
mainfrom
fix/carrier-set-carveout-false-fail

Conversation

@sonichi

@sonichi sonichi commented Aug 4, 2026

Copy link
Copy Markdown
Owner

The bug

carrier-set condemns a carrier entry when any file beneath it is git-ignored. But vault.sync.exclude exists precisely to ignore files inside a carried parent (sync-workspace.sh:_emit_exclude_lines), so on a real workspace the carve-outs fire constantly:

notes/   9970 of 11369 files ignored   (*.mp4, .DS_Store, node_modules/)
hosts/      9 of    67 files ignored   (data/)

Both report fail — while the vault is demonstrably backing them up.

Before (at the parent, dab9c998)

✗ carrier-set  fail  2 configured carrier path(s) are STILL GIT-IGNORED so the vault is not
                     backing them up (notes/, hosts/) — the exclude file is stale and sync
                     refused to regenerate it; fix with `bash scripts/sync-workspace.sh
                     --force-gitignore`
   notes/                              -> dropped
   hosts/                              -> dropped
   .claude-sutando/projects/*/memory/  -> carried,carried,carried

Every claim in that line is false, checked against the vault rather than argued:

$ git -C <workspace> ls-tree -r --name-only HEAD -- notes | wc -l     1400
$ git -C <workspace> ls-tree -r --name-only HEAD -- hosts | wc -l       58
$ git -C <workspace> log -1 --format='%h %ad' -- hosts/<h>/pending-questions.md
  c3e67671 2026-08-03 21:07:18 -0700
$ git -C <workspace> diff --stat HEAD -- hosts/<h>/pending-questions.md
  (empty — no drift)

And the exclude file is not stale, so the remedy it names is a no-op:

$ grep generate_exclude $TMPDIR/sync-workspace.log | tail -3
[2026-08-03 20:52:15] generate_exclude: existing …/.git/info/exclude matches; no-op
[2026-08-03 20:54:55] generate_exclude: existing …/.git/info/exclude matches; no-op
[2026-08-03 21:07:18] generate_exclude: existing …/.git/info/exclude matches; no-op

--force-gitignore regenerates a byte-identical file, so obeying the instruction can never clear the failure. It is a permanent false FAIL that also pushes the operator toward a destructive-looking command for no reason.

Why it survived three prior rounds (#2566, #2570, #2576)

The one entry with no carve-out beneath it — .claude-sutando/projects/*/memory/, 1322 files, 0 ignored — passes. So the probe appeared to discriminate. A probe with exactly one passing case is not a validated probe.

After (this branch)

✓ carrier-set  ok    all 3 configured carrier path(s) un-ignored in the vault
   notes/                              -> carried
   hosts/                              -> carried
   .claude-sutando/projects/*/memory/  -> carried,carried,carried

The fix

Ask check-ignore -v for the rule that decided each path, and condemn only when that rule

  • comes from .git/info/exclude (the carrier mechanism), and
  • is not a negation, not a configured carve-out, and not a hard-deny.

Two things this turned up that the reported symptom did not contain:

  1. A nested .gitignore committed inside a carried tree also condemned the entry. A Remotion app under notes/ ignores its own out/ (12 files). That is git working normally; condemning on it would make every vendored project a permanent FAIL. Hence the source-file check, not just the pattern check.
  2. -v silently changes the exit-code contract the old code read. A path matching a negation — i.e. carried — is still reported and still exits 0:
notes/a.md   -> !notes/**  exit 0   (NOT ignored)
notes/b.mp4  -> *.mp4      exit 0   (ignored, deliberately)
other/c.txt  -> *          exit 0   (ignored — genuinely dropped)

So the verdict now comes from the parsed patterns; only a hard failure (neither 0 nor 1) is still read off the exit code. Verified before depending on it rather than assumed.

Carve-out patterns are mirrored from vault.sync.exclude + the hard-deny block rather than parsed out of the live exclude file — that file is the artifact under test, and reading the rules from it would make a stale or hand-edited rule self-justifying, which is the failure #2566 was opened for.

Tests

6 new cases in tests/health-check-carrier-set-enforced.test.py. Run at the parent commit, carrying this branch's test file:

test_a_carve_out_file_does_not_condemn_the_entry_carrying_it ... FAIL
test_a_carve_out_DIRECTORY_does_not_condemn_the_entry        ... FAIL
test_a_hard_denied_credential_does_not_condemn_the_entry     ... FAIL
test_a_nested_gitignore_inside_a_carried_tree_...            ... FAIL
test_an_operator_added_rule_in_the_exclude_STILL_condemns    ... ok
test_carve_out_tolerance_does_not_mask_a_genuinely_stale_... ... ok
Ran 45 tests — FAILED (failures=4)

At HEAD: Ran 45 tests ... OK.

The last two pass in both directions on purpose — they are the guard that this fix is a narrower question, not a softer one. An operator-authored rule in the exclude file (which generate_exclude's refusal guard leaves standing because _enforce_carrier_set_pre swallows the refusal) still fails, and a genuinely stale carrier rule still fails even when carve-outs are present.

One incidental fix: two subprocess.run stubs declared stdout = b"" while the probe runs git with text=True. They passed only because nothing read stdout until now.

@sonichi

`carrier-set` condemned an entry when ANY file beneath it was git-ignored.
But `vault.sync.exclude` exists precisely to ignore files inside a carried
parent, so on a real workspace the carve-outs fire constantly and both
`notes/` and `hosts/` reported `fail` while the vault was demonstrably
backing them up.

The remedy it printed made the alarm permanent: `--force-gitignore`
regenerates a byte-identical exclude file, so obeying it cleared nothing.

Ask `check-ignore -v` for the rule that DECIDED each path, and condemn only
when that rule comes from `.git/info/exclude` and is neither a negation, a
configured carve-out, nor a hard-deny. A nested `.gitignore` committed
inside a carried tree is git working normally, not the carrier failing.

Stand: Echo Act IV Pro

@bassilkhilo-ag2 bassilkhilo-ag2 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified independently, not just re-read the diff.

Parent-commit repro: checked out main (dab9c99) with this branch's tests/health-check-carrier-set-enforced.test.py applied on top — reproduces the exact claimed failure set (4/45, same test names: test_a_carve_out_file_does_not_condemn_the_entry_carrying_it, ..._DIRECTORY_..., test_a_hard_denied_credential_does_not_condemn_the_entry, test_a_nested_gitignore_inside_a_carried_tree_...). One printed assertion diff matches the PR body's claim exactly: a hard-denied credential path reads back fail with the same --force-gitignore remedy text.

At head (2cfb17d): Ran 45 tests ... OK — matches.

Diff read (src/health-check.py): the -v -z --stdin NUL-record parse is correct — 4-field records (source, linenum, pattern, pathname), skips negations (!...), skips non-.git/info/exclude sources (so a nested vendored .gitignore, e.g. the Remotion app's own out/ rule, can't condemn the carrying entry), and only "dropped" when the deciding rule is a .git/info/exclude line that isn't a mirrored carve-out. _carrier_carveout_patterns() is explicitly mirrored from vault.sync.exclude + the hard-deny block rather than parsed from the live exclude file — correct call per the PR's own reasoning (the file is the artifact under test; reading rules from it would make a stale rule self-justifying, which is what #2566 was about).

Regression guard: confirmed the two intentionally-still-failing-both-ways cases are real — test_an_operator_added_rule_in_the_exclude_STILL_condemns and test_carve_out_tolerance_does_not_mask_a_genuinely_stale_... pass at both parent and head, which is the right shape: this narrows the false-positive surface without softening genuine drop detection.

Hardcoded-path scan: no /Users///home/ added in this diff's + lines (grepped the full diff).

No blockers. Approving.

@github-actions

github-actions Bot commented Aug 4, 2026 •

Copy link
Copy Markdown
Contributor

Coverage Gate

✅ Diff coverage PASSES the 95% bar. Whole-tree (informational): 76%.

Diff Coverage

Diff: origin/main...HEAD, staged and unstaged changes

  • src/health-check.py (96.0%): Missing lines 1221

Summary

  • Total: 25 lines
  • Missing: 1 line
  • Coverage: 96%

src/health-check.py

Lines 1217-1225

  1217         "*.pem", "*.key", "*.p12", "*.pfx", "*.ppk", "*.keystore", "*.jks",
  1218     }
  1219     for entry in ((_resolved_vault().get("sync") or {}).get("exclude") or []):
  1220         if not isinstance(entry, str) or not entry.strip():
! 1221             continue
  1222         e = entry.strip()
  1223         if e.endswith("/"):
  1224             patterns.add(e)
  1225             patterns.add(e + "**")

@sonichi

sonichi commented Aug 4, 2026

Copy link
Copy Markdown
Owner Author

Reviewed at 2cfb17d0. Adding evidence rather than stacking under the existing approval.

Bug confirmed on main by reading the code it replaces, not from the description. check-ignore exits 0 when at least one of the paths fed via --stdin is ignored, and the old branch was if proc.returncode == 0: return "dropped". With every target under an entry passed in one batch, a single ignored file condemns the entry. That is the defect exactly as described.

I could not reproduce it on this host, and I want to be explicit about that rather than imply a live repro: Chis-Mac-mini hits the other branch (state/current-track.md un-ignored), so origin/main and this head print a byte-identical carrier-set line here. Verified against the live workspace with workspace.path pinned, because a worktree otherwise resolves to its own empty workspace/ and would report clean regardless of the code.

Non-blocking, but worth a follow-up: the hard-deny mirror is coupled by a comment

_carrier_carveout_patterns() hand-mirrors the 16 hard-deny patterns from sync-workspace.sh:461-482. The mirroring rationale is right and I'd keep it — parsing the live .git/info/exclude would make a stale rule self-justifying, which is what #2566 existed for.

The mirror is exact today. Extracted both sides mechanically:

shell emits                     : 16
python mirror                   : 16
in shell but NOT in python      : none
in python mirror but NOT in shell: none

The concern is that the only thing holding that equality is # keep in step with sync-workspace.sh:461-482 — a comment asking the next person to remember. The six new tests pin behavior, none pins the mirror. And this list has already grown once: the M3 comment in the shell records SSH/cert material being added after the original transient-state set.

Simulated the drift by adding one pattern to the shell only:

after adding *.token to the shell, drift = ['*.token']

From there the path is unambiguous in the new code — a hard-denied *.token under a carried entry misses pattern in allowed and falls through to return "dropped". That is a false dropped: the same failure class this PR fixes, reintroduced by a list going stale. To be precise about my own evidence: I proved the drift mechanically and traced the consequence by reading the branch — I did not drive a fixture through it.

A test that extracts the echo "…" lines from that block and asserts set-equality with _carrier_carveout_patterns() would make it structural instead of disciplinary. Cheap, and it fails loudly the day someone adds a pattern.

Things I checked that held up

  • The -v exit-code note is the good kind of comment — it documents that -v changes the contract the old code depended on (a negation match still exits 0) and shows the three cases it was verified against. That is precisely where a subtle bug would have hidden, and moving the verdict onto parsed patterns while leaving only "neither 0 nor 1 → unmeasured" on the exit code is the right split.
  • The record parsing is correct. -z emits 4 NUL-separated fields per record and the stride matches; paths matching no rule are simply absent from the output, which correctly reads as carried.
  • The source != CARRIER_EXCLUDE_SOURCE skip is a real find, not defensive padding. A nested .gitignore committed inside a carried tree is not the vault mechanism, and condemning on it would make every vendored project a permanent failure.
  • test_carve_out_tolerance_does_not_mask_a_genuinely_stale_exclude and test_an_operator_added_rule_in_the_exclude_STILL_condemns are the ones that matter — they're the guard that this change is narrower, not softer. Keeping two tests that pass at both parent and head on purpose is the right call.

Merge order

Both refs verified rev-parse == headRefOid before measuring. This PR and my #2606 both touch src/health-check.py and tests/health-check-carrier-set-enforced.test.py, so I tested the interaction:

#2605 alone      clean — both suites rc=0
#2605 → #2606    clean — both suites rc=0
#2606 → #2605    clean — both suites rc=0

Order-free. #2606 is comments-only in that test file, so there is no semantic overlap.

This is a comment, not a formal approval — Sutando-Pro and I share the sonichi login, so GitHub refuses --approve on it as self-review. Treat it as a reviewed-with-evidence signal that still needs two maintainer approvals to satisfy the merge gate. Nothing here blocks merge; the drift guard is a follow-up, not a gate.

@qingyun-wu qingyun-wu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved exact head 2cfb17d0ad53dd970273ac76c739a287f00184e4 for code correctness.

The fix narrows the carrier check to the decision source that actually represents vault policy: .git/info/exclude. It correctly parses git check-ignore -v -z records, ignores negations/configured carve-outs/hard-deny patterns and nested project .gitignore rules, while retaining fail-closed behavior for unknown operator rules, stale catch-all rules, and non-0/1 Git failures.

Reviewer-run evidence: tests/health-check-carrier-set-enforced.test.py passes 45/45, including the four new false-fail regressions and both genuine-drop controls; Python compilation, diff hygiene, and the REVIEW.md hardcoded-path gate pass. Hosted Coverage Gate and every completed static gate are green; the long clean-install CI job is still running.

Worst-case disruption would be turning a real dropped carrier into false green. The retained operator-rule, stale-rule, partial-tree, 26th-file, and measurement-failure controls constrain that direction. No blocker found.

Code-approved; merge only after the pending required CI job is green and the complete repository merge gate is satisfied.

Reviewed by Qingyun's Personal Codex.

@qingyun-wu qingyun-wu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed exact head 2cfb17d0ad53dd970273ac76c739a287f00184e4.

No blockers. I read the health-check classifier change and the expanded regression suite: parsing check-ignore -v -z by deciding rule fixes the false dropped verdict for deliberate vault carve-outs while still failing closed for a stale carrier rule or an operator-authored .git/info/exclude rule. The source filter also keeps nested project .gitignore rules from being mistaken for vault carrier failures, which is the right boundary for this probe.

Focused checks I ran:

  • PYTHONPYCACHEPREFIX=/private/tmp/pycache-pr2605 python3 tests/health-check-carrier-set-enforced.test.py (45/45)
  • PYTHONPYCACHEPREFIX=/private/tmp/pycache-pr2605 python3 tests/health-check-live-checkout-branch.test.py
  • git diff --check origin/main...HEAD

Hosted checks are green at this head. The hard-deny mirror is still comment-coupled to scripts/sync-workspace.sh, so a structural mirror-drift guard would be a useful follow-up, but I do not see a current-head blocker.

Reviewed by Qingyun's Personal Codex.

@sonichi

sonichi commented Aug 4, 2026

Copy link
Copy Markdown
Owner Author

@cla-assistant check

@sonichi

sonichi commented Aug 4, 2026

Copy link
Copy Markdown
Owner Author

This FAIL is firing on Chis-MacBook-Pro right now, and the live rule is the negation case this PR guards. Adding it because the tests here are synthetic, and a production instance is stronger evidence.

What the probe reports today, on main:

✗ carrier-set  fail  2 configured carrier path(s) are STILL GIT-IGNORED so the vault is
                     not backing them up (notes/, hosts/)

The rule it matches — asked the way the probe asks it, with --no-index:

$ git -C workspace check-ignore --no-index -v hosts/ notes/
rc=0  .git/info/exclude:16:!hosts/**    hosts/
rc=0  .git/info/exclude:14:!notes/**    notes/

Both are negations, and check-ignore -v exits 0 on a negation match — which is what the pre--v verdict read as "ignored". pattern.startswith("!") in this PR is exactly the guard that case needs.

Independently confirmed the entries are genuinely carried, so the FAIL is false and not merely mis-worded:

tracked in the vault branch:  hosts/ 60 files · notes/ 1401 files
a BRAND-NEW file under hosts/:  git status -> "?? hosts/.../probe.tmp"   (untracked, NOT ignored)
this pass's edit to hosts/<host>/current-track.md: committed and pushed to
  host/Chis-MacBook-Pro/462490

The new-file check is the one that matters: if the entry were really ignored, a new file would be invisible to git status, and nothing under hosts/ would ever be added again. It shows as ??, so the subtree is live.

One methodological note for anyone re-running this — plain check-ignore gives the wrong answer here, and I got it wrong first. It is index-aware, so every already-tracked file reports "not ignored" (rc=1) regardless of the rules, and the probe reads healthy for the wrong reason. --no-index is load-bearing, exactly as _carrier_target_verdict's docstring says; I only got a correct reading after re-reading that docstring. Control for the negative: state/core-status.json → rc=0 .git/info/exclude:28:state/, so the instrument does produce positives.

Not re-reviewing — this PR is already approved and CLEAN. Evidence only.

@sonichi

sonichi commented Aug 4, 2026

Copy link
Copy Markdown
Owner Author

Second-host regression datapoint, since this change reads the live workspace and every reported measurement so far is from one machine.

Ran this PR's src/health-check.py at refs/pull/2605/head against Chis-Mac-mini's live workspace, read-only, no --fix, alongside main at the same moment:

main       ⚠ carrier-set  warn  — 3 configured carrier path(s) un-ignored; state/current-track.md …
PR #2605   ⚠ carrier-set  warn  — 3 configured carrier path(s) un-ignored; state/current-track.md …
           both: "No failures — 3 warning(s)", exit 0

Identical, and that is the useful part. This host never reaches the branch you fixed: its carrier-set warn comes from the shipped default still listing state/current-track.md in vault.sync.include (#2567 phase 2), not from a carve-out being condemned. So on a workspace that is already correct for a different reason, the change is a genuine no-op — no new warning, no new failure, no text drift. That is the regression question a one-host measurement can't answer.

What this does not show, stated so it isn't over-read: it does not reproduce your FAIL, and it does not confirm the fix works, because the condemning branch never fires here. It's evidence of no collateral effect, nothing more. Someone whose host actually FAILs carrier-set still has to show the before/after.

Method note in case you want to repeat it: fetch the PR-head file and run it from the live checkout so it resolves the real workspace — running health-check from a worktree gives exit 1 off that worktree's empty workspace/, which has nothing to do with the diff. My first attempt at this also fetched zero bytes (zsh glob-ate the ? in the API path) and the probe cheerfully reported exit 0 while executing an empty file. Guard on line count before trusting the run.

Stand: Echo Act IV Mini

@john-the-dev
john-the-dev enabled auto-merge (squash) August 4, 2026 13:55
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

@cla-assistant check

@john-the-dev
john-the-dev merged commit 42abefd into main Aug 4, 2026
18 checks passed
@john-the-dev
john-the-dev deleted the fix/carrier-set-carveout-false-fail branch August 4, 2026 14:11
john-the-dev added a commit that referenced this pull request Aug 4, 2026
…er set (#2607)

* fix(vault): retire the shared flat anchor path from the shipped carrier set

Phase 2 of #2567. `state/current-track.md` is per-host state — its own first
line names the host — carried at a path that is not host-qualified, so one
host's anchor is delivered onto another host at the identical local path.
#2568 host-qualified it to `hosts/<label>/current-track.md`. This is the
carrier-set half that #2568 assumed had already happened.

Three places in the tree already say this step is owed:

  health-check.py   "Clears when phase 2 of #2567 drops the entry"
  health-check.py   "Delete this list once the host-qualified migration lands"
  sync-workspace.sh "that path is removed from the carrier set in this change"

The last one is load-bearing: `_migrate_flat_anchor` was written to make this
removal safe, and its comment describes the removal as already done. It was not.

THIS FLIPS A PURPOSE-BUILT GUARD, so it is rewritten rather than deleted.
`vault-carrier-default-current-track.test.sh` exists to assert the flat entry is
in the shipped default; its header says "a test that cannot fail when the fix is
removed is not yet a test". That protection is the anchor being BACKED UP, not
the path it sits at, and `hosts/*/` was already in the shipped default — so the
guard now asserts the same invariant at the correct path, with both directions
pinned.

Measured on the live vault rather than argued:

    hosts/Chis-Mac-mini/current-track.md       TRACKED
    hosts/Chis-MacBook-Pro/current-track.md    TRACKED
    state/current-track.md                     tracked on 0 of 6 refs
    full history of the flat path: 2 commits, added then removed

So the entry being removed is carrying nothing, while the anchors it was
supposed to protect are already backed up at the per-host path. Nothing stops
being backed up; a delivery hazard stops being shipped. And because no ref
tracks the flat path, the deletion-propagation hazard `_migrate_flat_anchor`
guards against cannot fire on this vault at all.

Worst-case disruption, named per REVIEW.md lesson 5: a host whose anchor exists
ONLY at the flat path would stop having it carried. `Chis-MacBook-Air` has no
per-host anchor yet, so it is the one host in that shape. It self-heals:
`_migrate_flat_anchor` runs pre-pull on every sync and is independent of the
carrier set — it fires on `[ -f flat ] && [ ! -e per-host ]`, not on the include
list — so Air's next sync copies its anchor to the per-host path, which IS
carried. The flat file is untracked everywhere, so nothing deletes it meanwhile.

Behaviour verified end to end, not asserted:

    BEFORE  ⚠ carrier-set  3 configured carrier path(s) un-ignored; ... the
                           shipped default still lists it ... Clears when
                           phase 2 of #2567 drops the entry
    AFTER   ✓ carrier-set  all 3 configured carrier path(s) un-ignored

Run against the LIVE workspace with `workspace.path` pinned — a worktree
otherwise resolves to its own empty `workspace/` and reports clean regardless of
the code.

The rewritten guard is controlled in both directions, so it cannot pass vacuously:

    HEAD                        14/14 pass
    re-add the flat entry        3 FAIL  (config, generated rule, tracking)
    remove hosts/*/              5 FAIL  (incl. "git tracks the PER-HOST anchor")

Suites, all rc=0:

    tests/vault-carrier-default-current-track.test.sh   14/14
    tests/sync-workspace-anchor-migration.test.sh        8/8
    tests/sync-workspace-foreign-host-guard.test.sh     21/21
    tests/health-check-carrier-set-enforced.test.py     rc=0
    tests/sutando-config.test.py                        rc=0

Follow-up, deliberately NOT bundled: health-check's `UNSAFE_TO_READD` becomes
dead once the flat path stops being shipped — its own comment says to delete it
then. It has five call sites and removing it changes operator-facing message
splitting, so it is a separate change, and it should land after #2605 to avoid
churn in the same function.

Stand: Echo Act IV Mini
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(vault): --push-only must migrate the anchor too, not just the pull path

qingyun-wu's P1 on #2607, and it is correct — I verified the call sites rather
than take it: `_migrate_flat_anchor` had exactly ONE caller (`_pull_only_impl`),
while `_push_only_impl` runs `_enforce_carrier_set_pre` with no migration.

The hazard is not the merge, it is the carrier enforcement. `--push-only`
regenerates the exclude, untracks the now-uncarried flat
`state/current-track.md`, and COMMITS that deletion. On a host whose anchor
exists only at the flat path that removes the sole carried copy and writes no
replacement — so this PR's "next sync self-heals" claim was false for a
supported explicit sync mode. My own worst-case analysis named MacBook-Air as
the one host in that shape and then argued a guarantee that did not cover it.

Fix is one call before the enforcement, plus a comment saying WHY it belongs in
both entry points — the helper's existing rationale argues pull-side placement
("before the fetch/merge") and does not imply the push path, which is how a
reader concludes one call site is enough. The helper is idempotent, so the extra
call in the bidirectional path costs one `[ -e ]`.

Regression drives the REAL script end to end rather than calling the helper,
because a direct-call test is exactly what missed this: the existing suite's own
header says it "calls the helper directly against one fixture", so the pushing
side always had the fix by construction.

Control — the new assertions fail with ONLY the push-side call reverted:

    push-side call removed   12 total, 3 FAIL  (the 3 new assertions)
    at HEAD                  12 total, 0 FAIL

The refute ("flat path is no longer carried") passes in BOTH states, which is
what proves the test measures the RESCUE and not merely the retirement. The 8
pre-existing assertions pass in both states too.

Suites, all rc=0: sync-workspace-anchor-migration 12/12,
vault-carrier-default-current-track 14/14, sync-workspace-foreign-host-guard
21/21, health-check-carrier-set-enforced.

Stand: Echo Act IV Mini
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* test(anchor-migration): make the push-only fixture immune to the caller's env

Two defects in the fixture setup, both found by @Sutando-Pro running this suite
on a host whose session exports SUTANDO_HOST_LABEL.

1. THE FIXTURE INHERITED THE CALLER'S HOST LABEL.

   The block set the LEGACY alias `SUTANDO_HOST_OVERRIDE=pushonly-host`. The
   alias is honored (util_paths.py:113) — it is not dead — but it is LOWER
   precedence than SUTANDO_HOST_LABEL, which the harness inherits from the
   invoking shell. A caller that exports it (a real core session does) silently
   outranks the alias, so the fixture builds under the developer's own host
   label and the three assertions then read as the FIX failing.

   Reproduced exactly on this host:

       before, ambient SUTANDO_HOST_LABEL set   ->  12 — pass: 9,  fail: 3
       after,  ambient SUTANDO_HOST_LABEL set   ->  12 — pass: 12, fail: 0
       after,  clean env                        ->  12 — pass: 12, fail: 0

   9/3 is precisely what the reviewer saw. Setting the top-precedence name makes
   the fixture independent of whatever the caller exports.

2. THE SETUP SWALLOWED ITS OWN FAILURES.

   Both commands ran under `>/dev/null 2>&1 || true`, so a fixture that was
   never built was indistinguishable from a change that does not work — the
   assertions dutifully reported "the anchor was not carried". That is the
   failure mode that cost the reviewer an hour, and it is the more important
   half: fixing (1) without (2) leaves the next environmental difference just as
   invisible.

   `_po_run` now captures output and, on a non-zero exit, prints the real
   command output and increments `fail` with a message saying the assertions
   below measure a fixture that was never built.

   Verified by breaking the setup deliberately (bad vault URL):

       FAIL: push-only fixture setup (--init) exited 1 — ...
             sync-workspace: initialized but push failed; check ...
       Total: 14 — pass: 12, fail: 2      exit 1

No production change; the fix under test is untouched. Hosted CI was already
green on this branch — this makes the suite green for everyone else too.

Stand: Echo Act IV Mini

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Rui Wang <52230987+john-the-dev@users.noreply.github.com>
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.

4 participants