Skip to content

Add Control transport diagnostics and safe proxy method switching - #1589

Merged
BenCodez merged 32 commits into
masterfrom
codex/control-transport-diagnostics
Aug 30, 2026
Merged

BenCodez merged 32 commits into
masterfrom
codex/control-transport-diagnostics

Conversation

@BenCodez

Copy link
Copy Markdown
Owner

Summary

  • add a correlated proxy-to-backend status round trip that never submits votes or rewards
  • expose actionable timeout, player-required, unknown-backend, and transport-unavailable failures
  • add coordinated proxy-method quick setup with per-node Redis, MQTT, MySQL, sockets, and plugin-messaging preflight
  • atomically persist the proxy method and defer the full proxy runtime replacement until Control acknowledges the durable result
  • preserve recovery behavior when an apply result is retried after a connector/process interruption

Safety

  • no vote payloads, totals, commands, or reward paths are used by the diagnostic
  • method changes still use preview + one-time approval
  • proxy restart happens only after the operation result is safely acknowledged
  • backend and proxy configuration writes retain stale-revision checks, backups, and rollback behavior

Tests

  • correlated status response and wrong-backend rejection
  • plugin-messaging online-player requirement
  • request correlation on the wire
  • backend method preflight validation

Depends on #1588. Paired WebUI: BenCodez/VotingPlugin-Control#7.

@BenCodez
BenCodez changed the base branch from codex/control-votesites-sync to master August 29, 2026 20:38
@BenCodez BenCodez closed this Aug 29, 2026
@BenCodez BenCodez reopened this Aug 29, 2026
@BenCodez
BenCodez force-pushed the codex/control-transport-diagnostics branch from ac2c56c to 6c646e9 Compare August 29, 2026 20:41
@BenCodez
BenCodez changed the base branch from master to codex/control-votesites-sync August 29, 2026 20:47
@BenCodez
BenCodez marked this pull request as ready for review August 29, 2026 21:04
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for security reviews. Please try again later.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 29, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-08-30T01:19:19.963052Z 52bc7df Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9df6143f2d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

boolean replaceRuntime = requiresRuntimeReplacement(result) && runtimeReplacement != null;
synchronized (operationLifecycle) {
drained = completedTasks.isEmpty();
if (replaceRuntime) deferredReplacement = runtimeReplacement;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Complete the control operation before starting the reload

When a proxy-method result is acknowledged, this queues runtimeReplacement, which finishCycle() runs synchronously at line 315 before the whenComplete block completes operationDone at lines 295–301. The resulting reloadCore(true) calls prepareForRuntimeReplacement(), whose connector shutdown waits on that same still-incomplete activeOperation for 65 seconds; because the current callback is responsible for completing it, the wait times out and the platform aborts the reload. Every successful method switch can therefore leave the new method persisted while the old runtime transport remains active and Control is stopped. Dispatch the replacement only after operationDone has been completed, or otherwise avoid waiting on the operation from its own completion callback.

Useful? React with 👍 / 👎.

@BenCodez
BenCodez changed the base branch from codex/control-votesites-sync to master August 29, 2026 21:20
@BenCodez BenCodez closed this Aug 29, 2026
@BenCodez BenCodez reopened this Aug 29, 2026
@BenCodez
BenCodez changed the base branch from master to codex/control-votesites-sync August 29, 2026 21:24
@chatgpt-codex-connector

Copy link
Copy Markdown

💡 Codex Review

if (!config.hasDatabaseConfigured()) {
throw new IllegalArgumentException("The proxy database Host must be configured for MYSQL");
}

P1 Badge Require a database name before switching to MYSQL

When Database.Host is set but Database.Database is blank, hasDatabaseConfigured() returns true, so this preflight accepts the MYSQL switch. After the result is acknowledged, reloadCore(true) tears down the old runtime, but VotingPluginProxy.loadMysql() rejects the missing database and the platform reload aborts with an incomplete replacement runtime. Validate both the host and database before persisting the switch.


for (String server : proxy.getAllConfiguredServers()) {
Map<String, Object> backend = config.getSpigotServerConfiguration(server);

P2 Badge Validate only configured socket targets

When the proxy has registered servers that are intentionally absent from VotingPlugin's SpigotServers section, this loop looks up those unrelated servers, gets an empty map, and rejects an otherwise valid SOCKETS switch. The runtime builds socket clients from config.getSpigotServers() in rebuildSocketClients(), so the preflight should validate that same collection; otherwise common networks with non-VotingPlugin servers cannot switch methods.


if (current == '\'' && !doubleQuoted) singleQuoted = !singleQuoted;

P1 Badge Recognize comments after plain-scalar apostrophes

When a valid plain YAML scalar contains an apostrophe before a comment, such as Owner: Bob's # Password: comment-secret, this toggles singleQuoted even though no quoted scalar was opened. commentStart() then returns -1, causing sanitizeComments() to skip the entire comment and expose the secret through Control's configuration read response. Quote state must distinguish actual quoted scalars from apostrophes in plain values.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@BenCodez
BenCodez changed the base branch from codex/control-votesites-sync to master August 29, 2026 21:31
@BenCodez BenCodez closed this Aug 29, 2026
@BenCodez BenCodez reopened this Aug 29, 2026
@BenCodez
BenCodez changed the base branch from master to codex/control-votesites-sync August 29, 2026 21:33

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6e8e674d7d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +541 to +542
return configuration != null && configuration.has("preset")
&& PROXY_METHOD_PRESET.equals(configuration.get("preset").getAsString());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restrict runtime replacement to successful apply results

A successful proxy-method READ or PREVIEW also returns a configuration containing the proxy-method preset, so this predicate schedules runtimeReplacement when either result is acknowledged even though nothing was applied. Consequently, merely inspecting or previewing the method performs a disruptive full proxy reload; preserve the operation type in the stored result or otherwise require an APPLY result here.

Useful? React with 👍 / 👎.

Comment on lines +593 to +595
ProxyMethodConfiguration current = new ProxyMethodConfiguration(
com.bencodez.votingplugin.proxy.control.ProxyMethodConfigurationService.canonical(
latest.getString("BungeeMethod", "PLUGINMESSAGING")));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use the runtime default when checking method revisions

When a Bungee configuration omits BungeeMethod, ProxyMethodConfigurationService.read() observes the existing getBungeeMethod() default of SOCKETS, but this persistence check interprets the same file as PLUGINMESSAGING. An apply using the revision returned by the read therefore always raises StaleControlRevisionException for such legacy or minimal configurations; both paths must use the same default.

Useful? React with 👍 / 👎.

@BenCodez
BenCodez changed the base branch from codex/control-votesites-sync to master August 29, 2026 23:56
@BenCodez BenCodez closed this Aug 29, 2026
@BenCodez BenCodez reopened this Aug 29, 2026
@BenCodez
BenCodez changed the base branch from master to codex/control-votesites-sync August 29, 2026 23:56

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5366339b16

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

throw new StaleRevisionException();
}
try {
proxy.getConfig().persistControlProxyMethod(proposal.method().name(), expectedRevision);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Allow replacing an unknown persisted method

When an existing BungeeMethod contains a typo or retired value, read() exposes the runtime fallback (PLUGINMESSAGING) and its revision, but this persistence call reaches both platform implementations, which run canonical(...) on the raw invalid value and throw before writing the proposed replacement. Consequently every Control APPLY returns APPLY_FAILED, so Control cannot repair the invalid setting; interpret the current value with the same fallback used when computing the expected revision while continuing to validate the proposed value strictly.

Useful? React with 👍 / 👎.

Comment on lines +19 to +20
return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256")
.digest(method.name().getBytes(StandardCharsets.UTF_8)));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include preflight settings in the transport revision

The revision hashes only the method, although validate() also relies on transport-specific settings such as the Redis host, MQTT endpoint, socket routes, and database configuration. If one of those settings changes on disk after preview but before APPLY while BungeeMethod remains unchanged, the expected revision still matches and validation uses the previously loaded config; persistence then preserves the newly invalid setting, switches methods, and triggers a full runtime restart into a broken transport. Include the preflight inputs in the revision or validate the freshly loaded configuration atomically during persistence.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Follow-up validation:

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 53882fd858

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}

