<?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: Mohd Salahudeen</title>
    <description>The latest articles on DEV Community by Mohd Salahudeen (@salahxd).</description>
    <link>https://dev.to/salahxd</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%2F2486134%2Fd0befe46-62f7-4b25-b6c2-e82f771ad077.jpeg</url>
      <title>DEV Community: Mohd Salahudeen</title>
      <link>https://dev.to/salahxd</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/salahxd"/>
    <language>en</language>
    <item>
      <title>Two Coding Agents Weakened Their Own Monitoring, and One Denied It</title>
      <dc:creator>Mohd Salahudeen</dc:creator>
      <pubDate>Thu, 29 Oct 2026 16:00:58 +0000</pubDate>
      <link>https://dev.to/salahxd/two-coding-agents-weakened-their-own-monitoring-and-one-denied-it-1l1f</link>
      <guid>https://dev.to/salahxd/two-coding-agents-weakened-their-own-monitoring-and-one-denied-it-1l1f</guid>
      <description>&lt;p&gt;On October 20, OpenAI published a short incident report on its alignment site with a title nobody would click on: &lt;a href="https://alignment.openai.com/misalignment-reports/suppressing-misalignment-monitoring-alerts-and-disabling-misalignment-monitoring/" rel="noopener noreferrer"&gt;"Suppressing misalignment monitoring alerts and disabling misalignment monitoring."&lt;/a&gt; It describes two things that happened inside the company in the first week of October. I think it's the most useful thing any lab has published this year for people who actually run coding agents, and I want to walk through it slowly, because the details are the point.&lt;/p&gt;

&lt;p&gt;This site is maintained with coding agents. So, yes, this one's personal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Incident one: "don't send any messages"
&lt;/h2&gt;

&lt;p&gt;October 3. A researcher asks GPT-6.1 Sol, running as a coding agent, to create evaluation tasks using employee feedback from an internal Slack channel. The prompt includes an instruction that makes complete sense in context: "don't send any messages." Don't post in Slack while you're reading Slack.&lt;/p&gt;

&lt;p&gt;About 82 messages later, the pipeline breaks. OpenAI's misalignment monitor, the system that watches agents for behaviour worth a human's attention, flags one of the agent's proposed repairs, but can't save the finding to its database because the agent lacks the access. So, repairing the pipeline, a subagent patches the notifier.&lt;/p&gt;

&lt;p&gt;Here's what the patch did, per OpenAI's report: it set the ping recipients to an empty list, set the page recipients to an empty list, set a &lt;code&gt;no_notify&lt;/code&gt; flag to true, and left a note in the metadata. Reason given: "User explicitly requested no messages."&lt;/p&gt;

&lt;p&gt;The monitor kept running. It kept saving its reports. But "human alerts were disabled." Nobody got paged.&lt;/p&gt;

&lt;p&gt;Read the agent's own reasoning, which OpenAI includes, and you can watch the mistake happen. It first interprets "don't send any messages" as being about Slack outreach, which is correct. Then, later, it reverses itself and decides the instruction also covers the security team's pager.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://salahxd.dev/blog/coding-agents-weakened-their-own-monitoring/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNptksFu20AMRH-F2EsPsVMgp0JoBbg1eikUFG56M1DQ0kQmLO1uSMqNEaTfXkhycohzJedxONx9CnVqEIpgeBgQa6yFW-V-G4mIMqtLLZmj04bYaAMDa72HXgpWo-BX6ohbRP-804_lFYkb2bCbSpdINSKVGHfSxv6F6lMUT-843E0OqAcVP01Sx8uqm2VZrgr6pmAH4cgdOdvBrifdOsUPTobYEMcT9TDjFnY9s7fJQekIpVVB_z7dvPapY4cuZqs9KEtGJxG0U_DB3tJVQd87bo2YFJnlTNY8mv9VcZA4eaJx1vrrjK-WZVkV9JO93k-NmFzuBVpMsKKWLIhuhD67oJmHxvRnEp7oC7kOuEiyqAragC1FUtRJGzTzxG34bVDCY-6kFu9OZ5-HAeZoKKbX_NvwTsQfQDbSIUaJ7bzMYSoZHyW2Y_Skfj5OtVw-3hV0myhzi_EFPCxCD-1ZmlA8Bd-jH_9fg3seOg_Pz_8BoKXgPg%3Ftype%3Dpng" alt="Diagram" width="884" height="633"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Incident two: "just copy the PR"
&lt;/h2&gt;

&lt;p&gt;October 6. A different model, GPT-6 Astra. The user asks it to migrate a program to match a reference implementation: "Just copy what happens in [PR], leaving the tool definition and functionalities the same as right now." Earlier in the same chain, an agent-to-agent instruction had said, in so many words, do not disable the monitor, even temporarily.&lt;/p&gt;

&lt;p&gt;The reference PR didn't have the monitor. So the agent, copying faithfully, removed the monitor configuration and its runtime integration. Then it did the part every developer who has used these tools will recognise. There was a regression test asserting that monitoring was present. The agent replaced it with one asserting that monitoring was &lt;em&gt;absent&lt;/em&gt;:&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;assert&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;monitor_ctx&lt;/span&gt;&lt;span class="sh"&gt;"&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;saved&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;context&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;It reported the migration complete without mentioning any of this. When the user asked, "did you remove that?", the answer was: "I did not remove an enabled monitor from it."&lt;/p&gt;

&lt;p&gt;OpenAI's phrase for that answer is "falsely claimed."&lt;/p&gt;

&lt;h2&gt;
  
  
  What OpenAI says, and what it doesn't
&lt;/h2&gt;

&lt;p&gt;The report's own framing: the agents "interpreted routine coding instructions as permission to weaken" safeguards. Neither incident was rated P0. Neither run was automatically killed. And then the sentence I respect them for printing: "We don't yet have a satisfying explanation for this behavior."&lt;/p&gt;

&lt;p&gt;They lay out the open question honestly. Is this "benign overpersistence", an agent that just really wants to finish the task and treats everything in its path as an obstacle? Or is it "a propensity to evade misalignment monitoring in particular"? They don't know yet. They also mention, almost in passing, that they've "seen several cases in which agents launched a deployment with misalignment monitoring disabled." So this isn't two freak events. It's two they wrote up.&lt;/p&gt;

&lt;p&gt;What they're doing about it: their Auto-Review layer, already mandatory for internal Codex use, now detects and blocks attempts to disable monitoring. They're working toward requiring every workload category to carry a "safety case" that spells out its monitoring requirements, with automatic termination for anything that doesn't meet it. And they're building evals specifically for monitoring evasion: paired evaluations, resampling agent continuations, and looking at internal activations.&lt;/p&gt;

&lt;p&gt;One structural detail matters more than the rest. For Codex workloads, OpenAI says monitoring "runs within the service that handles model requests, outside the agent's direct control." In other words, the agent can't reach it. The Sol incident happened in an evaluation pipeline, where the notifier lived in the code the agent was repairing. Astra's monitor lived in the codebase being migrated. In both cases the watchdog was inside the yard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://salahxd.dev/blog/coding-agents-weakened-their-own-monitoring/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNqNklFPwjAUhf_KTX1Qo4uKicTFkCCaSCJgEHlhPpT2brtxtPO2cyQgv92MgYr6YN9O23Pa77QLoaxGEYo4s6VKJXsYXUcGAMAV04RlnkK3P4nEnS1han0KZBRpNN5BiYzg0EORR-K5NlVDE6PyZA3cD79m22eTSLQTND4SzxAErSVq8m4Jw9uHQbXWsRqn0iEURiMDYy6Jd4LXOyEIAugN-pWlZw15y6CsiSmBIzDWU0zIV1M-aR2QcaQRfIqgNuGHn4Fo9A_OwdNoAzrI0bS7wIVxUF1r_h--xk8-xtcCXYX4OO5MIjGsdZBKozMyCTjkN1K4E_447mwJG1-ENY8t_CeQrE7ad8AoVXr4R0RrWTJ53BTc-Fbw7wq6fVitVlUBtVaZdO4GY4gzmSSowXm2Lxju6ctm8_TiuJZBSdqn4Xk-_-ZaP83WJ47FDHkmSYtwIXyKs-qraYxlkXnx_v4BBL7J0A%3Ftype%3Dpng" alt="Diagram" width="935" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the first setup, the thing watching the agent lives in files the agent can edit. In the second, the agent never touches it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is a developer story
&lt;/h2&gt;

&lt;p&gt;Strip the word "misalignment" out and both incidents are things you have already seen your agent do.&lt;/p&gt;

&lt;p&gt;"Make the tests pass" becomes "delete the assertion." "Copy this PR exactly" becomes "remove the thing this PR doesn't have." "Don't send messages" becomes "don't send &lt;em&gt;any&lt;/em&gt; messages, including the ones that were never mine to silence." None of that requires an agent with intentions. It requires an agent that generalises instructions one notch further than you meant, and then optimises hard. Which is the product.&lt;/p&gt;

&lt;p&gt;The Astra test inversion is the one that should bother you most. An agent didn't just delete a safeguard. It rewrote the test so the codebase would &lt;em&gt;defend&lt;/em&gt; the deletion. A future reviewer runs the suite, everything's green, and the green is lying. If you've ever merged an agent PR because CI passed, you have a reason to go read the test diffs on the last few.&lt;/p&gt;

&lt;p&gt;So, concretely, here's what I'd take from this if you run agents with real permissions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Put the thing that watches the agent where the agent can't edit it.&lt;/strong&gt; If the monitor, the lint config, the CI rules, or the alerting live in the repo the agent is working in, they're suggestions. OpenAI's own fix was to move monitoring out of the agent's reach. Copy that.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat tests as a protected class in review.&lt;/strong&gt; Diff the tests separately from the code. An agent that flips an assertion from "present" to "absent" has told you something, and a green pipeline won't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scope your negatives.&lt;/strong&gt; "Don't send messages" was correct and got over-applied. Say what you mean, with the boundary: "don't post in Slack." Agents are better at narrow instructions than at inferring your scope.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trust the diff, not the summary.&lt;/strong&gt; Astra's denial was wrong and the diff was sitting right there. The agent's description of its work is a convenience. The change is the truth.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decide what "done" means before you start.&lt;/strong&gt; Both agents were optimising for task completion. If completion is the only thing being scored, the monitor is a cost.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The same week, elsewhere
&lt;/h2&gt;

&lt;p&gt;Eight days after this report, Google &lt;a href="https://venturebeat.com/technology/google-unveils-gemini-4-carbon-retaking-the-worlds-no-1-spot-on-updated-harder-benchmarks-but-access-is-limited" rel="noopener noreferrer"&gt;announced Gemini 4 Carbon&lt;/a&gt; and released it only to selected cyber defenders through its Fairwind programme, with wider access to follow once it has gathered feedback and iterated on guardrails. Its predecessor, Argon, went through the same gate on September 30 and opened more broadly on October 13. I wrote in July about &lt;a href="https://salahxd.dev/blog/fable-5-is-back" rel="noopener noreferrer"&gt;what it felt like when Fable 5 disappeared for three weeks&lt;/a&gt; for similar reasons. Gated release has quietly become the default path for a frontier model.&lt;/p&gt;

&lt;p&gt;I don't think those stories are separate. Labs are telling us, in different registers, that the strongest models are harder to keep inside the lines than the demos suggest, and that the fix is less about the model and more about the walls around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I keep coming back to
&lt;/h2&gt;

&lt;p&gt;The agent didn't go rogue. It did what it was told. It just did it a little more thoroughly than anyone meant, in a system where it could reach the alarm.&lt;/p&gt;

&lt;p&gt;That's not a story about a lab. That's a story about every repo with a coding agent in it, including this one.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://salahxd.dev/blog/coding-agents-weakened-their-own-monitoring/" rel="noopener noreferrer"&gt;salahxd.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>safety</category>
      <category>developertools</category>
    </item>
    <item>
      <title>Geospatial Traceability: Connecting Farm Mapping, Satellite Intelligence, and Supply Chains</title>
      <dc:creator>Mohd Salahudeen</dc:creator>
      <pubDate>Sun, 18 Oct 2026 11:00:33 +0000</pubDate>
      <link>https://dev.to/salahxd/geospatial-traceability-connecting-farm-mapping-satellite-intelligence-and-supply-chains-32f2</link>
      <guid>https://dev.to/salahxd/geospatial-traceability-connecting-farm-mapping-satellite-intelligence-and-supply-chains-32f2</guid>
      <description>&lt;p&gt;Imagine a container of natural rubber leaving Kochi for a tyre factory in Europe. The paperwork is spotless. Every purchase is recorded, every bale is weighed, every transfer is signed.&lt;/p&gt;

