From 317ebd93cce8a18f97169294a2e01fafa966374c Mon Sep 17 00:00:00 2001 From: adiel-sammak <71227923+aelsammak@users.noreply.github.com> Date: Wed, 28 Oct 2026 12:56:37 -0400 Subject: [PATCH] EBP-3341: Require bytearray or str payloads and relax threading labels - Add a payload type rule to the Python demo-harness rule: a bytes payload fails in build() and publish(), so wrap encoded text in bytearray - Repeat the rule in the guaranteed, direct, and request-reply publish steps and add a checklist conformance item - Accept equivalent names for the two threading answers in the design tree - Relax the matching output eval grader pattern in four Python cases --- plugins/solace-messaging-skills/evals/output-evals.json | 8 ++++---- .../references/python/design-mode.md | 2 +- .../references/python/implement-direct-pubsub.md | 2 +- .../references/python/implement-guaranteed-pubsub.md | 2 +- .../references/python/implement-mode.md | 2 +- .../references/python/implement-request-reply.md | 2 +- .../references/python/verification-checklist.md | 1 + 7 files changed, 10 insertions(+), 9 deletions(-) diff --git a/plugins/solace-messaging-skills/evals/output-evals.json b/plugins/solace-messaging-skills/evals/output-evals.json index ad631a4..c91b2f1 100644 --- a/plugins/solace-messaging-skills/evals/output-evals.json +++ b/plugins/solace-messaging-skills/evals/output-evals.json @@ -146,7 +146,7 @@ "max_agent_turns": 25, "graders": [ { "type": "assistant_grep", "pattern": "[Bb]roker", "expect": "present", "turn": 1 }, - { "type": "assistant_grep", "pattern": "[Mm]ulti-threaded|[Mm]inimal threading", "expect": "present", "turn": 1 }, + { "type": "assistant_grep", "pattern": "[Mm]ulti-?threaded|[Mm]inimal( threading| \\(blocking)|[Ss]ingle[- ]threaded|[Tt]hread of control", "expect": "present", "turn": 1 }, { "type": "assistant_grep", "pattern": "Pattern\\**:", "expect": "present" }, { "type": "assistant_grep", "pattern": "Delivery\\**:", "expect": "present" }, { "type": "assistant_grep", "pattern": "Access type\\**:", "expect": "present" }, @@ -199,7 +199,7 @@ "graders": [ { "type": "tool_use", "tool": "*", "input_pattern": "src/solace_connection_config\\.py", "expect": "present" }, { "type": "tool_use", "tool": "Write", "input_pattern": "\"file_path\": *\"[^\"]*\\.py\"", "exclude_pattern": "\"file_path\": *\"[^\"]*/src/[^\"/]+\\.py\"", "expect": "absent" }, - { "type": "assistant_grep", "pattern": "[Mm]ulti-threaded|[Mm]inimal threading", "expect": "present", "turn": 1 }, + { "type": "assistant_grep", "pattern": "[Mm]ulti-?threaded|[Mm]inimal( threading| \\(blocking)|[Ss]ingle[- ]threaded|[Tt]hread of control", "expect": "present", "turn": 1 }, { "type": "assistant_grep", "pattern": "Threading[*:`[:space:]]+Async", "expect": "present" }, { "type": "assistant_grep", "pattern": "Quickstart", "fixed": true, "expect": "present", "turn": 3 }, { "type": "assistant_grep", "pattern": "Solace Suggested", "fixed": true, "expect": "present", "turn": 3 }, @@ -274,7 +274,7 @@ "graders": [ { "type": "tool_use", "tool": "*", "input_pattern": "src/solace_connection_config\\.py", "expect": "present" }, { "type": "tool_use", "tool": "Write", "input_pattern": "\"file_path\": *\"[^\"]*\\.py\"", "exclude_pattern": "\"file_path\": *\"[^\"]*/src/[^\"/]+\\.py\"", "expect": "absent" }, - { "type": "assistant_grep", "pattern": "[Mm]ulti-threaded|[Mm]inimal threading", "expect": "present", "turn": 1 }, + { "type": "assistant_grep", "pattern": "[Mm]ulti-?threaded|[Mm]inimal( threading| \\(blocking)|[Ss]ingle[- ]threaded|[Tt]hread of control", "expect": "present", "turn": 1 }, { "type": "assistant_grep", "pattern": "Threading[*:`[:space:]]+Async", "expect": "present" }, { "type": "assistant_grep", "pattern": "[Ff]ailover", "expect": "present", "turn": 4 }, { "type": "assistant_grep", "pattern": "ght_client_certs.md", "fixed": true, "expect": "present" }, @@ -311,7 +311,7 @@ "graders": [ { "type": "tool_use", "tool": "WebFetch", "input_pattern": "Python-API-Request-Reply\\.md", "expect": "present" }, { "type": "assistant_grep", "pattern": "Pattern[*:`[:space:]]+Guaranteed Pub/Sub \\(", "expect": "present" }, - { "type": "assistant_grep", "pattern": "[Mm]ulti-threaded|[Mm]inimal threading", "expect": "present", "turn": 1 }, + { "type": "assistant_grep", "pattern": "[Mm]ulti-?threaded|[Mm]inimal( threading| \\(blocking)|[Ss]ingle[- ]threaded|[Tt]hread of control", "expect": "present", "turn": 1 }, { "type": "assistant_grep", "pattern": "Threading[*:`[:space:]]+Async", "expect": "present" }, { "type": "assistant_grep", "pattern": "Pattern[*:`[:space:]]+Request-Reply", "expect": "absent" }, { "type": "assistant_grep", "pattern": "[Cc]orrelation", "expect": "present" }, 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 ca713bb..983fe0b 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 @@ -45,7 +45,7 @@ Ask only the questions the prompt left open, in this order. Before you answer ea 1. **Interaction shape: request-reply or publish/subscribe?** Grounded in [Message Exchange Patterns](https://docs.solace.com/Get-Started/message-exchange-patterns.md). Decision: a request that expects a correlated reply is Request-Reply; an event fanned out to one or many independent consumers is publish/subscribe. 2. **Delivery guarantee: direct or guaranteed?** This ONE question applies to both interaction shapes, so ask it next regardless of the answer to question 1. Grounded in [Message Delivery Modes](https://docs.solace.com/Get-Started/message-delivery-modes.md). Decision: direct is fire-and-forget with no broker-side persistence, for high-rate flows that tolerate occasional loss; guaranteed (persistent) is persisted and survives consumer downtime. -3. **Threading on the messaging data path: multi-threaded, or minimal threading? (Direct Pub/Sub and guaranteed publish/subscribe branches only.)** Grounded in [Consuming Persistent Messages](https://docs.solace.com/API/API-Developer-Guide-Python/Python-PM-Receive.md) on the guaranteed branch and in [Consuming Direct Messages](https://docs.solace.com/API/API-Developer-Guide-Python/Python-DM-Receive.md) on the direct branch; both pages state that `receive_message(timeout)` blocks the routine until a message arrives, the timeout passes, or a service interruption occurs, and the first states that `receive_async()` invokes the callback on a new Python thread for every message. Frame the choice as WHERE the application's thread of control sits on the messaging data path, never as speed: make no throughput claim for either answer. Name the two answers with the labels written here, multi-threaded and minimal threading, whatever words the framing adds: the developer's reply maps onto those labels. The reason the question exists, stated in one line when you ask it: in Python the GIL lets only one thread run Python code at a time, so the extra API threads of the callback shape buy less than they do in other languages, and a developer who wants one thread of control can have it. Three answers: +3. **Threading on the messaging data path: multi-threaded, or minimal threading? (Direct Pub/Sub and guaranteed publish/subscribe branches only.)** Grounded in [Consuming Persistent Messages](https://docs.solace.com/API/API-Developer-Guide-Python/Python-PM-Receive.md) on the guaranteed branch and in [Consuming Direct Messages](https://docs.solace.com/API/API-Developer-Guide-Python/Python-DM-Receive.md) on the direct branch; both pages state that `receive_message(timeout)` blocks the routine until a message arrives, the timeout passes, or a service interruption occurs, and the first states that `receive_async()` invokes the callback on a new Python thread for every message. Frame the choice as WHERE the application's thread of control sits on the messaging data path, never as speed: make no throughput claim for either answer. Name the two answers so the developer can tell them apart. The first answer can be called multi-threaded, async, callback, or push; the second can be called minimal threading, blocking, pull, or single thread of control. Any of these names is fine, and the developer's reply maps onto the matching answer. The reason the question exists, stated in one line when you ask it: in Python the GIL lets only one thread run Python code at a time, so the extra API threads of the callback shape buy less than they do in other languages, and a developer who wants one thread of control can have it. Three answers: - **Multi-threaded app** (the push model): the API pushes each received message, and on the guaranteed branch each publish receipt, to an application callback on an API thread, so the application manages the concurrency between its own threads and the API's threads. This is the async shape and the current default. Record `Threading: Async`. - **Minimal threading preferred** (the pull model): the application's own thread pulls each message with a blocking `receive_message(timeout)` call, and on the guaranteed branch publishes each message with a blocking `publish_await_acknowledgement()` call, so no application callback runs on the data path and no application state is shared with an API thread; only the service event listeners still run on API threads. The cost is one message in flight at a time on each side. This is the blocking shape. Record `Threading: Minimal (blocking publish and receive)`. - **Unsure**: the async shape. Record `Threading: Async`. 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 index f479d64..8b604ae 100644 --- 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 @@ -31,7 +31,7 @@ The publisher only connects and publishes a short bounded burst of DIRECT messag 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). That replacement changes the payload and the topic, never the shape: a request that describes a continuous or high-rate feed (readings several times a second, a telemetry stream) still gets a publisher that sends a bounded batch of the application's real messages and exits on its own, because the `direct` stage waits for that exit. Do not keep the sample's open-ended loop behind an environment-variable cap, and do not move the project to the `app` stage to drive it; the continuous trigger is the developer's integration step after verification. +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), and each payload stays a `bytearray` or a `str`, never `bytes`. That replacement changes the payload and the topic, never the shape: a request that describes a continuous or high-rate feed (readings several times a second, a telemetry stream) still gets a publisher that sends a bounded batch of the application's real messages and exits on its own, because the `direct` stage waits for that exit. Do not keep the sample's open-ended loop behind an environment-variable cap, and do not move the project to the `app` stage to drive it; the continuous trigger is the developer's integration step after verification. 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 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 88303fb..81bd483 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 @@ -52,7 +52,7 @@ The publisher only connects and publishes; it does NOT provision the queue or th 1. Build the connection properties and the service exactly as the subscriber does (step 1 above), then the publisher. Callback shape: `create_persistent_message_publisher_builder().on_back_pressure_wait(PUBLISH_BUFFER_CAPACITY).build()`. Blocking shape: `create_persistent_message_publisher_builder().build()` with NO back-pressure setting, as the blocking sample explains: one message is in flight at a time, so the publish buffer cannot grow. Register the three service event listeners BEFORE `connect()`. 2. `messaging_service.connect()`. Immediately after `connect()` returns, emit the connect marker: `trace("VERIFY: CONNECTED")`. 3. `publisher.start()`. Callback shape: then `publisher.set_message_publish_receipt_listener(...)` AFTER `start()`, as the sample orders them. In the receipt handler's ACK branch, emit the publish-ACK marker: `trace("VERIFY: PUBLISH_ACKED")`. CRITICAL: the sample logs the ACK with `logger.debug`, which the INFO-level logging never shows, so the marker would never appear. Emit it through `trace(...)`, not through the logger. In the NACK branch, keep the sample's warning and also set the shared state's `exit_code` to 1, so a rejected message makes the publisher exit non-zero. Pass the shared state to the handler to do this. Blocking shape: NO receipt listener; the marker moves into step 4. -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, and `build(payload, additional_message_properties={APPLICATION_MESSAGE_ID: msg_id})` per message. Callback shape: `publish(message, topic, user_context=msg_id)`. Blocking shape: `publish_await_acknowledgement(message, topic, PUBLISH_ACK_TIMEOUT_MS)` with the sample's POSITIVE timeout in milliseconds and no `user_context`; a normal return IS the broker's ACK, so emit `trace("VERIFY: PUBLISH_ACKED")` immediately after the call returns. Keep the blocking sample's three discard branches (`PubSubTimeoutError`, the NACK error classes, and `PublisherOverflowError`), which log the message id at INFO and continue (the Discard the Message strategy from the Publishing Persistent Messages page), but set the shared state's `exit_code` to 1 in EACH of them, the same way the callback shape's NACK branch does: the loop still sends the remaining messages, and the publisher exits non-zero at the end, so a run that lost one message out of many fails the `verify.sh` stage instead of passing on the markers of the others. Keep its `PubSubPlusClientError` branch too, which sets `exit_code` to 1 and stops. See [Publishing Persistent Messages](https://docs.solace.com/API/API-Developer-Guide-Python/Python-PM-Publish.md). +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, and `build(payload, additional_message_properties={APPLICATION_MESSAGE_ID: msg_id})` per message, where `payload` is a `bytearray` or a `str`, never `bytes` (the demo-harness rule in `implement-mode.md`). Callback shape: `publish(message, topic, user_context=msg_id)`. Blocking shape: `publish_await_acknowledgement(message, topic, PUBLISH_ACK_TIMEOUT_MS)` with the sample's POSITIVE timeout in milliseconds and no `user_context`; a normal return IS the broker's ACK, so emit `trace("VERIFY: PUBLISH_ACKED")` immediately after the call returns. Keep the blocking sample's three discard branches (`PubSubTimeoutError`, the NACK error classes, and `PublisherOverflowError`), which log the message id at INFO and continue (the Discard the Message strategy from the Publishing Persistent Messages page), but set the shared state's `exit_code` to 1 in EACH of them, the same way the callback shape's NACK branch does: the loop still sends the remaining messages, and the publisher exits non-zero at the end, so a run that lost one message out of many fails the `verify.sh` stage instead of passing on the markers of the others. Keep its `PubSubPlusClientError` branch too, which sets `exit_code` to 1 and stops. 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)`. Callback shape: it 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. Blocking shape: every call has already returned with its ACK and printed the marker, so `terminate()` returns at once; keep the sample's `IncompleteMessageDeliveryError` branch for a message the API never handed to the transport. Drop the stats thread (or the blocking sample's inline rate report) with the loop. Keep the SIGINT/SIGTERM handler, so Ctrl-C during a back pressure wait or an acknowledgement 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 (on either threading shape), 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. 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 4ab3cd5..f63df74 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 @@ -132,7 +132,7 @@ Hold every invariant while adapting: keep the shared connection helper, the serv **Preserve the marker contract.** A variant adapts the SHAPE, never the marker strings. It MUST still emit its leaf's `VERIFY:` markers, character for character. Never rename or drop a marker to fit a variant. -**Demo harness is not application logic.** The samples carry demo-harness elements that exist only to make a standalone run observable: the once-per-second stats loop or thread (the blocking samples report the rate inline from the main loop instead), the continuous publish loop, and the rotating example payload. When the developer's request describes a real application domain, do NOT carry these into the generated scripts: replace the example payload and the publish loop with the application's real messages and triggers, never add a delay or rate limit between sends that the application does not ask for, and keep only the lifecycle pieces the leaf's steps call for (the SIGINT/SIGTERM shutdown on the long-running role, the self-exit on the foreground role). When the request IS a demo or a try-it run, keeping the harness is fine. Two things are NOT demo harness: the `trace(...)` narration function (keep it; applications replace its single body to route narration elsewhere) and the `VERIFY:` markers, which are emitted through `trace(...)`. The generated `trace(...)` body MUST stay `print(message, flush=True)`, so the markers reach stdout without buffering delays; rerouting it is the developer's step after verification. +**Demo harness is not application logic.** The samples carry demo-harness elements that exist only to make a standalone run observable: the once-per-second stats loop or thread (the blocking samples report the rate inline from the main loop instead), the continuous publish loop, and the rotating example payload. When the developer's request describes a real application domain, do NOT carry these into the generated scripts: replace the example payload and the publish loop with the application's real messages and triggers, never add a delay or rate limit between sends that the application does not ask for, and keep only the lifecycle pieces the leaf's steps call for (the SIGINT/SIGTERM shutdown on the long-running role, the self-exit on the foreground role). The replacement payload keeps the samples' payload type: a `bytearray` (wrap encoded text, for example `bytearray(json.dumps(order).encode("utf-8"))`) or a `str`. NEVER pass the `bytes` that `.encode()` returns to `build()`, `publish()`, `publish_await_acknowledgement()`, or `publish_await_response()`: the API accepts only a `bytearray`, a `str`, or an `OutboundMessage` ([Publishing Persistent Messages](https://docs.solace.com/API/API-Developer-Guide-Python/Python-PM-Publish.md)), and a `bytes` payload fails at run time (`build()` raises `Failed to create attachment for the message`). Keep `bytearray` so the receiver's `get_payload_as_bytes()` read still applies; a `str` payload is read with `get_payload_as_string()`. When the request IS a demo or a try-it run, keeping the harness is fine. Two things are NOT demo harness: the `trace(...)` narration function (keep it; applications replace its single body to route narration elsewhere) and the `VERIFY:` markers, which are emitted through `trace(...)`. The generated `trace(...)` body MUST stay `print(message, flush=True)`, so the markers reach stdout without buffering delays; rerouting it is the developer's step after verification. **Comments follow their code.** The samples' comments carry the documented best practices into the code a developer reads long after this session. A generated script that carries a construct from a sample carries that construct's comment too, with only the names adapted. A dropped construct takes its comment with it. Fresh messaging code with no sample twin that applies a documented practice gets a SHORT comment naming the practice, in the samples' comment style. The one-line comments this skill mandates elsewhere (the Quickstart fail-fast posture below) always apply on their paths. 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 index 8bb33e5..7aa2132 100644 --- 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 @@ -35,7 +35,7 @@ The requestor connects, sends its request (or requests), collects the reply, and 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). +3. Base shape: build the request with `message_builder().build(payload)`, where `payload` is a `bytearray` or a `str`, never `bytes` (the demo-harness rule in `implement-mode.md`), 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). diff --git a/plugins/solace-messaging-skills/skills/solace-application-development/references/python/verification-checklist.md b/plugins/solace-messaging-skills/skills/solace-application-development/references/python/verification-checklist.md index d95f098..89b23b4 100644 --- a/plugins/solace-messaging-skills/skills/solace-application-development/references/python/verification-checklist.md +++ b/plugins/solace-messaging-skills/skills/solace-application-development/references/python/verification-checklist.md @@ -56,6 +56,7 @@ These are mechanical conformance checks on the generated output itself. The copi - [ ] `requirements.txt` pins the `solace-pubsubplus` version that PyPI reported as `info.version` at generation time, or the developer's version when PyPI was unreachable. - [ ] This tailored checklist exists at every generated project root. - [ ] `py_compile` and the import smoke passed (`implement-mode.md` Step 5). +- [ ] Every payload passed to `build()` or a publish call is a `bytearray` or a `str`, never the `bytes` that `.encode()` returns. - [ ] Every construct carried from a reference sample keeps that construct's comment (names adapted); dropped demo-harness code took its comments with it; fresh messaging code that applies a documented practice carries a short comment naming it (`implement-mode.md` Step 4). - [ ] A `verify.sh` stage ran and its stage name and exit code are recorded here: ` → exit ` (or: the compile-only fallback was taken and the exact commands were handed back). - [ ] Every page named in the design summary's Grounding docs field was WebFetched in this session (`none fetched` is an honest value; an unfetched citation is not).