Skip to content

Instantly share code, notes, and snippets.

@twinge
Last active July 2, 2026 17:16
Show Gist options
  • Select an option

  • Save twinge/bb4dd79bcad542ff4cca1a58d624f2b5 to your computer and use it in GitHub Desktop.

Select an option

Save twinge/bb4dd79bcad542ff4cca1a58d624f2b5 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>AI Auditability & Control: Design Brief</title>
<style>
:root{
color-scheme: light;
--bg:#f4f5f8;
--slide:#ffffff;
--ink:#16202c;
--muted:#5a6675;
--line:#dfe3ea;
--accent:#3b5bdb;
--accent-soft:#eaeefb;
--accent2:#0c8599;
--accent2-soft:#e3f4f6;
--good:#2b8a3e;
--good-soft:#e8f6ec;
--warn:#b08900;
--warn-soft:#fbf3da;
--bad:#c92a2a;
--bad-soft:#fbe9e9;
--chip:#eef1f6;
--radius:14px;
--maxw:1040px;
--font:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
--mono:"SF Mono",ui-monospace,"Cascadia Code",Menlo,Consolas,monospace;
}
*{box-sizing:border-box;margin:0;padding:0}
html,body{background:var(--bg);color:var(--ink);font-family:var(--font);-webkit-font-smoothing:antialiased}
body{display:flex;flex-direction:column;align-items:center;min-height:100vh;padding:18px 14px 86px}
.deck{width:100%;max-width:var(--maxw)}
.slide{
display:none;background:var(--slide);border:1px solid var(--line);
border-radius:var(--radius);padding:40px 46px;min-height:600px;
box-shadow:0 1px 3px rgba(20,32,44,.05),0 12px 32px rgba(20,32,44,.06);
animation:fade .25s ease;
}
.slide.active{display:block}
@keyframes fade{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
.kicker{font-size:12px;letter-spacing:.12em;text-transform:uppercase;color:var(--accent);font-weight:700;margin-bottom:14px}
h1{font-size:37px;line-height:1.12;letter-spacing:-.02em;margin-bottom:16px}
h2{font-size:27px;line-height:1.18;letter-spacing:-.01em;margin-bottom:18px}
h3{font-size:15px;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);margin:18px 0 8px}
p{font-size:16.5px;line-height:1.55;color:var(--ink);margin-bottom:12px}
p.lead{font-size:18.5px;color:var(--ink)}
.sub{color:var(--muted)}
.muted{color:var(--muted)}
ul{list-style:none}
li{font-size:16px;line-height:1.5;margin-bottom:11px;padding-left:24px;position:relative}
li::before{content:"";position:absolute;left:4px;top:9px;width:7px;height:7px;border-radius:2px;background:var(--accent)}
li.good::before{background:var(--good)}
li.bad::before{background:var(--bad)}
li.warn::before{background:var(--warn)}
li.no{padding-left:24px}
li.no::before{content:"✕";background:none;width:auto;height:auto;left:3px;top:1px;color:var(--muted);font-size:13px;font-weight:700}
li.yes::before{content:"✓";background:none;width:auto;height:auto;left:2px;top:1px;color:var(--good);font-size:14px;font-weight:700}
strong{font-weight:670}
.tag{display:inline-block;font-size:12px;font-weight:600;padding:3px 10px;border-radius:999px;background:var(--chip);color:var(--muted);margin-right:6px}
.tag.a{background:var(--accent-soft);color:var(--accent)}
.tag.b{background:var(--accent2-soft);color:var(--accent2)}
.tag.pick{background:var(--good-soft);color:var(--good)}
.two{display:grid;grid-template-columns:1fr 1fr;gap:26px;margin-top:6px}
.three{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;margin-top:6px}
.card{border:1px solid var(--line);border-radius:11px;padding:16px 18px;background:#fcfcfe}
.card.accent{border-color:#c7d2f7;background:var(--accent-soft)}
.card.a{border-left:4px solid var(--accent)}
.card.b{border-left:4px solid var(--accent2)}
.card.pick{border-left:4px solid var(--good);background:var(--good-soft)}
.card h4{font-size:16px;margin-bottom:8px}
.card p,.card li{font-size:14.5px}
.card li{margin-bottom:7px}
table{width:100%;border-collapse:collapse;margin-top:8px;font-size:14.5px}
th,td{text-align:left;padding:10px 12px;border-bottom:1px solid var(--line);vertical-align:top}
th{font-size:12px;text-transform:uppercase;letter-spacing:.05em;color:var(--muted);font-weight:700}
thead th:nth-child(2){color:var(--accent)}
thead th:nth-child(3){color:var(--accent2)}
td:first-child{font-weight:600;width:30%}
.pill{display:inline-block;font-size:12px;font-weight:600;padding:2px 9px;border-radius:6px}
.pill.g{background:var(--good-soft);color:var(--good)}
.pill.w{background:var(--warn-soft);color:var(--warn)}
.pill.r{background:var(--bad-soft);color:var(--bad)}
.pill.n{background:var(--chip);color:var(--muted)}
pre{background:#0f1722;color:#e6edf3;border-radius:11px;padding:18px 20px;font-family:var(--mono);
font-size:13.5px;line-height:1.55;overflow:auto;margin-top:6px}
pre .c{color:#7d8aa0}
pre .k{color:#79c0ff}
pre .s{color:#a5d6ff}
.flow{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin:14px 0}
.node{border:1.5px solid var(--line);border-radius:10px;padding:11px 14px;font-size:13.5px;font-weight:600;background:#fff;text-align:center;line-height:1.3}
.node.app{border-color:#c7d2f7;background:var(--accent-soft)}
.node.gw{border-color:#9bd4dd;background:var(--accent2-soft)}
.node.store{border-color:#cdd4df;background:#f4f6fa}
.node.pick{border-color:#a3d9b1;background:var(--good-soft)}
.node small{display:block;font-weight:500;color:var(--muted);font-size:11.5px;margin-top:2px}
.arrow{color:var(--muted);font-size:20px;font-weight:700}
.arrow small{display:block;font-size:10.5px;font-weight:600;color:var(--muted);text-align:center;margin-top:-2px}
.schema{display:grid;grid-template-columns:1fr 1fr;gap:6px 26px;margin-top:8px}
.field{display:flex;gap:10px;align-items:baseline;padding:7px 0;border-bottom:1px solid var(--line);font-size:14px}
.field code{font-family:var(--mono);font-size:13px;color:var(--accent);font-weight:600;white-space:nowrap}
.field span{color:var(--muted);font-size:13px}
.phase{display:flex;gap:14px;margin-bottom:13px}
.phase .n{flex:none;width:30px;height:30px;border-radius:8px;background:var(--accent);color:#fff;font-weight:700;display:flex;align-items:center;justify-content:center;font-size:14px}
.phase .n.alt{background:var(--accent2)}
.phase .n.opt{background:#9aa4b2}
.phase h4{font-size:16px;margin-bottom:3px}
.phase p{font-size:14.5px;margin-bottom:0}
.titlewrap{display:flex;flex-direction:column;justify-content:center;min-height:560px}
.audience{display:flex;gap:8px;flex-wrap:wrap;margin-top:22px}
.aud{font-size:13px;font-weight:600;color:var(--ink);background:var(--chip);padding:6px 13px;border-radius:8px}
.foot{margin-top:24px;font-size:13px;color:var(--muted);border-top:1px solid var(--line);padding-top:14px}
.callout{background:var(--accent-soft);border:1px solid #c7d2f7;border-radius:11px;padding:16px 18px;margin-top:14px}
.callout.pick{background:var(--good-soft);border-color:#a3d9b1}
.callout p{margin-bottom:0;font-size:15.5px}
.big{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin-top:10px}
.stat{border:1px solid var(--line);border-radius:11px;padding:16px;background:#fcfcfe}
.stat h4{font-size:15px;margin-bottom:5px;color:var(--accent)}
.stat p{font-size:13.5px;margin-bottom:0;color:var(--muted)}
.nav{position:fixed;bottom:0;left:0;right:0;background:rgba(255,255,255,.94);backdrop-filter:blur(8px);
border-top:1px solid var(--line);display:flex;align-items:center;justify-content:center;gap:16px;padding:11px;z-index:10}
.nav button{font:inherit;font-size:14px;font-weight:600;border:1px solid var(--line);background:#fff;color:var(--ink);
padding:7px 16px;border-radius:9px;cursor:pointer}
.nav button:hover{border-color:var(--accent);color:var(--accent)}
.nav button:disabled{opacity:.4;cursor:default}
.counter{font-size:13.5px;color:var(--muted);font-variant-numeric:tabular-nums;min-width:54px;text-align:center}
.progress{position:fixed;top:0;left:0;height:3px;background:var(--accent);z-index:11;transition:width .25s}
@media(max-width:720px){.two,.three,.big,.schema{grid-template-columns:1fr}.slide{padding:28px 22px}h1{font-size:29px}}
</style>
</head>
<body>
<div class="progress" id="progress"></div>
<div class="deck" id="deck">
<!-- 1 TITLE -->
<section class="slide active">
<div class="titlewrap">
<div class="kicker">AI Compliance Framework · Design Brief</div>
<h1>Auditability for every AI feature,<br>with control where it counts</h1>
<p class="lead sub">A low-friction <strong>SDK</strong> that gives every internally-built AI feature a complete audit trail,<br>and a high-control <strong>gateway</strong> alongside it for the use cases that need enforcement.<br>Both, sequenced in phases.</p>
<div class="audience">
<span class="aud">Architects</span>
<span class="aud">Application developers</span>
<span class="aud">Data privacy</span>
<span class="aud">Legal / counsel</span>
</div>
<div class="foot">Scope: AI features <em>we build</em>. Purchased AI tools are a separate ingestion problem, out of scope here.</div>
</div>
</section>
<!-- 2 OUTCOME -->
<section class="slide">
<div class="kicker">The outcome we're solving for</div>
<h2>Every internally-developed AI feature should send audit-grade records to one central, immutable place.</h2>
<div class="two">
<div class="card">
<h4>Where we are today</h4>
<ul>
<li>Each tool calls model providers (OpenAI, Anthropic) directly, with no shared path.</li>
<li>Logging, where it exists, is per-team, inconsistent, and not built as evidence.</li>
<li>No way to answer "show me everything this model did" with confidence.</li>
</ul>
</div>
<div class="card accent">
<h4>Where we need to be</h4>
<ul>
<li>A <strong>complete</strong> record of what every AI feature did: inputs, outputs, model, actor, decisions.</li>
<li><strong>Immutable</strong> and tamper-evident, retained to a defined legal schedule.</li>
<li>And for some features, the ability to <strong>actively control</strong> what the model does, not just record it.</li>
</ul>
</div>
</div>
<div class="callout"><p>Two distinct needs sit inside that goal: <strong>auditability</strong> (know what happened) and <strong>control</strong> (govern what's allowed). They call for two different tools.</p></div>
</section>
<!-- 4 TWO NEEDS TWO TOOLS -->
<section class="slide">
<div class="kicker">The approach</div>
<h2>Two needs → two tools, and we're building both</h2>
<div class="two" style="margin-top:8px">
<div class="card pick">
<span class="tag pick">Start here</span>
<h4 style="margin-top:10px">The SDK: auditability, low friction</h4>
<p>Know exactly what every AI feature sent and received, in one immutable store. A few lines per feature, no latency, nothing new to operate.</p>
<p class="muted" style="font-size:13.5px;margin-bottom:0">Broad coverage, fast.</p>
</div>
<div class="card b">
<span class="tag b">Building alongside</span>
<h4 style="margin-top:10px">The gateway: control &amp; enforcement</h4>
<p>Govern which models may be used, block dangerous prompts/responses in-line, and capture with the strongest guarantee. For the use cases that need it.</p>
<p class="muted" style="font-size:13.5px;margin-bottom:0">Selective, high-assurance.</p>
</div>
</div>
<div class="callout"><p><strong>Both/and, not either/or.</strong> The SDK gives every feature auditability quickly; the gateway adds active control where the use case demands it. Both emit the same record to the same store.</p></div>
</section>
<!-- 5 SDK (merged: what + how) -->
<section class="slide">
<span class="tag pick">The SDK</span>
<h2 style="margin-top:10px">The low-friction path: capture the call, ship it out-of-band</h2>
<p>A thin client your application embeds. It captures each model call, both the request and the model's response, and ships an audit event out-of-band. Your app keeps calling the provider directly. The SDK <strong>logs the call, it doesn't proxy it.</strong></p>
<div class="flow">
<div class="node app">App + SDK<small>captures request + response</small></div>
<span class="arrow">→<small>direct</small></span>
<div class="node">Providers<small>no latency</small></div>
</div>
<div class="flow">
<div class="node app" style="visibility:hidden">App + SDK</div>
<span class="arrow">↘<small>async</small></span>
<div class="node pick">Co-located agent</div>
<span class="arrow">→</span>
<div class="node gw">HA logging service<small>parse</small></div>
<span class="arrow">→</span>
<div class="node store">Immutable store</div>
</div>
<ul style="margin-top:6px">
<li><strong>Capture point 1, the model call:</strong> the SDK records the request and the model's response, then returns the response to your app untouched.</li>
<li><strong>Capture point 2, the served output:</strong> when your app transforms the output before showing it (a safety gate, a rewrite), it records what the user actually saw. No post-processing? The model's response is the served output automatically.</li>
<li>All async and non-blocking: the model call reaches the provider <strong>directly</strong>, with no proxy hop or added latency, and a logging hiccup never breaks the feature.</li>
<li>The agent and logging service own <strong>delivery and parsing</strong>. The app does none of it.</li>
</ul>
<p class="muted" style="font-size:13px;margin-top:6px">Streaming responses are the one case needing per-provider handling: the SDK reassembles the stream to record the full completion.</p>
<div class="callout pick"><p>Adoption is <strong>a dependency and a few lines</strong>, not a migration and not new infrastructure for your team to run.</p></div>
</section>
<!-- 6 SDK VALUE -->
<section class="slide">
<span class="tag pick">The SDK · value</span>
<h2 style="margin-top:10px">What you get</h2>
<div class="two">
<div class="card">
<ul>
<li class="good"><strong>Complete, central audit record</strong>: every prompt, response, model, actor, and outcome in one immutable place.</li>
<li class="good"><strong>Zero added latency</strong>: capture is asynchronous; your model call is untouched.</li>
<li class="good"><strong>Nothing new to operate</strong>: a shared agent and logging service handle delivery and parsing. Not your team's problem.</li>
</ul>
</div>
<div class="card">
<ul>
<li class="good"><strong>No provider treadmill</strong>: calls are recorded as-is, without parsing the provider's parameters, so new model features never wait on the SDK.</li>
<li class="good"><strong>Business context included</strong>: you attach the feature, actor, and outcome the raw wire can't show.</li>
<li class="good"><strong>Consistent across the portfolio</strong>: every feature and provider produces the same record, so audit and reporting work the same way everywhere.</li>
</ul>
</div>
</div>
<div class="callout"><p>Net: every AI feature becomes <strong>auditable in days</strong>, with no latency cost and no operational burden on the building team.</p></div>
</section>
<!-- 8 DEVELOPER ADOPTION -->
<section class="slide">
<span class="tag pick">The SDK · what you actually do</span>
<h2 style="margin-top:10px">Adoption, from a developer's seat</h2>
<div class="two">
<div class="card pick">
<h4>You do, once per feature</h4>
<ul>
<li class="yes">Add the SDK dependency.</li>
<li class="yes">Wrap your model calls (a few lines), or enable auto-instrumentation for supported clients.</li>
<li class="yes">If you post-process the output before showing it, record the served output (one more call).</li>
<li class="yes">Pass a little context: feature ID, actor, outcome.</li>
<li class="yes">Register the feature and get your provider key through central issuance.</li>
</ul>
</div>
<div class="card">
<h4>You don't have to, ever</h4>
<ul>
<li class="no">Run or operate any logging infrastructure.</li>
<li class="no">Budget for latency or tune performance.</li>
<li class="no">Track provider API changes or parameters.</li>
</ul>
</div>
</div>
<div class="callout pick"><p>Total lift: <strong>a dependency, a wrapper, and a registry entry.</strong> Most features onboard in an afternoon.</p></div>
</section>
<!-- 9 API SKETCH -->
<section class="slide">
<span class="tag pick">The SDK · API sketch</span>
<h2 style="margin-top:10px">What the code looks like</h2>
<pre><span class="c"># Wrap the model call in a block. The SDK invokes it, and records</span>
<span class="c"># the request you pass plus the response the block returns.</span>
response = <span class="k">AiAudit</span>.capture(
feature: <span class="s">"support_summarizer"</span>,
actor: current_user.id,
request: request, <span class="c"># recorded as the audit input</span>
context: { ticket_id: 123, channel: <span class="s">"email"</span> },
outcome: <span class="s">"draft_suggested"</span>, <span class="c"># business meaning, app-supplied</span>
) { provider.chat(request, model: <span class="s">"claude-sonnet-4-6"</span>) }
<span class="c"># capture yields the block directly (no proxy, no added latency),</span>
<span class="c"># records the request and the returned response, returns the response</span>
<span class="c"># to you, and fires the audit event async to the local agent.</span>
</pre>
<ul style="margin-top:10px">
<li><strong>Non-blocking:</strong> a logging hiccup never touches the model call.</li>
<li><strong>Stack-agnostic:</strong> a native wrapper where we have one, a thin HTTP contract everywhere else.</li>
<li><strong>Same record</strong> whether emitted here by the SDK or, for control use cases, by the gateway.</li>
</ul>
</section>
<!-- 10b SAMPLE SCHEMA (illustrative) -->
<section class="slide">
<div class="kicker">Illustrative only · not the final schema</div>
<h2>Roughly, what one captured record looks like</h2>
<p class="muted">A sketch to make the idea concrete. The real field set, what is stored versus hashed, and retention all get defined and ratified later with legal.</p>
<pre><span class="c">// illustrative example, names and shape are not final</span>
{
<span class="k">"event_id"</span>: <span class="s">"evt_9f2c4a…"</span>,
<span class="k">"occurred_at"</span>: <span class="s">"2026-06-30T14:22:08Z"</span>,
<span class="k">"feature"</span>: <span class="s">"support_summarizer"</span>,
<span class="k">"actor"</span>: <span class="s">"user_4821"</span>,
<span class="k">"model"</span>: <span class="s">"claude-sonnet-4-6"</span>,
<span class="k">"input"</span>: <span class="s">"Summarize support ticket #123…"</span>,
<span class="k">"model_output"</span>: <span class="s">"Draft reply suggested to agent…"</span>, <span class="c">// raw completion</span>
<span class="k">"served_output"</span>: <span class="s">"Draft reply, after safety gate…"</span>, <span class="c">// what the user saw</span>
<span class="k">"tokens"</span>: { <span class="k">"in"</span>: 1840, <span class="k">"out"</span>: 220 },
<span class="k">"context"</span>: { <span class="k">"ticket_id"</span>: 123, <span class="k">"channel"</span>: <span class="s">"email"</span> },
<span class="k">"outcome"</span>: <span class="s">"draft_suggested"</span>
}
</pre>
<div class="callout"><p>The point is only that every feature emits a record in one shared shape. The exact fields are a later decision, not something we are approving here.</p></div>
</section>
<!-- 11 GATEWAY -->
<section class="slide">
<span class="tag b">The gateway · we're building this too</span>
<h2 style="margin-top:10px">The high-control path</h2>
<p>The gateway routes model calls through a central service. Reach for it when a use case needs more than visibility, when you need to <strong>actively govern</strong> what the model does:</p>
<div class="three">
<div class="stat"><h4>Model selection control</h4><p>Enforce which models may be used, centrally, with no unapproved models in scope.</p></div>
<div class="stat"><h4>Block dangerous content</h4><p>Inspect and stop unsafe prompts or responses in-line, before they land.</p></div>
<div class="stat"><h4>Strongest auditability</h4><p>Capture is structural and can fail closed: no call proceeds unrecorded.</p></div>
</div>
<div class="flow" style="margin-top:14px">
<div class="node app">AI Feature</div>
<span class="arrow">→</span>
<div class="node gw">Gateway<small>govern · block · record</small></div>
<span class="arrow">→</span>
<div class="node">Providers</div>
</div>
<div class="callout"><p>Because safety runs <strong>in-line</strong>, the response the gateway returns is exactly what the app serves, so no separate served-output capture is needed. The trade: it's in the call path and must be highly available, so we apply it where the control is worth it.</p></div>
</section>
<!-- 12 CHOOSING THE LANE -->
<section class="slide">
<div class="kicker">Choosing the lane</div>
<h2>Which path for a given feature?</h2>
<table>
<thead><tr><th>&nbsp;</th><th>SDK: start here</th><th>Gateway: when needed</th></tr></thead>
<tbody>
<tr><td>Primary need</td><td>Auditability / visibility</td><td>Control + enforcement</td></tr>
<tr><td>In the model call path?</td><td><span class="pill g">No</span></td><td>Yes</td></tr>
<tr><td>Latency</td><td><span class="pill g">None</span></td><td>Adds a hop</td></tr>
<tr><td>Enforce model selection</td><td><span class="pill n">n/a</span></td><td><span class="pill g">Yes</span></td></tr>
<tr><td>Block prompts / responses</td><td><span class="pill n">n/a</span></td><td><span class="pill g">Yes</span></td></tr>
<tr><td>Audit completeness</td><td>Verified (registry + code scan)</td><td><span class="pill g">Structural / fail-closed</span></td></tr>
<tr><td>Captures the served output</td><td>App records it when it post-processes</td><td><span class="pill g">In-line, automatic</span></td></tr>
<tr><td>Adoption effort</td><td><span class="pill g">Low (days)</span></td><td>Higher (route &amp; govern)</td></tr>
<tr><td>Default for</td><td>Most AI features</td><td>High-risk · regulated · content-sensitive</td></tr>
</tbody>
</table>
<div class="callout"><p>Same audit schema and store either way. Choosing a lane is about <strong>how much control</strong> a use case needs, never about whether it's audited.</p></div>
</section>
<!-- 13 COVERAGE -->
<section class="slide">
<div class="kicker">Assurance · the SDK path</div>
<h2>How we know SDK-path features are actually logged</h2>
<p class="muted">We don't enforce this at the network layer. Two controls that are realistic for us:</p>
<div class="two" style="margin-top:6px">
<div class="card a">
<h4>1 · Register the feature</h4>
<ul>
<li>Every internally-built AI feature must <strong>register with the central logging system</strong> before it ships.</li>
<li>The registry is the roster of what should be reporting, and the denominator for coverage.</li>
</ul>
</div>
<div class="card b">
<h4>2 · Periodic code scan</h4>
<ul>
<li>On a schedule, scan repositories to confirm <strong>no model calls are made outside the SDK wrapper</strong>.</li>
<li>Any direct provider call is flagged and fixed; a registered feature with no wrapper usage stands out.</li>
</ul>
</div>
</div>
<div class="callout"><p>Gateway-routed features <strong>still register</strong>, so their records carry feature context; what they skip is the code scan, since routing through the gateway makes capture structural.</p></div>
</section>
<!-- 15 ROLLOUT -->
<section class="slide">
<div class="kicker">Rollout · both/and, phased</div>
<h2>SDK first to prove it out, with the gateway in parallel or right after</h2>
<div class="phase"><div class="n">0</div><div><h4>Foundation</h4><p>Ratify the audit event schema. Stand up the shared agent + logging service + immutable store. Build the SDK. Stand up the feature registry and central key issuance.</p></div></div>
<div class="phase"><div class="n">1</div><div><h4>Prove the SDK</h4><p>Onboard AI features to the SDK; turn on the coverage controls. Broad, low-friction auditability across the portfolio, fast.</p></div></div>
<div class="phase"><div class="n alt">2</div><div><h4>Gateway for control use cases</h4><p>Build the gateway for features that need model governance, content blocking, or fail-closed capture. <strong>Can run in parallel with Phase 1</strong>, or follow once the SDK is proven. A resourcing call, not an architectural one.</p></div></div>
<div class="callout"><p>The shared schema makes either order safe: nothing built on the SDK has to be unwound when the gateway arrives.</p></div>
</section>
<!-- 16 DECISIONS -->
<section class="slide">
<div class="kicker">What we need to settle</div>
<h2>Open decisions &amp; owners</h2>
<table>
<thead><tr><th>Decision</th><th colspan="2">Primary owners</th></tr></thead>
<tbody>
<tr><td>Which use cases require the gateway's control (model governance / content blocking / fail-closed)</td><td colspan="2">Legal + feature owners</td></tr>
<tr><td>Schema ratification: stored-raw vs. hashed, per field</td><td colspan="2">Legal + Architecture</td></tr>
<tr><td>Ownership: SDK, agent/logger, store, registry, key issuance, gateway</td><td colspan="2">Engineering leadership</td></tr>
</tbody>
</table>
</section>
</div>
<div class="nav">
<button id="prev">‹ Prev</button>
<span class="counter" id="counter">1 / 16</span>
<button id="next">Next ›</button>
</div>
<script>
const slides=[...document.querySelectorAll('.slide')];
let i=0;
const counter=document.getElementById('counter');
const prog=document.getElementById('progress');
const prev=document.getElementById('prev');
const next=document.getElementById('next');
function show(n){
i=Math.max(0,Math.min(slides.length-1,n));
slides.forEach((s,k)=>s.classList.toggle('active',k===i));
counter.textContent=(i+1)+' / '+slides.length;
prog.style.width=((i+1)/slides.length*100)+'%';
prev.disabled=i===0;next.disabled=i===slides.length-1;
window.scrollTo({top:0,behavior:'smooth'});
}
prev.onclick=()=>show(i-1);
next.onclick=()=>show(i+1);
document.addEventListener('keydown',e=>{
if(e.key==='ArrowRight'||e.key==='PageDown'){show(i+1);}
if(e.key==='ArrowLeft'||e.key==='PageUp'){show(i-1);}
if(e.key==='Home'){show(0);}
if(e.key==='End'){show(slides.length-1);}
});
show(0);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment