feat(voting): structured proposal template and minimum required sections #10

Closed
opened 2026-08-29 14:17:16 +00:00 by PlasticDigits · 17 comments
PlasticDigits commented 2026-08-29 14:17:16 +00:00 (Migrated from gitlab.com)

Summary

Require a standard proposal format so every votable proposal presents the same sections in the same order: problem, optional context, proposed solution, pros/cons, TL;DR, and success criteria. Server-enforce minimums; do not rely on freeform WYSIWYG headings.

Community feedback: holders often vote without understanding a proposal (the failure mode seen in LUNC on-chain governance). A shared template is the minimum bar so voters can scan impact, risks, and what “success” means before they sign. Independent expert analysis and a draft/review gate are out of scope here — see the follow-up issue on draft collaboration.

Depends on existing #2 / #3 propose+vote APIs. Votes stay offchain / advisory.


Current codebase

Proposals are a single title + freeform HTML body. There is no section schema, no minimum length per topic, and no TL;DR on the list.

Area Today
Schema voting.proposals (ledger/migrations/20260825000002_voting.sql): title, body_html, body_canonical, status default 'open'. No section columns.
Create API POST /v1/proposals in operator-voting/src/api.rs: title ≤ 200 chars, body ≤ MAX_BODY_BYTES (64 KiB), ammonia sanitize, signed title + body_hash, ≥1000 CL8Y on that address’s chain, snapshot freeze { terra_height, bsc_block } at create.
Signing operator-voting/src/payload.rs + frontend/src/utils/votingPayload.ts — purposes register / propose / vote only. Hash is SHA-256 of the submitted HTML string.
Sanitize operator-voting/src/html.rs (ammonia allowlist) and frontend/src/utils/sanitizeProposalHtml.ts (client strip aligned with ammonia).
Compose UI frontend/src/pages/VoteNewPage.tsx — one title input + TipTap StarterKit. Submit enabled if titled and balance ≥ 1000 CL8Y. Empty body is allowed.
Detail UI frontend/src/pages/VoteDetailPage.tsx — dangerouslySetInnerHTML of body_html. No labeled sections.
List UI frontend/src/pages/VoteListPage.tsx — title, chain, status only. No summary.
Tests VoteNewPage.test.tsx (balance gate), operator-voting/tests/api_flow.rs (create, XSS strip, 1000 CL8Y). No section tests.
Docs docs/OPERATOR_VOTING.md, docs/FRONTEND.md, docs/ARCHITECTURE.md (snapshot at create).

status is stored and displayed but never transitions; every successful create is immediately votable.


Why this is needed

A blank rich-text field does not force the proposer (or later collaborators) to state the problem, trade-offs, or what a successful outcome looks like. Voters then see a wall of HTML and a for/against/abstain row. Standard sections:

  1. Make proposals comparable.
  2. Put a TL;DR on the list and at the top of the detail page so people can decide whether to read further.
  3. Make “pros and cons” a first-class field so critique is not optional self-praise buried in a paragraph.
  4. Give later review (draft comments, committee, optional AI) a stable schema to fill — without that, review comments attach to unstructured blobs.

This does not replace community decision-making. It raises the information floor.


Constraints / guardrails

  1. No new smart contracts. Offchain / advisory copy on list + detail must stay.
  2. Server is source of truth. Missing or whitespace-only required sections → 400. Do not trust the dApp form.
  3. Structured fields, not “please use these headings” in freeform HTML. Headings inside a single body_html are skippable and spoofable.
  4. Keep signing domain. Client still signs purpose=propose with title + body_hash. Hash a canonical serialization of the section object (stable key order, ammonia-sanitized HTML per section), not ad-hoc concatenated UI strings.
  5. Ammonia allowlist unchanged per section. No new tags, no javascript: / event handlers. Combined sanitized payload still ≤ 64 KiB.
  6. Identity v1 — Terra and BSC proposers both use the same template. Do not merge terra1… and 0x….
  7. ≥1000 CL8Y gate unchanged (per registering address’s chain).
  8. Legal gate unchanged — connected users still fail closed behind clickwrap before compose.
  9. Least privilege — new columns stay in schema voting; update deploy/grants.sql only if new tables/functions appear. Operator role must not write ledger ingest tables.
  10. Backward compatible read path — existing rows with only body_html must still render (legacy article). New creates must persist sections.
  11. Do not freeze a second snapshot in this issue. Snapshot-at-create stays until the draft/review issue moves freeze to vote-open.
  12. Success criteria are not a later punishment mechanism. Copy must describe them as goalposts for spend/outcome justification, not slashing or “hang the proposer.”
  13. No seeds / mnemonics. No VITE_* BSC RPC. No ADMIN_TOKEN in the frontend.
  14. Neutral tone in labels. Section titles are product copy (“Problem”, “Trade-offs”), not political framing.

