From 5db313ae7ca1d42fe6bdb3309b2dd54ab91fe461 Mon Sep 17 00:00:00 2001 From: adiel-sammak <71227923+aelsammak@users.noreply.github.com> Date: Wed, 19 Aug 2026 15:36:19 -0400 Subject: [PATCH 1/3] EBP-2889: Address session-analysis feedback on solace-application-development - Promote buried mandates to SKILL.md invariants: AI-assisted disclaimer header, verification artifacts (checklist + verify.sh + hooks), and the live-broker heads-up; extend the doc-grounding invariant to ban substitute channels and the version invariant to ban the solrsearch index - Surface the Step 0 door-question gate and the environment-discovery rule (a found broker/config never answers a question) in jcsmp.md, design-mode.md, and implement-mode Step 0 - Split implement-mode.md into a core file plus three per-leaf wiring files so the core fits a single Read; state the generation file-output contract up front and move the checklist write into Step 4 generation output - Redesign verification to be shape-agnostic: verify.sh gains an app stage driven by a generated verify-hooks.sh (START_CMD/TRIGGER_CMD/READY_MARKER/PASS_MARKER), a warn-only conformance preflight, and a live-run heads-up line; curl may trigger but never render the verdict - Replace the log4j2 2.26.0 pin with outcome rules: resolve the highest stable 2.x from authoritative metadata, framework-owned SLF4J backends win, config file required, com.solacesystems loggers never silenced, and the 2.17.1 Log4Shell floor enforced as a check - Bind doc grounding to each design question and redefine the Grounding docs field to fetched-pages-only; add a Generation conformance group to the verification checklist - Remove the mandated Core Concepts intro from SKILL.md and prerequisites.md; prerequisites is now broker acquisition only - Solace Suggested decoupling: keep the two-project default but require a surfaced conflict with three resolutions on single-deliverable asks; never merge silently - Rename the Java API Release Notes link to JCSMP API Release Notes (the JAVA_API_RN download serves the JCSMP release notes) and point sample logging comments at the Step 3 rule - Root-caused from 10 analyzed sessions; violations and fixes tracked in EBP-2889 --- .../solace-application-development/SKILL.md | 9 +- .../references/jcsmp.md | 28 +- .../references/jcsmp/custom-mode.md | 4 +- .../references/jcsmp/design-mode.md | 15 +- .../jcsmp/evals/compile-fixture/pom.xml | 7 +- .../jcsmp/implement-direct-pubsub.md | 34 ++ .../jcsmp/implement-guaranteed-pubsub.md | 73 ++++ .../references/jcsmp/implement-mode.md | 326 ++++++------------ .../jcsmp/implement-request-reply.md | 78 +++++ .../jcsmp/jcsmp-direct-publisher-sample.java | 6 +- .../jcsmp/jcsmp-direct-replier-sample.java | 6 +- .../jcsmp/jcsmp-direct-requestor-sample.java | 6 +- .../jcsmp/jcsmp-direct-subscriber-sample.java | 6 +- .../jcsmp-guaranteed-publisher-sample.java | 6 +- .../jcsmp-guaranteed-replier-sample.java | 6 +- .../jcsmp-guaranteed-requestor-sample.java | 6 +- .../jcsmp-guaranteed-subscriber-sample.java | 6 +- .../jcsmp/jcsmp-solace-connection-config.java | 4 + .../references/jcsmp/prerequisites.md | 9 +- .../references/jcsmp/scripts/verify.sh | 160 ++++++++- .../references/jcsmp/solace-suggested-mode.md | 14 +- .../jcsmp/verification-checklist.md | 13 +- 22 files changed, 552 insertions(+), 270 deletions(-) create mode 100644 plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/implement-direct-pubsub.md create mode 100644 plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/implement-guaranteed-pubsub.md create mode 100644 plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/implement-request-reply.md diff --git a/plugins/solace-messaging-skills/skills/solace-application-development/SKILL.md b/plugins/solace-messaging-skills/skills/solace-application-development/SKILL.md index e8b100f..a291599 100644 --- a/plugins/solace-messaging-skills/skills/solace-application-development/SKILL.md +++ b/plugins/solace-messaging-skills/skills/solace-application-development/SKILL.md @@ -20,7 +20,7 @@ This skill is the front door for building Solace messaging applications grounded ## What is Solace -Ground the developer in the high-level model before discussing patterns or generating code. Solace is an event-driven messaging platform: an event broker carries messages between applications, brokers connect into an event mesh, publishers send to named topics, and consumers read either directly or from durable queues that subscribe to those topics (the publish and subscribe model). Solace Cloud is the primary broker this skill assumes for the work it generates; a self-hosted Software Broker or an existing Appliance are supported alternatives. Point the developer at the canonical [Solace Core Concepts](https://docs.solace.com/Get-Started/event-mesh-basics.md) page for the grounding, confirm they grasp the publish/subscribe and queue concepts, then continue. Do not paraphrase the page; link it. +Solace is an event-driven messaging platform: an event broker carries messages between applications, brokers connect into an event mesh, publishers send to named topics, and consumers read either directly or from durable queues that subscribe to those topics (the publish and subscribe model). Solace Cloud is the primary broker this skill assumes for the work it generates; a self-hosted Software Broker or an existing Appliance are supported alternatives. The canonical [Solace Core Concepts](https://docs.solace.com/Get-Started/event-mesh-basics.md) page is the doc grounding for this model. For topic-hierarchy and topic-architecture design questions, the co-installed solace-topic-best-practices skill reads the canonical Topic Architecture Best Practices page online and applies it to those decisions. @@ -39,8 +39,11 @@ If the API is unclear but the request is clearly about Solace messaging in Java, Non-negotiable rules that apply across every API. Apply all. 1. **Content sourcing**: Every named entity (API, parameter, configuration value) must be traceable to a canonical Solace source. Do not assert behavior, defaults, or best practices that are not in the grounding documentation. -2. **WebFetch-on-demand doc grounding**: This skill bundles no documentation. When a step needs documentation content to ground a generation or design decision, WebFetch the live canonical page on demand (its `docs.solace.com` `.md` URL, the same link the reference files carry), then quote or summarize the fetched page. Do not answer from memory and do not paraphrase guidance the page does not contain. Do not restate doc content in the skill; link the live `.md` URL. A short list of references stays live for the same reason it always has (Javadoc HTML, the Java API Release Notes, Maven Central, the tutorials, and the GitHub samples) because those have no `docs.solace.com` `.md` form. -3. **No hardcoded versions**: Never pin a library version anywhere in skill content. The generated build resolves the latest release of the relevant Solace coordinate at generation time. Each API's entry file names the exact coordinate to use. +2. **WebFetch-on-demand doc grounding**: This skill bundles no documentation. When a step needs documentation content to ground a generation or design decision, WebFetch the live canonical page on demand (its `docs.solace.com` `.md` URL, the same link the reference files carry), then quote or summarize the fetched page. Do not answer from memory and do not paraphrase guidance the page does not contain. Do not restate doc content in the skill; link the live `.md` URL. Never substitute another channel for that WebFetch: grounding taken from a docs chatbot, a search tool, or decompiling a jar does not satisfy this invariant, and a page never fetched in the session must never be cited as grounding. A short list of references stays live for the same reason it always has (Javadoc HTML, the JCSMP API Release Notes, Maven Central, the tutorials, and the GitHub samples) because those have no `docs.solace.com` `.md` form. +3. **No hardcoded versions**: Never pin a library version anywhere in skill content. The generated build resolves the latest release of every dependency at generation time from the AUTHORITATIVE Maven repository metadata; each API's entry file names the exact coordinate and carries the exact lookup command. Never read a version from the legacy `search.maven.org/solrsearch` index: it lags the repository metadata and reports stale versions. Security floors (a minimum version below which known CVEs live) are enforced as checks on the resolved result, never as pins. +4. **AI-assisted disclaimer header**: Every source file generated under this skill starts with this exact line: `AI-assisted code. Review before production use.` Directly below it: `See the verification checklist: solace-verification-checklist.md`. This applies to EVERY generated source file, including custom builds, web apps, and embedded shapes that adapt the reference samples outside the canonical generator. The pom and other non-source artifacts are exempt; the bundled reference samples themselves carry no header. +5. **Verification artifacts, always**: Any session that generates Solace code from this skill writes a tailored `solace-verification-checklist.md` beside the code as generation output, copies the bundled `verify.sh` (plus its generated `verify-hooks.sh`) into the project, and ends in a real verify run or the compile-only fallback with the exact handed-back commands. The `VERIFY:` markers are non-optional in generated Solace messaging code, whatever the app shape. An improvised check (for example curl against the app's own HTTP API) may TRIGGER traffic but never renders the verdict; the verdict comes from the markers and the verify exit code. +6. **Live-broker heads-up**: Immediately before starting ANY process that will connect to a live broker — verify.sh, `java -jar`, a `mvn` run, a run script, anything — print one line that names the broker host and states the actual broker-side effects of this run (the queues it provisions, the connections it opens, the messages it publishes), scaled to the real app. Environment discovery is never consent and never an answer: a running local broker, an existing config file, or found credentials are facts to report, not answers to consume; report them, then still ask which broker the developer wants to target. ## Reference Files (read on-demand only) diff --git a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp.md b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp.md index a2784e9..6a89197 100644 --- a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp.md +++ b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp.md @@ -12,13 +12,26 @@ Determine the user's intent and enter the appropriate mode: |---|---|---| | "Help me choose the Solace JCSMP messaging pattern for a new app" / "Topic or queue?" / "What delivery semantics should I use?" | **Design** | Read `jcsmp/design-mode.md` | | "Build me a Solace JCSMP publisher/consumer app" / "Generate a Maven JCSMP project" / "Publish to a topic and consume from a queue" | **Implement** | Read `jcsmp/implement-mode.md` | +| "Build a web app / dashboard / service that connects to Solace", Spring Boot, or any embedded shape where the JCSMP layer lives inside a larger application | **Implement** | Read `jcsmp/implement-mode.md`; generate the messaging layer per its leaf rules and verify with the `app` stage of `verify.sh` (its Step 5) | | "My JCSMP app is throwing on connect" / "Why is my consumer not binding the queue?" | **Debug** | Debug mode is not yet available in this release. Redirect the user to the canonical Solace JCSMP troubleshooting documentation: [JCSMP API Home](https://docs.solace.com/API/Messaging-APIs/JCSMP-API/jcsmp-api-home.md). Do not generate debugging guidance from memory. | If unclear, default to **Design**. Understand the messaging problem before generating code. +Two gates hold on every path: + +- **Implement mode opens with a mandatory door question.** Ask Quickstart, Solace Suggested, or Custom — before you request or accept ANY broker details (`jcsmp/implement-mode.md` Step 0). Ask it even when a `config.json` with credentials already exists, even when a local broker is already running, and even when the requested app is bigger than the canonical generated shape; none of those answers the question. +- **Environment discovery never answers a question.** A running broker container, an existing config file, or found credentials are facts to report, not answers to consume. Report what you found, then still ask which broker the developer wants to target. + ## JCSMP coordinate -The single Solace dependency for this API is `com.solacesystems:sol-jcsmp`. Use the groupId `com.solacesystems` exactly; the shorter `com.solace` form does not resolve on Maven Central. Resolve the latest release at generation time (the no-hardcoded-versions invariant); never pin a `sol-jcsmp` version in skill content. +The single Solace dependency for this API is `com.solacesystems:sol-jcsmp`. Use the groupId `com.solacesystems` exactly; the shorter `com.solace` form does not resolve on Maven Central. Resolve the latest release at generation time (the no-hardcoded-versions invariant); never pin a `sol-jcsmp` version in skill content. Resolve it from the authoritative metadata with exactly this command, and read the `` element: + +```bash +curl -s https://repo1.maven.org/maven2/com/solacesystems/sol-jcsmp/maven-metadata.xml \ + | grep -oE '[^<]+' +``` + +Do NOT read the version from the legacy solrsearch index (`search.maven.org/solrsearch`): it lags behind the repository metadata and reports a stale `latestVersion`. ## Canonical doc links @@ -31,17 +44,20 @@ JCSMP-specific grounding. Each page below is a live `docs.solace.com` `.md` URL, Live exceptions (these have no `docs.solace.com` `.md` form; use the live URL directly): - [JCSMP Javadoc](https://docs.solace.com/API-Developer-Online-Ref-Documentation/java/index.html) -- [Java API Release Notes](https://products.solace.com/download/JAVA_API_RN) +- [JCSMP API Release Notes](https://products.solace.com/download/JAVA_API_RN) (the `JAVA_API_RN` download serves the JCSMP release notes; it is NOT the newer Solace Messaging API for Java) - [sol-jcsmp on Maven Central](https://central.sonatype.com/artifact/com.solacesystems/sol-jcsmp) ## Mode and reference files (read on-demand only) -- `jcsmp/prerequisites.md`: Solace core-concepts grounding and broker acquisition (route here first if the developer has no reachable broker). +- `jcsmp/prerequisites.md`: broker acquisition (route here first if the developer has no reachable broker). - `jcsmp/design-mode.md`: choose the messaging pattern/topology before generating code. -- `jcsmp/implement-mode.md`: generate a runnable Maven JCSMP pub/sub project. +- `jcsmp/implement-mode.md`: generate a runnable Maven JCSMP project (any leaf, any app shape); its Step 4 dispatches onto the per-leaf wiring files below. +- `jcsmp/implement-guaranteed-pubsub.md`: the Guaranteed Pub/Sub leaf wiring (read from implement-mode Step 4). +- `jcsmp/implement-direct-pubsub.md`: the Direct Pub/Sub leaf wiring (read from implement-mode Step 4). +- `jcsmp/implement-request-reply.md`: both Request-Reply leaf wirings (read from implement-mode Step 4). - `jcsmp/solace-suggested-mode.md`: the leaf-agnostic Solace Suggested overlay (TLS secure session, DMQ on PERSISTENT, HA failover, separate Maven projects) reached from implement-mode Step 0 on the Solace Suggested path. - `jcsmp/custom-mode.md`: the thin a la carte Custom overlay reached from implement-mode Step 0 on the Custom path; a leaf-aware checklist of the hardening knobs (secure session, DMQ, HA, decoupled projects, admin-provisioned queue) that generates exactly the ticked subset, reusing the Solace Suggested overlay steps for the mapped knobs. -- `jcsmp/verification-checklist.md`: the master template of binary verification checks organized into three responsibility groups (delivered by this generation, the developer's responsibility, and verified by the round-trip); Implement mode Step 6 emits a tailored `solace-verification-checklist.md` into each generated project seeded for the chosen mode and reports the same in chat. -- `jcsmp/scripts/verify.sh`: the bundled stage-dispatched run-and-observe verify script (`verify.sh [pass]`) that Implement mode runs per generation stage against a reachable broker. +- `jcsmp/verification-checklist.md`: the master template of binary verification checks organized into responsibility groups (delivered by this generation, the developer's responsibility, verified by the round-trip, and generation conformance); Implement mode Step 4 writes a tailored `solace-verification-checklist.md` into each generated project as generation output, and Step 6 reports the same resolution in chat. +- `jcsmp/scripts/verify.sh`: the bundled stage-dispatched run-and-observe verify script (`verify.sh ...`) that Implement mode copies into every generated project and runs against a reachable broker; the `app` stage drives web and embedded shapes through a generated `verify-hooks.sh`. - `jcsmp/jcsmp-guaranteed-publisher-sample.java`: best-practices publisher sample (basic-auth connect, PERSISTENT publish to a topic, graceful shutdown). - `jcsmp/jcsmp-guaranteed-subscriber-sample.java`: best-practices consumer sample (provisions a durable queue + topic subscription, CLIENT-ack flow). diff --git a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/custom-mode.md b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/custom-mode.md index 300dc87..ec2ed7c 100644 --- a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/custom-mode.md +++ b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/custom-mode.md @@ -2,7 +2,7 @@ The Custom overlay for Implement mode. This file is reached from `implement-mode.md` Step 0 when the developer chooses the Custom door. Custom is a la carte: the developer ticks which hardening knobs to include, and generation applies exactly the ticked subset and nothing more. It is a thin overlay. It does NOT re-describe how each knob works. For the four knobs that map to a `solace-suggested-mode.md` step, it points at that step and reuses it, so `solace-suggested-mode.md` stays the single source of truth for each hardening delta. -This overlay defers to `implement-mode.md` for all leaf and sample mechanics, exactly the way `solace-suggested-mode.md` does: the Step 2 design-summary input contract, the Step 4 leaf dispatch (which sample pair each leaf reads, and the near-verbatim adaptation discipline), the AI-assisted disclaimer header on every generated class, the explicit single-class imports invariant (keep every import explicit, per `implement-mode.md`; never collapse to a wildcard), and the four-file `VERIFY:` marker contract. Custom adds NO verify stage and changes NO `VERIFY:` marker string. +This overlay defers to `implement-mode.md` for all leaf and sample mechanics, exactly the way `solace-suggested-mode.md` does: the Step 2 design-summary input contract, the Step 4 leaf dispatch (which leaf wiring file and sample pair each leaf reads, and the near-verbatim adaptation discipline), the AI-assisted disclaimer header on every generated class, the explicit single-class imports invariant (keep every import explicit, per `implement-mode.md`; never collapse to a wildcard), and the fixed `VERIFY:` marker contract. Custom adds NO verify stage and changes NO `VERIFY:` marker string. Any live run on the Custom path prints the same one-line live-broker heads-up (SKILL.md Invariant 6) immediately before it starts: name the broker host and the run's actual broker-side effects. ## The checklist model: one up-front multi-select @@ -62,7 +62,7 @@ Because the ticked app no longer provisions anything, an autonomous round-trip w ## Return to the implement-mode contracts -After capturing the ticked subset and applying the deltas above, return to `implement-mode.md` for the unchanged leaf mechanics: the Step 4 dispatch generates the chosen leaf's classes, and Step 6 resolves each `verification-checklist.md` item to its state for the Custom ticked subset, writes the tailored `solace-verification-checklist.md` into the project (one copy per project root when the decoupled-projects knob is ticked, otherwise one at the project root), invites the developer's additions, and reports the same in chat, the same way it does on Quickstart and Solace Suggested. This overlay selects which hardening deltas to layer; it does not replace those contracts. +After capturing the ticked subset and applying the deltas above, return to `implement-mode.md` for the unchanged leaf mechanics: the Step 4 dispatch generates the chosen leaf's classes and writes the tailored `solace-verification-checklist.md` into the project as generation output (one copy per project root when the decoupled-projects knob is ticked, otherwise one at the project root), and Step 6 resolves each item to its state for the Custom ticked subset, invites the developer's additions, and reports the same in chat, the same way it does on Quickstart and Solace Suggested. This overlay selects which hardening deltas to layer; it does not replace those contracts. ## Grounding references diff --git a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/design-mode.md b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/design-mode.md index 9ea6e22..2064fdb 100644 --- a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/design-mode.md +++ b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/design-mode.md @@ -6,12 +6,9 @@ The output of this mode is a filled-in design summary the developer can carry in ## Step 0: Confirm broker access and grounding -Before triaging anything, confirm two things (skip whichever the developer already has): +Before triaging anything, confirm the developer has access to a reachable broker. Confirm that they HAVE one; Design mode only records the broker TYPE (for example, Solace Cloud) in the summary, so do NOT ask them to paste connection details (host, message VPN, client username, password) into the chat. Those values are handled later in Implement mode, where Solace Suggested credentials go only into a gitignored `config.json` (Quickstart falls back to CLI args when it is absent). A broker discovered running in the environment (a local container, an existing config file) is a fact to report, never an answer: still confirm which broker the developer wants to target. -- The developer has access to a reachable broker. Confirm that they HAVE one; Design mode only records the broker TYPE (for example, Solace Cloud) in the summary, so do NOT ask them to paste connection details (host, message VPN, client username, password) into the chat. Those values are handled later in Implement mode, where Solace Suggested credentials go only into a gitignored `config.json` (Quickstart falls back to CLI args when it is absent). -- The developer understands the basic Solace model (broker, topics, queues, publish/subscribe). - -If either is missing, route through `prerequisites.md` first (Core Concepts grounding and broker acquisition, Solace Cloud recommended), then return here. +If the developer has no reachable broker, route through `prerequisites.md` first (broker acquisition, Solace Cloud recommended), then return here. ## Step 1: Read the prompt for stated intent before asking anything @@ -38,7 +35,7 @@ If a row matches, or if your own judgment maps an unmatched prompt cleanly to on ## Step 2: Walk the decision tree to a leaf -Ask only the questions the prompt left open, in this order. Read each grounding link only when that question comes up; link the canonical doc and state the decision in one line, never paraphrase the doc. +Ask only the questions the prompt left open, in this order. Before you answer each question you do walk, WebFetch its grounding page first — even when the prompt pre-answers the question — then link the canonical doc and state the decision in one line, quoting one line from the fetched page; never paraphrase guidance the page does not contain. A page you did not fetch in this session must not appear in the summary's Grounding docs field. 1. **Interaction shape: request-reply or publish/subscribe?** Grounded in [Message Exchange Patterns](https://docs.solace.com/Get-Started/message-exchange-patterns.md). Decision: a request that expects a correlated reply is Request-Reply; an event fanned out to one or many independent consumers is publish/subscribe. 2. **Delivery guarantee: direct or guaranteed?** This ONE question applies to both interaction shapes, so ask it next regardless of the answer to question 1. Grounded in [Message Delivery Modes](https://docs.solace.com/Get-Started/message-delivery-modes.md). Decision: direct is fire-and-forget with no broker-side persistence, for high-rate flows that tolerate occasional loss; guaranteed (PERSISTENT) is persisted and survives consumer downtime. @@ -84,7 +81,7 @@ Solace JCSMP Design Summary - Consumption endpoint: - Auth: Basic username/password - Broker: Solace Cloud -- Grounding docs: +- Grounding docs: ``` The eight fields are fixed: Pattern, Delivery, Access type, Topic, Consumption endpoint, Auth, Broker, Grounding docs. Formatting is at your discretion, but always present all eight, every run, with explicit `n/a` where a field does not apply. The eight field NAMES are fixed; the field VALUES record the design's actual choices, defaulting to the base-leaf shape and holding the variant topology when the design deviates (the variant path in Step 1). The `Pattern` value stays one of the six leaf strings verbatim even for a variant; the deviation lives in the topology fields (`Consumption endpoint`, and `Delivery` or `Access type` where relevant), never in a new leaf string. @@ -98,6 +95,6 @@ Field semantics: - **Topic.** The recommended topic string or hierarchy, derived by applying the solace-topic-best-practices skill to the use case (not chosen by interrogating the developer). Always publish to topics; never address a queue directly. - **Auth.** Basic username/password. - **Broker.** Default to `Solace Cloud` and record it without asking. Switch to another type (Software Broker or Appliance) ONLY if the developer explicitly insists; otherwise leave it as `Solace Cloud`. -- **Grounding docs.** The canonical pages this design referenced. +- **Grounding docs.** ONLY the pages this session actually WebFetched. Never list a page you did not fetch: a citation without a fetch is fabricated grounding. Write `none fetched` when no page was fetched; that is an honest value, an unfetched citation is not. -After presenting the summary in chat, close Design mode explicitly so the developer knows exactly what happens next; do NOT just display the summary and stop. If the design is a variant of its leaf, state the deviation and its rationale in one line here too, so the developer approves the actual topology (not just the leaf name) before it is built. Ask them directly, in one step, both whether they are happy with this design AND whether to save it to `solace-design.md` in their project (for example: "Happy with this design? If so, I can save it to `solace-design.md` and move into Implement mode to generate the runnable Maven project."). Write the file only on their OK; never write it unprompted. Once they confirm, state the next step plainly: the work moves into Implement mode, which generates the runnable Maven project from this summary. Implement mode treats this summary, whether it lives in the chat or in the saved `solace-design.md`, as its input contract. +After presenting the summary in chat, close Design mode explicitly so the developer knows exactly what happens next; do NOT just display the summary and stop. If the design is a variant of its leaf, state the deviation and its rationale in one line here too, so the developer approves the actual topology (not just the leaf name) before it is built. Ask them directly, in one step, both whether they are happy with this design AND whether to save it to `solace-design.md` in their project (for example: "Happy with this design? If so, I can save it to `solace-design.md` and move into Implement mode to generate the runnable Maven project."). Write the file only on their OK; never write it unprompted. Once they confirm, state the next step plainly: the work moves into Implement mode, which generates the runnable Maven project from this summary. Implement mode treats this summary, whether it lives in the chat or in the saved `solace-design.md`, as its input contract. Implement mode OPENS with its own Step 0 door question (Quickstart, Solace Suggested, or Custom), asked before any broker details are requested or accepted; an existing `config.json` or a running broker does not answer it, so ask it on the way in. diff --git a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/evals/compile-fixture/pom.xml b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/evals/compile-fixture/pom.xml index 4d71da3..eb9016d 100644 --- a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/evals/compile-fixture/pom.xml +++ b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/evals/compile-fixture/pom.xml @@ -13,8 +13,11 @@ The sol-jcsmp version is intentionally version-free here (Invariant 3): the property default below is a NON-NUMERIC placeholder token that compile.sh overrides at run time with the live from Maven Central metadata. - No sol-jcsmp version number is committed anywhere. log4j2 is the deliberate - pinned exception (2.26.0, at/above the 2.17.1 Log4Shell floor). + No sol-jcsmp version number is committed anywhere. The log4j2 version below + is FIXTURE-ONLY tooling (a concrete number so this CI fixture compiles + deterministically, at/above the 2.17.1 Log4Shell floor); it is NOT a skill + rule. Generated projects resolve their logging backend live per + implement-mode.md Step 3 and never pin it. --> com.solace.samples diff --git a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/implement-direct-pubsub.md b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/implement-direct-pubsub.md new file mode 100644 index 0000000..e2b68c8 --- /dev/null +++ b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/implement-direct-pubsub.md @@ -0,0 +1,34 @@ +# Implement: Direct Pub/Sub leaf + +The per-leaf wiring for the Direct Pub/Sub leaf. Read this file from `implement-mode.md` Step 4 when the `Pattern` field is `Direct Pub/Sub`. The shared Step 4 rules in `implement-mode.md` (the AI-assisted disclaimer header, the shared `SolaceConnectionConfig` helper, the variant discipline, the demo-harness rule, and the explicit-single-class-imports rule) apply here unchanged. + +Generation reads the direct pub/sub sample pair (`jcsmp-direct-publisher-sample.java` + `jcsmp-direct-subscriber-sample.java`) and adapts each into a generated class, the same near-verbatim discipline the guaranteed flow uses. Direct messaging is at-most-once: there is no broker ACK and no redelivery, so this leaf has NO queue, NO provisioning, NO CLIENT-ack, and NO publish-ACK callback. Read the two samples by their exact relative paths and adapt each into its own generated class (a Publisher and a Subscriber), each building its own `JCSMPSession`. The named-exec rule from the guaranteed flow applies here unchanged. + +- `jcsmp-direct-publisher-sample.java`: basic-auth connect with reconnect-configured channel properties, a continuous DIRECT publish loop with a rotating payload to a dynamic topic (mirroring the guaranteed publisher's send flow), a stats thread, and a graceful SIGINT shutdown. No publish-ACK callback and no correlation key (direct has no broker ACK). Generation STRIPS the unbounded loop, stats thread, and SIGINT hook down to a short bounded burst of DIRECT messages that self-exits (Publisher class steps below), the same way the guaranteed publisher's loop is stripped, so the generated Publisher runs in the foreground and ends on its own. +- `jcsmp-direct-subscriber-sample.java`: basic-auth connect, a topic subscription confirmed via `session.addSubscription(topic, true)` (the `true` waits for broker confirm; no queue, no provisioning), an async DIRECT consumer started with `consumer.start()`, `message.getDiscardIndication()` for at-most-once egress-discard evidence in `onReceive`, and the SIGINT graceful-shutdown hook (the Subscriber is the long-running SIGINT target). + +## Subscriber class (near-verbatim `DirectSubscriber`) + +The Subscriber subscribes directly to the topic (no queue), consumes asynchronously, and is the long-running process the developer runs FIRST. Start the file with the disclaimer header (the exact two lines `implement-mode.md` Step 4 defines), then wire it in this sequence: + +1. Build basic-auth `JCSMPProperties` (`host`, `vpn_name`, `username`, `password`) via the shared `SolaceConnectionConfig` helper (`config.json` if present, else CLI args). Set `JCSMPProperties.REAPPLY_SUBSCRIPTIONS` to `true` on those properties BEFORE `createSession` (it defaults to false): a Direct topic subscription is a client-side session subscription, not held on a broker queue, so without this it is lost on an automatic reconnect and delivery silently stops. +2. `JCSMPFactory.onlyInstance().createSession(...)`, passing the sample's `SessionEventHandler`, then `session.connect()`. Immediately after `connect()` returns, emit the connect marker: `trace("VERIFY: CONNECTED");`. +3. Acquire the async consumer with `session.getMessageConsumer(listener)`, add the topic subscription with `session.addSubscription(topic, true)` (the `true` waits for broker confirm, so the route is live before the subscriber reports ready, since direct is at-most-once), then `consumer.start()`. Immediately after `consumer.start()` returns, emit the subscribed marker: `trace("VERIFY: SUBSCRIBED");`. There is NO queue and NO `session.provision(...)` on the direct path. +4. In the listener's `onReceive`, count the message and call `message.getDiscardIndication()` for at-most-once egress-discard evidence; immediately emit the receipt marker: `trace("VERIFY: MESSAGE_RECEIVED");`. There is NO `ackMessage()` and NO CLIENT-ack on the direct path (direct is at-most-once, the broker holds no copy and expects no acknowledgement). +5. KEEP the long-running loop and the SIGINT graceful-shutdown hook from the sample. The Subscriber is the SIGINT target: on SIGINT it calls `session.closeSession()` directly (closing the session also closes the consumer; direct has no acknowledgements to drain), printing `Shutdown signal received` so the verify script sees the shutdown-hook proof line. + +## Publisher class (near-verbatim `DirectPublisher`) + +The Publisher only connects and publishes a short bounded burst of DIRECT messages, then exits on its own; it does NOT provision anything and does NOT wait for a key press or a signal. Start the file with the disclaimer header (the exact two lines `implement-mode.md` Step 4 defines), then wire it in this sequence: + +1. Build basic-auth `JCSMPProperties` (`host`, `vpn_name`, `username`, `password`) via the shared `SolaceConnectionConfig` helper (`config.json` if present, else CLI args). +2. `JCSMPFactory.onlyInstance().createSession(...)`, passing the sample's `SessionEventHandler`, then `session.connect()`. Immediately after `connect()` returns, emit the connect marker: `trace("VERIFY: CONNECTED");`. +3. Acquire the producer with a minimal mandatory streaming-publish event handler (`session.getMessageProducer(handler)`). The handler parameter is mandatory even for direct: a `null` handler is rejected at runtime with "Blocking publishing mode is not supported". Direct messaging is at-most-once with no broker ACK, so the handler's `responseReceivedEx` is not expected to fire and `handleErrorEx` only logs the error (rendering the broker subcode when present); there is no correlation key. Do NOT pass `null`, and do NOT wait on an ACK: direct has no broker ACK to act on (ACK/NACK handling and correlation keys are the guaranteed publisher's contract). +4. Set the delivery mode EXPLICITLY with `message.setDeliveryMode(DeliveryMode.DIRECT)` and publish a SHORT BOUNDED BURST to the design topic via `producer.send(message, topic)` in a small fixed-count loop, then self-exit (close the session and return from `main`). DIRECT is the API default, but stating it makes the at-most-once intent unambiguous. REMOVE the sample's UNBOUNDED ENTER/SIGINT publish loop, its rotating-payload/dynamic-topic illustration, its stats thread, and its SIGINT shutdown hook (the sample mirrors the guaranteed publisher's send flow; here it collapses to a bounded burst): the generated Publisher publishes its burst and terminates by itself, not on a key press or a signal, so the verify script can run it in the foreground and wait for it to exit. Direct has no outstanding ACKs to drain, so no pre-close sleep is needed. The SIGINT shutdown hook belongs to the long-running Subscriber, not the Publisher. See [Message Delivery Modes](https://docs.solace.com/API/API-Developer-Guide-JCSMP/JCSMP-API-Message-Delivery-Modes.md). +5. The Publisher emits NO send-side marker. Direct has no broker ACK, so there is no `PUBLISH_ACKED` analog; a marker after `producer.send` would only prove the local call returned, not that the broker accepted the message, which is misleading for an at-most-once path. The real proof of the round-trip is `VERIFY: MESSAGE_RECEIVED` on the Subscriber side. + +## The marker contract and the named exec executions + +These three `VERIFY:` marker strings for the Direct Pub/Sub leaf (`VERIFY: CONNECTED`, `VERIFY: SUBSCRIBED`, `VERIFY: MESSAGE_RECEIVED`) are a fixed contract with `scripts/verify.sh` (the `direct` stage greps the Subscriber log for `VERIFY: SUBSCRIBED` as its readiness gate and for `VERIFY: MESSAGE_RECEIVED` as its pass condition). The marker MUST reach stdout: emit it via `trace(...)`, whose generated body prints with `System.out.println`, NOT via `logger.debug`. This is the single highest silent-failure risk in the whole verify flow. The strings here and the strings the script greps for MUST stay in sync character-for-character; if you change one marker string, change it in `scripts/verify.sh` in the same effort. + +**Expose both mains to the verify script via two named exec executions.** Just like the guaranteed flow, the generated pom MUST declare two `exec-maven-plugin` `` blocks for the direct pair, one with `publisher` and one with `subscriber`, each carrying its own `` set to that class's fully-qualified name. The `direct` stage addresses them as `mvn exec:java@subscriber` (started first, the readiness gate) and `mvn exec:java@publisher` (the foreground burst). Keep the `exec-maven-plugin` version resolved at generation time. diff --git a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/implement-guaranteed-pubsub.md b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/implement-guaranteed-pubsub.md new file mode 100644 index 0000000..ca6820d --- /dev/null +++ b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/implement-guaranteed-pubsub.md @@ -0,0 +1,73 @@ +# Implement: Guaranteed Pub/Sub leaves + +The per-leaf wiring for the three Guaranteed Pub/Sub leaves. Read this file from `implement-mode.md` Step 4 when the `Pattern` field is `Guaranteed Pub/Sub (single service, exclusive)`, `Guaranteed Pub/Sub (single service, non-exclusive)`, or `Guaranteed Pub/Sub (fan-out)`. The shared Step 4 rules in `implement-mode.md` (the AI-assisted disclaimer header, the shared `SolaceConnectionConfig` helper, the variant discipline, the demo-harness rule, and the explicit-single-class-imports rule) apply here unchanged. + +## Guaranteed pub/sub flow + +The skill ships two best-practices reference samples for this flow. Read them by their exact relative paths and adapt each into its own generated class. There is no merge and no third skeleton: the Publisher class is a near-verbatim adaptation of the publisher sample, and the Subscriber class is a near-verbatim adaptation of the consumer sample. Generating two near-verbatim classes is more reliable than synthesizing a single program from both, because the source shape is copied, not invented. + +- `jcsmp-guaranteed-publisher-sample.java`: basic-auth connect, PERSISTENT BytesMessage publish to a topic, publish ACK/NACK handler, session-event and reconnect-event handling that pauses publishing while the transport is down, SIGINT graceful shutdown that finishes outstanding ACKs. +- `jcsmp-guaranteed-subscriber-sample.java`: basic-auth connect, in-process durable-queue provisioning plus topic subscription, CLIENT-ack flow that ACKs only after processing, session-event and flow-event handling. + +Each class builds its OWN `JCSMPSession` (two sessions, one per class, matching the reference samples; there is no shared-session plumbing). Preserve the session-event and reconnect-event and publish ACK/NACK handlers in the Publisher, and the session-event and flow-event handlers in the Subscriber; they are part of the best-practices contract and must not be stripped during adaptation. + +The three guaranteed pub/sub leaves all generate the same two-class Publisher and Subscriber described below. The exclusive versus non-exclusive choice is realized inside the flow as a `setAccessType` snippet (read the `Access type` summary field), not a separate sample; the fan-out leaf reuses the single-service generator, repeating the queue-and-subscription setup once per consuming service. + +## Subscriber class (near-verbatim `GuaranteedSubscriber`) + +The Subscriber provisions the durable queue and the topic subscription, and it is the long-running process the developer runs FIRST. The Publisher does NOT provision anything. Start the file with the disclaimer header (the exact two lines `implement-mode.md` Step 4 defines), then wire the Subscriber in this canonical sequence, each step grounded in the docs linked in `implement-mode.md`'s Grounding references: + +1. Build basic-auth `JCSMPProperties` (`host`, `vpn_name`, `username`, `password`) via the shared `SolaceConnectionConfig` helper (`config.json` if present, else CLI args). Set `JCSMPProperties.IGNORE_DUPLICATE_SUBSCRIPTION_ERROR` to `true` BEFORE `createSession` (it defaults to false); otherwise `addSubscription` throws Subscription Already Exists on every run after the first (sample line 87). +2. `JCSMPFactory.onlyInstance().createSession(...)`, passing the consumer sample's `SessionEventHandler`, then `session.connect()`. Immediately after `connect()` returns, emit the connect marker: `trace("VERIFY: CONNECTED");`. +3. Best practice: check `session.isCapable(CapabilityType.ENDPOINT_MANAGEMENT)` before provisioning; exit cleanly if the broker disallows client-side endpoint management (sample lines 120-125). +4. `JCSMPFactory.onlyInstance().createQueue(queueName)` for the queue from the design summary. +5. Build the `EndpointProperties` and set the queue access type from the `Access type` summary field (see the access-type snippet below), then `session.provision(queue, endpointProps, JCSMPSession.FLAG_IGNORE_ALREADY_EXISTS)` so a re-run is idempotent. See [Provisioning a Durable Endpoint](https://docs.solace.com/API/API-Developer-Guide-JCSMP/JCSMP-API-Provisioning-Durable-End.md). +6. `session.addSubscription(queue, topic, JCSMPSession.WAIT_FOR_CONFIRM)` to map the topic onto the queue. See [Adding a Topic Subscription](https://docs.solace.com/API/API-Developer-Guide-JCSMP/JCSMP-API-Adding-Topic-Subscriptio.md). +7. Build a `ConsumerFlowProperties` flow with `setAckMode(JCSMPProperties.SUPPORTED_MESSAGE_ACK_CLIENT)`, create it with the consumer sample's `FlowEventHandler`, and `flow.start()`. Immediately after `flow.start()` returns, emit the queue-bound marker: `trace("VERIFY: QUEUE_BOUND");`. See [Acknowledging Messages](https://docs.solace.com/API/API-Developer-Guide-JCSMP/JCSMP-API-Acknowledging-Messages.md). +8. In the flow's `onReceive`, call `msg.ackMessage()` only after processing is complete; immediately after `ackMessage()`, emit the round-trip marker: `trace("VERIFY: MESSAGE_RECEIVED");`. +9. KEEP the long-running loop and the SIGINT graceful-shutdown hook from the consumer sample (sample lines 179-207). The Subscriber is the SIGINT target: on SIGINT it stops the flow, finishes outstanding ACKs, and calls `session.closeSession()`. + +**Access type (exclusive vs non-exclusive): a `setAccessType` snippet, not a separate sample.** The `Access type` field in the design summary picks one line. Set the queue access type on the `EndpointProperties` before provisioning (item 5 above): + +```java +EndpointProperties endpointProps = new EndpointProperties(); +endpointProps.setAccessType(EndpointProperties.ACCESSTYPE_EXCLUSIVE); // one active consumer / ordered failover +// endpointProps.setAccessType(EndpointProperties.ACCESSTYPE_NONEXCLUSIVE); // competing consumers / round-robin load balance +endpointProps.setPermission(EndpointProperties.PERMISSION_CONSUME); +``` + +When the `Access type` field reads exclusive, keep the `ACCESSTYPE_EXCLUSIVE` line: only one active consumer receives at a time, and on its disconnect a standby flow takes over in order (ordered failover). When it reads non-exclusive, use the `ACCESSTYPE_NONEXCLUSIVE` line instead: bound flows compete and receive in round-robin, so the load is shared across workers. Read the `Access type` field and choose the one line; do not emit both active. This is grounded in [Receiving Guaranteed Messages](https://docs.solace.com/API/API-Developer-Guide-JCSMP/JCSMP-API-Receiving-Guaranteed-Messages.md), which states an exclusive queue delivers in order with one active consumer and failover to the next flow, and a non-exclusive queue delivers round-robin across bound flows. + +## Publisher class (near-verbatim `GuaranteedPublisher`) + +The Publisher only connects and publishes; it does NOT provision the queue or the subscription (the Subscriber owns that, and starts first). It must exit on its own after the ack so the developer does not have to interrupt it. Start the file with the disclaimer header (the exact two lines `implement-mode.md` Step 4 defines), then wire the Publisher in this canonical sequence: + +1. Build basic-auth `JCSMPProperties` (`host`, `vpn_name`, `username`, `password`) via the shared `SolaceConnectionConfig` helper (`config.json` if present, else CLI args). +2. `JCSMPFactory.onlyInstance().createSession(...)`, passing the publisher sample's `SessionEventHandler`, then `session.connect()`. Immediately after `connect()` returns, emit the connect marker: `trace("VERIFY: CONNECTED");`. +3. Acquire the `XMLMessageProducer` with the publisher sample's publish ACK/NACK callback handler (`PublishCallbackHandler`), then register the publisher sample's `JCSMPReconnectEventHandler` through an empty never-started consumer (`session.getMessageConsumer(reconnectHandler, null)`). The docs ground this idiom for publish-only applications; keep it because it surfaces transport events that pause the send loop. In the ACK callback (`responseReceivedEx`), emit the publish-ACK marker: `trace("VERIFY: PUBLISH_ACKED");`. CRITICAL: the reference publisher sample logs the ACK at `logger.debug` (sample line 229), which is invisible in default output, so the verify script would never see it and would report a false code failure. The marker MUST reach stdout: emit it via `trace(...)`, whose generated body prints with `System.out.println`, NOT via `logger.debug`. This is the single highest silent-failure risk in the whole verify flow. +4. Publish a PERSISTENT `BytesMessage` to the topic (`DeliveryMode.PERSISTENT`). See [Message Delivery Modes](https://docs.solace.com/API/API-Developer-Guide-JCSMP/JCSMP-API-Message-Delivery-Modes.md). +5. EXIT on its own: after observing the ACK (the `VERIFY: PUBLISH_ACKED` marker), drain outstanding ACKs (the sample's `Thread.sleep(1500)` idiom at sample line 216), call `session.closeSession()`, and return from `main`. REMOVE the sample's ENTER/SIGINT loop (sample line 168): the Publisher must terminate by itself, not wait for a key press or a signal. The SIGINT shutdown hook belongs to the Subscriber, not the Publisher. + +## The marker contract and the named exec executions + +These four `VERIFY:` marker strings (`VERIFY: CONNECTED`, `VERIFY: PUBLISH_ACKED`, `VERIFY: QUEUE_BOUND`, `VERIFY: MESSAGE_RECEIVED`) are a fixed contract with `scripts/verify.sh`, which greps captured output PER PROCESS (the Subscriber log for `QUEUE_BOUND` and `MESSAGE_RECEIVED`, the Publisher log for `PUBLISH_ACKED`). Both classes connect, so both print `VERIFY: CONNECTED`; the script reads markers per-process for that reason. The strings here and the strings the script greps for MUST stay in sync character-for-character; if you change one marker string, change it in `scripts/verify.sh` in the same effort. + +**Expose both mains to the verify script via two named exec executions.** The fixed `scripts/verify.sh` runs each main without knowing the per-generation package, so the generated pom MUST declare two `exec-maven-plugin` `` blocks, one with `publisher` and one with `subscriber`, each carrying its own `` set to that class's fully-qualified name. The script then addresses them as `mvn exec:java@publisher` and `mvn exec:java@subscriber`. The script references the fixed execution ids, never the generated FQCN, so the package and class names stay your discretion at generation time. Keep the `exec-maven-plugin` version resolved at generation time (no hardcoded version), the same way the `sol-jcsmp` coordinate is resolved. + +```xml + + org.codehaus.mojo + exec-maven-plugin + RESOLVED_AT_GENERATION_TIME + + + publisher + com.example.app.OrderPublisher + + + subscriber + com.example.app.OrderSubscriber + + + +``` diff --git a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/implement-mode.md b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/implement-mode.md index 616e2a7..b9006cd 100644 --- a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/implement-mode.md +++ b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/implement-mode.md @@ -1,16 +1,35 @@ # Implement Mode -Generate a complete, runnable Maven JCSMP publish-subscribe project from a confirmed design summary. The generated output is TWO classes (a Publisher and a Subscriber) in ONE Maven project, the canonical solace-samples shape (one project, multiple mains). Each class is a near-verbatim adaptation of its reference sample. The Subscriber connects with basic auth, provisions its own durable queue, subscribes that queue to the topic, and consumes in a CLIENT-ack flow. The Publisher connects with basic auth and publishes a PERSISTENT message to the topic. Run the Subscriber first (it provisions the queue), then the Publisher, for a guaranteed pub/sub round-trip. Near-verbatim adaptation of two samples is more reliable than merging them into one program, and a real deployment lifts each class into its own service or project (see `verification-checklist.md`; Quickstart does not generate the decoupled two-project form). +Generate a complete, runnable Maven JCSMP project from a confirmed design summary. The canonical output is TWO classes (a Publisher and a Subscriber, or a Requestor and a Replier) in ONE Maven project, the canonical solace-samples shape (one project, multiple mains). Each class is a near-verbatim adaptation of its reference sample. Near-verbatim adaptation of two samples is more reliable than merging them into one program, and a real deployment lifts each class into its own service or project (see `verification-checklist.md`; Quickstart does not generate the decoupled two-project form). -Work through the steps in order. Read this file's grounding links only as each step needs them. Link the canonical doc and state the decision in one line, never paraphrase doc content. Resolve the `sol-jcsmp` version at generation time and pin no `sol-jcsmp` version in this skill (the Solace API coordinate stays fresh against Maven Central). log4j2 is the one deliberate exception: it is pinned to a known-good 2.x version, as Step 3 explains. +Implement mode is NOT only for the canonical shape. A web app, a dashboard, a service, or any embedded shape where the JCSMP layer lives inside a larger application follows the SAME steps: the same door question, the same leaf wiring for the messaging layer, the same generation outputs, and the same verification (the `app` stage of `verify.sh`). Building an off-catalog shape never waives a step in this file. + +Work through the steps in order. Read this file's grounding links only as each step needs them. Link the canonical doc and state the decision in one line, never paraphrase doc content. Resolve every dependency version at generation time; pin nothing (SKILL.md Invariant 3). Step 3 defines the logging backend's resolution and its security floor. + +## Generation outputs: the file contract + +Every Implement run writes ALL of these as generation output, on every door and every app shape. None of them is gated on consent, and none of them waits for the end of the session: + +1. The generated classes for the chosen leaf, plus the shared `SolaceConnectionConfig` helper. Every source file starts with the AI-assisted disclaimer header (Step 4). +2. `pom.xml` (or the leaf poms on a decoupled layout) with versions resolved per Step 3. +3. The logging config resource for the resolved backend (Step 3). +4. On Quickstart: `config.example.json` (placeholders) and a `.gitignore` that ignores `config.json` (Step 4). +5. A tailored `solace-verification-checklist.md` at each project root (tailoring rules in Step 6; the WRITE happens here at generation time, like the pom and the classes). +6. A copy of the bundled `scripts/verify.sh` at the project root, plus a generated `verify-hooks.sh` beside it (Step 5), so the verification commands resolve from inside the project. ## Step 0: Ask Quickstart, Solace Suggested, or Custom, before any broker details This is the FIRST question, and you ask it before you request or accept ANY broker connection details. Ask the developer plainly which of three doors they want: Quickstart, Solace Suggested, or Custom. Ask it as a direct question and wait for the answer. Frame the three doors honestly: Quickstart is a learning and try-it path to see a round-trip work, NOT a deployment target; Solace Suggested is the hardened baseline Solace suggests as a starting point, and Solace suggests it without guaranteeing it is ready for deployment; Custom lets the developer tick exactly which hardening knobs to include and generates that ticked subset and nothing more. Nothing has been generated yet and no credentials have been exchanged yet, so this is the up-front gate. The answer decides WHERE broker connection details belong, and it drives behavior for the rest of Implement mode. Every door generates the same classes (a Publisher and a Subscriber, plus the shared `SolaceConnectionConfig` helper that builds the connection `JCSMPProperties`). They differ in where the credential VALUES live: Solace Suggested puts them ONLY in a gitignored `config.json`, while Quickstart reads `config.json` when present and otherwise falls back to CLI args; Custom follows the Quickstart-shaped `config.json`-or-CLI convention by default, and `custom-mode.md` covers the decoupled-projects case where each project carries its own gitignored `config.json`. +Three things do NOT answer this question, and none of them waives it: + +- **An existing `config.json`**, even one that already holds real credentials. It tells you where credentials sit today; it does not tell you which door the developer wants. Ask anyway. +- **A running local broker discovered in the environment.** A discovered container or endpoint is a fact to report, never an answer to consume, neither for the door nor for WHICH broker to target. Report what you found, then ask both questions. +- **An app shape larger than the generated two-class project** (a web app, a dashboard, a service). The doors still govern the messaging layer and the credential placement. Ask anyway. + - **Quickstart (default).** Each class builds its connection through the shared `SolaceConnectionConfig` helper, which reads host, message VPN, username, and password from a `config.json` in the working directory when present and otherwise falls back to CLI args (the upstream `patterns/` samples' CLI-args style). Simplest path to a running round-trip, framed honestly as a learning path rather than a deployment target. Generate the stages without per-stage preview gates. Quickstart output fails fast: generation omits the samples' reconnect-tuning block so connect and reconnect settings stay at the JCSMP defaults (the fail-fast rule in Step 4). -- **Solace Suggested.** On the Solace Suggested path, read `solace-suggested-mode.md` and follow the Solace Suggested overlay (TLS, DMQ on PERSISTENT, HA failover, separate projects, and the `config.json` convention); the leaf and sample mechanics, the Step 4 dispatch, and the four-file `VERIFY:` contract stay here in implement-mode.md. When the developer picks Solace Suggested, ask a second question right here in Step 0: do they want Secure or Non-Secure? Secure is the TLS `tcps://` path for a broker that already has TLS configured, and it validates the server certificate. Non-Secure applies the same Solace Suggested hardening over a plaintext `tcp://` session for a dev or test broker that has no TLS configured, so there is no certificate to validate; state plainly that Non-Secure uses unencrypted transport and fits a dev or test broker, not real credentials or real data over untrusted networks. `solace-suggested-mode.md` reads that Secure or Non-Secure flag and forks only at its session build; every other part of the overlay is identical on both sub-modes. The overlay is leaf-agnostic: it rides on whatever `Pattern` leaf the design summary chose and only adds the Solace Suggested deltas. It is what Solace suggests as a hardened baseline, not a guarantee that the output is ready for deployment. -- **Custom.** On the Custom path, read `custom-mode.md` and follow it. Custom opens a SINGLE up-front multi-select checklist of the applicable hardening knobs, captured once, not a sequence of per-knob questions; generation then applies exactly the ticked subset and nothing more. Nothing ticked lands a Quickstart-shaped base app, everything ticked lands about the Solace Suggested Secure shape, and each ticked knob layers one Suggested delta on top of that floor. `custom-mode.md` owns the leaf-aware checklist content and the knob application; the leaf and sample mechanics, the Step 4 dispatch, and the four-file `VERIFY:` contract stay here in implement-mode.md, exactly as they do on the Solace Suggested path. +- **Solace Suggested.** On the Solace Suggested path, read `solace-suggested-mode.md` and follow the Solace Suggested overlay (TLS, DMQ on PERSISTENT, HA failover, separate projects, and the `config.json` convention); the leaf and sample mechanics, the Step 4 dispatch, and the fixed `VERIFY:` contract stay here in implement-mode.md and its leaf files. When the developer picks Solace Suggested, ask a second question right here in Step 0: do they want Secure or Non-Secure? Secure is the TLS `tcps://` path for a broker that already has TLS configured, and it validates the server certificate. Non-Secure applies the same Solace Suggested hardening over a plaintext `tcp://` session for a dev or test broker that has no TLS configured, so there is no certificate to validate; state plainly that Non-Secure uses unencrypted transport and fits a dev or test broker, not real credentials or real data over untrusted networks. `solace-suggested-mode.md` reads that Secure or Non-Secure flag and forks only at its session build; every other part of the overlay is identical on both sub-modes. The overlay is leaf-agnostic: it rides on whatever `Pattern` leaf the design summary chose and only adds the Solace Suggested deltas. It is what Solace suggests as a hardened baseline, not a guarantee that the output is ready for deployment. +- **Custom.** On the Custom path, read `custom-mode.md` and follow it. Custom opens a SINGLE up-front multi-select checklist of the applicable hardening knobs, captured once, not a sequence of per-knob questions; generation then applies exactly the ticked subset and nothing more. Nothing ticked lands a Quickstart-shaped base app, everything ticked lands about the Solace Suggested Secure shape, and each ticked knob layers one Suggested delta on top of that floor. `custom-mode.md` owns the leaf-aware checklist content and the knob application; the leaf and sample mechanics, the Step 4 dispatch, and the fixed `VERIFY:` contract stay here in implement-mode.md and its leaf files, exactly as they do on the Solace Suggested path. State plainly, grounded in the docs, that a real deployment lifts each class into its own service or project. Every door generates the same two classes in one project, unless the developer opts into decoupled projects on Solace Suggested or Custom; lifting each into its own project is the developer's next step beyond this generated project. See [JCSMP Best Practices](https://docs.solace.com/API/API-Developer-Guide-JCSMP/JCSMP-API-Best-Practices.md). @@ -18,12 +37,11 @@ Confirm which door the developer wants before continuing, and on the Solace Sugg ## Step 1: Confirm broker access and grounding -With the door chosen, confirm two things (skip whichever the developer already has): +With the door chosen, confirm the developer has access to a reachable broker, and confirm WHICH broker they want to target. Confirm that they HAVE one; do NOT ask them to paste connection details into the chat. A broker discovered in the environment (a running container, an existing config file) is a fact to report while asking, never an answer. Where the credential VALUES live depends on the Step 0 door: for Solace Suggested they go ONLY into the gitignored `config.json`; for Quickstart they reach the app through a `config.json` when present, otherwise as CLI args at run time. The skill itself never needs the values typed into the chat (see the honesty rule in Step 5: if the values are placeholders or withheld, compile only and hand the developer the exact commands to run against their own broker). -- The developer has access to a reachable broker. Confirm that they HAVE one; do NOT ask them to paste connection details into the chat. Where the credential VALUES live depends on the Step 0 door: for Solace Suggested they go ONLY into the gitignored `config.json`; for Quickstart they reach the app through a `config.json` when present, otherwise as CLI args at run time. The skill itself never needs the values typed into the chat (see the honesty rule in Step 5: if the values are placeholders or withheld, compile only and hand the developer the exact commands to run against their own broker). -- The developer understands the basic Solace model (broker, topics, queues, publish/subscribe). +If the developer has no reachable broker, route through `prerequisites.md` first (broker acquisition, Solace Cloud recommended), then return here. -If either is missing, route through `prerequisites.md` first (Core Concepts grounding and broker acquisition, Solace Cloud recommended), then return here. +Grounding catch-up: if the design summary names a page in its Grounding docs field that this session has not WebFetched, fetch it now before generating against it (SKILL.md Invariant 2; an unfetched citation is not grounding). ## Step 2: Establish the design as the input contract @@ -34,9 +52,9 @@ Implement mode works from the unified design summary of eight fields: Pattern, D - If no summary exists but the developer already knows their design, take the values directly from them and proceed (at minimum: the Pattern, the topic string, the `Consumption endpoint` the pattern requires (the queue name for the guaranteed pub/sub journey, `n/a` for patterns with no queue), and the message payload; Delivery and Auth default to PERSISTENT guaranteed and basic username/password for the guaranteed pub/sub journey). An experienced developer is NOT required to run Design mode or produce a `solace-design.md` first. If the developer does not supply a topic or a queue name, derive them rather than asking (the topic via the solace-topic-best-practices skill, the queue name via the `q.`-prefixed convention), the same way Design mode does; only the Pattern and payload genuinely need the developer's input. - Only route the developer to Design mode (`design-mode.md`) if they want help choosing the pattern/topology, or if the essential inputs above are missing and they cannot supply them. Do not invent design decisions; ask for the missing values or offer Design mode. -Treat the confirmed values (Pattern, topic string, `Consumption endpoint`, payload) as the requirements for everything that follows. The discriminated `Pattern` field carries the developer's chosen leaf, and Step 4 dispatches generation onto that leaf: it reads only the sample pair the chosen leaf needs. Read the design as the input contract and do not reject a summary because its `Pattern` is not pub/sub. +Treat the confirmed values (Pattern, topic string, `Consumption endpoint`, payload) as the requirements for everything that follows. The discriminated `Pattern` field carries the developer's chosen leaf, and Step 4 dispatches generation onto that leaf: it reads only the leaf file and sample pair the chosen leaf needs. Read the design as the input contract and do not reject a summary because its `Pattern` is not pub/sub. -## Step 3: Resolve the version, then write the pom and Maven layout +## Step 3: Resolve the versions, then write the pom and Maven layout ### Resolve the latest sol-jcsmp version at generation time @@ -57,10 +75,14 @@ Write `pom.xml` and the standard `src/main/java//` layout yourself, gro - **Single Solace dependency: `com.solacesystems:sol-jcsmp`.** Use the groupId `com.solacesystems` exactly. The Maven doc shows the groupId as `com.solace` (without the `systems` suffix), which is WRONG and does not resolve on Maven Central; always use `com.solacesystems`. Confirm the artifact at [Maven Central](https://central.sonatype.com/artifact/com.solacesystems/sol-jcsmp). - **No `solsuite` aggregate pom.** It was dropped at 10.29; depend on `sol-jcsmp` directly. -- **Logging: log4j2 `2.x`, two artifacts: `org.apache.logging.log4j:log4j-api` and `org.apache.logging.log4j:log4j-core`.** Both reference samples import log4j2 (`LogManager`, `Logger`), so neither generated class compiles without these artifacts. Also generate a `log4j2.xml` configuration resource at `src/main/resources/log4j2.xml` so it lands on the classpath (both samples carry the reminder "remember to add log4j2.xml to your classpath"). Pin both `log4j-api` and `log4j-core` to the same specific known-good 2.x version: `2.26.0`. Unlike `sol-jcsmp`, log4j2 is NOT resolved live from Maven Central metadata; the pin gives the developer a reproducible, vetted logging dependency. This matters for security: log4j2 `2.x` versions below `2.17.1` carry the critical Log4Shell vulnerabilities (CVE-2021-44228 family), so the pinned version MUST be at or above `2.17.1` (`2.26.0` satisfies that floor). Do not pull the log4j2 `3.x` beta; the live `` element currently reports a 3.x beta, which is exactly why a pin is safer here than live resolution. Updating the pin to a newer 2.x later is a deliberate maintenance action, not a silent live resolution on every generation. +- **Logging: one working backend, resolved like every other dependency; never pinned; never silenced.** The reference samples log through log4j2 (`LogManager`, `Logger`), so the DEFAULT backend is log4j2 (`org.apache.logging.log4j:log4j-api` plus `log4j-core`), and neither generated class compiles without a backend wired. Four outcome rules, in force on every door and every app shape: + 1. **Resolve, do not pin.** Resolve the backend's version at generation time from the authoritative Maven metadata, the same `maven-metadata.xml` mechanism `sol-jcsmp` uses. One caution: the log4j metadata's `` element can report a pre-release of the next major (a 3.x alpha or beta). The samples target the log4j2 2.x API, so resolve the HIGHEST STABLE 2.x from the metadata's `` list instead, excluding any alpha, beta, or RC: `curl -s https://repo1.maven.org/maven2/org/apache/logging/log4j/log4j-core/maven-metadata.xml | grep -oE '2[^<]*' | grep -viE 'alpha|beta|rc' | tail -1`. + 2. **Security floor, enforced as a check.** Any `log4j-core` on the classpath, including one dragged in transitively, MUST be at or above `2.17.1`: versions below it carry the critical Log4Shell vulnerabilities (CVE-2021-44228 family). The `verify.sh` preflight and the verification checklist test this floor; it is a check on the resolved result, never a pin. + 3. **A framework's own backend wins.** When the app is built on a framework that ships its own SLF4J backend (for example Spring Boot ships Logback), use the framework's backend instead of adding log4j2. NEVER put two logging backends on one classpath. Adapt the samples' log4j2 (`LogManager`/`Logger`) usage to the SLF4J API in that case, and hold the same floor discipline for that backend's own CVE floors. + 4. **Config file, visible Solace loggers.** Whatever the backend, generate its configuration resource on the classpath (`src/main/resources/log4j2.xml` for log4j2; the framework's equivalent otherwise) with a console appender, so diagnostics reach stdout. NEVER silence the Solace API's own logging: the `com.solacesystems` loggers stay at INFO or lower-threshold, because they carry the connect and reconnect diagnostics the developer needs most when something breaks. - **Java target:** Java 11 by default. It is the minimum the samples support, and they compile on 11 and up. Set `maven-compiler-plugin` to `release` 11; use a higher release only if the developer's environment requires it. groupId, artifactId, and package naming are your discretion. -Keep this skill `sol-jcsmp`-version-free. In any illustrative pom snippet, write the `sol-jcsmp` dependency version as a placeholder, never a concrete number (log4j2 is the deliberate exception, pinned to `2.26.0` as described above): +Keep this skill version-free. In any illustrative pom snippet, write every dependency version as a placeholder, never a concrete number: ```xml @@ -72,20 +94,24 @@ Keep this skill `sol-jcsmp`-version-free. In any illustrative pom snippet, write ## Step 4: Dispatch on the Pattern leaf, then generate the classes -Generation is keyed on the `Pattern` leaf read in Step 2. Each leaf reads its own sample pair plus the shared `jcsmp-solace-connection-config.java` helper; do not read samples for a leaf the design did not choose. The six discriminated leaf strings (spelled exactly as Design mode emits them) dispatch as follows: +Generation is keyed on the `Pattern` leaf read in Step 2. Each leaf has its own wiring file, which names its sample pair; read ONLY the leaf file the design chose, and do not read samples for a leaf the design did not choose. The six discriminated leaf strings (spelled exactly as Design mode emits them) dispatch as follows: -| `Pattern` leaf (verbatim) | Sample pair to read | Status | -|---------------------------|---------------------|--------| -| `Guaranteed Pub/Sub (single service, exclusive)` | `jcsmp-guaranteed-publisher-sample.java` + `jcsmp-guaranteed-subscriber-sample.java` | Generated here (the guaranteed pub/sub flow below) | -| `Guaranteed Pub/Sub (single service, non-exclusive)` | `jcsmp-guaranteed-publisher-sample.java` + `jcsmp-guaranteed-subscriber-sample.java` | Generated here (the guaranteed pub/sub flow below) | -| `Guaranteed Pub/Sub (fan-out)` | `jcsmp-guaranteed-publisher-sample.java` + `jcsmp-guaranteed-subscriber-sample.java` | Generated here (the guaranteed pub/sub flow below) | -| `Direct Pub/Sub` | `jcsmp-direct-publisher-sample.java` + `jcsmp-direct-subscriber-sample.java` | Generated here (the Direct Pub/Sub leaf below) | -| `Request-Reply (Direct)` | `jcsmp-direct-requestor-sample.java` + `jcsmp-direct-replier-sample.java` | Generated here (the Request-Reply (Direct) leaf below) | -| `Request-Reply (Guaranteed)` | `jcsmp-guaranteed-requestor-sample.java` + `jcsmp-guaranteed-replier-sample.java` | Generated here (the Request-Reply (Guaranteed) leaf below) | +| `Pattern` leaf (verbatim) | Leaf wiring file | Sample pair it reads | +|---------------------------|------------------|----------------------| +| `Guaranteed Pub/Sub (single service, exclusive)` | `implement-guaranteed-pubsub.md` | `jcsmp-guaranteed-publisher-sample.java` + `jcsmp-guaranteed-subscriber-sample.java` | +| `Guaranteed Pub/Sub (single service, non-exclusive)` | `implement-guaranteed-pubsub.md` | `jcsmp-guaranteed-publisher-sample.java` + `jcsmp-guaranteed-subscriber-sample.java` | +| `Guaranteed Pub/Sub (fan-out)` | `implement-guaranteed-pubsub.md` | `jcsmp-guaranteed-publisher-sample.java` + `jcsmp-guaranteed-subscriber-sample.java` | +| `Direct Pub/Sub` | `implement-direct-pubsub.md` | `jcsmp-direct-publisher-sample.java` + `jcsmp-direct-subscriber-sample.java` | +| `Request-Reply (Direct)` | `implement-request-reply.md` | `jcsmp-direct-requestor-sample.java` + `jcsmp-direct-replier-sample.java` | +| `Request-Reply (Guaranteed)` | `implement-request-reply.md` | `jcsmp-guaranteed-requestor-sample.java` + `jcsmp-guaranteed-replier-sample.java` | **Every leaf also generates the shared connection helper.** In addition to its pattern pair, each leaf reads `jcsmp-solace-connection-config.java` and generates it as a `SolaceConnectionConfig` class in the project. Each pattern class builds its basic-auth connection `JCSMPProperties` by calling `SolaceConnectionConfig.load(args, APP_NAME).toSessionProperties()` rather than setting `host`/`vpn_name`/`username`/`password` inline. The helper passes every flat string key in `config.json` through to `JCSMPProperties.setProperty` (host, vpn_name, and username are required; password is optional) and otherwise falls back to the CLI args, so it adds NO JSON-library dependency and keeps the single `sol-jcsmp` dependency intact. Like every generated class it carries the AI-assisted disclaimer header and keeps its imports explicit. -The three guaranteed pub/sub leaves all generate the same two-class Publisher and Subscriber described in the guaranteed pub/sub flow below. The exclusive versus non-exclusive choice is realized inside that flow as a `setAccessType` snippet (read the `Access type` summary field), not a separate sample; the fan-out leaf reuses the single-service generator, repeating the queue-and-subscription setup once per consuming service. +**Disclaimer header on EVERY generated source file.** Stamp the top of each generated source file (every class, in every shape; the pom is exempt as it is not source) with this exact line: `AI-assisted code. Review before production use.` Directly below it, add a pointer to the tailored verification checklist this generation writes into the project: `See the verification checklist: solace-verification-checklist.md`. That tailored `solace-verification-checklist.md` ships inside the generated project (this Step writes it as generation output), so the disclaimer points each generated file at the local checklist beside it rather than at a remote copy. Stamp the header AT WRITE TIME, as the first content of each new file, not as a later pass. The reference samples themselves carry NO disclaimer; that rule is the inverse. This rule is SKILL.md Invariant 4 and applies to every leaf's generated classes AND to custom or embedded builds that adapt the samples outside the canonical two-class shape. + +**Embedded and web-app shapes.** When the JCSMP layer sits inside a larger application (a web dashboard, a REST service, a framework app), the chosen leaf's wiring still governs the messaging layer: the same connection helper, the same session/flow/reconnect/ACK handlers, the same `VERIFY:` markers emitted through a stdout-reaching `trace(...)`, and the disclaimer header on every generated file, messaging or not. The `VERIFY:` markers are NOT demo harness; they stay in the messaging layer whatever the shape, because the `app` verify stage (Step 5) greps for them in the app's captured output. + +**Write the verification artifacts in this same step.** Alongside the classes and the pom, write: the tailored `solace-verification-checklist.md` at each project root (tailoring rules in Step 6; the write is generation output with NO consent gate), a copy of the bundled `scripts/verify.sh` at the project root, and the generated `verify-hooks.sh` (Step 5 defines its contract). A session that ends early still leaves the checklist and the verify entry points in the project. ### Adapting a sample to a variant of its leaf @@ -93,268 +119,120 @@ The samples are grounded REFERENCE for the API idioms, not templates that must b Hold every invariant while adapting: keep the shared connection helper, the session, flow, and reconnect handlers, the publish ACK/NACK handler, explicit single-class imports, the disclaimer header, and the leaf's `VERIFY:` markers. Any NEW named entity the variant introduces (an async reply listener, a durable reply endpoint, a redelivery setting) must be traceable to a canonical Solace source per Invariant 1: WebFetch the grounding page for it, the same way the base leaf grounds its idioms. A verified variant is fully first-class: it carries only the standard AI-assisted disclaimer, with no extra "hand-built" or "off-spec" caveat that would read as risky freelancing. -**Guardrail: variant versus off-catalog.** Adapt freely WITHIN the chosen pattern family when the change is a topology or reliability knob that the canonical docs ground and that still compiles, emits the leaf's `VERIFY:` markers, and is verifiable by the matching `verify.sh` stage (durable versus temporary reply queue, an async versus blocking reply listener, exclusive versus non-exclusive access, redelivery handling). If a requirement leaves the JCSMP topic/queue publish and subscribe model, or needs behavior the canonical docs cannot ground, STOP and tell the developer it is off-catalog rather than inventing it. +**Guardrail: variant versus off-catalog.** Adapt freely WITHIN the chosen pattern family when the change is a topology or reliability knob that the canonical docs ground and that still compiles, emits the leaf's `VERIFY:` markers, and is verifiable by a `verify.sh` stage (durable versus temporary reply queue, an async versus blocking reply listener, exclusive versus non-exclusive access, redelivery handling). If a requirement leaves the JCSMP topic/queue publish and subscribe model, or needs behavior the canonical docs cannot ground, STOP and tell the developer it is off-catalog rather than inventing it. -**Preserve the marker contract.** A variant adapts the SHAPE, never the marker strings. It MUST still emit its leaf's `VERIFY:` markers, character for character, so the fixed `verify.sh` stage drives it unchanged (this is exactly why a durable-reply-queue requestor still prints `VERIFY: REPLY_RECEIVED`). Never rename or drop a marker to fit a variant. +**Preserve the marker contract.** A variant adapts the SHAPE, never the marker strings. It MUST still emit its leaf's `VERIFY:` markers, character for character, so the fixed `verify.sh` stages drive it unchanged (this is exactly why a durable-reply-queue requestor still prints `VERIFY: REPLY_RECEIVED`). Never rename or drop a marker to fit a variant. -**Demo harness is not application logic.** The samples carry demo-harness elements that exist only to make a standalone run observable: the ENTER-to-quit `System.in` loop, the once-per-second stats-printing thread, the pacing `Thread.sleep` between sends, and the rotating example payload. When the developer's request describes a real application domain, do NOT carry these into the generated classes: replace the example payload and send cadence with the application's real messages and triggers, and keep only the lifecycle pieces the leaf's steps call for (the SIGINT hook on the long-running role, the self-exit on the foreground role). When the request IS a demo or a try-it run, keeping the harness is fine. Two things are NOT demo harness: the `trace(...)` narration method (keep it; applications replace its single body to route narration to their logging or reporting system) and the `VERIFY:` markers, which are emitted through `trace(...)` like every other status line. Because `verify.sh` greps captured stdout for the markers and for the shutdown hook's `Shutdown signal received` proof line (both flow through `trace(...)`), the generated `trace(...)` body MUST stay `System.out.println` through the Step 5 verification stages; rerouting that body to a logger or reporting system is the developer's step AFTER the verify stages pass, never part of generation. +**Demo harness is not application logic.** The samples carry demo-harness elements that exist only to make a standalone run observable: the ENTER-to-quit `System.in` loop, the once-per-second stats-printing thread, the pacing `Thread.sleep` between sends, and the rotating example payload. When the developer's request describes a real application domain, do NOT carry these into the generated classes: replace the example payload and send cadence with the application's real messages and triggers, and keep only the lifecycle pieces the leaf's steps call for (the SIGINT hook on the long-running role, the self-exit on the foreground role). When the request IS a demo or a try-it run, keeping the harness is fine. Two things are NOT demo harness: the `trace(...)` narration method (keep it; applications replace its single body to route narration to their logging or reporting system) and the `VERIFY:` markers, which are emitted through `trace(...)` like every other status line. Because `verify.sh` greps captured stdout for the markers and for the shutdown hook's `Shutdown signal received` proof line (both flow through `trace(...)`), the generated `trace(...)` body MUST stay `System.out.println` through the Step 5 verification stages; rerouting that body to a logger or reporting system is the developer's step AFTER the verify stages pass, never part of generation. Never treat the logging backend (Step 3) as demo harness: the backend and its config file ship in every generated app. ### Quickstart fail-fast channel defaults On the Quickstart door ONLY, do not carry the reference samples' reconnect-tuning block into the generated classes. The samples configure a `JCSMPChannelProperties` reconnect budget (`reconnectRetries` 20, `connectRetriesPerHost` 3); Quickstart generation OMITS that whole block (the `JCSMPChannelProperties` object and the `CLIENT_CHANNEL_PROPERTIES` setProperty call), leaving connect and reconnect retries and their timeouts at the JCSMP defaults, so a wrong host, port, or credential in this learning setup fails immediately instead of retrying for minutes. Drop ONE short comment at the session-properties build site in each generated class stating that posture: connect and reconnect settings are at the JCSMP defaults so failures surface immediately; see the [JCSMP Best Practices](https://docs.solace.com/API/API-Developer-Guide-JCSMP/JCSMP-API-Best-Practices.md) page for reconnect tuning beyond Quickstart. The Solace Suggested door is unchanged: it keeps the samples' baseline or upgrades it per `solace-suggested-mode.md` Step 2. Custom follows its floor exception in `custom-mode.md`. -### Guaranteed pub/sub flow (the three Guaranteed Pub/Sub leaves) - -The skill ships two best-practices reference samples for this flow. Read them by their exact relative paths and adapt each into its own generated class. There is no merge and no third skeleton: the Publisher class is a near-verbatim adaptation of the publisher sample, and the Subscriber class is a near-verbatim adaptation of the consumer sample. Generating two near-verbatim classes is more reliable than synthesizing a single program from both, because the source shape is copied, not invented. +### Scaffold the opt-in autonomous-run config (Quickstart only) -- `jcsmp-guaranteed-publisher-sample.java`: basic-auth connect, PERSISTENT BytesMessage publish to a topic, publish ACK/NACK handler, session-event and reconnect-event handling that pauses publishing while the transport is down, SIGINT graceful shutdown that finishes outstanding ACKs. -- `jcsmp-guaranteed-subscriber-sample.java`: basic-auth connect, in-process durable-queue provisioning plus topic subscription, CLIENT-ack flow that ACKs only after processing, session-event and flow-event handling. +On the Quickstart path only, alongside the pom and the two classes, also generate two small project files so the developer can opt into having the agent run verify.sh for them (Step 5): -Each class builds its OWN `JCSMPSession` (two sessions, one per class, matching the reference samples; there is no shared-session plumbing). Preserve the session-event and reconnect-event and publish ACK/NACK handlers in the Publisher, and the session-event and flow-event handlers in the Subscriber; they are part of the best-practices contract and must not be stripped during adaptation. +- A committed `config.example.json` carrying placeholder values for the same four keys the Solace Suggested path already defines (`host`, `vpn_name`, `username`, `password`, matching the `JCSMPProperties` property-name strings, the constants' values, exactly). The placeholder values must be obviously fake so a committed example can never be mistaken for live credentials: `host` is `tcp://HOST:55555`, `vpn_name` is `YOUR_VPN`, `username` is `YOUR_USERNAME`, `password` is `YOUR_PASSWORD`. Tell the developer they opt in by copying `config.example.json` to `config.json` and filling in their real broker values. +- A `.gitignore` at the generated project root that ignores `config.json`, so the real credential-bearing file is never committed; only the placeholder `config.example.json` is. -### Subscriber class (near-verbatim `GuaranteedSubscriber`) +This reuses the ONE config.json convention the skill already defines on the Solace Suggested path (Step 0): the same file name `config.json` and the same four keys (`host`, `vpn_name`, `username`, `password`), never a parallel file and never a divergent key set. Both doors read `config.json` through the shared `SolaceConnectionConfig` helper: Solace Suggested reads it directly (no CLI args), and Quickstart reads it when present and otherwise falls back to CLI args. On the Quickstart path a present `config.json` is therefore both the app's connection source and the agent's input for the Step 5 autonomous run. -The Subscriber provisions the durable queue and the topic subscription, and it is the long-running process the developer runs FIRST. The Publisher does NOT provision anything. Wire the Subscriber in this canonical sequence, each step grounded in the doc linked at the bottom of this file: +## Step 5: Verify with verify.sh — on every shape -1. Build basic-auth `JCSMPProperties` (`host`, `vpn_name`, `username`, `password`) via the shared `SolaceConnectionConfig` helper (`config.json` if present, else CLI args). Set `JCSMPProperties.IGNORE_DUPLICATE_SUBSCRIPTION_ERROR` to `true` BEFORE `createSession` (it defaults to false); otherwise `addSubscription` throws Subscription Already Exists on every run after the first (sample line 87). -2. `JCSMPFactory.onlyInstance().createSession(...)`, passing the consumer sample's `SessionEventHandler`, then `session.connect()`. Immediately after `connect()` returns, emit the connect marker: `trace("VERIFY: CONNECTED");`. -3. Best practice: check `session.isCapable(CapabilityType.ENDPOINT_MANAGEMENT)` before provisioning; exit cleanly if the broker disallows client-side endpoint management (sample lines 120-125). -4. `JCSMPFactory.onlyInstance().createQueue(queueName)` for the queue from the design summary. -5. Build the `EndpointProperties` and set the queue access type from the `Access type` summary field (see the access-type snippet below), then `session.provision(queue, endpointProps, JCSMPSession.FLAG_IGNORE_ALREADY_EXISTS)` so a re-run is idempotent. See [Provisioning a Durable Endpoint](https://docs.solace.com/API/API-Developer-Guide-JCSMP/JCSMP-API-Provisioning-Durable-End.md). -6. `session.addSubscription(queue, topic, JCSMPSession.WAIT_FOR_CONFIRM)` to map the topic onto the queue. See [Adding a Topic Subscription](https://docs.solace.com/API/API-Developer-Guide-JCSMP/JCSMP-API-Adding-Topic-Subscriptio.md). -7. Build a `ConsumerFlowProperties` flow with `setAckMode(JCSMPProperties.SUPPORTED_MESSAGE_ACK_CLIENT)`, create it with the consumer sample's `FlowEventHandler`, and `flow.start()`. Immediately after `flow.start()` returns, emit the queue-bound marker: `trace("VERIFY: QUEUE_BOUND");`. See [Acknowledging Messages](https://docs.solace.com/API/API-Developer-Guide-JCSMP/JCSMP-API-Acknowledging-Messages.md). -8. In the flow's `onReceive`, call `msg.ackMessage()` only after processing is complete; immediately after `ackMessage()`, emit the round-trip marker: `trace("VERIFY: MESSAGE_RECEIVED");`. -9. KEEP the long-running loop and the SIGINT graceful-shutdown hook from the consumer sample (sample lines 179-207). The Subscriber is the SIGINT target: on SIGINT it stops the flow, finishes outstanding ACKs, and calls `session.closeSession()`. +Verification is a `verify.sh` run, on every door and every app shape. Step 4 copied `scripts/verify.sh` into the project root and generated `verify-hooks.sh` beside it, so every command below resolves from inside the project. An improvised check (curl against the app's own HTTP API, a manual browser click) may TRIGGER traffic, but it NEVER renders the verdict: the verdict is the `VERIFY:` markers plus the script's exit code, which is what separates "your code is broken" (exit 1) from "your broker is unreachable" (exit 2). -**Access type (exclusive vs non-exclusive): a `setAccessType` snippet, not a separate sample.** The `Access type` field in the design summary picks one line. Set the queue access type on the `EndpointProperties` before provisioning (item 5 above): +**Live-broker heads-up (every run, every launcher).** Immediately before starting ANY process that will connect to a live broker — a verify.sh stage, `java -jar`, a `mvn` run, a run script — print ONE line that names the broker host and states the actual broker-side effects of this run: the queues it provisions (how many, durable or not), the connections it opens, and the messages it publishes, scaled to the real app (SKILL.md Invariant 6). -```java -EndpointProperties endpointProps = new EndpointProperties(); -endpointProps.setAccessType(EndpointProperties.ACCESSTYPE_EXCLUSIVE); // one active consumer / ordered failover -// endpointProps.setAccessType(EndpointProperties.ACCESSTYPE_NONEXCLUSIVE); // competing consumers / round-robin load balance -endpointProps.setPermission(EndpointProperties.PERMISSION_CONSUME); -``` +### The canonical two-class shape: the six fixed stages -When the `Access type` field reads exclusive, keep the `ACCESSTYPE_EXCLUSIVE` line: only one active consumer receives at a time, and on its disconnect a standby flow takes over in order (ordered failover). When it reads non-exclusive, use the `ACCESSTYPE_NONEXCLUSIVE` line instead: bound flows compete and receive in round-robin, so the load is shared across workers. Read the `Access type` field and choose the one line; do not emit both active. This is grounded in [Receiving Guaranteed Messages](https://docs.solace.com/API/API-Developer-Guide-JCSMP/JCSMP-API-Receiving-Guaranteed-Messages.md), which states an exclusive queue delivers in order with one active consumer and failover to the next flow, and a non-exclusive queue delivers round-robin across bound flows. +Generate the two classes rather than emitting everything at once, and verify each against the developer's reachable broker. The run story (run the Subscriber first, then the Publisher): the durable queue makes a sequential publish-then-consume safe once the Subscriber has provisioned it. The script compiles the project, starts the Subscriber first via `mvn exec:java@subscriber` (long-running), runs the Publisher via `mvn exec:java@publisher` (which exits on its own after the ack), watches each process's captured output for that stage's `VERIFY:` marker, confirms receipt on the Subscriber, sends SIGINT to the Subscriber to exercise its graceful-shutdown hook, and returns an exit code that classifies the outcome. Each stage waits for a different marker in a different process. -### Publisher class (near-verbatim `GuaranteedPublisher`) +Invoke the matching stage after generating each class, passing the connection params through as positional args (the same ` [password]` contract the apps use): -The Publisher only connects and publishes; it does NOT provision the queue or the subscription (the Subscriber owns that, and starts first). It must exit on its own after the ack so the developer does not have to interrupt it. Wire the Publisher in this canonical sequence: +1. After generating the Subscriber: `./verify.sh consumer `. It waits for `VERIFY: QUEUE_BOUND` in the Subscriber output (queue provisioned, subscribed, flow started). +2. After generating the Publisher: `./verify.sh publisher `. NOTE: this stage starts the Subscriber first to bind the queue, so it requires BOTH classes; it waits for `VERIFY: PUBLISH_ACKED` in the Publisher's own output (connect, then publish ACK). +3. At the end: `./verify.sh roundtrip `. It starts the Subscriber, runs the Publisher, and waits for `VERIFY: MESSAGE_RECEIVED` in the Subscriber output (a published PERSISTENT message lands on the queue and is consumed and ACKed). -1. Build basic-auth `JCSMPProperties` (`host`, `vpn_name`, `username`, `password`) via the shared `SolaceConnectionConfig` helper (`config.json` if present, else CLI args). -2. `JCSMPFactory.onlyInstance().createSession(...)`, passing the publisher sample's `SessionEventHandler`, then `session.connect()`. Immediately after `connect()` returns, emit the connect marker: `trace("VERIFY: CONNECTED");`. -3. Acquire the `XMLMessageProducer` with the publisher sample's publish ACK/NACK callback handler (`PublishCallbackHandler`), then register the publisher sample's `JCSMPReconnectEventHandler` through an empty never-started consumer (`session.getMessageConsumer(reconnectHandler, null)`). The docs ground this idiom for publish-only applications; keep it because it surfaces transport events that pause the send loop. In the ACK callback (`responseReceivedEx`), emit the publish-ACK marker: `trace("VERIFY: PUBLISH_ACKED");`. CRITICAL: the reference publisher sample logs the ACK at `logger.debug` (sample line 229), which is invisible in default output, so the verify script would never see it and would report a false code failure. The marker MUST reach stdout: emit it via `trace(...)`, whose generated body prints with `System.out.println`, NOT via `logger.debug`. This is the single highest silent-failure risk in the whole verify flow. -4. Publish a PERSISTENT `BytesMessage` to the topic (`DeliveryMode.PERSISTENT`). See [Message Delivery Modes](https://docs.solace.com/API/API-Developer-Guide-JCSMP/JCSMP-API-Message-Delivery-Modes.md). -5. EXIT on its own: after observing the ACK (the `VERIFY: PUBLISH_ACKED` marker), drain outstanding ACKs (the sample's `Thread.sleep(1500)` idiom at sample line 216), call `session.closeSession()`, and return from `main`. REMOVE the sample's ENTER/SIGINT loop (sample line 168): the Publisher must terminate by itself, not wait for a key press or a signal. The SIGINT shutdown hook belongs to the Subscriber, not the Publisher. +The stages above (`consumer`, `publisher`, `roundtrip`) verify the guaranteed pub/sub leaves. `verify.sh` also accepts three further stage names for the other leaves: `direct` (the Direct Pub/Sub leaf), `direct-request-reply` (the Request-Reply (Direct) leaf), and `guaranteed-request-reply` (the Request-Reply (Guaranteed) leaf). All are runnable. The two request-reply stages are replier-first single round-trips: each starts the Replier first (the readiness gate on `VERIFY: SUBSCRIBED`), then runs the Requestor in the foreground and passes on `VERIFY: REPLY_RECEIVED` in the Requestor log (with `VERIFY: REQUEST_RECEIVED` in the Replier log as richer evidence). The `guaranteed-request-reply` stage differs only in that the Replier provisions a durable request queue (an endpoint-management denial classifies as an environment failure, exit 2) and the Requestor uses a temporary reply queue plus a blocking `flow.receive` rather than the direct `Requestor` convenience. -These four `VERIFY:` marker strings (`VERIFY: CONNECTED`, `VERIFY: PUBLISH_ACKED`, `VERIFY: QUEUE_BOUND`, `VERIFY: MESSAGE_RECEIVED`) are a fixed contract with `scripts/verify.sh`, which greps captured output PER PROCESS (the Subscriber log for `QUEUE_BOUND` and `MESSAGE_RECEIVED`, the Publisher log for `PUBLISH_ACKED`). Both classes connect, so both print `VERIFY: CONNECTED`; the script reads markers per-process for that reason. The strings here and the strings the script greps for MUST stay in sync character-for-character; if you change one marker string, change it in `scripts/verify.sh` in the same effort. +### Every other shape: the `app` stage and `verify-hooks.sh` -**Expose both mains to the verify script via two named exec executions.** The fixed `scripts/verify.sh` runs each main without knowing the per-generation package, so the generated pom MUST declare two `exec-maven-plugin` `` blocks, one with `publisher` and one with `subscriber`, each carrying its own `` set to that class's fully-qualified name. The script then addresses them as `mvn exec:java@publisher` and `mvn exec:java@subscriber`. The script references the fixed execution ids, never the generated FQCN, so the package and class names stay your discretion at generation time. Keep the `exec-maven-plugin` version resolved at generation time (no hardcoded version), the same way the `sol-jcsmp` coordinate is resolved. +A web app, an embedded service, or any single-process shape that the six fixed stages cannot drive is verified with the `app` stage: `./verify.sh app`. The stage sources the generated `verify-hooks.sh`, which carries the ONLY app-specific facts — how to start the app and how to cause one publish — while the script keeps the universal logic: marker watching, timeouts, environment-signature classification, and the 0/1/2 exit contract. Generation writes `verify-hooks.sh` because the generator just wrote the app and knows both commands: -```xml - - org.codehaus.mojo - exec-maven-plugin - RESOLVED_AT_GENERATION_TIME - - - publisher - com.example.app.OrderPublisher - - - subscriber - com.example.app.OrderSubscriber - - - +```bash +# verify-hooks.sh — generated with the project; verify.sh's app stage sources it. +START_CMD='java -jar target/the-app.jar' # starts the app (long-running) +TRIGGER_CMD='curl -s -X POST localhost:8081/api/orders -d "{\"demo\":1}"' # causes exactly one publish +READY_MARKER='VERIFY: QUEUE_BOUND' # marker proving the consuming side is live +PASS_MARKER='VERIFY: MESSAGE_RECEIVED' # marker proving the round trip ``` -**Disclaimer header on BOTH generated source files.** Stamp the top of each generated source file (the Publisher and the Subscriber; the pom is exempt as it is not source) with this exact line: `AI-assisted code. Review before production use.` Directly below it, add a pointer to the tailored verification checklist this generation writes into the project: `See the verification checklist: solace-verification-checklist.md`. That tailored `solace-verification-checklist.md` ships inside the generated project, since Step 6 writes it into the project root on every run, so the disclaimer points each generated file at the local checklist beside it rather than at a remote copy. The reference samples themselves carry NO disclaimer; that rule is the inverse. The disclaimer-header and explicit-single-class-imports rules apply to every leaf's generated classes, not just the guaranteed pub/sub pair. - -### Direct Pub/Sub leaf - -When the `Pattern` field is `Direct Pub/Sub`, generation reads the direct pub/sub sample pair (`jcsmp-direct-publisher-sample.java` + `jcsmp-direct-subscriber-sample.java`) and adapts each into a generated class, the same near-verbatim discipline the guaranteed flow uses. Direct messaging is at-most-once: there is no broker ACK and no redelivery, so this leaf has NO queue, NO provisioning, NO CLIENT-ack, and NO publish-ACK callback. Read the two samples by their exact relative paths and adapt each into its own generated class (a Publisher and a Subscriber), each building its own `JCSMPSession`. The disclaimer-header, explicit-single-class-imports, and named-exec rules from the guaranteed flow apply here unchanged. - -- `jcsmp-direct-publisher-sample.java`: basic-auth connect with reconnect-configured channel properties, a continuous DIRECT publish loop with a rotating payload to a dynamic topic (mirroring the guaranteed publisher's send flow), a stats thread, and a graceful SIGINT shutdown. No publish-ACK callback and no correlation key (direct has no broker ACK). Generation STRIPS the unbounded loop, stats thread, and SIGINT hook down to a short bounded burst of DIRECT messages that self-exits (Publisher class steps below), the same way the guaranteed publisher's loop is stripped, so the generated Publisher runs in the foreground and ends on its own. -- `jcsmp-direct-subscriber-sample.java`: basic-auth connect, a topic subscription confirmed via `session.addSubscription(topic, true)` (the `true` waits for broker confirm; no queue, no provisioning), an async DIRECT consumer started with `consumer.start()`, `message.getDiscardIndication()` for at-most-once egress-discard evidence in `onReceive`, and the SIGINT graceful-shutdown hook (the Subscriber is the long-running SIGINT target). - -#### Subscriber class (near-verbatim `DirectSubscriber`) - -The Subscriber subscribes directly to the topic (no queue), consumes asynchronously, and is the long-running process the developer runs FIRST. Wire it in this sequence: - -1. Build basic-auth `JCSMPProperties` (`host`, `vpn_name`, `username`, `password`) via the shared `SolaceConnectionConfig` helper (`config.json` if present, else CLI args). Set `JCSMPProperties.REAPPLY_SUBSCRIPTIONS` to `true` on those properties BEFORE `createSession` (it defaults to false): a Direct topic subscription is a client-side session subscription, not held on a broker queue, so without this it is lost on an automatic reconnect and delivery silently stops. -2. `JCSMPFactory.onlyInstance().createSession(...)`, passing the sample's `SessionEventHandler`, then `session.connect()`. Immediately after `connect()` returns, emit the connect marker: `trace("VERIFY: CONNECTED");`. -3. Acquire the async consumer with `session.getMessageConsumer(listener)`, add the topic subscription with `session.addSubscription(topic, true)` (the `true` waits for broker confirm, so the route is live before the subscriber reports ready, since direct is at-most-once), then `consumer.start()`. Immediately after `consumer.start()` returns, emit the subscribed marker: `trace("VERIFY: SUBSCRIBED");`. There is NO queue and NO `session.provision(...)` on the direct path. -4. In the listener's `onReceive`, count the message and call `message.getDiscardIndication()` for at-most-once egress-discard evidence; immediately emit the receipt marker: `trace("VERIFY: MESSAGE_RECEIVED");`. There is NO `ackMessage()` and NO CLIENT-ack on the direct path (direct is at-most-once, the broker holds no copy and expects no acknowledgement). -5. KEEP the long-running loop and the SIGINT graceful-shutdown hook from the sample. The Subscriber is the SIGINT target: on SIGINT it calls `session.closeSession()` directly (closing the session also closes the consumer; direct has no acknowledgements to drain), printing `Shutdown signal received` so the verify script sees the shutdown-hook proof line. - -#### Publisher class (near-verbatim `DirectPublisher`) - -The Publisher only connects and publishes a short bounded burst of DIRECT messages, then exits on its own; it does NOT provision anything and does NOT wait for a key press or a signal. Wire it in this sequence: - -1. Build basic-auth `JCSMPProperties` (`host`, `vpn_name`, `username`, `password`) via the shared `SolaceConnectionConfig` helper (`config.json` if present, else CLI args). -2. `JCSMPFactory.onlyInstance().createSession(...)`, passing the sample's `SessionEventHandler`, then `session.connect()`. Immediately after `connect()` returns, emit the connect marker: `trace("VERIFY: CONNECTED");`. -3. Acquire the producer with a minimal mandatory streaming-publish event handler (`session.getMessageProducer(handler)`). The handler parameter is mandatory even for direct: a `null` handler is rejected at runtime with "Blocking publishing mode is not supported". Direct messaging is at-most-once with no broker ACK, so the handler's `responseReceivedEx` is not expected to fire and `handleErrorEx` only logs the error (rendering the broker subcode when present); there is no correlation key. Do NOT pass `null`, and do NOT wait on an ACK: direct has no broker ACK to act on (ACK/NACK handling and correlation keys are the guaranteed publisher's contract). -4. Set the delivery mode EXPLICITLY with `message.setDeliveryMode(DeliveryMode.DIRECT)` and publish a SHORT BOUNDED BURST to the design topic via `producer.send(message, topic)` in a small fixed-count loop, then self-exit (close the session and return from `main`). DIRECT is the API default, but stating it makes the at-most-once intent unambiguous. REMOVE the sample's UNBOUNDED ENTER/SIGINT publish loop, its rotating-payload/dynamic-topic illustration, its stats thread, and its SIGINT shutdown hook (the sample mirrors the guaranteed publisher's send flow; here it collapses to a bounded burst): the generated Publisher publishes its burst and terminates by itself, not on a key press or a signal, so the verify script can run it in the foreground and wait for it to exit. Direct has no outstanding ACKs to drain, so no pre-close sleep is needed. The SIGINT shutdown hook belongs to the long-running Subscriber, not the Publisher. See [Message Delivery Modes](https://docs.solace.com/API/API-Developer-Guide-JCSMP/JCSMP-API-Message-Delivery-Modes.md). -5. The Publisher emits NO send-side marker. Direct has no broker ACK, so there is no `PUBLISH_ACKED` analog; a marker after `producer.send` would only prove the local call returned, not that the broker accepted the message, which is misleading for an at-most-once path. The real proof of the round-trip is `VERIFY: MESSAGE_RECEIVED` on the Subscriber side. - -These three `VERIFY:` marker strings for the Direct Pub/Sub leaf (`VERIFY: CONNECTED`, `VERIFY: SUBSCRIBED`, `VERIFY: MESSAGE_RECEIVED`) are a fixed contract with `scripts/verify.sh` (the `direct` stage greps the Subscriber log for `VERIFY: SUBSCRIBED` as its readiness gate and for `VERIFY: MESSAGE_RECEIVED` as its pass condition). The marker MUST reach stdout: emit it via `trace(...)`, whose generated body prints with `System.out.println`, NOT via `logger.debug`. This is the single highest silent-failure risk in the whole verify flow. The strings here and the strings the script greps for MUST stay in sync character-for-character; if you change one marker string, change it in `scripts/verify.sh` in the same effort. - -**Expose both mains to the verify script via two named exec executions.** Just like the guaranteed flow, the generated pom MUST declare two `exec-maven-plugin` `` blocks for the direct pair, one with `publisher` and one with `subscriber`, each carrying its own `` set to that class's fully-qualified name. The `direct` stage addresses them as `mvn exec:java@subscriber` (started first, the readiness gate) and `mvn exec:java@publisher` (the foreground burst). Keep the `exec-maven-plugin` version resolved at generation time. - -### Request-Reply (Direct) leaf - -When the `Pattern` field is `Request-Reply (Direct)`, generation reads the direct request-reply sample pair (`jcsmp-direct-requestor-sample.java` + `jcsmp-direct-replier-sample.java`) and adapts each into a generated class, the same near-verbatim discipline the guaranteed and direct pub/sub flows use. Direct request-reply is at-most-once on both legs: there is no broker ACK and no redelivery, so this leaf has NO queue, NO provisioning, and NO CLIENT-ack. The requestor self-exits after the reply (its blocking request is the synchronization point); the replier is the long-running SIGINT target. Read the two samples by their exact relative paths and adapt each into its own generated class (a Requestor and a Replier), each building its own `JCSMPSession`. The disclaimer-header and explicit-single-class-imports rules from the guaranteed flow apply here unchanged. - -- `jcsmp-direct-requestor-sample.java`: basic-auth connect, a started consumer (required before any request), a blocking `session.createRequestor()` plus `requestor.request(requestMsg, REQUEST_TIMEOUT_MS, topic)` with a POSITIVE `REQUEST_TIMEOUT_MS` (3000), a `JCSMPRequestTimeoutException` catch (optionally retrying once to absorb a cold-start race), then a self-exit. No SIGINT hook (the Requestor is a foreground process that ends on its own). -- `jcsmp-direct-replier-sample.java`: basic-auth connect, a plain topic subscription on the request topic via `session.addSubscription(requestTopic)` (no queue, no provisioning), an async consumer started with `consumer.start()`, an `onReceive` that guards on `requestMsg.getReplyTo() != null` and answers with the DIRECT reply convenience `producer.sendReply(requestMsg, replyMsg)`, and the SIGINT graceful-shutdown hook (the Replier is the long-running SIGINT target). - -#### Replier class (near-verbatim `DirectReplier`) - -The Replier subscribes directly to the request topic (no queue), answers each request, and is the long-running process the developer runs FIRST. Wire it in this sequence: - -1. Build basic-auth `JCSMPProperties` (`host`, `vpn_name`, `username`, `password`) via the shared `SolaceConnectionConfig` helper (`config.json` if present, else CLI args). Set `JCSMPProperties.REAPPLY_SUBSCRIPTIONS` to `true` on those properties BEFORE `createSession` (it defaults to false): the Direct request-topic subscription is a client-side session subscription, not held on a broker queue, so without this it is lost on an automatic reconnect and the replier silently stops answering requests. -2. `JCSMPFactory.onlyInstance().createSession(...)`, passing the sample's `SessionEventHandler`, then `session.connect()`. Immediately after `connect()` returns, emit the connect marker: `trace("VERIFY: CONNECTED");`. -3. Acquire the reply producer with a minimal mandatory streaming-publish event handler (`session.getMessageProducer(handler)`). The handler parameter is mandatory even for direct: a `null` handler is rejected at runtime with "Blocking publishing mode is not supported". Direct messaging is at-most-once with no broker ACK, so the handler's `responseReceivedEx` is not expected to fire and `handleErrorEx` only logs. Do NOT pass `null`. -4. Acquire the async consumer with `session.getMessageConsumer(listener)`, add the plain request-topic subscription with `session.addSubscription(requestTopic)`, then `consumer.start()`. Immediately after `consumer.start()` returns, emit the subscribed marker: `trace("VERIFY: SUBSCRIBED");`. There is NO queue and NO `session.provision(...)` on the direct path. -5. In the listener's `onReceive`, guard on `requestMsg.getReplyTo() != null` (a message with no reply-to is not a request, so ignore it); immediately emit the request marker: `trace("VERIFY: REQUEST_RECEIVED");`. Then build a reply `BytesMessage` and answer with `producer.sendReply(requestMsg, replyMsg)`, the DIRECT reply convenience (it auto-routes to the reply-to and copies the correlation). Use `sendReply`, NOT a manual `producer.send(reply, getReplyTo())`: the manual form is the guaranteed replier's contract, and crossing them loses the explicit PERSISTENT reply control the guaranteed pattern documents. -6. KEEP the long-running loop and the SIGINT graceful-shutdown hook from the sample. The Replier is the SIGINT target: on SIGINT it calls `session.closeSession()` directly (closing the session also closes the consumer; direct has no acknowledgements to drain), printing `Shutdown signal received` so the verify script sees the shutdown-hook proof line. +The `app` stage starts `START_CMD` in the background and captures its output, waits for `VERIFY: CONNECTED` and then `READY_MARKER` (an environment signature or a timeout before that classifies as exit 2 or exit 1), runs `TRIGGER_CMD` in the foreground, waits for `PASS_MARKER` in the app's captured output, then stops the app with SIGINT and classifies. Set `READY_MARKER`/`PASS_MARKER` to the chosen leaf's own markers. A curl inside `TRIGGER_CMD` is exactly the right use of curl: it triggers, and the markers judge. On the canonical two-class shape, generate `verify-hooks.sh` too, carrying the classic commands (`START_CMD='mvn -q exec:java@subscriber'`, `TRIGGER_CMD='mvn -q exec:java@publisher'`) so the file documents the same contract everywhere; the fixed stages do not read it. -#### Requestor class (near-verbatim `DirectRequestor`) +### Interpret the exit code -The Requestor connects, issues one blocking request, and exits on its own; it does NOT provision anything and does NOT wait for a key press or a signal. Wire it in this sequence: - -1. Build basic-auth `JCSMPProperties` (`host`, `vpn_name`, `username`, `password`) via the shared `SolaceConnectionConfig` helper (`config.json` if present, else CLI args). -2. `JCSMPFactory.onlyInstance().createSession(...)`, passing the sample's `SessionEventHandler`, then `session.connect()`. Immediately after `connect()` returns, emit the connect marker: `trace("VERIFY: CONNECTED");`. -3. Acquire BOTH a producer and a started consumer BEFORE issuing the request. The blocking Requestor sends the request through the session's message producer AND receives the correlated reply through the session's message consumer, so both must be set up first. Acquire the producer with a minimal mandatory streaming-publish event handler (`session.getMessageProducer(handler)`): the API throws "No producer to perform operation" if no producer exists, and a `null` handler is rejected with "Blocking publishing mode is not supported", so pass a no-op handler (direct is at-most-once, the handler never acts on an ACK). Then start the consumer with `session.getMessageConsumer((XMLMessageListener) null)` and `consumer.start()`; a null listener is fine because the blocking `requestor.request(...)` returns the reply directly rather than via `onReceive`. -4. Define a POSITIVE `REQUEST_TIMEOUT_MS` (3000), create the blocking requestor with `session.createRequestor()`, and issue `requestor.request(requestMsg, REQUEST_TIMEOUT_MS, requestTopic)`. The positive timeout is the direct reliability knob (NOT a burst): the call blocks up to that long for the correlated reply before throwing `JCSMPRequestTimeoutException`. Catch that exception and optionally retry once to absorb a cold-start race (the replier may not have propagated its subscription on the very first attempt). -5. When the reply is non-null, emit the reply marker: `trace("VERIFY: REPLY_RECEIVED");`. Then self-exit: close the session and return from `main`. REMOVE any ENTER/SIGINT loop: the Requestor must terminate by itself, not wait for a key press or a signal. The SIGINT shutdown hook belongs to the Replier, not the Requestor. - -These four `VERIFY:` marker strings for the Request-Reply (Direct) leaf (`VERIFY: CONNECTED`, `VERIFY: SUBSCRIBED`, `VERIFY: REQUEST_RECEIVED`, `VERIFY: REPLY_RECEIVED`) are a fixed contract with `scripts/verify.sh` (the `direct-request-reply` stage greps the Replier log for `VERIFY: SUBSCRIBED` as its readiness gate and for `VERIFY: REQUEST_RECEIVED` as richer evidence, and the Requestor log for `VERIFY: REPLY_RECEIVED` as its pass condition). The marker MUST reach stdout: emit it via `trace(...)`, whose generated body prints with `System.out.println`, NOT via `logger.debug`. This is the single highest silent-failure risk in the whole verify flow. The strings here and the strings the script greps for MUST stay in sync character-for-character; if you change one marker string, change it in `scripts/verify.sh` in the same effort. - -**Expose both mains to the verify script via two named exec executions.** Just like the other flows, the generated pom MUST declare two `exec-maven-plugin` `` blocks for the request-reply pair, but with NEW ids `requestor` and `replier` (NOT `publisher`/`subscriber`), each carrying its own `` set to that class's fully-qualified name. The `direct-request-reply` stage addresses them as `mvn exec:java@replier` (started first, the readiness gate) and `mvn exec:java@requestor` (the foreground request). Keep the `exec-maven-plugin` version resolved at generation time. - -### Request-Reply (Guaranteed) leaf - -When the `Pattern` field is `Request-Reply (Guaranteed)`, generation reads the guaranteed request-reply sample pair (`jcsmp-guaranteed-requestor-sample.java` + `jcsmp-guaranteed-replier-sample.java`) and adapts each into a generated class, the same near-verbatim discipline the other leaves use. Guaranteed request-reply is PERSISTENT on both legs: the request and the reply are both delivered with `DeliveryMode.PERSISTENT` and broker-ACKed. Whatever the reply topology, this leaf answers with a MANUAL `producer.send(reply, request.getReplyTo())` and is explicitly NOT the direct-only blocking `Requestor` convenience API and NOT `producer.sendReply(...)` (those are the direct request-reply path, and crossing them onto the guaranteed path loses the explicit PERSISTENT reply control this pattern documents); that rule holds for the base shape and every variant. - -The BASE shape uses a per-request TEMPORARY reply queue plus a `FlowReceiver` (one outstanding request per temp queue, the reply read by a blocking `flow.receive(timeoutMs)`). The base also sets an application-managed CorrelationID on the request and matches it on receipt (the replier echoes it back): this is the doc-recommended request/reply matching mechanism, and it sits ALONGSIDE the dedicated temporary reply queue, which on its own already isolates the single outstanding reply. Generate the base shape when the design summary's `Consumption endpoint` reads `temporary reply queue + FlowReceiver` (the default). - -When the summary records a VARIANT instead (for example `durable reply queue + FlowReceiver`, chosen because the reply must survive the requestor restarting or because many requests are in flight at once), adapt the base shape to it per "Adapting a sample to a variant of its leaf" above: the requestor provisions a DURABLE reply queue (`createQueue` plus `session.provision(..., FLAG_IGNORE_ALREADY_EXISTS)`) instead of `createTemporaryQueue`, and binds it with a `FlowReceiver` on an ASYNC listener that matches each reply to its pending request by the CorrelationID (ground the durable-endpoint APIs against the canonical docs). The application-managed CorrelationID matching carries over from the base unchanged; what the variant adds is the durable reply endpoint and the async reception model (in place of the base's blocking `flow.receive`), needed because a shared durable reply queue can hold many in-flight replies at once and the CorrelationID is what sorts each reply to its request. Both legs stay PERSISTENT, the reply is still the manual `producer.send(reply, request.getReplyTo())`, CLIENT-ack discipline is unchanged, and the four `VERIFY:` markers are emitted exactly as below. A durable reply queue survives the requestor restarting because the broker retains the reply until it is consumed, which a temporary reply queue (deleted with its session) cannot do. - -The requestor self-exits after the reply (its blocking receive, or its correlated match, is the synchronization point); the replier is the long-running SIGINT target. Read the two samples by their exact relative paths and adapt each into its own generated class (a Requestor and a Replier), each building its own `JCSMPSession`. The disclaimer-header and explicit-single-class-imports rules from the guaranteed pub/sub flow apply here unchanged. - -- `jcsmp-guaranteed-requestor-sample.java`: basic-auth connect, a producer with a mandatory streaming-publish event handler, a per-request temporary reply queue (`session.createTemporaryQueue()`) bound by a `FlowReceiver` with a NULL listener (the blocking model), a PERSISTENT request whose reply-to is set to that temporary queue, an application-managed CorrelationID set on the request and matched on the reply (the request/reply matching mechanism), `setCorrelationKey` used for PUBLISHER ACK correlation ONLY (a local key, not request/reply matching), a `producer.send(request, requestTopic)`, then a BLOCKING `flow.receive(timeoutMs)` on the temporary reply queue, then a self-exit. No SIGINT hook (the Requestor is a foreground process that ends on its own). -- `jcsmp-guaranteed-replier-sample.java`: basic-auth connect, idempotent in-process provisioning of a durable request queue (behind a `session.isCapable(CapabilityType.ENDPOINT_MANAGEMENT)` gate) plus a topic subscription mapped onto it, a `FlowReceiver` whose handler answers each request with a MANUAL PERSISTENT `producer.send(reply, request.getReplyTo())`, and the SIGINT graceful-shutdown hook (the Replier is the long-running SIGINT target). - -#### Replier class (near-verbatim `GuaranteedReplier`) - -The Replier provisions a durable request queue and its topic subscription, answers each request with a manual PERSISTENT reply, and is the long-running process the developer runs FIRST. Wire it in this sequence: - -1. Build basic-auth `JCSMPProperties` (`host`, `vpn_name`, `username`, `password`) via the shared `SolaceConnectionConfig` helper (`config.json` if present, else CLI args). Set `JCSMPProperties.IGNORE_DUPLICATE_SUBSCRIPTION_ERROR` to `true` BEFORE `createSession` (it defaults to false); otherwise `addSubscription` on the request queue throws Subscription Already Exists on every run after the first. -2. `JCSMPFactory.onlyInstance().createSession(...)`, passing the sample's `SessionEventHandler`, then `session.connect()`. Immediately after `connect()` returns, emit the connect marker: `trace("VERIFY: CONNECTED");`. -3. Acquire the reply producer with a mandatory streaming-publish event handler (`session.getMessageProducer(handler)`). Guaranteed replies are broker-ACKed, so the handler's `responseReceivedEx` fires per reply; the handler parameter is mandatory (a `null` handler is rejected at runtime with "Blocking publishing mode is not supported"). Do NOT pass `null`. -4. Best practice: check `session.isCapable(CapabilityType.ENDPOINT_MANAGEMENT)` before provisioning; exit cleanly if the broker disallows client-side endpoint management (the sample prints the `does not allow client-side endpoint management` line, which the verify script classifies as an environment failure). Then `JCSMPFactory.onlyInstance().createQueue(requestQueueName)`, build the `EndpointProperties`, and `session.provision(requestQueue, endpointProps, JCSMPSession.FLAG_IGNORE_ALREADY_EXISTS)` so a re-run is idempotent. See [Provisioning a Durable Endpoint](https://docs.solace.com/API/API-Developer-Guide-JCSMP/JCSMP-API-Provisioning-Durable-End.md). -5. `session.addSubscription(requestQueue, requestTopic, JCSMPSession.WAIT_FOR_CONFIRM)` to map the request topic onto the durable request queue. See [Adding a Topic Subscription](https://docs.solace.com/API/API-Developer-Guide-JCSMP/JCSMP-API-Adding-Topic-Subscriptio.md). Build a `ConsumerFlowProperties` flow on the request queue with the sample's `FlowEventHandler` and `flow.start()`. Immediately after `flow.start()` returns, emit the subscribed marker: `trace("VERIFY: SUBSCRIBED");`. -6. In the flow handler's `onReceive`, guard on `requestMsg.getReplyTo() != null` (a message with no reply-to is not a request, so ACK it and ignore it); immediately emit the request marker: `trace("VERIFY: REQUEST_RECEIVED");`. Then build a reply `BytesMessage`, set it `DeliveryMode.PERSISTENT`, mark it a reply with `replyMsg.setAsReplyMessage(true)` (the requestor reads the reply field back with `isReplyMessage()` as one of the agreed reply-acceptance conditions), echo the request's CorrelationID onto it verbatim, and answer with a MANUAL `producer.send(replyMsg, requestMsg.getReplyTo())`, sending the PERSISTENT reply to the requestor's temporary reply queue. Use the manual `producer.send(reply, getReplyTo())`, NOT `producer.sendReply(...)`: `sendReply` is the direct replier's at-most-once convenience, and crossing it onto the guaranteed path loses the explicit PERSISTENT reply control. ACK the request (`requestMsg.ackMessage()`) only after the reply has been sent. -7. KEEP the long-running loop and the SIGINT graceful-shutdown hook from the sample. The Replier is the SIGINT target: on SIGINT it stops the flow, finishes outstanding ACKs, and calls `session.closeSession()`, printing `Shutdown signal received` so the verify script sees the shutdown-hook proof line. See [Message Delivery Modes](https://docs.solace.com/API/API-Developer-Guide-JCSMP/JCSMP-API-Message-Delivery-Modes.md). - -#### Requestor class (near-verbatim `GuaranteedRequestor`) - -The Requestor connects, issues one PERSISTENT request, blocks for the correlated reply on a temporary queue, and exits on its own; it does NOT provision the durable request queue (the Replier owns that, and starts first) and does NOT wait for a key press or a signal. Wire it in this sequence: - -1. Build basic-auth `JCSMPProperties` (`host`, `vpn_name`, `username`, `password`) via the shared `SolaceConnectionConfig` helper (`config.json` if present, else CLI args). -2. `JCSMPFactory.onlyInstance().createSession(...)`, passing the sample's `SessionEventHandler`, then `session.connect()`. Immediately after `connect()` returns, emit the connect marker: `trace("VERIFY: CONNECTED");`. -3. Acquire the producer with a mandatory streaming-publish event handler (`session.getMessageProducer(handler)`): the PERSISTENT request is broker-ACKed, and a `null` handler is rejected at runtime with "Blocking publishing mode is not supported", so pass a real handler. Create the per-request temporary reply queue with `session.createTemporaryQueue()`, build a `ConsumerFlowProperties` with `setEndpoint(replyQueue)`, create the flow with a NULL listener via `session.createFlow(null, flowProps)` (the blocking model returns the reply directly from `flow.receive(...)` rather than via an async `onReceive`), and `flow.start()`. Immediately after `flow.start()` returns, emit the subscribed marker: `trace("VERIFY: SUBSCRIBED");`. Do NOT use `session.createRequestor()`: that blocking-Requestor convenience is the direct path; the guaranteed path uses the temp-queue plus blocking `flow.receive`. VARIANT: when the summary records a durable reply queue, replace `session.createTemporaryQueue()` with `createQueue(replyQueueName)` plus `session.provision(replyQueue, endpointProps, JCSMPSession.FLAG_IGNORE_ALREADY_EXISTS)`, and bind it with a `FlowReceiver` on an async listener that matches each reply to its pending request by the CorrelationID (the same CorrelationID the base already sets, now read on the async listener instead of the blocking receive); keep everything else (PERSISTENT request, the marker, no direct convenience) unchanged. -4. Build the request `BytesMessage`, set it `DeliveryMode.PERSISTENT`, and set its reply-to to the temporary reply queue with `requestMsg.setReplyTo(replyQueue)`. Set an application-managed CorrelationID on the request (`requestMsg.setCorrelationId(...)`, a unique value such as a `UUID` carrying the application's agreed notable prefix, per the reference sample's `CORRELATION_ID_PREFIX`); the replier echoes it onto the reply and the requestor matches it on receipt (step 5). This is the request/reply matching mechanism the Solace docs recommend, and it sits alongside the dedicated temporary reply queue. Also set `requestMsg.setCorrelationKey(requestMsg)`, and state in a code comment that `setCorrelationKey` is for PUBLISHER ACK correlation ONLY (a local key that identifies the request in the producer's ACK callback, never sent on the wire), distinct from the on-the-wire CorrelationID used for request/reply matching. Then `producer.send(requestMsg, requestTopic)`. -5. BLOCK on `flow.receive(timeoutMs)` on the temporary reply queue (a positive timeout; it returns null on timeout). A single PERSISTENT request plus a temp-queue reply is reliable, so issue exactly ONE request with NO burst and NO retry. When the reply meets the agreed reply-acceptance contract (its reply field is set, read with `isReplyMessage()`, and its CorrelationID carries the agreed prefix and equals the one set on the request; the reference sample's `isExpectedReply` method is the contract's single home), emit the reply marker: `trace("VERIFY: REPLY_RECEIVED");`. Then self-exit: stop the flow, close the session (which auto-deletes the temporary reply queue), and return from `main`. REMOVE any ENTER/SIGINT loop: the Requestor must terminate by itself. The SIGINT shutdown hook belongs to the Replier, not the Requestor. VARIANT: with a durable reply queue, the reply arrives on the async `FlowReceiver` listener and is matched by correlation id rather than a blocking `flow.receive`; emit `VERIFY: REPLY_RECEIVED` on the matched reply, and on self-exit close the session WITHOUT relying on temp-queue auto-delete (the durable reply queue is provisioned and persists by design, which is what delivers the restart-survival guarantee). - -These four `VERIFY:` marker strings for the Request-Reply (Guaranteed) leaf (`VERIFY: CONNECTED`, `VERIFY: SUBSCRIBED`, `VERIFY: REQUEST_RECEIVED`, `VERIFY: REPLY_RECEIVED`) are a fixed contract with `scripts/verify.sh` (the `guaranteed-request-reply` stage greps the Replier log for `VERIFY: SUBSCRIBED` as its readiness gate and for `VERIFY: REQUEST_RECEIVED` as richer evidence, and the Requestor log for `VERIFY: REPLY_RECEIVED` as its pass condition). The marker MUST reach stdout: emit it via `trace(...)`, whose generated body prints with `System.out.println`, NOT via `logger.debug`. This is the single highest silent-failure risk in the whole verify flow. The strings here and the strings the script greps for MUST stay in sync character-for-character; if you change one marker string, change it in `scripts/verify.sh` in the same effort. - -**Expose both mains to the verify script via two named exec executions.** Just like the direct request-reply flow, the generated pom MUST declare two `exec-maven-plugin` `` blocks for the guaranteed request-reply pair, with the ids `requestor` and `replier` (NOT `publisher`/`subscriber`), each carrying its own `` set to that class's fully-qualified name. The `guaranteed-request-reply` stage addresses them as `mvn exec:java@replier` (started first, the readiness gate) and `mvn exec:java@requestor` (the foreground request). Keep the `exec-maven-plugin` version resolved at generation time. - -### Scaffold the opt-in autonomous-run config (Quickstart only) - -On the Quickstart path only, alongside the pom and the two classes, also generate two small project files so the developer can opt into having the agent run verify.sh for them (Step 5): - -- A committed `config.example.json` carrying placeholder values for the same four keys the Solace Suggested path already defines (`host`, `vpn_name`, `username`, `password`, matching the `JCSMPProperties` property-name strings, the constants' values, exactly). The placeholder values must be obviously fake so a committed example can never be mistaken for live credentials: `host` is `tcp://HOST:55555`, `vpn_name` is `YOUR_VPN`, `username` is `YOUR_USERNAME`, `password` is `YOUR_PASSWORD`. Tell the developer they opt in by copying `config.example.json` to `config.json` and filling in their real broker values. -- A `.gitignore` at the generated project root that ignores `config.json`, so the real credential-bearing file is never committed; only the placeholder `config.example.json` is. - -This reuses the ONE config.json convention the skill already defines on the Solace Suggested path (Step 0): the same file name `config.json` and the same four keys (`host`, `vpn_name`, `username`, `password`), never a parallel file and never a divergent key set. Both doors read `config.json` through the shared `SolaceConnectionConfig` helper: Solace Suggested reads it directly (no CLI args), and Quickstart reads it when present and otherwise falls back to CLI args. On the Quickstart path a present `config.json` is therefore both the app's connection source and the agent's input for the Step 5 autonomous run. - -## Step 5: Verify each stage with scripts/verify.sh - -Generate the two classes rather than emitting everything at once, and verify each against the developer's reachable broker with the bundled `scripts/verify.sh`. The run story (run the Subscriber first, then the Publisher): the durable queue makes a sequential publish-then-consume safe once the Subscriber has provisioned it. The script compiles the project, starts the Subscriber first via `mvn exec:java@subscriber` (long-running), runs the Publisher via `mvn exec:java@publisher` (which exits on its own after the ack), watches each process's captured output for that stage's `VERIFY:` marker, confirms receipt on the Subscriber, sends SIGINT to the Subscriber to exercise its graceful-shutdown hook, and returns an exit code that classifies the outcome. Each stage waits for a different marker in a different process. - -Invoke the matching stage after generating each class, passing the connection params through as positional args (the same ` [password]` contract the apps use): - -1. After generating the Subscriber: `scripts/verify.sh consumer `. It waits for `VERIFY: QUEUE_BOUND` in the Subscriber output (queue provisioned, subscribed, flow started). -2. After generating the Publisher: `scripts/verify.sh publisher `. NOTE: this stage starts the Subscriber first to bind the queue, so it requires BOTH classes; it waits for `VERIFY: PUBLISH_ACKED` in the Publisher's own output (connect, then publish ACK). -3. At the end: `scripts/verify.sh roundtrip `. It starts the Subscriber, runs the Publisher, and waits for `VERIFY: MESSAGE_RECEIVED` in the Subscriber output (a published PERSISTENT message lands on the queue and is consumed and ACKed). - -The stages above (`consumer`, `publisher`, `roundtrip`) verify the guaranteed pub/sub leaves. `scripts/verify.sh` also accepts three further stage names for the other leaves: `direct` (the Direct Pub/Sub leaf), `direct-request-reply` (the Request-Reply (Direct) leaf), and `guaranteed-request-reply` (the Request-Reply (Guaranteed) leaf). All three are runnable. The two request-reply stages are replier-first single round-trips: each starts the Replier first (the readiness gate on `VERIFY: SUBSCRIBED`), then runs the Requestor in the foreground and passes on `VERIFY: REPLY_RECEIVED` in the Requestor log (with `VERIFY: REQUEST_RECEIVED` in the Replier log as richer evidence). The `guaranteed-request-reply` stage differs only in that the Replier provisions a durable request queue (an endpoint-management denial classifies as an environment failure, exit 2) and the Requestor uses a temporary reply queue plus a blocking `flow.receive` rather than the direct `Requestor` convenience. - -**Interpret the exit code:** - -- **Exit 0 (stage passes).** The stage marker appeared, the graceful-shutdown hook ran (the `Shutdown signal received...` line is in the captured Subscriber output, proving `kill -INT` reached the app), and the JVM exited cleanly. A clean JVM exit is exit code `0` OR `130`: a JVM terminated by SIGINT *after* its shutdown hooks run reports `130` (128 + SIGINT), which is the expected graceful outcome, so the script accepts both. Advance to the next stage. -- **Exit 1 (code failure).** Compile failed, the marker never appeared, the shutdown hook did not run, or the run hung after SIGINT and had to be force-killed. Enter the bounded fix loop below. +- **Exit 0 (stage passes).** The stage milestone appeared and the run tore down cleanly (for the fixed stages that includes the graceful-shutdown proof line and a clean JVM exit, 0 or 130). Advance to the next stage. +- **Exit 1 (code failure).** Compile failed, a marker never appeared, a process died before its milestone, or a run hung and had to be force-killed. Enter the bounded fix loop below. - **Exit 2 (environment failure).** A doc-traceable JCSMP connection/auth signature appeared (the broker is unreachable or the credentials are wrong). STOP. Do NOT enter the fix loop. The code is not the problem. **Bounded fix loop (exit 1 only).** Diagnose the captured output, fix the code, and re-run the same stage, up to 3 automatic attempts per stage. After the third failure, stop fixing: summarize exactly what you tried across the attempts and what the captured output shows, then hand the decision to the developer. Report the evidence, not just "it failed". -**Fixes apply without a confirmation gate (both doors).** During the fix loop, apply the fix and re-run the same stage; do not preview the changed lines and ask the developer to confirm before writing them. This holds on the Solace Suggested path too, consistent with Step 0 generating the classes without a per-stage gate. +**Fixes apply without a confirmation gate (all doors).** During the fix loop, apply the fix and re-run the same stage; do not preview the changed lines and ask the developer to confirm before writing them. This holds on the Solace Suggested path too, consistent with Step 0 generating the classes without a per-stage gate. **Environment-failure path (exit 2).** Report the environment problem with the matched signature line from the captured output as evidence, and do NOT modify the code (a working app against a bad broker or wrong credentials must not be "fixed"). The fix is the developer's broker or credentials. -**Autonomous run from config.json (Quickstart only).** This applies to Quickstart ONLY. On the Quickstart path, run scripts/verify.sh for real instead of handing the commands back ONLY when BOTH conditions hold: a `config.json` is present at the generated project root, AND its four values are real, meaning they are NOT the `config.example.json` placeholders (`tcp://HOST:55555`, `YOUR_VPN`, `YOUR_USERNAME`, `YOUR_PASSWORD`). The opt-in is filling in real values, not merely copying the example: a `config.json` that still carries any placeholder value is treated as not-yet-opted-in, so fall through to the honesty-rule path below (compile only, then hand back the three commands). This keeps a half-finished `cp config.example.json config.json` from triggering a real run against placeholder credentials. When the values are real, read the four (`host`, `vpn_name`, `username`, `password`) from `config.json` and pass them to verify.sh as POSITIONAL CLI args, using the existing `verify.sh [password]` contract: config `host` becomes the `` arg, `vpn_name` the `` arg, `username` the `` arg, `password` the `[password]` arg (the stage name is the first positional, the four values follow). Run the three stages in this order: consumer first (it provisions the queue), then publisher, then roundtrip; this is the operational run order, distinct from the reference command list in the honesty rule below. verify.sh itself is unchanged; the agent reads `config.json` and passes the four values as positional args. Because a real `config.json` sits at the project root during this run, the generated apps read it directly through the `SolaceConnectionConfig` helper (the positional args are a harmless same-value fallback), so verify.sh drives the run unchanged. Interpret each stage's exit code exactly as the "Interpret the exit code" block below defines it: an exit 1 enters the bounded fix loop, an exit 2 stops without a fix, an exit 0 advances. Immediately before the live run, print ONE heads-up line that names the host from `config.json` and states the side effects (it provisions a durable queue and publishes one message), then run immediately with NO confirmation prompt: a config.json with real values is the consent, so there is no gate. This is the config.json-present-with-real-values branch of the honesty rule below; when `config.json` is absent or still holds placeholders, Quickstart keeps the existing behavior (compile only, then hand back the three commands). On that compile-only fall-through, also point the developer at the autonomous option so the loop is discoverable at the moment it matters: tell them you generated a `config.example.json`, and that they can have you run all three stages for them by copying it to `config.json`, filling in their real broker values, and asking you to verify (rather than running the handed-back commands by hand). This specific autonomous-run mechanism (reading the `config.json` VALUES and passing them to verify.sh as POSITIONAL args) applies to Quickstart ONLY. The Solace Suggested path ALSO runs verify.sh under the same real-values consent, but in its two-project mode (the per-role `--subscriber-dir`/`--publisher-dir` flags, each app reading its own `config.json` directly), as `solace-suggested-mode.md` Step 4 defines; the agent does not extract config values into positional args on the Solace Suggested path. +**Autonomous run from config.json (Quickstart only).** This applies to Quickstart ONLY. On the Quickstart path, run verify.sh for real instead of handing the commands back ONLY when BOTH conditions hold: a `config.json` is present at the generated project root, AND its four values are real, meaning they are NOT the `config.example.json` placeholders (`tcp://HOST:55555`, `YOUR_VPN`, `YOUR_USERNAME`, `YOUR_PASSWORD`). The opt-in is filling in real values, not merely copying the example: a `config.json` that still carries any placeholder value is treated as not-yet-opted-in, so fall through to the honesty-rule path below (compile only, then hand back the stage commands). This keeps a half-finished `cp config.example.json config.json` from triggering a real run against placeholder credentials. When the values are real, read the four (`host`, `vpn_name`, `username`, `password`) from `config.json` and pass them to verify.sh as POSITIONAL CLI args, using the existing `verify.sh [password]` contract. Run the three stages in this order: consumer first (it provisions the queue), then publisher, then roundtrip. Interpret each stage's exit code exactly as defined above. Immediately before the live run, print the ONE heads-up line this Step requires — name the host from `config.json` and state the ACTUAL broker-side effects of this run (the queues it provisions, the connections it opens, the messages it publishes) — then run immediately with NO confirmation prompt: a config.json with real values is the consent, so there is no gate. When `config.json` is absent or still holds placeholders, Quickstart keeps the existing behavior (compile only, then hand back the commands), and on that fall-through also point the developer at the autonomous option: tell them you generated a `config.example.json`, and that they can have you run all stages for them by copying it to `config.json`, filling in their real broker values, and asking you to verify. The Solace Suggested path ALSO runs verify.sh under the same real-values consent, but in its two-project mode (the per-role `--subscriber-dir`/`--publisher-dir` flags, each app reading its own `config.json` directly), as `solace-suggested-mode.md` Step 4 defines; the agent does not extract config values into positional args on the Solace Suggested path. -**Honesty rule (no fabricated runs).** If the developer's credentials are placeholders or not provided, do NOT fabricate a successful run. Compile only (for example `mvn -q compile`), report the compile result, and list the exact commands the developer must run against their broker: +**Honesty rule (no fabricated runs).** If the developer's credentials are placeholders or not provided, do NOT fabricate a successful run. Compile only (for example `mvn -q compile`), report the compile result, and list the exact commands the developer must run against their broker (from the project root, where Step 4 placed the script): ``` -scripts/verify.sh publisher -scripts/verify.sh consumer -scripts/verify.sh roundtrip +./verify.sh publisher +./verify.sh consumer +./verify.sh roundtrip ``` +For an `app`-stage shape, the handed-back command is `./verify.sh app` after the developer fills in `config.json`. + **Close every run with the next steps, stated plainly.** After generating the project, do NOT leave the developer guessing what to do next; end with a short, explicit next-steps message. -- **Quickstart.** Tell the developer, in order: (1) copy `config.example.json` to `config.json` and fill in their real broker values (`host`, `vpn_name`, `username`, `password`); (2) then just ask you to verify, and you will run `scripts/verify.sh` for them against their broker (it provisions the durable queue and publishes one message). If they would rather run it themselves, point them at the three `verify.sh` stage commands above. Make the fill-in-`config.json`-then-ask-me path the recommended one; it is the frictionless path to a verified round-trip. Step 6 writes the tailored `solace-verification-checklist.md` into the project root; when you deliver this close-of-run message, invite the developer to add their own items in its Your additional items section and to record any skill-undelivered concerns under Developer-owned items (not delivered by this skill). -- **Solace Suggested.** Same shape as Quickstart, via `verify.sh` two-project mode: fill in each project's gitignored `config.json` with real values, then just ask you to verify and you will run `scripts/verify.sh` against the broker with the per-role directory flags (`--subscriber-dir`/`--publisher-dir`, or `--replier-dir`/`--requestor-dir` for request-reply); it reads each `config.json` (no CLI-arg credentials) and needs a TLS-capable broker. If any `config.json` is still placeholder, compile each project and hand back the two-project `verify.sh` command instead. See `solace-suggested-mode.md` Step 4. Then emit and report the verification checklist per Step 6, which writes the tailored `solace-verification-checklist.md` into each project root; when you deliver this close-of-run message, invite the developer to add their own items in its Your additional items section and to record any skill-undelivered concerns under Developer-owned items (not delivered by this skill). +- **Quickstart.** Tell the developer, in order: (1) copy `config.example.json` to `config.json` and fill in their real broker values (`host`, `vpn_name`, `username`, `password`); (2) then just ask you to verify, and you will run `verify.sh` for them against their broker (naming the host and side effects first). If they would rather run it themselves, point them at the stage commands above. Make the fill-in-`config.json`-then-ask-me path the recommended one; it is the frictionless path to a verified round-trip. Step 4 already wrote the tailored `solace-verification-checklist.md` into the project root; when you deliver this close-of-run message, invite the developer to add their own items in its Your additional items section and to record any skill-undelivered concerns under Developer-owned items (not delivered by this skill). +- **Solace Suggested.** Same shape as Quickstart, via `verify.sh` two-project mode: fill in each project's gitignored `config.json` with real values, then just ask you to verify and you will run `verify.sh` against the broker with the per-role directory flags (`--subscriber-dir`/`--publisher-dir`, or `--replier-dir`/`--requestor-dir` for request-reply); it reads each `config.json` (no CLI-arg credentials) and needs a TLS-capable broker. If any `config.json` is still placeholder, compile each project and hand back the two-project `verify.sh` command instead. See `solace-suggested-mode.md` Step 4. Then report the verification checklist per Step 6; when you deliver this close-of-run message, invite the developer to add their own items in its Your additional items section and to record any skill-undelivered concerns under Developer-owned items (not delivered by this skill). ### Traps to avoid +- **Improvised verdicts.** Curl against the app's own API, or a browser click, proves the web layer answered; it does not prove the broker round trip, and it loses the exit-code triad. Trigger with whatever fits; judge ONLY by the markers and the verify.sh exit code. - **Publishing direct-to-queue instead of pub/sub.** Publish to a topic; let the durable queue carry a topic subscription. See [Provisioning a Durable Endpoint](https://docs.solace.com/API/API-Developer-Guide-JCSMP/JCSMP-API-Provisioning-Durable-End.md). - **XML or text payload instead of binary.** Default to a binary `BytesMessage` payload. The `XML` in JCSMP type names like `XMLMessage` and `BytesXMLMessage` is legacy API naming, not an XML payload format. Do not conflate them. - **DIRECT delivery where Guaranteed is required.** Use PERSISTENT delivery for the guaranteed pub/sub journey. See [Message Delivery Modes](https://docs.solace.com/API/API-Developer-Guide-JCSMP/JCSMP-API-Message-Delivery-Modes.md). -- **Hardcoded credentials.** Never inline host, VPN, username, or password. Both doors build the connection `JCSMPProperties` through the shared `SolaceConnectionConfig` helper: Solace Suggested reads a gitignored `config.json`; Quickstart reads `config.json` when present and otherwise falls back to CLI args. -- **Hardcoded `sol-jcsmp` version.** Resolve the `sol-jcsmp` version at generation time (Step 3); never pin a `sol-jcsmp` number in the skill. log4j2 is the intentional exception: it is pinned to a known-good 2.x version (at or above `2.17.1`) per Step 3, and updating that pin is a deliberate maintenance action. -- **Wildcard imports.** When adapting the two samples, keep every import explicit in BOTH generated classes as the reference samples do; never collapse them to a wildcard like `import com.solacesystems.jcsmp.*;`. Wildcard imports are an anti-pattern: they hide which API types the code actually depends on and risk silent collisions as packages evolve. +- **Hardcoded credentials.** Never inline host, VPN, username, or password. All doors build the connection `JCSMPProperties` through the shared `SolaceConnectionConfig` helper: Solace Suggested reads a gitignored `config.json`; Quickstart reads `config.json` when present and otherwise falls back to CLI args. +- **Hardcoded versions.** Resolve every dependency version at generation time (Step 3); never pin a number in the skill or the generated pom rationale. The log4j2 `2.17.1` Log4Shell floor is a CHECK on the resolved result, not a pin. +- **Wildcard imports.** When adapting the samples, keep every import explicit in ALL generated classes as the reference samples do; never collapse them to a wildcard like `import com.solacesystems.jcsmp.*;`. Wildcard imports are an anti-pattern: they hide which API types the code actually depends on and risk silent collisions as packages evolve. - **Wrong groupId.** The Maven doc shows `com.solace`; the correct groupId is `com.solacesystems`. The shorter form does not resolve on Maven Central. -- **Running the Publisher before the Subscriber has provisioned the queue.** If the Publisher runs first, the PERSISTENT message is published to a topic the durable queue is not yet subscribed to, so it is silently dropped and never received. The default is NOT to add a Publisher fail-fast that probes for the queue (real producers do not check consumer queues, and it would dilute the near-verbatim shape). Instead, run the Subscriber first: `scripts/verify.sh` enforces that ordering, and the `VERIFY: MESSAGE_RECEIVED` marker is the real proof the round-trip worked. +- **Running the Publisher before the Subscriber has provisioned the queue.** If the Publisher runs first, the PERSISTENT message is published to a topic the durable queue is not yet subscribed to, so it is silently dropped and never received. The default is NOT to add a Publisher fail-fast that probes for the queue (real producers do not check consumer queues, and it would dilute the near-verbatim shape). Instead, run the Subscriber first: `verify.sh` enforces that ordering, and the `VERIFY: MESSAGE_RECEIVED` marker is the real proof the round-trip worked. - **Treating a sample as a spec that overrides the approved design.** The samples are grounded reference, not fixed templates. When the design summary records a variant of its leaf (a topology or reliability deviation from the base sample), adapt the sample to the design; do NOT snap the variant back to the sample's default shape, and do NOT report the deviation as a "conflict" with the skill. See "Adapting a sample to a variant of its leaf" above; hold the invariants and the marker contract while adapting, and treat a verified variant as fully first-class. -## Step 6: Emit the tailored verification checklist and report +## Step 6: Tailor the checklist (written at Step 4) and report -At the end of EVERY Implement run, on every door (Quickstart, Solace Suggested Secure or Non-Secure, and Custom), do two things: write a tailored `solace-verification-checklist.md` into the generated project, and report the same resolution in chat. The checklist is generation output, not an in-chat-only walk. +Step 4 writes the tailored `solace-verification-checklist.md` into the generated project as generation output. This step defines the tailoring and adds the end-of-run report in chat. At the end of EVERY Implement run, on every door (Quickstart, Solace Suggested Secure or Non-Secure, and Custom) and every app shape, report the checklist resolution in chat; the emitted file and the report say the same thing. -**1. Resolve each item to its state for the chosen mode.** Read `verification-checklist.md` (the master template) and resolve each item to its actual state for the door the developer chose and the code that was generated, using the master template's per-mode branch notes. Sort each resolved item into one of the three responsibility groups the master template defines: +**1. Resolve each item to its state for the chosen mode.** Read `verification-checklist.md` (the master template) and resolve each item to its actual state for the door the developer chose and the code that was generated, using the master template's per-mode branch notes. Sort each resolved item into one of the responsibility groups the master template defines: - **Delivered by this generation**: the chosen mode generated this into the code. - **Your responsibility (not delivered here)**: the chosen mode did not generate this, so it stays the developer's job. - **Verified by the round-trip**: the conformance checks the publisher to consumer round-trip already exercises. +- **Generation conformance**: the mechanical checks the `verify.sh` preflight reports (the disclaimer header, the logging floor, the version freshness, the recorded verify stage and exit code). An item the chosen mode generated lands under Delivered by this generation; an item that mode did not generate lands under Your responsibility (not delivered here). For example the TLS secure-session item is Delivered by this generation on Solace Suggested Secure and on Custom with the secure-session knob ticked, and it is Your responsibility on Quickstart, on Solace Suggested Non-Secure, and on Custom with that knob unticked. Resolve the admin-provisioned-queue item explicitly, not just the session-and-project items: it is Delivered by this generation ONLY on Custom with the admin-provisioned-queue knob ticked (the generated app binds a pre-existing admin-owned queue per `custom-mode.md`), and it is Your responsibility on Quickstart, on Solace Suggested (both sub-modes, which give admin-provisioned-queue guidance only and generate no binding code per `solace-suggested-mode.md`), and on Custom with that knob unticked. -**2. Write the tailored file into the generated project, always.** Write the resolved result into the generated project as a file named exactly `solace-verification-checklist.md` at the project root, parallel to the `solace-design.md` that Design mode writes, so the two per-project Solace files read as a set. This write is ALWAYS done as generation output, like the pom and the classes, with NO consent gate. It differs from `solace-design.md`, which `design-mode.md` Step 4 writes only on the developer's OK: the checklist is always written, never gated. On the decoupled two-project layout (Solace Suggested, both sub-modes, and Custom with the decoupled-projects knob ticked) write ONE copy into EACH project root, consistent with each project already carrying its own `config.json` and `.gitignore`; single-project modes (Quickstart, and Custom without the decoupled-projects knob) get one copy at the project root. - -**3. Give the emitted file its tailored shape.** The emitted file carries the three responsibility groups above with each item pre-resolved to its state for this mode (not the master template's branch notes, which exist only for tailoring), then two more sections carried over from the master template: +**2. The emitted file's tailored shape.** The emitted file carries the responsibility groups above with each item pre-resolved to its state for this mode (not the master template's branch notes, which exist only for tailoring), then two more sections carried over from the master template: -- **Developer-owned items (not delivered by this skill)**: any item the skill does not deliver (non-Solace, out-of-scope, or otherwise), recorded as developer-owned rather than skill-satisfied, so at a glance a reader sees which items the skill stands behind and which the developer is tracking on their own. +- **Developer-owned items (not delivered by this skill)**: any item the skill does not deliver (non-Solace, out-of-scope, or otherwise), recorded as developer-owned rather than skill-satisfied, so at a glance a reader sees which items the skill stands behind and which the developer is tracking on their own. A consented deviation (for example dropping the decoupled-projects shape on a single-deliverable ask) is recorded here. - **Your additional items**: open space for the developer to keep growing the list after the run. -The emitted checklist is an artifact, not source, so by default it carries NO AI-assisted disclaimer header; that header is for generated source files, per Step 4. +On the decoupled two-project layout (Solace Suggested, both sub-modes, and Custom with the decoupled-projects knob ticked) write ONE copy into EACH project root, consistent with each project already carrying its own `config.json` and `.gitignore`; single-project modes get one copy at the project root, parallel to the `solace-design.md` that Design mode writes, so the per-project Solace files read as a set. The emitted checklist is an artifact, not source, so it carries NO AI-assisted disclaimer header; that header is for generated source files, per Step 4. -**4. Report the same resolution in chat.** Tell the developer what landed under each responsibility group and that the tailored `solace-verification-checklist.md` was written into the project (into each project root on the decoupled layout). The in-chat report is retained: the emitted file and the report say the same thing. +**3. Report the same resolution in chat.** Tell the developer what landed under each responsibility group and that the tailored `solace-verification-checklist.md` was written into the project (into each project root on the decoupled layout). Record the verify.sh stage that ran and its exit code in the checklist's Generation conformance group; on the compile-only fallback, record that the run was handed back instead. ## Grounding references @@ -371,5 +249,5 @@ Live `docs.solace.com` `.md` pages (WebFetch on demand): Public URLs (not docs.solace.com doc pages; use the live URL directly): - [JCSMP Javadoc](https://docs.solace.com/API-Developer-Online-Ref-Documentation/java/index.html) -- [Java API Release Notes](https://products.solace.com/download/JAVA_API_RN) +- [JCSMP API Release Notes](https://products.solace.com/download/JAVA_API_RN) - [sol-jcsmp on Maven Central](https://central.sonatype.com/artifact/com.solacesystems/sol-jcsmp) diff --git a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/implement-request-reply.md b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/implement-request-reply.md new file mode 100644 index 0000000..a252e44 --- /dev/null +++ b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/implement-request-reply.md @@ -0,0 +1,78 @@ +# Implement: Request-Reply leaves + +The per-leaf wiring for both request-reply leaves. Read this file from `implement-mode.md` Step 4 when the `Pattern` field is `Request-Reply (Direct)` or `Request-Reply (Guaranteed)`. The shared Step 4 rules in `implement-mode.md` (the AI-assisted disclaimer header, the shared `SolaceConnectionConfig` helper, the variant discipline, the demo-harness rule, and the explicit-single-class-imports rule) apply here unchanged. + +## Request-Reply (Direct) leaf + +When the `Pattern` field is `Request-Reply (Direct)`, generation reads the direct request-reply sample pair (`jcsmp-direct-requestor-sample.java` + `jcsmp-direct-replier-sample.java`) and adapts each into a generated class, the same near-verbatim discipline the guaranteed and direct pub/sub flows use. Direct request-reply is at-most-once on both legs: there is no broker ACK and no redelivery, so this leaf has NO queue, NO provisioning, and NO CLIENT-ack. The requestor self-exits after the reply (its blocking request is the synchronization point); the replier is the long-running SIGINT target. Read the two samples by their exact relative paths and adapt each into its own generated class (a Requestor and a Replier), each building its own `JCSMPSession`. + +- `jcsmp-direct-requestor-sample.java`: basic-auth connect, a started consumer (required before any request), a blocking `session.createRequestor()` plus `requestor.request(requestMsg, REQUEST_TIMEOUT_MS, topic)` with a POSITIVE `REQUEST_TIMEOUT_MS` (3000), a `JCSMPRequestTimeoutException` catch (optionally retrying once to absorb a cold-start race), then a self-exit. No SIGINT hook (the Requestor is a foreground process that ends on its own). +- `jcsmp-direct-replier-sample.java`: basic-auth connect, a plain topic subscription on the request topic via `session.addSubscription(requestTopic)` (no queue, no provisioning), an async consumer started with `consumer.start()`, an `onReceive` that guards on `requestMsg.getReplyTo() != null` and answers with the DIRECT reply convenience `producer.sendReply(requestMsg, replyMsg)`, and the SIGINT graceful-shutdown hook (the Replier is the long-running SIGINT target). + +### Replier class (near-verbatim `DirectReplier`) + +The Replier subscribes directly to the request topic (no queue), answers each request, and is the long-running process the developer runs FIRST. Start the file with the disclaimer header (the exact two lines `implement-mode.md` Step 4 defines), then wire it in this sequence: + +1. Build basic-auth `JCSMPProperties` (`host`, `vpn_name`, `username`, `password`) via the shared `SolaceConnectionConfig` helper (`config.json` if present, else CLI args). Set `JCSMPProperties.REAPPLY_SUBSCRIPTIONS` to `true` on those properties BEFORE `createSession` (it defaults to false): the Direct request-topic subscription is a client-side session subscription, not held on a broker queue, so without this it is lost on an automatic reconnect and the replier silently stops answering requests. +2. `JCSMPFactory.onlyInstance().createSession(...)`, passing the sample's `SessionEventHandler`, then `session.connect()`. Immediately after `connect()` returns, emit the connect marker: `trace("VERIFY: CONNECTED");`. +3. Acquire the reply producer with a minimal mandatory streaming-publish event handler (`session.getMessageProducer(handler)`). The handler parameter is mandatory even for direct: a `null` handler is rejected at runtime with "Blocking publishing mode is not supported". Direct messaging is at-most-once with no broker ACK, so the handler's `responseReceivedEx` is not expected to fire and `handleErrorEx` only logs. Do NOT pass `null`. +4. Acquire the async consumer with `session.getMessageConsumer(listener)`, add the plain request-topic subscription with `session.addSubscription(requestTopic)`, then `consumer.start()`. Immediately after `consumer.start()` returns, emit the subscribed marker: `trace("VERIFY: SUBSCRIBED");`. There is NO queue and NO `session.provision(...)` on the direct path. +5. In the listener's `onReceive`, guard on `requestMsg.getReplyTo() != null` (a message with no reply-to is not a request, so ignore it); immediately emit the request marker: `trace("VERIFY: REQUEST_RECEIVED");`. Then build a reply `BytesMessage` and answer with `producer.sendReply(requestMsg, replyMsg)`, the DIRECT reply convenience (it auto-routes to the reply-to and copies the correlation). Use `sendReply`, NOT a manual `producer.send(reply, getReplyTo())`: the manual form is the guaranteed replier's contract, and crossing them loses the explicit PERSISTENT reply control the guaranteed pattern documents. +6. KEEP the long-running loop and the SIGINT graceful-shutdown hook from the sample. The Replier is the SIGINT target: on SIGINT it calls `session.closeSession()` directly (closing the session also closes the consumer; direct has no acknowledgements to drain), printing `Shutdown signal received` so the verify script sees the shutdown-hook proof line. + +### Requestor class (near-verbatim `DirectRequestor`) + +The Requestor connects, issues one blocking request, and exits on its own; it does NOT provision anything and does NOT wait for a key press or a signal. Start the file with the disclaimer header (the exact two lines `implement-mode.md` Step 4 defines), then wire it in this sequence: + +1. Build basic-auth `JCSMPProperties` (`host`, `vpn_name`, `username`, `password`) via the shared `SolaceConnectionConfig` helper (`config.json` if present, else CLI args). +2. `JCSMPFactory.onlyInstance().createSession(...)`, passing the sample's `SessionEventHandler`, then `session.connect()`. Immediately after `connect()` returns, emit the connect marker: `trace("VERIFY: CONNECTED");`. +3. Acquire BOTH a producer and a started consumer BEFORE issuing the request. The blocking Requestor sends the request through the session's message producer AND receives the correlated reply through the session's message consumer, so both must be set up first. Acquire the producer with a minimal mandatory streaming-publish event handler (`session.getMessageProducer(handler)`): the API throws "No producer to perform operation" if no producer exists, and a `null` handler is rejected with "Blocking publishing mode is not supported", so pass a no-op handler (direct is at-most-once, the handler never acts on an ACK). Then start the consumer with `session.getMessageConsumer((XMLMessageListener) null)` and `consumer.start()`; a null listener is fine because the blocking `requestor.request(...)` returns the reply directly rather than via `onReceive`. +4. Define a POSITIVE `REQUEST_TIMEOUT_MS` (3000), create the blocking requestor with `session.createRequestor()`, and issue `requestor.request(requestMsg, REQUEST_TIMEOUT_MS, requestTopic)`. The positive timeout is the direct reliability knob (NOT a burst): the call blocks up to that long for the correlated reply before throwing `JCSMPRequestTimeoutException`. Catch that exception and optionally retry once to absorb a cold-start race (the replier may not have propagated its subscription on the very first attempt). +5. When the reply is non-null, emit the reply marker: `trace("VERIFY: REPLY_RECEIVED");`. Then self-exit: close the session and return from `main`. REMOVE any ENTER/SIGINT loop: the Requestor must terminate by itself, not wait for a key press or a signal. The SIGINT shutdown hook belongs to the Replier, not the Requestor. + +### Marker contract and exec executions (direct) + +These four `VERIFY:` marker strings for the Request-Reply (Direct) leaf (`VERIFY: CONNECTED`, `VERIFY: SUBSCRIBED`, `VERIFY: REQUEST_RECEIVED`, `VERIFY: REPLY_RECEIVED`) are a fixed contract with `scripts/verify.sh` (the `direct-request-reply` stage greps the Replier log for `VERIFY: SUBSCRIBED` as its readiness gate and for `VERIFY: REQUEST_RECEIVED` as richer evidence, and the Requestor log for `VERIFY: REPLY_RECEIVED` as its pass condition). The marker MUST reach stdout: emit it via `trace(...)`, whose generated body prints with `System.out.println`, NOT via `logger.debug`. This is the single highest silent-failure risk in the whole verify flow. The strings here and the strings the script greps for MUST stay in sync character-for-character; if you change one marker string, change it in `scripts/verify.sh` in the same effort. + +**Expose both mains to the verify script via two named exec executions.** Just like the other flows, the generated pom MUST declare two `exec-maven-plugin` `` blocks for the request-reply pair, but with NEW ids `requestor` and `replier` (NOT `publisher`/`subscriber`), each carrying its own `` set to that class's fully-qualified name. The `direct-request-reply` stage addresses them as `mvn exec:java@replier` (started first, the readiness gate) and `mvn exec:java@requestor` (the foreground request). Keep the `exec-maven-plugin` version resolved at generation time. + +## Request-Reply (Guaranteed) leaf + +When the `Pattern` field is `Request-Reply (Guaranteed)`, generation reads the guaranteed request-reply sample pair (`jcsmp-guaranteed-requestor-sample.java` + `jcsmp-guaranteed-replier-sample.java`) and adapts each into a generated class, the same near-verbatim discipline the other leaves use. Guaranteed request-reply is PERSISTENT on both legs: the request and the reply are both delivered with `DeliveryMode.PERSISTENT` and broker-ACKed. Whatever the reply topology, this leaf answers with a MANUAL `producer.send(reply, request.getReplyTo())` and is explicitly NOT the direct-only blocking `Requestor` convenience API and NOT `producer.sendReply(...)` (those are the direct request-reply path, and crossing them onto the guaranteed path loses the explicit PERSISTENT reply control this pattern documents); that rule holds for the base shape and every variant. + +The BASE shape uses a per-request TEMPORARY reply queue plus a `FlowReceiver` (one outstanding request per temp queue, the reply read by a blocking `flow.receive(timeoutMs)`). The base also sets an application-managed CorrelationID on the request and matches it on receipt (the replier echoes it back): this is the doc-recommended request/reply matching mechanism, and it sits ALONGSIDE the dedicated temporary reply queue, which on its own already isolates the single outstanding reply. Generate the base shape when the design summary's `Consumption endpoint` reads `temporary reply queue + FlowReceiver` (the default). + +When the summary records a VARIANT instead (for example `durable reply queue + FlowReceiver`, chosen because the reply must survive the requestor restarting or because many requests are in flight at once), adapt the base shape to it per "Adapting a sample to a variant of its leaf" in `implement-mode.md`: the requestor provisions a DURABLE reply queue (`createQueue` plus `session.provision(..., FLAG_IGNORE_ALREADY_EXISTS)`) instead of `createTemporaryQueue`, and binds it with a `FlowReceiver` on an ASYNC listener that matches each reply to its pending request by the CorrelationID (ground the durable-endpoint APIs against the canonical docs). The application-managed CorrelationID matching carries over from the base unchanged; what the variant adds is the durable reply endpoint and the async reception model (in place of the base's blocking `flow.receive`), needed because a shared durable reply queue can hold many in-flight replies at once and the CorrelationID is what sorts each reply to its request. Both legs stay PERSISTENT, the reply is still the manual `producer.send(reply, request.getReplyTo())`, CLIENT-ack discipline is unchanged, and the four `VERIFY:` markers are emitted exactly as below. A durable reply queue survives the requestor restarting because the broker retains the reply until it is consumed, which a temporary reply queue (deleted with its session) cannot do. + +The requestor self-exits after the reply (its blocking receive, or its correlated match, is the synchronization point); the replier is the long-running SIGINT target. Read the two samples by their exact relative paths and adapt each into its own generated class (a Requestor and a Replier), each building its own `JCSMPSession`. + +- `jcsmp-guaranteed-requestor-sample.java`: basic-auth connect, a producer with a mandatory streaming-publish event handler, a per-request temporary reply queue (`session.createTemporaryQueue()`) bound by a `FlowReceiver` with a NULL listener (the blocking model), a PERSISTENT request whose reply-to is set to that temporary queue, an application-managed CorrelationID set on the request and matched on the reply (the request/reply matching mechanism), `setCorrelationKey` used for PUBLISHER ACK correlation ONLY (a local key, not request/reply matching), a `producer.send(request, requestTopic)`, then a BLOCKING `flow.receive(timeoutMs)` on the temporary reply queue, then a self-exit. No SIGINT hook (the Requestor is a foreground process that ends on its own). +- `jcsmp-guaranteed-replier-sample.java`: basic-auth connect, idempotent in-process provisioning of a durable request queue (behind a `session.isCapable(CapabilityType.ENDPOINT_MANAGEMENT)` gate) plus a topic subscription mapped onto it, a `FlowReceiver` whose handler answers each request with a MANUAL PERSISTENT `producer.send(reply, request.getReplyTo())`, and the SIGINT graceful-shutdown hook (the Replier is the long-running SIGINT target). + +### Replier class (near-verbatim `GuaranteedReplier`) + +The Replier provisions a durable request queue and its topic subscription, answers each request with a manual PERSISTENT reply, and is the long-running process the developer runs FIRST. Start the file with the disclaimer header (the exact two lines `implement-mode.md` Step 4 defines), then wire it in this sequence: + +1. Build basic-auth `JCSMPProperties` (`host`, `vpn_name`, `username`, `password`) via the shared `SolaceConnectionConfig` helper (`config.json` if present, else CLI args). Set `JCSMPProperties.IGNORE_DUPLICATE_SUBSCRIPTION_ERROR` to `true` BEFORE `createSession` (it defaults to false); otherwise `addSubscription` on the request queue throws Subscription Already Exists on every run after the first. +2. `JCSMPFactory.onlyInstance().createSession(...)`, passing the sample's `SessionEventHandler`, then `session.connect()`. Immediately after `connect()` returns, emit the connect marker: `trace("VERIFY: CONNECTED");`. +3. Acquire the reply producer with a mandatory streaming-publish event handler (`session.getMessageProducer(handler)`). Guaranteed replies are broker-ACKed, so the handler's `responseReceivedEx` fires per reply; the handler parameter is mandatory (a `null` handler is rejected at runtime with "Blocking publishing mode is not supported"). Do NOT pass `null`. +4. Best practice: check `session.isCapable(CapabilityType.ENDPOINT_MANAGEMENT)` before provisioning; exit cleanly if the broker disallows client-side endpoint management (the sample prints the `does not allow client-side endpoint management` line, which the verify script classifies as an environment failure). Then `JCSMPFactory.onlyInstance().createQueue(requestQueueName)`, build the `EndpointProperties`, and `session.provision(requestQueue, endpointProps, JCSMPSession.FLAG_IGNORE_ALREADY_EXISTS)` so a re-run is idempotent. See [Provisioning a Durable Endpoint](https://docs.solace.com/API/API-Developer-Guide-JCSMP/JCSMP-API-Provisioning-Durable-End.md). +5. `session.addSubscription(requestQueue, requestTopic, JCSMPSession.WAIT_FOR_CONFIRM)` to map the request topic onto the durable request queue. See [Adding a Topic Subscription](https://docs.solace.com/API/API-Developer-Guide-JCSMP/JCSMP-API-Adding-Topic-Subscriptio.md). Build a `ConsumerFlowProperties` flow on the request queue with the sample's `FlowEventHandler` and `flow.start()`. Immediately after `flow.start()` returns, emit the subscribed marker: `trace("VERIFY: SUBSCRIBED");`. +6. In the flow handler's `onReceive`, guard on `requestMsg.getReplyTo() != null` (a message with no reply-to is not a request, so ACK it and ignore it); immediately emit the request marker: `trace("VERIFY: REQUEST_RECEIVED");`. Then build a reply `BytesMessage`, set it `DeliveryMode.PERSISTENT`, mark it a reply with `replyMsg.setAsReplyMessage(true)` (the requestor reads the reply field back with `isReplyMessage()` as one of the agreed reply-acceptance conditions), echo the request's CorrelationID onto it verbatim, and answer with a MANUAL `producer.send(replyMsg, requestMsg.getReplyTo())`, sending the PERSISTENT reply to the requestor's temporary reply queue. Use the manual `producer.send(reply, getReplyTo())`, NOT `producer.sendReply(...)`: `sendReply` is the direct replier's at-most-once convenience, and crossing it onto the guaranteed path loses the explicit PERSISTENT reply control. ACK the request (`requestMsg.ackMessage()`) only after the reply has been sent. +7. KEEP the long-running loop and the SIGINT graceful-shutdown hook from the sample. The Replier is the SIGINT target: on SIGINT it stops the flow, finishes outstanding ACKs, and calls `session.closeSession()`, printing `Shutdown signal received` so the verify script sees the shutdown-hook proof line. See [Message Delivery Modes](https://docs.solace.com/API/API-Developer-Guide-JCSMP/JCSMP-API-Message-Delivery-Modes.md). + +### Requestor class (near-verbatim `GuaranteedRequestor`) + +The Requestor connects, issues one PERSISTENT request, blocks for the correlated reply on a temporary queue, and exits on its own; it does NOT provision the durable request queue (the Replier owns that, and starts first) and does NOT wait for a key press or a signal. Start the file with the disclaimer header (the exact two lines `implement-mode.md` Step 4 defines), then wire it in this sequence: + +1. Build basic-auth `JCSMPProperties` (`host`, `vpn_name`, `username`, `password`) via the shared `SolaceConnectionConfig` helper (`config.json` if present, else CLI args). +2. `JCSMPFactory.onlyInstance().createSession(...)`, passing the sample's `SessionEventHandler`, then `session.connect()`. Immediately after `connect()` returns, emit the connect marker: `trace("VERIFY: CONNECTED");`. +3. Acquire the producer with a mandatory streaming-publish event handler (`session.getMessageProducer(handler)`): the PERSISTENT request is broker-ACKed, and a `null` handler is rejected at runtime with "Blocking publishing mode is not supported", so pass a real handler. Create the per-request temporary reply queue with `session.createTemporaryQueue()`, build a `ConsumerFlowProperties` with `setEndpoint(replyQueue)`, create the flow with a NULL listener via `session.createFlow(null, flowProps)` (the blocking model returns the reply directly from `flow.receive(...)` rather than via an async `onReceive`), and `flow.start()`. Immediately after `flow.start()` returns, emit the subscribed marker: `trace("VERIFY: SUBSCRIBED");`. Do NOT use `session.createRequestor()`: that blocking-Requestor convenience is the direct path; the guaranteed path uses the temp-queue plus blocking `flow.receive`. VARIANT: when the summary records a durable reply queue, replace `session.createTemporaryQueue()` with `createQueue(replyQueueName)` plus `session.provision(replyQueue, endpointProps, JCSMPSession.FLAG_IGNORE_ALREADY_EXISTS)`, and bind it with a `FlowReceiver` on an async listener that matches each reply to its pending request by the CorrelationID (the same CorrelationID the base already sets, now read on the async listener instead of the blocking receive); keep everything else (PERSISTENT request, the marker, no direct convenience) unchanged. +4. Build the request `BytesMessage`, set it `DeliveryMode.PERSISTENT`, and set its reply-to to the temporary reply queue with `requestMsg.setReplyTo(replyQueue)`. Set an application-managed CorrelationID on the request (`requestMsg.setCorrelationId(...)`, a unique value such as a `UUID` carrying the application's agreed notable prefix, per the reference sample's `CORRELATION_ID_PREFIX`); the replier echoes it onto the reply and the requestor matches it on receipt (step 5). This is the request/reply matching mechanism the Solace docs recommend, and it sits alongside the dedicated temporary reply queue. Also set `requestMsg.setCorrelationKey(requestMsg)`, and state in a code comment that `setCorrelationKey` is for PUBLISHER ACK correlation ONLY (a local key that identifies the request in the producer's ACK callback, never sent on the wire), distinct from the on-the-wire CorrelationID used for request/reply matching. Then `producer.send(requestMsg, requestTopic)`. +5. BLOCK on `flow.receive(timeoutMs)` on the temporary reply queue (a positive timeout; it returns null on timeout). A single PERSISTENT request plus a temp-queue reply is reliable, so issue exactly ONE request with NO burst and NO retry. When the reply meets the agreed reply-acceptance contract (its reply field is set, read with `isReplyMessage()`, and its CorrelationID carries the agreed prefix and equals the one set on the request; the reference sample's `isExpectedReply` method is the contract's single home), emit the reply marker: `trace("VERIFY: REPLY_RECEIVED");`. Then self-exit: stop the flow, close the session (which auto-deletes the temporary reply queue), and return from `main`. REMOVE any ENTER/SIGINT loop: the Requestor must terminate by itself. The SIGINT shutdown hook belongs to the Replier, not the Requestor. VARIANT: with a durable reply queue, the reply arrives on the async `FlowReceiver` listener and is matched by correlation id rather than a blocking `flow.receive`; emit `VERIFY: REPLY_RECEIVED` on the matched reply, and on self-exit close the session WITHOUT relying on temp-queue auto-delete (the durable reply queue is provisioned and persists by design, which is what delivers the restart-survival guarantee). + +### Marker contract and exec executions (guaranteed) + +These four `VERIFY:` marker strings for the Request-Reply (Guaranteed) leaf (`VERIFY: CONNECTED`, `VERIFY: SUBSCRIBED`, `VERIFY: REQUEST_RECEIVED`, `VERIFY: REPLY_RECEIVED`) are a fixed contract with `scripts/verify.sh` (the `guaranteed-request-reply` stage greps the Replier log for `VERIFY: SUBSCRIBED` as its readiness gate and for `VERIFY: REQUEST_RECEIVED` as richer evidence, and the Requestor log for `VERIFY: REPLY_RECEIVED` as its pass condition). The marker MUST reach stdout: emit it via `trace(...)`, whose generated body prints with `System.out.println`, NOT via `logger.debug`. This is the single highest silent-failure risk in the whole verify flow. The strings here and the strings the script greps for MUST stay in sync character-for-character; if you change one marker string, change it in `scripts/verify.sh` in the same effort. + +**Expose both mains to the verify script via two named exec executions.** Just like the direct request-reply flow, the generated pom MUST declare two `exec-maven-plugin` `` blocks for the guaranteed request-reply pair, with the ids `requestor` and `replier` (NOT `publisher`/`subscriber`), each carrying its own `` set to that class's fully-qualified name. The `guaranteed-request-reply` stage addresses them as `mvn exec:java@replier` (started first, the readiness gate) and `mvn exec:java@requestor` (the foreground request). Keep the `exec-maven-plugin` version resolved at generation time. diff --git a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/jcsmp-direct-publisher-sample.java b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/jcsmp-direct-publisher-sample.java index fcfe43f..93c618f 100644 --- a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/jcsmp-direct-publisher-sample.java +++ b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/jcsmp-direct-publisher-sample.java @@ -18,6 +18,10 @@ * teardown run from main's finally on every exit path. * * Only practices documented in canonical Solace sources are encoded here. + * + * Any generated adaptation of this sample MUST begin with the exact line: + * AI-assisted code. Review before production use. + * (This reference sample itself carries no such header by design.) */ package com.solace.samples.jcsmp; @@ -57,7 +61,7 @@ public class DirectPublisher { private static final int PAYLOAD_SIZE = 512; // remember to add log4j2.xml to your classpath - private static final Logger logger = LogManager.getLogger(); // log4j2, but could also use SLF4J, JCL, etc. + private static final Logger logger = LogManager.getLogger(); // log4j2 by default; any backend swap follows the logging rule in implement-mode.md Step 3 private static volatile int msgSentCounter = 0; // num messages sent private static volatile boolean isShutdown = false; diff --git a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/jcsmp-direct-replier-sample.java b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/jcsmp-direct-replier-sample.java index 021efa8..27fe303 100644 --- a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/jcsmp-direct-replier-sample.java +++ b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/jcsmp-direct-replier-sample.java @@ -20,6 +20,10 @@ * teardown run from main's finally on every exit path. * * Only practices documented in canonical Solace sources are encoded here. + * + * Any generated adaptation of this sample MUST begin with the exact line: + * AI-assisted code. Review before production use. + * (This reference sample itself carries no such header by design.) */ package com.solace.samples.jcsmp; @@ -59,7 +63,7 @@ public class DirectReplier { private static XMLMessageProducer producer; // shared producer used to send replies // remember to add log4j2.xml to your classpath - private static final Logger logger = LogManager.getLogger(); // log4j2, but could also use SLF4J, JCL, etc. + private static final Logger logger = LogManager.getLogger(); // log4j2 by default; any backend swap follows the logging rule in implement-mode.md Step 3 /** This is the main app. Use this type of app to answer Direct (at-most-once) requests on a topic. */ public static void main(String... args) throws JCSMPException, InterruptedException, IOException { diff --git a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/jcsmp-direct-requestor-sample.java b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/jcsmp-direct-requestor-sample.java index 29897d5..3bfe9a2 100644 --- a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/jcsmp-direct-requestor-sample.java +++ b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/jcsmp-direct-requestor-sample.java @@ -20,6 +20,10 @@ * main's finally on every exit path. * * Only practices documented in canonical Solace sources are encoded here. + * + * Any generated adaptation of this sample MUST begin with the exact line: + * AI-assisted code. Review before production use. + * (This reference sample itself carries no such header by design.) */ package com.solace.samples.jcsmp; @@ -63,7 +67,7 @@ public class DirectRequestor { private static JCSMPSession session; // remember to add log4j2.xml to your classpath - private static final Logger logger = LogManager.getLogger(); // log4j2, but could also use SLF4J, JCL, etc. + private static final Logger logger = LogManager.getLogger(); // log4j2 by default; any backend swap follows the logging rule in implement-mode.md Step 3 /** Main. */ public static void main(String... args) throws JCSMPException, IOException, InterruptedException { diff --git a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/jcsmp-direct-subscriber-sample.java b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/jcsmp-direct-subscriber-sample.java index 7e4913b..d4b6607 100644 --- a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/jcsmp-direct-subscriber-sample.java +++ b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/jcsmp-direct-subscriber-sample.java @@ -17,6 +17,10 @@ * main's finally on every exit path. * * Only practices documented in canonical Solace sources are encoded here. + * + * Any generated adaptation of this sample MUST begin with the exact line: + * AI-assisted code. Review before production use. + * (This reference sample itself carries no such header by design.) */ package com.solace.samples.jcsmp; @@ -54,7 +58,7 @@ public class DirectSubscriber { private static JCSMPSession session; // remember to add log4j2.xml to your classpath - private static final Logger logger = LogManager.getLogger(); // log4j2, but could also use SLF4J, JCL, etc. + private static final Logger logger = LogManager.getLogger(); // log4j2 by default; any backend swap follows the logging rule in implement-mode.md Step 3 /** This is the main app. Use this type of app for receiving Direct (at-most-once) messages from a topic. */ public static void main(String... args) throws JCSMPException, InterruptedException, IOException { diff --git a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/jcsmp-guaranteed-publisher-sample.java b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/jcsmp-guaranteed-publisher-sample.java index 9974cd2..a4d0ef1 100644 --- a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/jcsmp-guaranteed-publisher-sample.java +++ b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/jcsmp-guaranteed-publisher-sample.java @@ -15,6 +15,10 @@ * teardownSolace(), with teardown run from main's finally on every exit path. * * Only practices documented in canonical Solace sources are encoded here. + * + * Any generated adaptation of this sample MUST begin with the exact line: + * AI-assisted code. Review before production use. + * (This reference sample itself carries no such header by design.) */ package com.solace.samples.jcsmp; @@ -58,7 +62,7 @@ public class GuaranteedPublisher { private static final int PAYLOAD_SIZE = 512; // remember to add log4j2.xml to your classpath - private static final Logger logger = LogManager.getLogger(); // log4j2, but could also use SLF4J, JCL, etc. + private static final Logger logger = LogManager.getLogger(); // log4j2 by default; any backend swap follows the logging rule in implement-mode.md Step 3 private static volatile int msgSentCounter = 0; // num messages sent private static volatile boolean isShutdown = false; diff --git a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/jcsmp-guaranteed-replier-sample.java b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/jcsmp-guaranteed-replier-sample.java index b15d82b..471a19c 100644 --- a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/jcsmp-guaranteed-replier-sample.java +++ b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/jcsmp-guaranteed-replier-sample.java @@ -15,6 +15,10 @@ * teardownSolace(), with teardown run from main's finally on every exit path. * * Only practices documented in canonical Solace sources are encoded here. + * + * Any generated adaptation of this sample MUST begin with the exact line: + * AI-assisted code. Review before production use. + * (This reference sample itself carries no such header by design.) */ package com.solace.samples.jcsmp; @@ -66,7 +70,7 @@ public class GuaranteedReplier { private static JCSMPSession session; // remember to add log4j2.xml to your classpath - private static final Logger logger = LogManager.getLogger(); // log4j2, but could also use SLF4J, JCL, etc. + private static final Logger logger = LogManager.getLogger(); // log4j2 by default; any backend swap follows the logging rule in implement-mode.md Step 3 /** This is the main app. Use this type of app to answer Guaranteed (PERSISTENT) requests off a durable queue. */ public static void main(String... args) throws JCSMPException, InterruptedException, IOException { diff --git a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/jcsmp-guaranteed-requestor-sample.java b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/jcsmp-guaranteed-requestor-sample.java index 8c9f8ed..9d0c1d8 100644 --- a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/jcsmp-guaranteed-requestor-sample.java +++ b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/jcsmp-guaranteed-requestor-sample.java @@ -15,6 +15,10 @@ * exit path. * * Only practices documented in canonical Solace sources are encoded here. + * + * Any generated adaptation of this sample MUST begin with the exact line: + * AI-assisted code. Review before production use. + * (This reference sample itself carries no such header by design.) */ package com.solace.samples.jcsmp; @@ -66,7 +70,7 @@ public class GuaranteedRequestor { private static FlowReceiver replyFlow; // remember to add log4j2.xml to your classpath - private static final Logger logger = LogManager.getLogger(); // log4j2, but could also use SLF4J, JCL, etc. + private static final Logger logger = LogManager.getLogger(); // log4j2 by default; any backend swap follows the logging rule in implement-mode.md Step 3 /** Main. */ public static void main(String... args) throws JCSMPException, IOException, InterruptedException { diff --git a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/jcsmp-guaranteed-subscriber-sample.java b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/jcsmp-guaranteed-subscriber-sample.java index 720ff6e..6f13cbd 100644 --- a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/jcsmp-guaranteed-subscriber-sample.java +++ b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/jcsmp-guaranteed-subscriber-sample.java @@ -17,6 +17,10 @@ * with teardown run from main's finally on every exit path. * * Only practices documented in canonical Solace sources are encoded here. + * + * Any generated adaptation of this sample MUST begin with the exact line: + * AI-assisted code. Review before production use. + * (This reference sample itself carries no such header by design.) */ package com.solace.samples.jcsmp; @@ -62,7 +66,7 @@ public class GuaranteedSubscriber { private static JCSMPSession session; // remember to add log4j2.xml to your classpath - private static final Logger logger = LogManager.getLogger(); // log4j2, but could also use SLF4J, JCL, etc. + private static final Logger logger = LogManager.getLogger(); // log4j2 by default; any backend swap follows the logging rule in implement-mode.md Step 3 /** This is the main app. Use this type of app for receiving Guaranteed messages (e.g. via a queue endpoint). */ public static void main(String... args) throws JCSMPException, InterruptedException, IOException { diff --git a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/jcsmp-solace-connection-config.java b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/jcsmp-solace-connection-config.java index 04b43b6..2a2476f 100644 --- a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/jcsmp-solace-connection-config.java +++ b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/jcsmp-solace-connection-config.java @@ -1,6 +1,10 @@ /* * Shared connection-config helper for the JCSMP reference samples. * + * Any generated adaptation of this sample MUST begin with the exact line: + * AI-assisted code. Review before production use. + * (This reference sample itself carries no such header by design.) + * * Loads connection properties and turns them into a JCSMPProperties. Source * precedence: * 1. a config.json in the working directory (the project root), when present. diff --git a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/prerequisites.md b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/prerequisites.md index 6b94832..f817f62 100644 --- a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/prerequisites.md +++ b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/prerequisites.md @@ -1,24 +1,19 @@ # Prerequisites -Read this when a developer is new to Solace or does not yet have a broker to build against. Both Design mode and Implement mode route here first if the developer lacks basic Solace grounding or a reachable broker. +Read this when a developer does not yet have a broker to build against. Both Design mode and Implement mode route here first if the developer lacks a reachable broker. ## Table of contents -- What is Solace - Obtain a broker - Learn the JCSMP basics -## What is Solace - -Ground the developer in the high-level model (event broker, event mesh, topics, queues) before discussing patterns or generating code: [Solace Core Concepts](https://docs.solace.com/Get-Started/event-mesh-basics.md). Do not paraphrase the page. Point the developer at it, confirm they grasp the publish/subscribe and queue concepts, then continue. - ## Obtain a broker You need one reachable broker. The primary, recommended choice is [Solace Cloud](https://docs.solace.com/Get-Started/Getting-Started-Try-Broker.md): it is the simplest path to a running broker for a greenfield app and for the run-and-observe round-trip. Default to it unless the developer has a reason not to. As brief alternatives, the same page also documents a self-hosted Software Broker (run locally via container or VM) and an Appliance (existing hardware the developer already operates) for developers who cannot use Solace Cloud. -The skill does not provision or configure the broker. It assumes the broker is reachable and that the developer has connection details (host, message VPN, client username, password). +The skill does not provision or configure the broker. It assumes the broker is reachable and that the developer has connection details (host, message VPN, client username, password). A broker discovered running in the environment (for example a local container) is a fact to report, never an answer: still ask which broker the developer wants to target. ## Learn the JCSMP basics diff --git a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/scripts/verify.sh b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/scripts/verify.sh index 64fb433..88dc03d 100755 --- a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/scripts/verify.sh +++ b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/scripts/verify.sh @@ -7,6 +7,13 @@ # Solace Suggested two-project (each app reads its own gitignored config.json): # verify.sh --subscriber-dir --publisher-dir # verify.sh --replier-dir --requestor-dir +# Any other app shape (web app / embedded service; the app reads its own config): +# verify.sh app +# Sources ./verify-hooks.sh (generated with the project) for the ONLY +# app-specific facts: START_CMD (starts the app), TRIGGER_CMD (causes one +# publish; curl is fine HERE, as the trigger), READY_MARKER, PASS_MARKER. +# The observer logic (marker watching, timeouts, env classification, the +# 0/1/2 exit contract) stays in this script, identical for every shape. # # What it does (guaranteed pub/sub stage; the direct and request-reply stages are # documented at their own functions below): compiles the generated Maven project, then runs the subscriber @@ -86,6 +93,9 @@ usage: # Solace Suggested two-project (each app reads its own gitignored config.json): verify.sh --subscriber-dir --publisher-dir verify.sh --replier-dir --requestor-dir + + # Any other app shape (web app, embedded service; reads ./verify-hooks.sh): + verify.sh app EOF exit 1 } @@ -134,12 +144,65 @@ if [ "$TWO_PROJECT" -eq 1 ]; then # project dir (its config.json is read relative to that dir) regardless of cwd. SUB_DIR="$(cd "$SUB_DIR" && pwd)" PUB_DIR="$(cd "$PUB_DIR" && pwd)" -else +elif [ "$STAGE" != "app" ]; then [ -z "$HOST" ] && usage [ -z "$VPN" ] && usage [ -z "$USER_NAME" ] && usage fi +# ── Preflight conformance warnings (warn-only; never changes the exit contract) ── +# Reports the generation-conformance misses that the verification checklist's +# "Generation conformance" group records: a missing tailored checklist, source +# files without the AI-assisted disclaimer header, a log4j-core below the 2.17.1 +# Log4Shell floor, and a sol-jcsmp version that drifted from the authoritative +# repo1.maven.org metadata. Warnings only: the 0/1/2 contract is untouched. +preflight() { + local d="$1" + if [ ! -f "$d/solace-verification-checklist.md" ]; then + echo "PREFLIGHT WARN: $d/solace-verification-checklist.md is missing (every generation must emit it; implement-mode.md Step 4)" >&2 + fi + if [ -d "$d/src/main/java" ]; then + local missing + missing=$(grep -rL --include='*.java' 'AI-assisted code. Review before production use.' "$d/src/main/java" 2>/dev/null || true) + if [ -n "$missing" ]; then + echo "PREFLIGHT WARN: generated source files missing the AI-assisted disclaimer header:" >&2 + echo "$missing" >&2 + fi + fi + if [ -f "$d/pom.xml" ]; then + local l4j lo pomv relv + l4j=$(grep -A2 'log4j-core' "$d/pom.xml" 2>/dev/null | grep -oE '[^<]+' | head -1 | sed 's///') || true + case "${l4j:-}" in + ''|*'${'*) : ;; # absent or a Maven property; nothing to compare + *) + lo=$(printf '%s\n' "$l4j" '2.17.1' | sort -V 2>/dev/null | head -1) || true + if [ -n "${lo:-}" ] && [ "$lo" != "2.17.1" ]; then + echo "PREFLIGHT WARN: log4j-core $l4j in $d/pom.xml is below the 2.17.1 Log4Shell floor (CVE-2021-44228 family)" >&2 + fi + ;; + esac + pomv=$(grep -A2 'sol-jcsmp' "$d/pom.xml" 2>/dev/null | grep -oE '[^<]+' | head -1 | sed 's///') || true + case "${pomv:-}" in + ''|*'${'*) : ;; # absent or a Maven property; nothing to compare + *) + relv=$(curl -s --max-time 5 https://repo1.maven.org/maven2/com/solacesystems/sol-jcsmp/maven-metadata.xml 2>/dev/null | grep -oE '[^<]+' | sed -E 's/<\/?release>//g') || true + if [ -n "${relv:-}" ] && [ "$pomv" != "$relv" ]; then + echo "PREFLIGHT WARN: pom sol-jcsmp $pomv in $d differs from the authoritative latest GA $relv (resolve from repo1.maven.org metadata, never solrsearch)" >&2 + fi + ;; + esac + fi +} +if [ "$TWO_PROJECT" -eq 1 ]; then + preflight "$SUB_DIR" + preflight "$PUB_DIR" +else + preflight "." +fi + +# ── Live-run heads-up (SKILL.md Invariant 6): name the target and the side effects ─ +echo "── Live-run heads-up: stage '$STAGE' starts processes that connect to broker ${HOST:-}; broker-side effects can include provisioned durable queues, opened connections, and published messages ──" + # ── Stage → marker mapping ────────────────────────────────────────────────────── # The generated app emits four milestone markers across its two classes # (implement-mode.md Step 4): @@ -164,6 +227,7 @@ case "$STAGE" in direct) ;; # NEW (waves 2-4): direct pub/sub stage direct-request-reply) ;; # NEW (waves 2-4): direct request-reply stage guaranteed-request-reply) ;; # NEW (waves 2-4): guaranteed request-reply stage + app) ;; # generic single-app stage driven by ./verify-hooks.sh *) echo "unknown stage: $STAGE" >&2; usage ;; esac @@ -261,6 +325,8 @@ if [ "$TWO_PROJECT" -eq 1 ]; then exit 1 fi done +elif [ "$STAGE" = "app" ] && [ ! -f pom.xml ]; then + echo "── No pom.xml in the working directory; skipping the compile step (the app stage builds through its own START_CMD) ──" else echo "── Compiling (mvn -q compile) ─────────────────────────────────────" if ! mvn -q compile; then @@ -779,6 +845,89 @@ run_request_reply_stage() { fi } +# ── run_app_stage: the generic single-app flow (web app / embedded service) ────── +# The shape-agnostic observer. The generated ./verify-hooks.sh carries the ONLY +# app-specific facts (how to start the app, how to cause one publish, which leaf +# markers gate readiness and prove the pass); this function keeps the universal +# logic: marker watching in the app's own captured output, bounded waits, the +# ENV_SIGNATURES classification, and the shared 0/1/2 exit contract. Sourcing the +# generated hooks file runs generated shell by design: the hooks are generation +# output, exactly like the app they drive. TRIGGER_CMD may be a curl against the +# app's own API — the curl is the TRIGGER; the markers render the VERDICT. The +# app stage does not require the samples' shutdown-hook proof line: an embedded +# app may manage shutdown its own way, so a pass is READY_MARKER + PASS_MARKER +# observed and a bounded teardown. +run_app_stage() { + if [ ! -f ./verify-hooks.sh ]; then + echo "app stage needs ./verify-hooks.sh (generated with the project; defines START_CMD, TRIGGER_CMD, READY_MARKER, PASS_MARKER)" >&2 + exit 1 + fi + # shellcheck disable=SC1091 + . ./verify-hooks.sh + local v + for v in START_CMD TRIGGER_CMD READY_MARKER PASS_MARKER; do + [ -n "${!v:-}" ] || { echo "verify-hooks.sh must set $v" >&2; exit 1; } + done + + # Start the app (long-running, the SIGINT target); job control per the header note. + echo "── Starting app (START_CMD from verify-hooks.sh), watching for: $READY_MARKER ──" + set -m + bash -c "$START_CMD" >"$SUB_LOG" 2>&1 & + SUB_PID=$! + set +m + + local ready=0 + local deadline=$(( SECONDS + TIMEOUT_S )) + while [ "$SECONDS" -lt "$deadline" ]; do + if grep -qE "$ENV_SIGNATURES" "$SUB_LOG"; then + status=2 # environment failure; bypass the fix loop + break + fi + if grep -qF "$ENDPOINT_DENIED" "$SUB_LOG"; then + status=2 # broker disallows client-side endpoint management; environment, not code + break + fi + if grep -qF "$READY_MARKER" "$SUB_LOG"; then + ready=1 + break + fi + if ! kill -0 "$SUB_PID" 2>/dev/null; then + break # app exited before readiness (code failure, status stays 1) + fi + sleep 1 + done + + if [ "$status" -eq 2 ]; then + shutdown_long_running "$SUB_PID" + elif [ "$ready" -eq 1 ]; then + # Settle window, then the trigger (foreground; must return on its own). + sleep 2 + echo "── Running trigger (TRIGGER_CMD from verify-hooks.sh), watching for: $PASS_MARKER ──" + bash -c "$TRIGGER_CMD" >"$PUB_LOG" 2>&1 || true + + local pass_deadline=$(( SECONDS + TIMEOUT_S )) + while [ "$SECONDS" -lt "$pass_deadline" ]; do + if grep -qE "$ENV_SIGNATURES" "$SUB_LOG"; then + status=2 + break + fi + if grep -qF "$PASS_MARKER" "$SUB_LOG"; then + status=0 # the pass marker appeared in the app's own captured output + break + fi + if ! kill -0 "$SUB_PID" 2>/dev/null; then + break # app died before the pass marker (code failure) + fi + sleep 1 + done + + # Bounded teardown (SIGINT, then escalate); no shutdown-line requirement here. + shutdown_long_running "$SUB_PID" + else + shutdown_long_running "$SUB_PID" + fi +} + # ── Stage-dispatch front ───────────────────────────────────────────────────────── # Route the validated stage to its per-pattern function. The guaranteed arm holds # the byte-stable run_guaranteed_stage; the three new arms route to placeholder @@ -789,6 +938,7 @@ case "$STAGE" in direct) run_direct_stage ;; direct-request-reply) run_request_reply_stage direct ;; guaranteed-request-reply) run_request_reply_stage guaranteed ;; + app) run_app_stage ;; *) echo "unknown stage: $STAGE" >&2; usage ;; esac @@ -801,7 +951,13 @@ cat "$PUB_LOG" echo "────────────────────────────────────────────────────────────────────" case "$status" in - 0) echo "PASS ($STAGE): the stage milestone was observed in the role's own log, the subscriber's shutdown hook ran ('$SHUTDOWN_LINE'), and the subscriber exited cleanly (exit 0 or 130)." ;; + 0) + if [ "$STAGE" = "app" ]; then + echo "PASS (app): the readiness and pass markers were observed in the app's own captured output, and the app was stopped with a bounded teardown." + else + echo "PASS ($STAGE): the stage milestone was observed in the role's own log, the subscriber's shutdown hook ran ('$SHUTDOWN_LINE'), and the subscriber exited cleanly (exit 0 or 130)." + fi + ;; 2) echo "ENV FAILURE ($STAGE): a JCSMP connection/auth signature matched in a process log (broker or credentials are wrong, not the code). NOT entering the fix loop." >&2 ;; *) if [ "$DIRECT_ZERO_RECEIPT" -eq 1 ]; then diff --git a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/solace-suggested-mode.md b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/solace-suggested-mode.md index 6a32f7d..43b3806 100644 --- a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/solace-suggested-mode.md +++ b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/solace-suggested-mode.md @@ -8,7 +8,7 @@ This overlay's Secure sub-mode assumes a Solace Cloud broker whose TLS server ce The Solace Suggested hardening is described ONCE here and applies on top of whatever `Pattern` leaf the design summary selected. It is not duplicated per leaf. The session-level deltas (TLS and HA failover) are identical code in every leaf, the message-level delta (DMQ eligibility) lands only on PERSISTENT sends, and the separate-projects layout applies to any two-role pattern (Publisher and Subscriber, or Requestor and Replier). -This overlay defers to `implement-mode.md` for all leaf and sample mechanics: the Step 2 design-summary input contract, the Step 4 leaf dispatch (which sample pair each leaf reads, the near-verbatim adaptation discipline), and the four-file `VERIFY:` marker contract. It ADDS only the Solace Suggested deltas below. It adds NO verify stages and changes NO `VERIFY:` marker string. The marker contract, the leaf dispatch table, and the Step 6 checklist emit and report all stay in `implement-mode.md` and run unchanged on the Solace Suggested path. +This overlay defers to `implement-mode.md` for all leaf and sample mechanics: the Step 2 design-summary input contract, the Step 4 leaf dispatch (which leaf wiring file and sample pair each leaf reads, the near-verbatim adaptation discipline), and the fixed `VERIFY:` marker contract. It ADDS only the Solace Suggested deltas below. It adds NO verify stages and changes NO `VERIFY:` marker string. The marker contract, the leaf dispatch table, and the checklist write (Step 4) and report (Step 6) all stay in `implement-mode.md` and its leaf wiring files, and run unchanged on the Solace Suggested path. Every generated class this overlay describes carries the AI-assisted disclaimer header (the exact `AI-assisted code. Review before production use.` line plus a pointer to the local `solace-verification-checklist.md`, as `implement-mode.md` Step 4 defines it) and keeps every import explicit. Never collapse imports to a wildcard such as `import com.solacesystems.jcsmp.*;`; explicit single-class imports only, in every generated class, on every leaf. This overlay names identifiers, constants, and method calls; it does not paste whole generated classes. @@ -58,18 +58,20 @@ At the call site, state the broker-side prerequisite as the developer's responsi ## Step 4: Generate two separate, independent Maven projects -The Solace Suggested path decouples the two roles. Generate TWO standalone single-module Maven projects, one per role (Publisher and Subscriber, or Requestor and Replier), each in its own project root with its OWN `pom.xml`. Each pom resolves `sol-jcsmp` live at generation time (the no-hardcoded-versions invariant), pins log4j2 to `2.26.0` (the one allowed pin, at or above the `2.17.1` Log4Shell floor), and declares exactly ONE `exec-maven-plugin` execution for that project's single main. +The Solace Suggested path decouples the two roles. The DEFAULT shape is TWO standalone single-module Maven projects, one per role (Publisher and Subscriber, or Requestor and Replier), each in its own project root with its OWN `pom.xml`. Each pom resolves `sol-jcsmp` live at generation time (the no-hardcoded-versions invariant), resolves its logging backend the same way per `implement-mode.md` Step 3 (no pinned versions; the `2.17.1` Log4Shell floor is enforced as a check, not a pin), and declares exactly ONE `exec-maven-plugin` execution for that project's single main. -There is NO parent POM and NO `` aggregator. Two independent projects, not a multi-module reactor: each project builds on its own. Recommend each project is self-contained, carrying its own `config.json` plus `.gitignore` (the same four lowercase `JCSMPProperties` property-name keys, the same gitignored-and-never-committed rule), consistent with two independent projects and no shared parent. +On this default shape there is NO parent POM and NO `` aggregator. Two independent projects, not a multi-module reactor: each project builds on its own. Recommend each project is self-contained, carrying its own `config.json` plus `.gitignore` (the same four lowercase `JCSMPProperties` property-name keys, the same gitignored-and-never-committed rule), consistent with two independent projects and no shared parent. -The Solace Suggested path adds no new Maven dependency. Every Solace Suggested surface in this overlay (TLS, DMQ, HA) lives in the same `com.solacesystems:sol-jcsmp` JAR Quickstart already uses, plus the already-pinned log4j2; there is no new coordinate. +**When the developer's ask requires a single deliverable** (one command, one jar, one web app with one dashboard), the two-project default conflicts with the ask. Surface that conflict plainly and let the developer resolve it; NEVER merge silently. Offer three resolutions: (1) keep two independent projects plus a thin launcher script that starts both; (2) one Maven build with separate publisher and consumer modules under a parent pom, which keeps the decoupling and still yields one command; (3) drop the decoupled-projects shape with the developer's explicit consent. Whichever they choose is recorded in the emitted `solace-verification-checklist.md` — a consented drop lands under Developer-owned items, so the deviation is written down, not lost. + +The Solace Suggested path adds no new Maven dependency. Every Solace Suggested surface in this overlay (TLS, DMQ, HA) lives in the same `com.solacesystems:sol-jcsmp` JAR Quickstart already uses, plus the logging backend Step 3 resolves; there is no new coordinate. Solace Suggested verification uses the SAME bundled `scripts/verify.sh` as Quickstart, run in its two-project mode. `verify.sh` accepts per-role project directories, so it can drive the decoupled layout: it compiles each project, starts the long-running role first and the foreground role second, watches each process for the fixed `VERIFY:` markers, exercises the SIGINT graceful-shutdown hook, and classifies the outcome by the same 0/1/2 exit codes and bounded fix loop that `implement-mode.md` Step 5 defines. In two-project mode it passes NO connection details on the command line; each app reads its own gitignored `config.json`, so the broker credentials never appear in a process listing (the checklist's credential-handling item). Invoke it with the role-directory flags instead of the positional connection args: - pub/sub leaves: `scripts/verify.sh roundtrip --subscriber-dir --publisher-dir ` - request-reply leaves: `scripts/verify.sh guaranteed-request-reply --replier-dir --requestor-dir ` (use `direct-request-reply` for the direct leaf). -Run it for real under the SAME consent Quickstart uses: only when each project's `config.json` is present with REAL values (not the `config.example.json` placeholders). When any project's `config.json` is absent or still holds placeholders, do NOT fabricate a run: compile each project (`mvn -q compile` per root), report the compile result, and hand back the two-project `verify.sh` command for the developer to run themselves (the honesty rule). A live Solace Suggested round-trip needs a TLS-capable broker, since the `tcps://` Solace Suggested session validates the server certificate; a plaintext or unreachable broker surfaces as an environment failure (exit 2), which `verify.sh` classifies without entering the fix loop, so it never masquerades as a code bug. +Run it for real under the SAME consent Quickstart uses: only when each project's `config.json` is present with REAL values (not the `config.example.json` placeholders). Immediately before any live run, print the one-line live-broker heads-up (SKILL.md Invariant 6): name the broker host and state the run's actual broker-side effects (the queues it provisions, the connections it opens, the messages it publishes); the real-values `config.json` is the consent, the heads-up is not a prompt. When any project's `config.json` is absent or still holds placeholders, do NOT fabricate a run: compile each project (`mvn -q compile` per root), report the compile result, and hand back the two-project `verify.sh` command for the developer to run themselves (the honesty rule). A live Solace Suggested round-trip needs a TLS-capable broker, since the `tcps://` Solace Suggested session validates the server certificate; a plaintext or unreachable broker surfaces as an environment failure (exit 2), which `verify.sh` classifies without entering the fix loop, so it never masquerades as a code bug. ## Step 5: Topic architecture @@ -77,7 +79,7 @@ For how to structure, order, name, and place wildcards in the topic hierarchy, p ## Step 6: Return to the implement-mode contracts -After the Solace Suggested deltas above, return to `implement-mode.md` for the unchanged leaf mechanics: the Step 4 dispatch generates the chosen leaf's classes, and Step 6 resolves each `verification-checklist.md` item to its state for the chosen mode, writes the tailored `solace-verification-checklist.md` into the project (one copy per project root on this overlay's decoupled two-project layout), invites the developer's additions, and reports the same in chat, on the Solace Suggested path the same way it does on Quickstart. This overlay adds the Solace Suggested hardening; it does not replace those contracts. +After the Solace Suggested deltas above, return to `implement-mode.md` for the unchanged leaf mechanics: the Step 4 dispatch generates the chosen leaf's classes and writes the tailored `solace-verification-checklist.md` into the project as generation output (one copy per project root on this overlay's decoupled two-project layout), and Step 6 resolves each item to its state for the chosen mode, invites the developer's additions, and reports the same in chat, on the Solace Suggested path the same way it does on Quickstart. This overlay adds the Solace Suggested hardening; it does not replace those contracts. ## Grounding references diff --git a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/verification-checklist.md b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/verification-checklist.md index 067c402..2c63606 100644 --- a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/verification-checklist.md +++ b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/verification-checklist.md @@ -2,7 +2,7 @@ This checklist records what the chosen mode delivered and what remains the developer's responsibility. Each item is one binary (yes/no) check with one canonical Solace doc link. Read the linked doc to decide; the check is not a substitute for the doc. -This file is the master template. Every Implement run emits a per-project copy into the generated project, tailored to the chosen mode and the code that was generated. Nothing here implies the generated output is ready to carry real traffic; each mode delivers a baseline, and the items below name what it delivered and what stays the developer's job. The three groups make that split explicit. "Delivered by this generation" names what the chosen mode generated into the code, so on that mode those items report as delivered; each carries a per-mode branch note because the same item is the developer's responsibility on a mode that did not generate it (for example, the secure-session item on Quickstart, which connects over plaintext). "Your responsibility (not delivered here)" names what no generation satisfies for the chosen mode, so those items stay the developer's responsibility. "Verified by the round-trip" names the conformance checks the publisher to consumer round-trip already exercises. +This file is the master template. Every Implement run emits a per-project copy into the generated project at generation time (implement-mode.md Step 4), tailored to the chosen mode and the code that was generated. Nothing here implies the generated output is ready to carry real traffic; each mode delivers a baseline, and the items below name what it delivered and what stays the developer's job. The responsibility groups make that split explicit. "Delivered by this generation" names what the chosen mode generated into the code, so on that mode those items report as delivered; each carries a per-mode branch note because the same item is the developer's responsibility on a mode that did not generate it (for example, the secure-session item on Quickstart, which connects over plaintext). "Your responsibility (not delivered here)" names what no generation satisfies for the chosen mode, so those items stay the developer's responsibility. "Verified by the round-trip" names the conformance checks the publisher to consumer round-trip already exercises. ## Delivered by this generation @@ -25,6 +25,17 @@ This file is the master template. Every Implement run emits a per-project copy i - [ ] Messages are published with PERSISTENT (Guaranteed) delivery for the guaranteed journey. [Message Delivery Modes](https://docs.solace.com/API/API-Developer-Guide-JCSMP/JCSMP-API-Message-Delivery-Modes.md) - [ ] The publisher to consumer round-trip is verified against a reachable broker: a PERSISTENT message published to the topic lands on the subscribed durable queue and is consumed and acknowledged. [Provisioning a Durable Endpoint](https://docs.solace.com/API/API-Developer-Guide-JCSMP/JCSMP-API-Provisioning-Durable-End.md) +## Generation conformance (checked by the verify.sh preflight) + +These are mechanical conformance checks on the generated output itself; the copied `verify.sh` reports the first four in its preflight, so a miss is visible on every run. They carry no doc link because they check skill conformance, not Solace guidance. + +- [ ] Every generated source file starts with the exact line `AI-assisted code. Review before production use.` followed by the checklist pointer line. +- [ ] Exactly one logging backend is configured with a config resource on the classpath; any `log4j-core` on the classpath (including transitive) is at or above `2.17.1`; the `com.solacesystems` loggers are not silenced below INFO. +- [ ] The `sol-jcsmp` version in the pom matches the authoritative `` from the `repo1.maven.org` Maven metadata at generation time (never the solrsearch index). +- [ ] This tailored checklist file exists at every generated project root. +- [ ] A `verify.sh` stage ran and its stage name and exit code are recorded here: ` → exit ` (or: the compile-only fallback was taken and the exact commands were handed back). +- [ ] Every page named in the design summary's Grounding docs field was WebFetched in this session (`none fetched` is an honest value; an unfetched citation is not). + ## Developer-owned items (not delivered by this skill) Record here any item the skill does not deliver: non-Solace concerns, out-of-scope concerns, or anything you are tracking on your own. Each is marked developer-owned rather than skill-satisfied, so at a glance a reader sees which items the skill stands behind and which you own. In this master template the section is a skeleton; the per-project copy emitted into a generated project is where concrete developer-owned items are recorded during a run. From 314e99fb17ff7ad1592af2f9e5c54d8ba5deac23 Mon Sep 17 00:00:00 2001 From: adiel-sammak <71227923+aelsammak@users.noreply.github.com> Date: Thu, 20 Aug 2026 11:28:15 -0400 Subject: [PATCH 2/3] EBP-2889: Route build requests through Design mode before Implement - Key mode routing on the design contract instead of request phrasing; a build prompt without a contract enters Design first, including web and embedded shapes - Add the design-contract gate to jcsmp.md and make the summary a hard precondition in Implement Step 2 (valid sources: same-session summary, explicit summary in chat, saved solace-design.md) - Add the topology rule for edits to existing apps: topology-changing edits re-enter Design, mechanical edits go straight to Implement - Add Design mode's fully-specified path: a prompt that answers every tree question gets one merged confirm at close-out - Dedupe the broker-access question in Implement Step 1 when Design Step 0 already confirmed it this session - Keep the door question an isolated Implement Step 0; a confirmed design never answers it --- .../references/jcsmp.md | 19 ++++++++++++------- .../references/jcsmp/design-mode.md | 4 +++- .../references/jcsmp/implement-mode.md | 15 +++++++++------ 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp.md b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp.md index 6a89197..d3ba17d 100644 --- a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp.md +++ b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp.md @@ -6,20 +6,25 @@ The cross-cutting invariants (content sourcing, WebFetch-on-demand doc grounding ## Mode Detection -Determine the user's intent and enter the appropriate mode: +Determine the user's intent and enter the appropriate mode. Routing is keyed on the design contract, not on the phrasing of the request: Implement mode never starts without a confirmed design (the design-contract gate below), so a "build me..." prompt is NOT an Implement signal by itself. | User intent | Mode | What to do | |---|---|---| | "Help me choose the Solace JCSMP messaging pattern for a new app" / "Topic or queue?" / "What delivery semantics should I use?" | **Design** | Read `jcsmp/design-mode.md` | -| "Build me a Solace JCSMP publisher/consumer app" / "Generate a Maven JCSMP project" / "Publish to a topic and consume from a queue" | **Implement** | Read `jcsmp/implement-mode.md` | -| "Build a web app / dashboard / service that connects to Solace", Spring Boot, or any embedded shape where the JCSMP layer lives inside a larger application | **Implement** | Read `jcsmp/implement-mode.md`; generate the messaging layer per its leaf rules and verify with the `app` stage of `verify.sh` (its Step 5) | +| Build or generate a NEW app or messaging component with NO valid design contract yet — "Build me a Solace JCSMP publisher/consumer app", "Generate a Maven JCSMP project", a web app / dashboard / service, Spring Boot, or any embedded shape where the JCSMP layer lives inside a larger application | **Design first** | Read `jcsmp/design-mode.md`. A prompt that already states the design resolves on its fully-specified path in ONE confirm; a bare prompt walks the tree. The confirmed summary then feeds Implement. | +| A build request WITH a valid design contract (see the design-contract gate below) | **Implement** | Read `jcsmp/implement-mode.md`; for an embedded shape generate the messaging layer per its leaf rules and verify with the `app` stage of `verify.sh` (its Step 5) | +| An edit to an EXISTING app that changes the messaging topology (the topology rule below) | **Design first** | Re-enter `jcsmp/design-mode.md` to re-confirm the affected summary fields, then Implement applies the change | +| A mechanical edit to an EXISTING app with no topology change — reconnect handling, payload format, logging, renames, ack tuning | **Implement** | Read `jcsmp/implement-mode.md`; no design pass | | "My JCSMP app is throwing on connect" / "Why is my consumer not binding the queue?" | **Debug** | Debug mode is not yet available in this release. Redirect the user to the canonical Solace JCSMP troubleshooting documentation: [JCSMP API Home](https://docs.solace.com/API/Messaging-APIs/JCSMP-API/jcsmp-api-home.md). Do not generate debugging guidance from memory. | If unclear, default to **Design**. Understand the messaging problem before generating code. -Two gates hold on every path: +**The topology rule** decides between the two existing-app rows. An edit changes the messaging topology when it changes the structure the design summary records: a new app or messaging component (a new publisher, consumer, requestor, or replier, including a leaf change such as single-service growing into fan-out), a pattern change, a delivery-mode change, a consumption-endpoint change, or a queue access-type change. That list is the core; also treat an unlisted change as topology when your judgment says it alters the message flow between the apps and the broker. Everything else is a mechanical edit. -- **Implement mode opens with a mandatory door question.** Ask Quickstart, Solace Suggested, or Custom — before you request or accept ANY broker details (`jcsmp/implement-mode.md` Step 0). Ask it even when a `config.json` with credentials already exists, even when a local broker is already running, and even when the requested app is bigger than the canonical generated shape; none of those answers the question. +Three gates hold on every path: + +- **The design-contract gate.** Implement mode never starts without a design contract, and never invents design values. Exactly three sources satisfy it: (1) a summary Design mode confirmed in this session — use it as-is, with no re-confirm; (2) an explicit summary the developer supplied in chat (the eight fields or an equivalent statement of them) — use it directly; (3) a saved `solace-design.md` in the project — restate it in one line and proceed on confirm. A prose build request that merely mentions pattern details is NOT a contract: route it through Design mode, whose fully-specified path derives the summary, echoes it, and takes one confirm. The one carve-out is the mechanical-edit row above. An experienced developer therefore never has to walk the design tree, but never skips the contract either. +- **Implement mode opens with a mandatory door question.** Ask Quickstart, Solace Suggested, or Custom — before you request or accept ANY broker details (`jcsmp/implement-mode.md` Step 0). Ask it even when a `config.json` with credentials already exists, even when a local broker is already running, and even when the requested app is bigger than the canonical generated shape; none of those answers the question. A confirmed design summary does not answer it either: the door question always follows the design contract, never merges into it. - **Environment discovery never answers a question.** A running broker container, an existing config file, or found credentials are facts to report, not answers to consume. Report what you found, then still ask which broker the developer wants to target. ## JCSMP coordinate @@ -50,8 +55,8 @@ Live exceptions (these have no `docs.solace.com` `.md` form; use the live URL di ## Mode and reference files (read on-demand only) - `jcsmp/prerequisites.md`: broker acquisition (route here first if the developer has no reachable broker). -- `jcsmp/design-mode.md`: choose the messaging pattern/topology before generating code. -- `jcsmp/implement-mode.md`: generate a runnable Maven JCSMP project (any leaf, any app shape); its Step 4 dispatches onto the per-leaf wiring files below. +- `jcsmp/design-mode.md`: choose and confirm the messaging pattern/topology before any code is generated; every new build without a design contract routes here first. +- `jcsmp/implement-mode.md`: generate a runnable Maven JCSMP project from a confirmed design (any leaf, any app shape); its Step 4 dispatches onto the per-leaf wiring files below. - `jcsmp/implement-guaranteed-pubsub.md`: the Guaranteed Pub/Sub leaf wiring (read from implement-mode Step 4). - `jcsmp/implement-direct-pubsub.md`: the Direct Pub/Sub leaf wiring (read from implement-mode Step 4). - `jcsmp/implement-request-reply.md`: both Request-Reply leaf wirings (read from implement-mode Step 4). diff --git a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/design-mode.md b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/design-mode.md index 2064fdb..92f9710 100644 --- a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/design-mode.md +++ b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/design-mode.md @@ -27,6 +27,8 @@ If a row matches, or if your own judgment maps an unmatched prompt cleanly to on **Recommend path (intent is clear).** Recommend the leaf in ONE line and confirm, for example "Sounds like Guaranteed Pub/Sub (fan-out), confirm?", then proceed on the developer's confirm. One line plus one confirm, not a rationale paragraph and not a silent zero-confirm jump. +**Fully-specified path (the prompt answers every applicable tree question).** When the prompt resolves the leaf AND answers every Step 2 question that applies to that leaf, skip the separate leaf confirm. Derive the full summary, present it (Step 4), and let the close-out question carry the single confirm: one confirm covers the leaf and the design. Do not ask a leaf confirm and then a close-out confirm; that is two questions where the prompt earned one. + **Partial-intent path (intent is partial).** When the prompt answers some branches but not all (for example it says "request a reply" but not direct vs guaranteed), lock the branches the prompt already answers and ask ONLY the remaining tree questions, in the Step 2 order, skipping every question the prompt already settled. Reach a leaf, then confirm it the same one-line way. **Unclear path (no stated intent).** When the prompt states no usable intent, walk all four tree questions in Step 2 order to reach a leaf. @@ -97,4 +99,4 @@ Field semantics: - **Broker.** Default to `Solace Cloud` and record it without asking. Switch to another type (Software Broker or Appliance) ONLY if the developer explicitly insists; otherwise leave it as `Solace Cloud`. - **Grounding docs.** ONLY the pages this session actually WebFetched. Never list a page you did not fetch: a citation without a fetch is fabricated grounding. Write `none fetched` when no page was fetched; that is an honest value, an unfetched citation is not. -After presenting the summary in chat, close Design mode explicitly so the developer knows exactly what happens next; do NOT just display the summary and stop. If the design is a variant of its leaf, state the deviation and its rationale in one line here too, so the developer approves the actual topology (not just the leaf name) before it is built. Ask them directly, in one step, both whether they are happy with this design AND whether to save it to `solace-design.md` in their project (for example: "Happy with this design? If so, I can save it to `solace-design.md` and move into Implement mode to generate the runnable Maven project."). Write the file only on their OK; never write it unprompted. Once they confirm, state the next step plainly: the work moves into Implement mode, which generates the runnable Maven project from this summary. Implement mode treats this summary, whether it lives in the chat or in the saved `solace-design.md`, as its input contract. Implement mode OPENS with its own Step 0 door question (Quickstart, Solace Suggested, or Custom), asked before any broker details are requested or accepted; an existing `config.json` or a running broker does not answer it, so ask it on the way in. +After presenting the summary in chat, close Design mode explicitly so the developer knows exactly what happens next; do NOT just display the summary and stop. On the fully-specified path (Step 1) this close-out is the ONLY confirm of the run; it carries the leaf confirmation too. If the design is a variant of its leaf, state the deviation and its rationale in one line here too, so the developer approves the actual topology (not just the leaf name) before it is built. Ask them directly, in one step, both whether they are happy with this design AND whether to save it to `solace-design.md` in their project (for example: "Happy with this design? If so, I can save it to `solace-design.md` and move into Implement mode to generate the runnable Maven project."). Write the file only on their OK; never write it unprompted. Once they confirm, state the next step plainly: the work moves into Implement mode, which generates the runnable Maven project from this summary. Implement mode treats this summary, whether it lives in the chat or in the saved `solace-design.md`, as its input contract. Implement mode OPENS with its own Step 0 door question (Quickstart, Solace Suggested, or Custom), asked before any broker details are requested or accepted; an existing `config.json` or a running broker does not answer it, so ask it on the way in. diff --git a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/implement-mode.md b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/implement-mode.md index b9006cd..aa756e1 100644 --- a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/implement-mode.md +++ b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/implement-mode.md @@ -37,7 +37,7 @@ Confirm which door the developer wants before continuing, and on the Solace Sugg ## Step 1: Confirm broker access and grounding -With the door chosen, confirm the developer has access to a reachable broker, and confirm WHICH broker they want to target. Confirm that they HAVE one; do NOT ask them to paste connection details into the chat. A broker discovered in the environment (a running container, an existing config file) is a fact to report while asking, never an answer. Where the credential VALUES live depends on the Step 0 door: for Solace Suggested they go ONLY into the gitignored `config.json`; for Quickstart they reach the app through a `config.json` when present, otherwise as CLI args at run time. The skill itself never needs the values typed into the chat (see the honesty rule in Step 5: if the values are placeholders or withheld, compile only and hand the developer the exact commands to run against their own broker). +With the door chosen, confirm the developer has access to a reachable broker, and confirm WHICH broker they want to target. Confirm that they HAVE one; do NOT ask them to paste connection details into the chat. When Design mode already confirmed broker access in this session (its Step 0), do not re-ask whether a broker exists; ask only WHICH broker to target. A broker discovered in the environment (a running container, an existing config file) is a fact to report while asking, never an answer. Where the credential VALUES live depends on the Step 0 door: for Solace Suggested they go ONLY into the gitignored `config.json`; for Quickstart they reach the app through a `config.json` when present, otherwise as CLI args at run time. The skill itself never needs the values typed into the chat (see the honesty rule in Step 5: if the values are placeholders or withheld, compile only and hand the developer the exact commands to run against their own broker). If the developer has no reachable broker, route through `prerequisites.md` first (broker acquisition, Solace Cloud recommended), then return here. @@ -45,12 +45,15 @@ Grounding catch-up: if the design summary names a page in its Grounding docs fie ## Step 2: Establish the design as the input contract -Implement mode works from the unified design summary of eight fields: Pattern, Delivery, Access type, Topic, Consumption endpoint, Auth, Broker, Grounding docs. The `Pattern` field is the discriminated leaf string (one of the six Design-mode leaves), `Consumption endpoint` carries what used to be the fixed `Queue` field (for the guaranteed pub/sub journey this resolves to a durable queue plus topic subscription), and `Access type` is surfaced explicitly. The summary can come from Design mode, but it does not have to: an experienced developer who already has a design can supply the fields directly. +Implement mode works from the unified design summary of eight fields: Pattern, Delivery, Access type, Topic, Consumption endpoint, Auth, Broker, Grounding docs. The `Pattern` field is the discriminated leaf string (one of the six Design-mode leaves), `Consumption endpoint` carries what used to be the fixed `Queue` field (for the guaranteed pub/sub journey this resolves to a durable queue plus topic subscription), and `Access type` is surfaced explicitly. -- If a summary is already in the chat, use it as-is. -- If the developer saved one, read `solace-design.md` from their project. -- If no summary exists but the developer already knows their design, take the values directly from them and proceed (at minimum: the Pattern, the topic string, the `Consumption endpoint` the pattern requires (the queue name for the guaranteed pub/sub journey, `n/a` for patterns with no queue), and the message payload; Delivery and Auth default to PERSISTENT guaranteed and basic username/password for the guaranteed pub/sub journey). An experienced developer is NOT required to run Design mode or produce a `solace-design.md` first. If the developer does not supply a topic or a queue name, derive them rather than asking (the topic via the solace-topic-best-practices skill, the queue name via the `q.`-prefixed convention), the same way Design mode does; only the Pattern and payload genuinely need the developer's input. -- Only route the developer to Design mode (`design-mode.md`) if they want help choosing the pattern/topology, or if the essential inputs above are missing and they cannot supply them. Do not invent design decisions; ask for the missing values or offer Design mode. +The summary is a HARD precondition (the design-contract gate in `jcsmp.md`): Implement mode never proceeds past this step without one, and never invents design values. Exactly three sources satisfy it: + +- A summary Design mode confirmed in this session: use it as-is, with no re-confirm. +- An explicit summary the developer supplied in chat (the eight fields or an equivalent statement of them): use it directly. If it omits a derivable field, derive it rather than asking (the topic via the solace-topic-best-practices skill, the queue name via the `q.`-prefixed convention), the same way Design mode does; only the Pattern and payload genuinely need the developer's input. An experienced developer is not required to walk the design tree; supplying the summary satisfies the contract. +- A saved `solace-design.md` in the developer's project: read it, restate it in one line, and proceed on their confirm. + +If none of the three exists, do NOT collect the fields piecemeal here and do NOT proceed: route through Design mode (`design-mode.md`) and return with its confirmed summary. This holds even when the request sounds like a build. A prose build request that mentions pattern details is not a contract; Design mode's fully-specified path resolves it in ONE confirm (derive the summary, echo it, confirm). The only carve-out is the topology rule in `jcsmp.md`: a mechanical edit to an existing app (no topology change) proceeds without a summary, while a topology-changing edit re-enters Design mode to re-confirm the affected fields first. Treat the confirmed values (Pattern, topic string, `Consumption endpoint`, payload) as the requirements for everything that follows. The discriminated `Pattern` field carries the developer's chosen leaf, and Step 4 dispatches generation onto that leaf: it reads only the leaf file and sample pair the chosen leaf needs. Read the design as the input contract and do not reject a summary because its `Pattern` is not pub/sub. From 882b9631a1ec322b409b57e036cf4b5b7697f361 Mon Sep 17 00:00:00 2001 From: adiel-sammak <71227923+aelsammak@users.noreply.github.com> Date: Thu, 20 Aug 2026 13:50:54 -0400 Subject: [PATCH 3/3] EBP-2889: Carry sample best-practice comments into generated code - Add the comments-follow-their-code contract to implement-mode Step 4: a construct carried from a sample keeps that construct's comment (names adapted), dropped demo harness takes its comments with it, and fresh messaging code that applies a documented practice gets a short comment naming it - Add a binary item to the checklist's Generation conformance group so a run that strips the comments fails its own checklist; keep it outside the preflight's first-four mechanical checks - Align the Step 6 group description with the new non-preflight item - Evidence: a generated embedded Direct Pub/Sub project carried sample constructs without their attached best-practice comments, while every explicitly mandated one-line comment survived (slot-bound rules get followed; unbound ones do not) --- .../references/jcsmp/implement-mode.md | 11 ++++++++++- .../references/jcsmp/verification-checklist.md | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/implement-mode.md b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/implement-mode.md index aa756e1..54f9b0d 100644 --- a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/implement-mode.md +++ b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/implement-mode.md @@ -128,6 +128,15 @@ Hold every invariant while adapting: keep the shared connection helper, the sess **Demo harness is not application logic.** The samples carry demo-harness elements that exist only to make a standalone run observable: the ENTER-to-quit `System.in` loop, the once-per-second stats-printing thread, the pacing `Thread.sleep` between sends, and the rotating example payload. When the developer's request describes a real application domain, do NOT carry these into the generated classes: replace the example payload and send cadence with the application's real messages and triggers, and keep only the lifecycle pieces the leaf's steps call for (the SIGINT hook on the long-running role, the self-exit on the foreground role). When the request IS a demo or a try-it run, keeping the harness is fine. Two things are NOT demo harness: the `trace(...)` narration method (keep it; applications replace its single body to route narration to their logging or reporting system) and the `VERIFY:` markers, which are emitted through `trace(...)` like every other status line. Because `verify.sh` greps captured stdout for the markers and for the shutdown hook's `Shutdown signal received` proof line (both flow through `trace(...)`), the generated `trace(...)` body MUST stay `System.out.println` through the Step 5 verification stages; rerouting that body to a logger or reporting system is the developer's step AFTER the verify stages pass, never part of generation. Never treat the logging backend (Step 3) as demo harness: the backend and its config file ship in every generated app. +**Comments follow their code.** The samples' comments are part of the reference, not decoration: they carry the documented best practices (the session-independent `createMessage`, the binary payload and the legacy `XML` naming, `getData()` versus `getBytes()`, registering a session event handler at session creation, the ACK/NACK handling options, the correlation-key rule) into the code a developer reads long after this session. Generation carries them under ONE contract, on every door and every app shape: + +- A generated class that carries a construct from a sample (near-verbatim or adapted) carries that construct's comment too, with only the names adapted to the application. Never strip a comment from a construct you kept. +- A dropped construct (the demo harness above, or a part the leaf does not use) takes its comment with it. Never orphan a comment onto unrelated code. +- Fresh messaging code with no sample twin (an embedded shape's own wiring) that applies a documented practice gets a SHORT comment naming the practice, in the samples' comment style. +- The one-line comments this skill mandates elsewhere (the Quickstart fail-fast posture below, the Non-Secure caveat in `solace-suggested-mode.md`, the `setCorrelationKey` distinction in `implement-request-reply.md`) always apply on their paths; this contract adds to them, it does not replace them. + +The tailored checklist's Generation conformance group carries a binary item for this contract, so a run that strips the comments fails its own checklist. + ### Quickstart fail-fast channel defaults On the Quickstart door ONLY, do not carry the reference samples' reconnect-tuning block into the generated classes. The samples configure a `JCSMPChannelProperties` reconnect budget (`reconnectRetries` 20, `connectRetriesPerHost` 3); Quickstart generation OMITS that whole block (the `JCSMPChannelProperties` object and the `CLIENT_CHANNEL_PROPERTIES` setProperty call), leaving connect and reconnect retries and their timeouts at the JCSMP defaults, so a wrong host, port, or credential in this learning setup fails immediately instead of retrying for minutes. Drop ONE short comment at the session-properties build site in each generated class stating that posture: connect and reconnect settings are at the JCSMP defaults so failures surface immediately; see the [JCSMP Best Practices](https://docs.solace.com/API/API-Developer-Guide-JCSMP/JCSMP-API-Best-Practices.md) page for reconnect tuning beyond Quickstart. The Solace Suggested door is unchanged: it keeps the samples' baseline or upgrades it per `solace-suggested-mode.md` Step 2. Custom follows its floor exception in `custom-mode.md`. @@ -224,7 +233,7 @@ Step 4 writes the tailored `solace-verification-checklist.md` into the generated - **Delivered by this generation**: the chosen mode generated this into the code. - **Your responsibility (not delivered here)**: the chosen mode did not generate this, so it stays the developer's job. - **Verified by the round-trip**: the conformance checks the publisher to consumer round-trip already exercises. -- **Generation conformance**: the mechanical checks the `verify.sh` preflight reports (the disclaimer header, the logging floor, the version freshness, the recorded verify stage and exit code). +- **Generation conformance**: the conformance checks on the generated output itself: the mechanical ones the `verify.sh` preflight reports (the disclaimer header, the logging floor, the version freshness, the recorded verify stage and exit code), plus the comments-follow-their-code item, which is checked by reading the generated classes rather than by the preflight. An item the chosen mode generated lands under Delivered by this generation; an item that mode did not generate lands under Your responsibility (not delivered here). For example the TLS secure-session item is Delivered by this generation on Solace Suggested Secure and on Custom with the secure-session knob ticked, and it is Your responsibility on Quickstart, on Solace Suggested Non-Secure, and on Custom with that knob unticked. Resolve the admin-provisioned-queue item explicitly, not just the session-and-project items: it is Delivered by this generation ONLY on Custom with the admin-provisioned-queue knob ticked (the generated app binds a pre-existing admin-owned queue per `custom-mode.md`), and it is Your responsibility on Quickstart, on Solace Suggested (both sub-modes, which give admin-provisioned-queue guidance only and generate no binding code per `solace-suggested-mode.md`), and on Custom with that knob unticked. diff --git a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/verification-checklist.md b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/verification-checklist.md index 2c63606..e2a0a19 100644 --- a/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/verification-checklist.md +++ b/plugins/solace-messaging-skills/skills/solace-application-development/references/jcsmp/verification-checklist.md @@ -33,6 +33,7 @@ These are mechanical conformance checks on the generated output itself; the copi - [ ] Exactly one logging backend is configured with a config resource on the classpath; any `log4j-core` on the classpath (including transitive) is at or above `2.17.1`; the `com.solacesystems` loggers are not silenced below INFO. - [ ] The `sol-jcsmp` version in the pom matches the authoritative `` from the `repo1.maven.org` Maven metadata at generation time (never the solrsearch index). - [ ] This tailored checklist file exists at every generated project root. +- [ ] Every construct carried from a reference sample keeps that construct's comment (names adapted); dropped demo-harness code took its comments with it; fresh messaging code that applies a documented practice carries a short comment naming it (the comments-follow-their-code contract, implement-mode.md Step 4). - [ ] A `verify.sh` stage ran and its stage name and exit code are recorded here: ` → exit ` (or: the compile-only fallback was taken and the exact commands were handed back). - [ ] Every page named in the design summary's Grounding docs field was WebFetched in this session (`none fetched` is an honest value; an unfetched citation is not).