<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Jitsumi]]></title><description><![CDATA[Practical tools for the PC you already use.]]></description><link>https://jitsumi.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6ae845c8a4acd9000f3a66f4/78c775f3-f170-4efa-9cb9-96b95f545506.jpg</url><title>Jitsumi</title><link>https://jitsumi.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Sat, 07 Nov 2026 10:09:05 GMT</lastBuildDate><atom:link href="https://jitsumi.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Request IDs, Trace IDs and Job IDs: Follow One Failure Across Services]]></title><description><![CDATA[Follow failures with request IDs, trace context and durable job IDs. Understand retries, propagation boundaries and logging privacy.]]></description><link>https://jitsumi.hashnode.dev/request-trace-job-ids-follow-failure-services</link><guid isPermaLink="true">https://jitsumi.hashnode.dev/request-trace-job-ids-follow-failure-services</guid><category><![CDATA[api]]></category><dc:creator><![CDATA[Jitsumi]]></dc:creator><pubDate>Fri, 06 Nov 2026 11:03:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ae845c8a4acd9000f3a66f4/b4988050-f747-4999-a1af-f3fd0d2e3027.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A customer reports that an export failed at 14:03. Your API log contains the request, a worker log contains an exception, and a storage log contains a timeout. All three happened near the same minute. That does not yet prove they belong together.</p>
<p>Give individual requests an identifier, connect distributed work through trace context, and preserve a separate job or operation identity for work that outlives one request. These identifiers solve different lookup problems. Keeping their meanings explicit makes a failure easier to follow without dumping credentials or entire payloads into logs.</p>
<p>This walkthrough uses an illustrative report-export service. It explains the design rather than prescribing one tracing vendor or claiming a benchmark of observability tools.</p>
<h2>Decide what each identifier names</h2>
<p>Teams use “correlation ID” to mean several things. Before choosing a header, define its scope:</p>
<table>
<thead>
<tr>
<th>Identifier</th>
<th>Question it should answer</th>
</tr>
</thead>
<tbody><tr>
<td>Request ID</td>
<td>Which individual HTTP request or attempt was this?</td>
</tr>
<tr>
<td>Trace ID</td>
<td>Which distributed trace does this work participate in?</td>
</tr>
<tr>
<td>Span ID</td>
<td>Which particular operation within that trace produced this record?</td>
</tr>
<tr>
<td>Job ID</td>
<td>Which durable background job does the product track?</td>
</tr>
<tr>
<td>Idempotency key</td>
<td>Which repeated submissions represent the same intended operation?</td>
</tr>
</tbody></table>
<p>A request-ID convention belongs to your system or infrastructure; its exact header and lifecycle are not automatically standardised by calling it <code>X-Request-ID</code>. Document which layer creates it, whether downstream services retain it, and how retries are identified.</p>
<p>For tracing, prefer the established <a href="https://www.w3.org/TR/trace-context/">W3C Trace Context format</a> through a compatible library. It provides a portable way to propagate trace relationships across services. A random UUID in a custom header can be useful for lookup, but it does not by itself establish parent-child spans, timing or error relationships.</p>
<h2>Follow one export through the boundaries</h2>
<p>Suppose a browser submits an export. The gateway assigns request <code>R1</code>. The API creates job <code>J1</code>, commits it and returns a status URL. A worker later claims <code>J1</code>, writes a file, then records completion.</p>
<p>Useful structured events might be represented as:</p>
<pre><code class="language-json">{"event":"export.accepted","request_id":"R1","job_id":"J1","status":202}
{"event":"export.started","job_id":"J1","attempt":1}
{"event":"export.failed","job_id":"J1","attempt":1,"error_code":"storage_timeout"}
</code></pre>
<p>These are illustrative labels, not valid W3C trace IDs or real production records. Actual trace IDs and span IDs should come from the tracing implementation and can be attached as additional structured fields.</p>
<p>The job ID bridges time and execution ownership. Even if a trace is sampled out, expires from the backend or deliberately ends at the queue boundary, the product still needs to know which job failed. Store the durable state in the job system; logs help investigate it, rather than becoming the only record that work exists.</p>
<p>On a retry, retain <code>J1</code> and increment or otherwise identify the attempt. A new worker attempt is different work on the same durable job. Recording only a new random identifier would make the relationship needlessly difficult to recover.</p>
<h2>Propagation carries relationships, not permission</h2>
<p>OpenTelemetry's <a href="https://opentelemetry.io/docs/concepts/context-propagation/">context propagation documentation</a> explains how context is injected into and extracted from carriers such as HTTP headers. Instrumentation normally creates child operations while preserving the trace relationship. Let a compatible SDK handle that work instead of hand-building trace headers around every call.</p>
<p>Decide what happens at an external trust boundary. An incoming trace ID or request ID is caller-controlled until your gateway or instrumentation validates and applies its policy. It must never select an account, approve a tenant, bypass rate limiting or grant access to logs.</p>
<p>Also avoid unlimited, unchecked request-ID strings. A system that reflects arbitrary values into headers or concatenates them into text logs has introduced an input-handling problem into its diagnostic path. If a trusted proxy supplies the identifier, establish that trust through the actual deployment configuration, not the header's name.</p>
<p>For asynchronous work, use the queue integration's supported propagation or linking behaviour. A long-lived job can have a different tracing lifecycle from the HTTP request that created it. Preserve the job identity regardless, and document whether worker attempts continue a trace, start a new trace or link to earlier work.</p>
<h2>Correlate logs with spans without copying the request</h2>
<p><a href="https://opentelemetry.io/docs/specs/otel/logs/data-model/">OpenTelemetry's log data model</a> includes trace and span fields so logs emitted during a span can be connected to it. Add them through supported logging integration where available. Include the service name, operation, outcome and stable error classification as well: an ID with no meaningful event tells you little.</p>
<p>For an export failure, <code>job_id</code>, <code>attempt</code>, <code>error_code</code>, duration and the affected subsystem can be enough to locate the next step. The access token, full SQL query, customer document and raw request body usually add exposure without helping that first diagnosis.</p>
<p><a href="https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html">OWASP's logging guidance</a> identifies credentials, session values and other sensitive fields that should not be recorded directly. Prefer an explicit event schema to a generic “log every header” middleware. Redaction after collection is a weaker boundary because the original has already entered the pipeline.</p>
<p>Use structured serialisation rather than concatenating untrusted text into a line format. Test how your logging stack handles control characters and oversized fields. Then protect the log store with appropriate access and retention: a random correlation ID does not make the associated records public.</p>
<h2>Baggage travels further than it looks</h2>
<p>Trace context is not an invitation to attach customer details. OpenTelemetry <a href="https://opentelemetry.io/docs/concepts/signals/baggage/">baggage</a> can propagate key-value data downstream, including through automatically instrumented network calls. A field added for convenience in one service can reach a third-party API later.</p>
<p>Keep personally identifying data, credentials and sensitive business content out of propagation headers. Use non-sensitive routing or diagnostic metadata only where its distribution is understood and permitted. Trace IDs themselves can correlate activity, so apply a deliberate boundary policy when communicating outside your system.</p>
<p>Sampling also affects what investigators can retrieve. Seeing a trace ID in a log does not prove that the backend retained a complete trace for it. Document sampling and retention so “trace not found” is interpreted as a coverage question rather than proof that the request never happened.</p>
<h2>Make support lookup work on a deliberately failed request</h2>
<p>Before an incident, create a controlled failure in a test environment. Capture the support-facing reference, locate the gateway and API events, follow the durable job, then identify the worker attempt and downstream failure. Repeat after a retry.</p>
<p>Check that the same operation can be followed without access to raw credentials or payloads. Verify that caller-supplied request or trace IDs cannot select privileges or bypass the gateway's propagation policy. An identifier does not authenticate its source; that trust comes from the authenticated connection and deployment boundary. Bound or replace oversized and malformed values according to the documented policy.</p>
<p>Finally, distinguish correlation from deduplication. An idempotency key can intentionally survive several HTTP attempts, while each attempt deserves its own request record. Jitsumi's <a href="https://jitsumi.com/posts/api-idempotency-retry-requests-without-duplicates">API idempotency guide</a> explains why those attempts should not create repeated effects.</p>
<p>A useful incident record lets you answer four questions: which request, which job, which attempt, and which dependency failed? Design those relationships before adding more log volume.</p>
<h2>References</h2>
<ul>
<li><a href="https://www.w3.org/TR/trace-context/">W3C: Trace Context</a></li>
<li><a href="https://opentelemetry.io/docs/concepts/context-propagation/">OpenTelemetry: context propagation</a></li>
<li><a href="https://opentelemetry.io/docs/specs/otel/logs/data-model/">OpenTelemetry: Logs Data Model</a></li>
<li><a href="https://opentelemetry.io/docs/concepts/signals/baggage/">OpenTelemetry: baggage and sensitive data</a></li>
<li><a href="https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html">OWASP: Logging Cheat Sheet</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Two Successful Requests, One Lost Update: Fixing API Writes with ETags]]></title><description><![CDATA[How to stop stale browser tabs from overwriting newer changes, with a runnable Python and SQLite demonstration.]]></description><link>https://jitsumi.hashnode.dev/two-successful-requests-one-lost-update-fixing-api-writes-with-etags</link><guid isPermaLink="true">https://jitsumi.hashnode.dev/two-successful-requests-one-lost-update-fixing-api-writes-with-etags</guid><dc:creator><![CDATA[Jitsumi]]></dc:creator><pubDate>Wed, 04 Nov 2026 23:19:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ae845c8a4acd9000f3a66f4/19c66563-cd50-4fa0-bdc1-a5338ce887f2.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Alice edits an article's title. Bob edits its summary. They both click Save. Both requests succeed.</p>
<p>Then Alice reloads the page. Her title is gone.</p>
<p>Nothing crashed. The database accepted both writes. The application simply allowed Bob's older copy of the article to replace Alice's newer one.</p>
<p>This is a <strong>lost update</strong>. A useful defence is optimistic concurrency control: return a version identifier when a client reads a resource, then accept its write only if that version is still current. For HTTP APIs, <code>ETag</code> and <code>If-Match</code> provide an existing vocabulary for this contract.</p>
<p>The header is only half the solution. The version check must stay attached to the database write, and the editor needs a sensible way to recover when a save is rejected.</p>
<p>Let's build the failure, fix the write, and follow the conflict back to the person using the editor.</p>
<h2>The race starts when the form opens</h2>
<p>Consider this illustrative article:</p>
<pre><code class="language-json">{
  "title": "Draft",
  "summary": "Old summary"
}
</code></pre>
<p>Alice and Bob both load version 7. Their screens now contain independent copies.</p>
<table>
<thead>
<tr>
<th>Event</th>
<th>Alice's copy</th>
<th>Bob's copy</th>
<th>Stored article</th>
</tr>
</thead>
<tbody><tr>
<td>Both open the editor</td>
<td>Draft / Old summary</td>
<td>Draft / Old summary</td>
<td>Draft / Old summary</td>
</tr>
<tr>
<td>Alice changes the title</td>
<td>Ready to publish / Old summary</td>
<td>Draft / Old summary</td>
<td>Draft / Old summary</td>
</tr>
<tr>
<td>Alice saves</td>
<td>Ready to publish / Old summary</td>
<td>Draft / Old summary</td>
<td>Ready to publish / Old summary</td>
</tr>
<tr>
<td>Bob changes the summary and saves his full copy</td>
<td>Ready to publish / Old summary</td>
<td>Draft / New summary</td>
<td><strong>Draft / New summary</strong></td>
</tr>
</tbody></table>
<p>Bob never deliberately reverted the title. His form submitted a title that was correct when he opened the page.</p>
<p>This is why disabling the Save button while a request runs cannot solve the problem. The competing request can come from another tab, another device, an administrator or a background worker.</p>
<p>It also explains why two writes do not need to arrive at precisely the same instant. Bob can save ten minutes later and still overwrite a change made after his original read.</p>
<h2>Send the version back with the write</h2>
<p>An <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/ETag">ETag identifies a particular representation</a>. The server chooses its value; it does not have to be a content hash.</p>
<p>An API might return the following simplified exchange:</p>
<pre><code class="language-http">GET /articles/42 HTTP/1.1
Host: api.example.com
</code></pre>
<pre><code class="language-http">HTTP/1.1 200 OK
Content-Type: application/json
ETag: "article-42-v7"

