pit: an ending can be bought and renewed - #152
Merged
Merged
Conversation
ENDING_PRICE_USD was $5 and charged by nothing. registerTld inserted a row and claiming was free and permanent — no term, no expiry, nothing to renew. This is the lifecycle underneath that price: PRD 0005 §5's one-year term on a direct ending, and the CoinPay checkout that starts one. Modelled on the name purchase flow rather than invented beside it. Same shape, same reasons: keyed on the payment id so a webhook redelivery settles the same row; a conditional UPDATE to claim it, because CoinPay retries anything it never got an ack for and two deliveries read 'pending' before either write lands; a reservation that stops two people paying at once while the UNIQUE constraint stays the real arbiter at settlement. An ending taken between checkout and confirmation is recorded `refund_due` and logged. That is real money against something the buyer cannot have, and the name flow already decided that is not a thing to swallow. A renewal extends from whichever is later, the current expiry or now. Renewing early adds to what is left instead of throwing it away; renewing after a lapse does not backdate the term into the past. PRD 0005 R7. Both new columns are NULLable and every existing row keeps NULL. A NULL expiry means "no term recorded", which is what all ~250 endings claimed before today have, and isExpired deliberately reads it as not-expired. Backfilling an invented expiry would put a namespace on a clock its owner never agreed to; §21.8 wants a published grandfathering policy first, and that is a decision rather than a migration. 13 tests, including the two races that cost money: a redelivered webhook, and an ending claimed in the gap. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
vu1nz Security Review0 finding(s) in PR #? No security issues found. |
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.
ENDING_PRICE_USDwas $5 and charged by nothing.registerTldinserted a row and claiming was free and permanent — no term, no expiry, nothing to renew.This is the lifecycle underneath that price: PRD 0005 §5's one-year term on a direct ending, plus the CoinPay checkout that starts one.
Modelled on the name flow, not invented beside it
Same shape and same reasons: keyed on the payment id so a webhook redelivery settles the same row; a conditional
UPDATEto claim it, because CoinPay retries anything it never got an ack for and two deliveries both readpendingbefore either write lands; a reservation that stops two people paying at once, with theUNIQUEconstraint still the real arbiter at settlement.An ending taken between checkout and confirmation is recorded
refund_dueand logged — real money against something the buyer can't have.Renewals never shorten a term
Extends from whichever is later, the current expiry or now. Renewing early adds to what's left instead of throwing it away; renewing after a lapse doesn't backdate into the past. PRD 0005 R7. Both directions tested.
Grandfathering — deliberately not done
Both new columns are NULLable and every existing row keeps NULL. A NULL expiry means "no term recorded", which is what all ~250 endings claimed before today have, and
isExpiredreads it as not expired.Backfilling an invented expiry would put a namespace on a clock its owner never agreed to. §21.8 wants a published grandfathering policy first — that's a decision, not a migration.
Claiming is still free. This adds the paid path; it doesn't switch the free one off. Doing that would break the bulk-claim flow mid-session and is a product call, not a refactor.
Tests
13, including the two races that cost money: a redelivered webhook, and an ending claimed in the gap. Full pwa suite green.
Next
Buy Now button for endings (now unblocked), then grace/auctions from §13.
🤖 Generated with Claude Code