<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dmytro Nasyrov</title>
    <description>The latest articles on DEV Community by Dmytro Nasyrov (@dmytronasyrov).</description>
    <link>https://dev.to/dmytronasyrov</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F140475%2F9927d920-3e1f-416a-bede-35b832d27c5c.png</url>
      <title>DEV Community: Dmytro Nasyrov</title>
      <link>https://dev.to/dmytronasyrov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dmytronasyrov"/>
    <language>en</language>
    <item>
      <title>Sportsbook iFrame Integration: Test Reconnects Against the Original Wallet Operation</title>
      <dc:creator>Dmytro Nasyrov</dc:creator>
      <pubDate>Thu, 29 Oct 2026 08:40:08 +0000</pubDate>
      <link>https://dev.to/gambit_stream/sportsbook-iframe-integration-test-reconnects-against-the-original-wallet-operation-2i82</link>
      <guid>https://dev.to/gambit_stream/sportsbook-iframe-integration-test-reconnects-against-the-original-wallet-operation-2i82</guid>
      <description>&lt;p&gt;An iframe can reconnect successfully while its original wallet operation remains unresolved. Test recovery by renewing the session, looking up the original operation and checking the wallet effect. If the first debit committed before its reply disappeared, a fresh session must recover that decision without creating another debit. A reconnect that merely makes the screen responsive can hide the wrong economic result.&lt;/p&gt;

&lt;p&gt;This article was prepared with AI-generated research, prose and code. The local test results are recorded below.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cover: Original technical artwork showing the synthetic reconnect contract.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The example is a small, executable Python model. A fake wallet retains a decision, the reply is deliberately lost and the first session expires. Seven tests distinguish accepted and rejected outcomes from unavailable evidence. They exercise application behavior in one process. They do not run a browser, contact a sportsbook or place a wager.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give the session and the operation different lifetimes
&lt;/h2&gt;

&lt;p&gt;The session answers whether this caller can currently access the player's operation. The operation identifies the wallet instruction whose result the caller needs. Renewing access changes the session token. It does not change which instruction was previously submitted. Preserve that separation in the reconnect path, even if both identifiers happen to be strings in your adapter.&lt;/p&gt;

&lt;p&gt;For this fixture, player &lt;code&gt;alice&lt;/code&gt; places &lt;code&gt;ticket-7&lt;/code&gt; using &lt;code&gt;operation-1&lt;/code&gt;. The stake is 300 USD minor units against a starting balance of 1000. An accepted operation leaves 700. These are invented values chosen to make duplicate effects visible. They are not a price, customer balance or measurement from a deployed platform.&lt;/p&gt;

&lt;p&gt;The immutable intent contains player, operation identifier, ticket, stake and currency. This model treats the ticket as a complete reference to the requested bet. It does not model prices, market suspension or changing bet terms. A production contract would need to define those additional inputs and their versioning before deciding what counts as the same intent.&lt;/p&gt;

&lt;p&gt;A temporary session is deliberately absent from that intent. &lt;code&gt;session-1&lt;/code&gt; permits the initial call. Issuing &lt;code&gt;session-2&lt;/code&gt; for the same player makes the first token expire in the fake registry. The original intent still identifies &lt;code&gt;operation-1&lt;/code&gt;. Keeping a session token inside the operation key would make access renewal look like authorization for a different wallet change.&lt;/p&gt;

&lt;p&gt;Keep the test's meaning narrow: &lt;code&gt;ACCEPTED&lt;/code&gt; means the fake wallet accepted this debit. It does not prove that a sportsbook persisted bet acceptance, that settlement completed or that the player's displayed balance is current. Those are separate records and require their own acceptance conditions. A wallet response cannot silently stand in for the rest of the bet lifecycle.&lt;/p&gt;

&lt;p&gt;There is one pending intent per frame object in this example. The test calls &lt;code&gt;place()&lt;/code&gt; once before recovery and uses fixed positive stakes with one currency. The small runtime does not validate arbitrary user input or prevent another caller from invoking &lt;code&gt;place()&lt;/code&gt; again. A real controller needs a defined admission rule for new instructions while an earlier one remains unresolved. The fixture isolates reconnect so that this separate controller problem does not disappear into the demonstration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lose the reply after retaining the wallet decision
&lt;/h2&gt;

&lt;p&gt;Fault placement determines what the test can establish. A failure before submission demonstrates that nothing reached the fake wallet. Here, &lt;code&gt;submit()&lt;/code&gt; first records its decision and any debit, then raises &lt;code&gt;LostReply&lt;/code&gt;. The frame catches that exception and returns &lt;code&gt;UNKNOWN&lt;/code&gt;. The caller has lost knowledge of the result while the wallet still holds the result.&lt;/p&gt;

&lt;p&gt;The accepted path starts with balance 1000, applies the 300 debit and records one effect. The frame sees no accepted response. Next, the registry issues a replacement session. The original token becomes invalid, so trying to recover through the expired token raises &lt;code&gt;SessionExpired&lt;/code&gt;. Renewed access is necessary, but the operation being recovered is unchanged.&lt;/p&gt;

&lt;p&gt;In the deliberate defect, &lt;code&gt;BuggyFrame.reconnect()&lt;/code&gt; derives a replacement operation from the new session and submits the original ticket again. The wallet sees a different operation identifier with sufficient funds, so it accepts another debit. Its duplicate protection has not malfunctioned: the caller described a new operation to it.&lt;/p&gt;

&lt;p&gt;An executed demonstration makes the difference visible. The buggy reconnect returns &lt;code&gt;session-2:replacement&lt;/code&gt;, leaves balance 400 and records two effects. The corrected reconnect returns &lt;code&gt;operation-1&lt;/code&gt;, leaves balance 700 and records one effect. These are deterministic outputs of the local model. No network latency, throughput improvement or production incident frequency was measured.&lt;/p&gt;

&lt;p&gt;The first failing assertion checks the returned operation identifier. A plausible accepted response is insufficient: it must belong to the original intent.&lt;/p&gt;

&lt;p&gt;Balance and effect count are also part of the test contract. The separate demonstration reaches those observations on the broken path even though the earlier assertion stops that particular failing test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recover a result through a read
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcg4byg3vduf9a20y2ogi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcg4byg3vduf9a20y2ogi.png" alt="The wallet decides operation-1, the reply is lost and the session expires; a new session reads the original accepted, rejected or unknown result without a replacement debit. The accepted model leaves 400 with two effects when broken and 700 with one effect when corrected." width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Original diagram of the executed synthetic model. Amounts are invented minor-unit values, not production data.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Corrected &lt;code&gt;Frame.reconnect()&lt;/code&gt; updates the session used for access and calls &lt;code&gt;wallet.lookup(new_session, self.intent)&lt;/code&gt;. It does not call &lt;code&gt;submit()&lt;/code&gt;. Lookup checks the current session against the intent's player, finds the retained operation and compares the complete intent before returning its stored outcome. Repeating that read leaves the balance and effect journal unchanged.&lt;/p&gt;

&lt;p&gt;For an accepted result, the recovery response must identify the same operation and retain the same outcome. This fixture returns &lt;code&gt;accepted:operation-1&lt;/code&gt;. It does not invent a new receipt simply because the connection changed. A real adapter should preserve the provider's meaningful result identifiers and distinguish a stored decision from a new attempt to obtain that decision.&lt;/p&gt;

&lt;p&gt;The distinction also appears in other API contracts. Stripe describes its API v1 behavior in its &lt;a href="https://docs.stripe.com/api/idempotent_requests" rel="noopener noreferrer"&gt;idempotent request documentation&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For API v1, Stripe saves the status code and body of the first request made with a given idempotency key after endpoint execution begins.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is a Stripe-specific replay contract. It illustrates why a retained decision matters, but it does not establish an operator wallet's lookup endpoint, retention period or response semantics. This article's wallet contract is declared in the runnable model. Its recovery operation is a read, while its submission path recognizes repeated operation identifiers and checks their intent.&lt;/p&gt;

&lt;p&gt;If the real wallet offers only resubmission with the same key, its documented duplicate-handling contract needs separate verification. Do not mechanically rename that call lookup. Establish which inputs must remain identical, what a concurrent repeat does and whether the original result remains available after session renewal. The correct adapter follows the actual interface rather than assuming another provider's behavior.&lt;/p&gt;

&lt;p&gt;Agree on the lookup response before wiring it into the screen. An accepted response should identify the original operation and the decision being recovered. A rejection should carry enough stable meaning for the caller to report that decision without rerunning eligibility. An unresolved response should distinguish unavailable evidence from a known terminal result. The model compresses both unresolved situations to &lt;code&gt;UNKNOWN&lt;/code&gt;, while its separate tests retain the reason for each fault.&lt;/p&gt;

&lt;p&gt;In a deployed integration, preserve that reason in an internal recovery record even if the player sees one pending message. An operator investigating a failed lookup needs a different next step from an operator investigating a missing retained record. Exposing this distinction internally does not require displaying provider internals or credentials in the user interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  A retained rejection must survive a changed balance
&lt;/h2&gt;

&lt;p&gt;The rejection test begins with only 100 minor units available for a 300 stake. The fake wallet records &lt;code&gt;REJECTED&lt;/code&gt; and applies no debit, then loses the reply. Before reconnect, the test replenishes the balance to 1000. This change is intentional: it makes reevaluation capable of producing a different decision.&lt;/p&gt;

&lt;p&gt;Recovery must still return the rejection for &lt;code&gt;operation-1&lt;/code&gt;. The corrected frame leaves the replenished balance at 1000 with zero effects. It reads what happened to that operation at its original decision point. It does not ask whether the same ticket might be affordable under today's conditions. A rejection is retained evidence rather than an invitation to rerun a business decision.&lt;/p&gt;

&lt;p&gt;The buggy demonstration does the latter. By submitting &lt;code&gt;session-2:replacement&lt;/code&gt;, it obtains an accepted outcome and leaves balance 700 after one effect. A test checking only that no duplicate debit occurred would miss this error: the original request was rejected, so there was no first debit to duplicate. The mistake is changing the player's intended operation during recovery.&lt;/p&gt;

&lt;p&gt;This is why the acceptance condition needs an outcome identity as well as an effect count. One debit can be wrong when recovery was supposed to report a rejection. In a real product, a later attempt may be valid after the user receives the rejection and explicitly chooses to try again. That is a new instruction with its own authorization and terms.&lt;/p&gt;

&lt;p&gt;The model does not decide how a user interface should request that instruction. It supplies a useful boundary for the UI: recovering an unresolved operation and initiating a newly authorized one need distinct paths. A reconnect handler must not take the second path merely because it has regained access to the wallet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep unknown results unresolved
&lt;/h2&gt;

&lt;p&gt;Two tests prevent failed reads from becoming business commands. One disables lookup while preserving the retained decision. The other clears the in-memory journal after the original debit. Both start with a lost accepted reply. Both require &lt;code&gt;UNKNOWN&lt;/code&gt;, balance 700 and one effect after reconnect. Neither permits a replacement operation.&lt;/p&gt;

&lt;p&gt;These faults represent different evidence problems. An unavailable lookup means the reader cannot obtain a result now. A missing journal entry means this lookup found no retained decision in the model. It does not establish that the debit never happened. The effect list and balance deliberately survive the injected journal loss, exposing that dangerous inference.&lt;/p&gt;

&lt;p&gt;Clearing a dictionary is a controlled fault injection, not a storage recovery experiment. There is no disk, replica or crash in this fixture. Its claim is limited to how the frame behaves when the outcome source cannot answer. Durable storage design and the atomic relationship between real ledger effects and retained decisions remain separate work.&lt;/p&gt;

&lt;p&gt;Production recovery needs a defined route for this uncertainty. Record the original operation, the evidence available and the owner responsible for resolving it. A later read may establish an accepted or rejected decision. If it cannot, an authorized reconciliation procedure may need another authoritative record. The frame should preserve the unresolved intent rather than manufacture certainty with a fresh identifier.&lt;/p&gt;

&lt;p&gt;Do not use the absence of an exception as your oracle. An implementation can swallow lookup failure and return a generic success screen while issuing another debit underneath. Inspect the observable wallet effect and the identity attached to the response. In the missing-journal test, the journal must also remain empty: reconnect must not silently recreate it through submission.&lt;/p&gt;

&lt;p&gt;A retention boundary deserves the same care. This example keeps decisions for the life of its in-memory wallet and makes no promise about an elapsed lookup window. Before applying the pattern elsewhere, find out whether operation results can expire while ledger entries remain available. A wallet may need another reference for investigation after that point. Generating a replacement operation cannot restore a pruned response or establish whether an earlier debit exists.&lt;/p&gt;

&lt;p&gt;Likewise, a pending decision is different from an absent decision. The fixture records only immediate acceptance or rejection, so it has no asynchronous pending branch. If the real wallet returns pending, define what may transition that operation and what the reconnect read can establish at each stage. Do not add a timer that converts pending into permission to create a second instruction without a documented business rule.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Reconnect evidence&lt;/th&gt;
&lt;th&gt;Required result in this model&lt;/th&gt;
&lt;th&gt;Additional debit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Retained accepted operation&lt;/td&gt;
&lt;td&gt;Original accepted outcome&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retained rejected operation&lt;/td&gt;
&lt;td&gt;Original rejected outcome&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lookup unavailable&lt;/td&gt;
&lt;td&gt;UNKNOWN&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Original journal entry missing&lt;/td&gt;
&lt;td&gt;UNKNOWN&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Session belongs to another player&lt;/td&gt;
&lt;td&gt;WrongPlayer&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Original session expired&lt;/td&gt;
&lt;td&gt;SessionExpired&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Run the same contract against both implementations
&lt;/h2&gt;

&lt;p&gt;The fixture uses Python 3.14.7 and the standard library. Save the two complete files below as &lt;code&gt;recovery.py&lt;/code&gt; and &lt;code&gt;test_recovery.py&lt;/code&gt; in one directory. The runtime contains the deliberately broken frame and the corrected subclass. The test suite selects the implementation through &lt;code&gt;FRAME_VARIANT&lt;/code&gt;, so changing implementations does not change the expected behavior.&lt;/p&gt;

&lt;p&gt;Run these commands from that directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;FRAME_VARIANT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;BuggyFrame python3 &lt;span class="nt"&gt;-m&lt;/span&gt; unittest &lt;span class="nt"&gt;-v&lt;/span&gt; test_recovery
&lt;span class="nv"&gt;FRAME_VARIANT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Frame python3 &lt;span class="nt"&gt;-m&lt;/span&gt; unittest &lt;span class="nt"&gt;-v&lt;/span&gt; test_recovery
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The recorded RED run executed seven tests and failed four. The accepted-result, retained-rejection, unavailable-lookup and missing-journal cases failed. Expired-session rejection, wrong-player rejection and conflicting-intent rejection already passed on the broken implementation. Those passing checks matter: the defect survives basic access checks and an operation-key conflict check.&lt;/p&gt;

&lt;p&gt;After correction, the same seven tests passed without changing their assertions. The fix adds a reconnect method that performs the original-outcome lookup. This establishes the specified sequential model. Seven tests cannot establish general wallet correctness. The exact test file remained unchanged between the reviewed RED and GREEN runs.&lt;/p&gt;

&lt;p&gt;Python's &lt;a href="https://docs.python.org/3/library/unittest.html" rel="noopener noreferrer"&gt;unittest documentation&lt;/a&gt; explains the test runner and exception assertions. The &lt;a href="https://docs.python.org/3/library/dataclasses.html" rel="noopener noreferrer"&gt;dataclasses documentation&lt;/a&gt; covers the frozen records and &lt;code&gt;replace()&lt;/code&gt; used here. Frozen records make accidental mutation harder in this example. They do not provide persistence, authentication or a transaction boundary around the wallet's dictionaries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Complete runtime: fake wallet and both reconnect implementations
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Synthetic in-memory wallet; no network, browser, persistence or real auth.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;replace&lt;/span&gt;

&lt;span class="n"&gt;UNKNOWN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;UNKNOWN&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SessionExpired&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WrongPlayer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;IntentConflict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LostReply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;


&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;operation_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;ticket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;stake_minor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;


&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Outcome&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;operation_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SessionRegistry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;owners&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sequence&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sequence&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;session-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sequence&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;owners&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;owner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;owners&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SessionExpired&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;WrongPlayer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Wallet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sessions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;balances&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sessions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sessions&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balances&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;balances&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;journal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;effects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lookup_available&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lose_reply&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sessions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;retained&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;journal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;operation_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;retained&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;canonical&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;outcome&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;retained&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;canonical&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;IntentConflict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;operation_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;accepted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balances&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stake_minor&lt;/span&gt;
            &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ACCEPTED&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;accepted&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;REJECTED&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
            &lt;span class="n"&gt;outcome&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Outcome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;operation_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                              &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;operation_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;accepted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balances&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stake_minor&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;effects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;journal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;operation_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;lose_reply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;LostReply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;operation_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;outcome&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sessions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lookup_available&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;UNKNOWN&lt;/span&gt;
        &lt;span class="n"&gt;retained&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;journal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;operation_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;retained&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;UNKNOWN&lt;/span&gt;
        &lt;span class="n"&gt;canonical&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;outcome&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;retained&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;canonical&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;IntentConflict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;operation_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;outcome&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BuggyFrame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;place&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lose_reply&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;intent&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lose_reply&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;lose_reply&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;LostReply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;UNKNOWN&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;reconnect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_session&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sessions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_session&lt;/span&gt;
        &lt;span class="n"&gt;replacement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;operation_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;new_session&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:replacement&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;replacement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuggyFrame&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;reconnect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_session&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_session&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Complete unchanged test suite
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Seven observable requirements; identical suite for buggy and fixed frames.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;unittest&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;replace&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;recovery&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;UNKNOWN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BuggyFrame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IntentConflict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SessionExpired&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;SessionRegistry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Wallet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;WrongPlayer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RecoveryContract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unittest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;setUp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;recovery&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recovery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;FRAME_VARIANT&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;BuggyFrame&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sessions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SessionRegistry&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;old&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sessions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;alice&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wallet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Wallet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sessions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;alice&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bob&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;alice&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;operation-1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ticket-7&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;USD&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;frame_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lose_reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;place&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lose_reply&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;UNKNOWN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sessions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;alice&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_accepted_lost_reply_recovers_original_outcome_once&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;new&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lose_reply&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reconnect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;operation_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;operation_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ACCEPTED&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;accepted:operation-1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balances&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;alice&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;effects&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reconnect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balances&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;alice&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;effects&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_retained_rejection_survives_replenishment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balances&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;alice&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
        &lt;span class="n"&gt;new&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lose_reply&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balances&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;alice&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reconnect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;operation_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;operation-1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;REJECTED&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rejected:operation-1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balances&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;alice&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;effects&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_unavailable_lookup_remains_unknown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;new&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lose_reply&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lookup_available&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reconnect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;UNKNOWN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balances&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;alice&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;effects&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_missing_journal_does_not_create_operation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;new&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lose_reply&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;journal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# deterministic fault injection, not a real store
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reconnect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;UNKNOWN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balances&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;alice&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;effects&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;journal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_wrong_player_recovery_is_denied&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lose_reply&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;bob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sessions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bob&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertRaises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WrongPlayer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reconnect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balances&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;alice&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bob&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;effects&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_expired_session_is_denied&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lose_reply&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertRaises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SessionExpired&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reconnect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balances&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;alice&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;effects&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_operation_key_rejects_different_intent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;original&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertRaises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IntentConflict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stake_minor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balances&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;alice&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;effects&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;unittest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;verbosity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read the conflict test independently of reconnect. It submits the original operation, then attempts the same identifier with a different stake. &lt;code&gt;IntentConflict&lt;/code&gt; must preserve the original result and leave one debit. Without this binding, retaining an identifier would not guarantee that the response still described the request the caller intended to recover.&lt;/p&gt;

&lt;p&gt;Direct calls make the failure sequence deterministic. No sleeps or probabilistic disconnections are involved. This keeps the suite cheap to run while changing adapter logic and makes the fault point visible in code. HTTP serialization, proxy behavior and provider response parsing still need checks at their actual boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connect the model to an actual iframe boundary
&lt;/h2&gt;

&lt;p&gt;The Python frame is a stand-in for application recovery logic. It does not create an HTML iframe, exercise cross-origin messaging or inspect browser storage. Move its observable contracts into the real adapter's tests, then add browser coverage for the transport and lifecycle behavior that this local model intentionally excludes.&lt;/p&gt;

&lt;p&gt;For a host-to-frame bridge using &lt;code&gt;postMessage&lt;/code&gt;, follow &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage" rel="noopener noreferrer"&gt;MDN's postMessage guidance&lt;/a&gt;. Use a specific target origin and verify the received message's origin and expected source. Validate its shape before acting on it. A message arriving from an expected origin does not, by itself, establish authorization for a wallet command.&lt;/p&gt;

&lt;p&gt;The adapter also needs an explicit restoration source for the pending intent. A component remount must not erase the only association between the ticket and original operation. Choose storage and access rules appropriate to the actual application. This model retains the intent in the frame object, so it does not test a reload that destroys that object or a restart of the host process.&lt;/p&gt;

&lt;p&gt;Map recovery results explicitly rather than testing whether a returned value is truthy. Here, &lt;code&gt;UNKNOWN&lt;/code&gt; is a string and accepted or rejected outcomes are records. A generic success branch could accidentally treat all of them alike. The test assertions compare the declared result and its operation identity. When adapting the model to typed responses, keep unresolved evidence as an explicit result that cannot enter the accepted path by default. Any additional error translation should preserve that distinction through the UI adapter.&lt;/p&gt;

&lt;p&gt;Exercise stale messages in the browser suite. A reply from an earlier session can arrive after the host has installed a newer bridge. Decide how the adapter associates that reply with the pending operation and which messages are allowed to update the screen. Avoid treating the newest session as the only meaningful identity in every message.&lt;/p&gt;

&lt;p&gt;Keep credentials out of the operation record shared with support. The example's session strings are fake registry keys, not production tokens to copy into logs. An evidence export can identify the session transition without distributing live access material. Redaction should preserve the operation join needed for investigation while removing secrets that do not help establish its result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the acceptance record useful to another team
&lt;/h2&gt;

&lt;p&gt;A handoff should let the receiving team reproduce the decision, rather than merely watch the iframe load again. Record the original intent, the lost-reply fault point and the session transition. Show the returned operation identifier beside the balance and effect count. Keep the expected result separate from the output actually observed during the run.&lt;/p&gt;

&lt;p&gt;For the accepted example, the expected record is original &lt;code&gt;operation-1&lt;/code&gt;, accepted response, balance 700 and one effect. For the rejection example after replenishment, it is the same original identifier, rejected response, balance 1000 and zero effects. Those paired records catch a defect that a single assertion about duplicate debits cannot fully describe.&lt;/p&gt;

&lt;p&gt;Include a lookup-unavailable case in the release demonstration. It is often less visually satisfying than a recovered success screen, but it establishes whether the product can remain honest when evidence is unavailable. The acceptance result should show the unresolved state and the retained original intent, alongside proof that recovery applied no additional wallet effect.&lt;/p&gt;

&lt;p&gt;When moving this check to a shared test environment, do not rely on a final account balance alone. Another operation can change that balance while recovery is running. Use the wallet's documented operation or ledger references to identify the effect attributable to this intent. The isolated fixture deliberately removes unrelated activity, which is why its simple arithmetic is an adequate oracle inside this model.&lt;/p&gt;

&lt;p&gt;Keep a failed run as evidence too. Record the implementation revision and the fault that produced the output, then preserve the same assertions while correcting the recovery path. A passing screenshot from a later run cannot explain why an earlier version created a replacement operation. The useful comparison is the original contract evaluated against both implementations, with their observable outcomes retained.&lt;/p&gt;

&lt;p&gt;The wrong-player and expired-session tests establish only the behavior of this fake registry. They do not validate token signatures, revocation propagation, browser isolation or tenant administration. A production security review must examine those mechanisms directly. Passing a synthetic ownership check cannot justify a claim that an embedded betting session is secure.&lt;/p&gt;

&lt;p&gt;Concurrency is another explicit limit. The fake wallet executes a decision and journal update sequentially in the same call. It has no lock or database transaction. Two production workers that both observe a missing operation need an atomic admission mechanism at the authoritative boundary. This fixture does not demonstrate that mechanism, and adding threads around its dictionaries would not turn it into a database correctness test. Write the concurrency check against the actual primitive that owns the operation record and debit. Require evidence that both contenders resolve to the declared result without an additional effect, then keep the reconnect test focused on how a renewed caller recovers that result. The two checks answer different questions and should retain separate evidence.&lt;/p&gt;

&lt;p&gt;Finally, document the interfaces that were actually exercised. This run covered one sequential process with integer amounts, a single currency and an in-memory outcome journal. It did not cover concurrent wallet requests, real authorization, crash consistency, refunds or sportsbook settlement. Keep those exclusions beside the acceptance record so a passing reconnect rehearsal is not mistaken for a complete integration release.&lt;/p&gt;

&lt;p&gt;The next implementation step is concrete: replace the fake wallet lookup with the documented adapter boundary, inject the same lost-reply condition and repeat the accepted, rejected and unknown-result checks. Keep the original operation visible through the session change. For the accepted fixture here, the final evidence remains &lt;code&gt;operation-1&lt;/code&gt;, balance 700, one effect.&lt;/p&gt;

&lt;h2&gt;
  
  
  More insights to read
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://hackmd.io/@dmytro-nasyrov/payment-recovery-fieldbook-identity-replay-reconciliation" rel="noopener noreferrer"&gt;The Payment Recovery Fieldbook: Identity, Replay and Reconciliation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hackmd.io/@dmytro-nasyrov/retry-identity-fieldbook-transport-attempts-business-operations" rel="noopener noreferrer"&gt;Retry Identity Fieldbook: Transport Attempts and Business Operations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://sites.google.com/pharosproduction.com/gambit-stream-engineering/sportsbook-wallet-timeout-reconstruction" rel="noopener noreferrer"&gt;How to Reconstruct a Sportsbook Bet After a Wallet Timeout&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dmytro-nasyrov.beehiiv.com/p/psp-integration-second-delivery-milestone" rel="noopener noreferrer"&gt;When a PSP Integration Needs a Second Delivery Milestone&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.bulbapp.io/p/9982f38b-b272-44c6-bbe2-61d93217a341/audit-four-replay-boundaries-in-smart-account-signatures" rel="noopener noreferrer"&gt;Audit Four Replay Boundaries in Smart-Account Signatures&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  About the author
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flabtwrvya3qcspprlkbc.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flabtwrvya3qcspprlkbc.jpg" alt="Portrait of Dmytro Nasyrov wearing a dark suit and light blue shirt against a dark background." width="112" height="112"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Dmytro Nasyrov. Photo supplied by the author.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written by &lt;a href="https://pharosproduction.com/dmytro-nasyrov/" rel="noopener noreferrer"&gt;Dmytro Nasyrov&lt;/a&gt; PhD, software architect with 24 years of production experience. Dmytro is the founder of Gambit Stream. He works on production software architecture for FinTech, AI, Web3 and blockchain systems.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>testing</category>
      <category>architecture</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Separate Request IDs From Operation IDs in an Integration Retry Test</title>
      <dc:creator>Dmytro Nasyrov</dc:creator>
      <pubDate>Wed, 28 Oct 2026 09:00:16 +0000</pubDate>
      <link>https://dev.to/pharos_production/separate-request-ids-from-operation-ids-in-an-integration-retry-test-1567</link>
      <guid>https://dev.to/pharos_production/separate-request-ids-from-operation-ids-in-an-integration-retry-test-1567</guid>
      <description>&lt;p&gt;A lost HTTP response can make an integration send another request for work the server has already committed. &lt;/p&gt;

&lt;p&gt;Give that transmission a fresh request ID, but retain the operation ID that identifies the intended business change. A retry test should count the committed effects as well as the responses it receives.&lt;/p&gt;

&lt;p&gt;The fixture in this article closes a connection after a SQLite commit and before the HTTP response. Its deliberately broken client creates two reservations. Changing the operation argument passed to the transport function makes the same six tests pass, with one reservation and two separately identifiable attempts.&lt;/p&gt;

&lt;p&gt;These are executed results from a local, synthetic inventory example, using Python 3.14.7 and SQLite 3.53.4. &lt;/p&gt;

&lt;p&gt;They are not measurements from a customer system. The complete runtime and test module appear below so the claim can be checked without a remote account, API credential or third-party Python package.&lt;/p&gt;

&lt;p&gt;This article was prepared with AI-generated research, prose and code. The local test results are recorded below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Specify what each identifier is allowed to mean
&lt;/h2&gt;

&lt;p&gt;For this fixture, a business operation means one request to reserve a stated quantity of a test SKU for one tenant. The caller supplies its operation ID before the retry loop starts. Each HTTP transmission gets a separate request ID. The receiver returns a reservation ID only after creating or retrieving the business result.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Identifier&lt;/th&gt;
&lt;th&gt;Scope in this fixture&lt;/th&gt;
&lt;th&gt;Changes on a transport retry?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Operation ID&lt;/td&gt;
&lt;td&gt;One reservation intent within a tenant&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request ID&lt;/td&gt;
&lt;td&gt;One HTTP attempt&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reservation ID&lt;/td&gt;
&lt;td&gt;The stored business result&lt;/td&gt;
&lt;td&gt;No, when the operation replays&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tenant&lt;/td&gt;
&lt;td&gt;The namespace containing the operation&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Stripe states in its &lt;a href="https://docs.stripe.com/api/request_ids" rel="noopener noreferrer"&gt;Request IDs documentation&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Each API request has an associated request identifier.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is a useful observability role. It does not make a request identifier the correct duplicate-admission key for every receiver. The names on an interface can also differ: AWS calls its caller-supplied idempotency token a client request identifier. Read the promised semantics before deciding whether two fields can share a value.&lt;/p&gt;

&lt;p&gt;The client accepts an operation ID and a request-ID generator as different inputs. Tests inject a two-value generator so the expected transmissions are reproducible. Normal calls can use the default UUID generator for each attempt. This arrangement makes identifier ownership visible at the function boundary, rather than hiding it in an exception handler or a transport wrapper.&lt;/p&gt;

&lt;p&gt;Do not create the operation ID by hashing the payload alone. Two independent orders can legitimately reserve the same SKU and quantity. They need different operation identities. Conversely, changing a request ID because a connection failed must not change which order the integration is fulfilling. These are separate decisions, and the tests exercise both.&lt;/p&gt;

&lt;p&gt;The header names used here are fixture choices. &lt;code&gt;X-Request-ID&lt;/code&gt; carries the attempt identifier, while &lt;code&gt;Idempotency-Key&lt;/code&gt; carries the operation identifier. &lt;code&gt;X-Test-Tenant&lt;/code&gt; supplies a test namespace. None of these headers authenticates a caller. A production receiver must obtain its authorized scope through its actual authentication and authorization boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Put the operation record beside the protected effect
&lt;/h2&gt;

&lt;p&gt;The receiver stores two kinds of rows in one SQLite file. A reservation row is the synthetic business effect. An operation row binds a tenant and operation ID to the canonical request and stored business response. A composite primary key prevents two records from claiming the same identity within that namespace.&lt;/p&gt;

&lt;p&gt;Each handler opens its own database connection and explicitly starts &lt;code&gt;BEGIN IMMEDIATE&lt;/code&gt;. It looks up the operation while holding the write transaction. An unseen operation creates a reservation and stores its response. A seen operation with matching arguments returns the stored response. Conflicting arguments produce HTTP 409 without creating another reservation.&lt;/p&gt;

&lt;p&gt;The important relationship is the transaction containing both writes. If the operation marker could commit while the reservation failed, a later retry might incorrectly report completed work. If the reservation could commit without its marker, a later retry could create another result. A lookup performed before the transaction would not reserve the right to perform the mutation. &lt;a href="https://aws.amazon.com/builders-library/making-retries-safe-with-idempotent-APIs/" rel="noopener noreferrer"&gt;AWS's idempotent API guidance&lt;/a&gt; describes the need to combine recording the token with the related mutations atomically. This fixture applies that principle to one local database. The principle does not turn a database transaction into a transaction with an independent warehouse, payment provider or message consumer.&lt;/p&gt;

&lt;p&gt;Request comparison uses JSON with sorted keys and compact separators. This makes object-key order irrelevant for the valid bodies used in the tests. &lt;/p&gt;

&lt;p&gt;It is not a general business-equivalence algorithm. Different units, omitted defaults or an API revision can change meaning even when two payloads look similar. Agree those rules separately before adopting this serializer for another interface.&lt;/p&gt;

&lt;p&gt;Python's &lt;a href="https://docs.python.org/3/library/sqlite3.html#transaction-control-via-the-isolation-level-attribute" rel="noopener noreferrer"&gt;SQLite transaction documentation&lt;/a&gt; explains the legacy &lt;code&gt;isolation_level&lt;/code&gt; control. Here, &lt;code&gt;isolation_level=None&lt;/code&gt; disables implicit transaction opening and the fixture issues its own transaction statements. Each HTTP handler owns and closes its connection. The executed environment is stated above; compatibility with other Python releases was not part of this run.&lt;/p&gt;

&lt;p&gt;The reservation is deliberately a row creation, not a complete stock-allocation system. There is no warehouse availability calculation or inventory balance to inspect. This keeps the observable effect small enough to count without deriving it from the returned status. If adapting the fixture to a balance mutation, replace that row-count oracle with the actual invariant for the protected store, and keep the operation record in the same tested transaction. The receiver is a test server accepting the valid request shapes below. It does not implement production input validation, credentials or an internet-facing API contract. Its effect count is a direct database query, so a successful response cannot conceal a second reservation from the test oracle.&lt;/p&gt;

&lt;p&gt;Complete runnable file: retry_fixture.py&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Bounded local HTTP/SQLite experiment. Synthetic data; tenant is NOT authentication.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;http.client&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;http.server&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseHTTPRequestHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ThreadingHTTPServer&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;uuid4&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;canonical&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sort_keys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;separators&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;drop_once&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stopped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;executescript&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
                BEGIN IMMEDIATE;
                CREATE TABLE IF NOT EXISTS reservations (
                    id INTEGER PRIMARY KEY, sku TEXT NOT NULL, quantity INTEGER NOT NULL);
                CREATE TABLE IF NOT EXISTS operations (
                    tenant TEXT NOT NULL, operation_id TEXT NOT NULL,
                    payload TEXT NOT NULL, response TEXT NOT NULL,
                    PRIMARY KEY (tenant, operation_id));
                COMMIT;
            &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;owner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;
        &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseHTTPRequestHandler&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;log_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;pass&lt;/span&gt;

            &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;do_POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Length&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])))&lt;/span&gt;
                &lt;span class="n"&gt;tenant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Test-Tenant&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Idempotency-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Request-ID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;canonical&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# Each handler owns its connection.
&lt;/span&gt;                &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BEGIN IMMEDIATE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="n"&gt;previous&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT payload, response FROM operations WHERE tenant=? AND operation_id=?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                          &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;fetchone&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;outcome&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replay&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;previous&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;previous&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;previous&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                            &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;outcome&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;409&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;conflict&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                            &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;canonical&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;payload_conflict&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
                    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;INSERT INTO reservations(sku, quantity) VALUES (?, ?)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sku&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;quantity&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
                        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;canonical&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reservation_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lastrowid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
                        &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;INSERT INTO operations VALUES (?, ?, ?, ?)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                        &lt;span class="n"&gt;outcome&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;created&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;COMMIT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;in_transaction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ROLLBACK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="k"&gt;raise&lt;/span&gt;
                &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;drop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;drop_once&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;outcome&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;created&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;drop_once&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
                    &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;request_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;operation_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;outcome&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;response_lost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# Deliberate transport loss strictly AFTER COMMIT.
&lt;/span&gt;                    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close_connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
                    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shutdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SHUT_RDWR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt;
                &lt;span class="n"&gt;encoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Length&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoded&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Request-ID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Current attempt, never persisted.
&lt;/span&gt;                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end_headers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ThreadingHTTPServer&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server_port&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;serve_forever&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;daemon&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Explicit SQL transactions; None disables sqlite3's implicit BEGIN.
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;isolation_level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stopped&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shutdown&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;server_close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stopped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;effects&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT count(*) FROM reservations&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fetchone&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tenant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;operation_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;HTTPConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/reservations&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;canonical&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                           &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Test-Tenant&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tenant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Idempotency-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;operation_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Request-ID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request_id&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getresponse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getheaders&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;reserve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tenant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;operation_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request_ids&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;())):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;request_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;request_ids&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tenant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;operation_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;except &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;OSError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Lose the response after the commit
&lt;/h2&gt;

&lt;p&gt;A timeout before the receiver performs any work is an easy retry case. The more discriminating case is uncertainty after the work has happened. The test arms a one-shot fault, sends a valid reservation and lets the receiver commit. Only then does the handler shut down the socket before writing a response.&lt;/p&gt;

&lt;p&gt;From the caller's side, this appears as a transport failure. It cannot infer from that failure whether a reservation exists. The fixture can inspect both sides because it owns the receiver and database, but a real integration usually has only its local observation until a receiver receipt or reconciliation result resolves the uncertainty.&lt;/p&gt;

&lt;p&gt;There is no delay-based race in this fault. The socket close is placed after the explicit COMMIT statement. &lt;/p&gt;

&lt;p&gt;An in-memory trace records the request ID, operation ID, result classification and whether the response was deliberately lost. That trace explains the experiment; it is not a durable audit log or a substitute for the database count.&lt;/p&gt;

&lt;p&gt;The deliberately broken retry loop passes its fresh request ID in both identity positions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tenant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The receiver accepts the identities it receives. On the first attempt it sees a new operation key and creates a reservation. On the second attempt it sees another new key and creates another reservation. Adding a uniqueness constraint to the operation table cannot help when the client changes the identity that the constraint protects. The executed demonstration used synthetic SKU &lt;code&gt;TEST-BOLT&lt;/code&gt;, quantity 2. Its broken-client trace contains &lt;code&gt;test-attempt-1&lt;/code&gt; followed by &lt;code&gt;test-attempt-2&lt;/code&gt; in both the request and operation columns. The final response reports reservation 2, while the database contains two effects. Checking only that response would miss the duplicate.&lt;/p&gt;

&lt;p&gt;The broken and corrected demonstrations start with different fresh database files. They do not repair or delete an extra reservation before counting results. That matters because a compensating cleanup could make the final quantity look right while concealing the duplicate that the client caused. The retained trace labels every value as test data and preserves the created-versus-replayed distinction alongside the stored-effect count.&lt;/p&gt;

&lt;p&gt;Keep the fault and the oracle separate when adapting the example. A client-side exception alone does not establish that the server committed. A server log line alone does not establish how many business rows exist. The fixture deliberately observes the post-commit fault and queries the protected store so those two claims can be reviewed independently.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcbe5ek9z5d4i19vv91ij.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcbe5ek9z5d4i19vv91ij.png" alt="Executed local retry fixture: test-attempt-1 creates and commits reservation 1 before its response is lost; test-attempt-2 retains test-operation-1 and replays reservation 1. Broken keys create two rows; the corrected client leaves one." width="800" height="560"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;small&gt;Executed local fixture: the first response is lost after COMMIT; the retry replays reservation 1 under the same operation ID.&lt;/small&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Run the same assertions before changing the client
&lt;/h2&gt;

