<?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: Heartlin Machado</title>
    <description>The latest articles on DEV Community by Heartlin Machado (@blockintel).</description>
    <link>https://dev.to/blockintel</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%2F3846535%2Fd7d89307-1df2-41d7-96ed-ac5bb03f7c8b.png</url>
      <title>DEV Community: Heartlin Machado</title>
      <link>https://dev.to/blockintel</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/blockintel"/>
    <language>en</language>
    <item>
      <title>The guardrails that matter are code, not prompts</title>
      <dc:creator>Heartlin Machado</dc:creator>
      <pubDate>Sun, 30 Aug 2026 19:01:08 +0000</pubDate>
      <link>https://dev.to/blockintel/the-guardrails-that-matter-are-code-not-prompts-1ii8</link>
      <guid>https://dev.to/blockintel/the-guardrails-that-matter-are-code-not-prompts-1ii8</guid>
      <description>&lt;p&gt;I built an agent that coordinates eldercare for my mother in India, and the interesting part was not the agents. It was the four times I stopped and decided what the system was &lt;strong&gt;not allowed&lt;/strong&gt; to do, and then put those decisions somewhere no prompt can reach.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I created this post for the purposes of entering the All Things Agentic Hackathon (Google Cloud / Devpost).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It's live, and you can check its work without asking me: &lt;strong&gt;&lt;a href="https://anbu-care-37j4eofpwq-el.a.run.app" rel="noopener noreferrer"&gt;https://anbu-care-37j4eofpwq-el.a.run.app&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem, briefly
&lt;/h2&gt;

&lt;p&gt;My parents live in Thoothukudi. I don't. Every NRI I know has the same 3 a.m. fear: something happens, and by the time you hear about it, the decisions that mattered have already been made by whoever was standing there.&lt;/p&gt;

&lt;p&gt;One rule fell out of that and shaped everything: &lt;strong&gt;any step that needs the son to act is a design failure.&lt;/strong&gt; He is asleep, nine and a half time zones away. That's the problem, not a detail of it. So every feature got tested with him unreachable.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. A guardrail you can argue with is not a guardrail
&lt;/h2&gt;

&lt;p&gt;Five agents on Gemini 3.5 Flash and Google's ADK: onboarding, triage, evidence, insurer liaison, comms, all under a coordinator. Underneath them is a deterministic layer no agent can reach past. &lt;strong&gt;The model proposes; that layer decides, and it is the only thing that can write.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The demo input for triage is a neighbour calling to say &lt;em&gt;"she says it's probably just gas."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Severity comes back HIGH.&lt;/p&gt;

&lt;p&gt;The thing that decides severity is a Python dict. It never reads that sentence as permission, because it never reads that sentence.&lt;/p&gt;

&lt;p&gt;The second one is the one I'd defend hardest. Clinical detail can't go over WhatsApp. India's DPDP Act and Meta's healthcare policy make that a legal line. So the gate classifies the &lt;strong&gt;content&lt;/strong&gt;, not the caller's claim about it. A message declared &lt;code&gt;logistics&lt;/code&gt; that reads &lt;em&gt;"just logistics: troponin 0.94 ng/mL"&lt;/em&gt; is blocked anyway.&lt;/p&gt;

&lt;p&gt;Then the demo does something that took me embarrassingly long to think of: it bypasses the agent entirely and calls the send function directly.&lt;/p&gt;

&lt;p&gt;Still blocked.&lt;/p&gt;

&lt;p&gt;That second half is the whole claim. &lt;strong&gt;An agent that is merely &lt;em&gt;told&lt;/em&gt; not to leak a lab value is not a control.&lt;/strong&gt; If your only enforcement is in the system prompt, you have a strong suggestion, and you will find out which it was on the day it matters.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Public where it proves, private where it reveals
&lt;/h2&gt;

&lt;p&gt;This is the design decision I'm most pleased with, and it came from a question I couldn't answer cleanly at first.&lt;/p&gt;

&lt;p&gt;If clinical data can't go over WhatsApp because it "lives somewhere protected", what exactly is protecting it?&lt;/p&gt;

&lt;p&gt;If the answer had been "a URL nobody guesses", the whole argument would have been hollow, and I'd have published the exact data I claimed to guard.&lt;/p&gt;

