diff --git a/docs/auto-create-site-reminder.md b/docs/auto-create-site-reminder.md deleted file mode 100644 index 5bdab25e7..000000000 --- a/docs/auto-create-site-reminder.md +++ /dev/null @@ -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. diff --git a/docs/http-transport.md b/docs/http-transport.md deleted file mode 100644 index 544bc1507..000000000 --- a/docs/http-transport.md +++ /dev/null @@ -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 `. 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 ` 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. diff --git a/docs/neoforge-bootstrap.md b/docs/neoforge-bootstrap.md deleted file mode 100644 index 15a2ec9e8..000000000 --- a/docs/neoforge-bootstrap.md +++ /dev/null @@ -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. diff --git a/docs/offline-uuid-compatibility.md b/docs/offline-uuid-compatibility.md deleted file mode 100644 index 885a81131..000000000 --- a/docs/offline-uuid-compatibility.md +++ /dev/null @@ -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. diff --git a/docs/player-vote-event.md b/docs/player-vote-event.md deleted file mode 100644 index 8dc040a91..000000000 --- a/docs/player-vote-event.md +++ /dev/null @@ -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. diff --git a/docs/proxy-vote-delivery.md b/docs/proxy-vote-delivery.md deleted file mode 100644 index e91a0a559..000000000 --- a/docs/proxy-vote-delivery.md +++ /dev/null @@ -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. diff --git a/docs/reward-recovery.md b/docs/reward-recovery.md deleted file mode 100644 index 7bbb3c928..000000000 --- a/docs/reward-recovery.md +++ /dev/null @@ -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 status -/av OfflineVoteRecovery delivered -/av OfflineVoteRecovery retry -/av OfflineVoteRecovery already-cleared -``` - -- `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 status -/av NameMCLikeRecovery delivered -/av NameMCLikeRecovery retry -``` - -- `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. diff --git a/docs/service-site-rendering.md b/docs/service-site-rendering.md deleted file mode 100644 index a806799e1..000000000 --- a/docs/service-site-rendering.md +++ /dev/null @@ -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. diff --git a/docs/vote-streaks-menu.md b/docs/vote-streaks-menu.md deleted file mode 100644 index f52dec809..000000000 --- a/docs/vote-streaks-menu.md +++ /dev/null @@ -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.