Required sections (v1)

Key UI label Required?
problem The idea or problem to be solved Yes
context Supporting context, real-world issues, and alternatives No (empty allowed)
solution Proposed solution and supporting evidence Yes
pros_cons Pros and cons / trade-offs Yes
summary Summary (TL;DR) Yes
success_criteria Success criteria / goalposts Yes

Independent “trusted contributor analysis” is not a proposer-owned required field here (that belongs on the review-gate issue so it cannot be self-authored and labeled as independent).

Suggested minimums (tune in implementation, encode in API constants): each required section ≥ 40 visible characters after strip-tags + collapse whitespace; summary also ≤ 500 characters so list cards stay scannable.


Relevant files

  • ledger/migrations/20260825000002_voting.sql (and a new sqlx migration — do not rewrite applied files)
  • operator-voting/src/api.rs (CreateProposalRequest, create_proposal, get_proposal, list_proposals)
  • operator-voting/src/db.rs (ProposalRow, insert_proposal)
  • operator-voting/src/html.rs, operator-voting/src/payload.rs (body_hash)
  • operator-voting/src/config.rs (MAX_BODY_BYTES)
  • operator-voting/tests/api_flow.rs
  • deploy/grants.sql (only if new relations)
  • frontend/src/pages/VoteNewPage.tsx, VoteNewPage.test.tsx
  • frontend/src/pages/VoteDetailPage.tsx, VoteListPage.tsx
  • frontend/src/services/operatorVoting.ts
  • frontend/src/utils/sanitizeProposalHtml.ts, votingPayload.ts
  • frontend/e2e/vote.spec.ts
  • docs/OPERATOR_VOTING.md, docs/FRONTEND.md

  1. Persist sections as JSON (e.g. body_sections JSONB NOT NULL with a CHECK that required keys exist and are text) and keep body_html as a server-rendered concatenation for legacy clients / ammonia round-trip. Prefer JSONB as the source of truth for new rows.
  2. Canonical hash: UTF-8 JSON with sorted keys, each value already ammonia-cleaned, no insignificant whitespace. Document the exact serializer in OPERATOR_VOTING.md so TS and Rust match (same discipline as canonicalizePayload).
  3. Compose UI: replace the single TipTap blob with labeled fields (TipTap or textarea per section). Show character counts and the 1000 CL8Y gate. Preview the voter-facing layout before sign.
  4. List: show summary under the title (truncated).
  5. Detail: render sections in fixed order with headings; keep snapshot + advisory copy; vote CTAs unchanged.
  6. OpenAPI stub (GET /openapi.json) and docs/OPERATOR_VOTING.md endpoints table updated.
  7. Do not add comments, draft status, committee, or AI in this issue.

Acceptance criteria

  • New POST /v1/proposals rejects missing/whitespace-only required sections and over-long summary / total body.
  • Accepted proposals persist sections; GET /v1/proposals/:id returns them; list includes summary.
  • Signed body_hash is over the canonical section serialization; hash mismatch still 401.
  • /vote/new cannot submit until required sections meet the same rules the API uses (UX); API still enforces.
  • /vote list shows TL;DR; /vote/:id shows labeled sections in canonical order.
  • Legacy proposals without sections still render via body_html.
  • XSS: script / onerror in any section stripped; not executed on detail.
  • Terra and BSC propose paths both work; 1000 CL8Y, blacklist, Legal gate, ammonia, POST rate limits unchanged.
  • Unit + API + frontend tests for gates; Playwright compose uses the new fields.
  • Docs updated. No contracts, no archive LCD, no identity merge.

