Skip to content

Commit 018f088

Browse files
committed
Merge 2.1 witness publication release
2 parents 0092be3 + cbaf2cf commit 018f088

9 files changed

Lines changed: 649 additions & 55 deletions

File tree

CHANGELOG.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,32 @@
11
# Changelog
22

33
All notable changes to ModNVote are documented in this file.
4+
## [2.1.0] - 2026-04-26
45

6+
### Added
7+
8+
- `/modnvote clone <sourcePollId>` for cloning existing polls into new editable drafts.
9+
- `/modnvote checkpoint <pollId>` for manual witness checkpoint publication.
10+
- `/poll` as a short alias for `/modnvote`.
11+
- External witness publication via configured Discord-compatible webhooks.
12+
- Poll opened witness publication.
13+
- Poll closed witness publication with result summary.
14+
- Automatic integrity checkpoint publication every configured ballot interval.
15+
- Clear first-run config guidance for webhook list formatting.
16+
17+
### Changed
18+
19+
- Poll lifecycle commands can now publish best-effort external witness events.
20+
- Vote submission can now trigger automatic privacy-safe checkpoint publication.
21+
- Config comments clarify how to configure one or more webhook URLs.
22+
23+
### Security / Privacy
24+
25+
- Witness publication does not include player names, UUIDs, IP addresses, proof phrases, participation receipts, or per-player vote content.
26+
- Webhook delivery failures are logged without exposing full webhook URLs and do not affect poll lifecycle or ballot persistence.
527
## [2.0.0] - 2026-04-25
628

29+
730
### Summary
831

932
ModNVote 2.0 replaces the original Yes/No-only plugin with a privacy-first, audit-aware polling system.

CURRENT_STATE.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ Normal admin workflow:
7171
- `/modnvote open <pollId>`
7272
- `/modnvote close <pollId>`
7373
- `/modnvote result <pollId>`
74+
-
75+
Alias:
76+
77+
- `/poll` is a direct alias of `/modnvote` for all commands.
7478

7579
Player workflow:
7680

README.md