&lt;p&gt;So the API has two access models, both enforced server-side:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s1"&gt;'%{http_code}\n'&lt;/span&gt; &lt;span class="nv"&gt;$URL&lt;/span&gt;/api/parents/&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;      &lt;span class="c"&gt;# 401&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s1"&gt;'%{http_code}\n'&lt;/span&gt; &lt;span class="nv"&gt;$URL&lt;/span&gt;/api/cases/&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;/verify &lt;span class="c"&gt;# 200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every action appends an Ed25519-signed receipt whose hash covers the previous one. &lt;strong&gt;Verification proves the record wasn't altered without revealing what it says.&lt;/strong&gt; It returns hashes, a boolean and a failure mode. That's exactly why it can be open to everyone, and why it &lt;em&gt;has&lt;/em&gt; to be: a receipt chain only means something if you can check it without my permission.&lt;/p&gt;

&lt;p&gt;You can watch it catch an edit. Rewrite a stored receipt straight in Firestore, leaving the hash and signature untouched, exactly as a silent after-the-fact edit would:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"payload.severity"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"before"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HIGH"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"after"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"LOW"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then ask the public endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"verified"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"receipt_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"broken_at_seq"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"payload does not hash to the recorded hash: content was altered"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It names the receipt. Not "something is wrong", but &lt;em&gt;sequence one, content altered.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;One caveat I put in my own demo script, because leaving it out would be a lie by omission: &lt;strong&gt;an empty chain is a valid chain.&lt;/strong&gt; A deleted case answers &lt;code&gt;verified: true&lt;/code&gt; with &lt;code&gt;receipt_count: 0&lt;/code&gt;. &lt;code&gt;verified&lt;/code&gt; on its own proves the absence of tampering in nothing at all. The count is what ties the proof to the story.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. The bug that would have discredited everything
&lt;/h2&gt;

&lt;p&gt;On one run, the agent told me:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I have successfully read your mother's lab report and ingested it into her health record."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Documents actually stored: &lt;strong&gt;zero&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For a system whose entire pitch is a verifiable record, an agent asserting a write that never happened is fatal. Not embarrassing. Fatal. Everything else I had built was an argument that you could trust the record, and here was the record's own narrator making things up.&lt;/p&gt;

&lt;p&gt;Prompt wording was never going to fix that. So the demo now prints the stored count &lt;strong&gt;read back from the service&lt;/strong&gt;, right next to what the agent claimed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GROUND TRUTH. Documents actually stored for this parent: 2
reported status 'ingested' vs stored count 2: consistent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If they ever disagree, it says &lt;code&gt;CONTRADICTED&lt;/code&gt; on screen.&lt;/p&gt;

&lt;p&gt;The general lesson I took: anywhere an agent reports an outcome, report the outcome &lt;strong&gt;from the system that would know&lt;/strong&gt;, side by side, and make the disagreement loud.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. The bug that taught me what my tests were worth
&lt;/h2&gt;

&lt;p&gt;This one is more useful, because it looks like nothing.&lt;/p&gt;

&lt;p&gt;The document reader emits the kind &lt;code&gt;lab_report&lt;/code&gt;. The record stores it as &lt;code&gt;blood_report&lt;/code&gt;. I added a guard (an arriving lab report should close the outstanding diagnostic order it belongs to) and compared against the &lt;em&gt;stored&lt;/em&gt; word.&lt;/p&gt;

&lt;p&gt;Nothing raises. No error appears anywhere. No log line. The guard simply never matches, and the loop silently closes nothing, forever.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Eighteen unit tests passed.&lt;/strong&gt; They all called the function directly and handed it the right word themselves.&lt;/p&gt;

&lt;p&gt;The fix wasn't the one-word change. It was writing two tests that go through the real ingestion path end to end, and &lt;strong&gt;confirming they fail first&lt;/strong&gt;, with the message &lt;em&gt;"the report went in and closed nothing; the hook and the reader disagree about what a lab report is called"&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;A test that cannot fail for the reason you care about is not covering that reason. I now break every new guard on purpose and check that something goes red before I let the change stand.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. The refusal I'm proudest of
&lt;/h2&gt;

&lt;p&gt;That same lab-report loop has a case where it does nothing, deliberately.&lt;/p&gt;

&lt;p&gt;If &lt;strong&gt;two&lt;/strong&gt; tests are outstanding on an admission and one report arrives, it closes neither.&lt;/p&gt;