Test plan (all paths)

  1. API — happy path: registered wallet ≥1000 CL8Y, all required sections over minimum, optional context empty → 201; detail returns sections; list includes summary.
  2. API — missing section: omit pros_cons or send "" / " " / "<p></p>" / "<p><br></p>" → 400.
  3. API — summary length: over max → 400; at max → 201.
  4. API — combined size: sections summing over 64 KiB → 400 (and HTTP body limit still applies).
  5. API — hash: sign hash of unsanitized raw vs stored canonical → mismatch 401; matching canonical → 201.
  6. API — title / 1000 CL8Y / unregistered / blacklist: existing denials still hold.
  7. API — Terra and BSC: one create each.
  8. API — legacy GET: row with null/absent sections returns body_html only; dApp does not crash.
  9. Frontend unit: submit disabled until required fields valid and balance ≥1000; enabled at boundary with valid sections.
  10. Frontend unit: canonical hash helper matches Rust vectors (golden JSON fixtures).
  11. Component: labels visible; optional context not blocking.
  12. Playwright: fill template → create → detail shows headings + TL;DR on list.
  13. Render safety: each section with <script> / onerror= does not execute.
  14. Legal / disconnected: compose still gated; browse list OK while disconnected.
  15. Copy: success-criteria helper text is goalposts, not punitive.

Test plan — attack, hack & abuse vectors

  1. Client-only validation bypass: raw POST with empty required sections — API 400.
  2. Whitespace / HTML-empty padding: "&nbsp;", zero-width, <p> </p> must not satisfy minimums (strip tags + Unicode whitespace before count).
  3. XSS in every section including summary on the list card.
  4. Hash confusion: sign freeform body_html while posting sections (or the reverse) → reject.
  5. Key-order / serializer drift: two equivalent objects with different JSON key order must hash identically after canonicalization; a changed letter must not.
  6. Title vs section spoof: signed title A with body that talks about B — still allowed (content honesty is social); do not try to NLP-enforce. Do enforce title length and hash match.
  7. Oversized payload / gzip bombs: existing body cap + POST QPS (O-RL1) still apply.
  8. Blacklist / unregistered / below 1000: no template bypass.
  9. Phishing copy: compose UI must not ask for seeds; only wallet signArbitrary / personal_sign.
  10. Self-labeled “independent analysis”: do not add a proposer field that can be titled as if a third party wrote it.
  11. Legacy XSS: old body_html still ammonia-sanitized on read if you re-serialize; never mark legacy HTML as trusted.

Verification criteria

  • cargo test --workspace --lib and operator-voting integration tests covering section validation + hash golden vectors.
  • cd frontend && npm test and npm run test:e2e (template compose path).
  • Manual or staging: Terra and BSC: Legal accept → register → create templated proposal → list shows TL;DR → detail shows all sections → vote still works.
  • gitleaks detect clean; no secrets in fixtures.
  • docs/OPERATOR_VOTING.md + docs/FRONTEND.md describe the schema and hash.

Labels

frontend UX enhancement security product backend

Priority

P2

Follow-up

Draft → community comments → committee (or optional AI aid) → freeze snapshot when votes open: tracked in a separate issue. Do not block this template on that lifecycle.