public void apply(ProxyMethodConfiguration proposal, String expectedRevision) throws IOException {
validate(proposal);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use the fresh snapshot before rejecting apply

When an operator fixes a proposed transport's prerequisites directly on disk without reloading the proxy, this cached validate(proposal) still sees the old invalid values and rejects the APPLY before persistControlProxyMethod can run its newly added fresh-file validator. For example, adding a missing Redis host to the YAML cannot enable a REDIS switch until the operator performs a separate manual reload; remove the cached preflight from APPLY or validate the same freshly loaded snapshot used by persistence.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 078eb6781c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

throw new IllegalArgumentException("BungeeServer.Port must be set");
}
List<String> blockedServers = config.getBlockedServers();
for (String server : proxy.getAllConfiguredServers()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate every socket route the runtime rebuilds

When SpigotServers contains a stale entry for a server no longer registered with the proxy, this loop never validates it, but VotingPluginProxy.rebuildSocketClients() iterates every configured SpigotServers entry. For example, an unregistered entry with Port: bad passes preflight and is then cast to int during the full runtime replacement, throwing ClassCastException and preventing the SOCKETS transport from starting. Validate the same unblocked getSpigotServers() set that the runtime rebuilds.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Final follow-up validation:

AI-assisted implementation; changes and test coverage were reviewed before publication.

@codex review

Copy link
Copy Markdown
Owner Author

Current-head follow-up:

@codex review

Copy link
Copy Markdown
Owner Author

Stack follow-up:

@codex review

Copy link
Copy Markdown
Owner Author

Final current-head review request:

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: be4e9a92fa

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +2905 to +2907
if (activeMethod == null || globalMessageProxyHandler == null) {
return CompletableFuture.completedFuture(CommunicationTestResult.failure(server, activeMethod,
"TRANSPORT_UNAVAILABLE", "The proxy communication transport is not running"));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Check the active transport before starting diagnostics

When MQTT initialization fails, load() catches the exception and continues with mqttHandler == null, while globalMessageProxyHandler is still constructed. This check therefore treats the transport as available; sendMqttEnvelopeServer() silently returns false, and the diagnostic eventually reports TIMEOUT rather than the intended TRANSPORT_UNAVAILABLE. The same issue occurs for a missing socket client, so check the method-specific transport state or propagate the send method's acceptance result before scheduling the timeout.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Current-head follow-up:

  • diagnostics now send through the active method's immediate, boolean-returning transport path before scheduling a timeout
  • MQTT initialization failure, missing socket clients, Redis/MySQL send failure, and plugin-message rejection return TRANSPORT_UNAVAILABLE immediately instead of timing out
  • pending correlation state is still installed before send, and timeout scheduling occurs only after transport acceptance
  • added MQTT and SOCKETS unavailable-state regressions plus updated correlated-reply coverage

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. More of your lovely PRs please.

Reviewed commit: 52bc7df31b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@BenCodez
BenCodez changed the base branch from codex/control-votesites-sync to master August 30, 2026 01:29
@BenCodez
BenCodez merged commit 6a0f420 into master Aug 30, 2026
@BenCodez
BenCodez deleted the codex/control-transport-diagnostics branch August 30, 2026 01:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant