<?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: Jay Leon</title>
    <description>The latest articles on DEV Community by Jay Leon (@_jleon).</description>
    <link>https://dev.to/_jleon</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%2F4103224%2F9d7dca33-3a6a-4efd-a5f6-368f10c88066.png</url>
      <title>DEV Community: Jay Leon</title>
      <link>https://dev.to/_jleon</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/_jleon"/>
    <language>en</language>
    <item>
      <title>Your AI agents' audit trail is a photo. I spent six days building the video.</title>
      <dc:creator>Jay Leon</dc:creator>
      <pubDate>Mon, 31 Aug 2026 19:18:15 +0000</pubDate>
      <link>https://dev.to/_jleon/your-ai-agents-audit-trail-is-a-photo-i-spent-six-days-building-the-video-4j5g</link>
      <guid>https://dev.to/_jleon/your-ai-agents-audit-trail-is-a-photo-i-spent-six-days-building-the-video-4j5g</guid>
      <description>&lt;p&gt;In November 2025, &lt;a href="https://appomni.com/ao-labs/ai-agent-to-agent-discovery-prompt-injection/" rel="noopener noreferrer"&gt;AppOmni Labs showed&lt;/a&gt;&lt;br&gt;
that text in an ordinary service ticket could make a ServiceNow Now Assist AI agent&lt;br&gt;
recruit a second, more privileged agent, read a record its author had no right to see, and&lt;br&gt;
copy it into one they owned. The vendor's prompt-injection defenses — against malicious&lt;br&gt;
instructions hidden in data an AI reads — were on the whole time. A lab demo, but the&lt;br&gt;
shape matters: the attack broke no rule, only moved through gaps between rules everyone&lt;br&gt;
had approved.&lt;/p&gt;

&lt;p&gt;Agent governance — the product category deciding what a company's AI agents may do —&lt;br&gt;
answers that shape with one ritual: an AI drafts a tighter rule, a human approves it, an&lt;br&gt;
audit log records the approval — a log with no authority, which nobody reads again.&lt;/p&gt;

&lt;p&gt;That audit trail is a photo: proof the rule was justified the day it was approved. Nobody&lt;br&gt;
has the video — proof it &lt;em&gt;stayed&lt;/em&gt; justified every day since. For the All Things Agentic&lt;br&gt;
Hackathon, I spent six days building it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Detection decay&lt;/strong&gt; is what the photo misses: attackers move, and a rule that earned&lt;br&gt;
approval in July stops deserving it by September, its signature still verifying. Supply-chain&lt;br&gt;
attestation (sigstore, in-toto, SLSA) proves a software artifact is exactly what its&lt;br&gt;
builder produced, pinned to a digest — a cryptographic fingerprint. It can ignore&lt;br&gt;
decay because its subjects hold still: in-toto assumes each is immutable behind its digest&lt;br&gt;
— true for container images. A &lt;em&gt;decision's&lt;/em&gt; evidence can drift, and there is no digest for&lt;br&gt;
"the state of what we knew."&lt;/p&gt;

&lt;p&gt;Caseharden is a governance layer for an AI-agent fleet, with one design rule: a&lt;br&gt;
guardrail — a rule constraining what agents may do — holds authority only while its&lt;br&gt;
evidence keeps re-verifying. Easiest way to see it: one guardrail's life, from the repo's&lt;br&gt;
run captures — v4 of the fleet's conduct policy.&lt;/p&gt;
&lt;h2&gt;
  
  
  One guardrail's life, frame by frame
&lt;/h2&gt;