{"title":"Draft","summary":"Old summary"}
</code></pre>
<p>The client keeps the ETag alongside the form's original data. When Alice saves, it sends the tag unchanged, including the quotes:</p>
<pre><code class="language-http">PUT /articles/42 HTTP/1.1
Host: api.example.com
Content-Type: application/json
If-Match: "article-42-v7"

{"title":"Ready to publish","summary":"Old summary"}
</code></pre>
<p>The condition means: apply this change only if the selected representation still matches this tag. <a href="https://www.rfc-editor.org/rfc/rfc9110.html#section-13.1.1">RFC 9110 defines this use of <code>If-Match</code></a>, including its role in preventing accidental overwrites.</p>
<p>Alice's save can succeed because version 7 is current. Suppose that write advances the resource to version 8.</p>
<p>Bob then submits his full copy with the same version-7 condition. The server rejects the stale write:</p>
<pre><code class="language-http">HTTP/1.1 412 Precondition Failed
Content-Type: application/json

{"error":"article_changed","message":"Reload and compare your changes."}
</code></pre>
<p>That response is a deliberately simple API design, rather than a universal error-body format. <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/412">The <code>412</code> status documents a failed precondition</a>. For this workflow, Bob's proposed update has not been applied.</p>
<p>ETags do not reserve an editing session. Alice and Bob can work freely; the conflict becomes visible when a stale copy tries to save.</p>
<h2>Put the comparison inside the update</h2>
<p>A tempting implementation is:</p>
<ol>
<li>Read the current version.</li>
<li>Compare it with the client's version.</li>
<li>Run an unconditional update.</li>
</ol>
<p>There is still a gap between steps two and three. Another request can change the row after the check passes.</p>
<p>Instead, make the expected version part of the write's predicate:</p>
<pre><code class="language-sql">UPDATE articles
SET title = ?,
    summary = ?,
    version = version + 1
