fix(organizations): expire handshakes that pass their deadline - #2551
Merged
Merged
Conversation
AWS gives a handshake 15 days and expires it on its own. fakecloud stamped an `ExpirationTimestamp`, reported it, and then never acted on it, so an overdue invitation stayed OPEN and acceptable forever and the timestamp was decoration. `EXPIRED` is a modeled member of both `HandshakeState` and `ResponsibilityTransferStatus`, and nothing ever produced it. Every Organizations call now sweeps past-due handshakes first. The sweep cannot hang off the mutating paths alone: a `DescribeHandshake` has to report `EXPIRED` too, and an `AcceptHandshake` must be refused rather than reviving an offer that lapsed. A read-locked staleness check keeps the write lock for the requests that actually have something to expire, and it is self-limiting -- once swept, nothing is stale. Expiry runs through `resolve_handshake` like every other terminal transition, so a responsibility transfer riding an expired handshake ends with it instead of sitting REQUESTED with a live `ActiveHandshakeId` pointing at a handshake that is gone. Its `EndTimestamp` is the handshake's deadline, not the moment the sweep noticed: `resolve_handshake` stamps "now", which is right for a decline or a cancel because somebody acted then, but an idle process would otherwise report a transfer ending days after the `ExpirationTimestamp` on the same record. The sweep is a mutation even when it happens on the way into a read, so it is persisted there too -- otherwise the expiry is lost on restart and the handshake comes back OPEN, past its deadline and acceptable again. `/_fakecloud/organizations/responsibility-transfers` sweeps for the same reason: it read the same object and answered `REQUESTED` with a live `activeHandshakeId` while the API already said `EXPIRED`. Tests: an overdue invitation reports EXPIRED and cannot be accepted or enroll its target; an expired transfer ends at its deadline, drops its handshake, agrees with introspection, and stops blocking a fresh offer; an expiry noticed by a read reaches the snapshot store; and a handshake inside its 15 days is left alone. Each behavioral assertion was checked to fail with the fix reverted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EeYYZqG5anqYXR8761ZjWu
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.
Summary
AWS gives a handshake 15 days and expires it on its own. fakecloud stamped an
ExpirationTimestamp, reported it, and then never acted on it, so an overdue invitation stayedOPENand acceptable forever.EXPIREDis a modeled member of bothHandshakeStateandResponsibilityTransferStatus, and nothing ever produced it. The docs already claimed "expired handshakes flip toEXPIRED" -- this makes that true.DescribeHandshakehas to reportEXPIREDtoo, and anAcceptHandshakemust be refused rather than reviving a lapsed offer. A read-locked staleness check keeps the write lock for requests that actually have something to expire, and it is self-limiting.resolve_handshakelike every other terminal transition, so a responsibility transfer riding an expired handshake ends with it rather than sittingREQUESTEDwith anActiveHandshakeIdpointing at a handshake that is gone.EndTimestampis the handshake's deadline, not the moment the sweep noticed.resolve_handshakestamps "now", which is right for a decline or cancel because somebody acted then; an idle process would otherwise report a transfer ending days after theExpirationTimestampon the same record.OPEN, past its deadline and acceptable again./_fakecloud/organizations/responsibility-transferssweeps too: it read the same object and answeredREQUESTEDwith a liveactiveHandshakeIdwhile the API already saidEXPIRED.Surface impact
website/content/docs/services/organizations.mdsharpened -- it already asserted expiry worked, now it says when and what happens to a riding transfer.Test plan
cargo nextest run -p fakecloud-conformance -E 'test(organizations)'-- 10/10cargo nextest run -p fakecloud-e2eorganizations + cloudformation_organizations -- 47/47cargo clippy --workspace --all-targets -- -D warningsclean🤖 Generated with Claude Code
https://claude.ai/code/session_01EeYYZqG5anqYXR8761ZjWu
Summary by cubic
Expires Organization handshakes that pass their 15-day deadline. Overdue handshakes previously stayed
OPENand acceptable forever —EXPIREDwas modeled but never produced, so the reportedExpirationTimestampwas decoration.DescribeHandshakereportsEXPIREDandAcceptHandshakeis refused instead of reviving a lapsed offer./_fakecloud/organizations/responsibility-transfersintrospection route sweeps too, so it no longer contradicts the API.Written for commit 7063da9. Summary will update on new commits.