Skip to content

fix(x12): record the seven ADR 0021 connection events its raw-TCP twin already records (BACKLOG #1665) - #1222

Open
wshallwshall wants to merge 3 commits into
mainfrom
claude/b1665-x12-events
Open

wshallwshall wants to merge 3 commits into
mainfrom
claude/b1665-x12-events

Conversation

@wshallwshall

Copy link
Copy Markdown
Collaborator

What this is

X12Source carried no _emit_event call at all. An allow-list refusal, a capacity refusal, an over-cap interchange and a peer reset recorded nothing, where TcpSource -- the listener X12 is otherwise a near-copy of -- recorded a row for each.

On first deployment that would leave an X12 feed's connects and refusals absent from the one stream an operator reads to answer "did the sender connect, and why did it drop". Zero deployments, so no live stream is missing anything today. Severity is low and conditional, per the item.

Same shape as BACKLOG #1617, with a smaller reply surface.

The drain half of the item was ALREADY MERGED. Nothing here rebuilds it.

Item #1665 has two halves. The first -- bounding the inbound reply-write drain -- shipped in PR 1122, commit 87c83574c, merged 2026-09-14, under sibling row #1766, which is why a title grep for 1665 finds nothing.

Verified in the tree before writing anything, not taken on trust: _REPLY_DRAIN_GRACE and a _drain_reply wrapping writer.drain() in asyncio.wait_for are present in both messagefoundry/transports/x12.py and messagefoundry/transports/tcp.py. This PR touches that code only to correct a docstring it left behind, and to add the peer_reset assertion the docstring said was impossible.

SEVEN kinds, not the four the item names

Measured by walking the emit sites, not counted by eye. TcpSource emits seven, at seven call sites:

Kind Where Item named it
peer_not_allowlisted allow-list gate yes
at_capacity max_connections gate yes
established slot taken no
frame_oversize the over-cap frame arm yes
framing_error the last-resort Exception arm no
peer_reset the outer OSError arm yes
closed the finally, when established and not failed no

Nothing was dropped. The two listeners share an accept path, both gates, the per-chunk decode loop, the outer OSError arm and the finally, so every TCP kind has an exact X12 analogue. After the change both modules report 7 call sites and 7 distinct kinds, with identical sets.

Emitting only the four the item names would have left X12 asymmetric on the three paths an operator most wants -- did it connect at all, did it close cleanly, and was the close an idle drop or a hangup -- and would have left the docs/PHI.md row 7 sentence half-true rather than false, which is harder to notice.

No new kind name. frame_oversize is reused for the X12FrameError arm even though this listener's frame is an interchange. A second name for one condition would red the documented-vocabulary guard, the console filter tuple and the docs/PHI.md row together, and would split an operator's query across two spellings. The console tuple and docs/PHI.md row 7 therefore need no change: the vocabulary is unchanged, only its coverage.

The positive control

Built and confirmed before any code changed, because a search that finds nothing is indistinguishable from a clean repository.

test_allowlist_refusal_is_recorded_by_both_listeners drives the same refusal on TcpSource and X12Source, through the same capture sink, in the same test. Before the change it failed with:

the X12 listener refuses an out-of-allowlist peer identically to TcpSource but records a
different event set: [] vs ['peer_not_allowlisted']

The TCP arm was non-zero, so the zero on the X12 side was attributable to the listener rather than to a sink that is never called. All eight tests in the new file failed before; all eight pass after.

test_x12_emits_the_same_event_vocabulary_as_its_tcp_twin derives both kind sets by AST and asserts equality, so neither listener can grow a kind the other lacks without a red.

Doc repairs: SIX engine sites, not four

The item's own cited doc was one of them. All six read as correct English, and every guard stayed green with them in place -- confirmed by running the doc-drift suites before touching any prose. That is the whole reason they are corrected here rather than filed.

  1. docs/PHI.md row 7 -- the connection_event stream's coverage statement.
  2. docs/CONNECTIONS.md -- the accept-rate bullet in Resource management (also drops two transports/x12.py:NNN citations that had already drifted).
  3. docs/CONNECTIONS.md Table B -- the X12 listener row.
  4. docs/CONNECTIONS.md -- the timeout-and-failure table's X12 row. Missed on the first pass and found by re-sweeping, which is the argument for sweeping with a known population rather than stopping at the sites the item names. It asserted three false things at once.
  5. docs/SECURITY.md -- the pre-auth DENY table's X12 row.
  6. docs/SECURITY.md -- the "Telemetry honesty" note.

Three test-prose sites go with them, since a stale docstring misleads the next reader exactly as a stale doc does: the test_reply_write_drain_is_bounded docstring in tests/test_x12_transport.py, test_every_socket_listener_that_emits_nothing_is_named_in_row_7, and the CONNECTOR_ROW_TOKENS comment.

The DICOM half of every one of these sentences is left alone. transports/dicom.py still contains zero _emit_event calls, so each sentence names DICOM correctly and editing that half would turn a true statement false. The asymmetry the sentences exist to disclose is real; it is just smaller.

One pinned guard moved rather than being deleted

REQUIRED_TRUTHS in tests/test_communications_inventory.py pinned the literal sentence "No ADR 0021 connection_event is emitted" as present in the resource-management section -- a prior correction, pinned so it could not silently regress. This change makes that sentence false. The pin moved to the half that survives, "contains zero \_emit_event` call sites"`, which still names DICOM as the silent listener. Deleting it would have retired a live guard on the strength of a change that only narrowed what it guards.

FOR THE LANDER: a seventh site is in the vault and I cannot reach it

docs/security/ is gitignored in this repository (git ls-files docs/security returns 0, and the directory does not exist in this checkout), and the vault is held by another session on vault/close-six-shipped-rows. Naming it is the whole of what I can do.

  • File: docs/security/THREAT-MODEL.md
  • Table: Table B, the row labelled Raw TCP / X12 listener (its label is transcribed in tests/test_threat_model_doc_drift.py, in the row-token map and in the "covered by" map).
  • What needs changing: any clause in that row, or the prose beside it, asserting that the X12 listener emits no ADR 0021 connection_event -- or that an X12 allow-list or capacity refusal is log-only or silent. Those are now false in the same way sites 1 to 6 were. Replace with the raw-TCP behaviour: the two listeners now emit the same seven kinds.
  • Leave the DICOM half alone, for the reason above.
  • tests/test_threat_model_doc_drift.py is structural and numeric, not prose-matching, and it is inert in a public checkout, so a prose edit there should not red it. Worth re-running in the vault after the edit.

I did not attempt this edit and made no change under docs/security/.

Checks

Run, from this worktree's venv:

  • ruff check . -- pass
  • ruff format --check . -- pass
  • mypy messagefoundry (strict) -- pass, 275 source files
  • pytest tests/test_x12_connection_events.py -- 8 passed (all 8 failed before the code change)
  • pytest tests/test_x12_transport.py tests/test_tcp_transport.py -- pass
  • pytest tests/test_phi_logging_inventory.py -- pass. Carries test_connection_event_vocabulary_is_derived_from_the_emit_sites, which is exactly what this change touches, plus the console filter-tuple cross-check.
  • pytest tests/test_communications_inventory.py tests/test_security_doc_drift.py tests/test_connection_event_emit.py -- pass
  • 20 further doc-drift guard suites (CONNECTIONS / SECURITY / PHI readers) -- 750 passed, 1 skipped

Skipped, and why: the full local suite. It has been measured not to finish under fleet contention, so targeted coverage plus named CI legs was the trade. Legs a reviewer must read on the hosted runner: the SQL Server and Postgres store legs, windows-service-smoke, and the web console leg -- none of which this change should touch, but none of which a Builder can see.

/simplify was run as a single review agent over all four angles (reuse, simplification, efficiency, altitude) rather than four passes, given the weekly pool.

Possible conflict with open PR 1214

PR 1214 (BACKLOG #1735) is rewriting tests/test_x12_transport.py's expect_reply round-trip tests. The seven new tests here live in their own file, tests/test_x12_connection_events.py, to stay out of its way. What does touch tests/test_x12_transport.py is a 14-line hunk in test_reply_write_drain_is_bounded -- a different function -- correcting its now-false docstring and adding the peer_reset assertion. That should merge cleanly, but it is the one place these two PRs meet.

Ledger

docs/BACKLOG.md is a policy stub here and is not edited by this PR. BACKLOG #1665 -- the X12 connection-event half is built; the drain half shipped earlier under #1766 in PR 1122 (87c83574c). One vault site remains, named above for the Lander.

#1766 and #1617 are referenced as context only. Neither is closed by this PR.

wshallwshall added 3 commits September 16, 2026 16:40
… records (BACKLOG #1665)

`X12Source` carried no `_emit_event` call at all. An allow-list refusal, a capacity refusal, an
over-cap interchange and a peer reset recorded nothing, where `TcpSource` -- the listener X12 is
otherwise a near-copy of -- recorded a row for each. On first deployment that would leave an X12
feed's connects and refusals absent from the one stream an operator reads to answer "did the sender
connect, and why did it drop". Zero deployments, so no live stream is missing anything today.

SEVEN kinds, not the four the row names. Measured by walking the emit sites: `TcpSource` emits
`peer_not_allowlisted`, `at_capacity`, `established`, `frame_oversize`, `framing_error`,
`peer_reset` and `closed`. The row names the first two, `frame_oversize` and `peer_reset`. Emitting
only those four would have left X12 asymmetric with its twin on the three paths an operator most
wants -- did it connect at all, did it close cleanly, and was the close an idle drop or a hangup --
and would have left the `docs/PHI.md` row 7 sentence about X12 half-true instead of false, which is
harder to notice. None was dropped: the two listeners share an accept path, both gates, the
per-chunk decode loop, the outer `OSError` arm and the `finally`, so every TCP kind has an exact
X12 analogue.

No new kind NAME. `frame_oversize` is reused for the `X12FrameError` arm even though this
listener's frame is an interchange: a second name for one condition would red the documented-
vocabulary guard, the console filter tuple and the `docs/PHI.md` row together, and would split an
operator's query across two spellings for no gain.

`closed` is emitted only when the connection was established AND did not fail, so a failure is
never also counted as a clean close -- the same `established`/`failed`/`close_reason` bookkeeping
`TcpSource` uses, and the reason separates an `eof` hangup from an `idle_timeout` drop.

The tests are their own file rather than a hunk in `tests/test_x12_transport.py`, which open PR
1214 is rewriting. Each kind is driven and asserted, and
`test_allowlist_refusal_is_recorded_by_both_listeners` carries the positive control: it drives the
same refusal on `TcpSource` through the same sink in the same test. Before this change that test
failed with `[] vs ['peer_not_allowlisted']` -- the zero on the X12 side was attributable because
the TCP arm was non-zero. All eight failed before; all eight pass after.

`test_x12_emits_the_same_event_vocabulary_as_its_tcp_twin` derives both kind sets by AST and
asserts they are equal, so neither listener can grow a kind the other lacks without a red.

Also corrects the `_drain_reply` docstring, which said this listener emits no connection event at
all and that its warning was therefore the whole of the engine-side evidence. Both halves are now
false: the outer `OSError` arm emits `peer_reset`. The warning is kept for what it still adds --
naming which bound fired, which the redacted `peer_reset` cannot.
…nection event (BACKLOG #1665)

The code change beside this one wires `X12Source` to the seven ADR 0021 kinds its raw-TCP twin
emits. Shipping that alone would leave CI green while the repository carried an explicit written
claim that an X12 feed's connects and refusals are captured nowhere -- a compensating control
resting on a false premise, and the more dangerous half of the defect, because prose is what an
operator reads when deciding whether to trust a counter.

Nothing caught these. All five sites read as correct English and no guard derives them from the
emit sites, which is exactly why they are corrected here rather than filed.

- `docs/PHI.md` row 7, the connection_event stream's coverage statement. Named X12 as one of two
  silent listeners and said its connects and refusals were "absent from this stream".
- `docs/CONNECTIONS.md`, the accept-rate bullet in Resource management. Said X12 and DICOM "contain
  zero `_emit_event` call sites", and that X12's at-capacity refusal left "no engine-side evidence
  of any kind". Its two `transports/x12.py:NNN` line citations went with it; they had already
  drifted, and a line number is a navigation aid, never evidence.
- `docs/CONNECTIONS.md` Table B, the X12 listener row.
- `docs/SECURITY.md`, the pre-auth DENY table's X12 row.
- `docs/SECURITY.md`, the "Telemetry honesty" note.

Three test-prose sites go with them, since a stale docstring misleads the next reader exactly as a
stale doc does: the `test_reply_write_drain_is_bounded` docstring in `tests/test_x12_transport.py`
(which said there was no `peer_reset` to assert -- so the assertion is added, mirroring the raw-TCP
twin), `test_every_socket_listener_that_emits_nothing_is_named_in_row_7`, and the
`CONNECTOR_ROW_TOKENS` comment explaining why X12 has a row of its own.

THE DICOM HALF OF EVERY ONE OF THESE SENTENCES IS LEFT ALONE. `transports/dicom.py` still contains
zero `_emit_event` calls, so each sentence names DICOM correctly and editing that half would turn a
true statement false. The asymmetry these sentences exist to disclose is real; it is just smaller.

One pinned guard had to move rather than be deleted. `REQUIRED_TRUTHS` in
`tests/test_communications_inventory.py` asserted the literal sentence "No ADR 0021
connection_event is emitted" stays present in the resource-management section -- a prior
correction, pinned so it could not silently regress. That sentence is now false, so the pin moved
to the half that survives, "contains zero `_emit_event` call sites", which still names DICOM as the
silent listener. Deleting the pin instead would have retired a live guard on the strength of a
change that only narrowed what it guards.
…lure table (BACKLOG #1665)

Found by re-sweeping after the first five were fixed, which is the point of sweeping with a known
population rather than stopping at the sites the item named. The row's failure column asserted three
things that the code change beside it makes false at once: that an allow-list refusal is "log-only
(no connection_event)", that a "capacity refusal is silent -- no event and no log", and that "this
listener emits no connection_event of any kind".

The reply-write bound's own clause is corrected rather than deleted. It said the drop leaves a
logged warning only; the warning is still there and still earns its place -- it names which bound
fired, which the redacted `peer_reset` cannot -- so the row now carries both.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant