ops: post-merge Coolify, Legal property, live wallet QA, POST rate limits #7

Closed
opened 2026-08-25 06:47:47 +00:00 by PlasticDigits · 28 comments
PlasticDigits commented 2026-08-25 06:47:47 +00:00 (Migrated from gitlab.com)

Summary

Follow-up after !18 merged the #1–#6 bundle onto main. In-tree ledger / operator-voting / /vote code is on origin. These items were not completed in that MR and are required before production voting is marked done.

Remaining work

  1. Coolify / staging

    • Deploy voting-ledger, operator-voting, and the dApp as three services.
    • Apply migrations from the ledger writer role.
    • operator-voting must use the restricted DATABASE_URL from deploy/grants.sql (not the ledger writer).
    • Unset VITE_PLAYWRIGHT_E2E on production builds.
  2. Legal ops (cl8y-ecosystem-legal checkout — confirm hostname first)

    • Register property (proposed vote.cl8y.com).
    • Add the voting origin to Legal API CORS_ORIGINS.
    • Add the voting origin to portal VITE_REDIRECT_URI_ALLOWLIST.
  3. Live wallet QA

    • Keplr Terra: Legal accept → register → propose (≥1000 CW20) → vote.
    • MetaMask BSC 56: Legal accept → register → vote (and propose if ≥1000 BEP-20).
    • Confirm Cosmos WalletConnect mobile pairing still works with the ported DEX helpers.
  4. POST rate limits

    • operator-voting has a body size cap; wire IP/QPS limits (tower-governor or equivalent) before public expose.
  5. LocalTerra / LCD equality (optional hardening)

    • Mint TCL8Y, register, transfer, assert ledger tip equals live LCD for a registered wallet.

Closed by !18 (in-tree): #1 #2 #3 #4. #5 and #6 still have ops/QA leftovers listed here.

## Summary Follow-up after [!18](https://gitlab.com/PlasticDigits/voting/-/merge_requests/1) merged the #1–#6 bundle onto `main`. In-tree ledger / operator-voting / `/vote` code is on origin. These items were **not** completed in that MR and are required before production voting is marked done. ## Remaining work 1. **Coolify / staging** - Deploy `voting-ledger`, `operator-voting`, and the dApp as three services. - Apply migrations from the ledger writer role. - `operator-voting` must use the restricted `DATABASE_URL` from `deploy/grants.sql` (not the ledger writer). - Unset `VITE_PLAYWRIGHT_E2E` on production builds. 2. **Legal ops** (cl8y-ecosystem-legal checkout — confirm hostname first) - Register property (proposed `vote.cl8y.com`). - Add the voting origin to Legal API `CORS_ORIGINS`. - Add the voting origin to portal `VITE_REDIRECT_URI_ALLOWLIST`. 3. **Live wallet QA** - Keplr Terra: Legal accept → register → propose (≥1000 CW20) → vote. - MetaMask BSC 56: Legal accept → register → vote (and propose if ≥1000 BEP-20). - Confirm Cosmos WalletConnect mobile pairing still works with the ported DEX helpers. 4. **POST rate limits** - `operator-voting` has a body size cap; wire IP/QPS limits (`tower-governor` or equivalent) before public expose. 5. **LocalTerra / LCD equality** (optional hardening) - Mint TCL8Y, register, transfer, assert ledger tip equals live LCD for a registered wallet. ## Related Closed by !18 (in-tree): #1 #2 #3 #4. #5 and #6 still have ops/QA leftovers listed here.
PlasticDigits commented 2026-08-25 06:48:14 +00:00 (Migrated from gitlab.com)

mentioned in issue #1

mentioned in issue #1
PlasticDigits commented 2026-08-25 06:48:20 +00:00 (Migrated from gitlab.com)

mentioned in issue #2

mentioned in issue #2
PlasticDigits commented 2026-08-25 06:48:21 +00:00 (Migrated from gitlab.com)

mentioned in issue #3

mentioned in issue #3
PlasticDigits commented 2026-08-25 06:48:23 +00:00 (Migrated from gitlab.com)

mentioned in issue #4

mentioned in issue #4
PlasticDigits commented 2026-08-25 06:48:24 +00:00 (Migrated from gitlab.com)

mentioned in issue #5

mentioned in issue #5
PlasticDigits commented 2026-08-25 06:48:25 +00:00 (Migrated from gitlab.com)

mentioned in issue #6

mentioned in issue #6
PlasticDigits commented 2026-08-25 06:48:31 +00:00 (Migrated from gitlab.com)

Opened after !18 merged to main (8fe45a1). Tracks Coolify, Legal property/CORS/allowlist, live Keplr+MetaMask QA, POST rate limits, and optional LocalTerra LCD equality. #1–#6 in-tree work is closed; leftovers are here.

Opened after !18 merged to `main` (`8fe45a1`). Tracks Coolify, Legal property/CORS/allowlist, live Keplr+MetaMask QA, POST rate limits, and optional LocalTerra LCD equality. #1–#6 in-tree work is closed; leftovers are here.
PlasticDigits commented 2026-08-25 07:01:03 +00:00 (Migrated from gitlab.com)

