Skip to content

Add the local MCP gateway for online pipeline control - #18

Merged
ply0011 merged 12 commits into
mainfrom
ws/beta-mcp-online-pipeline
Aug 4, 2026
Merged

ply0011 merged 12 commits into
mainfrom
ws/beta-mcp-online-pipeline

Conversation

@feynmx

@feynmx feynmx commented Jul 30, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Add a foreground stdio MCP sidecar backed exclusively by the HTTP control contract.
  • Expose 11 read tools by default and 17 tools with explicit write enablement.
  • Add machine-token administration, Source spec preflight, environment expansion, secret redaction, and bounded HTTP requests.
  • Add tapstate mcp launcher support with native and Java 21 fallback distribution layouts.
  • Document the online AI workflow and enforce CLI, control-face, error-code, and ring architecture gates.

Verification

  • mvn -o -q clean verify
  • e2e/src/test/java/io/tapstate/e2e/McpOnlineControlIT.java launches the packaged MCP Boot JAR, seeds a live connector spec behind a loopback HTTP peer, invokes source_create, and verifies the exact spec/source request sequence, Bearer forwarding, environment expansion, secret redaction, 17 write-enabled tools, and clean EOF.
  • The declarative e2e vocabulary cannot express a JSON-RPC stdio process, MCP tool discovery, or an outbound wire request, so this boundary remains a bespoke process-level case.
  • Local and GitHub e2e admission checks pass.
  • MCP stdio smoke: initialization, 11 read tools, clean EOF, and no credential leak.
  • Native CLI smoke: 13 passed, 0 failed.
  • Extracted fallback bundle smoke through bin/tapstate mcp.
  • Focused deadline regressions cover a stalled shared-client exchange and an unreachable legacy CLI call.
  • Zero CJK, zero agent footprint, and clean Git diff checks.

Dependencies and Follow-up

  • source_create consumes the connector runtime spec from GET /api/connectors/{id}. The registered connector spec producer remains owned by ws/alpha-connector-trunk.
  • The verified release path is the Java 21 JAR fallback. Spring Boot 4 native-image support requires the GraalVM 25 baseline, while the project toolchain currently targets GraalVM 21.
  • The real MySQL-to-Mongo online quickstart remains an integration follow-up after the connector spec producer lands.

Summary by CodeRabbit

  • New Features

    • Added MCP sidecar support through the CLI, with read-only and optional write-enabled tools.
    • Added machine-token creation, listing, and revocation through the CLI and API.
    • Added artifact validation, bounded pipeline log retrieval, and enhanced connector configuration checks.
    • Added versioned CLI bundles containing the CLI and MCP sidecar, with installer and launcher support.
  • Bug Fixes

    • Secrets are now redacted from artifacts and pipeline logs.
    • Improved connector visibility and configuration error reporting.
  • Documentation

    • Updated installation and MCP usage guidance.

@github-actions

github-actions Bot commented Jul 30, 2026 •

Copy link
Copy Markdown

✅ SonarQube quality gate: passed

Open the pull-request dashboard

Posted by CI from the SonarQube pull-request quality gate.

@feynmx
feynmx force-pushed the ws/beta-mcp-online-pipeline branch from 949e177 to 3307c87 Compare August 3, 2026 09:52
@coderabbitai

coderabbitai Bot commented Aug 3, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d906dc2d-cb4d-405f-bbb2-78bbc51c2e60

📥 Commits

Reviewing files that changed from the base of the PR and between 5a3138e and 6478d14.

📒 Files selected for processing (3)
  • control/mcp-server/src/test/java/io/tapstate/mcp/McpOperationExecutorTest.java
  • control/mcp-server/src/test/java/io/tapstate/mcp/TapstateMcpApplicationTest.java
  • control/rest-api/src/test/java/io/tapstate/control/restapi/AuthTest.java
🚧 Files skipped from review as they are similar to previous changes (3)
  • control/mcp-server/src/test/java/io/tapstate/mcp/TapstateMcpApplicationTest.java
  • control/rest-api/src/test/java/io/tapstate/control/restapi/AuthTest.java
  • control/mcp-server/src/test/java/io/tapstate/mcp/McpOperationExecutorTest.java

📝 Walkthrough

Walkthrough

The change adds a stdio MCP sidecar, token administration, shared control-plane HTTP transport, artifact and connector validation, secret redaction, and versioned CLI bundle packaging. It also updates release workflows, installation scripts, documentation, architecture checks, and integration tests.

Changes

MCP control-plane and CLI delivery

Layer / File(s) Summary
Control transport and operation contracts
control/control-client/..., control/control-core/..., control/rest-api/...
Adds bounded HTTP requests, response models, operation schemas, MCP exposure, artifact validation, bounded logs, connector validation, and token administration contracts.
MCP stdio sidecar
control/mcp-server/..., e2e/...
Adds option parsing, environment expansion, tool projection, remote control execution, structured errors, stdio startup, packaging, smoke tests, and process-level integration coverage.
CLI MCP and token commands
cli/src/main/..., cli/src/test/...
Adds the mcp command, sidecar discovery, token create/list/revoke flows, timeout handling, output rendering, and client shutdown.
Secret tracking and online validation
core/core-logging/..., app/src/main/..., core/core-dsl/...
Adds shared secret redaction, artifact-store tracking, pipeline log redaction, and live catalog validation for required connector fields.
CLI bundle release flow
distribution/cli-bundle/..., install/..., .github/workflows/release.yml, deploy/...
Builds and packages the CLI with the MCP sidecar, updates installation to versioned bundles, and adds CLI and MCP smoke tests.
Documentation and support updates
README.md, docs/quickstart-online.md, pom.xml, bom/pom.xml, arch-tests/..., messages/..., core/core-catalog/..., core/core-common/...
Documents bundle and MCP workflows, updates dependency boundaries and catalog rules, registers domains and error codes, and extends architecture and integration checks.

Estimated code review effort: 5 (Critical) | ~120 minutes

Suggested reviewers: ply0011

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.80% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a local MCP gateway for online pipeline control.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ws/beta-mcp-online-pipeline

Comment @coderabbitai help to get the list of available commands.

@feynmx
feynmx marked this pull request as ready for review August 3, 2026 10:43
@feynmx
feynmx requested a review from ply0011 as a code owner August 3, 2026 10:43

@coderabbitai coderabbitai 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.

Actionable comments posted: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
arch-tests/src/test/java/io/tapstate/archtests/CliFaceProjectionGatesTest.java (1)

70-85: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update the deferral note; the security domain now has a face.

Line 85 removed token.create, token.revoke, and token.list from the deferral list. The javadoc at lines 72-74 still states that the security domain has no face anywhere, with no command and no endpoint. Only user administration remains deferred. The sibling ControlFaceProjectionGatesTest updated the equivalent prose. This comment is the reviewed record for each deferral, so a stale reason weakens the gate.

📝 Proposed change
      * The registered operations the CLI does not project, each a deliberate deferral rather than an
