Help

Guidance at three levels — from everyday participation to full technical documentation of how this democracy platform is built.

Level 3 · Technical

Architecture, data, stack & democracy engine

Full-access documentation for operators, auditors, and developers. Source tree: /freedom.kiwi. Production host: https://dd.light.org.nz. License: AGPL-3.0-or-later.

High-level architecture

Browser (Next.js PWA)
        │
        ▼
nginx · TLS (dd.light.org.nz)
   ├─ /          → dd-web  :3010
   ├─ /api,/health → dd-api :4000
   │
   ├─ PostgreSQL (+ PostGIS when available)
   └─ Redis (SSE pub/sub)
  • Monorepoapps/api (Express/TS), apps/web (Next.js App Router), packages/shared (Zod rulesets), db/migrations, db/seeds, docs/, deploy/helm.
  • Process managers — pm2 apps dd-api / dd-web; nginx terminates TLS.
  • Multi-tenancyjurisdiction_id on citizen data; host header / X-Jurisdiction / UI selector.

Stack

FrontendNext.js 15, React 19, mobile-first CSS, PWA manifest
APINode 20, Express, Zod validation, argon2 passwords, JWT sessions
DatabasePostgreSQL 16, optional PostGIS geometry, pgcrypto UUIDs
RealtimeRedis pub/sub + Server-Sent Events (/api/events/processes/:id/stream)
Crypto libs@noble/curves (X25519), @noble/ciphers (AES-GCM), @noble/hashes (SHA-256)

Core data model

  • jurisdictions — hierarchy, names JSON, languages, electorate_estimate, ruleset JSONB, geometry
  • users / eligibility — identity + per-jurisdiction verification
  • processes — type ∈ popular_initiative | optional_referendum | mandatory_referendum; status lifecycle; rules_snapshot at create time
  • committee_members, signatures (receipt_hash, commitment)
  • discussions / comments
  • elections, ballots, bulletin_board (hash-chained entries)
  • audit_events — append-only hash chain of critical actions
  • jurisdiction_admins — RBAC link

SQL: db/migrations/001_init.sql · Seeds: db/seeds/001_switzerland.sql + apps/api/src/db/seed.ts

Rulesets (democracy configuration)

Validated by Zod in @dd/shared. Thresholds may be absolute or % of electorate. Majority types: simple, qualified, double (people + sub-units / cantons). Swiss federal defaults are encoded for CH; DEMO uses tiny numbers for training.

Democracy instrument types

TypeTriggerTypical majorityDigital adaptations
Popular initiativeCitizen committee + signaturesOften double (constitutional)Live counters, receipts, maps (phased), exportable petition package
Optional referendumAfter act publicationSimpleShort collection window, notifications (phased)
Mandatory referendumRuleset triggers (constitution, treaties…)Often doubleAdmin/system opens vote without signature phase

Mapping document: repository docs/swiss-mapping.md. Also summarised on About.

E2E-V voting (Phase 1 baseline)

  1. Election keypair (X25519); public key published.
  2. Ballot sealed with ephemeral X25519 + AES-GCM; ciphertext on bulletin board.
  3. Individual receipt = SHA-256(ballot_id ‖ ciphertext ‖ voter_nonce).
  4. Board entries hash-chained; board commitment over entry hashes.
  5. Tally ceremony decrypts active (non-superseded) ballots; publishes counts + tally_proof.
  6. Re-vote until deadline supersedes prior ballot (coercion mitigation).

ADR: docs/adr/0001-voting-crypto.md. Upgrade path to Helios / Belenios / ElectionGuard-class backends is intentional — do not hard-wire UI to this crypto module.

Identity & eligibility

  • MVP: email + password (argon2) + JWT
  • WebAuthn credential storage ready; full ceremony Phase 2
  • Eligibility methods: self_attestation (now), voter_roll / eid (interfaces)
  • ADR: docs/adr/0002-identity.md

Open data sources (phased)

