Repository navigation
MLLP and TCP deliveries refuse a payload holding more than one MSH (BACKLOG #2858, ADR 0209) - #2383
Conversation
check_frame_bytes in transports/framing.py now refuses, before the encode, a payload that opens with MSH (past whitespace and a byte order mark) and holds another MSH line, CR or LF line ends alike: NegativeAckError (permanent=True), code "multiple-messages", content-free text. The live send (frame_for_delivery), each MLLP batch member (check_frame, rewrite=False) and a shadow outbound all reach it, so wiring_runner.py is unchanged. A payload not led by MSH (X12 relay, the BHS batch envelope) is never read by this rule. The ingress reading _holds_more_than_one_message moves unchanged from pipeline/ingress_guards.py to parsing/split.py beside _MSH_BOUNDARY; the delivery's _sends_more_than_one_message sits beside it, and a test holds the two to one answer. BACKLOG #2858
…one MSH Records the outbound decision ADR 0206 rule 5 deferred, with its index row, a forward link from ADR 0206, the CONNECTIONS.md MLLP and TCP notes, and the changelog fragment. HTTP and File outbounds are out of scope; no batch-sender opt-out (ADR 0082 is the batch path). BACKLOG #2858 Proposed PR title: MLLP and TCP deliveries refuse a payload holding more than one MSH (BACKLOG #2858, ADR 0209) Proposed ledger banner: DONE -- an MLLP or TCP delivery refuses an MSH-led payload holding a second MSH line as a permanent "multiple-messages" dead-letter, on the live send, each batch member and a shadow outbound; the BHS envelope and X12 relays are not read. The decision (refuse, no batch-sender opt-out, HTTP and File out of scope) is the batch 265 Manager's under the owner's driver rule, recorded in ADR 0209; the owner may overrule at review.
|
QA (lane M, BACKLOG #2858, head 86f08bf): /simplify (four agents), then code-review skill at level xhigh (BUILDER.md step 11), args as briefed plus "including the uncommitted working-tree changes". Two rounds (the cap), 13 then 14 findings; each run returned a JSON findings list and printed no separate tag. Fixed: BOM handling leading and between messages, ASCII source, a false ADR claim that TCP has a batch path, completeness overclaims reworded, ADR prose on rewrite ordering, stale docstrings in tcp/mllp/base and a stale test comment, head-member batch case, TCP tests parametrized. Shipped open: findings 1-3 in the PR body. |
|
Lander: enqueuing at pinned head 86f08bf. QA names this head. The refusal with no opt-out is the batch 265 Manager's decision under the owner's driver rule (ADR 0209), which the owner may overrule. Its open findings are filed as vault #3759, with a note on #2824 (PR 2503, merged). Required checks are green. |
…ine, restartable (BACKLOG #3120) - A notice refused while the notifier closes is recorded on its undelivered row with reason "closing", as a full queue's drop is, so the full-queue-at- close window keeps the BACKLOG #2383 record it used to get. - The refusal warning names the kind through notice_kind_log_label and the username through scrub_log_argument, and says "is closing". - start() after a close clears the flag, so a restarted notifier sends again. - Lists of why a lock notice reads mailed: false now include the closing notifier, or point at the SecurityNotifier contract (notifications.py, service.py _notify_security, docs/SECURITY.md). - SECURITY.md: a cut close can also lose or unrecord a send under way, and a mailed: true row then holds the next lock notice back for 24 hours. - Tests close the relay in finally; the end-to-end test runs a started relay with its drain loop, sends the control, and reads the closing record.
Batch 265 (transports), BACKLOG #2858. This is its own PR because it changes a delivery control and adds ADR 0209. The decision it records was made by the batch 265 Manager under the owner's driver rule, the same way batch 186 decided ADR 0205 and 0206. The owner may overrule it at review. The decision is: refuse, with no batch-sender opt-out, and with HTTP and File out of scope.
Merge danger
Reversible: yes. A hand revert of ee1d77e and 86f08bf undoes it, ADR 0209 included.
Blast radius:
check_frame_bytesin messagefoundry/transports/framing.py. It is reached by a live send (frame_for_delivery), by each MLLP batch member (check_frame(rewrite=False)) and by a shadow outbound (check_frame)._holds_more_than_one_messagemoved unchanged into parsing/split.py. pipeline/ingress_guards.py imports it, and ingress behaviour is unchanged.Evidence
check_frame, shadow and batch-member tests. The single-message controls pass. Today two MSH in one Send go out as one frame.NegativeAckError(permanent=True), codemultiple-messages.MLLPDestination.send.Builder report: lane M, BACKLOG #2858
The source branch is
b265-2858, head86f08bf007c4e502f9ff5c648094542996472154, with 2 commits on origin/main 6f66234:ee1d77e8(code and tests) and86f08bf0(ADR, docs and changelog).MLLPDestination.check_frameandTcpDestination.check_frame. The live single-row path callssend(), which frames throughframe_for_deliveryand never callscheck_frame. So the check is in the sharedcheck_frame_bytes.batchon anything but MLLP. "Batch sending exists through ADR 0082" is therefore true for MLLP only. ADR 0209 says so and accepts that cost for TCP.pytest -n 8 -x) exited 0, but its summary line was not captured. A pre-commit stash ran during it./simplifyran, then two code-review rounds at xhigh (13 findings, then 14), each followed by a repair round._split_unframeable_membersdocstring and the_dead_letter_refusedlog line still say "its frame cannot carry it" and "names a byte and an offset". For amultiple-messagesmember that misleads triage.\r\x0bMSHtoward stx_etx TCP, or a space) is not refused;hl7v2MLLP or TCP inbound admits multi-message bodies, which then dead-letter at an MLLP or TCP outbound;checkand the Test Bench still preview such a payload as delivered (vault #2824). The reviewer suggested moving the check to where a Send becomes an outbox row.str.findscans;aclose();Hosted legs to read