Blocks any idempotent correction path for the writer, and is cheap to answer.
What is established
TALLY_PROTOCOL_REFERENCE.md §3.3a:
import #1 REMOTEID="…-001" -> CREATED=1 ALTERED=0
import #2 byte-identical -> CREATED=0 ALTERED=1
vouchers in Tally afterwards -> 1
So a client-supplied REMOTEID makes a repeat upsert rather than duplicate.
What is not
The measured repeat was byte-identical. That establishes idempotency on retry. It says nothing about amendment, which is the case any correction path actually needs:
- Does a same-REMOTEID import carrying different content replace the voucher's entry set, or merge into it? If a corrected voucher has fewer legs than the original, do the removed legs survive? A merge would leave an unbalanced voucher, and a correction path built on it would be worse than duplicating.
- §9.8's scope limit applies: upsert-on-repeat is verified on the licensed Journal path and UNVERIFIED for every other voucher type — including the Payment, Receipt and Contra that a bank statement is made of.
Why it matters now
Bridge derives REMOTEID = SHA256("bridge.mcp.import.v1", batch_id, txn_id) with a random per-build batch_id, deliberately, so that a caller's reused transaction label cannot become a silent upsert key. The consequence is that rebuilding a corrected business event duplicates it. The operator-side scripts/bank_statement_import.py takes the opposite approach — a stable content-addressed REMOTEID — and uses re-import as its correction path.
A design that reconciles the two is written up in the knowledge base as idempotent-correction-needs-a-compare-and-swap-not-a-stable-key: amend a Bridge-owned batch, under a read-back compare-and-swap so the upsert is never blind. That design survives reasoning and still dies if Tally merges rather than replaces, which is why this measurement should come before anything is built.
Proposed measurement
On a synthetic company, not a client book:
- Post a two-leg Journal with a client REMOTEID. Read back.
- Re-post the same REMOTEID with one amount changed. Read back — replaced or merged?
- Re-post the same REMOTEID with one leg removed. Read back — does the removed leg survive?
- Repeat 1–3 for Payment, to test §9.8's scope limit directly.
Record the captured responses, not counts: a repeat that was rejected, or whose transport failed before Tally processed it, also leaves the voucher count unchanged.
Note on the environment
As of this morning the lab gateway answers HTTP 200 but no company is loaded — List of Companies returns STATUS=0, LINEERROR: Could not find Report and the Company collection returns STATUS=1 with zero <NAME> elements. Someone has to load a company at the machine before this can be run.
Blocks any idempotent correction path for the writer, and is cheap to answer.
What is established
TALLY_PROTOCOL_REFERENCE.md§3.3a:So a client-supplied REMOTEID makes a repeat upsert rather than duplicate.
What is not
The measured repeat was byte-identical. That establishes idempotency on retry. It says nothing about amendment, which is the case any correction path actually needs:
Why it matters now
Bridge derives
REMOTEID = SHA256("bridge.mcp.import.v1", batch_id, txn_id)with a random per-buildbatch_id, deliberately, so that a caller's reused transaction label cannot become a silent upsert key. The consequence is that rebuilding a corrected business event duplicates it. The operator-sidescripts/bank_statement_import.pytakes the opposite approach — a stable content-addressed REMOTEID — and uses re-import as its correction path.A design that reconciles the two is written up in the knowledge base as
idempotent-correction-needs-a-compare-and-swap-not-a-stable-key: amend a Bridge-owned batch, under a read-back compare-and-swap so the upsert is never blind. That design survives reasoning and still dies if Tally merges rather than replaces, which is why this measurement should come before anything is built.Proposed measurement
On a synthetic company, not a client book:
Record the captured responses, not counts: a repeat that was rejected, or whose transport failed before Tally processed it, also leaves the voucher count unchanged.
Note on the environment
As of this morning the lab gateway answers HTTP 200 but no company is loaded —
List of CompaniesreturnsSTATUS=0, LINEERROR: Could not find Reportand theCompanycollection returnsSTATUS=1with zero<NAME>elements. Someone has to load a company at the machine before this can be run.