Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ VotingPlugin does not depend on it for startup, joins, routing, rewards, or shut

Maintainers and coding agents should read [AGENTS.md](AGENTS.md). The complete management, inspection, limits, and threat
model reference is [docs/control-management.md](docs/control-management.md).
The scope-first WebUI and legacy single-source editing boundary are documented in
[docs/webui-workspaces.md](docs/webui-workspaces.md).

## Trust and deployment boundary

Expand Down Expand Up @@ -206,10 +208,19 @@ Control and VotingPlugin both enforce fixed quick-setup preset/option schemas; u
than becoming arbitrary YAML writes. The WebUI settings catalog is a static versioned reference over these typed paths,
not a generic setting API.

Opening Settings or changing the selected server automatically reads that server's current configuration. A failed read
clears the editor and exposes an inline retry; successful applies invalidate cached values and read the confirmed state
again. Read actions load only the primary server shown in the configuration header. Preview and apply still cover every server
explicitly included in configuration changes, so one slow secondary node does not delay opening the editor or guided form.
Opening Settings automatically reads current configuration; errors expose inline Retry. The Phase 2 General Settings visual
editor reads **each** selected workspace backend and previews only explicitly changed fields independently per target.
The Phase 3 [Vote Sites visual editor](docs/webui-vote-sites.md) likewise reads each selected backend's main
`VoteSites.yml`, distinguishes mixed/partial/error states, and uses source-preserving per-target add/edit/remove previews.
Successful applies invalidate cached values and read the confirmed state again. Legacy guided forms and raw YAML retain
their explicitly labeled source-server behavior; Vote Sites synchronization remains a separate source-copy operation.
The [Rewards workspace](docs/webui-rewards.md) reads reward scopes from each selected backend, shows mixed
command lists and advanced structure, and supports bounded source-preserving inline Vote Site reward operations.
Connectors with the separate `config.reward-files.v1` capability support bounded inventory and editing of
existing named `Rewards/*.yml` files. Older connectors remain usable but show named files as unsupported; complex
nested rewards and unrecognized metadata retain the Full YAML fallback.
The disposable running-connector and packaged-browser validation boundaries are recorded in
[WebUI integration validation](docs/webui-integration.md).

`plugin.deploy.v1` is additive and exact: older nodes remain connected but are excluded from JAR staging. The WebUI uploads
at most 64 MiB, Control validates the ZIP structure and root `plugin.yml`, and every node re-verifies the SHA-256 before
Expand Down Expand Up @@ -318,3 +329,5 @@ support archive, cloud relay, or remote-support sessions. Current topology and a
nodes automatically re-register after a Control restart and an interrupted change requires a new preview. Redacted
operation history, audit metadata, and configuration snapshots persist without making an ambiguous write resumable.
Manual installation remains supported; VotingPlugin may also opt in to verified download and child-process hosting.
The scope-aware [General Settings editor](docs/webui-general-settings.md) reads each selected backend independently,
previews only explicit boolean edits with per-target revision protection, and keeps Full YAML/Compare available for advanced settings.
22 changes: 22 additions & 0 deletions docs/control-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Control accepts only the intersection with its own allow-list.
| `config.proxy-routing.v1` | `SendVotesToAllServers` and `BlockedServers` on a proxy |
| `config.proxy-files.v1` | Revisioned, redacted management of that proxy's single `bungeeconfig.yml` file |
| `config.files.v1` | Bounded reads/previews/applies for managed Bukkit YAML files |
| `config.reward-files.v1` | Bounded inventory and revisioned editing of existing direct `Rewards/<name>.yml` files on a Bukkit node |
| `config.file-comments.v1` | Preserves Control-managed comment metadata where supported |
| `config.quick-setup.v1` | Typed guided settings and reward/site presets |
| `config.quick-setup.v2` | Vote Party guided settings including revision-safe Enabled round trips |
Expand Down Expand Up @@ -119,6 +120,27 @@ Public/history-only quick-setup selectors carry an internal non-serialized redac
so they cannot become executable requests. The node's acknowledged result exposes only the derived target file, not
proposal actions/messages.

### Named reward files

`config.reward-files.v1` is an additive Bukkit capability for an existing direct
`Rewards/<name>.yml` file, where `<name>` is an ASCII letter or digit followed by at
most 99 ASCII letters, digits, `_`, or `-`. It is independent from ordinary managed
files and from inspections: an older connector remains connected and simply leaves
named files unavailable. The connector advertises it only when its Rewards directory
supports secure pinned-directory staging. Paths, `.yaml`, traversal, symlinks,
case-only aliases, files over 512 KiB, and inventories over 100 eligible files are
rejected; a missing Rewards directory has an empty inventory.

The read-only `reward-file-inventory` inspection takes no filters and returns only
the bounded names list. It requires both accepted `data.inspect.v1` and
`config.reward-files.v1`. A named-file READ, PREVIEW, or APPLY uses the normal
`domain:"file"` envelope with `fileName:"Rewards/<name>.yml"`, requires the exact
accepted capability, and keeps the usual revision-bound retained READ and one-time
approval. APPLY stages and atomically publishes through the pinned directory,
reloads VotingPlugin, verifies the file is active, and rolls back locally if reload
or verification fails. This capability never creates or deletes files and never
grants access outside `Rewards/`.