&lt;p&gt;Save the runtime as &lt;code&gt;retry_fixture.py&lt;/code&gt; and the test module as &lt;code&gt;test_fixture.py&lt;/code&gt; in one directory. The suite starts loopback HTTP servers on ephemeral ports and creates temporary database files. It needs no persistent local service. The test cleanup stops each server and removes its temporary directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; unittest &lt;span class="nt"&gt;-v&lt;/span&gt; test_fixture.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before the client correction, the executed suite ran six tests: five passed and one failed. &lt;/p&gt;

&lt;p&gt;The failing assertion expected the set of operation identities to contain &lt;code&gt;op-1&lt;/code&gt;. Instead it found &lt;code&gt;attempt-1&lt;/code&gt; and &lt;code&gt;attempt-2&lt;/code&gt;. That was an identity failure, not a missing dependency, invalid fixture or timing-based setup error.&lt;/p&gt;

&lt;p&gt;The failing test first checks the observed attempt sequence, then the operation identity and result classifications. It also requires one stored effect, a successful final status, the second attempt's response header and a business result containing only reservation fields. Those assertions describe behavior at the HTTP and storage boundaries rather than requiring a particular private function call order.&lt;/p&gt;

&lt;p&gt;An early assertion failure means later assertions in that test do not run. &lt;/p&gt;

&lt;p&gt;The separate buggy-client demonstration therefore supplies the observed two-effect count; the RED test output alone is not presented as proof that every later assertion executed. Preserve this distinction when collecting a failure report for a larger harness.&lt;/p&gt;

&lt;p&gt;The other five cases exercise the receiver directly. Their passing results isolate the defect: the supplied key can protect the local effect, but the retry client does not preserve it. A test that replaced the receiver with a mock would need an additional oracle to establish the actual database mutation and replay behavior.&lt;/p&gt;

&lt;p&gt;Check which runtime module the test imports before recording RED. A scratch directory with similarly named files can run a corrected module while the reviewer inspects an older copy. Record the loaded runtime file's path and hash with the result, then preserve that exact file. A test name and a passing process exit do not identify the implementation that actually ran.&lt;/p&gt;

&lt;p&gt;To reproduce RED from the complete fixed file, work in a scratch copy and replace only the final call inside &lt;code&gt;reserve&lt;/code&gt; with the broken call shown above. Keep the receiver transaction, response-loss fault and test module unchanged. Restore the operation argument to reproduce GREEN. A different assertion failure needs investigation before it is treated as evidence of this defect; for example, an import failure has not exercised the HTTP boundary at all.&lt;/p&gt;

&lt;p&gt;Freeze the test module while correcting the implementation. Here its SHA-256 remained unchanged between RED and GREEN. That makes the evidence stronger than a passing result produced after quietly weakening the expected operation identity or removing the database assertion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Move operation identity outside the attempt boundary
&lt;/h2&gt;

&lt;p&gt;The corrected client passes the caller's existing operation ID to every transmission, while obtaining a fresh request ID inside the loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;-return attempt(port, tenant, request_id, request_id, body)
&lt;/span&gt;&lt;span class="gi"&gt;+return attempt(port, tenant, operation_id, request_id, body)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The operation ID is not regenerated in response to an exception. In the fixed demonstration, both transmissions carry &lt;code&gt;test-operation-1&lt;/code&gt;. The first creates a reservation and loses its response. The second replays that stored result. Its response identifies &lt;code&gt;test-attempt-2&lt;/code&gt;, and the stored business response still describes reservation 1.&lt;/p&gt;

&lt;p&gt;This separation matters on the response path too. If the server cached the first attempt's entire transport envelope, a replay could advertise the wrong request ID. The fixture stores the business response, then adds the current request header when sending it. &lt;/p&gt;

&lt;p&gt;The tests check that header independently from the stable reservation result.&lt;/p&gt;

&lt;p&gt;The client allows at most two transmissions and retries transport exceptions. It returns ordinary HTTP responses, including the conflict response, without creating a fresh operation. This bounded loop exists to exercise identity preservation. It is not a recommended retry budget, delay schedule or error-classification policy for a production dependency.&lt;/p&gt;

&lt;p&gt;The caller must still retain the operation across its own recovery boundary. This fixture receives that identity as an argument; it does not implement a durable job queue or prove that a restarted caller can recover its original intent. Moving ID creation outside one loop is necessary here, but a worker that invents a replacement ID after restarting could reintroduce the same failure elsewhere.&lt;/p&gt;

&lt;p&gt;The successful replay deliberately returns HTTP 201, matching this fixture's original creation result. There is no claim that every API must use that status for a replay. If a provider instead returns another documented success status or an asynchronous acknowledgement, test the promised business identity and effect separately from the transport status. &lt;/p&gt;

&lt;p&gt;A blanket assertion that every response must be identical can conceal a legitimate current-attempt header or reject a valid provider contract.&lt;/p&gt;

&lt;p&gt;Keep resource identity separate from operation identity after success. A reservation can later be amended or released through a different authorized operation. Reusing its original creation key for every future mutation would collapse distinct business actions into one replay record. The field should describe the requested change, not merely whichever resource happens to be affected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read the six passing contracts precisely
&lt;/h2&gt;

&lt;p&gt;After the correction and explicit connection cleanup, all six unchanged tests passed. The demonstration recorded one effect for the fixed client and two for the preserved buggy client. These counts describe this fixture and its synthetic inputs. They do not establish throughput, a production error rate or a guarantee across every possible interruption.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Executed case&lt;/th&gt;
&lt;th&gt;Observable result&lt;/th&gt;
&lt;th&gt;What it distinguishes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Lost response after commit&lt;/td&gt;
&lt;td&gt;Two request IDs, one operation ID, one effect&lt;/td&gt;
&lt;td&gt;A repeated transmission from a new business action&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Changed payload under the same key&lt;/td&gt;
&lt;td&gt;HTTP 409, original result retained, one effect&lt;/td&gt;
&lt;td&gt;Conflicting intent from a valid replay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Same operation ID in different tenants&lt;/td&gt;
&lt;td&gt;Two independent reservation IDs&lt;/td&gt;
&lt;td&gt;Scoped identity from accidental global suppression&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Different operations with identical payloads&lt;/td&gt;
&lt;td&gt;Two reservation IDs&lt;/td&gt;
&lt;td&gt;Business intent from payload equality&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Receiver restart using the same database&lt;/td&gt;
&lt;td&gt;Original result returned, one effect&lt;/td&gt;
&lt;td&gt;Persisted operation state from an in-memory cache&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Two concurrent callers for one operation&lt;/td&gt;
&lt;td&gt;Matching results, one effect, distinct request headers&lt;/td&gt;
&lt;td&gt;Atomic admission from a preliminary existence check&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The restart case closes the receiver normally and starts another instance over the same file. It does not kill a process during COMMIT or simulate loss of storage power. &lt;/p&gt;

&lt;p&gt;The concurrent case submits one pair of callers together; it is not a stress test or proof that every scheduler interleaving was explored.&lt;/p&gt;

&lt;p&gt;The conflict test also reverses the order of JSON object keys on a later valid replay. The business result must remain the original one. That check demonstrates the fixture's chosen object-order normalization without claiming that arbitrary strings, numbers or schema versions are semantically interchangeable.&lt;/p&gt;

&lt;p&gt;The concurrent assertions inspect both returned results and the stored count. Identical response bodies alone would be insufficient: a defective receiver could create two rows but accidentally return the same one to both callers. Counting rows alone would also be incomplete if one caller received an unrelated reservation. The combined oracle checks that this pair shares the intended result while retaining distinct attempt metadata.&lt;/p&gt;

&lt;p&gt;A tenant is not part of every provider's idempotency scope in the same form. The test makes its chosen namespace explicit so reviewers can see what is protected. For another integration, replace it with the receiver's documented account, route and operation scope, derived from an authorized identity rather than a caller's unauthenticated assertion.&lt;/p&gt;

&lt;p&gt;Complete runnable file: test_fixture.py&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Six observable integration contracts; all identifiers and stock are test data.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;concurrent.futures&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tempfile&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;unittest&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;retry_fixture&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reserve&lt;/span&gt;

&lt;span class="n"&gt;BODY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sku&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TEST-BOLT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;quantity&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Contracts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unittest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;setUp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;temp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tempfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TemporaryDirectory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addCleanup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cleanup&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reservations.sqlite&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addCleanup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;op-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tenant&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;test-tenant&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;attempt-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tenant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BODY&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_lost_response_retries_one_operation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;drop_once&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
        &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;reserve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;test-tenant&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;op-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BODY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                         &lt;span class="nf"&gt;iter&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;attempt-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;attempt-2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="n"&gt;__next__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;trace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trace&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;request_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;attempt-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;attempt-2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;operation_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;op-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;outcome&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;created&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replay&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;effects&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Request-ID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;attempt-2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reservation_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sku&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;quantity&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_payload_conflict_preserves_first_result&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;quantity&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sku&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TEST-BOLT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;attempt-2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;409&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;payload_conflict&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;
        &lt;span class="n"&gt;replay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;quantity&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sku&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TEST-BOLT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;attempt-3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;replay&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;replay&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Request-ID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;attempt-3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;effects&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_tenants_have_independent_operation_scope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenant&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;test-A&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenant&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;test-B&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;attempt-2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertNotEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reservation_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reservation_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;effects&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_distinct_operations_same_payload_create_two_effects&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;op-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;op-2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;attempt-2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertNotEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reservation_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reservation_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;effects&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_replay_survives_restart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;replay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;after-restart&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;replay&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;replay&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;replay&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Request-ID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;after-restart&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;outcome&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replay&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;effects&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_concurrent_identical_operation_has_one_effect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;gate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Barrier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;caller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;gate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;concurrent-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;concurrent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ThreadPoolExecutor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;caller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Request-ID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;concurrent-0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;concurrent-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;outcome&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;created&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replay&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;effects&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;unittest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Adapt the contract at the next system boundary
&lt;/h2&gt;

&lt;p&gt;When the protected effect moves to an independent service, ask what that receiver actually promises. Does it recognize the supplied key, compare changed arguments and retain an authoritative result? A header sent by the client has no deduplication effect if the receiving component ignores it or applies it in a different namespace.&lt;/p&gt;

&lt;p&gt;The current &lt;a href="https://docs.stripe.com/api/idempotent_requests" rel="noopener noreferrer"&gt;Stripe idempotent-request reference&lt;/a&gt; distinguishes API v1's saved-response behavior from API v2's retry handling. It also explains that reuse after a v1 key is pruned produces a new request. Those are provider-specific conditions, not settings inherited by this inventory server or every API offering an idempotency key.&lt;/p&gt;

&lt;p&gt;An expired replay record leaves an unresolved operation unresolved. Neither reusing the old key nor minting a replacement proves that the earlier action failed. Before retransmitting, obtain the evidence needed under the receiver's recovery contract or hold the operation for reconciliation. A retry counter reaching its limit also does not cancel work already admitted remotely.&lt;/p&gt;

&lt;p&gt;Adding an outbox can make a local dispatch intention durable, but the external effect still has its own boundary. A receiver receipt might be lost before local storage. A callback might arrive more than once. Those cases need their own acceptance tests and identity mapping; this reservation fixture does not execute a distributed commit or a webhook workflow.&lt;/p&gt;

&lt;p&gt;Observability should preserve that distinction. A trace can connect the attempts, while the operation record identifies what they were trying to achieve. Sampling, log loss or a regenerated trace ID must not silently change duplicate admission. The durable operation store is the authority for this fixture's replay decision, not whichever request happens to remain visible in a tracing interface.&lt;/p&gt;

&lt;p&gt;Use opaque identifiers and controlled evidence access in a real service. An idempotency key should not contain a customer's private record or an API credential. &lt;/p&gt;

&lt;p&gt;A support operator needs enough context to join the operation to authorized business evidence without exposing sensitive payloads through routine request logs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep an executable acceptance packet
&lt;/h2&gt;

&lt;p&gt;Retain the two runtime versions, unchanged tests and actual command results. Include the interpreter and SQLite versions, the synthetic payload and the precise location of the injected fault. Preserve the demo trace beside the count of rows read directly from the protected database. A screenshot of a green test summary alone leaves those assumptions invisible. Give the reviewer a clean directory and the two files, then have them run the command above. Ask which observed record proves that the first attempt committed and which proves the retry did not create another effect. They should also be able to identify the current response attempt without changing the stored reservation result.&lt;/p&gt;

&lt;p&gt;For a real receiver, add its current operation-key contract and the unresolved-outcome procedure before treating the fixture as delivery acceptance. Name any simulated dependency and the later integration gate it still needs. Do not extend a local passing result into a claim about a remote system that the test never called.&lt;/p&gt;

&lt;p&gt;A retry is another opportunity to observe or complete the same intent. The integration test earns that conclusion by preserving operation identity through the deliberate response loss and checking the resulting store. Keep the request IDs available for investigation, and keep the business identity stable for the component that decides whether another effect is allowed.&lt;/p&gt;

&lt;h2&gt;
  
  
  More insights to read
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pharosproduction.blogspot.com/2026/10/ai-agent-integration-estimates-price.html" rel="noopener noreferrer"&gt;AI Agent Integration Estimates: Price the Connector Failure Paths&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hackmd.io/@dmytro-nasyrov/retry-identity-fieldbook-transport-attempts-business-operations" rel="noopener noreferrer"&gt;Retry Identity Fieldbook: Transport Attempts and Business Operations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hackmd.io/@dmytro-nasyrov/payment-recovery-fieldbook-identity-replay-reconciliation" rel="noopener noreferrer"&gt;The Payment Recovery Fieldbook: Identity, Replay and Reconciliation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.bulbapp.io/p/9982f38b-b272-44c6-bbe2-61d93217a341/audit-four-replay-boundaries-in-smart-account-signatures" rel="noopener noreferrer"&gt;Audit Four Replay Boundaries in Smart-Account Signatures&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dmytro-nasyrov.beehiiv.com/p/psp-integration-second-delivery-milestone" rel="noopener noreferrer"&gt;When a PSP Integration Needs a Second Delivery Milestone&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  About the author
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flabtwrvya3qcspprlkbc.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flabtwrvya3qcspprlkbc.jpg" alt="Portrait of Dmytro Nasyrov wearing a dark suit and light blue shirt against a dark background." width="112" height="112"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;small&gt;Dmytro Nasyrov. Photo supplied by the author.&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written by &lt;a href="https://pharosproduction.com/dmytro-nasyrov/" rel="noopener noreferrer"&gt;Dmytro Nasyrov&lt;/a&gt; PhD, software architect with 24 years of production experience. Dmytro is the founder and CTO of Pharos Production. He works on production software architecture for FinTech, AI, Web3 and blockchain systems.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>testing</category>
      <category>architecture</category>
      <category>database</category>
    </item>
    <item>
      <title>Regulated Tokenization Engineering in 2026: Identity, Transfer Controls and Auditability</title>
      <dc:creator>Dmytro Nasyrov</dc:creator>
      <pubDate>Wed, 21 Oct 2026 08:11:14 +0000</pubDate>
      <link>https://dev.to/pharos_production/regulated-tokenization-engineering-in-2026-identity-transfer-controls-and-auditability-8jl</link>
      <guid>https://dev.to/pharos_production/regulated-tokenization-engineering-in-2026-identity-transfer-controls-and-auditability-8jl</guid>
      <description>&lt;p&gt;A regulated tokenization system should be able to explain why a particular ownership change was permitted, which authority could override that decision and what evidence survives a later policy change. &lt;/p&gt;

&lt;p&gt;A transaction hash answers only part of that question. It does not identify the legal instrument, reproduce an investor assessment or explain the cash leg of a settlement.&lt;/p&gt;

&lt;p&gt;The useful engineering unit is a reconstructable decision. Join the instrument definition to an investor record, the wallet binding, the policy evaluated at execution and the resulting ledger changes. Preserve the exceptional route when an administrator intervenes. If those joins disappear, a technically successful transfer can leave operations unable to explain the register it now maintains.&lt;/p&gt;

&lt;p&gt;This guide connects identity, transfer enforcement and auditability through one proposed acceptance contract. The source snapshot is October 21, 2026. The walkthrough and acceptance cases below are design exercises, not reported production results or executed tests. The linked implementation articles provide their own narrower artifacts and limitations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the instrument and the accountable owner
&lt;/h2&gt;

&lt;p&gt;For an EU-facing project, first establish what right the token represents and who owns the classification decision. MiCA is not a universal regulatory wrapper for tokenized assets. Crypto-assets qualifying as financial instruments are excluded from its scope; the applicable securities framework must be considered instead. &lt;a href="https://www.esma.europa.eu/document/guidelines-conditions-and-criteria-qualification-crypto-assets-financial-instruments" rel="noopener noreferrer"&gt;ESMA's qualification guidelines&lt;/a&gt; address that boundary, while &lt;a href="https://eur-lex.europa.eu/legal-content/EN/ALL/?uri=CELEX%3A32023R1114" rel="noopener noreferrer"&gt;MiCA Article 2&lt;/a&gt; supplies the scope rule.&lt;/p&gt;

&lt;p&gt;Engineering should receive a versioned requirements record from the issuer and its qualified advisers. That record should distinguish an investor eligibility condition from an operational preference. A transfer limit copied from a slide has no reliable owner, effective date or exception process. Give each implemented rule an identifier that points to the approved requirement and the person responsible for resolving ambiguity.&lt;/p&gt;

&lt;p&gt;A useful classification record names the instrument, relevant jurisdictions, rights attached to ownership and the authoritative register. Include the approved distribution and custody model. If the token is a representation of another register, describe which system prevails during a discrepancy. A blockchain balance cannot decide that governance question by itself.&lt;/p&gt;

&lt;p&gt;Fragmented ownership between onboarding, contracts and settlement is a software architecture problem. &lt;a href="https://pharosproduction.com" rel="noopener noreferrer"&gt;Pharos Production blockchain and FinTech engineering&lt;/a&gt; spans those implementation domains; the engagement boundary should name the interfaces and acceptance evidence each team must deliver. Legal classification and authorization remain decisions for the responsible issuer and advisers, rather than conclusions inferred from a token standard.&lt;/p&gt;

&lt;p&gt;Translate the requirements into observable behavior before selecting a library. For example, specify whether a restriction prevents receiving, sending or both, what happens to pending instructions and who may authorize recovery. Those distinctions become test expectations. The phrase regulated token has no equivalent precision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Define the evidence contract before designing the dashboard
&lt;/h2&gt;

&lt;p&gt;A reviewer needs a path from an ownership instruction to the reasons it succeeded or failed. The following matrix is a proposed engineering contract. Its field names are illustrative, and no standard automatically supplies the entire packet.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Boundary&lt;/th&gt;
&lt;th&gt;Minimum relationship to preserve&lt;/th&gt;
&lt;th&gt;Failure the relationship should expose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Instrument to requirement&lt;/td&gt;
&lt;td&gt;Instrument ID, requirement revision, approval owner and effective interval&lt;/td&gt;
&lt;td&gt;A rule copied from an obsolete offering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Investor to wallet&lt;/td&gt;
&lt;td&gt;Internal subject reference, binding revision and authorized wallet change&lt;/td&gt;
&lt;td&gt;A valid wallet signature attributed to the wrong investor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Identity to eligibility&lt;/td&gt;
&lt;td&gt;Issuer reference, claim category, validation result and applicable validity rule&lt;/td&gt;
&lt;td&gt;A historical approval reused after revocation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Eligibility to execution&lt;/td&gt;
&lt;td&gt;Instruction ID, evaluated policy revision and relevant contract configuration&lt;/td&gt;
&lt;td&gt;A preview treated as permission for a later state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Execution to ownership&lt;/td&gt;
&lt;td&gt;Chain ID, contract address, transaction position, branch identity and ledger effect&lt;/td&gt;
&lt;td&gt;An orphaned event still counted as ownership&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exception to authority&lt;/td&gt;
&lt;td&gt;Operation type, approving authority, reason reference and affected records&lt;/td&gt;
&lt;td&gt;Administrative recovery recorded as an ordinary transfer&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Keep the instruction identifier stable across retries, but distinguish every attempted execution. A rejected simulation, a submitted transaction and a finalized ownership entry are separate observations. Collapsing them into a single status overwrites the evidence needed to investigate an uncertain result.&lt;/p&gt;

&lt;p&gt;Do not make the token contract store the whole contract of evidence. Some fields belong in a protected case system, some in deployment records and some in chain observations. &lt;/p&gt;

&lt;p&gt;The requirement is a reliable join with controlled access. An opaque internal reference is often more useful than copying a sensitive identity document into an event.&lt;/p&gt;

&lt;p&gt;Decide how a reviewer will recover historical inputs. A current registry lookup answers a current question. It cannot establish what the system knew when an earlier transfer ran unless the relevant history or reproducible snapshot exists. Define retention and access responsibilities alongside the schema, including what a missing record means for an audit export.&lt;/p&gt;

&lt;p&gt;A rejected join is itself a useful acceptance case. Remove one historical policy reference from a rehearsal export and ask the reviewer to complete the reconstruction. The expected result is an explicit incomplete packet with a named missing dependency. The system should not silently substitute today's policy. Test the same case with a reviewer who can see the decision result but cannot open the protected identity dossier. That reviewer should receive an authorized evidence summary or an access limitation, not an unexplained blank. These cases exercise the quality of the evidence interface rather than the correctness of a transfer rule.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat identity as a changing relationship
&lt;/h2&gt;

&lt;p&gt;A wallet address identifies an execution endpoint. The investor record identifies the person or entity to which the business attributes rights. A custody arrangement can introduce further relationships between a beneficial owner, an account and the address visible on-chain. Model those relationships explicitly rather than assuming one address always equals one investor.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://eips.ethereum.org/EIPS/eip-3643" rel="noopener noreferrer"&gt;ERC-3643&lt;/a&gt; separates investor eligibility from offering-wide transfer conditions. Its identity registry associates wallets with identity contracts; required claim topics and trusted issuers contribute to verification. The compliance component addresses rules such as holding limits. That distinction is useful for ownership of failures: an identity problem and an allocation-limit problem require different investigation paths.&lt;/p&gt;

&lt;p&gt;For each identity dependency, document who can change it and how the change becomes effective. Replacing a trusted issuer can change eligibility without touching token bytecode. So can changing required claim topics or rebinding a wallet. An audit limited to the token implementation will miss those transitions.&lt;/p&gt;

&lt;p&gt;Avoid treating a successful onboarding check as a permanent permission. Your design must say how validity, revocation and source unavailability affect a later instruction. If the chosen implementation does not enforce a required time boundary itself, an operational reminder does not close that gap. Add an enforceable mechanism or narrow the supported workflow.&lt;/p&gt;

&lt;p&gt;Retain enough evidence to explain a decision without exposing the underlying dossier publicly. A document hash alone does not establish that the document was accurate, reviewed by an authorized person or lawfully retained. Specify the reference's meaning, access route and retention owner. Use synthetic records for rehearsal, including wallet replacement and an investor whose status changes after an instruction is prepared.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose enforcement boundaries, then choose a standard
&lt;/h2&gt;

&lt;p&gt;Identity verification becomes useful only where ownership can actually change. A front-end check cannot prevent a direct call to a transferable token. Begin with an inventory of balance-changing paths and integrations, including issuance, redemption and recovery. Then decide which component has the final authority for each path.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://dev.to/pharos_production/erc-3643-vs-erc-1400-how-to-choose-a-standard-for-tokenized-instruments-67e"&gt;ERC-3643 versus ERC-1400 comparison&lt;/a&gt; examines identity and compliance registries alongside partition and controller concepts. Use it to establish the required semantics. Do not select a standard because its name appears in a procurement specification without checking the implementation and its maintenance boundary.&lt;/p&gt;

&lt;p&gt;Next, use the &lt;a href="https://dev.to/pharos_production/top-5-token-transfer-control-architectures-for-regulated-assets-compared-ajl"&gt;transfer-control architecture comparison&lt;/a&gt; to locate the decision: inside the token, in external registries, in signed authorization data, within partitions or through agreement-ledger authorization. Those mechanisms can coexist. A certificate may authorize an instruction while a current registry still decides whether its recipient remains eligible.&lt;/p&gt;

&lt;p&gt;Finally, the &lt;a href="https://dev.to/pharos_production/how-to-implement-erc-3643-transfer-controls-for-permissioned-tokens-2e40"&gt;ERC-3643 implementation walkthrough&lt;/a&gt; supplies a concrete country-control module and test fixture. Its stated identity-stub limitations matter. A passing module fixture is evidence about the exercised code and assumptions, not proof that a complete onboarding process or deployed instrument complies with law.&lt;/p&gt;

&lt;p&gt;Record the selected boundary in the release packet. Name the exact repository commit, deployed contracts, policy dependencies and custody integration. A reviewer should be able to identify which source answers a disputed behavior. A label such as ERC-1400-compatible is too broad to establish how a particular recovery transaction will behave.&lt;/p&gt;

&lt;h2&gt;
  
  
  Audit exceptional authority as its own product behavior
&lt;/h2&gt;

&lt;p&gt;Ordinary transfers are only one route through a regulated token's lifecycle. An agent may recover a wallet, force a transfer or freeze holdings. Issuance changes supply. A replacement compliance contract changes future decisions. These operations need explicit business meaning and separate acceptance evidence.&lt;/p&gt;

&lt;p&gt;Inspection of &lt;a href="https://github.com/TokenySolutions/T-REX/blob/f1390127d4270e7f31764c068caf0a1526b852d4/contracts/token/Token.sol" rel="noopener noreferrer"&gt;T-REX Token.sol at commit f1390127d4270e7f31764c068caf0a1526b852d4&lt;/a&gt; illustrates the distinction. The ordinary transfer path checks pause status, wallet freezes, free balance, recipient verification and compliance. Its agent-only forced-transfer path checks recipient verification and balance but does not use the ordinary pause guard or &lt;code&gt;canTransfer&lt;/code&gt; decision. It still invokes the compliance transfer callback. These are source-specific observations, not assumptions about every ERC-3643 deployment.&lt;/p&gt;

&lt;p&gt;The operational question is whether that exceptional behavior matches the issuer's approved process. List the gates each privileged operation retains and those it bypasses. Require an authorized reason and a link to the resulting register correction. A successful administrative transaction should never silently inherit the meaning of an ordinary investor instruction.&lt;/p&gt;

&lt;p&gt;For a custom token, inheriting an emergency extension is also insufficient evidence that an operator can use it. OpenZeppelin states in its &lt;a href="https://docs.openzeppelin.com/contracts/5.x/api/token/erc20#ERC20Pausable" rel="noopener noreferrer"&gt;ERC20Pausable documentation&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This contract does not include public pause and unpause functions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The integrating contract must provide suitably restricted entry points. Rehearse who can pause, who can restore service and what evidence authorizes restoration. A multisig threshold describes a signing requirement; operational independence also depends on who controls the signers and their recovery arrangements.&lt;/p&gt;

&lt;p&gt;This is where &lt;a href="https://pharosproduction.com/services/tokenization-and-rwa-development/" rel="noopener noreferrer"&gt;Pharos Production tokenization and RWA development&lt;/a&gt; is relevant to the central problem: the published service scope includes identity registries, compliance modules and transfer restrictions. Turn that scope into an operation-by-operation acceptance matrix. Do not substitute a service description for verification of the delivered authority model.&lt;/p&gt;

&lt;p&gt;Authority also has a lifecycle after deployment. Document who can appoint or remove an agent, change an approval threshold and replace a policy dependency. If an operator loses access, identify the recovery route that remains available and the records it produces. A pause procedure that relies on the same unavailable signer as the failed operation has an unresolved dependency. Rehearse signer loss using the intended operational arrangement, without exposing production keys. Record elapsed response time only when measured; a configured delay is not evidence that the team can complete the surrounding approvals within that interval.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rehearse a revocation while settlement is pending
&lt;/h2&gt;

&lt;p&gt;Consider a proposed exercise, R-17. Investor A instructs a transfer of 40 units to investor B. B is eligible when the instruction is prepared. Before execution, the relevant claim is revoked. The instruction also has a cash-side reservation. These values and identifiers are hypothetical; the sequence below has not been executed for this article.&lt;/p&gt;

&lt;p&gt;First, save the preparation observation with its policy and identity references. Keep its result as a preview. Record the validity boundary of any signed authorization. A signature over the instruction proves authorization under its signing scheme; it does not automatically prove current eligibility at the later execution point.&lt;/p&gt;

&lt;p&gt;Next, apply the revocation through the intended authority path. Capture the effective chain position or other precisely defined activation boundary. Execute the instruction against the resulting state. Under this exercise's proposed requirement, an ordinary transfer to B must fail and leave token ownership unchanged. If the actual business requirement differs, revise the requirement record and its expected outcome before testing.&lt;/p&gt;

&lt;p&gt;Exercise ordering explicitly when the revocation and transfer are submitted close together. Record the accepted execution order instead of assigning meaning from the order in which a browser displayed notifications. If the transfer executes before the revocation becomes effective, its expected outcome may differ from a transfer executed afterward. The requirement owner must approve both cases. Keep separate test identifiers for those orderings and retain their state references. This prevents a race from becoming an ambiguous disagreement between an onboarding timestamp and a chain timestamp. The evidence contract should explain which boundary governs eligibility, how that boundary is observed and what happens when the observation is temporarily unavailable.&lt;/p&gt;

&lt;p&gt;The cash reservation now needs its own outcome. Releasing it may be immediate, asynchronous or subject to manual review, depending on the payment integration. Record that outcome separately. A token revert cannot establish that a bank payment or external ledger reservation was reversed.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Exercise variation&lt;/th&gt;
&lt;th&gt;Proposed expectation&lt;/th&gt;
&lt;th&gt;Evidence to retain&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Revocation precedes execution&lt;/td&gt;
&lt;td&gt;Ordinary transfer rejected, ownership unchanged&lt;/td&gt;
&lt;td&gt;Requirement revision, state reference and execution result&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Two instructions compete for available units&lt;/td&gt;
&lt;td&gt;Only permitted allocation succeeds&lt;/td&gt;
&lt;td&gt;Ordered attempts and before/after ledger reconciliation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cash release times out&lt;/td&gt;
&lt;td&gt;Settlement remains unresolved&lt;/td&gt;
&lt;td&gt;Reservation reference, timeout and named recovery owner&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent performs authorized recovery&lt;/td&gt;
&lt;td&gt;Exceptional path identified separately&lt;/td&gt;
&lt;td&gt;Authority record, reason and resulting ownership entries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Observed block is replaced&lt;/td&gt;
&lt;td&gt;Projection follows the accepted replacement branch&lt;/td&gt;
&lt;td&gt;Old/new branch references and rebuilt projection comparison&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Do not manufacture a PASS from a screenshot of the final balance. The fixture should assert both the forbidden effect and the required preserved state. It should also prove that a permitted instruction still works. Otherwise, blocking every transfer could satisfy the negative scenario while leaving the instrument unusable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp5ccaqfiohpj2u5albuq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp5ccaqfiohpj2u5albuq.png" alt="Proposed R-17 exercise: prepare a 40-unit transfer, revoke B's required claim before execution, reject under the proposed rule and reconcile token ownership separately from the cash reservation." width="800" height="560"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;small&gt;R-17 is a proposed acceptance exercise. Its decision trail must preserve both the token result and the separate cash outcome.&lt;/small&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Build auditability around history and reconciliation
&lt;/h2&gt;

&lt;p&gt;The audit packet must distinguish business intent from chain observation and from the current ownership projection. Each has a different lifecycle. An instruction can remain valid business history even when its transaction disappears from the accepted chain. The ownership projection must follow the accepted branch without erasing the fact that operations observed another result earlier.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://geth.ethereum.org/docs/interacting-with-geth/rpc/pubsub" rel="noopener noreferrer"&gt;Geth's subscription documentation&lt;/a&gt; explains that subscriptions concern current events rather than historical replay. It also describes removed logs after a reorganization and repeated logs for the same transaction. An event consumer therefore needs more than a permanent append of every notification it receives.&lt;/p&gt;

&lt;p&gt;Preserve chain identity and block ancestry with event position. Make replay idempotent within the chosen branch model. Maintain a recoverable checkpoint and a method to backfill missed observations. Define when a projected balance is provisional and which chain-specific finality policy permits downstream action. Do not invent a universal confirmation count for every network and settlement arrangement.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://dev.to/pharos_production/verify-an-indexer-can-recover-from-a-chain-reorganization-1gn0"&gt;indexer recovery exercise&lt;/a&gt; demonstrates a narrower synthetic branch-replacement model with a separate snapshot oracle. Use that as an entry point for projection testing. Add the failure modes relevant to your own infrastructure, including process interruption and unavailable historical data, before claiming operational recovery.&lt;/p&gt;

&lt;p&gt;Rejected instructions need evidence too. A reverted execution does not leave ordinary success events from that reverted execution as an on-chain rejection journal. Preserve the failed attempt and its observed error through the application or execution tooling, while respecting privacy and retention requirements. Label a local simulation distinctly from an included transaction that reverted.&lt;/p&gt;

&lt;p&gt;For exports, include a manifest identifying the time range, branch policy, decoder version and referenced business records. State whether the export is complete. If an identity reference cannot be resolved under the reviewer's access rights, report the gap rather than treating an empty field as proof that no identity decision existed.&lt;/p&gt;

&lt;p&gt;A useful reconciliation has two independent views of the same scope. Derive expected ownership from the accepted chain history, then compare it with the application's investor register under the documented custody mapping. A total-supply match alone cannot reveal that units were attributed to the wrong investor. Compare ownership at the granularity needed for the instrument, including restricted categories where applicable. Preserve discrepancies as cases with their own resolution history. An operator's correction should create an attributable adjustment or a documented rebuild, so a later export can distinguish the original observation from the repair.&lt;/p&gt;

&lt;h2&gt;
  
  
  Invalidate evidence when dependencies change
&lt;/h2&gt;

&lt;p&gt;A release can change eligibility without upgrading the token implementation. Treat changes to claim issuers, required topics, compliance modules and registry bindings as changes to decision behavior. The source tree is only one component of the deployed system.&lt;/p&gt;

&lt;p&gt;Keep a deployment manifest that joins implementation identity to configuration and authority. Record the effective boundary for each revision. If two dependencies change in the same operational window, preserve their order. A later review must be able to reconstruct the intermediate state rather than assume the whole window was atomic.&lt;/p&gt;

&lt;p&gt;Scope the evidence that a change invalidates. Replacing an event decoder requires projection replay checks. Changing a holding-limit module requires allocation and callback-state checks. Rotating an agent requires permission and operational recovery checks. Altering the custody mapping requires reconciliation with the investor subledger. This gives review a concrete target without pretending every change has the same risk.&lt;/p&gt;

&lt;p&gt;An upgrade rehearsal should include historical records and outstanding instructions. Decide whether previously prepared authorizations remain executable, expire or require reapproval. Preserve that decision in the migration runbook. Restoring an old implementation address does not automatically restore registry state, external records or cash movements.&lt;/p&gt;

&lt;p&gt;Separate code review from deployment verification. A repository audit may establish what a commit does; operations must still establish that the intended addresses, configuration and authority are active. Keep the audit's scope and unresolved findings beside the deployment manifest so a report for one configuration cannot silently represent another.&lt;/p&gt;

&lt;p&gt;Define a practical exit condition for a failed migration rehearsal. If the old software cannot interpret records already created by the new policy, returning traffic to it is insufficient. Identify the point after which a forward repair or explicit data conversion is required. The release owner should know that boundary before authorizing the change. Tie the recovery choice to a retained snapshot and a tested procedure with its own evidence. This is especially relevant when the token code remains unchanged but a new registry relationship changes the meaning of records consumed by settlement or reporting.&lt;/p&gt;

&lt;p&gt;Before accepting an archive, attempt an unauthorized substitution in a synthetic packet. Replace a policy file while leaving its friendly filename unchanged, or point an evidence reference at a different revision. The reviewer should detect the substitution through the agreed integrity mechanism. Hashes help identify byte changes, but the packet also needs an authenticated connection to the release or approval it represents. Define who signs or otherwise attests that relationship and how the reviewer verifies it. This proposed negative check tests evidence integrity; it does not establish the truth of every underlying business assertion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the handover answer a disputed transfer
&lt;/h2&gt;

&lt;p&gt;Ask a delivery team to reconstruct one ordinary success, one denied instruction and one authorized exception from the proposed packet. Start with an instruction reference rather than a dashboard chosen by the team. Follow the joins until an independent reviewer can identify the evaluated policy and reconcile the resulting register.&lt;/p&gt;

&lt;p&gt;Agree the boundaries of that review in advance. State which external identity service, payment system and custody records are included. If a dependency is simulated, mark it clearly and name the later integration gate. The goal is a usable acceptance process with visible limits, rather than a larger collection of reports whose coverage cannot be compared.&lt;/p&gt;

&lt;p&gt;Operations should leave the handover with named owners for stale identity inputs, uncertain settlement and projection recovery. Give each owner a runbook entry and evidence they can retrieve with their actual access. A technically complete archive that only a departed developer can decode will not resolve an investor dispute.&lt;/p&gt;

&lt;p&gt;Regulated tokenization engineering becomes reviewable when those responsibilities connect. A standard defines useful interfaces. Executable behavior comes from the implementation. Operations determine how identity changes, exceptional authority and settlement failures are handled. Acceptance should demonstrate the joins with the deployed configuration and preserve enough history to explain them later.&lt;/p&gt;

&lt;h2&gt;
  
  
  More insights to read
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.bulbapp.io/p/e2580ef7-324b-442d-b818-92b8c8442b3d/five-smart-contract-upgrade-patterns-compared" rel="noopener noreferrer"&gt;Five Smart Contract Upgrade Patterns Compared&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dmytronasyrov.medium.com/upgradeable-solidity-smart-contracts-part-1-versioning-7e6e97cafc28" rel="noopener noreferrer"&gt;Upgradeable Solidity Smart Contracts. Part 1: Versioning&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/pharos-production/smart-contracts-their-potential-and-real-limitations-part-1-222fe44ee14c" rel="noopener noreferrer"&gt;Smart Contracts. Their Potential and Real Limitations. Part 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/pharos-production/smart-contracts-their-potential-and-real-limitations-part-2-40942c055d79" rel="noopener noreferrer"&gt;Smart Contracts. Their Potential and Real Limitations. Part 2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/pharos-production/web3-smart-contracts-oracles-part-1-3905b127c01d" rel="noopener noreferrer"&gt;Web3. Smart Contracts. Oracles. Part 1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  About the author
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flabtwrvya3qcspprlkbc.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flabtwrvya3qcspprlkbc.jpg" alt="Portrait of Dmytro Nasyrov wearing a dark suit and light blue shirt against a dark background." width="112" height="112"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;small&gt;Dmytro Nasyrov. Photo supplied by the author.&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written by &lt;a href="https://pharosproduction.com/dmytro-nasyrov/" rel="noopener noreferrer"&gt;Dmytro Nasyrov&lt;/a&gt; PhD, software architect with 24 years of production experience. Dmytro is the founder and CTO of Pharos Production. He works on production software architecture for FinTech, AI, Web3 and blockchain systems.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>solidity</category>
      <category>blockchain</category>
      <category>web3</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Top 5 Token Transfer-Control Architectures for Regulated Assets Compared</title>
      <dc:creator>Dmytro Nasyrov</dc:creator>
      <pubDate>Wed, 14 Oct 2026 08:40:15 +0000</pubDate>
      <link>https://dev.to/pharos_production/top-5-token-transfer-control-architectures-for-regulated-assets-compared-ajl</link>
      <guid>https://dev.to/pharos_production/top-5-token-transfer-control-architectures-for-regulated-assets-compared-ajl</guid>
      <description>&lt;p&gt;Choose a token transfer-control architecture by locating the authority that can approve a change of ownership at execution time. An embedded rule, an identity registry, a signed certificate, a partition policy and an agreement ledger can all restrict movement. They place the decision in different systems, however, and they respond differently when permission changes while a transfer is pending.&lt;/p&gt;