WHERE id = ?
  AND version = ?;
</code></pre>
<p>The last parameter is the version associated with the representation the editor originally loaded.</p>
<p>A matching row is updated and its version advances together. A stale version matches no row. <a href="https://www.sqlite.org/lang_update.html">SQLite's UPDATE documentation</a> explicitly notes that a condition matching no rows produces a zero-row update rather than a SQL error.</p>
<p>This is a compare-and-set pattern. Its success signal comes from the database operation, not an earlier application-side guess.</p>
<p>The SQL version column and the HTTP ETag are related design choices, but they are not interchangeable syntax. An HTTP handler must validate and interpret the client's condition before invoking the storage operation. Do not implement a general ETag parser by blindly splitting any incoming header on <code>-v</code>.</p>
<h2>Reproduce both outcomes without a web framework</h2>
<p>Save the following as <code>lost_update_demo.py</code> and run it with Python 3.12:</p>
<pre><code class="language-sh">python3 lost_update_demo.py
</code></pre>
<p>On Windows, use <code>py -3.12 lost_update_demo.py</code> if you installed Python through the Windows launcher.</p>
<p>This example uses Python's <code>sqlite3</code> module and an in-memory database. It creates no database file and installs no packages. It deterministically models two readers loading the same version before either writes; it does not start an HTTP server or rely on thread timing.</p>
<pre><code class="language-python">"""Deterministic stale-write demonstration; no server or files required."""
import sqlite3