-     * oversight. The security domain has no face anywhere — no command and no endpoint — so nothing
-     * invokes those verbs today; the first administrator is created through the bootstrap entry point
-     * instead. {`@code` cluster.members} is routed over HTTP but answers 501 with no topology service
+     * oversight. User administration has no face anywhere — no command and no endpoint — so nothing
+     * invokes those verbs today; the first administrator is created through the bootstrap entry point
+     * instead. Machine-token administration now has both a command and an endpoint, so it is no longer
+     * deferred. {`@code` cluster.members} is routed over HTTP but answers 501 with no topology service
      * behind it, so a command for it would surface a stub rather than a cluster. The frontend-only source
      * and connector detail faces are HTTP contracts with no command shape yet.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@arch-tests/src/test/java/io/tapstate/archtests/CliFaceProjectionGatesTest.java`
around lines 70 - 85, Update the Javadoc for DEFERRED_WITH_NO_VERB to remove the
claim that the security domain has no face anywhere, and describe only the
remaining deferred user-administration operations as lacking a CLI command. Keep
the existing explanations for cluster.members and the frontend-only
source/connector faces unchanged.
🧹 Nitpick comments (16)
distribution/cli-bundle/pom.xml (1)

47-72: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add CI coverage for mcp-jar-fallback.

The release job builds and packages only -Pnative and tests only libexec/tapstate-mcp. Build the fallback archive and run the CLI with libexec/tapstate-mcp.jar under Java 21.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@distribution/cli-bundle/pom.xml` around lines 47 - 72, Add CI coverage for
the mcp-jar-fallback Maven profile: update the release job to build the fallback
archive with -Pmcp-jar-fallback and execute the CLI using
libexec/tapstate-mcp.jar under Java 21, alongside the existing native build and
libexec/tapstate-mcp test.
app/src/test/java/io/tapstate/app/PipelineLogAppenderTest.java (1)

94-111: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Reuse withAppender by adding a redactor overload.

This test repeats the attach, try/finally, detach, and stop sequence that withAppender already owns. An overload that accepts a SecretRedactor removes the duplicated lifecycle code and keeps the cleanup rules in one place.