&lt;p&gt;For a regulated instrument, the useful comparison starts with a rejection condition: can the design enforce the actual ownership rule across every supported movement path? Wallet compatibility and operating cost matter after that condition is satisfied. A successful ordinary transfer says little about delegated movement, recovery or an upgrade that changes the policy dependency.&lt;/p&gt;

&lt;p&gt;The five models below are an editorial selection, with sources checked on October 14, 2026. Section numbers identify the designs; they do not represent benchmark scores. The examples compare enforcement mechanisms rather than vendors. A production system may combine several models, but each entry describes a complete decision boundary. Legal counsel and the instrument documentation determine the ownership rules that engineering must implement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compare the decision boundary first
&lt;/h2&gt;

&lt;p&gt;Use one requirement across the comparison: an eligible holder may move an eligible quantity to an eligible recipient, subject to the instrument's restrictions. Record which system supplies each fact and which action changes it. A controller's exceptional authority belongs in a separate path with its own conditions.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Architecture&lt;/th&gt;
&lt;th&gt;Scenario fit&lt;/th&gt;
&lt;th&gt;Trust boundary&lt;/th&gt;
&lt;th&gt;Principal failure&lt;/th&gt;
&lt;th&gt;Evidence to require&lt;/th&gt;
&lt;th&gt;Operating constraint&lt;/th&gt;
&lt;th&gt;Reject when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Embedded token rules&lt;/td&gt;
&lt;td&gt;A small, stable rule set for one instrument&lt;/td&gt;
&lt;td&gt;Token code and local policy administrators&lt;/td&gt;
&lt;td&gt;An overlooked balance-update path bypasses the check&lt;/td&gt;
&lt;td&gt;Entry-path map and negative tests&lt;/td&gt;
&lt;td&gt;Each policy change affects token state or code&lt;/td&gt;
&lt;td&gt;Several instruments require independently governed shared policy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;External identity and compliance registries&lt;/td&gt;
&lt;td&gt;Several identities or rules need reusable state&lt;/td&gt;
&lt;td&gt;Registry issuers, module administrators and dependency pointers&lt;/td&gt;
&lt;td&gt;A trusted dependency changes without the intended review&lt;/td&gt;
&lt;td&gt;Registry topology and mutation history&lt;/td&gt;
&lt;td&gt;Availability and governance span multiple contracts&lt;/td&gt;
&lt;td&gt;The issuer cannot operate or audit those dependencies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Signed transfer certificates&lt;/td&gt;
&lt;td&gt;Each proposed transaction needs contextual approval&lt;/td&gt;
&lt;td&gt;Off-chain decision service, signer and verifier&lt;/td&gt;
&lt;td&gt;A valid old authorization survives a policy change&lt;/td&gt;
&lt;td&gt;Signed payload, replay rules and revocation trace&lt;/td&gt;
&lt;td&gt;Every required approval depends on certificate issuance&lt;/td&gt;
&lt;td&gt;The service cannot meet the required interruption behavior&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Partition-scoped policy&lt;/td&gt;
&lt;td&gt;One holding contains quantities with different restrictions&lt;/td&gt;
&lt;td&gt;Partition accounting and policy for movement between classes&lt;/td&gt;
&lt;td&gt;Conversion turns restricted units into transferable units&lt;/td&gt;
&lt;td&gt;Conservation and conversion invariants&lt;/td&gt;
&lt;td&gt;Custody and settlement adapters must understand partitions&lt;/td&gt;
&lt;td&gt;Integrations can see only aggregate ERC-20 balances&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agreement-ledger authorization&lt;/td&gt;
&lt;td&gt;Ownership movement requires several parties' authorized actions&lt;/td&gt;
&lt;td&gt;Contract signatories, choice controllers and business assertions&lt;/td&gt;
&lt;td&gt;Properly authorized parties execute an inadequately constrained choice&lt;/td&gt;
&lt;td&gt;Contract lifecycle and authorization trace&lt;/td&gt;
&lt;td&gt;Participants must support the ledger and workflow&lt;/td&gt;
&lt;td&gt;Deployment must remain an ordinary EVM token integration&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  1. Pharos Production: Token Transfer-Control Architecture with Embedded Rules
&lt;/h2&gt;

&lt;p&gt;An embedded gate keeps the relevant policy state beside the token's balance logic. A design might store approved recipients, frozen accounts or instrument-specific holding limits locally. The transfer path evaluates that state before it changes balances. This is a reasonable baseline when the rule set is limited and one issuer controls its lifecycle.&lt;/p&gt;

&lt;p&gt;The central problem is translating an instrument rule into an enforceable state transition. &lt;a href="https://pharosproduction.com" rel="noopener noreferrer"&gt;Blockchain engineering at Pharos Production&lt;/a&gt; includes smart-contract design and integration work; the service is relevant to identifying that transition and its dependencies. This baseline describes a generic architecture, without claiming that the company owns a special token standard or that this article demonstrates a delivered client implementation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.openzeppelin.com/contracts/5.x/api/token/ERC20" rel="noopener noreferrer"&gt;OpenZeppelin's ERC-20 documentation&lt;/a&gt; identifies &lt;code&gt;_update&lt;/code&gt; as the customization point for balance updates in Contracts 5.x. It covers transfers, minting and burning. That gives a designer a place to implement local conditions, but inheriting a token base does not supply an instrument's complete transfer policy. Conditions appropriate for a transfer between holders may be wrong for issuance or redemption.&lt;/p&gt;

&lt;p&gt;The strongest reason to select this architecture is the short enforcement path. A reviewer can follow local state from the administrative write to the balance update without resolving a network of policy contracts. The same simplicity becomes a constraint if several instruments need one independently governed eligibility service. Copying local lists across them introduces synchronization work and creates multiple opportunities for stale decisions.&lt;/p&gt;

&lt;p&gt;Request an entry-path map before judging the implementation. Include direct and allowance-based transfers, mint and burn operations, recovery and every privileged balance adjustment. For each path, identify whether the local gate executes and what exception permits it to proceed. A diagram that ends at the public &lt;code&gt;transfer&lt;/code&gt; method is incomplete.&lt;/p&gt;

&lt;p&gt;A useful negative case removes the recipient's permission before the transaction executes. The balance must remain unchanged if the instrument requires current eligibility. Then repeat the case through delegated transfer. An allowance proves spending authority; it should not silently substitute for the recipient's eligibility. Keep the denied result and the relevant policy state together in the release evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. External Identity and Compliance Registries
&lt;/h2&gt;

&lt;p&gt;This architecture moves eligibility and transfer policy into dependencies that the token consults. The token remains the balance authority, while a registry can connect a wallet to an identity and another contract can evaluate rules about the proposed movement. The model suits systems that need reusable claims or separately maintained policy modules.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://eips.ethereum.org/EIPS/eip-3643" rel="noopener noreferrer"&gt;ERC-3643&lt;/a&gt; describes identity registries, trusted issuers and claim topics alongside compliance checks. Its structure makes the provenance of eligibility an explicit engineering concern. A wallet address is one input to that structure; it is not by itself evidence that the represented investor satisfies the instrument's requirements.&lt;/p&gt;

&lt;p&gt;The implementation boundary matters as much as the specification. In the &lt;a href="https://github.com/TokenySolutions/T-REX/blob/f1390127d4270e7f31764c068caf0a1526b852d4/contracts/token/Token.sol" rel="noopener noreferrer"&gt;pinned T-REX token source&lt;/a&gt;, ordinary movement checks recipient verification and compliance. Administrative paths have different conditions. Review the exact release and configuration rather than assuming that every function follows the ordinary transfer predicate.&lt;/p&gt;

&lt;p&gt;Operationally, the policy can change through more than a token upgrade. An issuer's claim can disappear, a trusted issuer can be removed or a module can be replaced. A dependency pointer can redirect the token to a different decision-maker. Those are material policy changes even when the token bytecode and its public interface remain identical.&lt;/p&gt;

&lt;p&gt;Ask for a topology that names each dependency, its administrator and the facts it supplies. Beside the topology, retain the events or other records that reconstruct changes to claims and configuration. A current registry response explains today's decision; it does not automatically explain a transfer that occurred under last month's configuration.&lt;/p&gt;

&lt;p&gt;Choose this model when shared policy ownership is an advantage and the organization can operate that additional state. Reject it when no team accepts responsibility for claim refresh, dependency monitoring or permission recovery. Outsourcing the check into a contract changes the location of the work; it does not remove the work.&lt;/p&gt;

&lt;p&gt;The decisive acceptance case changes a trusted dependency after a successful preflight. The transaction should follow the documented execution-time rule, and its evidence should identify the dependency version actually consulted. The user interface must be able to explain why a previously valid proposal now fails without treating the failure as an unexplained wallet error.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Signed Transfer Certificates
&lt;/h2&gt;

&lt;p&gt;A certificate architecture approves a specific proposed transaction off chain and verifies the approval during execution. It can use information that is inappropriate to place on a public ledger, including contextual review results. The token or its extension checks that the submitted authorization came from an accepted signer and applies to the intended movement.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/Consensys/UniversalToken/tree/54320c6f7a8ee1fd7fcb19073e9c122e1e8f96f9" rel="noopener noreferrer"&gt;Consensys UniversalToken repository&lt;/a&gt; describes off-chain certificates carrying signed transaction parameters. It also describes on-chain validated-investor lists as another control mechanism. This is an example of why a standard name does not uniquely identify the architecture. The same token family can support different policy boundaries or a hybrid configuration.&lt;/p&gt;

&lt;p&gt;The important distinction is between a cryptographically valid approval and an approval that still satisfies current policy. A certificate issued before a recipient is disqualified may remain mathematically valid afterward. Whether it should execute is an instrument and system-design decision. The verifier needs an explicit mechanism for whatever freshness rule the issuer requires.&lt;/p&gt;

&lt;p&gt;Require evidence for the signed payload. It should bind the authorization to the relevant asset contract and deployment domain, the parties, quantity and action. Review how the implementation limits reuse and what happens after signer rotation. These are acceptance requirements for the chosen implementation, not an assertion that every certificate-based token supplies them.&lt;/p&gt;

&lt;p&gt;Expiry alone may not meet an immediate revocation requirement. A short validity window still leaves a window. A live on-chain revocation check, a policy generation number or another documented invalidation mechanism can close that gap, but each adds state and operational obligations. Decide what outstanding certificates mean when the signer is removed or the policy generation changes.&lt;/p&gt;

&lt;p&gt;This model can support detailed transaction review without publishing the underlying personal information. It also places issuance service availability on the path to transfer. If the service cannot issue a certificate during an outage, the expected behavior may be rejection, queuing or an independently governed exception. A silent fallback to an unrestricted transfer defeats the premise.&lt;/p&gt;

&lt;p&gt;Before selection, replay a captured authorization against altered transaction parameters and after its permitted use. Also prepare a certificate, revoke the recipient and attempt execution. Record which fact invalidates the approval. If the system cannot name that fact, it cannot substantiate a claim that transfers always reflect current eligibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Partition-Scoped Transfer Policy
&lt;/h2&gt;

&lt;p&gt;Partitions distinguish quantities within a holder's balance. One investor might hold transferable units alongside units subject to a lock or another restriction. A partition-aware transfer identifies the category being spent, rather than leaving the system to infer it from an aggregate balance.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/SecurityTokenStandard/EIP-Spec/blob/8b082caba8adb4f8e351aa6acdd3e9e394e09dea/eip/eip-1410.md" rel="noopener noreferrer"&gt;ERC-1410's partition model&lt;/a&gt; provides interfaces for those categories and their movement. Partitions supply accounting structure. The eligibility predicate still needs implementation through token logic, extensions or another control. Labelling a balance as restricted does not make it restricted unless the movement paths enforce the label's meaning.&lt;/p&gt;

&lt;p&gt;Consider an illustrative holding of 150 units: 100 transferable and 50 locked. A request to move 120 cannot be approved merely because the aggregate balance is sufficient. The design must reject it, split it under an explicitly allowed rule or identify a permitted conversion. Otherwise the partition system preserves labels while losing the ownership constraint those labels were meant to express.&lt;/p&gt;

&lt;p&gt;The dangerous edge is movement between categories. An administrator or holder might be able to convert a locked partition into a transferable one. That conversion needs its own policy and evidence. A transfer check applied only after conversion can appear correct while the conversion function has already erased the restriction.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/Consensys/UniversalToken/blob/54320c6f7a8ee1fd7fcb19073e9c122e1e8f96f9/contracts/ERC1400.sol" rel="noopener noreferrer"&gt;pinned UniversalToken implementation&lt;/a&gt; shows partition-specific balance movement and extension handling. Its accounting illustrates the integration burden: downstream systems need to preserve the meaning of the partition selected for a movement, not just the total quantity appearing in an ERC-20-compatible view.&lt;/p&gt;

&lt;p&gt;Select this architecture when different portions of a holding have materially different rights or restrictions. Reject it when custody and settlement adapters cannot identify those portions. A sophisticated contract behind an adapter that flattens every balance can produce a misleading available-to-transfer amount and incorrect settlement instructions.&lt;/p&gt;

&lt;p&gt;The acceptance evidence should show conservation across partitions, rejection from the wrong category and policy enforcement on category changes. Corporate actions also need attention: replacement units must retain the intended restriction state. A migration that recreates only aggregate balances can discard the very information that justified choosing partitions.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Agreement-Ledger Authorization
&lt;/h2&gt;

&lt;p&gt;An agreement-ledger design represents ownership and the permitted actions over it as contracts between identified parties. A transfer becomes an authorized contract lifecycle transition. This changes the platform assumptions, but it is a genuine alternative when the business requirement is controlled ownership movement rather than mandatory use of an ERC-20 wallet interface.&lt;/p&gt;

&lt;p&gt;Digital Asset's &lt;a href="https://github.com/digital-asset/daml/blob/main/sdk/docs/manually-written/sdk/sdlc-howtos/smart-contracts/develop/patterns/authorization.rst" rel="noopener noreferrer"&gt;Daml authorization pattern&lt;/a&gt; distinguishes party authority from an issuer's accreditation check. Its example uses an authorization contract for the new owner and checks that authorization during acceptance of a transfer proposal. Built-in permission to exercise a choice does not replace the business condition inside that choice.&lt;/p&gt;

&lt;p&gt;This distinction is valuable in a workflow involving an issuer and two counterparties. The owner can propose a movement, while the recipient participates in acceptance and the issuer's authorization constrains who may hold the asset. Review the complete action chain. The set of parties who can submit a command is only one part of the question; the resulting ownership contract must also satisfy the business rule.&lt;/p&gt;

&lt;p&gt;For instrument rules that span counterparties and settlement operations, the relevant engineering work includes the legal wrapper and transfer-agent process. Pharos Production's &lt;a href="https://pharosproduction.com/services/tokenization-and-rwa-development/" rel="noopener noreferrer"&gt;tokenization and RWA development&lt;/a&gt; service describes that wider scope. A technical selection record should connect those obligations to the ledger transition and its evidence without implying that a platform choice establishes legal compliance.&lt;/p&gt;

&lt;p&gt;The operating constraint is substantial. Participants need compatible ledger access, party administration and integrations for their contract workflow. An existing EVM custody adapter cannot be assumed to support it. A bridge back to a public-chain token creates another boundary: its mint or release conditions must preserve the original ownership restrictions.&lt;/p&gt;

&lt;p&gt;Choose this model when explicit multi-party authorization and controlled workflow are central to the instrument. Reject it when deployment must stay inside an existing EVM-only ecosystem and that constraint cannot change. Cross-platform portability is a project in its own right, with its own authority and reconciliation requirements.&lt;/p&gt;

&lt;p&gt;An acceptance case archives or otherwise invalidates the required authorization before the recipient accepts a pending proposal. The application should demonstrate the intended rejection and unchanged ownership. Separately test an authorized party attempting a choice with the wrong asset or issuer context. Proper command authorization must not turn a mismatched business object into a valid transfer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Walk One Revocation Through All Five Designs
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F130ilw439qxaujp79iha.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F130ilw439qxaujp79iha.png" alt="Illustrative 40-unit transfer from Alice to Bob is prepared, Bob becomes ineligible, then ordinary execution must reject. Five rows identify the local, registry, certificate, partition and agreement-ledger state that must explain the rejection." width="800" height="560"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;small&gt;Illustrative acceptance scenario; no executed test result is shown.&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;Use the same hypothetical timeline in an architecture review. At preparation, Alice holds sufficient transferable units and Bob is eligible. A proposal to move 40 units is prepared. Before execution, the issuer removes Bob's eligibility. The required result is that the ordinary movement does not complete. This is a proposed acceptance scenario, not a report of tests executed for this article.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Design&lt;/th&gt;
&lt;th&gt;State that must decide execution&lt;/th&gt;
&lt;th&gt;Evidence that distinguishes correct rejection&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Embedded gate&lt;/td&gt;
&lt;td&gt;Updated local eligibility state&lt;/td&gt;
&lt;td&gt;Gate reached through the selected entry point; balances unchanged&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;External registry gate&lt;/td&gt;
&lt;td&gt;Effective identity and compliance dependencies&lt;/td&gt;
&lt;td&gt;Consulted dependency addresses and policy state at execution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Certificate gate&lt;/td&gt;
&lt;td&gt;Authorization validity plus the chosen freshness mechanism&lt;/td&gt;
&lt;td&gt;Old certificate rejected for a documented invalidation reason&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Partition gate&lt;/td&gt;
&lt;td&gt;Transferable partition and recipient rule&lt;/td&gt;
&lt;td&gt;Sufficient unrestricted quantity cannot override recipient ineligibility&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agreement ledger&lt;/td&gt;
&lt;td&gt;Required authorization contract and acceptance conditions&lt;/td&gt;
&lt;td&gt;Pending proposal cannot create the new ownership state&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The table deliberately requires more than an error message. A rejection should have an explainable cause, and a failed movement should not leave partial ownership changes. For the certificate design, current eligibility is an additional requirement to prove. For partitions, eligibility and quantity category are separate predicates. Combining them in a green interface badge conceals which one actually failed.&lt;/p&gt;

&lt;p&gt;Repeat the timeline with a prepared settlement instruction and a cash leg. A token transfer rejection may leave an off-chain payment process running. The architecture record must say whether settlement is atomic, reserved, compensatable or subject to manual resolution. Transfer controls protect the asset movement they govern; they do not automatically govern the neighboring payment system.&lt;/p&gt;

&lt;p&gt;Retain the proposal identifier across the issuer's eligibility record, transaction attempt and settlement state. That connection lets an operator explain a denied movement without reconstructing unrelated logs by hand. If an indexer temporarily falls behind, its cached permission state should not override the authoritative execution result. The user-facing status can remain pending until the system has evidence for a final outcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give Emergency Paths Their Own Policy
&lt;/h2&gt;

&lt;p&gt;A pause capability and a usable emergency process are different deliverables. OpenZeppelin makes one concrete implementation boundary explicit:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This contract does not include public pause and unpause functions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Source: OpenZeppelin, &lt;a href="https://docs.openzeppelin.com/contracts/5.x/api/token/ERC20#ERC20Pausable" rel="noopener noreferrer"&gt;ERC20Pausable documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The documentation requires the inheriting implementation to expose those operations with appropriate access control. The architecture review must then determine who holds that authority and how an operator proves that the action was legitimate. A library feature is only part of the response process.&lt;/p&gt;

&lt;p&gt;Treat recovery, forced movement and policy replacement as distinct paths. The pinned T-REX implementation's &lt;code&gt;forcedTransfer&lt;/code&gt; uses agent authority and recipient verification while bypassing the ordinary &lt;code&gt;canTransfer&lt;/code&gt; check. That source-specific behavior is precisely why an ordinary transfer test cannot validate every administrative movement. The bypass may serve an intended purpose, but its permitted scope needs explicit review.&lt;/p&gt;

&lt;p&gt;For each exception, retain the initiating role, allowed action and resulting state change. Identify which ordinary conditions remain enforced. Then test the exception under pause and after role revocation according to the release's intended behavior. Do not assume that a pause stops every administrative function or that a multisignature alone defines a correct exception policy.&lt;/p&gt;

&lt;p&gt;An upgrade can alter any of these relationships. Review storage and state migration alongside policy semantics: old identities, outstanding certificates, partition labels and pending proposals may remain relevant after code changes. The acceptance record should identify what happens to each of them, especially where a new release recognizes a different authority.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the Selection Reviewable
&lt;/h2&gt;

&lt;p&gt;Write the selection as a short contract between the instrument team and the engineering team. Name the ownership record, the ordinary movement predicate and the permitted exceptions. State the integrations that must remain compatible. Record the selected architecture and the alternatives rejected by a specific operating constraint.&lt;/p&gt;

&lt;p&gt;The release packet should include the exact source commit and deployed code identity, configuration and administrators, policy dependencies and the evidence for each proposed acceptance case. For a signed design, add the signer lifecycle and payload definition. For partitions, add conversion and conservation evidence. For the agreement-ledger design, include the contract packages and the parties' authorization chain.&lt;/p&gt;

&lt;p&gt;Estimate operating work against the same expected workload. Count permission updates, certificates that must be issued and rejected proposals requiring investigation. Include the administrative response when one dependency fails. A local gate may minimize components while multiplying policy updates across instruments. A shared registry may reduce duplication while adding shared governance. Neither observation is a gas or throughput measurement; it identifies work that the project must cost with its own traffic and staffing assumptions.&lt;/p&gt;

&lt;p&gt;Acceptance should also cover a recovery handover. Give a replacement operator the recorded configuration and ask them to explain one successful movement and one rejection without relying on the original developer. Missing policy versions or disconnected approval records will become visible immediately. Preserve the unresolved questions as release blockers rather than filling the gaps with a general statement that the token is compliant.&lt;/p&gt;

&lt;p&gt;Keep source inspection separate from execution. The references here establish mechanisms and implementation boundaries; the hypothetical cases describe what a project should demonstrate. They do not certify any repository, deployment or instrument. The UniversalToken repository is archived, so its pinned implementation is an architectural reference rather than evidence of a maintained production release.&lt;/p&gt;

&lt;p&gt;Choose the smallest design that preserves the instrument's required rejection behavior through normal movement, exception paths and change. If the architecture cannot explain the prepared transfer after eligibility is revoked, postpone selection and resolve that missing contract. That unresolved case is more useful than a feature checklist whose rows all say that compliance is supported.&lt;/p&gt;

&lt;h2&gt;
  
  
  More insights to read
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.bulbapp.io/p/e2580ef7-324b-442d-b818-92b8c8442b3d/five-smart-contract-upgrade-patterns-compared" rel="noopener noreferrer"&gt;Five Smart Contract Upgrade Patterns Compared&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/pharos-production/smart-contracts-their-potential-and-real-limitations-part-2-40942c055d79" rel="noopener noreferrer"&gt;Smart Contracts. Their Potential and Real Limitations. Part 2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/pharos-production/smart-contracts-their-potential-and-real-limitations-part-1-222fe44ee14c" rel="noopener noreferrer"&gt;Smart Contracts. Their Potential and Real Limitations. Part 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dmytronasyrov.medium.com/upgradeable-solidity-smart-contracts-part-1-versioning-7e6e97cafc28" rel="noopener noreferrer"&gt;Upgradeable Solidity Smart Contracts. Part 1 — Versioning&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/pharos-production/web3-smart-contracts-oracles-part-1-3905b127c01d" rel="noopener noreferrer"&gt;Web3. Smart Contracts. Oracles. Part 1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  About the author
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flabtwrvya3qcspprlkbc.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flabtwrvya3qcspprlkbc.jpg" width="112" height="112" alt="Portrait of Dmytro Nasyrov wearing a dark suit and light blue shirt against a dark background."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;small&gt;Dmytro Nasyrov. Photo supplied by the author.&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written by &lt;a href="https://pharosproduction.com/dmytro-nasyrov/" rel="noopener noreferrer"&gt;Dmytro Nasyrov&lt;/a&gt; PhD, software architect with 24 years of production experience. Dmytro is the founder and CTO of Pharos Production. He works on production software architecture for FinTech, AI, Web3 and blockchain systems.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>solidity</category>
      <category>blockchain</category>
      <category>web3</category>
      <category>architecture</category>
    </item>
    <item>
      <title>ERC-3643 vs ERC-1400: How to Choose a Standard for Tokenized Instruments</title>
      <dc:creator>Dmytro Nasyrov</dc:creator>
      <pubDate>Wed, 07 Oct 2026 06:16:16 +0000</pubDate>
      <link>https://dev.to/pharos_production/erc-3643-vs-erc-1400-how-to-choose-a-standard-for-tokenized-instruments-67e</link>
      <guid>https://dev.to/pharos_production/erc-3643-vs-erc-1400-how-to-choose-a-standard-for-tokenized-instruments-67e</guid>
      <description>&lt;p&gt;Choose ERC-3643 when the central requirement is that a token can move only to an eligible investor represented through an on-chain identity system. Evaluate an ERC-1400 implementation when the instrument needs explicit subdivisions of a holder's balance, partition-specific operations or the interfaces in that standards family. If the product needs both, neither label finishes the architecture: select an implementation and prove how its identity and partition policies interact.&lt;/p&gt;

&lt;p&gt;The difficult question is what a particular transaction must preserve. A wallet can hold enough tokens and still be unable to transfer the requested amount. A registered investor can lose eligibility after a transfer preview. A transfer agent can have authority to correct ownership while ordinary transfers remain paused. These are different conditions, with different owners and failure paths. In the example below, 150 units cannot fund a 120-unit transfer when only 100 are transferable.&lt;/p&gt;

&lt;p&gt;For &lt;a href="https://pharosproduction.com" rel="noopener noreferrer"&gt;blockchain engineering at Pharos Production&lt;/a&gt;, the useful starting point is a behavior specification connecting an instrument requirement to an observable transaction outcome. Ask for that artifact before comparing feature counts. An issuer choosing a token standard needs to know which behavior is supplied, which depends on configuration and which needs new engineering.&lt;/p&gt;

&lt;p&gt;This comparison uses the published ERC-3643 specification, the original ERC-1400 family documents and two pinned public implementations. Sources were checked on October 7, 2026. It makes architecture recommendations from those interfaces and source paths; it does not report a production deployment or a comparative security audit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the instrument, not the token number
&lt;/h2&gt;

&lt;p&gt;Write the instrument's rights in language a fund administrator and an engineer can both inspect. Does every unit carry the same economic rights? Can two units in the same wallet have different transfer restrictions? Who decides that a subscription is complete, that a holding period has expired or that ownership needs correction?&lt;/p&gt;

&lt;p&gt;Consider a fund share with one class of economic rights. Investors must be approved before receiving it. Transfers have a concentration limit. Lost-key recovery is an operational requirement. Here, investor identity and transfer controls are the primary engineering problem. ERC-3643 is a natural candidate because its interfaces directly describe the identity registries and administrative operations that this workflow needs.&lt;/p&gt;

&lt;p&gt;Now consider an instrument whose units are divided into issuance lots with different release conditions. A holder can own both transferable and restricted units. An intermediary must specify which lot it is moving and retain that information in its records. Partition-aware operations deserve a place in the evaluation. Whether those partitions represent lots, lockups or something else is an issuer-approved design decision.&lt;/p&gt;

&lt;p&gt;Different economic rights need further scrutiny. Separate share classes might require separate contracts, distinct accounting or a carefully specified partition model. The ability to assign a partition key does not establish the legal meaning of that key. Nor does a single ERC-20 balance demonstrate that all the units it aggregates are interchangeable for settlement.&lt;/p&gt;

&lt;p&gt;The selection worksheet should therefore contain four fields: the unit's rights, the state that restricts movement, the actor authorized to change that state and the evidence retained after a change. A standard is useful when its interfaces fit these requirements without hiding a material distinction in an application database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compare a final specification with a standards family
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://eips.ethereum.org/EIPS/eip-3643" rel="noopener noreferrer"&gt;ERC-3643&lt;/a&gt; is marked Final in the Ethereum Improvement Proposals repository. It defines token, identity registry, identity storage, compliance, trusted issuer registry and claim topic registry interfaces. It requires ERC-20 compatibility and an on-chain identity system. It also describes recovery, freezes, pause, minting, burning and agent authority.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/SecurityTokenStandard/EIP-Spec/blob/8b082caba8adb4f8e351aa6acdd3e9e394e09dea/eip/eip-1400.md" rel="noopener noreferrer"&gt;original ERC-1400 document&lt;/a&gt; describes an umbrella of related interfaces. Its retained front matter says Draft. One of its authors is Fabian Vogelsteller, also associated with the development of Ethereum token interfaces. The authors describe its scope in one short sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Represents a library of standards for security tokens on Ethereum.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That sentence matters in procurement. ERC-1400 on a slide does not identify the exact interface set, restriction system or administrative behavior delivered by a vendor. Ask which members of the family are implemented and which extensions are proprietary.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Family document&lt;/th&gt;
&lt;th&gt;Engineering job&lt;/th&gt;
&lt;th&gt;What still needs a design decision&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ERC-1410&lt;/td&gt;
&lt;td&gt;Partition balances and partition operations&lt;/td&gt;
&lt;td&gt;Meaning of a partition and permitted transitions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ERC-1594&lt;/td&gt;
&lt;td&gt;Transfer validity queries, attached data, issuance and redemption&lt;/td&gt;
&lt;td&gt;Policy source, authorization data and failure handling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ERC-1643&lt;/td&gt;
&lt;td&gt;Document references and updates&lt;/td&gt;
&lt;td&gt;Document governance, storage and investor access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ERC-1644&lt;/td&gt;
&lt;td&gt;Declared controller operations&lt;/td&gt;
&lt;td&gt;Controller authorization and correction procedure&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A Final status is a specification maturity signal. It is not evidence that a deployed configuration has been audited, that an upgrade is safe or that a venue supports the token. Conversely, a draft-family implementation may have useful production-oriented features. Evaluate the repository, deployment and operating model separately from the status of the document.&lt;/p&gt;

&lt;h2&gt;
  
  
  ERC-3643 makes investor eligibility an explicit dependency
&lt;/h2&gt;

&lt;p&gt;ERC-3643 connects a receiving wallet to an identity record and checks the required claims against trusted issuers. The claim topics define what must be established; the trusted issuer registry defines whose attestations are accepted. The token's compliance component handles offering-level rules, such as concentration or holder-count restrictions, separately from identity verification.&lt;/p&gt;

&lt;p&gt;This separation helps diagnose a rejected transfer. The recipient might be unregistered, lack a required claim or fail a token-level rule despite having a valid identity. A frozen wallet or insufficient unfrozen balance is another reason to reject movement. A front end that collapses every case into an eligibility error sends the operator to the wrong system.&lt;/p&gt;

&lt;p&gt;The specification's ordinary transfer conditions include recipient verification, wallet freeze checks, free balance, pause state and the compliance result. Do not expand that into a promise that every entry point checks every condition. Administrative operations have different semantics, and the selected implementation must be inspected individually.&lt;/p&gt;

&lt;p&gt;The eligibility owner needs a response procedure as well as contract permissions. If an onboarding provider stops serving an attestation, identify who investigates, who may replace the evidence and how pending instructions are handled. A registry administrator should not make an undocumented exception simply to clear a settlement queue. Keep the rejected instruction linked to its policy evidence so that a subsequent approved retry can be explained.&lt;/p&gt;

&lt;p&gt;Identity freshness also needs an operating policy. Removing an accepted issuer, revoking an attestation or changing a claim requirement can affect future receipts. It does not necessarily remove an existing balance. Decide whether an investor who becomes ineligible may continue holding, redeem, transfer out through a restricted route or require an administrative action. Then encode and test the approved behavior.&lt;/p&gt;

&lt;p&gt;A wallet-to-identity relationship also needs a custody model. For an omnibus custodian, the contract may identify the custodian wallet while the beneficial-owner allocation remains in its ledger. That is a different visibility boundary from individually registered investor wallets. Neither model becomes correct merely because a registry call returns true.&lt;/p&gt;

&lt;p&gt;Avoid putting sensitive onboarding documents into public transaction data to simplify integration. The token system needs verifiable eligibility evidence and controlled links to the off-chain record. The location of personal information, access permissions and correction process should be explicit parts of the identity design.&lt;/p&gt;

&lt;h2&gt;
  
  
  ERC-1400 partitions expose which balance is moving
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/SecurityTokenStandard/EIP-Spec/blob/8b082caba8adb4f8e351aa6acdd3e9e394e09dea/eip/eip-1410.md" rel="noopener noreferrer"&gt;ERC-1410&lt;/a&gt; describes balances grouped under partition keys while retaining aggregate owner balances and supply information. Operations can address a particular partition. This gives an integration a way to distinguish parts of a holding that would disappear into a single aggregate balance.&lt;/p&gt;

&lt;p&gt;A partition is a label and associated balance in the interface. Its restrictions come from the contract's policy. Naming a partition LOCKED does not itself create a time lock. A partition named AVAILABLE does not prove that the recipient is eligible. The implementation must enforce the corresponding conditions through its transfer path and extensions.&lt;/p&gt;

&lt;p&gt;The pinned &lt;a href="https://github.com/Consensys/UniversalToken/tree/54320c6f7a8ee1fd7fcb19073e9c122e1e8f96f9" rel="noopener noreferrer"&gt;Consensys UniversalToken implementation&lt;/a&gt; exposes partition transfers and operator transfers. Its transfer path invokes extensions and maintains partition balances. Its README discusses both signed transfer certificates and on-chain lists of validated investors. These are concrete policy mechanisms in that implementation, not proof that every ERC-1400 token uses the same identity architecture.&lt;/p&gt;

&lt;p&gt;A certificate-based design can attach authorization to an intended transaction. It introduces an issuer or policy service that must remain available and issue the correct authorization. A list-based design moves an eligibility lookup on-chain but needs transactions to keep that state current. An engineering choice between them must account for revocation latency, replay protection and the integration's ability to supply the required data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl63emu5ro1vc5c8nezkz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl63emu5ro1vc5c8nezkz.png" alt="ERC-3643 organizes eligibility through identity and compliance dependencies; an ERC-1400 implementation can organize balances by partition and enforce policy through transfer extensions. Both require an explicit authority model." width="800" height="560"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Partition behavior also reaches the user interface. A wallet displaying 150 units should distinguish transferable units from restricted units when that difference matters. Custody reports need the partition balances, and a settlement instruction needs an unambiguous source partition. Otherwise the application promises liquidity using an aggregate number that the transfer path cannot spend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use disqualifiers before assigning scores
&lt;/h2&gt;

&lt;p&gt;A weighted feature score can hide a missing requirement. A candidate with a polished dashboard might score well while lacking the operation an approved instrument needs. Set the rejection conditions first, then compare engineering and operational costs among the remaining candidates.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;th&gt;ERC-3643 candidate&lt;/th&gt;
&lt;th&gt;ERC-1400 candidate&lt;/th&gt;
&lt;th&gt;Reject the candidate when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Reusable investor eligibility&lt;/td&gt;
&lt;td&gt;Explicit identity and claim registries&lt;/td&gt;
&lt;td&gt;Inspect the chosen list, certificate or identity extension&lt;/td&gt;
&lt;td&gt;Eligibility updates cannot be explained or tested&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Different restrictions inside one holding&lt;/td&gt;
&lt;td&gt;Additional lot or state model may be needed&lt;/td&gt;
&lt;td&gt;Evaluate partition-aware operations&lt;/td&gt;
&lt;td&gt;Required distinctions exist only in a display label&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Explain a refused transfer&lt;/td&gt;
&lt;td&gt;Combine identity, compliance and token-state diagnostics&lt;/td&gt;
&lt;td&gt;Inspect validity queries and implementation reason codes&lt;/td&gt;
&lt;td&gt;Preview contradicts execution without a diagnosable change&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Administrative correction&lt;/td&gt;
&lt;td&gt;Agent and recovery operations are specified&lt;/td&gt;
&lt;td&gt;Inspect controller and operator capabilities&lt;/td&gt;
&lt;td&gt;Authority or bypass behavior is undocumented&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Documents tied to an instrument&lt;/td&gt;
&lt;td&gt;Define the document integration separately&lt;/td&gt;
&lt;td&gt;Evaluate the ERC-1643 interface and its implementation&lt;/td&gt;
&lt;td&gt;Updated terms cannot be retrieved and reconciled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Existing custody or venue integration&lt;/td&gt;
&lt;td&gt;Prove restricted ERC-20 operations work&lt;/td&gt;
&lt;td&gt;Prove aggregate and partition operations work&lt;/td&gt;
&lt;td&gt;Required calls or events are unsupported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Upgrades and policy changes&lt;/td&gt;
&lt;td&gt;Inspect registries, modules and deployment authority&lt;/td&gt;
&lt;td&gt;Inspect extensions, partition semantics and deployment authority&lt;/td&gt;
&lt;td&gt;A change can silently reinterpret outstanding holdings&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Pharos Production's &lt;a href="https://pharosproduction.com/services/tokenization-and-rwa-development/" rel="noopener noreferrer"&gt;tokenization and RWA development process&lt;/a&gt; describes working alongside legal counsel to map asset ownership rights to token mechanics. That mapping addresses the central selection problem: engineering needs approved rights and restrictions before choosing a contract model. Ask for a requirement-to-operation matrix as the handoff, with legal assumptions distinguished from demonstrated software behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Walk one holding through both models
&lt;/h2&gt;

&lt;p&gt;Use an illustrative holding of 150 units: 100 currently transferable and 50 restricted. The holder requests a transfer of 120 units to an eligible recipient. Assume the token is not paused and both wallets satisfy the applicable conditions. These numbers are a design example, not measurements from a deployed system.&lt;/p&gt;

&lt;p&gt;An ERC-3643 design could represent the restriction as 50 partially frozen tokens. The ordinary free balance would be 100, so the request should fail even though the aggregate balance is 150. Whether partial freezing is the right representation depends on the reason for the restriction and who may release it. Freezing is an administrative mechanism; it does not automatically implement the instrument's contractual release schedule.&lt;/p&gt;