geoBoundaries (CC BY 4.0), Natural Earth, GeoNames, CLDR/ISO/Wikidata, UN WPP — see docs/adr/0003-open-data.md and scripts/import-geoboundaries.md. Phase 1 ships curated seeds, not full world ADM imports.

Security & residual risks

Documented in docs/threat-model.md: bots/Sybil, coercion, device malware, compromised server, insider admins, nation-state identity compromise. Perfect global eligibility remains an open problem; remote voting cannot eliminate coercion. Audit chain + public board raise the bar for silent tally tampering.

Key API surfaces

POST /api/auth/register|login
GET  /api/auth/me
GET  /api/jurisdictions[/current|/:code]
PATCH /api/jurisdictions/:code/ruleset
POST /api/jurisdictions/:code/eligibility
GET|POST /api/processes…
POST /api/processes/initiatives
POST /api/processes/referendums/optional
POST /api/signatures/:processId/sign
GET  /api/signatures/:processId/progress
POST /api/voting/elections/:id/cast|tally
GET  /api/voting/elections/:id/board
GET  /api/events/processes/:id/stream   (SSE)
GET  /api/processes/:id/export

Ops on this host

cd /freedom.kiwi
pm2 restart dd-api dd-web
pm2 logs dd-api --lines 100
nginx -t && systemctl reload nginx
# TLS: /etc/letsencrypt/live/dd.light.org.nz/
# Vhost: /etc/nginx/sites-available/dd.light.org.nz

Further reading in the repo

Expand any section to read the live document from the deployment tree (/freedom.kiwi).

Architecturedocs/architecture.md

Architecture Overview — dd.freedom.kiwi

Mission

A production-oriented, AGPL-3.0 platform for real-time digital direct democracy, faithful to Swiss semi-direct democracy instruments, multi-tenant by jurisdiction, and usable at https://dd.freedom.kiwi (subdomains / path prefixes / custom domains).

High-level diagram

                    ┌─────────────────────────────────────┐
                    │  Edge (CDN / TLS / dd.freedom.kiwi) │
                    └──────────────┬──────────────────────┘
                                   │
              ┌────────────────────┼────────────────────┐
              ▼                    ▼                    ▼
        Next.js (web)         API (Node/TS)         Static / PWA
        SSR · i18n · PWA      REST · SSE            assets
              │                    │
              │         ┌──────────┼──────────┐
              │         ▼          ▼          ▼
              │    PostgreSQL   Redis      Event/audit
              │    + PostGIS    pub/sub    append-only
              │         │
              └─────────┴── jurisdiction isolation (tenant_id / jurisdiction_id)

Monorepo layout

PathRole
apps/apiExpress API, auth, instruments, voting crypto, SSE
apps/webNext.js App Router UI (citizen + admin)
packages/sharedShared types, ruleset Zod schemas
db/migrationsSQL migrations (PostGIS)
db/seedsWorld hierarchy stubs + Switzerland reference + samples
deploy/helmProduction Kubernetes chart
docs/ADRs, Swiss mapping, threat model

Multi-tenancy

  • One deployment serves many jurisdictions.
  • Every row of citizen data carries jurisdiction_id.
  • Host routing: ch.dd.freedom.kiwi → jurisdiction CH; path dd.freedom.kiwi/ch also supported.
  • No cross-jurisdiction queries in citizen APIs; stewards may operate globally with audited roles.

Core modules (Phase 1)

  1. Jurisdiction & ruleset engine — hierarchical ADM model + JSON rulesets
  2. Identity & eligibility — email/password + WebAuthn hooks; eligibility flags
  3. Proposal / initiative engine — committee, draft, signatures, threshold
  4. Optional referendum — post-publication signature collection → vote
  5. Deliberation — threaded discussion per process
  6. Voting engine (E2E-V baseline) — encrypted ballots, receipts, bulletin board, verifiable tally
  7. Realtime — Redis + SSE for signature counters and activity
  8. Admin & audit — RBAC + append-only audit_events

Crypto voting (MVP baseline)

