113 lines
5.6 KiB
Markdown
113 lines
5.6 KiB
Markdown
# Voting domain, assurance, and operations
|
|
|
|
## Ownership boundary
|
|
|
|
Voting owns the protocol and authoritative state of a governed ballot:
|
|
|
|
- immutable ballot definitions after opening
|
|
- frozen electorate entries, weights, and provenance
|
|
- eligibility enforcement, vote replacement, and receipts
|
|
- deterministic tally, quorum, threshold, closure, and certification
|
|
- challenge, annulment, command replay, and lifecycle evidence
|
|
- provider-neutral handoff for confidential, secret, or externally certified
|
|
voting
|
|
|
|
Committee owns the body, meeting, agenda, deliberation, and minute context. It
|
|
stores a Voting ballot reference and a verified aggregate result for meeting
|
|
records. Poll owns informal preference, response, and availability collection.
|
|
Decisions owns the resulting formal institutional outcome. Encryption and
|
|
Identity Trust own cryptographic key and device primitives rather than the
|
|
voting ceremony.
|
|
|
|
## Assurance profiles
|
|
|
|
The native `recorded` profile stores each current vote with its elector,
|
|
selection, weight, generation, and receipt hash. Superseded votes remain
|
|
available for authorized reconstruction. It is auditable but **not secret**.
|
|
|
|
The `confidential`, `secret`, and `external_certified` profiles require a
|
|
provider exposed as `voting.provider.<provider-id>`. Voting refuses to open the
|
|
ballot when that provider is unavailable. The provider keeps raw ballots and
|
|
credentials inside its own assurance boundary and returns aggregate counts,
|
|
weighted counts, a result hash, and evidence. GovOPlaN does not claim that a
|
|
provider or deployment satisfies legal or certification requirements merely
|
|
because the adapter contract is implemented.
|
|
Core bounds provider evidence to JSON, 64 items and 64 KiB and rejects fields
|
|
that can carry credentials, private key material, plaintext, or raw
|
|
selections before Voting or Committee can persist the projection.
|
|
|
|
### Bundled local confidential provider
|
|
|
|
`local_confidential` is an interactive reference provider for the
|
|
`confidential` profile. Opening a ballot creates a ballot-scoped Encryption
|
|
vault using the configured `local_aesgcm` provider. Casting stores the elector,
|
|
weight, receipt, and replacement generation as provider metadata while the
|
|
selection payload is stored only as authenticated ciphertext. Finalization
|
|
opens active cast envelopes inside the provider boundary and exports only the
|
|
aggregate and sanitized evidence.
|
|
|
|
Encryption records a context-bound keyed commitment rather than an enumerable
|
|
plaintext digest, so low-entropy choices are not exposed to an offline database
|
|
reader through idempotency metadata.
|
|
|
|
This profile is server-readable. It does not provide voter anonymity,
|
|
unlinkability, coercion resistance, client-held keys, an HSM/KMS claim, or
|
|
external certification. It therefore cannot be selected for `secret` or
|
|
`external_certified` ballots. A provider ballot reference is generated when
|
|
the ballot opens; externally hosted providers may continue to require a
|
|
pre-existing reference.
|
|
|
|
## Lifecycle and concurrency
|
|
|
|
Ballots move through `draft -> open -> closed -> certified`. A closed or
|
|
certified result can be challenged. An administrator can annul an invalid
|
|
ballot from any non-annulled lifecycle state. Opening freezes SHA-256 hashes of
|
|
the definition and electorate. Each state change creates a new immutable
|
|
revision and lifecycle event. Mutating commands require an expected revision
|
|
and an idempotency key; a key replay returns the first outcome and a key reused
|
|
with another payload is rejected.
|
|
|
|
Recorded voters can replace a vote only when the frozen definition allows it.
|
|
The previous generation is retained and excluded from the active tally.
|
|
Lifecycle events contain the receipt hash and generation, not the selection.
|
|
|
|
## Operations and recovery
|
|
|
|
Back up these tables as one consistency unit:
|
|
|
|
- `voting_ballot_revisions`
|
|
- `voting_cast_records`
|
|
- `voting_lifecycle_events`
|
|
- `voting_command_replays`
|
|
- `voting_confidential_ballots`
|
|
- `voting_confidential_casts`
|
|
|
|
A restore is valid only when current revisions are unique per tenant and
|
|
ballot, cast generations are unique per elector, every active cast references
|
|
the current frozen definition hash, result hashes can be recomputed, and event
|
|
sequences are contiguous. Provider-backed restores also require the external
|
|
provider evidence and ballot reference to remain resolvable. Do not reopen a
|
|
restored external ballot merely to compensate for missing provider state.
|
|
For `local_confidential`, restore the Voting and Encryption tables from the
|
|
same consistency point and restore the exact deployment `MASTER_KEY_B64` from
|
|
the approved secret store. A database-only restore or a changed/lost master
|
|
key must fail closed; it cannot reconstruct confidential casts.
|
|
|
|
Before destructive retirement, stop new casts, retain a verified database
|
|
snapshot, export result/certification evidence under the applicable retention
|
|
policy, and reconcile any Committee or Decision references. Horizontal nodes
|
|
must use the shared database; no authoritative Voting state is stored on a
|
|
node-local filesystem.
|
|
|
|
## Security checks
|
|
|
|
- tenant predicates apply to every ballot, cast, event, and replay lookup
|
|
- the authenticated account must match the frozen elector subject
|
|
- native casting fails closed for non-recorded assurance profiles
|
|
- interactive provider casting validates the frozen tenant, electorate,
|
|
definition, options, replacement policy, and time window again
|
|
- raw selections are never returned by list, detail, result, or history APIs
|
|
- provider result keys must exactly match frozen options
|
|
- external results require evidence and cannot exceed the frozen electorate
|
|
- certification and annulment use separate permissions
|