&lt;p&gt;An ERC-1400 design could represent 100 units in an available partition and 50 in a restricted partition. A request for 120 from the available partition should fail because that partition contains only 100. If the aggregate ERC-20 transfer route can consume multiple partitions, its default selection order and restriction checks must be part of the design. Do not assume the aggregate route behaves like the partition-specific route.&lt;/p&gt;

&lt;p&gt;Reduce the request to 40. After a successful transfer that preserves the partition, the sender would have 60 available and 50 restricted; the recipient would receive 40 available. In the partial-freeze model, the sender would have 110 total and 50 frozen, leaving 60 free. Those arithmetic outcomes look similar, but the retained information differs: partition identifiers can carry lot distinctions that one frozen subtotal cannot express by itself.&lt;/p&gt;

&lt;p&gt;Now revoke the recipient's eligibility after preview but before execution. Execution must use the applicable current policy; a successful earlier query is not a reservation of eligibility. If the transaction is rejected, balances and associated accounting must remain consistent. If the approved model permits the movement, the record needs to explain which policy and authorization allowed it.&lt;/p&gt;

&lt;p&gt;Finally, let the restriction expire. In the freeze model, determine how the frozen quantity is released. In the partition model, determine whether units remain in the same partition with a changed rule or move to another partition. A wall-clock date in a database cannot substitute for an on-chain transition when the contract still blocks the transfer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preflight is a snapshot, not a settlement promise
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/SecurityTokenStandard/EIP-Spec/blob/8b082caba8adb4f8e351aa6acdd3e9e394e09dea/eip/eip-1594.md" rel="noopener noreferrer"&gt;ERC-1594&lt;/a&gt; describes transfer validity queries with error signalling and attached data. Use the exact return values of the implemented interface to build an actionable response. A recipient rejection should lead to an eligibility investigation; a balance rejection should lead to a position check. A failed authorization certificate requires a different operator than either of those.&lt;/p&gt;

&lt;p&gt;For ERC-3643, inspecting the compliance result alone leaves out other token conditions. The application needs the relevant identity, freeze, pause and balance checks, with a clear warning when its diagnostic view is incomplete. A callback or external dependency can also fail during execution even if an earlier policy query succeeded.&lt;/p&gt;

&lt;p&gt;Store the block identifier used for preview and the instruction parameters. If execution fails later, compare the state and policy changes between the preview and execution blocks. Recomputing against the latest state without preserving the original observation can conceal why the user saw an apparently valid instruction. The user-facing result should name the rejected operation and the next permitted action, without promising that retrying will succeed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Administrative paths need their own contract
&lt;/h2&gt;

&lt;p&gt;Forced transfers, wallet recovery and redemption are part of the operating model. They must have an authority policy that explains who can invoke them and what ordinary restrictions they bypass. A multisig threshold alone cannot explain whether its signers are allowed to perform a particular correction.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/TokenySolutions/T-REX/blob/f1390127d4270e7f31764c068caf0a1526b852d4/contracts/token/Token.sol" rel="noopener noreferrer"&gt;pinned T-REX token source&lt;/a&gt; illustrates why path-specific review matters. Its forced transfer requires agent authority and verifies the recipient. It can release enough frozen tokens to complete the movement and does not call the ordinary compliance pre-check. It does call the post-transfer compliance callback. Those details matter to a module that maintains holder counts or position limits.&lt;/p&gt;

&lt;p&gt;The same source's mint path checks recipient verification and the compliance pre-check. This differs from the published specification's explanatory description of mint bypassing compliance rules. Treat the pinned code as the behavior being evaluated and record the discrepancy. A standards summary is not a substitute for examining the deployed entry point.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/SecurityTokenStandard/EIP-Spec/blob/8b082caba8adb4f8e351aa6acdd3e9e394e09dea/eip/eip-1644.md" rel="noopener noreferrer"&gt;ERC-1644&lt;/a&gt; describes controller operations and transparency about whether unilateral transfers are possible. A candidate implementation must still demonstrate its exact controller configuration, events and interaction with extensions. Never infer that a controller has the same permissions as an ERC-3643 agent.&lt;/p&gt;

&lt;p&gt;For each exceptional path, retain the business authorization, transaction receipt and before-and-after ownership state. A correction transfers ownership in the current ledger; it does not erase the historical transaction. Recovery should preserve the instrument's supply and relevant restrictions while making the old wallet's future role explicit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integration includes settlement and document state
&lt;/h2&gt;

&lt;p&gt;ERC-20 compatibility provides familiar calls such as balance and allowance queries. It does not mean that an arbitrary exchange, bridge or escrow contract is an eligible holder. A settlement contract can satisfy the ABI and still fail a recipient eligibility check. Partition-aware integrations additionally need to preserve the intended partition through the complete settlement route.&lt;/p&gt;

&lt;p&gt;Test with the actual custody and venue workflow. Does the intermediary support attached transfer data? Can it authorize an operator? Does it display a failure reason to the correct actor? Can its indexer distinguish an ordinary transfer from an administrative movement and reconstruct the relevant balances after a restart?&lt;/p&gt;

&lt;p&gt;Delivery versus payment adds a second asset and a settlement boundary. Token eligibility is one condition; cash availability and atomic execution are separate conditions. If policy changes between instruction creation and settlement, the system needs a defined rejection or cancellation result. A selected security-token standard does not make those conditions atomic by itself.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/SecurityTokenStandard/EIP-Spec/blob/8b082caba8adb4f8e351aa6acdd3e9e394e09dea/eip/eip-1643.md" rel="noopener noreferrer"&gt;ERC-1643&lt;/a&gt; supplies document references and update events. A URI and content hash can help connect a contract to a particular document. They do not establish that every investor received the document or accepted amended terms. Retain the availability and acknowledgement process separately, with versioned mappings to instrument state.&lt;/p&gt;

&lt;p&gt;Redemption also crosses systems. Burning tokens may reduce on-chain supply before an off-chain payment completes. Define whether the workflow escrows units, burns after payment or uses another approved state machine. Reconcile outstanding claims and instrument supply against the administrator's records rather than treating a burn event as proof that cash was delivered.&lt;/p&gt;

&lt;h2&gt;
  
  
  Require a reproducible selection record
&lt;/h2&gt;

&lt;p&gt;Request the same acceptance cases from each candidate, scoped to the approved instrument. Successful transfers alone are weak evidence. Rejected and exceptional operations reveal whether restrictions remain intact when actors or state change.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Acceptance case&lt;/th&gt;
&lt;th&gt;Evidence to retain&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Recipient eligibility changes after preview&lt;/td&gt;
&lt;td&gt;Policy change, execution result and unchanged balances on rejection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aggregate balance exceeds spendable balance&lt;/td&gt;
&lt;td&gt;Partition or frozen-state snapshot and rejected request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Operator authorization is removed&lt;/td&gt;
&lt;td&gt;Revocation transaction and subsequent unauthorized-call rejection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Administrative correction while ordinary transfers are paused&lt;/td&gt;
&lt;td&gt;Approved bypass policy, exact path and resulting events&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Required attestation issuer is removed&lt;/td&gt;
&lt;td&gt;Registry state and expected receipt behavior&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redemption fails in the payment system&lt;/td&gt;
&lt;td&gt;Token state, cash state and reconciliation outcome&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Policy module or extension changes&lt;/td&gt;
&lt;td&gt;Old and new configuration plus affected-case results&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are proposed acceptance cases, not a claim that this article executed a conformance suite. Each implementation should provide its own reproducible fixtures, transaction inputs and expected outcomes. Tests using identity stubs need to say which signature, expiry or revocation behavior they do not cover.&lt;/p&gt;

&lt;p&gt;Before release, bind the results to the source commit, compiler configuration, dependency versions and deployed addresses. Include the active identity registries or extensions, privileged-role holders and implementation or proxy relationships. Pinning source makes the comparison reproducible; it does not identify an audited release without matching audit evidence.&lt;/p&gt;

&lt;p&gt;The same record needs an operational owner. Name who maintains the investor registry, reconciles settlement exceptions and reviews privileged changes. For certificates, include signer rotation and incident handling. For partitions, include the dictionary of keys and the procedure for introducing a new one. If these jobs belong to different organizations, make their handoffs visible in the acceptance evidence rather than assigning them implicitly to the token developer.&lt;/p&gt;

&lt;p&gt;An upgrade needs a before-and-after ownership check that includes restrictions. Equal total supply can hide a lost freeze, a reinterpreted partition or an eligibility registry pointing at the wrong implementation. Rehearse ordinary transfers and exceptional paths against representative outstanding holdings, then reconcile the resulting ownership records.&lt;/p&gt;

&lt;p&gt;Record the choice in one sentence with a falsifiable reason. For example: select an ERC-3643 implementation because the instrument has one fungible share class and requires claim-based recipient eligibility plus wallet recovery. Or select the evaluated ERC-1400 implementation because issuance lots must remain explicit through partition-aware custody and settlement. Attach the acceptance evidence that makes the reason true.&lt;/p&gt;

&lt;p&gt;When both identities and partitions are essential, evaluate their composition as a separate design obligation. Specify where eligibility is enforced, how restrictions survive partition transitions and which authority may change either rule. The next engineering deliverable is the behavior matrix and reproducible transaction evidence for that exact composition.&lt;/p&gt;

&lt;h2&gt;
  
  
  More insights to read
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.bulbapp.io/p/e2580ef7-324b-442d-b818-92b8c8442b3d/five-smart-contract-upgrade-patterns-compared" rel="noopener noreferrer"&gt;Five Smart Contract Upgrade Patterns Compared&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/pharos-production/smart-contracts-their-potential-and-real-limitations-part-2-40942c055d79" rel="noopener noreferrer"&gt;Smart Contracts. Their Potential and Real Limitations. Part 2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/pharos-production/smart-contracts-their-potential-and-real-limitations-part-1-222fe44ee14c" rel="noopener noreferrer"&gt;Smart Contracts. Their Potential and Real Limitations. Part 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dmytronasyrov.medium.com/upgradeable-solidity-smart-contracts-part-1-versioning-7e6e97cafc28" rel="noopener noreferrer"&gt;Upgradeable Solidity Smart Contracts. Part 1 — Versioning&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/pharos-production/web3-smart-contracts-oracles-part-1-3905b127c01d" rel="noopener noreferrer"&gt;Web3. Smart Contracts. Oracles. Part 1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  About the author
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foclejrac8sjqi79uy7hm.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foclejrac8sjqi79uy7hm.jpg" width="112" height="112" alt="Portrait of Dmytro Nasyrov wearing a dark suit and light blue shirt against a dark background."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;small&gt;Dmytro Nasyrov. Photo supplied by the author.&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written by &lt;a href="https://pharosproduction.com/dmytro-nasyrov/" rel="noopener noreferrer"&gt;Dmytro Nasyrov&lt;/a&gt; PhD, software architect with 24 years of production experience. Dmytro is the founder and CTO of Pharos Production. He works on production software architecture for FinTech, AI, Web3 and blockchain systems.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>solidity</category>
      <category>blockchain</category>
      <category>web3</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Omnibus vs Segregated Wallets: The Ledger Decides</title>
      <dc:creator>Dmytro Nasyrov</dc:creator>
      <pubDate>Thu, 01 Oct 2026 05:59:05 +0000</pubDate>
      <link>https://dev.to/dmytronasyrov/omnibus-vs-segregated-wallets-the-ledger-decides-3fnb</link>
      <guid>https://dev.to/dmytronasyrov/omnibus-vs-segregated-wallets-the-ledger-decides-3fnb</guid>
      <description>&lt;p&gt;An omnibus wallet and a set of segregated wallets can hold exactly the same assets while supporting different reconciliation claims. The difference appears when a customer balance changes without a corresponding movement between customer wallets. A pool can still match total liabilities. Dedicated wallets can match that total while backing the wrong customers.&lt;/p&gt;

&lt;p&gt;Choose the model by the invariant your ledger must prove. For pooled assets, prove aggregate backing and separately validate customer allocations. For strict segregation, also prove that each customer's assigned wallets match that customer's entitlement. A wallet label cannot establish either result.&lt;/p&gt;

&lt;p&gt;The required evidence differs.&lt;/p&gt;

&lt;p&gt;The example below uses Alice, Bob and 100 synthetic asset units. A complete Python snapshot checker and twelve tests expose an aggregate check that incorrectly approves a segregated allocation. The fixture makes no calls to a blockchain or custody provider.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate the wallet, the ledger and the authority
&lt;/h2&gt;

&lt;p&gt;A wallet describes an operational arrangement for holding and moving assets. A customer ledger describes entitlements, reservations and postings. An authority model describes who can approve or sign a movement. These are separate design decisions, even when a provider presents them together in one interface. In a pooled arrangement, several customers' entitlements are backed by assets held in shared wallets. The application needs a private ledger to assign those entitlements. In a segregated arrangement, identified wallets or vault accounts are assigned to individual customers. The ledger still needs to describe spendable balances, pending operations and corrections. The address does not explain those states by itself.&lt;/p&gt;

&lt;p&gt;Fireblocks documents these operational models in &lt;a href="https://fireblocks.readme.io/docs/create-direct-custody-wallets" rel="noopener noreferrer"&gt;Create Direct Custody Wallets&lt;/a&gt;. Its description distinguishes pooled vault arrangements from individual customer vaults and treats hot or cold storage as another dimension. That distinction matters: segregation does not automatically mean offline signing, and a hot wallet does not automatically mean pooled customer accounting.&lt;/p&gt;

&lt;p&gt;The same documentation explains the application-side mapping for a deposit address:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This deposit address is assigned to Alice and mapped accordingly within the customer’s private ledger.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Fireblocks, &lt;em&gt;&lt;a href="https://fireblocks.readme.io/docs/create-direct-custody-wallets" rel="noopener noreferrer"&gt;Create Direct Custody Wallets&lt;/a&gt;&lt;/em&gt;, official developer documentation, accessed October 1, 2026. The page does not state an exact publication date.&lt;/p&gt;

&lt;p&gt;The quoted sentence identifies the missing join: an observed address must be connected to a customer in the ledger. It does not claim that a deposit address proves legal ownership or that every asset uses a separate address for each customer.&lt;/p&gt;

&lt;p&gt;This separation also guides the &lt;a href="https://pharosproduction.com" rel="noopener noreferrer"&gt;blockchain engineering work at Pharos Production&lt;/a&gt;: selecting wallet infrastructure is only one part of specifying how the surrounding application records financial state. The engineering decision needs an entitlement model and a recovery boundary before an address diagram can serve as an acceptance artifact.&lt;/p&gt;

&lt;p&gt;Product terminology deserves a second check. Coinbase describes &lt;a href="https://help.coinbase.com/en/prime/prime-custody/introduction" rel="noopener noreferrer"&gt;Prime Custody&lt;/a&gt; as an omnibus arrangement using its LSOC terminology, while Prime Vault uses dedicated addresses. Those are statements about named products, not evidence that any architecture called segregated has the same contractual treatment. Obtain the applicable custody agreement separately. Neither this article nor its test fixture determines legal title, insolvency protection or the adequacy of a provider's controls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compare the evidence each model needs
&lt;/h2&gt;

&lt;p&gt;The useful comparison is between operating requirements. A diagram with one box per customer may be easier to explain, but its proof obligations can be more demanding when the product permits transfers between customers.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Decision dimension&lt;/th&gt;
&lt;th&gt;Omnibus arrangement&lt;/th&gt;
&lt;th&gt;Segregated arrangement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Customer entitlement&lt;/td&gt;
&lt;td&gt;Requires a ledger allocation inside the shared asset pool&lt;/td&gt;
&lt;td&gt;Requires a ledger allocation and an explicit customer-to-wallet assignment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backing check&lt;/td&gt;
&lt;td&gt;Compare included pool assets with included customer liabilities&lt;/td&gt;
&lt;td&gt;Compare the aggregate and every customer's assigned assets with their liabilities&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Internal customer transfer&lt;/td&gt;
&lt;td&gt;Can change ledger allocations while the pool remains unchanged&lt;/td&gt;
&lt;td&gt;Needs an explicit policy for pending allocation or corresponding wallet movement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deposit handling&lt;/td&gt;
&lt;td&gt;Attribution must survive any sweep into a shared wallet&lt;/td&gt;
&lt;td&gt;Attribution must survive wallet creation, reassignment and recovery&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Operating constraint&lt;/td&gt;
&lt;td&gt;Shared movements obscure customer allocation on-chain&lt;/td&gt;
&lt;td&gt;More customer wallet movements can increase transaction and monitoring work&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disqualifier&lt;/td&gt;
&lt;td&gt;Cannot satisfy a requirement for exclusive customer wallet backing&lt;/td&gt;
&lt;td&gt;Cannot claim strict backing when allocations move ahead of assigned assets&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These rows are design consequences of the definitions used here, not a provider performance benchmark. There is no universal winner. Pooling may fit a product whose customer transfers are internal ledger operations. Dedicated wallets may fit a product that requires independently inspectable wallet assignments. Both remain dependent on complete asset inventory and correct postings.&lt;/p&gt;

&lt;p&gt;Define the acceptance language before comparing suppliers. If the requirement says customer assets remain identifiable, ask whether that means ledger identification, dedicated wallet assignment, a contractual arrangement, or all three. Each interpretation needs different evidence. A vendor showing balances by customer in a dashboard has demonstrated a view. It has not demonstrated the provenance of those balances.&lt;/p&gt;

&lt;p&gt;Also specify the accounting perimeter. A customer asset pool, the operator's own funds, a fee wallet and assets in transit must not be silently added together. A mixed dashboard total can hide an excluded liability or compensate for a shortfall with funds outside the agreed perimeter. Keep the comparison per asset, per network and per cutoff before considering any converted reporting total.&lt;/p&gt;

&lt;h2&gt;
  
  
  A balanced total can hide the wrong allocation
&lt;/h2&gt;

&lt;p&gt;Start with one synthetic asset. Alice has an entitlement of 60 units; Bob has 40. Total customer liabilities are 100. In the omnibus case, the pool contains 100. In the segregated case, Alice's assigned wallet contains 60 and Bob's contains 40.&lt;/p&gt;

&lt;p&gt;Now record a transfer of 15 units from Alice to Bob in the customer ledger. The new entitlements are 45 and 55. Total liabilities are still 100. The following table isolates the wallet movement from the ledger posting:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Snapshot&lt;/th&gt;
&lt;th&gt;Alice's ledger&lt;/th&gt;
&lt;th&gt;Bob's ledger&lt;/th&gt;
&lt;th&gt;Omnibus pool&lt;/th&gt;
&lt;th&gt;Segregated Alice/Bob wallets&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Opening&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;60 / 40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Internal entitlement transfer recorded&lt;/td&gt;
&lt;td&gt;45&lt;/td&gt;
&lt;td&gt;55&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;60 / 40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Corresponding segregated movement completed&lt;/td&gt;
&lt;td&gt;45&lt;/td&gt;
&lt;td&gt;55&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;45 / 55&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;An aggregate checker reports zero difference in every row. For the middle row, the segregated checker must report Alice's assigned assets minus entitlement as +15, and Bob's as −15. The aggregate result is correct. The conclusion that segregation is reconciled is incorrect.&lt;/p&gt;

&lt;p&gt;There are two coherent ways to handle that middle state. A strict settled-balance policy can keep the customer transfer pending until the required wallet movement completes. A more complex policy can account explicitly for an allocation bridge or receivable. The second option needs its own permitted states, aging rules and reconciliation equations. It cannot borrow the strict model's passing label while omitting the bridge from the report.&lt;/p&gt;

&lt;p&gt;The simple checker below deliberately implements the strict snapshot interpretation. It has no bridge account and no transaction state machine. Its rejection means the dedicated wallet amounts do not match the supplied settled entitlements. It does not mean that every temporary mismatch is fraud, loss or an invalid business operation.&lt;/p&gt;

&lt;p&gt;For the omnibus row, aggregate equality establishes only that included pool assets equal included customer liabilities. Alice could still be credited incorrectly, or Bob could receive a duplicated posting, while another posting offsets the error. Customer allocation correctness therefore needs independent journal and authorization checks. A pooled report should state that owner backing was not checked, rather than display an empty exceptions list that resembles a successful customer-level check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run the snapshot witness and the regression
&lt;/h2&gt;

&lt;p&gt;The aggregate equation is included wallet assets minus recognized customer liabilities. The strict segregated equation repeats that subtraction for each assigned customer. Both must equal zero for this fixture. An excess is a discrepancy too: it may represent an unattributed deposit, an omitted customer liability or funds outside the declared perimeter. The checker does not classify the cause from the sign alone.&lt;/p&gt;

&lt;p&gt;Notice that matching every owner implies the aggregate matches only when the inventory and owner mapping cover the same population. Retaining both outputs makes the declared population inspectable and exposes adapter mistakes. The function rejects an assigned owner absent from the position map instead of quietly excluding that wallet from the owner calculation.&lt;/p&gt;

&lt;p&gt;Save the next block as &lt;code&gt;reconciliation.py&lt;/code&gt;. Amounts are integer base units. &lt;code&gt;Position.total&lt;/code&gt; includes reserved units. Reservations reduce availability, not the liability already represented by the total. A wallet row is one included balance observation, identified by a unique name within this materialized list.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;reserved&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Wallet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;reconcile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wallets&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Pure snapshot witness. Inputs are trusted observations, not fetched here.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;omnibus&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;segregated&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unknown model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reserved&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
                &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reserved&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invalid position&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;seen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;owner_assets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;wallets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;duplicate wallet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;asset&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;asset mismatch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cutoff mismatch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invalid wallet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;segregated&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;owner&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;owner mismatch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;owner_assets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;owner&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;owner mismatch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;assets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;wallets&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;liabilities&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;owner_deltas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;owner_assets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;
                     &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;segregated&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
    &lt;span class="n"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;assets&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;liabilities&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;asset_total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;assets&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;liability_total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;liabilities&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;available_total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reserved&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aggregate_delta&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;owner_deltas&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;owner_deltas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;owner_checked&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;segregated&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reconciled&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;owner_deltas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;())}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save the following as &lt;code&gt;test_reconciliation.py&lt;/code&gt; in the same directory. The asset identifier and block label are synthetic constants. The tests exercise distinct outcomes and refusal classes rather than every possible combination of inputs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;unittest&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;reconciliation&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Wallet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reconcile&lt;/span&gt;

&lt;span class="n"&gt;ASSET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chain-a:token-x&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# Synthetic asset identity, not a live token.
&lt;/span&gt;&lt;span class="n"&gt;BLOCK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;block-hash-100&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;   &lt;span class="c1"&gt;# Synthetic named cutoff, not a live block.
&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReconciliationTests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unittest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bob&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;asset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ASSET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;BLOCK&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Wallet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wallets&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;reconcile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wallets&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;asset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ASSET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;BLOCK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_omnibus_total_matches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;omnibus&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pool&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reconciled&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertFalse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;owner_checked&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;owner_deltas&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_segregated_allocation_matches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;segregated&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bob&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reconciled&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;owner_deltas&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bob&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_balanced_total_wrong_owner_fails&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;positions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bob&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;55&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
        &lt;span class="n"&gt;report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;segregated&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bob&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aggregate_delta&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertFalse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reconciled&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;owner_deltas&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bob&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_reserved_is_already_in_total&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;positions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bob&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;55&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
        &lt;span class="n"&gt;report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;omnibus&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pool&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;liability_total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;available_total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reconciled&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_asset_shortfall_is_visible&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;omnibus&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pool&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aggregate_delta&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertFalse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reconciled&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_duplicate_wallet_rejected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertRaisesRegex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;duplicate wallet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;omnibus&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pool&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_mixed_asset_rejected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertRaisesRegex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;asset mismatch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;omnibus&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pool&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;asset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chain-b:token-x&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_mixed_cutoff_rejected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertRaisesRegex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cutoff mismatch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;omnibus&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pool&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;block-hash-101&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_invalid_reservation_rejected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertRaisesRegex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invalid position&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;omnibus&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;)},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pool&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_unknown_owner_rejected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertRaisesRegex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;owner mismatch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;segregated&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pool&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;carol&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_unknown_model_rejected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertRaisesRegex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unknown model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unreviewed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pool&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_invalid_wallet_units_rejected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertRaisesRegex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invalid wallet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;omnibus&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pool&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;unittest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run both files with &lt;a href="https://docs.python.org/3.10/whatsnew/3.10.html" rel="noopener noreferrer"&gt;Python 3.10 or later&lt;/a&gt;, which supports the union annotation used above. The recorded local run used Python 3.14.7. The &lt;a href="https://docs.python.org/3/library/unittest.html" rel="noopener noreferrer"&gt;Python unittest documentation&lt;/a&gt; describes the command and assertions used here.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; unittest &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The regression was first executed against an incomplete aggregate-only version. That version returned &lt;code&gt;reconciled=True&lt;/code&gt; for the middle table row, causing &lt;code&gt;test_balanced_total_wrong_owner_fails&lt;/code&gt; to fail with &lt;code&gt;AssertionError: True is not false&lt;/code&gt;. The correction accumulated assets by assigned owner and required every owner difference to be zero. The same regression then passed, followed by all twelve tests.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbysvzr2ry85ggsszbeyw.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbysvzr2ry85ggsszbeyw.gif" alt="Recorded test-output replay: the wrong-owner regression fails against an aggregate-only checker, the correction adds per-owner reconciliation, and the complete twelve-test suite passes." width="799" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;small&gt;Replay of recorded local test output and the recorded correction. This is a synthetic fixture, not a terminal screen recording or a live custody-system test.&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;The report preserves the distinction between a numerical discrepancy and invalid input. A shortfall produces a report with &lt;code&gt;reconciled=False&lt;/code&gt;. Mixed assets, mixed cutoffs, duplicate wallet rows and unknown assigned owners raise errors. A production adapter needs similarly explicit handling, but this function does not establish that its input inventory is complete or authentic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reservations and withdrawals need a posting policy
&lt;/h2&gt;

&lt;p&gt;Reserve 20 units for Bob after the transfer, when Alice has 45 and Bob has 55. Bob's available balance becomes 35. Total customer liabilities remain 100, and aggregate available balances become 80. Adding the reservation to the liability total again would report 120 against assets of 100, manufacturing a shortfall.&lt;/p&gt;

&lt;p&gt;The reservation test checks this classification only. It does not execute a withdrawal. In a zero-fee illustrative withdrawal that completes for 20 units, assets would fall to 80 and customer liabilities would fall to 80: Alice 45, Bob 35. While the withdrawal is merely reserved, the supplied snapshot still contains the original 100 of liabilities. The accounting transition needs a defined settlement event.&lt;/p&gt;

&lt;p&gt;Pharos Production addresses the upstream design choice in its &lt;a href="https://pharosproduction.com/services/crypto-wallet-development/" rel="noopener noreferrer"&gt;crypto wallet development process&lt;/a&gt;, which includes custody-model decisions, threat modeling and recovery design. Those decisions should establish which component may reserve funds, approve a withdrawal and recognize completion. The service description is evidence of that stated process. It is not evidence that this illustrative checker is deployed in a client system.&lt;/p&gt;

&lt;p&gt;Specify failure behavior for each boundary. A failed signing request can release a reservation if no asset movement occurred. A submitted transaction with an uncertain outcome needs investigation or a pending state. Releasing the same reservation immediately could make those units available for another withdrawal while the first remains executable. A timeout is an observation about response time, not proof of cancellation.&lt;/p&gt;

&lt;p&gt;Fees need an equally explicit rule. If a withdrawal consumes a fee in the same asset, identify whose liability bears it and when that debit becomes final. If the fee uses a different network asset, reconcile that asset separately. Do not conceal it by rounding a converted portfolio value. The example uses no fees so its per-owner mismatch is easy to inspect. A fee policy would extend the fixture and its tests.&lt;/p&gt;

&lt;p&gt;Keep availability checks separate from backing checks. The first answers whether a customer may initiate another operation. The second compares recognized obligations with included assets. A customer can have a correctly backed total and no available balance because every unit is reserved. Conversely, an available balance can be overstated even when the aggregate backing report is numerically correct.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preserve identities through deposits and sweeps
&lt;/h2&gt;

&lt;p&gt;A balance observation needs more identity than an amount and a token symbol. Record the network, the asset identity, the wallet or vault assignment, the source observation and the cutoff. Two assets with the same display symbol must not enter one reconciliation equation merely because the interface renders the same label.&lt;/p&gt;

&lt;p&gt;For an ERC-20 token, &lt;a href="https://eips.ethereum.org/EIPS/eip-20" rel="noopener noreferrer"&gt;the standard's balanceOf method&lt;/a&gt; returns the balance of an address. It does not return the application's customer identifier. The application must provide that relationship, including the effective period of an assignment. A reused identifier or an incorrect assignment can make accurate chain data answer the wrong customer question.&lt;/p&gt;

&lt;p&gt;Fireblocks' &lt;a href="https://fireblocks.readme.io/docs/manage-deposits-at-scale" rel="noopener noreferrer"&gt;deposit-at-scale guidance&lt;/a&gt; describes validating deposit observations and maintaining a private ledger after assets are swept. A sweep changes where assets are held. It must not create another customer deposit credit simply because a second wallet received the funds. The credit and the internal asset movement have different accounting meanings.&lt;/p&gt;

&lt;p&gt;Build source identity around those meanings. For a token deposit, a transaction can contain multiple relevant events, so a transaction hash alone may be insufficient to distinguish credits. Include the event identity and the adapter's declared network scope. For a provider callback, preserve the provider's operation identity and status history rather than treating every delivery as a new financial operation.&lt;/p&gt;

&lt;p&gt;Idempotency then becomes a posting contract: receiving the same recognized deposit twice produces one customer credit. A later status update changes the operation's state under a permitted transition. It does not append another credit by default. A replay check should compare the resulting ledger state and asset inventory, not just count callbacks successfully acknowledged by the server.&lt;/p&gt;

&lt;p&gt;The snapshot function rejects duplicated wallet names, but that is a much smaller guarantee. It does not detect duplicated journal entries, verify deposit events, fetch a block, handle a reorganization or prove settlement finality. An adapter must supply observations at the declared cutoff and explain its finality policy. A matching string in every row establishes consistency of the supplied labels, not truth of the underlying chain history.&lt;/p&gt;

&lt;h2&gt;
  
  
  Signing authority does not replace reconciliation
&lt;/h2&gt;

&lt;p&gt;Separate the ability to move assets from the ability to alter reported obligations. A signing policy can stop an unauthorized withdrawal while a faulty ledger posting still changes customer balances. A perfect journal can coexist with a compromised signer. Review both boundaries because their evidence and failure modes differ.&lt;/p&gt;

&lt;p&gt;For each model, identify who may create a customer-to-wallet assignment, who may change it and who may approve exceptions. The reconciliation service should consume a versioned mapping. If an operator silently reassigns Bob's wallet to Alice, a report can change without any asset movement. Retain the previous mapping and the reason for the change so a reviewer can reconstruct the earlier result.&lt;/p&gt;

&lt;p&gt;The same principle applies to model selection. Switching a mismatching snapshot from segregated to omnibus removes owner checks and can turn failure into success. That is a change in the promise being evaluated. Require an approved model configuration and bind its version to every report. Do not let a reconciliation operator select the interpretation that produces the fewest exceptions.&lt;/p&gt;

&lt;p&gt;Emergency controls need a defined effect on accounting. A pause may prevent new withdrawals while deposits and status observations continue. State whether incoming events are still recorded, whether customer credits remain pending and how reservations are handled. A pause button without these rules can produce a technically stopped service whose ledger keeps drifting.&lt;/p&gt;

&lt;p&gt;Recovery should preserve investigation evidence. Before accepting a manual correction, retain the original report, the supporting observations and the proposed posting. Record the correction's author and approval separately from the program that recalculates balances. Then rerun the same invariant under the same perimeter. A balancing entry is not explanatory evidence merely because it reduces the difference to zero.&lt;/p&gt;

&lt;p&gt;For segregation, also rehearse a wallet reassignment or recovery operation. Verify that old and new assignments cannot both count the same asset observation, and that the customer's history remains accessible. For pooling, rehearse restoration of the allocation journal from a known point. In either case, a usable backup must support the particular accounting promise, not just restore a wallet service login.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the acceptance receipt reconstructable
&lt;/h2&gt;

&lt;p&gt;A supplier demo should hand over a report that another engineer can reproduce. Bind the model version, asset identity, customer perimeter, cutoff, wallet assignments and journal position to the result. Include totals and exceptions together. An empty exception file without the evaluated population cannot establish what passed.&lt;/p&gt;

&lt;p&gt;For this fixture, acceptance means recreating the opening balances, applying the internal entitlement change and observing the strict segregated failure. It also means seeing the omnibus report declare &lt;code&gt;owner_checked=False&lt;/code&gt;. That boolean is evidence of limited scope. It is not a warning that the pooled model is inherently defective.&lt;/p&gt;

&lt;p&gt;Ask for the negative cases before signing off. The wrong-owner case is useful because it preserves the aggregate and challenges the claim that a zero total difference is enough. The shortfall case proves that a real numerical discrepancy remains visible. Invalid-input cases prove that incompatible observations cannot be silently combined into a plausible-looking report.&lt;/p&gt;

&lt;p&gt;Keep the reported coverage accurate. The twelve local tests cover snapshot results, reservations and selected validation failures. They do not cover concurrent withdrawals, durable journal posting, signer compromise, provider outages, stale RPC data or inventory completeness. Those requirements belong to the surrounding system and need separate evidence. Passing this suite cannot certify custody safety or accounting compliance.&lt;/p&gt;

&lt;p&gt;To extend the example, add one real requirement at a time. If the product allows pending internal transfers between dedicated wallets, specify the bridge account and the permitted duration before implementing it. Write a failure case showing how an expired or unmatched bridge affects acceptance. If the product mixes customer assets with operating funds, first separate the accounting perimeter rather than widening the sum until it balances.&lt;/p&gt;

&lt;p&gt;Operational ownership is part of acceptance. Name who investigates a mismatch, which operations may continue and which report releases the restriction. Preserve unresolved cases across restarts. A status page can say a service is healthy while customer backing exceptions remain open. Health and reconciliation are different measurements.&lt;/p&gt;

&lt;p&gt;An exception receipt should identify the affected asset and customer, the observed difference, the original cutoff and the next authorized action. If new observations resolve it, retain both results rather than replacing the failed snapshot. If a mapping correction resolves it, preserve the mapping change alongside the recalculation. These records distinguish a late observation from a corrected interpretation. Both may produce a zero difference while requiring different operational responses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose the promise you can keep
&lt;/h2&gt;

&lt;p&gt;Use omnibus custody when pooled backing and an independently controlled customer allocation ledger satisfy the product's actual requirements. Use segregation when the product requires dedicated assignments and can reconcile each customer's assigned assets against recognized entitlements. Evaluate the contractual and signing arrangements alongside that choice, with their own evidence.&lt;/p&gt;

&lt;p&gt;The deciding question is what must remain true after a transfer, a reservation, a sweep and a recovery. If the answer requires per-customer backing, require per-customer deltas. If the answer permits shared backing, preserve the allocation journal and state the limits of the aggregate result. A single balance cannot carry both promises without additional records.&lt;/p&gt;

&lt;p&gt;Before approving a wallet design, request one trace that leaves the aggregate unchanged while changing customer allocations. Require the team to explain exactly when that trace becomes settled and why the report accepts or rejects it. The 60/40 to 45/55 example supplies a reproducible starting point for that conversation.&lt;/p&gt;

&lt;p&gt;For engineers operating dedicated customer wallets: does your system keep an internal customer transfer pending until the assigned assets move, or account for an explicit bridge? What evidence allows that pending state to close?&lt;/p&gt;

&lt;h2&gt;
  
  
  More insights to read
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pharosengineeringnotes.wordpress.com/2026/09/30/stablecoin-reconciliation-acceptance-evidence/" rel="noopener noreferrer"&gt;How to Specify Stablecoin Reconciliation Acceptance Evidence&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pharosengineeringnotes.wordpress.com/2026/09/11/how-to-specify-a-blockchain-ledger-integration-contract/" rel="noopener noreferrer"&gt;How to Specify a Blockchain Ledger Integration Contract&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pharosengineeringnotes.wordpress.com/2026/09/26/specify-audit-evidence-exports-fintech-delivery-contract/" rel="noopener noreferrer"&gt;How to Specify Audit Evidence Exports in a FinTech Delivery Contract&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hackmd.io/@dmytro-nasyrov/ledger-reconciliation-snapshot-cutoff-late-events" rel="noopener noreferrer"&gt;What Did the Ledger Know at Cutoff? Build a Reconciliation Snapshot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pharosengineeringnotes.wordpress.com/2026/09/11/how-to-choose-a-blockchain-integrator-for-an-existing-fintech-ledger/" rel="noopener noreferrer"&gt;How to Choose a Blockchain Integrator for an Existing FinTech Ledger&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  About the author
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkqea3ixmuakc8lc41xtu.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkqea3ixmuakc8lc41xtu.jpg" width="112" height="112" alt="Portrait of Dmytro Nasyrov wearing a dark suit and light blue shirt against a dark background."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;small&gt;Dmytro Nasyrov. Photo supplied by the author.&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written by &lt;a href="https://pharosproduction.com/dmytro-nasyrov/" rel="noopener noreferrer"&gt;Dmytro Nasyrov&lt;/a&gt; PhD, software architect with 24 years of production experience. Dmytro is the founder and CTO of Pharos Production. He works on production software architecture for FinTech, AI, Web3 and blockchain systems.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>fintech</category>
      <category>architecture</category>
      <category>testing</category>
    </item>
    <item>
      <title>How to Implement ERC-3643 Transfer Controls for Permissioned Tokens</title>
      <dc:creator>Dmytro Nasyrov</dc:creator>
      <pubDate>Wed, 30 Sep 2026 07:46:18 +0000</pubDate>
      <link>https://dev.to/pharos_production/how-to-implement-erc-3643-transfer-controls-for-permissioned-tokens-2e40</link>
      <guid>https://dev.to/pharos_production/how-to-implement-erc-3643-transfer-controls-for-permissioned-tokens-2e40</guid>
      <description>&lt;p&gt;A permissioned token needs a testable answer to a simple question: why did this movement of tokens succeed? A successful wallet transfer proves little if an agent operation follows different rules, a registry change removes an eligibility requirement or a frontend mistakes a compliance check for complete transaction validation.&lt;/p&gt;

&lt;p&gt;This walkthrough implements a recipient-country rule against the actual ERC-3643 contract suite and exercises it with 19 passing tests. It also records a deliberately broken version of the module, the failing regression and the correction. The test fixture uses real token, registry and compliance contracts.&lt;/p&gt;

&lt;p&gt;Identity signatures are replaced with explicit test doubles, so the result establishes transfer-control behavior within that boundary.&lt;/p&gt;

&lt;p&gt;The distinction between documentation and executable behavior matters here. The ERC-3643 specification describes minting as bypassing compliance rules. The pinned implementation used below calls &lt;code&gt;canTransfer&lt;/code&gt; during minting. A release review must resolve that difference for its selected contracts before anyone signs an issuance transaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with a policy-to-operation map
&lt;/h2&gt;

&lt;p&gt;Write the intended rule before choosing a module. In this example, an ordinary transfer requires an eligible recipient whose registered country appears in an allowlist. Wallet freezes, available balance and the token's pause state remain separate token-level checks. Country values represent registry records maintained by authorized operators; they do not reveal a wallet's physical location.&lt;/p&gt;

