diff --git a/plans/names-codes/2026-10-21-store-service-tasks.md b/plans/names-codes/2026-10-21-store-service-tasks.md index a52ff3e1ca..5cf0123d4f 100644 --- a/plans/names-codes/2026-10-21-store-service-tasks.md +++ b/plans/names-codes/2026-10-21-store-service-tasks.md @@ -384,30 +384,31 @@ The owner's decisions of 2026-10-22 remove the code for a taken order and keep a ### Terms and community names (owner, 2026-10-23) -The owner's decisions of 2026-10-23: community names come from a list the operator keeps in the service's configuration and reach the page with every search answer; every order signs the version of the terms the buyer agreed to (contract section 1, Terms and community names; Appendix A, items 39 and 40). T28 builds the configuration and the answers; T29 the signed `terms`. T29 needs T28's `termsFor`, so they run in waves 12 and 13. +The owner's decisions of 2026-10-23: community names come from a list the operator keeps in the service's configuration and reach the page with every search answer; every order signs the version of the terms the buyer agreed to (contract section 1, Terms and community names; Appendix A, items 39 and 40). T28 builds the configuration and the answers; T29 the signed `terms`. T29 needs T28's `termsFor` and the webapp's vectors (W1j-0), so they run in waves 12 and 13. #### T28. The community list and the terms setting -- **Goal:** the operator's list of community names and the terms version and URL, read at startup, in the search answers and the registration view. -- **Files:** `StoreService/Config.hs` (`[community]`, and `[names] terms_version`, `terms_url`); `apps/simplex-store-service/store_service.ini.example`; new `StoreService/Names/Community.hs`; `StoreService/Names/Env.hs` (the list in `NamesEnv`); `StoreService/Web/Names/Search.hs`; `StoreService/Names/View.hs`; `apps/simplex-store-service/README.md` (editing the list and bumping the terms version, each taking effect on restart); `simplex-chat.cabal`; `tests/Test.hs`; `tests/Bots/StoreService/ConfigTests.hs`, `NamesSearchTests.hs`, `NamesOrdersTests.hs` (the view); new `tests/Bots/StoreService/NamesCommunityTests.hs`. +- **Goal:** the operator's list of community names and the terms version and URL, read at startup into `NamesConfig`, in the search answers and the registration view. +- **Files:** `StoreService/Config.hs` (`[community]`, `[names] terms_version` and `terms_url`, the list built into `NamesConfig` as `nCommunity :: Map LabelHash Text` and `nTerms`, the repeated-section check, the count logged at startup); new `StoreService/Names/Community.hs` (`communityFor`, `termsFor`, over `NamesConfig`); `StoreService/Web/Names/Search.hs`; `StoreService/Names/View.hs`; `apps/simplex-store-service/store_service.ini.example`; `scripts/store-service/store_service.ini.example`; `scripts/store-service/names-contract-run.sh` (writes `terms_version`, `terms_url` and one `[community]` line for the e2e spec's fixed label `bakers`); `apps/simplex-store-service/README.md`; `simplex-chat.cabal`; `tests/Test.hs`; `tests/Bots/StoreService/ConfigTests.hs` (and `namesSection`, which other specs reuse), `NamesSearchTests.hs` (its `NamesConfig` at line 117), `NamesOrdersTests.hs` (the view, its `NamesConfig` at line 295); new `tests/Bots/StoreService/NamesCommunityTests.hs`. In this repository, on `sh/names-design`: `plans/names-codes/e2e/deployLocal.ts` (its `[names]` block gains the two settings), committed by the manager with T28's merge. - **Do:** - - `[names] terms_version` (required with `[names]`; 1–32 characters of `A-Za-z0-9._-`) and `terms_url` (required; an `https://` URL). - - `[community]`: each key a label, each value its text. Its keys are names, so `unknownKeys` skips the section instead of warning. Startup refuses, naming the line: a label that fails `validLabel` (T2) for the configured TLD, a label listed twice (the task confirms how the `ini` parser treats a repeated key and reads the section's raw lines if it keeps only one), a text empty, over 200 characters, or with control characters. - - `Names/Community.hs`: the list as a map from label hash (keccak256 of the label, as the page sends it) to text, built once at startup; `communityFor :: NamesEnv -> LabelHash -> Maybe Text`; `termsFor :: NamesEnv -> LabelHash -> Text` (the version, with `/community` for a listed name), which T29 uses. + - `[names] terms_version` (required; 1–32 characters of `A-Za-z0-9._-`) and `terms_url` (required; an `https://` URL). No default: a default version would record agreement to a document nobody named. + - `[community]`: each key a label, each value its text, built by `Config.hs` into `nCommunity` keyed by the label's keccak256 (as the page sends it). `unknownKeys` skips the section's keys. Startup refuses, naming the line or key: `[community]` without `[names]`; any section header that appears twice in the file (`ini` 0.4.1 builds its section map last-wins, so the check reads the file's header lines before parsing); a key listed twice in `[community]` (`keys "community"` returns every occurrence, in order); a label that fails `validLabel` (T2) for the configured TLD; a text empty, over 200 characters, or with control characters. Startup logs how many community names it loaded. + - `Names/Community.hs`: `communityFor :: NamesConfig -> LabelHash -> Maybe Text`; `termsFor :: NamesConfig -> LabelHash -> Text` (the version, with `/community` for a listed name), which T29 uses. Every consumer already holds `NamesConfig`, so no handler signature changes. - Search: `community` in every answer, `terms: {version, url}` in the available one. View: `community` from the registration's label hash. - - The README says the list is read only at startup, that editing it changes which wording new orders must sign, and that a terms version bump makes every unsigned page re-ask (`terms_changed`). -- **Acceptance:** the answers' JSON as the contract's examples, field for field; badge specs green. -- **Tests:** the config parsed with and without `[community]`; each refusal (a bad label, an upper-case one, a repeated one, an empty, an over-long and a multi-line or control-character text, a missing or malformed `terms_version` or `terms_url`), naming its line; no warning for community keys; `community` in available, registered, being-registered and reserved answers, and null for an unlisted name; `terms` in the available answer; the view's `community`; `termsFor` for a listed and an unlisted name. + - README: the list and the version are read only at startup; an edit applies to new orders and moves only, and open orders keep what they signed; after a restart, search each listed name and check its marker shows; a text should not end with a full stop, since the popup adds one; an upgrade note that an existing ini needs `terms_version` and `terms_url` before the service starts. +- **Acceptance:** the answers' JSON as the contract's examples, field for field; T21's script starts the service with the new settings; badge specs green. +- **Tests:** the config parsed with and without `[community]`; each refusal, naming its line or key: `[community]` without `[names]`, a repeated `[community]` header and a repeated `[names]` header, a repeated key, a bad label, an upper-case one, an empty, an over-long and a control-character text, a missing or malformed `terms_version` or `terms_url`; no warning for community keys; the loaded count logged; `community` in available, registered, being-registered and reserved answers, and null for an unlisted name; `terms` in the available answer; the view's `community`; `termsFor` for a listed and an unlisted name. - **Depends on:** T10, T11 (merged). Wave 12. #### T29. The terms version in the signed order -- **Goal:** every order and move carries and signs `terms`, and the service refuses terms that are not the current ones for the name. -- **Files:** `StoreService/Names/Signatures.hs` (`orderMessage`, `retargetMessage`, `spendMessage` gain `` before ``; `OrderFields` gains `ofTerms`); `StoreService/Names/Orders.hs` (`parseOrderFields`, `checkTerms`, `ORTermsChanged`); `StoreService/Web/Names/Orders.hs` (`/api/invoice`, `/api/name/register`, `/spend`, and `terms_changed`'s response); `StoreService/Web/Names/Owner.hs` (`/name`); `StoreService/Names/Balances.hs` (the spend's checks); `StoreService/Names/Store.hs` (the `terms` column, `orderHash` over `terms`, `retargetRegistration` storing the new `terms`); the two migration modules (migration `20261023_name_terms`: `name_registrations.terms`, text, not null, default ''); `tests/Bots/StoreService/NamesSignatureTests.hs`, `NamesOrdersTests.hs`, `NamesSpendTests.hs`, `NamesOwnerTests.hs`, `NamesStoreTests.hs`. -- **Do:** the contract's checks in their order: `terms` format with the other formats (`bad_request`); existing orders before it, so an exact resend, `terms` included in its hash, is answered whatever the version is now; then, after the duration, `terms` equal to `termsFor` for the (new) label hash, else `409 {"error": "terms_changed", "terms": {"version", "url"}, "community": …}`. The registration stores the signed `terms`; a move replaces it; the original `order_hash` stays. Existing rows keep '' (no database holds names rows yet). -- **The shared vector:** one order, one spend and one move signed with `terms` `2026-10-23/community` by the webapp's fixture owner code, their messages, EIP-191 digests and signatures computed once by the webapp (viem) and pasted, with the same inputs, into `NamesSignatureTests.hs` and the webapp's test (W1j); both must reproduce them byte for byte. -- **Tests:** the vectors; each endpoint refusing an old version and the wrong variant for a listed and an unlisted name with `terms_changed` and the current terms and `community`; an exact resend after the version changed answered with its order; two orders differing only in `terms` with different `order_hash`; a malformed `terms` `bad_request`; a move to a community name with the plain version refused, with the community variant accepted, its `terms` stored; the order of checks (a stale timestamp with old terms is `bad_request`; old terms on a taken name is `terms_changed`, before the chain). -- **Depends on:** T28. Wave 13. T21 is run again after it against the webapp with W1j. +- **Goal:** every order and move carries and signs `terms`; the service refuses terms that are not the current ones for the name, stores what was signed, and returns it in the view. +- **Naming:** the code already uses `OrderTerms` (`OTInvoice`, `OTCode`, `OTBalance`) for the payment side of `order_hash`. The new field is `ofAgreedTerms` / `nrAgreedTerms`, column `agreed_terms`; the JSON name stays `terms`. +- **Files:** `StoreService/Names/Signatures.hs` (`orderMessage`, `retargetMessage`, `spendMessage` gain `` before ``; `OrderFields` gains `ofAgreedTerms`); `StoreService/Names/Orders.hs` (`parseOrderFields`, `checkTerms`, `ORTermsChanged`; `orderFieldsOf` compares it, so a body differing only in `terms` is a move); `StoreService/Web/Names/Orders.hs` (`/api/invoice`, `/api/name/register`, `/spend`, `terms_changed`'s response); `StoreService/Web/Names/Owner.hs` (`/name`); `StoreService/Names/Balances.hs` (the spend's checks); `StoreService/Names/Store.hs` (the column, `orderHash` over it, `retargetRegistration` storing the new value); `StoreService/Names/View.hs` (`terms`: the stored value, null for ''); the two migration modules (migration `20261023_name_terms`: up `ALTER TABLE @name_registrations ADD COLUMN agreed_terms TEXT NOT NULL DEFAULT ''`, SQLite needing the default for `NOT NULL`; down `ALTER TABLE @name_registrations DROP COLUMN agreed_terms`); `tests/Bots/StoreService/NamesSignatureTests.hs` (the order, name and spend vectors at lines 38–62 regenerated from Appendix C, `find`'s unchanged), `NamesOrdersTests.hs`, `NamesSpendTests.hs`, `NamesOwnerTests.hs`, `NamesStoreTests.hs`. +- **Do:** the contract's checks in their order: `terms` format with the other formats (`bad_request`); existing orders before it, so an exact resend, `terms` included in its hash, is answered whatever the version is now; then, after the duration, `terms` equal to `termsFor` for the (new) label hash, else `409 {"error": "terms_changed", "terms": {"version", "url"}, "community": …}`, before `/spend`'s balance, the code's state and the chain. The registration stores the signed value; a move replaces it; the original `order_hash` stays. +- **Tests:** Appendix C's vectors, plain and `/community`; each endpoint refusing an old version and the wrong variant, for a listed and an unlisted name, with `terms_changed` and the current terms and `community`; the stored value on each insert path (invoice, code, covered spend, difference spend) and in the view, null for a '' row; an exact resend after a version bump for each kind (the invoice view, the code order's `{registrationId}`, the spend); two orders differing only in `terms` with different `order_hash`; a `/name` body differing only in `terms` counted as a move and checked; a malformed `terms` `bad_request`; a move to a community name with the plain version refused and with the community variant accepted, its value stored; the order of checks: a stale timestamp with old terms `bad_request`, old terms on a taken name `terms_changed` before the chain, `/spend` `terms_changed` before `balance_changed`, `/api/name/register` `terms_changed` before the code's state, and `/name` from `failed` running `/retry`'s check 1 before the terms; the migration up and down on SQLite and Postgres. +- **Rollout:** an older page that sends no `terms` gets `bad_request` once T29 lands; W1j ships with it (nothing is live yet). +- **Depends on:** T28, and W1j-0 (Appendix C's values written). Wave 13. T21 is run again after it, against the webapp with W1j, and must pass. ### Stages 6 and 7: the service side @@ -444,8 +445,8 @@ Each wave starts from `sh/store-service` as merged at the end of the last one. " | 9 | T23, T27 | `Names/Store.hs`, the migrations, `Orders.hs`, `Names/Turns.hs`, `Names/Balances.hs`, `Names/View.hs`, `Names/Orders.hs`, and, for `RPBalance`'s case, `balance_changed`'s response and `createPaidRegistration`'s call only, `Names/Worker.hs`, `Web/Names/Orders.hs`, `Web/Names/Owner.hs`. T27: `README.md` | | 10 | T24, T25, T26 | T24: `Names/Worker.hs`, `Orders.hs`, `Names/View.hs`, the migrations, `Poller.hs`, `Providers.hs`, `Providers/BTCPay.hs`, `Providers/Stripe.hs`, `Store/Invoices.hs`, `Names/Store.hs`, `Names/Orders.hs`, `Web/Names/Owner.hs`. T25: `Web/Names/Orders.hs`, `Names/Balances.hs`, `Names/Signatures.hs`. T26: `Service.hs`, `README.md` | | 11 | T21 | `README.md` | -| 12 | T28 | `Config.hs`, `Names/Env.hs`, `Web/Names/Search.hs`, `Names/View.hs`, `README.md` | -| 13 | T29 | `Names/Signatures.hs`, `Names/Orders.hs`, `Web/Names/Orders.hs`, `Web/Names/Owner.hs`, `Names/Balances.hs`, `Names/Store.hs`, the migrations | +| 12 | T28 | `Config.hs`, `Names/Community.hs`, `Web/Names/Search.hs`, `Names/View.hs`, both ini examples, `names-contract-run.sh`, `README.md`, `simplex-chat.cabal` and `tests/Test.hs` lines, `ConfigTests.hs`, `NamesSearchTests.hs`, `NamesOrdersTests.hs`, `NamesCommunityTests.hs` | +| 13 | T29 | `Names/Signatures.hs`, `Names/Orders.hs`, `Web/Names/Orders.hs`, `Web/Names/Owner.hs`, `Names/Balances.hs`, `Names/Store.hs`, `Names/View.hs`, the migrations, `NamesSignatureTests.hs` (after W1j-0) | The wave-10 integrator, after merging T24, T25 and T26, adds one end-to-end spec to `NamesSpendTests.hs`: a difference spend paid at `FakeBTCPay` and settled (the order `queued`, the hold spent); the same cancelled (the hold released); a covered spend's order ending `taken` (a balance of its own, its whole `price`); a `//refund` of the rest; and a difference invoice paid after it closed, credited to its order's balance. Wave 10 has no other dependency between its tasks. @@ -492,29 +493,41 @@ In simplex.domains, branch `sh/names-webapp`, in the webapp's workstream beside ## Webapp tasks for the terms, community names and the approved mockup -In simplex.domains, `sh/names-webapp`, beside waves 12 and 13, following the contract (Appendix A, items 39 and 40) and ep's approved mockup (2026-10-23; the queue in `.superpowers/program/webapp-queue.md`, entries of 2026-10-22 and 2026-10-23). +In simplex.domains, `sh/names-webapp`, beside waves 12 and 13, following the contract (Appendix A, items 39 and 40; Appendix C), ep's approved mockup f72e14fc (v6), and `plans/names-codes/2026-10-23-ux-principles.md`. + +#### W1j-0. The signature vectors + +- **Goal:** Appendix C's values, before wave 13 starts. +- **Files:** a script or test under `test/` that computes them with viem from the stated inputs; `test/flow.test.ts` and the other tests whose order, move and spend vectors change (SPEND among them), regenerated, `find`'s unchanged; in this repository, `plans/names-codes/2026-10-21-store-api.md` Appendix C, filled by the manager from the task's output. +- **Do:** for `order`, `name` and `spend`, each with `2026-10-23` and `2026-10-23/community`: the message text, its EIP-191 digest and the signature, by the fixture owner code's key. +- **Depends on:** the contract. T29 depends on it. #### W1j. Terms and community names -- **Goal:** the page signs the terms the buyer ticked, and shows community names with their wording. -- **Files:** `dev/mock/service.ts`, `dev/mock.ts` (a community list and `POST /control/community`, the terms version and URL, `terms` in bodies, messages, the order hash and the check, `terms_changed`); `src/api.ts` (`community` and `terms` in the answers and the view, `terms_changed`); `src/flow.ts` (`` in the order, spend and move messages; the record's `terms`); `src/store.ts`; `src/views/checkout.ts` and `src/screens/checkout.ts` (the checkbox); `src/views/search.ts`, `src/views/names.ts`, `src/views/order.ts` (the marker); the stylesheet; their tests; `test/crypto.test.ts` or `test/flow.test.ts` (the shared vector); `e2e/` (a community spec). +- **Goal:** the page signs only terms the buyer agreed to, on every path that signs an order, and shows community names with their wording. +- **Files:** `dev/mock/service.ts`, `dev/mock.ts` (a community list and `POST /control/community`, the terms version and URL, `terms` in bodies, messages, the order hash and the check, the view's `terms`, `terms_changed`); `src/api.ts` (`community` and `terms` in the answers, `community` and `terms` in `RegistrationView`, `terms_changed`); `src/flow.ts` (`` in the order, spend and move messages; the record's agreed `terms`; `reopen`, `openOrder` and `syncRecord` taking it from the view); `src/store.ts`; `src/views/checkout.ts`, `src/screens/checkout.ts`, `src/views/search.ts`, `src/views/order.ts`, `src/views/names.ts`; the stylesheet; their tests; `e2e/` (a community spec). - **Do:** - - The checkbox above the main button on Check your order (and before "Register with your code"), required: "I agree to and do not knowingly violate any trademarks.", or for a community name "I agree to and will use this name for its community. ⓘ"; the link is the answer's `terms.url`; ⓘ opens "This name is for . Its owner agrees to use it for this community, not as the name of one person or business." - - The signed `terms`: the answer's `terms.version`, with `/community` for a community name; kept on the order record, so a resend sends the same; on `terms_changed`, the page takes the answer's terms and `community`, unticks the box, says the terms changed, and signs anew on the next press. - - "Community ⓘ" inline after the name in search rows, Check your order and Your names: one button opening the popup, on the name's baseline with the icon centred on the word, 16 px after the name on desktop, right-aligned on the name's last line on a phone, no hover underline, pointer cursor; community names at the size of other names (22 px, 20 px on a phone); a taken community name's popup caption as the mockup. -- **Acceptance:** the mockup's community states at 1280 and 390 px in Chromium, Firefox (158 too) and WebKit, motion and no flashes; the shared vector reproduced byte for byte. -- **Tests:** the vector (T29's); each order kind and the move sending `terms`; `terms_changed` re-asking; the checkbox's two wordings and the popup; the marker's placement measured in e2e. -- **Depends on:** the contract (items 39 and 40). The service side lands with T28 and T29. + - **Every signing path** (manager's ruling, 2026-10-23): + - Check your order (a new order, a code order, a spend): the checkbox before the main button. + - "Choose another name" from search (`views/search.ts` `retarget`), a move to a different name: the checkbox before signing, with the community wording when the target is listed. + - The same name again: a new invoice after one expired (`views/order.ts` ~865), spending a balance again (`spendAgain`, ~913), a move without links (`moveWithoutLinks`, ~1050): reuse the record's (or the view's) agreed `terms` without asking only when they equal the current terms for the name; otherwise show the checkbox first. + - `terms_changed` on any path: take the answer's terms and `community`, untick, say the terms changed, ask again. + - **The checkbox**, required: "I agree to and do not knowingly violate any trademarks.", or for a community name "I agree to and will use this name for its community. ⓘ"; the link is `terms.url`; ⓘ opens "This name is for . Its owner agrees to use it for this community, not as the name of one person or business." + - **The marker:** "Community ⓘ" inline after the name in search rows, Check your order and Your names, as v6: one button opening the popup, on the name's baseline with the icon centred on the word, 16 px after the name on desktop, right-aligned on the name's last line on a phone, no hover underline, pointer cursor; community names at the size of other names (22 px, 20 px on a phone); a taken community name's popup caption. + - The community e2e spec uses the label `bakers`, which T28's `names-contract-run.sh` lists, so it passes against the mock and in T21's rerun. +- **Acceptance:** v6's community states at 1280 and 390 px in Chromium, Firefox (158 too) and WebKit, with motion and no flashes (the UX principles); Appendix C reproduced byte for byte. +- **Tests:** Appendix C; each signing path sending `terms`, asking or reusing as ruled, and handling `terms_changed`; a restored order (the view's `terms`) paying again without asking at an unchanged version and asking after a bump; the checkbox's two wordings and the popup; the marker's placement measured in e2e. +- **Depends on:** W1j-0. Ships with T29. #### W1k. The phone card page and the remaining mockup items -- **Goal:** the approved mockup's remaining layout items. +- **Goal:** v6's remaining layout items (mockup f72e14fc, `2026-10-23-ux-principles.md`). - **Files:** `src/views/order.ts` (the card page), `src/views/search.ts` (rows), the stylesheet, their e2e. - **Do:** - Pay by card on a phone keeps its fixed button; Stripe's note moves under it; the name note is one line: "Available at HH:MM. If someone registers it first, you keep the payment as balance." - Search answer rows keep equal top and bottom padding for long names (16 px `padding-block`, 72 px minimum height). - - Any other 2026-10-22/23 queue entry the mockup shows and the page does not, listed in the task's report with what it measured. -- **Acceptance:** positions measured in e2e at 390 and 1280 px against the mockup; no layout shift when the card form loads. + - Any other 2026-10-22/23 queue entry v6 shows and the page does not, listed in the task's report with what it measured. +- **Acceptance:** positions measured in e2e at 390 and 1280 px against v6; no layout shift when the card form loads. - **Depends on:** nothing new in the service. ## Owner answers (2026-10-21, binding)