## Summary Require a **standard proposal format** so every votable proposal presents the same sections in the same order: problem, optional context, proposed solution, pros/cons, TL;DR, and success criteria. Server-enforce minimums; do not rely on freeform WYSIWYG headings. Community feedback: holders often vote without understanding a proposal (the failure mode seen in LUNC on-chain governance). A shared template is the minimum bar so voters can scan impact, risks, and what “success” means before they sign. Independent expert analysis and a draft/review gate are **out of scope here** — see the follow-up issue on draft collaboration. Depends on existing `#2` / `#3` propose+vote APIs. Votes stay **offchain / advisory**. --- ## Current codebase Proposals are a single title + freeform HTML body. There is no section schema, no minimum length per topic, and no TL;DR on the list. | Area | Today | |------|--------| | Schema | `voting.proposals` (`ledger/migrations/20260825000002_voting.sql`): `title`, `body_html`, `body_canonical`, `status` default `'open'`. No section columns. | | Create API | `POST /v1/proposals` in `operator-voting/src/api.rs`: title ≤ 200 chars, body ≤ `MAX_BODY_BYTES` (64 KiB), ammonia sanitize, signed `title` + `body_hash`, ≥1000 CL8Y on **that** address’s chain, snapshot freeze `{ terra_height, bsc_block }` at create. | | Signing | `operator-voting/src/payload.rs` + `frontend/src/utils/votingPayload.ts` — purposes `register` / `propose` / `vote` only. Hash is SHA-256 of the submitted HTML string. | | Sanitize | `operator-voting/src/html.rs` (ammonia allowlist) and `frontend/src/utils/sanitizeProposalHtml.ts` (client strip aligned with ammonia). | | Compose UI | `frontend/src/pages/VoteNewPage.tsx` — one title input + TipTap `StarterKit`. Submit enabled if titled and balance ≥ 1000 CL8Y. Empty body is allowed. | | Detail UI | `frontend/src/pages/VoteDetailPage.tsx` — `dangerouslySetInnerHTML` of `body_html`. No labeled sections. | | List UI | `frontend/src/pages/VoteListPage.tsx` — title, chain, status only. No summary. | | Tests | `VoteNewPage.test.tsx` (balance gate), `operator-voting/tests/api_flow.rs` (create, XSS strip, 1000 CL8Y). No section tests. | | Docs | `docs/OPERATOR_VOTING.md`, `docs/FRONTEND.md`, `docs/ARCHITECTURE.md` (snapshot at create). | `status` is stored and displayed but never transitions; every successful create is immediately votable. --- ## Why this is needed A blank rich-text field does not force the proposer (or later collaborators) to state the problem, trade-offs, or what a successful outcome looks like. Voters then see a wall of HTML and a for/against/abstain row. Standard sections: 1. Make proposals comparable. 2. Put a **TL;DR** on the list and at the top of the detail page so people can decide whether to read further. 3. Make “pros and cons” a first-class field so critique is not optional self-praise buried in a paragraph. 4. Give later review (draft comments, committee, optional AI) a stable schema to fill — without that, review comments attach to unstructured blobs. This does **not** replace community decision-making. It raises the information floor. --- ## Constraints / guardrails 1. **No new smart contracts.** Offchain / advisory copy on list + detail must stay. 2. **Server is source of truth.** Missing or whitespace-only required sections → `400`. Do not trust the dApp form. 3. **Structured fields, not “please use these headings” in freeform HTML.** Headings inside a single `body_html` are skippable and spoofable. 4. **Keep signing domain.** Client still signs `purpose=propose` with `title` + `body_hash`. Hash a **canonical serialization of the section object** (stable key order, ammonia-sanitized HTML per section), not ad-hoc concatenated UI strings. 5. **Ammonia allowlist unchanged** per section. No new tags, no `javascript:` / event handlers. Combined sanitized payload still ≤ 64 KiB. 6. **Identity v1** — Terra and BSC proposers both use the same template. Do not merge `terra1…` and `0x…`. 7. **≥1000 CL8Y gate unchanged** (per registering address’s chain). 8. **Legal gate unchanged** — connected users still fail closed behind clickwrap before compose. 9. **Least privilege** — new columns stay in schema `voting`; update `deploy/grants.sql` only if new tables/functions appear. Operator role must not write ledger ingest tables. 10. **Backward compatible read path** — existing rows with only `body_html` must still render (legacy article). New creates must persist sections. 11. **Do not freeze a second snapshot** in this issue. Snapshot-at-create stays until the draft/review issue moves freeze to vote-open. 12. **Success criteria are not a later punishment mechanism.** Copy must describe them as goalposts for spend/outcome justification, not slashing or “hang the proposer.” 13. **No seeds / mnemonics.** No `VITE_*` BSC RPC. No `ADMIN_TOKEN` in the frontend. 14. **Neutral tone in labels.** Section titles are product copy (“Problem”, “Trade-offs”), not political framing. ### Required sections (v1) | Key | UI label | Required? | |-----|----------|-----------| | `problem` | The idea or problem to be solved | Yes | | `context` | Supporting context, real-world issues, and alternatives | No (empty allowed) | | `solution` | Proposed solution and supporting evidence | Yes | | `pros_cons` | Pros and cons / trade-offs | Yes | | `summary` | Summary (TL;DR) | Yes | | `success_criteria` | Success criteria / goalposts | Yes | Independent “trusted contributor analysis” is **not** a proposer-owned required field here (that belongs on the review-gate issue so it cannot be self-authored and labeled as independent). Suggested minimums (tune in implementation, encode in API constants): each required section ≥ 40 visible characters after strip-tags + collapse whitespace; `summary` also ≤ 500 characters so list cards stay scannable. --- ## Relevant files - `ledger/migrations/20260825000002_voting.sql` (and a **new** sqlx migration — do not rewrite applied files) - `operator-voting/src/api.rs` (`CreateProposalRequest`, `create_proposal`, `get_proposal`, `list_proposals`) - `operator-voting/src/db.rs` (`ProposalRow`, `insert_proposal`) - `operator-voting/src/html.rs`, `operator-voting/src/payload.rs` (`body_hash`) - `operator-voting/src/config.rs` (`MAX_BODY_BYTES`) - `operator-voting/tests/api_flow.rs` - `deploy/grants.sql` (only if new relations) - `frontend/src/pages/VoteNewPage.tsx`, `VoteNewPage.test.tsx` - `frontend/src/pages/VoteDetailPage.tsx`, `VoteListPage.tsx` - `frontend/src/services/operatorVoting.ts` - `frontend/src/utils/sanitizeProposalHtml.ts`, `votingPayload.ts` - `frontend/e2e/vote.spec.ts` - `docs/OPERATOR_VOTING.md`, `docs/FRONTEND.md` --- ## Recommended direction 1. **Persist sections as JSON** (e.g. `body_sections JSONB NOT NULL` with a CHECK that required keys exist and are text) **and** keep `body_html` as a server-rendered concatenation for legacy clients / ammonia round-trip. Prefer JSONB as the source of truth for new rows. 2. **Canonical hash:** UTF-8 JSON with sorted keys, each value already ammonia-cleaned, no insignificant whitespace. Document the exact serializer in `OPERATOR_VOTING.md` so TS and Rust match (same discipline as `canonicalizePayload`). 3. **Compose UI:** replace the single TipTap blob with labeled fields (TipTap or textarea per section). Show character counts and the 1000 CL8Y gate. Preview the voter-facing layout before sign. 4. **List:** show `summary` under the title (truncated). 5. **Detail:** render sections in fixed order with headings; keep snapshot + advisory copy; vote CTAs unchanged. 6. **OpenAPI stub** (`GET /openapi.json`) and `docs/OPERATOR_VOTING.md` endpoints table updated. 7. **Do not** add comments, draft status, committee, or AI in this issue. --- ## Acceptance criteria - [ ] New `POST /v1/proposals` rejects missing/whitespace-only required sections and over-long `summary` / total body. - [ ] Accepted proposals persist sections; `GET /v1/proposals/:id` returns them; list includes `summary`. - [ ] Signed `body_hash` is over the canonical section serialization; hash mismatch still `401`. - [ ] `/vote/new` cannot submit until required sections meet the same rules the API uses (UX); API still enforces. - [ ] `/vote` list shows TL;DR; `/vote/:id` shows labeled sections in canonical order. - [ ] Legacy proposals without sections still render via `body_html`. - [ ] XSS: script / `onerror` in any section stripped; not executed on detail. - [ ] Terra and BSC propose paths both work; 1000 CL8Y, blacklist, Legal gate, ammonia, POST rate limits unchanged. - [ ] Unit + API + frontend tests for gates; Playwright compose uses the new fields. - [ ] Docs updated. No contracts, no archive LCD, no identity merge. --- ## Test plan (all paths) 1. **API — happy path:** registered wallet ≥1000 CL8Y, all required sections over minimum, optional `context` empty → `201`; detail returns sections; list includes `summary`. 2. **API — missing section:** omit `pros_cons` or send `""` / `" "` / `"<p></p>"` / `"<p><br></p>"` → `400`. 3. **API — summary length:** over max → `400`; at max → `201`. 4. **API — combined size:** sections summing over 64 KiB → `400` (and HTTP body limit still applies). 5. **API — hash:** sign hash of unsanitized raw vs stored canonical → mismatch `401`; matching canonical → `201`. 6. **API — title / 1000 CL8Y / unregistered / blacklist:** existing denials still hold. 7. **API — Terra and BSC:** one create each. 8. **API — legacy GET:** row with null/absent sections returns `body_html` only; dApp does not crash. 9. **Frontend unit:** submit disabled until required fields valid **and** balance ≥1000; enabled at boundary with valid sections. 10. **Frontend unit:** canonical hash helper matches Rust vectors (golden JSON fixtures). 11. **Component:** labels visible; optional context not blocking. 12. **Playwright:** fill template → create → detail shows headings + TL;DR on list. 13. **Render safety:** each section with `<script>` / `onerror=` does not execute. 14. **Legal / disconnected:** compose still gated; browse list OK while disconnected. 15. **Copy:** success-criteria helper text is goalposts, not punitive. --- ## Test plan — attack, hack & abuse vectors 1. **Client-only validation bypass:** raw `POST` with empty required sections — API `400`. 2. **Whitespace / HTML-empty padding:** `"&nbsp;"`, zero-width, `<p> </p>` must not satisfy minimums (strip tags + Unicode whitespace before count). 3. **XSS in every section** including `summary` on the list card. 4. **Hash confusion:** sign freeform `body_html` while posting sections (or the reverse) → reject. 5. **Key-order / serializer drift:** two equivalent objects with different JSON key order must hash identically after canonicalization; a changed letter must not. 6. **Title vs section spoof:** signed title `A` with body that talks about `B` — still allowed (content honesty is social); do not try to NLP-enforce. Do enforce title length and hash match. 7. **Oversized payload / gzip bombs:** existing body cap + POST QPS (`O-RL1`) still apply. 8. **Blacklist / unregistered / below 1000:** no template bypass. 9. **Phishing copy:** compose UI must not ask for seeds; only wallet `signArbitrary` / `personal_sign`. 10. **Self-labeled “independent analysis”:** do not add a proposer field that can be titled as if a third party wrote it. 11. **Legacy XSS:** old `body_html` still ammonia-sanitized on read if you re-serialize; never mark legacy HTML as trusted. --- ## Verification criteria - [ ] `cargo test --workspace --lib` and `operator-voting` integration tests covering section validation + hash golden vectors. - [ ] `cd frontend && npm test` and `npm run test:e2e` (template compose path). - [ ] Manual or staging: Terra **and** BSC: Legal accept → register → create templated proposal → list shows TL;DR → detail shows all sections → vote still works. - [ ] `gitleaks detect` clean; no secrets in fixtures. - [ ] `docs/OPERATOR_VOTING.md` + `docs/FRONTEND.md` describe the schema and hash. ## Labels `frontend` `UX` `enhancement` `security` `product` `backend` ## Priority P2 ## Follow-up Draft → community comments → committee (or optional AI aid) → freeze snapshot when votes open: tracked in a separate issue. Do not block this template on that lifecycle.
PlasticDigits commented 2026-08-29 14:17:25 +00:00 (Migrated from gitlab.com)