Verification (b306e37 on main)

In-tree leftovers from #7 are implemented and pushed. Issue stays open — Coolify project create, Legal admin writes, and live Keplr/MetaMask QA still need a human/ops session. Those cannot be completed from this workstation.

What landed

  • POST IP/QPS limits on operator-voting (governor, keyed by IP). GET /health is unlimited. Prod refuses RATE_LIMIT_POST_PER_MINUTE=0. Coolify must set RATE_LIMIT_TRUST_FORWARDED=true.
  • Restricted role / migrations: RUN_MODE=prod defaults APPLY_MIGRATIONS=false so the API never owns sqlx schema (L10).
  • Coolify images: deploy/docker/{ledger,operator-voting,frontend}.Dockerfile. Frontend image fails if VITE_PLAYWRIGHT_E2E, VITE_DEV_MNEMONIC, or a VITE_* BSC RPC is set.
  • Docs + skills: docs/OPS.md (O1–O7, O-RL1–O-RL5) and skills/AGENTS_OPS_STAGING.md, cross-linked from HANDOFF, OPERATOR_VOTING, FRONTEND, LEDGER_INVARIANTS, Legal/wallet skills.
  • Test flake: parallel cargo test binaries were TRUNCATE-racing the shared DB. They now take pg_advisory_lock(739001).

In-tree checks that passed

  • cargo test --workspace --lib (includes POST 429 + untrusted X-Forwarded-For)
  • LEDGER_TEST_DATABASE_URL=… cargo test --workspace --tests (API flow, privilege, ledger)
  • cd frontend && npm test (35)
  • Prod config rejects empty CORS and a zero POST quota
  • vite.config.ts already fails production builds when VITE_PLAYWRIGHT_E2E=true

Checklist still required before close

1. Coolify / staging

  • Deploy voting-ledger, operator-voting, and the dApp as three services from deploy/docker/
  • Apply migrations from the ledger writer role, then deploy/grants.sql
  • operator-voting DATABASE_URL is operator_voting (not the writer); APPLY_MIGRATIONS unset/false
  • Production build has VITE_PLAYWRIGHT_E2E unset; GET /health is 200 on ledger + API

2. Legal ops (confirm hostname first — proposed vote.cl8y.com)

  • Register property in cl8y-ecosystem-legal (interactive admin token; never in this repo)
  • Legal API CORS_ORIGINS includes the voting origin
  • Portal VITE_REDIRECT_URI_ALLOWLIST includes the voting origin

3. Live wallet QA (Playwright Legal hatch is not a substitute)

  • Keplr Terra: Legal accept → register → propose (≥1000 CW20) → vote
  • MetaMask BSC 56: Legal accept → register → vote (propose if ≥1000 BEP-20)
  • Cosmos WalletConnect mobile pairing still works with the ported DEX helpers

4. POST rate limits (staging smoke)

  • Burst of POSTs from one IP returns 429 + Retry-After
  • GET /health stays 200 during that burst

5. LocalTerra / LCD equality (optional)

  • Mint TCL8Y, register, transfer, assert ledger tip equals live LCD for that wallet

Leave closed only after 1–4 are true on staging. Optional item 5 can stay a follow-up.