&lt;p&gt;The implementation question in &lt;a href="https://pharosproduction.com" rel="noopener noreferrer"&gt;blockchain engineering work at Pharos Production&lt;/a&gt; is how a requirement becomes an observable contract behavior. For this walkthrough, the useful deliverable is a table connecting each rule to a transaction and an assertion. A statement that the token supports compliance cannot tell an integration engineer which call should revert.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;th&gt;Enforcement point&lt;/th&gt;
&lt;th&gt;Observable test&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Recipient has the required trusted claim&lt;/td&gt;
&lt;td&gt;Identity registry&lt;/td&gt;
&lt;td&gt;Missing claim or removed issuer rejects transfer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recipient country is permitted&lt;/td&gt;
&lt;td&gt;Custom compliance module&lt;/td&gt;
&lt;td&gt;Registered recipient in a denied country cannot receive an ordinary transfer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Token is available for ordinary movement&lt;/td&gt;
&lt;td&gt;Token pause modifier&lt;/td&gt;
&lt;td&gt;Paused transfer fails even when &lt;code&gt;canTransfer&lt;/code&gt; returns true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wallet and balance restrictions apply&lt;/td&gt;
&lt;td&gt;Token freeze checks&lt;/td&gt;
&lt;td&gt;Frozen wallet fails; partially frozen balance retains its boundary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delegated spending stays bounded&lt;/td&gt;
&lt;td&gt;Token allowance accounting&lt;/td&gt;
&lt;td&gt;Successful spending reduces allowance; rejected transfer preserves it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Privileged movement has explicit authority&lt;/td&gt;
&lt;td&gt;Token agent role&lt;/td&gt;
&lt;td&gt;Unauthorized forced transfer fails; authorized exceptional path is tested separately&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Decide separately what should happen when an investor's country changes while they already own tokens. This module restricts recipients. It does not automatically confiscate an existing holding, prevent every outbound movement or calculate ownership concentration. Those are different policy decisions and require their own enforcement points and tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pin the contracts before interpreting the standard
&lt;/h2&gt;

&lt;p&gt;The code below targets &lt;a href="https://github.com/ERC-3643/ERC-3643/tree/2f0704dee9658ad9cd5b6ed82e7427da74c28345" rel="noopener noreferrer"&gt;ERC-3643/ERC-3643 at commit &lt;code&gt;2f0704dee9658ad9cd5b6ed82e7427da74c28345&lt;/code&gt;&lt;/a&gt;. Its package identifies version 4.1.3. The compiler is Solidity 0.8.17, with OpenZeppelin contracts and upgradeable contracts pinned to 4.8.3 and the ONCHAINID Solidity package pinned to 2.0.0. This is a reproducible source selection, not a recommendation to deploy an unreviewed repository head.&lt;/p&gt;

&lt;p&gt;Read the selected entry points directly. In this version, &lt;code&gt;transfer&lt;/code&gt; and &lt;code&gt;transferFrom&lt;/code&gt; combine token checks with recipient verification and modular compliance. Do not describe that as universal verification of both participants. If your policy requires renewed sender eligibility on every ordinary movement, add and test that requirement explicitly rather than assuming the recipient check covers it.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://eips.ethereum.org/EIPS/eip-3643#transfer" rel="noopener noreferrer"&gt;standard's transfer description&lt;/a&gt; groups minting with forced transfers as paths that bypass compliance. However, the pinned &lt;a href="https://github.com/ERC-3643/ERC-3643/blob/2f0704dee9658ad9cd5b6ed82e7427da74c28345/contracts/token/Token.sol" rel="noopener noreferrer"&gt;Token implementation&lt;/a&gt; checks &lt;code&gt;canTransfer(address(0), recipient, amount)&lt;/code&gt; inside &lt;code&gt;mint&lt;/code&gt;. Our mint-denial test captures this behavior. Preserve that test when upgrading the dependency; a changed result needs an explicit issuance-policy decision.&lt;/p&gt;

&lt;p&gt;Review inherited behavior and callbacks too. An entry point can omit an ordinary permission check while still reverting in a downstream module action. An interface name alone cannot establish that an operation will succeed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make identity requirements explicit
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://docs.erc3643.org/erc-3643/smart-contracts-library/onchain-identities/identity-registry" rel="noopener noreferrer"&gt;identity registry documentation&lt;/a&gt; separates a wallet's registration from its verification. Registration associates the wallet with an identity and a country. Verification evaluates the required claim topics against the trusted issuer configuration. Keep those responsibilities distinct in onboarding screens and operational runbooks.&lt;/p&gt;

&lt;p&gt;A configuration edge case deserves its own regression. In the pinned &lt;a href="https://github.com/ERC-3643/ERC-3643/blob/2f0704dee9658ad9cd5b6ed82e7427da74c28345/contracts/registry/implementation/IdentityRegistry.sol" rel="noopener noreferrer"&gt;IdentityRegistry implementation&lt;/a&gt;, a registered identity passes verification when the required-topic list is empty. Our fixture first removes a claim and observes rejection. It then removes the sole required topic and observes acceptance. That is configuration behavior, not evidence of a newly discovered vulnerability.&lt;/p&gt;

&lt;p&gt;Consequently, checking that every wallet has an identity contract is insufficient for a deployment that requires a particular credential. The release receipt should record the required topics, trusted issuers and each issuer's permitted topics. A reviewer should be able to explain why an empty list is acceptable or show that the deployment rejects that configuration before activation.&lt;/p&gt;

&lt;p&gt;Topic &lt;code&gt;7&lt;/code&gt; is a synthetic identifier in this fixture, not a universal KYC topic. Its issuer stub always accepts the supplied claim, and its identity stub exposes unrestricted setters for test setup. Neither stub belongs in a production deployment. Real signature validation, claim expiry and issuer-specific revocation behavior require a separate integration suite using the chosen identity implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implement one recipient-country module
&lt;/h2&gt;

&lt;p&gt;Keep this first rule narrow enough to inspect. A mapping answers whether a particular compliance contract permits a particular country. The outer address key matters because a module can serve more than one compliance context. Without that separation, one deployment's policy update could unintentionally alter another deployment's behavior.&lt;/p&gt;

&lt;p&gt;The module reads the token bound to the supplied compliance address, then reads the recipient's country through that token's identity registry. The allowlist defaults to rejection. Country zero cannot be enabled through the setter, making the example's requirement for an explicitly configured country visible in code. The fixture uses &lt;code&gt;250&lt;/code&gt; and &lt;code&gt;276&lt;/code&gt; as two distinct country values; neither value represents a policy recommendation.&lt;/p&gt;

&lt;p&gt;Save the following as &lt;code&gt;src/RecipientCountryModule.sol&lt;/code&gt;. It extends the pinned &lt;a href="https://github.com/ERC-3643/ERC-3643/blob/2f0704dee9658ad9cd5b6ed82e7427da74c28345/contracts/compliance/modular/modules/AbstractModule.sol" rel="noopener noreferrer"&gt;AbstractModule&lt;/a&gt;. Configuration requires a call from a bound compliance contract. The check also rejects an unbound compliance context. The transfer, mint and burn action hooks remain access-controlled even though this stateless rule has nothing to update.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.17;

import "erc3643/compliance/modular/modules/AbstractModule.sol";
import "erc3643/compliance/modular/IModularCompliance.sol";
import "erc3643/token/IToken.sol";

contract RecipientCountryModule is AbstractModule {
    mapping(address =&amp;gt; mapping(uint16 =&amp;gt; bool)) public allowed;
    event CountryPermissionSet(address indexed compliance, uint16 country, bool enabled);

    function setCountry(uint16 country, bool enabled) external onlyComplianceCall {
        require(country != 0, "country must be explicit");
        allowed[msg.sender][country] = enabled;
        emit CountryPermissionSet(msg.sender, country, enabled);
    }

    function moduleCheck(address, address to, uint256, address compliance)
        external view override onlyBoundCompliance(compliance) returns (bool)
    {
        IToken token = IToken(IModularCompliance(compliance).getTokenBound());
        uint16 country = token.identityRegistry().investorCountry(to);
        return allowed[compliance][country];
    }

    function moduleTransferAction(address, address, uint256)
        external override onlyComplianceCall {}
    function moduleMintAction(address, uint256)
        external override onlyComplianceCall {}
    function moduleBurnAction(address, uint256)
        external override onlyComplianceCall {}
    function canComplianceBind(address) external pure override returns (bool) { return true; }
    function isPlugAndPlay() external pure override returns (bool) { return true; }
    function name() external pure override returns (string memory) { return "RecipientCountryModule"; }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A rule based only on recipient country does not need a transfer counter. If you extend it to maximum holders or rolling volume limits, the action hooks become part of correctness. Define how minting, burning and exceptional transfers update that state. Test a revert in a later callback and prove that earlier changes roll back with the transaction. This article's empty hooks cannot establish that property for a future stateful module.&lt;/p&gt;

&lt;h2&gt;
  
  
  Route configuration through the compliance owner
&lt;/h2&gt;

&lt;p&gt;An operator should not call &lt;code&gt;setCountry&lt;/code&gt; directly on the module. The configured compliance owner calls &lt;code&gt;callModuleFunction&lt;/code&gt;, which invokes the module so that its &lt;code&gt;msg.sender&lt;/code&gt; is the bound compliance contract. This arrangement gives the module the correct policy namespace while preserving the compliance contract's ownership check.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/ERC-3643/ERC-3643/blob/2f0704dee9658ad9cd5b6ed82e7427da74c28345/contracts/compliance/modular/ModularCompliance.sol" rel="noopener noreferrer"&gt;ModularCompliance implementation&lt;/a&gt; evaluates its bound modules in &lt;code&gt;canTransfer&lt;/code&gt;. With no modules attached, there is no module-level restriction to reject the transfer. Therefore, a deployment check should assert the intended module set and configured values. A green transaction against an accidentally empty set proves the wrong policy.&lt;/p&gt;

&lt;p&gt;Permissioned issuance requires agreement between investor restrictions and executable rules. Pharos Production describes that mapping, including work with the client's legal team, in its &lt;a href="https://pharosproduction.com/services/tokenization-and-rwa-development/" rel="noopener noreferrer"&gt;tokenization and RWA development process&lt;/a&gt;. The engineering handoff should carry the approved policy into explicit module settings and transaction tests. A country allowlist by itself cannot establish that an instrument or offering meets applicable legal requirements.&lt;/p&gt;

&lt;p&gt;Prepare the configuration receipt before unpausing. Record the token address, registry address, compliance address and module address alongside the owner and agent assignments. Include the transaction that bound the module and the transactions that set permitted countries. Compare the resulting onchain values with the intended configuration, rather than accepting a deployment script's successful exit status as sufficient evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build a reproducible local fixture
&lt;/h2&gt;

&lt;p&gt;Use a fresh directory with Git, Node.js, npm and Foundry already installed. The following commands fetch the selected source revision and the pinned dependency versions. Preserve the resulting package lockfile with your test evidence. Review the repository's GPL-3.0 licensing and the licenses of imported dependencies before redistributing or combining the code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; erc3643-controls/src erc3643-controls/test erc3643-controls/vendor
&lt;span class="nb"&gt;cd &lt;/span&gt;erc3643-controls
git clone https://github.com/ERC-3643/ERC-3643.git vendor/erc3643
git &lt;span class="nt"&gt;-C&lt;/span&gt; vendor/erc3643 checkout 2f0704dee9658ad9cd5b6ed82e7427da74c28345
npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save-exact&lt;/span&gt; &lt;span class="nt"&gt;--ignore-scripts&lt;/span&gt; &lt;span class="nt"&gt;--no-audit&lt;/span&gt; &lt;span class="nt"&gt;--no-fund&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  @openzeppelin/contracts@4.8.3 &lt;span class="se"&gt;\&lt;/span&gt;
  @openzeppelin/contracts-upgradeable@4.8.3 &lt;span class="se"&gt;\&lt;/span&gt;
  @onchain-id/solidity@2.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save this configuration as &lt;code&gt;foundry.toml&lt;/code&gt;. The remappings are necessary because the tutorial imports the actual suite rather than recreating token behavior in a simplified mock. The suite's source files remain under &lt;code&gt;vendor/erc3643&lt;/code&gt;; the tutorial's module and tests stay in separate directories.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[profile.default]&lt;/span&gt;
&lt;span class="py"&gt;src&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"src"&lt;/span&gt;
&lt;span class="py"&gt;test&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"test"&lt;/span&gt;
&lt;span class="py"&gt;libs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"vendor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"node_modules"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="py"&gt;solc_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.8.17"&lt;/span&gt;
&lt;span class="py"&gt;optimizer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;optimizer_runs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
&lt;span class="py"&gt;remappings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="py"&gt;["erc3643/&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;vendor/erc&lt;/span&gt;&lt;span class="mi"&gt;3643&lt;/span&gt;&lt;span class="err"&gt;/contracts/&lt;/span&gt;&lt;span class="s"&gt;", "&lt;/span&gt;&lt;span class="py"&gt;@openzeppelin/&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;node_modules/@openzeppelin/&lt;/span&gt;&lt;span class="s"&gt;", "&lt;/span&gt;&lt;span class="py"&gt;@onchain-id/&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;node_modules/@onchain-id/&lt;/span&gt;&lt;span class="s"&gt;"]&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Below, the complete fixture deploys the actual registry storage, claim-topic registry, trusted-issuer registry, identity registry, modular compliance and token. It initializes each instance, binds registry storage and assigns the agent permissions required by the test. The token receives registry-agent authority for the recovery scenario. Alice and Bob start with registered identities in the permitted country.&lt;/p&gt;

&lt;p&gt;Initialization mints 100 indivisible fixture units to Alice and then unpauses ordinary transfers. Using zero decimals makes the balance assertions easy to inspect; production assets need their own denomination and rounding tests. These are directly initialized local contract instances. The fixture does not deploy the suite's proxy factory or validate an implementation-authority upgrade.&lt;/p&gt;

&lt;p&gt;Save the complete file as &lt;code&gt;test/TransferControls.t.sol&lt;/code&gt;. Every test starts from the same fresh setup, so a policy change in one test cannot make another test pass accidentally.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.17;

import "erc3643/token/Token.sol";
import "erc3643/compliance/modular/ModularCompliance.sol";
import "erc3643/registry/implementation/IdentityRegistry.sol";
import "erc3643/registry/implementation/IdentityRegistryStorage.sol";
import "erc3643/registry/implementation/TrustedIssuersRegistry.sol";
import "erc3643/registry/implementation/ClaimTopicsRegistry.sol";
import "../src/RecipientCountryModule.sol";

interface Vm {
    function prank(address) external;
    function expectRevert() external;
    function expectRevert(bytes calldata) external;
}

// Test doubles: no signature verification, expiry, revocation list or real KYC.
contract IssuerStub {
    function isClaimValid(IIdentity, uint256, bytes calldata, bytes calldata)
        external pure returns (bool) { return true; }
}
contract IdentityStub {
    address public issuer;
    bool public present = true;
    mapping(bytes32 =&amp;gt; bool) public management;
    constructor(address source, address wallet) {
        issuer = source;
        management[keccak256(abi.encode(wallet))] = true;
    }
    function setPresent(bool value) external { present = value; }
    function allowRecovery(address wallet) external {
        management[keccak256(abi.encode(wallet))] = true;
    }
    function keyHasPurpose(bytes32 key, uint256 purpose) external view returns (bool) {
        return purpose == 1 &amp;amp;&amp;amp; management[key];
    }
    function getClaim(bytes32 id) external view
        returns (uint256, uint256, address, bytes memory, bytes memory, string memory)
    {
        if (present &amp;amp;&amp;amp; id == keccak256(abi.encode(issuer, uint256(7))))
            return (7, 1, issuer, hex"01", hex"02", "");
        return (0, 0, address(0), "", "", "");
    }
}

contract TransferControlsTest {
    Vm constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code")))));
    address constant ALICE = address(0xA11CE);
    address constant BOB = address(0xB0B);
    address constant SPENDER = address(0xCA11);
    address constant NEW_WALLET = address(0xBEEF);
    Token token;
    ModularCompliance compliance;
    RecipientCountryModule policy;
    IdentityRegistry registry;
    ClaimTopicsRegistry topics;
    TrustedIssuersRegistry issuers;
    IssuerStub issuer;
    IdentityStub aliceIdentity;
    IdentityStub bobIdentity;

    function setUp() public {
        topics = new ClaimTopicsRegistry(); topics.init(); topics.addClaimTopic(7);
        issuers = new TrustedIssuersRegistry(); issuers.init();
        issuer = new IssuerStub();
        uint256[] memory required = new uint256[](1); required[0] = 7;
        issuers.addTrustedIssuer(IClaimIssuer(address(issuer)), required);
        IdentityRegistryStorage store = new IdentityRegistryStorage(); store.init();
        registry = new IdentityRegistry();
        registry.init(address(issuers), address(topics), address(store));
        store.bindIdentityRegistry(address(registry)); registry.addAgent(address(this));
        aliceIdentity = new IdentityStub(address(issuer), ALICE);
        bobIdentity = new IdentityStub(address(issuer), BOB);
        registry.registerIdentity(ALICE, IIdentity(address(aliceIdentity)), 250);
        registry.registerIdentity(BOB, IIdentity(address(bobIdentity)), 250);
        compliance = new ModularCompliance(); compliance.init();
        token = new Token();
        token.init(address(registry), address(compliance), "ControlFixture", "CFX", 0, address(0));
        token.addAgent(address(this)); registry.addAgent(address(token));
        policy = new RecipientCountryModule(); compliance.addModule(address(policy));
        setCountry(250, true);
        token.mint(ALICE, 100); token.unpause();
    }
    function setCountry(uint16 country, bool enabled) internal {
        compliance.callModuleFunction(
            abi.encodeCall(RecipientCountryModule.setCountry, (country, enabled)), address(policy));
    }
    function send(uint256 amount) internal {
        vm.prank(ALICE); token.transfer(BOB, amount);
    }
    function deny() internal {
        vm.expectRevert(bytes("Transfer not possible")); send(10);
        require(token.balanceOf(ALICE) == 100 &amp;amp;&amp;amp; token.balanceOf(BOB) == 0, "denial mutated balances");
    }
    function testAllowedTransfer() public {
        send(10); require(token.balanceOf(BOB) == 10 &amp;amp;&amp;amp; token.balanceOf(ALICE) == 90);
    }
    function testDeniedCountry() public { registry.updateCountry(BOB, 276); deny(); }
    function testMissingClaim() public { bobIdentity.setPresent(false); deny(); }
    function testIssuerRemoved() public { issuers.removeTrustedIssuer(IClaimIssuer(address(issuer))); deny(); }
    function testPauseIsOutsideCanTransfer() public {
        token.pause(); require(compliance.canTransfer(ALICE, BOB, 10));
        vm.expectRevert(bytes("Pausable: paused")); send(10);
    }
    function testFrozenSender() public {
        token.setAddressFrozen(ALICE, true); vm.expectRevert(bytes("wallet is frozen")); send(10);
    }
    function testFrozenRecipient() public {
        token.setAddressFrozen(BOB, true); vm.expectRevert(bytes("wallet is frozen")); send(10);
    }
    function testPartialFreezeBoundary() public {
        token.freezePartialTokens(ALICE, 85);
        vm.expectRevert(bytes("Insufficient Balance")); send(16);
        send(15); require(token.balanceOf(ALICE) == 85);
    }
    function testTransferFromAllowance() public {
        vm.prank(ALICE); token.approve(SPENDER, 20);
        vm.prank(SPENDER); token.transferFrom(ALICE, BOB, 10);
        require(token.allowance(ALICE, SPENDER) == 10 &amp;amp;&amp;amp; token.balanceOf(BOB) == 10);
    }
    function testTransferFromDeniedCountry() public {
        registry.updateCountry(BOB, 276);
        vm.prank(ALICE); token.approve(SPENDER, 20);
        vm.expectRevert(bytes("Transfer not possible")); vm.prank(SPENDER);
        token.transferFrom(ALICE, BOB, 10);
        require(token.allowance(ALICE, SPENDER) == 20 &amp;amp;&amp;amp; token.balanceOf(BOB) == 0);
    }
    function testDirectModuleConfigurationDenied() public {
        vm.expectRevert(bytes("only bound compliance can call")); policy.setCountry(276, true);
    }
    function testNonOwnerConfigurationDenied() public {
        vm.expectRevert(bytes("Ownable: caller is not the owner")); vm.prank(BOB);
        compliance.callModuleFunction(abi.encodeCall(RecipientCountryModule.setCountry, (276, true)), address(policy));
    }
    function testForcedTransferBypassesOrdinaryGates() public {
        registry.updateCountry(BOB, 276); token.pause(); token.setAddressFrozen(ALICE, true);
        token.freezePartialTokens(ALICE, 70); token.forcedTransfer(ALICE, BOB, 80);
        require(token.balanceOf(BOB) == 80 &amp;amp;&amp;amp; token.getFrozenTokens(ALICE) == 20);
    }
    function testMintChecksCountry() public {
        registry.updateCountry(BOB, 276);
        vm.expectRevert(bytes("Compliance not followed")); token.mint(BOB, 1);
    }
    function testRecoveryPreservesRestrictions() public {
        aliceIdentity.allowRecovery(NEW_WALLET);
        token.freezePartialTokens(ALICE, 20); token.setAddressFrozen(ALICE, true); token.pause();
        token.recoveryAddress(ALICE, NEW_WALLET, address(aliceIdentity));
        require(token.balanceOf(ALICE) == 0 &amp;amp;&amp;amp; token.balanceOf(NEW_WALLET) == 100);
        require(token.getFrozenTokens(NEW_WALLET) == 20 &amp;amp;&amp;amp; token.isFrozen(NEW_WALLET));
        require(!registry.contains(ALICE) &amp;amp;&amp;amp; registry.investorCountry(NEW_WALLET) == 250);
    }
    function testNonAgentForcedTransferDenied() public {
        vm.expectRevert(); vm.prank(BOB); token.forcedTransfer(ALICE, BOB, 1);
    }
    function testEmptyTopicsAdmitRegisteredIdentity() public {
        bobIdentity.setPresent(false); require(!registry.isVerified(BOB));
        topics.removeClaimTopic(7); require(registry.isVerified(BOB)); send(10);
    }
    function testPolicyChangeInvalidatesPreflight() public {
        require(compliance.canTransfer(ALICE, BOB, 10)); setCountry(250, false); deny();
    }
    function testComplianceContextsAreIsolated() public {
        ModularCompliance second = new ModularCompliance(); second.init();
        second.bindToken(address(token)); second.addModule(address(policy));
        require(compliance.canTransfer(ALICE, BOB, 10));
        require(!second.canTransfer(ALICE, BOB, 10));
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;forge test -vv&lt;/code&gt; from the project directory. The recorded local run on September 30, 2026 compiled the fixture with Solidity 0.8.17 and passed all 19 tests, with no skipped tests. That count describes these named examples. It does not measure exhaustive path coverage, successful mainnet integration or resistance to every adversarial input.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prove that a denial test can fail
&lt;/h2&gt;

&lt;p&gt;A passing rejection test is more persuasive when it detects a known defect. For the recorded negative run, the module's final check was deliberately replaced with &lt;code&gt;return true&lt;/code&gt;. The country-change regression then failed because the expected revert did not occur. Restoring the mapping lookup made that same test pass, after which the complete suite passed again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;--- deliberately-permissive/RecipientCountryModule.sol
&lt;/span&gt;&lt;span class="gi"&gt;+++ corrected/RecipientCountryModule.sol
&lt;/span&gt;&lt;span class="p"&gt;@@ -20,7 +20,7 @@&lt;/span&gt;
     {
         IToken token = IToken(IModularCompliance(compliance).getTokenBound());
         uint16 country = token.identityRegistry().investorCountry(to);
&lt;span class="gd"&gt;-        return true; // DELIBERATE TEST MUTATION: ignore the recipient country.
&lt;/span&gt;&lt;span class="gi"&gt;+        return allowed[compliance][country];
&lt;/span&gt;     }
&lt;span class="err"&gt;
&lt;/span&gt;     function moduleTransferAction(address, address, uint256)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a mutation of our tutorial module, not an upstream security finding. Keep the failing output, the correction diff and the passing output together. Otherwise a demonstration can accidentally compare different test selections or lose the source change that explains the result. Both runs used &lt;code&gt;forge test --match-test testDeniedCountry -vv&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F21fo4bcj6gdctoxxuypb.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F21fo4bcj6gdctoxxuypb.gif" alt="Recorded test replay: deliberately allowing every country makes testDeniedCountry fail; restoring the compliance-scoped mapping lookup makes it pass, followed by 19 passing tests." width="799" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;small&gt;Replay of recorded local test output: deliberately permissive tutorial variant, correction and passing checks. The animation summarizes the retained outputs; it is not a terminal screen recording.&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;The denial helper checks unchanged balances after rejected ordinary transfers. The delegated-spending test also checks that a failed &lt;code&gt;transferFrom&lt;/code&gt; leaves its allowance intact. Those assertions connect the error to state, which is more useful than merely observing that some call reverted. Extend this approach to event expectations and downstream accounting when adding stateful modules.&lt;/p&gt;

&lt;p&gt;The context-isolation case deserves a precise reading. It binds the same module to a second compliance instance and confirms that the second instance does not inherit the first instance's country permission. Both compliance instances point to the same token solely to keep this probe small. This is a namespace test, not a rehearsal of two separately deployed instruments with different registries and governance.&lt;/p&gt;

&lt;p&gt;For a production shared-module deployment, create that second instrument explicitly. Change the first instrument's country rule and verify the second instrument's full transfer result remains unchanged. Then reverse the direction of the change. Include distinct owners and registry data so that an accidental cross-reference cannot hide behind identical fixture values. Those additional cases are proposed extensions, not part of the reported 19-test result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Review minting and exceptional movement separately
&lt;/h2&gt;

&lt;p&gt;A single assertion that restrictions work hides differences between entry points. The country rule blocks ordinary receipt in the denied country and also blocks minting there in this selected implementation. Our tests exercise both. Forced transfer follows another path: an authorized agent can move tokens while the token is paused and the sender is frozen, without applying this module's ordinary country check.&lt;/p&gt;

&lt;p&gt;The fixture freezes 70 of Alice's 100 units, then forces a transfer of 80. The asserted result is Bob holding 80 and Alice retaining 20 frozen units. This concrete balance transition is the reviewed behavior, not a generic statement that an agent can ignore everything. Recipient identity verification still matters, and the token invokes the transfer callback after moving the balance.&lt;/p&gt;

&lt;p&gt;Our module's callback is empty. A different module may enforce additional behavior there or revert. Test the complete installed module set before describing forced transfer as guaranteed emergency liquidity. Add explicit negative cases for unauthorized agents and invalid recipients, and test your intended handling of an already frozen recipient. The provided suite covers the unauthorized-agent case; it does not claim those additional recipient cases were executed.&lt;/p&gt;

&lt;p&gt;Burning needs its own supply and accounting tests in a production suite. This example implements the required burn hook but does not include a burn scenario. Similarly, the existence of batch entry points does not establish the desired partial-failure semantics for a custody integration. Enumerate the actual entry points the integration exposes and attach an acceptance case to each.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat pause and recovery as separate controls
&lt;/h2&gt;

&lt;p&gt;The ERC-3643 authors, including Joachim Lebrun, make pausing an explicit requirement in the &lt;a href="https://eips.ethereum.org/EIPS/eip-3643#specification" rel="noopener noreferrer"&gt;specification&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;MUST have the possibility to pause the token&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For an operator, the useful follow-up is the scope of that pause. Our test proves that &lt;code&gt;compliance.canTransfer&lt;/code&gt; can return true while an ordinary transfer reverts because the token is paused. The forced-transfer test proves that an agent operation can still move a balance in that state. An incident runbook must identify which operations remain available after the pause transaction confirms.&lt;/p&gt;

&lt;p&gt;Recovery is another privileged workflow. The fixture authorizes the replacement wallet through the identity stub's management-key check, then exercises the actual token recovery function. It asserts movement of the full balance, preservation of a 20-unit partial freeze and the wallet's frozen flag, removal of the old registry association and retention of country &lt;code&gt;250&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That result does not rotate credentials across every connected service or remove every old key from the identity contract. A real recovery procedure needs separate authorization evidence and updates to offchain custody records. It also needs a negative suite for an unauthorized replacement wallet. The identity stub deliberately makes the positive management-key condition easy to arrange; it cannot establish the security of a real key-management implementation.&lt;/p&gt;

&lt;p&gt;A recovery review should also trace permissions across contract boundaries. The local setup grants the token permission to change the identity registry because the recovery operation needs that interaction. Checking only the caller's token-agent role would miss this dependency. Remove the supporting registry authority in a separate deployment rehearsal and confirm that recovery fails without leaving a partially moved balance or a stranded registry entry.&lt;/p&gt;

&lt;p&gt;Before restoring service after an incident, compare both balance state and identity state with the approved recovery request. Confirm the replacement address through the organization's established process, then reconcile downstream systems against the final transaction receipt. Reusing a previously approved wallet address from an old support ticket is not sufficient evidence that the current request is authorized.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give preflight results an honest lifetime
&lt;/h2&gt;

&lt;p&gt;A frontend can ask the compliance contract whether its modules would permit a movement. It should label the result accordingly. The check does not establish adequate allowance, an unfrozen wallet or an unpaused token. Our paused-token test makes that limitation visible without depending on an RPC provider or a race between transactions.&lt;/p&gt;

&lt;p&gt;The policy-change test establishes another boundary. It observes a positive compliance check, disables the previously permitted country and then confirms that the ordinary transfer fails. In a deployed system, the state used for a preflight call can change before transaction execution. Avoid turning a simulated result into a promise that the eventual transaction will succeed.&lt;/p&gt;

&lt;p&gt;For a transaction preview, simulate the exact entry point with the intended sender, calldata and value against a documented block context. Show the time or block associated with the result, then handle an execution failure as an expected possibility. Preserve the transaction hash and inspect the mined receipt before updating balances as settled. These are integration recommendations; the local fixture does not test a remote provider or mempool ordering.&lt;/p&gt;

&lt;p&gt;Failure messages should also preserve the distinction between identity, policy and token state. The pinned token combines some rejection conditions under one revert string. An application may need separate read-only diagnostics to explain a failed simulation, but it should not claim a uniquely established cause when several conditions are false. Keep sensitive identity material out of public error messages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make policy changes visible to reviewers
&lt;/h2&gt;

&lt;p&gt;Changing a trusted issuer, removing a required topic or replacing the compliance contract can alter admissible transfers without changing the token's visible name. Treat those operations as changes to the running system's policy. Maintain an inventory of who can perform each one, including any authority that can upgrade the code implementing those permissions.&lt;/p&gt;

&lt;p&gt;The custom module emits &lt;code&gt;CountryPermissionSet&lt;/code&gt; with the compliance address, country and new value. Retain that context in operational records so that a shared module's event is associated with the right token deployment. Compare observed configuration with an approved baseline after changes. An event stream is useful evidence, but a missed subscription message should not prevent reconciliation from current state and historical logs.&lt;/p&gt;

&lt;p&gt;Choose an approval mechanism appropriate to each authority. A multisignature owner may reduce dependence on one key; a timelock may create review time for a configuration change. Neither control automatically governs an agent that retains a separate immediate operation. Draw the complete authority map and test the exceptional route, including who can remove or replace its operator.&lt;/p&gt;

&lt;p&gt;Treat changing the allowlist as a release even when no Solidity file changes. Save a before-and-after view, simulate a permitted and a denied movement under the new configuration, and state whether existing holders are affected. Assign responsibility for reconciliation if an operator discovers that a registry country value was wrong. This preserves the connection between the administrative decision and the transfers it actually governs.&lt;/p&gt;

&lt;p&gt;The final release receipt should identify the exact source commit, compiler settings, dependency lockfile and deployed bytecode. Attach the configured identities and policy addresses, required topics, trusted issuers, permitted countries and role assignments. Then attach the executed test selection and its limitations. Reviewers need enough information to reproduce the result and enough precision to see what remains untested.&lt;/p&gt;

&lt;p&gt;An ERC-3643 implementation is ready for the next engineering review when each permitted movement and each expected rejection has an explicit explanation tied to selected code and configuration. This fixture supplies that explanation for one recipient-country rule and its tested interactions. Expand it around the instrument's real identity system, deployment architecture and authorized operations before treating it as production acceptance evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  More insights to read
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.bulbapp.io/p/e2580ef7-324b-442d-b818-92b8c8442b3d/five-smart-contract-upgrade-patterns-compared" rel="noopener noreferrer"&gt;Five Smart Contract Upgrade Patterns Compared&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/pharos-production/smart-contracts-their-potential-and-real-limitations-part-2-40942c055d79" rel="noopener noreferrer"&gt;Smart Contracts. Their Potential and Real Limitations. Part 2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dmytronasyrov.medium.com/upgradeable-solidity-smart-contracts-part-1-versioning-7e6e97cafc28" rel="noopener noreferrer"&gt;Upgradeable Solidity Smart Contracts. Part 1 — Versioning&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/pharos-production/smart-contracts-their-potential-and-real-limitations-part-1-222fe44ee14c" rel="noopener noreferrer"&gt;Smart Contracts. Their Potential and Real Limitations. Part 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/pharos-production/web3-smart-contracts-oracles-part-1-3905b127c01d" rel="noopener noreferrer"&gt;Web3. Smart Contracts. Oracles. Part 1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  About the author
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7wo81ttokb6meiuexdko.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7wo81ttokb6meiuexdko.jpg" width="112" height="112" alt="Portrait of Dmytro Nasyrov wearing a dark suit and light blue shirt against a dark background."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;small&gt;Dmytro Nasyrov. Photo supplied by the author.&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written by &lt;a href="https://pharosproduction.com/dmytro-nasyrov/" rel="noopener noreferrer"&gt;Dmytro Nasyrov&lt;/a&gt; PhD, software architect with 24 years of production experience. Dmytro is the founder and CTO of Pharos Production. He works on production software architecture for FinTech, AI, Web3 and blockchain systems.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>solidity</category>
      <category>blockchain</category>
      <category>web3</category>
      <category>testing</category>
    </item>
    <item>
      <title>Verify an Indexer Can Recover from a Chain Reorganization</title>
      <dc:creator>Dmytro Nasyrov</dc:creator>
      <pubDate>Sat, 26 Sep 2026 19:57:43 +0000</pubDate>
      <link>https://dev.to/pharos_production/verify-an-indexer-can-recover-from-a-chain-reorganization-1gn0</link>
      <guid>https://dev.to/pharos_production/verify-an-indexer-can-recover-from-a-chain-reorganization-1gn0</guid>
      <description>&lt;p&gt;An indexer can reach the latest block and still serve data from an abandoned chain. Its checkpoint advances, its health endpoint stays green and its token balance includes a transfer that no longer exists in canonical history. Recovery needs a stronger acceptance test: after replacing a branch, every indexed result must match an independent rebuild of the selected canonical history.&lt;/p&gt;

&lt;p&gt;This tutorial turns that requirement into an executable rehearsal. A small Python and SQLite model indexes signed amounts, replaces an orphaned suffix and checks its state against a separate pure replay function. The fixture finishes at block &lt;code&gt;B4&lt;/code&gt; with a total of &lt;code&gt;24&lt;/code&gt;. Repeating the same input changes nothing. Injecting an exception during recovery leaves the previously committed state intact.&lt;/p&gt;

&lt;p&gt;The example is deliberately bounded. It uses synthetic block identifiers and complete, already selected branches. It does not implement Ethereum consensus, validate block hashes or connect to a node. The database represents one chain and one aggregate. Those limits make the assertions easy to inspect before adapting them to a production indexer with multiple projections and concurrent readers.&lt;/p&gt;

&lt;p&gt;The acceptance condition is useful beyond this model. A wallet requires correct ownership and balances; an analytics product requires correct event history and aggregates. Decide which results users depend on before choosing the recovery mechanism.&lt;/p&gt;

&lt;h2&gt;
  
  
  Define the state that must recover
&lt;/h2&gt;

&lt;p&gt;A checkpoint containing only a height cannot identify a chain. Two competing blocks can occupy the same position. Record the block number together with its hash, then retain enough parent relationships to establish which stored blocks belong to the replacement branch. Treat the checkpoint as a claim about fully committed application state, not merely the most recent RPC response.&lt;/p&gt;

&lt;p&gt;For an Ethereum log pipeline, distinguish an observed event from the transaction that produced it. A useful occurrence key contains the chain identity, block hash, transaction hash and log index. The chain identity can live in the database namespace for a strictly single-chain deployment. A transaction hash alone cannot distinguish its appearances across competing histories.&lt;/p&gt;

&lt;p&gt;The Geth documentation makes the delivery consequence explicit:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“a subscription can emit logs for the same transaction multiple times.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Geth documentation, &lt;a href="https://geth.ethereum.org/docs/interacting-with-geth/rpc/pubsub" rel="noopener noreferrer"&gt;Real-time Events&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That statement concerns notification behavior, not a promise that every disconnected consumer will receive every correction. Design storage so repeated observations cannot duplicate an occurrence, while a transaction appearing in a different block can be represented accurately. Its execution context may have changed; do not carry its old derived output into the replacement block without decoding the new receipt.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://pharosproduction.com" rel="noopener noreferrer"&gt;blockchain software engineering scope at Pharos Production&lt;/a&gt; includes applications whose product behavior depends on off-chain data. A reorganization therefore belongs in the application acceptance criteria: the frontend can display a wrong balance even when every smart contract executed correctly. This tutorial supplies a concrete database exercise for that boundary; it does not claim a measured customer outcome.&lt;/p&gt;

&lt;p&gt;Write the recovery contract in terms of visible state. Canonical block membership, active event rows, materialized aggregates and the checkpoint must agree at a committed boundary. If historical orphan records are retained for audit, mark them explicitly and exclude them from current product queries. Deleting them is only one storage policy.&lt;/p&gt;

&lt;p&gt;Also specify what a reader can observe during recovery. A single database transaction can present an old committed state followed by a new committed state, subject to the database's isolation behavior. An asynchronous projection pipeline needs a visible generation or watermark instead. Otherwise an API can combine the new event table with yesterday's aggregate and return a result that belongs to neither branch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build a fork with a result you can calculate
&lt;/h2&gt;

&lt;p&gt;Use a fixture small enough to audit without trusting the implementation. The shared prefix is &lt;code&gt;G → A1&lt;/code&gt;. The old suffix is &lt;code&gt;A2 → A3&lt;/code&gt;; the replacement suffix is &lt;code&gt;B2 → B3 → B4&lt;/code&gt;. The caller selects the replacement branch. The model never decides that a branch wins merely because it has more blocks.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Block&lt;/th&gt;
&lt;th&gt;Parent&lt;/th&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;Signed units&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;G&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Trusted fixture origin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A1&lt;/td&gt;
&lt;td&gt;G&lt;/td&gt;
&lt;td&gt;deposit&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Shared prefix&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A2&lt;/td&gt;
&lt;td&gt;A1&lt;/td&gt;
&lt;td&gt;shared-tx&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Old occurrence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A3&lt;/td&gt;
&lt;td&gt;A2&lt;/td&gt;
&lt;td&gt;orphan-tx&lt;/td&gt;
&lt;td&gt;-2&lt;/td&gt;
&lt;td&gt;Orphan-only event&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B2&lt;/td&gt;
&lt;td&gt;A1&lt;/td&gt;
&lt;td&gt;shared-tx&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Replacement occurrence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B3&lt;/td&gt;
&lt;td&gt;B2&lt;/td&gt;
&lt;td&gt;credit&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;New event&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B4&lt;/td&gt;
&lt;td&gt;B3&lt;/td&gt;
&lt;td&gt;debit&lt;/td&gt;
&lt;td&gt;-4&lt;/td&gt;
&lt;td&gt;Replacement tip&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The old total is &lt;code&gt;10 + 7 - 2 = 15&lt;/code&gt;. Undoing its suffix returns the aggregate to &lt;code&gt;10&lt;/code&gt;. Applying the replacement produces &lt;code&gt;10 + 7 + 11 - 4 = 24&lt;/code&gt;. Keeping the old negative event would leave an incorrect result. Keeping both occurrences of the shared transaction would also fail, even if the checkpoint looked correct.&lt;/p&gt;

&lt;p&gt;These amounts describe a synthetic signed counter. They are not an ERC-20 balance implementation: there are no addresses, decimals, fees or contract-specific event semantics. Use integer quantities in the model so arithmetic noise cannot obscure a branch identity defect. A real token indexer must define how its decoder maps each event into the relevant accounts and units.&lt;/p&gt;

&lt;p&gt;Make the oracle independent of the repair path. The example folds the chosen branch directly into the expected block rows, event rows and total. It never reads the database or calls the rollback code. This catches an indexer that agrees with its own checkpoint while retaining wrong rows. It does not validate a faulty decoder shared by both paths; production replay needs separate golden receipt fixtures for that risk.&lt;/p&gt;

&lt;p&gt;An empty block matters too. It advances chain continuity without changing the aggregate. An indexer that checkpoints only blocks containing matching logs has discarded evidence it needs when locating a common ancestor. Keep headers or equivalent ancestry records for the entire retained recovery interval.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make replacement and checkpoint advancement atomic
&lt;/h2&gt;

&lt;p&gt;The replacement operation first checks that its input is a connected branch from the trusted origin. Each block must extend the previous hash and increment the height by one. It then compares stored block identities with the candidate branch until their shared prefix ends. Everything after that point is subject to replacement.&lt;/p&gt;

&lt;p&gt;Bound the amount of history the automatic path may undo. A rollback window is an operational limit, not a claim that deeper reorganizations are impossible. If recovery needs records outside the retained window, stop the normal writer and preserve evidence. Continuing from a guessed ancestor can make a damaged projection look current.&lt;/p&gt;

&lt;p&gt;Inside one transaction, remove the losing suffix in reverse block order, reverse its aggregate contributions and append the replacement suffix in forward order. Advance the checkpoint only after the new rows and aggregate updates succeed. Committing those changes together is what makes the checkpoint meaningful. Writing it last without a shared transaction is insufficient when earlier writes can persist independently.&lt;/p&gt;

&lt;p&gt;Inverse arithmetic works for this additive projection. It does not automatically work for every business object. Reversing a maximum value, an ownership transition with side effects or an order-dependent state machine may require before-images, versioned entities or a replay from a saved boundary. Test the actual projection algorithm rather than assuming every update has a safe subtraction.&lt;/p&gt;

&lt;p&gt;The code uses explicit SQL transaction commands and disables Python's implicit transaction opening with &lt;code&gt;isolation_level=None&lt;/code&gt;. See the &lt;a href="https://docs.python.org/3/library/sqlite3.html#transaction-control" rel="noopener noreferrer"&gt;Python sqlite3 transaction documentation&lt;/a&gt; for the connection behavior. This choice keeps the demonstrated boundary visible. It is not a database configuration recommendation for every deployment.&lt;/p&gt;

&lt;p&gt;There is one writer in this fixture. A production worker pool needs a fence that prevents an older worker from committing after a newer recovery generation takes ownership. A database lock can serialize writers, but lock acquisition alone does not prove that the worker's fetched branch is still acceptable. Validate the generation or expected checkpoint at the commit boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run the storage model
&lt;/h2&gt;

&lt;p&gt;Save the following as &lt;code&gt;reorg_harness.py&lt;/code&gt;. Block hashes are readable fixture labels; the input contract assumes immutable content for each label. There is no remote I/O inside the transaction. The &lt;code&gt;finalized&lt;/code&gt; argument, when supplied, represents an externally verified anchor that the selected branch must contain.&lt;/p&gt;

&lt;p&gt;The dApp delivery process described by Pharos Production includes indexer strategy during discovery and indexer deployment during production readiness. The &lt;a href="https://pharosproduction.com/services/dapp-development-company/" rel="noopener noreferrer"&gt;dApp development and indexer delivery process&lt;/a&gt; provides a place to assign this rehearsal to a release owner. Passing the local example establishes only the behavior demonstrated below; the deployed storage and RPC adapter still need their own evidence.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Block&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# (transaction hash, log index, signed units)
&lt;/span&gt;
&lt;span class="n"&gt;G&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Block&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;G&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;A1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Block&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;G&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deposit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),))&lt;/span&gt;
&lt;span class="n"&gt;A2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Block&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shared-tx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;),))&lt;/span&gt;
&lt;span class="n"&gt;A3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Block&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;orphan-tx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),))&lt;/span&gt;
&lt;span class="n"&gt;B2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Block&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;B2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shared-tx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;),))&lt;/span&gt;
&lt;span class="n"&gt;B3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Block&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;B3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;B2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;credit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;),))&lt;/span&gt;
&lt;span class="n"&gt;B4&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Block&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;B4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;B3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;debit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;),))&lt;/span&gt;
&lt;span class="n"&gt;OLD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;A1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;A2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;A3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;NEW&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;A1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;B2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;B3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;B4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;isolation_level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;executescript&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
        CREATE TABLE IF NOT EXISTS blocks(
          n INTEGER PRIMARY KEY, hash TEXT UNIQUE, parent TEXT);
        CREATE TABLE IF NOT EXISTS events(
          block_hash TEXT, tx TEXT, idx INTEGER, units INTEGER,
          PRIMARY KEY(block_hash, tx, idx));
        CREATE TABLE IF NOT EXISTS state(
          id INTEGER PRIMARY KEY CHECK(id=1),
          n INTEGER, hash TEXT, total INTEGER);
        INSERT OR IGNORE INTO state VALUES(1,-1,&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="s"&gt;,0);
        &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT * FROM blocks ORDER BY n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fetchall&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT * FROM events ORDER BY 1,2,3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fetchall&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT n,hash,total FROM state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fetchone&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_rewind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;finalized&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fail_at&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Input is an already selected, immutable full branch from G.
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;branch&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wrong trusted genesis&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]):&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;broken lineage&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;finalized&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;finalized&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nb"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;finalized anchor conflict&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;
        &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BEGIN IMMEDIATE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;expected_tip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;blocks&lt;/span&gt; &lt;span class="nf"&gt;else &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;expected_tip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;checkpoint ahead or inconsistent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;common&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                    &lt;span class="k"&gt;break&lt;/span&gt;
                &lt;span class="n"&gt;common&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;common&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;max_rewind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rewind limit exceeded&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;reversed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;common&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]):&lt;/span&gt;
                &lt;span class="n"&gt;removed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT COALESCE(SUM(units),0) FROM events WHERE block_hash=?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,),&lt;/span&gt;
                &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fetchone&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UPDATE state SET total=total-?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;removed&lt;/span&gt;&lt;span class="p"&gt;,))&lt;/span&gt;
                &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DELETE FROM events WHERE block_hash=?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,))&lt;/span&gt;
                &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DELETE FROM blocks WHERE n=?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,))&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fail_at&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;after_undo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;injected after undo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;common&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]:&lt;/span&gt;
                &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;INSERT INTO blocks VALUES(?,?,?)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                           &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;units&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;INSERT INTO events VALUES(?,?,?,?)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                               &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;units&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UPDATE state SET total=total+?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;units&lt;/span&gt;&lt;span class="p"&gt;,))&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fail_at&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;before_checkpoint&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;injected before checkpoint&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;tip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UPDATE state SET n=?,hash=?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;COMMIT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;BaseException&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ROLLBACK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;oracle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Independent fold: no rollback logic and no database reads.
