Updated
This commit is contained in:
305
POLL01-SPEC.md
Normal file
305
POLL01-SPEC.md
Normal file
@@ -0,0 +1,305 @@
|
||||
# POLL01 — Homeowner Poll Specification
|
||||
|
||||
**Project:** kane-diagnostics
|
||||
**Version:** 1.0
|
||||
**Prerequisite reading:** README.md, ADDON-SKELETON-SPEC.md, ASSOCIATION-CHANNEL-MODEL.md, CRY01-SPEC.md, FOR-PARTICIPANTS-AND-PROFESSIONALS.md
|
||||
|
||||
**Credential infrastructure dependency:** `cry01` — this addon produces no significant result without the credential stack defined in `CRY01-SPEC.md`. A poll result that is not backed by verified identities, Verifiable Credentials, and an OpenTimestamps proof is not a Civic Infrastructure poll result. It is just a website form.
|
||||
|
||||
---
|
||||
|
||||
## Purpose
|
||||
|
||||
`poll01` enables verified homeowners to conduct formal polls within their association — and to produce poll results that carry independent, cryptographically verifiable weight.
|
||||
|
||||
The foundational diagnostic question this addon answers is: **Do we have the votes?**
|
||||
|
||||
This is the most consequential question in HOA governance. It determines whether a bylaw amendment can pass, whether a Board recall is valid, whether a special assessment has proper authorization, whether quorum was achieved at a meeting. The answer has been contested, manipulated, and litigated at enormous cost to homeowners throughout the history of HOA governance in Illinois.
|
||||
|
||||
`poll01` makes the answer verifiable. Not just persuasive — verifiable. By anyone. Without trusting the Civic Infrastructure.
|
||||
|
||||
---
|
||||
|
||||
## What Makes a Poll01 Result Different
|
||||
|
||||
A Board can conduct a poll by email and report any result it chooses. A management company can hold a meeting and declare quorum without documentation. A homeowner can circulate a petition and claim any number of signatures.
|
||||
|
||||
A `poll01` result is different because every component of it is independently verifiable:
|
||||
|
||||
- **Who was eligible to vote** — derived from VS-02 (unit size) and the SASE participant list. The eligible voter list is published before the poll opens. It cannot be changed after the fact.
|
||||
- **Who voted** — each ballot is a W3C Verifiable Credential issued by the Civic Infrastructure, signed by the operator, identifying the voter by their SASE-verified identity.
|
||||
- **What they voted** — the ballot VC contains the vote, the poll question, and the timestamp.
|
||||
- **When they voted** — each ballot receives an OpenTimestamps proof anchoring it to the Bitcoin blockchain at the moment of submission.
|
||||
- **What the result is** — the poll result is itself a VC, timestamped on Bitcoin at poll close. It contains the complete vote tally, the eligible voter count, the quorum threshold from VS-02, and a determination of whether quorum was achieved.
|
||||
- **That the result has not been altered** — the OpenTimestamps proof on the result document makes post-hoc alteration provably impossible.
|
||||
|
||||
A Board attorney who receives this result and disputes it must explain why the Bitcoin timestamp is wrong, why the W3C VC signatures are invalid, and why the SASE-verified voter identities are fraudulent. That is not a viable legal position.
|
||||
|
||||
---
|
||||
|
||||
## Relationship to VS-02
|
||||
|
||||
VS-02 (Unit Size and Voting Structure) is the prerequisite for any poll. It establishes:
|
||||
|
||||
- The total number of units in the association — which governs quorum achievability
|
||||
- The voting structure — one vote per unit, or percentage-interest weighted
|
||||
- The quorum threshold — what percentage of units must participate for a vote to be valid
|
||||
|
||||
A poll opened without a confirmed VS-02 record for the association is invalid. `poll01` enforces this: if VS-02 has no confirmed public record for the association, the operator cannot open a poll.
|
||||
|
||||
---
|
||||
|
||||
## Poll Types — Skeleton Scope
|
||||
|
||||
The skeleton implements one poll type. Additional types are future versions.
|
||||
|
||||
**Type: Simple Majority — Yes/No**
|
||||
|
||||
A single question with two possible answers. The poll passes if the number of Yes votes exceeds the number of No votes among votes cast, AND quorum is achieved (number of votes cast meets or exceeds the quorum threshold from VS-02).
|
||||
|
||||
Future types: supermajority (two-thirds, three-quarters), ranked choice, approval voting, multi-question ballot.
|
||||
|
||||
---
|
||||
|
||||
## Poll Lifecycle
|
||||
|
||||
```
|
||||
DRAFT → OPEN → CLOSED → CERTIFIED
|
||||
```
|
||||
|
||||
**DRAFT** — operator creates the poll, defines the question, sets the voting period, confirms VS-02 is on record. The eligible voter list is computed from SASE participants and published. The poll is visible to the operator only.
|
||||
|
||||
**OPEN** — operator opens the poll. Eligible participants can submit ballots. The poll question, eligible voter list, quorum threshold, and voting period are all publicly visible. No ballot contents are visible until the poll closes.
|
||||
|
||||
**CLOSED** — the voting period ends. No further ballots are accepted. The operator triggers result computation.
|
||||
|
||||
**CERTIFIED** — the orchestrator computes the result, issues the poll result VC, generates the OpenTimestamps proof, and publishes the certified result. The result is publicly visible and independently verifiable.
|
||||
|
||||
---
|
||||
|
||||
## Ballot Secrecy
|
||||
|
||||
The skeleton does not implement secret ballots. Each ballot is a signed VC that identifies the voter. This is appropriate for HOA governance polls where the Illinois Condominium Property Act requires that individual votes be recorded and available for inspection.
|
||||
|
||||
Secret ballot support is a future version feature, requiring a commitment scheme (the voter commits to their vote cryptographically before the poll closes, then reveals it at close time).
|
||||
|
||||
---
|
||||
|
||||
## Route Structure
|
||||
|
||||
```
|
||||
/poll01/[association-slug]/ — poll index: list of polls (public read)
|
||||
/poll01/[association-slug]/[poll-id]/ — poll detail: question, status, result (public read)
|
||||
/poll01/[association-slug]/[poll-id]/ballot — ballot submission form (participant access)
|
||||
/poll01/[association-slug]/[poll-id]/result — certified result with VC and OTS proof (public read)
|
||||
/poll01/[association-slug]/manage — operator poll management (operator only)
|
||||
/poll01/[association-slug]/manage/new — create a new poll (operator only)
|
||||
/poll01/[association-slug]/manage/[poll-id] — manage a specific poll (operator only)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Access Model
|
||||
|
||||
| Route | Public | Participant | Professional | Operator |
|
||||
|---|---|---|---|---|
|
||||
| Poll index | ✓ read | ✓ read | ✓ read | ✓ read |
|
||||
| Poll detail (open/closed) | ✓ read | ✓ read | ✓ read | ✓ read |
|
||||
| Ballot submission | wall | ✓ submit (if eligible) | — | — |
|
||||
| Certified result | ✓ read | ✓ read | ✓ read | ✓ read |
|
||||
| Operator manage | — | — | — | ✓ only |
|
||||
|
||||
Access state resolution follows the same pattern as all other addons: direct `pgrp_member` queries using `get_observer_hash()`, group IDs from `addon/vs01/config.json`.
|
||||
|
||||
**Eligibility within participant access:** Not all SASE participants are eligible voters in every poll. Eligibility is determined by the eligible voter list computed at poll creation time from VS-02 and the SASE participant list. A participant who is in the SASE group but not in the eligible voter list cannot submit a ballot.
|
||||
|
||||
---
|
||||
|
||||
## Skeleton File Structure
|
||||
|
||||
```
|
||||
hubzilla/addon/poll01/
|
||||
poll01.php — entry point: hooks, routing, access state, CSRF
|
||||
poll01_renderer.php — poll index, poll detail, ballot form, result display
|
||||
poll01_spool.php — POST handler: ballot submission, poll lifecycle transitions
|
||||
poll01.apd — app descriptor
|
||||
mod_poll01.pdl — PDL layout: aside, content, right_aside
|
||||
config.json.template — all config fields documented
|
||||
Widget/
|
||||
Poll01.php — sidebar widget: institutional directory (standard pattern)
|
||||
view/
|
||||
css/
|
||||
poll01.css
|
||||
js/
|
||||
poll01.js
|
||||
contracts/
|
||||
poll-v1.json — the poll definition contract
|
||||
ballot-v1.json — the ballot submission spool envelope contract
|
||||
result-v1.json — the certified poll result contract
|
||||
README.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Config Template Fields
|
||||
|
||||
```json
|
||||
{
|
||||
"_note": "Copy to config.json. Do not commit config.json.",
|
||||
"cry01_credential_endpoint": "REPLACE — orchestrator credential issuance endpoint, e.g. http://10.0.0.105:8700/cry01/credential/issue",
|
||||
"node_token": "REPLACE — shared secret for node-to-orchestrator auth",
|
||||
"receiver_url": "REPLACE — orchestrator spool receiver endpoint",
|
||||
"listings_file": "REPLACE — absolute path to listings.json on the host",
|
||||
"directory_default_tab": "core"
|
||||
}
|
||||
```
|
||||
|
||||
Note: `poll01` does not maintain its own association or group config. It reads from `addon/vs01/config.json` for association data and calls `addon/cry01` for VS-02 quorum data.
|
||||
|
||||
---
|
||||
|
||||
## Poll Definition Contract — `contracts/poll-v1.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"_meta": {
|
||||
"contract": "poll01-poll",
|
||||
"version": "1.0",
|
||||
"purpose": "Poll definition — created by the operator, published at poll open."
|
||||
},
|
||||
"_header": {
|
||||
"poll_id": "Unique identifier, generated at creation. Format: [association-slug]-[timestamp].",
|
||||
"association_slug": "The association this poll is conducted for.",
|
||||
"operator_xchan": "The xchan hash of the operator who created the poll.",
|
||||
"created_at": "ISO 8601 timestamp of poll creation.",
|
||||
"opens_at": "ISO 8601 timestamp of poll open.",
|
||||
"closes_at": "ISO 8601 timestamp of poll close."
|
||||
},
|
||||
"_payload": {
|
||||
"question": "The plain-language poll question. One sentence. No sub-questions.",
|
||||
"poll_type": "simple_majority_yes_no",
|
||||
"vs02_unit_count": "Total unit count from VS-02 public record.",
|
||||
"vs02_quorum_threshold": "Quorum threshold from VS-02 — number of units required.",
|
||||
"eligible_voter_xchans": "Array of xchan hashes of eligible voters at poll creation time.",
|
||||
"eligible_voter_count": "Count of eligible voters."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Ballot Contract — `contracts/ballot-v1.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"_meta": {
|
||||
"contract": "poll01-ballot",
|
||||
"version": "1.0",
|
||||
"purpose": "Ballot submission by an eligible verified participant."
|
||||
},
|
||||
"_header": {
|
||||
"addon": "poll01",
|
||||
"poll_id": "The poll this ballot is cast in.",
|
||||
"association_slug": "The association this poll is conducted for.",
|
||||
"participant_xchan": "The xchan hash of the voting participant.",
|
||||
"submitted_at": "ISO 8601 timestamp.",
|
||||
"node_token_hash": "SHA-256 of the node token — not the token itself."
|
||||
},
|
||||
"_payload": {
|
||||
"vote": "yes or no.",
|
||||
"participant_statement": "Optional — plain-language statement from the voter. Public after poll closes."
|
||||
},
|
||||
"_credentials": {
|
||||
"vc": "W3C Verifiable Credential issued for this ballot. See CRY01-SPEC.md contracts/vc-v1.json.",
|
||||
"ots": "OpenTimestamps proof anchoring this ballot to Bitcoin. See CRY01-SPEC.md contracts/ots-v1.json."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Poll Result Contract — `contracts/result-v1.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"_meta": {
|
||||
"contract": "poll01-result",
|
||||
"version": "1.0",
|
||||
"purpose": "Certified poll result — the authoritative record of the poll outcome."
|
||||
},
|
||||
"_header": {
|
||||
"poll_id": "The poll this result certifies.",
|
||||
"association_slug": "The association this poll was conducted for.",
|
||||
"certified_at": "ISO 8601 timestamp of result certification.",
|
||||
"operator_xchan": "The xchan hash of the operator who certified the result."
|
||||
},
|
||||
"_payload": {
|
||||
"question": "The poll question, reproduced verbatim from the poll definition.",
|
||||
"votes_yes": "Count of Yes votes cast.",
|
||||
"votes_no": "Count of No votes cast.",
|
||||
"votes_cast": "Total votes cast.",
|
||||
"eligible_voter_count": "Total eligible voters at poll open.",
|
||||
"quorum_threshold": "Quorum threshold from VS-02.",
|
||||
"quorum_achieved": "true or false.",
|
||||
"outcome": "passed | failed | no_quorum",
|
||||
"ballot_vcs": "Array of VC identifiers for all ballots cast — the verifiable vote chain.",
|
||||
"result_summary": "Plain-language summary of the result suitable for distribution."
|
||||
},
|
||||
"_credentials": {
|
||||
"vc": "W3C Verifiable Credential issued for this result. See CRY01-SPEC.md contracts/vc-v1.json.",
|
||||
"ots": "OpenTimestamps proof anchoring this result to Bitcoin. See CRY01-SPEC.md contracts/ots-v1.json."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## civicnav.json Entry
|
||||
|
||||
When `poll01` is installed, the operator adds one entry to `addon/vs01/civicnav.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "poll01",
|
||||
"label": "Polls",
|
||||
"module": "poll01",
|
||||
"icon": "check2-square",
|
||||
"enabled": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Build Sequence
|
||||
|
||||
`poll01` depends on `cry01` being installed and the orchestrator credential endpoint being operational. Build sequence:
|
||||
|
||||
1. Confirm `cry01` is installed and the `/cry01/credential/issue` endpoint on the orchestrator responds
|
||||
2. `poll01.php` — entry point, routing, access state, VS-02 guard
|
||||
3. `poll01_renderer.php` — poll index, poll detail, ballot form, certified result display
|
||||
4. `poll01_spool.php` — ballot POST handler, lifecycle transition handler
|
||||
5. Widget, PDL, CSS, JS
|
||||
6. Integration test: create a draft poll, open it, submit a test ballot, close it, certify the result, verify the OTS proof
|
||||
|
||||
---
|
||||
|
||||
## What the Operator Will Decide Before This Is Built
|
||||
|
||||
1. **VS-02 public record requirement** — the spec requires a confirmed VS-02 public record before a poll can be opened. The operator confirms this enforcement is acceptable or proposes an alternative.
|
||||
|
||||
2. **Ballot visibility at close** — the spec makes ballot contents (including voter identity and optional statement) publicly visible after poll close. The operator confirms this is consistent with Illinois Condominium Property Act requirements for vote records.
|
||||
|
||||
3. **Poll result distribution** — after certification, does the poll result automatically publish to the association channel stream, or does the operator trigger publication manually?
|
||||
|
||||
4. **Ballot finality** — once cast, a ballot cannot be changed. The operator confirms this is acceptable.
|
||||
|
||||
---
|
||||
|
||||
## What Not To Do
|
||||
|
||||
- Do not open a poll without a confirmed VS-02 public record.
|
||||
- Do not issue a poll result VC without first calling the `cry01` credential endpoint.
|
||||
- Do not implement secret ballots in this skeleton — that is a future version feature.
|
||||
- Do not exceed 500 lines in any PHP file.
|
||||
- Do not hardcode any association, quorum threshold, or eligible voter list.
|
||||
- Do not push from the host. Gitea is the SSOT.
|
||||
Reference in New Issue
Block a user