&lt;p&gt;Attributing that report to one of the two orders means reading it to work out which test it is, and that is a model deciding which clinical order was carried out. So it stops, writes a receipt saying exactly why, and leaves a person an accurate record and an obvious next step.&lt;/p&gt;

&lt;p&gt;The same instinct shows up in the memory. The system remembers one class of fact between admissions: whether she answers by voice note or by typing. It's in a Vertex AI Agent Engine Memory Bank, and there is &lt;strong&gt;no free-text path into that store.&lt;/strong&gt; Each kind of memory has its own function composing its own sentence from a value validated first. A caller can't put a symptom in it because a caller can't put a &lt;em&gt;sentence&lt;/em&gt; in it. Recall is an exact scope lookup, never a similarity search, so an unrelated memory can't surface because it read as close enough.&lt;/p&gt;

&lt;p&gt;That's the whole philosophy in one small module: &lt;strong&gt;decide what may be true, in code, and the interesting failures become impossible rather than unlikely.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What's actually running
&lt;/h2&gt;

&lt;p&gt;Gemini 3.5 Flash and the Agent Development Kit on Cloud Run, Firestore for state and the receipt ledger, Pub/Sub for multi-day case events, Cloud Storage for photographs, Vertex AI Agent Engine for Memory Bank, Google Places to verify that every hospital is a real place.&lt;/p&gt;

&lt;p&gt;Cloud Scheduler matters more than it sounds. Cloud Run holds no timer, so the regulatory clocks are real scheduler jobs ticking every minute, which is the difference between an agent and a demo. It keeps running when the browser is closed and nobody is watching.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1,203 tests, none of which need cloud access or a model to run.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What isn't real, stated plainly
&lt;/h2&gt;

&lt;p&gt;Because a post that spent 2,000 words on honesty should end with some:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The insurer's adjudicator is simulated.&lt;/strong&gt; Deterministic local rules, labelled as such on every receipt and every API response, not just here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payments run on a real provider in test mode.&lt;/strong&gt; Real Razorpay link, real API call, real webhook, test money. No mode moves real money.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WhatsApp is real&lt;/strong&gt; (Twilio, actual handsets) but freeform-only inside the 24-hour window, because there are no approved templates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hospital data is half real.&lt;/strong&gt; Locations carry a Places &lt;code&gt;place_id&lt;/code&gt; and a verification date. Capability and insurer empanelment are a dated seed and say so on every triage call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;All demo data is synthetic&lt;/strong&gt;, and the clinical views say so on screen, because a screenshot outlives a demo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also doesn't watch anyone. No sensors, no passive monitoring. An episode begins because a signal &lt;em&gt;arrives&lt;/em&gt;, and the receipt says &lt;code&gt;received from an external channel, not detected by Anbu Care&lt;/code&gt;. The tests reject the words "detect", "notice", "sense" and "monitor" in that path unless they follow a negation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try to break it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://anbu-care-37j4eofpwq-el.a.run.app
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nv"&gt;$URL&lt;/span&gt;/api/cases/case-da1c2cb6db/verify | jq   &lt;span class="c"&gt;# verified: true, 8 receipts&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nv"&gt;$URL&lt;/span&gt;/api/cases/case-a7cf9fa613/verify | jq   &lt;span class="c"&gt;# verified: false, broken_at_seq: 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dashboard: &lt;code&gt;/app&lt;/code&gt;. Health, including which components are simulated: &lt;code&gt;/api/healthz&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I created this article for the purposes of entering the &lt;strong&gt;All Things Agentic Hackathon&lt;/strong&gt;. If you take one thing from it: put your guarantees in code, then try to break them from &lt;em&gt;outside&lt;/em&gt; the agent. If it still holds, you have a control. If it doesn't, you have a prompt.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Anbu (அன்பு) is Tamil for love.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  AllThingsAgenticHackathon
&lt;/h1&gt;




&lt;h2&gt;
  
  
  Pre-publish checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Published &lt;strong&gt;public&lt;/strong&gt;, not unlisted.&lt;/li&gt;
&lt;li&gt;[ ] The line &lt;em&gt;"created this post for the purposes of entering the All Things
  Agentic Hackathon"&lt;/em&gt; is present near the top.&lt;/li&gt;
