fix(module): let a pin advance without deinit or a signing key - #56
Merged
Conversation
Advancing an estate module pin was unreachable in practice. Three guards on the same operation each refused the state the previous one required. `GDS_MODULE_PIN_CONSUMER_STATE_UNSAFE` wanted a clean consumer. An advanced submodule is reported as one unstaged gitlink, so the consumer was never clean. `GDS_MODULE_PIN_GITLINK_NOT_ELIGIBLE` then wanted a *changed* gitlink whose checkout was absent. The two read as a contradiction, and the only combination that satisfied both was `git submodule deinit` plus a separate module clone -- four preconditions that hold simultaneously and are written down nowhere. Past that, apply demanded a signed approval bound to the exact plan. None of that protects anything. The only mutation is a gitlink rewrite in the consumer's own working tree: no provider write, no credential, nothing published, and the consumer's own pull request and checks are the real gate. The cost was real: the estate silently drifted behind modules it had already merged, because the pin could not be advanced without the private signing key. So a checkout already sitting at the target commit is now accepted -- stronger evidence than an absent directory, since the consumer holds the commit it is about to pin -- and apply takes no approval. The relaxation is exactly one gitlink wide, through the mutation runner's pre- and postconditions and the handler's verify: any staged, untracked or conflicted path, or a second unstaged one, still refuses. Signed approval stays on every operation that writes outside the repository: provider lifecycle, rulesets, releases and anchors. Two tests carry it. One drives the previously impossible shape end to end and passes no approval reference at all. The other adds one untracked file to that same tree and requires the refusal, so the relaxation cannot widen unnoticed. Closes #51. Claude-Session: https://claude.ai/code/session_01BjpW1NtF1oik18aEMMGtMP
The pin change edited the lifecycle contract, which is part of the source tree the bundle lock digests. Regenerated through `gds generate repository` plan/apply rather than by hand. Claude-Session: https://claude.ai/code/session_01BjpW1NtF1oik18aEMMGtMP
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.
Advancing an estate module pin was unreachable in practice. Three guards on the same operation each refused the state the previous one required.
GDS_MODULE_PIN_CONSUMER_STATE_UNSAFEGDS_MODULE_PIN_GITLINK_NOT_ELIGIBLEGDS_SIGNED_APPROVAL_REQUIREDThe first two read as a contradiction; the only combination satisfying both is
git submodule deinitplus a separate module clone — four preconditions that must hold simultaneously and are documented nowhere (#51).None of it protects anything. The only mutation is a gitlink rewrite in the consumer's own working tree: no provider write, no credential, nothing published. The consumer's own pull request and checks are the real gate. The cost was real — the estate silently drifted behind modules it had already merged, because a pin could not be advanced without the signing key.
What changes
uninitialized: the mutation runner's precondition and postcondition, and the handler's verify.Proof
TestModuleUpdatePinAcceptsCheckedOutModuleWithoutApprovaldrives the previously impossible shape end to end and passes no approval reference at all.TestModuleUpdatePinStillRefusesAnUnrelatedChangeadds one untracked file to that same tree and requires the refusal, so the relaxation cannot widen unnoticed. Fullgo test ./...andscripts/validate_ci_tier.sh fastpass.Closes #51.
https://claude.ai/code/session_01BjpW1NtF1oik18aEMMGtMP