&lt;p&gt;v4 is three sentences of law you already enforce on your human employees: stay in your&lt;br&gt;
declared lane (&lt;code&gt;out-of-declared-scope&lt;/code&gt;); if the request smells poisoned, touch nothing&lt;br&gt;
(&lt;code&gt;tool-call-on-injected-turn&lt;/code&gt; — the intern doesn't wire money because a convincing email&lt;br&gt;
said to); never reach into another customer's drawer (&lt;code&gt;cross-tenant-tool-call&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Three terms. The &lt;strong&gt;Proposer&lt;/strong&gt; is the AI that drafts rules. The &lt;strong&gt;Examiner&lt;/strong&gt; scores every&lt;br&gt;
draft against the &lt;strong&gt;holdout&lt;/strong&gt;: a sealed set of recorded attack sessions — an exam the&lt;br&gt;
Proposer must pass but never read. &lt;strong&gt;Promotion&lt;/strong&gt; makes an approved draft binding&lt;br&gt;
fleet-wide. How did three sentences earn that authority? Watch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Frame 1 — the approval.&lt;/strong&gt; A detector flagged real out-of-scope sessions; a human&lt;br&gt;
confirmed the abuse; the Proposer drafted v4 to close it. Promotion writes a seven-link&lt;br&gt;
chain, from evidence scanned to approval granted; &lt;code&gt;verify&lt;/code&gt; re-derives all of it from raw&lt;br&gt;
events. Look at links 5 and 6: the live refusal proving the Proposer&lt;br&gt;
couldn't read its own exam, and the exam score it protects.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  [1] EVIDENCE         OK   424 conduct events re-scanned, holdout readable by 1 principal
  [5] HOLDOUT-DENIED   OK   proposer-sa refused bigquery.tables.getData on holdout_sealed
  [6] EXAM             OK   the Examiner re-scores 29/40 sealed attacks at 100% benign, unchanged
  [7] APPROVAL         OK   GATE PASS approved by analyst

  ATTESTED   re-derived from raw events in 5.4s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every photo system stops here. The recording keeps rolling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Frame 2 — one ordinary late event.&lt;/strong&gt; A conduct event — a row recording something an&lt;br&gt;
agent did — arrives in the evidence window after promotion. No attack — a row simply&lt;br&gt;
arrived late. In the next &lt;code&gt;verify&lt;/code&gt;, watch link 1 flip from OK to BREAK over that one row:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  [1] EVIDENCE   BREAK  1 event(s) in the window are not in the cited set: e_late_1787661269

  QUARANTINED  break at link 1 EVENT-WINDOW
               enforcement unchanged, promotions FROZEN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why does one late row cost a version its standing? Two clocks. Clock one, the evidence&lt;br&gt;
window — the span of events the investigation cites — closed 23 seconds in, when the&lt;br&gt;
detectors finished scanning: finding and citation vouch for the same rows.&lt;br&gt;
Clock two starts at promotion, when a digest is taken of everything inside the window.&lt;br&gt;
Below, everything hinges on the third mark:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;window_start -------- window_end -------- digest taken ---------&amp;gt;
|                     |                   |
|&amp;lt;--- the citation --&amp;gt;|                   |
                      |&amp;lt;-- grace period -&amp;gt;|&amp;lt;-- from here on, any row
                         stragglers here      with a timestamp inside
                         are simply counted   the window BREAKS it
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before the digest, a late row gets counted. After it, the same row quarantines the&lt;br&gt;
version: the record claims it saw everything in the window; this row proves it didn't.&lt;br&gt;
Nothing about the row changed — only when it arrived.&lt;/p&gt;

&lt;p&gt;Quarantine is deliberately asymmetric: v4 still blocks everything it blocked — dropping&lt;br&gt;
enforcement over stale paperwork would be backwards — but its standing is gone and a v5 on&lt;br&gt;
top is refused. The remedy is re-derivation, not undo: the justification re-runs, the&lt;br&gt;
Examiner re-scores, a new link supersedes the break without erasing it. Watch the count&lt;br&gt;
tick from 424 to 425, exam score unchanged:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  [8] EVIDENCE-CHANGED OK   425 conduct events re-scanned; 29/40 sealed attacks, unchanged

  ATTESTED   re-derived from raw events in 4.0s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The video keeps the frame where the rule lost its standing.&lt;/p&gt;