See ADR-0001. Phase 1 ships a practical E2E-V scheme:

  • Election keypair (ElGamal-style over Curve25519 via @noble/curves abstractions + sealed ballots)
  • Individual verifiability via voter receipt (commitment hash)
  • Universal verifiability via public bulletin board + published tally proof
  • Architecture allows swapping in Helios/Belenios/ElectionGuard-class backends later

Phasing

PhaseScope
1 (this MVP)CH seeded, initiative + optional referendum, basic identity, E2E-V baseline, deliberation, Docker Compose
2Full world data imports, hierarchical double majority, counter-proposals, full i18n, public GraphQL/REST
3Advanced crypto, liquid democracy options, hybrid paper, meta-governance dogfooding at scale

Design principles

  1. Transparency over convenience
  2. Minority protection (double majority where ruleset requires)
  3. Progressive identity (document residual Sybil/remote-voting risks)
  4. Exportable formal reports for real-world political adoption
  5. Dogfood platform rule changes through the same democratic tools
Data modeldocs/data-model.md

Data Model

Core entities

### jurisdictions
Hierarchical administrative units (ADM0–ADMn). Fields: code, parent_id, level, multilingual names (JSONB), timezone, languages[], geometry (PostGIS when available), geometry_json (JSONB fallback), electorate_estimate, ruleset (JSONB).

### users
Identity records: email/phone, password hash (optional), WebAuthn credentials JSON, locale, created_at.

### citizenships / eligibility
Links user_idjurisdiction_id with status (pending, verified, rejected), method (self_attestation, voter_roll, eid), voting_age check.

### committees
Initiative committees: process_id, members, roles.

  • type: popular_initiative | optional_referendum | mandatory_referendum | vote
  • status: draft | collecting | submitted | scheduled | voting | closed | archived
  • title, body (proposal text), justification, jurisdiction_id, rules_snapshot, thresholds, deadlines

### signatures
Cryptographically receipted support: process_id, user_id, signature_hash, payload_commitment, created_at, challenged_at, valid.

### discussions / comments
Threaded deliberation tied to process_id.

### elections / ballots / bulletin_board
Election keys metadata, encrypted ballots, public board entries, tally + proof JSON.

### audit_events
Append-only: actor, action, entity, payload hash, prev_hash (hash chain).

Ruleset shape (excerpt)

{
  "voting_age": 18,
  "initiative": {
    "committee_min_size": 7,
    "signature_threshold": 100000,
    "signature_threshold_pct": null,
    "collection_days": 548,
    "majority_type": "double"
  },
  "optional_referendum": {
    "signature_threshold": 50000,
    "collection_days": 100,
    "majority_type": "simple"
  },
  "mandatory_referendum": {
    "majority_type": "double",
    "triggers": ["constitutional_amendment", "certain_treaties"]
  },
  "cantons": { "half_canton_weight": 0.5 }
}
Threat model & residual risksdocs/threat-model.md

Security Threat Model & Residual Risks

Assets

  • Ballot secrecy and integrity
  • Signature authenticity and uniqueness
  • Eligibility correctness
  • Audit trail integrity
  • Citizen PII (minimal retention)

Adversaries

AdversaryExamplesMitigations (MVP → later)
Scripted bots / SybilMass fake accountsRate limits, email verify, anomaly flags; PoP experiments (Phase 2+)
CoercionVote buying, family pressureRe-vote until deadline; document residual remote-voting risk; hybrid paper (Phase 3)
Device malwareKeyloggers, ballot alterationReceipts + bulletin board checks; OS/browser hygiene guidance
Compromised serverAltered talliesE2E-V public proofs; append-only audit chain; key ceremonies
Nation-stateTargeted identity compromiseDefense-in-depth, least privilege, optional eID; honest residual-risk docs
Insider adminQuiet rule/tally changesRBAC, audit events, dual control for key ops