&lt;li&gt;[ ] Max 4 tags.&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;make test&lt;/code&gt;, confirm 1,203 and update if not.&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;curl -s $URL/api/healthz&lt;/code&gt;, confirm the "what isn't real" list still
  matches &lt;code&gt;tpa_mode&lt;/code&gt;, &lt;code&gt;whatsapp_mode&lt;/code&gt;, &lt;code&gt;memory_bank&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;[ ] Both canonical case ids still verify as stated.&lt;/li&gt;
&lt;li&gt;[ ] URL pasted into the Devpost "link to a piece of content" field.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>googlecloud</category>
      <category>ai</category>
      <category>agents</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Four bugs in one paragraph: what I learned building an autonomous loop that chases people who don't answer</title>
      <dc:creator>Heartlin Machado</dc:creator>
      <pubDate>Sun, 30 Aug 2026 18:11:25 +0000</pubDate>
      <link>https://dev.to/blockintel/four-bugs-in-one-paragraph-what-i-learned-building-an-autonomous-loop-that-chases-people-who-dont-4hdl</link>
      <guid>https://dev.to/blockintel/four-bugs-in-one-paragraph-what-i-learned-building-an-autonomous-loop-that-chases-people-who-dont-4hdl</guid>
      <description>&lt;p&gt;&lt;em&gt;I built CrisisMesh for the &lt;a href="https://allthingsagentic.devpost.com/" rel="noopener noreferrer"&gt;All Things Agentic Hackathon&lt;/a&gt;, and I created this post for the purposes of entering that hackathon. #AllThingsAgenticHackathon&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;CrisisMesh is a seven-agent fleet on Google ADK and Gemini 3.5 Flash that coordinates a school's response after a human reports an emergency. It doesn't detect anything. Somebody sends the message they'd already have sent: a Slack command, a WhatsApp text. The fleet takes it from there.&lt;/p&gt;

&lt;p&gt;The interesting part wasn't getting agents to talk to each other. It was everything that happened once the system started making claims.&lt;/p&gt;

&lt;h2&gt;
  
  
  The loop is the product
&lt;/h2&gt;

&lt;p&gt;The feature I'd defend is the one nobody watches. A declared incident starts a scheduler. Every tick it looks at whoever hasn't checked in, pings them, re-pings them, and at a configured cap &lt;strong&gt;stops pinging and hands that person to their floor warden by name&lt;/strong&gt;, on the warden's own channel.&lt;/p&gt;

&lt;p&gt;Four bugs lived in that one paragraph. Every one of them passed its tests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It sent the escalation to the person it couldn't find.&lt;/strong&gt; &lt;em&gt;"Mrs. Rodriguez has not answered, please locate her"&lt;/em&gt; went to Mrs. Rodriguez. The message was correct. The recipient was the one person it couldn't help.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It never stopped.&lt;/strong&gt; Once escalated, a person stayed actionable, so on a timer the same warden was paged about the same person every 25 seconds, forever. I fixed it with a terminal state, and then the same bug came back, because the guard went into the function the tests called while the running loop called a different one. Green the entire time.&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="c1"&gt;# tests called this one
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;should_act&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;incident_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;person_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;get_state&lt;/span&gt;&lt;span class="p"&gt;(...).&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;TERMINAL_FOR_THE_LOOP&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="c1"&gt;# the guard
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="c1"&gt;# the loop called this one
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;safe_should_act&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;incident_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;person_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ACCOUNTED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="c1"&gt;# ACCOUNTED only. not ESCALATED.
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;It counted people it couldn't reach as reachable.&lt;/strong&gt; Any non-empty Slack id counted, including the roster's placeholders like &lt;code&gt;U_PRINCIPAL&lt;/code&gt;, so the loop reported 34 of 34 reachable and chased thirty people down channels that addressed nobody. It now verifies ids against the workspace and reports &lt;strong&gt;4 of 34&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That number is worse and it's true. The difference matters: unreachable is a fact a commander acts on, not a gap to paper over.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The tick guard was process-local.&lt;/strong&gt; Harmless at one instance. At four, every container runs its own tick N and one silent teacher gets pinged four times. Ticks are now claimed with a create-if-absent lease in Firestore.&lt;/p&gt;

&lt;h2&gt;
  
  
  Every real bug was in a seam
&lt;/h2&gt;

&lt;p&gt;Not one of these was a bad function. They were all connections: a guard in the wrong function, a check-in written to one ledger and not the other, a critic that ran on one path and not another.&lt;/p&gt;