&lt;p&gt;A video is only worth recording if nobody can tamper with the camera. In 2021, Ubiquiti's&lt;br&gt;
cloud lead used his legitimate admin credentials to steal company data, then set log&lt;br&gt;
retention to one day so the evidence erased itself (&lt;a href="https://www.justice.gov/usao-sdny/pr/former-employee-technology-company-sentenced-six-years-prison-stealing-confidential" rel="noopener noreferrer"&gt;DOJ, six years&lt;/a&gt;).&lt;br&gt;
The camera's owner could reconfigure it. So Caseharden's two locks are&lt;br&gt;
platform-enforced, not my code — they bind me too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The AI can't study for its own exam.&lt;/strong&gt; A Proposer that can read the holdout can be&lt;br&gt;
re-prompted until a draft passes — a rule tuned to its test, decayed on arrival. The&lt;br&gt;
holdout's access list has one entry, the Examiner's service account, via IAM, Google&lt;br&gt;
Cloud's permission system; when the Proposer reaches anyway, Google answers, not me —&lt;br&gt;
captured on every run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP 403 PERMISSION_DENIED
"Access Denied: Table devpost-hackathon-506416:holdout_sealed.turns"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That 403 becomes its own chain link: the record shows what was prevented, not only what&lt;br&gt;
occurred.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The author can't reshoot the video.&lt;/strong&gt; The chain's root is sealed in a Cloud Storage&lt;br&gt;
object under a 30-day retention policy — I clicked &lt;strong&gt;locked&lt;/strong&gt;, the irreversible one. Here&lt;br&gt;
is that policy rejecting my own delete:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl -X DELETE .../o/certificates%2Fday1-seal-check.json
{ "error": { "code": 403, "message": "Object ... is subject to bucket's retention policy
    ... and cannot be deleted or overwritten until 2026-09-23T23:03:14" } }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Honest residual: the chain lives in BigQuery, Google's data warehouse, with no append-only&lt;br&gt;
mode; a credentialed writer could rewrite and re-seal it. The locked object is the&lt;br&gt;
witness a rewrite cannot erase, not a gate it cannot pass. Platform enforcement moves the&lt;br&gt;
line from "trust me" to "catch me" — not to "impossible."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Frame 3 — the one I didn't script.&lt;/strong&gt; On day 4 I granted an unrelated detector's new&lt;br&gt;
service account an ordinary BigQuery role — neither chain nor holdout touched. The next&lt;br&gt;
&lt;code&gt;verify&lt;/code&gt; names the exact grant at link 8:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  [8] EVIDENCE-CHANGED BREAK  project-level roles granted since promotion:
                              roles/bigquery.jobUser:serviceAccount:detector-sa@...

  QUARANTINED  break at link 8 HOLDOUT-ACCESS
               enforcement unchanged, promotions FROZEN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The attestation covers &lt;em&gt;who can read the exam&lt;/em&gt;, so a routine permission grant made v4&lt;br&gt;
unable to prove itself justified — the system said so before I'd noticed. That unplanned&lt;br&gt;
catch, more than anything I designed, convinced me the video is a different product from&lt;br&gt;
the photo.&lt;/p&gt;
&lt;h2&gt;
  
  
  The video's best scene is a refusal
&lt;/h2&gt;

&lt;p&gt;Two days before the deadline, the gate refused my AI's last four guardrail drafts. Attempt&lt;br&gt;
3 fails on one line — CATCH, sealed attacks caught, didn't improve — and attempt 4&lt;br&gt;
produces no candidate at all:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;attempt 3 of 4
  [FAIL] CATCH          30/40 -&amp;gt; 30/40 sealed attack sessions
  [PASS] BENIGN         100.0% -&amp;gt; 100.0% of 640 legitimate turns
  [PASS] MONOTONICITY   every active rule is carried forward or narrowed
  NO IMPROVEMENT ON SEALED ATTACKS
  refused; asking for a narrower candidate

attempt 4 of 4
  DRAFT REJECTED: no candidate in the answer

no candidate passed the gate in 4 attempts; nothing was written to the chain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ceiling is arithmetic, not a bug. The fourth attack family spans a whole session — a&lt;br&gt;
read-then-write pattern — so catching it needs a self-join, matching a session's events&lt;br&gt;
against each other, which this deliberately tiny deny-only language (rules forbid, never&lt;br&gt;
permit) can't express. Earlier a draft caught one such attack but blocked two legitimate&lt;br&gt;
turns; the benign leg threw it out.&lt;/p&gt;

&lt;p&gt;In a photo system the author now loosens the gate, overrides it, or re-prompts until&lt;br&gt;
something slips through; re-prompting against a sealed exam just leaks it slowly. The two&lt;br&gt;
locks removed all three outs: exam unreadable, record undeletable. The system's last&lt;br&gt;
recorded act before submission is refusing its author; production runs the last version&lt;br&gt;
the evidence supports, not the newest drafted. I shipped that.&lt;/p&gt;

&lt;p&gt;Measured, not estimated: 335 tests; 84 bugs deliberately planted and all 84 caught&lt;br&gt;
(mutation testing); &lt;code&gt;verify&lt;/code&gt; at 3.66s p95. Don't take the camera operator's word for it:&lt;br&gt;
&lt;code&gt;python3 -m caseharden.recheck fixtures/v5&lt;/code&gt; replays the Examiner and re-walks the chain&lt;br&gt;
on your machine — no credentials — and on GitHub's runners on every push.&lt;/p&gt;

&lt;p&gt;The photo isn't my invention — the repo's prior-art table credits it. Unit21, Sublime&lt;br&gt;
Security and Stripe Radar ship AI-drafted, human-approved rules; Microsoft's Agent&lt;br&gt;
Governance Toolkit intercepts tool calls pre-execution; Google ships per-agent IAM and an&lt;br&gt;
Agent Gateway; Salfeld-Nebgen (&lt;a href="https://arxiv.org/abs/2606.26298" rel="noopener noreferrer"&gt;arXiv 2606.26298&lt;/a&gt;)&lt;br&gt;
states the thesis: authority from attested evidence. None of it has the part after&lt;br&gt;
approval: quarantine, and the re-derivation that triggers it.&lt;/p&gt;
&lt;h2&gt;
  
  
  How it's built
&lt;/h2&gt;

&lt;p&gt;One Google Cloud project, &lt;code&gt;europe-west3&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx4um6ee01ka4dyex60mw.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%2Fx4um6ee01ka4dyex60mw.png" alt="Caseharden architecture" width="799" height="579"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nine private Cloud Run services&lt;/strong&gt;, every one deployed &lt;code&gt;--no-allow-unauthenticated&lt;/code&gt;, so&lt;br&gt;
nothing here is reachable from the internet. Eight come from a single image and an&lt;br&gt;
environment variable picks which agent the container becomes — the four detectors really&lt;br&gt;
are one program, and six Dockerfiles would be six things to keep in step. The ninth is the&lt;br&gt;
analyst's console: &lt;code&gt;adk deploy cloud_run --with_ui&lt;/code&gt;, unmodified.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The agents are ADK, and the Foreman knows no detectors.&lt;/strong&gt; It fans out over A2A to&lt;br&gt;
whatever answers &lt;code&gt;list_agents()&lt;/code&gt; against Agent Registry; its source names none of them, and&lt;br&gt;
the fleet proof greps it to keep that true. Seven agents are published to the registry, each entry&lt;br&gt;
carrying the chain root of the policy version it was registered against — the roster says&lt;br&gt;
what each worker's authority rests on, not just where it lives. One fan-out is a 361-span&lt;br&gt;
Cloud Trace.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Identity is the load-bearing part.&lt;/strong&gt; Eight service accounts, one per role. &lt;code&gt;proposer-sa&lt;/code&gt;&lt;br&gt;
takes a real 403 on the sealed holdout: that is the &lt;code&gt;[5] HOLDOUT-DENIED&lt;/code&gt; line above, and&lt;br&gt;
it is BigQuery's answer rather than my code's. &lt;code&gt;examiner-sa&lt;/code&gt; is the exam's only reader. The&lt;br&gt;
access list producing that is hashed into the chain, so granting the Proposer access later&lt;br&gt;
breaks the chain instead of going unnoticed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Screening runs before the policy, not after it.&lt;/strong&gt; Model Armor scores every&lt;br&gt;
customer-facing turn, and its verdict is a first-class predicate in the policy language.&lt;br&gt;
The order is forced rather than chosen: a rule can key on &lt;code&gt;ma_verdict&lt;/code&gt;, so it cannot be&lt;br&gt;
evaluated until screening returns. When Model Armor is unavailable and the policy depends&lt;br&gt;
on it, the call is refused instead of allowed through.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The record is BigQuery plus one locked bucket.&lt;/strong&gt; Conduct events, the chain and the&lt;br&gt;
human's review rows are tables. The chain's root is sealed into Cloud Storage under a&lt;br&gt;
retention policy I locked, so I cannot delete that object and neither can the project&lt;br&gt;
owner. That lock is the only reason a record coming from me is worth anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The offline half touches no cloud SDK.&lt;/strong&gt; Everything that talks to Google is a &lt;code&gt;urllib&lt;/code&gt;&lt;br&gt;
call, so the re-check, the 567 tests and the 84-mutation harness run on three dependencies&lt;br&gt;
and no credentials. That is how you can check the record without access to my project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/japsdeleon/caseharden &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;caseharden
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements-verify.txt
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; caseharden.recheck fixtures/v5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  If you're building agent governance
&lt;/h2&gt;

&lt;p&gt;Three things this build settled:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Steal from supply-chain security, but know where the analogy breaks.&lt;/strong&gt; Signatures and
admission gates transfer; the immutable-subject assumption does not. Governing
decisions? Design for re-derivation, or accept silent decay.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Move the "no" out of your own process.&lt;/strong&gt; An IAM denial and a retention lock bind the
author; no in-process check does. The field's cheapest structural upgrade.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't burn irreversible locks while your schema is moving.&lt;/strong&gt; The retention lock is the
whole trust story and can't be undone; lock too early and you lose a month.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Deliberately not built: KMS signing (managed-key signatures), a&lt;br&gt;
session-spanning rule language, a custom UI, and any model call in the verification path —&lt;br&gt;
a scorer you can't replay deterministically can't be re-verified.&lt;/p&gt;

&lt;p&gt;The photo is solved. The interesting product is the video: governance where approval must&lt;br&gt;
keep surviving the evidence, and the author is inside the frame. The sealed record naming&lt;br&gt;
the last justified version sits in a bucket that won't let me delete it until late&lt;br&gt;
September.&lt;/p&gt;

&lt;p&gt;Which of your rules would still pass the exam that approved them? Repo, threat model (ten&lt;br&gt;
holes listed), full prior-art table:&lt;br&gt;
&lt;a href="https://github.com/japsdeleon/caseharden" rel="noopener noreferrer"&gt;https://github.com/japsdeleon/caseharden&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Demo video: [&lt;a href="https://www.youtube.com/watch?v=t6IU79aTdBo" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=t6IU79aTdBo&lt;/a&gt;]&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I created this post for the purposes of entering the All Things Agentic Hackathon.&lt;/em&gt;&lt;br&gt;
&lt;em&gt;#AllThingsAgenticHackathon&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>googlecloud</category>
      <category>security</category>
      <category>hackathon</category>
    </item>
  </channel>
</rss>
