Files
427e7578-d7bf-49c8-aee9-2dd…/specs/004-poll-data-model/README.md
2026-03-16 23:03:27 +13:00

4.3 KiB

status, created, tags, priority, created_at, depends_on, updated_at, completed_at, completed, transitions
status created tags priority created_at depends_on updated_at completed_at completed transitions
complete 2026-03-16
data
core
high 2026-03-16T07:51:48.793Z
002-p2p-networking
003-user-identity-profiles
2026-03-16T10:01:27.181Z 2026-03-16T10:01:27.181Z 2026-03-16
status at
complete 2026-03-16T10:01:27.181Z

Poll Data Model & Sync

Status: Complete · Priority: High · Created: 2026-03-16 · Tags: data, core

Overview

Define the core data structures for polls, options, votes, and their sync behavior across peers. Polls are owned by the creating user and stored on their device (and cached by participants).

Design

Poll Schema

interface Poll {
  id: string;                    // uuid
  ownerId: string;               // creator's public key
  title: string;
  description: string;
  anonymous: boolean;            // set at creation, immutable
  status: 'draft' | 'open' | 'closed';
  visibility: 'private' | 'link' | 'public'; // who can view via URL
  createdAt: number;
  closedAt?: number;
  options: Option[];
  votes: Vote[];
  roles: RoleAssignment[];
}

interface Option {
  id: string;
  text: string;
  addedBy: string;               // user ID
  addedAt: number;
}

interface Vote {
  optionId: string;
  voterId: string | null;        // null if poll.anonymous === true
  timestamp: number;
  signature: string;             // proves vote authenticity
}

interface RoleAssignment {
  userId: string;
  role: 'viewer' | 'participant' | 'moderator';
}

Anonymity

  • When anonymous: true, votes store voterId: null. The owner's device may transiently see the sender's peer ID during live submission, but the identity is not persisted. Anonymity means: hidden from other participants and public snapshots. It is not cryptographic anonymity from the poll owner.
  • When anonymous: false, voterId is the voter's public key
  • This flag is set at poll creation and cannot be changed after any votes are cast

Visibility

  • private: Only users with assigned roles can access. Poll link requires role assignment.
  • link: Anyone with the poll link can view as a viewer. No directory listing.
  • public: Poll snapshot is published to the server. Discoverable via direct link (no directory listing yet — can be added with future discovery feature).

Roles & Permissions

Action Viewer Participant Moderator Owner
View poll & results
Add options
Vote
Add/remove users
Start/stop poll
Delete poll
  • Owner is implicit (poll.ownerId === userId); not stored in roles[]
  • RoleAssignment entries in poll.roles[] grant viewer, participant, or moderator access
  • Users without a role assignment who connect via link get viewer by default
  • Permission checks happen both client-side (UI gating) and owner-side on message receipt (owner validates before applying any mutation)
  • Role changes are broadcast to all connected peers

Sync Strategy

  • Owner is the source of truth
  • On connect: owner sends full poll snapshot
  • Changes (new vote, new option, role change) are sent as incremental messages
  • Participants cache poll locally for offline viewing

Plan

  • Define TypeScript interfaces for Poll, Option, Vote, RoleAssignment
  • Create poll store (Svelte store + IndexedDB)
  • Implement poll CRUD operations locally
  • Implement sync: snapshot on connect, incremental updates
  • Enforce anonymity invariant (voterId null when anonymous)
  • Create permission check utility (canVote(), canAddOption(), canModerate(), etc.)
  • Implement owner-side validation of incoming messages against roles
  • Implement role change broadcast over PeerJS

Test

  • Poll can be created, read, updated locally
  • Anonymous polls never store voter identity
  • Poll state syncs correctly between two peers
  • Incremental updates apply correctly to cached state
  • Viewer cannot vote or add options
  • Participant can vote and add options
  • Moderator can add/remove users and start/stop poll
  • Only owner can delete
  • Unknown users connecting via link get viewer role