## Verification (`b306e37` on `main`) In-tree leftovers from #7 are implemented and pushed. **Issue stays open** — Coolify project create, Legal admin writes, and live Keplr/MetaMask QA still need a human/ops session. Those cannot be completed from this workstation. ### What landed - **POST IP/QPS limits** on `operator-voting` (`governor`, keyed by IP). GET `/health` is unlimited. Prod refuses `RATE_LIMIT_POST_PER_MINUTE=0`. Coolify must set `RATE_LIMIT_TRUST_FORWARDED=true`. - **Restricted role / migrations:** `RUN_MODE=prod` defaults `APPLY_MIGRATIONS=false` so the API never owns sqlx schema (L10). - **Coolify images:** `deploy/docker/{ledger,operator-voting,frontend}.Dockerfile`. Frontend image fails if `VITE_PLAYWRIGHT_E2E`, `VITE_DEV_MNEMONIC`, or a `VITE_*` BSC RPC is set. - **Docs + skills:** `docs/OPS.md` (O1–O7, O-RL1–O-RL5) and `skills/AGENTS_OPS_STAGING.md`, cross-linked from HANDOFF, OPERATOR_VOTING, FRONTEND, LEDGER_INVARIANTS, Legal/wallet skills. - **Test flake:** parallel `cargo test` binaries were TRUNCATE-racing the shared DB. They now take `pg_advisory_lock(739001)`. ### In-tree checks that passed - [x] `cargo test --workspace --lib` (includes POST 429 + untrusted X-Forwarded-For) - [x] `LEDGER_TEST_DATABASE_URL=… cargo test --workspace --tests` (API flow, privilege, ledger) - [x] `cd frontend && npm test` (35) - [x] Prod config rejects empty CORS and a zero POST quota - [x] `vite.config.ts` already fails production builds when `VITE_PLAYWRIGHT_E2E=true` ### Checklist still required before close **1. Coolify / staging** - [ ] Deploy `voting-ledger`, `operator-voting`, and the dApp as three services from `deploy/docker/` - [ ] Apply migrations from the ledger writer role, then `deploy/grants.sql` - [ ] `operator-voting` `DATABASE_URL` is `operator_voting` (not the writer); `APPLY_MIGRATIONS` unset/false - [ ] Production build has `VITE_PLAYWRIGHT_E2E` unset; `GET /health` is 200 on ledger + API **2. Legal ops** (confirm hostname first — proposed `vote.cl8y.com`) - [ ] Register property in cl8y-ecosystem-legal (interactive admin token; never in this repo) - [ ] Legal API `CORS_ORIGINS` includes the voting origin - [ ] Portal `VITE_REDIRECT_URI_ALLOWLIST` includes the voting origin **3. Live wallet QA** (Playwright Legal hatch is not a substitute) - [ ] Keplr Terra: Legal accept → register → propose (≥1000 CW20) → vote - [ ] MetaMask BSC 56: Legal accept → register → vote (propose if ≥1000 BEP-20) - [ ] Cosmos WalletConnect mobile pairing still works with the ported DEX helpers **4. POST rate limits (staging smoke)** - [ ] Burst of POSTs from one IP returns `429` + `Retry-After` - [ ] `GET /health` stays 200 during that burst **5. LocalTerra / LCD equality (optional)** - [ ] Mint TCL8Y, register, transfer, assert ledger tip equals live LCD for that wallet Leave closed only after 1–4 are true on staging. Optional item 5 can stay a follow-up.
PlasticDigits commented 2026-08-25 07:04:39 +00:00 (Migrated from gitlab.com)

mentioned in issue cl8y-ecosystem-legal#12

mentioned in issue cl8y-ecosystem-legal#12
PlasticDigits commented 2026-08-25 07:04:45 +00:00 (Migrated from gitlab.com)

Legal ops leftover is now tracked on the Legal repo:

https://gitlab.com/PlasticDigits/cl8y-ecosystem-legal/-/issues/12

Register vote.cl8y.com, append https://vote.cl8y.com to API CORS_ORIGINS and portal VITE_REDIRECT_URI_ALLOWLIST. Same playbook as Legal #8 (ust1cmm.com). Both Terra Classic and EVM networks.

Legal ops leftover is now tracked on the Legal repo: https://gitlab.com/PlasticDigits/cl8y-ecosystem-legal/-/issues/12 Register `vote.cl8y.com`, append `https://vote.cl8y.com` to API `CORS_ORIGINS` and portal `VITE_REDIRECT_URI_ALLOWLIST`. Same playbook as Legal #8 (`ust1cmm.com`). Both Terra Classic and EVM networks.
PlasticDigits commented 2026-08-25 13:03:37 +00:00 (Migrated from gitlab.com)

Verification (67edeb5 on main)

In-tree #7 checks were re-run from a git worktree (verify/issue-7). Existing unit/integration tests were already green; this pass found and fixed deploy/privilege gaps, then reset Postgres and re-verified. Issue stays open — Coolify project create, Legal admin writes, and live Keplr/MetaMask QA still need a human/ops session.

What this pass changed

  • deploy/grants.sql is now what privilege tests apply (via psql -d, the Coolify path). GRANT EXECUTE is on public.voting_*_balance_at. The writer role is often named voting and schema voting exists, so unqualified names follow "$user", public and can grant or create a shadow copy that operator_voting cannot execute (L10). Migration 20260825000003 drops those copies.
  • Least privilege: USAGE on schema voting (no CREATE). CONNECT uses current_database(). Restricted role can write voting.signatures and cannot write ledger ingest tables.
  • Prod refuses APPLY_MIGRATIONS=true in addition to a zero POST quota. The operator-voting image pins APPLY_MIGRATIONS=false.
  • O3/O4 build guards shared by frontend/src/utils/prodEnvGuards.ts and the frontend Dockerfile (hatch, mnemonic, any VITE_* BSC RPC).
  • Production CSP stamped from deploy/docker/frontend.security-headers.conf (connect-src with Legal + API + WC origins; no blanket https:).
  • 429 includes Retry-After (asserted in rate_limit tests).
  • CI test:rust-integration against Postgres 16.
  • Docs/skills updated and cross-linked: docs/OPS.md, LEDGER_INVARIANTS.md, OPERATOR_VOTING.md, FRONTEND.md, skills/AGENTS_OPS_STAGING.md, AGENTS_LEGAL_CLICKWRAP.md, AGENTS_WALLET_CONNECTORS.md.

