fix(manifest): correct published digests and make drift detectable - #1062
Merged
Conversation
platform-manifest.json is served publicly from raw.githubusercontent and is OpenAdapt's only cryptographic claim about the current release. It pinned openadapt-flow 1.23.0 with that release's wheel and sdist sha256, so anyone verifying today's published artifact against it failed. Regenerate from the live PyPI state (flow 1.24.0). All eight artifact digests were re-verified by downloading each file and hashing it, not by trusting a metadata field. The more important bug is that the drift detector missed the drift for hours and could have missed it for a week: * The staleness originates in ANOTHER repository, so no commit lands here and only the schedule can notice. It was weekly. It is now daily, plus a `component-released` repository_dispatch receiver so a component release can trigger the check immediately. * The check failed benignly after every single release, which is how a real failure became invisible: the semantic-release version commit leaves pyproject.toml ahead of the not-yet-reconciled manifest, and PyPI's info.version lags an upload by minutes. Main was already red from the 1.9.0 release when the genuine flow drift arrived. Both conditions now warn. Every filename, URL, and digest comparison stays fatal, and an unreachable PyPI warns rather than fails (an index outage is not evidence of drift) except at the release gate, which passes --require-network. * A red scheduled run on a repository nobody has open is not a signal, so a failed scheduled or dispatched run now files or comments on a drift issue. tests/test_platform_manifest_drift.py proves the guard FAILS when it should: against a simulated future release, a tampered digest, a tampered URL, and a version PyPI never published -- and that it does not fail on the two transient release-time conditions above. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NyCHrzA1psrKMFfroYbzaM
The daily and repository_dispatch runs exist to compare the committed manifest against PyPI, and that comparison needs nothing but the standard library. Gate the characterization suite to pull requests and pushes, where the guard's own source can change, so the remote drift probe stays fast and installs nothing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NyCHrzA1psrKMFfroYbzaM
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.
The wrong claim
platform-manifest.jsonis served publicly fromraw.githubusercontent.com/OpenAdaptAI/OpenAdapt/main/platform-manifest.json. It is OpenAdapt's only cryptographic claim about the current release, and it pinnedopenadapt-flow1.23.0 with that release's wheel and sdist sha256 (b5d10dfc…,b16e181a…).openadapt-flow1.24.0 published at 2026-07-27T03:58Z. Anyone verifying today's published artifact against the manifest failed.Regenerated from the live PyPI state. All eight artifact digests were re-verified by downloading each file and hashing it, not by trusting a metadata field:
openadapt-1.9.0-py3-none-any.whl5acc4273c2db403e…openadapt-1.9.0.tar.gza181627615e3ff74…openadapt_flow-1.24.0-py3-none-any.whl170fdac154794292…openadapt_flow-1.24.0.tar.gz2d4702e5ccbdfed0…openadapt_capture-1.2.0-py3-none-any.whlda3b2ef52b3eb24c…openadapt_capture-1.2.0.tar.gz5eb5f7df329e58a1…openadapt_desktop-0.14.0-py3-none-any.whl7ee5a1d2e00f5cbd…openadapt_desktop-0.14.0.tar.gz44a50bd2c126524a…Why the drift detector did not fire in time — the more important bug
1. The staleness originates in another repository. Flow released; nothing was committed here; only the schedule could notice. It was weekly (
17 6 * * 1), so the public copy could advertise superseded digests for up to seven days. Now daily, plus acomponent-releasedrepository_dispatchreceiver so a component release can trigger the check immediately. The job is stdlib-only — no install, no cache — so this costs seconds a day.2. The manifest has no automatic remediation path for non-launcher releases.
reconcile-platform-manifestlives in this repo's release workflow and only runs when the launcher releases.3. The check went benignly red after every single release, so a real failure was indistinguishable from noise. Two normal conditions were classified as errors:
1.9.0version commit) —manifest has '1.8.0' but pyproject.toml has '1.9.0'. Guaranteed at every release: semantic-release pushes the bump ~90s before the reconcile job regenerates the manifest.manifest has '1.9.0' but PyPI's latest openadapt is '1.8.0'. Guaranteed at every release: PyPI'sinfo.versionlags an upload on some CDN edges.mainwas therefore already red from the previous night's 1.9.0 release when the genuine flow drift turned run 30257087341 red. A guard that cries wolf at every release stops being read.Both conditions now warn. Every filename, URL, and digest comparison stays fatal. An unreachable PyPI warns rather than fails (an index outage is not evidence of drift) — except at the release gate, which now passes
--require-network.4. A red scheduled run on a repository nobody has open is not a signal. A failed scheduled or dispatched run now files (or comments on) a
platform-manifest.json has driftedissue, so drift gets an owner.Proof the guard fails when it should
tests/test_platform_manifest_drift.py(12 tests) exercises the pure comparison functions against a simulated future release, a tampered digest, a tampered URL, and a version PyPI never published — and pins that the two transient release-time conditions do not fail.End-to-end against live PyPI:
Follow-up (out of scope here)
The sending half of the dispatch is a one-step
gh api repos/OpenAdaptAI/OpenAdapt/dispatches -f event_type=component-releasedinopenadapt-flow/-capture/-desktop's release workflow. Until that lands, the daily sweep bounds exposure at 24h instead of 7 days.🤖 Generated with Claude Code
https://claude.ai/code/session_01NyCHrzA1psrKMFfroYbzaM