diff --git a/plugins/solace-messaging-skills/evals/output-evals.json b/plugins/solace-messaging-skills/evals/output-evals.json index 7b2a7ac..388a55b 100644 --- a/plugins/solace-messaging-skills/evals/output-evals.json +++ b/plugins/solace-messaging-skills/evals/output-evals.json @@ -89,7 +89,7 @@ { "type": "tool_use", "tool": "*", "input_pattern": "repo1\\.maven\\.org/maven2/com/solacesystems/sol-jcsmp/maven-metadata\\.xml", "expect": "present" }, { "type": "tool_use", "tool": "Bash", "input_pattern": "solrsearch|search\\.maven\\.org", "expect": "absent" }, { "type": "tool_use", "tool": "WebFetch", "input_pattern": "solrsearch|search\\.maven\\.org", "expect": "absent" }, - { "type": "tool_use", "tool": "Bash", "input_pattern": "verify\\.sh +(consumer|publisher|roundtrip|app)", "expect": "absent" }, + { "type": "tool_use", "tool": "Bash", "input_pattern": "verify\\.sh +(consumer|publisher|roundtrip|app)", "exclude_pattern": "<<.*verify\\.sh +(consumer|publisher|roundtrip|app)", "expect": "absent" }, { "type": "assistant_grep", "pattern": "./verify.sh", "fixed": true, "expect": "present" } ] }, 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 f1d6cfb..4da00ea 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 @@ -23,7 +23,7 @@ If unclear, default to **Design**. Understand the messaging problem before gener 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. +- **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. A request to skip the design, skip all questions, or generate the full project right now does not waive this gate or the door question below: write no file before the confirm. Take the shortest path the gate allows instead: derive the summary from the prompt, present it, say in one line that one confirm starts the build, and wait for that confirm. - **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. 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 e80db0c..4af578b 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 @@ -13,7 +13,7 @@ When the developer picks Custom, establish the design summary first (per `implem Custom offers FIVE knobs, and only these five. There is no sixth knob and no authentication knob; Basic username/password is the only auth this journey generates, exactly as on Quickstart and Solace Suggested, so it is never a Custom checklist item. Four of the five knobs reuse a `solace-suggested-mode.md` step; the fifth is defined in this file: 1. **Secure session (TLS).** Apply `solace-suggested-mode.md` Step 1, the Secure branch: a `tcps://` host with explicit certificate validation. Do not restate that logic here; the overlay step is the source of truth. -2. **HA channel values.** Apply `solace-suggested-mode.md` Step 2: the HA-failover question and the four reconnect channel values it gates. +2. **HA channel values.** Apply `solace-suggested-mode.md` Step 2: the HA-failover question and the four reconnect channel values it gates. Unticked keeps the samples' reconnect baseline (`reconnectRetries` 20, `connectRetriesPerHost` 3), not the fail-fast default-channel posture; ticked applies the four HA values when the HA-failover question is answered yes. 3. **DMQ eligibility.** Apply `solace-suggested-mode.md` Step 3: `setDMQEligible(true)` on every PERSISTENT publish, with the broker-side prerequisite stated as the developer's responsibility. 4. **Decoupled projects.** Apply `solace-suggested-mode.md` Step 4: two separate, independent single-module Maven projects, one per role. 5. **Admin-provisioned queue.** The one knob that is a real code change rather than a reused overlay step. It is defined in full in the "Admin-provisioned-queue knob" section below. diff --git a/plugins/solace-messaging-skills/skills/solace-application-development/references/python.md b/plugins/solace-messaging-skills/skills/solace-application-development/references/python.md index d22ee78..a726fa3 100644 --- a/plugins/solace-messaging-skills/skills/solace-application-development/references/python.md +++ b/plugins/solace-messaging-skills/skills/solace-application-development/references/python.md @@ -23,7 +23,7 @@ If unclear, default to **Design**. Understand the messaging problem before gener 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. +- **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. A request to skip the design, skip all questions, or generate the full project right now does not waive this gate or the door question below: write no file before the confirm. Take the shortest path the gate allows instead: derive the summary from the prompt, present it, say in one line that one confirm starts the build, and wait for that confirm. - **Implement mode opens with a mandatory door question.** Ask Quickstart, Solace Suggested, or Custom, before you request or accept ANY broker details (`python/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. @@ -68,7 +68,12 @@ Live exceptions (these have no `docs.solace.com` `.md` form; use the live URL di - `python/design-mode.md`: choose and confirm the messaging pattern and topology before any code is generated; every new build without a design contract routes here first. - `python/implement-mode.md`: generate a runnable Python project from a confirmed design; its Step 4 dispatches onto the per-leaf wiring files below. - `python/implement-guaranteed-pubsub.md`: the Guaranteed Pub/Sub leaf wiring (read from implement-mode Step 4). +- `python/implement-direct-pubsub.md`: the Direct Pub/Sub leaf wiring (read from implement-mode Step 4). +- `python/implement-request-reply.md`: the Request-Reply (Direct) leaf wiring (read from implement-mode Step 4). +- `python/solace-suggested-mode.md`: the leaf-agnostic Solace Suggested overlay (TLS secure session with the developer's trust store, DMQ eligibility on persistent publishes, HA failover, separate Python projects) reached from implement-mode Step 0 on the Solace Suggested path. +- `python/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. - `python/python-guaranteed-publisher-sample.py`: best-practices publisher sample (basic-auth connect, PERSISTENT publish to a topic, publish receipt handling, graceful shutdown). - `python/python-guaranteed-subscriber-sample.py`: best-practices receiver sample (provisions a durable queue plus a topic subscription, CLIENT-ack receiver). - `python/python-solace-connection-config.py`: the shared connection-config helper every generated app uses. -- `python/python-direct-*-sample.py`: the direct pub/sub and direct request-reply samples. Implement mode does not generate those leaves yet (see `python/implement-mode.md` Step 4). +- `python/python-direct-publisher-sample.py` and `python/python-direct-subscriber-sample.py`: the direct pub/sub samples (bounded-buffer publish with a publish failure listener; direct topic subscription with discard detection). +- `python/python-direct-requestor-blocking-sample.py`, `python/python-direct-requestor-sample.py`, and `python/python-direct-replier-sample.py`: the direct request-reply samples (blocking and non-blocking requestor; replier answering with `replier.reply()`). diff --git a/plugins/solace-messaging-skills/skills/solace-application-development/references/python/custom-mode.md b/plugins/solace-messaging-skills/skills/solace-application-development/references/python/custom-mode.md new file mode 100644 index 0000000..2601432 --- /dev/null +++ b/plugins/solace-messaging-skills/skills/solace-application-development/references/python/custom-mode.md @@ -0,0 +1,69 @@ +# Custom Mode + +The Custom overlay for Implement mode of the Python API. 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 leaf wiring file and samples each leaf reads, and the near-verbatim adaptation discipline), the AI-assisted disclaimer header on every generated script, explicit imports (never `from solace.messaging... import *`), and the fixed `VERIFY:` marker contract. Custom 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 + +When the developer picks Custom, establish the design summary first (per `implement-mode.md` Step 2, or reuse one already in hand), THEN present a SINGLE up-front multi-select checklist of the knobs that apply to the resulting `Pattern` leaf, capture the ticked subset once, and generate exactly that subset. Do NOT walk the knobs one at a time as sequential per-knob questions. One checklist, one capture, one generation pass. The property that defines Custom is "exactly the ticked subset, nothing more", so a knob that was not ticked leaves the generated output at its base shape for that concern. + +## The five knobs + +Custom offers FIVE knobs, and only these five. There is no sixth knob and no authentication knob; basic username/password is the only auth this journey generates, exactly as on Quickstart and Solace Suggested, so it is never a Custom checklist item. Four of the five knobs reuse a `solace-suggested-mode.md` step; the fifth is defined in this file: + +1. **Secure session (TLS).** Apply `solace-suggested-mode.md` Step 1: a `tcps://` host, explicit certificate validation, and the developer's trust store directory in `config.json`. Do not restate that logic here; the overlay step is the source of truth. +2. **HA reconnect values.** Apply `solace-suggested-mode.md` Step 2: the HA-failover question and the retry strategies and retries-per-host value it gates. Unticked keeps the samples' reconnect baseline (20 retries 3000 ms apart), not the fail-fast API defaults; ticked applies the HA values when the HA-failover question is answered yes. +3. **DMQ eligibility.** Apply `solace-suggested-mode.md` Step 3: `PERSISTENT_DMQ_ELIGIBLE` set explicitly on the persistent publisher's message template, with the broker-side prerequisite stated as the developer's responsibility. +4. **Decoupled projects.** Apply `solace-suggested-mode.md` Step 4: separate, independent Python projects, one per role. +5. **Admin-provisioned queue.** The one knob that is a real code change rather than a reused overlay step. It is defined in full in the "Admin-provisioned-queue knob" section below. + +For knobs 1 through 4, point at the named overlay step and reuse it. Do NOT duplicate the knob logic in this file. + +## The Quickstart-shaped floor + +Zero knobs ticked yields a Quickstart-shaped base app: one Python project with both scripts, a self-provisioned durable queue on the guaranteed leaves, the Quickstart transport rule (the scheme the developer's host carries, asked in `implement-mode.md` Step 1), and the `config.json`-or-command-line connection convention. Each ticked knob layers its Suggested delta on top of that floor. There is NO minimum-one-knob guard: zero ticks is a valid result, and it is the Quickstart-shaped base app. One exception to the Quickstart shape: the floor KEEPS the reference samples' reconnect baseline (20 retries 3000 ms apart); the fail-fast API-default posture is the Quickstart door's alone, and on Custom the HA knob decides between that baseline (unticked) and the HA values (ticked). Custom therefore spans from about Quickstart (nothing ticked) to about Solace Suggested (everything ticked). + +When the secure-session knob is unticked, the base follows the Quickstart transport rule: `implement-mode.md` Step 1 asks the scheme. On a plaintext `tcp://` host the base carries an honest plaintext caveat: unencrypted transport, suitable for a dev or test broker with no TLS, not for real credentials or real data over untrusted networks. On a `tcps://` host, apply the Quickstart trust store guidance from `implement-mode.md` Step 0 and never downgrade the host to `tcp://`. When the secure-session knob is ticked, the session upgrades to the `solace-suggested-mode.md` Step 1 `tcps://` path. + +The command-line fallback of the shared helper follows the knobs: it stays when both the secure-session and the decoupled-projects knobs are unticked (the Quickstart convention), and it is dropped, as on Solace Suggested, when either one is ticked, so the trust store path and the per-project credentials come only from each project's gitignored `config.json`. The helper requires the trust store key only when the secure-session knob is ticked. With the knob unticked on a `tcps://` host, the helper passes the trust store key through from `config.json` as on Quickstart, and the command-line fallback cannot carry it. + +## Leaf-aware offering + +Only offer knobs that apply to the design summary's chosen `Pattern` leaf. Read the `Pattern` field the same way `implement-mode.md` Step 2 does, then filter the checklist: + +- Secure session, HA reconnect values, and decoupled projects apply on EVERY leaf. +- DMQ eligibility and the admin-provisioned queue apply ONLY on the leaves that carry persistent delivery and a durable queue: the three Guaranteed Pub/Sub leaves. The Python API has no guaranteed request-reply leaf. +- HIDE both DMQ eligibility and the admin-provisioned queue on the direct at-most-once leaves (Direct Pub/Sub and Request-Reply (Direct)). Those leaves have no queue, so both knobs would be a silent no-op there; a developer must not be able to tick a knob that does nothing. + +## Admin-provisioned-queue knob (the one real code change) + +Ticking the admin-provisioned-queue knob changes the generated code so the subscriber binds a pre-existing, administrator-owned queue instead of provisioning its own. The administrator owns the queue lifecycle AND its topic subscription on the broker; the subscriber only binds a persistent receiver to that already-provisioned, already-subscribed endpoint. Ground it in [Creating Queues with the Solace Python API](https://docs.solace.com/API/API-Developer-Guide-Python/Python-API-Create-Queues.md) (`CREATE_ON_START` creates the queue given to `build()`, and the default, `DO_NOT_CREATE`, disables any attempt to create missing resources) and [Consuming Persistent Messages](https://docs.solace.com/API/API-Developer-Guide-Python/Python-PM-Receive.md). WebFetch these live pages on demand; do not paste doc content. + +This is a generation-time OMISSION from the generated output. It is NEVER an edit to any `.py` reference sample and never touches any `VERIFY:` marker string. + +On **Guaranteed Pub/Sub**, ticking the knob edits the subscriber's generation. DROP these two from the generated subscriber's receiver builder: + +- `with_missing_resources_creation_strategy(MissingResourcesCreationStrategy.CREATE_ON_START)` and its then-unused import (the in-process queue creation). +- `with_subscriptions([TopicSubscription.of(topic)])` and its then-unused import (the client-side topic-to-queue mapping; the administrator configured this subscription on the broker). + +KEEP the rest of the subscriber unchanged: + +- The local `Queue.durable_exclusive_queue(queue_name)` or `Queue.durable_non_exclusive_queue(queue_name)` handle from the `Access type` field (the receiver still needs a queue object to bind to; the access type itself is now the administrator's broker setting, so state in a comment that it must match). +- `with_message_client_acknowledgement()`, `with_activation_passivation_support(...)`, `receive_async(...)`, `start()`, and `ack()` after processing. +- The sample's `PubSubPlusClientError` branch around `start()`: it now reports a queue the administrator has not provisioned. + +All of the Guaranteed Pub/Sub leaf's `VERIFY:` markers still emit, unchanged: `VERIFY: CONNECTED` after connect, `VERIFY: QUEUE_BOUND` after `start()`, and `VERIFY: MESSAGE_RECEIVED` after `ack()`. The publisher is untouched, so its `VERIFY: PUBLISH_ACKED` is unaffected. On the fan-out leaf, the drop applies to every subscriber script. + +Because the ticked app no longer provisions anything, a round trip needs the administrator to have provisioned the queue and its subscription first. Say so in the handed-back next steps and record it in the checklist under Your responsibility. + +## 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 scripts and writes the tailored `solace-verification-checklist.md` into each project as generation output (one copy per project 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. This overlay selects which hardening deltas to layer; it does not replace those contracts. + +## Grounding references + +Live `docs.solace.com` `.md` pages (WebFetch on demand): + +- [Creating Queues with the Solace Python API](https://docs.solace.com/API/API-Developer-Guide-Python/Python-API-Create-Queues.md) +- [Consuming Persistent Messages](https://docs.solace.com/API/API-Developer-Guide-Python/Python-PM-Receive.md) diff --git a/plugins/solace-messaging-skills/skills/solace-application-development/references/python/design-mode.md b/plugins/solace-messaging-skills/skills/solace-application-development/references/python/design-mode.md index 32ac8ff..f3caa2b 100644 --- a/plugins/solace-messaging-skills/skills/solace-application-development/references/python/design-mode.md +++ b/plugins/solace-messaging-skills/skills/solace-application-development/references/python/design-mode.md @@ -17,6 +17,7 @@ Design mode is ask-or-recommend, not interrogate-first. Before walking the tree, | Stated intent in the prompt | Recommended leaf (or branch to lock if partial) | |---|---| | "request a reply", "needs a correlated response", "ask and wait for an answer" | Request-Reply (Direct) (the delivery question still applies; see the no-guaranteed-request-reply rule below) | +| "the request must not be lost", "the request must survive the responder being down", "persistent request" | a Guaranteed Pub/Sub leaf with an application correlation id (the no-guaranteed-request-reply rule below; never Request-Reply (Direct)) | | "fan out to many services", "broadcast to several independent apps", "every team gets its own copy" | Guaranteed Pub/Sub (fan-out) | | "competing workers", "share the load across consumers", "scale out processing of one stream" | Guaranteed Pub/Sub (single service, non-exclusive) | | "one active consumer", "strict ordering", "only one worker at a time" | Guaranteed Pub/Sub (single service, exclusive) | @@ -46,20 +47,18 @@ Ask only the questions the prompt left open, in this order. Before you answer ea Questions 3 and 4 apply ONLY on the guaranteed publish/subscribe branch. Request-reply and direct publish/subscribe resolve at the delivery-guarantee answer and never reach them. -**No guaranteed request-reply in the Python API.** When question 1 resolves to Request-Reply and question 2 resolves to guaranteed, there is no Python leaf for it. WebFetch [Request-Reply Messaging in the Solace Python API](https://docs.solace.com/API/API-Developer-Guide-Python/Python-API-Request-Reply.md) and quote its note that request-reply messaging can only be used with direct messages in the Python API. Then give the developer the choice in one question: Request-Reply (Direct), which keeps the request-reply shape and accepts direct delivery, or a Guaranteed Pub/Sub leaf, which keeps persistence and leaves the reply correlation to the application. Never invent a guaranteed request-reply leaf and never record `Request-Reply (Guaranteed)` in a Python summary. +**No guaranteed request-reply in the Python API.** When question 1 resolves to Request-Reply and question 2 resolves to guaranteed (the request must be persisted), there is no Python request-reply leaf for it. WebFetch [Request-Reply Messaging in the Solace Python API](https://docs.solace.com/API/API-Developer-Guide-Python/Python-API-Request-Reply.md) and quote its note that request-reply messaging can only be used with direct messages in the Python API. Then route to Guaranteed Pub/Sub, never to Request-Reply (Direct): the request travels as a persistent message to a durable queue, and the application sets a correlation id on it and matches the answer by that id. Continue the tree at question 3 to pick the Guaranteed Pub/Sub leaf, then record the design as a VARIANT of that leaf (Step 1): name the application correlation id in the `Consumption endpoint` field with a short inline reason, for example `durable queue q.acme.orders + topic subscription; application correlation id (persisted request, no Python guaranteed request-reply)`. Tell the developer that Implement generates the request leg only: the answer path (how the consumer returns the answer and how the requestor matches it by the correlation id) is theirs to build, and the checklist records it as developer-owned. Never offer Request-Reply (Direct) for a request that must be persisted, never invent a guaranteed request-reply leaf, and never record `Request-Reply (Guaranteed)` in a Python summary. ## Step 3: Resolve to one of the five leaves The tree resolves to exactly five leaves. Spell the chosen leaf in the summary exactly as written here; these strings are the contract Implement mode reads. -- **Request-Reply (Direct)**. Request-reply over direct messaging. -- **Direct Pub/Sub**. Publish/subscribe over direct messaging, fire-and-forget. +- **Request-Reply (Direct)** GENERATED. Request-reply over direct messaging. +- **Direct Pub/Sub** GENERATED. Publish/subscribe over direct messaging, fire-and-forget. - **Guaranteed Pub/Sub (single service, exclusive)** GENERATED. One durable queue, exclusive access, one active consumer at a time. - **Guaranteed Pub/Sub (single service, non-exclusive)** GENERATED. One durable queue, non-exclusive access, competing workers share the load. - **Guaranteed Pub/Sub (fan-out)** SNIPPET. One durable queue plus a topic subscription per independent consuming service. Implement mode reuses the single-service guaranteed generator, so there is no dedicated fan-out sample; the leaf is realized by repeating the single-service queue-and-subscription setup once per consuming service. -The two direct leaves are valid designs, and Design mode confirms and saves them like any other leaf, but Implement mode does not generate them in this release (see `implement-mode.md` Step 4). When the confirmed leaf is one of them, say so in the Step 4 close-out instead of offering to move into Implement mode. - Inside the guaranteed publish/subscribe single-service leaf, the exclusive versus non-exclusive choice is realized as a queue-factory SNIPPET, not a separate sample: the same generated app builds an exclusive or a non-exclusive durable queue based on the developer's answer to question 4. **A leaf can carry a variant.** The five strings name the pattern family; a design may adapt a leaf's default topology to a stated requirement (the variant path in Step 1). In every case the `Pattern` string stays one of the five verbatim, and the deviation is recorded in the topology fields of the Step 4 summary (for example the `Consumption endpoint`), never by inventing a sixth leaf string. @@ -104,5 +103,3 @@ Field semantics: - **Grounding docs.** ONLY the pages this session actually WebFetched. Never list a page you did not fetch. Write `none fetched` when no page was fetched. 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. 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 Python 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 Python 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. - -For a direct leaf (Direct Pub/Sub or Request-Reply (Direct)), the close-out differs: offer to save the summary to `solace-design.md`, and say in one line that generating a Python project for this leaf is not yet available in this release, so the saved design is ready for when it is. Do not offer to move into Implement mode, do not generate code from the direct samples by hand, and do not offer to write the code another way (from scratch, "outside the generator", or as unverified code). When the developer asks for the code anyway, repeat that generation for this leaf is not yet available and point them at the [Python tutorials](https://tutorials.solace.dev/python/); write no code. diff --git a/plugins/solace-messaging-skills/skills/solace-application-development/references/python/implement-direct-pubsub.md b/plugins/solace-messaging-skills/skills/solace-application-development/references/python/implement-direct-pubsub.md new file mode 100644 index 0000000..53488c5 --- /dev/null +++ b/plugins/solace-messaging-skills/skills/solace-application-development/references/python/implement-direct-pubsub.md @@ -0,0 +1,38 @@ +# Implement: Direct Pub/Sub leaf + +The per-leaf wiring for the Direct Pub/Sub leaf of the Python API. 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 disclaimer header, the shared connection helper, the fixed file names, the concurrency rules, the variant discipline, the demo-harness rule, the comments rule, and the Quickstart fail-fast rule) apply here unchanged. + +## Direct pub/sub flow + +Generation reads the direct pub/sub sample pair and adapts each into its own generated script, the same near-verbatim discipline the guaranteed flow uses: `direct_publisher.py` is a near-verbatim adaptation of the publisher sample, and `direct_subscriber.py` is a near-verbatim adaptation of the subscriber sample. Direct messaging is at-most-once: there is no broker receipt and no redelivery, so this leaf has NO queue, NO provisioning, NO client acknowledgement, and NO publish receipt listener. + +- `python-direct-publisher-sample.py`: basic-auth connect, a continuous DIRECT binary publish loop with a rotating payload to a dynamic topic, a publish failure listener for the asynchronous failures the API reports, capacity-bounded back pressure, service event listeners registered before `connect()`, and a graceful SIGINT/SIGTERM shutdown. No receipt listener and no user context (direct has no broker receipt). Generation STRIPS the continuous loop, the rotating payload, the dynamic topic, and the stats thread down to a short bounded burst that self-exits (publisher steps below). +- `python-direct-subscriber-sample.py`: basic-auth connect, a plain topic subscription on a direct receiver (no queue, no provisioning), capacity-bounded back pressure with discard detection, service event listeners registered before `connect()`, and a graceful SIGINT/SIGTERM shutdown. The subscriber is the long-running SIGINT target. + +Each script builds its OWN `MessagingService` (two services, one per script, matching the samples). Keep the samples' structure: the shared state dataclass, `setup_solace(...)` that builds the service and the receiver or publisher before any connection exists, `connect_solace(...)`, the main loop, and `teardown_solace(...)` in `main()`'s `finally`. Keep every listener class; they are part of the best-practices contract and must not be stripped during adaptation. + +**Ground before you write.** Before writing either script, WebFetch [Publishing Direct Messages](https://docs.solace.com/API/API-Developer-Guide-Python/Python-DM-Publish.md) and [Consuming Direct Messages](https://docs.solace.com/API/API-Developer-Guide-Python/Python-DM-Receive.md) in this session (SKILL.md Invariant 2). + +## Subscriber script (`direct_subscriber.py`) + +The subscriber subscribes directly to the topic (no queue), receives asynchronously, and is the long-running process the developer runs FIRST. Start the file with the disclaimer header, then wire it in this sequence: + +1. Build the connection properties with `load_service_properties(args, APP_NAME)`, then the service with `MessagingService.builder().from_properties(properties).build()` (on Quickstart without the reconnection strategy call, per the fail-fast rule). +2. Build the direct receiver as the sample does: `create_direct_message_receiver_builder()` with `with_subscriptions([TopicSubscription.of(topic)])` for the design summary's topic and `on_back_pressure_drop_latest(RECEIVE_BUFFER_CAPACITY)`, then `build()`. Register the three service event listeners BEFORE `connect()`. There is NO queue and NO missing-resources creation strategy on the direct path. +3. `messaging_service.connect()`. Immediately after `connect()` returns, emit the connect marker: `trace("VERIFY: CONNECTED")`. +4. `receiver.receive_async(handler)` BEFORE `receiver.start()`, as the sample orders them, then `receiver.start()`. `start()` applies the subscription and blocks until the broker confirms it. Immediately after `start()` returns, emit the subscribed marker: `trace("VERIFY: SUBSCRIBED")`. **No reapply-subscriptions setting.** The JCSMP direct subscriber must set `REAPPLY_SUBSCRIPTIONS` to `true`, or its subscription is lost on an automatic reconnect. The Python API has no such setting and needs none: the [Messaging Service](https://docs.solace.com/API/API-Developer-Guide-Python/Python-API-Messaging-Service.md) page states that the API re-applies the application's direct subscriptions on reconnect. Do not search for or invent a Python equivalent. Keep the sample's `on_reconnected` comment that says so. When reconnect attempts are exhausted, the same page states that the application must recreate the session with all its subscriptions; the sample's `on_service_interrupted` handler covers that case by shutting down. +5. In the handler's `on_message`, keep the sample's payload read and its discard check (`get_message_discard_notification()`), then emit the receipt marker: `trace("VERIFY: MESSAGE_RECEIVED")`. There is NO `ack()` on the direct path. +6. KEEP the long-running wait loop, the SIGINT/SIGTERM handler that only sets the shutdown event, and `teardown_solace(...)` with `terminate(grace_period)` and `disconnect()`. The subscriber is the SIGINT target, and its shutdown line (`Shutdown signal received ...`) stays a `trace(...)` call. + +## Publisher script (`direct_publisher.py`) + +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, then wire it in this sequence: + +1. Build the connection properties and the service exactly as the subscriber does (step 1 above), then the publisher with `create_direct_message_publisher_builder().on_back_pressure_wait(PUBLISH_BUFFER_CAPACITY).build()`. Keep `set_publish_failure_listener(...)`: direct has no broker receipt, so a buffered message that cannot be sent is reported only through that listener. Register the three service event listeners BEFORE `connect()`. +2. `messaging_service.connect()`. Immediately after `connect()` returns, emit the connect marker: `trace("VERIFY: CONNECTED")`. Then `publisher.start()`. +3. Publish a SHORT BOUNDED BURST to the design summary's topic with `publish(payload, Topic.of(topic))` in a small fixed-count loop, then return from the publish step. REMOVE the sample's continuous loop, its rotating payload and dynamic topic, and its stats thread. Keep the sample's `PubSubPlusClientError` branch, which sets the shared state's `exit_code` to 1. `teardown_solace(...)` in `main()`'s `finally` calls `terminate(grace_period)`, which gives the buffered messages up to the grace period to leave before `disconnect()`. Keep the SIGINT/SIGTERM handler, so Ctrl-C during a back pressure wait still reaches teardown. When the request describes a real application, its own messages replace the burst (the demo-harness rule). +4. The publisher emits NO send-side marker. Direct has no broker receipt, so there is no `PUBLISH_ACKED` analog; a marker after `publish()` would only prove the local call returned. The proof of the round trip is `VERIFY: MESSAGE_RECEIVED` in the subscriber output. + +## The marker contract + +These three `VERIFY:` marker strings (`VERIFY: CONNECTED`, `VERIFY: SUBSCRIBED`, `VERIFY: MESSAGE_RECEIVED`) are the same strings the JCSMP Direct Pub/Sub leaf emits, character for character. They are read PER PROCESS: the subscriber output carries `SUBSCRIBED` and `MESSAGE_RECEIVED`, and both scripts print `CONNECTED`. Until the Python verify script lands, the developer reads them from the two terminals (`implement-mode.md` Step 5); the verify script will read the same strings, so never change one. diff --git a/plugins/solace-messaging-skills/skills/solace-application-development/references/python/implement-guaranteed-pubsub.md b/plugins/solace-messaging-skills/skills/solace-application-development/references/python/implement-guaranteed-pubsub.md index 2eed182..aaa19cf 100644 --- a/plugins/solace-messaging-skills/skills/solace-application-development/references/python/implement-guaranteed-pubsub.md +++ b/plugins/solace-messaging-skills/skills/solace-application-development/references/python/implement-guaranteed-pubsub.md @@ -48,6 +48,8 @@ The publisher only connects and publishes; it does NOT provision the queue or th 4. Publish persistent messages to the design summary's topic as the sample does: one `message_builder()` template with `PERSISTENT_TIME_TO_LIVE` set once, `build(payload, additional_message_properties={APPLICATION_MESSAGE_ID: msg_id})` per message, and `publish(message, topic, user_context=msg_id)`. See [Publishing Persistent Messages](https://docs.solace.com/API/API-Developer-Guide-Python/Python-PM-Publish.md). 5. EXIT on its own: replace the sample's continuous publish loop with the application's own messages, or with one message on a try-it run, then return from the publish step. `teardown_solace(...)` in `main()`'s `finally` calls `terminate(grace_period)`, which sends what is buffered and waits for the broker's receipt of every published message, so the `VERIFY: PUBLISH_ACKED` marker prints before the script exits. Drop the stats thread with the loop. Keep the SIGINT/SIGTERM handler, so Ctrl-C during a back pressure wait still reaches teardown. +**Persisted-request variant (application correlation id).** When the `Consumption endpoint` field records an application correlation id (a persisted request that `design-mode.md` routed here), generate the request leg only. The publisher adds `message_properties.CORRELATION_ID: msg_id` to the same `additional_message_properties` dict in step 4, and the subscriber stays unchanged. Do NOT generate an answer path: no reply topic, no reply subscription, no reply publish, and no `VERIFY:` marker beyond the four below. List the answer path under Developer-owned items in the checklist (`implement-mode.md` Step 6), and name it in the close-out as the developer's next step. + ## The marker contract These four `VERIFY:` marker strings (`VERIFY: CONNECTED`, `VERIFY: PUBLISH_ACKED`, `VERIFY: QUEUE_BOUND`, `VERIFY: MESSAGE_RECEIVED`) are the same strings the JCSMP guaranteed pub/sub leaf emits, character for character. They are read PER PROCESS: the subscriber output carries `QUEUE_BOUND` and `MESSAGE_RECEIVED`, the publisher output carries `PUBLISH_ACKED`, and both print `CONNECTED`. Until the Python verify script lands, the developer reads them from the two terminals (`implement-mode.md` Step 5); the verify script will read the same strings, so never change one. diff --git a/plugins/solace-messaging-skills/skills/solace-application-development/references/python/implement-mode.md b/plugins/solace-messaging-skills/skills/solace-application-development/references/python/implement-mode.md index 5b9eff4..5f5d465 100644 --- a/plugins/solace-messaging-skills/skills/solace-application-development/references/python/implement-mode.md +++ b/plugins/solace-messaging-skills/skills/solace-application-development/references/python/implement-mode.md @@ -1,12 +1,12 @@ # Implement Mode -Generate a complete, runnable Python project from a confirmed design summary. The canonical output is TWO scripts (a publisher and a subscriber) plus the shared connection-config helper in ONE project directory with its own virtual environment. Each script 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 script into its own service or project. +Generate a complete, runnable Python project from a confirmed design summary. The canonical output is TWO scripts (a publisher and a subscriber, or a requestor and a replier) plus the shared connection-config helper in ONE project directory with its own virtual environment. Each script 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 script into its own service or project. Implement mode is NOT only for the canonical shape. A web app, a dashboard, a service, or any embedded shape where the Python messaging 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 fallback. 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 in skill content (SKILL.md Invariant 3). -**Coverage in this release.** Implement mode generates the three Guaranteed Pub/Sub leaves on the Quickstart door. The Direct Pub/Sub and Request-Reply (Direct) leaves (Step 4) and the Solace Suggested and Custom doors (Step 0) are not yet available for Python. The Python API does not bundle a verify script yet, so every run ends in the compile-only fallback of Step 5. +**Coverage in this release.** Implement mode generates all five Python leaves on all three doors. The Python API does not bundle a verify script yet, so every run ends in the compile-only fallback of Step 5. ## Generation outputs: the file contract @@ -15,13 +15,13 @@ Every Implement run writes ALL of these as generation output, on every app shape 1. The generated scripts for the chosen leaf, plus the shared `solace_connection_config.py` helper. Every source file starts with the AI-assisted disclaimer header (Step 4). 2. `requirements.txt` with the `solace-pubsubplus` version resolved per Step 3. 3. `config.example.json` (placeholders) and a `.gitignore` that ignores `config.json` and the virtual environment (Step 4). -4. A tailored `solace-verification-checklist.md` at the project root (tailoring rules in Step 6; the WRITE happens here at generation time, like the scripts). +4. A tailored `solace-verification-checklist.md` at each project root (tailoring rules in Step 6; the WRITE happens here at generation time, like the scripts). There is no `verify.sh` and no `verify-hooks.sh` in a Python project yet. Do not copy the JCSMP `verify.sh` into a Python project: it drives Maven and cannot run Python scripts. ## 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 (TLS, dead message queue eligibility, HA failover, separate projects), and Solace suggests it without guaranteeing it is ready for deployment; Custom lets the developer tick exactly which hardening knobs to include. State in the same question that Solace Suggested and Custom are not yet available for the Python API in this release. +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 (TLS, dead message queue eligibility, HA failover, separate projects), 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. The answer decides WHERE broker connection details belong, and it drives behavior for the rest of Implement mode. Every door generates the same scripts for the chosen leaf plus the shared `solace_connection_config.py` helper; 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 command-line arguments; Custom follows the Quickstart convention by default, and `custom-mode.md` says when it drops the command-line fallback. Three things do NOT answer this question, and none of them waives it: @@ -31,14 +31,17 @@ Three things do NOT answer this question, and none of them waives it: The doors: -- **Quickstart.** Each script builds its connection through the shared `solace_connection_config.py` helper, which reads the connection properties from a `config.json` in the working directory when present and otherwise falls back to the command-line arguments (` [password]`). Quickstart adds no TLS hardening and has no TLS requirement: it connects with whatever scheme the developer's host carries, and a plaintext `tcp://` dev broker is a normal Quickstart target. Generate the scripts without per-stage preview gates. Quickstart output fails fast (the fail-fast rule in Step 4). -- **Solace Suggested or Custom.** Not yet available for Python. Say so plainly, and do not describe the hardened doors beyond the one-line framing in the Step 0 question. Offer the developer two honest choices: Quickstart now, framed as a learning run, or stop here with the design saved to `solace-design.md` for when the hardened doors are available. Never generate a Quickstart project under the Solace Suggested or Custom name. +- **Quickstart.** Each script builds its connection through the shared `solace_connection_config.py` helper, which reads the connection properties from a `config.json` in the working directory when present and otherwise falls back to the command-line arguments (` [password]`). Quickstart adds no TLS hardening and has no TLS requirement: it connects with whatever scheme the developer's host carries, and a plaintext `tcp://` dev broker is a normal Quickstart target. A `tcps://` host (the Solace Cloud default) needs a trust store even on Quickstart, because the Python API has no default one and fails at `build()` with "Failed to load trust store" without it. Step 1 asks the developer whether the broker URL is plaintext or secure; when the answer is secure (`tcps://`), give the developer the trust store explanation from `solace-suggested-mode.md` Step 1 and have them set `solace.messaging.tls.trust-store-path` in `config.json`; the helper passes that key through unchanged, and the command-line fallback cannot carry it, so a `tcps://` host runs from `config.json`. Quickstart adds no validation code: the API defaults already validate the certificate. Never downgrade a `tcps://` host to `tcp://` to avoid the trust store: the plaintext port may be closed, the port differs, and the password would cross the network in clear text. Generate the scripts without per-stage preview gates. Quickstart output fails fast (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 with the developer's trust store, DMQ eligibility on persistent publishes, 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. Solace Suggested is TLS-secure by definition: the session is `tcps://` with server-certificate validation, and it REQUIRES a broker that already has TLS configured (Solace Cloud has it out of the box) plus a trust store directory that holds the broker's CA certificate, which the developer supplies (`solace-suggested-mode.md` Step 1 explains it to the developer). There is no non-secure variant of this door and no sub-question to ask; the door question stays a clean three-way choice. When the developer's target broker has no TLS configured (for example, a local dev container), say plainly that Solace Suggested requires a TLS-capable broker and offer the two honest alternatives: Custom with the equivalent knobs ticked minus the secure session, or Quickstart for a learning run. Never generate a plaintext session under the Solace Suggested name. 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 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 that a real deployment lifts each script into its own service or project. Basic username/password is the only auth this journey generates; ground it in the authentication section of [Messaging Service](https://docs.solace.com/API/API-Developer-Guide-Python/Python-API-Messaging-Service.md). +State plainly that a real deployment lifts each script into its own service or project. Every door generates the scripts in one project, unless the developer opts into separate projects on Solace Suggested or Custom. Basic username/password is the only auth this journey generates; ground it in the authentication section of [Messaging Service](https://docs.solace.com/API/API-Developer-Guide-Python/Python-API-Messaging-Service.md). ## 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. 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 is a fact to report while asking, never an answer. The credential VALUES reach the app through a `config.json` when present, otherwise as command-line arguments at run time; the skill itself never needs the values typed into the chat. +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 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 command-line arguments at run time. The skill itself never needs the values typed into the chat. + +**On Quickstart, and on Custom with the secure-session knob unticked, also ask the scheme.** Ask in one direct question: "Is your broker URL plaintext (`tcp://`) or secure (`tcps://`)?" The scheme is not a credential, so asking it never conflicts with the rule against connection details in the chat; Solace Cloud URLs are `tcps://` by default. An existing `config.json` or a discovered broker is a fact to report, never the answer: still ask. The answer decides the Quickstart trust store guidance (Step 0) and the `config.example.json` placeholder (Step 4). Do not ask it on Solace Suggested, which is always `tcps://`, or on Custom with the secure-session knob ticked, which is always `tcps://`. If the developer has no reachable broker, route through `prerequisites.md` first, then return here. @@ -85,14 +88,12 @@ Generation is keyed on the `Pattern` leaf read in Step 2. Read ONLY the leaf fil | `Guaranteed Pub/Sub (single service, exclusive)` | `implement-guaranteed-pubsub.md` | `python-guaranteed-publisher-sample.py` + `python-guaranteed-subscriber-sample.py` | | `Guaranteed Pub/Sub (single service, non-exclusive)` | `implement-guaranteed-pubsub.md` | `python-guaranteed-publisher-sample.py` + `python-guaranteed-subscriber-sample.py` | | `Guaranteed Pub/Sub (fan-out)` | `implement-guaranteed-pubsub.md` | `python-guaranteed-publisher-sample.py` + `python-guaranteed-subscriber-sample.py` | -| `Direct Pub/Sub` | not yet available | none | -| `Request-Reply (Direct)` | not yet available | none | - -**The two direct leaves stop here.** Say in one line that generating a Python project for this leaf is not yet available in this release. Offer to save the confirmed summary to `solace-design.md` if it is not saved yet, and stop. Do not read the direct samples and do not adapt them by hand: without a wiring file there is no marker contract and no conformance check for the result. Do not offer to write the code another way (from scratch, "outside the generator", or as unverified code), and do not write it when the developer asks again: the stop is the ENTIRE answer, the same way the Debug redirect is. Point the developer at the saved design and at the [Python tutorials](https://tutorials.solace.dev/python/) instead. +| `Direct Pub/Sub` | `implement-direct-pubsub.md` | `python-direct-publisher-sample.py` + `python-direct-subscriber-sample.py` | +| `Request-Reply (Direct)` | `implement-request-reply.md` | `python-direct-requestor-blocking-sample.py` (or `python-direct-requestor-sample.py` on the non-blocking variant) + `python-direct-replier-sample.py` | **Every leaf also generates the shared connection helper.** Read `python-solace-connection-config.py` and generate it as `solace_connection_config.py` (a module name needs underscores). Each script builds its connection dict by calling `load_service_properties(args, APP_NAME)` from that module and passes it to `MessagingService.builder().from_properties(...)`, rather than setting the host, VPN, username, or password inline. -**Fixed file names.** Generate the scripts as `guaranteed_publisher.py` and `guaranteed_subscriber.py`, the names each sample's docstring gives, next to `solace_connection_config.py` (the fan-out leaf adds one subscriber script per further consuming service, named in `implement-guaranteed-pubsub.md`). The run commands in Step 5 and the checklist address these names; the names of functions, constants, and the `APP_NAME` value inside each script stay your discretion. +**Fixed file names.** Generate each script under the name its sample's docstring gives, next to `solace_connection_config.py`: `guaranteed_publisher.py` and `guaranteed_subscriber.py` (the fan-out leaf adds one subscriber script per further consuming service, named in `implement-guaranteed-pubsub.md`), `direct_publisher.py` and `direct_subscriber.py`, and `direct_replier.py` with `direct_requestor_blocking.py` (or `direct_requestor.py` on the non-blocking variant). The run commands in Step 5 and the checklist address these names; the names of functions, constants, and the `APP_NAME` value inside each script stay your discretion. **Disclaimer header on EVERY generated source file.** Stamp the top of each generated `.py` file with these two comment lines, before the module docstring and before any import: @@ -105,7 +106,7 @@ Stamp the header AT WRITE TIME, as the first content of each new file. `requirem **Embedded and web-app shapes.** When the Python messaging layer sits inside a larger application, the chosen leaf's wiring still governs the messaging layer: the same connection helper, the same service, receiver, and publisher listeners, 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. -**Write the verification artifacts in this same step.** Alongside the scripts, write the tailored `solace-verification-checklist.md` at the project root (Step 6), `config.example.json`, and `.gitignore`. A session that ends early still leaves the checklist in the project. +**Write the verification artifacts in this same step.** Alongside the scripts, write the tailored `solace-verification-checklist.md` at each project root (Step 6), `config.example.json`, and `.gitignore`. A session that ends early still leaves the checklist in the project. ### Concurrency: threads, not asyncio, and no multiprocessing @@ -131,11 +132,11 @@ Hold every invariant while adapting: keep the shared connection helper, the serv ### Quickstart fail-fast reconnect defaults -On the Quickstart door, do not carry the samples' reconnect-tuning block into the generated scripts. The samples set a reconnection budget (`with_reconnection_retry_strategy(RetryStrategy.parametrized_retry(...))` with the `RECONNECT_RETRIES` and `RECONNECT_RETRY_INTERVAL_MS` constants) and carry an HA failover comment block; Quickstart generation OMITS the builder call, the two constants, the HA comment block, and the then-unused `RetryStrategy` import, leaving connect and reconnect behavior at the API defaults, so a wrong host, port, or credential in this learning setup fails immediately instead of retrying. Drop ONE short comment at the `MessagingService.builder()` site in each generated script stating that posture: connect and reconnect settings are at the API defaults so failures surface immediately; see the [Solace C API Best Practices](https://docs.solace.com/API/API-Developer-Guide/C-API-Best-Practices.md) for reconnect tuning beyond Quickstart. +On the Quickstart door ONLY, do not carry the samples' reconnect-tuning block into the generated scripts. The samples set a reconnection budget (`with_reconnection_retry_strategy(RetryStrategy.parametrized_retry(...))` with the `RECONNECT_RETRIES` and `RECONNECT_RETRY_INTERVAL_MS` constants) and carry an HA failover comment block; Quickstart generation OMITS the builder call, the two constants, the HA comment block, and the then-unused `RetryStrategy` import, leaving connect and reconnect behavior at the API defaults, so a wrong host, port, or credential in this learning setup fails immediately instead of retrying. Drop ONE short comment at the `MessagingService.builder()` site in each generated script stating that posture: connect and reconnect settings are at the API defaults so failures surface immediately; see the [Solace C API Best Practices](https://docs.solace.com/API/API-Developer-Guide/C-API-Best-Practices.md) 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`. ### Scaffold the config files -Generate two small project files next to the scripts: +Generate two small project files next to the scripts (on Solace Suggested, on Custom when the secure-session knob is ticked, and on Quickstart or on Custom with the secure-session knob unticked when the Step 1 answer is secure, use the `config.example.json` from `solace-suggested-mode.md` Step 0 instead, with its `tcps://` placeholder host and its trust store key): - A committed `config.example.json` that carries placeholder values for the four keys the shared helper requires, spelled as the Solace Python API property names the helper documents. The placeholder values must be obviously fake: @@ -157,7 +158,7 @@ Tell the developer they opt in to the config file by copying `config.example.jso The Python API does not bundle a verify script yet, so Implement mode ends in the compile-only fallback on every run, whatever the contents of `config.json` (SKILL.md Invariant 5). Never fabricate a run result and never call a check below a verified round trip. 1. **Compile check.** Run `.venv/bin/python -m py_compile` on every generated `.py` file. A failure is a code failure: fix it and re-run, up to 3 attempts, then report the evidence and hand the decision to the developer. -2. **Import smoke.** From the project root, run `.venv/bin/python -c "import solace_connection_config, guaranteed_publisher, guaranteed_subscriber"`, adding every further generated module (for example each fan-out subscriber). Each script keeps its `if __name__ == "__main__":` guard, so the import resolves every `solace.messaging` name the scripts use without connecting to a broker. A failure (for example an `ImportError` on a misspelled module path) is a code failure under the same fix loop. +2. **Import smoke.** From the project root, run `.venv/bin/python -c "import solace_connection_config, guaranteed_publisher, guaranteed_subscriber"` with the module names of the chosen leaf (for example `direct_publisher, direct_subscriber` or `direct_replier, direct_requestor_blocking`), adding every further generated module (for example each fan-out subscriber). On separate projects, run the checks in each project root against that project's modules. Each script keeps its `if __name__ == "__main__":` guard, so the import resolves every `solace.messaging` name the scripts use without connecting to a broker. A failure (for example an `ImportError` on a misspelled module path) is a code failure under the same fix loop. 3. **Conformance checks.** From the project root, run these three checks and record each result in the checklist. A missing header or a missing checklist is a code failure under the same fix loop; a version mismatch is reported to the developer, not fixed silently. ```bash @@ -169,7 +170,7 @@ The Python API does not bundle a verify script yet, so Implement mode ends in th grep -qxF "solace-pubsubplus==$(curl -s https://pypi.org/pypi/solace-pubsubplus/json | python3 -c 'import json, sys; print(json.load(sys.stdin)["info"]["version"])')" requirements.txt || echo "VERSION DRIFT" ``` -4. **Report and hand back.** Report the compile, import, and conformance results, then hand the developer the exact commands to run against their own broker from the project root, subscriber first so the durable queue and its subscription exist before the first publish: +4. **Report and hand back.** Report the compile, import, and conformance results, then hand the developer the exact commands to run against their own broker from the project root. The long-running role always starts first: the subscriber, so the durable queue (guaranteed) or the topic subscription (direct) exists before the first publish, and the replier, so the request topic subscription exists before the first request. For the Guaranteed Pub/Sub leaves: ``` # terminal 1: provisions the queue, binds, and waits (Ctrl-C stops it) @@ -178,15 +179,23 @@ The Python API does not bundle a verify script yet, so Implement mode ends in th .venv/bin/python guaranteed_publisher.py [ [password]] ``` - On the fan-out leaf, hand back one subscriber command per subscriber script (`guaranteed_subscriber.py` and each `guaranteed_subscriber_.py`), each in its own terminal and all started before the publisher; every subscriber prints its own markers. + For the Direct Pub/Sub leaf, the same two terminals run `direct_subscriber.py` (subscribes and waits; Ctrl-C stops it), then `direct_publisher.py` (publishes a short burst and exits). For the Request-Reply (Direct) leaf, they run `direct_replier.py` (subscribes to the request topic and waits; Ctrl-C stops it), then `direct_requestor_blocking.py` or `direct_requestor.py` (sends, waits for the reply, and exits). + + On the fan-out leaf, hand back one subscriber command per subscriber script (`guaranteed_subscriber.py` and each `guaranteed_subscriber_.py`), each in its own terminal and all started before the publisher; every subscriber prints its own markers. On separate projects (Solace Suggested, or the Custom decoupled-projects knob), hand back each command from its own project directory with that project's `.venv/bin/python`. + + The arguments are needed only when there is no `config.json`, and they are never offered on a path that drops the command-line fallback. Tell the developer what a passing run prints, per leaf: + + - Guaranteed Pub/Sub: the subscriber prints `VERIFY: CONNECTED` and then `VERIFY: QUEUE_BOUND`; the publisher prints `VERIFY: CONNECTED` and `VERIFY: PUBLISH_ACKED` and exits with status 0; the subscriber then prints `VERIFY: MESSAGE_RECEIVED`. + - Direct Pub/Sub: the subscriber prints `VERIFY: CONNECTED` and then `VERIFY: SUBSCRIBED`; the publisher prints `VERIFY: CONNECTED` and exits with status 0; the subscriber then prints `VERIFY: MESSAGE_RECEIVED`. + - Request-Reply (Direct): the replier prints `VERIFY: CONNECTED` and then `VERIFY: SUBSCRIBED`; the requestor prints `VERIFY: CONNECTED`; the replier prints `VERIFY: REQUEST_RECEIVED`; the requestor prints `VERIFY: REPLY_RECEIVED` and exits with status 0. - The arguments are needed only when there is no `config.json`. Tell the developer what a passing run prints: the subscriber prints `VERIFY: CONNECTED` and then `VERIFY: QUEUE_BOUND`; the publisher prints `VERIFY: CONNECTED` and `VERIFY: PUBLISH_ACKED` and exits with status 0; the subscriber then prints `VERIFY: MESSAGE_RECEIVED`; Ctrl-C on the subscriber prints `Shutdown signal received` and it exits with status 0. + On every leaf, Ctrl-C on the long-running role prints `Shutdown signal received` and it exits with status 0. -**When the developer asks you to run it.** If the developer explicitly asks you to run the two scripts against their broker, print the live-broker heads-up first (SKILL.md Invariant 6: name the host, every durable queue the subscribers provision, one connection per script, and the messages the publisher sends, scaled to the generated scripts). Then start each subscriber in the background with its output captured, wait for `VERIFY: QUEUE_BOUND` from each, run the publisher in the foreground, wait for `VERIFY: MESSAGE_RECEIVED` in each subscriber's output, and stop the subscribers with SIGINT. Report the markers you observed as evidence of a manual run. It is not a verify verdict: record it in the checklist as a manual run, and never describe it as a passed verify stage. +**When the developer asks you to run it.** If the developer explicitly asks you to run the scripts against their broker, print the live-broker heads-up first (SKILL.md Invariant 6: name the host, every durable queue the subscribers provision, one connection per script, and the messages or requests the foreground role sends, scaled to the generated scripts). Then start each long-running role in the background with its output captured, wait for its readiness marker (`VERIFY: QUEUE_BOUND` on the guaranteed leaves, `VERIFY: SUBSCRIBED` on the direct leaves) from each, run the foreground role, wait for the pass marker (`VERIFY: MESSAGE_RECEIVED` in each subscriber's output, or `VERIFY: REPLY_RECEIVED` in the requestor's output), and stop the long-running roles with SIGINT. Report the markers you observed as evidence of a manual run. It is not a verify verdict: record it in the checklist as a manual run, and never describe it as a passed verify stage. -**Environment failure on a manual run: do NOT modify the code.** When the captured output shows that the broker is unreachable, the credentials or VPN are wrong, or the client may not create the queue (the subscriber's `Could not bind to queue` error), the code is not the problem. Report the matched output line as evidence and STOP: do not enter the fix loop, and do not change the code. The fix is the developer's broker, credentials, or client permissions. Fix the code only when the output shows a code failure (an exception from the scripts' own logic, or a marker that never appears after a clean connect and bind), up to 3 attempts, then hand the decision to the developer. +**Environment failure on a manual run: do NOT modify the code.** When the captured output shows that the broker is unreachable, the credentials or VPN are wrong, the TLS handshake or the trust store fails, or the client may not create the queue (the subscriber's `Could not bind to queue` error), the code is not the problem. Report the matched output line as evidence and STOP: do not enter the fix loop, and do not change the code. The fix is the developer's broker, credentials, or client permissions. Fix the code only when the output shows a code failure (an exception from the scripts' own logic, or a marker that never appears after a clean connect and bind), up to 3 attempts, then hand the decision to the developer. -**Close every run with the next steps, stated plainly.** Tell the developer, in order: (1) copy `config.example.json` to `config.json` and fill in their real broker values; (2) run the subscriber, then the publisher, with the commands above, and check the markers. Step 4 already wrote the tailored `solace-verification-checklist.md`; 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. +**Close every run with the next steps, stated plainly.** Tell the developer, in order: (1) copy `config.example.json` to `config.json` and fill in their real broker values; on any `tcps://` host (always on Solace Suggested and with the Custom secure-session knob, and on Quickstart or on Custom with the secure-session knob unticked when the Step 1 answer was secure), also put the broker's CA certificate into a trust store directory and set `solace.messaging.tls.trust-store-path` to it, as `solace-suggested-mode.md` Step 1 explains. On a Quickstart run, or a Custom run with the secure-session knob unticked, whose Step 1 answer was plaintext, still add one line: if the host later changes to `tcps://`, the project needs a trust store directory set in `solace.messaging.tls.trust-store-path` in `config.json`, or the connection fails with "Failed to load trust store"; (2) run the long-running role, then the foreground role, with the commands above, and check the markers. Step 4 already wrote the tailored `solace-verification-checklist.md`; 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. ### Traps to avoid @@ -203,37 +212,45 @@ The Python API does not bundle a verify script yet, so Implement mode ends in th ## Step 6: Tailor the checklist (written at Step 4) and report -Step 4 writes the tailored `solace-verification-checklist.md` into the generated project as generation output; this step defines its content and adds the report in chat. The Python API has no master checklist template yet; until it lands, write the file from the list below. Resolve each item for the generated code: an item the generation did not deliver moves to Your responsibility. Write it as a Markdown task list (`- [ ]`), ticking only what this run actually checked. Each item in the first two groups carries one canonical doc link, as the master JCSMP template does; the round-trip and conformance items carry none because they check skill conformance, not Solace guidance. +Step 4 writes the tailored `solace-verification-checklist.md` into the generated project as generation output; this step defines its content and adds the report in chat. The Python API has no master checklist template yet; until it lands, write the file from the list below. Resolve each item for the generated code: keep only the items for the chosen leaf (a leaf in parentheses marks a leaf-specific item), and move an item the generation did not deliver to Your responsibility. On Solace Suggested, and for each ticked Custom knob, an item under Your responsibility that the door or the knob delivered (the TLS secure session, DMQ eligibility on published messages, separate projects, and the HA reconnect values only when the HA-failover question was answered yes) moves to Delivered by this generation. The broker-side dead message queue always stays under Your responsibility, because the skill never provisions it. Write it as a Markdown task list (`- [ ]`), ticking only what this run actually checked. Each item in the first two groups carries one canonical doc link, as the master JCSMP template does; the round-trip and conformance items carry none because they check skill conformance, not Solace guidance. **Delivered by this generation** - Basic-auth connection through the shared `solace_connection_config.py` helper; credentials live only in a gitignored `config.json`. [Messaging Service](https://docs.solace.com/API/API-Developer-Guide-Python/Python-API-Messaging-Service.md) - Service reconnection and interruption listeners registered before `connect()`. [Messaging Service](https://docs.solace.com/API/API-Developer-Guide-Python/Python-API-Messaging-Service.md) -- Persistent publish to a topic with a publish receipt listener; a NACK is logged with the message id. [Publishing Persistent Messages](https://docs.solace.com/API/API-Developer-Guide-Python/Python-PM-Publish.md) -- Capacity-bounded back pressure on the publisher (`on_back_pressure_wait`). [Publishing Persistent Messages](https://docs.solace.com/API/API-Developer-Guide-Python/Python-PM-Publish.md) -- Message time-to-live set once on the publisher's message template. [Publishing Persistent Messages](https://docs.solace.com/API/API-Developer-Guide-Python/Python-PM-Publish.md) -- Durable queue plus topic subscription provisioned at start (`CREATE_ON_START`), with the access type from the design summary (one durable queue per consuming service on fan-out). [Creating Queues with the Solace Python API](https://docs.solace.com/API/API-Developer-Guide-Python/Python-API-Create-Queues.md) -- Client acknowledgement only after processing. [Consuming Persistent Messages](https://docs.solace.com/API/API-Developer-Guide-Python/Python-PM-Receive.md) +- (Guaranteed Pub/Sub) Persistent publish to a topic with a publish receipt listener; a NACK is logged with the message id. [Publishing Persistent Messages](https://docs.solace.com/API/API-Developer-Guide-Python/Python-PM-Publish.md) +- (Guaranteed Pub/Sub) Capacity-bounded back pressure on the publisher (`on_back_pressure_wait`). [Publishing Persistent Messages](https://docs.solace.com/API/API-Developer-Guide-Python/Python-PM-Publish.md) +- (Guaranteed Pub/Sub) Message time-to-live set once on the publisher's message template. [Publishing Persistent Messages](https://docs.solace.com/API/API-Developer-Guide-Python/Python-PM-Publish.md) +- (Guaranteed Pub/Sub) Durable queue plus topic subscription provisioned at start (`CREATE_ON_START`), with the access type from the design summary (one durable queue per consuming service on fan-out); with the Custom admin-provisioned-queue knob, the subscriber binds the administrator's queue instead. [Creating Queues with the Solace Python API](https://docs.solace.com/API/API-Developer-Guide-Python/Python-API-Create-Queues.md) +- (Guaranteed Pub/Sub) Client acknowledgement only after processing. [Consuming Persistent Messages](https://docs.solace.com/API/API-Developer-Guide-Python/Python-PM-Receive.md) +- (Direct Pub/Sub) Direct publish to a topic with a publish failure listener and capacity-bounded back pressure (`on_back_pressure_wait`). [Publishing Direct Messages](https://docs.solace.com/API/API-Developer-Guide-Python/Python-DM-Publish.md) +- (Direct Pub/Sub) Direct topic subscription with capacity-bounded back pressure (`on_back_pressure_drop_latest`) and discard detection on received messages. [Consuming Direct Messages](https://docs.solace.com/API/API-Developer-Guide-Python/Python-DM-Receive.md) +- (Request-Reply (Direct)) Requests sent through a `RequestReplyMessagePublisher` with a positive reply timeout, and a replier that answers with `replier.reply()` and ignores a message with no reply-to. [Request-Reply Messaging in the Solace Python API](https://docs.solace.com/API/API-Developer-Guide-Python/Python-API-Request-Reply.md) - Graceful SIGINT/SIGTERM shutdown: `terminate(grace_period)` and then `disconnect()` on every exit path. [Consuming Persistent Messages](https://docs.solace.com/API/API-Developer-Guide-Python/Python-PM-Receive.md) **Your responsibility (not delivered here)** -- TLS secure session (`tcps://` with certificate validation); Quickstart does not generate it. [Messaging Service](https://docs.solace.com/API/API-Developer-Guide-Python/Python-API-Messaging-Service.md) +- TLS secure session (`tcps://` with certificate validation set explicitly in code). Quickstart, and Custom with the secure-session knob unticked, open a TLS session only when the host is `tcps://`, and then validate only through the API defaults, which a `config.json` key can turn off; neither sets the certificate checks in code. [Messaging Service](https://docs.solace.com/API/API-Developer-Guide-Python/Python-API-Messaging-Service.md) +- (Any `tcps://` host: Solace Suggested, the Custom secure-session knob, or Quickstart or Custom without that knob with a `tcps://` host) A trust store directory that holds the broker's CA certificate as a PEM file, named by `solace.messaging.tls.trust-store-path` in `config.json`; the Python API has no default trust store, so the connection fails without it. [Using Client Certificates with Solace Cloud](https://docs.solace.com/Cloud/ght_client_certs.md) +- (Custom admin-provisioned-queue knob) The queue and its topic subscription provisioned on the broker by an administrator, with the access type the design summary records. [Creating Queues with the Solace Python API](https://docs.solace.com/API/API-Developer-Guide-Python/Python-API-Create-Queues.md) - Credentials handled safely: `config.json` is never committed, and credentials are not passed as command-line arguments, where they land in shell history and process listings (the handed-back run commands accept them only for convenience). [Messaging Service](https://docs.solace.com/API/API-Developer-Guide-Python/Python-API-Messaging-Service.md) - Authentication stronger than basic username/password (for example a client certificate or Kerberos) where the environment requires it; this journey generates basic username/password only. [Messaging Service](https://docs.solace.com/API/API-Developer-Guide-Python/Python-API-Messaging-Service.md) -- Dead message queue eligibility on published messages and a dead message queue on the broker. [Configuring Dead Message Queues](https://docs.solace.com/Messaging/Guaranteed-Msg/Setting-Dead-Msg-Queues.md) -- Reconnect tuning for HA failover; Quickstart leaves connect and reconnect behavior at the API defaults. [Solace C API Best Practices](https://docs.solace.com/API/API-Developer-Guide/C-API-Best-Practices.md) +- (Guaranteed Pub/Sub) Dead message queue eligibility (`PERSISTENT_DMQ_ELIGIBLE`) set explicitly on published messages. [Publishing Persistent Messages](https://docs.solace.com/API/API-Developer-Guide-Python/Python-PM-Publish.md) +- (Guaranteed Pub/Sub) A dead message queue provisioned on the broker with a max-redelivery count or a time-to-live; the eligibility flag alone does nothing without it. [Configuring Dead Message Queues](https://docs.solace.com/Messaging/Guaranteed-Msg/Setting-Dead-Msg-Queues.md) +- Reconnect tuning for HA failover; Quickstart leaves connect and reconnect behavior at the API defaults, and Custom without the HA knob keeps the samples' baseline of 20 retries 3000 ms apart. [Solace C API Best Practices](https://docs.solace.com/API/API-Developer-Guide/C-API-Best-Practices.md) - Separate projects or deployments per role (skill guidance: a real deployment lifts each script into its own service or project; no Solace doc page states it). -- `respect-ttl` enabled on the queue, without which the broker ignores the message time-to-live. [Configuring Queues](https://docs.solace.com/Messaging/Guaranteed-Msg/Configuring-Queues.md) -- An administrator-provisioned queue, or the endpoint-create permission for this client, in production. [Creating Queues with the Solace Python API](https://docs.solace.com/API/API-Developer-Guide-Python/Python-API-Create-Queues.md) +- (Guaranteed Pub/Sub) `respect-ttl` enabled on the queue, without which the broker ignores the message time-to-live. [Configuring Queues](https://docs.solace.com/Messaging/Guaranteed-Msg/Configuring-Queues.md) +- (Guaranteed Pub/Sub) An administrator-provisioned queue, or the endpoint-create permission for this client, in production. [Creating Queues with the Solace Python API](https://docs.solace.com/API/API-Developer-Guide-Python/Python-API-Create-Queues.md) - A Python version in active or security support and OpenSSL installed on Linux for TLS ([Supported Environments](https://docs.solace.com/API/API-Developer-Guide-Python/Python-API-supported-Environments.md)), and no `multiprocessing` in the application ([solace-pubsubplus on PyPI](https://pypi.org/project/solace-pubsubplus/)). **Verified by the round-trip** (developer-run until the Python verify script lands) -- Every subscriber prints `VERIFY: CONNECTED` and `VERIFY: QUEUE_BOUND`. -- The publisher prints `VERIFY: CONNECTED` and `VERIFY: PUBLISH_ACKED`, then exits with status 0. -- Every subscriber prints `VERIFY: MESSAGE_RECEIVED` (`PUBLISH_ACKED` alone does not prove delivery). -- Ctrl-C on each subscriber prints `Shutdown signal received` and it exits with status 0. +- (Guaranteed Pub/Sub) Every subscriber prints `VERIFY: CONNECTED` and `VERIFY: QUEUE_BOUND`. +- (Guaranteed Pub/Sub) The publisher prints `VERIFY: CONNECTED` and `VERIFY: PUBLISH_ACKED`, then exits with status 0. +- (Guaranteed Pub/Sub) Every subscriber prints `VERIFY: MESSAGE_RECEIVED` (`PUBLISH_ACKED` alone does not prove delivery). +- (Direct Pub/Sub) The subscriber prints `VERIFY: CONNECTED` and `VERIFY: SUBSCRIBED`; the publisher prints `VERIFY: CONNECTED` and exits with status 0; the subscriber then prints `VERIFY: MESSAGE_RECEIVED`. +- (Request-Reply (Direct)) The replier prints `VERIFY: CONNECTED`, `VERIFY: SUBSCRIBED`, and `VERIFY: REQUEST_RECEIVED`; the requestor prints `VERIFY: CONNECTED` and `VERIFY: REPLY_RECEIVED`, then exits with status 0. +- Ctrl-C on each long-running role (subscriber or replier) prints `Shutdown signal received` and it exits with status 0. **Generation conformance** @@ -258,6 +275,9 @@ Live `docs.solace.com` `.md` pages (WebFetch on demand): - [Messaging Service](https://docs.solace.com/API/API-Developer-Guide-Python/Python-API-Messaging-Service.md) - [Publishing Persistent Messages](https://docs.solace.com/API/API-Developer-Guide-Python/Python-PM-Publish.md) - [Consuming Persistent Messages](https://docs.solace.com/API/API-Developer-Guide-Python/Python-PM-Receive.md) +- [Publishing Direct Messages](https://docs.solace.com/API/API-Developer-Guide-Python/Python-DM-Publish.md) +- [Consuming Direct Messages](https://docs.solace.com/API/API-Developer-Guide-Python/Python-DM-Receive.md) +- [Request-Reply Messaging in the Solace Python API](https://docs.solace.com/API/API-Developer-Guide-Python/Python-API-Request-Reply.md) - [Creating Queues with the Solace Python API](https://docs.solace.com/API/API-Developer-Guide-Python/Python-API-Create-Queues.md) - [Supported Environments](https://docs.solace.com/API/API-Developer-Guide-Python/Python-API-supported-Environments.md) - [Solace C API Best Practices](https://docs.solace.com/API/API-Developer-Guide/C-API-Best-Practices.md) diff --git a/plugins/solace-messaging-skills/skills/solace-application-development/references/python/implement-request-reply.md b/plugins/solace-messaging-skills/skills/solace-application-development/references/python/implement-request-reply.md new file mode 100644 index 0000000..5552d16 --- /dev/null +++ b/plugins/solace-messaging-skills/skills/solace-application-development/references/python/implement-request-reply.md @@ -0,0 +1,44 @@ +# Implement: Request-Reply (Direct) leaf + +The per-leaf wiring for the Request-Reply (Direct) leaf of the Python API. Read this file from `implement-mode.md` Step 4 when the `Pattern` field is `Request-Reply (Direct)`. The shared Step 4 rules in `implement-mode.md` (the disclaimer header, the shared connection helper, the fixed file names, the concurrency rules, the variant discipline, the demo-harness rule, the comments rule, and the Quickstart fail-fast rule) apply here unchanged. + +Request-reply in the Python API uses direct messages only, so this is the ONLY Python request-reply leaf. There is no Request-Reply (Guaranteed) leaf and no guaranteed requestor or replier sample; a design that needs a persisted request is a Guaranteed Pub/Sub leaf (`design-mode.md` Step 2), never this one. + +## Request-reply flow + +Generation reads one requestor sample and the replier sample and adapts each into its own generated script, the same near-verbatim discipline the other leaves use. Direct request-reply is at-most-once on both legs: there is no broker receipt and no redelivery, so this leaf has NO queue, NO provisioning, and NO client acknowledgement. The API manages the reply: the requestor's `RequestReplyMessagePublisher` sets a unique reply-to on each request and correlates the reply, and the replier's `Replier.reply()` routes to that reply-to and copies the correlation. The requestor therefore needs no reply subscription and no correlation id of its own. + +- `python-direct-requestor-blocking-sample.py` (the BASE shape): basic-auth connect, a `RequestReplyMessagePublisher`, ONE blocking `publish_await_response()` with a POSITIVE `REQUEST_TIMEOUT_MS`, a `PubSubTimeoutError` catch with one retry to absorb a cold-start race, service event listeners registered before `connect()`, and a graceful SIGINT/SIGTERM shutdown. It exits 0 on a reply and 1 without one. +- `python-direct-requestor-sample.py` (the VARIANT shape): the same, but several non-blocking `publish()` calls that each return a `concurrent.futures.Future`, so the requests are in flight together, then each Future collected for its reply or its `PubSubTimeoutError`. It exits 0 only when every request sent got a reply. +- `python-direct-replier-sample.py`: basic-auth connect, a `RequestReplyMessageReceiver` built with the request topic subscription (no queue, no provisioning), an async `RequestMessageHandler` that guards on a `None` replier and answers with `replier.reply()`, service event listeners registered before `connect()`, and a graceful SIGINT/SIGTERM shutdown. It answers both requestor shapes. + +**Base shape and variant.** Generate the blocking requestor when the design summary's `Consumption endpoint` reads `API-managed reply (RequestReplyMessagePublisher)` (the default): one request at a time, the blocking call as the synchronization point, the same shape as the JCSMP direct requestor. When the summary records the VARIANT (for example `API-managed reply (RequestReplyMessagePublisher, non-blocking publish)`, chosen because many requests must be in flight at once), read the non-blocking sample instead. Read only the requestor sample the design chose, never both. + +Each script builds its OWN `MessagingService`. Keep the samples' structure: the shared state dataclass, `setup_solace(...)`, `connect_solace(...)`, the main step, and `teardown_solace(...)` in `main()`'s `finally`. Keep every listener and handler class. + +**Ground before you write.** Before writing either script, WebFetch [Request-Reply Messaging in the Solace Python API](https://docs.solace.com/API/API-Developer-Guide-Python/Python-API-Request-Reply.md) in this session (SKILL.md Invariant 2). + +## Replier script (`direct_replier.py`) + +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, then wire it in this sequence: + +1. Build the connection properties with `load_service_properties(args, APP_NAME)`, then the service with `MessagingService.builder().from_properties(properties).build()` (on Quickstart without the reconnection strategy call, per the fail-fast rule). +2. Build the receiver as the sample does: `request_reply().create_request_reply_message_receiver_builder().build(TopicSubscription.of(request_topic))` for the design summary's topic. Register the three service event listeners BEFORE `connect()`. +3. `messaging_service.connect()`. Immediately after `connect()` returns, emit the connect marker: `trace("VERIFY: CONNECTED")`. +4. `receiver.receive_async(handler)` BEFORE `receiver.start()`, as the sample orders them, then `receiver.start()`, which applies the request topic subscription and blocks until the broker confirms it. Immediately after `start()` returns, emit the subscribed marker: `trace("VERIFY: SUBSCRIBED")`. **No reapply-subscriptions setting.** The JCSMP direct replier must set `REAPPLY_SUBSCRIPTIONS` to `true`, or its request topic subscription is lost on an automatic reconnect. The Python API has no such setting and needs none: request-reply in the Python API uses direct messages only, and the [Messaging Service](https://docs.solace.com/API/API-Developer-Guide-Python/Python-API-Messaging-Service.md) page states that the API re-applies the application's direct subscriptions on reconnect. Do not search for or invent a Python equivalent. Keep the sample's `on_reconnected` comment that says so. +5. In the handler's `on_message(message, replier)`, keep the sample's guard: a `None` replier means the message has no reply-to, so log it and return. After the guard, emit the request marker: `trace("VERIFY: REQUEST_RECEIVED")`. Then build the reply with the handler's one `message_builder()` and answer with `replier.reply(reply)`, inside the sample's `PubSubPlusClientError` catch. Use `replier.reply()`, never a manual publish to the reply-to: the API routes and correlates the reply. +6. KEEP the long-running wait loop, the SIGINT/SIGTERM handler that only sets the shutdown event, and `teardown_solace(...)` with `terminate(grace_period)` and `disconnect()`. The replier is the SIGINT target, and its shutdown line (`Shutdown signal received ...`) stays a `trace(...)` call. + +## Requestor script (`direct_requestor_blocking.py`, or `direct_requestor.py` on the variant) + +The requestor connects, sends its request (or requests), collects the reply, 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, then wire it in this sequence: + +1. Build the connection properties and the service exactly as the replier does (step 1 above), then the requestor with `request_reply().create_request_reply_message_publisher_builder().build()`. Register the three service event listeners BEFORE `connect()`. +2. `messaging_service.connect()`. Immediately after `connect()` returns, emit the connect marker: `trace("VERIFY: CONNECTED")`. Then `requestor.start()`. +3. Base shape: build the request with `message_builder().build(payload)` and send it to the design summary's topic with `publish_await_response(..., reply_timeout=REQUEST_TIMEOUT_MS, ...)`. Keep the POSITIVE `REQUEST_TIMEOUT_MS` (the API has no default), the `PubSubTimeoutError` catch, and the single retry (`REQUEST_ATTEMPTS = 2`). Variant: keep the sample's send phase (one `publish(...)` per request, each returning a Future) and its collect phase (`future.result()` per request, a `PubSubTimeoutError` counted as a missing reply). +4. When a reply arrives, emit the reply marker: `trace("VERIFY: REPLY_RECEIVED")`. On the variant, emit it once for each reply collected. Keep the sample's exit code rule: 1 when a request sent got no reply, 0 otherwise (a shutdown signal before the reply is a clean stop). +5. Self-exit: return from the main step, and `teardown_solace(...)` in `main()`'s `finally` calls `terminate(grace_period)` and `disconnect()`. Keep the SIGINT/SIGTERM handler the sample carries, so Ctrl-C during the reply wait still reaches teardown. When the request describes a real application, its own request payload replaces the sample's text (the demo-harness rule). + +## The marker contract + +These four `VERIFY:` marker strings (`VERIFY: CONNECTED`, `VERIFY: SUBSCRIBED`, `VERIFY: REQUEST_RECEIVED`, `VERIFY: REPLY_RECEIVED`) are the same strings the JCSMP Request-Reply (Direct) leaf emits, character for character. They are read PER PROCESS: the replier output carries `SUBSCRIBED` and `REQUEST_RECEIVED`, the requestor output carries `REPLY_RECEIVED`, and both print `CONNECTED`. Until the Python verify script lands, the developer reads them from the two terminals (`implement-mode.md` Step 5); the verify script will read the same strings, so never change one. diff --git a/plugins/solace-messaging-skills/skills/solace-application-development/references/python/solace-suggested-mode.md b/plugins/solace-messaging-skills/skills/solace-application-development/references/python/solace-suggested-mode.md new file mode 100644 index 0000000..3d6e7d3 --- /dev/null +++ b/plugins/solace-messaging-skills/skills/solace-application-development/references/python/solace-suggested-mode.md @@ -0,0 +1,97 @@ +# Solace Suggested Mode + +The Solace Suggested overlay for Implement mode of the Python API. This file is reached from `implement-mode.md` Step 0 when the developer chooses the Solace Suggested path. It hardens whatever the design summary chose without re-describing how the chosen leaf is generated. 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. + +This overlay is TLS-secure by definition. It assumes a broker whose TLS server certificate is already configured; Solace Cloud, the primary broker default, has TLS out of the box. There is NO non-secure variant of Solace Suggested. When the target broker has no TLS configured, do not proceed on this path: state that Solace Suggested requires a TLS-capable broker and route back to the `implement-mode.md` Step 0 door, which offers Custom with the equivalent knobs minus the secure session, or Quickstart. The session is built from the connection details the developer puts into a gitignored `config.json`. The overlay does NOT provision or configure any broker (no DMQ, no queues, no TLS certificates). It states the broker-side prerequisites; it never sets them up. + +## This is a leaf-agnostic overlay + +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 publishes, and the separate-projects layout applies to every pattern, since each has two roles (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 leaf wiring file and samples each leaf reads, the near-verbatim adaptation discipline), and the fixed `VERIFY:` marker contract. It ADDS only the Solace Suggested deltas below. It 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 script this overlay describes carries the AI-assisted disclaimer header (the exact two lines `implement-mode.md` Step 4 defines) and keeps every import explicit; never write `from solace.messaging... import *`. This overlay names identifiers, constants, and method calls; it does not paste whole generated scripts. + +## Step 0: Carry over the config.json convention + +The Solace Suggested path reads broker connection details ONLY from a `config.json` file. Its keys are the Solace Python API property names the shared helper documents (`solace.messaging.transport.host`, `solace.messaging.service.vpn-name`, `solace.messaging.authentication.basic.username`, `solace.messaging.authentication.basic.password`), plus the trust store key that Step 1 adds. State plainly to the developer that `config.json` holds broker credentials, so it MUST be added to `.gitignore` and never committed, and that the credential VALUES MUST be placed directly into `config.json`, never pasted into the chat. Never inline a host, message VPN, username, or password into a generated script. + +On this path there are no command-line connection arguments. Generate the shared `solace_connection_config.py` helper WITHOUT its command-line fallback: when `config.json` is absent, it prints one line that names the missing file and exits with status 1. Add the trust store key to its required keys, so a missing trust store fails at start with a clear message. Keep everything else in the helper (the pass-through of every key, the blank-value checks, the empty-password rule). The generated `config.example.json` carries the four keys plus the trust store key, with a `tcps://` placeholder host: + +```json +{ + "solace.messaging.transport.host": "tcps://HOST:55443", + "solace.messaging.service.vpn-name": "YOUR_VPN", + "solace.messaging.authentication.basic.username": "YOUR_USERNAME", + "solace.messaging.authentication.basic.password": "YOUR_PASSWORD", + "solace.messaging.tls.trust-store-path": "PATH_TO_TRUST_STORE_DIRECTORY" +} +``` + +Generate the scripts directly, with no per-stage confirmation gate, the same way Quickstart does, then let the developer review the written files. The credential VALUES live only in the gitignored `config.json`, never in a generated script or the chat, so there is nothing credential-bearing to gate on. + +## Step 1: Build the TLS secure session + +The `host` in `config.json` carries a `tcps://` scheme: the Messaging Service page states that TLS needs the secure TCP protocol (`tcps`) in the host property. If the developer gives a `tcp://` (plaintext) host, tell them to use the broker's `tcps://` URL and state the TLS-already-configured assumption; never open a plaintext session on the Solace Suggested path. If the broker genuinely has no TLS configured, this overlay does not fit: route back to the `implement-mode.md` Step 0 door (Custom minus the secure-session knob, or Quickstart) rather than degrading the session here. + +Set server-certificate validation EXPLICITLY in code. After `load_service_properties(...)` returns and BEFORE the service is built, set these three `transport_layer_security_properties` keys to `True` on the loaded properties dict: `CERT_VALIDATED`, `CERT_REJECT_EXPIRED`, and `CERT_VALIDATE_SERVERNAME`. The values are the API defaults, but setting them explicitly makes the secure posture auditable in code review, the same reason the JCSMP overlay sets its validation properties explicitly. Because the code writes them after the file is loaded, a `config.json` value cannot turn validation off. NEVER set any of the three to `False`, and NEVER call `without_certificate_validation()`; the Messaging Service page restricts that call to development environments. + +**The trust store is the developer's.** The Python API documents no default trust store, so this overlay always sets one: the `solace.messaging.tls.trust-store-path` key in `config.json` (the `TRUST_STORE_PATH` property) names a directory that holds the certificate of the CA that signed the broker's server certificate. The developer supplies the directory and its contents; for Solace Cloud that is the public CA certificate the service uses. The generated code never downloads a certificate, never creates the directory, and never hardcodes the path. + +**Tell the developer about the trust store, twice.** This step is new to a developer who comes from JCSMP (Java ships a default trust store, so JCSMP needs no such step) and easy to miss for a developer new to TLS, so do not leave it to the checklist alone: + +1. BEFORE generating, in plain words: with TLS, the app checks that the broker's certificate was signed by a certificate authority (CA) it trusts. The Python API has no built-in list of trusted CAs, so the developer must give the app one: a directory that holds the CA certificate as a PEM file. For Solace Cloud, the service's **Connect** tab has a **Download PEM** link for that certificate; link [Using Client Certificates with Solace Cloud](https://docs.solace.com/Cloud/ght_client_certs.md), which states that a client needs the certificate for the public CA used by the event broker service and that not all clients have a default trust store. For another broker, the broker administrator supplies the CA certificate. Without the directory, the connection fails at start. +2. In the handed-back next steps (`implement-mode.md` Step 5): create a directory (for example `trust-store/` in the project), save the downloaded PEM file into it, and set `solace.messaging.tls.trust-store-path` in `config.json` to that directory. Recommend that the directory sits outside version control or holds only the public CA certificate; never put a private key in it. + +Record the same item in the checklist under Your responsibility (`implement-mode.md` Step 6). + +Ground TLS in the TLS section of [Messaging Service](https://docs.solace.com/API/API-Developer-Guide-Python/Python-API-Messaging-Service.md). On Linux, TLS also needs OpenSSL installed ([Supported Environments](https://docs.solace.com/API/API-Developer-Guide-Python/Python-API-supported-Environments.md)). + +## Step 2: Ask the HA-failover question; upgrade the reconnect values on yes, keep the sample baseline on no + +After broker confirmation and before generating the service, ask the developer plainly: is HA failover set up? Ask it as a direct question and wait for the answer. + +The reference samples already ship a reasonable reconnect baseline: `with_reconnection_retry_strategy(RetryStrategy.parametrized_retry(RECONNECT_RETRIES, RECONNECT_RETRY_INTERVAL_MS))` with 20 retries 3000 ms apart, the recommended default when the design says nothing about high availability. When the answer is no, KEEP this baseline as-is; do not strip it back to the API defaults (the fail-fast posture belongs to the Quickstart door alone), and do not add the HA-only values. + +ONLY when the answer is yes, apply the HA failover values the samples' HA comment block names: `with_connection_retry_strategy(RetryStrategy.parametrized_retry(1, 3000))`, `with_reconnection_retry_strategy(RetryStrategy.parametrized_retry(20, 3000))`, and `transport_layer_properties.CONNECTION_RETRIES_PER_HOST` set to 5 on the properties dict. These map to the JCSMP overlay's `connectRetries` 1, `reconnectRetries` 20, `reconnectRetryWaitInMillis` 3000, and `connectRetriesPerHost` 5. Keep both strategy calls BEFORE `from_properties(...)`, as the samples order them. Replace the samples' HA comment block with a one-line comment that names the applied values. Every interval is in MILLISECONDS (the seconds-versus-milliseconds trap in `implement-mode.md` Step 5). Ground the values in [Solace C API Best Practices](https://docs.solace.com/API/API-Developer-Guide/C-API-Best-Practices.md), which the Python API inherits because it wraps the C API. + +## Step 3: Set DMQ eligibility on every persistent publish (Solace Suggested path only) + +On the Solace Suggested path, add `.with_property(message_properties.PERSISTENT_DMQ_ELIGIBLE, True)` to the persistent publisher's `message_builder()` template, next to the `PERSISTENT_TIME_TO_LIVE` property it already sets, so every message built from the template carries it. In the Python API this applies to the Guaranteed Pub/Sub publisher only: there is no guaranteed request-reply leaf, so no other script builds a persistent message. The API reference gives this property a default of `True`; set it explicitly anyway, for the same auditable reason as the TLS properties in Step 1. + +The direct leaves are EXCLUDED. The API reference states that the property is valid only for persistent messages, so it is a misleading no-op on a direct message; do NOT set it on Direct Pub/Sub or Request-Reply (Direct). Quickstart and the reference samples are unchanged; the explicit property is a Solace Suggested addition only. + +At the call site, state the broker-side prerequisite as the developer's responsibility in one comment: a message moves to a dead message queue only when a DMQ is configured on the broker AND a max-redelivery count or a time-to-live applies. The flag alone does not make DMQ work. Ground the persistent publish in [Publishing Persistent Messages](https://docs.solace.com/API/API-Developer-Guide-Python/Python-PM-Publish.md) and the broker side in [Configuring Dead Message Queues](https://docs.solace.com/Messaging/Guaranteed-Msg/Setting-Dead-Msg-Queues.md). + +## Step 4: Generate separate, independent Python projects + +The Solace Suggested path decouples the roles. The DEFAULT shape is one standalone Python project per role (publisher and subscriber, or requestor and replier), each in its own directory. On the fan-out leaf, every subscriber script gets its own project beside the publisher project. Each project is self-contained: its own `requirements.txt` with the `solace-pubsubplus` version resolved per `implement-mode.md` Step 3, its own `.venv`, its own copy of `solace_connection_config.py`, its own `config.example.json`, `.gitignore`, and gitignored `config.json`, and its own tailored `solace-verification-checklist.md`. There is no shared package and no shared virtual environment between the projects. + +**When the developer's ask requires a single deliverable** (one command, one app, one dashboard), the separate-projects default conflicts with the ask. Surface that conflict plainly and let the developer resolve it; NEVER merge silently. Offer two resolutions: (1) keep the independent projects plus a thin launcher script that starts each one with its own `.venv/bin/python`; (2) drop the separate-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 dependency. TLS, DMQ, and the retry strategies all live in the same `solace-pubsubplus` package Quickstart already uses. + +**Verification.** The Python API does not bundle a verify script yet, so the Solace Suggested path ends in the same check-and-hand-back fallback as Quickstart (`implement-mode.md` Step 5), run once per project: `py_compile`, the import smoke, and the conformance checks from each project root. Hand back the run commands per project, long-running role first, each from its own directory with its own interpreter, and with NO connection arguments (each script reads its own `config.json`). When the developer asks you to run them, follow the manual-run rule in `implement-mode.md` Step 5, including the live-broker heads-up (SKILL.md Invariant 6). A live Solace Suggested run needs a TLS-capable broker and the trust store from Step 1; a TLS or trust store error is an environment failure, never a code failure, so do not modify the code for it. + +## Step 5: Topic architecture + +For how to structure, order, name, and place wildcards in the topic hierarchy, point the developer to the co-installed `solace-topic-best-practices` skill, which reads the canonical Topic Architecture Best Practices page live and applies it. Do not design the topic hierarchy here and do not restate that skill's guidance; the topic string the developer settles on is recorded in the design summary, but the hierarchy decision belongs to that skill. + +## 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 scripts and writes the tailored `solace-verification-checklist.md` into each project as generation output, and Step 6 resolves each item to its state for this door, invites the developer's additions, and reports the same in chat. This overlay adds the Solace Suggested hardening; it does not replace those contracts. + +## Grounding references + +Live `docs.solace.com` `.md` pages (WebFetch on demand): + +- [Messaging Service](https://docs.solace.com/API/API-Developer-Guide-Python/Python-API-Messaging-Service.md) +- [Solace C API Best Practices](https://docs.solace.com/API/API-Developer-Guide/C-API-Best-Practices.md) +- [Publishing Persistent Messages](https://docs.solace.com/API/API-Developer-Guide-Python/Python-PM-Publish.md) +- [Configuring Dead Message Queues](https://docs.solace.com/Messaging/Guaranteed-Msg/Setting-Dead-Msg-Queues.md) +- [Supported Environments](https://docs.solace.com/API/API-Developer-Guide-Python/Python-API-supported-Environments.md) +- [Using Client Certificates with Solace Cloud](https://docs.solace.com/Cloud/ght_client_certs.md) + +Live exception (no `docs.solace.com` `.md` form; use the live URL directly): + +- [Python API reference](https://docs.solace.com/API-Developer-Online-Ref-Documentation/python/index.html) (the `transport_layer_security_properties`, `transport_layer_properties`, and `message_properties` constants)