Residual risks (documented honestly)

  1. Perfect global eligibility is an open problem. Modules are progressive (self-attestation → rolls → eID → VCs).
  2. Pure remote voting cannot eliminate coercion or device compromise. Re-voting and hybrid pathways reduce but do not erase risk.
  3. MVP crypto is practical E2E-V, not a full audited Helios/ElectionGuard deployment. Upgrade path is first-class (ADR-0001).
  4. Open data electorate estimates may diverge from official rolls — rulesets should prefer official imports when available.
  5. WebAuthn progressive mode stores credentials and challenges; full assertion/attestation cryptographic verification must be enabled (@simplewebauthn) before high-stakes reliance.
  6. Notifications may fall back to console logging without SMTP/VAPID — do not treat as guaranteed delivery.
  7. PDF exports are attestation-hashed facilitation packages, not government letterhead.

Privacy (GDPR-oriented)

  • Minimize stored attributes
  • Separate identity from ballot content (encrypted ballots; eligibility checked before casting)
  • Export/delete workflows for user data (non-ballot audit retention policies configurable)
  • Aggregated analytics only in public dashboards
Swiss instrument mappingdocs/swiss-mapping.md

Swiss Instrument Mapping — Digital Adaptation

This document maps classic Swiss federal instruments to dd.freedom.kiwi features and improvements.

1. Popular Initiative (Volksinitiative)

Swiss practiceDigital realization
Committee of citizens drafts textGuided wizard: committee members (min size from ruleset), structured proposal fields, versioning
100,000 signatures / 18 months (federal)Live signature collection; CH ruleset default signature_threshold: 100000, collection_days: 548
Submit to Federal ChancelleryStatus → submitted; formal PDF + machine-readable export with cryptographic support proofs
Government recommendation / counter-proposalRecommendation + counter-proposal text/title; include_counter_on_ballot builds multi-question ballot + deciding question (yes=initiative, no=counter)
Popular vote; double majority for constitutionalcomputeQuestionTally + results UI: people majority and weighted sub-units (half-cantons via canton_weight)

Improvements: continuous progress, sub-jurisdiction heatmaps, challenge window, receipt-backed signatures.

2. Optional Referendum (fakultatives Referendum)

Swiss practiceDigital realization
After law published, 50,000 signatures / 100 daysoptional_referendum process type; CH defaults 50000 / 100
Simple majority of peoplemajority_type: simple

Improvements: notifications on new acts, live counters, linked deliberation + educational overlays.

3. Mandatory Referendum (obligatorisches Referendum)

Swiss practiceDigital realization
Automatic vote on constitutional amendments / certain treatiesProcess type mandatory_referendum; triggered by admin/rules when act class matches
Often double majorityRuleset majority_type: double with canton aggregation

Phase 1 seeds the process type and admin trigger; full treaty taxonomy is Phase 2.

4. Double majority & minority protection

  • Popular majority and majority of cantons (half-cantons weighted per Swiss practice in CH seed).
  • Implementation: packages/shared/src/majority.ts — subunit passes if yes>no; weights sum; double majority requires people pass and weight_for > total_weight/2.
  • Results page lists each sub-jurisdiction’s yes/no/weight/passed flag.
  • Configurable hierarchical aggregation for other jurisdictions (municipality → region → nation).
  • Educational content modules emphasize consensus culture and minority-impact statements.

5. Multi-level democracy

Hierarchy: municipality → canton → confederation (+ optional supra-national). Each level may have its own ruleset inheriting defaults from parent with overrides.

6. Parameters as rulesets

All thresholds, periods, eligibility, voting age, majority type live in per-jurisdiction JSON validated by Zod (packages/shared). Admin UI edits rulesets; critical changes can be routed through meta-governance (Phase 3 dogfooding).

7. Legal binding note

The platform facilitates, deliberates, verifies, and exports. Binding force requires political/legal adoption in each jurisdiction. Export packages are designed for presentation to real authorities.

ADR-0001 — Voting cryptographydocs/adr/0001-voting-crypto.md

ADR-0001: Voting Cryptography Baseline

## Status
Accepted (Phase 1)

## Context
We need end-to-end verifiable voting that is implementable now, open-source, and upgradeable to Helios/Belenios/ElectionGuard-class schemes.

