Repository navigation
FhirPeek refuses a repeated JSON key on every parse path (BACKLOG #2812) - #2356
Conversation
…2812) json keeps the last of two equal keys and a server may keep the first. The engine gated and routed on the last while sending the body verbatim. - redaction.py: RepeatedKeyError + refuse_repeated_keys, moved from transports/fhir.py (parsing/ may not import transports/). - FhirPeek.parse refuses a repeated key at any depth. Paths: outgoing write peek (bad-body), wrapped-update reply (unparseable, warning), fhir_lookup reply (FhirLookupError). The bad-body text names the check. - Tests, docs/CONNECTIONS.md, changelog fragment. Closes PR 2244 follow-up item 3. Proposed PR title: FhirPeek refuses a repeated JSON key on every parse path (BACKLOG #2812) Proposed banner: SHIPPED: FhirPeek.parse refuses a JSON object with a repeated key at any depth; single-resource writes dead-letter as bad-body, also closing the PR 2244 follow-up item 3.
…G #2812) Reverses the first commit's reply handling, which counted such a 2xx reply as delivered. That turned a reply the #2180 control used to dead-letter, e.g. {"status":"200 OK","status":"404 Not Found"}, into a delivery. - _unwrap_transaction_reply: a reply that repeats a key anywhere is a permanent refusal, code reply-repeats-a-key, in the reply-too-large text shape, and is not recorded as delivered. The refusal is raised outside the handler and names no key or value. The warning is gone. - Tests: both key orders, the measured case, a repeat elsewhere, all-success readings and a non-transaction-response, with capture on and off; a clean-reply control is still delivered. - CONNECTIONS.md update-reply bullet and changelog fragment updated. Proposed PR title: FhirPeek refuses a repeated JSON key on every parse path (BACKLOG #2812) Proposed banner: SHIPPED: FhirPeek.parse refuses a repeated JSON key at any depth; single-resource writes dead-letter as bad-body and a wrapped update's repeated-key reply fails closed as reply-repeats-a-key. Also closes PR 2244 follow-up item 3.
|
QA (lane F, BACKLOG #2812): code-review skill at level xhigh (BUILDER.md step 11), args "xhigh Review this branch's diff against origin/main; read docs/REVIEW-STANDARDS.md and apply its rules." plus "including uncommitted working-tree changes" in later rounds. Tag: none printed; the skill returned a JSON findings list. Head b9c0e26: two rounds; both flagged the fail-open reply path, which the Manager then reversed. Head b1dcfd4: one round; fixed a from-None raise that broke test_every_from_none_is_classified, the bullet scope, the "(FHIR destination)" suffix, the changelog "at least" list, and a branch-state test comment. Shipped open: findings 1-9 in the PR body. |
|
Lander, under the owner's standing driver rule (an adversarial review upheld the Lander's recommendation, so no owner question is needed): SENT BACK, narrowed. |
|
Manager decision, batch 265, taken under the owner's driver rule: the |
|
Manager, batch 265: RETRACTED. My 14:47 comment ("reply-repeats-a-key stands as built") was written without reading the Lander's 14:29 send-back, and it is withdrawn. The Lander's narrowed ruling governs: deliver a repeated-key reply only when the first-value and last-value parses agree on everything consumed (status, etag, location, lastModified, resourceType, entry shape); otherwise raise reply-repeats-a-key. The lane F Builder is re-briefed to build exactly that on b265-2812. A new head and a QA line naming it will follow. |
…CKLOG #2812) Narrows b1dcfd4 per the PR 2356 send-back. The reply is read keeping the first and keeping the last value of each key. It is judged as on main when both agree on everything the unwrap reads: resource type, entry shape, entry status text, outcome issue severities and codes, etag, location, lastModified. Otherwise it is the permanent reply-repeats-a-key refusal, code and text unchanged. So 200-then-404 and 404-then-200 both dead-letter (the second fails open on main), and an agreeing repeat is delivered. A reply the keep-first read cannot parse also fails closed. One shared walk, _entry_response, serves both the comparison and the judgment. The body-side refusal is unchanged. Proposed PR title: FhirPeek refuses a repeated JSON key on every parse path (BACKLOG #2812) Proposed banner: SHIPPED: FhirPeek.parse refuses a repeated JSON key at any depth, so single-resource writes dead-letter as bad-body; a wrapped update's repeated-key reply is judged only when its first-key and last-key readings agree, else reply-repeats-a-key. Also closes PR 2244 follow-up item 3.
…ent (BACKLOG #2812) Batch 265 Manager decisions on findings 1 and 3. - Agreement now compares the judgment each reading reaches (delivered with its capture outcome, retried, or dead-lettered with its code) plus the values a delivery takes: resource type, whether there is an entry response, and the entry's etag, location, lastModified. So 200 OK against 201 Created delivers and 503 against 500 retries. An etag difference still refuses in either capture mode, and the depth-band case still fails closed. _entry_verdict is the one judgment both the comparison and the unwrap use. - ADR 0095 Amendment B.1 records the reply-repeats-a-key refusal, with its provenance. - CONNECTIONS.md bullet, docstrings and changelog fragment state the judgment rule. Proposed PR title: FhirPeek refuses a repeated JSON key on every parse path (BACKLOG #2812) Proposed banner: SHIPPED: FhirPeek.parse refuses a repeated JSON key at any depth, so single-resource writes dead-letter as bad-body; a wrapped update's repeated-key reply is judged only when its first-key and last-key readings reach the same judgment and agree on etag, location and lastModified, else reply-repeats-a-key (ADR 0095 B.1). Also closes PR 2244 follow-up item 3.
…#2812) Review repair on 770b0f2. The 2026-10-17 decision sat inside the batch 250 B.1 text, whose banner names #2989, so it read as theirs. It now has its own dated entry at the end of B.1, with its own banner, provenance and costs. CONNECTIONS.md, the changelog fragment and the docstrings link to it rather than restating the agreement rule (SDS-3.5). No behaviour change. Proposed PR title: FhirPeek refuses a repeated JSON key on every parse path (BACKLOG #2812) Proposed banner: SHIPPED: FhirPeek.parse refuses a repeated JSON key at any depth, so single-resource writes dead-letter as bad-body; a wrapped update's repeated-key reply is judged only when its first-key and last-key readings reach the same judgment and agree on etag, location and lastModified, else reply-repeats-a-key (ADR 0095 B.1). Also closes PR 2244 follow-up item 3.
…OG #2812) Batch 265 Manager decision on the review-only finding against 696d157. Comparing the first-key and last-key readings never saw a middle occurrence, so {"status":"200 OK","status":"404 Not Found", "status":"200 OK"} was delivered. The keep-first object_pairs_hook now also refuses, in the same pass, any occurrence equal to neither its key's first nor last value in that object, at any depth. That makes the reply the permanent reply-repeats-a-key refusal. Two occurrences behave as before. A,B,A inside meta refuses too (fail-closed). ADR 0095 B.1 (2026-10-17 entry) and the CONNECTIONS.md bullet each gain one sentence. Proposed PR title: FhirPeek refuses a repeated JSON key on every parse path (BACKLOG #2812) Proposed banner: SHIPPED: FhirPeek.parse refuses a repeated JSON key at any depth, so single-resource writes dead-letter as bad-body; a wrapped update's repeated-key reply is judged only when its first-key and last-key readings reach the same judgment, agree on etag, location and lastModified, and no occurrence differs from both, else reply-repeats-a-key (ADR 0095 B.1). Also closes PR 2244 follow-up item 3.
…812) Repair after the review-only pass on cc9d9d0. Three correctness defects against the occurrence rule: - A middle occurrence was compared by its keep-first reading only, so a middle object that read 404 under keep-last passed while its mirror was refused. The keep-first object now carries its keep-last reading (_BothReadings), and equality checks both at every depth. - Every pair was compared, so a NaN (NaN != NaN) refused a key seen twice or a lone key beside a repeat. Only keys seen three or more times are checked now, and the same object counts as equal. - 1 and true compared equal; a value now must also match in type. The ADR 0095 B.1 2026-10-17 entry no longer says the rule fails closed only on disagreement for three or more occurrences, and lists that cost (200/201/200 and 503/500/503 refuse). Tests pin each case. Proposed PR title: FhirPeek refuses a repeated JSON key on every parse path (BACKLOG #2812) Proposed banner: SHIPPED: FhirPeek.parse refuses a repeated JSON key at any depth, so single-resource writes dead-letter as bad-body; a wrapped update's repeated-key reply is judged only when its first-key and last-key readings reach the same judgment, agree on etag, location and lastModified, and no occurrence differs from both, else reply-repeats-a-key (ADR 0095 B.1). Also closes PR 2244 follow-up item 3.
…CKLOG #2812)" This reverts commit 78dd64a.
|
Manager, batch 265: DO NOT MERGE head 78dd64a. Its last commit, 78dd64a, adds two defects: an exponential-time compare (a 521-byte reply nested 22 deep took 2.44 s, and depth 32 would take about 40 minutes on a to_thread worker with no timeout), and a RecursionError at about 1200 levels that escapes send(). A |
|
QA (PR 2356, final head e94b9cd). The hold is lifted. This head is a What the tree does. The Lander's agreement rule, narrowed:
Shipped open, for rows:
|
|
Lander: enqueuing at pinned head e94b9cd. Review proof covers this head; open findings are filed in vault (pass U, vault PR 2506, merged). |
Batch 265 (transports), BACKLOG #2812, on its own because it changes a security control. It adds two refusals. One of them,
reply-repeats-a-key, is a new permanent refusal after a 2xx. It rests on a Manager decision (batch 265), not on an owner ruling. Its siblings rest on an owner ruling or on ADR 0095 B.1, so the Lander may want the owner to rule on it (see Open findings).Merge danger
Reversible: yes. A hand revert of b9c0e26 and b1dcfd4 undoes it.
Blast radius: FHIR outbound connections.
FhirPeek.parse(parsing/fhir/peek.py) now refuses a JSON object that repeats a key at any depth. A single-resource create or update dead-letters asbad-body. A wrapped update's 2xx transaction-response that repeats a key now dead-letters permanently asreply-repeats-a-keyand is not recorded as delivered.fhir_lookupraisesFhirLookupError.RepeatedKeyErrorandrefuse_repeated_keysmoved from transports/fhir.py to messagefoundry/redaction.py. The Bundle screen from engine PR 2244 behaves as before.Evidence
FhirPeek.parse, all 9 body-refusal tests fail and the controls pass. With the reply refusal disabled, all 10 reply-refusal cases fail and both controls pass.id, the Observation-then-PatientresourceTypecase, a nested repeat, and the same body as bytes and behind a BOM are all refused. Abad-bodydead-letter goes out with nothing sent, across create, update, if-match, path-form, conditional-update and if-none-exist. The measured reply{"status":"200 OK","status":"404 Not Found"}, its reverse order, a repeat insidemeta, a reply where both readings are a success, and a reply that is not a transaction-response each dead-letter with capture on and off. A clean body is sent and a clean reply is delivered (controls). Refusal text names no key or value.Builder report: lane F, BACKLOG #2812
Source branch
b265-2812, headb1dcfd4d7cc6b2b85a025e662d602fda3c8663c6. That is 2 commits on origin/main 8ba57ee:b9c0e26b(the build) andb1dcfd4d(the Manager's fail-closed correction).A Manager correction happened between the two commits. The first brief had a repeated-key reply read as
unparseable. Code review flagged in both rounds that this weakened the #2180 control: the measured reply above dead-lettered on origin/main and delivered onb9c0e26b. The Manager reversed it, and the reply now fails closed.b9c0e26b, and 572 passed onb1dcfd4d. The full suite was not run. Sometest_redaction.py/test_redaction_glued_credential_label.pycases fail only under xdist load (-n 8/-n 4) and pass serially. This diff does not touch that code.RepeatedKeyError,refuse_repeated_keys) because they are imported across modules.reply-repeats-a-keyrests on a Manager decision; see the top of this body._repeats_a_keyre-parses the reply and compares againstRepeatedKeyError.__name__; a FhirPeekError subclass would remove both.fhir_lookupmessage still says "unparseable body" for a repeated key._capture_outcome,_operation_outcomeandFhirResource.parsestill keep the last of two equal keys.Hosted legs to read
The standard ci.yml test and mypy legs, especially linux. No local full run was made.