♻️ Proposed refactor
-    private void withAppender(RingBufferLogSink sink, String loggerName, java.util.function.Consumer<Logger> body) {
+    private void withAppender(RingBufferLogSink sink, String loggerName, java.util.function.Consumer<Logger> body) {
+        withAppender(sink, new SecretRedactor(), loggerName, body);
+    }
+
+    private void withAppender(RingBufferLogSink sink, SecretRedactor redactor, String loggerName,
+            java.util.function.Consumer<Logger> body) {
         Logger logger = (Logger) LoggerFactory.getLogger(loggerName);
-        PipelineLogAppender appender = new PipelineLogAppender(sink, new SecretRedactor());
+        PipelineLogAppender appender = new PipelineLogAppender(sink, redactor);
     `@Test`
     void redactsRegisteredSecretsBeforeWritingToTheSink() {
         RingBufferLogSink sink = new RingBufferLogSink(8, 8);
         SecretRedactor redactor = new SecretRedactor();
-        redactor.replace("source-orders", java.util.List.of("sentinel-password"));
-        Logger logger = (Logger) LoggerFactory.getLogger("test.logs.redacted");
-        PipelineLogAppender appender = new PipelineLogAppender(sink, redactor);
-        appender.setContext(logger.getLoggerContext());
-        appender.start();
-        logger.addAppender(appender);
-        try {
-            MDC.put(MDC_KEY, "orders");
-            logger.warn("connector rejected {}", "sentinel-password");
-        } finally {
-            MDC.remove(MDC_KEY);
-            logger.detachAppender(appender);
-            appender.stop();
-        }
+        redactor.replace("source-orders", List.of("sentinel-password"));
+        withAppender(sink, redactor, "test.logs.redacted", logger -> {
+            MDC.put(MDC_KEY, "orders");
+            logger.warn("connector rejected {}", "sentinel-password");
+        });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/test/java/io/tapstate/app/PipelineLogAppenderTest.java` around lines
94 - 111, Refactor redactsRegisteredSecretsBeforeWritingToTheSink to use the
existing withAppender helper instead of manually managing appender attachment
and cleanup. Add a withAppender overload that accepts a SecretRedactor,
constructs PipelineLogAppender with it, and preserves the helper’s existing
lifecycle behavior.
control/control-core/src/test/java/io/tapstate/control/core/TokenAdminServiceTest.java (1)

58-77: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a case for the documented idempotent revocation.

TokenAdminService.revoke documents revocation as idempotent. No test revokes an unknown token id or revokes the same id twice. Both paths write an audit record and change no state, so the contract deserves a pinned case.

💚 Proposed additional test
`@Test`
void revokingAnUnknownOrAlreadyRevokedTokenStaysIdempotent() {
    FakeTokenStore tokens = new FakeTokenStore();
    List<AuditRecord> audit = new ArrayList<>();
    TokenAdminService service = service(tokens, audit::add);
    service.create("alice", Scope.READ);
    audit.clear();

    service.revoke("alice", "tok-1");
    service.revoke("alice", "tok-1");
    service.revoke("alice", "no-such-token");

    assertThat(service.list()).containsExactly(new TokenInfo("tok-1", Scope.READ, true, NOW));
    assertThat(audit).hasSize(3);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@control/control-core/src/test/java/io/tapstate/control/core/TokenAdminServiceTest.java`
around lines 58 - 77, Add a test alongside
listIsSecretFreeAndRevokeIsAuditedAndImmediatelyAuthoritative that revokes the
same token twice and then an unknown token ID, verifying the token remains
revoked, no additional state changes occur, and three audit records are written.
control/control-client/src/test/java/io/tapstate/control/client/HttpControlClientTest.java (1)

85-107: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add a case for a non-2xx status with a non-JSON body.

malformedJsonBodyIsReturnedAsUnreachable covers status 200 only. A gateway answers 502 or 504 with an HTML body, and the current decode returns Unreachable for it. Add a test that asserts the status survives as Rejected. This pins the behaviour described in the comment on HttpControlClient.decode.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@control/control-client/src/test/java/io/tapstate/control/client/HttpControlClientTest.java`
around lines 85 - 107, Add a test alongside
malformedJsonBodyIsReturnedAsUnreachable in HttpControlClientTest that returns a
non-2xx status such as 502 or 504 with a non-JSON body, then assert
HttpControlClient.get produces ControlResponse.Rejected preserving that status
rather than ControlResponse.Unreachable. Keep the existing generic rejection
fields consistent with rejectionWithoutAStableCodeUsesTheGenericControlContract.
control/control-client/src/main/java/io/tapstate/control/client/HttpControlClient.java (1)

162-169: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Shut down the HttpClient before its executor.

The project targets Java 21, where HttpClient.shutdownNow() is available. Shutting down executor does not terminate the HttpClient selector manager. Call client.shutdownNow() before executor.shutdownNow().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@control/control-client/src/main/java/io/tapstate/control/client/HttpControlClient.java`
around lines 162 - 169, Update HttpControlClient.close() to call
client.shutdownNow() after cancelling in-flight futures and before
executor.shutdownNow(). Preserve the existing compare-and-set guard and
cancellation behavior.
e2e/src/test/java/io/tapstate/e2e/McpOnlineControlIT.java (2)

109-109: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

This close is redundant.

The try-with-resources at line 73 closes input, which wraps process.getOutputStream(). Resources close before the finally block runs, so this call always acts on a closed stream.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@e2e/src/test/java/io/tapstate/e2e/McpOnlineControlIT.java` at line 109,
Remove the redundant process.getOutputStream().close() call from the finally
block in McpOnlineControlIT, since the try-with-resources input already closes
that stream before finally executes.

108-124: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Surface the child stderr when the process fails.

This test launches a separate JVM. If that JVM fails to start or exits early, receive blocks for 10 seconds and then fails with a bare timeout. The reason sits in the stderr file at line 71, but no assertion message includes it.

Also, line 117 asserts the exit value after a possible destroyForcibly. A forced kill produces a non-zero exit value and masks the original cause.

Attach the stderr content to the failure output.

💚 Proposed change
         } finally {
             process.getOutputStream().close();
             if (!process.waitFor(5, TimeUnit.SECONDS)) {
                 process.destroyForcibly();
                 assertThat(process.waitFor(5, TimeUnit.SECONDS)).isTrue();
             }
             server.stop(0);
         }
 
-        assertThat(process.exitValue()).isZero();
+        assertThat(process.exitValue())
+                .as("MCP process stderr:%n%s", Files.readString(stderr))
+                .isZero();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@e2e/src/test/java/io/tapstate/e2e/McpOnlineControlIT.java` around lines 108 -
124, Update the child-process lifecycle assertions around process and stderr so
failures include the contents of the stderr file. Capture whether
process.waitFor completes before forcibly terminating it, and report a failure
with stderr when it times out instead of asserting process.exitValue() after a
forced kill. Preserve the zero-exit assertion only for processes that terminate
normally.
control/mcp-server/src/main/resources/application.properties (1)

1-2: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicate settings are fine, but keep one source of truth in mind.

spring.main.web-application-type and spring.main.banner-mode repeat what TapstateMcpApplication.start sets programmatically. The programmatic values win. Keep both only if the properties file must also serve direct java -jar launches without the start helper. The e2e test launches the boot JAR through main, which calls start, so the properties are redundant today.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@control/mcp-server/src/main/resources/application.properties` around lines 1
- 2, Remove the redundant spring.main.web-application-type and
spring.main.banner-mode entries from application.properties, since
TapstateMcpApplication.start already sets them for the current launch paths.
control/mcp-server/src/test/java/io/tapstate/mcp/McpOperationExecutorTest.java (2)

58-77: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use the imported Operation type.

Operation is imported at line 7. The fully qualified io.tapstate.control.core.Operation in the declaration at line 58 and the loop at line 77 adds no information.

♻️ Proposed change
-            List<Map.Entry<io.tapstate.control.core.Operation, Map<String, Object>>> calls = List.of(
+            List<Map.Entry<Operation, Map<String, Object>>> calls = List.of(
-            for (Map.Entry<io.tapstate.control.core.Operation, Map<String, Object>> call : calls) {
+            for (Map.Entry<Operation, Map<String, Object>> call : calls) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@control/mcp-server/src/test/java/io/tapstate/mcp/McpOperationExecutorTest.java`
around lines 58 - 77, Replace the fully qualified
io.tapstate.control.core.Operation references in the calls declaration and
enhanced for-loop within McpOperationExecutorTest with the already imported
Operation type, leaving the surrounding test logic unchanged.

55-56: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

State the limit clamp explicitly.

The request sets limit to 999. The path assertion at line 90 expects limit=200. That difference encodes a clamp contract, but the test does not say so. A reader cannot tell whether 200 is a clamp or a fixed default that discards the caller value.

Add a short comment, or add a second case with a limit below the bound to prove the caller value passes through.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@control/mcp-server/src/test/java/io/tapstate/mcp/McpOperationExecutorTest.java`
around lines 55 - 56, Clarify the limit-clamping contract in the test around the
logs map and its path assertion: document that the requested limit of 999 is
capped at 200, or add a below-bound case proving caller values are preserved.
Keep the existing assertions and behavior unchanged.
control/mcp-server/src/test/java/io/tapstate/mcp/McpOptionsTest.java (1)

42-51: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Cover the --token= form.

McpOptions.parse rejects both --token and --token=<value> at line 28 of McpOptions.java. This test only covers the space form. The startsWith("--token=") branch guards a credential-leak rule, so add a case for it.

💚 Proposed addition
         assertThatThrownBy(() -> McpOptions.parse(
                 new String[] {"--token", "leak"}, Map.of("TAPSTATE_TOKEN", "token")))
                 .isInstanceOf(IllegalArgumentException.class)
                 .hasMessageContaining("--token");
+        assertThatThrownBy(() -> McpOptions.parse(
+                new String[] {"--token=leak"}, Map.of("TAPSTATE_TOKEN", "token")))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("--token");
         assertThatThrownBy(() -> McpOptions.parse(new String[0], Map.of()))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@control/mcp-server/src/test/java/io/tapstate/mcp/McpOptionsTest.java` around
lines 42 - 51, Extend tokenFlagAndMissingEnvironmentTokenAreRejected to call
McpOptions.parse with a "--token=leak" argument and assert
IllegalArgumentException with a message containing "--token", covering the
startsWith("--token=") rejection branch while preserving the existing
assertions.
control/mcp-server/src/test/java/io/tapstate/mcp/McpEnvironmentTest.java (1)

22-29: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The test name promises null coverage that the test does not provide.

Map.of rejects null values, so this case only covers an empty string value. Rename the test, or build the map with HashMap and add a real null entry. If McpEnvironment must tolerate null values from System.getenv-like maps, the second option adds real coverage.

♻️ Proposed rename
     `@Test`
-    void emptyAndNullEnvironmentValuesAreRetainedWithoutControlVariables() {
+    void emptyEnvironmentValuesAreRetainedWithoutControlVariables() {
         McpEnvironment environment = new McpEnvironment(Map.of(
                 "EMPTY", "", "OTHER", "value"));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@control/mcp-server/src/test/java/io/tapstate/mcp/McpEnvironmentTest.java`
around lines 22 - 29, Update
emptyAndNullEnvironmentValuesAreRetainedWithoutControlVariables to either rename
it so it only claims empty-value coverage, or construct the input with a
nullable map implementation and add an actual null entry to verify
McpEnvironment.values() retains it. Prefer the latter if McpEnvironment is
expected to handle System.getenv-like maps.
control/mcp-server/src/test/java/io/tapstate/mcp/McpStdioProcessTest.java (1)

26-76: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use @TempDir so the stderr file is cleaned up on assertion failure.

Files.createTempFile creates stderr at line 27, but Files.deleteIfExists(stderr) runs only at line 75, after multiple assertions (lines 37-65, 73-74) that can throw. If any of them fails, the temp file is never deleted. Use a JUnit-managed @TempDir so cleanup happens regardless of test outcome.

♻️ Proposed fix
 class McpStdioProcessTest {
 
     `@Test`
-    void stdioNegotiatesListsReadToolsAndExitsAfterEofWithoutStdoutNoise() throws Exception {
-        Path stderr = Files.createTempFile("tapstate-mcp-", ".stderr");
+    void stdioNegotiatesListsReadToolsAndExitsAfterEofWithoutStdoutNoise(`@TempDir` Path tempDir) throws Exception {
+        Path stderr = tempDir.resolve("tapstate-mcp.stderr");
         Process process = start(stderr, unusedLoopbackPort());

The last two lines (assertThat(process.exitValue())... and Files.deleteIfExists(stderr)) can then drop the explicit delete, since @TempDir removes the directory afterward.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@control/mcp-server/src/test/java/io/tapstate/mcp/McpStdioProcessTest.java`
around lines 26 - 76, Update
stdioNegotiatesListsReadToolsAndExitsAfterEofWithoutStdoutNoise to use a JUnit
`@TempDir` directory for the stderr file instead of Files.createTempFile. Store
the stderr file beneath that managed directory, remove the explicit
Files.deleteIfExists cleanup, and retain the existing process and assertion
behavior.
cli/src/main/java/io/tapstate/cli/HttpControlPlaneClient.java (1)

60-93: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Close the lazily-built httpClient too, not just sharedClient.

close() releases sharedClient but never touches the pre-existing httpClient field used by login, apply, get, list, test, testResult, discoverSchema, schema, register, connectorList, lifecycle, status, metrics, snapshot, logs, and the websocket stream() helper. Since Java 21, java.net.http.HttpClient implements AutoCloseable and exposes close()/shutdown() precisely so callers are not left relying on GC to reclaim the selector thread and pooled connections. Adding AutoCloseable to this class now promises deterministic cleanup; closing only half of its two underlying HTTP clients does not fully honor that promise, particularly for long REPL sessions that also open websocket streams.

♻️ Proposed fix
     `@Override`
     public void close() {
         sharedClient.close();
+        if (httpClient != null) {
+            httpClient.close();
+        }
     }

Confirm the project's declared Java target for the cli module supports HttpClient.close() (Java 21+); the pattern-matching switch already used in tokenCreate/tokenList/tokenRevoke suggests it does.

Also applies to: 183-186

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/src/main/java/io/tapstate/cli/HttpControlPlaneClient.java` around lines
60 - 93, Update HttpControlPlaneClient.close() to deterministically close the
lazily initialized httpClient as well as sharedClient, guarding against it never
being created; first verify the cli module targets Java 21 or newer so
HttpClient.close() is available, and preserve cleanup for long-lived sessions
and stream() resources.
cli/src/main/java/io/tapstate/cli/RemoteCreatedToken.java (1)

3-5: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Mask the bearer value in toString().

The record's generated toString() prints token in clear text. Any future log line, assertion message, or exception that includes this record leaks the bearer value. McpOptions.toString() in this same change already masks its token, so apply the same rule here.

🔒️ Proposed change
 record RemoteCreatedToken(String tokenId, String scope, String token, String createdAt) {
+
+    `@Override`
+    public String toString() {
+        return "RemoteCreatedToken[tokenId=" + tokenId + ", scope=" + scope
+                + ", token=********, createdAt=" + createdAt + "]";
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/src/main/java/io/tapstate/cli/RemoteCreatedToken.java` around lines 3 -
5, Override RemoteCreatedToken.toString() to mask the token field using the same
masking behavior as McpOptions.toString(), while retaining the other record
components in the representation and ensuring the bearer value is never emitted
in clear text.
cli/src/main/java/io/tapstate/cli/McpLauncher.java (1)

74-77: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Reuse executable for the native sidecar so Windows resolution matches the Java lookup.

executable(Path, String) already handles the .exe suffix, but the native sidecar probe only tries tapstate-mcp. On Windows a bundled tapstate-mcp.exe is not found, and the launcher falls back to the JAR path, which then requires a separate Java 21 runtime.

♻️ Proposed refactor
         Path libexec = bin.resolve("../libexec").normalize();
-        Path nativeSidecar = libexec.resolve("tapstate-mcp");
+        Path nativeSidecar = executable(libexec, "tapstate-mcp");
         List<String> command = new ArrayList<>();
-        if (Files.isRegularFile(nativeSidecar) && Files.isExecutable(nativeSidecar)) {
+        if (nativeSidecar != null) {
             command.add(nativeSidecar.toString());
         } else {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/src/main/java/io/tapstate/cli/McpLauncher.java` around lines 74 - 77,
Update the native sidecar lookup in McpLauncher to resolve the candidate through
the existing executable(Path, String) helper using the tapstate-mcp name, then
use that resolved Path for the regular-file and executable checks and command
construction. Preserve the existing JAR fallback when no executable is found.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/main/java/io/tapstate/app/SecretTrackingArtifactStore.java`:
- Around line 90-105: Bound the catalog-miss fallback in secretValues so it no
longer passes every scalar from source.config() to redaction. Replace
scalarValues(source.config()) with a restricted fallback that collects only
values under secret-like keys and excludes trivially short values, reusing
existing traversal helpers where possible; preserve catalog-based secret field
handling unchanged.

In `@cli/native-smoke.sh`:
- Around line 84-87: Wrap the os.write(fd, data) call in the ready/input_sent
branch with OSError handling, matching the existing os.read handling. Treat the
pty-closed EIO case as a normal child-exit race so the harness continues to
report the actual test result instead of terminating with a traceback.

In `@cli/src/main/java/io/tapstate/cli/Repl.java`:
- Around line 498-508: The TEXT branch of renderTokens should print a benign
empty-state message when tokens is empty, matching the existing behavior of
lsOnline and renderConnectors. Add this check before iterating tokens, flush the
output, and return; preserve the current token-row rendering for non-empty
lists.

In
`@control/control-client/src/main/java/io/tapstate/control/client/HttpControlClient.java`:
- Around line 112-131: Update HttpControlClient.decode so malformed response
bodies return Unreachable only for 2xx responses; for non-2xx statuses, continue
with body set to null and return Rejected carrying the HTTP status. Add coverage
in HttpControlClientTest for a non-2xx response with a non-JSON body, asserting
the Rejected outcome preserves that status. Apply changes at
control/control-client/src/main/java/io/tapstate/control/client/HttpControlClient.java:112-131
and
control/control-client/src/test/java/io/tapstate/control/client/HttpControlClientTest.java:85-107.

In
`@control/control-core/src/main/java/io/tapstate/control/core/ControlOperations.java`:
- Around line 55-66: Update the implementations backing
ControlOperations.CONNECTION_TEST and CONNECTION_DISCOVER_SCHEMA to enforce an
egress allow-list or equivalent network isolation before invoking connectors
with caller-supplied settings. Reject loopback, private, link-local, metadata,
and other disallowed destinations at runtime while preserving the existing
persisted-result behavior for permitted targets.

In
`@control/control-core/src/main/java/io/tapstate/control/core/SourceService.java`:
- Around line 68-69: Use one TapstateCatalog snapshot for each source mutation
validation: in SourceService.java lines 68-69 and 104-105, resolve catalog.get()
once before Workspace.of and pass that same snapshot to
CapabilityRules.validateOnline in both create and replace flows.

In `@control/mcp-server/mcp-smoke.sh`:
- Around line 51-58: Update receive() so each process.stdout.readline() call is
bounded by the smoke test’s response deadline, preventing an unresponsive
sidecar from blocking indefinitely before the existing timeout handling.
Preserve the current EOF and JSON decoding errors, and reuse the established
timeout mechanism rather than adding an unrelated global timeout.

In `@control/mcp-server/src/main/java/io/tapstate/mcp/McpOperationExecutor.java`:
- Around line 36-67: Update McpOperationExecutor.execute so the
unsupported-operation default path is converted into a coded McpResult rather
than escaping as an uncaught IllegalArgumentException. Either catch and map that
exception alongside TapstateException, or replace the default throw with a
TapstateException using a dedicated McpError code, while preserving existing
structured handling for all other operation failures.

In
`@control/mcp-server/src/test/java/io/tapstate/mcp/McpOperationExecutorTest.java`:
- Around line 192-207: Update
unreachableServerIsReturnedAsStructuredCodedFailure to use the existing
deterministic http://127.0.0.1:1 endpoint instead of allocating and closing a
ServerSocket, and remove the now-unused ServerSocket import.

In
`@control/mcp-server/src/test/java/io/tapstate/mcp/TapstateMcpApplicationTest.java`:
- Around line 36-46: Update
startsAsAStdioOnlyApplicationAndRegistersTheConfiguredToolSurface to set
spring.ai.mcp.server.stdio to false for the in-process application context,
while leaving McpStdioProcessTest responsible for real stdio transport coverage.

In
`@control/rest-api/src/main/java/io/tapstate/control/restapi/ArtifactController.java`:
- Around line 50-53: Update ArtifactController request extraction to validate
every ArtifactDraft entry and its required content before calling ApplyService,
rejecting null or incomplete drafts as control.malformed-request. Introduce a
shared extraction helper and use it from both apply and validate, then add MVC
coverage for null array entries and null draft content.

In
`@control/rest-api/src/main/java/io/tapstate/control/restapi/PipelineLogsController.java`:
- Around line 30-33: Update the PipelineLogsController method to validate limit
values before calling PipelineLogQueryService: preserve the existing behavior
for null or positive limits, and map zero or negative limits to the documented
client-error response instead of allowing IllegalArgumentException to produce
HTTP 500. Add API tests covering both limit=0 and a negative limit.

In `@control/rest-api/src/test/java/io/tapstate/control/restapi/AuthTest.java`:
- Around line 219-223: Update the token-listing assertions in AuthTest so they
verify that the stored secret hash value produced by FakeTokenSecrets, such as
hash:sec-N for the created token, is absent from the response, in addition to
checking the secretHash field name. Keep the existing token and WRITE assertions
unchanged.

In
`@core/core-catalog/src/main/java/io/tapstate/core/catalog/SpecNormalizer.java`:
- Around line 20-22: Update the operator tracking in the SELF_VALUE
normalization logic around the method covering lines 145–165 to record both
equality and inequality operators. Return null as soon as an operator of the
opposite type appears, regardless of order, while preserving existing handling
for expressions containing only one operator type.

In `@install/install.sh`:
- Around line 211-219: Update the installation flow around the final directory
move and tapstate symlink creation to keep the stable entry resolvable during
same-version reinstalls. Install the staged bundle into an immutable directory,
then atomically replace the versions/$version indirection symlink, or retain an
existing complete version directory instead of removing it before moving the
staged bundle.

---

Outside diff comments:
In
`@arch-tests/src/test/java/io/tapstate/archtests/CliFaceProjectionGatesTest.java`:
- Around line 70-85: Update the Javadoc for DEFERRED_WITH_NO_VERB to remove the
claim that the security domain has no face anywhere, and describe only the
remaining deferred user-administration operations as lacking a CLI command. Keep
the existing explanations for cluster.members and the frontend-only
source/connector faces unchanged.

---

Nitpick comments:
In `@app/src/test/java/io/tapstate/app/PipelineLogAppenderTest.java`:
- Around line 94-111: Refactor redactsRegisteredSecretsBeforeWritingToTheSink to
use the existing withAppender helper instead of manually managing appender
attachment and cleanup. Add a withAppender overload that accepts a
SecretRedactor, constructs PipelineLogAppender with it, and preserves the
helper’s existing lifecycle behavior.

In `@cli/src/main/java/io/tapstate/cli/HttpControlPlaneClient.java`:
- Around line 60-93: Update HttpControlPlaneClient.close() to deterministically
close the lazily initialized httpClient as well as sharedClient, guarding
against it never being created; first verify the cli module targets Java 21 or
newer so HttpClient.close() is available, and preserve cleanup for long-lived
sessions and stream() resources.

In `@cli/src/main/java/io/tapstate/cli/McpLauncher.java`:
- Around line 74-77: Update the native sidecar lookup in McpLauncher to resolve
the candidate through the existing executable(Path, String) helper using the
tapstate-mcp name, then use that resolved Path for the regular-file and
executable checks and command construction. Preserve the existing JAR fallback
when no executable is found.

In `@cli/src/main/java/io/tapstate/cli/RemoteCreatedToken.java`:
- Around line 3-5: Override RemoteCreatedToken.toString() to mask the token
field using the same masking behavior as McpOptions.toString(), while retaining
the other record components in the representation and ensuring the bearer value
is never emitted in clear text.

In
`@control/control-client/src/main/java/io/tapstate/control/client/HttpControlClient.java`:
- Around line 162-169: Update HttpControlClient.close() to call
client.shutdownNow() after cancelling in-flight futures and before
executor.shutdownNow(). Preserve the existing compare-and-set guard and
cancellation behavior.

In
`@control/control-client/src/test/java/io/tapstate/control/client/HttpControlClientTest.java`:
- Around line 85-107: Add a test alongside
malformedJsonBodyIsReturnedAsUnreachable in HttpControlClientTest that returns a
non-2xx status such as 502 or 504 with a non-JSON body, then assert
HttpControlClient.get produces ControlResponse.Rejected preserving that status
rather than ControlResponse.Unreachable. Keep the existing generic rejection
fields consistent with rejectionWithoutAStableCodeUsesTheGenericControlContract.

In
`@control/control-core/src/test/java/io/tapstate/control/core/TokenAdminServiceTest.java`:
- Around line 58-77: Add a test alongside
listIsSecretFreeAndRevokeIsAuditedAndImmediatelyAuthoritative that revokes the
same token twice and then an unknown token ID, verifying the token remains
revoked, no additional state changes occur, and three audit records are written.

In `@control/mcp-server/src/main/resources/application.properties`:
- Around line 1-2: Remove the redundant spring.main.web-application-type and
spring.main.banner-mode entries from application.properties, since
TapstateMcpApplication.start already sets them for the current launch paths.

In `@control/mcp-server/src/test/java/io/tapstate/mcp/McpEnvironmentTest.java`:
- Around line 22-29: Update
emptyAndNullEnvironmentValuesAreRetainedWithoutControlVariables to either rename
it so it only claims empty-value coverage, or construct the input with a
nullable map implementation and add an actual null entry to verify
McpEnvironment.values() retains it. Prefer the latter if McpEnvironment is
expected to handle System.getenv-like maps.

In
`@control/mcp-server/src/test/java/io/tapstate/mcp/McpOperationExecutorTest.java`:
- Around line 58-77: Replace the fully qualified
io.tapstate.control.core.Operation references in the calls declaration and
enhanced for-loop within McpOperationExecutorTest with the already imported
Operation type, leaving the surrounding test logic unchanged.
- Around line 55-56: Clarify the limit-clamping contract in the test around the
logs map and its path assertion: document that the requested limit of 999 is
capped at 200, or add a below-bound case proving caller values are preserved.
Keep the existing assertions and behavior unchanged.

In `@control/mcp-server/src/test/java/io/tapstate/mcp/McpOptionsTest.java`:
- Around line 42-51: Extend tokenFlagAndMissingEnvironmentTokenAreRejected to
call McpOptions.parse with a "--token=leak" argument and assert
IllegalArgumentException with a message containing "--token", covering the
startsWith("--token=") rejection branch while preserving the existing
assertions.

In `@control/mcp-server/src/test/java/io/tapstate/mcp/McpStdioProcessTest.java`:
- Around line 26-76: Update
stdioNegotiatesListsReadToolsAndExitsAfterEofWithoutStdoutNoise to use a JUnit
`@TempDir` directory for the stderr file instead of Files.createTempFile. Store
the stderr file beneath that managed directory, remove the explicit
Files.deleteIfExists cleanup, and retain the existing process and assertion
behavior.

In `@distribution/cli-bundle/pom.xml`:
- Around line 47-72: Add CI coverage for the mcp-jar-fallback Maven profile:
update the release job to build the fallback archive with -Pmcp-jar-fallback and
execute the CLI using libexec/tapstate-mcp.jar under Java 21, alongside the
existing native build and libexec/tapstate-mcp test.

In `@e2e/src/test/java/io/tapstate/e2e/McpOnlineControlIT.java`:
- Line 109: Remove the redundant process.getOutputStream().close() call from the
finally block in McpOnlineControlIT, since the try-with-resources input already
closes that stream before finally executes.
- Around line 108-124: Update the child-process lifecycle assertions around
process and stderr so failures include the contents of the stderr file. Capture
whether process.waitFor completes before forcibly terminating it, and report a
failure with stderr when it times out instead of asserting process.exitValue()
after a forced kill. Preserve the zero-exit assertion only for processes that
terminate normally.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0de9f3ba-4af2-4a90-bd1f-d52117aa1819

📥 Commits

Reviewing files that changed from the base of the PR and between db3ca96 and dad4db9.

📒 Files selected for processing (119)
  • .github/workflows/release.yml
  • README.md
  • app/src/main/java/io/tapstate/app/ControlPlaneConfiguration.java
  • app/src/main/java/io/tapstate/app/PipelineLogAppender.java
  • app/src/main/java/io/tapstate/app/PipelineLogCapture.java
  • app/src/main/java/io/tapstate/app/SecretTrackingArtifactStore.java
  • app/src/test/java/io/tapstate/app/ControlPlaneAssemblyIT.java
  • app/src/test/java/io/tapstate/app/ConvergenceDriverTest.java
  • app/src/test/java/io/tapstate/app/PipelineLogAppenderTest.java
  • app/src/test/java/io/tapstate/app/PipelineLogCaptureTest.java
  • app/src/test/java/io/tapstate/app/SecretTrackingArtifactStoreTest.java
  • arch-tests/pom.xml
  • arch-tests/src/test/java/io/tapstate/archtests/CliFaceProjectionGatesTest.java
  • arch-tests/src/test/java/io/tapstate/archtests/ControlFaceProjectionGatesTest.java
  • arch-tests/src/test/java/io/tapstate/archtests/RingDependencyRulesTest.java
  • arch-tests/src/test/resources/error-codes.golden
  • bom/pom.xml
  • cli/native-smoke.sh
  • cli/pom.xml
  • cli/src/main/java/io/tapstate/cli/Cli.java
  • cli/src/main/java/io/tapstate/cli/CliError.java
  • cli/src/main/java/io/tapstate/cli/ControlPlaneClient.java
  • cli/src/main/java/io/tapstate/cli/HttpControlPlaneClient.java
  • cli/src/main/java/io/tapstate/cli/McpCmd.java
  • cli/src/main/java/io/tapstate/cli/McpLauncher.java
  • cli/src/main/java/io/tapstate/cli/RemoteCreatedToken.java
  • cli/src/main/java/io/tapstate/cli/RemoteToken.java
  • cli/src/main/java/io/tapstate/cli/Repl.java
  • cli/src/main/java/io/tapstate/cli/TokenCreateOutcome.java
  • cli/src/main/java/io/tapstate/cli/TokenListOutcome.java
  • cli/src/main/java/io/tapstate/cli/TokenRevokeOutcome.java
  • cli/src/test/java/io/tapstate/cli/CliTest.java
  • cli/src/test/java/io/tapstate/cli/HttpControlPlaneClientTest.java
  • cli/src/test/java/io/tapstate/cli/McpLauncherTest.java
  • cli/src/test/java/io/tapstate/cli/ReplTest.java
  • control/control-client/pom.xml
  • control/control-client/src/main/java/io/tapstate/control/client/ControlResponse.java
  • control/control-client/src/main/java/io/tapstate/control/client/HttpControlClient.java
  • control/control-client/src/main/java/io/tapstate/control/client/RequestBudget.java
  • control/control-client/src/test/java/io/tapstate/control/client/HttpControlClientTest.java
  • control/control-core/src/main/java/io/tapstate/control/core/ApplyService.java
  • control/control-core/src/main/java/io/tapstate/control/core/ArtifactValidationResult.java
  • control/control-core/src/main/java/io/tapstate/control/core/ConnectionTestService.java
  • control/control-core/src/main/java/io/tapstate/control/core/ConnectorConfigValidator.java
  • control/control-core/src/main/java/io/tapstate/control/core/ControlApiSchema.java
  • control/control-core/src/main/java/io/tapstate/control/core/ControlError.java
  • control/control-core/src/main/java/io/tapstate/control/core/ControlOperations.java
  • control/control-core/src/main/java/io/tapstate/control/core/CreatedToken.java
  • control/control-core/src/main/java/io/tapstate/control/core/Operation.java
  • control/control-core/src/main/java/io/tapstate/control/core/PipelineLogQueryService.java
  • control/control-core/src/main/java/io/tapstate/control/core/SchemaDiscoveryService.java
  • control/control-core/src/main/java/io/tapstate/control/core/SchemaRef.java
  • control/control-core/src/main/java/io/tapstate/control/core/SourceService.java
  • control/control-core/src/main/java/io/tapstate/control/core/TokenAdminService.java
  • control/control-core/src/main/java/io/tapstate/control/core/TokenService.java
  • control/control-core/src/main/java/io/tapstate/control/core/ValidationDiagnostic.java
  • control/control-core/src/test/java/io/tapstate/control/core/ApplyServiceTest.java
  • control/control-core/src/test/java/io/tapstate/control/core/AuditedSourceServiceTest.java
  • control/control-core/src/test/java/io/tapstate/control/core/ConnectorConfigValidatorTest.java
  • control/control-core/src/test/java/io/tapstate/control/core/ControlApiSchemaTest.java
  • control/control-core/src/test/java/io/tapstate/control/core/ControlOperationsTest.java
  • control/control-core/src/test/java/io/tapstate/control/core/PipelineLogQueryServiceTest.java
  • control/control-core/src/test/java/io/tapstate/control/core/SourceServiceTest.java
  • control/control-core/src/test/java/io/tapstate/control/core/TokenAdminServiceTest.java
  • control/mcp-server/mcp-smoke.sh
  • control/mcp-server/pom.xml
  • control/mcp-server/src/main/java/io/tapstate/mcp/EnvironmentExpander.java
  • control/mcp-server/src/main/java/io/tapstate/mcp/McpEnvironment.java
  • control/mcp-server/src/main/java/io/tapstate/mcp/McpError.java
  • control/mcp-server/src/main/java/io/tapstate/mcp/McpOperationExecutor.java
  • control/mcp-server/src/main/java/io/tapstate/mcp/McpOptions.java
  • control/mcp-server/src/main/java/io/tapstate/mcp/McpResult.java
  • control/mcp-server/src/main/java/io/tapstate/mcp/McpToolCatalog.java
  • control/mcp-server/src/main/java/io/tapstate/mcp/TapstateMcpApplication.java
  • control/mcp-server/src/main/resources/application.properties
  • control/mcp-server/src/main/resources/logback-spring.xml
  • control/mcp-server/src/test/java/io/tapstate/mcp/EnvironmentExpanderTest.java
  • control/mcp-server/src/test/java/io/tapstate/mcp/McpEnvironmentTest.java
  • control/mcp-server/src/test/java/io/tapstate/mcp/McpErrorTest.java
  • control/mcp-server/src/test/java/io/tapstate/mcp/McpOperationExecutorTest.java
  • control/mcp-server/src/test/java/io/tapstate/mcp/McpOptionsTest.java
  • control/mcp-server/src/test/java/io/tapstate/mcp/McpResultTest.java
  • control/mcp-server/src/test/java/io/tapstate/mcp/McpStdioProcessTest.java
  • control/mcp-server/src/test/java/io/tapstate/mcp/McpToolCatalogTest.java
  • control/mcp-server/src/test/java/io/tapstate/mcp/TapstateMcpApplicationTest.java
  • control/pom.xml
  • control/rest-api/src/main/java/io/tapstate/control/restapi/ArtifactController.java
  • control/rest-api/src/main/java/io/tapstate/control/restapi/ControlHttpFace.java
  • control/rest-api/src/main/java/io/tapstate/control/restapi/PipelineLogsController.java
  • control/rest-api/src/main/java/io/tapstate/control/restapi/TokenController.java
  • control/rest-api/src/main/java/io/tapstate/control/restapi/TokenCreateRequest.java
  • control/rest-api/src/main/java/io/tapstate/control/restapi/TokenList.java
  • control/rest-api/src/test/java/io/tapstate/control/restapi/AuthTest.java
  • control/rest-api/src/test/java/io/tapstate/control/restapi/ControlApiTest.java
  • control/rest-api/src/test/java/io/tapstate/control/restapi/PipelineLogsApiTest.java
  • control/rest-api/src/test/java/io/tapstate/control/restapi/SourceApiTest.java
  • core/core-catalog/src/main/java/io/tapstate/core/catalog/SpecNormalizer.java
  • core/core-catalog/src/main/resources/catalog/mongodb.json
  • core/core-catalog/src/test/java/io/tapstate/core/catalog/SpecNormalizerTest.java
  • core/core-common/src/main/java/io/tapstate/core/common/Domain.java
  • core/core-common/src/test/java/io/tapstate/core/common/DomainTest.java
  • core/core-dsl/src/main/java/io/tapstate/core/dsl/CapabilityRules.java
  • core/core-dsl/src/main/java/io/tapstate/core/dsl/DslError.java
  • core/core-dsl/src/test/java/io/tapstate/core/dsl/CapabilityRulesTest.java
  • core/core-dsl/src/test/java/io/tapstate/core/dsl/CorpusSemanticGateTest.java
  • core/core-dsl/src/test/java/io/tapstate/core/dsl/DslErrorTest.java
  • core/core-logging/src/main/java/io/tapstate/core/logging/SecretRedactor.java
  • core/core-logging/src/test/java/io/tapstate/core/logging/SecretRedactorTest.java
  • deploy/quickstart/quickstart-smoke.sh
  • distribution/cli-bundle/pom.xml
  • distribution/cli-bundle/src/assembly/jar-fallback.xml
  • distribution/cli-bundle/src/assembly/native.xml
  • docs/quickstart-online.md
  • e2e/pom.xml
  • e2e/src/test/java/io/tapstate/e2e/McpOnlineControlIT.java
  • install/install-smoke.sh
  • install/install.sh
  • messages/src/main/resources/messages/en.yml
  • pom.xml

Comment thread cli/native-smoke.sh Resolved
Comment thread install/install.sh Resolved

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
control/control-client/src/test/java/io/tapstate/control/client/HttpControlClientTest.java (1)

35-57: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Assert the HTTP method and request path.

The handler records only the authorization header and parsed body. A regression that sends a different method or uses the wrong path would still pass. Capture and assert POST and /api/tokens.

Suggested assertions
+        AtomicReference<String> method = new AtomicReference<>();
+        AtomicReference<String> path = new AtomicReference<>();
         HttpServer server = server(exchange -> {
+            method.set(exchange.getRequestMethod());
+            path.set(exchange.getRequestURI().getPath());
             authorization.set(exchange.getRequestHeaders().getFirst("Authorization"));
             body.set(JsonReader.parse(...));
...
+            assertThat(method.get()).isEqualTo("POST");
+            assertThat(path.get()).isEqualTo("/api/tokens");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@control/control-client/src/test/java/io/tapstate/control/client/HttpControlClientTest.java`
around lines 35 - 57, Update the postSendsBearerAndJsonAndReturnsTheDecodedTree
test and its server handler to capture the incoming HTTP method and request path
from the exchange, then assert they are POST and /api/tokens respectively
alongside the existing authorization and body assertions.
control/mcp-server/src/test/java/io/tapstate/mcp/McpOperationExecutorTest.java (2)

33-45: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Assert the complete HTTP request sequence.

The test records only the URI and uses contains(...). It does not verify the HTTP method, request count, or order. source.create uses a connector-spec GET followed by a source POST, while both paths overlap with other operations. A missing write or incorrect verb can therefore pass.

Record the method with the URI and assert the full sequence with containsExactly(...), including both requests from source.create.

Proposed assertion fix
-            paths.add(exchange.getRequestURI().toString());
+            paths.add(exchange.getRequestMethod() + " " + exchange.getRequestURI());

-            assertThat(paths).contains(
+            assertThat(paths).containsExactly(
+                    "GET /api/connectors",
+                    "GET /api/connectors/mysql",
+                    "GET /api/sources",
+                    "GET /api/sources/orders",
+                    "GET /api/connectors/mysql",
+                    "POST /api/sources",
                     ...

Also applies to: 82-89

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@control/mcp-server/src/test/java/io/tapstate/mcp/McpOperationExecutorTest.java`
around lines 33 - 45, Update the request recording and assertions in
McpOperationExecutorTest to capture each HTTP method together with its URI, then
replace partial contains checks with an ordered containsExactly assertion.
Verify the complete sequence, including the connector-spec GET followed by the
source POST, while preserving the existing server behavior.

130-133: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Assert the Bearer token on every request.

authorization is overwritten for each exchange. Since source.create makes multiple requests, line 160 checks only the final request. A missing Authorization header on the connector-spec request would pass.

Store every header value and assert that each one equals Bearer machine-token.

Also applies to: 158-162

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@control/mcp-server/src/test/java/io/tapstate/mcp/McpOperationExecutorTest.java`
around lines 130 - 133, Update the test server handler around authorization and
the assertions near source.create to collect every request’s Authorization
header instead of overwriting a single AtomicReference. Assert that each
captured header equals “Bearer machine-token,” ensuring all requests, including
the connector-spec request, are validated.
🧹 Nitpick comments (2)
control/control-client/src/test/java/io/tapstate/control/client/HttpControlClientTest.java (2)

25-33: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add negative-duration cases to the constructor test.

constructorRejectsNonPositiveTimeouts checks only Duration.ZERO. Add a negative duration for both lightTimeout and heavyTimeout. Otherwise, a constructor that accepts negative values can pass this test.

Suggested additions
+        assertThatThrownBy(() -> new HttpControlClient(
+                Duration.ofMillis(-1), Duration.ofSeconds(1)))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("lightTimeout");
+
+        assertThatThrownBy(() -> new HttpControlClient(
+                Duration.ofSeconds(1), Duration.ofMillis(-1)))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("heavyTimeout");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@control/control-client/src/test/java/io/tapstate/control/client/HttpControlClientTest.java`
around lines 25 - 33, Extend constructorRejectsNonPositiveTimeouts to assert
IllegalArgumentException for negative lightTimeout and negative heavyTimeout
values, while preserving the existing zero-duration assertions and message
checks for each parameter.

130-151: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Assert the configured request budget.

The client uses 250 ms timeouts, but response.get(5, TimeUnit.SECONDS) allows a several-second regression to pass. Measure elapsed time and assert completion within the request budget plus explicit scheduler slack.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@control/control-client/src/test/java/io/tapstate/control/client/HttpControlClientTest.java`
around lines 130 - 151, Update requestBudgetBoundsAStalledExchange to measure
elapsed time around the client request and assert that response completion
occurs within the configured 250 ms request budget plus an explicit, narrowly
defined scheduler slack. Replace the five-second response.get allowance with a
timeout aligned to that budget, while preserving the existing Unreachable
assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@control/control-client/src/test/java/io/tapstate/control/client/HttpControlClientTest.java`:
- Around line 121-128: Update connectionFailureIsAStableUnreachableOutcome so
the ServerSocket is closed before invoking HttpControlClient.get, ensuring the
endpoint is unreachable and the test covers connection failure rather than a
stalled exchange. Keep the existing assertion and client setup unchanged.

---

Outside diff comments:
In
`@control/control-client/src/test/java/io/tapstate/control/client/HttpControlClientTest.java`:
- Around line 35-57: Update the postSendsBearerAndJsonAndReturnsTheDecodedTree
test and its server handler to capture the incoming HTTP method and request path
from the exchange, then assert they are POST and /api/tokens respectively
alongside the existing authorization and body assertions.

In
`@control/mcp-server/src/test/java/io/tapstate/mcp/McpOperationExecutorTest.java`:
- Around line 33-45: Update the request recording and assertions in
McpOperationExecutorTest to capture each HTTP method together with its URI, then
replace partial contains checks with an ordered containsExactly assertion.
Verify the complete sequence, including the connector-spec GET followed by the
source POST, while preserving the existing server behavior.
- Around line 130-133: Update the test server handler around authorization and
the assertions near source.create to collect every request’s Authorization
header instead of overwriting a single AtomicReference. Assert that each
captured header equals “Bearer machine-token,” ensuring all requests, including
the connector-spec request, are validated.

---

Nitpick comments:
In
`@control/control-client/src/test/java/io/tapstate/control/client/HttpControlClientTest.java`:
- Around line 25-33: Extend constructorRejectsNonPositiveTimeouts to assert
IllegalArgumentException for negative lightTimeout and negative heavyTimeout
values, while preserving the existing zero-duration assertions and message
checks for each parameter.
- Around line 130-151: Update requestBudgetBoundsAStalledExchange to measure
elapsed time around the client request and assert that response completion
occurs within the configured 250 ms request budget plus an explicit, narrowly
defined scheduler slack. Replace the five-second response.get allowance with a
timeout aligned to that budget, while preserving the existing Unreachable
assertion.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 830b489e-f029-4a60-b8b0-2abd588f1dfc

📥 Commits

Reviewing files that changed from the base of the PR and between dad4db9 and a2bdf40.

📒 Files selected for processing (22)
  • .github/workflows/release.yml
  • app/src/main/java/io/tapstate/app/SecretTrackingArtifactStore.java
  • app/src/test/java/io/tapstate/app/SecretTrackingArtifactStoreTest.java
  • cli/native-smoke.sh
  • cli/src/main/java/io/tapstate/cli/Repl.java
  • cli/src/test/java/io/tapstate/cli/ReplTest.java
  • control/control-client/src/main/java/io/tapstate/control/client/HttpControlClient.java
  • control/control-client/src/test/java/io/tapstate/control/client/HttpControlClientTest.java
  • control/control-core/src/main/java/io/tapstate/control/core/SourceService.java
  • control/control-core/src/test/java/io/tapstate/control/core/SourceServiceTest.java
  • control/mcp-server/mcp-smoke.sh
  • control/mcp-server/src/main/java/io/tapstate/mcp/McpOperationExecutor.java
  • control/mcp-server/src/test/java/io/tapstate/mcp/McpOperationExecutorTest.java
  • control/rest-api/src/main/java/io/tapstate/control/restapi/ArtifactController.java
  • control/rest-api/src/main/java/io/tapstate/control/restapi/PipelineLogsController.java
  • control/rest-api/src/test/java/io/tapstate/control/restapi/AuthTest.java
  • control/rest-api/src/test/java/io/tapstate/control/restapi/ControlApiTest.java
  • control/rest-api/src/test/java/io/tapstate/control/restapi/PipelineLogsApiTest.java
  • core/core-catalog/src/main/java/io/tapstate/core/catalog/SpecNormalizer.java
  • core/core-catalog/src/test/java/io/tapstate/core/catalog/SpecNormalizerTest.java
  • install/install-smoke.sh
  • install/install.sh
🚧 Files skipped from review as they are similar to previous changes (17)
  • core/core-catalog/src/main/java/io/tapstate/core/catalog/SpecNormalizer.java
  • control/rest-api/src/test/java/io/tapstate/control/restapi/PipelineLogsApiTest.java
  • cli/native-smoke.sh
  • control/rest-api/src/test/java/io/tapstate/control/restapi/ControlApiTest.java
  • install/install-smoke.sh
  • install/install.sh
  • control/control-core/src/main/java/io/tapstate/control/core/SourceService.java
  • control/mcp-server/mcp-smoke.sh
  • cli/src/main/java/io/tapstate/cli/Repl.java
  • control/rest-api/src/main/java/io/tapstate/control/restapi/ArtifactController.java
  • app/src/test/java/io/tapstate/app/SecretTrackingArtifactStoreTest.java
  • control/control-client/src/main/java/io/tapstate/control/client/HttpControlClient.java
  • cli/src/test/java/io/tapstate/cli/ReplTest.java
  • app/src/main/java/io/tapstate/app/SecretTrackingArtifactStore.java
  • control/control-core/src/test/java/io/tapstate/control/core/SourceServiceTest.java
  • control/rest-api/src/test/java/io/tapstate/control/restapi/AuthTest.java
  • control/mcp-server/src/main/java/io/tapstate/mcp/McpOperationExecutor.java

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.

2 participants