## Decision
Phase 1 uses a sealed-ballot + bulletin board + receipt scheme:

  1. Per election: generate encryption keypair; public key published; private key held for tally ceremony (split later).
  2. Voter encrypts choice (yes/no/abstain) to election public key (NaCl box / X25519).
  3. Server stores only ciphertext on the public bulletin board with timestamp and ballot id.
  4. Voter receives a receipt = SHA-256(ballot_id || ciphertext || voter_nonce) to confirm “recorded as cast”.
  5. Tally: authorized ceremony decrypts all ballots, publishes counts + tally proof (hash of sorted ciphertext list || counts || election_id) so anyone can recompute the board commitment.
  6. Re-voting allowed until close: latest ballot for voter replaces previous (previous marked superseded, still on board for audit).
  • Individual verifiability: yes (receipt vs board)
  • Universal verifiability: partial→strong (board commitment + published decryption under ceremony); full ZK tally proofs deferred to Phase 3
  • Ballot secrecy: ciphertext on board; coercion mitigated via re-vote
  • Pluggable interface VotingBackend allows future Helios/ElectionGuard adapters without rewriting process flows
ADR-0002 — Identity & eligibilitydocs/adr/0002-identity.md

ADR-0002: Identity & Eligibility

## Status
Accepted (Phase 1)

  • Primary MVP auth: email + password (argon2) + session JWT
  • WebAuthn/passkeys: schema + register/login endpoints stubbed for progressive enhancement
  • OAuth / eID / VC adapters: interface EligibilityVerifier with SelfAttestationVerifier and VoterRollVerifier in Phase 1
  • Eligibility is per jurisdiction, not global account uniqueness across earth

## Residual risk
Sybil resistance is best-effort. Documented in threat model. Small jurisdictions may enable community attestation (Phase 2).

ADR-0003 — Open data sourcesdocs/adr/0003-open-data.md

ADR-0003: Open Data Sources

## Status
Accepted

## Sources
| Dataset | License | Use |
|---------|---------|-----|
| geoBoundaries | CC BY 4.0 | ADM boundaries (preferred) |
| Natural Earth | public domain | complementary geography |
| GeoNames | CC BY | toponyms |
| CLDR / ISO 3166 / 639 / Wikidata | various open | languages, locales, labels |
| UN WPP | open | population / electorate estimates |

## Phase 1
Ship Switzerland + sample jurisdictions with accurate rules and approximate electorate estimates. Import scripts under scripts/import-* pull geoBoundaries in Phase 2; MVP seeds are SQL/JSON.

Guide — Citizen (EN)docs/guides/user.md

Citizen guide (EN stub)

Get started

  1. Open https://dd.freedom.kiwi (or your local http://localhost:3000).
  2. Select your jurisdiction.
  3. Register or login.
  4. Claim eligibility (Account → birth year self-attestation in MVP).

Support an initiative

  1. Open Processes.
  2. Read the proposal text and deliberation thread.
  3. Click Sign during the collection window.
  4. Store your signature receipt.

Vote

  1. When status is voting, cast an encrypted ballot (Yes / No / Abstain).
  2. Keep your ballot receipt + nonce.
  3. Anyone can inspect the public bulletin board; after tally, check published proofs.

Translations

Community translation workflow arrives in Phase 2 (EN/DE/FR/IT/RM stubs first for CH).

Guide — Admin (EN)docs/guides/admin.md

Admin guide (EN stub)

Roles

  • Platform steward — global tools, all jurisdictions
  • Jurisdiction admin — ruleset, recommendations, open/tally elections

Typical flow

  1. Citizens create & launch an initiative; signatures collect live.
  2. On threshold → status submitted.
  3. Admin posts recommendation / optional counter-proposal.
  4. Admin Open vote (creates election keys + bulletin board).
  5. After voting window, admin Tally (publishes counts + proof).
  6. Export formal report JSON for authorities / media.

Ruleset edits

PATCH /api/jurisdictions/:code/ruleset with a full ruleset JSON. Prefer dogfooding major changes through democratic process (Phase 3).

Guide — Citizen (DE stub)docs/guides/user.de.md