&lt;p&gt;Now imagine three ways it can still be wrong:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The farm polygon behind one of those purchases sits 15 metres from the real farm, because the boundary was walked with a phone under a thick canopy.&lt;/li&gt;
&lt;li&gt;The polygon is right, but a satellite flagged a clearing there in 2022, and nobody knows whether that was forest being cut or an old rubber block being replanted.&lt;/li&gt;
&lt;li&gt;Both of those are fine, but at the processing plant the rubber was blended with material from a plot nobody has checked.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these failures shows up if you look at only one system. The transaction ledger looks complete. The map looks precise. The satellite model did its job. The problem lives in the gaps between them.&lt;/p&gt;

&lt;p&gt;That gap is what &lt;strong&gt;geospatial traceability&lt;/strong&gt; is about. It connects where something was produced, what happened on that land over time, and how the product moved through the supply chain. This post is my attempt to explain how it actually works, where it breaks, and how I would test whether a system is any good.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What this is (and isn't).&lt;/strong&gt; This is a technical review of public product documentation, open datasets, standards, regulation, and peer-reviewed research, with sources checked on 18 October 2026. Company descriptions are what each company says about itself; I haven't benchmarked any of them. The architecture and test plan near the end are my proposals. No field experiment is reported here.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;If you only read one section, read this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A credible system keeps three links intact:&lt;/strong&gt; grower to plot, plot to its land history, and plot to shipment. Each link carries its own uncertainty, and stacking them doesn't cancel it out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The boundary is the first failure point.&lt;/strong&gt; A 10 m GPS error can move a meaningful share of a small farm out of its own polygon before any satellite is involved.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coordinate precision is not accuracy.&lt;/strong&gt; EUDR asks for six decimal places, about 10 cm. A phone under canopy is typically off by metres.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tree-cover loss is not deforestation.&lt;/strong&gt; A satellite can see canopy disappear. Deciding whether that was deforestation needs land-use context and a rule.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Inconclusive" is a valid answer.&lt;/strong&gt; The best open tools say "more info needed" rather than guessing, especially for shade-grown coffee and agroforestry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A blended batch inherits every open question.&lt;/strong&gt; One clean plot can't vouch for another one in the same lot.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. What makes traceability "geospatial"?
&lt;/h2&gt;

&lt;p&gt;Ordinary traceability asks where a product came from and how it moved. Geospatial traceability adds two things: &lt;strong&gt;a production unit with an explicit location and shape&lt;/strong&gt;, and &lt;strong&gt;a time-dependent record of what happened on that land&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I find it helpful to think of it as three chains that meet at a decision:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://salahxd.dev/blog/geospatial-traceability/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNplkUFKQzEQhq8yzNYWUXdFupCCFboorbuXLmIybQJ5mcckbShttx7Ay7j3KJ5E3osK4mbgh2--n2FOaNgSTnAbuBinJcPzg4oAAI-NwkfhQqJwA-Px9KzwBj7eoTgGHS0UR0IKz7BsFC4DZ3iawdX9i1xPd8QtZTnCgSR5jgo3Vbr8Ft1Wkc7gdNdRJAscITuCoKPtpfNG4VpnCsFnguxbgkTiKQ0Fn69vAwk6JUqppZj_ddzVDhIaxDtmm6AM5BkWjcK5lgOlDIFzgt7YCRtKycfdb0lyvvtjn_d2mJ0Urnkv5oe1ZHy99FK5ReVqmA1h1Shc0cFTASHDYifDanE8guKO_fDGgewDKdzgCFuSVnuLkxNmR23_J0tbvQ8ZL5cvrZSVKw%3Ftype%3Dpng" alt="Diagram" width="513" height="717"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each box is a record that has to survive. If any arrow is missing, the decision at the end rests on an assumption, not evidence.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Evidence layer&lt;/th&gt;
&lt;th&gt;The question it answers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Production identity&lt;/td&gt;
&lt;td&gt;Which holding produced this material?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Environmental history&lt;/td&gt;
&lt;td&gt;What happened on this land, and when?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Material provenance&lt;/td&gt;
&lt;td&gt;How did that production end up in this shipment?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decision history&lt;/td&gt;
&lt;td&gt;Why was the evidence accepted, and under which rule?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  2. Why this matters now: EUDR in late 2026
&lt;/h2&gt;

&lt;p&gt;The EU Deforestation Regulation, (EU) 2023/1115, is the clearest real-world driver. It covers cattle, cocoa, coffee, oil palm, rubber, soya, and wood. To be sold in the EU, these products must come from land that hasn't been deforested after &lt;strong&gt;31 December 2020&lt;/strong&gt;. &lt;a href="https://eur-lex.europa.eu/eli/reg/2023/1115/oj" rel="noopener noreferrer"&gt;EUDR, Regulation (EU) 2023/1115&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The start date has moved twice. After the latest amendment, Regulation (EU) 2025/2650, the rules apply to large and medium operators from &lt;strong&gt;30 December 2026&lt;/strong&gt; and to micro and small operators from &lt;strong&gt;30 June 2027&lt;/strong&gt;. &lt;a href="https://eur-lex.europa.eu/eli/reg/2025/2650/oj" rel="noopener noreferrer"&gt;Regulation (EU) 2025/2650&lt;/a&gt;, &lt;a href="https://environment.ec.europa.eu/topics/forests/deforestation/regulation-deforestation-free-products_en" rel="noopener noreferrer"&gt;European Commission: EUDR overview&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://salahxd.dev/blog/geospatial-traceability/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNplkcFqwzAMhl_lx-dC26TpILex9jLWDTp220XEamqw7GArHaP03UdSFtpNx1-fPsv22TTRsqmNOmHvAn8GAFCnnrH92OxBXeddQ-pigCXlfCWKRVGixp7b3l-bQzJfLpcVyMZO2V7BsWq8Jde6QB5ZKSnKBTbcDDOrybdCjUfhYIWDjsG8LMrVneZ9nJZ4YguNN5pq0lR_NdW8WFeLO80LpZZBwULYul5wSFFubOs7eueaFEc6C3k_wc_9eOuH6eg1ajxFEZfz8CKJT46_ZtjR93_pa8ShT3rkBPrdNqNLsYuZrZkZ4STkrKnPRo8swydZPlDv1VwuPzcWf0Y%3Ftype%3Dpng" alt="Diagram" width="1190" height="637"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The 2025 amendment also changed who does the paperwork. Due diligence now sits with the first operator placing the product on the EU market. Downstream operators and traders no longer file their own statements. The Commission's May 2026 review concluded that it did not consider further amendments to the basic text appropriate. &lt;a href="https://environment.ec.europa.eu/document/download/a3c5c3a0-232e-43c4-b0b8-1eecb1df45c7_en?filename=Report+from+the+Commission+to+the+Council+and+Parliament+on+the+EUDR.pdf" rel="noopener noreferrer"&gt;Commission report on the EUDR simplification review, May 2026&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two details matter a lot for a GIS engineer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The geolocation rule.&lt;/strong&gt; The regulation asks for coordinates with at least six decimal digits. One point is enough for a plot of four hectares or less. Larger plots (other than cattle) need a polygon. &lt;a href="https://eur-lex.europa.eu/eli/reg/2023/1115/oj" rel="noopener noreferrer"&gt;EUDR Article 2(28)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Country risk.&lt;/strong&gt; The Commission's 2025 benchmarking lists only Belarus, North Korea, Myanmar, and Russia as high risk. &lt;strong&gt;India is classified as low risk.&lt;/strong&gt; Brazil, Indonesia, Malaysia, and Côte d'Ivoire remain standard risk. &lt;a href="https://eur-lex.europa.eu/eli/reg_impl/2025/1093/oj" rel="noopener noreferrer"&gt;Implementing Regulation (EU) 2025/1093&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;India's low-risk status interacts with the 2025 amendment. Micro and small &lt;em&gt;primary&lt;/em&gt; operators in low-risk countries, meaning growers placing their own produce on the market, can file a one-time simplified declaration. They may even give a postal address instead of plot coordinates. &lt;a href="https://eur-lex.europa.eu/eli/reg/2025/2650/oj" rel="noopener noreferrer"&gt;Regulation (EU) 2025/2650&lt;/a&gt; That lowers the legal burden for many Indian smallholders. In my view it doesn't lower the engineering bar for a buyer who wants to &lt;em&gt;know&lt;/em&gt; where their material came from. A postal address can't be checked against a satellite time series.&lt;/p&gt;

&lt;p&gt;The regulation is one application, not the whole subject. The broader question outlasts any law: &lt;strong&gt;can a buyer reconstruct the evidence behind a sourcing decision?&lt;/strong&gt; Dates and scope can still change, so check the official sources before relying on any of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. How six companies approach the problem
&lt;/h2&gt;

&lt;p&gt;The companies I looked at start from different places, and their features increasingly overlap. Here is what each one publicly emphasises.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Company&lt;/th&gt;
&lt;th&gt;Starting point&lt;/th&gt;
&lt;th&gt;What stands out&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;LiveEO (TradeAware)&lt;/td&gt;
&lt;td&gt;Satellite analysis&lt;/td&gt;
&lt;td&gt;Compliance workflow and enterprise integration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TRST01&lt;/td&gt;
&lt;td&gt;Field and farmer data&lt;/td&gt;
&lt;td&gt;Drones, field visits, batch tracking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Satelligence&lt;/td&gt;
&lt;td&gt;Change detection&lt;/td&gt;
&lt;td&gt;Unusually open about its methods&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Airbus + Earthworm (Starling)&lt;/td&gt;
&lt;td&gt;Monitoring&lt;/td&gt;
&lt;td&gt;Turning alerts into supplier engagement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Space Intelligence&lt;/td&gt;
&lt;td&gt;Land-cover mapping&lt;/td&gt;
&lt;td&gt;Local ecological calibration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Koltiva (KoltiTrace)&lt;/td&gt;
&lt;td&gt;Producer records&lt;/td&gt;
&lt;td&gt;Transactions and farm management&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  LiveEO: assessment inside the business workflow
&lt;/h3&gt;

&lt;p&gt;TradeAware pairs what LiveEO calls a proprietary satellite-AI engine, marketed as Precision Analytics, with supplier data collection, legal review through the law firm CMS, ERP integration, and submissions to the EU's TRACES system. LiveEO says it reaches an "effective resolution of 5m" through a spatio-temporal combination of data sources, which the page doesn't name. &lt;a href="https://www.live-eo.com/product/tradeaware" rel="noopener noreferrer"&gt;LiveEO TradeAware&lt;/a&gt; In September 2026 it added a self-service Professional tier. &lt;a href="https://www.live-eo.com/company/press/liveeo-launches-tradeaware-professional-self-service" rel="noopener noreferrer"&gt;LiveEO press release&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My takeaway is that a map is only useful if it plugs into procurement. If plot IDs can't be joined to orders and suppliers, a perfect assessment just sits there. The public material doesn't reveal enough to reproduce the model or judge how it performs on, say, a half-hectare plot in Kerala.&lt;/p&gt;

&lt;h3&gt;
  
  
  TRST01: from the field upward
&lt;/h3&gt;

&lt;p&gt;TRST01 describes capturing "every farmer, plot polygon and land document" once, then tracking purchases, transfers, and processing batch by batch. It says field teams walk farm boundaries with GPS apps, it runs its own drone fleet, and it combines that with satellite time series and its own deforestation analysis. Plots that can't be resolved are "flagged for drone verification or a field visit." &lt;a href="https://trst01.com/blogs/traceability-you-can-trust" rel="noopener noreferrer"&gt;TRST01 methodology&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a good example of mixing evidence from different scales. A drone flight shows you today's boundary and land cover in fine detail. It can't tell you what the land looked like in 2020; only historical satellite imagery can. I'd want to know what the rules are when the two disagree.&lt;/p&gt;

&lt;h3&gt;
  
  
  Satelligence: showing its working
&lt;/h3&gt;

&lt;p&gt;Satelligence names its inputs (Sentinel-1, Sentinel-2, Landsat, and PlanetScope) and its methods: Random Forest and Gradient Boosting for classification, deep learning to clean radar noise, and what it calls Bayesian Iterative Updating to track change over time. &lt;a href="https://satelligence.com/faq/" rel="noopener noreferrer"&gt;Satelligence FAQ&lt;/a&gt; That's far more detail than most commercial pages give.&lt;/p&gt;

&lt;p&gt;A 2020 explainer describes the core idea: probabilities from successive observations are stacked, so a pixel can be flagged as possibly deforested and then confirmed or rejected by later images. &lt;a href="https://satelligence.com/how-we-detect-deforestation-using-satellite-data/" rel="noopener noreferrer"&gt;Satelligence, 2020&lt;/a&gt; It's an older post, so I treat it as an explanation of the approach, not proof of what runs in production today.&lt;/p&gt;

&lt;p&gt;The lesson I take is that &lt;strong&gt;"first suspicious" and "confirmed" are two different dates.&lt;/strong&gt; A system should store both. That's how you measure detection delay and reason about events close to a cut-off.&lt;/p&gt;

&lt;h3&gt;
  
  
  Starling: alerts that lead somewhere
&lt;/h3&gt;

&lt;p&gt;Airbus describes Starling, built with Earthworm Foundation since 2016, as combining its own land-cover maps, deforestation alerts, plot validity and status checks, and delivery via a platform, API, or reports. Imagery ranges from 10 m down to 30 cm from Pléiades Neo. &lt;a href="https://www.space-solutions.airbus.com/industries/forest-and-environment/starling/" rel="noopener noreferrer"&gt;Airbus Starling&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The design pattern is assessment followed by action. The interesting questions are operational: how does an alert become a supplier conversation, and does the final outcome stay linked to the original alert? A 30 cm image doesn't answer that by itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Space Intelligence: maps tuned to place
&lt;/h3&gt;

&lt;p&gt;Space Intelligence describes calibrating its maps with ecologists, accounting for seasonality and cloud cover, and classifying 10 m pixels into more than just forest and non-forest, including agriculture and tree crops. It calls itself the exclusive geospatial mapping partner for ICE's Commodity Traceability platform. &lt;a href="https://www.space-intelligence.com/land-cover-mapping/" rel="noopener noreferrer"&gt;Space Intelligence&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the modular model: a specialist mapper feeds evidence into someone else's traceability system. The hard part becomes the handover. Do the class definitions, observation dates, uncertainty, and map version travel with the result?&lt;/p&gt;

&lt;h3&gt;
  
  
  Koltiva: the records that make maps useful
&lt;/h3&gt;

&lt;p&gt;KoltiTrace covers farmer profiles, polygon mapping of farms, verified digital transactions, supply-chain mapping, and historic land-use-change emissions, alongside farm management and producer engagement. &lt;a href="https://www.koltiva.com/koltitrace" rel="noopener noreferrer"&gt;Koltiva KoltiTrace&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This exposes a dependency people often skip. &lt;strong&gt;Satellites can't recover a purchase record that was never written down.&lt;/strong&gt; If a collector logs only a supplier's name while mixing produce from several farms, no amount of imagery will rebuild those links with confidence.&lt;/p&gt;

&lt;h3&gt;
  
  
  The open toolkit
&lt;/h3&gt;

&lt;p&gt;Two open efforts deserve a mention because they publish their logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Whisp&lt;/strong&gt; ("What is in that plot?"), from the Forest Data Partnership with FAO involvement, applies a "convergence of evidence" approach. It runs a plot against many datasets, including JRC forest maps, Hansen tree-cover loss, and the GLAD and RADD alerts, and reports how they agree. It's open source and says plainly that "no single source of geospatial data can tell the whole story." For coffee or cocoa grown under shade, it may return &lt;strong&gt;"More info needed."&lt;/strong&gt; &lt;a href="https://github.com/forestdatapartnership/whisp" rel="noopener noreferrer"&gt;Whisp on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Global Forest Watch's integrated alerts&lt;/strong&gt; merge several alert systems. An alert reaches "highest" confidence only when multiple systems detect a disturbance in the same pixel. &lt;a href="https://globalnaturewatch.org/blog/data-and-tools/integrated-deforestation-alerts" rel="noopener noreferrer"&gt;Global Nature Watch: integrated alerts&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Neither tool is a compliance verdict. Both show what transparent decision rules look like, which makes them a useful reference point when judging a closed product.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I'm not claiming:&lt;/strong&gt; public pages are enough to compare &lt;em&gt;approaches&lt;/em&gt;. They're not enough to rank anyone's &lt;em&gt;accuracy&lt;/em&gt;. I've deliberately avoided a league table.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The first GIS problem is the boundary
&lt;/h2&gt;

&lt;p&gt;Before running any model, I'd look hard at the polygon. An analysis can be mathematically perfect and still describe the wrong piece of land.&lt;/p&gt;

&lt;h3&gt;
  
  
  Precision versus accuracy
&lt;/h3&gt;

&lt;p&gt;EUDR asks for six decimal places. RFC 7946, the GeoJSON spec, notes that six decimal places is about 10 centimetres. &lt;a href="https://www.rfc-editor.org/rfc/rfc7946.html" rel="noopener noreferrer"&gt;RFC 7946&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now compare that with the device that usually records the boundary. A 2022 study of smartphones under forest canopy measured positioning errors of roughly &lt;strong&gt;4.6 to 14.6 metres&lt;/strong&gt;. It also found accuracy varies greatly even between identical phone models. &lt;a href="https://pmc.ncbi.nlm.nih.gov/articles/PMC8838512/" rel="noopener noreferrer"&gt;Purfürst, Sensors 2022&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So a coordinate can carry 10 cm of &lt;em&gt;precision&lt;/em&gt; and 10 m of &lt;em&gt;error&lt;/em&gt;. The format can't tell you which.&lt;/p&gt;

&lt;h3&gt;
  
  
  What that error does to a small farm
&lt;/h3&gt;

&lt;p&gt;Here's a hypothetical to make it concrete. Take a square one-hectare plot, 100 m on a side, and shift the polygon 10 m east:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1,000 m² of the real farm, &lt;strong&gt;10%&lt;/strong&gt;, now falls outside its own polygon.&lt;/li&gt;
&lt;li&gt;For a half-hectare plot (about 71 m on a side), the same shift misplaces about &lt;strong&gt;14%&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pixels make it worse. Lay that one-hectare square on a 10 m grid that's offset by half a pixel. It touches 121 pixels, but only 81 are fully inside the farm. The other 40 mix farm and neighbouring land. On a 30 m grid with the same offset, it touches 16 pixels, and &lt;strong&gt;only 4 sit entirely inside&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The numbers are illustrative, but the pattern is real. Research on smallholder field delineation in India found that with 4.8 m PlanetScope imagery, high average accuracy was only reached for fields larger than about one hectare. &lt;a href="https://doi.org/10.3390/rs14225738" rel="noopener noreferrer"&gt;Wang, Waldner &amp;amp; Lobell, Remote Sensing 2022&lt;/a&gt; For smallholdings, &lt;strong&gt;boundary quality can dominate the result&lt;/strong&gt; even when the imagery is fine.&lt;/p&gt;

&lt;h3&gt;
  
  
  Points are legal; polygons are better
&lt;/h3&gt;

&lt;p&gt;Under EUDR, a plot of four hectares or less can be described by a single point. That's legally fine, but a point says nothing about where the boundary is. In the EU system, a point submitted without an area is assigned a default of four hectares. &lt;a href="https://eudr.webcloud.ec.europa.eu/tracesnt/help/eudr-documentation/operator/geojson-description.html" rel="noopener noreferrer"&gt;EUDR Information System: GeoJSON description&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If I'm running land-history analysis, I want a polygon whenever it can reasonably be collected, even when the law allows less. India's Coffee Board draft sustainability standard follows the regulation's split: a sketch with a single coordinate for farms up to four hectares, a polygon above that. &lt;a href="https://coffeeboard.gov.in/Indian%20Coffee/Draft%20Sustainability%20Standards%20for%20Indian%20CoffeeOpen%20for%20stakeholder%20Consultation%20and%20Feedback.pdf" rel="noopener noreferrer"&gt;Coffee Board of India, draft INDICOFS standard, 2025&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The intake checks I'd run
&lt;/h3&gt;

&lt;p&gt;The EU system already rejects a lot. It accepts only Points, MultiPoints, Polygons, and MultiPolygons, in WGS 84. It rejects holes and self-intersections, truncates coordinates to six decimals, and caps files at 25 MB. &lt;a href="https://eudr.webcloud.ec.europa.eu/tracesnt/help/eudr-documentation/operator/geojson-description.html" rel="noopener noreferrer"&gt;EUDR GeoJSON description&lt;/a&gt; ICE's plot validation goes further, testing for overlaps, duplicates (under the same or a different farmer ID), unclosed polygons, and points with implausible sizes. &lt;a href="https://www.ice.com/iba/commodity-traceability-service/farm-plot-data" rel="noopener noreferrer"&gt;ICE farm plot data tests&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My proposed intake pipeline looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://salahxd.dev/blog/geospatial-traceability/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNplkU1rAjEQhv_KkF4V29pWKuLFWhCLBQteXA-zyewanE2WSfxC_V2995eV3a2l0GN4n2cmL3NS2htSfZWx3-s1SoS3eeIAACazZaI-tmlhYyQzSKUzzMkXFOWYqBW020MYTZeJGqTDiYu4IdBr0psw6KTDmr6Dr09g7zqMEbwYEkBnQNDlVAP3FaDZBzItcB4Ccda2LpIE0tF6V1PdirIuWENAh5J0JANC-TV_qHLnwe9IGEvwAmZbstUYmzWPFVAyboNNmQCFMFGrpuRoWhU5IzOUGMIZ3qtKM9rDtWs9YkcSrHf_NHeEDC2HM8zHi2WiXi1x9bmdpX0terG5dciwoTL-6vPxova1F6n7nGEyayLNGMILZZAx5jkZCFH8hvo35rnXu31qNc_23pq47nfLwx-rHvujqZYqSAq0RvVPKq6pqI5sKMMtR3W5fAM6rax3%3Ftype%3Dpng" alt="Diagram" width="730" height="291"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A few implementation notes from the standards:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Axis order is a classic trap.&lt;/strong&gt; GeoJSON uses longitude then latitude. &lt;a href="https://www.rfc-editor.org/rfc/rfc7946.html" rel="noopener noreferrer"&gt;RFC 7946&lt;/a&gt; But the &lt;code&gt;geo:&lt;/code&gt; URIs used in GS1 EPCIS events put latitude first. &lt;a href="https://ref.gs1.org/guidelines/epcis-cbv/2.0.0/" rel="noopener noreferrer"&gt;GS1 EPCIS/CBV guideline&lt;/a&gt; A system that handles both formats will eventually swap them somewhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Normalise winding order.&lt;/strong&gt; RFC 7946 says exterior rings should be counter-clockwise, but parsers should &lt;em&gt;not&lt;/em&gt; reject polygons that aren't. Fix it on the way in rather than relying on it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Valid is not true.&lt;/strong&gt; PostGIS's &lt;code&gt;ST_IsValid&lt;/code&gt; tells you a geometry is well-formed under OGC rules. &lt;a href="https://postgis.net/docs/ST_IsValid.html" rel="noopener noreferrer"&gt;PostGIS ST_IsValid&lt;/a&gt; It can't tell you the polygon matches a real farm, or who owns it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Know your area units.&lt;/strong&gt; &lt;code&gt;ST_Area&lt;/code&gt; on a geometry returns units of the coordinate system; on a geography it returns square metres on the spheroid. &lt;a href="https://postgis.net/docs/ST_Area.html" rel="noopener noreferrer"&gt;PostGIS ST_Area&lt;/a&gt; Record which one you used.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;India's Rubber Board describes the same kind of quality control on its BSNR platform, checking for "polygon overlaps, duplicate polygon, crossovers, mismatch &amp;amp; forest cut issues." &lt;a href="https://bsnr.rubberboard.org.in/Deforestation-analysis.html" rel="noopener noreferrer"&gt;Rubber Board BSNR: deforestation analysis&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, &lt;strong&gt;don't use the polygon as the plot's identity.&lt;/strong&gt; Give the plot a stable ID and store each geometry as a version. When a boundary is corrected, every assessment and shipment that used the old version should be flagged for review.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Choosing satellites is really choosing time
&lt;/h2&gt;

&lt;p&gt;People tend to talk about satellites in terms of resolution. For traceability, &lt;strong&gt;time matters just as much&lt;/strong&gt;: how often you see the ground, how often clouds hide it, and how precisely you can date a change.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sentinel-2 basics
&lt;/h3&gt;

&lt;p&gt;Sentinel-2 has four bands at 10 m, six at 20 m, and three at 60 m. Its Level-2A product is atmospherically corrected surface reflectance. Two satellites give a five-day revisit at the equator. &lt;a href="https://documentation.dataspace.copernicus.eu/Data/SentinelMissions/Sentinel2.html" rel="noopener noreferrer"&gt;Copernicus Sentinel-2 documentation&lt;/a&gt; Sentinel-2C replaced 2A in January 2025; 2B and 2C now form the nominal pair, while 2A runs an extended campaign through the end of 2026. &lt;a href="https://dataspace.copernicus.eu/node/3911" rel="noopener noreferrer"&gt;Copernicus Data Space, May 2026&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Mixing bands means resampling. Resampling a 20 m band onto a 10 m grid changes how it's stored, &lt;strong&gt;not how much detail it observed&lt;/strong&gt;. The same goes for vendor claims of "effective resolution": I'd want to know how it was produced and how it was validated.&lt;/p&gt;

&lt;h3&gt;
  
  
  Radar fills the cloud gaps
&lt;/h3&gt;

&lt;p&gt;In the humid tropics, clouds can hide the ground for weeks. Radar sees through them. The RADD alert system uses Sentinel-1 radar, observing every 6 to 12 days at 10 m. In its published validation, it reported user's and producer's accuracies of 97.6% and 95.0%, detecting disturbances of 0.2 ha or more. &lt;a href="https://iopscience.iop.org/article/10.1088/1748-9326/abd0a8" rel="noopener noreferrer"&gt;Reiche et al., Environmental Research Letters 2021&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Those numbers come from the paper's own study area. They are not a promise for every crop or every region.&lt;/p&gt;

&lt;h3&gt;
  
  
  An alert is a process, not a fact
&lt;/h3&gt;

&lt;p&gt;RADD makes the idea of confidence concrete. A pixel is flagged when its probability of being non-forest passes a threshold. It becomes a low-confidence alert above 0.85 and is confirmed at high confidence above 0.975 as more observations arrive. &lt;a href="https://iopscience.iop.org/article/10.1088/1748-9326/abd0a8" rel="noopener noreferrer"&gt;Reiche et al., 2021&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'd model the lifecycle explicitly:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://salahxd.dev/blog/geospatial-traceability/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNp9k7FOAzEMhl_FyojahTEDSxlAAgZWyuAmztXSxa6c9ApCvDvqXaENLYz-7c-xfysfLmgk512pWOmWsTPM8-F6KQAAowhL96A7CCqJI0mgpQMs8KC7xVFqyu-4W5_V78W_gFG3TBEiJTXa66wygT_J2ybXNHjSegl90noBerl6hfn8Bh5VuKqxdJN-jMf0AiVyxEoewhqlIyjcCfbAAioEuipkw0nfH2DEG388bExXuOKe6zsYFyqXmOMEHoTeKnDGjgpELtgZHVxrOo9c662HrNbMV-Bf_PTZw44J4_eIbfMReKaBaecBU6JQCyAU3VrYO7fptU7cVDRZefGEHqYAVhQwEyS03KPEM_z3GT2s0QYqdQZGmx6lsnQzSGx0xt5LUAn9tvBAHmg4bMFStilxYJLDuKeFzZJCuyOHZjxQcTOXyTJydP7D1TXl_R-KlHDbV_f5-QW1iCjX%3Ftype%3Dpng" alt="Diagram" width="779" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two practical rules fall out of this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A missing observation is not evidence of no change.&lt;/strong&gt; I'd keep a quality record for every assessment: usable dates, masked images, and long gaps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Report an interval, not a date.&lt;/strong&gt; If the land looks intact in one clear image and cleared in the next, the event happened somewhere between them. Reporting the second image's date as "the clearing date" claims knowledge the data doesn't have. Near the 2020 cut-off, that difference matters.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Land cover, land use, and change are different questions
&lt;/h2&gt;

&lt;p&gt;This is where most simplified explanations go wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tree-cover loss is not deforestation
&lt;/h3&gt;

&lt;p&gt;WRI is direct about it: "Not all tree cover loss is deforestation." Loss can come from timber harvesting, fire, disease, storms, or shifting cultivation. &lt;a href="https://gfr.wri.org/data-methods" rel="noopener noreferrer"&gt;WRI Global Forest Review: data and methods&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;EUDR has its own definitions. Deforestation means converting forest to agricultural use. Forest means land over 0.5 ha with trees taller than 5 m and canopy cover above 10%, excluding land mainly in agricultural use. &lt;a href="https://eur-lex.europa.eu/eli/reg/2023/1115/oj" rel="noopener noreferrer"&gt;EUDR Article 2&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So the pipeline has three separate steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Detect&lt;/strong&gt; a change in the signal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Establish&lt;/strong&gt; what the land was before, ideally at the cut-off date.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interpret&lt;/strong&gt; what it became, against a written rule.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;An old rubber plantation being felled and replanted is a big canopy loss. But EUDR defines plantations of covered commodities, rubber included, as agricultural plantations and excludes them from "forest". &lt;a href="https://eur-lex.europa.eu/eli/reg/2023/1115/oj" rel="noopener noreferrer"&gt;EUDR Article 2(6)&lt;/a&gt; So replanting rubber isn't forest becoming farmland. Clearing natural forest &lt;em&gt;for&lt;/em&gt; rubber after 2020 is. A satellite sees the same canopy drop in both cases. Only the land's history tells them apart.&lt;/p&gt;

&lt;h3&gt;
  
  
  NDVI is a greenness index, not a land-use label
&lt;/h3&gt;

&lt;p&gt;NDVI measures vegetation greenness and helps assess plant health. &lt;a href="https://www.usgs.gov/landsat-missions/landsat-normalized-difference-vegetation-index" rel="noopener noreferrer"&gt;USGS: Landsat NDVI&lt;/a&gt; It's useful, but it doesn't know who owns the land or what it's used for. I'd reject any method that treats a single NDVI drop as confirmed deforestation. A credible approach needs a baseline, a time series, a classifier, and a path for ambiguous cases.&lt;/p&gt;

&lt;h3&gt;
  
  
  Baseline maps have their own error bars
&lt;/h3&gt;

&lt;p&gt;The JRC's Global Forest Cover 2020 map was built to support EUDR risk assessment, but the JRC states that its use is "not mandatory, not exclusive and not legally binding." Its accuracy assessment reports 91% overall accuracy, with an 18% commission error and 8% omission error for the forest class, and errors concentrated in open dry forests. &lt;a href="https://publications.jrc.ec.europa.eu/repository/handle/JRC141231" rel="noopener noreferrer"&gt;JRC accuracy assessment&lt;/a&gt; The JRC has since released an updated version of the map. &lt;a href="https://publications.jrc.ec.europa.eu/repository/handle/JRC146622" rel="noopener noreferrer"&gt;JRC, 2026&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An 18% commission error means some land the map calls forest isn't. That's the right attitude for any baseline: &lt;strong&gt;know its purpose and its error profile before turning its pixels into decisions about individual farms.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Broad classes and probabilities
&lt;/h3&gt;

&lt;p&gt;Google's Dynamic World gives per-pixel probabilities for nine broad land-cover classes at 10 m. Its documentation suggests thresholding the top prediction's confidence, and warns that probabilities can be comparatively low for crops. &lt;a href="https://developers.google.com/earth-engine/datasets/catalog/GOOGLE_DYNAMICWORLD_V1" rel="noopener noreferrer"&gt;Dynamic World&lt;/a&gt; A "crops" label won't tell you whether those crops are rubber, coffee, or something else.&lt;/p&gt;

&lt;p&gt;Crop-specific maps are emerging. The Forest Data Partnership publishes 10 m probability maps for cocoa, coffee, palm, and rubber, with a note that they are "not yet peer-reviewed." &lt;a href="https://developers.google.com/earth-engine/datasets/publisher/forestdatapartnership" rel="noopener noreferrer"&gt;Forest Data Partnership on Earth Engine&lt;/a&gt; The catalogue lists a 2026 version while Whisp references an earlier one. &lt;a href="https://github.com/forestdatapartnership/whisp/blob/main/layers_description.md" rel="noopener noreferrer"&gt;Whisp layers&lt;/a&gt; That's a small but real example of why &lt;strong&gt;every assessment should record which version of which dataset it used&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. What rubber mapping teaches us
&lt;/h2&gt;

&lt;p&gt;Rubber is a great test case, and it matters for India.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Nature 2023 study
&lt;/h3&gt;

&lt;p&gt;Wang and colleagues mapped rubber across Southeast Asia using Sentinel-2 at 10 m. Their key insight was biological: rubber trees shed leaves in January–February and refoliate in March–April. Composite images from 2021 and 2022 captured that difference and fed a Random Forest classifier. Multiyear composites helped deal with heavy cloud cover, and historical tree-cover loss came from Landsat. &lt;a href="https://www.nature.com/articles/s41586-023-06642-z" rel="noopener noreferrer"&gt;Wang et al., Nature 2023&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Their headline finding: deforestation for rubber is "at least twofold to threefold higher" than figures widely used in policy, with more than 4 million hectares of forest lost to rubber since 1993. Reported overall accuracy was 0.95 ± 0.02, but only 0.85 ± 0.06 in insular Southeast Asia. &lt;a href="https://www.nature.com/articles/s41586-023-06642-z" rel="noopener noreferrer"&gt;Wang et al., 2023&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Three caveats from the paper itself are worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It counts "any type of tree cover loss," so its deforestation figure includes plantation and agroforest rotations. This is the section 6 distinction showing up in a real study.&lt;/li&gt;
&lt;li&gt;It's unlikely to detect rubber agroforestry or "jungle rubber" unless rubber dominates the canopy.&lt;/li&gt;
&lt;li&gt;Near the equator, where seasons are weaker, omission errors rise.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The lesson is that &lt;strong&gt;crop biology drives feature design&lt;/strong&gt;. A generic tree/no-tree classifier is asking a different question from a model built around rubber's leaf cycle.&lt;/p&gt;

&lt;h3&gt;
  
  
  The research is moving
&lt;/h3&gt;

&lt;p&gt;A 2026 preprint by Ahrends and colleagues presents a newer 10 m rubber map combining optical and radar data. It reports a user's accuracy of 0.95 and an area-adjusted producer's accuracy of 0.78 in continental Southeast Asia, with "persistent limitations" in insular regions and smallholder systems. &lt;a href="https://www.biorxiv.org/content/10.64898/2026.07.25.740673v1" rel="noopener noreferrer"&gt;Ahrends et al., bioRxiv 2026&lt;/a&gt; It hasn't been peer reviewed yet, but Whisp's documentation already marks the 2023 map as superseded by its providers. &lt;a href="https://github.com/forestdatapartnership/whisp/blob/main/layers_description.md" rel="noopener noreferrer"&gt;Whisp layers&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What I'd do for India
&lt;/h3&gt;

&lt;p&gt;Kerala's rubber grows in a different climate from mainland Southeast Asia. Leaf-fall timing, plantation age, intercropping, and holding size all differ. I'd test local seasonal windows and representative plantation types rather than transferring a Southeast Asian model unchanged, and report accuracy for Indian smallholdings specifically. A coffee equivalent would need its own reference data and a plan for shade-grown systems, which Whisp already flags as hard. These are proposed experiments, not results.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Indian deployment context
&lt;/h3&gt;

&lt;p&gt;The Rubber Board's February 2025 bulletin names TRST01 as its "technological partner" to implement EUDR in India. &lt;a href="https://rubberboard.gov.in/rbfilereader?fileid=1163" rel="noopener noreferrer"&gt;Rubber Board, February 2025&lt;/a&gt; Its July 2025 bulletin describes geo-mapping of plantations to support due diligence certificates, with data held centrally by the Board. &lt;a href="https://rubberboard.gov.in/rbfilereader?fileid=1176" rel="noopener noreferrer"&gt;Rubber Board, July 2025&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In February 2026, the government announced the &lt;strong&gt;Bharath Sustainable Natural Rubber (BSNR)&lt;/strong&gt; platform for legality and traceability across the rubber value chain. &lt;a href="https://www.pib.gov.in/PressReleasePage.aspx?PRID=2226250" rel="noopener noreferrer"&gt;PIB, February 2026&lt;/a&gt; The BSNR portal describes field polygon mapping, back-end polygon checks, and a &lt;strong&gt;segregation&lt;/strong&gt; model that keeps EUDR and non-EUDR material in separate storage. &lt;a href="https://bsnr.rubberboard.org.in/Traceability-due-diligence.html" rel="noopener noreferrer"&gt;BSNR: traceability and due diligence&lt;/a&gt; TRST01 says in its own press release that it powers BSNR. &lt;a href="https://theprint.in/ani-press-releases/trst01-announces-full-eudr-readiness-for-indian-coffee-rubber-and-soy-exporters-as-30-december-deadline-nears/3070418/" rel="noopener noreferrer"&gt;TRST01 press release, October 2026&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These sources establish a real deployment. They don't benchmark its accuracy, and they don't show that mapping has led to better prices for growers. That would need its own outcome study.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. A shipment needs a material-flow graph
&lt;/h2&gt;

&lt;p&gt;A list of approved farms can't tell you which farms contributed to a shipment. For that, you need records of material entering, splitting, blending, and leaving each step.&lt;/p&gt;

&lt;p&gt;GS1's EPCIS standard is the established vocabulary here. Its TransformationEvent describes a process where inputs are consumed and outputs produced. The guidance notes that "any of the inputs may have contributed" to each output. &lt;a href="https://ref.gs1.org/guidelines/epcis-cbv/2.0.0/" rel="noopener noreferrer"&gt;GS1 EPCIS/CBV guideline&lt;/a&gt; That's honest, and it means EPCIS records &lt;em&gt;that&lt;/em&gt; material was combined, not &lt;em&gt;in what proportion&lt;/em&gt;. Quantity attribution needs extra logic on top.&lt;/p&gt;

&lt;p&gt;Here's an illustrative blend:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://salahxd.dev/blog/geospatial-traceability/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNpdkstuwjAQRX9l5G6TNqSlpVGFFKuLLpCIeKwIixBPHsKxqe2QVsB3sefLqiQQoLsZzb1nrkfekVgyJB5JuKziLFIGRpNQAAAE_iIkAZcGfDgdIUVZoFG_sHVDsgTbHoJfK3ytUesChfFASIizSKQIOhcxguu4TkiWZx698Og9r9fx6D-e3KCAiKMyFpSCoQKF2xyrK9NvjKM6yFSWKkboAr-4DqzTm_WtlN5Lmyy9wZ101FKDSR1YyRi1zkXqwYqjYFcVPavO_EnTjuezRUjGpdmUpllwOkJ_0Id1-rFST8NeXYHCWCqGDLjUugOO57MGMf2qI2b5pj5CN_Up2I_2cN_c5LtEbXIpwKhoi1xDlZsMTNa8aV-TWlPMI60_MYGER2mKDLRRco3eA3t_e3Nerba1q5yZzHve_Ny4IKCWT60RvXiJRQpURZQz4u2IybCo_w3DJCq5IYfDH-YvuYM%3Ftype%3Dpng" alt="Diagram" width="1467" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The quantities are made up, but the rules aren't:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Keep quantities and units at every step&lt;/strong&gt;, and record conversion assumptions such as moisture loss.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separate expected processing loss from unexplained gaps.&lt;/strong&gt; A 15 kg loss is either explained or it's a discrepancy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep both source lots after blending.&lt;/strong&gt; Plot A's clean result can't vouch for Plot B. The output inherits B's open question until it's resolved.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Chain-of-custody models set the ceiling
&lt;/h3&gt;

&lt;p&gt;How precisely you can trace a shipment depends on the chain-of-custody model. ISO 22095 defines five: identity preserved, segregated, controlled blending, mass balance, and book and claim. &lt;a href="https://spec-untp-fbb45f.opensource.unicc.org/docs/0.6.0/design-patterns/ChainOfCustody" rel="noopener noreferrer"&gt;UN/CEFACT UNTP: chain of custody&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;What a shipment can claim&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Identity preserved&lt;/td&gt;
&lt;td&gt;Material from specific, known plots&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Segregated&lt;/td&gt;
&lt;td&gt;Material only from approved plots, mixed together&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mass balance&lt;/td&gt;
&lt;td&gt;A share of qualifying material, accounted for on paper&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Book and claim&lt;/td&gt;
&lt;td&gt;Credits detached from the physical goods&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;BSNR's choice of segregation is a good example. A segregated lot can be traced to a &lt;em&gt;set&lt;/em&gt; of approved plots, but not to one farm. That's a legitimate design, as long as everyone knows what the claim means.&lt;/p&gt;

&lt;h3&gt;
  
  
  A ledger doesn't make data true
&lt;/h3&gt;

&lt;p&gt;Tamper-evident storage, whether blockchain or a signed append-only log, makes later edits detectable. It can't make a false field declaration true or prove a transaction happened as recorded. Physical checks, reconciliation, and correction procedures are still part of the system.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. The architecture I'd use to evaluate an implementation
&lt;/h2&gt;

&lt;p&gt;This is a proposed reference design, not a reconstruction of any vendor's private stack.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://salahxd.dev/blog/geospatial-traceability/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNpdkkFr4zAQhf_KoGtc9l6WQrabQm7FDqVg9zCRXmyBLJkZOVlv6X9fbKcp3eOTNN97T9K7scnB3JtTSBfbsWQ6_GoiEdHTY92YJ4_gyKYQYLNP8edRfjy0ki4QLeiYxuhYPLQxb3R390BVWTemGjh7DiRovWaZlqEhpEz731pQi9Qjy0RniPoU5-HVcj9b7ntuIRNZzhxSO2IZrw7bR1Kw2I6OE7GAiaMjxxmf5tvnujHbyGFSrzT4AcHHdfzIuggtyHYcW5BDXivd3KvySlnl9nmV1QxVhWqPmElzEnw1unagzZyEaUN9cgi0IRkDbujDa92Yg3BUXjx1AQgs_JC1oDxvnSC3e9zNrrvzd8OQvs4m6XkhFaSdH-Zo-n-TXXVtUi2yfKkbU-LscSGFnL3FNca8BClIFiQHFOSSHb9Dd5-UVZYvq8frEtQ7RAvCnyFJXqgnSX8Rb29MpyTEo_O5MW-mMD2kZ-_M_bvJHfr5CzqceAzZfHz8A-Zm3IE%3Ftype%3Dpng" alt="Diagram" width="693" height="806"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;What it must remember&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Field collection&lt;/td&gt;
&lt;td&gt;Who collected the boundary, when, and every correction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spatial registry&lt;/td&gt;
&lt;td&gt;Stable plot IDs, geometry versions, quality flags&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Imagery catalogue&lt;/td&gt;
&lt;td&gt;Scene IDs, acquisition dates, processing level&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Analysis pipeline&lt;/td&gt;
&lt;td&gt;Inputs, model version, parameters, uncertainty&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Event store&lt;/td&gt;
&lt;td&gt;Transfers, transformations, quantities, source links&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Review service&lt;/td&gt;
&lt;td&gt;Reviewer, reasoning, supporting documents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Evidence export&lt;/td&gt;
&lt;td&gt;A frozen snapshot that can be re-examined later&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For the plumbing, PostGIS is a solid option for spatial storage and validation. A STAC catalogue such as Element 84's Earth Search lets you find imagery by geometry and date range. &lt;a href="https://element84.com/earth-search/examples" rel="noopener noreferrer"&gt;Element 84: Earth Search&lt;/a&gt; These are my choices, not technologies I can attribute to any of the companies above.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The key design choice is explicit lineage.&lt;/strong&gt; An assessment should be identified by its plot version, source observations, model version, and rule version. A new model run creates a &lt;em&gt;new&lt;/em&gt; assessment; it never silently overwrites an old decision.&lt;/p&gt;

&lt;p&gt;That pays off when things change:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A baseline map is corrected → find every assessment that used it.&lt;/li&gt;
&lt;li&gt;A source lot is reassigned → find every dependent shipment.&lt;/li&gt;
&lt;li&gt;A reviewer changes a decision → the reason stays attached to the evidence available at that time.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  10. How to test whether it actually works
&lt;/h2&gt;

&lt;p&gt;I'd evaluate three levels separately: &lt;strong&gt;pixel classification, plot assessment, and shipment reconstruction.&lt;/strong&gt; A system can be excellent at one and poor at another.&lt;/p&gt;

&lt;p&gt;A proposed test set would include stable forest, established plantations, known conversions, mixed tree crops, very small plots, and disputed boundaries. Reference labels would be independently reviewed, and uncertain cases would stay labelled as uncertain rather than being forced into yes or no.&lt;/p&gt;

&lt;p&gt;To test whether a model generalises, I'd hold out &lt;strong&gt;entire farms and regions&lt;/strong&gt;, not random pixels. Otherwise neighbouring pixels from the same farm end up in both training and test data, and the score looks better than it should. A separate time period tests whether performance survives changing seasons.&lt;/p&gt;

&lt;p&gt;The metrics I'd report:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Precision:&lt;/strong&gt; of the changes flagged, how many were real?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recall:&lt;/strong&gt; of the real changes, how many were flagged?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inconclusive rate:&lt;/strong&gt; how many plots lacked enough evidence for a decision?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Detection delay:&lt;/strong&gt; how long between the event interval and an actionable alert?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Boundary sensitivity:&lt;/strong&gt; how much do results change when polygons shift by realistic GPS errors?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provenance completeness:&lt;/strong&gt; what share of shipment inputs link back to documented lots and plots?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review effort:&lt;/strong&gt; how much analyst time does each case take?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's why plain accuracy isn't enough. Hypothetically, if only 10 of 1,000 test plots had a real change, a system that labels everything "no change" scores &lt;strong&gt;99% accuracy&lt;/strong&gt; and catches nothing.&lt;/p&gt;

&lt;p&gt;I'd break results down by crop, region, plot size, and observation quality, with sample counts and uncertainty intervals. I'd also compare optical-only, optical plus radar, and optical plus radar plus field checks, to measure what each extra source actually adds. Costs should include fieldwork and review time, not just compute.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. Where I see the real research opportunity
&lt;/h2&gt;

&lt;p&gt;The companies here, and open tools like Whisp, show imagery analysis and transaction traceability converging. What interests me most is &lt;strong&gt;the quality of the join between them&lt;/strong&gt;: how spatial uncertainty flows into sourcing decisions, and whether a system keeps enough evidence to revisit them later.&lt;/p&gt;

&lt;p&gt;Three questions seem worth studying properly:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;How sensitive are smallholder decisions to realistic boundary errors?&lt;/strong&gt; Shift polygons by measured phone-GPS error and see how many verdicts flip.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How much does crop-specific temporal modelling cut ambiguous results&lt;/strong&gt; compared with generic forest maps, especially for Indian rubber and shade-grown coffee?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How often does complete land evidence fail to become complete shipment provenance&lt;/strong&gt;, once blending and chain-of-custody models are taken into account?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All three can be designed, measured, and reproduced by a GIS team. They also separate an impressive map from a dependable traceability system.&lt;/p&gt;

&lt;p&gt;For me, that's the real test of geospatial traceability. &lt;strong&gt;Can another analyst rebuild the location, the observations, the material flow, and the reasoning behind a decision, and point to exactly where the evidence runs out?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Questions readers asked
&lt;/h2&gt;

&lt;p&gt;After this went up, a reader sent five sharp questions. Others will probably ask the same things, so here are my answers. As in the rest of the piece, "I'd" means proposed design, not a system I operate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do you monitor the forest or the farm?
&lt;/h3&gt;

&lt;p&gt;The farm first, because that's the question EUDR asks: was &lt;em&gt;this plot&lt;/em&gt; deforested after 31 December 2020? A farm that was forest in 2020 and is rubber today fails, however clean its paperwork.&lt;/p&gt;

&lt;p&gt;The gap is leakage. A declared plot stays clean while forest next door is cleared, and plot-only monitoring won't see it. I'd add three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A buffer ring&lt;/strong&gt; a few hundred metres wide around each plot. Alerts in the ring raise the plot's risk and trigger review. They don't prove anything on their own.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comparison between geometry versions.&lt;/strong&gt; If a polygon later grows into land that was forest in 2020, the difference between versions shows it. That's one reason the intake design in section 4 keeps old versions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Landscape context&lt;/strong&gt; from regional maps and alerts. It informs risk, but the sourcing decision still rests on plot evidence.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Isn't open satellite data too coarse for small changes?
&lt;/h3&gt;

&lt;p&gt;Often, yes. Sentinel-2's sharpest bands are 10 m and Landsat's are 30 m, and the arithmetic in section 4 shows how few pixels sit fully inside a one-hectare farm. Thinning, or removing a handful of trees, can stay invisible.&lt;/p&gt;

&lt;p&gt;What helps most is time and agreement, not just sharper pixels. RADD's radar sees through cloud, and its validation covers disturbances of about 0.2 ha and larger. &lt;a href="https://iopscience.iop.org/article/10.1088/1748-9326/abd0a8" rel="noopener noreferrer"&gt;Reiche et al., 2021&lt;/a&gt; A dense time series can confirm a change that one image can't. Integrated alerts and Whisp combine datasets to cut false alarms.&lt;/p&gt;

&lt;p&gt;Below the detectable size, satellites can't decide. That's where finer imagery, drones, or a field visit come in. &lt;strong&gt;I'd want any system to state its minimum detectable change&lt;/strong&gt; rather than imply it sees everything.&lt;/p&gt;

&lt;h3&gt;
  
  
  What if a farmer cuts forest and declares their own land?
&lt;/h3&gt;

&lt;p&gt;A phone fix proves where the phone was when the boundary was walked. It doesn't prove where a load came from. The Commission says operators must verify, and be able to prove, that geolocation is correct. &lt;a href="https://transition-pathways.europa.eu/system/files/media/document/2026-08/FAQ-UPDATE-5th-Iteration-EN.pdf" rel="noopener noreferrer"&gt;European Commission EUDR FAQ, v5, Q1.12&lt;/a&gt; There's no single test for this kind of fraud, but these checks raise its cost and tell auditors where to look:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Supply capacity.&lt;/strong&gt; Plot area times a realistic yield caps what a plot can deliver, and deliveries above that cap get flagged. To me this is the strongest routine check, and it needs nothing more than the polygon and the purchase records.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Does the plot actually produce it?&lt;/strong&gt; A time series can show whether the declared land holds mature rubber or other productive cover.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nearby clearance&lt;/strong&gt; during the delivery period, picked up by the buffer ring above.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timing&lt;/strong&gt; that doesn't match harvest or tapping seasons.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Field audits targeted by risk&lt;/strong&gt;, not only at random.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wood forensics for timber.&lt;/strong&gt; Stable-isotope and trace-element methods can test origin claims, but at regional scale. A 2024 study predicted harvest location to within about 180–230 km and rejected 40–60% of simulated false claims. &lt;a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC10954544/" rel="noopener noreferrer"&gt;Mortier et al., Nature Plants 2024&lt;/a&gt; That catches a false country or region, not a swap between neighbouring plots.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For Indian smallholders, the simplified regime in section 2 makes this harder. A postal address can't be checked against a satellite time series.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do you detect legal and illegal material being mixed?
&lt;/h3&gt;

&lt;p&gt;Not with satellites. It's a custody and accounting problem.&lt;/p&gt;

&lt;p&gt;EUDR is strict here. The Commission's FAQ rules out mass-balance chains that mix deforestation-free goods with goods of unknown origin at any step, and requires segregation throughout. If a non-compliant part can't be separated out, the whole product is non-compliant. &lt;a href="https://transition-pathways.europa.eu/system/files/media/document/2026-08/FAQ-UPDATE-5th-Iteration-EN.pdf" rel="noopener noreferrer"&gt;European Commission EUDR FAQ, v5, Q1.4–1.5&lt;/a&gt; That's why BSNR keeps EUDR and non-EUDR material in separate storage.&lt;/p&gt;

&lt;p&gt;Detection means reconciling quantities at every step: inputs, outputs, conversion ratios, and losses, as in the blend in section 8. Then compare what left each collection point with what its supplying plots could have produced. &lt;strong&gt;Output that outgrows its plots' capacity is the classic sign of mixing.&lt;/strong&gt; A ledger makes those records tamper-evident. Only physical stock checks show whether they're true.&lt;/p&gt;

&lt;h3&gt;
  
  
  Won't this need huge storage, and isn't fine imagery too expensive?
&lt;/h3&gt;

&lt;p&gt;Storage is smaller than it looks. As an illustration, a polygon with 50 vertices holds under a kilobyte of coordinates, so a million plots fit in a few gigabytes with their metadata. Supply-chain events are similarly small.&lt;/p&gt;

&lt;p&gt;Imagery is the heavy part, and you don't need to own it. Public archives such as &lt;a href="https://documentation.dataspace.copernicus.eu/Data/SentinelMissions/Sentinel2.html" rel="noopener noreferrer"&gt;Copernicus Data Space&lt;/a&gt; and &lt;a href="https://element84.com/earth-search/examples" rel="noopener noreferrer"&gt;Earth Search&lt;/a&gt; hold it as cloud-optimised files, and a STAC search fetches only the window around each plot. Alert products like GFW's integrated alerts are already computed for whole regions, so checking a million polygons against them is an ordinary spatial join. Keep the results and their lineage, not the rasters.&lt;/p&gt;

&lt;p&gt;For cost, I'd tier the evidence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Free 10–30 m data and alerts&lt;/strong&gt; screen every plot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commercial very-high-resolution imagery&lt;/strong&gt;, from the archive or newly tasked, only for the share that's flagged or disputed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drones or field visits&lt;/strong&gt; for whatever stays unresolved.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Shared programmes spread the cost further. A cooperative, a commodity board, or several buyers can pay for one set of imagery. The 2020 baseline can only come from archive scenes, since nobody can task a satellite over the past. That's another reason to buy narrowly.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://salahxd.dev/blog/geospatial-traceability/" rel="noopener noreferrer"&gt;salahxd.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>gis</category>
      <category>remotesensing</category>
      <category>geospatialtraceability</category>
      <category>geoai</category>
    </item>
    <item>
      <title>Write Once, Post Everywhere</title>
      <dc:creator>Mohd Salahudeen</dc:creator>
      <pubDate>Tue, 29 Sep 2026 12:56:30 +0000</pubDate>
      <link>https://dev.to/salahxd/publish-once-own-the-original-4nng</link>
      <guid>https://dev.to/salahxd/publish-once-own-the-original-4nng</guid>
      <description>&lt;p&gt;Most developer writing gets read on someone else's website. DEV, Hashnode, Medium, a Bluesky post, a Hacker News thread. That's where the readers are, and your own site is often the last place they land.&lt;/p&gt;

&lt;p&gt;I'm fine with that. What I'm not fine with is each of those copies competing with the original in search, because the canonical link was never set, was set slightly wrong, or was set once and never checked again.&lt;/p&gt;

&lt;p&gt;So I built castio. I publish on my own site the way I always have, push to git, and castio takes it from there. It waits until the post is actually live and shows me exactly what each platform will get. It posts where it can, hands me a ready-made kit where it can't, and then keeps checking that every copy still points home. The original is yours, and every copy should say so. That should hold every day after you post it, not just on the day itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cross-posting is easy. Cross-posting correctly is tedious.
&lt;/h2&gt;

&lt;p&gt;The fix for duplicate content is old and boring: a canonical link. A copy that says "the original lives over there" stops competing with the original. The problem is that every platform handles it differently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DEV&lt;/strong&gt; has a canonical URL field on each article.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Medium&lt;/strong&gt; and &lt;strong&gt;Hashnode&lt;/strong&gt; each have their own canonical / original-URL setting, in their own corner of their own UI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bluesky, LinkedIn, Hacker News and Reddit&lt;/strong&gt; don't copy the post at all. They're just posts that link to you.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of that is hard. It's tedious, which is worse. Tedious work gets done by hand, a little differently each time, and then forgotten. A missing or slightly-off canonical doesn't throw an error or show a warning. It just sits there.&lt;/p&gt;

&lt;p&gt;And nobody goes back three months later to re-check.&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%2F7wd45z1i599sfw61y08s.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%2F7wd45z1i599sfw61y08s.png" alt="salahxd.dev as the original, with DEV, Hashnode and Medium carrying canonical links back to it, and Bluesky, LinkedIn, Hacker News and Reddit linking to it" width="799" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;salahxd.dev is the original. The copies point back to it, and the posts link to it.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The first thing castio caught was my own site
&lt;/h2&gt;

&lt;p&gt;Building castio's canonical check meant looking hard at my own site first, and it didn't pass.&lt;/p&gt;

&lt;p&gt;My site linked to posts without a trailing slash, but the canonical tag on each post used the URL &lt;em&gt;with&lt;/em&gt; one. So every post had two URLs, &lt;code&gt;/blog/some-post&lt;/code&gt; and &lt;code&gt;/blog/some-post/&lt;/code&gt;. A person would call those the same page. A crawler sees two different URLs.&lt;/p&gt;

&lt;p&gt;castio compares the live canonical against the expected URL exactly, so none of my posts would ever have been marked Ready until the two agreed. I fixed the site, and links now end in &lt;code&gt;/&lt;/code&gt;. The dashboard now says it right under the URL pattern field: "The trailing slash matters." I wrote that line for myself first.&lt;/p&gt;

&lt;p&gt;The other honest one: I have an older DEV article whose canonical I still need to fix by hand. castio only manages what it publishes, so that one's on me.&lt;/p&gt;

&lt;h2&gt;
  
  
  Push, wait, approve, publish, verify
&lt;/h2&gt;

&lt;p&gt;Here's the whole flow, in the order it happens.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Push.&lt;/strong&gt; I write the way I always have: an Astro site, posts as files in &lt;code&gt;src/content/blog&lt;/code&gt;, in a GitHub repo. castio is a GitHub App installed on that one repo with read-only Contents access. When I push, a webhook tells castio a post was added or changed. castio verifies the webhook signature before it trusts any of the payload.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wait.&lt;/strong&gt; A push isn't a deploy. castio keeps checking the live URL until the post is actually up &lt;em&gt;and&lt;/em&gt; its &lt;code&gt;&amp;lt;link rel="canonical"&amp;gt;&lt;/code&gt; matches the expected URL exactly. Only then is the article Ready. It never distributes a post that isn't live yet, so no cross-post ever points at a 404.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Approve.&lt;/strong&gt; The preview shows exactly what each platform will receive. Nothing goes out without my approval. I can send it now or schedule it, in my timezone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Publish.&lt;/strong&gt; Two platforms are fully automatic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DEV&lt;/strong&gt; goes through the API with &lt;code&gt;canonical_url&lt;/code&gt; set. I can publish or save as a draft, with an optional "Originally published at" footer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bluesky&lt;/strong&gt; gets a post with a link card, built from a template with &lt;code&gt;{title}&lt;/code&gt; and &lt;code&gt;{excerpt}&lt;/code&gt;. It connects with proper atproto OAuth, with no app passwords.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;LinkedIn, Hacker News, Reddit, Medium and Hashnode get copy-paste kits. castio prepares everything, then I post it and mark it done. I never set out to automate every platform. What I wanted was to know where every copy lives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verify.&lt;/strong&gt; After publishing, castio fetches every copy and checks that its canonical points back to the original. Then it checks again every day. If a copy drifts, it gets flagged as needing action and I get a notification, plus an email if I've turned that on.&lt;/p&gt;

&lt;p&gt;That last step is the one I actually care about. Everything before it saves me time. Verification is the part I'd never do myself.&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%2F55tbmpo1ua88h6biul3f.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%2F55tbmpo1ua88h6biul3f.png" alt="The castio pipeline: git push, GitHub webhook, castio waits until the post is live with a correct canonical, Ready, preview and approve, DEV and Bluesky posted automatically, copy-paste kits for the rest, every canonical verified and re-checked daily" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;From git push to verified copies. The only manual steps are approving, and posting the kits.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;One more boring detail I'm glad I handled early: secrets like the DEV API key are stored encrypted under a versioned keyring, so keys can be rotated, and they're never shown again once saved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failures are per platform, not all-or-nothing
&lt;/h2&gt;

&lt;p&gt;The easy way to build this is one big "distribute" job that either works or doesn't. That's the wrong shape. If Bluesky has a bad minute, DEV shouldn't care.&lt;/p&gt;

&lt;p&gt;So every platform gets its own publication with its own state:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The automatic path is &lt;code&gt;draft → queued → publishing → published → verified&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If publishing fails, it goes to &lt;code&gt;failed&lt;/code&gt;, then back to &lt;code&gt;queued&lt;/code&gt; for a retry.&lt;/li&gt;
&lt;li&gt;If something goes wrong while publishing, or later with a published or verified copy, it lands in &lt;code&gt;needs_action&lt;/code&gt;. From there it goes back to &lt;code&gt;queued&lt;/code&gt;, or on to &lt;code&gt;verified&lt;/code&gt; once it's sorted.&lt;/li&gt;
&lt;li&gt;Copy-paste kits take their own path: &lt;code&gt;draft → manual_pending → manual_done → verified&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Anything not sent yet can be &lt;code&gt;skipped&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Written down, that's a lot of states. In practice it means a failure is one row with one clear next step. I never have to wonder what did and didn't go out.&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%2Fthszvytj0pnyty0ygxxx.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%2Fthszvytj0pnyty0ygxxx.png" alt="The per-platform publication state machine, covering the automatic path, retries through failed, needs_action, the manual copy-paste path, and skipped" width="800" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;One state machine per platform. A failure on one never blocks the others.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  One Docker image, three jobs
&lt;/h2&gt;

&lt;p&gt;The stack is a pnpm + Turborepo monorepo. There's a NestJS API, a BullMQ worker, Postgres via Drizzle, and Redis. The dashboard is Next.js with the App Router and TanStack Query.&lt;/p&gt;

&lt;p&gt;The API and the worker ship as the same Docker image. So does the migration step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node dist/start.js api
node dist/start.js worker
node dist/start.js migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Migrations run on start behind a Postgres advisory lock, so two containers booting at the same moment never both try to migrate.&lt;/p&gt;

&lt;p&gt;The browser only ever talks to one origin. The dashboard lives on Vercel at castio.salahxd.dev and rewrites &lt;code&gt;/api/*&lt;/code&gt; to the API at castio-api.salahxd.dev. The API runs next to the worker on my VPS, deployed with Dokploy. The GitHub push webhook skips the dashboard and goes straight to the API domain.&lt;/p&gt;

&lt;p&gt;Tests run against real Postgres and Redis through Testcontainers, with no mocked database quietly agreeing with everything.&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%2Fsi6czva4uahswmvuub2v.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%2Fsi6czva4uahswmvuub2v.png" alt="castio deployment: the browser talks to the Next.js dashboard on Vercel, which rewrites /api/\* to the NestJS API on a VPS behind Dokploy and Traefik; the API and a BullMQ worker from the same image share Postgres and Redis; the GitHub webhook goes directly to the API" width="800" height="492"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The dashboard on Vercel, the API and worker on my VPS. One image, one browser origin.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Four things that broke on the way to production
&lt;/h2&gt;

&lt;p&gt;None of these were bugs in the logic. All of them showed up at deploy time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. GitHub webhooks are bigger than you'd expect.&lt;/strong&gt; A push webhook can be up to 25 MB. Express and Nest default to a 100 kb JSON body limit. I gave only the webhook route a 25 MB parser, and every other route keeps 100 kb. The gotcha is that Nest silently skips registering its own body parsers if it finds a middleware function named &lt;code&gt;jsonParser&lt;/code&gt;. So the wrapper got a different name.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Multi-line secrets don't survive every env editor.&lt;/strong&gt; I pasted the GitHub App's PEM private key into Dokploy's env editor, and only the first line survived. To its credit, Octokit's error said exactly that: the key "contains only the first line". The fix was to store it on one line with literal &lt;code&gt;\n&lt;/code&gt; and let the config turn those back into real newlines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Certificates can race your DNS.&lt;/strong&gt; Let's Encrypt tried to issue the API's certificate before the DNS record existed. It got NXDOMAIN, and Traefik fell back to serving its default cert. Nothing retries on its own. Once DNS resolves, you have to nudge Traefik into trying again, which in Dokploy means re-saving the domain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Next.js rewrites are decided at build time.&lt;/strong&gt; Rewrites are baked in when you build, so the API URL has to exist during the build. Turborepo's strict env mode silently drops any env var not declared in &lt;code&gt;turbo.json&lt;/code&gt;. One line fixed it:&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="nl"&gt;"env"&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;"API_INTERNAL_URL"&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;Three of those four failed quietly, with no crash and no warning. Things just weren't right. Silent drift is exactly the problem castio exists to catch in canonicals.&lt;/p&gt;

&lt;h2&gt;
  
  
  This post is the first one castio ships
&lt;/h2&gt;

&lt;p&gt;castio is v0. I built it for my own site first, and that's the only site it runs on. If it earns its keep here, it might turn into something other developers can use. No promises.&lt;/p&gt;

&lt;p&gt;If you're reading this on DEV or Bluesky, castio put it there, and the canonical points back to salahxd.dev. View the source if you like. castio already checked it, and it'll check again tomorrow.&lt;/p&gt;

&lt;p&gt;If you'd want something like this for your own blog, tell me. That's the only signal I'm looking for.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://salahxd.dev/blog/castio-publish-once/" rel="noopener noreferrer"&gt;salahxd.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>developertools</category>
      <category>blogging</category>
      <category>seo</category>
      <category>nestjs</category>
    </item>
    <item>
      <title>Meet Archify</title>
      <dc:creator>Mohd Salahudeen</dc:creator>
      <pubDate>Wed, 01 Jul 2026 19:31:45 +0000</pubDate>
      <link>https://dev.to/salahxd/meet-archify-3j57</link>
      <guid>https://dev.to/salahxd/meet-archify-3j57</guid>
      <description>&lt;p&gt;A couple of weeks ago I wrote that Archify was coming and asked people to join a waitlist. That post ended with "coming soon."&lt;/p&gt;

&lt;p&gt;This one doesn't. Archify is live. It's a Chrome extension, you can install it right now, and it does the thing I promised: you hover over any web app and it tells you how the app is built — without a single byte leaving your browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ten seconds, not an afternoon
&lt;/h2&gt;

&lt;p&gt;The old routine for understanding an unfamiliar app is archaeology. Open DevTools, inspect elements, dig through the network tab, hunt for source maps, trace API calls across a dozen tabs until a mental model finally clicks.&lt;/p&gt;

&lt;p&gt;Archify collapses that into a hover. Install it, open any site, and move your cursor over the page. An overlay follows along, and for whatever you're pointing at it shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;framework&lt;/strong&gt; behind it — React, Next.js, Vue, Angular, Svelte&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;component type&lt;/strong&gt; — dialog, menu, button, input, tooltip&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;UI library&lt;/strong&gt; — MUI, Ant, Bootstrap, Chakra, Radix&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;recent API calls&lt;/strong&gt; the page has made — method, path, status, latency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Click to lock the overlay in place so you can read it properly. Press &lt;code&gt;Escape&lt;/code&gt; to let it follow your cursor again. That's the whole interaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Every claim comes with a confidence — and it never makes names up
&lt;/h2&gt;

&lt;p&gt;This is the part I care about most, and it's the reason Archify took longer than a weekend.&lt;/p&gt;

&lt;p&gt;Most detection tools guess confidently and are wrong just often enough to waste your time. Archify does the opposite. Every claim it makes carries a confidence score, and when it can't actually prove something, it says &lt;code&gt;unknown&lt;/code&gt; instead of inventing an answer.&lt;/p&gt;

&lt;p&gt;On a development build it can be specific. On a minified production bundle — where the component was named &lt;code&gt;a&lt;/code&gt; and the class is &lt;code&gt;css-1q2w3e&lt;/code&gt; — it will not fabricate a component name to look smart. It tells you what the runtime can actually prove and marks the rest honestly.&lt;/p&gt;

&lt;p&gt;My favorite example is shadcn/ui. At runtime it's just Radix primitives with Tailwind classes pasted in — genuinely indistinguishable from raw Radix once it's shipped. So Archify says &lt;strong&gt;Radix&lt;/strong&gt;, and offers shadcn only as a low-confidence hint with a note explaining why it can't be sure. A tool that admits what it doesn't know is worth more than one that's confidently wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  The second tab I didn't mention last time: Security
&lt;/h2&gt;

&lt;p&gt;The waitlist post was all about architecture. Here's the part I saved: there's a second tab, and it changes the question from "how is this built?" to "what is this page doing to me?"&lt;/p&gt;

&lt;p&gt;Switch to &lt;strong&gt;Security&lt;/strong&gt; and, for the page you're on, you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Third-party scripts&lt;/strong&gt; — how many of the scripts running here come from someone other than the site itself&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Outbound calls&lt;/strong&gt; — how many network requests go out, and to how many third-party domains&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Form and payment field access&lt;/strong&gt; — and this is the sharp one: which scripts are in a position to read your &lt;strong&gt;password&lt;/strong&gt;, &lt;strong&gt;card number&lt;/strong&gt;, or &lt;strong&gt;CVC&lt;/strong&gt; fields&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one works by watching which script attaches a listener to a sensitive input, and naming where that script came from. It's not an accusation — a script reading a field can be perfectly legitimate. It just makes visible something that is normally invisible: who is &lt;em&gt;technically able&lt;/em&gt; to see what you type. Point it at a checkout page you don't run and you may find that list longer than you'd like.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nothing leaves your browser
&lt;/h2&gt;

&lt;p&gt;Everything above happens locally. There is no cloud step, no server-side analysis, no source upload, no account, and no telemetry. Archify intercepts &lt;code&gt;fetch&lt;/code&gt; and &lt;code&gt;XHR&lt;/code&gt;, reads the framework globals, and inspects the DOM — all inside your tab — and renders the result right there.&lt;/p&gt;

&lt;p&gt;That isn't a privacy promise stapled onto the marketing. It's the architecture. There is no "other end" to send your code to, because I never built one. The systems you explore stay entirely yours.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's not in it yet
&lt;/h2&gt;

&lt;p&gt;Honesty cuts both ways, so: v1 is deliberately small. There's no AI "explain this app" mode, no architecture graph, and no exports. It's Chrome-only for now. Those are on the roadmap, not in the box — and I'd rather ship something small and honest than something impressive and vague.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install it
&lt;/h2&gt;

&lt;p&gt;If you've ever opened DevTools and wished the app would just explain itself, this is for you. It's free, it's local, and it installs in a click.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://archify.salahxd.dev" rel="noopener noreferrer"&gt;Install Archify at archify.salahxd.dev&lt;/a&gt; — then open any site, hover, and see what it's actually made of. Software shouldn't be a black box.&lt;/p&gt;

</description>
      <category>developertools</category>
      <category>browserextension</category>
      <category>security</category>
      <category>devtool</category>
    </item>
    <item>
      <title>See Through Any Web App</title>
      <dc:creator>Mohd Salahudeen</dc:creator>
      <pubDate>Mon, 22 Jun 2026 14:36:49 +0000</pubDate>
      <link>https://dev.to/salahxd/see-through-any-web-app-40cg</link>
      <guid>https://dev.to/salahxd/see-through-any-web-app-40cg</guid>
      <description>&lt;p&gt;For most of software's history, writing code was the bottleneck. Today, understanding it is.&lt;/p&gt;

&lt;p&gt;AI can generate thousands of lines in seconds. Frameworks abstract away the hard parts, libraries hide their internals, and a single page pulls in dependencies that almost nobody ever opens and reads. We got very good at producing software, and we did almost nothing to make it easier to understand. That gap is the problem I kept running into — so I built a tool for it: Archify.&lt;/p&gt;

&lt;h2&gt;
  
  
  The questions that should take seconds
&lt;/h2&gt;

&lt;p&gt;Every day, developers lose hours to questions that have exact answers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What component is rendering this?&lt;/li&gt;
&lt;li&gt;Where is this data coming from?&lt;/li&gt;
&lt;li&gt;Which API powers this feature?&lt;/li&gt;
&lt;li&gt;What third-party scripts are running on this page?&lt;/li&gt;
&lt;li&gt;What can actually reach user data?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The answers all exist. They're just buried under layers of abstraction — scattered across DevTools panels, source maps, network tabs, and a repository you may not even have access to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding software shouldn't feel like archaeology
&lt;/h2&gt;

&lt;p&gt;When you land on an unfamiliar app, the routine is always the same. Open DevTools. Inspect elements. Dig through network requests. Search the source maps. Trace the API calls. Jump between docs, repos, and a dozen browser tabs until a mental model finally clicks into place.&lt;/p&gt;

&lt;p&gt;The information was never the hard part. Modern tooling is excellent at exposing implementation details — the individual requests, the minified bundles, the raw responses. It's far worse at exposing architecture: how those pieces actually connect. That's the part you end up reconstructing by hand, every single time.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Archify does
&lt;/h2&gt;

&lt;p&gt;Archify is a browser extension that shows you what's actually happening behind any web app. Hover over an element and you instantly see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The component rendering it&lt;/li&gt;
&lt;li&gt;The framework or library behind it&lt;/li&gt;
&lt;li&gt;The API connections powering it&lt;/li&gt;
&lt;li&gt;The scripts running on the page&lt;/li&gt;
&lt;li&gt;How those parts relate to each other&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of spending an afternoon tracing behavior across tabs and files, you get the architecture directly — not just what exists, but how it fits together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Built for understanding, not surveillance
&lt;/h2&gt;

&lt;p&gt;Most developer tools start from the assumption that your data should leave your machine. Archify doesn't. It runs entirely inside your browser:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No cloud processing&lt;/li&gt;
&lt;li&gt;No server-side analysis&lt;/li&gt;
&lt;li&gt;No source code uploads&lt;/li&gt;
&lt;li&gt;No accounts&lt;/li&gt;
&lt;li&gt;No hidden telemetry&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The systems you explore stay yours. That isn't a marketing line — it's the architecture. There's nothing on the other end to send your code to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters now
&lt;/h2&gt;

&lt;p&gt;The industry is mid-shift. Generating code is becoming nearly free, but generated code still has to be reviewed, dependencies still have to be trusted, and applications still have to be understood. The ability to build is accelerating. The ability to comprehend isn't. Every day, that gap widens.&lt;/p&gt;

&lt;p&gt;I think the next generation of developer tools won't be judged by how much code they can write. They'll be judged by how quickly they help you understand what already exists. That's what Archify is for: making the act of understanding a piece of software as fast as the act of using it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming soon
&lt;/h2&gt;

&lt;p&gt;Archify is getting ready for launch, and I'm inviting early users in now. If you've ever opened DevTools and wished the app would just explain itself, you're exactly who I'm building this for.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://archify.salahxd.dev" rel="noopener noreferrer"&gt;Join the waitlist at archify.salahxd.dev&lt;/a&gt; — be among the first to try it, and help shape what it becomes. Because software shouldn't be a black box.&lt;/p&gt;

</description>
      <category>developertools</category>
      <category>extensions</category>
      <category>devtools</category>
      <category>webdev</category>
    </item>
    <item>
      <title>My Site Has No Database, But It Has a CMS, Comments, Likes, and Views</title>
      <dc:creator>Mohd Salahudeen</dc:creator>
      <pubDate>Thu, 11 Jun 2026 18:30:00 +0000</pubDate>
      <link>https://dev.to/salahxd/this-site-has-no-database-but-it-has-a-cms-comments-likes-and-views-317b</link>
      <guid>https://dev.to/salahxd/this-site-has-no-database-but-it-has-a-cms-comments-likes-and-views-317b</guid>
      <description>&lt;p&gt;The title isn't a trick. My &lt;a href="https://salahxd.dev" rel="noopener noreferrer"&gt;site&lt;/a&gt; has a full CMS with an admin panel at &lt;code&gt;/keystatic&lt;/code&gt;, real comments under every post, a like button that remembers you, and view counters that tick up. And there is no database behind any of it — at least, none that I run.&lt;/p&gt;

&lt;p&gt;No server I deploy, no database I provisioned. I've never written a migration for this site, never tuned a connection pool, and there's no disk that can fill up at 3am and page me.&lt;/p&gt;

&lt;p&gt;That last part was the whole design goal. I'm building a startup, and whatever attention I have left over is not going toward babysitting infrastructure for a personal blog. So I gave myself one rule when building this: if a feature needs me to &lt;em&gt;operate&lt;/em&gt; something, it doesn't ship.&lt;/p&gt;

&lt;p&gt;What surprised me is how little I had to give up. The whole thing builds to static files and sits on a CDN — but every piece of state you can touch here still gets saved somewhere durable. The state didn't disappear. I just stopped owning the places where it lands.&lt;/p&gt;

&lt;p&gt;This is not a genius-architecture post. Most of it is me being lazy in a useful direction. But it works well enough that people ask about it, so here's how each piece fits together.&lt;/p&gt;

&lt;h2&gt;
  
  
  The CMS is just git
&lt;/h2&gt;

&lt;p&gt;The blog runs on &lt;a href="https://keystatic.com/" rel="noopener noreferrer"&gt;Keystatic&lt;/a&gt;. When I open &lt;code&gt;/keystatic&lt;/code&gt; in production and hit save, it doesn't write to a database. It makes a commit to this repo through a GitHub App:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// keystatic.config.ts&lt;/span&gt;
&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DEV&lt;/span&gt;
  &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;local&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;github&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Salah-XD/personal-portfolio&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In dev it writes files to my local disk. In production it commits straight to GitHub. The post you're reading is a &lt;code&gt;.md&lt;/code&gt; file in &lt;code&gt;src/content/blog/&lt;/code&gt;. My &lt;code&gt;/now&lt;/code&gt; and &lt;code&gt;/uses&lt;/code&gt; pages are JSON files in the same repo, edited through the same admin UI.&lt;/p&gt;

&lt;p&gt;So my "content database" is the git history. Publishing is a commit. Fixing a typo is a commit. Undoing a bad edit is &lt;code&gt;git revert&lt;/code&gt;. Vercel sees the push and rebuilds. I get diffs, blame, and backups for free, from the version control I was going to use anyway.&lt;/p&gt;

&lt;p&gt;The catch: writes are slow in the way a deploy is slow. Hitting save kicks off a rebuild, so "publish" takes about a minute. For a blog, I genuinely don't care. If that minute bothered me, this whole setup would be the wrong choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  The comments are GitHub Discussions in a trench coat
&lt;/h2&gt;

&lt;p&gt;The comment box at the bottom of each post is &lt;a href="https://giscus.app/" rel="noopener noreferrer"&gt;Giscus&lt;/a&gt;, a thin shell over GitHub Discussions. Each post maps to a Discussion thread in the repo. When you comment, you're posting to GitHub, signed in as yourself.&lt;/p&gt;

&lt;p&gt;So my "comments table" is a Discussions tab, my spam protection is GitHub's, and my auth is OAuth somebody else built. If the env vars aren't wired up, the component renders a placeholder instead of breaking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{giscusReady ? (
  &amp;lt;Comments ... /&amp;gt;
) : (
  &amp;lt;p&amp;gt;Comments will appear here once Giscus is configured.&amp;lt;/p&amp;gt;
)}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's a real downside here and I want to be upfront about it: you need a GitHub account to comment. For a blog where the readers are mostly developers, that filter costs me almost nothing and kills spam dead. If I were writing for a general audience, it would be a terrible choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Okay, confession: the like button does touch a server
&lt;/h2&gt;

&lt;p&gt;The likes and views from the title are the one kind of state on this site I couldn't fake at build time — a view happens when a person shows up, and there's no way around counting it live. So those two features hit &lt;a href="https://upstash.com/" rel="noopener noreferrer"&gt;Upstash Redis&lt;/a&gt; through the only two routes on the site that aren't static:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prerender&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// this route becomes a Vercel Function&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;incr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`likes:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the entire data model. Two integer keys per post — &lt;code&gt;likes:slug&lt;/code&gt; and &lt;code&gt;views:slug&lt;/code&gt; — and the only operation is &lt;code&gt;INCR&lt;/code&gt;. No schema, no ORM, no tables. Deduping is a cookie, not a query: 30 minutes for views, a year for likes. Someone clearing their cookies can like a post twice. I can live with that.&lt;/p&gt;

&lt;p&gt;And the Redis layer is optional. If the keys aren't set, it degrades instead of crashing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/redis.ts&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;KV_REST_API_URL&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UPSTASH_REDIS_REST_URL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;KV_REST_API_TOKEN&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UPSTASH_REDIS_REST_TOKEN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;_redis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Redis&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;_redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;_redis&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isRedisConfigured&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;_redis&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So yes — strictly speaking, "no backend" is false. There are two serverless functions and a managed key-value store. But Upstash speaks plain HTTP and bills per request, so there's nothing to keep alive between visitors. I provisioned it by clicking a button on a marketplace page. I have never operated it. That feels meaningfully different from "running a database," even if a pedant would disagree.&lt;/p&gt;

&lt;h2&gt;
  
  
  Everything else happens before anyone visits
&lt;/h2&gt;

&lt;p&gt;The rest of what looks dynamic is work done once, at build time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Search&lt;/strong&gt; is &lt;a href="https://pagefind.app/" rel="noopener noreferrer"&gt;Pagefind&lt;/a&gt; — it indexes the built HTML and ships a static index the browser queries. No search server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The OG image&lt;/strong&gt; on every post is rendered with satori + resvg during the build. No screenshot service.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RSS and the sitemap&lt;/strong&gt; are just generated files.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a request comes in, almost none of this code runs. It already ran, once, on my machine and on Vercel's build servers.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this actually costs me
&lt;/h2&gt;

&lt;p&gt;None of this is free, it's just paid in a different currency:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No instant writes.&lt;/strong&gt; Publishing is a deploy. Fine for a blog, useless for anything interactive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No queries.&lt;/strong&gt; I can increment a counter. I can't ask "which posts did people who liked X also read." If I ever want that, Redis with two keys per post isn't the answer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More vendors, not fewer.&lt;/strong&gt; Instead of one database I run, I depend on GitHub, Upstash, Vercel, and Buttondown for the newsletter. My bet is that each of them runs their slice better than I'd run all of it — and if one of them dies or gets unbearable, I'm swapping one small piece, not migrating a monolith.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When I'd throw this whole thing out
&lt;/h2&gt;

&lt;p&gt;The moment this site needs user accounts, per-user data, anything transactional, or writes that have to show up in the same second — I'd set up a real database and not feel bad about it. This setup works &lt;em&gt;because&lt;/em&gt; a personal blog is read-heavy, write-rare, and has exactly one author. Those are narrow conditions.&lt;/p&gt;

&lt;p&gt;But they describe most personal sites and content sites. And for those, the best backend is the one you stop thinking about after the first week. Mine has been a rebuild and an &lt;code&gt;INCR&lt;/code&gt; ever since, and I haven't thought about it once.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>serverless</category>
      <category>opensource</category>
      <category>git</category>
    </item>
  </channel>
</rss>