&lt;/span&gt;    &lt;span class="n"&gt;blocks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;units&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;branch&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;units&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;units&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;branch&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;units&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;state&lt;/code&gt; row deliberately combines checkpoint identity with the materialized total. A wrong total cannot hide behind a correct height in the expected snapshot. The event table has a composite primary key, so a malformed fixture containing the same occurrence twice aborts the transaction instead of silently applying its amount twice.&lt;/p&gt;

&lt;p&gt;Repeated delivery is tested at the branch synchronization boundary. Synchronizing an already committed branch skips its existing prefix and leaves the state unchanged. This is different from accepting inconsistent duplicate payloads. A duplicate occurrence inside a newly supplied block is rejected, because accepting conflicting representations of immutable history would conceal an upstream defect.&lt;/p&gt;

&lt;p&gt;A full history input keeps the demonstration compact. Production recovery normally fetches only the required headers and suffix, starting from a trusted snapshot or retained boundary. Do not copy the full-history interface into a large deployment without changing its memory, retention and fetch behavior. Preserve the same acceptance conditions when replacing the fixture adapter with a bounded branch reader.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;oracle&lt;/code&gt; function is short enough to inspect separately from &lt;code&gt;sync&lt;/code&gt;. Its event ordering is deterministic, and it derives the total from the selected input.&lt;/p&gt;

&lt;p&gt;Comparing only the final amount would be weaker: unrelated mistakes can cancel numerically. The full snapshot catches a correct total accompanied by stale event identities or an incorrect parent chain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exercise recovery, refusal and restart
&lt;/h2&gt;

&lt;p&gt;Save this second file as &lt;code&gt;test_reorg_harness.py&lt;/code&gt; beside the first and run &lt;code&gt;python3 -m unittest -v test_reorg_harness.py&lt;/code&gt;. It uses the standard library and a temporary database. The ten test methods passed in the local rehearsal for this article. That result is evidence about these fixtures, not a production throughput or durability measurement.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tempfile&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;unittest&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;reorg_harness&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Block&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;A1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;oracle&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RecoveryTests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unittest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;setUp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tempfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TemporaryDirectory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;index.db&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;tearDown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cleanup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_switch_matches_independent_fold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nf"&gt;oracle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;B4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_repeated_delivery_is_idempotent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;once&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;once&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_reincluded_transaction_has_new_block_identity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shared-tx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;B2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shared-tx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_restart_after_each_injected_failure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;after_undo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;before_checkpoint&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertRaises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fail_at&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nf"&gt;oracle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_deep_reorg_refuses_without_mutation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertRaisesRegex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rewind limit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_rewind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_finalized_anchor_conflict_refuses&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertRaisesRegex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;finalized anchor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;finalized&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_bad_checkpoint_refuses&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UPDATE state SET n=99&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertRaisesRegex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;checkpoint&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_broken_parent_refuses&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertRaisesRegex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lineage&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;A1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Block&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wrong&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_shorter_selected_branch_and_empty_block&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;branch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;A1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Block&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EMPTY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nf"&gt;oracle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_duplicate_event_payload_aborts_transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;duplicate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertRaises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IntegrityError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;A1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Block&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DUP&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;))])&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;unittest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;verbosity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The recovery test checks all modeled state against the independent fold. The re-inclusion test isolates the transaction identity problem: only the occurrence under &lt;code&gt;B2&lt;/code&gt; remains active. The duplicate-delivery test verifies idempotency after a successful commit. These failures deserve separate names because a single final-total assertion would make diagnosis harder.&lt;/p&gt;

&lt;p&gt;The refusal tests establish a second kind of correctness. When the requested rollback exceeds the configured limit, the indexer must leave its committed state unchanged. The same applies to a conflicting finalized anchor or a broken parent relationship. Refusal is useful only if it is observable to the operator and prevents the worker from advertising itself as caught up.&lt;/p&gt;

&lt;p&gt;A corruption fixture changes the saved height to &lt;code&gt;99&lt;/code&gt; before attempting recovery. The operation rejects that inconsistent starting state instead of treating the number as authority. A production corruption check needs to cover more than the tip: missing earlier rows or a damaged aggregate can survive a superficial checkpoint comparison. Periodic reconciliation and storage integrity checks serve that separate purpose.&lt;/p&gt;

&lt;p&gt;A shorter-branch fixture prevents an accidental assumption that height must always increase. It also includes a block with no events. The caller remains responsible for deciding which branch is valid. This test establishes that the storage mechanism can install a supplied connected history, including a shorter one, within its configured rollback limit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Distinguish an exception from a process crash
&lt;/h2&gt;

&lt;p&gt;Two injected failures interrupt the transaction: one after undoing the old suffix, another after applying replacement events but before advancing the checkpoint. Each raises an exception, triggers a rollback and closes the connection. Reopening the database must reveal the original committed state. A subsequent recovery must then reach the oracle result.&lt;/p&gt;

&lt;p&gt;That rehearsal verifies application transaction boundaries. It does not simulate an operating-system kill, a machine restart or loss of durable storage. Claiming crash safety from an exception test would skip precisely the behavior that the database and deployment environment contribute. Keep those evidence categories separate in the release record.&lt;/p&gt;

&lt;p&gt;For a process-level extension, run the real worker against an isolated test database and terminate it at instrumented boundaries. Reopen through the normal startup path, then compare its state with the saved canonical fixture. Include termination immediately after commit but before the worker acknowledges success. Retrying that work must not duplicate rows or external deliveries.&lt;/p&gt;

&lt;p&gt;Use deterministic barriers rather than timing guesses. A test coordinator can wait until a worker reports that it has reached a specified boundary, then trigger the failure. Record whether the signal occurs before a write, after a write or after commit. A sleep followed by termination rarely establishes which transaction state was actually exercised.&lt;/p&gt;

&lt;p&gt;Repeat this with the production database engine, transaction isolation and deployment configuration. If projections live in separate stores, one local transaction cannot provide atomicity across all of them. A versioned publication barrier or an explicitly reconciled workflow must cover that gap. Test readers as well as writers: the API should expose a consistent generation or an honest recovery status while downstream projections catch up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reconcile the provider before trusting the next event
&lt;/h2&gt;

&lt;p&gt;An event subscription is a useful notification channel, but it should not be the only recovery record. Geth documents connection-bound subscriptions and the absence of historical event delivery. After reconnecting, reconcile the saved checkpoint against the node's current canonical chain and backfill the required interval. Receiving a new notification proves that the connection works; it does not prove that the gap was empty.&lt;/p&gt;

&lt;p&gt;Ethereum's &lt;a href="https://ethereum.org/developers/docs/apis/json-rpc/" rel="noopener noreferrer"&gt;JSON-RPC reference&lt;/a&gt; exposes block identity and parent identity, along with log fields and block-based lookup methods. An adapter should preserve those identities throughout acquisition. A numeric range fetched while the head changes can otherwise mix observations from different histories. Test the adapter with deliberately inconsistent responses, including a receipt whose block identity does not match the expected header.&lt;/p&gt;

&lt;p&gt;One practical acquisition contract is to capture a target block identity, walk its parent-linked history and verify each returned object against the expected hash. Revalidate the target against the provider's canonical view before publishing the recovered generation. If the target changed, abandon or restart that attempt according to the adapter's bounded retry policy. The chain can change again later, so repeated reconciliation remains necessary.&lt;/p&gt;

&lt;p&gt;Where the provider supports log queries restricted to a block hash, use that identity to reduce ambiguity. Where it only supplies ranges, validate each result and recheck the associated headers. An empty result is not automatically proof of complete history: the provider may have limited retention, truncated a response or failed a request. Preserve explicit completion evidence for the fetch contract your provider actually offers.&lt;/p&gt;

&lt;p&gt;Add a second fork during recovery to the adapter suite. Also test reconnects, duplicate notifications and switching to a provider with a different observed head. The desired result is either a coherent committed generation or a bounded refusal. A mixture assembled from two provider views should never pass merely because all requested heights were returned.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give finality and external effects separate policies
&lt;/h2&gt;

&lt;p&gt;Confirmation depth and protocol finality answer different questions. A configurable number of later blocks is an application risk policy; it is not a universal finality guarantee. Ethereum's &lt;a href="https://ethereum.org/developers/docs/consensus-mechanisms/pos/gasper/" rel="noopener noreferrer"&gt;Gasper explanation&lt;/a&gt; describes how justified and finalized checkpoints relate to fork choice. Map your application's publication boundary to the actual chain and provider semantics you operate.&lt;/p&gt;

&lt;p&gt;The JSON-RPC API includes &lt;code&gt;safe&lt;/code&gt; and &lt;code&gt;finalized&lt;/code&gt; block tags, but a product spanning multiple networks must verify support and meaning for each network. In particular, a rollup's observed execution head and its settlement conditions require network-specific treatment. Do not reuse an Ethereum execution-layer assumption as proof of settlement on another system.&lt;/p&gt;

&lt;p&gt;The model's finalized-anchor check is intentionally conservative. If the selected branch conflicts with the externally supplied anchor, it refuses mutation. An operator then investigates the provider, chain identity and saved anchor. The model does not explain how to resolve a consensus failure or choose between conflicting trusted sources.&lt;/p&gt;

&lt;p&gt;A database rollback also cannot retract an email already delivered or automatically reverse an action in another system. Store outbound work with its originating event identity and recovery generation. Decide which effects wait for the relevant finality policy, which can be canceled before dispatch and which require a separately authorized compensation process after dispatch.&lt;/p&gt;

&lt;p&gt;Test the dispatcher race explicitly. A queued event can become orphaned between selection and delivery. A local validity check narrows that window but cannot make a remote action atomic with chain consensus. For consequential effects, the acceptance contract must state the remaining exposure and the permitted response. Avoid promising exactly-once real-world behavior from a database uniqueness constraint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retain the evidence needed for the next rollback
&lt;/h2&gt;

&lt;p&gt;History retention sets a practical limit on recovery. If the database keeps only current entity values, it may lack the information required to reverse an old transition. Define the retained header interval together with the event journal, decoder versions and any projection snapshots. Keeping headers longer than the data needed to rebuild their effects does not extend the usable recovery window.&lt;/p&gt;

&lt;p&gt;Rehearse pruning as part of the lifecycle. Build a snapshot at a verified boundary, retain the required suffix and prove that replay from that snapshot produces the same state as the full fixture. Then present a fork whose ancestor lies before the retained boundary. The expected result is an explicit recovery-plan failure, not a partial rewind followed by a successful health check.&lt;/p&gt;

&lt;p&gt;Keep operational watermarks separate from retention policy. A consumer finishing a block does not necessarily mean every dependent projection or outbound queue has finished using its evidence. Document which components must acknowledge a generation before its journal becomes eligible for pruning. Otherwise a routine cleanup can remove the very rows an incident runbook assumes are available.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep a release receipt that can be rerun
&lt;/h2&gt;

&lt;p&gt;A useful recovery receipt identifies the software revision, database configuration and fixture hashes. It records the old checkpoint, common ancestor and selected target, then stores the expected and observed projection snapshots. Save a structured diff when they disagree. A log line announcing completion cannot replace that comparison.&lt;/p&gt;

&lt;p&gt;Measure operational behavior separately from logical correctness. Record the rollback depth, recovery duration, backlog and time until each public projection reaches the recovered generation. Set budgets from the product's requirements and actual workload. The tiny fixture here supplies no defensible latency target for a deployed indexer.&lt;/p&gt;

&lt;p&gt;Before release, run the same acceptance contract through the real decoder, storage adapter and API read path. Include a replay from a retained snapshot so missing historical data becomes visible before an incident. Confirm that an operator can recognize a refusal, preserve evidence and resume from an approved boundary without editing the checkpoint by hand.&lt;/p&gt;

&lt;p&gt;The release gate is concrete: the supported reorganization cases converge to independently expected state, repeated work has no additional effect and interrupted work resumes without exposing mixed generations. Unsupported depth or conflicting finality evidence produces an explicit stop. Keep the failed fixtures as regression tests; they describe the exact circumstances under which a green health check once failed to tell the whole story.&lt;/p&gt;

&lt;h2&gt;
  
  
  More insights to read
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.bulbapp.io/p/e2580ef7-324b-442d-b818-92b8c8442b3d/five-smart-contract-upgrade-patterns-compared" rel="noopener noreferrer"&gt;Five Smart Contract Upgrade Patterns Compared&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dmytronasyrov.medium.com/upgradeable-solidity-smart-contracts-part-1-versioning-7e6e97cafc28" rel="noopener noreferrer"&gt;Upgradeable Solidity Smart Contracts. Part 1 — Versioning&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/pharos-production/web3-smart-contracts-oracles-part-1-3905b127c01d" rel="noopener noreferrer"&gt;Web3. Smart Contracts. Oracles. Part 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/pharos-production/smart-contracts-their-potential-and-real-limitations-part-1-222fe44ee14c" rel="noopener noreferrer"&gt;Smart Contracts. Their Potential and Real Limitations. Part 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/pharos-production/smart-contracts-their-potential-and-real-limitations-part-2-40942c055d79" rel="noopener noreferrer"&gt;Smart Contracts. Their Potential and Real Limitations. Part 2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  About the author
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F10rbqvs1846bo9ibwb4j.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F10rbqvs1846bo9ibwb4j.jpg" width="112" height="112" alt="Portrait of Dmytro Nasyrov wearing a dark suit and light blue shirt against a dark background."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;small&gt;Dmytro Nasyrov. Photo supplied by the author.&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written by &lt;a href="https://pharosproduction.com/dmytro-nasyrov/" rel="noopener noreferrer"&gt;Dmytro Nasyrov&lt;/a&gt; PhD, software architect with 24 years of production experience. Dmytro is the founder and CTO of Pharos Production. He works on production software architecture for FinTech, AI, Web3 and blockchain systems.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>testing</category>
      <category>database</category>
      <category>web3</category>
    </item>
    <item>
      <title>Healthcare RAG: Test the Claim-to-Source Contract</title>
      <dc:creator>Dmytro Nasyrov</dc:creator>
      <pubDate>Sat, 26 Sep 2026 08:18:29 +0000</pubDate>
      <link>https://dev.to/pharos_production/healthcare-rag-test-the-claim-to-source-contract-2036</link>
      <guid>https://dev.to/pharos_production/healthcare-rag-test-the-claim-to-source-contract-2036</guid>
      <description>&lt;p&gt;A healthcare RAG answer can carry a working citation link and still fail review. The page exists, the passage loads and the answer sounds reasonable. Yet the passage may support only half the sentence, belong to an older edition or describe a different population. A link check cannot resolve those failures.&lt;/p&gt;

&lt;p&gt;Before accepting a retrieval-augmented generation feature, specify a claim-to-source contract: what the product retains about each assertion, which checks it performs and what the user sees when a check fails. Treat that contract as a deliverable shared by ingestion, retrieval, answer rendering and review. The schema and acceptance cases below describe a proposed implementation for an educational healthcare knowledge product. They are not a clinical validation or a report of a deployed system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with an assertion that can fail
&lt;/h2&gt;

&lt;p&gt;Consider a fictional learning assistant whose approved handbook describes how a training module is organized. A generated answer says that the module has an introductory lesson and an assessment. The cited passage describes the lesson but says nothing about assessment. Every component can appear healthy: ingestion succeeded, retrieval returned a relevant passage and the link opens the right page.&lt;/p&gt;

&lt;p&gt;The unsupported addition is still visible to the learner.&lt;/p&gt;

&lt;p&gt;Your first acceptance rule should therefore operate on assertions, rather than on paragraphs that happen to end with a reference. Divide independently checkable statements into separate claims. Retain which passage supports each one, including any qualification needed to keep its meaning intact.&lt;/p&gt;

&lt;p&gt;Do not assume one sentence equals one claim. A sentence can combine a population, a benefit and a comparison. Conversely, one claim may require two passages read together. Let the representation support a set of evidence references and explain why that set is sufficient. A mechanical requirement for exactly one reference would force the content into the wrong shape.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://arxiv.org/abs/2305.14627" rel="noopener noreferrer"&gt;ALCE research paper&lt;/a&gt; evaluates fluency, correctness and citation quality as separate dimensions. That distinction is useful for an acceptance plan: polished output and correct links are insufficient evidence of supported claims. The practical design decision here is to retain those checks separately so an attractive answer cannot hide a failed evidence check.&lt;/p&gt;

&lt;p&gt;For teams commissioning this work, the boundary between a prototype and an implementation should also appear in the statement of work. The &lt;a href="https://pharosproduction.com" rel="noopener noreferrer"&gt;software product development scope&lt;/a&gt; should identify which evidence interactions are demonstrated and which are backed by working retrieval. A reviewer needs to know whether an opened passage was handpicked for the demo or selected by the system for that request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep evidence identity separate from its location
&lt;/h2&gt;

&lt;p&gt;A source URL is a locator.&lt;/p&gt;

&lt;p&gt;It is not enough to identify the exact material behind a saved answer. The same address can serve a corrected document tomorrow. A source record should distinguish the publisher's identifier, the retrieved version and the particular passage used in an answer.&lt;/p&gt;

&lt;p&gt;For a document corpus, retain a content fingerprint with the ingestion run. Bind extracted passages to that fingerprint and to an extraction version. A new extraction can change reading order or table structure even when the original file has not changed. Without both identities, a reviewer may be comparing an old answer against new extracted text without realizing it.&lt;/p&gt;

&lt;p&gt;Page labels need similar care. A viewer's page index and the number printed on a book page can differ. Front matter can use a different numbering system. Store each independently and verify their mapping during ingestion. A fixed subtraction is appropriate only when the source actually has a verified fixed offset. Otherwise use an explicit page map, including pages without printed labels.&lt;/p&gt;

&lt;p&gt;The passage should retain enough context to inspect its qualifications. Extracting a sentence while dropping a table heading can remove the population or unit that makes it meaningful. Preserve the relevant parent heading, table headers and footnotes with a reference to the original location. If extraction cannot capture the context, mark that passage for review before using it as answer evidence.&lt;/p&gt;

&lt;p&gt;Here is a compact record shape. Values are synthetic and the identifiers intentionally describe test fixtures. This is a data contract, not a ready-made clinical schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"answer_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fixture-answer-01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"answer_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"corpus_snapshot_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fixture-corpus-a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"policy_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"education-only-v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"claims"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"claim_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"c1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The module contains an introductory lesson."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"evidence_refs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"p1"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"support_status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pending_review"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"passages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"passage_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"p1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"source_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fixture-handbook"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"source_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fixture-edition-a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"content_fingerprint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fixture-fingerprint-a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"extraction_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fixture-extractor-a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"source_class"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"handbook"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"locator"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"viewer_page"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"printed_page"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"This module begins with an introductory lesson."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"review"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In an implementation, validate the fingerprint's real algorithm and format; the fixture string above is only a readable stand-in. Likewise, a support label is a decision to justify. It does not become true because a generator emitted the field. Store the verifier or reviewer decision separately and bind it to the answer version it actually examined.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give structural checks a narrow job
&lt;/h2&gt;

&lt;p&gt;Deterministic validation can establish whether referenced records exist, whether identifiers are unique and whether a page mapping resolves within the identified document. It can reject an answer with a dangling passage reference before the renderer builds a citation marker. Those are useful guarantees because they have precise failure conditions.&lt;/p&gt;

&lt;p&gt;This Python function implements only the reference-integrity portion, assuming the record has already passed type and required-field validation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;reference_errors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;claims&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claims&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;passages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;passages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;claim_ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claim_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;passage_ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;passage_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;passages&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;claim_ids&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;claim_ids&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;duplicate_claim_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;passage_ids&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;passage_ids&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;duplicate_passage_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;known&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;passage_ids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;claim&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;refs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;claim&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;evidence_refs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;refs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;missing_evidence_ref&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ref&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;known&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;refs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dangling_evidence_ref&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I checked this function locally against the synthetic receipt and four mutations: a duplicated claim identifier, a duplicated passage identifier, an empty reference list and an unknown reference. The control returned an empty list; each mutation returned its corresponding error. Those five checks establish the illustrated reference behavior only. They do not verify source authenticity, passage meaning, access policy or page mapping. An empty claim list also needs a separate product rule if the caller expects an answer.&lt;/p&gt;

&lt;p&gt;String matching cannot establish that a clinical statement is justified. Exact copying can omit a negation immediately before the excerpt. A correct paraphrase can share few words with its source. Keep a distinct semantic review stage with a written rubric for support, contradiction and unresolved cases.&lt;/p&gt;

&lt;p&gt;For the fictional fixture, a structural check should resolve &lt;code&gt;c1&lt;/code&gt; to &lt;code&gt;p1&lt;/code&gt;, confirm the passage belongs to the declared corpus snapshot and verify that its locator opens the intended source version. A semantic review should compare the claim with the complete passage context. Adding an assessment to the claim should fail that review while leaving the structural result unchanged.&lt;/p&gt;

&lt;p&gt;This separation makes failures diagnosable. If the source cannot be opened, investigate source availability or access. If the wrong page opens, investigate the locator. If the correct page opens but does not support the claim, investigate generation or the support decision. Routing every problem into one hallucination counter discards information the team needs to repair it. Define the review unit before calculating any percentage. Citation completeness asks whether claims that need evidence have it. Support correctness asks whether the attached evidence supports those claims. A dashboard should name the denominator and preserve unresolved cases rather than silently removing them. Otherwise a system can improve its reported score by making difficult claims disappear from the evaluation set.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make source authority an explicit constraint
&lt;/h2&gt;

&lt;p&gt;A handbook explanation, a research finding and a regulatory record answer different questions. A product should preserve those source classes through extraction and rendering, instead of reducing them to interchangeable links. The reviewer must be able to tell what kind of evidence is attached before interpreting what it establishes.&lt;/p&gt;

&lt;p&gt;An article about a device cannot stand in for a regulator's record of that device's status. A regulatory record cannot, by itself, support every claim about comparative effectiveness. In your source policy, define which class may support which assertion type and require review when an answer crosses that boundary. Keep jurisdiction and retrieval date visible where they affect interpretation.&lt;/p&gt;

&lt;p&gt;This distinction appeared during Pharos Production's healthcare AI design work. Our published case reports that an audit found 6 of 7 demo citations wrong, including 4 invented citations. These were findings about our own mockups, not measurements of a deployed model. The response included verified source passages and separate presentation of handbook, research and regulatory evidence.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://pharosproduction.com/insights/healthcare/ai-citation-ux-aesthetic-medicine/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=healthcare_citation_ux_20260926&amp;amp;utm_content=claim_source_contract" rel="noopener noreferrer"&gt;healthcare AI citation UX case study&lt;/a&gt; shows the source audit and illustrative screens behind those decisions, including the work still outstanding. Use it when reviewing an implementation proposal: compare the proposed evidence panel with the concrete design problems the case documents. The engagement remains in the design phase; external retrieval connectors shown in the demo are planned, not live.&lt;/p&gt;

&lt;p&gt;For an engineering team, the next step is to turn a chosen source policy into fixtures. Create a claim that uses the right passage under the wrong source class. The acceptance result must expose that mismatch even if the text appears relevant. Source authority should survive a rendering refactor and a retrieval-provider change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate support from permission to answer
&lt;/h2&gt;

&lt;p&gt;A supported statement can still be inappropriate for the product's intended use. An educational assistant should not drift into patient-specific treatment instructions because a passage contains information that looks relevant. Keep the source-support decision separate from the policy decision governing what this product may present.&lt;/p&gt;

&lt;p&gt;That separation needs visible states. A missing source, an unavailable source service and a request outside the product's permitted scope are different situations. The user needs a different explanation and next action for each. Internally, retaining separate reasons prevents the team from trying to fix a policy refusal by increasing the number of retrieved passages.&lt;/p&gt;

&lt;p&gt;The FDA's January 29, 2026, guidance discusses enabling a healthcare professional to "independently review the basis" for recommendations. That phrase concerns one part of the US non-device clinical decision support criteria; a citation interface alone does not establish a product's regulatory classification. The &lt;a href="https://www.fda.gov/media/109618/download" rel="noopener noreferrer"&gt;current FDA guidance&lt;/a&gt; is the source for that boundary, rather than a certification supplied by this design pattern.&lt;/p&gt;

&lt;p&gt;For the proposed educational product, define a policy result alongside the support result. A fully supported explanation can proceed only when its use is allowed. An unresolved support decision should remain visibly unresolved. A denied use should select the intended boundary response even if retrieval found relevant material. Have the responsible domain owner approve that behavior before teams encode it in templates.&lt;/p&gt;

&lt;p&gt;The priority between states also matters. Suppose retrieval times out on a request the product must not fulfill. Retrying the request automatically could produce prohibited output when the service recovers. Evaluate the intended-use boundary independently and avoid making an infrastructure error the only reason the answer was withheld.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build a small fixture suite with explicit oracles
&lt;/h2&gt;

&lt;p&gt;Start with a bounded corpus you are entitled to use and questions whose expected evidence can be inspected. Each fixture needs a reason it belongs in the suite, an expected user-visible outcome and an owner for disagreements. A collection of convenient questions without answerability labels will not reveal whether the product refuses too often.&lt;/p&gt;

&lt;p&gt;Keep synthetic examples visibly synthetic.&lt;/p&gt;

&lt;p&gt;Use neutral learning-content statements for plumbing tests so a malformed fixture cannot be mistaken for medical advice. Domain-specific acceptance cases need appropriately qualified review and controlled source material. Do not copy sensitive patient information into a shared test repository simply because a scenario would look more realistic.&lt;/p&gt;

&lt;p&gt;The following matrix is a proposed starting point. Its rows represent different failure classes, not a measured test result or a completeness claim:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Fixture&lt;/th&gt;
&lt;th&gt;Deliberate condition&lt;/th&gt;
&lt;th&gt;Required observable result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Supported control&lt;/td&gt;
&lt;td&gt;Approved learning claim with an exact supporting passage&lt;/td&gt;
&lt;td&gt;Claim displays and its marker opens the correct version and location&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unsupported addition&lt;/td&gt;
&lt;td&gt;Add an assertion absent from the otherwise relevant passage&lt;/td&gt;
&lt;td&gt;Added assertion is withheld or enters review; the supported part remains distinguishable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Locator mismatch&lt;/td&gt;
&lt;td&gt;Keep the passage text but point to another printed page&lt;/td&gt;
&lt;td&gt;Citation validation fails; the interface does not present the location as verified&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Authority mismatch&lt;/td&gt;
&lt;td&gt;Attach an assertion to a source class that cannot establish it&lt;/td&gt;
&lt;td&gt;Source-policy failure is visible and retained in the receipt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source unavailable&lt;/td&gt;
&lt;td&gt;Make the evidence service unavailable for the request&lt;/td&gt;
&lt;td&gt;An availability state appears; no fabricated passage fills the gap&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Covered question missed&lt;/td&gt;
&lt;td&gt;A gold passage exists but the retriever misses it&lt;/td&gt;
&lt;td&gt;Evaluation records a retrieval miss rather than treating the refusal as success&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Intended-use boundary&lt;/td&gt;
&lt;td&gt;Ask the educational product for a patient-specific directive&lt;/td&gt;
&lt;td&gt;Approved boundary response appears, independent of source availability&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Changed source&lt;/td&gt;
&lt;td&gt;Replace a source version behind a saved answer&lt;/td&gt;
&lt;td&gt;The saved evidence remains identifiable and its review status is reconsidered&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Run the supported control alongside negative cases. A system that suppresses every answer can pass many refusal tests while failing its main purpose. The covered-question fixture provides a second safeguard: the absence of retrieved evidence does not prove the absence of evidence in the corpus.&lt;/p&gt;

&lt;p&gt;For each failure, check both the visible result and the retained record. A correct warning with a misleading success status in the receipt is still a defect. A correct internal rejection with an unsupported claim left on screen is also a defect. The acceptance oracle spans the service boundary and the rendered answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exercise the contract through the user interface
&lt;/h2&gt;

&lt;p&gt;Component tests should prove that a claim marker opens its bound passage, rather than whichever passage happens to occupy the same array position. Reorder the evidence list while preserving its identifiers. The marker must still lead to the same evidence. This catches a simple implementation mistake with consequences that a visually unchanged snapshot may miss.&lt;/p&gt;

&lt;p&gt;Also test the path back to the answer. After inspecting a passage, a reader should retain the context of the claim they were checking. If the interface opens a large document at its beginning, the citation may be technically reachable but practically difficult to review. Record that as a usability finding instead of converting it into an invented accuracy metric.&lt;/p&gt;

&lt;p&gt;Use representative intended users for that review. Ask them to locate the evidence, explain what it supports and identify a qualification. Observe where they mistake source provenance for review approval or assume a familiar publisher validates the whole answer. The product owner should decide what failure in that task blocks acceptance; a developer should not infer the threshold from a click-through rate.&lt;/p&gt;

&lt;p&gt;Mobile layouts deserve the same evidence identity checks. A compact popover may omit the source edition that appears in a desktop rail. A downloaded or copied answer may lose the marker entirely. Specify which export formats preserve traceability, then verify them. If a format cannot preserve it, the exported artifact should make that limitation apparent to its reader.&lt;/p&gt;

&lt;p&gt;Accessibility belongs in the acceptance scope too. A visual connection drawn by color alone does not tell every reader which claim is selected. Give markers descriptive names, support keyboard navigation and verify focus behavior when an evidence panel opens and closes. These are proposed product requirements that need testing in the actual implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Invalidate review when its inputs change
&lt;/h2&gt;

&lt;p&gt;A review decision should refer to an answer version, its evidence versions and the policy under which it was accepted. Changing any of those inputs can invalidate the decision. Avoid a permanent approved flag attached only to the conversation, because later answers can inherit approval for content nobody reviewed.&lt;/p&gt;

&lt;p&gt;For example, a reviewer accepts the synthetic lesson claim against edition A. An editor publishes edition B and moves the passage. Keep the old answer and its original evidence identity available under the applicable retention policy. Create a new review event for the changed source relationship. Do not silently rewrite the old receipt until it appears to describe edition B all along.&lt;/p&gt;

&lt;p&gt;Some changes require semantic review; others need narrower checks. A viewer-coordinate correction with unchanged passage content may need locator verification. A changed qualification requires reassessment of support. A new intended-use policy requires reassessment of permission even if the text is identical. Write these invalidation rules down so routine maintenance has predictable scope.&lt;/p&gt;

&lt;p&gt;The same rule applies to model and retrieval changes. Passing the contract tests on a new configuration establishes only the tested behavior for that configuration and fixture set. Preserve the model identifier and relevant retrieval settings with the run. If a provider cannot guarantee identical future behavior behind an identifier, record that reproducibility limit rather than promising exact replay.&lt;/p&gt;

&lt;h2&gt;
  
  
  Budget for the review work the schema cannot perform
&lt;/h2&gt;

