Repository navigation
fix(#0140): stamp app_version into the record contribute() returns - #260
Merged
Merged
Conversation
The dict `contribute` returns is the only record a depositing site keeps, and it carried `schema_version` but no `app_version` — so a saved response could not be dated. `schema_version` does not cover this: 0.27.6 moved every `l2_from_base` value without changing the record's shape, so two responses of one shape can hold values computed by different releases. The stored metadata.json has carried `app_version` since 0.27.14 (#116); the copy that leaves the worker did not. Resolved once into a local and used by both writers, so the two records cannot date the same contribution differently by construction rather than by assertion. Found by the paper campaign auditing 92 saved responses in five undatable shapes. Their report framed it as three disagreeing write paths with nullable digests; that part does not hold — an AST walk finds exactly one return site carrying weights_sha256, and the digest comes from sha256_file, which returns a hexdigest or raises. #139 covers the reader-side guard. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Oct 27, 2026
Clearance-time bump for PR #260, measured at this moment rather than read off a row: origin/main's apps/model-finetune/manifest.yaml is 0.30.0 and the published bioimage-io/model-finetune artifact's newest committed version is also 0.30.0 (55 versions), so 0.30.1 is strictly greater than both baselines the gates compare against. Patch rather than minor because the change is additive to a returned record and alters no existing behaviour: contribute() now carries app_version in the value the client keeps, single-sourced with the copy already written to the stored metadata (#140). A caller that ignored the field is unaffected. Refs: #140
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 dict
contribute()returns is the only record a depositing site keeps, and it could not be dated. It carriedschema_versionbut noapp_version, so a client holding a saved response cannot tell a field that is null because this release could not compute it from one that is null because an older release did not have the field at all. That is precisely the ambiguityapp_versionwas added to remove, and the one record it was missing from is the one that leaves the worker.schema_versiondoes not substitute for it, andpool.pysays so on the line above the field: "0.27.6 moved everyl2_from_basevalue without changing the record's shape." Two responses of identical shape can hold values computed by different releases.The change
app_versionis resolved once into a local incontribute()and used by both the storedmetadata.jsonand the returned dict, so the two records cannot date the same contribution differently — by construction, not by assertion. Empty-is-unset is preserved (an exported-but-empty var would otherwise stamp""and read as a known version).Purely additive: a new key on a return dict, colliding with no existing name and duplicating no value. No stored record changes.
How it was found, and what in the report does NOT hold
Reported by the paper campaign auditing 92 saved deposit responses that came back in five shapes, with 4 nulls and 4 absent keys. They framed it as "the weights digest is written by at least three paths that disagree". Measured against
origin/main(2708230), that framing does not hold and is not what this PR fixes:return <dict>inentry.pyfinds exactly one site carryingweights_sha256(entry.py:1562). There are not three writers; the five shapes are five app versions of one writer.weights_bytes_on_serverand then_bytes/n_train_imagesshapes appear nowhere in the tree.runtime_cellpose.py:533sets it frompool.sha256_file(ckpt), which returnsh.hexdigest()or raises.metadata.json(wherebuild_contribution_metadatatakesweights_sha256: str, required) and in the index stub atentry.py:1540. So a real federation reading the pool finds it; the campaign's 8 gaps were gaps in their own saved responses.What survives is the datability gap above, which is real and is what this changes. The reader-side guard (refusing merge members with an absent or null digest) is #139, on its own branch.
Verification
Tests drive the real
contribute()coroutine with collaborators stubbed — the patterntest_merge_serialisation.pyalready uses — and compare the returned value against the metadata bytes actually PUT, not against a second read of the environment, which would pass even if the two writers diverged. No GPU, no torch, no live Hypha.Mutation control: deleting the one added line from the return dict turns 4 of the 5 new tests red (
4 failed, 1 passed). The survivor is the digest pin, which does not touchapp_version— so the new tests discriminate rather than pass vacuously.Why this is a DRAFT
No version edit yet, per the repo's bump-last rule.
app-version-check.ymlrequiresapps/model-finetune/manifest.yaml's version to already exist in the published artifact before this can go non-draft, so the bump + upload happen immediately beforegh pr ready— with the floor re-measured against the live artifact at that moment, not againstorigin/main. Holding until the paper campaign's merge chain is quiet.🤖 Generated with Claude Code