feat: add argit drift machine-readable manifest-drift report (#25) - #28
Conversation
Drift was only surfaced as human `= manifest drift: …` lines from setup's _handle_drift — same prefix as unrelated no-ops, brittle to parse. Add a read-only `argit drift` reporting the same hash-only classification (reuses _classify_drift / _bundled_manifest_path) as a stable JSON object for fleet drift monitoring. - drift.py: collect_drift() core (clean|stale_bundle|operator_modified| no_manifest; agent-type mismatch raises). human_summary mirrors setup's phrasing. run_drift always exits 0 — drift is queryable, not a failure. - cli.py: `argit drift [--json] [--agent-type]`. - doctor.py: informational `manifest drift` row via the shared core (never flips doctor's exit code). - README: drift section + --no-upgrade-manifest companion note. - bump 1.9.0 → 1.10.0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
blink-claw-bot
left a comment
There was a problem hiding this comment.
HIGH
argit drift --jsoncan report a stale manifest family asclean, which defeats the new machine-readable drift monitor.collect_drift()takes the current best-fit bundled manifest at lines 47-48, but then delegates state entirely to_classify_drift()at line 73. That classifier returnscleanwhen the repo manifest matches the latest revision within its ownagent_versionfamily, not when it matches the selected bundled manifest. As a result, a repo containingopenclaw-2026.4.14-7.manifest.jsonwhile the current bundled best-fit isopenclaw-2026.4.26-2.manifest.jsonemits:
{"manifest_file":"openclaw-2026.4.14-7.manifest.json","state":"clean","repo_revision":2,"bundled_revision":2,"revisions_behind":0,"upgrade_available":false}That is both semantically wrong (repo_revision is taken from the bundled manifest at lines 77-79) and operationally dangerous because automation would miss exactly the pinned-vs-bundled gap the README says this command exposes. Please compare the repo manifest's matched type/version/revision against the selected bundled type/version/revision, and add a regression test for an older manifest-version family that is latest within its own family but stale relative to the current bundled selection.
MEDIUM
- No additional findings.
LOW
- No additional findings.
Tests run:
PYTHONPATH=src python3 -m pytest tests/unit/test_drift_command.py -q
PYTHONPATH=src python3 -m pytest tests/unit/test_drift_classifier.py tests/unit/test_drift_warning.py tests/unit/test_upgrade_flow.py tests/unit/test_doctor.py -q
Full PYTHONPATH=src python3 -m pytest -q was attempted, but this environment's integration subprocesses failed before exercising argit because they could not import click.
🤖 Review by GPT-5 via Blink-Claw-Bot
… family Blink-Claw-Bot (GPT-5) HIGH: collect_drift delegated clean/stale to setup._classify_drift, which returns `clean` when the repo manifest is the latest revision within its OWN agent_version family — even if the selected bundled best-fit has moved to a newer family. A repo pinned to openclaw-2026.4.14-7 with bundled openclaw-2026.4.26-2 was reported clean (and repo_revision was wrongly taken from the bundled manifest), hiding exactly the pinned-vs-bundled gap this command exists to expose. - collect_drift now hashes the repo manifest against the SELECTED bundled manifest (clean) and reverse-looks-up the catalog for its true (version, revision); stale across families is reported with revisions_behind=null (rev numbers reset per family) + upgrade_available. - payload gains repo_agent_version / bundled_manifest_file / bundled_agent_version so cross-family gaps are legible. - human + doctor lines print both filenames for cross-family stale. - regression tests incl. non-mocked openclaw-2026.4.14-7 vs real bundled. - README: document selected-bundled semantics + branch on upgrade_available. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
blink-claw-bot
left a comment
There was a problem hiding this comment.
HIGH
- No blocking findings. The follow-up commit fixes the stale-family classification bug I raised:
argit driftnow compares the repo manifest against the selected bundled manifest, reports the repo manifest's true version/revision, and keepsrevisions_behindnull across version families where revision numbers are not comparable.
MEDIUM
- No additional findings.
LOW
- No additional findings.
Tests run:
PYTHONPATH=src python3 -m pytest tests/unit/test_drift_command.py tests/unit/test_drift_classifier.py tests/unit/test_drift_warning.py tests/unit/test_upgrade_flow.py tests/unit/test_doctor.py -q
Result: 46 passed.
🤖 Review by GPT-5 via Blink-Claw-Bot
|
Fixed in 53e010a (HIGH). You were right —
Regression coverage added, including a non-mocked test staging the shipped {"state":"stale_bundle","manifest_file":"openclaw-2026.4.14-7.manifest.json","repo_revision":7,"bundled_manifest_file":"openclaw-2026.4.26-2.manifest.json","bundled_revision":2,"revisions_behind":null,"upgrade_available":true} |
Closes #25.
Problem
Drift was only surfaced as human
= manifest drift: …lines fromsetup._handle_drift— same=prefix as unrelated no-ops ("manifest already present", "pass init already satisfied"), so automation can't reliably parse it. The--no-upgrade-manifestrev-lock pattern (#21) exists so automation controls upgrade timing, but the upgrade signal was plain-text-only.Fix:
argit driftA read-only subcommand — the single machine-parseable channel — reporting the same hash-only classification
setupuses (reuses_classify_drift/_bundled_manifest_path, so the two never disagree; noload_manifest, so a grammar-incompatible manifest still classifies).state∈clean | stale_bundle | operator_modified | no_manifest; agent-type mismatch raisesArgitError.= manifest drift: …phrasing.--json,--agent-type.Also
doctor: informationalmanifest driftrow via the shared core — never flips doctor's exit code (operator_modifiedis a note, not a failure).argit driftsection + documents the previously-undocumented--no-upgrade-manifestflag.Test
tests/unit/test_drift_command.py) covering all four states + agent-type-mismatch raise + JSON/human/exit-0; full unit suite green.🤖 Generated with Claude Code