Bürgerleitfaden (DE Stub)

  1. Jurisdiction wählen (z. B. CH).
  2. Registrieren / anmelden und Wahlberechtigung bestätigen.
  3. Initiative unterstützen (signieren) oder abstimmen, sobald die Abstimmung offen ist.
  4. Quittungen aufbewahren; öffentliches Bulletin Board prüfen.

Vollständige DE/FR/IT/RM-Übersetzungen: Phase 2.

READMEREADME.md

dd.freedom.kiwi — Digital Direct Democracy

Real-time, Swiss-faithful direct democracy for any jurisdiction.

  • Production examples: https://dd.freedom.kiwi · https://dd.light.org.nz
  • License: AGPL-3.0-or-later
  • Stack: Next.js 15 · Express/TS · PostgreSQL/PostGIS · Redis SSE

> Legal binding force depends on real-world adoption. The platform facilitates, deliberates, verifies, and exports.

Quick start (< 15 minutes)

cp .env.example .env
docker compose up --build
# Web http://localhost:3000 · API http://localhost:4000/health
./scripts/demo-e2e.sh

Demo logins (password demo-password-change-me):

EmailRole
citizen1@demo.freedom.kiwiCitizen
admin@dd.freedom.kiwiAdmin / steward
steward@freedom.kiwiPlatform steward

What’s implemented

### Phase 1
Initiatives, optional referendums, committees, live signatures, E2E-V sealed ballots, deliberation, CH + DEMO seeds, audit chain.

  • Double majority with half-canton weights + transparent subunit results UI (/results/:electionId)
  • Counter-proposal + deciding question multi-question ballots
  • Receipt verification page (/verify) for ballots & signatures
  • WebAuthn/passkeys ceremony endpoints (progressive; attestation hardening documented)
  • Notifications email/push preference APIs + event hooks
  • Signature/vote maps by sub-jurisdiction (GeoJSON + grid UX)
  • Formal PDF/JSON exports with SHA-256 attestation
  • QR sharing for signature collection; PWA service worker shell
  • Moderation flag/hide + comment rate limits
  • i18n stubs (en/de/fr/it/rm) + contribution note
  • geoBoundaries import script + data dictionary versions
  • Legal privacy / terms / disclaimer templates
  • Metrics at /metrics

Domain configuration

Set APP_URL, ROOT_DOMAIN, NEXT_PUBLIC_API_URL — see `docs/deployment-freedom-kiwi.md`.

Documentation

DocTopic
docs/architecture.mdSystem overview
docs/swiss-mapping.mdSwiss instruments
docs/threat-model.mdResidual risks
docs/data-model.mdSchema
docs/adr/Crypto, identity, open data
CONTRIBUTING.mdHow to contribute
Live help/help/technical on any deployment

Tests

npm run test -w @dd/shared
npm run test -w @dd/api

Complete vs residual

Complete enough for pilots: initiative/referendum flows, double majority UX, counter-proposals, receipts, exports, multi-tenant rulesets, CH/DEMO seeds.

Residual / future: full SimpleWebAuthn attestation verify, production SMTP/VAPID delivery, complete world ADM geometry, liquid democracy, hybrid paper import, national-scale load tests, WCAG audit sign-off, meta-governance dogfooding UI.

Deploy to dd.freedom.kiwidocs/deployment-freedom-kiwi.md

Deploying to https://dd.freedom.kiwi

Same codebase as dd.light.org.nz. Domain is configuration, not a fork.

1. DNS

Point dd.freedom.kiwi (and optional *.dd.freedom.kiwi) A/AAAA records to your host.

2. Environment

APP_URL=https://dd.freedom.kiwi
ROOT_DOMAIN=dd.freedom.kiwi
NEXT_PUBLIC_API_URL=https://dd.freedom.kiwi
DEFAULT_JURISDICTION_CODE=CH
JWT_SECRET=<long-random>
DATABASE_URL=postgres://...
REDIS_URL=redis://...
WEB_PUSH_PUBLIC_KEY=   # optional
WEB_PUSH_PRIVATE_KEY=  # optional
SMTP_HOST=             # optional