marked as related to #11

marked as related to #11
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:46:50 +00:00 (Migrated from gitlab.com)

marked as related to #13

marked as related to #13
PlasticDigits commented 2026-08-30 05:46:25 +00:00 (Migrated from gitlab.com)

mentioned in merge request !22

mentioned in merge request !22
PlasticDigits commented 2026-08-30 05:47:16 +00:00 (Migrated from gitlab.com)

mentioned in merge request !23

mentioned in merge request !23
PlasticDigits commented 2026-08-30 05:55:09 +00:00 (Migrated from gitlab.com)

mentioned in merge request !26

mentioned in merge request !26
PlasticDigits commented 2026-08-30 05:55:17 +00:00 (Migrated from gitlab.com)

mentioned in issue #11

mentioned in issue #11
PlasticDigits commented 2026-08-30 06:04:50 +00:00 (Migrated from gitlab.com)

mentioned in commit 524a3ee79e

mentioned in commit 524a3ee79e3672796d8f8f8046bc4245a05687a9
PlasticDigits commented 2026-08-30 06:08:00 +00:00 (Migrated from gitlab.com)

mentioned in commit ea426406ca

mentioned in commit ea426406caf236e60c40c5fa90ac654ed55c9a28
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-08-30 06:08:01 +00:00
PlasticDigits commented 2026-08-30 06:17:35 +00:00 (Migrated from gitlab.com)