def run(guarded):
    db = sqlite3.connect(":memory:")
    db.row_factory = sqlite3.Row
    db.execute("""
        CREATE TABLE articles (
            id INTEGER PRIMARY KEY,
            title TEXT NOT NULL,
            summary TEXT NOT NULL,
            version INTEGER NOT NULL
        )
    """)
    db.execute("INSERT INTO articles VALUES (1, 'Draft', 'Old summary', 7)")
    db.commit()

    def read():
        return dict(db.execute("SELECT * FROM articles WHERE id = 1").fetchone())

    def save(copy):
        sql = """
            UPDATE articles
            SET title = ?, summary = ?, version = version + 1
            WHERE id = ?
        """
        params = (copy["title"], copy["summary"], copy["id"])
        if guarded:
            sql += " AND version = ?"
            params += (copy["version"],)
        with db:
            changed = db.execute(sql, params).rowcount
        return changed == 1

    # Both editors read before either saves. No timing tricks needed.
    alice = read()
    bob = read()
    alice["title"] = "Ready to publish"
    bob["summary"] = "New summary"

    alice_saved = save(alice)
    bob_saved = save(bob)
    final = read()
    db.close()
    label = "guarded" if guarded else "unguarded"
    print(f"{label}: Alice={alice_saved}, Bob={bob_saved}")
    print(f"  title={final['title']!r}, summary={final['summary']!r}, "
          f"version={final['version']}")
    return alice_saved, bob_saved, final


