plans: names terms review fixes in contract

This commit is contained in:
shum committed 2026-10-23 13:02:24 +00:00
1 parent d84c19d3de
commit 32419107a9
1 file changed
+22 -4
+22 -4
View File
@@ -117,10 +117,12 @@ The mock reads every body up to 16384 bytes. A body the page builds is never nea
The buyer agrees to the terms on Check your order, and the signed order records which terms (owner, 2026-10-23).
- **The terms version** is the service's setting `[names] terms_version` (1–32 characters of `A-Za-z0-9._-`), with `[names] terms_url`, the page the checkbox links to. The available search answer carries both, as `terms: {"version", "url"}`. They change only with a restart.
- **Community names** are a list the operator keeps in the service's ini, section `[community]`, one line per name: `<label> = <what it is for>`, for example `bakers = bakers and bakeries in Portugal`. Each label must pass the label rule (section 1, Sizes) and be listed once; each text is one line of 1–200 characters with no control characters; anything else stops the service at startup, naming the line. The service keys the list by label hash, so search still never needs the label. It is read at startup; an edit takes effect on restart.
- **Community names** are a list the operator keeps in the service's ini, section `[community]`, one line per name: `<label> = <what it is for>`, for example `bakers = bakers and bakeries in Portugal`. Each label must pass the label rule (section 1, Sizes) and be listed once; each text is one line of 1–200 characters with no control characters; anything else stops the service at startup, naming the line. The service keys the list by label hash, so search still never needs the label. It is read at startup; an edit takes effect on restart, for new orders and moves only (an open order keeps what it signed). The service also refuses a `[community]` section without `[names]` and any section header that appears twice in the file (the `ini` parser keeps only the last), and logs how many community names it loaded.
- **`community`**: every search answer (available, registered, being registered here, reserved) and the registration view carry `community: {"for": "<text>"}` for a listed name, else null. The page shows "Community ⓘ" with the name and the popup "This name is for <text>. Its owner agrees to use it for this community, not as the name of one person or business."
- **The signed terms.** Every order (`/api/invoice` for a name, `/api/name/register`, `/spend`) and every move (`/name`) carries `terms` and signs it: the version for a name not on the list, and the version followed by `/community` (`2026-10-23/community`) for a listed name, which records that the buyer ticked the community wording ("I agree to SimpleX name terms and will use this name for its community."). One terms document and one version serve both; the suffix is the community agreement, not another version. The service expects exactly that value for the name at the time of the request; any other value (an old version, or the wrong variant because the list changed) is `409 {"error": "terms_changed", "terms": {"version", "url"}, "community": {…} or null}`, from which the page shows the checkbox again and signs anew.
- **Stored and resent.** The registration stores the `terms` it was ordered or last moved with. `terms` is in `order_hash`, and an exact resend is answered before the terms check, so a lost answer is safe even after the version changed. A move stores the new name's `terms`; the original `order_hash` stays.
- **When the page asks** (manager's ruling, 2026-10-23): before every signature for a name the order has not agreed for (a new order, a spend, a move to a different name) the page shows the checkbox, with the community wording for a listed name. A signature for the same name again (pay again after an expired invoice, spend a balance again, a move without links) reuses the order's `terms` without asking only when they equal the current terms for that name; otherwise it asks first.
- **Signature vectors** for these messages are in Appendix C, which both implementations reproduce byte for byte.
### Signatures
@@ -383,7 +385,7 @@ A cancel that lost to settlement answers `200` with `status: paid`; the page re-
{"id": "t2Jk8RmQ4vXw9NcP3yLb6g", "version": 7, "moves": 0, "linksRefused": false, "phase": "committed", "labelHash": "0x…", "label": null, "owner": "0x…",
"commitment": "0x…", "sealed": "…", "resolver": "0x…", "sponsor": "0x…", "chainId": 1, "tld": "simplex",
"years": 2, "duration": 69292800, "price": 10000, "fromBalance": 0, "balance": null, "latePayment": null, "commitTx": "0x…", "registerTx": null,
"revealAfter": 1792828800, "revealBefore": 1792915140, "intentNonce": 0, "unread": [], "expiresAt": null, "error": null, "community": null}
"revealAfter": 1792828800, "revealBefore": 1792915140, "intentNonce": 0, "unread": [], "expiresAt": null, "error": null, "community": null, "terms": "2026-10-23"}
```
- `id`: a random 128-bit token in base64url. Whoever holds it can follow the registration and send its reveal and retry.
@@ -395,6 +397,7 @@ A cancel that lost to settlement answers `200` with `status: paid`; the page re-
- `balance`: this order's balance (section 8): null until its first credit, then always an object, with `amount` 0 once spent or refunded: `{"amount": 10000, "held": false, "heldUntil": null, "refunded": 0}`. `amount`: the cents left to spend. `held`: an order's invoice holds the balance. `heldUntil`: that invoice's `expiresAt` while no money has reached it, so the hold ends then unless paid; null when not held, and null while held by an invoice that money reached (it holds until its payment settles or the provider closes it, whatever the time; the page shows "in use by a payment being confirmed" and reads the view again). A holder whose invoice is past its expiry time with no money received reads as none (`held` false), as its invoice reads `expired`. `refunded`: the cents support refunded from it, over all its refunds. The holder is not named: whoever holds this order's link gets no other order's id from it. The page knows the holder from its own records (the order it made by `/spend`), or from `find`'s `heldBy` (section 8).
- `latePayment`: null, or `{"waiting": "confirming" | "crediting" | "support"}` while money on this closed `unpaid` order is not all credited to its balance (section 8, late payments). `confirming`: money received, not yet confirmed. `crediting`: confirmed, the credit not yet made (the poller's next pass, or a rate read that is failing for now). In both the page reads the view again. `support`: the credit will not come by itself (an invoice made under `HighSpeed`, an amount that could not be counted in units, a current rate below the floor, or unconfirmed money past the read window, once alerted); the page stops reading and tells the buyer to write to support with the reference. It turns null once all is credited, whatever it said before.
- `community`: the community entry for `labelHash`, as in search (section 1, Terms and community names), else null.
- `terms`: the terms this order signed when it was made or last moved (section 1), null for an order stored without them. The page compares it with the current terms before signing again for the same name.
- `commitTx`, `registerTx`: among the hashes sent since they were last cleared, the one mined with status 1, else the latest sent. Null when none, or when it was judged mined from chain state with no hash known.
- `revealAfter`, `revealBefore`: the commit's block time plus the controller's `minCommitmentAge` and `maxCommitmentAge`.
- `intentNonce`: `SimplexSponsor.nonces(owner)`, read only in `committed`, null otherwise.
@@ -494,7 +497,7 @@ Finds a name code's orders. Used by Check your code (the name code's verdict and
```
- Message: `simplex.domains name <registrationId> <labelHash> <length> <duration> <commitment> <sha256(sealed)> <terms> <timestamp>`. An `owner` in the body is ignored. `terms` are the new name's (section 1, Terms): a move reaches a name the buyer has not agreed for, and a community name needs the community wording.
- A body whose fields equal the registration's current ones is a repeat: it answers the view unchanged and counts no move.
- A body whose fields equal the registration's current ones, `terms` included, is a repeat: it answers the view unchanged and counts no move. A body that differs only in `terms` is a move, checked like any other.
- Allowed only (manager's ruling, 2026-10-21; `taken` removed by the owner's decisions of 2026-10-22, which keep a taken order's payment as its balance): for any registration in `failed`; and for any registration in `committed` whose reveal was refused for its links (`invalid_link`, recorded by the reveal). Never from a `committed` registration that has had no such refusal, nor from `taken`. Else `bad_request`. The new name may be the same name with other links.
- From `failed`, `/name` first runs `/retry`'s check 1 (section 9) on the current name: a register that went through makes the order `registered`, and `/name` answers `409 registration_changed` with that view, so one payment never registers two names. A failed chain read in that check answers `503 name_check_unavailable`.
- At most `max_order_moves` (3) successful moves per order over its life, counted on success; past that, `409 {"error": "move_limit", "registration": {…the current view}}`. The order stays as it is (`failed`, or `committed` with its links refused); the page shows its state with a line to contact SimpleX support and the order's reference, and support settles it by hand, refunding a paid order.
@@ -661,7 +664,7 @@ Each line: the difference, the choice here, and what changes in the mock (`webap
| `order_exists` | 409 | | orders, register, `/name`, `/spend` |
| `name_taken` | 409 | | orders, register, `/name`, `/spend` |
| `catalog_changed` | 409 | `price` | `/api/invoice`, `/spend` |
| `terms_changed` | 409 | `terms` (`{version, url}`), `community` | orders, register, `/name`, `/spend` |
| `terms_changed` | 409 | `terms` (`{version, url}`), `community` | orders, register, `/name`, `/spend` (before `balance_changed`, the code's state and the chain) |
| `balance_changed` | 409 | `balance` (the current balance, or null) | `/spend` |
| `price_too_high` | 409 | `price` | `/name` |
| `code_used`, `code_reserved`, `code_expired`, `payment_pending`, `code_not_covering` | 409 | | register, `/name` (a code order; `code_expired` only for a released code), retry |
@@ -674,3 +677,18 @@ Each line: the difference, the choice here, and what changes in the mock (`webap
| `provider_unavailable` | 503 | | search, cancel, invoice and `/spend` (payment method), reveal, retry, records |
| `name_check_unavailable` | 503 | | orders, register, `/name`, `/spend` |
| `edit_pending` | 504 | | records |
## Appendix C. Signature vectors
The webapp computes these once (task W1j-0, with viem) and writes them here; the service's `NamesSignatureTests.hs` and the webapp's tests paste them unchanged. Inputs: the webapp's fixture owner code (`test/fixtures`), label `alicebakery` (length 11), `duration` 69292800, the commitment the webapp builds for it with no links, `sealed` as the fixture's, registration id `t2Jk8RmQ4vXw9NcP3yLb6g`, `timestamp` 1792828800.
| Message | `terms` | Message text | EIP-191 digest | Signature |
|---|---|---|---|---|
| order | `2026-10-23` | (W1j-0) | (W1j-0) | (W1j-0) |
| order | `2026-10-23/community` | (W1j-0) | (W1j-0) | (W1j-0) |
| name (move) | `2026-10-23` | (W1j-0) | (W1j-0) | (W1j-0) |
| name (move) | `2026-10-23/community` | (W1j-0) | (W1j-0) | (W1j-0) |
| spend | `2026-10-23` | (W1j-0) | (W1j-0) | (W1j-0) |
| spend | `2026-10-23/community` | (W1j-0) | (W1j-0) | (W1j-0) |
| find | (none) | unchanged | unchanged | unchanged |