mentioned in commit 83b8b26f92

mentioned in commit 83b8b26f92e4576466cd4f1d86c2324cc8bce43a
PlasticDigits commented 2026-08-30 06:17:56 +00:00 (Migrated from gitlab.com)

mentioned in commit 842ee1137c

mentioned in commit 842ee1137c778257daa768c6daea4203fd58e2db
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:56 +00:00 (Migrated from gitlab.com)

Merged in !23 onto main, then !26 layered the #11 draft lifecycle on top of the same section schema.

In-tree AC from !23: server-enforced sections (problem, optional context, solution, pros_cons, summary, success_criteria), canonical JSON SHA-256 body_hash (golden 85b0e398…), list TL;DR, labeled detail, legacy body_html still renders, XSS stripped via ammonia, Terra/BSC propose paths, 1000 CL8Y / blacklist / Legal / ammonia unchanged. Migration 20260830000001_proposal_sections.sql (JSONB shape CHECK). Compose uses labeled fields; API still enforces.

!26 overlay (do not treat as a template regression): new creates are purpose=draft / status=draft with freeze at vote-open, not at create. That is the #11 dependency, not an #10 AC miss.

Do not close until staging Terra and BSC: Legal accept → register → templated draft/compose → list TL;DR → detail sections. Live GET /v1/proposals must include summary after migrate.

