Summary
When a transaction Bundle fails at an entry, the OperationOutcome names the entry by its index in the server's processing order (DELETE → POST → PUT/PATCH → GET), not the index in the Bundle the client sent.
Current behavior
process_transaction in the REST layer sorts the entries by method_processing_order before handing them to the backend. The backend reports a failure as TransactionError::BundleError { index, .. } using its position in that sorted list, and transaction_error_response_parts renders it verbatim as "Transaction failed at entry {index}: …".
Reproduced on the batch_conformance harness with two seeded Patients matching identifier=http://example.org|12345:
entry 0: PUT Patient/x (valid)
entry 1: POST Patient, ifNoneExist=identifier=http://example.org|12345 (ambiguous → 412)
400 Bad Request
"Transaction failed at entry 0: Entry failed with status 412"
The failing entry is the client's entry 1; POST sorts before PUT, so the backend saw it at index 0. The overlap message added by #919 (… which entry {other} … also addresses) names both entries by the same sorted index.
Expected behavior
The index in the message refers to the entry's position in the request Bundle, since that is the only numbering the client can act on.
Evidence
crates/rest/src/handlers/batch.rs:628 — indexed_entries.sort_by_key(|(_, entry, _)| method_processing_order(&entry.method)); each element still carries its original index as the first tuple field
crates/rest/src/handlers/batch.rs:2421 — transaction_error_response_parts formats BundleError { index } unmapped
crates/persistence/src/core/bundle_conditionals.rs:120 — check_identity_overlap names two entries by executor index
Suggested approach
The mapping already exists in process_transaction: indexed_entries[index].0 is the original index. Map BundleError.index through it before rendering, in the Err arm that calls transaction_error_response_parts, and do the same for the audit fan-out's entry_index if it reads the sorted position. For the overlap message, either have the REST layer rewrite both indices (they are embedded in text, so a structured variant such as BundleError { index, related: Option<usize> } or a dedicated TransactionError::OverlappingIdentity { index, other } would be cleaner) or pass the original indices down on BundleEntry so the executors report them directly. The structured variant also gives the 400 a better issue code than processing.
Test: batch_conformance — the reproduction above, asserting the message names entry 1; and an overlap case whose conditional entry precedes the instance entry in the request but follows it after sorting.
Found while implementing #859 (PR #919).
Summary
When a transaction Bundle fails at an entry, the
OperationOutcomenames the entry by its index in the server's processing order (DELETE → POST → PUT/PATCH → GET), not the index in the Bundle the client sent.Current behavior
process_transactionin the REST layer sorts the entries bymethod_processing_orderbefore handing them to the backend. The backend reports a failure asTransactionError::BundleError { index, .. }using its position in that sorted list, andtransaction_error_response_partsrenders it verbatim as "Transaction failed at entry {index}: …".Reproduced on the
batch_conformanceharness with two seeded Patients matchingidentifier=http://example.org|12345:The failing entry is the client's entry 1; POST sorts before PUT, so the backend saw it at index 0. The overlap message added by #919 (
… which entry {other} … also addresses) names both entries by the same sorted index.Expected behavior
The index in the message refers to the entry's position in the request Bundle, since that is the only numbering the client can act on.
Evidence
crates/rest/src/handlers/batch.rs:628—indexed_entries.sort_by_key(|(_, entry, _)| method_processing_order(&entry.method)); each element still carries its original index as the first tuple fieldcrates/rest/src/handlers/batch.rs:2421—transaction_error_response_partsformatsBundleError { index }unmappedcrates/persistence/src/core/bundle_conditionals.rs:120—check_identity_overlapnames two entries by executor indexSuggested approach
The mapping already exists in
process_transaction:indexed_entries[index].0is the original index. MapBundleError.indexthrough it before rendering, in theErrarm that callstransaction_error_response_parts, and do the same for the audit fan-out'sentry_indexif it reads the sorted position. For the overlap message, either have the REST layer rewrite both indices (they are embedded in text, so a structured variant such asBundleError { index, related: Option<usize> }or a dedicatedTransactionError::OverlappingIdentity { index, other }would be cleaner) or pass the original indices down onBundleEntryso the executors report them directly. The structured variant also gives the400a better issue code thanprocessing.Test:
batch_conformance— the reproduction above, asserting the message names entry 1; and an overlap case whose conditional entry precedes the instance entry in the request but follows it after sorting.Found while implementing #859 (PR #919).