Updated
This commit is contained in:
165
CRY01-SPEC.md
165
CRY01-SPEC.md
@@ -1,16 +1,52 @@
|
|||||||
# CRY01 — Value Layer Specification
|
# CRY01 — Value Layer Specification
|
||||||
|
|
||||||
**Project:** kane-diagnostics
|
**Project:** kane-diagnostics
|
||||||
**Version:** 2.0
|
**Version:** 3.0
|
||||||
**Prerequisite reading:** README.md, ADDON-SKELETON-SPEC.md, ASSOCIATION-CHANNEL-MODEL.md, FOR-PARTICIPANTS-AND-PROFESSIONALS.md
|
**Prerequisite reading:** README.md, ADDON-SKELETON-SPEC.md, ASSOCIATION-CHANNEL-MODEL.md, FOR-PARTICIPANTS-AND-PROFESSIONALS.md
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
|
|
||||||
`cry01` is a value layer. It is not a payment processor, an escrow, a marketplace, or an intermediary. The platform is never a party to any transaction. What participants do with the signals recorded here is entirely between them.
|
`cry01` is a value layer and a credential infrastructure layer. It serves two distinct functions that reinforce each other.
|
||||||
|
|
||||||
The Civic Infrastructure contributes one thing: verified identity. Every signal on this platform is backed by a real name, a real address, and a real property — vouched for through the SASE process. That is the product.
|
**As a value layer:** It is not a payment processor, an escrow, a marketplace, or an intermediary. The platform is never a party to any transaction. What participants do with the signals recorded here is entirely between them. The Civic Infrastructure contributes one thing: verified identity. Every signal on this platform is backed by a real name, a real address, and a real property — vouched for through the SASE process.
|
||||||
|
|
||||||
|
**As a credential infrastructure layer:** `cry01` provides the cryptographic underpinning that makes significant diagnostic results independently verifiable — by attorneys, courts, regulators, and the general public — without trusting the Civic Infrastructure at all. This is the foundational service that `poll01` and all future significant diagnostic result addons depend on.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The Credential Stack
|
||||||
|
|
||||||
|
Every significant diagnostic result produced by the Civic Infrastructure — a poll result, a quorum determination, a bylaw amendment vote — is anchored by the following credential stack. Each layer adds independent verifiability.
|
||||||
|
|
||||||
|
### Layer 1 — SASE Identity
|
||||||
|
|
||||||
|
The participant's identity has been verified as a current resident member of a specific association through the SASE process. Real name, real address, real property. Vouched for by the association channel operator. Stored in the Hubzilla `pgrp_member` table with a timestamp.
|
||||||
|
|
||||||
|
This is the foundation. Without it, no other layer has meaning.
|
||||||
|
|
||||||
|
### Layer 2 — Ğ1 Web of Trust Certification
|
||||||
|
|
||||||
|
The SASE process satisfies the requirements for Ğ1 (June) web of trust certification. A verified SASE participant can be certified as a Ğ1 member by the operator, linking their Hubzilla identity to their Duniter public key. This certification is broadcast to the Duniter network and is permanently recorded on the Ğ1 blockchain.
|
||||||
|
|
||||||
|
A Ğ1-certified participant's identity is now vouched for by two independent systems: the Civic Infrastructure's SASE process and the Duniter network's web of trust. These are structurally equivalent vouching mechanisms that arrived at the same conclusion independently.
|
||||||
|
|
||||||
|
### Layer 3 — W3C Verifiable Credential
|
||||||
|
|
||||||
|
Each significant diagnostic result — a ballot cast, a poll result, a SASE admission — is issued as a W3C Verifiable Credential by the Civic Infrastructure. The VC is cryptographically signed by the operator, identifies the subject by their Hubzilla xchan expressed as a `did:web` identifier, and contains the structured claim.
|
||||||
|
|
||||||
|
A VC is machine-readable and independently verifiable by any system that implements the W3C VC standard. It does not require contacting the Civic Infrastructure to verify. An attorney receiving a poll result VC can verify its authenticity offline.
|
||||||
|
|
||||||
|
### Layer 4 — OpenTimestamps
|
||||||
|
|
||||||
|
Every significant diagnostic result is timestamped against the Bitcoin blockchain via OpenTimestamps at the moment it enters the orchestrator spool. The timestamp proof anchors the SHA-256 hash of the result document to a specific Bitcoin block. No one can claim the document was produced after the fact.
|
||||||
|
|
||||||
|
OpenTimestamps requires no account, no token, no Bitcoin transaction fee (proofs are aggregated by public calendar servers), and no understanding of the Civic Infrastructure. Any person with the document and the proof file can verify the timestamp independently using the `ots` command line tool or the OpenTimestamps web verifier.
|
||||||
|
|
||||||
|
### Layer 5 — DANE (DNS-Based Authentication of Named Entities)
|
||||||
|
|
||||||
|
The domain serving the Civic Infrastructure's diagnostic records is bound to its TLS certificate via TLSA records in DNS, secured by DNSSEC. A document served from `directory.diagnostics.kane-il.us` cannot be impersonated — the DNS record cryptographically identifies the server. This prevents man-in-the-middle attacks and domain spoofing when attorneys or regulators retrieve diagnostic records.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -67,11 +103,11 @@ Automated certification signing is a future version feature, pending operator de
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Three Functions
|
## Four Functions
|
||||||
|
|
||||||
### 1. Display
|
### 1. Display
|
||||||
|
|
||||||
The operator's verified Ğ1 balance is shown publicly on the association channel. This is read-only from the Duniter blockchain. No keys are involved. No custody changes. The balance is cached locally and refreshed on a schedule defined in `config.json`.
|
The operator's verified Ğ1 balance is shown publicly on the association channel. This is read-only from the Duniter blockchain via the local Duniter mirror node on the orchestrator. No keys are involved. No custody changes. The balance is cached locally and refreshed on a schedule defined in `config.json`.
|
||||||
|
|
||||||
If the Duniter node is unreachable, the display renders the last cached balance with a staleness timestamp. It does not fail silently. It does not show zero.
|
If the Duniter node is unreachable, the display renders the last cached balance with a staleness timestamp. It does not fail silently. It does not show zero.
|
||||||
|
|
||||||
@@ -79,19 +115,22 @@ If the Duniter node is unreachable, the display renders the last cached balance
|
|||||||
|
|
||||||
A SASE-verified participant registers a capacity signal against their verified identity. The signal describes what they are offering or seeking, denominated in Ğ1. The platform records the signal. No transaction occurs at registration time.
|
A SASE-verified participant registers a capacity signal against their verified identity. The signal describes what they are offering or seeking, denominated in Ğ1. The platform records the signal. No transaction occurs at registration time.
|
||||||
|
|
||||||
A signal contains:
|
At signal registration, the orchestrator:
|
||||||
- The participant's Ğ1 public key (self-reported, linked to their SASE xchan)
|
- Accepts the signal via the spool receiver
|
||||||
- A plain-language description of the capacity being offered or sought
|
- Issues a W3C Verifiable Credential for the signal
|
||||||
- A Ğ1 denomination (the amount the participant considers fair exchange)
|
- Generates an OpenTimestamps proof anchoring the signal to the Bitcoin blockchain
|
||||||
- A duration (how long the capacity is available)
|
- Stores the signal, VC, and OTS proof together in the spool
|
||||||
- An optional service category tag from the operator-defined list
|
|
||||||
|
|
||||||
Signals are visible to all SASE participants on the association channel. They are not visible to public (unauthenticated) visitors.
|
|
||||||
|
|
||||||
### 3. Ğ1 Certification Bridge
|
### 3. Ğ1 Certification Bridge
|
||||||
|
|
||||||
The operator sees a candidate list of all SASE participants who have registered a Ğ1 public key. For each candidate, the addon builds a Duniter-compatible attestation document. The operator reviews it and certifies manually via their Ğ1 wallet. The platform does not sign anything automatically.
|
The operator sees a candidate list of all SASE participants who have registered a Ğ1 public key. For each candidate, the addon builds a Duniter-compatible attestation document. The operator reviews it and certifies manually via their Ğ1 wallet. The platform does not sign anything automatically.
|
||||||
|
|
||||||
|
### 4. Credential Service (shared infrastructure)
|
||||||
|
|
||||||
|
`cry01` exposes a credential issuance endpoint on the orchestrator that other addons (`poll01` and future significant diagnostic result addons) call to issue VCs and OTS proofs for their own results. This endpoint is internal — accessible only over the Wireguard tunnel — and authenticated with the node token.
|
||||||
|
|
||||||
|
This is the function that makes `cry01` a dependency of `poll01`. A poll result is not just a count of votes. It is a VC issued by the Civic Infrastructure, timestamped on Bitcoin, carried by verified identities that have been independently certified through SASE and optionally through Ğ1.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Route Structure
|
## Route Structure
|
||||||
@@ -119,6 +158,16 @@ Access state resolution follows the same pattern as `vs01`, `dsc01`, and `scn01`
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Orchestrator Components
|
||||||
|
|
||||||
|
The orchestrator node (`orchestrator1`) runs two services that support `cry01` and all future credential-dependent addons:
|
||||||
|
|
||||||
|
**`duniter-mirror.service`** — Duniter v2 mirror node, synced to Ğ1 mainnet. Provides the RPC endpoint at `ws://127.0.0.1:9944` for balance queries and attestation document construction. Accessible to the Hubzilla node via Wireguard tunnel at `http://10.0.0.105:9944`.
|
||||||
|
|
||||||
|
**`civic-orchestrator.service`** — FastAPI spool receiver. Accepts signal POST requests from `cry01_spool.php`, validates the node token, issues VCs, generates OTS proofs, and writes the complete spool entry. Listens on `http://10.0.0.105:8700`. The credential issuance endpoint (`/cry01/credential/issue`) is the shared service that `poll01` and future addons call.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Skeleton File Structure
|
## Skeleton File Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -140,6 +189,8 @@ hubzilla/addon/cry01/
|
|||||||
contracts/
|
contracts/
|
||||||
signal-v1.json — the capacity signal spool envelope contract
|
signal-v1.json — the capacity signal spool envelope contract
|
||||||
attestation-v1.json — the Ğ1 attestation document contract
|
attestation-v1.json — the Ğ1 attestation document contract
|
||||||
|
vc-v1.json — the W3C Verifiable Credential envelope contract
|
||||||
|
ots-v1.json — the OpenTimestamps proof envelope contract
|
||||||
README.md
|
README.md
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -152,10 +203,13 @@ hubzilla/addon/cry01/
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"_note": "Copy to config.json. Do not commit config.json — it contains installation-specific values.",
|
"_note": "Copy to config.json. Do not commit config.json — it contains installation-specific values.",
|
||||||
"g1_rpc_endpoint": "REPLACE — local Duniter node endpoint, e.g. http://localhost:10901",
|
"g1_rpc_endpoint": "REPLACE — Duniter node RPC over Wireguard, e.g. http://10.0.0.105:9944",
|
||||||
|
"orchestrator_url": "REPLACE — spool receiver base URL, e.g. http://10.0.0.105:8700",
|
||||||
"operator_g1_pubkey": "REPLACE — operator Ğ1 public key (not private key — never store private keys)",
|
"operator_g1_pubkey": "REPLACE — operator Ğ1 public key (not private key — never store private keys)",
|
||||||
|
"operator_did": "REPLACE — operator did:web identifier, e.g. did:web:directory.diagnostics.kane-il.us",
|
||||||
"cache_file": "REPLACE — absolute path to balance cache JSON on the host",
|
"cache_file": "REPLACE — absolute path to balance cache JSON on the host",
|
||||||
"cache_max_age_seconds": 3600,
|
"cache_max_age_seconds": 3600,
|
||||||
|
"ots_calendar_url": "https://alice.btc.calendar.opentimestamps.org",
|
||||||
"signal_categories": [
|
"signal_categories": [
|
||||||
{
|
{
|
||||||
"_note": "Operator-defined capacity categories. Participants tag their signals with these.",
|
"_note": "Operator-defined capacity categories. Participants tag their signals with these.",
|
||||||
@@ -179,7 +233,7 @@ hubzilla/addon/cry01/
|
|||||||
"description": "General availability — labor, assistance, or presence."
|
"description": "General availability — labor, assistance, or presence."
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"receiver_url": "REPLACE — orchestrator receiver endpoint",
|
"receiver_url": "REPLACE — orchestrator spool receiver endpoint",
|
||||||
"node_token": "REPLACE — shared secret for node-to-orchestrator auth",
|
"node_token": "REPLACE — shared secret for node-to-orchestrator auth",
|
||||||
"listings_file": "REPLACE — absolute path to listings.json on the host",
|
"listings_file": "REPLACE — absolute path to listings.json on the host",
|
||||||
"directory_default_tab": "core"
|
"directory_default_tab": "core"
|
||||||
@@ -188,6 +242,66 @@ hubzilla/addon/cry01/
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## W3C Verifiable Credential Contract — `contracts/vc-v1.json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"_meta": {
|
||||||
|
"contract": "cry01-vc",
|
||||||
|
"version": "1.0",
|
||||||
|
"purpose": "W3C Verifiable Credential envelope for Civic Infrastructure diagnostic results."
|
||||||
|
},
|
||||||
|
"_note": "This contract describes the VC structure. The actual VC is a JSON-LD document signed by the operator. The _payload field contains the claim specific to the issuing addon (signal, ballot, poll result, etc.).",
|
||||||
|
"vc_structure": {
|
||||||
|
"@context": ["https://www.w3.org/2018/credentials/v1"],
|
||||||
|
"type": ["VerifiableCredential"],
|
||||||
|
"issuer": "did:web:directory.diagnostics.kane-il.us",
|
||||||
|
"issuanceDate": "ISO 8601 timestamp",
|
||||||
|
"credentialSubject": {
|
||||||
|
"id": "did:web:directory.diagnostics.kane-il.us/channel/{xchan}",
|
||||||
|
"association": "association slug",
|
||||||
|
"claim_type": "signal | ballot | poll_result | sase_admission",
|
||||||
|
"claim": "addon-specific structured claim — see issuing addon spec"
|
||||||
|
},
|
||||||
|
"proof": {
|
||||||
|
"type": "Ed25519Signature2020",
|
||||||
|
"created": "ISO 8601 timestamp",
|
||||||
|
"verificationMethod": "did:web:directory.diagnostics.kane-il.us#operator-key",
|
||||||
|
"proofValue": "base58-encoded signature"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## OpenTimestamps Contract — `contracts/ots-v1.json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"_meta": {
|
||||||
|
"contract": "cry01-ots",
|
||||||
|
"version": "1.0",
|
||||||
|
"purpose": "OpenTimestamps proof envelope anchoring a diagnostic result to the Bitcoin blockchain."
|
||||||
|
},
|
||||||
|
"_header": {
|
||||||
|
"document_hash": "SHA-256 hash of the document being timestamped.",
|
||||||
|
"hash_algorithm": "sha256",
|
||||||
|
"timestamped_at": "ISO 8601 timestamp of OTS submission to calendar server.",
|
||||||
|
"calendar_url": "The OTS calendar server used.",
|
||||||
|
"addon": "The addon that generated this document (cry01, poll01, etc.)",
|
||||||
|
"association_slug": "The association this document pertains to."
|
||||||
|
},
|
||||||
|
"_payload": {
|
||||||
|
"ots_proof_base64": "Base64-encoded .ots proof file. Verifiable offline with the ots tool.",
|
||||||
|
"bitcoin_block": "Bitcoin block number in which the timestamp was confirmed. Null until confirmed.",
|
||||||
|
"confirmed_at": "ISO 8601 timestamp of Bitcoin confirmation. Null until confirmed."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Capacity Signal Contract — `contracts/signal-v1.json`
|
## Capacity Signal Contract — `contracts/signal-v1.json`
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -211,6 +325,10 @@ hubzilla/addon/cry01/
|
|||||||
"duration_days": "How many days this capacity is available from submission date.",
|
"duration_days": "How many days this capacity is available from submission date.",
|
||||||
"category_id": "One of the operator-defined signal_categories ids from config.json.",
|
"category_id": "One of the operator-defined signal_categories ids from config.json.",
|
||||||
"direction": "offer or seek — whether the participant is providing or requesting this capacity."
|
"direction": "offer or seek — whether the participant is providing or requesting this capacity."
|
||||||
|
},
|
||||||
|
"_credentials": {
|
||||||
|
"vc": "The W3C Verifiable Credential issued for this signal. See contracts/vc-v1.json.",
|
||||||
|
"ots": "The OpenTimestamps proof for this signal. See contracts/ots-v1.json."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -261,20 +379,6 @@ No changes to `CivicNav.php` or any PDL are required.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## What the Operator Will Decide Before This Is Built
|
|
||||||
|
|
||||||
1. **Duniter node setup** — the operator must run a local Duniter node. Duniter is lightweight and can run on the existing Hubzilla host. The operator confirms the node is running and the RPC endpoint is accessible before any code is written.
|
|
||||||
|
|
||||||
2. **Operator Ğ1 keypair** — the operator needs a Ğ1 keypair. Cesium wallet is the standard tool. The public key goes in `config.json`. The private key never leaves the operator's wallet and is never stored on the server.
|
|
||||||
|
|
||||||
3. **Signal category list** — the four categories in the config template (storage, transport, skill, time) are proposals. The operator confirms, modifies, or extends this list before the skeleton is built.
|
|
||||||
|
|
||||||
4. **Signal visibility** — signals are visible to SASE participants by default. The operator decides whether signals should also be visible to civic professionals, or restricted further.
|
|
||||||
|
|
||||||
5. **Signal duration policy** — when a signal's duration expires, does it disappear automatically or remain marked as expired? The operator decides.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Build Sequence Within cry01
|
## Build Sequence Within cry01
|
||||||
|
|
||||||
1. `cry01_chain.php` — Duniter RPC stubs, cache read/write
|
1. `cry01_chain.php` — Duniter RPC stubs, cache read/write
|
||||||
@@ -282,7 +386,8 @@ No changes to `CivicNav.php` or any PDL are required.
|
|||||||
3. `cry01_renderer.php` — balance display, signal board, signal form, Ğ1 candidate list
|
3. `cry01_renderer.php` — balance display, signal board, signal form, Ğ1 candidate list
|
||||||
4. `cry01_spool.php` — signal POST handler
|
4. `cry01_spool.php` — signal POST handler
|
||||||
5. Widget, PDL, CSS, JS
|
5. Widget, PDL, CSS, JS
|
||||||
6. Ğ1 attestation builder — last, after all other functions confirmed working
|
6. Credential issuance — VC and OTS — integrated into spool receiver on orchestrator
|
||||||
|
7. Ğ1 attestation builder — last, after all other functions confirmed working
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
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