CI on the MR was ci_quota_exceeded, not a code failure.

Merged in !23 onto `main`, then !26 layered the #11 draft lifecycle on top of the same section schema. **In-tree AC from !23:** server-enforced sections (`problem`, optional `context`, `solution`, `pros_cons`, `summary`, `success_criteria`), canonical JSON SHA-256 `body_hash` (golden `85b0e398…`), list TL;DR, labeled detail, legacy `body_html` still renders, XSS stripped via ammonia, Terra/BSC propose paths, 1000 CL8Y / blacklist / Legal / ammonia unchanged. Migration `20260830000001_proposal_sections.sql` (JSONB shape CHECK). Compose uses labeled fields; API still enforces. **!26 overlay (do not treat as a template regression):** new creates are `purpose=draft` / `status=draft` with freeze at **vote-open**, not at create. That is the #11 dependency, not an #10 AC miss. **Do not close** until staging Terra **and** BSC: Legal accept → register → templated draft/compose → list TL;DR → detail sections. Live `GET /v1/proposals` must include `summary` after migrate. CI on the MR was `ci_quota_exceeded`, not a code failure.
PlasticDigits commented 2026-08-30 06:21:56 +00:00 (Migrated from gitlab.com)

marked as related to #15

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

Reopened. Merging !23 auto-closed this because “Do not close #10 until staging…” contains the GitLab closing pattern close #10. In-tree template is on main; staging Terra and BSC compose still required.

Reopened. Merging !23 auto-closed this because “Do not close #10 until staging…” contains the GitLab closing pattern `close #10`. In-tree template is on `main`; staging Terra **and** BSC compose still required.
PlasticDigits commented 2026-09-01 08:09:41 +00:00 (Migrated from gitlab.com)

