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)- Monorepo —
apps/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-tenancy —
jurisdiction_idon citizen data; host header /X-Jurisdiction/ UI selector.
Stack
| Frontend | Next.js 15, React 19, mobile-first CSS, PWA manifest |
| API | Node 20, Express, Zod validation, argon2 passwords, JWT sessions |
| Database | PostgreSQL 16, optional PostGIS geometry, pgcrypto UUIDs |
| Realtime | Redis 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, geometryusers/eligibility— identity + per-jurisdiction verificationprocesses— type ∈ popular_initiative | optional_referendum | mandatory_referendum; status lifecycle; rules_snapshot at create timecommittee_members,signatures(receipt_hash, commitment)discussions/commentselections,ballots,bulletin_board(hash-chained entries)audit_events— append-only hash chain of critical actionsjurisdiction_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
| Type | Trigger | Typical majority | Digital adaptations |
|---|---|---|---|
| Popular initiative | Citizen committee + signatures | Often double (constitutional) | Live counters, receipts, maps (phased), exportable petition package |
| Optional referendum | After act publication | Simple | Short collection window, notifications (phased) |
| Mandatory referendum | Ruleset triggers (constitution, treaties…) | Often double | Admin/system opens vote without signature phase |
Mapping document: repository docs/swiss-mapping.md. Also summarised on About.
E2E-V voting (Phase 1 baseline)
- Election keypair (X25519); public key published.
- Ballot sealed with ephemeral X25519 + AES-GCM; ciphertext on bulletin board.
- Individual receipt = SHA-256(ballot_id ‖ ciphertext ‖ voter_nonce).
- Board entries hash-chained; board commitment over entry hashes.
- Tally ceremony decrypts active (non-superseded) ballots; publishes counts + tally_proof.
- 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
| Path | Role |
|---|---|
apps/api | Express API, auth, instruments, voting crypto, SSE |
apps/web | Next.js App Router UI (citizen + admin) |
packages/shared | Shared types, ruleset Zod schemas |
db/migrations | SQL migrations (PostGIS) |
db/seeds | World hierarchy stubs + Switzerland reference + samples |
deploy/helm | Production 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→ jurisdictionCH; pathdd.freedom.kiwi/chalso supported. - No cross-jurisdiction queries in citizen APIs; stewards may operate globally with audited roles.
Core modules (Phase 1)
- Jurisdiction & ruleset engine — hierarchical ADM model + JSON rulesets
- Identity & eligibility — email/password + WebAuthn hooks; eligibility flags
- Proposal / initiative engine — committee, draft, signatures, threshold
- Optional referendum — post-publication signature collection → vote
- Deliberation — threaded discussion per process
- Voting engine (E2E-V baseline) — encrypted ballots, receipts, bulletin board, verifiable tally
- Realtime — Redis + SSE for signature counters and activity
- 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/curvesabstractions + 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
| Phase | Scope |
|---|---|
| 1 (this MVP) | CH seeded, initiative + optional referendum, basic identity, E2E-V baseline, deliberation, Docker Compose |
| 2 | Full world data imports, hierarchical double majority, counter-proposals, full i18n, public GraphQL/REST |
| 3 | Advanced crypto, liquid democracy options, hybrid paper, meta-governance dogfooding at scale |
Design principles
- Transparency over convenience
- Minority protection (double majority where ruleset requires)
- Progressive identity (document residual Sybil/remote-voting risks)
- Exportable formal reports for real-world political adoption
- 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_id ↔ jurisdiction_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|votestatus: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
| Adversary | Examples | Mitigations (MVP → later) |
|---|---|---|
| Scripted bots / Sybil | Mass fake accounts | Rate limits, email verify, anomaly flags; PoP experiments (Phase 2+) |
| Coercion | Vote buying, family pressure | Re-vote until deadline; document residual remote-voting risk; hybrid paper (Phase 3) |
| Device malware | Keyloggers, ballot alteration | Receipts + bulletin board checks; OS/browser hygiene guidance |
| Compromised server | Altered tallies | E2E-V public proofs; append-only audit chain; key ceremonies |
| Nation-state | Targeted identity compromise | Defense-in-depth, least privilege, optional eID; honest residual-risk docs |
| Insider admin | Quiet rule/tally changes | RBAC, audit events, dual control for key ops |
Residual risks (documented honestly)
- Perfect global eligibility is an open problem. Modules are progressive (self-attestation → rolls → eID → VCs).
- Pure remote voting cannot eliminate coercion or device compromise. Re-voting and hybrid pathways reduce but do not erase risk.
- MVP crypto is practical E2E-V, not a full audited Helios/ElectionGuard deployment. Upgrade path is first-class (ADR-0001).
- Open data electorate estimates may diverge from official rolls — rulesets should prefer official imports when available.
- WebAuthn progressive mode stores credentials and challenges; full assertion/attestation cryptographic verification must be enabled (
@simplewebauthn) before high-stakes reliance. - Notifications may fall back to console logging without SMTP/VAPID — do not treat as guaranteed delivery.
- 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 practice | Digital realization |
|---|---|
| Committee of citizens drafts text | Guided 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 Chancellery | Status → submitted; formal PDF + machine-readable export with cryptographic support proofs |
| Government recommendation / counter-proposal | Recommendation + counter-proposal text/title; include_counter_on_ballot builds multi-question ballot + deciding question (yes=initiative, no=counter) |
| Popular vote; double majority for constitutional | computeQuestionTally + 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 practice | Digital realization |
|---|---|
| After law published, 50,000 signatures / 100 days | optional_referendum process type; CH defaults 50000 / 100 |
| Simple majority of people | majority_type: simple |
Improvements: notifications on new acts, live counters, linked deliberation + educational overlays.
3. Mandatory Referendum (obligatorisches Referendum)
| Swiss practice | Digital realization |
|---|---|
| Automatic vote on constitutional amendments / certain treaties | Process type mandatory_referendum; triggered by admin/rules when act class matches |
| Often double majority | Ruleset 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:
- Per election: generate encryption keypair; public key published; private key held for tally ceremony (split later).
- Voter encrypts choice (
yes/no/abstain) to election public key (NaCl box / X25519). - Server stores only ciphertext on the public bulletin board with timestamp and ballot id.
- Voter receives a receipt = SHA-256(ballot_id || ciphertext || voter_nonce) to confirm “recorded as cast”.
- 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.
- 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
VotingBackendallows 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
EligibilityVerifierwithSelfAttestationVerifierandVoterRollVerifierin 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
- Open https://dd.freedom.kiwi (or your local http://localhost:3000).
- Select your jurisdiction.
- Register or login.
- Claim eligibility (Account → birth year self-attestation in MVP).
Support an initiative
- Open Processes.
- Read the proposal text and deliberation thread.
- Click Sign during the collection window.
- Store your signature receipt.
Vote
- When status is voting, cast an encrypted ballot (Yes / No / Abstain).
- Keep your ballot receipt + nonce.
- 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
- Citizens create & launch an initiative; signatures collect live.
- On threshold → status
submitted. - Admin posts recommendation / optional counter-proposal.
- Admin Open vote (creates election keys + bulletin board).
- After voting window, admin Tally (publishes counts + proof).
- 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)
- Jurisdiction wählen (z. B. CH).
- Registrieren / anmelden und Wahlberechtigung bestätigen.
- Initiative unterstützen (signieren) oder abstimmen, sobald die Abstimmung offen ist.
- 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):
| Role | |
|---|---|
citizen1@demo.freedom.kiwi | Citizen |
admin@dd.freedom.kiwi | Admin / steward |
steward@freedom.kiwi | Platform 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
| Doc | Topic |
|---|---|
| docs/architecture.md | System overview |
| docs/swiss-mapping.md | Swiss instruments |
| docs/threat-model.md | Residual risks |
| docs/data-model.md | Schema |
| docs/adr/ | Crypto, identity, open data |
| CONTRIBUTING.md | How 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)
- Multi-question sealed ballots (
{ v:2, answers }) when counter-proposal is on the ballot; v1 single-choice remains supported. - Double majority computed in shared pure functions; API attributes voters to child jurisdictions via eligibility.
- 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
- DFS of democracy — extend instruments via rulesets; don’t hard-code one country’s law.
- Residual-risk honesty — never over-claim remote voting or global identity security.
- Swiss fidelity — changes to CH defaults need documentation in
docs/swiss-mapping.md. - Tests — add unit tests for majority/crypto/rules; keep
scripts/demo-e2e.shgreen. - i18n — add strings in
apps/web/src/lib/i18n.ts(de/fr/it/rm/en) and guide stubs underdocs/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"