Compare commits

...
Author SHA1 Message Date
Ben 43943ee78b Partially remove migrated development documentation 2026-11-02 09:57:25 -06:00
9 changed files with 0 additions and 721 deletions

No files matched your search

-36
View File
@@ -1,36 +0,0 @@
# Automatic vote-site setup nag
While `AutoCreateVoteSites` remains true, VotingPlugin occasionally prints a brief
console nag:
> After voting-site setup, set AutoCreateVoteSites: false in Config.yml.
There is no new configuration section, command, permission, or player message.
After configuring the intended sites, set the existing `AutoCreateVoteSites`
option to false and reload. This stops the nag while preserving configured sites
and explicit administrative site creation. The nag never changes configuration,
vote matching, totals, or rewards.
The first nag appears on the first active startup checkpoint; another may
appear after each further 48 cumulative active hours while the option remains enabled.
Downtime is excluded. The internal checkpoint keeps reloads/restarts from
resetting progress or repeating a recent nag. Existing installations begin
counting when first using this feature; historical uptime is not guessed.
Earlier experimental reminder-specific keys are ignored and may be removed.
A dedicated bounded background worker checks and saves progress once per minute;
file work never holds the vote-processing timer or adds a synchronous shutdown
wait. The marker is published before logging. A crash between publication and
logging can omit one nag, and a crash can lose uncheckpointed runtime. Invalid
or unverified checkpoints pause the nag without changing voting. Repair and
reload rather than deleting an existing checkpoint to reset history.
The internal checkpoint belongs to one server JVM per plugin data directory.
Plugin instances coordinate retirement and replacement inside that JVM; sharing
the directory across server JVMs is unsupported. No database/config migration or
companion plugin is required.
Automated tests cover timing, persisted markers, reload/retirement, first-write
failure, invalid/missing records and uncertain publication. Live server testing
should separately confirm normal votes, console-only output, reload/stop and
continued explicit admin site creation when automatic creation is disabled.
-54
View File
@@ -1,54 +0,0 @@
# Secure HTTP proxy transport
The `HTTP` bungee method gives every backend an outbound encrypted connection to one HTTPS listener on the proxy. Only the proxy port is opened; backend servers need no inbound transport ports.
## Quick setup
1. On the proxy, set the following in `bungeeconfig.yml`:
```yaml
BungeeMethod: HTTP
HTTP:
Host: '0.0.0.0'
Port: 1297
PublicEndpoint: 'https://proxy.example.com:1297/'
```
2. Allow TCP port `1297` to the proxy. `PublicEndpoint` must resolve directly to this VotingPlugin listener.
3. Restart the proxy and run `/votingpluginproxy httpcode <server>`. The name must exactly identify the intended backend; generate a separate code for each backend.
4. On a backend, set a unique `Server`, enable bungee mode, select `HTTP`, and paste its code into `BungeeSettings.yml`:
```yaml
UseBungeecord: true
Server: lobby-1
BungeeMethod: HTTP
HTTP:
ConnectionCode: 'paste-code-here'
```
5. Restart the backend. Once enrollment succeeds, remove `ConnectionCode` from the configuration. The backend's private identity is stored in its VotingPlugin data folder and is reused automatically. A different nonblank code is treated as an explicit re-enrollment request; a digest lets harmless restarts recognize the already-consumed code without storing its secret token.
Connection codes expire after 15 minutes and can be used only once. Treat a fresh code like a temporary password: transfer it privately and do not publish it in logs, tickets, or chat rooms.
## Security model
- TLS keys and a private certificate authority are generated automatically on the proxy. No shared transport password or public CA setup is required.
- Enrollment pins both the exact proxy certificate and its private authority. Normal traffic trusts only that pinned private authority and keeps HTTPS hostname verification enabled, allowing the proxy leaf certificate to rotate safely without trusting public certificate authorities. It uses a distinct client certificate bound to that backend's canonical `Server` name.
- Proxy and backend leaf certificates renew automatically during a 30-day pre-expiry window. Backend renewal is authenticated by the still-valid mTLS identity, persisted before use, and switches the proxy binding only after the replacement successfully connects; no new connection code is needed.
- Every normal request is authenticated again at the application boundary. A payload cannot claim another backend identity, and redirect following is disabled.
- TLS 1.3 is required and weak protocols are disabled.
- Enrollment tokens are 256-bit random capabilities, single-use, short-lived, and retained by the proxy only as hashes.
- Credentials, keys, pins, and revocation state are written atomically with owner-only permissions where the operating system supports them.
- Request bodies, queues, batches, worker pools, concurrent requests, per-backend polling, and request rates are bounded. Exact paths, methods, and JSON content types are enforced.
- Proxy-to-backend messages stay in an owner-only durable proxy queue until the backend acknowledgement is durably applied. The backend fsyncs three delivery states around each callback: a reserved callback resumes after restart, a completed callback is acknowledged without running twice, and a running callback with an uncertain outcome is neither replayed nor acknowledged. The proxy therefore retains the durable source copy instead of silently losing it. The completed entry is deleted only after a matching authenticated HTTP 200 confirms the proxy removed the queued message.
- A host failure while a callback is running has an inherently ambiguous result because arbitrary reward commands and external plugin effects cannot share a transaction with the transport journal. Such a delivery remains quarantined for operator investigation, avoiding both an automatic duplicate reward and an acknowledgement that could hide a missed reward.
- HTTP vote-party threshold commits journal the exact proxy broadcast and ordered proxy commands alongside backend reward deliveries. Pending proxy effects resume after restart and advance only after execution completes and progress is fsynced. These local effects are at-least-once: a crash after an effect executes but before its progress record is durable can repeat that effect rather than silently skip it. A Velocity command that does not complete within 60 seconds is moved to the durable `VoteParty.QuarantinedProxyEffects` list without automatic retry so it cannot block later vote-party work or execute concurrently with a retry; operators must inspect and resolve quarantined commands.
- Backend-to-proxy messages retain the existing bounded in-process retry semantics. Vote caching remains responsible for application-level durability in that direction.
The listener must terminate TLS itself because client-certificate authentication is part of the protocol. Do not put an HTTP TLS-terminating reverse proxy or CDN in front of it. A TCP/L4 proxy that passes TLS through unchanged is suitable. Internet-facing installations should also use the host firewall or provider firewall for volumetric denial-of-service protection; an application cannot fully absorb a link or TCP flood.
If a backend host or its private credential is compromised, run `/votingpluginproxy httprevoke <server>` on the proxy before generating a new connection code. Revocation takes effect on the next request and permits a replacement identity to enroll under that server name. Keep the proxy's `http` data directory backed up and private: it contains the transport authority.
## Performance
The connector reuses HTTP/1.1 TLS connections, batches messages and acknowledgements, and performs all network, certificate, and delivery-journal work off the game thread. A two-second bounded long poll avoids busy polling while limiting the worst-case delay for a backend message queued just after an idle request began. Callback lanes are single-worker FIFO queues; when their fixed capacity is reached, bounded listener/poller workers apply backpressure instead of dropping an older callback and admitting newer work out of order. All queues and admission pools remain bounded to prevent traffic bursts from causing unbounded memory growth.
-92
View File
@@ -1,92 +0,0 @@
# NeoForge bootstrap status
The single `VotingPlugin.jar` contains an experimental NeoForge 21.1 entry point for Minecraft 1.21.1. Native vote ingress remains disabled. Bukkit and proxy behavior remains on its existing paths.
On server start, the entry point creates `config/votingplugin/`, installs the packaged `Config.yml`, `VoteSites.yml`, and `SpecialRewards.yml` only when missing, reads them, and opens the existing AdvancedCore SQLite user backend in `VotingPlugin.db`. The bootstrap currently accepts `DataStorage: SQLITE` only. Its separate `VotingPlugin_NeoForgeUsers` table stores the player name, all-time/monthly/daily/weekly totals, points, and per-site last-vote timestamps needed by the shared accounting step. Existing UUID-only bootstrap tables are extended in place. The accounting adapter is a storage foundation and is not wired to live vote ingress. The runtime closes the backend and clears queued tasks and online player identities on server stop.
The NeoForge bridge tracks login/logout identity and runs submitted bootstrap tasks on server ticks. Player identity access uses reflection because NeoForge's published universal Maven JAR does not include Minecraft classes on Maven's compile path; these method names were checked against the installed 1.21.1 server JAR.
An internal synthetic vote boundary now supports identity and enabled-site resolution, Bukkit-compatible vote-delay decisions, and the existing shared totals/points policy in one atomic SQLite transaction. `ACCOUNTING_ONLY` is deliberately an internal test/migration scope and returns `ACCOUNTED`, rather than claiming that a production vote completed. `COMPLETE` votes do not change timestamps, totals, or points. They are retained in an ordered SQLite inbox under their stable vote ID until NeoForge implements rewards, offline queues, vote party, broadcasts, streaks, milestones, cooldown actions, post-vote events, and placeholder updates. Repeated submission of the same vote ID is idempotent even after player or site state changes. The inbox admits at most 64 votes per player and 4,096 votes across the runtime. A full inbox returns an explicit capacity result that a future ingress must treat as non-success.
Native Votifier ingress remains disabled. VotifierPlus's hardened packet handling is currently packaged with its Bukkit, BungeeCord, and Velocity plugin runtime rather than as a platform-neutral API that this single VotingPlugin artifact can reuse. Depending on that full plugin would pull unrelated platform code into VotingPlugin, while copying its security-sensitive protocol implementation would create a second implementation.
## Proxy vote delivery
NeoForge can receive real votes from ordinary VotingPlugin installations on BungeeCord and Velocity through the existing encrypted `SOCKETS` transport. Both proxy platforms use the same VotingPlugin proxy runtime and wire protocol. The backend durably retains a COMPLETE vote before sending its delivery acknowledgement; a duplicate stable vote ID is acknowledged idempotently and cannot create another pending occurrence or repeat an already completed vote.
Copy the same `secretkey.key` to the proxy VotingPlugin directory and `config/votingplugin/` on the NeoForge server. Keep this key private. Also create a separate random 32-byte base64 socket authentication key for this backend and copy only that file to the proxy and this NeoForge backend. Do not reuse one backend's socket authentication key for another backend. On the NeoForge backend, configure `BungeeSettings.yml` with values matching the proxy's server map:
```yaml
UseBungeecord: true
BungeeMethod: SOCKETS
Server: neoforge-survival
BungeeServer:
Name: proxy1
Host: 127.0.0.1
Port: 1297
SpigotServer:
Host: 0.0.0.0
Port: 1298
SocketAuthenticationKeyFile: neoforge-socket-auth.key
```
On either BungeeCord or Velocity, use the normal proxy `bungeeconfig.yml` socket setup and map the same backend name to the NeoForge listener:
```yaml
BungeeMethod: SOCKETS
BungeeServer:
Host: 0.0.0.0
Port: 1297
SpigotServers:
neoforge-survival:
Host: 127.0.0.1
Port: 1298
AuthenticationKeyFile: neoforge-socket-auth.key
BungeeManageTotals: false
```
`BungeeServer.Name` must match the proxy's `ProxyServerName`. Authentication covers the complete envelope, sender and destination identities, a timestamp, and a bounded replay nonce before NeoForge admits the vote. Existing Bukkit socket backends without `AuthenticationKeyFile` retain their legacy behavior; NeoForge refuses to start its socket receiver without this per-backend key.
`BungeeManageTotals` must remain `false` for a NeoForge target in this version because NeoForge does not yet import proxy-owned total snapshots. The NeoForge receiver rejects rather than acknowledges incompatible managed-total or batched envelopes. Presence, status probes, stable vote IDs, durable delivery acknowledgements, receipt-release acknowledgements, and restart retries use the existing proxy protocol. A full deferred inbox is not acknowledged, so the proxy retains and retries the occurrence.
NeoForge cannot execute delegated backend broadcasts. Envelopes with `vote.broadcast=true` are rejected before retention, accounting, and acknowledgement; the proxy keeps its durable delivery instead of treating an omitted broadcast as complete. `ProxyBroadcast.Enabled: false` delegates broadcasts to backends and therefore does not disable this requirement. For new votes, configure proxy broadcast routing to exclude the NeoForge backend (for example `ProxyBroadcast.Enabled: true`, `Scope.Mode: ALL_EXCEPT`, and `Scope.Servers: [neoforge]`, using its canonical backend name). Already retained proxy envelopes preserve their original broadcast flag and remain unacknowledged until that side effect can be supported or explicitly reconciled; changing configuration does not rewrite them. Backend-local `VoteBroadcast` settings remain subject to the unsupported-feature limits described below.
Current transport support is:
| Path | NeoForge status |
| --- | --- |
| BungeeCord `SOCKETS` | Supported |
| Velocity `SOCKETS` | Supported |
| Native Votifier packets | Not yet supported |
| Plugin messaging | Not yet supported |
| HTTP | Not yet supported |
| Redis / MQTT / MySQL | Not yet supported |
Only the reward subset described below can complete. Votes requiring unsupported reward or post-vote semantics remain durable and observable instead of being acknowledged as fully processed and lost.
Deferred votes can now be claimed in memory and completed through one SQLite transaction that writes a completion receipt and removes the pending payload. An abandoned claim leaves the durable payload pending, while a committed transaction leaves no pending-without-receipt window. Completion receipts are bounded to 4,096 per player and 262,144 across the runtime. They are not evicted by age because no native ingress acknowledgement can yet prove that a sender has stopped retrying. If either receipt limit is full, completion stops and keeps the original pending payload. A later ingress handshake may add safe receipt release with a bounded post-release tombstone, following the existing proxy delivery pattern. A bounded replay worker now processes at most 16 retained votes per scan. It supports one existing inline `Messages.Player` or `Commands` action per vote. Player messages and console commands run on the NeoForge server tick lane; SQLite scans and completion writes remain on the replay worker. The worker validates the entire reward tree before running any action. A vote stays pending with an observable blocked or waiting result if it requires multiple or unsupported reward actions, `AnySiteRewards`, an offline player message, vote broadcasts, vote party, streaks, milestones, monthly vote limiting, cooldown events, `CloseInventoryOnVote`, or `WaitUntilVoteDelay` processing. An ambiguous external action or completion-write result is durably quarantined and remains fenced across restart for operator reconciliation. Legacy retained rows without an accepted accounting snapshot also remain blocked instead of being recalculated with newer configuration. The packaged defaults enable some unsupported features, so administrators must explicitly disable them before retained votes can complete. Configuration changes currently take effect after a server restart.
Before a supported external action is dispatched, replay durably fences the pending occurrence. After the action succeeds, accounting, the completion receipt, and pending removal commit in one SQLite transaction. A completed vote ID remains fenced across restart and cannot rerun accounting. If the JVM or completion write fails after the pre-dispatch fence, automatic replay stays stopped for operator reconciliation, so it cannot blindly repeat a command or message. A crash between writing that fence and executing the action is indistinguishable from a crash immediately after execution and can therefore leave an action omitted unless an operator retries it; a manual retry after an uncertain outcome can instead duplicate it. The current reward APIs cannot make a Minecraft command and SQLite commit atomic, so this version does not claim exactly-once external side effects.
`SharedVoteProcessor.Operations` was reviewed operation by operation. NeoForge can currently implement its configuration, identity, site, delay, timestamp, and accounting operations. Logging and proxy-only identity fields have no processing effect at this internal non-proxy boundary. Reward delivery, offline queuing, vote party, broadcast, inventory/effects, streak, milestone, cooldown, post-event, and placeholder operations require future platform services. The adapter therefore reuses `SharedVoteIdentity`, `SharedVoteInput`, `SharedVotePolicy`, and `SharedVoteAccounting` without implementing the broad Bukkit production interface with false no-ops.
Validation for this step covers unit startup/shutdown, isolated classloader startup/shutdown from the packaged JAR, and a NeoForge 21.1.211 dedicated server smoke run. The server reached its ready state, initialized the bootstrap, and exited cleanly after `stop`. This does not verify player joins, vote receipt, or rewards.
### Proxy receipt release before deferred completion
A reliable proxy may retire its outbox entry once NeoForge durably owns the
pending occurrence. Receipt release is therefore acknowledged while a vote is
still pending: the inbox transaction records a release-request flag, without
claiming rewards or accounting completed. Pending payloads and their stable IDs
remain durable, bounded and duplicate-fenced across restart. Quarantine and
replay-context changes preserve this flag.
Replay reserves bounded released-receipt capacity before external actions.
Completion atomically writes accounting, a released completion tombstone and
pending removal. The seven-day released-tombstone retention begins at completion,
not at the earlier release request; pending occurrences never expire on that
timer. Capacity exhaustion retains pending work and prevents effect execution.
A release racing an active claim is retried after that claim completes or closes.
Existing pending v1/v2/v3 rows remain readable; release-requested rows use v4
within the existing storage column. No configuration or SQL schema change is
required. The external-action uncertainty/quarantine boundary remains unchanged.
-13
View File
@@ -1,13 +0,0 @@
# Offline UUID compatibility
VotingPlugin intentionally derives offline UUIDs from lowercase player names. This keeps voting data consistent when vote sites send different capitalization. Minecraft can use a different UUID because its offline calculation preserves case. Existing voting records and the UUID algorithm remain unchanged.
With `OnlineMode: false`, `getVotingPluginUser(UUID)` accepts a confidently matched, currently online player's server UUID and uses that player's captured VotingPlugin storage UUID. Identity capture uses the existing presence tracker at join and player-owned refresh boundaries; asynchronous API lookups read captured values without Bukkit player calls or profile/database searches. Delayed login may update only the current owner. Disconnect, replacement, reload and disable invalidate stale captures.
An actual correction logs one WARNING per affected storage identity per plugin runtime, including the player name and both UUIDs. Reload and reconnect do not reset this warning limit. The warning describes third-party compatibility without identifying an unknown caller.
Online mode, unknown/offline/ambiguous identities and already-canonical UUIDs retain their identity. No stored rows are moved, merged, deleted or rewritten by this workaround. A refresh whose entity capture has not completed cannot resolve a new alias yet and conservatively retains the supplied UUID.
The `getVotingPluginUser(UUID, boolean loadName)` overload remains an **exact storage UUID** lookup for record iteration, recovery and administrative operations. It does not apply aliases, regardless of `loadName`. The UUID-and-name and player overloads retain their existing offline name-derived behavior. Constructors and name-based lookups retain their existing contracts. Callers must still follow existing user-storage threading requirements when reading or mutating balances.
This improves compatibility with integrations using the affected UUID-only API. EconomyShopGUI Premium is closed-source and has not been tested with this change; the reporter must confirm its purchase/balance behavior. Deterministic API tests do not establish live Paper/Folia or third-party integration acceptance.
-9
View File
@@ -1,9 +0,0 @@
# PlayerVoteEvent cancellation
`PlayerVoteEvent` implements Bukkit `Cancellable`. Integrations may reject a vote before the MONITOR listener by setting `setCancelled(true)` at an earlier event priority. The native vote consumer then skips totals and reward processing. An uncancelled vote follows its existing processing path; cancelling is not a refund mechanism for work already processed.
The existing cancellation accessors and asynchronous event context are unchanged. Integrations should follow Bukkit priority rules and must not change cancellation state at MONITOR. This correction requires only replacing the VotingPlugin JAR; no configuration or data migration is needed.
For proxy deliveries, cancellation also suppresses backend `PerServerPoints`. A cancelled reliable delivery is acknowledged as handled and retained in the existing replay cache, so transport retries do not re-run a rejected vote. This is a backend decision; it does not undo totals or rewards already processed on the originating proxy or another server. Existing `bungeeVotePluginMessaging` overloads remain available; the additive event-returning dispatch method lets the router observe cancellation.
For votes delayed during a time change, cancellation retires only that queued vote. The same drain continues with later entries, whose existing timestamps and normal reward/accounting behavior are preserved; cancellation does not require another time-change cycle to resume the queue.
-249
View File
@@ -1,249 +0,0 @@
# Proxy vote delivery
Reward-bearing `Vote` and `VoteOnline` messages use an additive, versioned
acknowledgement protocol when both the proxy and backend support it. The backend
advertises `voteDeliveryAckVersion` through presence or status replies. Older
backends ignore the extra fields and retain the existing transport behavior.
Plugin messaging probes each available backend every minute because that
transport does not use backend presence heartbeats.
Before a capable route reports acceptance to the existing vote pipeline, the
proxy writes the exact target server and envelope to
`ProxyVoteDeliveryOutbox.dat`. It retries that entry every ten seconds and after
capability discovery. The backend acknowledges only after its ordered vote lane
reports completion. A spilled message is acknowledged after removal from the
durable backend overflow queue. Before acknowledgement, the backend journals the
completed vote ID without time expiry so a delayed or lost acknowledgement
followed by backend restart does not repeat normal completed processing. The
bounded receipt journal fails closed at its capacity instead of evicting an ID
that may still have a proxy outbox entry. It reserves completion headroom larger
than the bounded ordered lane and separate space for release tombstones. A
release for an already-durable receipt may run independently of a
capacity-blocked vote because that receipt proves the vote effects completed;
unknown releases remain ordered. The proxy then durably transitions the
matching server, vote ID, and subchannel entry into a receipt-release phase. The
backend durably converts its completed receipt into a 24-hour post-release
tombstone and acknowledges that release;
only then does the proxy remove the outbox entry. Lost release messages and
acknowledgements remain retryable across either process restarting, so completed
receipts can be reclaimed without turning the journal bound into a lifetime cap.
If a backend generation stops advertising acknowledgements, the proxy drains
already-journaled entries once through the existing legacy send path and moves
each accepted entry into receipt release. This keeps rolling downgrades from
stranding accepted votes while retaining at-least-once behavior in case the previous capable backend
journaled completion before its acknowledgement was lost. Receipt-release
entries remain until a capable backend confirms retirement.
This is an **at least once delivery guarantee**. Proxy shutdown, restart, a lost
send, or a lost acknowledgement leaves the outbox entry available for retry.
After proxy startup, votes for a backend whose acknowledgement capability has
not been negotiated are journaled and wait for that negotiation instead of
being reported accepted through the legacy path.
The backend vote ID cache and durable completion journal suppress ordinary and
restart-spanning duplicate retries. The bounded post-release tombstone also
fences transport retries that were already in flight when completion was
acknowledged. A backend process crash during reward side
effects, before the completion record is durable, can still lead to a repeated
attempt because reward execution and the receipt cannot be committed atomically.
Stronger exactly once reward execution would require a separate reward API and
storage design.
HTTP retains its existing request recovery and also uses the completion outbox
after capability discovery. The generic outbox covers plugin messaging, Redis,
MQTT, MySQL, sockets, and HTTP. No reward data model or Bukkit vote ordering
changes.
## Velocity configuration reload failures
Normal Velocity soft/full reload validates `bungeeconfig.yml` before changing
channels, cancelling tasks, or replacing the proxy runtime. If the file is
missing, unreadable, or malformed, reload logs a failure and keeps the previous
active configuration and runtime. It does not rewrite the invalid file or
substitute default transport settings. Repair the file and reload again.
This preservation contract is distinct from initial startup default handling.
## Plugin-message pacing and cached batches
Plugin-message handoffs share one dispatcher for each backend, including control
messages, cache replay, capability probes and reliable delivery retries. The
minimum interval is 20 milliseconds after an accepted handoff; a failed carrier
attempt backs off for one second. Overdue scheduler tasks do not catch up by
sending a burst. This paces proxy API handoffs, not measured network arrival.
HTTP, Redis, MQTT, MySQL and socket transports keep their existing send paths.
The dispatcher has bounded queues and a 32-vote in-flight window per backend.
Receipt releases use separate headroom so completed receipts can progress while
that vote window is full. Reliable entries retain their original IDs and durable
outbox ownership until the existing acknowledgement protocol completes. Cache
entries remain owned by the cache when no carrier, send budget or durable outbox
capacity is available. Ready controls and durable replay alternate; a future
control does not block a ready control behind it.
Ordinary controls retain their existing best-effort semantics. Queue admission
is not backend acknowledgement. Effect-bearing control batches, such as vote-party announcements, are admitted
atomically; per-target vote updates retain their own durable admission identities. Full runtime replacement
and a change away from plugin messaging fail closed while admitted effect-bearing
controls are pending; retry the reload after they drain. Effect-free server-name
and status metadata do not block replacement when a backend has no carrier or
has been removed. Normal shutdown remains bounded and warns if effect-bearing
best-effort controls are still pending. Committed plugin-message vote-party
backend targets are separately durable in `VoteParty.PluginMessageRewards` in
the existing vote cache. Their original batch and delivery IDs survive cancelled
tasks, carrier loss, and restart without regenerating the party threshold.
They do not occupy the HTTP pending-reward namespace or change transport choice.
Immediately before each transport attempt, that target is durably fenced. A
confirmed rejected handoff returns to retryable state; an accepted handoff is
retired durably. Failed completion saves retry persistence without repeating the
handoff. A crash during the handoff, or an exception with an uncertain outcome,
leaves a fence for reconciliation rather than automatically risking duplicate
legacy party rewards. Legacy party messages have no backend acknowledgement;
proxy transport acceptance is not proof that backend rewards completed.
Backlogs are bounded to 1,024 deliveries, 256 backends and 64 per backend; full
admission retains the unspent threshold. Other best-effort controls are not made
restart-durable by this change. An admitted control that becomes unencodable after settings
change is reported and retired without a handoff; durable votes remain queued.
Cached replay writes and verifies original batch number, batch size and SQL
source-row identity in the existing JSON journal before delivery. When waiting
for players to be online, only currently eligible player/backend rows receive
new batch positions. Existing durable positions are retained. Partial replay
and restarts preserve those positions instead of producing a new position-one
vote. Legacy SQL recovery associates an emergency JSON entry only with its
positive source-row ID and matching original vote fields; matching-looking
independent votes remain separate. Existing cache files remain readable and no
SQL schema migration is required. SQL cache snapshots must be read completely
before recovery replaces live cache maps or merges emergency journals. A failed
or partial read retains prior ownership instead of treating missing rows as
confirmed absence.
BungeeCord and Velocity JSON cache loads reject malformed, duplicate-field and
trailing-token documents. A failed reload keeps the last loaded in-memory state
and prevents writes until valid data can be loaded; it does not overwrite the
unreadable file with defaults. Repair or restore the cache file before retrying
recovery. Saves publish atomically and durable vote transitions verify the
journal before exposing staged state.
Frame validation uses the actual `writeUTF` encoding and encryption overhead,
including supplementary Unicode, before a platform handoff. Oversized durable
votes are retained for correction rather than discarded. Tests use controlled
clocks, schedulers and synthetic payloads; they do not establish live network
rate-limit or production delivery measurements.
Known effect-free metadata probes expire after 30 seconds of readiness when
no carrier accepts them. Admission rejection emits a bounded warning and does
not abort periodic probes for other backends. Unknown effect-bearing controls
are not expired by that policy.
Plugin-message `VoteUpdate` targets are now retained in the existing durable
outbox instead of occupying control slots while a backend is unavailable. They
retain the original vote ID and use a separate 1,024-entry ready window plus
bounded deferred ownership, preserving the reward-vote entry allowance. Failed handoffs retain ownership across restart;
accepted one-way handoffs use the existing conservative legacy attempt fence.
`VoteUpdate` has no completion acknowledgement protocol, including on backends
that acknowledge reward votes. Transport acceptance does not prove backend
execution. An ambiguous interrupted one-way attempt is not automatically
replayed, to avoid repeating external reward work. Pending plugin-message updates
remain retained when another transport is configured and resume when plugin
messaging is used again. Other transports keep their existing update behavior.
Live extra-target plugin-message broadcasts use the existing durable broadcast
journal. Targets are recorded as forwarded only after actual paced handoff;
unavailable targets stay pending without blocking other backends or later vote
work. Accepted reward target markers survive graceful restart in existing queue
metadata and keep a single-server reward on its original backend. They do not
represent a backend completion receipt or a pending HTTP transmission. No SQL
schema or owner configuration migration is needed.
Restart recovery retains the original reward routing mode and all-server target
plan. Routing edits and backend removal cannot turn an accepted single-server
reward into a new reward on another backend. Previously saved acceptance-only
markers are treated conservatively as a completed occurrence; existing pending
target-specific journals still own any remaining all-server work.
Single-server replay updates the canonical voter-keyed cache completion before
retiring the timed source. Cached delivery reconciles saved single-server
acceptance before sending, including after the player changes backend. A failed
completion write retains recovery ownership for retry.
### Deferred plugin-message updates
The update ready window and durable ownership are separate. An unavailable carrier
or a full ready window can leave a `VoteUpdate` deferred in the same outbox journal.
Normal vote logging, reward processing and proxy forwarding can finish once every
update target is durably retained; they do not wait for that backend to reconnect.
No updates are coalesced by player or discarded: each occurrence keeps its original
vote ID, site, timestamp, totals and backend target.
Replay moves deferred updates into the ready window only when a carrier and pacing
budget are available. Updates for each backend keep their journal order. A backend
without a carrier releases its ready slots while retaining its deferred owners;
other backends can continue. The existing one-way pre-send fence and definite-rejection
handling still govern restart recovery. Deferred admission is not backend execution
or a completion receipt.
The journal retains at most 1,024 ready updates, 4,096 deferred updates and 4,096
ordinary reliable deliveries, within the existing 16 MiB file limit and terminal-record
reserve. Exhausting durable capacity or failing publication leaves the original vote
owned for retry; it is not reported accepted, dropped or silently switched to another
transport. Durable capacity remains a finite backpressure boundary, distinct from an
unavailable backend occupying ready slots.
Deferred/ready transitions are journaled. Existing journals remain readable. Older
proxy JARs that do not recognize these new records must not be used while deferred
work remains: their strict reader rejects the journal rather than ignoring pending
votes. Drain pending deliveries with the updated JAR before downgrading; never delete
the journal to bypass a read error.
### Paired-cache recovery faults
A confirmed SQL deletion immediately removes its owner from live replay even if
paired JSON cleanup cannot publish. Recovery bytes remain protected on disk, and
a failed SQL deletion does not discard their completion metadata.
A complete checked SQL snapshot can prove that a positive-source emergency JSON twin
has lost its original SQL row. Such a confirmed orphan is excluded from replay even
when its verified JSON cleanup fails, preventing expired votes or completed broadcasts
from becoming new work. An incomplete SQL read or an identity mismatch alone does not
prove deletion and must not collapse independent cache records.
Failed batch staging leaves new positions unpublished. Already prepared, clean batches
with stable IDs can continue into the independently durable outbox for a negotiated
reliable plugin-message backend. Dirty entries, new batches, legacy delivery and
unsettled standalone broadcasts stay blocked. Quarantined JSON writes and paired
cleanup remain fail-closed until valid reload recovery; durable batch identity is not
proof that a reward completed.
Reliable cache acceptance remains bound to the outbox's original backend while a
cache save is blocked. Receipt release and retirement wait for matching surviving
cache and timed-source acceptance to be durable, including timed votes with no
voter-cache entry. After restart, cache and timed-queue reconciliation check that
existing outbox ownership before selecting a different backend; a player reconnecting
elsewhere must not create another reward delivery. A failed source write retains the
receipt-release phase for retry instead of retiring its last durable target record.
The same transfer requirement applies when retiring a legacy one-way send fence;
recovery does not resend a possibly accepted legacy reward while its source save fails.
Per-backend update admission is also retained in an unfinished timed source or graceful
shutdown snapshot before its outbox owner retires. A later vote-processing failure
must not cause already handed-off updates to replay after restart.
This does not change the reward receipt protocol or claim universal exactly-once
execution of external commands. Original plugin-message routing and acceptance
markers remain in shutdown snapshots if the transport changes while a vote is
unfinished; changing transport must not erase already accepted effects. Resuming
that vote through another transport does not resend its plugin-message updates.
Those updates retain their original outbox ownership and resume when plugin
messaging is available again; already handed-off updates stay completed. The
original single-server or all-server reward plan remains fixed on a live retry.
Cached reward reconciliation also honors an existing backend owner regardless of
the current transport, before offering the occurrence on a different backend.
When an unfinished plugin-message source completes through another transport,
its original cache owner is reused and its completion is saved before removing
the timed source, so graceful restart cannot revive that pending reward.
A positive-source JSON entry whose SQL row still exists with a different identity
is retained on disk for recovery but excluded from ordinary replay. It is not
silently deleted, merged with that different row, or treated as a new vote. Restore
consistent source data before retrying recovery.
Legacy UTF-8 JSON caches may retain one leading byte-order mark. Additional or
misplaced marks outside string values remain invalid, and accepting the prefix
does not relax duplicate-field, malformed-syntax or complete-document checks.
-137
View File
@@ -1,137 +0,0 @@
# Interrupted offline-vote and NameMC rewards
VotingPlugin retains a pending marker before these asynchronous reward chains
start. If delivery or its final storage acknowledgment fails, the marker prevents
an automatic replay that could repeat money, items, or external commands. A
restart or timeout does not prove that a reward was never delivered.
These console-only commands inspect and reconcile that state. They do not grant
rewards or change vote totals. They require `VotingPlugin.Admin` or the respective
`VotingPlugin.Commands.AdminVote.OfflineVoteRecovery` /
`VotingPlugin.Commands.AdminVote.NameMCLikeRecovery` permission. Use the exact
stored player UUID, rather than a player name.
## Before making a recovery change
1. Stop new reward processing and let accepted work finish. In a network, stop
**all** backends using the same user database, including pending callbacks.
Stopping only the server where the command runs is insufficient.
2. Check console logs and any affected economy, inventory, or external command
system. Establish whether the entire batch was delivered, no effects were
delivered, or delivery was partial/unknown.
3. Request a fresh status preview. Its token expires in five minutes, is
single-use, and is bound to the reviewed pending state. Changes to that state
require another preview.
4. Apply only the resolution supported by the evidence. Do not blindly retry a
partial or unknown delivery. These commands cannot undo external effects or
guarantee exactly-once execution across different systems.
The local in-flight check blocks recovery while this server is delivering the
reward. With shared user storage, the first valid mutation command explains the
precautions and makes **no changes**. After taking those precautions, repeat the
**identical command** from the same console within 30 seconds to confirm. No
extra argument or separate command is needed. The shared-storage check also
applies to the current shared SQLite runtime.
Confirmation is an **operator acknowledgement**, not a distributed lock or
server-verified proof that other backends are idle. Keep other database writers
and vote/reward ingress stopped until reconciliation finishes. The existing
five-minute, single-use recovery preview token remains mandatory; confirmation
cannot bypass its state checks. A different action, UUID, or token needs a fresh
confirmation. Expired confirmations, reloads, and restarts do not authorize a
mutation. At most 128 pending console identities are retained per confirmation
owner; excess requests fail without changing data until capacity is available.
## Offline vote batches
Run from the server console (omit the leading slash there):
```text
/av OfflineVoteRecovery <uuid> status
/av OfflineVoteRecovery <uuid> delivered <token>
/av OfflineVoteRecovery <uuid> retry <token>
/av OfflineVoteRecovery <uuid> already-cleared <token>
```
- `delivered`: after verifying all effects were delivered, remove only the
reviewed original queue prefix and its pending marker in one storage write.
Votes appended after that batch remain queued.
- `retry`: after verifying **no** effects were delivered, clear the pending
marker while retaining the original votes for normal reward processing.
- `already-cleared`: remove only an orphaned marker when audit evidence shows
that the original prefix was already removed. A nonmatching prefix alone is
not evidence of delivery. The command rejects a still-matching prefix.
Legacy queue entries contain site names, not unique vote occurrence IDs. If new
votes from the same sites resemble an old prefix, do not guess which votes to
remove. Retain the quarantine and reconcile using independent evidence.
`clearOfflineVotes()`, `ClearOfflineVotes`, and `ClearOfflineVoteRewards` cannot
replace a queue containing an unresolved batch. The bulk commands check stored
users before clearing any queue. Reconcile the pending batch first; do not
manually delete its database marker. Non-MySQL bulk clears without a pending batch keep
their existing single-invocation behavior. Bulk clears on a network likewise require operational
quiescence; the preflight is not a cross-process lock.
## NameMC claims
```text
/av NameMCLikeRecovery <uuid> status
/av NameMCLikeRecovery <uuid> delivered <token>
/av NameMCLikeRecovery <uuid> retry <token>
```
- `delivered`: confirm the claim before clearing the pending marker, after
verifying that the complete reward was delivered.
- `retry`: clear the pending marker only after verifying no effects were
delivered. An already-completed claim cannot be reset for retry.
Recovery changes are logged with the UUID, operator, and chosen resolution.
Failures retain the available recovery evidence; inspect the console and request
another preview before taking further action. A restored healthy storage worker
or a plugin reload alone never authorizes a replay of an uncertain reward.
### Bulk clear and replay concurrency
The legacy void join/background replay entry point now admits work to the user
storage worker and uses the same durable asynchronous completion protocol as
backend replay. Returning from that void method does not confirm delivery.
Login rewards, the public background-update hook, and dependent generic offline rewards
continue only after confirmed replay. The player-aware completion API captures permissions
on the entity owner and returns dependent storage work to the storage worker. Disabled
replay and absent players remain successful no-ops; failed replay leaves dependent work
unexecuted and logs the pending failure. Non-shared-storage proxy updates also wait
for replay before writing the vote timestamp and acknowledging the ordered update.
Both entry points acquire the same local per-player fence before reading a queue
or its pending marker. Empty queues and
failed replay release that fence; asynchronous delivery retains it through confirmed
storage completion. This fence coordinates one JVM, not multiple backends.
`ClearOfflineVotes` and the vote-queue portion of `ClearOfflineVoteRewards` require
a console confirmation when the authoritative active storage type is MySQL.
SQLite retains its existing single-invocation bulk-clear behavior, including
AdvancedCore’s shared-runtime SQLite adapter. A scan followed by a bulk deletion
cannot safely exclude a concurrently starting remote reward batch.
Use the existing commands (including on a single-backend MySQL installation):
```text
/av ClearOfflineVotes
/av ClearOfflineVoteRewards
```
On MySQL the first invocation makes no changes and asks you to stop other writers
and vote/reward ingress. Repeat the same command from the same console within
30 seconds after doing so. Changing from one bulk-clear command to the other
replaces the previous prompt and cannot confirm it. Confirmation is consumed
before the attempted write; a failure requires a fresh confirmation. No automatic
retry is performed. `ClearOfflineVoteRewards` clears both the vote queue and the
separate generic offline reward queue, only after the vote-queue safeguards pass.
This acknowledgement does not detect or lock other JVMs. Confirming while another
backend can modify the database can race reward delivery or delete concurrent
votes. Local bulk clearing continues to reject active or unresolved batches and
excludes new local replay admissions until the bulk write finishes. No monitor is
held during storage work; competing replay fails or defers rather than waiting.
Individual audited recovery uses the same repeat-command acknowledgement above;
it does not automatically coordinate other JVMs.
-42
View File
@@ -1,42 +0,0 @@
# Voting-service identifiers and displayed names
Bukkit/Paper site-name formatting follows the GitHub VotingPlugin `7.1.1` release baseline
(`39f4717f363beb1d338b353a060630eba6b60cbb`). Configured display names and
fallback site keys enter the existing formatter unchanged. No extra leading or
trailing spaces, invisible separators, or printable delimiter substitutions are
inserted. For example, `&7Site: &e%SiteName%` with `Minecraft Menu` displays
`Site: Minecraft Menu`, and `[%SiteName%]` displays `[Minecraft Menu]`.
Administrator-authored colors, placeholders and intentional whitespace retain
the release behavior. `R&D` remains `R&D` in formatting input.
The exact service identifier remains unchanged for matching, actions, storage
and proxy communication. Compatibility accessors `getDisplayNameForFormatting`,
`getServiceSiteForFormatting` remain available; their historical names do not
promise literal insertion or interpreter isolation.
All Bukkit GUI, book, dialog, broadcast, cooldown, reward and reminder callers
use the original raw getters. Display-only reward/reminder overrides introduced
with the guard are removed. Compatibility accessors simply return those values;
they are not an active formatting path. No AdvancedCore literal-rendering
extension is required.
This deliberately restores the release's formatting trust semantics. The later
external-identifier guards are removed: external or fallback text can once again
participate in the configured color, placeholder or rich-text interpretation.
This change does not claim the previous protection against tokens formed across
placeholder boundaries. Input validation, bounded single-line logging, raw vote
identity, asynchronous reward ordering and delivery bookkeeping are retained.
The separate NeoForge replay adapter was not part of release `7.1.1`; its guarded
expansion and the shared `inertForFormatting` helper are unchanged.
New auto-created sites use the release's original reward-message template,
`&aThanks for voting on %ServiceSite%!`. Existing site files and configured
messages are preserved; no configuration or database migration is required.
Proxy receipt logs sanitize player names using the Minecraft-username sanitizer
and service identifiers using the separate service-site sanitizer. Service dots,
hyphens, spaces and ordinary punctuation survive; controls and unsupported
formatting characters cannot add log lines, and output remains bounded.
These display/log representations do not alter the received vote's identity.
Automated component tests check final visible text and the actual BungeeCord and
Velocity receipt-log paths. Native Minecraft client rendering is not claimed.
-89
View File
@@ -1,89 +0,0 @@
# Vote streak progression menu
`/vote streaks` opens a separate read-only inventory menu showing your existing
vote streaks. The singular `/vote streak`, normal `/vote`, voting-site menus,
and configured reward delivery keep their existing behavior. This feature is
independent of the experimental GUI test commands.
If an enabled custom command already matches `streaks` (including an alias),
that configured command keeps precedence; the new menu does not replace it.
Grant `VotingPlugin.Commands.Vote.Streaks` to players who should use it. This
permission also passes the `/vote` root command gate and grants no other voting
subcommands. The existing `VotingPlugin.Player` command bundle is accepted as
well; its normal root-command access still applies. Console and other-player
inspection are not part of this command.
The three summary icons show the current and best stored daily, weekly and
monthly streaks. The progression row shows enabled `VoteStreaks` definitions
from **SpecialRewards.yml**, including progress-group milestones. Each entry
shows its own current/best progress, threshold, required votes per period and
recurrence. Units are days, weeks or months, not a mixture of vote totals.
A threshold being reached is progress information, not proof that a reward was
delivered. There are no claim buttons. Opening, clicking, navigating and
refreshing never advance a streak, write progression or execute rewards.
The normal voting pipeline remains responsible for reward processing.
## Configuration and navigation
No configuration migration or companion-plugin update is required. Existing
streak definitions are used unchanged; no streak is enabled automatically.
An empty configuration still shows the three stored summary values and a clear
message that no enabled milestones exist.
The new optional title in **GUI.yml** defaults when absent:
```yaml
CHEST:
VoteStreaks:
Name: '&6Vote streaks'
```
Merge `VoteStreaks` into the existing `CHEST` section; do not duplicate it.
Run `/av reload` after changing it. The title accepts legacy colors. Other
configured menus and their titles are unaffected.
Use Previous/Next for five milestones per page, Refresh to read current values,
and Close or Escape to leave. Milestones follow the existing configuration
order. At most 512 enabled milestones are shown, with an explicit truncation
notice; this display limit does not affect reward eligibility or processing.
Legacy summary values and configured progress groups are separate existing
counters and can differ. This menu observes stored values without simulating a
vote or changing missed-period accounting.
## Scheduling and lifecycle
Player UUID and name are captured on the player owner, then resolved together
on the existing persistence worker so configured offline-mode storage identities
remain authoritative. User loading and state reads stay on that worker; inventory
creation and interaction run on the player owner. The menu uses an exact custom
inventory holder, never a title match or a production GUI registration. Click,
drag, shift, hotbar and drop actions cannot move preview items. Unrelated
inventories and their events are left alone.
A newer request, another menu opening, close, disconnect, world change, death,
reload or shutdown invalidates pending callbacks. A delayed read cannot force
a closed menu back open or overwrite a newer menu. Reload blocks admission
while configuration changes and closes only this menu. Reads and menus are
bounded (16 pending data loads and 128 sessions); overload reports a useful
retry message. A data read taking over 30 seconds is discarded when it returns.
There are no animation loops, new worker pools or new persistent files.
This is a native Bukkit inventory interface, without display entities, dialogs,
client mods or a resource pack. It uses the existing entity completion scheduler
for Paper/Folia ownership. Normal shutdown closes views directly only when the
public platform ownership check permits it (including Folia's stopped-region
shutdown owner); it otherwise uses the owner scheduler. External live plugin
unload/reload tools that disable owner scheduling are unsupported: use the
plugin's own reload or a normal server restart. Mocked scheduling and inventory tests are not a live
Spigot/Paper/Folia or visual-client verification claim.
## Testing
On a disposable server with existing streak definitions, grant the permission,
run `/vote streaks`, and check all three summaries and the configured milestones
against the stored streak values. Test multiple pages, Refresh, Escape, two
players, opening another menu during loading, and reload with a menu open.
Try shift-clicks, number keys and dragging: no preview items should move.
Confirm normal `/vote`, `/vote streak` and unrelated inventories still work.
Opening the menu repeatedly must not grant rewards or change any counters.