Skip to content

test: versioned wire-contract fixtures + drift guard (#351) - #382

Merged
VijitSingh97 merged 3 commits into
developfrom
fix/351-wire-contract-fixtures
Aug 21, 2026
Merged

test: versioned wire-contract fixtures + drift guard (#351)#382
VijitSingh97 merged 3 commits into
developfrom
fix/351-wire-contract-fixtures

Conversation

@VijitSingh97

Copy link
Copy Markdown
Contributor

Closes #351.

Pins the two wire shapes pithead's tier-2 fixtures depend on inside THIS repo, so drift trips CI here instead of a manual live run there (the v1.16 fixture-drift failure mode; #320's noop/throttled statuses were the confirmed example).

  • tests/contract/v1/feed.json — the canonical /2/summary body (XMRig passthrough envelope + the full rigforge block), produced through the real api_refresh code path.
  • tests/contract/v1/control-status.json — the exhaustive /status vocabulary: applied, failed, noop, rejected, rolled_back, started, throttled (the control-upgrade status contract: no in-progress state, no version echo, refusals collapse into 'failed' #320 additions included), plus real example bodies.
  • tests/contract/v1/README.md — what the contract covers, its version, and the change rule: a wire-shape change touches the fixture in the same PR, paired with a pithead-side update.
  • A guard in tests/run.sh: regenerates the shapes via the real code paths (the existing RIGFORGE_HOME harness) and compares against the fixtures after jq -S normalization; every normalized key is documented in the README.

Ordering: land this BEFORE the #344 branch — that PR adds pending/age_seconds to /status and should rebase onto this guard and update the fixture as its first customer.

What was run: shellcheck --severity=warning + shfmt -i 4 -d on tests/run.sh and rigforge.sh (clean); markdownlint-cli2 0.22.1 on the new READMEs (0 errors); full bash tests/run.sh foreground twice, before and after wiring the guard: pass. Not run: the Linux/Docker-only suites (e2e, kcov) — zero rigforge.sh lines changed.

🤖 Generated with Claude Code

…351)

Nothing in this repo asserted the two wire shapes pithead's fakes consume —
drift was only ever caught by a manual live run (the confirmed case: #320's
noop/throttled statuses landed here with pithead's poller still unaware of
them). Add tests/contract/v1/{feed,control-status}.json, versioned plain-JSON
fixtures a sibling repo's CI can fetch and byte-compare, plus a README stating
what they cover and the change rule (touch the fixture in the same PR that
changes the shape).

Wire a guard into tests/run.sh that regenerates both shapes through the real
code (api_refresh, _control_status — not hand-written JSON), normalizes the
few fields that legitimately vary release-to-release or run-to-run (install
provenance, a wall-clock timestamp; see the fixture README), and byte-diffs
against the committed file. The status vocabulary is additionally cross-
checked against every literal status word rigforge.sh hands to
_control_status, straight off the call sites, so a new status can't land
silently.

No rigforge.sh lines changed, so no coverage impact.
…st rigforge.sh (#351)

Three gaps in the #351 guard, all confirmed:

- It regenerated control-status.json's terminal record shapes by calling
  rigforge.sh's _control_status() and reading the file it wrote, never
  touching util/control-server.py — so the receiver's own 503 no-history
  body, the `pending` status stage_pending() writes at /apply accept time,
  and the age_seconds _with_age() injects into every served body were all
  invisible to it (and could drift unnoticed). The status-vocabulary check
  was similarly one-sided: it only grepped rigforge.sh's call sites, never
  control-server.py's own "status" literals.

  Fixed by adding a server-driven leg (same spin-up/curl/kill harness as
  the "#236 black-box: the control server" section above it) that exercises
  the real util/control-server.py over HTTP for exactly the three things
  only it can produce, and by unioning the vocabulary grep across both
  files.

- The fixture undersold the contract it was supposed to pin: no `pending`
  in statuses, no pending example, no age_seconds anywhere, and the 503
  body undocumented. control-status.json and its README now cover all of
  it, including which keys are normalized (and why — serve-time-derived
  age_seconds, server-random change_id on the pending example) — still v1,
  purely additive.

- The FEEDFX block deliberately kept the generic systemctl stub off PATH so
  API_CMD stayed the only faked external command, but missed that
  _health_json calls systemctl directly for service_active — on any rig
  genuinely running the xmrig unit that call returns true and byte-diffs
  the fixture. SERVICE_NAME is now pinned to a unit name no rig will ever
  have, so service_active/clock_pct_of_boost/throttling read false/null
  identically on a dev Mac, CI, and a mining rig.

Verified: a corrupted no_history body in the fixture makes the new guard
leg fail; a systemctl stub simulating a genuinely active xmrig unit flips
service_active true without the SERVICE_NAME override and false with it.

shellcheck 0.11.0 + shfmt -i 4 clean, markdownlint-cli2 0.22.1 clean, full
tests/run.sh: 1728 passed, 0 failed.
@VijitSingh97
VijitSingh97 merged commit 90373c7 into develop Aug 21, 2026
9 checks passed
@VijitSingh97
VijitSingh97 deleted the fix/351-wire-contract-fixtures branch August 21, 2026 18:15
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.

Cross-repo wire contracts have no automated drift guard against pithead's fixtures

1 participant