Lines changed: 112 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ Developed by [MODN METL LTD](https://modnmetl.com).
1515

1616
---
1717

18-
## ModNVote 2.0 status
18+
## ModNVote 2.x status
1919

2020
ModNVote 2.0 is the active replacement for the legacy 1.x Yes/No-only plugin.
2121

22-
2.0 introduces:
22+
2.x includes:
2323

2424
- GUI-driven poll creation and editing
2525
- Ranked single-winner polls
@@ -30,6 +30,10 @@ ModNVote 2.0 is the active replacement for the legacy 1.x Yes/No-only plugin.
3030
- Tamper-evident audit records
3131
- Java/Bedrock-friendly inventory interfaces
3232
- Mandatory confirmation before ballots are cast
33+
- Poll cloning for repeated or template-based poll setup
34+
- Optional external witness publication via Discord-compatible webhooks
35+
- Automatic and manual integrity checkpoint publication
36+
- `/poll` as a short alias for `/modnvote`
3337

3438
2.0 is a clean install target. Migration from legacy 1.x databases is not currently supported.
3539

@@ -57,6 +61,7 @@ This means:
5761
- Vote content and voter identity are not stored together.
5862
- `/modnvote verify participation` confirms participation without revealing a vote.
5963
- `/modnvote verify ballot` uses a proof phrase as a bearer-token style verification mechanism.
64+
- `/poll` may be used as a shorter alias for `/modnvote`.
6065
- GUI/session state does not directly write ballots or lifecycle state.
6166

6267
---
@@ -98,6 +103,24 @@ This creates a DRAFT Yes/No poll and opens the Poll Builder GUI.
98103

99104
---
100105

106+
## Command alias
107+
108+
All `/modnvote` commands can also be used via the shorter alias:
109+
110+
```text
111+
/poll ...
112+
```
113+
114+
For example:
115+
116+
```text
117+
/poll create ranked_single_winner 5
118+
/poll open <pollId>
119+
/poll vote <pollId>
120+
```
121+
122+
---
123+
101124
## Admin workflow
102125

103126
### Create a ranked poll
@@ -144,6 +167,16 @@ Yes/No polls do not show ranked-only settings such as Max Rankings or Allow Part
144167

145168
This reopens the Poll Builder for an existing DRAFT poll.
146169

170+
### Clone an existing poll
171+
172+
```text
173+
/modnvote clone <sourcePollId>
174+
```
175+
176+
This creates a new DRAFT poll by copying the source poll’s definition and options, then opens the Poll Builder so the clone can be adjusted.
177+
178+
Cloning does not copy ballots, participation records, lifecycle timestamps, proof phrases, or audit history.
179+
147180
### Open a poll for voting
148181

149182
```text
@@ -181,6 +214,64 @@ For ranked polls:
181214

182215
Results are calculated from anonymous ballots only.
183216

217+
### Publish a manual integrity checkpoint
218+
219+
```text
220+
/modnvote checkpoint <pollId>
221+
```
222+
223+
This publishes a privacy-safe witness checkpoint to the configured webhook targets.
224+
225+
Manual checkpoints include poll-level integrity status only. They do not publish player names, UUIDs, IP addresses, proof phrases, participation receipts, or per-player vote content.
226+
227+
---
228+
229+
## Witness publication
230+
231+
ModNVote can publish public witness events to configured Discord-compatible webhooks.
232+
233+
Supported witness events:
234+
235+
- Poll opened
236+
- Poll closed, including a public result summary
237+
- Automatic integrity checkpoints every configured number of accepted ballots
238+
- Manual integrity checkpoints via `/modnvote checkpoint <pollId>`
239+
240+
Webhook delivery is best-effort and non-blocking. A failed webhook does not cancel voting, poll opening, poll closing, or persistence.
241+
242+
Configure webhook publication in `config.yml`:
243+
244+
```yaml
245+
publication:
246+
# External witness publication targets.
247+
#
248+
# Leave this as [] to disable webhook publication:
249+
# discord_webhooks: []
250+
#
251+
# To enable Discord publication, change it to a YAML list:
252+
# discord_webhooks:
253+
# - "https://discord.com/api/webhooks/WEBHOOK_ID/WEBHOOK_TOKEN"
254+
#
255+
# Multiple webhooks are supported:
256+
# discord_webhooks:
257+
# - "https://discord.com/api/webhooks/FIRST_WEBHOOK_ID/FIRST_WEBHOOK_TOKEN"
258+
# - "https://discord.com/api/webhooks/SECOND_WEBHOOK_ID/SECOND_WEBHOOK_TOKEN"
259+
#
260+
# Never commit real webhook URLs to source control.
261+
discord_webhooks: []
262+
publish_poll_opened: true
263+
publish_poll_closed: true
264+
publish_checkpoints: true
265+
266+
integrity:
267+
# Automatic witness checkpoints are published every N accepted ballots
268+
# when publication.publish_checkpoints is true and at least one webhook is configured.
269+
#
270+
# Set to 0 or a negative number to disable automatic interval checkpoints.
271+
checkpoint_interval_ballots: 25
272+
canonicalization_version: 1
273+
```
274+
184275
---
185276
186277
## Verification commands
@@ -213,19 +304,23 @@ Treat ballot proof phrases like bearer tokens: anyone with the phrase can verify
213304

214305
## Admin command reference
215306

307+
All commands below may use either `/modnvote` or `/poll`.
308+
216309
Normal admin-facing commands:
217310

218311
```text
219312
/modnvote guide
220313
/modnvote create ranked_single_winner <optionCount>
221314
/modnvote create yes_no
222315
/modnvote edit <draftPollId>
316+
/modnvote clone <sourcePollId>
223317
/modnvote list
224318
/modnvote show <pollId>
225319
/modnvote delete <pollId>
226320
/modnvote open <pollId>
227321
/modnvote close <pollId>
228322
/modnvote result <pollId>
323+
/modnvote checkpoint <pollId>
229324
```
230325

231326
Player-facing commands:
@@ -244,6 +339,14 @@ Utility commands:
244339
/modnvote reload
245340
```
246341

342+
Short alias examples:
343+
344+
```text
345+
/poll status
346+
/poll guide
347+
/poll vote <pollId>
348+
```
349+
247350
Some older low-level authoring commands may remain callable as recovery tools, but normal poll setup should use the GUI builder.
248351

249352
---
@@ -256,7 +359,7 @@ Common permissions include:
256359

257360
| Permission | Purpose |
258361
|---|---|
259-
| `modnvote.admin.poll.create` | Create, edit, inspect, and manage draft polls |
362+
| `modnvote.admin.poll.create` | Create, clone, edit, inspect, checkpoint, and manage draft polls |
260363
| `modnvote.admin.poll.list` | List polls |
261364
| `modnvote.admin.poll.open` | Open polls for voting |
262365
| `modnvote.admin.poll.close` | Close polls |
@@ -355,14 +458,17 @@ Integrity checks include:
355458
- Ballot hash verification
356459
- Ballot commitment verification
357460
- Audit chain validation
461+
- Optional witness checkpoint publication
358462

359463
The goal is not to identify how someone voted. The goal is to verify that the stored election data remains internally consistent and that a voter can verify their own ballot proof phrase.
360464

465+
Witness publication can optionally publish poll-level lifecycle and checkpoint events to configured webhooks. These events are privacy-safe and do not include voter identity, proof phrases, participation receipts, IP data, or per-player vote content.
466+
361467
---
362468

363469
## Installation
364470

365-
ModNVote 2.0 currently requires a clean install.
471+
ModNVote 2.x requires a clean install (No upgrade path from v1.x).
366472

367473
1. Stop the server.
368474
2. Remove any legacy ModNVote 1.x jar.
@@ -408,36 +514,17 @@ The Java source/target level should remain Java 21 unless explicitly changed.
408514

409515
---
410516

411-
## Release smoke test
412-
413-
Recommended smoke test before release:
414-
415-
```text
416-
/modnvote create ranked_single_winner 3
417-
/modnvote create yes_no
418-
/modnvote edit <draftPollId>
419-
/modnvote open <readyPollId>
420-
/modnvote vote <openPollId>
421-
/modnvote close <openPollId>
422-
/modnvote result <closedPollId>
423-
/modnvote mypolls
424-
/modnvote verify participation <pollId>
425-
/modnvote verify ballot <pollId> <proofPhrase>
426-
```
427-
428-
---
429-
430517
## Roadmap
431518

432519
Potential future 2.x work:
433520

434521
- Multi-winner STV
435522
- Combined elections such as Mayor + Council
436-
- Exportable audit snapshots
437-
- Optional external witness publication
523+
- Exportable signed audit snapshots
438524
- Advanced reporting and dashboards
439525
- Dedicated GUI delete confirmation flow
440526
- Additional admin transparency tooling
527+
- Multi-target witness publication beyond Discord-compatible webhooks
441528

442529
---
443530

src/main/java/com/modnmetl/modnvote/ModNVotePlugin.java

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.modnmetl.modnvote.platform.ModNScheduler;
77
import com.modnmetl.modnvote.platform.PaperPlatformAdapter;
88
import com.modnmetl.modnvote.platform.PlatformAdapter;
9+
import com.modnmetl.modnvote.publication.WitnessPublicationService;
910
import com.modnmetl.modnvote.service.BallotService;
1011
import com.modnmetl.modnvote.service.IntegrityVerificationService;
1112
import com.modnmetl.modnvote.service.PollService;
@@ -42,12 +43,6 @@
4243
import java.util.Objects;
4344
import java.util.logging.Level;
4445

45-
/**
46-
* ModNVote 2.0 plugin bootstrap.
47-
*
48-
* This replaces the 1.x single-round yes/no runtime with a ballot-first
49-
* platform foundation for the 2.x architecture.
50-
*/
5146
public final class ModNVotePlugin extends JavaPlugin {
5247

5348
private ModNScheduler scheduler;
@@ -59,6 +54,7 @@ public final class ModNVotePlugin extends JavaPlugin {
5954
private MessageService messageService;
6055
private IntegrityVerificationService integrityVerificationService;
6156
private ResultService resultService;
57+
private WitnessPublicationService witnessPublicationService;
6258

6359
private VoteSessionManager voteSessionManager;
6460
private YesNoVoteSessionManager yesNoVoteSessionManager;
@@ -102,6 +98,15 @@ public void onEnable() {
10298
databaseManager,
10399
getLogger()
104100
);
101+
102+
this.witnessPublicationService = new WitnessPublicationService(
103+
this,
104+
pollService,
105+
integrityVerificationService,
106+
databaseManager,
107+
getLogger()
108+
);
109+
105110
this.voteSessionManager = new VoteSessionManager(Duration.ofMinutes(10));
106111
this.yesNoVoteSessionManager = new YesNoVoteSessionManager(Duration.ofMinutes(10));
107112
this.ballotSummaryFormatter = new BallotSummaryFormatter();
@@ -110,7 +115,7 @@ public void onEnable() {
110115
this.voteSoundService = new VoteSoundService(this);
111116
this.javaInventoryVoteRenderer = new JavaInventoryVoteRenderer(scheduler, voteGuiText);
112117
this.yesNoInventoryVoteRenderer = new YesNoInventoryVoteRenderer(scheduler, yesNoGuiText);
113-
this.voteSubmissionCoordinator = new VoteSubmissionCoordinator(this, ballotService);
118+
this.voteSubmissionCoordinator = new VoteSubmissionCoordinator(this, ballotService, witnessPublicationService);
114119
this.pollBuilderSessionManager = new PollBuilderSessionManager();
115120
this.pollBuilderInputPromptManager = new PollBuilderInputPromptManager();
116121
this.pollBuilderRenderer = new PollBuilderRenderer(pollService);
@@ -133,9 +138,6 @@ public void onDisable() {
133138
if (yesNoVoteSessionManager != null) {
134139
yesNoVoteSessionManager.clearAllSessions();
135140
}
136-
if (pollBuilderSessionManager != null) {
137-
// Poll builder sessions are in-memory only and intentionally discarded on shutdown.
138-
}
139141

140142
if (databaseManager != null) {
141143
databaseManager.close();
@@ -154,6 +156,7 @@ private void registerCommands() {
154156
ballotService,
155157
integrityVerificationService,
156158
resultService,
159+
witnessPublicationService,
157160
messageService,
158161
voteSessionManager,
159162
yesNoVoteSessionManager,
@@ -288,6 +291,7 @@ public YesNoVoteSessionManager getYesNoVoteSessionManager() {
288291
public JavaInventoryVoteRenderer getJavaInventoryVoteRenderer() {
289292
return Objects.requireNonNull(javaInventoryVoteRenderer, "javaInventoryVoteRenderer");
290293
}
294+
291295
public PollBuilderSessionManager getPollBuilderSessionManager() {
292296
return Objects.requireNonNull(pollBuilderSessionManager, "pollBuilderSessionManager");
293297
}

0 commit comments

Comments
 (0)