drift/plan compare a github_release's live --version with its pin and upstream latest (Refs #613) - #616
Closed
noahgift wants to merge 5 commits into
Closed
drift/plan compare a github_release's live --version with its pin and upstream latest (Refs #613)#616noahgift wants to merge 5 commits into
noahgift wants to merge 5 commits into
Conversation
… upstream latest (Refs #613) paiml/infra pinned ollama to v0.33.2 on a box running 0.34.2. `plan` said Create, `drift` said nothing, and the apply would have downgraded it: both trusted the lock and asked only whether the binary exists. - New detector tripwire::drift::version_pin, run from the DECLARATION (so a pinned binary that is absent from the lock is still compared). It runs `<bin> --version` on the target, parses a semver at a token boundary, and reports DRIFT when the live version differs from the pin. Output with no parseable version is UNMEASURED ("cannot measure"), never clean. - It also reports a pin behind the repo's latest GitHub release as drift. It asks api.github.com from the target with curl, the same way apply_script already fetches releases. `--offline` (drift and plan) skips that check and the census, plan rows and JSON NAME the skip. `tag: nightly` is NOT CHECKED and named. - `plan` gains a "Version pins" section plus a `version_pins` JSON field. It changes no plan action, because turning version drift into Update would schedule the downgrade it warns about. - Kept out of every path that ACTS on findings: detect_drift_full (apply gate, pull agent) and MCP's unattended drift turn the check off and name that; `drift --auto-remediate` refuses when version-pin findings exist. Pmat-Ticket: PMAT-613 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ive binary is newer than a version pin The guard runs first in apply_script's present branch, so every path that installs (lockless create, drift gate, --force, pull agent) goes through it. It reads the live version the way drift does and refuses before the download, naming the pin and the live version. Unversioned pins are not ordered. Six tests execute the generated script against a fake binary and a fake curl; four go red with the guard emptied, the two controls stay green. Pmat-Ticket: PMAT-613 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Quorum round 1 (lane 3, claude-sonnet-5): the guard's trailer rejected a period at end of line, so `client version is 0.34.2.` read as no version and the install went through, while drift's parse_semver reads 0.34.2 from the same output. A period followed by end of line now ends a version; a four-part run is still refused. Pmat-Ticket: PMAT-613 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…inary
The unit tests execute the generated script. This one runs the forjar binary
against localhost with a fresh state dir: a v0.33.2 pin over a live 0.34.2
must not reach the download, and an upgrade from 0.33.1 still must. RED with
`downgrade_guard` returning empty ("the download of v0.33.2 started over a
live 0.34.2"); the upgrade control stays green.
Pmat-Ticket: PMAT-613
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
file-health would fail this branch for two files. src/cli/drift.rs went from 490 to 551 lines, and the new src/tripwire/drift/version_pin.rs had 516. - The drift --dry-run trio (record_dry_run_checks, print_dry_run_report, cmd_drift_dry_run) moves to src/cli/drift_dry_run.rs. drift.rs re-exports it, so every existing caller path still resolves. drift.rs is now 463 lines. - Semver, parse_semver and its byte helpers move to src/tripwire/drift/semver.rs. version_pin re-exports them, so version_pin::parse_semver is unchanged. version_pin.rs is now 427 lines. Both moved bodies are byte-identical to the originals (checked with diff). clippy -D warnings is clean. The 547 drift, version_pin and downgrade lib tests and the 2 integration falsifiers pass. Pmat-Ticket: PMAT-613 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Contributor
Author
|
folded into #628 (emergency fold, operator 2026-09-24) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #613
The defect
paiml/infra pinned ollama to
v0.33.2on lambda-labs, and the box runs0.34.2.forjar planandforjar driftboth said nothing, because both trust the lock and agithub_releasecheck only asks[ -x <bin> ]. An apply would have silently downgraded the box.The fix
tripwire::drift::version_pin. It works from the declaration, not the lock, so a pinned binary missing from the lock (infra's actual state) is still compared.<bin> --versionon the target and parses the firstN.N.Nat a token boundary. It skips IPs such as10.42.0.11, 4-part versions andabc1.2.3, and it accepts ollama'sWarning: … client version is 0.34.2.expected: version 0.33.2,actual: version 0.34.2, and a detail saying the apply would DOWNGRADE (or upgrade) the binary.api.github.com/repos/<repo>/releases/latestwith curl, the same wayapply_scriptalready fetches releases. If that call fails, the result is UNMEASURED.tag: latestcompares live with upstream.tag: nightlyis reported as NOT CHECKED.--offline(on bothdriftandplan) skips the upstream check. The skip is named in the drift census (version pin NOT checked N: id (why), JSONversion_not_checked), in the plan rows and in the plan JSON.planadds aVersion pins (live --version vs declared pin):section and aversion_pinsJSON field. It changes no plan action, because turning version drift intoUpdatewould schedule the downgrade it warns about.detect_drift_full(the apply drift gate and the pull agent) turns the check off.drift --auto-remediaterefuses when a version-pin finding exists: "Update the pin in the config instead".apply never downgrades (added by forjar-d3 on takeover)
The first version kept version findings out of the remediation paths, but apply still downgraded on its own. A lockless
github_releasehas no lock entry, so it plans Create, and itsapply_scriptinstalled the pin over the newer live binary. That is the exact lambda-labs case. The same happens for a locked one whose hash drifted and is re-applied by the drift gate.github_release::apply_scriptnow opens withdowngrade_guard:--versionwith a newer one, the script exits 1 before any download. The message isERROR: refusing to downgrade <repo>: the pin is X. Update the pin to the live version, or remove the binary to roll back on purpose. Live: Y.parse_semverdoes: the firstN.N.Nat a token boundary, with ollama's warning lines tolerated. An IP or a 4-part run is not a version.latestandnightlyare not ordered against the box.--force, the pull agent), so this is the one on-box place to stop a downgrade.src/resources/tests_github_release_downgrade.rsEXECUTES the generated script with a fake binary and a fakecurl, so no test touches the network. It covers refusals across patch, minor and major; controls (older, equal, no binary, no parseable version,latest/nightly); and the script passing forjar's own bashrs gate. Mutant (downgrade_guardreturns empty): 4 of 6 go red. The 2 control tests stay green, as they should.tests/falsification_github_release_never_downgrades.rsruns the realforjar applyagainst localhost with a fresh state dir. The live "ollama" is a script that prints a version, and a fakecurlfirst on PATH records that the download was reached. Av0.33.2pin over a live0.34.2must not reach the download, and the binary must be unchanged. The control: an upgrade from0.33.1must reach it. Mutant (downgrade_guardreturns empty): the refusal test goes red with "the download of v0.33.2 started over a live 0.34.2", and the control stays green.Quorum round 1 (f3ad95f): 3/3 PASS, one finding fixed anyway
The guard rejected a version followed by a period at end of line (
client version is 0.34.2.), which drift'sparse_semveraccepts. The two disagreed, and the downgrade went through. It is fixed inebf82324, andfj613_apply_reads_the_live_version_the_way_drift_doesgoes red with the old pattern. A second note, thatdrift --dry-runnever shows the version probe, is listed under follow-ups.Quorum round 2 (ebf8232): 3/3 PASS
The only notes were two asserted, non-blocking ones, listed under follow-ups.
e6fa61afthen added the real-binary falsifier, because forjar's receipt gate runs a--testtarget, and the downgrade guard's tests were all unit tests.Quorum round 3 (e6fa61a): 3/3 PASS
This round judged the integration falsifier. Lane 3 (claude-sonnet-5) made two non-blocking notes, both already follow-ups. First, a pin that is not yet installed but is behind upstream is still reported. Second, the
--versionprobe has no timeout.file-health fix (d6222c2): delta round 3/3 PASS
Two files were over forjar's 500-line ratchet:
src/cli/drift.rshad 551 lines and the newsrc/tripwire/drift/version_pin.rshad 516.d6222c2conly moves code:--dry-runtrio goes tosrc/cli/drift_dry_run.rs.Semver,parse_semverand their helpers go tosrc/tripwire/drift/semver.rs.Re-exports keep every caller path (
drift::cmd_drift_dry_run,version_pin::parse_semver). The moved bodies are byte-identical to the originals (checked withdiff). A delta round of claude-sonnet-5, claude-haiku-4-5 and claude-sonnet-5 returned 3/3 PASS.Red / green
The headline test is
tripwire::drift::tests_version_pin::fj613_live_version_ahead_of_pin_is_drift_even_when_the_lock_agrees. A fakeollamaprintsollama version is 0.34.2undertag: v0.33.2, with a converged lock entry baselined after the install. That means the digest path compares the box with itself, which is exactly the blind spot.The test cannot compile against pristine
main, becauseDriftOptionshas nocheck_upstreamthere and the test must stay off the network. So "revert the fix" means reverse-applying just the twoversion_pin::detect(...)call sites indrift/mod.rsanddrift/lockless.rs(git apply -R wiring.patch). The module stays in place, but nothing calls it.RED (fix reverted):
GREEN (fix restored):
The remediation-exclusion test can also fail. With
detect_drift_fullswitched tocheck_version_pins: true,fj613_remediation_wrapper_never_sees_version_findingsgoes FAILED, and it passes again after the switch is reverted.All 16
fj613_*tests pass. They cover parsing, the pure verdict table, the lockless path, a pin absent from the lock, unparseable output → unmeasured, and the disabled surface being named.End-to-end (real CLI, fake binary, no apply)
The lock exists but has no entry for the resource, which is lambda-labs' shape:
Checks
cargo fmt --all -- --check: clean.cargo clippy --all-targets -- -D warnings: clean.cargo test --lib: 13568 passed, 1 failed. The failure iscore::store::tests_conda::test_fj1349_store_hash_deterministic, which is conda-only and not touched here. It passes 3/3 in isolation, so it flaked under parallel load.falsification_*drift*,*plan*,*github_release*,*mcp*): all green.cargo test --lib --testswith the downgrade guard (nowf3ad95fb): 18443 passed, 2 failed, neither caused by this diff.falsification_release_workflow_shape::rule2_…also fails onmain(PMAT-604 addedv*tag pushes to three workflows).falsification_replace_running_binary::cargo_provider_replaces_a_running_binaryfails only on the lambda-labs host: that host'scargowrapper (paiml/infra#930) finds the real cargo through$HOME, which the test overrides. WithLAMBDA_MEMCAP_REAL_CARGOset, it passes 6/6.Not fixed here (follow-ups)
drift --dry-rundoes not preview the version checks, and ignores--offline(quorum round 1, lane 3).<bin> --versionprobe inversion_pin.rshas no timeout, while the upstreamcurlhas--max-time 20. A hung binary would stalldriftandplan. macOS has notimeoutcommand, so the fix needs a portable form (quorum round 2, lane 3).plannow runs the target binary and asks upstream by default (--offlineturns that off). That widensplan's side effects, and it is documented (quorum round 2, lane 3).ollama-binaryis actuallytype: task(agrep '0\.33\.2'completion_check), notgithub_release, so this detector does not cover it until it is converted. Converting it is blocked becausegithub_releaseapply handles only.tar.gz/.zip, not.tar.zst, and installs one binary, not ollama'slib/tree.driftover a state dir that exists but holds no lock for a declared machine prints0 resource(s) inspected … No drift detectedand does not fall back to the lockless scan, which runs only when the state dir is absent (forjar#385).🤖 Generated with Claude Code