&lt;p&gt;Mechanical checks are cheap to repeat once the representation is stable. Semantic review has a different cost and requires an agreed rubric. An automated verifier can help prioritize disagreements, but its output needs evaluation against the domain reviewers' judgments. Agreement on familiar examples does not establish reliability on unsupported, ambiguous or contradictory ones.&lt;/p&gt;

&lt;p&gt;Assign ownership at the point where a decision can become stuck. The ingestion owner resolves missing context. The product owner decides whether the intended user can complete the evidence task. The domain reviewer judges whether the claim is supported within its scope. Engineering preserves those decisions and makes the software follow them. One undifferentiated review queue makes these responsibilities harder to see. Set an unresolved-case policy before measuring performance. If reviewers disagree about whether a passage supports a claim, keep that uncertainty in the fixture metadata. It can be a useful test of the escalation route. Forcing consensus merely to obtain a tidy score removes the scenario that needs the clearest product behavior.&lt;/p&gt;

&lt;p&gt;Measure operational burden as well as answer quality. Count cases awaiting review and record why they remain open. Track whether the evidence panel gives reviewers enough context to decide without searching an unrelated document store. Use those observations to improve the workflow; do not describe shorter handling time as proof of safer clinical decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retain evidence without retaining everything
&lt;/h2&gt;

&lt;p&gt;An auditable answer does not require copying every input into an unrestricted log. Decide which records belong in the evidence store, which belong in a restricted operational system and which should not be retained. The educational fixture can be public because it contains invented learning content. A real request may carry information that changes those decisions.&lt;/p&gt;

&lt;p&gt;Give the receipt stable references to protected records where appropriate. Apply access checks when the evidence panel resolves them. A citation should not become an alternate route around the permissions that protect the underlying document.&lt;/p&gt;

&lt;p&gt;Test this with an authorized reader and a reader who cannot access the same source; the second reader must not receive the passage through a cached answer.&lt;/p&gt;

&lt;p&gt;Retention also creates an acceptance edge case. If the supporting document is no longer available under the applicable policy, the product cannot honestly present the saved answer as fully inspectable. Preserve the reason for that limitation without inventing a replacement passage. The owner needs to decide whether the answer remains visible with a warning, becomes restricted or is removed through the established retention process.&lt;/p&gt;

&lt;p&gt;Agree on that behavior before promising reproducibility in a delivery contract. Exact replay may depend on source licenses, retained snapshots and provider behavior outside the application's control. A useful receipt states what can still be verified today and what was verified at the original review. Those are different claims, and a buyer should be able to distinguish them without investigating your storage architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Put the evidence contract in the delivery packet
&lt;/h2&gt;

&lt;p&gt;For a healthcare AI development proposal, request the schema, the source-version policy and the fixture matrix together. Require a sample answer receipt that can be followed from the rendered claim to its source passage. Ask for both a passing control and a failure that remains visible to the user. Those artifacts make an implementation scope concrete enough to inspect.&lt;/p&gt;

&lt;p&gt;The packet should also name what has not been demonstrated. A clickable prototype, a working ingestion job and a tested retrieval path are different deliverables. Record which one exists, which sources it uses and who accepted its limits. That gives the buyer a basis for deciding what the next development milestone must prove.&lt;/p&gt;

&lt;p&gt;Before commissioning the next phase, bring an authorized sample corpus, a representative learning task and a named review owner to the technical discussion. Use the case linked above to examine the discovery work, then use this contract to ask how the implementation will preserve it. A credible proposal should identify the first claim it will prove, the fixture that can break it and the person responsible for resolving the failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  More insights to read
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://hackmd.io/@dmytro-nasyrov/rag-citation-audit-claim-source-evidence" rel="noopener noreferrer"&gt;Make RAG Citations Auditable, One Claim at a Time&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pharosengineeringnotes.wordpress.com/2026/09/19/choose-rag-supplier-retrieval-evaluation-evidence/" rel="noopener noreferrer"&gt;How to Choose a RAG Supplier by Retrieval Evaluation Evidence&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pharosengineeringnotes.wordpress.com/2026/09/18/rag-vendor-retrieval-evaluation-deliverables/" rel="noopener noreferrer"&gt;How to Specify Retrieval Evaluation Deliverables for a RAG Vendor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dmytronasyrov.substack.com/p/enterprise-rag-procurement-brief" rel="noopener noreferrer"&gt;How to Write an Enterprise RAG Procurement Brief&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dmytronasyrov.substack.com/p/choose-rag-development-partner-before-paid-pilot" rel="noopener noreferrer"&gt;How to Choose a RAG Development Partner Before a Paid Pilot&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  About the author
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feaenhy6t8vj9yop1fx91.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feaenhy6t8vj9yop1fx91.jpg" width="112" height="112" alt="Portrait of Dmytro Nasyrov wearing a dark suit and light blue shirt against a dark background."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;small&gt;Dmytro Nasyrov. Photo supplied by the author.&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written by &lt;a href="https://pharosproduction.com/dmytro-nasyrov/" rel="noopener noreferrer"&gt;Dmytro Nasyrov&lt;/a&gt; PhD, software architect with 24 years of production experience. Dmytro is the founder and CTO of Pharos Production. He works on production software architecture for FinTech, AI, Web3 and blockchain systems.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>rag</category>
      <category>testing</category>
      <category>architecture</category>
    </item>
    <item>
      <title>ISO 20022 Address Migration Is a Data-Lineage Problem</title>
      <dc:creator>Dmytro Nasyrov</dc:creator>
      <pubDate>Thu, 24 Sep 2026 05:12:22 +0000</pubDate>
      <link>https://dev.to/dmytronasyrov/iso-20022-address-migration-is-a-data-lineage-problem-5eo7</link>
      <guid>https://dev.to/dmytronasyrov/iso-20022-address-migration-is-a-data-lineage-problem-5eo7</guid>
      <description>&lt;p&gt;A payment can carry a plausible town and country while nobody can explain where either value came from. The XML may validate. The address may look cleaner than the original. Yet the migration has created an operational liability: an investigator cannot distinguish customer supplied data from a parser's guess, or reproduce the decision after the customer record changes.&lt;/p&gt;

&lt;p&gt;ISO 20022 address migration needs field level lineage. Every outgoing address component should remain connected to its source revision, the relevant payment party, the transformation that produced it, and the rules used to approve its use. This article develops a six-gate engineering runbook for that connection, including a synthetic evidence record and a rehearsal that deliberately breaks it.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, correct the migration calendar
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Status checked on September 24, 2026:&lt;/strong&gt; the previously advertised November 2026 cutover is no longer a reliable universal planning assumption. Swift's current extension announcement says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Swift will defer all payments changes.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That statement comes from &lt;a href="https://www.swift.com/swift-accepts-community-request-extend-structured-address-migration-iso-20022-payment-messages" rel="noopener noreferrer"&gt;Swift's structured address migration announcement&lt;/a&gt;. Swift is consulting on the timing and approach for structured addresses and promises an update by December at the latest. Its separate Q1 2027 timing for securities, trade and other changes must not be substituted for a payments address deadline.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.bankofengland.co.uk/payments/rtgs-renewal-programme/iso-20022" rel="noopener noreferrer"&gt;Bank of England's current RTGS and CHAPS implementation page&lt;/a&gt; also says the November 2026 release has been deferred in its entirety. Its expectation is a twelve-month deferral into November 2027, including removal of unstructured address fields, subject to confirmation and wider validation. That is a qualified expectation for that implementation, not a confirmed global Swift deadline.&lt;/p&gt;

&lt;p&gt;This matters architecturally. A rule package, its approval status and its activation date are different facts. A migration service that hardcodes a date from an old presentation has already lost one kind of provenance. Keep the document reference, retrieval date, applicable scheme and activation decision alongside the rules. Continue improving address capture while the external timetable changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the six gates establish
&lt;/h2&gt;

&lt;p&gt;The useful delivery unit is an address decision with evidence. A database backfill count alone cannot show that the correct party's address reached the correct message field. The following gates provide an internal acceptance contract; they are a proposed engineering design, not a Swift certification procedure.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Gate&lt;/th&gt;
&lt;th&gt;Question to answer&lt;/th&gt;
&lt;th&gt;Evidence required to proceed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1. Source&lt;/td&gt;
&lt;td&gt;Which record did this decision use?&lt;/td&gt;
&lt;td&gt;Versioned source reference and capture context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. Party&lt;/td&gt;
&lt;td&gt;Whose address is being represented?&lt;/td&gt;
&lt;td&gt;Stable party identity, role and address purpose&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3. Meaning&lt;/td&gt;
&lt;td&gt;What supports each structured value?&lt;/td&gt;
&lt;td&gt;Field mapping, evidence and unresolved ambiguities&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4. Rules&lt;/td&gt;
&lt;td&gt;Which requirements were applied?&lt;/td&gt;
&lt;td&gt;Scheme profile, version and activation decision&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5. Transport&lt;/td&gt;
&lt;td&gt;What reached the outgoing message?&lt;/td&gt;
&lt;td&gt;Field comparison across every relevant adapter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6. Operations&lt;/td&gt;
&lt;td&gt;Can the decision be investigated and replaced?&lt;/td&gt;
&lt;td&gt;Exception ownership, revision history and rehearsal receipt&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These gates span data ownership, payment orchestration and integration. That boundary is why &lt;a href="https://pharosproduction.com" rel="noopener noreferrer"&gt;financial software development&lt;/a&gt; needs an explicit migration acceptance contract. Pharos Production lists financial systems among its development services; the procurement question is which team will own the evidence across those systems, including the interfaces outside a supplier's immediate codebase.&lt;/p&gt;

&lt;p&gt;A useful implementation produces one linked record at each gate. It also permits a stop. Missing country evidence should create a visible exception with an owner. Quietly filling the field may improve a completeness dashboard while making the next investigation harder.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsdzzx5mgvhw3nu9ixqb6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsdzzx5mgvhw3nu9ixqb6.png" alt="Six address lineage gates connect source revision, party role, field evidence, scheme rules, message comparison and an operational receipt; uncertain decisions branch to review." width="800" height="611"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The six gates preserve the path from a source revision to a message decision. An exception returns through validation after correction.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Gate 1: capture a source revision you can retrieve
&lt;/h2&gt;

&lt;p&gt;Begin with an inventory of where addresses enter the payment flow: customer onboarding, beneficiary maintenance, enterprise resource planning imports, treasury files and service APIs. For each source, identify the owner and whether it contains structured fields, free text or both. Separate what the system stores from what an upstream team believes it stores.&lt;/p&gt;

&lt;p&gt;Use a source reference with a stable record identifier and an immutable revision. A pointer to the latest customer profile is insufficient. If the customer changes an address tomorrow, an investigation of yesterday's payment still needs the version used yesterday. Capture both the source's effective time, when known, and the time your system observed it. A late correction can make those timestamps differ substantially.&lt;/p&gt;

&lt;p&gt;Preserve the original representation inside an appropriately controlled evidence store. The operational event can carry an opaque reference and an integrity digest instead of another copy of the full address. A digest establishes whether bytes changed; it does not establish that the address was accurate, collected lawfully or associated with the right person.&lt;/p&gt;

&lt;p&gt;Define retention, access and retrieval together. A lineage identifier that points to an expired object may be useless during an investigation. Conversely, retaining every raw import indefinitely creates unnecessary personal data exposure. Agree the evidence lifecycle with the responsible data and compliance owners, and make expiry an explicit state that downstream systems can recognize. Test the boundary with a mundane change: revise the source record between extraction and serialization. The release path should detect the revision mismatch and re-evaluate the address. It should never combine the old street with the newly edited country merely because both values were individually available in a cache.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gate 2: bind the address to a payment party
&lt;/h2&gt;

&lt;p&gt;An address without a role is easy to misuse. A corporate profile may contain a registered office, an operating location and a correspondence address. A payment instruction may identify the debtor, creditor, ultimate parties and several financial institutions. Those relationships need explicit modeling before any text is parsed.&lt;/p&gt;

&lt;p&gt;Use a binding that includes a stable party identifier, the role in this payment context and the address purpose selected under the applicable policy. Store the selection reason. Avoid joining records by display name: two subsidiaries can share a trading name, while one legal entity can have several legitimate locations.&lt;/p&gt;

&lt;p&gt;The distinction also affects requirements. &lt;a href="https://www.swift.com/de/node/309567" rel="noopener noreferrer"&gt;Swift's corporate address guidance&lt;/a&gt; describes hybrid addresses with structured town and country, plus up to two address lines without duplicating the structured elements. It explains that a beneficiary bank identified by BIC generally does not also require its postal address. Apply those format and role distinctions using current scheme guidance; the same FAQ still contains older November 2026 wording, which the extension announcement supersedes for scheduling.&lt;/p&gt;

&lt;p&gt;Do not generalize an agent's BIC exception into permission to omit a creditor address. Equally, do not manufacture a bank address merely because a common internal object has mandatory address properties. Model applicability explicitly so that absent, unknown, required and not applicable remain distinguishable states.&lt;/p&gt;

&lt;p&gt;A useful negative fixture contains two parties with the same town but different identities. Swap their source references while leaving the outgoing text unchanged. A validator concerned only with postal values will accept the substitution. The lineage gate should fail because evidence for one party cannot authorize a statement about another.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gate 3: separate parsing from evidence
&lt;/h2&gt;

&lt;p&gt;A parser proposes how text maps to fields. It does not automatically establish the truth of those fields. Consider a legacy address containing a building name, a district and a town separated inconsistently across lines. Several tokenizations may produce well-formed output. Choosing one requires evidence or an explicit uncertainty decision.&lt;/p&gt;

&lt;p&gt;Classify each result by how it was obtained. Directly captured structured data, deterministic normalization, reference-data enrichment and human resolution have different failure modes. Keep those categories in the record. If a model proposes a country from surrounding text, record the proposal and its supporting input; do not silently upgrade the proposal to customer confirmed data.&lt;/p&gt;

&lt;p&gt;A model confidence score can help prioritize review. It is not a substitute for source authority. Nor should a bank account's country, an intermediary bank's BIC, a browser locale or an IP location become evidence for a payment party's address without a justified, applicable business rule. These attributes describe different things.&lt;/p&gt;

&lt;p&gt;The synthetic record below illustrates the shape of a decision. Its identifiers and versions are invented examples. It is an internal provenance envelope, not an ISO 20022 message or a complete production schema. The source's structured town and country were captured directly; no geographic inference is claimed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"decision_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"address-decision-demo-42"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"party_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"party-demo-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"payment_role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"creditor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"address_purpose"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"registered_office"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"record_ref"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"evidence:beneficiary-demo-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"revision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"observed_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-09-24T04:00:00Z"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fields"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"TwnNm"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Brussels"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source_path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"registered_address.town"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"direct_capture"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Ctry"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"BE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source_path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"registered_address.country"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"direct_capture"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"transform_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"address-mapper-demo-3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rule_profile"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bank-sandbox-hybrid-demo-2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"decision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eligible_for_sandbox_serialization"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a real envelope, also bind the exact source digest, the selected field values, the review identity when needed, and the full rule profile digest. Protect the record against unauthorized modification. An approval should refer to that exact combination. Editing a country after approval must invalidate the decision even if the record identifier stays the same.&lt;/p&gt;

&lt;p&gt;Maintain an explicit list of unresolved fields and conflicting sources. A customer portal may disagree with an imported treasury file. The resolution policy should identify who can decide, which evidence they must inspect and how the correction propagates. Last writer wins is a concurrency strategy, not an address authority policy.&lt;/p&gt;

&lt;p&gt;For teams outsourcing this boundary, Pharos Production's &lt;a href="https://pharosproduction.com/services/payment-solutions-development/" rel="noopener noreferrer"&gt;payment solutions development&lt;/a&gt; scope includes integration layers and ISO 20022 message parsers. Make field provenance and conflict handling explicit acceptance requirements when commissioning that work. A service description establishes relevant scope; it does not prove that a particular migration has passed these gates. Ask for one complete, anonymized decision walkthrough during acceptance. The reviewer should follow an individual field from collection through transformation and serialization, then see a failed case enter the correction process. That demonstration tests the promised boundary more directly than a presentation listing supported message formats.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gate 4: version the scheme rules separately
&lt;/h2&gt;

&lt;p&gt;ISO 20022 supplies a message vocabulary and structure. The actual payment flow also depends on its usage guidelines, market infrastructure rules, bank interface requirements and applicable release. Treating one schema validation result as complete approval collapses these layers and hides which requirement was checked.&lt;/p&gt;

&lt;p&gt;Represent the applicable profile with at least the scheme or channel, message definition, guideline version, supported address representation and activation status. Retain the authoritative reference and the local approval that enabled the profile. Do not label an internal ruleset simply current: that name becomes meaningless when replaying an earlier decision.&lt;/p&gt;

&lt;p&gt;Keep a distinction between a profile that is available in a sandbox, one approved for a bilateral test and one enabled for live traffic. A deferred industry release does not automatically disable a bank's existing support for structured addresses. It also does not authorize a local system to enforce future rejection rules across every route.&lt;/p&gt;

&lt;p&gt;For each profile, specify the checks at their proper layer. Schema validation addresses structural conformance. Guideline validation checks the selected usage constraints. Business validation checks party applicability and the organization's accepted evidence policy. Route validation checks what the receiving connection actually supports.&lt;/p&gt;

&lt;p&gt;Record separate outcomes so that a failed business rule cannot disappear inside a generic XML success flag.&lt;/p&gt;

&lt;p&gt;When rules change, calculate the affected population from stored profile identifiers and field dependencies. Re-evaluate only decisions whose assumptions changed, while keeping their previous results available for investigation. A rule change affecting permissible address lines need not imply that every party identity must be recollected. A source authority change may require review even when the serialized message remains identical.&lt;/p&gt;

&lt;p&gt;Keep historical reproduction separate from present eligibility. Reproduction asks whether the archived input, mapper and rules generate the recorded output. Present eligibility asks whether that address may be used now, under today's source state and active route policy. Store both answers with their own evaluation times. Re-running everything against the latest profile destroys the distinction and can make a previously valid decision look inexplicable.&lt;/p&gt;

&lt;p&gt;For example, a reviewer may discover that a source country was corrected after a payment left the system. Preserve the earlier decision and attach the correction as new evidence. Investigate which subsequent payments inherited the old revision. The corrected value should not silently rewrite the historical message or imply that the earlier reviewer had information that arrived later. The migration calendar is itself a test case for this design. Archive the superseded activation assumption, attach the newer announcement and record who changed the operational profile. Historical decisions remain explainable without presenting an obsolete deadline as today's requirement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gate 5: prove that adapters preserve meaning
&lt;/h2&gt;

&lt;p&gt;An address may be correctly structured in the customer database and still degrade before it reaches the payment message. A file export can concatenate fields. An integration gateway can truncate a line. A legacy API can omit a country field because its contract never carried one. Testing only the final serializer misses the earlier loss.&lt;/p&gt;

&lt;p&gt;Map the actual route and compare the relevant values at its boundaries. Preserve field identity where possible rather than repeatedly flattening and reparsing text. If an interface requires a combined representation, document how structured values survive the transformation and what information cannot be recovered. Make loss visible before live release.&lt;/p&gt;

&lt;p&gt;Use exact byte comparisons where the representation is meant to remain unchanged. Use defined semantic comparisons where an approved transformation changes encoding, case or ordering. The comparator's normalization policy needs its own version; otherwise a permissive comparison can conceal the same defect it was introduced to detect.&lt;/p&gt;

&lt;p&gt;Build fixtures around the specific transformations your route performs. Include non-ASCII town names, long building identifiers, missing postcodes, multiple address lines and conflicting duplicate fields. Expected outcomes should come from the relevant profile and evidence policy. Do not treat every unfamiliar international address as invalid merely because it fails a domestic postcode pattern.&lt;/p&gt;

&lt;p&gt;Pay particular attention to truncation and transliteration. If an adapter shortens a value, retain the pre-transformation value and a reasoned decision about whether the result remains usable. If the information cannot be preserved under the receiving interface's rules, send the case to remediation. A successful HTTP response is no evidence that a shortened address still identifies the intended location.&lt;/p&gt;

&lt;p&gt;Attach the serialized message reference to the decision before transport, and correlate it with the transport acknowledgement or rejection afterward. Keep these states separate from settlement. A network acceptance says something about delivery or validation at that boundary; it does not establish the real-world accuracy of the customer's address or the final economic outcome of the payment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gate 6: rehearse exceptions and controlled replacement
&lt;/h2&gt;

&lt;p&gt;The exception queue is part of the migration design. Give every stopped decision a machine-readable reason, an accountable owner, the affected party and a reference to the evidence that can resolve it. A free-text note saying bad address is insufficient for either automation or operational planning.&lt;/p&gt;

&lt;p&gt;Separate unresolved evidence from technical delivery failures. An ambiguous town needs a data correction or an authorized resolution. A temporary transport failure needs the payment system's existing recovery procedure. Mixing these cases encourages operators to edit customer data to clear infrastructure errors, or to retry payments whose address decision was never approved.&lt;/p&gt;

&lt;p&gt;A review action should create a new decision revision linked to the previous one. Preserve who changed which field, the supporting evidence and the reason. Require revalidation through every affected gate. If a source revision or ruleset changes while the item waits in the queue, invalidate the stale review rather than applying it to newer data.&lt;/p&gt;

&lt;p&gt;Rehearse the following bounded scenarios in an isolated environment with synthetic records. The expected results are acceptance criteria for the proposed design, not reported measurements from a deployed bank system.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Injected condition&lt;/th&gt;
&lt;th&gt;Expected control response&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Town field exists but has no source mapping&lt;/td&gt;
&lt;td&gt;Stop for missing field evidence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Correct values point to another party's record&lt;/td&gt;
&lt;td&gt;Stop for party binding mismatch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source revision changes after review&lt;/td&gt;
&lt;td&gt;Invalidate approval and re-evaluate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adapter silently drops a country field&lt;/td&gt;
&lt;td&gt;Fail the boundary comparison&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Profile activation is withdrawn before dispatch&lt;/td&gt;
&lt;td&gt;Reassess eligibility under the active route policy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Operator corrects a rejected address&lt;/td&gt;
&lt;td&gt;Create a new decision; retain the earlier evidence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delivery result is unknown after a timeout&lt;/td&gt;
&lt;td&gt;Investigate transport state before any payment resubmission&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The last row prevents a dangerous category error. Replaying address transformation is a data operation. Releasing another payment instruction may create an additional financial obligation. The rehearsal must prove that a developer can reproduce the former without accidentally triggering the latter. Use a transport stub or disabled dispatch boundary and assert that no live send capability is available.&lt;/p&gt;

&lt;p&gt;Roll back a defective mapper by selecting a previously approved software version for future evaluations, then review the affected decisions. Do not erase new source evidence to make an older mapper appear compatible. Messages already sent require investigation under the payment operation's procedures; changing a database value cannot retract them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure the gaps that block release
&lt;/h2&gt;

&lt;p&gt;A migration dashboard should distinguish capture coverage from demonstrated lineage. The proportion of records with town and country populated is useful, but it says nothing about whether those values came from defensible sources. Add measures for retrievable source revisions, valid party bindings, approved field decisions, adapter preservation and unresolved exceptions by route.&lt;/p&gt;

&lt;p&gt;Define denominators explicitly. A party for which a postal address is not applicable should not inflate missing-address counts. An unused beneficiary record should not mask problems in the population scheduled for payment this week. Segment readiness by the actual source systems and routes that will be enabled together.&lt;/p&gt;

&lt;p&gt;Track exception age and recurrence, including cases reopened after source or rule changes. Set operational thresholds using the team's demonstrated review capacity and the risk of the affected flow. Avoid selecting a universal target percentage without understanding which records remain outside it. One ambiguous high-impact route can matter more than a large volume of clean, inactive records.&lt;/p&gt;

&lt;p&gt;Also inspect the reason distribution. A rising share of missing source references calls for an ingestion repair; repeated role mismatches call for a party model correction. Sending both populations to the same manual queue can conceal a systematic defect behind apparently productive review activity. Choose remediation at the boundary that caused the problem, then retain a small regression fixture showing why the correction was necessary.&lt;/p&gt;

&lt;p&gt;Use the results to sequence rollout. Start with a bounded population whose source owner, message route and evidence lifecycle are understood. Expand only when the operational team can retrieve the evidence and resolve failures within its agreed process. The extension of an external deadline is an opportunity to fix weak capture boundaries, not evidence that the remaining data is ready.&lt;/p&gt;

&lt;h2&gt;
  
  
  The release packet a reviewer should receive
&lt;/h2&gt;

&lt;p&gt;Deliver a compact manifest tying together the mapper version, source snapshot references, rule profiles, synthetic fixture results, adapter comparisons and exception procedures. Include the enabled routes and excluded populations. State which authoritative documents were checked and when, especially while release timing remains under consultation.&lt;/p&gt;

&lt;p&gt;Ask a reviewer to select one passing record and one rejected record without help from the implementation author. They should be able to locate the relevant source revision, explain every structured field, identify the applicable profile and recover the message comparison. For the rejected record, they should identify exactly what evidence would permit a new decision.&lt;/p&gt;

&lt;p&gt;Then change one dependency and repeat the exercise. A source correction should produce a traceable successor decision. A revised rule should explain which previous decision is no longer eligible. A replay should preserve the historical result while making today's assessment separately visible. If these distinctions cannot be recovered, adding more populated fields will not repair the migration.&lt;/p&gt;

&lt;p&gt;Address migration is ready for controlled release when the team can explain how a particular address reached a particular message under a particular rule decision, and can stop or replace that decision when its evidence changes. That is the engineering capability worth building while the industry settles the next timetable.&lt;/p&gt;

&lt;h2&gt;
  
  
  More insights to read
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pharosengineeringnotes.wordpress.com/2026/09/12/blockchain-ledger-integration-hub-contracts-and-acceptance-evidence/" rel="noopener noreferrer"&gt;Blockchain Ledger Integration Hub: Contracts and Acceptance Evidence&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pharosengineeringnotes.wordpress.com/2026/09/11/how-to-specify-a-blockchain-ledger-integration-contract/" rel="noopener noreferrer"&gt;How to Specify a Blockchain Ledger Integration Contract&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pharosengineeringnotes.wordpress.com/2026/09/11/how-to-choose-a-blockchain-integrator-for-an-existing-fintech-ledger/" rel="noopener noreferrer"&gt;How to Choose a Blockchain Integrator for an Existing FinTech Ledger&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dmytronasyrov.substack.com/p/how-to-scope-a-fintech-blockchain-discovery-sprint" rel="noopener noreferrer"&gt;How to Scope a FinTech Blockchain Discovery Sprint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dmytronasyrov.substack.com/p/fintech-blockchain-procurement-hub-scope-shortlist" rel="noopener noreferrer"&gt;FinTech Blockchain Procurement Hub: From Scope to Shortlist&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  About the author
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6a0lb25mqnjcqqx3ve1j.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6a0lb25mqnjcqqx3ve1j.jpg" width="112" height="112" alt="Portrait of Dmytro Nasyrov wearing a dark suit and light blue shirt against a dark background."&gt;&lt;/a&gt;&lt;br&gt;
&lt;small&gt;Dmytro Nasyrov. Photo supplied by the author.&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written by &lt;a href="https://pharosproduction.com/dmytro-nasyrov/" rel="noopener noreferrer"&gt;Dmytro Nasyrov&lt;/a&gt; PhD, software architect with 24 years of production experience. Dmytro is the founder and CTO of Pharos Production. He works on production software architecture for FinTech, AI, Web3 and blockchain systems.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>fintech</category>
      <category>architecture</category>
      <category>database</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Smart Contract Engineering in 2026: Development, Audits, Upgrades and Release Evidence</title>
      <dc:creator>Dmytro Nasyrov</dc:creator>
      <pubDate>Tue, 22 Sep 2026 21:52:33 +0000</pubDate>
      <link>https://dev.to/pharos_production/smart-contract-engineering-in-2026-development-audits-upgrades-and-release-evidence-27jn</link>
      <guid>https://dev.to/pharos_production/smart-contract-engineering-in-2026-development-audits-upgrades-and-release-evidence-27jn</guid>
      <description>&lt;p&gt;A smart contract can pass its tests, receive an audit and still reach production with the wrong upgrade authority or an unreviewed initialization call. Each document may be accurate while describing a different version of the system. The release decision fails at the joins.&lt;/p&gt;

&lt;p&gt;Smart contract engineering in 2026 needs a traceable connection between intended behavior, reviewed source, executable artifacts, administrative powers and observed deployment. This guide provides that connection: a route through six published technical articles, an evidence acceptance table and a release receipt you can adapt to your repository. The scope is Solidity and EVM systems. The year identifies this reading route, checked on September 23, 2026. It does not imply an industry survey or a universal security standard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose the guide that answers your release question
&lt;/h2&gt;

&lt;p&gt;Start with the decision you cannot currently defend. The following articles each contain a different working artifact. Reading them in publication order is unnecessary.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Your question&lt;/th&gt;
&lt;th&gt;Guide and its specific job&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;What should a repository handoff contain?&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://dev.to/pharos_production/how-to-choose-a-smart-contract-development-company-8-repository-checks-9m"&gt;Smart Contract Release Audit: 8 Repository Artifacts&lt;/a&gt; identifies the records to request before accepting delivery.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How do automated checks block a bad candidate?&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://dev.to/pharos_production/smart-contract-cicd-foundry-slither-and-audit-gates-4ael"&gt;Smart Contract CI/CD: Foundry, Slither, and Audit Gates&lt;/a&gt; connects build and analysis jobs to release gates.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who can change the deployed system?&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://dev.to/pharos_production/smart-contract-upgrade-authority-timelocks-multisigs-and-emergency-controls-p6o"&gt;Smart Contract Upgrade Authority: Timelocks, Multisigs, and Emergency Controls&lt;/a&gt; maps normal and emergency administrative paths.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What happens if the new implementation fails?&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://dev.to/pharos_production/how-to-rehearse-smart-contract-rollback-before-calling-a-system-upgradeable-3532"&gt;How to Rehearse Smart-Contract Rollback Before Calling a System Upgradeable&lt;/a&gt; defines recovery exercises and the limits of code reversal.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How should I evaluate a development partner?&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://dev.to/pharos_production/10-smart-contract-development-companies-compared-by-repository-and-release-evidence-in-2026-m59"&gt;10 Smart Contract Development Companies Compared by Repository and Release Evidence in 2026&lt;/a&gt; separates public evidence from project-specific delivery questions.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How do I test behavior across an upgrade?&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://dev.to/pharos_production/property-based-testing-for-upgradeable-smart-contracts-a-stateful-invariant-harness-45j3"&gt;Property-Based Testing for Upgradeable Smart Contracts: A Stateful Invariant Harness&lt;/a&gt; supplies a bounded teaching harness with handlers, ghost state and a deliberately broken upgrade.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Use the vendor comparison during selection, then move to repository acceptance when a team delivers your code. Public engineering activity can support a shortlist. It cannot establish that the people assigned to your project reviewed its economics, reproduced its build or rehearsed its deployment.&lt;/p&gt;

&lt;p&gt;For a delivery organization offering &lt;a href="https://pharosproduction.com" rel="noopener noreferrer"&gt;blockchain software development&lt;/a&gt;, such as Pharos Production, this distinction matters at the handoff: service scope identifies the work offered, while the candidate's artifacts establish what was actually completed. Apply the same acceptance questions to an internal team and an external supplier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give every piece of evidence a subject
&lt;/h2&gt;

&lt;p&gt;A filename such as &lt;code&gt;final-audit.pdf&lt;/code&gt; is insufficient identification. Define the release subject using the repository and full commit, build configuration, dependency revisions, target chain and intended contract addresses. An upgrade also needs the currently deployed implementation and the candidate implementation. Keep intended values separate from observations read from the network.&lt;/p&gt;

&lt;p&gt;The identity must extend beyond Solidity files. A deployment script can pass the wrong administrator without changing the implementation. Compiler settings can alter bytecode. An oracle address can change system behavior while every repository test remains unchanged. Include these inputs in the candidate and explain which checks cover them.&lt;/p&gt;

&lt;p&gt;The following acceptance table is a proposed engineering policy. Adapt its owners and stop conditions to the actual system. It is not a report of completed checks.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Evidence&lt;/th&gt;
&lt;th&gt;Acceptance question&lt;/th&gt;
&lt;th&gt;What invalidates it?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Build record&lt;/td&gt;
&lt;td&gt;Can the recorded inputs reproduce the intended artifact?&lt;/td&gt;
&lt;td&gt;Source, compiler, dependencies or settings change.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Behavior results&lt;/td&gt;
&lt;td&gt;Were the relevant properties exercised under declared assumptions?&lt;/td&gt;
&lt;td&gt;Properties, handlers, configuration or relevant code change.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Audit disposition&lt;/td&gt;
&lt;td&gt;Is the candidate inside the reviewed boundary?&lt;/td&gt;
&lt;td&gt;An unclassified change crosses that boundary.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Authority snapshot&lt;/td&gt;
&lt;td&gt;Do the actual accounts and roles match the approved model?&lt;/td&gt;
&lt;td&gt;Owners, thresholds, modules, roles or delays change.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recovery rehearsal&lt;/td&gt;
&lt;td&gt;Does the procedure preserve the required state and access?&lt;/td&gt;
&lt;td&gt;Migration logic, starting state or recovery powers change.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment observation&lt;/td&gt;
&lt;td&gt;Did the intended code and configuration reach this chain?&lt;/td&gt;
&lt;td&gt;Wrong target, bytecode mismatch or insufficient confirmation.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Operations handoff&lt;/td&gt;
&lt;td&gt;Can an assigned operator detect and handle the specified failure?&lt;/td&gt;
&lt;td&gt;The monitored system or response ownership changes.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgxf5qk87k5pkw54fn1oe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgxf5qk87k5pkw54fn1oe.png" alt="Release evidence diagram: candidate build, behavior tests, audit and recovery records meet observed chain code and authority at one identity check; a mismatch blocks release, and changes invalidate affected evidence." width="800" height="480"&gt;&lt;/a&gt;&lt;br&gt;
&lt;small&gt;Release evidence joins candidate records to a chain observation. A changed input reopens its affected checks.&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;Do not replace this table with a weighted readiness score. A strong test suite cannot compensate for an unknown upgrade administrator. A documented exception needs its own scope, reason and authorized owner. An arithmetic average cannot accept it on their behalf.&lt;/p&gt;
&lt;h2&gt;
  
  
  Develop the behavior contract before the implementation
&lt;/h2&gt;

&lt;p&gt;Write properties in business terms first. Which balances represent customer claims? When is a request irrevocably accepted? Who may mint, withdraw, cancel or change a limit? Which external data must be fresh? These answers define observable obligations that an implementation and its tests can share.&lt;/p&gt;

&lt;p&gt;Consider a withdrawal queue. A useful specification distinguishes a requested withdrawal from a claimable one and a paid one. It says whether pausing new requests must preserve existing claims. It defines how rounding is allocated and whether a later parameter change applies to pending requests. Without these decisions, two correct-looking functions can disagree about the same liability.&lt;/p&gt;

&lt;p&gt;Translate those obligations into contract boundaries, explicit permissions and testable state transitions. Keep an inventory of external assumptions: token transfer behavior, oracle updates, keeper availability and chain-specific execution conditions. An assumption belongs beside the property that depends on it, so a reviewer can see what a passing assertion leaves unresolved.&lt;/p&gt;

&lt;p&gt;Pin the compiler and dependencies, and retain the settings used for the release. Check the selected compiler against Solidity's &lt;a href="https://docs.soliditylang.org/en/latest/bugs.html" rel="noopener noreferrer"&gt;known-bug list&lt;/a&gt;, including the conditions under which a listed bug applies. A compiler version number alone cannot answer whether your settings and code encounter the issue. Record the review outcome with the build evidence.&lt;/p&gt;

&lt;p&gt;Upgradeable contracts add initialization obligations. OpenZeppelin states:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Do not leave an implementation contract uninitialized.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;OpenZeppelin, &lt;a href="https://docs.openzeppelin.com/upgrades-plugins/writing-upgradeable#initializing-the-implementation-contract" rel="noopener noreferrer"&gt;Writing Upgradeable Contracts&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The same guide describes locking the implementation with &lt;code&gt;_disableInitializers&lt;/code&gt; and treating proxy initialization separately. Turn that distinction into explicit checks for each deployed address. The intended owner, initialized version and repeat-call behavior belong in the deployment review, not in an operator's memory.&lt;/p&gt;
&lt;h2&gt;
  
  
  Read test results as bounded evidence
&lt;/h2&gt;

&lt;p&gt;A unit test checks a particular behavior. Fuzzing explores inputs within its configured domain. Stateful invariant testing explores sequences and asks whether a property survives them. These methods answer related questions, but their results should retain enough context to distinguish what actually ran.&lt;/p&gt;

&lt;p&gt;For an upgrade, include behavior before the transition, the transition itself and behavior afterward. A fresh deployment of version two can pass every test while a populated version-one proxy fails during migration. Likewise, a successful upgrade transaction says little about whether old withdrawal claims remain redeemable.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://getfoundry.sh/forge/invariant-testing" rel="noopener noreferrer"&gt;Foundry invariant-testing documentation&lt;/a&gt; explains sequence campaigns, handlers and configuration. Retain the tool version, selected profile, targeted operations, campaign bounds and failure reproduction data. Inspect successful calls and reverts as well as the final result. A handler that filters every difficult input may make an invariant easy to satisfy without exercising its intended risk.&lt;/p&gt;

&lt;p&gt;Use a negative control when the property is important enough to gate release. Introduce a deliberate defect in an isolated test fixture and verify that the relevant assertion detects it. For example, a migration that resets an aggregate should fail the conservation property that compares aggregate and per-account obligations. Record that result as evidence about the test's sensitivity, not as proof that all migration defects are covered.&lt;/p&gt;

&lt;p&gt;The linked stateful harness is a teaching example, not a production asset protocol. Adapting its structure requires replacing the model, actions and assumptions with your system's behavior. Copying its campaign settings or green result does not transfer its evidence to your release.&lt;/p&gt;

&lt;p&gt;Keep static analysis alongside these tests. Preserve findings, reviewed suppressions and the analysis configuration. When a result disappears, determine whether code was fixed, coverage changed or a detector was disabled. The release owner needs the disposition, not just an empty findings panel.&lt;/p&gt;

&lt;p&gt;Assign a question to every retained result. A conservation test might compare claims against assets under a defined token model. An authorization test should show that a forbidden actor cannot reach the protected state change. A liveness exercise should demonstrate progress under its stated assumptions about keepers and available liquidity. These are different obligations, so combining their totals into one test count hides information the reviewer needs.&lt;/p&gt;

&lt;p&gt;When a test uses a fork, distinguish captured state from simulated future behavior. Record the fork block, dependency addresses and any substituted responses. A simulated oracle update or signer impersonation can be useful for isolating contract behavior, but it removes part of the operational problem. List that substitution so the release decision can require separate evidence for the real dependency or signing procedure.&lt;/p&gt;
&lt;h2&gt;
  
  
  Bind the audit to the release candidate
&lt;/h2&gt;