File content is limited to 512 KiB. Node results mask secret-like YAML paths. A replacement secret may pass through an
authenticated proposal, but Control omits file proposal contents from operation views and never records them in its audit.
`bungeeconfig.yml` is strict UTF-8 safe YAML with duplicate keys, aliases, merge keys, symlinks, and oversized content
Expand Down
94 changes: 94 additions & 0 deletions docs/webui-general-settings.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# General Settings: independent workspace proposals

General Settings uses `selectedTargetIds`, not the inspected server or the legacy tool source. One target uses the same
state model as many targets. Global mode is separate and cannot open this backend editor. Other guided presets, Rewards,
Full YAML, Compare, snapshots and network workflows retain their existing boundaries.

## Source-verified curated settings

All entries are top-level booleans in VotingPlugin `Config.yml`. Source verification used VotingPlugin `origin/master`
at `11f20c39f551c40c55923c6a686b1b6c2a260f83`, its default Config.yml, configuration reader, backend connector and
`VotingPluginMain.reloadFromControl`. This is evidence, not a pinned development dependency.

| YAML path | Documented default | Effect | Runtime action |
| --- | --- | --- | --- |
| `ProcessRewards` | true | Process vote rewards (offline processing still follows VotingPlugin behavior) | Configuration reload |
| `AutoCreateVoteSites` | true | Create sites for newly received services | Configuration reload |
| `ExtraAllSitesCheck` | false | Additional duplicate all-sites reward check | Configuration reload |
| `CountFakeVotes` | true | Include fake votes in points/totals | Configuration reload |
| `DisableNoServiceSiteMessage` | false | Suppress missing service-site and related site warnings | Configuration reload |
| `DisableUpdateChecking` | false | Disable update checks | Configuration reload plus backend restart to reconcile scheduler lifecycle |
| `UseVoteGUIMainCommand` | false | Open the vote GUI from the main vote command | Configuration reload |
| `CloseInventoryOnVote` | true | Close the vote inventory when voting | Configuration reload |
| `ExtraVoteShopCheck` | true | Additional vote shop purchase check | Configuration reload |

The update-check timer is started by `CheckUpdate.startUp` at plugin startup, not by the configuration reload path.
The editor does not restart a backend. A confirmed READ proves persisted configuration, not completion of a restart.
Vote Logging and Vote Party remain accessible through their existing typed/capability-gated tools; they are deliberately
not added to this first visual group. Missing values are never synthesized from defaults or inserted automatically.

## State and capability contract

`configuration-state.js` provides per-target snapshots and explicit dirty overrides. Fields distinguish SAME, MIXED,
MISSING, UNSUPPORTED and ERROR, with supported-subset values and each excluded target visible separately. Mixed controls
have a placeholder, not an implicit true/false choice. Focusing a control is not an edit. A reset removes its override.

The required connector contract is the existing `config.files.v1` on an online Bukkit node. Actual parsed file presence
and boolean type gate each field, instead of guessing support from a version string. Missing keys, wrong types and
anchored/aliased edited values are not writable. Malformed/ambiguous YAML fails closed with sanitized errors. Offline,
unsupported and failed targets remain visible. Applying only a supported subset requires explicit acknowledgement.

## Additive Control API (no connector/protocol change)

1. Existing `/api/v1/configuration/read` reads Config.yml from every eligible selected target in a bounded batch.
2. POST `/api/v1/configuration/general-settings/state` with `readOperationId` and `nodeId` returns nine typed fields,
that target's session and revision. It requires a retained successful READ of that node's Config.yml.
3. POST `/api/v1/configuration/general-settings/preview` adds `overrides` (one to nine allowlisted JSON booleans).
Control patches only changed scalar source spans in that target's retained redacted document and creates an ordinary
FILE PREVIEW bound to the READ revision. Comments, unknown keys, unrelated values and redacted placeholders are not
regenerated. Each target has its own proposal and approval token; no common document is distributed.
4. Existing `/api/v1/configuration/apply` consumes that exact preview/token once. The connector verifies its revision,
writes/reloads and retains its existing rollback semantics. No network-wide atomicity is claimed.

The additive POST routes use existing admin authentication and session CSRF protection, request-size/JSON constraints,
and strict known request fields. The typed view does not expose document content or secrets. Operation history, audit
and journal continue to omit file proposal contents. Existing node file redaction/restoration remains unchanged.

## Approval, retention and confirmation

Target IDs/sessions, every relevant READ revision/state and explicit overrides form the preview signature. Edits, resets,
scope changes and changed revisions invalidate approval. Before APPLY a fresh scope READ checks that signature again;
the connector revision guard closes the remaining race. Navigation only reads and never previews, applies or reloads.