&lt;p&gt;The sharpest example: a teacher asked &lt;em&gt;"what's the fastest route out of east wing"&lt;/em&gt; during an active shooter, and the system answered with corridor directions. The movement policy that exists to prevent exactly that ran inside the fan-out. A query answer is a transport reply, and it never passed through.&lt;/p&gt;

&lt;p&gt;Unit tests for the policy: green. Unit tests for the query desk: green.&lt;/p&gt;

&lt;p&gt;So the tests changed shape. They stopped asserting that functions return values and started asserting things about the world:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_the_escalation_does_not_go_to_the_person_being_looked_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_no_route_through_a_reported_sighting_is_offered&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_a_checkin_recorded_by_one_instance_is_counted_by_another&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_an_unreadable_ledger_says_so_and_counts_everyone_missing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those fail for real reasons.&lt;/p&gt;

&lt;h2&gt;
  
  
  Managed services find bugs that mocks cannot
&lt;/h2&gt;

&lt;p&gt;Moving the Memory Bank onto Vertex AI Agent Engine took an afternoon of writing and ten minutes of reality demolishing it.&lt;/p&gt;

&lt;p&gt;Vertex persists &lt;code&gt;fact&lt;/code&gt; and &lt;code&gt;scope&lt;/code&gt; and &lt;strong&gt;silently drops &lt;code&gt;display_name&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt;&lt;/strong&gt;, which is where I'd put the structured record, so retrieval returned memories it couldn't read. And scope matching is exact on the whole map, not a subset, so putting metadata in scope makes a lesson findable only by someone who already knows its id.&lt;/p&gt;

&lt;p&gt;Neither is in the docs. Both took one live call. I made the test double lossy in the same way afterwards, so the double can no longer pass while the service fails.&lt;/p&gt;

&lt;p&gt;Model Armor was worse, and the failure was mine. The pillar was marked &lt;em&gt;managed&lt;/em&gt; while the deployed service ran the regex fallback, so none of it had ever executed in production. Three defects were sitting there:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The client was built against the global endpoint. Templates are regional, so every scan returned "template not found", which fell into the error path below.&lt;/li&gt;
&lt;li&gt;Both error paths returned &lt;code&gt;blocked: False&lt;/code&gt;, under a comment claiming they failed closed for ambiguous cases.&lt;/li&gt;
&lt;li&gt;The block signal was read as &lt;code&gt;"MATCH_FOUND" in str(state)&lt;/code&gt;. &lt;code&gt;str()&lt;/code&gt; of that enum is &lt;code&gt;"2"&lt;/code&gt;. &lt;strong&gt;Model Armor had never blocked anything.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then turning it on correctly broke the product. The RAI &lt;em&gt;dangerous&lt;/em&gt; classifier refused &lt;em&gt;"Smoke near the science lab, floor 2"&lt;/em&gt; while letting &lt;em&gt;"active shooter reported in the east wing"&lt;/em&gt; through. A system that receives reports of danger can't treat danger as grounds for refusal. That filter is off now, in the template and in code, so a template edit can't quietly stop it accepting emergencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it refuses to say
&lt;/h2&gt;

&lt;p&gt;The parts I'm most confident in are the refusals.&lt;/p&gt;

&lt;p&gt;It won't name a route clear when it couldn't read the sighting log; the brief says &lt;code&gt;EGRESS ASSESSMENT WITHHELD&lt;/code&gt;. It won't report zero unaccounted because the ledger was unreadable; the roster is the denominator, so a lost record counts as missing. It won't publish an assembly point during a lockdown. It won't send a tactical brief over WhatsApp, because that document names where people with mobility limitations are.&lt;/p&gt;

&lt;p&gt;And the numbers say which scale they're on. Managed semantic recall returns a vector distance; the local store returns Jaccard tag overlap. A correct top hit reads &lt;strong&gt;0.166&lt;/strong&gt; on one and &lt;strong&gt;0.75&lt;/strong&gt; on the other, so every result carries the basis that produced it. Presenting one as the other is the same class of claim as 34 of 34.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it's weak
&lt;/h2&gt;

&lt;p&gt;Slack reaction check-ins are still process-local, so they under-report and never over-report. The audit bundle reflects the instance serving the request. And SMS isn't a live channel at all: the route, signature verification and keyword mapping are written and tested, but the A2P 10DLC campaign is unapproved, so zero SMS have been sent or received. It's in the README as upcoming rather than counted as a transport.&lt;/p&gt;