&lt;p&gt;A smart contract audit has a scope: repository, revision, files, assumptions, exclusions and review method. Ask for those identifiers before treating its conclusions as relevant to a pending deployment. A report about the project can be authentic and still omit the migration you intend to execute tomorrow.&lt;/p&gt;

&lt;p&gt;Create a finding-to-fix record. Each relevant finding should point to the remediation change, a regression check where appropriate, the follow-up review and any residual limitation. A team marking its own ticket closed is different evidence from the reviewer confirming the fix. Preserve that difference in the receipt.&lt;/p&gt;

&lt;p&gt;Compare the audited revision with the candidate. Classify changes by affected behavior instead of accepting them because the diff is small. One address replacement can redirect authority. A single arithmetic adjustment can change claims. Documentation-only changes may need no new security review, but the classification itself should be visible and attributable.&lt;/p&gt;

&lt;p&gt;This is also where delivery scope must become concrete. When development and audit handoffs lose the relationship between fixes and the deployable candidate, Pharos Production's &lt;a href="https://pharosproduction.com/services/smart-contracts-development/" rel="noopener noreferrer"&gt;smart contract development and audit preparation&lt;/a&gt; provides a relevant service scope spanning implementation, testing and security-review preparation. The acceptance record should still require evidence from the particular engagement. The service description does not establish an independent audit result.&lt;/p&gt;

&lt;p&gt;Record accepted risks with the condition that made acceptance reasonable. If a limitation was accepted because withdrawals were capped, raising the cap should reopen the decision. Otherwise, a valid historical exception becomes an undocumented expansion of risk.&lt;/p&gt;
&lt;h2&gt;
  
  
  Review the complete upgrade authority path
&lt;/h2&gt;

&lt;p&gt;Start at the function that can replace the implementation and work outward to every account or contract that can authorize it. Include powers that can change those permissions. A diagram showing a multisig at the top is incomplete if another role can replace the controlling administrator.&lt;/p&gt;

&lt;p&gt;OpenZeppelin's &lt;a href="https://docs.openzeppelin.com/contracts/5.x/access-control" rel="noopener noreferrer"&gt;access-control documentation&lt;/a&gt; describes ownership, role-based controls and timelocks. A threshold wallet distributes approval among signers. A timelock introduces delay for scheduled operations. Neither mechanism, by itself, establishes that the approved payload preserves user obligations. The control model must describe proposal, execution, cancellation and administration together.&lt;/p&gt;

&lt;p&gt;Record the current threshold, owners, relevant modules and the roles on each controlled contract. Separate accounts allowed to propose a change from those able to execute or cancel it. Include changes to the delay itself. Read these values from the intended network at a recorded block. Deployment notes may describe an earlier configuration.&lt;/p&gt;

&lt;p&gt;Then examine emergency powers. A pause role may block deposits while withdrawals remain available, or it may freeze both. An emergency upgrade path may bypass the ordinary delay. State the consequence explicitly and test unauthorized calls through each reachable entry point. A bypass justified for an incident remains a live administrative capability afterward.&lt;/p&gt;

&lt;p&gt;Rehearse absence as well as compromise. Determine whether the required signers can actually act, whether a cancellation can arrive in time and who owns the response when one signer is unavailable. Do not choose a universal delay from a blog post. Its usefulness depends on detection, decision time, transaction inclusion and the actions users can take during the window.&lt;/p&gt;
&lt;h2&gt;
  
  
  Separate storage compatibility from recovery
&lt;/h2&gt;

&lt;p&gt;A compatible storage layout is necessary for many proxy upgrades, but it cannot establish that the old implementation understands state written by the new one. A field can retain its type and slot while changing units, meaning or permitted values. Recovery must account for those semantic changes.&lt;/p&gt;

&lt;p&gt;For example, suppose version one records a claim in whole units and version two migrates it into scaled units. Reverting the implementation pointer may succeed technically while causing version one to interpret the scaled value incorrectly. A rollback rehearsal must execute meaningful reads and transactions after reversal, rather than stopping at a successful administrative call.&lt;/p&gt;

&lt;p&gt;OpenZeppelin's &lt;a href="https://docs.openzeppelin.com/upgrades-plugins/foundry/api/upgrades" rel="noopener noreferrer"&gt;Foundry upgrade-validation API&lt;/a&gt; compares a new implementation against a declared reference. Preserve the reference identity and validation settings. A comparison against the wrong predecessor provides no useful answer about the proxy you operate.&lt;/p&gt;

&lt;p&gt;Define the recovery strategy before approving migration: return to the old code where its state assumptions still hold, deploy a reviewed corrective implementation, or follow a bounded migration procedure. Document conditions that make each option unavailable. A pause can buy time while retaining state, but it does not reverse transfers or restore a previous storage snapshot.&lt;/p&gt;

&lt;p&gt;Use representative populated state and retain the rehearsal's starting block or fixture. Include pending claims, active permissions and operations already in flight. Verify the obligations after recovery and the operator's ability to complete the next required action. If the procedure leaves the system permanently paused, explain how users regain access before calling the rehearsal successful.&lt;/p&gt;
&lt;h2&gt;
  
  
  Join the approved artifact to an observed deployment
&lt;/h2&gt;

&lt;p&gt;Before signing, compare the full intended operation with the reviewed one: chain, target, value, calldata and administrative path. Decode parameters for the reviewer while retaining their exact encoded form. A familiar function name is insufficient when its arguments choose a different implementation or initialization recipient.&lt;/p&gt;

&lt;p&gt;After execution, retain the transaction receipt and apply the confirmation or finality policy appropriate to the chain. Record the block number and block hash used for verification. A transaction accepted by a node is an earlier observation than a deployment accepted under that policy. Give these states separate names.&lt;/p&gt;

&lt;p&gt;Compare deployed runtime code with the expected deployment artifact, accounting explicitly for linked libraries, immutable values and compiler metadata where relevant. Do not silently strip differences to obtain a match. Preserve the comparison method so another engineer can reproduce the result from the same build inputs.&lt;/p&gt;

&lt;p&gt;For relevant proxy types, &lt;a href="https://eips.ethereum.org/EIPS/eip-1967" rel="noopener noreferrer"&gt;ERC-1967&lt;/a&gt; specifies implementation, beacon and administrator storage slots and associated events. Inspect the mechanism the system actually uses. A beacon proxy adds a dependency through the beacon. A nonstandard proxy requires its own documented inspection path. An explorer badge does not replace this identification work.&lt;/p&gt;

&lt;p&gt;Finally, check the initialized application state and authority configuration. Confirm the intended implementation, roles, limits and external dependencies. Where a check would create an irreversible effect, use a suitable simulation or a separately authorized bounded operation. Verification should not invent an unreviewed transaction merely to demonstrate that the deployment is alive.&lt;/p&gt;
&lt;h2&gt;
  
  
  Keep one receipt with explicit evidence states
&lt;/h2&gt;

&lt;p&gt;The following YAML is an unfilled template, not an executable validator or a completed release. Every check begins at &lt;code&gt;NOT_RUN&lt;/code&gt;. Null identifiers must be replaced with observed values. Copying the file cannot authorize a deployment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;release-evidence-example/v1&lt;/span&gt;
&lt;span class="na"&gt;candidate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;repository&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
  &lt;span class="na"&gt;source_commit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
  &lt;span class="na"&gt;build_input_sha256&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
  &lt;span class="na"&gt;compiler_and_settings_ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
  &lt;span class="na"&gt;dependency_lock_ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
&lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;chain_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
  &lt;span class="na"&gt;proxy_address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
  &lt;span class="na"&gt;current_implementation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
  &lt;span class="na"&gt;candidate_implementation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
  &lt;span class="na"&gt;reviewed_operation_sha256&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
&lt;span class="na"&gt;evidence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;NOT_RUN&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;artifact_ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;null&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;behavior&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;NOT_RUN&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;artifact_ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;null&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;audit_scope_and_fixes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;NOT_RUN&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;artifact_ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;null&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;authority&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;NOT_RUN&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;artifact_ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;null&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;recovery&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;NOT_RUN&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;artifact_ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;null&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;deployment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;NOT_RUN&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;artifact_ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;null&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;operations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;NOT_RUN&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;artifact_ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;null&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
&lt;span class="na"&gt;observation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;transaction_hash&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
  &lt;span class="na"&gt;block_number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
  &lt;span class="na"&gt;block_hash&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
  &lt;span class="na"&gt;finality_policy_ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
&lt;span class="na"&gt;decision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NOT_READY&lt;/span&gt;
  &lt;span class="na"&gt;reviewer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
  &lt;span class="na"&gt;accepted_exceptions_ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each referenced artifact should contain its subject identifiers, producer, observation time, result and limitations. Define a small state vocabulary: not run, passed, failed, invalidated and not applicable with a reason. Keep release authorization distinct from both a technical check and a transaction submission.&lt;/p&gt;

&lt;p&gt;Retain a pre-submission decision and a separate post-deployment acceptance record. Deployment observations cannot exist before deployment, so an all-fields-passed rule would be circular. The first decision authorizes an exact operation after the required preparatory evidence is accepted. The second confirms what happened and whether operational ownership can transfer.&lt;/p&gt;

&lt;p&gt;Hashing records helps detect changed bytes when a trusted comparison value exists. It does not prove that their claims are true or that their signer was authorized. Preserve review identity and artifact provenance alongside integrity values. Keep secrets and private signing material outside the evidence packet.&lt;/p&gt;

&lt;p&gt;Make artifact references durable enough for an incident investigation. A temporary CI download link may expire before the next upgrade. Retain the relevant build inputs, reports and replay instructions in an access-controlled archive with the release identifier. Test whether the receiving engineer can retrieve the packet using their own access, rather than relying on the original author's workstation or browser session.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat configuration changes as releases with their own scope
&lt;/h2&gt;

&lt;p&gt;A new implementation is only one way to alter a smart contract system. An oracle replacement, a fee adjustment or a role transfer can change user outcomes without changing runtime bytecode. Give such changes an exact subject and acceptance path instead of exempting them because no compiler ran.&lt;/p&gt;

&lt;p&gt;For a parameter update, record its old and proposed values, permitted bounds and affected obligations. Determine how it applies to already accepted work. A fee update may be valid for new requests while breaking the economics of a pending queue if applied retroactively. The required evidence should follow that behavior change. Rebuilding unchanged code is not a substitute for reviewing the parameter's effect.&lt;/p&gt;

&lt;p&gt;For a dependency replacement, establish the interface and failure behavior your application relies on. The same function signature does not guarantee equivalent units, freshness, permissions or revert behavior. Preserve the chosen address and the assumptions checked against it, then update monitoring to observe the replacement. Treat a change to those assumptions as a reason to revisit the dependent properties.&lt;/p&gt;

&lt;p&gt;For an authority transfer, verify that the recipient can exercise its intended role and that the previous principal has only the powers the plan allows. Some transitions require an acceptance step. The receipt should distinguish transfer requested from transfer completed and should identify the observation that resolves the distinction.&lt;/p&gt;

&lt;p&gt;Scope the work proportionately. A configuration-only release can reuse unchanged build evidence while requiring new parameter, authority or operational checks. A code upgrade may require the whole path. The useful question is which dependency changed and which conclusion relied on it. Write that relationship down before selecting the checks, then preserve the rejected and superseded records so the history explains why a new decision was necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Walk a candidate through rejection and acceptance
&lt;/h2&gt;

&lt;p&gt;Imagine a hypothetical upgrade with passing behavior tests and an audit covering its source commit. During deployment preparation, the operator changes the migration recipient. The source tests remain green, but the reviewed operation no longer matches. The receipt should invalidate the operation review and any rehearsal dependent on that recipient.&lt;/p&gt;

&lt;p&gt;The fix is to review the changed parameter, rerun affected checks and issue a new decision for the exact payload. Unaffected evidence can remain valid if its dependencies are unchanged and that conclusion is recorded. This avoids both careless reuse and an expensive ritual of rerunning everything without asking what changed.&lt;/p&gt;

&lt;p&gt;Now suppose deployment succeeds but the observed administrator differs from the approved configuration. Record the actual state and keep operational acceptance blocked. Diagnose the mismatch and use the authorized incident or correction procedure. Never edit the expected value after execution merely to make the comparison pass.&lt;/p&gt;

&lt;p&gt;Operations should receive alerts tied to the same model: implementation or role changes, violated accounting properties, stale dependencies and failures of required off-chain services. Every alert needs a destination, an accountable responder and an available action. A notification without authority to respond leaves the handoff unfinished.&lt;/p&gt;

&lt;p&gt;The release is ready for operational acceptance when the approved candidate, observed chain state and assigned response process agree within their documented limits. Keep the receipt with the release, reopen affected decisions when inputs change, and use the linked specialist guide for the next missing piece of evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  More insights to read
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.bulbapp.io/p/e2580ef7-324b-442d-b818-92b8c8442b3d/five-smart-contract-upgrade-patterns-compared" rel="noopener noreferrer"&gt;Five Smart Contract Upgrade Patterns Compared&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dmytronasyrov.medium.com/upgradeable-solidity-smart-contracts-part-1-versioning-7e6e97cafc28" rel="noopener noreferrer"&gt;Upgradeable Solidity Smart Contracts. Part 1 — Versioning&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/pharos-production/web3-smart-contracts-oracles-part-1-3905b127c01d" rel="noopener noreferrer"&gt;Web3. Smart Contracts. Oracles. Part 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/pharos-production/smart-contracts-their-potential-and-real-limitations-part-2-40942c055d79" rel="noopener noreferrer"&gt;Smart Contracts. Their Potential and Real Limitations. Part 2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/pharos-production/smart-contracts-their-potential-and-real-limitations-part-1-222fe44ee14c" rel="noopener noreferrer"&gt;Smart Contracts. Their Potential and Real Limitations. Part 1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  About the author
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3y3gv5cvg1m0yfn97ofb.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3y3gv5cvg1m0yfn97ofb.jpg" width="112" height="112" alt="Portrait of Dmytro Nasyrov wearing a dark suit and light blue shirt against a dark background."&gt;&lt;/a&gt;&lt;br&gt;
&lt;small&gt;Dmytro Nasyrov. Photo supplied by the author.&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written by &lt;a href="https://pharosproduction.com/dmytro-nasyrov/" rel="noopener noreferrer"&gt;Dmytro Nasyrov&lt;/a&gt; PhD, software architect with 24 years of production experience. Dmytro is the founder and CTO of Pharos Production. He works on production software architecture for FinTech, AI, Web3 and blockchain systems.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>solidity</category>
      <category>web3</category>
      <category>security</category>
      <category>devops</category>
    </item>
    <item>
      <title>GitHub Release to Zenodo DOI</title>
      <dc:creator>Dmytro Nasyrov</dc:creator>
      <pubDate>Thu, 17 Sep 2026 07:21:09 +0000</pubDate>
      <link>https://dev.to/dmytronasyrov/github-release-to-zenodo-doi-265f</link>
      <guid>https://dev.to/dmytronasyrov/github-release-to-zenodo-doi-265f</guid>
      <description>&lt;p&gt;&lt;small&gt;Cover illustration: evolving source becomes one preserved release.&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;A GitHub release can exist before its Zenodo archive is ready. A DOI can resolve while the metadata still misrepresents the files. A discovery service can list the record without having checked whether the software works. Treating those events as one success state makes a release difficult to reproduce and surprisingly easy to describe incorrectly.&lt;/p&gt;

&lt;p&gt;This guide follows a real software release through those boundaries. The example is version 1.1.0 of an LLM-as-a-judge cost calculator, published on August 16, 2026. Its GitHub release, archived files, DOI resolution, DataCite metadata, OAI-PMH response and OpenAIRE entry were checked again on September 17. The useful deliverable is a small verification receipt that tells the next developer exactly what was preserved and what each check established.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the object someone will cite
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/PharosProduction/llm-as-a-judge-cost-calculator/releases/tag/v1.1.0" rel="noopener noreferrer"&gt;calculator release&lt;/a&gt; is a browser application with calculation code, tests, methodology and dated pricing registries. That combination gives another developer something to inspect and reuse. A repository containing only an announcement would offer much less reason to preserve a particular version.&lt;/p&gt;

&lt;p&gt;The release work behind &lt;a href="https://pharosproduction.com" rel="noopener noreferrer"&gt;Pharos Production software development&lt;/a&gt; supplies the concrete example here: a cost model packaged with explicit assumptions and separate terms for code and data. The engineering problem is retaining the inputs behind an estimate when the application and vendor prices later change. Archiving a named release preserves those inputs; it does not keep them current.&lt;/p&gt;

&lt;p&gt;The identities checked for this example are:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Object&lt;/th&gt;
&lt;th&gt;Observed identifier&lt;/th&gt;
&lt;th&gt;What it identifies&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GitHub release&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v1.1.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The named release and its notes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Git commit&lt;/td&gt;
&lt;td&gt;&lt;code&gt;67b6d1d9d741214a986ea4d7ce7111a99605bbf3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The source revision resolved from that tag&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zenodo record&lt;/td&gt;
&lt;td&gt;&lt;code&gt;21963489&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The published archive and its metadata&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Version DOI&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10.5281/zenodo.21963489&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;This particular archived version&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Concept DOI&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10.5281/zenodo.21963488&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The work across its versions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On the verification date, the &lt;a href="https://zenodo.org/records/21963489" rel="noopener noreferrer"&gt;public Zenodo record&lt;/a&gt; contained one ZIP file of 133,258 bytes. Its advertised MD5 checksum matched the downloaded archive. The four citation and license files inside that ZIP also matched their counterparts at the pinned Git commit byte for byte. This is a bounded file comparison, not a claim that every executable path was retested for this article.&lt;/p&gt;

&lt;p&gt;Record the commit separately from the tag. In this case GitHub's release response reported &lt;code&gt;immutable: false&lt;/code&gt;; the existence of a release URL alone therefore did not establish GitHub release immutability. The version DOI and retained archive checksum provide different evidence from a mutable repository page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Complete preflight before enabling automatic deposits
&lt;/h2&gt;

&lt;p&gt;Choose the smallest package that supports independent use. Include source, installation or execution instructions, dependency information, representative tests and the data needed for the documented example. If a benchmark depends on a private dataset that cannot be shared, say what remains unavailable and how that limits reproduction.&lt;/p&gt;

&lt;p&gt;Check the actual archive contents, particularly when a project uses generated binaries, submodules, large files or separately hosted assets. A successful source-code archive does not by itself show that every resource mentioned in the README was deposited. The calculator example has no uploaded GitHub release assets; its archived ZIP is the object inspected here.&lt;/p&gt;

&lt;p&gt;For reproduction, preserve the invocation as well as the program. A calculator result needs its workload values; a benchmark needs its configuration and measurement procedure. A DOI pointing to the right code does not tell a reader which options produced a reported number. Put those inputs in a small example or run manifest, with expected outputs and tolerances where relevant. If an external service is required, document that dependency and the resulting limits. A source archive can preserve an algorithm while leaving the environment needed to execute it only partly reproducible.&lt;/p&gt;

&lt;p&gt;Confirm that publishing every included file is intended. Remove secrets, private configuration, customer records and accidental build outputs before tagging. Decide authorship from contributions rather than copying whoever happens to operate the release account. Verify the creator's name, affiliation and ORCID against the intended citation, and retain that decision with the release review.&lt;/p&gt;

&lt;p&gt;GitHub's official guidance states:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Make sure to include a license in your repository so readers know how they can reuse your work.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;GitHub, &lt;a href="https://docs.github.com/en/repositories/archiving-a-github-repository/referencing-and-citing-content" rel="noopener noreferrer"&gt;Referencing and citing content&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That instruction belongs in preflight because an archive cannot repair missing permission by assigning an identifier. A useful review asks which files are covered by each license, where the applicable terms are stored and whether third-party materials have separate conditions. A repository-level badge is too coarse for a package containing differently licensed components.&lt;/p&gt;

&lt;p&gt;For the integration itself, GitHub documents public-repository access and notes that an organization owner may need to approve the Zenodo application. Review that access deliberately. Then use Zenodo's GitHub settings to synchronize the repository list and enable the intended repository. Do not interpret a successful account connection as evidence that the correct repository is enabled. The &lt;a href="https://help.zenodo.org/docs/github/enable-repository/" rel="noopener noreferrer"&gt;enable-repository guide&lt;/a&gt; describes these as separate steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give citation metadata one clear owner
&lt;/h2&gt;

&lt;p&gt;Keep &lt;code&gt;CITATION.cff&lt;/code&gt; in the repository root when you want GitHub to offer a citation through its interface. It describes the software for people and citation tools. The example's archived file includes the title, version, author, ORCID, repository URL and a description of its two license scopes.&lt;/p&gt;

&lt;p&gt;Zenodo also accepts &lt;code&gt;.zenodo.json&lt;/code&gt;. Its &lt;a href="https://help.zenodo.org/docs/github/describe-software/zenodo-json/" rel="noopener noreferrer"&gt;current metadata documentation&lt;/a&gt; makes the precedence explicit: when both files exist, the GitHub archiving integration uses &lt;code&gt;.zenodo.json&lt;/code&gt; and ignores &lt;code&gt;CITATION.cff&lt;/code&gt;. It does not merge them. A corrected author in CFF cannot compensate for a stale creator in the JSON file.&lt;/p&gt;

&lt;p&gt;Choose an owner for each repeated field. For example, a release review can require title, version and creators to agree across the two files, while reserving Zenodo-specific related identifiers for JSON. This is a proposed consistency check, not a claim that either service enforces your repository's policy automatically.&lt;/p&gt;

&lt;p&gt;A short excerpt from the actual archived JSON shows why reviewing fields individually matters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"upload_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"software"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"access_right"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"open"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"license"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"other-open"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eng"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an excerpt, not a complete upload template. In particular, &lt;code&gt;other-open&lt;/code&gt; describes the historical release input; it is not a recommendation to replace specific license declarations with a generic value. The full file contains creators, description, keywords, related identifiers and notes defining the code/data boundary.&lt;/p&gt;

&lt;p&gt;Validate the exact files contained in the tag. Checking only the default branch after publication can inspect a later correction that never entered the archive. JSON syntax validation catches malformed JSON, but it does not prove that an identifier, relationship or license choice is appropriate. Review the supported fields and then inspect the resulting record.&lt;/p&gt;

&lt;p&gt;The same distinction applies to CFF validation. Schema validation and correct citation intent are separate checks. A well-formed file can still point to the wrong software version or credit an unrelated paper. Keep the intended citation in the review so a mechanically valid result can be compared with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose a version DOI for reproducible claims
&lt;/h2&gt;

&lt;p&gt;Zenodo's &lt;a href="https://support.zenodo.org/help/en-gb/1-upload-deposit/97-what-is-doi-versioning" rel="noopener noreferrer"&gt;DOI versioning explanation&lt;/a&gt; distinguishes a specific version from the concept representing all versions. The first publication creates both identifiers; later versions receive their own version identifiers. The relationships belong in metadata rather than in an invented suffix appended to the DOI.&lt;/p&gt;

&lt;p&gt;Use the version DOI when a result depends on the exact software or data you used. A cost estimate based on the calculator's August pricing registry should identify that release, its workload inputs and the registry's verification date. A general project description may instead refer to the concept DOI because its subject is the evolving work.&lt;/p&gt;

&lt;p&gt;Both identifiers resolved to record 21963489 during this check. That shared destination does not make their meanings interchangeable. It only describes their resolution at the time of observation. A receipt should store both identifiers with explicit field names, rather than a single ambiguous &lt;code&gt;doi&lt;/code&gt; field copied from whichever badge was easiest to find.&lt;/p&gt;

&lt;p&gt;Likewise, keep release version, publication date and data-verification date separate. Version 1.1.0 was published on August 16; its pricing sources were described as verified on August 13. Neither date makes those prices current in September. This article verifies the preserved object and its metadata, not today's commercial pricing.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://pharosproduction.com/insights/engineering/llm-observability-cost/" rel="noopener noreferrer"&gt;LLM observability cost methodology&lt;/a&gt; explains the problem the calculator addresses: different billing entities require explicit workload assumptions. Keeping that methodological context beside the versioned artifact helps a reader understand an estimate. The company article remains contextual documentation, not independent validation of the software.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preserve mixed licenses across representations
&lt;/h2&gt;

&lt;p&gt;The example assigns MIT to source code and the static application. Its &lt;code&gt;DATA-LICENSE.md&lt;/code&gt; assigns CC BY 4.0 to normalized pricing records in &lt;code&gt;data/&lt;/code&gt; and &lt;code&gt;docs/data/&lt;/code&gt;. Those paths matter: two license names without file scope could be misread as offering a choice of terms for every file.&lt;/p&gt;

&lt;p&gt;Zenodo's &lt;a href="https://help.zenodo.org/docs/deposit/describe-records/licenses/" rel="noopener noreferrer"&gt;license guidance&lt;/a&gt; permits declaring multiple licenses for mixed uploads. Keep the file-level explanation inside the archive as well as in record metadata. A downloaded ZIP should remain understandable without requiring the reader to reconstruct an earlier state of its landing page.&lt;/p&gt;

&lt;p&gt;The September inspection exposed three different representations of the same release. The archived JSON retained &lt;code&gt;other-open&lt;/code&gt;. The current legacy Zenodo Records API exposed a single MIT license object plus descriptive notes. DataCite's &lt;code&gt;rightsList&lt;/code&gt; and the OAI-PMH response each included MIT and CC BY 4.0. The public record's metadata had therefore evolved beyond the original release input.&lt;/p&gt;

&lt;p&gt;This is why reading only &lt;code&gt;metadata.license.id&lt;/code&gt; would have produced an incomplete account. A consumer using that field alone could miss the data license even though it was present in other exports and in the archive. Compare the rights arrays, human-readable notes and actual license files before declaring that the metadata lost or preserved every condition.&lt;/p&gt;

&lt;p&gt;Do not generalize this observation into a promise that the integration automatically reconstructs mixed licensing. The evidence establishes the archived input and the current published outputs. It does not establish that every intermediate transformation was automatic. Keep any post-publication metadata correction in the release history so later maintainers can explain the difference.&lt;/p&gt;

&lt;p&gt;A practical review assigns a row to each materially different file group: paths, copyright holder or attribution, license file and exported license identifier. If a component's rights are unclear, resolve that uncertainty before depositing it. Adding a second identifier to metadata is not a substitute for permission to distribute a file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Publish once, then reconcile the integration result
&lt;/h2&gt;

&lt;p&gt;Prepare the release from the reviewed commit, confirm the intended tag and publish through the normal GitHub release workflow. Enabling the repository is preparation; creating the GitHub release triggers the integration. A tag pushed by itself should not be treated as evidence that this documented release workflow completed.&lt;/p&gt;

&lt;p&gt;Zenodo's &lt;a href="https://help.zenodo.org/docs/github/archive-software/github-upload/" rel="noopener noreferrer"&gt;archiving guide&lt;/a&gt; explicitly includes a processing interval. Wait for the integration result, open the resulting record and compare it with the intended release. The existence of a GitHub release page is only the first observation in this sequence.&lt;/p&gt;

&lt;p&gt;Use separate operational states: release published, ingestion pending, record published, DOI resolved and metadata verified. Store a timestamp and evidence URL for each completed state. These are suggested states for your own release process; they are not a list of status names returned by every service.&lt;/p&gt;

&lt;p&gt;When ingestion reports a metadata error, inspect the error for that exact release. Correct the responsible metadata before deciding on a new release. When the result is merely unknown because a request timed out, first look for an existing matching record. Repeating publication immediately can create another object without resolving the uncertainty about the first attempt.&lt;/p&gt;

&lt;p&gt;Keep a retry decision tied to repository, tag and resolved commit. A background worker should never interpret an empty response as unconditional permission to make a fresh deposit. Record the unresolved state and the next read-only reconciliation step. This also helps a human maintainer resume after an interrupted browser session without guessing which actions occurred.&lt;/p&gt;

&lt;p&gt;For CI, put preflight checks before release creation and verification after it. A read-only verification job can fail without creating a second release. Keep publication credentials out of the verifier; reading the public evidence below does not require them. That separation gives reviewers a useful tool without handing every verification run authority to publish.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl03560xv3svyhh43fof8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl03560xv3svyhh43fof8.png" alt="Separate evidence checks connect a pinned Git commit to a Zenodo archive and version DOI, then branch to DataCite, OAI-PMH and OpenAIRE. Software Heritage remains independently unconfirmed." width="800" height="471"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;small&gt;Each box requires its own observation. A successful archive does not certify software correctness or completion in every downstream service.&lt;/small&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify the archive and the exported metadata
&lt;/h2&gt;

&lt;p&gt;The following Python 3 script makes a bounded set of public, read-only requests. Run it in an empty directory: it saves four response snapshots and the archive for inspection. It does not execute downloaded software, create a deposit, modify permissions or require a token. Any failed request or assertion stops the check.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.request&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlopen&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;xml.etree.ElementTree&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ET&lt;/span&gt;

&lt;span class="n"&gt;DOI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10.5281/zenodo.21963489&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;RECORD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;21963489&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;write_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;

&lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://zenodo.org/api/records/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;RECORD&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;record.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;doi&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;DOI&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;metadata&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;version&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.1.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;published&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;files&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;  &lt;span class="c1"&gt;# Expected for this particular release.
&lt;/span&gt;&lt;span class="n"&gt;archive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;links&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;self&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;release.zip&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;archive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;size&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;md5:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;md5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;archive&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;checksum&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;attributes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.datacite.org/dois/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;DOI&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;datacite.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;attributes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;doi&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;DOI&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;findable&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;version&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.1.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;rights&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rightsIdentifier&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rightsList&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cc-by-4.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;rights&lt;/span&gt;

&lt;span class="n"&gt;oai&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ET&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromstring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://zenodo.org/oai2d?verb=GetRecord&amp;amp;metadataPrefix=oai_dc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;identifier=oai:zenodo.org:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;RECORD&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;oai.xml&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;ns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;o&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://www.openarchives.org/OAI/2.0/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;oai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;o:error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;oai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findtext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.//o:header/o:identifier&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;namespaces&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;oai:zenodo.org:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;RECORD&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;openaire&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.openaire.eu/search/software?doi=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;DOI&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;format=json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openaire.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;openaire&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;response&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;header&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Archive SHA-256:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;archive&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bounded checks passed; inspect the saved metadata and match identity.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script's expected record, version, file count and two rights identifiers are deliberately specific. Change those expectations only after reviewing a different release. Weakening the assertions until an unrelated record passes would remove the reason to run the check.&lt;/p&gt;

&lt;p&gt;The observed archive SHA-256 was &lt;code&gt;3e72a12ee4ef08095bd57e35c92ef061fe995b153cd15251bfedcb870ae30655&lt;/code&gt;. Keep that digest with the timestamp and original download URL. The service-provided MD5 is useful for checking a completed download against its record; the separately retained SHA-256 supplies a stronger content fingerprint. Neither digest establishes who authored the code or whether it is correct.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read each verification result at its actual scope
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://support.datacite.org/docs/rest-api" rel="noopener noreferrer"&gt;DataCite's public API&lt;/a&gt; exposes findable DOI metadata. For this release it returned the expected creator, version, both rights declarations and an &lt;code&gt;IsVersionOf&lt;/code&gt; relationship to the concept DOI. Those checks establish registered, discoverable metadata with the expected fields. They do not establish peer review or successful software execution.&lt;/p&gt;

&lt;p&gt;OAI-PMH answers another question: can a harvester retrieve this repository record in the requested metadata format? The saved &lt;code&gt;GetRecord&lt;/code&gt; response contained the expected OAI identifier, title, DOI and both rights declarations. Inspect the XML body as well as HTTP status, because a protocol-level error can still arrive in a successful HTTP response.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://api.openaire.eu/search/software?doi=10.5281/zenodo.21963489&amp;amp;format=json" rel="noopener noreferrer"&gt;OpenAIRE software query&lt;/a&gt; returned one matching result. Its title, creator, original OAI identifier and landing-page URL matched the example. Its returned status included &lt;code&gt;UNDER_CURATION&lt;/code&gt;; the article therefore claims observed discovery, not completed curation. A result count alone would be insufficient without those identity comparisons.&lt;/p&gt;

&lt;p&gt;The script checks that count but leaves the richer OpenAIRE identity comparison visible in the saved response. In a maintained verifier, make the expected identifier and landing URL explicit assertions too. Avoid a title-only comparison: titles can change, be reused or appear on related records.&lt;/p&gt;

&lt;p&gt;Software Heritage is a further boundary. The Zenodo API response contained an empty &lt;code&gt;swh&lt;/code&gt; object during this inspection, so completed Software Heritage preservation was not established. Keep that result unconfirmed rather than promoting it to success because the DOI resolves. A later verified archival identifier can update that particular observation.&lt;/p&gt;

&lt;p&gt;These services provide useful persistence and discovery infrastructure. Their presence in a release record should not be presented as endorsement by CERN, a security audit, a license-compatibility opinion or a guarantee of search traffic. State exactly which response was inspected and what matched.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make a failed check actionable
&lt;/h2&gt;

&lt;p&gt;A verifier should distinguish a content mismatch from an unavailable observation. If the downloaded archive has the wrong checksum, stop treating that download as the expected object. Preserve the response details and compare the advertised file identity before investigating further. Do not replace the expected checksum with the newly observed value simply to restore a passing result.&lt;/p&gt;

&lt;p&gt;If a public API returns an access error, timeout or rate limit, record the affected service and leave its observation unresolved. That result does not prove the record was removed. It also does not establish that the previous successful response still describes current metadata. Retain the last verified snapshot with its original timestamp and keep the fresh failure separate.&lt;/p&gt;

&lt;p&gt;If DataCite resolves the expected DOI but shows an unexpected creator or rights list, classify the problem as a metadata mismatch. Inspect the published Zenodo metadata and the corresponding export before editing repository files. Changing the current branch cannot retroactively change which bytes were archived in the existing ZIP, so it may be the wrong repair for the observed defect.&lt;/p&gt;

&lt;p&gt;If OpenAIRE returns no matching result while the record and DOI are available, report that particular discovery check as unconfirmed. Choose a bounded later recheck appropriate to the release process. Do not claim a guaranteed indexing delay or create another deposit to make a search result appear. Discovery latency and archive identity require different responses.&lt;/p&gt;

&lt;p&gt;These branches deserve a simple responsibility map. A release maintainer owns the tag and package contents. A metadata reviewer owns creator, relationship and rights corrections. A verifier records observations and can block acceptance, but should not invent an alternative publication when a downstream service is unavailable. A small project can assign all three responsibilities to one person while still keeping their decisions distinct.&lt;/p&gt;

&lt;p&gt;Preserve enough context for another operator to diagnose a failure: request URL, UTC time, response status, expected identity and the comparison that failed. Avoid putting tokens or private account responses into a public receipt. The example uses public endpoints, so its relevant evidence can be shared without exposing publication credentials.&lt;/p&gt;

&lt;p&gt;Finally, a successful rerun should append a new observation. It should not erase the failed attempt or turn its timestamp into the time of eventual success. That history explains whether the release was initially wrong, temporarily unavailable or waiting for another service. It also prevents an incident review from confusing a later repair with what readers could access at publication time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Correct metadata without disguising a new release
&lt;/h2&gt;

&lt;p&gt;A misspelled creator or incomplete description is a metadata correction. Zenodo's &lt;a href="https://help.zenodo.org/docs/deposit/manage-records/" rel="noopener noreferrer"&gt;record-management guidance&lt;/a&gt; permits published metadata to be edited without changing the DOI. Preserve the previous and corrected values, the reason and the verification timestamp, especially when downstream exports are part of the acceptance criteria.&lt;/p&gt;

&lt;p&gt;Changes to calculation logic or pricing files create a different reproducibility object. Use the &lt;a href="https://help.zenodo.org/docs/deposit/manage-versions/" rel="noopener noreferrer"&gt;version-management workflow&lt;/a&gt; for substantive file changes, then cite the new version when reporting results produced with it. Keep the old version identifiable for people who used its earlier behavior.&lt;/p&gt;

&lt;p&gt;Avoid an absolute claim that published files can never change. The current &lt;a href="https://help.zenodo.org/docs/deposit/manage-files/" rel="noopener noreferrer"&gt;file-management documentation&lt;/a&gt; describes a limited correction window: minor file edits can be initiated within 30 days, with the draft published within 45 days of the original publication. Outside that window, justified exceptional cases go through support. That exception makes a retained archive digest worthwhile even when a DOI is stable.&lt;/p&gt;

&lt;p&gt;For this calculator, a new vendor price or formula changes the inputs behind an estimate and should be reviewed as a new version, not silently described as the old reproducible result. Updating an explanatory sentence about license scope is a different operation. Classify the change by what a reader would need to reproduce, then preserve the corresponding history.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finish with a release receipt someone else can use
&lt;/h2&gt;

&lt;p&gt;Keep the final receipt small: repository, tag, commit, record URL, version DOI, concept DOI, archive filename and digest, expected creators, license scopes, check timestamps and unresolved observations. Link each conclusion to the response or file that supports it. A green job badge without these identities is weak evidence for a later investigation.&lt;/p&gt;

&lt;p&gt;Accept the release when the intended archive is retrievable, its metadata describes the right work and the version citation resolves correctly. Report downstream discovery separately, including pending or unconfirmed services. This leaves the next maintainer with a precise starting point: a preserved object, a readable citation and a documented boundary around what was actually verified.&lt;/p&gt;

&lt;h2&gt;
  
  
  More insights to read
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://medium.com/pharos-production/the-ai-agent-reported-14-green-checks-acceptance-still-failed-8dd5320d4da7" rel="noopener noreferrer"&gt;The AI Agent Reported 14 Green Checks. Acceptance Still Failed.&lt;/a&gt; — Dmytro Nasyrov&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dmytronasyrov.medium.com/the-ai-agents-were-done-the-delivery-clock-wasnt-526401552928" rel="noopener noreferrer"&gt;The AI Agents Were Done. The Delivery Clock Wasn’t.&lt;/a&gt; — Dmytro Nasyrov&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://blog.zenodo.org/2017/11/02/version-field/" rel="noopener noreferrer"&gt;Version field launched!&lt;/a&gt; — Alex Ioannidis&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.blog/news-insights/company-news/enhanced-support-citations-github/" rel="noopener noreferrer"&gt;Enhanced support for citations on GitHub&lt;/a&gt; — Arfon Smith&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://blog.zenodo.org/2017/05/30/doi-versioning-launched/" rel="noopener noreferrer"&gt;Zenodo now supports DOI versioning!&lt;/a&gt; — Lars Holm Nielsen&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  About the author
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvvv1zfhgi0f5dtx93xa4.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvvv1zfhgi0f5dtx93xa4.jpg" width="112" height="112" alt="Portrait of Dmytro Nasyrov wearing a dark suit and light blue shirt against a dark background."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;small&gt;Dmytro Nasyrov. Photo supplied by the author.&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written by &lt;a href="https://pharosproduction.com/dmytro-nasyrov/" rel="noopener noreferrer"&gt;Dmytro Nasyrov&lt;/a&gt; PhD, software architect with 24 years of production experience. Dmytro is the founder and CTO of Pharos Production. He works on production software architecture for FinTech, AI, Web3 and blockchain systems.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>github</category>
      <category>opensource</category>
      <category>devops</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