In-tree checks that passed (after infra reset)

  • cargo test --workspace --lib (POST 429 + Retry-After, untrusted X-Forwarded-For, prod migrate refuse)
  • LEDGER_TEST_DATABASE_URL=… cargo test --workspace --tests (API flow, grants.sql privilege, ledger)
  • cd frontend && npm test (38; includes prodEnvGuards)
  • Prod config rejects empty CORS, zero POST quota, and APPLY_MIGRATIONS=true
  • vite.config.ts / prodEnvGuards fail production builds when hatch, mnemonic, or a VITE_* BSC RPC is set

Checklist still required before close

1. Coolify / staging

  • Deploy voting-ledger, operator-voting, and the dApp as three services from deploy/docker/
  • Apply migrations from the ledger writer role, then psql -v ON_ERROR_STOP=1 -d "$WRITER_URL" -f deploy/grants.sql
  • operator-voting DATABASE_URL is operator_voting (not the writer); APPLY_MIGRATIONS unset/false (image pins false; prod refuses true)
  • Production build has VITE_PLAYWRIGHT_E2E unset; nginx CSP connect-src has no blanket https:; GET /health is 200 on ledger + API

2. Legal ops (confirm hostname first — proposed vote.cl8y.com; tracked at https://gitlab.com/PlasticDigits/cl8y-ecosystem-legal/-/issues/12)

  • Register property in cl8y-ecosystem-legal (interactive admin token; never in this repo)
  • Legal API CORS_ORIGINS includes the voting origin
  • Portal VITE_REDIRECT_URI_ALLOWLIST includes the voting origin

3. Live wallet QA (Playwright Legal hatch is not a substitute)

  • Keplr Terra: Legal accept → register → propose (≥1000 CW20) → vote
  • MetaMask BSC 56: Legal accept → register → vote (propose if ≥1000 BEP-20)
  • Cosmos WalletConnect mobile pairing still works with the ported DEX helpers

4. POST rate limits (staging smoke)

  • Burst of POSTs from one IP returns 429 + Retry-After
  • GET /health stays 200 during that burst

5. LocalTerra / LCD equality (optional)

  • Mint TCL8Y, register, transfer, assert ledger tip equals live LCD for that wallet

Leave closed only after 1–4 are true on staging. Optional item 5 can stay a follow-up.

Playbook: https://gitlab.com/PlasticDigits/voting/-/blob/main/docs/OPS.md · https://gitlab.com/PlasticDigits/voting/-/blob/main/skills/AGENTS_OPS_STAGING.md

## Verification (`67edeb5` on `main`) In-tree #7 checks were re-run from a git worktree (`verify/issue-7`). Existing unit/integration tests were already green; this pass found and fixed deploy/privilege gaps, then reset Postgres and re-verified. **Issue stays open** — Coolify project create, Legal admin writes, and live Keplr/MetaMask QA still need a human/ops session. ### What this pass changed - **`deploy/grants.sql` is now what privilege tests apply** (via `psql -d`, the Coolify path). `GRANT EXECUTE` is on `public.voting_*_balance_at`. The writer role is often named `voting` and schema `voting` exists, so unqualified names follow `"$user", public` and can grant or create a **shadow copy** that `operator_voting` cannot execute (L10). Migration `20260825000003` drops those copies. - **Least privilege:** `USAGE` on schema `voting` (no `CREATE`). CONNECT uses `current_database()`. Restricted role can write `voting.signatures` and cannot write ledger ingest tables. - **Prod refuses `APPLY_MIGRATIONS=true`** in addition to a zero POST quota. The operator-voting image pins `APPLY_MIGRATIONS=false`. - **O3/O4 build guards** shared by `frontend/src/utils/prodEnvGuards.ts` and the frontend Dockerfile (hatch, mnemonic, any `VITE_*` BSC RPC). - **Production CSP** stamped from `deploy/docker/frontend.security-headers.conf` (`connect-src` with Legal + API + WC origins; no blanket `https:`). - **429 includes `Retry-After`** (asserted in `rate_limit` tests). - **CI** `test:rust-integration` against Postgres 16. - Docs/skills updated and cross-linked: `docs/OPS.md`, `LEDGER_INVARIANTS.md`, `OPERATOR_VOTING.md`, `FRONTEND.md`, `skills/AGENTS_OPS_STAGING.md`, `AGENTS_LEGAL_CLICKWRAP.md`, `AGENTS_WALLET_CONNECTORS.md`. ### In-tree checks that passed (after infra reset) - [x] `cargo test --workspace --lib` (POST 429 + `Retry-After`, untrusted X-Forwarded-For, prod migrate refuse) - [x] `LEDGER_TEST_DATABASE_URL=… cargo test --workspace --tests` (API flow, **grants.sql** privilege, ledger) - [x] `cd frontend && npm test` (38; includes `prodEnvGuards`) - [x] Prod config rejects empty CORS, zero POST quota, and `APPLY_MIGRATIONS=true` - [x] `vite.config.ts` / `prodEnvGuards` fail production builds when hatch, mnemonic, or a `VITE_*` BSC RPC is set ### Checklist still required before close **1. Coolify / staging** - [ ] Deploy `voting-ledger`, `operator-voting`, and the dApp as three services from `deploy/docker/` - [ ] Apply migrations from the ledger writer role, then `psql -v ON_ERROR_STOP=1 -d "$WRITER_URL" -f deploy/grants.sql` - [ ] `operator-voting` `DATABASE_URL` is `operator_voting` (not the writer); `APPLY_MIGRATIONS` unset/false (image pins false; prod refuses true) - [ ] Production build has `VITE_PLAYWRIGHT_E2E` unset; nginx CSP `connect-src` has no blanket `https:`; `GET /health` is 200 on ledger + API **2. Legal ops** (confirm hostname first — proposed `vote.cl8y.com`; tracked at https://gitlab.com/PlasticDigits/cl8y-ecosystem-legal/-/issues/12) - [ ] Register property in cl8y-ecosystem-legal (interactive admin token; never in this repo) - [ ] Legal API `CORS_ORIGINS` includes the voting origin - [ ] Portal `VITE_REDIRECT_URI_ALLOWLIST` includes the voting origin **3. Live wallet QA** (Playwright Legal hatch is not a substitute) - [ ] Keplr Terra: Legal accept → register → propose (≥1000 CW20) → vote - [ ] MetaMask BSC 56: Legal accept → register → vote (propose if ≥1000 BEP-20) - [ ] Cosmos WalletConnect mobile pairing still works with the ported DEX helpers **4. POST rate limits (staging smoke)** - [ ] Burst of POSTs from one IP returns `429` + `Retry-After` - [ ] `GET /health` stays 200 during that burst **5. LocalTerra / LCD equality (optional)** - [ ] Mint TCL8Y, register, transfer, assert ledger tip equals live LCD for that wallet Leave closed only after 1–4 are true on staging. Optional item 5 can stay a follow-up. Playbook: https://gitlab.com/PlasticDigits/voting/-/blob/main/docs/OPS.md · https://gitlab.com/PlasticDigits/voting/-/blob/main/skills/AGENTS_OPS_STAGING.md
PlasticDigits commented 2026-08-26 01:30:27 +00:00 (Migrated from gitlab.com)

Checklist item 2. Legal ops is done on Legal Coolify / hosts. No ADMIN_TOKEN recorded here.

  • Property vote.cl8y.com registered as CL8Y Voting (display_name set).
  • Legal API CORS_ORIGINS includes https://vote.cl8y.com — preflight echoes Access-Control-Allow-Origin: https://vote.cl8y.com.
  • Portal VITE_REDIRECT_URI_ALLOWLIST rebuilt with https://vote.cl8y.com (assets/index--hE9wWsL.js). VITE_ALLOW_LOCALHOST_REDIRECT left off.

Terms 1.5 sign URLs:

  • https://terms.cl8y.com/sign/terra-classic?property=vote.cl8y.com
  • https://terms.cl8y.com/sign/evm?property=vote.cl8y.com

Still required on this issue: Coolify/staging (item 1), live Keplr + MetaMask QA (item 3), POST 429 smoke (item 4). https://vote.cl8y.com currently 503 no available server (self-signed placeholder), so wallet round-trip cannot run until the dApp is up.

## Legal ops complete ([cl8y-ecosystem-legal#12](https://gitlab.com/PlasticDigits/cl8y-ecosystem-legal/-/issues/12) closed) Checklist item **2. Legal ops** is done on Legal Coolify / hosts. No `ADMIN_TOKEN` recorded here. - Property `vote.cl8y.com` registered as **CL8Y Voting** (`display_name` set). - Legal API `CORS_ORIGINS` includes `https://vote.cl8y.com` — preflight echoes `Access-Control-Allow-Origin: https://vote.cl8y.com`. - Portal `VITE_REDIRECT_URI_ALLOWLIST` rebuilt with `https://vote.cl8y.com` (`assets/index--hE9wWsL.js`). `VITE_ALLOW_LOCALHOST_REDIRECT` left off. Terms `1.5` sign URLs: - `https://terms.cl8y.com/sign/terra-classic?property=vote.cl8y.com` - `https://terms.cl8y.com/sign/evm?property=vote.cl8y.com` **Still required on this issue:** Coolify/staging (item 1), live Keplr + MetaMask QA (item 3), POST 429 smoke (item 4). `https://vote.cl8y.com` currently `503 no available server` (self-signed placeholder), so wallet round-trip cannot run until the dApp is up.
PlasticDigits commented 2026-08-29 11:16:25 +00:00 (Migrated from gitlab.com)

mentioned in issue cl8y-ecosystem-legal#14

mentioned in issue cl8y-ecosystem-legal#14
PlasticDigits commented 2026-08-29 14:15:29 +00:00 (Migrated from gitlab.com)

mentioned in issue #8

mentioned in issue #8
PlasticDigits commented 2026-08-29 14:16:37 +00:00 (Migrated from gitlab.com)

mentioned in issue #9

mentioned in issue #9
PlasticDigits commented 2026-08-29 14:16:50 +00:00 (Migrated from gitlab.com)

Live wallet QA on vote.cl8y.com (Terra Classic, mobile in-app browser) turned up two product bugs. Tracking separately so they can close independently of the rest of this ops checklist:

  • #8 — Legal Accept / hard navigation to /vote returns 404; / still loads the SPA
  • #9 — Connected / registered Terra CL8Y shown as 0 (propose gate blocked)

Do not close this issue as “live QA done” while those remain open.

Live wallet QA on vote.cl8y.com (Terra Classic, mobile in-app browser) turned up two product bugs. Tracking separately so they can close independently of the rest of this ops checklist: - #8 — Legal Accept / hard navigation to `/vote` returns 404; `/` still loads the SPA - #9 — Connected / registered Terra CL8Y shown as 0 (propose gate blocked) Do not close this issue as “live QA done” while those remain open.
PlasticDigits commented 2026-08-29 14:43:47 +00:00 (Migrated from gitlab.com)

mentioned in merge request !19

mentioned in merge request !19
PlasticDigits commented 2026-08-29 15:02:44 +00:00 (Migrated from gitlab.com)

!19 (#8 SPA /vote 404) and !20 (#9 registered balance 0) are on main. Neither closes this issue. Remaining Coolify / Legal admin / live Keplr+MetaMask QA / edge try_files still block production-done. GitLab CI on those MRs did not run (ci_quota_exceeded).

!19 (#8 SPA `/vote` 404) and !20 (#9 registered balance 0) are on `main`. Neither closes this issue. Remaining Coolify / Legal admin / live Keplr+MetaMask QA / edge `try_files` still block production-done. GitLab CI on those MRs did not run (`ci_quota_exceeded`).
PlasticDigits commented 2026-08-30 02:42:03 +00:00 (Migrated from gitlab.com)

mentioned in issue #12

mentioned in issue #12
PlasticDigits commented 2026-08-30 02:42:04 +00:00 (Migrated from gitlab.com)

marked as related to #12

marked as related to #12
PlasticDigits commented 2026-08-30 02:46:49 +00:00 (Migrated from gitlab.com)

mentioned in issue #13

mentioned in issue #13
PlasticDigits commented 2026-08-30 02:47:14 +00:00 (Migrated from gitlab.com)

mentioned in issue #14

mentioned in issue #14
PlasticDigits commented 2026-08-30 02:47:15 +00:00 (Migrated from gitlab.com)

marked as related to #14

marked as related to #14
PlasticDigits commented 2026-08-30 05:36:49 +00:00 (Migrated from gitlab.com)

Verification (5b86f64 on main)

In-tree #7 checks were re-run from a git worktree (verify/issue-7). Postgres was reset (docker compose -f docker-compose.test.yml down -v && up -d) and the suite re-run after the O3 fix. Issue stays open — live vote.cl8y.com still lacks CSP/X-Frame-Options until the Coolify static nginx snippet is re-pasted, and Keplr/MetaMask QA is still blocked on #9.

What this pass found

  • Live dApp https://vote.cl8y.com: /, /vote, /new, /vote/new are 200 HTML. /assets/missing.js is 404. #8 stays closed. Edge is Coolify stock nginx/1.31.4, not the Dockerfile 1.27 image — SPA try_files is in place, O3 headers are not (curl -sI has no Content-Security-Policy / X-Frame-Options).
  • Live API https://operator.vote.cl8y.com: GET /health 200, CORS allows https://vote.cl8y.com. Production bundle has no PLAYWRIGHT_E2E / DEV_MNEMONIC / VITE_* BSC RPC.
  • POST 429 smoke: sequential curls refill GCRA and never 429. A parallel burst of 80 POSTs returned many 429 + Retry-After; GET /health stayed 200. A few proxy 502s under burst are not a fail.
  • Legal ops already done (cl8y-ecosystem-legal#12); preflight still echoes Access-Control-Allow-Origin: https://vote.cl8y.com.
  • Ledger has no public hostname (expected). Writer role / grants.sql on the live DB cannot be proven from this workstation.

What this pass changed

  • O3 on the Coolify static path: deploy/coolify-frontend.nginx.conf now inlines CSP (connect-src with https://operator.vote.cl8y.com, Legal API, terms; no blanket https:) and X-Frame-Options: DENY. Nested add_header Cache-Control was dropped so nginx does not discard server-level CSP. Ops must re-paste this file in Coolify — pushing main does not update the live edge.
  • CI SPA smoke now runs both nginx configs. Prod-shaped rate-limit test: .with_state(AppState).layer(limiter) still 429s with Retry-After (unit + api_flow integration).
  • Docs/skills: live hosts, parallel 429 recipe, O3 re-paste, #8 closed / #9 still blocks live QA. Playbook: docs/OPS.md · skills/AGENTS_OPS_STAGING.md.

In-tree checks that passed (after infra reset)

  • cargo test --workspace --lib (26 operator-voting + 21 ledger; includes POST 429, untrusted XFF, prod migrate refuse, layer-after-with_state)
  • LEDGER_TEST_DATABASE_URL=… cargo test --workspace --tests (API flow including router 429, grants.sql privilege, ledger)
  • cd frontend && npm test (56; includes Coolify snippet CSP)
  • sh deploy/docker/test-frontend-spa-fallback.sh (Dockerfile conf and Coolify static paste)
  • cd frontend && npx playwright test (9 passed, 5 workers)
  • gitleaks detect — no leaks

Checklist still required before close

1. Coolify / staging

  • dApp is up at https://vote.cl8y.com (SPA fallback 200)
  • operator-voting is up at https://operator.vote.cl8y.com (GET /health 200, CORS)
  • Confirm voting-ledger is running with the writer role (private; not curl-able from here)
  • Apply psql -v ON_ERROR_STOP=1 -d "$WRITER_URL" -f deploy/grants.sql on the live DB if not already
  • operator-voting DATABASE_URL is operator_voting (not the writer); APPLY_MIGRATIONS unset/false
  • Re-paste deploy/coolify-frontend.nginx.conf (or switch the dApp to frontend.Dockerfile) so curl -sI https://vote.cl8y.com/vote shows X-Frame-Options: DENY and a Content-Security-Policy without connect-src https:
  • Production build has VITE_PLAYWRIGHT_E2E unset (not in the live JS bundle)

2. Legal ops (done — cl8y-ecosystem-legal#12)

  • Property vote.cl8y.com registered
  • Legal API CORS_ORIGINS includes https://vote.cl8y.com
  • Portal VITE_REDIRECT_URI_ALLOWLIST includes https://vote.cl8y.com

3. Live wallet QA (Playwright Legal hatch is not a substitute; still blocked on #9)

  • Keplr Terra: Legal accept → register → propose (≥1000 CW20) → vote
  • MetaMask BSC 56: Legal accept → register → vote (propose if ≥1000 BEP-20)
  • Cosmos WalletConnect mobile pairing still works with the ported DEX helpers

4. POST rate limits (staging smoke)

  • Parallel burst of POSTs from one IP returns 429 + Retry-After
  • GET /health stays 200 during that burst

5. LocalTerra / LCD equality (optional)

  • Mint TCL8Y, register, transfer, assert ledger tip equals live LCD for that wallet

Leave closed only after 1 (including live O3 headers) and 3 are true on staging. Item 2 and 4 passed. Optional item 5 can stay a follow-up. Do not close as “live QA done” while #9 is open.

## Verification (`5b86f64` on `main`) In-tree #7 checks were re-run from a git worktree (`verify/issue-7`). Postgres was reset (`docker compose -f docker-compose.test.yml down -v && up -d`) and the suite re-run after the O3 fix. **Issue stays open** — live `vote.cl8y.com` still lacks CSP/`X-Frame-Options` until the Coolify static nginx snippet is re-pasted, and Keplr/MetaMask QA is still blocked on [#9](https://gitlab.com/PlasticDigits/voting/-/issues/9). ### What this pass found - **Live dApp** `https://vote.cl8y.com`: `/`, `/vote`, `/new`, `/vote/new` are **200 HTML**. `/assets/missing.js` is **404**. [#8](https://gitlab.com/PlasticDigits/voting/-/issues/8) stays closed. Edge is Coolify stock **nginx/1.31.4**, not the Dockerfile 1.27 image — SPA `try_files` is in place, **O3 headers are not** (`curl -sI` has no `Content-Security-Policy` / `X-Frame-Options`). - **Live API** `https://operator.vote.cl8y.com`: `GET /health` 200, CORS allows `https://vote.cl8y.com`. Production bundle has **no** `PLAYWRIGHT_E2E` / `DEV_MNEMONIC` / `VITE_*` BSC RPC. - **POST 429 smoke:** sequential curls refill GCRA and never 429. A **parallel** burst of 80 POSTs returned many `429` + `Retry-After`; `GET /health` stayed 200. A few proxy `502`s under burst are not a fail. - **Legal ops** already done ([cl8y-ecosystem-legal#12](https://gitlab.com/PlasticDigits/cl8y-ecosystem-legal/-/issues/12)); preflight still echoes `Access-Control-Allow-Origin: https://vote.cl8y.com`. - **Ledger** has no public hostname (expected). Writer role / `grants.sql` on the live DB cannot be proven from this workstation. ### What this pass changed - **O3 on the Coolify static path:** [`deploy/coolify-frontend.nginx.conf`](https://gitlab.com/PlasticDigits/voting/-/blob/main/deploy/coolify-frontend.nginx.conf) now inlines CSP (`connect-src` with `https://operator.vote.cl8y.com`, Legal API, terms; **no** blanket `https:`) and `X-Frame-Options: DENY`. Nested `add_header Cache-Control` was dropped so nginx does not discard server-level CSP. **Ops must re-paste this file** in Coolify — pushing `main` does not update the live edge. - CI SPA smoke now runs **both** nginx configs. Prod-shaped rate-limit test: `.with_state(AppState).layer(limiter)` still 429s with `Retry-After` (unit + `api_flow` integration). - Docs/skills: live hosts, parallel 429 recipe, O3 re-paste, #8 closed / #9 still blocks live QA. Playbook: [`docs/OPS.md`](https://gitlab.com/PlasticDigits/voting/-/blob/main/docs/OPS.md) · [`skills/AGENTS_OPS_STAGING.md`](https://gitlab.com/PlasticDigits/voting/-/blob/main/skills/AGENTS_OPS_STAGING.md). ### In-tree checks that passed (after infra reset) - [x] `cargo test --workspace --lib` (26 operator-voting + 21 ledger; includes POST 429, untrusted XFF, prod migrate refuse, **layer-after-with_state**) - [x] `LEDGER_TEST_DATABASE_URL=… cargo test --workspace --tests` (API flow including **router 429**, grants.sql privilege, ledger) - [x] `cd frontend && npm test` (56; includes Coolify snippet CSP) - [x] `sh deploy/docker/test-frontend-spa-fallback.sh` (Dockerfile conf **and** Coolify static paste) - [x] `cd frontend && npx playwright test` (9 passed, **5 workers**) - [x] `gitleaks detect` — no leaks ### Checklist still required before close **1. Coolify / staging** - [x] dApp is up at `https://vote.cl8y.com` (SPA fallback 200) - [x] `operator-voting` is up at `https://operator.vote.cl8y.com` (`GET /health` 200, CORS) - [ ] Confirm `voting-ledger` is running with the **writer** role (private; not curl-able from here) - [ ] Apply `psql -v ON_ERROR_STOP=1 -d "$WRITER_URL" -f deploy/grants.sql` on the live DB if not already - [ ] `operator-voting` `DATABASE_URL` is `operator_voting` (not the writer); `APPLY_MIGRATIONS` unset/false - [ ] **Re-paste** [`deploy/coolify-frontend.nginx.conf`](https://gitlab.com/PlasticDigits/voting/-/blob/main/deploy/coolify-frontend.nginx.conf) (or switch the dApp to `frontend.Dockerfile`) so `curl -sI https://vote.cl8y.com/vote` shows `X-Frame-Options: DENY` and a `Content-Security-Policy` **without** `connect-src https:` - [x] Production build has `VITE_PLAYWRIGHT_E2E` unset (not in the live JS bundle) **2. Legal ops** (done — [cl8y-ecosystem-legal#12](https://gitlab.com/PlasticDigits/cl8y-ecosystem-legal/-/issues/12)) - [x] Property `vote.cl8y.com` registered - [x] Legal API `CORS_ORIGINS` includes `https://vote.cl8y.com` - [x] Portal `VITE_REDIRECT_URI_ALLOWLIST` includes `https://vote.cl8y.com` **3. Live wallet QA** (Playwright Legal hatch is not a substitute; still blocked on #9) - [ ] Keplr Terra: Legal accept → register → propose (≥1000 CW20) → vote - [ ] MetaMask BSC 56: Legal accept → register → vote (propose if ≥1000 BEP-20) - [ ] Cosmos WalletConnect mobile pairing still works with the ported DEX helpers **4. POST rate limits (staging smoke)** - [x] Parallel burst of POSTs from one IP returns `429` + `Retry-After` - [x] `GET /health` stays 200 during that burst **5. LocalTerra / LCD equality (optional)** - [ ] Mint TCL8Y, register, transfer, assert ledger tip equals live LCD for that wallet Leave closed only after 1 (including live O3 headers) and 3 are true on staging. Item 2 and 4 passed. Optional item 5 can stay a follow-up. Do not close as “live QA done” while [#9](https://gitlab.com/PlasticDigits/voting/-/issues/9) is open.
PlasticDigits commented 2026-08-30 06:21:55 +00:00 (Migrated from gitlab.com)

mentioned in issue #15

mentioned in issue #15
PlasticDigits commented 2026-08-30 06:21:55 +00:00 (Migrated from gitlab.com)

marked as related to #15

marked as related to #15
PlasticDigits commented 2026-08-30 06:22:06 +00:00 (Migrated from gitlab.com)

!21 / !23 / !25 / !26 are on main. Remaining Coolify / live-wallet work is sequenced in #15 (migrations, grants, committee env, WC project id, nginx re-paste, writer /health). Do not close this issue from those merges; Keplr/MetaMask QA is still blocked on #9 / #12 / #14.

!21 / !23 / !25 / !26 are on `main`. Remaining Coolify / live-wallet work is sequenced in [#15](https://gitlab.com/PlasticDigits/voting/-/issues/15) (migrations, grants, committee env, WC project id, nginx re-paste, writer `/health`). Do not close this issue from those merges; Keplr/MetaMask QA is still blocked on #9 / #12 / #14.
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-08-30 07:11:39 +00:00
PlasticDigits commented 2026-08-30 07:11:40 +00:00 (Migrated from gitlab.com)

Closing

Closing
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
code/voting#7
No description provided.