if __name__ == "__main__":
    run(False)
    run(True)
</code></pre>
<p>The observed output on Linux with Python 3.12.14 was:</p>
<pre><code class="language-text">unguarded: Alice=True, Bob=True
  title='Draft', summary='New summary', version=9
guarded: Alice=True, Bob=False
  title='Ready to publish', summary='Old summary', version=8
</code></pre>
<p>The unguarded run accepts Bob's outdated title and loses Alice's edit. The guarded run preserves Alice's title and reports that Bob did not save.</p>
<p>Notice that Bob's new summary is also absent in the guarded result. The operation rejects his whole proposed replacement. It has no rule for deciding which parts to merge.</p>
<p>The example uses <a href="https://docs.python.org/3.12/library/sqlite3.html#sqlite3.Cursor.rowcount">Python's <code>Cursor.rowcount</code></a> after an ordinary <code>UPDATE</code>. The connection context commits a successful transaction before <code>save()</code> returns. An SQL or commit error raises an exception; it is not converted into a successful save.</p>
<p>For a real endpoint, a zero-row result also needs context: the row might have been deleted, or a tenant or authorisation predicate might not match. Preserve your access-control rules and documented error policy instead of reporting every database miss as an edit conflict.</p>
<h2>A database transaction does not span a person's thinking time</h2>
<p>SQLite already <a href="https://www.sqlite.org/isolation.html">serialises writes</a>. How can this example still lose an update?</p>
<p>The reads and saves are separate operations. Serialising Alice's write before Bob's write does not tell the database that Bob's values came from an obsolete screen. An unconditional update asks it to replace those values, so it does.</p>
<p>You generally should not hold a database transaction open while someone spends ten minutes editing a form. The version travels across that gap and lets the eventual write express its original assumption.</p>
<p>For operations involving several rows, the single-row example is not the whole design. Put related writes and their checks in an appropriate transaction, define the invariant you need, and handle the database's concurrency errors. One article-version column does not protect every relationship in an application.</p>
<h2>The conflict screen is part of the feature</h2>
<p>A correct <code>412</code> response can still produce a miserable editor if the frontend immediately reloads and discards the user's draft.</p>
<p>Keep three things separate:</p>
<ul>
<li><strong>Base:</strong> the article Bob originally loaded.</li>
<li><strong>Local:</strong> Bob's unsaved edits.</li>
<li><strong>Latest:</strong> the server's current article, fetched after the conflict.</li>
</ul>
<p>In this example, comparing the three reveals a straightforward merge: Alice changed the title, Bob changed the summary. The editor can offer a combined draft with both changes.</p>
<p>That is an application policy. Two people changing the same title, or changes to fields whose meanings depend on each other, require a different decision. Even edits to separate fields can conflict semantically.</p>
<p>After reviewing the merged draft, submit it with the ETag belonging to <strong>Latest</strong>. Another person could save during the review, so a second conflict remains possible.</p>
<p><strong>Do not automatically fetch a fresh ETag and resend the same stale full document.</strong> That changes the condition while preserving the overwrite. It turns a detected conflict back into lost data.</p>
<p>Give users a way to copy their draft, inspect the changed fields and retry deliberately. A short message such as “This article changed while you were editing. Your draft is still here” is more useful than an unexplained technical error.</p>
<h2>Three details that can quietly undo the protection</h2>
<h3>Weak ETags cannot satisfy If-Match</h3>
<p>A tag such as <code>W/"article-42-v7"</code> is weak. <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/If-Match">If-Match uses strong comparison</a>, so a weak tag will not match even if its quoted text looks identical.</p>
<p>A revision-based strong ETag requires a disciplined representation contract: the same tag must not describe different bytes. Advance the revision for relevant changes, keep serialisation stable, and account for representation variants. Avoid reusing old identifiers after deletion and recreation.</p>
<h3>If-Match: * does not check which version the editor saw</h3>
<p>The wildcard checks that a current representation exists. It does not mean “the resource is unchanged since I loaded it”.</p>
<p>For stale-form protection, send the specific tag captured with the original read. Treat it as opaque client-side data.</p>
<h3>Optional conditions leave an unconditional path</h3>
<p>If your endpoint accepts writes without a condition, those callers can still overwrite newer data.</p>
<p>An API can require conditional requests and use <a href="https://www.rfc-editor.org/rfc/rfc6585.html#section-3">428 Precondition Required</a> when the required condition is missing. Explain the expected request in the error response and plan compatibility for existing clients.</p>
<p>A useful contract distinguishes:</p>
<table>
<thead>
<tr>
<th>Situation</th>
<th>Response policy for this API</th>
</tr>
</thead>
<tbody><tr>
<td>Required condition missing</td>
<td><code>428 Precondition Required</code></td>
</tr>
<tr>
<td>Valid condition refers to an outdated representation</td>
<td><code>412 Precondition Failed</code></td>
</tr>
<tr>
<td>Condition matches and the write commits</td>
<td>Normal success response</td>
</tr>
<tr>
<td>Header is malformed, access is denied, or the resource is missing</td>
<td>Handle through the API's validation and access rules</td>
</tr>
</tbody></table>
<h2>Caching, retries and stale writes need different checks</h2>
<p>The same ETag can participate in different HTTP workflows.</p>
<p>For cache validation, a conditional <code>GET</code> uses <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/If-None-Match"><code>If-None-Match</code></a>: if the stored representation still matches, the server can respond with <code>304 Not Modified</code>.</p>
<p>For an edit, <code>If-Match</code> asks the server to apply a change only while the supplied version still matches.</p>
<p>Retry deduplication answers another question: is this another attempt at the same intended operation? A version precondition alone is not a stored record of an operation's outcome. If a successful save's response disappears, the old tag may fail on a retry; the client still needs a way to establish what happened.</p>
<p>Choose each mechanism for the uncertainty it resolves. A cache header cannot merge edits, and a retry key does not, by itself, prove that a form was based on current data.</p>
<h2>Test with two copies before you ship</h2>
<p>Open the same resource in two editors before saving either one. Save a change in the first editor, then submit the second editor's stale copy.</p>
<p>The result you want is observable: the first change survives, the second save is rejected clearly, and the second person's draft remains available for comparison.</p>
<p>Also test a missing condition, a weak tag, deletion during editing, writes from background jobs, and a second conflict during merge. Ensure every path that changes the protected representation maintains its version.</p>
<p>Two green Save messages are a poor success criterion when one person's work has disappeared. Test whether the application preserves both people's intent when their assumptions stop matching.</p>
<h2>Sources and further reading</h2>
<ul>
<li><a href="https://www.rfc-editor.org/rfc/rfc9110.html#section-13.1.1">RFC 9110: If-Match and conditional request semantics</a>.</li>
<li><a href="https://www.rfc-editor.org/rfc/rfc6585.html#section-3">RFC 6585: 428 Precondition Required</a>.</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/ETag">MDN: ETag identifiers and edit collisions</a>.</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/If-Match">MDN: If-Match syntax, strong comparison and wildcard behaviour</a>.</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/412">MDN: 412 Precondition Failed</a>.</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/If-None-Match">MDN: If-None-Match and cache validation</a>.</li>
<li><a href="https://www.sqlite.org/lang_update.html">SQLite: UPDATE semantics</a> and <a href="https://www.sqlite.org/isolation.html">transaction isolation</a>.</li>
<li><a href="https://docs.python.org/3.12/library/sqlite3.html#sqlite3.Cursor.rowcount">Python 3.12: sqlite3 and affected-row counts</a>.</li>
</ul>
]]></content:encoded></item></channel></rss>