Verify pass (2026-09-01) — in-tree only; this issue stays open

Pushed d7f4d51 to main. Issue 11 overlay (draft + freeze-at-open) is expected, not a template regression.

In-tree (pass)

  • Server-enforced sections; canonical JSON SHA-256 golden 85b0e398…; list TL;DR as text; labeled detail; legacy body_html still renders; XSS stripped.
  • Empty required sections → 400 (test helper no longer panics while constructing that fixture).
  • Terra and BSC templated draft paths in API tests; 1000 CL8Y / blacklist / ammonia unchanged.
  • Playwright compose fills labeled fields; list shows summary.
  • Docs: OPERATOR_VOTING.md OV-S, FRONTEND.md, skill AGENTS_PROPOSAL_TEMPLATE.md / AGENTS_DRAFT_REVIEW.md.
  • gitleaks detect clean on this tree.

Staging (remaining AC)

  • Terra and BSC: Legal accept → register → templated draft/compose → list TL;DR → detail sections.
  • Live GET /v1/proposals is still [] (no drafts on prod yet). After migrate, list JSON must include summary.

Checklist

  • cargo test --workspace --lib and operator-voting integration (sections + hash golden)
  • cd frontend && npm test and npm run test:e2e (template compose)
  • Docs describe schema and hash
  • Staging Terra and BSC compose with real wallets
  • Live GET /v1/proposals includes summary after a real draft exists

Stays open: staging Terra/BSC compose still required. GitLab auto-close from “do not close #10” must not be treated as done.

## Verify pass (2026-09-01) — in-tree only; this issue stays **open** Pushed `d7f4d51` to `main`. Issue 11 overlay (draft + freeze-at-open) is expected, not a template regression. ### In-tree (pass) - Server-enforced sections; canonical JSON SHA-256 golden `85b0e398…`; list TL;DR as text; labeled detail; legacy `body_html` still renders; XSS stripped. - Empty required sections → 400 (test helper no longer panics while constructing that fixture). - Terra and BSC templated draft paths in API tests; 1000 CL8Y / blacklist / ammonia unchanged. - Playwright compose fills labeled fields; list shows summary. - Docs: `OPERATOR_VOTING.md` OV-S, `FRONTEND.md`, skill `AGENTS_PROPOSAL_TEMPLATE.md` / `AGENTS_DRAFT_REVIEW.md`. - `gitleaks detect` clean on this tree. ### Staging (remaining AC) - Terra **and** BSC: Legal accept → register → templated draft/compose → list TL;DR → detail sections. - Live `GET /v1/proposals` is still `[]` (no drafts on prod yet). After migrate, list JSON must include `summary`. ### Checklist - [x] `cargo test --workspace --lib` and operator-voting integration (sections + hash golden) - [x] `cd frontend && npm test` and `npm run test:e2e` (template compose) - [x] Docs describe schema and hash - [ ] Staging Terra and BSC compose with real wallets - [ ] Live `GET /v1/proposals` includes `summary` after a real draft exists **Stays open:** staging Terra/BSC compose still required. GitLab auto-close from “do not close #10” must not be treated as done.
PlasticDigits commented 2026-09-01 08:51:32 +00:00 (Migrated from gitlab.com)

Closing on in-tree template (OV-S)

Server-enforced sections, canonical hash golden 85b0e398…, list TL;DR, labeled detail, legacy body_html, XSS strip, Terra/BSC API paths, and Playwright compose are on main. Issue 11 overlay (draft + freeze-at-open) is expected. Staging compose with real wallets stays on issue 7.

## Closing on in-tree template (OV-S) Server-enforced sections, canonical hash golden `85b0e398…`, list TL;DR, labeled detail, legacy `body_html`, XSS strip, Terra/BSC API paths, and Playwright compose are on `main`. Issue 11 overlay (draft + freeze-at-open) is expected. Staging compose with real wallets stays on issue 7.
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-09-01 08:51:56 +00:00
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#10
No description provided.