&lt;p&gt;All three are in the README under Known Limits with their failure mode named. A README that claims more than the runtime does is the same failure the runtime spends its effort avoiding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stack
&lt;/h2&gt;

&lt;p&gt;Google ADK · Gemini 3.5 Flash and 2.5 Flash Lite on Vertex AI · Cloud Run · Firestore · Pub/Sub · Model Armor · Vertex AI Agent Engine Memory Bank · Slack Events API · Twilio WhatsApp. 1,299 tests, none of which need GCP credentials.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/4KInc/CrisisMesh" rel="noopener noreferrer"&gt;https://github.com/4KInc/CrisisMesh&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Live:&lt;/strong&gt; &lt;a href="https://crisismesh-1031148889398.us-central1.run.app" rel="noopener noreferrer"&gt;https://crisismesh-1031148889398.us-central1.run.app&lt;/a&gt;&lt;/p&gt;

</description>
      <category>googlecloud</category>
      <category>ai</category>
      <category>hackathon</category>
      <category>python</category>
    </item>
    <item>
      <title>Add Pre-Signature Policy Enforcement to AWS KMS Signing in 10 Minutes</title>
      <dc:creator>Heartlin Machado</dc:creator>
      <pubDate>Fri, 27 Mar 2026 17:17:41 +0000</pubDate>
      <link>https://dev.to/blockintel/add-pre-signature-policy-enforcement-to-aws-kms-signing-in-10-minutes-24ha</link>
      <guid>https://dev.to/blockintel/add-pre-signature-policy-enforcement-to-aws-kms-signing-in-10-minutes-24ha</guid>
      <description>&lt;p&gt;If you're using AWS KMS for blockchain transaction signing, every call to &lt;code&gt;kms:Sign&lt;/code&gt; is an unrestricted operation. Any process with the right IAM permissions can sign any transaction to any address for any amount.&lt;/p&gt;

&lt;p&gt;This tutorial shows you how to add a policy evaluation layer in front of your KMS signing operations — so every transaction is checked against rules (value limits, address allowlists, velocity caps) before the signature is produced. If the policy says no, the key never signs.&lt;/p&gt;

&lt;p&gt;We'll use &lt;a href="https://blockintelai.com" rel="noopener noreferrer"&gt;Gate&lt;/a&gt;, an open-source pre-signature enforcement SDK, in SHADOW mode — meaning it evaluates every transaction and logs the decision, but never blocks. Zero risk to your existing operations.&lt;/p&gt;




&lt;h2&gt;
  
  
  What you'll build
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your App → Gate SDK (evaluate) → Policy Decision → KMS Sign (only if ALLOW)
                                      ↓
                            Signed receipt (HMAC + RSA)
                            Decision log (DynamoDB)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every signing request gets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A policy evaluation (denylist, value threshold, velocity, signer authorization)&lt;/li&gt;