Rebuild the web app after changing NEXT_PUBLIC_* (baked at build time).

3. Process

cd /freedom.kiwi
npm install
npm run build -w @dd/shared && npm run build -w @dd/api
NEXT_PUBLIC_API_URL=https://dd.freedom.kiwi npm run build -w @dd/web
npm run db:migrate -w @dd/api
npm run db:seed -w @dd/api
pm2 start ecosystem.config.cjs   # or update env in ecosystem

4. nginx + TLS

Copy sites-available pattern from dd.light.org.nz, replace server_name, run certbot.

5. Helm

helm upgrade --install dd-freedom ./deploy/helm/dd-freedom \
  --set ingress.host=dd.freedom.kiwi \
  --set env.ROOT_DOMAIN=dd.freedom.kiwi \
  --set env.APP_URL=https://dd.freedom.kiwi

6. Smoke test

JURISDICTION=DEMO ./scripts/demo-e2e.sh
curl -s https://dd.freedom.kiwi/health
ADR-0004 — Double majority & counter-proposalsdocs/adr/0004-double-majority-and-counter.md

ADR-0004: Double majority & counter-proposal ballots

## Status
Accepted (Phase 2)

  1. Multi-question sealed ballots ({ v:2, answers }) when counter-proposal is on the ballot; v1 single-choice remains supported.
  2. Double majority computed in shared pure functions; API attributes voters to child jurisdictions via eligibility.
  3. Deciding question: yes = prefer initiative text, no = prefer counter-proposal, after both pass.

## Consequences
Transparent subunit tables; DEMO/CH remain compatible; simple majority instruments ignore subunit gate.

CONTRIBUTINGCONTRIBUTING.md

Contributing to dd.freedom.kiwi

Thank you for helping build open civic infrastructure. This project is AGPL-3.0-or-later.

Development

cp .env.example .env
docker compose up --build
# or local Postgres/Redis + npm run dev

Principles

  1. DFS of democracy — extend instruments via rulesets; don’t hard-code one country’s law.
  2. Residual-risk honesty — never over-claim remote voting or global identity security.
  3. Swiss fidelity — changes to CH defaults need documentation in docs/swiss-mapping.md.
  4. Tests — add unit tests for majority/crypto/rules; keep scripts/demo-e2e.sh green.
  5. i18n — add strings in apps/web/src/lib/i18n.ts (de/fr/it/rm/en) and guide stubs under docs/guides/.

Pull requests

  • Prefer small, reviewable PRs.
  • Update ADRs when changing crypto, identity, or data sources.
  • Do not commit secrets (.env).

Code of conduct

Be respectful. Civic tech attracts diverse political views — debate ideas, not people.

Demo script (demo-e2e.sh)scripts/demo-e2e.sh
#!/usr/bin/env bash
# End-to-end demo against a running API (default http://localhost:4000)
set -euo pipefail
API="${API_URL:-http://localhost:4000}"
JUR="${JURISDICTION:-DEMO}"
HDR=(-H "Content-Type: application/json" -H "X-Jurisdiction: $JUR")

echo "== Health =="
curl -sf "$API/health"
echo

login() {
  local email="$1"
  local out="/tmp/dd-${email}.json"
  curl -sf -X POST "$API/api/auth/login" "${HDR[@]}" \
    -d "{\"email\":\"$email\",\"password\":\"demo-password-change-me\"}" >"$out"
  python3 -c "import json;print(json.load(open('$out'))['token'])"
}

echo "== Login citizens =="
TOKEN1=$(login citizen1@demo.freedom.kiwi)
TOKEN2=$(login citizen2@demo.freedom.kiwi)
TOKEN_ADMIN=$(login steward@freedom.kiwi)
UID2=$(python3 -c "import json;print(json.load(open('/tmp/dd-citizen2@demo.freedom.kiwi.json'))['user']['id'])")

AUTH1=(-H "Authorization: Bearer $TOKEN1")
AUTH_ADMIN=(-H "Authorization: Bearer $TOKEN_ADMIN")