At most eight changed targets can be previewed in one visual batch, within the existing bounded FILE-operation retention.
All workspace targets are still read/displayed. Larger changed sets must be reduced explicitly, not silently truncated.
Successful revision-bound pending previews are protected from ordinary FILE-retention eviction for fifteen minutes,
or until consumed/discarded. `/api/v1/configuration/general-settings/discard-preview` releases the exact abandoned
approval without scheduling a task or modifying a node. The browser disposes approvals on edits/scope changes/re-preview;
disposal is best-effort during connection loss, with bounded retention as the fallback. A stale visual PREVIEW cannot
be retried from Activity with its old document; it requires a fresh READ and dirty-field preview.
There is no unlimited retention or additional durable proposal store. Expired/evicted READ snapshots require a fresh READ.

Per-target previews show exact changes, unchanged targets and exclusions. Apply results retain operation IDs, success,
structured failures, reload and rollback flags. Affected caches are invalidated and fresh confirmed READs rebuild mixed
state after the batch. Failed targets/confirmations remain visible; overrides clear only when confirmed resolved. Retry
is read-only, never an automatic write retry. Workspace/configuration cache is session-bound and not persisted to storage.

## Verification

```shell
node --check src/main/resources/web/app.js
node --check src/main/resources/web/workspace.js
node --check src/main/resources/web/configuration-state.js
node --check src/main/resources/web/general-settings.js
node --test src/test/web/*.test.cjs
mvn -B test
mvn -B clean verify
git diff --check
```

Deterministic tests cover source-preserving per-target proposals, allowlists/capabilities, stale revision/approval,
single-flight and stale-result guards, mixed/partial reads, dirty fields and confirmed partial apply results. Browser
smoke evidence uses actual static assets against controlled API fixtures. A disposable Paper 26.2 server with the current
VotingPlugin connector also exercised the real HTTP connector, Config.yml READ/PREVIEW/APPLY/reload/confirmed READ,
and stale revision rejection. That local gate never points at production configuration.
66 changes: 66 additions & 0 deletions docs/webui-integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Disposable connector and browser validation

The WebUI visual editors use the existing authenticated Control HTTP API and
VotingPlugin connector. A local integration gate was run with one disposable
Paper 26.2 server (Java 25), current VotingPlugin 7.1.2-SNAPSHOT, and the
Control shaded JAR on loopback addresses. It did not use a production node or
configuration. The test server used offline mode solely for this isolated gate;
never expose that setup to a network.

## Running connector path

The external test harness drove real API requests through Control, the HTTP
connector, the Paper plugin, its configuration files, and VotingPlugin reload.
It exercised:

- General Settings `Config.yml` READ → typed state → preview → approval/APPLY →
reload → confirmed READ.
- Vote Site add, edit, remove, and confirmed READ, including an inline reward
left intact by an unrelated site field edit.
- Inline reward create, command append, Money edit, and confirmed READ.
- Named reward-file names-only inventory, READ, command append, Money and item
Amount edits, reload and confirmed READ. The disposable file retained its
custom item metadata and unknown field.
- A named reward deliberately made inactive at reload was reported as
`RELOAD_FAILED`; Control restored the previous file and a fresh READ
confirmed the rollback. No player reward was executed.
- An old expected revision rejected after a later write; an old connector
session's retained READ rejected after a Paper restart (`TARGET_CHANGED`).
- A traversal filename rejected by Control before it could reach the node.

The first running `Config.yml` READ revealed a null comment-metadata entry in
the Bukkit YAML loader. The connector now preserves that blank-line marker
while redacting comments and returns a sanitized structured read error if a genuine I/O failure remains.
The disposable test reproduced the failure before the fix and completed the
above read/apply path after the fix.

This gate used disposable files and no real votes or player rewards. It did not
exercise a running two-backend partial apply, a full proxy, or a failure of the
general VotingPlugin reload routine itself. The named-reward post-reload failure
above and service-layer tests cover restoration; they are not evidence for every
possible plugin-reload failure. These gaps are not described as successful live
integration results.

The local evidence, artifacts, and gate scripts were kept **outside** the Git
repositories and are not part of this change. Credential and admin-token files
were private (mode 0600); never copy their contents into logs, URLs, commits,
or bug reports. The scripts assumed a loopback disposable Paper/Control pair
and must not be pointed at a live server.

## Browser and package gate

The actual `web/*` assets extracted from the clean-verified Control shaded JAR
were served to Chromium behind controlled API fixtures. Separate browser gates
covered Home/server selection, single/multi/global scope, General Settings
mixed/partial preview and stale approval, Vote Sites add/edit/remove and
partial rollback presentation, Rewards mixed commands/append and named item
preview. Desktop widths 1440, 1280, 1024 and 768 px plus mobile 390 px were
checked for page errors, horizontal overflow, visible focus, and usable
navigation. A browser fixture is not substituted for the running connector
gate above.

For this candidate, JavaScript syntax checks, the complete frontend suite,
`mvn -B test`, `mvn -B clean verify`, `git diff --check`, and JAR ZIP
integrity/content checks were run alongside the disposable connector/browser
gates. Keep Maven builds sequential. Visual editors must still require exact
preview and approval; an automatic READ or navigation action must never APPLY.
Loading
Loading