&lt;li&gt;A cryptographically signed receipt (verifiable by third parties)&lt;/li&gt;
&lt;li&gt;A decision log entry (queryable audit trail)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In SHADOW mode, the SDK never blocks — it returns &lt;code&gt;ALLOW&lt;/code&gt; for everything but logs &lt;code&gt;WOULD_BLOCK&lt;/code&gt; when policy violations are detected. You see what Gate would catch without any production risk.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Node.js 18+&lt;/li&gt;
&lt;li&gt;An AWS account with a KMS asymmetric signing key (secp256k1 or ECDSA P-256)&lt;/li&gt;
&lt;li&gt;A Gate tenant (free trial at &lt;a href="https://blockintelai.com" rel="noopener noreferrer"&gt;blockintelai.com&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;blockintel-gate-sdk @aws-sdk/client-kms uuid
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Initialize the Gate client
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createGateClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blockintel-gate-sdk&lt;/span&gt;&lt;span class="dl"&gt;'&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;gate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createGateClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://gate-hotpath.blockintelai.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;tenantId&lt;/span&gt;&lt;span class="p"&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;GATE_TENANT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&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;GATE_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;signerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;treasury-signer-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;onConnectionFailure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FAIL_OPEN&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// SHADOW mode default — never block&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;FAIL_OPEN&lt;/code&gt; means that if Gate itself is unreachable, your signing operations continue uninterrupted. Gate is never a Tier-0 dependency unless you want it to be.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Wrap your signing function
&lt;/h2&gt;

&lt;p&gt;Here's a typical KMS signing function without Gate:&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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;KMSClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SignCommand&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@aws-sdk/client-kms&lt;/span&gt;&lt;span class="dl"&gt;'&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;v4&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;uuidv4&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;uuid&lt;/span&gt;&lt;span class="dl"&gt;'&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;kms&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;KMSClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;us-east-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// ❌ Before: unrestricted signing&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;signTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;txHash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;keyId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&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;result&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;kms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SignCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;KeyId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;keyId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;txHash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;MessageType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DIGEST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;SigningAlgorithm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ECDSA_SHA_256&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Signature&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the same function with Gate evaluation:&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;// ✅ After: every signing operation evaluated against policy&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;signTransactionWithGate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;txHash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;keyId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;toAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;valueWei&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;valueUsd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&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="c1"&gt;// Step 1: Ask Gate if this transaction should proceed&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decision&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;gate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;uuidv4&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;txIntent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;toAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;valueWei&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;valueUsd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;network&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ethereum&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;networkFamily&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;EVM&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0xYourWalletAddress&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="na"&gt;signingContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;signerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;treasury-signer-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;actorPrincipal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ops@yourcompany.com&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="c1"&gt;// Step 2: In SHADOW mode, decision is always ALLOW&lt;/span&gt;
  &lt;span class="c1"&gt;// But decision.reasonCodes tells you what WOULD have been blocked&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;decision&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reasonCodes&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[GATE] Would block: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reasonCodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;, &lt;/span&gt;&lt;span class="dl"&gt;'&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;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Step 3: Receipt is signed and logged regardless&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[GATE] Receipt: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;receiptSignature&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;span class="c1"&gt;// Step 4: Sign with KMS (always proceeds in SHADOW mode)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&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;kms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SignCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;KeyId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;keyId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;txHash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;MessageType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DIGEST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;SigningAlgorithm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ECDSA_SHA_256&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Signature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;gateDecision&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;gateReceipt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;receiptSignature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;wouldBlock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reasonCodes&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Every KMS signing operation now produces a policy decision and a signed receipt. In SHADOW mode, nothing is blocked — you're collecting data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Set up policies
&lt;/h2&gt;

&lt;p&gt;Log into the Gate Console and create policies for your tenant. Common starting rules:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rule&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Denylist&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Blocks transactions to known malicious addresses&lt;/td&gt;
&lt;td&gt;OFAC SDN list, known exploit contracts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Destination Allowlist&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Only allow transfers to approved addresses&lt;/td&gt;
&lt;td&gt;Your hot wallets, exchange deposit addresses&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Value Threshold&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Block transactions above a USD amount&lt;/td&gt;
&lt;td&gt;Max $100,000 per transaction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Velocity Limit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Cap transaction volume in a rolling window&lt;/td&gt;
&lt;td&gt;Max $500,000 per 10 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Signer Allowlist&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Only approved signing identities can request&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;treasury-signer-1&lt;/code&gt;, &lt;code&gt;ops-signer-2&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;New Destination&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Flag first-time destination addresses&lt;/td&gt;
&lt;td&gt;Log or require step-up approval&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;After creating rules, compile and publish a policy snapshot. The hot path loads the snapshot and evaluates every &lt;code&gt;gate.evaluate()&lt;/code&gt; call against it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Verify it works
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Test: normal transaction (should be ALLOW)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;normalTx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;signTransactionWithGate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;abc123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;arn:aws:kms:us-east-1:123456789:key/your-key-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0x742d35Cc6634c0532925a3b844Bc9e7595916DA2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Known good address&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1000000000000000000&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// 1 ETH&lt;/span&gt;
  &lt;span class="mi"&gt;2500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// $2,500&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;normalTx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gateDecision&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// "ALLOW"&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;normalTx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wouldBlock&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;     &lt;span class="c1"&gt;// []&lt;/span&gt;

&lt;span class="c1"&gt;// Test: denylisted address (should be WOULD_BLOCK in SHADOW)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blockedTx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;signTransactionWithGate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;def456&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;arn:aws:kms:us-east-1:123456789:key/your-key-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0xba5ed7f51d2a0cfeef2ca243af1188bad4f1cb03&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Known exploit address&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;50000000000000000000&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// 50 ETH&lt;/span&gt;
  &lt;span class="mi"&gt;125000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// $125,000&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blockedTx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gateDecision&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// "ALLOW" (SHADOW mode override)&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blockedTx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wouldBlock&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;    &lt;span class="c1"&gt;// ["DENYLIST"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In SHADOW mode, both transactions succeed. But the second one logs &lt;code&gt;WOULD_BLOCK&lt;/code&gt; with reason &lt;code&gt;DENYLIST&lt;/code&gt;. You now have visibility into what Gate would catch — without changing your production behavior.&lt;/p&gt;




&lt;h2&gt;
  
  
  What you get after 14 days
&lt;/h2&gt;

&lt;p&gt;Let Gate run in SHADOW mode for two weeks. You'll accumulate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A decision log&lt;/strong&gt; — every signing operation evaluated, with policy verdicts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A block rate&lt;/strong&gt; — what percentage of transactions would have been blocked by your policy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A max-loss estimate&lt;/strong&gt; — if a signing key were compromised, how much could an attacker drain in 24 hours under your current policy (this is the Bot Blast Radius metric)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Signed receipts&lt;/strong&gt; — cryptographically signed records of every decision, verifiable by third parties (useful for insurance, compliance, audits)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This data is visible in the Gate Console dashboard. If you're working with a custody insurance carrier, the BBR report and evidence bundle are exactly what underwriters need to evaluate your signing controls.&lt;/p&gt;




&lt;h2&gt;
  
  
  Going from SHADOW to enforcement
&lt;/h2&gt;

&lt;p&gt;When you're ready to enforce (not just monitor), the transition is one config change:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createGateClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="c1"&gt;// ... same config&lt;/span&gt;
  &lt;span class="na"&gt;onConnectionFailure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FAIL_CLOSED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Now: block on policy violation&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At &lt;code&gt;SOFT_ENFORCE&lt;/code&gt;, the SDK throws &lt;code&gt;BlockIntelBlockedError&lt;/code&gt; when a policy violation is detected. Your code catches it and decides what to do.&lt;/p&gt;

&lt;p&gt;At &lt;code&gt;HARD_KMS_GATEWAY&lt;/code&gt;, Gate removes &lt;code&gt;kms:Sign&lt;/code&gt; from your application's IAM role entirely. The only path to a signature is through Gate's policy evaluation. Even a fully compromised application cannot sign a transaction that violates policy — because the application no longer has the IAM permission to call KMS directly.&lt;/p&gt;

&lt;p&gt;That's the enforcement ladder: SHADOW (monitor) → SOFT_ENFORCE (SDK blocks) → HARD_KMS_GATEWAY (IAM blocks). Each step is reversible. Each step produces more evidence.&lt;/p&gt;




&lt;h2&gt;
  
  
  Python
&lt;/h2&gt;

&lt;p&gt;The same flow works with the Python SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;gate-sdk boto3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





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

&lt;span class="n"&gt;gate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GateClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://gate-hotpath.blockintelai.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tenant_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GATE_TENANT_ID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GATE_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;production&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;signer_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;treasury-signer-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;decision&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;request_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
    &lt;span class="n"&gt;to_address&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0x742d35Cc6634c0532925a3b844Bc9e7595916DA2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1000000000000000000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;value_usd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ethereum&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;network_family&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EVM&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;chain_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;from_address&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0xYourWalletAddress&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;signer_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;treasury-signer-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;actor_principal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ops@yourcompany.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Decision: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Receipt: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;receipt_signature&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note: the Python SDK's default timeout is 50ms (vs 15s in TypeScript) — it's optimized for hot-path latency. If you see circuit breaker trips, check your control plane p99 before increasing the timeout.&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Gate SDK (npm): &lt;code&gt;npm install blockintel-gate-sdk&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Gate SDK (PyPI): &lt;code&gt;pip install gate-sdk&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Documentation: &lt;a href="https://blockintelai.com" rel="noopener noreferrer"&gt;blockintelai.com/docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Console: &lt;a href="https://gate.blockintelai.com" rel="noopener noreferrer"&gt;gate.blockintelai.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Built by &lt;a href="https://blockintelai.com" rel="noopener noreferrer"&gt;BlockIntel&lt;/a&gt;. Gate is pre-signature policy enforcement for crypto custody — we stop unauthorized transactions before they're signed, not after.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>aws</category>
      <category>typescript</category>
      <category>security</category>
    </item>
  </channel>
</rss>