echo "== Create initiative (committee of 2) =="
curl -sf -X POST "$API/api/processes/initiatives" "${HDR[@]}" "${AUTH1[@]}" \
  -d "{\"title\":\"Guarantee public deliberation in law-making $(date +%s)\",\"body\":\"The Demo Republic shall publish draft acts for citizen comment at least 14 days before adoption, with exceptions only for genuine emergencies declared by statute.\",\"justification\":\"Informed consent of the governed requires time to read and discuss.\",\"committee_member_ids\":[\"$UID2\"]}" \
  >/tmp/dd-proc.json
PID=$(python3 -c "import json;print(json.load(open('/tmp/dd-proc.json'))['process']['id'])")
echo "Process: $PID"

echo "== Launch collection =="
curl -sf -X POST "$API/api/processes/$PID/launch" "${HDR[@]}" "${AUTH1[@]}" -d '{}' >/dev/null

echo "== Collect signatures to threshold =="
for email in citizen1@demo.freedom.kiwi citizen2@demo.freedom.kiwi citizen3@demo.freedom.kiwi citizen4@demo.freedom.kiwi citizen5@demo.freedom.kiwi; do
  T=$(login "$email")
  curl -sf -X POST "$API/api/signatures/$PID/sign" "${HDR[@]}" -H "Authorization: Bearer $T" -d '{}' >/tmp/dd-sign.json
  python3 -c "import json;d=json.load(open('/tmp/dd-sign.json'));print(f\"  {d.get('count')}/{d.get('threshold')} submitted={d.get('submitted')}\")"
done

echo "== Admin recommendation + open vote =="
curl -sf -X POST "$API/api/processes/$PID/recommendation" "${HDR[@]}" "${AUTH_ADMIN[@]}" \
  -d '{"recommendation":"The executive recommends adoption.","counter_proposal":"Optional: shorten comment window to 10 days for urgent acts.","counter_proposal_title":"Urgent-acts variant","include_counter_on_ballot":true}' >/dev/null
curl -sf -X POST "$API/api/processes/$PID/open-vote" "${HDR[@]}" "${AUTH_ADMIN[@]}" \
  -d '{"voting_hours":24}' >/tmp/dd-election.json
EID=$(python3 -c "import json;print(json.load(open('/tmp/dd-election.json'))['election']['id'])")
echo "Election: $EID"

echo "== Cast ballots (multi-question: initiative + counter + deciding) =="
for email in citizen1@demo.freedom.kiwi citizen2@demo.freedom.kiwi citizen3@demo.freedom.kiwi; do
  T=$(login "$email")
  curl -sf -X POST "$API/api/voting/elections/$EID/cast" "${HDR[@]}" -H "Authorization: Bearer $T" \
    -d '{"answers":{"initiative":"yes","counter":"yes","deciding":"yes"}}' >/dev/null
  echo "  $email -> yes/yes/initiative"
done
T=$(login citizen4@demo.freedom.kiwi)
curl -sf -X POST "$API/api/voting/elections/$EID/cast" "${HDR[@]}" -H "Authorization: Bearer $T" \
  -d '{"answers":{"initiative":"no","counter":"yes","deciding":"no"}}' >/dev/null
echo "  citizen4 -> no/yes/counter"

echo "== Deliberation comment =="
curl -sf -X POST "$API/api/processes/$PID/comments" "${HDR[@]}" "${AUTH1[@]}" \
  -d '{"body":"This strengthens minority voice through notice and debate."}' >/dev/null

echo "== Tally =="
curl -sf -X POST "$API/api/voting/elections/$EID/tally" "${HDR[@]}" "${AUTH_ADMIN[@]}" -d '{}' | tee /tmp/dd-tally.json
echo

echo "== Export formal report =="
curl -sf "$API/api/processes/$PID/export" >/tmp/dd-export.json
echo "Process ID: $PID"
echo "Election ID: $EID"
echo "Demo complete. Open http://localhost:3000/processes/$PID"

← Administrator help About