<?xml version="1.0" encoding="UTF-8"?>
<rss  xmlns:atom="http://www.w3.org/2005/Atom" 
      xmlns:media="http://search.yahoo.com/mrss/" 
      xmlns:content="http://purl.org/rss/1.0/modules/content/" 
      xmlns:dc="http://purl.org/dc/elements/1.1/" 
      version="2.0">
<channel>
<title>Adam Ezzat</title>
<link>https://adamezzat1.github.io/blog/index.html</link>
<atom:link href="https://adamezzat1.github.io/blog/index.xml" rel="self" type="application/rss+xml"/>
<description>Systems engineer building CJC-Lang — a deterministic scientific computing language</description>
<generator>quarto-1.3.340</generator>
<lastBuildDate>Thu, 11 Jun 2026 07:00:00 GMT</lastBuildDate>
<item>
  <title>The MLP That Never Got Written: Teaching a Deterministic Compiler to See Heat</title>
  <dc:creator>Adam Ezzat</dc:creator>
  <link>https://adamezzat1.github.io/blog/posts/pinn-v2-deterministic-compiler-ml/index.html</link>
  <description><![CDATA[ 



<div class="reading-time">
🕑 ~24 min read
</div>
<div class="stats-row">
<div class="stat">
<span class="number">−0.05 → 0.96</span><span class="stat-label">held-out R², thermal prediction</span>
</div>
<div class="stat">
<span class="number">9×</span><span class="stat-label">error reduction vs v1, held-out</span>
</div>
<div class="stat">
<span class="number">22</span><span class="stat-label">numbers in the shipped model</span>
</div>
<div class="stat">
<span class="number">0</span><span class="stat-label">plans changed by the quantum layer</span>
</div>
</div>
<div class="callout callout-style-simple callout-tip no-icon callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-title-container flex-fill">
TL;DR
</div>
</div>
<div class="callout-body-container callout-body">
<p><a href="../../../blog/posts/introducing-cjc/index.html">CJC-Lang</a>’s compiler has an advisory stack: <strong>CANA</strong> (Compiler-Aware Neural Architecture) extracts deterministic features from MIR and ranks optimization passes; <strong>NSS</strong> (Neural Systems Simulator) models runtime pressures — thermal, memory, CPU — from instrumented execution traces; a physics-informed cost model (<strong>PINN</strong>) sits at their boundary, consuming NSS pressures to veto or down-weight passes that would overheat hot code. This week that stack got its first <em>trained</em> component, and four things happened that I think are worth writing down honestly:</p>
<ul>
<li><strong>The planned neural network died in the data-sanity pass.</strong> The handoff prescribed a 2-layer MLP. The mandated pre-training audit found the static feature set was <em>type-blind</em> — <code>estimated_flops</code> counted every expression, float or int alike — so recorded thermal pressure (which is FP-op <em>density</em>) was unpredictable from those inputs by any model class. Held-out R² of a linear probe: <strong>−0.05</strong>. You can’t out-architect missing information.</li>
<li><strong>One feature beat all the model capacity I was about to throw at the problem.</strong> A ~350-line conservative float-propagation analysis (<code>TypeMix</code>) gave the compiler a static FP-density signal that correlates <strong>+0.947</strong> with measured thermal labels. Linear regression then saturated the task: held-out R² <strong>0.952</strong>. The shipped model is 22 numbers — 7 coefficients, 7 means, 7 standard deviations, 1 intercept — trained by closed-form ridge OLS with Kahan accumulation, so even <em>training</em> is bit-reproducible. No SGD, no seeds to manage, no nondeterminism to apologize for.</li>
<li><strong>A shadow gate promoted it on evidence.</strong> Against measured per-program thermal labels, the v1 closed form scored held-out MAE 0.190 with correlation +0.18 (functionally blind — its predictions maxed at 0.0001). The trained head: MAE <strong>0.021</strong>, correlation <strong>+0.98</strong>. It now changes real ranking decisions on 14/134 corpus programs, withholding loop-unrolling and LICM on exactly the floating-point-dense functions v1 couldn’t see.</li>
<li><strong>The quantum-inspired layer measured at exactly zero — and training can’t fix it.</strong> The Ising-style energy re-ranker reorders recommendations <em>within</em> an already-decided pass set; it never adds or drops a pass. Measured consequence across 2,680 corpus rows: zero plan changes, zero score changes. That’s an architecture problem (a minimizer over a one-candidate space), and it defines part two: candidate-plan generation + a trained energy head as the <em>selector</em>.</li>
</ul>
<p>Everything below was measured on the branch; the corpus, the trainer, the weights, and every gate are committed and re-runnable.</p>
</div>
</div>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Prototype disclaimer
</div>
</div>
<div class="callout-body-container callout-body">
<p>CJC-Lang and its CANA/NSS stack are research-stage prototypes. The corpus is 134 small deterministic programs (arithmetic kernels, loop nests, a thermal-gradient family, a 95-program training set) × 20 ranker configurations = 2,680 rows — micro-benchmarks, not production workloads. “Energy” throughout means a <em>modeled, deterministic</em> score (executed statements + weighted FP ops + heap pages), not wall-clock or watts; hardware validation is explicitly future work. Single machine, single seed, every number from one reproducible campaign — read as honest exploratory engineering, not a benchmark.</p>
</div>
</div>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Where this sits in the series
</div>
</div>
<div class="callout-body-container callout-body">
<ul>
<li><a href="../../../blog/posts/deterministic-green-compute/index.html">Green by Construction: Thermal Bounds Without Losing a Bit</a> — the <em>runtime</em> half of the thermal story: policy profiles, race-to-idle scheduling, fused kernels, all under the same-bits guarantee.</li>
<li><a href="../../../blog/posts/neural-networks-in-cjc/index.html">Neural Networks in CJC-Lang</a> — the GradGraph autodiff arena this stack’s training infrastructure descends from.</li>
<li><strong>This post</strong> — the <em>compile-time</em> half: the first trained cost model inside the pass-ranking pipeline, and the measurement discipline that decided what (not) to train.</li>
</ul>
</div>
</div>
<section id="the-stack-in-one-diagram" class="level2">
<h2 class="anchored" data-anchor-id="the-stack-in-one-diagram">1. The stack, in one diagram</h2>
<p>CJC-Lang’s whole pitch is <em>same seed, same bits, every run</em>. That constraint shapes everything in this post, so here’s the compile-time advisory stack it has to survive in:</p>
<div class="cell">
<div class="cell-output-display">
<div>
<div>
<pre class="mermaid mermaid-js">flowchart TD
    SRC["source (.cjcl)"] --&gt; MIR["MIR"]
    MIR --&gt; FEAT["CANA features&lt;br/&gt;(CFG, memory proxies, reductions, TypeMix)"]
    FEAT --&gt; COST["cost-model stack&lt;br/&gt;LinearCostModel → PinnPhysicalCostModel"]
    TRACE["instrumented run&lt;br/&gt;(cjc-mir-exec trace events)"] --&gt; NSS["NSS recorded pressures&lt;br/&gt;thermal / memory / cpu"]
    NSS --&gt;|"PressurePredictor"| COST
    HEAD["trained thermal head&lt;br/&gt;(CPB0 bundle, 22 numbers)"] -.-&gt;|"with_thermal_head"| COST
    COST --&gt; RANK["PassRanker → per-function pass plan"]
    RANK --&gt; GATE["legality gate (final authority)"]
    GATE --&gt; OPT["optimized MIR → executor"]
</pre>
</div>
</div>
</div>
</div>
<p>Three names to keep straight:</p>
<ul>
<li><strong>CANA</strong> (Compiler-Aware Neural Architecture) is the <em>advisory</em> layer: it extracts deterministic per-function features from MIR and ranks which optimization passes are worth running where. Advisory means exactly that — a separate legality gate retains final authority, always.</li>
<li><strong>NSS</strong> (Neural Systems Simulator) models system pressures over time. In the compiler loop it consumes <em>real instrumented traces</em> from the MIR executor — per-iteration loop events, FP-op density, heap occupancy — and produces per-function pressure predictions in [0, 1].</li>
<li><strong>PINN</strong> is the physics-informed cost model at their boundary: <code>PinnPhysicalCostModel</code> wraps the base cost model and adjusts pass-benefit estimates using physical pressure estimates (thermal, memory traffic, bandwidth, locality), with a hard rule — it can <em>withhold</em> a recommendation, never force one. v1 shipped as a closed-form coefficient model. v2, this post, is the first <em>trained</em> component.</li>
</ul>
<p>There’s also a quantum-inspired piece — an Ising-style energy decomposition that scores plans, reusing the deterministic numerics discipline from CJC’s quantum-simulation crate. Hold that thought; it stars in the section about zeros.</p>
</section>
<section id="the-rule-that-ran-the-session" class="level2">
<h2 class="anchored" data-anchor-id="the-rule-that-ran-the-session">2. The rule that ran the session</h2>
<p>The handoff document for this session carried a warning at the top, earned over two prior sessions: <strong>verify every handoff claim against the code before building on it.</strong> Its predecessor documents had been wrong in seven separate implementation-blocking ways — types that never existed, prescribed file locations that created dependency cycles, an instrumentation path that under-counted straight-lined code 24× (caught only because an energy ratio came out physically impossible).</p>
<p>This session added two more catches to the list before lunch:</p>
<ol type="1">
<li>The handoff’s own header said the branch HEAD was one commit older than reality — the handoff predated its own final commit.</li>
<li>The corpus harness had a label bug: configs were matched with <code>ends_with("_rec")</code>, which silently missed the three threshold-sweep variants (<code>_t50</code>, <code>_c80</code>, <code>_c60</code>). Those rows <em>ranked</em> under real recorded pressures but <em>recorded</em> synthetic pressure labels — 402 of 1,474 rows carried wrong labels. One membership test and a 35-second deterministic regeneration fixed it, but training on the unaudited corpus would have quietly learned from contaminated labels.</li>
</ol>
<p>The same discipline produced the handoff’s best instruction, the one this whole post hangs on: <em>before writing any training code, run a data-sanity pass and find out whether the linear signal already saturates.</em> So that’s where the session started — and it’s where the plan died.</p>
</section>
<section id="what-the-sanity-pass-found" class="level2">
<h2 class="anchored" data-anchor-id="what-the-sanity-pass-found">3. What the sanity pass found</h2>
<p>The training corpus is one row per (program × ranker-configuration): 134 programs × 11 configurations, each row carrying static workload estimates, NSS pressure predictions, the chosen pass plan, both-executor parity verdicts, and a baseline-relative modeled energy score. The sanity tool (~700 lines of deterministic statistics: Kahan-accumulated moments, Pearson correlations, ridge OLS via normal equations with partial-pivot Gaussian elimination, a by-program FNV-hash train/test split so no program leaks across the boundary) reported four structural facts:</p>
<p><strong>Fact 1 — the energy signal was almost nonexistent.</strong> A config’s score is <code>energy(its plan) / energy(baseline plan)</code>, so any config whose plan ties baseline scores <em>exactly</em> 1.0. Measured: only <strong>39 of 1,474 rows</strong> diverged, taking 17 distinct values in [0.967, 1.009]. You cannot train a neural network on 39 informative samples, and worse —</p>
<p><strong>Fact 2 — linear already saturated what little energy signal existed.</strong> On the divergent rows, log-flops alone correlated +0.996 with score. The handoff’s own warning (“if the linear signal saturates, a 2-layer MLP may add nothing — know this BEFORE writing the loop”) was confirmed for the energy target before any model existed.</p>
<p><strong>Fact 3 — pressure labels are per-program, not per-row.</strong> The recorded predictor is built from one instrumented run of the unoptimized program, so all recorded-config rows of a program carry bitwise-identical pressure labels (verified bitwise, not assumed — that’s how the <code>ends_with</code> bug surfaced). Effective sample size for pressure prediction: <strong>134</strong>, not 804.</p>
<p><strong>Fact 4 — and the headline — recorded thermal was unpredictable from the existing features.</strong> Ridge OLS from the full workload-estimate feature set to measured thermal labels, by-program split:</p>
<table class="table">
<thead>
<tr class="header">
<th>target</th>
<th>R² (train)</th>
<th>R² (held-out)</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>recorded thermal</td>
<td>0.157</td>
<td><strong>−0.050</strong></td>
</tr>
<tr class="even">
<td>recorded cpu</td>
<td>0.172</td>
<td>0.170</td>
</tr>
<tr class="odd">
<td>recorded memory</td>
<td>0.075</td>
<td>0.030</td>
</tr>
</tbody>
</table>
<p>A held-out R² of −0.05 means the model is <em>worse than predicting the mean</em>. The reflex at this point is to reach for capacity — surely an MLP finds what linear missed. The sanity pass had one more table that killed that reflex.</p>
</section>
<section id="the-information-gap" class="level2">
<h2 class="anchored" data-anchor-id="the-information-gap">4. The information gap</h2>
<p>Recorded thermal pressure in this stack is <strong>FP-op density</strong>: the executor’s instrumentation counts every binary operation where at least one operand is a float — comparisons included — and <code>thermal_intensity = fp_ops / instructions</code> per trace window. Floating-point units burn more power per retired op than integer ALUs; density of FP work is the static-analysis-shaped proxy for heat.</p>
<p>Now look at what the features knew. The per-function feature record contained CFG metrics (block counts, loop depths, branch counts), memory proxies (allocation sites, copy-on-write sites, tensor ops, <em>total expression count</em>), and reduction classifications. The workload estimate fed to the cost model was:</p>
<pre><code>flops_estimate = expr_count × loop_amplification</code></pre>
<p>Every expression. Float or int. <strong>Nothing anywhere in the feature set distinguished a floating-point operation from an integer one.</strong> The label was FP density; the features were type-blind. The signal wasn’t noisy — it was <em>absent</em>. No model class recovers information that isn’t in its inputs, and an MLP trained on these features would have been an exercise in memorizing 103 training programs.</p>
<p>This reframes the problem completely: it’s not a modeling task, it’s a feature-engineering task. The fix is <code>TypeMix</code> — a conservative intra-function float-propagation walk over MIR:</p>
<ul>
<li><strong>Seed:</strong> parameters annotated <code>f64</code> (CJC-Lang requires parameter type annotations, which makes this tractable).</li>
<li><strong>Propagate:</strong> <code>FloatLit</code> is float; a variable is float once anything float is assigned to it; arithmetic (<code>+ − × ÷ % **</code>) produces float iff either operand is float; comparisons produce bool. Iterate to fixpoint — the lattice is monotone (bindings only ever <em>become</em> float), so loop-carried accumulators converge in two rounds.</li>
<li><strong>Count:</strong> every <code>Binary</code> node with ≥1 float operand, <em>any operator</em> — deliberately mirroring the runtime counter’s definition, comparisons included, so the static proxy and the measured label quantify the same phenomenon.</li>
<li><strong>Direction of error:</strong> calls are conservatively non-float, so the analysis under-counts. For a density signal that’s the safe direction — it can make a hot function look cooler, never invent heat that would withhold optimizations from cold code.</li>
</ul>
<p>Two implementation notes for fellow travelers. First, the walker had a bug my own test caught: the expression-children enumerator handled if-<em>bodies</em> but forgot the if-<em>condition</em>, so every <code>x &gt; 0.5</code> comparison silently vanished from the count — a one-arm fix, locked by a regression test. Second, adding the field changes the content-addressed <code>FeatureHash</code> for every program, which is the fingerprint doing its job: the features genuinely changed, and the profile-database schema version bumped 1 → 2 with it.</p>
<p>The derived signal is one ratio: <code>float_ops_estimate / flops_estimate</code> — static FP density, amplification-invariant by construction (numerator and denominator carry the same loop and pass amplification factors).</p>
</section>
<section id="one-feature-later" class="level2">
<h2 class="anchored" data-anchor-id="one-feature-later">5. One feature later</h2>
<p>Corpus regenerated (35 seconds, deterministic, parity 100%, row hashes double-run stable), sanity pass re-run:</p>
<table class="table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>metric</th>
<th>before TypeMix</th>
<th>after</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>corr(static FP density, recorded thermal)</td>
<td>— (no feature)</td>
<td><strong>+0.947</strong></td>
</tr>
<tr class="even">
<td>thermal OLS R², held-out, by-program split</td>
<td><strong>−0.050</strong></td>
<td><strong>+0.956</strong></td>
</tr>
</tbody>
</table>
<p>One feature. The planned MLP didn’t survive contact with that table: with n=134 programs and a linear model already explaining 95% of held-out variance, a neural network chasing the residual is overfit theater. The honest model class is linear, and the §2.2 decision — <em>“model class decided by re-run sanity numbers, not by fiat”</em> — wrote itself.</p>
<p>So the shipped PINN v2 is: <strong>the v1 closed form for every dimension except thermal, plus a trained linear thermal head.</strong> Seven features, all derivable from the same workload query the cost model already builds (log1p of flops / bytes-read / bytes-written / alloc-bytes / working-set / float-ops, plus the raw FP-density ratio), z-scored with training statistics, dotted with trained coefficients, clamped to [0, 1].</p>
<p>Twenty-two numbers.</p>
</section>
<section id="training-a-model-a-deterministic-compiler-can-live-with" class="level2">
<h2 class="anchored" data-anchor-id="training-a-model-a-deterministic-compiler-can-live-with">6. Training a model a deterministic compiler can live with</h2>
<p>This is the part with no precedent to crib from, because the constraint set is unusual: CJC-Lang’s compiler reports embed a content hash, and two compilations of the same program with the same model must produce byte-identical reports — across runs, machines, and time. A model that trains differently on each invocation, or loads approximately, breaks the language’s core promise. The choices that follow from that:</p>
<p><strong>Training is a pure function.</strong> The trainer fits the head by closed-form ridge regression — normal equations accumulated with Kahan compensation, solved by Gaussian elimination with deterministic partial pivoting (<code>f64::total_cmp</code> on absolute values), ridge λ = 10⁻⁸ for the collinearity the feature basis inevitably has. No SGD, no shuffling, no learning-rate schedule, no RNG anywhere. Same corpus in, same 22 numbers out, to the bit. (The GradGraph + Adam training loop the handoff sketched exists in this codebase and is proven — the chess-RL and PINN-demo posts run on it — but reaching for it here would have added nondeterminism-management work to buy nothing the closed form doesn’t already give.)</p>
<p><strong>Weights are a content-addressed artifact.</strong> The head persists as a <code>CPB0</code> bundle: magic, schema version, then a losslessly-compressed payload (the codec embeds its own input hash, so corruption is detected on read) carrying the model id, model version, and the raw <code>f64</code> bits of every parameter. Same weights → byte-identical file; the round-trip test asserts bit equality, and double-writing the bundle produces identical bytes. Loaders reject wrong magic, wrong version, wrong feature count, truncation, trailing bytes, and — separately — <em>valid encodings of invalid weights</em> (NaN coefficients, zero standard deviations). A corrupted bundle is an error, never a silently-degraded model.</p>
<p><strong>Model identity flows into report hashes.</strong> Attaching the head flips the cost model’s reported identity from <code>pinn_coeffs_v1</code> to <code>pinn_thermal_v2</code>, and that identity participates in report hashing — so two compilations under different models are <em>distinguishable by hash</em>, which is what reproducibility means when models become swappable.</p>
<p><strong>Promotion is gated on evidence, in shadow mode.</strong> Trained weights don’t get trusted because the training metrics looked good; v2 ran against v1 on the same measured per-program thermal labels, same held-out split:</p>
<table class="table">
<colgroup>
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
</colgroup>
<thead>
<tr class="header">
<th>cohort</th>
<th>v1 MAE</th>
<th>v1 corr</th>
<th>v2 MAE</th>
<th>v2 corr</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>train (103 programs)</td>
<td>0.142</td>
<td>+0.43</td>
<td>0.016</td>
<td>+0.99</td>
</tr>
<tr class="even">
<td><strong>held-out (31 programs)</strong></td>
<td><strong>0.190</strong></td>
<td><strong>+0.18</strong></td>
<td><strong>0.021</strong></td>
<td><strong>+0.98</strong></td>
</tr>
<tr class="odd">
<td>overall (134)</td>
<td>0.153</td>
<td>+0.37</td>
<td>0.017</td>
<td>+0.99</td>
</tr>
</tbody>
</table>
<p>A 9× held-out error reduction, and a correlation jump that tells the real story: v1’s closed-form thermal predictions topped out at 0.0001 against labels spanning [0, 1] — it was not weakly informative, it was <em>blind</em>. The gate’s verdict: <strong>promote</strong>.</p>
<p>One more physics-informed touch, in the literal sense: the post-fit checks assert that the standardized FP-density coefficient is positive (+0.194 — more FP work rate must predict more heat; a sign flip would mean the model learned something physically wrong from a quirk of the corpus) and that a zero-workload query predicts cold (raw −0.81, clamps to 0). Cheap assertions, but they’re the difference between “the loss went down” and “the model believes something compatible with thermodynamics.”</p>
</section>
<section id="did-the-compiler-actually-change-its-mind" class="level2">
<h2 class="anchored" data-anchor-id="did-the-compiler-actually-change-its-mind">7. Did the compiler actually change its mind?</h2>
<p>Yes — measurably, and in the right places. A new ablation configuration ranks the corpus under the trained head, and <strong>14 of 134 programs get different pass plans</strong> than under v1. The diffs all have the same shape: on functions the gradient family deliberately made FP-dense, v2 predicts thermal pressure above the withholding threshold and the ranker drops <code>licm</code> and <code>loop_unroll</code> — the passes that amplify sustained FP throughput — while keeping the cheap structural cleanups. v1, predicting ≈0 thermal everywhere, withheld nothing. The trained model isn’t just more accurate in a table; it changes decisions, and the decisions are physically coherent.</p>
<p>It’s also wired end-to-end: <code>cjcl run --pinn-weights path/to/pinn_thermal_v2.cpb</code> loads the bundle, attaches the head, and compiles under it (implying <code>--mir-opt</code>; superseding the older single-axis <code>--thermal-aware</code> wrapper, because stacking both would double-penalize heat). A four-test parity suite locks the load-bearing claim: output under the trained head is byte-identical to output without it, on integer-only and FP-heavy programs alike — the model changes <em>which optimizations run</em>, never <em>what the program computes</em>.</p>
<p>Now the honest caveat, because this is where overclaiming lives: <strong>on the modeled energy scoreboard, thermal withholding roughly breaks even</strong> (best case −3.3% on a single program; ties elsewhere). That is not a failure of the head — it’s a structural limitation of the metric. The value of refusing to unroll a hot loop is <em>avoiding thermal throttling on real silicon</em>, a benefit that a deterministic scalar energy proxy cannot represent and that the determinism rules correctly forbid measuring inside the compiler (wall-clock and sensors are diagnostic-only, excluded from every decision path and every hash). The plans are different and defensible; the end-to-end hardware win is an explicitly open claim, queued for diagnostic measurement <em>outside</em> the deterministic boundary.</p>
</section>
<section id="the-section-where-i-report-zeros" class="level2">
<h2 class="anchored" data-anchor-id="the-section-where-i-report-zeros">8. The section where I report zeros</h2>
<p>Three negative results, each with a mechanism. I think these are the most useful part of the post.</p>
<p><strong>The quantum-inspired energy ranker changes nothing — architecturally, not fixably-by-training.</strong> The <code>EnergyAwarePassRanker</code> wraps the base ranker, computes an Ising-style energy decomposition per recommendation (compute cost, memory traffic, thermal pressure, code-size, reconstruction-error terms — Kahan-summed, deterministically tie-broken), and re-sorts each function’s recommendation list by ascending energy. It carries an explicit invariant: it never adds a pass and never drops one. Which means the <em>set</em> of passes — the thing that determines compiled output — is decided entirely upstream. Measured across every quantum-flavored configuration and 2,680 rows: <strong>zero plan-set changes, zero score changes</strong>. An energy minimizer is only as useful as the candidate space it discriminates over, and this one was handed a one-candidate space per function. A perfect energy function evaluated over a single candidate still selects that candidate. The fix is architectural — give it candidates — and that’s part two, not a training run. (Worth saying: the layer was <em>designed</em> advisory-first on purpose; “cannot override selection” is the safe default in a deterministic compiler. The zero is the cost of that safety, now measured rather than assumed.)</p>
<p><strong>The cpu and memory heads will probably never be trained — and that’s a data verdict, not a modeling one.</strong> Measured label standard deviations across the corpus: cpu 0.013, memory 0.0007. The corpus programs are arithmetic kernels; they barely touch the allocator. There is nothing for any model to learn until the corpus contains workloads that actually generate memory-pressure variance. (This is the same lesson the thermal head taught in reverse: thermal only became learnable after a deliberately-constructed gradient family of programs created label variance. Datasets are built, not found.)</p>
<p><strong>The first energy-head probe fails spectacularly, and the failure is informative.</strong> With the new forced-plan rows (next section) providing real energy variance, a linear probe on the divergent rows scores held-out R² ≈ <strong>−32</strong>. Not noise — structure the basis can’t express: energy ratios are multiplicative and span [0.91, 11.2], so a linear model on raw ratios gets shredded by the long tail. The constraint it buys for part two: log-ratio target, and possibly the nonlinearity the thermal head never needed.</p>
</section>
<section id="buying-the-next-dataset" class="level2">
<h2 class="anchored" data-anchor-id="buying-the-next-dataset">9. Buying the next dataset</h2>
<p>The thinnest thing in the original corpus was energy-label variance — 39 informative rows, because ranked configurations mostly agree with baseline. This session closed that the blunt way: eight <strong>forced-plan configurations</strong> that apply a fixed pass list to every function (<code>force_none</code> through <code>force_all</code>, one per canonical pass), bypassing the cost model but <em>not</em> the legality gate — every (function, pass) pair is individually gate-checked, and the gate vetoed pairs on 282 of the new rows, because “forced” must never mean “illegal.”</p>
<p>The corpus is now 134 × 20 = 2,680 rows, with <strong>295 energy-informative rows</strong> spanning [0.909, 11.16] — and one genuinely exciting measurement hiding in them: <code>force_unroll</code> and <code>force_all</code> <em>sometimes beat the ranked baseline</em>. The current cost model measurably mis-ranks on some programs. That’s not an embarrassment; that’s <strong>headroom</strong> — proof there’s signal for a trained energy selector to exploit, which closes the loop on the quantum layer’s zero: candidate plans (the forced variants are exactly that) + a trained energy head as the selection criterion, replacing the hand-tuned <code>FP_ENERGY_WEIGHT = 3.0</code> that currently prices FP ops in the energy score.</p>
<p>That’s part two, and its training data now exists.</p>
</section>
<section id="takeaways" class="level2">
<h2 class="anchored" data-anchor-id="takeaways">10. Takeaways</h2>
<ol type="1">
<li><strong>Run the sanity pass before the training loop.</strong> One session of deterministic statistics killed a doomed MLP, found a 402-row label bug, and located the real problem (features, not models). Cheapest experiment of the year.</li>
<li><strong>Information beats capacity.</strong> Held-out R² went −0.05 → +0.96 by adding one physically-motivated feature. No architecture search touches that.</li>
<li><strong>Match the static proxy to the measured label’s <em>definition</em>.</strong> TypeMix counts exactly what the runtime instrumentation counts — same op classes, comparisons included. Proxy-label agreement is a design decision, not luck.</li>
<li><strong>Deterministic ML is mostly subtraction.</strong> Closed-form training, bit-stable artifacts, identity-in-hashes, validity-gated loading — each choice <em>removes</em> a source of nondeterminism rather than managing one. The result trains the same way twice.</li>
<li><strong>Gate promotion on measured labels, in shadow.</strong> “Training metrics looked good” and “beats the incumbent on held-out ground truth” are different claims; only the second one ships.</li>
<li><strong>Publish the zeros with mechanisms.</strong> The quantum layer’s zero took an afternoon to explain structurally, and the explanation is the roadmap.</li>
</ol>
</section>
<section id="pointers" class="level2">
<h2 class="anchored" data-anchor-id="pointers">11. Pointers</h2>
<p>Everything is in the <a href="https://github.com/AdamEzzat1/CJC">CJC-Lang repository</a>: the <code>TypeMix</code> analysis (<code>crates/cjc-cana/src/type_mix.rs</code>), the trained head and its swap-in surface (<code>crates/cjc-cana/src/pinn_thermal_v2.rs</code>, <code>pinn_cost_model.rs</code>), the CPB0 bundle codec (<code>crates/cjc-cana-compress/src/pinn_bundle.rs</code>), the sanity/train/shadow harness (<code>bench/cana_train_pinn/</code>), the ablation corpus generator (<code>bench/cana_ablation/</code>), the committed weights (<code>bench_results/cana_train_pinn/pinn_thermal_v2.cpb</code>), and the design note with every number in this post (<code>docs/cana/PINN_V2_DESIGN.md</code>). The session landed as two feature commits and a merge, 31 new tests, with the cross-executor parity gate green at every step.</p>
<div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb2-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># install</span></span>
<span id="cb2-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cargo</span> install cjc-lang</span>
<span id="cb2-3"></span>
<span id="cb2-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># compile + run under the trained thermal head</span></span>
<span id="cb2-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> run <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--pinn-weights</span> bench_results/cana_train_pinn/pinn_thermal_v2.cpb program.cjcl</span>
<span id="cb2-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># output is byte-identical to a plain run — the model changes which</span></span>
<span id="cb2-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># optimizations are attempted, never what the program computes</span></span></code></pre></div>
<p>If you’re building anything where a learned component has to coexist with a reproducibility contract — compilers, scientific pipelines, anything audited — I’d genuinely like to compare notes.</p>


</section>

 ]]></description>
  <category>CJC-Lang</category>
  <category>CANA</category>
  <category>NSS</category>
  <category>compilers</category>
  <category>machine-learning</category>
  <category>physics-informed</category>
  <category>determinism</category>
  <category>energy</category>
  <category>green-computing</category>
  <guid>https://adamezzat1.github.io/blog/posts/pinn-v2-deterministic-compiler-ml/index.html</guid>
  <pubDate>Thu, 11 Jun 2026 07:00:00 GMT</pubDate>
</item>
<item>
  <title>Locke v0.8: A LendingClub Demo, an Honest Model, and the Three Gaps the Audit Found</title>
  <dc:creator>Adam Ezzat</dc:creator>
  <link>https://adamezzat1.github.io/blog/posts/locke-v0.8-lendingclub-and-the-three-gaps/index.html</link>
  <description><![CDATA[ 



<div class="reading-time">
🕑 ~38 min read
</div>
<div class="stats-row">
<div class="stat">
<span class="number">2.26M</span><span class="stat-label">rows audited</span>
</div>
<div class="stat">
<span class="number">0.7388</span><span class="stat-label">honest-model |AUC| with custom det.</span>
</div>
<div class="stat">
<span class="number">1879</span><span class="stat-label">E9070 firings after v0.8 wiring</span>
</div>
<div class="stat">
<span class="number">71</span><span class="stat-label">new tests, all passing</span>
</div>
</div>
<div class="callout callout-style-simple callout-tip no-icon callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-title-container flex-fill">
TL;DR
</div>
</div>
<div class="callout-body-container callout-body">
<p>A single session against the full Kaggle <a href="https://www.kaggle.com/datasets/wordsforthewise/lending-club"><code>wordsforthewise/lending-club</code></a> snapshot (~2.26M loans, 151 columns) produced:</p>
<ul>
<li><strong>A working end-to-end LC demo</strong> with byte-identical determinism — two 8-minute runs over the 1.6 GB decompressed CSV produce SHA-256-matching 469 KB JSON reports.</li>
<li><strong>An honest finding about Locke’s own value</strong>: the built-in <code>|AUC| ≥ 0.85</code> leakage heuristic (E9061) catches the strongest signals (<code>last_fico_range_low/high</code>, <code>total_rec_prncp</code> — <em>not</em> the columns the audit-design exercise predicted) but not all of them. A 3-way logistic regression on the binarized frame: removing only E9061-flagged columns leaves <code>|AUC| = 0.9995</code> essentially unchanged from the naive <code>0.9993</code>. Only removing the 12-column hand-curated domain post-origination list collapses AUC to <code>0.7394</code> — squarely in the Bao et al.&nbsp;(2019) credit-risk literature band.</li>
<li><strong>ADR-0041, the custom-detector extension layer</strong>: a new <code>CustomDetector</code> trait + <code>FindingSink</code> + <code>BeliefAxisSet</code>, code namespace <code>E9500..=E9999</code>, belief-axis routing for the meet-semilattice algebra, plus a Python bridge whose determinism contract is <strong>structural</strong> rather than behavioural — proven by an emission-shuffle proptest. The LC re-measurement: a 30-LOC <code>PostOriginationByNameDetector</code> flags 39 columns by name pattern; the honest-model harness reads the report’s <code>E9500 ∪ E9061</code> columns as exclusion set; resulting <code>|AUC| = 0.7388</code> lands <strong>within 0.0006 of the hand-curated domain-honest baseline</strong>.</li>
<li><strong>ADR-0042, Str-to-Float auto-promotion + E9070 wiring</strong>: closes a silent-skip gap on numeric columns CSV readers mis-type as <code>Str</code>. The investigation revealed <em>two</em> gaps in one ADR — auto-promotion AND a missing call site for <code>detect_conditional_missingness</code>, which had been defined and unit-tested in v0.6.4 but never invoked from <code>validate_dataframe</code>. LC post-fix: <code>E9009 × 22</code> auto-promoted columns, <code>E9070 × 1879</code> firings (was zero pre-v0.8). A third gap (CSV quoted-string handling) was discovered, scoped, and deferred.</li>
<li><strong>A release</strong>: <code>cjc-locke v0.1.11</code> on <a href="https://crates.io/crates/cjc-locke/0.1.11">crates.io</a> (alongside 24 sibling crates), <code>cjc-locke v0.1.2</code> on <a href="https://pypi.org/project/cjc-locke/0.1.2/">PyPI</a>.</li>
</ul>
<p>The secondary headline is the discovery cascade: each step in the session surfaced the gap the next step closed. The handoff predicted X; Locke found Y; Y didn’t match the honest model; the architectural response is ADR-0041; preparing for the <em>next</em> demo surfaced ADR-0042’s gap; both shipped in the same release.</p>
</div>
</div>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Prototype disclaimer
</div>
</div>
<div class="callout-body-container callout-body">
<p>Locke and CJC-Lang remain research-stage prototypes. The numbers below are one reproducible engineering campaign — five commits on a single PR, every chain head verifiable — not a validated benchmark, not deployed at production scale, not peer-reviewed. The four-way AUC comparison is on a 200K-row subsample with a single seed; full-dataset and multi-seed work is queued. Read as honest exploratory engineering on a real but bounded dataset.</p>
</div>
</div>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Where this sits in the Locke series
</div>
</div>
<div class="callout-body-container callout-body">
<ul>
<li><a href="../../../blog/posts/cjc-locke-v0.1-released/index.html">Locke v0.1.10: An Epistemic Layer for Data Validation</a> — the framing post. Determinism, content-addressed findings, belief-score algebra, side-by-side with Great Expectations.</li>
<li><a href="../../../blog/posts/abng-diabetes-revisited-with-locke/index.html">Diabetes-130 Revisited with Locke</a> — Locke + ABNG on medical data. The handoff’s “Locke prune → AUC up” hypothesis falsified itself; Locke’s contribution turned out to be structural (a patient-encounter leakage hole, a <code>?</code>-sentinel detector gap proposal).</li>
<li><strong>This post</strong> — Locke against real consumer-credit data at scale, the audit’s honest-model AUC test, and the two ADRs the audit forced into the v0.8 release.</li>
</ul>
</div>
</div>
<section id="the-brief" class="level2">
<h2 class="anchored" data-anchor-id="the-brief">1. The brief</h2>
<p>The session started with a handoff document I’d drafted earlier under a different worktree. The document specified two demos to ship — <strong>Project A</strong>: LendingClub leakage audit (the “fits in RAM, 2-minute iteration loop” demo); <strong>Project B</strong>: NYC Taxi 2019–2020 pandemic drift (the “streaming, 30-minute sweep, thermal observability” demo). The intent was that together they’d cover all four Locke value claims: numerical rigor, determinism, thermal control, memory efficiency. Separately each covers two.</p>
<p>This post is exclusively about Project A — but the <em>reason</em> we hit two ADRs is that Project A’s findings made Project B’s blockers visible.</p>
<p>The Project A spec, condensed:</p>
<table class="table">
<colgroup>
<col style="width: 27%">
<col style="width: 72%">
</colgroup>
<thead>
<tr class="header">
<th>Field</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Dataset</td>
<td>Kaggle <code>wordsforthewise/lending-club</code>, accepted-loans CSV</td>
</tr>
<tr class="even">
<td>Size</td>
<td>~600 MB compressed → ~1.6 GB raw → 2.26M rows × 151 cols</td>
</tr>
<tr class="odd">
<td>Source target</td>
<td><code>loan_status</code> (multi-class)</td>
</tr>
<tr class="even">
<td>Derived binary target</td>
<td><code>target_default</code> (Charged Off ∪ Default = 1; Fully Paid = 0; other rows dropped)</td>
</tr>
<tr class="odd">
<td>Locke entry point</td>
<td><code>cjc-locke::api::validate</code> + <code>detect_target_leakage</code> + <code>detect_id_like_columns</code></td>
</tr>
<tr class="even">
<td>Determinism contract</td>
<td>Two runs → byte-identical JSON output</td>
</tr>
<tr class="odd">
<td>Compute budget</td>
<td>&lt;2 min per iteration (laptop)</td>
</tr>
</tbody>
</table>
<p>The handoff itself was opinionated about what would fire. From handoff §3.3, “E-codes expected to fire”:</p>
<blockquote class="blockquote">
<p><strong>E9060</strong> | Error | <code>total_pymnt</code>, <code>total_rec_int</code>, <code>last_pymnt_amnt</code>, <code>recoveries</code>, <code>collection_recovery_fee</code> | AUC ≥ 0.95 against <code>loan_status</code> (<code>charged_off</code> vs <code>fully_paid</code>)</p>
<p><strong>E9061</strong> | Warning | <code>out_prncp</code>, <code>total_pymnt_inv</code> | AUC in [0.85, 0.95]</p>
</blockquote>
<p>That prediction is the first thing the demo would refute.</p>
</section>
<section id="building-the-demo-the-boring-parts-that-have-to-work" class="level2">
<h2 class="anchored" data-anchor-id="building-the-demo-the-boring-parts-that-have-to-work">2. Building the demo: the boring parts that have to work</h2>
<p>I’ll keep this short because the audit findings are the interesting bit, but the foundation deserves a note. Three engineering decisions in the demo scaffolding compounded:</p>
<p><strong>Workspace member, not standalone.</strong> <code>demos/lendingclub/</code> is a workspace member with <code>publish = false</code>. Three workspace deps (<code>cjc-data</code>, <code>cjc-locke</code>, <code>flate2</code>) plus standard Cargo conventions. The crate has both a <code>[lib]</code> (testable helpers) and two <code>[[bin]]</code> entries (the main demo and an <code>honest_model</code> evaluator I’ll describe below).</p>
<p><strong>Library + thin binary split.</strong> <code>src/lib.rs</code> exposes <code>load_csv_gz</code>, <code>binarize_loan_status</code>, <code>run_locke_audit</code>, and the helper types. <code>src/main.rs</code> is a CLI argument parser around them. The integration test in <code>tests/expected_findings.rs</code> and the unit tests in <code>src/lib.rs</code> exercise the helpers without ever touching the 600 MB CSV. Eight library unit tests passed before I downloaded any data.</p>
<p><strong>Binarization as a row filter + new column.</strong> <code>binarize_loan_status</code> classifies each <code>loan_status</code> string via:</p>
<div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb1-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">pub</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> classify_status(raw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">Option</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">bool</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb1-2">    <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">match</span> raw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>trim() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb1-3">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Charged Off"</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Default"</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">Some</span>(<span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">true</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-4">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Fully Paid"</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">Some</span>(<span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">false</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-5">        _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">None</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-6">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb1-7"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div>
<p><code>None</code> drops the row. <code>Some(b)</code> is appended to a new <code>Column::Bool</code> named <code>target_default</code>. This makes <code>cjc-locke::leakage::detect_target_leakage</code>’s binary-target requirement satisfied without modifying Locke’s contract. The function’s signature is also a contract: any future status not in the match drops out cleanly. Out of 2.26M rows loaded, <strong>1,325,938 reach a terminal outcome</strong> — 58.7% of the dataset.</p>
<p><strong>Determinism verified by hash, not by intuition.</strong> Two demo runs of <code>cargo run -p lendingclub-demo</code> over the identical CSV produce identical 469 KB JSON reports. SHA-256 match across runs. The Rust side has it from construction; the demo is the integration verification.</p>
<p>The total scaffolding came in at ~290 lines of library code + ~150 lines of CLI binary + ~270 lines of unit + integration tests. The tests caught two mistakes during writing — an <code>ImpossibleValueRule</code> constructor I assumed existed that didn’t (it’s an enum variant, not a function), and a non-exhaustive <code>Column</code> match (I forgot <code>Categorical { .. }</code>, <code>CategoricalAdaptive(_)</code>, and <code>DateTime(_)</code> exist). Compile-fail discipline is its own dividend.</p>
</section>
<section id="the-audit-and-the-first-surprise" class="level2">
<h2 class="anchored" data-anchor-id="the-audit-and-the-first-surprise">3. The audit, and the first surprise</h2>
<p>A full-dataset audit produces an 8-9 minute wall time, dominated by the Locke audit phase (~437 s on 1.32M binarized rows × 152 cols). The summary the demo’s CLI prints:</p>
<pre><code>[load] 1597.5 MB decompressed, 2260701 rows × 151 cols, 52.2s
[derive] kept 1325938 rows × 152 cols, 27.50s
[locke] running validate() + detect_target_leakage + detect_id_like_columns
[locke] 577 findings (40 error, 104 warning, 289 notice, 144 info), 437.90s
[locke] findings by code:
        E9001 × 55
        E9002 × 10
        E9007 × 47
        E9008 × 55
        ...
        E9061 × 3
        E9072 × 2
        ...</code></pre>
<p>The headline result wasn’t the run time. It was the E9061 column set.</p>
<section id="the-handoff-said-x.-the-data-said-y." class="level3">
<h3 class="anchored" data-anchor-id="the-handoff-said-x.-the-data-said-y.">The handoff said X. The data said Y.</h3>
<p>I’d told the handoff E9061 (suspicious target leakage, <code>|AUC|</code> in <code>[0.85, 0.95]</code>) would fire on <code>out_prncp</code> and <code>total_pymnt_inv</code> — <em>partial</em> signals of the payment streams. E9060 (the strong-leakage code, <code>|AUC| ≥ 0.95</code>) would fire on the payment streams proper.</p>
<p>What actually fired:</p>
<table class="table">
<thead>
<tr class="header">
<th>Code</th>
<th>Column</th>
<th><code>\|AUC\|</code></th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>E9061</td>
<td><code>last_fico_range_high</code></td>
<td>0.9162</td>
</tr>
<tr class="even">
<td>E9061</td>
<td><code>last_fico_range_low</code></td>
<td>0.9284</td>
</tr>
<tr class="odd">
<td>E9061</td>
<td><code>total_rec_prncp</code></td>
<td>0.8675</td>
</tr>
</tbody>
</table>
<p><code>E9060</code> — the “near-deterministic” leakage code at the <code>|AUC| ≥ 0.95</code> threshold — <strong>fired zero times</strong>. Every post-origination column the handoff named landed <em>below</em> the 0.85 threshold and produced no finding at all (E9061 is <code>continue</code> in the loop, not an <code>Info</code>-severity ack).</p>
<p>The top two signals were <code>last_fico_range_high</code> and <code>last_fico_range_low</code> — the borrower’s last credit-bureau refresh, recorded <em>after</em> the loan exists. The handoff hadn’t predicted those columns. It had predicted total payment amounts.</p>
<p>The retrospective interpretation is sensible: payment amounts are dominated by loan size and term duration; their distributional spread under both terminal outcomes overlaps significantly, dragging AUC down to the sub-threshold band. The borrower’s <em>post-loan</em> FICO score, by contrast, collapses sharply when the loan is charged off (delinquency reports update the bureau record), so the FICO refresh column carries a much cleaner separation signal.</p>
<p><strong>Locke disagreed with the analyst, and Locke was right.</strong> This is the first finding worth banking: not “Locke caught what the analyst predicted” but “Locke surfaced something the analyst’s reasoning missed.” A tool that updates the analyst’s priors is more valuable than one that confirms them.</p>
</section>
<section id="other-useful-surprises-in-the-same-run" class="level3">
<h3 class="anchored" data-anchor-id="other-useful-surprises-in-the-same-run">Other useful surprises in the same run</h3>
<ul>
<li><code>E9072</code> (ID-like cardinality, <code>distinct / n_rows ≥ 0.95</code>) fired on <code>id</code> (the loan key, expected) AND on <code>url</code> (unexpected, but the LC URL embeds the loan ID — so it inherits the cardinality). <code>member_id</code> did <em>not</em> fire because the same member can hold multiple loans.</li>
<li><code>E9001</code> (high missingness) fired on the joint-application column set (<code>annual_inc_joint</code>, <code>dti_joint</code>, <code>verification_status_joint</code>) at ~85% missingness, matching the empirical ratio of individual-to-joint loans.</li>
<li><code>E9008</code> (auto-detected string sentinel) fired on <code>emp_length</code> for <code>"n/a"</code> and on the joint columns for <code>""</code>.</li>
</ul>
<p>But it also under-fired in two ways the handoff <em>had</em> anticipated, and getting those right matters for the rest of the post:</p>
<ul>
<li><code>E9008</code> did <strong>not</strong> fire on <code>term</code> despite its leading-space <code>" 36 months"</code> format. The detector matches against <code>BUILTIN_STRING_SENTINELS</code> exactly (<code>""</code>, <code>"?"</code>, <code>"NA"</code>, etc.) — leading whitespace on a domain-meaningful value isn’t a sentinel by Locke’s definition. The handoff’s prediction here conflated “sentinel” with “formatting artifact.”</li>
<li><code>E9070</code> (conditional missingness on <code>(A, B)</code> pairs) did <strong>not</strong> fire on the joint columns — a real gap that would later turn out to be <em>two</em> gaps. We come back to this in §5.</li>
</ul>
<p>The empirical fixture I committed (<code>expected_findings.json</code>) was calibrated against measured reality, not the handoff’s predictions. The doc-vs-data divergence is itself the artifact: a future contributor reading the fixture sees the predictions that didn’t pan out and the empirical replacement.</p>
</section>
</section>
<section id="the-honest-model-auc-follow-up-the-gut-punch" class="level2">
<h2 class="anchored" data-anchor-id="the-honest-model-auc-follow-up-the-gut-punch">4. The honest-model AUC follow-up: the gut punch</h2>
<p>The cross-validation file (<code>cross_validate.md</code>) in the demo had a <code>TBD</code> row labeled “honest-model AUC”. The literature for similar credit-risk feature spaces (Tsai &amp; Wu 2008, Bao et al.&nbsp;2019, FICO score in isolation) puts the achievable AUC on pre-origination features in the <strong>0.69 – 0.74</strong> band. The optional follow-up was to actually measure it: train a logistic regression on the binarized frame with and without leakage columns, and confirm the post-Locke variant lands in the literature band.</p>
<section id="what-was-available-in-cjc-runtime" class="level3">
<h3 class="anchored" data-anchor-id="what-was-available-in-cjc-runtime">What was available in cjc-runtime</h3>
<p>This is one of the moments where having the underlying language stack pays off. The four primitives needed already existed:</p>
<div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb3-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// From cjc-runtime</span></span>
<span id="cb3-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">pub</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> logistic_regression(x_flat<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>[<span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">f64</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>[<span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">f64</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> n<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">usize</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> p<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">usize</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">Result</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>LogisticResult<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">String</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;;</span></span>
<span id="cb3-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">pub</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> auc_roc(scores<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>[<span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">f64</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> labels<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>[<span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">bool</span>]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">Result</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">f64</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">String</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;;</span></span>
<span id="cb3-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">pub</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> train_test_split(n<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">usize</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> test_fraction<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">f64</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> seed<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">u64</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> (<span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">Vec</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">usize</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">Vec</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">usize</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb3-5"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">pub</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> z_score(data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>[<span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">f64</span>]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">Result</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">Vec</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">f64</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">String</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;;</span></span></code></pre></div>
<p><code>logistic_regression</code> is IRLS (Iteratively Reweighted Least Squares) with the intercept auto-added. <code>auc_roc</code> is the trapezoidal ROC integration with <code>f64::total_cmp</code> tie-breaking — deterministic. <code>train_test_split</code> uses a seeded Fisher-Yates shuffle. <code>z_score</code> does Kahan-summed mean + standard deviation.</p>
<p>That covers everything needed for a deterministic tabular-ML evaluator. The demo’s <code>honest_model</code> binary is ~300 LOC of Rust orchestrating these primitives:</p>
<div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb4-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Pseudocode, not the actual source.</span></span>
<span id="cb4-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> (raw_df<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> _) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> load_csv_gz(input<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">Some</span>(load_cap))<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">?;</span></span>
<span id="cb4-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> df <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> binarize_loan_status(raw_df)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">?;</span></span>
<span id="cb4-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> sample_indices<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">Vec</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">usize</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">..</span>actual_sample)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>collect()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb4-5"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> (train_local<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> test_local) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> train_test_split(sample_indices<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>len()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> test_fraction<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> seed)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb4-6"></span>
<span id="cb4-7"><span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">for</span> (label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> exclusion_set) <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">in</span> [</span>
<span id="cb4-8">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pre-Locke (naive)"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">vec!</span>[])<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span>                                  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// 87 cols</span></span>
<span id="cb4-9">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Locke-filtered"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span>     LOCKE_E9061_COLUMNS<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>to_vec())<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span>           <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// 84 cols (–3)</span></span>
<span id="cb4-10">    (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"domain-honest"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span>      DOMAIN_POST_ORIGINATION_COLUMNS<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>to_vec())<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// 75 cols (–12)</span></span>
<span id="cb4-11">] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb4-12">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> (names<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> columns) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> select_numeric_columns(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>df<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>full_excludes)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb4-13">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> (names<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> columns) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> drop_useless_columns(names<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> columns)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb4-14">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Per-column: fit mean/std on train only; apply to train + test.</span></span>
<span id="cb4-15">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// No information leak across the split.</span></span>
<span id="cb4-16">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> logreg <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> logistic_regression(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>x_train_flat<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>y_train<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> n_train<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> p)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">?;</span></span>
<span id="cb4-17">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> eta <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> score_logistic(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>logreg<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>coefficients<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>x_test_flat<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> n_test<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> p)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb4-18">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> auc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> auc_roc(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>eta<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>y_test_bool)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">?;</span></span>
<span id="cb4-19">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// ...</span></span>
<span id="cb4-20"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div>
<p>The deliberate constraints: 200K-row stratified subsample (because IRLS at full n × p² is slow), seed = 42 fixed, 70/30 train/test split, z-score normalization fit on train only.</p>
</section>
<section id="the-three-way-comparison" class="level3">
<h3 class="anchored" data-anchor-id="the-three-way-comparison">The three-way comparison</h3>
<pre class="text"><code>=== Summary ===
variant                     |AUC|    n_train     n_test          p
pre-Locke (naive)          0.9993     140000      60000         87
Locke-filtered             0.9995     140000      60000         84
domain-honest              0.7394     140000      60000         75</code></pre>
<p><strong>Pre-Locke (the naive model).</strong> 87 numeric columns; only the target column itself and the two ID columns excluded. Test <code>|AUC| = 0.9993</code>. IRLS hits the iteration cap of 100 without converging. The model is catastrophically over-fit via leakage — but more interestingly, the <em>exact</em> form of catastrophe is the post-origination columns moving together as a multicollinear cluster. The <code>X^T W X</code> matrix in IRLS becomes near-singular; Newton steps stop converging.</p>
<p><strong>Locke-filtered (E9061 only).</strong> Same setup minus <code>last_fico_range_high</code>, <code>last_fico_range_low</code>, <code>total_rec_prncp</code> — the three columns Locke flagged at <code>|AUC| ≥ 0.85</code>. Test <code>|AUC| = 0.9995</code>. <em>Essentially unchanged</em> from naive. IRLS still hits the iteration cap.</p>
<p><strong>Domain-honest (data dictionary).</strong> Same setup minus the full handoff §3.3 post-origination list: 12 columns covering payment streams, recoveries, outstanding principal, and the last-refreshed FICO. Test <code>|AUC| = 0.7394</code>. IRLS converges cleanly in 14 iterations.</p>
<p>The headline three-way table prints in the binary’s CLI output and lands in <code>cross_validate.md</code> §3.1.</p>
</section>
<section id="the-finding" class="level3">
<h3 class="anchored" data-anchor-id="the-finding">The finding</h3>
<p>Locke’s <code>|AUC| ≥ 0.85</code> heuristic catches the <em>strongest</em> leakage but not all of it. The columns that survive the Locke filter (<code>total_pymnt</code>, <code>out_prncp</code>, <code>total_pymnt_inv</code>, <code>last_pymnt_amnt</code>, <code>recoveries</code>, etc.) carry enough leakage signal <em>jointly</em> to keep the model’s AUC inflated. Removing them — via the domain-knowledge list — is what actually collapses AUC into the honest band.</p>
<p><strong>This is not a bug in Locke.</strong> Locke’s per-feature ROC AUC computation is correct. The post-origination columns are leaky; their individual AUCs are merely in <code>[0.5, 0.85)</code> because each carries a noisy signal that happens to align with a sibling column. The detector’s threshold is doing what it was designed to do — fire on individually-strong signals. The architectural question is whether Locke should also detect <em>jointly</em>-strong-but-individually-weak leakage. v0.8 doesn’t change that.</p>
<p>What v0.8 <em>does</em> change is the framing of Locke’s value claim.</p>
<p>The naive framing is “Locke catches leakage.” That doesn’t survive contact with the LC follow-up. The Locke-filtered AUC stays at 0.9995. A reviewer who tried this would conclude the tool failed.</p>
<p>The honest framing is “<strong>Locke shows you where to look; the analyst applies domain triage</strong>.” That survives the LC follow-up because the data shows exactly that. <code>last_fico_range_high</code> jumping out at <code>|AUC| 0.94</code> is the analyst’s cue to ask: <em>is this column recorded before or after the loan?</em> The answer (after) implies a fuller exclusion list — the kind of list <code>DOMAIN_POST_ORIGINATION_COLUMNS</code> encodes. Without Locke, the analyst might never have realized the last-refreshed FICO range was post-origination at all.</p>
<p>But that framing implies a missing piece: the analyst’s domain triage has nowhere to live <em>inside</em> Locke. It lives in their own code, as a separate exclusion list maintained per project. The triage doesn’t compose with the belief algebra, doesn’t get fingerprinted, doesn’t show up in the canonical JSON, doesn’t get caught by regression gates if the domain knowledge changes.</p>
<p>That gap is what ADR-0041 closes.</p>
</section>
</section>
<section id="adr-0041-the-custom-detector-extension-layer" class="level2">
<h2 class="anchored" data-anchor-id="adr-0041-the-custom-detector-extension-layer">5. ADR-0041: The Custom Detector Extension Layer</h2>
<p>The architectural decision is in <a href="https://github.com/AdamEzzat1/CJC/blob/master/CJC-Lang_Obsidian_Vault/13_ADRs/ADR-0041%20Locke%20v0.8%20%E2%80%94%20Custom%20Detector%20Extension%20Layer.md"><code>ADR-0041</code></a>. The brief is:</p>
<div class="callout callout-style-simple callout-note no-icon">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-body-container">
<p><strong>Goal.</strong> Let analysts encode domain checks as first-class Locke detectors that share the same finding format, belief composition, and determinism guarantees as the built-ins — without diluting the opinionated baseline or opening the determinism contract.</p>
<p><strong>Non-goal.</strong> Become Great Expectations. Locke is a determinism-first belief-aware auditor; GE is a test framework. The custom-detector layer extends Locke’s vocabulary, not its identity.</p>
</div>
</div>
</div>
<p>The trait shape, after several iterations:</p>
<div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb6-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">pub</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">trait</span> CustomDetector<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">Send</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">Sync</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">std::fmt::</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">Debug</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb6-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/// E-code in E9500..=E9999. Validated at registration; codes</span></span>
<span id="cb6-3">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/// outside this range produce a CustomDetectorError and the</span></span>
<span id="cb6-4">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/// detector is silently skipped at run.</span></span>
<span id="cb6-5">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> code(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">'static</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">str</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb6-6"></span>
<span id="cb6-7">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/// Which belief axes this detector's findings affect.</span></span>
<span id="cb6-8">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> belief_axes(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> BeliefAxisSet<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb6-9"></span>
<span id="cb6-10">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> name(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">str</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">self</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>code() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb6-11"></span>
<span id="cb6-12">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> run(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">self</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> df<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>DataFrame<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> sink<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">mut</span> FindingSink)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb6-13"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div>
<p>Three architectural decisions deserve unpacking.</p>
<section id="code-namespace-e9500..e9999" class="level3">
<h3 class="anchored" data-anchor-id="code-namespace-e9500..e9999">5.1 Code namespace <code>E9500..=E9999</code></h3>
<p>Locke’s built-in codes occupy <code>E9001..=E9112</code>. The custom range starts safely above them and leaves 500 slots, with the upper 4000 reserved for future framework use. Reading a report, you can always tell which findings came from Locke’s opinionated baseline (E9xxx with <code>xxx &lt; 9500</code>) versus a team’s custom rules (<code>xxx &gt;= 9500</code>).</p>
<p>The validator <code>validate_custom_code(&amp;str) -&gt; Result&lt;u32, CustomDetectorError&gt;</code> is the single point of truth. Both the Rust-side registration and the Python bridge call it, so error messages are uniform. Codes outside the namespace are rejected at registration time (not at run time, which would be a footgun) with a clear <code>CustomDetectorError::CodeOutOfNamespace { code, reason }</code>. The result type is exposed so callers can inspect rejection reasons without trying to parse error strings.</p>
</section>
<section id="findingsink-as-the-only-emission-surface" class="level3">
<h3 class="anchored" data-anchor-id="findingsink-as-the-only-emission-surface">5.2 <code>FindingSink</code> as the only emission surface</h3>
<p>The sink is the restricted finding-emission surface. <code>emit()</code> is the only mutation method:</p>
<div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb7-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">pub</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> emit(</span>
<span id="cb7-2">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">mut</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">self</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb7-3">    severity<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> FindingSeverity<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb7-4">    message<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">impl</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">Into</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">String</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;,</span></span>
<span id="cb7-5">    column<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">Option</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">String</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;,</span></span>
<span id="cb7-6">    row_range<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">Option</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>(<span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">usize</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">usize</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;,</span></span>
<span id="cb7-7">    evidence<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">Vec</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>FindingEvidence<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;,</span></span>
<span id="cb7-8">    sample_size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">u64</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb7-9">) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">usize</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span></code></pre></div>
<p>All canonicalization (fingerprint composition, byte ordering) happens inside <code>emit</code>. The detector cannot construct findings directly. This is what makes determinism a <em>structural</em> guarantee rather than a <em>trust</em> contract — you can’t write a non-deterministic custom detector that produces non-deterministic output, even if you try. The sink also rejects two cases:</p>
<ul>
<li><strong>Empty message.</strong> A finding without a message is unhelpful; rejected with <code>CustomDetectorError::EmptyMessage</code>.</li>
<li><strong>Non-<code>Info</code> severity from an axes-empty detector.</strong> A detector that declared <code>BeliefAxisSet::NONE</code> produces findings that affect no belief score (advisory-only). Allowing it to emit Warning- or Error-severity findings would be a self-contradiction — the finding would appear in the report but couldn’t drag the score. Rejected with <code>CustomDetectorError::NonAdvisoryAxesEmpty</code>.</li>
</ul>
<p>Both errors are <em>recorded</em>, not raised. The detector’s <code>run()</code> continues; the framework drains the errors after <code>run()</code> returns and surfaces them in <code>LockeReport.assumptions</code>.</p>
</section>
<section id="belief-axis-routing-via-beliefaxisset" class="level3">
<h3 class="anchored" data-anchor-id="belief-axis-routing-via-beliefaxisset">5.3 Belief-axis routing via <code>BeliefAxisSet</code></h3>
<p>The 8 Locke belief axes (schema, missingness, drift, leakage, lineage, sample, duplication, constraint) are represented as a <code>u16</code> bitset:</p>
<div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb8-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">pub</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">struct</span> BeliefAxisSet(<span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">pub</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">u16</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb8-2"></span>
<span id="cb8-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">impl</span> BeliefAxisSet <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb8-4">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">pub</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">const</span> SCHEMA<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">Self</span>      <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">Self</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;&lt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb8-5">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">pub</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">const</span> MISSINGNESS<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">Self</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">Self</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;&lt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb8-6">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">pub</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">const</span> DRIFT<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">Self</span>       <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">Self</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;&lt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb8-7">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">pub</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">const</span> LEAKAGE<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">Self</span>     <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">Self</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;&lt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb8-8">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// ...</span></span>
<span id="cb8-9">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">pub</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">const</span> NONE<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">Self</span>        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">Self</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb8-10"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div>
<p>When the framework runs a custom detector, it records the detector’s declared axes in a new field on <code>LockeReport</code>:</p>
<div class="sourceCode" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb9-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">pub</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">struct</span> LockeReport <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb9-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// ... existing fields ...</span></span>
<span id="cb9-3">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">pub</span> custom_axis_assignments<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> BTreeMap<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">String</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> BeliefAxisSet<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;,</span></span>
<span id="cb9-4"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div>
<p>The belief composition (the meet-semilattice algebra over <code>(BeliefScore, meet, ⊤=[1,...,1])</code> from <a href="../../../blog/posts/cjc-locke-v0.1-released/index.html#composing-evidence-across-stages-v07">ADR-0036</a>) gets one new closure:</p>
<div class="sourceCode" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb10-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> custom_contains <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span>code<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">str</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> BeliefAxisSet<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">bool</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb10-2">    report<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>custom_axis_assignments</span>
<span id="cb10-3">        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>get(code)</span>
<span id="cb10-4">        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>map(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span>axes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span> axes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>contains(axis))</span>
<span id="cb10-5">        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>unwrap_or(<span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">false</span>)</span>
<span id="cb10-6"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">};</span></span></code></pre></div>
<p>Then each per-axis filter ORs the existing built-in code matchers with a <code>custom_contains(code, AXIS)</code> check. Built-in codes still match by name; custom codes match by their declared axes. No restructuring of the algebra was needed — the closure-based filter pattern in <code>penalty_from_findings_with_model</code> extends cleanly.</p>
<p>The empty-map default is byte-identical to pre-v0.8 belief composition when no custom detectors are registered.</p>
</section>
<section id="the-python-bridge-structural-determinism-across-the-ffi-boundary" class="level3">
<h3 class="anchored" data-anchor-id="the-python-bridge-structural-determinism-across-the-ffi-boundary">5.4 The Python bridge: structural determinism across the FFI boundary</h3>
<p>The most architecturally interesting piece is the Python side. The naive PyO3 approach — give Python a mutable reference to the Rust <code>FindingSink</code> — runs into lifetime restrictions (<code>#[pyclass]</code> can’t hold borrowed references that might outlive the Python object). The path I took is a two-staged collector:</p>
<div class="sourceCode" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb11-1"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">#[</span>pyclass<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">(</span>name <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"CustomDetectorSink"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> module <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"cjc_locke"</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">)]</span></span>
<span id="cb11-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">struct</span> PyDetectorSink <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb11-3">    pending<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">Vec</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>PyEmittedFinding<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;,</span></span>
<span id="cb11-4">    code<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">String</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb11-5">    axes_empty<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">bool</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb11-6">    last_error<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">Option</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">String</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;,</span></span>
<span id="cb11-7"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div>
<p>The Python detector’s <code>run(df, sink)</code> receives a <code>PyDetectorSink</code> it can call <code>emit(...)</code> on; the <code>emit</code> pushes into <code>pending</code> but does NOT yet construct a canonical <code>ValidationFinding</code>. After Python’s <code>run()</code> returns, the Rust adapter drains <code>pending</code> into the real Rust <code>FindingSink</code>:</p>
<div class="sourceCode" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb12-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">impl</span> CustomDetector <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">for</span> PyCustomDetectorAdapter <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb12-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> run(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">self</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> df<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>DataFrame<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> sink<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">mut</span> FindingSink) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb12-3">        <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">Python::</span>with_gil(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span>py<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb12-4">            <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> view <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">Py::</span>new(py<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> PyDetectorDataFrame <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> inner<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">Arc::</span>new(df<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>clone()) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">?;</span></span>
<span id="cb12-5">            <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> py_sink <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">Py::</span>new(py<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> PyDetectorSink <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/* ... */</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">?;</span></span>
<span id="cb12-6">            <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">self</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>instance<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>bind(py)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>call_method1(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"run"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> (view<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> py_sink<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>clone_ref(py)))<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">?;</span></span>
<span id="cb12-7">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Drain pending emissions into the Rust sink. Canonicalization</span></span>
<span id="cb12-8">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// happens at the Rust-side emit call.</span></span>
<span id="cb12-9">            <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">for</span> emission <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">in</span> py_sink<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>bind(py)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>borrow()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>pending<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>clone() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb12-10">                sink<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>emit(emission<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>severity<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> emission<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>message<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> emission<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>column<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/* ... */</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb12-11">            <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb12-12">        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb12-13">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb12-14"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div>
<p>This is the architectural sleight-of-hand: <strong>the framework canonicalizes the Python detector’s findings on the Rust side after Python returns</strong>. The user-supplied Python code can be as non-deterministic as it wants — <code>random.shuffle</code>, <code>set</code> iteration, <code>dict</code> order from <code>PYTHONHASHSEED</code> — and the framework still produces byte-identical reports across runs as long as the <em>set</em> of findings is the same.</p>
<p>This is provable. The pytest in <code>python/tests/test_custom_detectors.py</code>:</p>
<div class="sourceCode" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb13-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">class</span> _ShuffleEmissionDetector(cjc_locke.CustomDetector):</span>
<span id="cb13-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Emits findings in a randomised order each run."""</span></span>
<span id="cb13-3">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, seed: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>):</span>
<span id="cb13-4">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._rng <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> random.Random(seed)</span>
<span id="cb13-5"></span>
<span id="cb13-6">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">def</span> code(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>): <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">return</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"E9502"</span></span>
<span id="cb13-7">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">def</span> belief_axes(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>): <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">return</span> [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"leakage"</span>]</span>
<span id="cb13-8"></span>
<span id="cb13-9">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">def</span> run(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, df, sink):</span>
<span id="cb13-10">        names <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(df.column_names())</span>
<span id="cb13-11">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._rng.shuffle(names)            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># explicit non-determinism</span></span>
<span id="cb13-12">        <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">for</span> n <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">in</span> names:</span>
<span id="cb13-13">            sink.emit(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"warning"</span>, <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"shuffle finding on `</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>n<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">`"</span>,</span>
<span id="cb13-14">                      column<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>n, sample_size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>df.n_rows)</span>
<span id="cb13-15"></span>
<span id="cb13-16"></span>
<span id="cb13-17"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">def</span> test_determinism_under_emission_shuffle(lc_like_data):</span>
<span id="cb13-18">    d1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> _ShuffleEmissionDetector(seed<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb13-19">    d2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> _ShuffleEmissionDetector(seed<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># different shuffle seed</span></span>
<span id="cb13-20">    r1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cjc_locke.validate(lc_like_data, custom_detectors<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[d1])</span>
<span id="cb13-21">    r2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cjc_locke.validate(lc_like_data, custom_detectors<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[d2])</span>
<span id="cb13-22">    <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">assert</span> r1.to_json() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> r2.to_json()     <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># byte-identical</span></span></code></pre></div>
<p>This is the credibility-anchor test for the whole Python bridge. The detector emits findings in a <code>random.Random</code>-shuffled order each call — two different shuffle seeds in two different runs — and the resulting JSON output is byte-identical. The framework’s canonical sort on the Rust side masks Python’s emission order entirely.</p>
<p>Without this property, “Locke determinism survives the Python boundary” would be a brittle promise. With it, it’s a structural guarantee.</p>
<p>The data path that makes that work:</p>
<div class="cell">
<div class="cell-output-display">
<div>
<div>
<pre class="mermaid mermaid-js">sequenceDiagram
    autonumber
    participant Py as Python user code
    participant Sink as PyDetectorSink&lt;br/&gt;(collector)
    participant Adapter as PyCustomDetectorAdapter&lt;br/&gt;(implements Rust trait)
    participant Rust as Rust FindingSink
    participant Report as LockeReport JSON

    Note over Py: Free to use random.shuffle,&lt;br/&gt;set iter, dict order
    Py-&gt;&gt;Sink: sink.emit("warning", msg₁, ...)
    Py-&gt;&gt;Sink: sink.emit("warning", msg₃, ...)
    Py-&gt;&gt;Sink: sink.emit("warning", msg₂, ...)
    Note over Sink: pending = [f₁, f₃, f₂]&lt;br/&gt;non-canonical order
    Py--&gt;&gt;Adapter: run() returns
    Adapter-&gt;&gt;Rust: drain pending → emit each
    Note over Rust: canonical fingerprint&lt;br/&gt;per finding
    Rust-&gt;&gt;Report: findings.sort_by(sort_key)
    Note over Report: byte-identical across runs,&lt;br/&gt;regardless of Python order
</pre>
</div>
</div>
</div>
</div>
<p>The “non-canonical order” → “canonical sort” transition is the structural fix. The user is free to emit in whatever order they like; the framework reorders before the bytes leave Rust.</p>
</section>
<section id="the-python-abc" class="level3">
<h3 class="anchored" data-anchor-id="the-python-abc">5.5 The Python ABC</h3>
<p>The Python facade is intentionally small. Three methods, no metaclass magic:</p>
<div class="sourceCode" id="cb14" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb14-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">class</span> CustomDetector:</span>
<span id="cb14-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Base class for user-defined Locke detectors (ADR-0041)."""</span></span>
<span id="cb14-3"></span>
<span id="cb14-4">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">def</span> code(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb14-5">        <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">NotImplementedError</span>(...)</span>
<span id="cb14-6"></span>
<span id="cb14-7">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">def</span> belief_axes(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> List[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>]:</span>
<span id="cb14-8">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Names from {schema, missingness, drift, leakage, lineage,</span></span>
<span id="cb14-9">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># sample, duplication, constraint}. Empty list = advisory-only.</span></span>
<span id="cb14-10">        <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">NotImplementedError</span>(...)</span>
<span id="cb14-11"></span>
<span id="cb14-12">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">def</span> run(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, df, sink) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>:</span>
<span id="cb14-13">        <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">NotImplementedError</span>(...)</span></code></pre></div>
<p>A working detector subclass is ~10 lines:</p>
<div class="sourceCode" id="cb15" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb15-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">class</span> PostOriginationByName(cjc_locke.CustomDetector):</span>
<span id="cb15-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">def</span> code(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb15-3">        <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">return</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"E9500"</span></span>
<span id="cb15-4"></span>
<span id="cb15-5">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">def</span> belief_axes(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>]:</span>
<span id="cb15-6">        <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">return</span> [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"leakage"</span>]</span>
<span id="cb15-7"></span>
<span id="cb15-8">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">def</span> run(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, df, sink) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>:</span>
<span id="cb15-9">        <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">for</span> col <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">in</span> df.column_names():</span>
<span id="cb15-10">            <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">if</span> col.startswith((<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"total_"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"last_pymnt_"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"recoveries"</span>)):</span>
<span id="cb15-11">                sink.emit(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"error"</span>,</span>
<span id="cb15-12">                          <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"`</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>col<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">` matches a post-origination naming pattern"</span>,</span>
<span id="cb15-13">                          column<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>col, sample_size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>df.n_rows)</span></code></pre></div>
<p>Wiring it into a validate call:</p>
<div class="sourceCode" id="cb16" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb16-1">report <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cjc_locke.validate(</span>
<span id="cb16-2">    data,</span>
<span id="cb16-3">    label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"lc"</span>,</span>
<span id="cb16-4">    custom_detectors<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[PostOriginationByName()],</span>
<span id="cb16-5">)</span></code></pre></div>
<p>Axis names cross the boundary as strings. Unknown names (e.g.&nbsp;<code>"nonsense"</code>) raise <code>ValueError</code> at registration — the underlying <code>BeliefAxisSet::from_names</code> returns a structured <code>Err</code> which the bridge converts to a <code>PyValueError</code>.</p>
</section>
<section id="the-4-way-lc-re-measurement" class="level3">
<h3 class="anchored" data-anchor-id="the-4-way-lc-re-measurement">5.6 The 4-way LC re-measurement</h3>
<p>The reason the layer exists is to close the LC gap. The Rust-side reference implementation is in the demo crate itself (<code>demos/lendingclub/src/lib.rs</code>):</p>
<div class="sourceCode" id="cb17" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb17-1"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">#[</span>derive<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">(</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">Debug</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">Default</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">)]</span></span>
<span id="cb17-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">pub</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">struct</span> PostOriginationByNameDetector<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb17-3"></span>
<span id="cb17-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">impl</span> PostOriginationByNameDetector <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb17-5">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">pub</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> is_post_origination(name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">bool</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb17-6">        <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">const</span> PREFIX_PATTERNS<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">str</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>[</span>
<span id="cb17-7">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"total_"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"last_pymnt_"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"last_fico_range_"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb17-8">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"out_prncp"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"debt_settlement_"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb17-9">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"hardship_"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"settlement_"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb17-10">        ]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb17-11">        <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">const</span> EXACT_PATTERNS<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">str</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>[</span>
<span id="cb17-12">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"recoveries"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"collection_recovery_fee"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"debt_settlement_flag"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb17-13">        ]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb17-14">        EXACT_PATTERNS<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>iter()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>any(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span>p<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>p <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> name)</span>
<span id="cb17-15">            <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">||</span> PREFIX_PATTERNS<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>iter()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>any(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span>p<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span> name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>starts_with(p))</span>
<span id="cb17-16">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb17-17"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb17-18"></span>
<span id="cb17-19"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">impl</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">cjc_locke::custom_detector::</span>CustomDetector <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">for</span> PostOriginationByNameDetector <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb17-20">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> code(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">'static</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">str</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"E9500"</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb17-21">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> belief_axes(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> BeliefAxisSet <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">BeliefAxisSet::</span>LEAKAGE <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb17-22">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> run(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">self</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> df<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>DataFrame<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> sink<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">mut</span> FindingSink) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb17-23">        <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">for</span> (name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> _col) <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">in</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>df<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>columns <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb17-24">            <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">if</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">Self</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span>is_post_origination(name) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb17-25">                sink<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>emit(</span>
<span id="cb17-26">                    <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">FindingSeverity::</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">Error</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb17-27">                    <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">format!</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"column `{}` matches a known post-origination naming pattern"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> name)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb17-28">                    <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">Some</span>(name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>clone())<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb17-29">                    <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">None</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb17-30">                    <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">vec!</span>[]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb17-31">                    df<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>nrows() <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">as</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">u64</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb17-32">                )<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb17-33">            <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb17-34">        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb17-35">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb17-36"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div>
<p>The CLI gets a <code>--use-custom-detectors</code> flag that wires this detector into <code>ValidateOptions::custom_detectors</code>. On the full LC dataset:</p>
<blockquote class="blockquote">
<p><code>E9500 × 39</code> — the custom detector fires 39 times, flagging 39 columns by name.</p>
</blockquote>
<p>The honest-model harness gets a <code>--from-report &lt;path&gt;</code> flag that reads the report JSON, extracts every column with an E9500 <em>or</em> E9061 finding, and uses that as the exclusion set for a fourth model variant.</p>
<p>The four-way result, same 200K-row sample, same seed=42:</p>
<pre class="text"><code>=== Summary ===
variant                     |AUC|    n_train     n_test          p   iter
pre-Locke (naive)          0.9993     140000      60000         87    100
Locke-filtered             0.9995     140000      60000         84    100
Locke + custom det.        0.7388     140000      60000         68     17  ← closes the gap inside Locke
domain-honest              0.7394     140000      60000         75     14</code></pre>
<figure style="margin: 2em 0;" class="figure">
<svg viewbox="0 0 720 380" xmlns="http://www.w3.org/2000/svg" aria-label="Bar chart of the four-way honest-model AUC results">
<!-- Title -->
</svg><p><text x="360" y="22" text-anchor="middle" font-size="15" font-weight="600" fill="currentColor">Honest-model |AUC| — four-way comparison</text> <text x="360" y="40" text-anchor="middle" font-size="11" fill="currentColor" opacity="0.7">200K-row LendingClub sample, seed=42, test fraction 0.3</text></p>
<!-- Plot area: x 80-700, y 70-310 (height 240 = AUC of 1.0) -->
<!-- Literature band 0.69–0.74 → y 226.4 to 238.4 -->
<p><rect x="80" y="226.4" width="620" height="12" fill="#2d8b50" opacity="0.12"> <text x="694" y="234" text-anchor="end" font-size="10" fill="currentColor" opacity="0.65">literature band 0.69–0.74</text></rect></p>
<!-- Y axis ticks at 0.0, 0.2, 0.4, 0.6, 0.8, 1.0 -->
<p><line x1="80" y1="70" x2="80" y2="310" stroke="currentColor" stroke-opacity="0.4"> <line x1="80" y1="310" x2="700" y2="310" stroke="currentColor" stroke-opacity="0.4"> <line x1="75" y1="70" x2="80" y2="70" stroke="currentColor" stroke-opacity="0.4"> <text x="70" y="74" text-anchor="end" font-size="10" fill="currentColor">1.0</text> <line x1="75" y1="118" x2="80" y2="118" stroke="currentColor" stroke-opacity="0.4"> <text x="70" y="122" text-anchor="end" font-size="10" fill="currentColor">0.8</text> <line x1="75" y1="166" x2="80" y2="166" stroke="currentColor" stroke-opacity="0.4"> <text x="70" y="170" text-anchor="end" font-size="10" fill="currentColor">0.6</text> <line x1="75" y1="214" x2="80" y2="214" stroke="currentColor" stroke-opacity="0.4"> <text x="70" y="218" text-anchor="end" font-size="10" fill="currentColor">0.4</text> <line x1="75" y1="262" x2="80" y2="262" stroke="currentColor" stroke-opacity="0.4"> <text x="70" y="266" text-anchor="end" font-size="10" fill="currentColor">0.2</text> <line x1="75" y1="310" x2="80" y2="310" stroke="currentColor" stroke-opacity="0.4"> <text x="70" y="314" text-anchor="end" font-size="10" fill="currentColor">0.0</text></line></line></line></line></line></line></line></line></p>
<!-- Bars: width 110, x positions 110, 270, 430, 590; bar height = AUC * 240 -->
<!-- Pre-Locke 0.9993 → height 239.83 → y 70.17 -->
<p><rect x="110" y="70.17" width="110" height="239.83" fill="#c1432d"> <text x="165" y="62" text-anchor="middle" font-size="13" font-weight="700" fill="currentColor">0.9993</text> <text x="165" y="332" text-anchor="middle" font-size="11" fill="currentColor">Pre-Locke (naive)</text> <text x="165" y="346" text-anchor="middle" font-size="9" fill="currentColor" opacity="0.7">p=87, iter=100 cap</text></rect></p>
<!-- Locke-filtered 0.9995 → height 239.88 → y 70.12 -->
<p><rect x="270" y="70.12" width="110" height="239.88" fill="#c1432d"> <text x="325" y="62" text-anchor="middle" font-size="13" font-weight="700" fill="currentColor">0.9995</text> <text x="325" y="332" text-anchor="middle" font-size="11" fill="currentColor">Locke-filtered</text> <text x="325" y="346" text-anchor="middle" font-size="9" fill="currentColor" opacity="0.7">E9061 only, p=84</text></rect></p>
<!-- Locke + custom 0.7388 → height 177.31 → y 132.69 -->
<p><rect x="430" y="132.69" width="110" height="177.31" fill="#2d8b50"> <text x="485" y="125" text-anchor="middle" font-size="13" font-weight="700" fill="currentColor">0.7388</text> <text x="485" y="332" text-anchor="middle" font-size="11" fill="currentColor">Locke + custom det.</text> <text x="485" y="346" text-anchor="middle" font-size="9" fill="currentColor" opacity="0.7">E9500 ∪ E9061, p=68</text></rect></p>
<!-- Domain-honest 0.7394 → height 177.46 → y 132.54 -->
<p><rect x="590" y="132.54" width="110" height="177.46" fill="#2d8b50"> <text x="645" y="125" text-anchor="middle" font-size="13" font-weight="700" fill="currentColor">0.7394</text> <text x="645" y="332" text-anchor="middle" font-size="11" fill="currentColor">Domain-honest</text> <text x="645" y="346" text-anchor="middle" font-size="9" fill="currentColor" opacity="0.7">hand-curated, p=75</text></rect></p>
<!-- Bracket connecting bars 3 and 4 with the "0.0006 gap" annotation -->
<line x1="485" y1="115" x2="485" y2="105" stroke="currentColor" stroke-opacity="0.5"> <line x1="485" y1="105" x2="645" y2="105" stroke="currentColor" stroke-opacity="0.5"> <line x1="645" y1="105" x2="645" y2="115" stroke="currentColor" stroke-opacity="0.5"> <text x="565" y="98" text-anchor="middle" font-size="10" fill="currentColor" opacity="0.8" font-style="italic">|Δ| = 0.0006</text>

<figcaption style="text-align: center; font-size: 0.9em; opacity: 0.85; margin-top: 0.4em;" class="figure-caption">
The custom detector (bar #3) recovers the honest credit-risk baseline (bar #4) inside Locke configuration. Bars #1 and #2 are inflated by leakage; bars #3 and #4 land within 0.0006 of each other, both inside the published credit-risk literature band of 0.69–0.74.
</figcaption>
</line></line></line></figure>
<p><strong><code>Locke + custom det.</code> at <code>|AUC| = 0.7388</code> lands within 0.0006 of the hand-curated <code>domain-honest</code> baseline at <code>0.7394</code>.</strong> The custom detector recovered honest credit-risk performance entirely <em>inside</em> Locke configuration — using a ~30-LOC detector to encode the same domain knowledge that previously lived as a separate hard-coded exclusion list in the modeling code.</p>
<p>Two further notes on this table:</p>
<ul>
<li><strong>The custom-detector variant uses FEWER features (68 vs 75).</strong> It’s <em>more</em> aggressive about exclusion (39 columns flagged vs the handoff’s 12, because the custom detector catches <code>sec_app_*</code>, <code>hardship_*</code>, <code>settlement_*</code> etc. that the handoff’s list didn’t anticipate). And yet it achieves essentially the same AUC. That’s empirical evidence the extra exclusions weren’t carrying useful signal — the custom detector is at least as conservative as expert curation.</li>
<li><strong>IRLS convergence speed is a real diagnostic.</strong> The leaky models hit the 100-iter cap because the multicollinear post-origination cluster makes <code>X^T W X</code> near-singular. The honest variants both converge in 14–17 iterations because removing that cluster conditions the matrix well. Convergence speed is a proxy for “how well did the exclusion set capture the multicollinear leakage subspace” — and Locke + custom converged in 17 iters, comparable to domain-honest’s 14.</li>
</ul>
</section>
<section id="tests-in-three-columns" class="level3">
<h3 class="anchored" data-anchor-id="tests-in-three-columns">5.7 Tests, in three columns</h3>
<table class="table">
<colgroup>
<col style="width: 17%">
<col style="width: 43%">
<col style="width: 4%">
<col style="width: 34%">
</colgroup>
<thead>
<tr class="header">
<th>Layer</th>
<th>File</th>
<th>Tests</th>
<th>Kind</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Rust unit</td>
<td><code>crates/cjc-locke/src/custom_detector.rs</code></td>
<td>15</td>
<td>Trait, sink, namespace, axis set</td>
</tr>
<tr class="even">
<td>Rust integration</td>
<td><code>tests/locke/custom_detector_tests.rs</code></td>
<td>14</td>
<td>End-to-end + 3 proptest + 3 bolero fuzz</td>
</tr>
<tr class="odd">
<td>Python end-to-end</td>
<td><code>python/tests/test_custom_detectors.py</code></td>
<td>14</td>
<td>Registration, sink contract, emission shuffle</td>
</tr>
<tr class="even">
<td>Demo</td>
<td><code>demos/lendingclub/src/lib.rs</code> (in-module)</td>
<td>2</td>
<td>Pattern detector smoke tests</td>
</tr>
<tr class="odd">
<td><strong>Total new</strong></td>
<td></td>
<td><strong>45</strong></td>
<td></td>
</tr>
</tbody>
</table>
<p>The integration test file leans on a <code>NameMatchDetector</code> helper plus <code>ProptestDetector</code> plus <code>ShuffleEmissionDetector</code> — three detector subclasses that exercise different aspects of the trait. Three of the integration tests are bolero fuzz tests that exercise the registration namespace validator (<code>validate_custom_code</code>) and the emit input validation under arbitrary byte sequences. None of the fuzz inputs panic; either the input is valid (no error recorded) or it’s invalid (error recorded, no finding constructed). Determinism is also exercised in the Rust proptest: random axes + emit count + arbitrary DataFrame → byte-identical JSON across runs.</p>
<p>Workspace test totals after ADR-0041 alone: <code>cjc-locke</code> lib passes 423 (was 408, +15); <code>tests/locke</code> integration passes 291 (was 277, +14); zero regressions in pre-existing tests.</p>
</section>
</section>
<section id="adr-0042-the-str-with-sentinels-gap-which-turned-out-to-be-two-gaps" class="level2">
<h2 class="anchored" data-anchor-id="adr-0042-the-str-with-sentinels-gap-which-turned-out-to-be-two-gaps">6. ADR-0042: The Str-with-sentinels gap (which turned out to be two gaps)</h2>
<p>The ADR-0041 retrospective in <code>cross_validate.md</code> ended with a “Suggested Locke adjustments” section. The first item:</p>
<blockquote class="blockquote">
<p><strong>Str-with-sentinels columns silently bypass three detectors</strong> (E9070 conditional missingness, E9039 numeric drift, E9050+ temporal). The CSV reader infers a column as <code>Str</code> if the first row has empty/sentinel values. Once typed <code>Str</code>, the column carries the sentinel string instead of NaN, and the Float-only detectors skip it. <code>annual_inc_joint</code> is the canonical example — logically numeric, but the reader sees <code>""</code> in row 1 and types it <code>Str</code>.</p>
</blockquote>
<p>The intent was to address it before Project B (NYC Taxi), where every drift-detection scenario depends on Float typing. The investigation surfaced something the brief hadn’t anticipated.</p>
<section id="gap-one-type-inference-from-the-first-data-row" class="level3">
<h3 class="anchored" data-anchor-id="gap-one-type-inference-from-the-first-data-row">6.1 Gap one: type inference from the first data row</h3>
<p><code>cjc-data::CsvReader</code> types each column from its first data row only. For <code>annual_inc_joint</code>, the first row’s value is <code>""</code> (empty string — the loan was an individual application, not joint). <code>infer_type("")</code> returns <code>InferredType::Str</code>. Once the column is <code>Str</code>, every subsequent numeric value (<code>"100000"</code>, <code>"50000.0"</code>, etc.) gets stored as a string. Downstream Float-only detectors skip the column.</p>
<p>The fix lives in a new module <code>crates/cjc-locke/src/auto_promote.rs</code>. Each <code>Str</code> column is scanned and each value classified as:</p>
<ul>
<li><strong>Sentinel</strong> — matches the existing <code>BUILTIN_STRING_SENTINELS</code> (<code>""</code>, <code>"?"</code>, <code>"NA"</code>, <code>"N/A"</code>, <code>"NULL"</code>, <code>"null"</code>, <code>"nan"</code>, <code>"NaN"</code>, <code>"None"</code>, <code>"-"</code>) or <code>cfg.additional_sentinels</code>.</li>
<li><strong>Parseable</strong> — <code>str::parse::&lt;f64&gt;()</code> succeeds. Accepts integer and float literals, including scientific notation (<code>1.5e-3</code>).</li>
<li><strong>Other</strong> — text, malformed numbers, anything else.</li>
</ul>
<p>A column is <strong>promoted</strong> when:</p>
<ul>
<li><code>non_sentinel_rows &gt;= cfg.min_non_sentinel_rows_for_promotion</code> (default 10), AND</li>
<li><code>n_parseable / non_sentinel &gt;= cfg.min_parseable_fraction_for_promotion</code> (default 0.80).</li>
</ul>
<p>Promoted columns are rebuilt as <code>Column::Float</code> where sentinels and unparseable values become <code>NaN</code>. Each emits one <code>E9009</code> (<code>Info</code> severity) finding with full evidence (counts, fraction).</p>
<div class="sourceCode" id="cb19" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb19-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">pub</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> auto_promote_str_columns(</span>
<span id="cb19-2">    df<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>DataFrame<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb19-3">    cfg<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>ValidationConfig<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb19-4">) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> (<span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">Option</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>DataFrame<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">Vec</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>ValidationFinding<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span></code></pre></div>
<p>The <code>Option&lt;DataFrame&gt;</code> return is the zero-allocation no-op path: if no column qualifies, returns <code>None</code> and the caller uses the original DataFrame without any clone cost. The wiring in <code>api::validate</code> is the <code>working_df</code> pattern:</p>
<div class="sourceCode" id="cb20" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb20-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> (maybe_promoted_df<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> promo_findings) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span></span>
<span id="cb20-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">crate</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">::auto_promote::</span>auto_promote_str_columns(df<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>opts<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>config)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb20-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> working_df<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>DataFrame <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> maybe_promoted_df<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>as_ref()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>unwrap_or(df)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb20-4"></span>
<span id="cb20-5"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">mut</span> findings <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> validate_dataframe(working_df<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">...</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb20-6">findings<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>extend(promo_findings)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb20-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// All downstream detectors + column reports + custom detectors use working_df.</span></span></code></pre></div>
<p>The report’s <code>input.column_types</code> reflects post-promotion types, so JSON consumers see the right view. The default <code>auto_promote_str_to_float = true</code> was the deliberate choice — a flag that’s off by default doesn’t fix the gap for users who don’t know it exists.</p>
</section>
<section id="gap-two-e9070-was-defined-but-never-invoked" class="level3">
<h3 class="anchored" data-anchor-id="gap-two-e9070-was-defined-but-never-invoked">6.2 Gap two: E9070 was defined but never invoked</h3>
<p>Here’s where the investigation got interesting. After adding the auto-promotion, I ran the LC demo with promotion enabled. E9070 (conditional missingness) didn’t fire. Promotion was working — column types changed, joint columns were <code>Float</code>, NaN positions were correct — but E9070 stayed at zero.</p>
<p>The cause was simpler and more embarrassing than the typing fix:</p>
<div class="sourceCode" id="cb21" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb21-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> grep <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-n</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"detect_conditional_missingness\b"</span> crates/cjc-locke/src/validation.rs</span>
<span id="cb21-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">1425:pub</span> fn detect_conditional_missingness<span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span></span>
<span id="cb21-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">2398:</span>    let r = detect_conditional_missingness<span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">&amp;</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">df</span>, ...<span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">);</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># inside a unit test</span></span>
<span id="cb21-4"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">2420:</span>    let r = detect_conditional_missingness<span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">&amp;</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">df</span>, ...<span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">);</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># inside a unit test</span></span>
<span id="cb21-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">2771:</span>    let r = detect_conditional_missingness<span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span>            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># inside a unit test</span></span></code></pre></div>
<p><code>detect_conditional_missingness</code> was defined, exported, unit-tested. But the call site inside <code>validate_dataframe</code> — the function that orchestrates the actual validation pipeline — never invoked it. The detector had been silently zero-firing for everyone, on every input, since v0.6.4. The unit tests that exercised it caught the function’s own behavior; they didn’t catch the missing wire.</p>
<p>The fix is one <code>out.extend(...)</code> line:</p>
<div class="sourceCode" id="cb22" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb22-1">out<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>extend(detect_missingness(df<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> cfg<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>effective_masks))<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb22-2">out<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>extend(detect_conditional_missingness(   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// &lt;-- new</span></span>
<span id="cb22-3">    df<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb22-4">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">ConditionalMissingnessConfig::</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">default</span>()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb22-5">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>effective_masks<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb22-6">))<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb22-7">out<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>extend(detect_duplicates_full_row(df<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> cfg))<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span></code></pre></div>
<p>The detector honors <code>effective_masks</code> (the merged caller + auto-sentinel mask map), so it works for both Float-NaN columns and Str-with-mask columns. The opt-out path — disable auto-promotion via <code>ValidationConfig::auto_promote_str_to_float = false</code> — still fires E9070 via the sentinel-mask path. Both code paths produce findings; the auto-promotion makes them more numerous and the per-column missingness rates cleaner.</p>
</section>
<section id="the-real-lc-measurement-post-fix" class="level3">
<h3 class="anchored" data-anchor-id="the-real-lc-measurement-post-fix">6.3 The real LC measurement post-fix</h3>
<p>Re-running <code>lendingclub_demo</code> on the full 2.26M-row CSV with v0.8 enabled:</p>
<table class="table">
<colgroup>
<col style="width: 8%">
<col style="width: 36%">
<col style="width: 36%">
<col style="width: 17%">
</colgroup>
<thead>
<tr class="header">
<th>Code</th>
<th>Pre-v0.8 (2026-06-01)</th>
<th>Post-v0.8 (2026-06-02)</th>
<th>What changed</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>E9008</td>
<td>55</td>
<td>33</td>
<td>Some Str cols promoted; E9009 supersedes E9008 for those</td>
</tr>
<tr class="even">
<td>E9009</td>
<td>not implemented</td>
<td><strong>22</strong></td>
<td>Auto-promoted columns</td>
</tr>
<tr class="odd">
<td>E9070</td>
<td><strong>0</strong> (silently never invoked)</td>
<td><strong>1879</strong></td>
<td>Now fires across ~54 unique columns</td>
</tr>
<tr class="even">
<td>E9001</td>
<td>55</td>
<td>55</td>
<td>unchanged</td>
</tr>
<tr class="odd">
<td>E9061</td>
<td>3</td>
<td>3</td>
<td>unchanged</td>
</tr>
<tr class="even">
<td>E9072</td>
<td>2</td>
<td>2</td>
<td>unchanged</td>
</tr>
<tr class="odd">
<td>total</td>
<td>577</td>
<td>2421</td>
<td>mostly E9070 + a few E9009 + E9024/E9040 ticks</td>
</tr>
</tbody>
</table>
<figure style="margin: 2em 0;" class="figure">
<svg viewbox="0 0 720 320" xmlns="http://www.w3.org/2000/svg" aria-label="Horizontal bar chart of LC finding counts that changed between pre-v0.8 and post-v0.8">
<!-- Title -->
</svg><p><text x="360" y="22" text-anchor="middle" font-size="15" font-weight="600" fill="currentColor">LC finding counts that changed across v0.7 → v0.8</text> <text x="360" y="40" text-anchor="middle" font-size="11" fill="currentColor" opacity="0.7">Full 2.26M-row dataset; codes unchanged across the release omitted</text></p>
<!-- Legend -->
<p><rect x="480" y="56" width="14" height="10" fill="#9ca3af"> <text x="498" y="65" font-size="11" fill="currentColor">pre-v0.8</text> <rect x="568" y="56" width="14" height="10" fill="#2d6db5"> <text x="586" y="65" font-size="11" fill="currentColor">post-v0.8 (ADR-0042)</text></rect></rect></p>
<!-- Plot area: x label column 0-150, bars start at 160, end at 700 (540px wide) -->
<!-- Max value 1879; scale: 1px = 3.479 counts -->
<!-- Pre and post bars stacked vertically per row (height 13 each, 2px gap) -->
<!-- ===== Row 1: E9008 (55 → 33, decreased) ===== -->
<p><text x="150" y="100" text-anchor="end" font-size="13" font-weight="600" fill="currentColor">E9008</text> <text x="150" y="116" text-anchor="end" font-size="10" fill="currentColor" opacity="0.65">sentinel</text> <rect x="160" y="92" width="15.81" height="13" fill="#9ca3af"> <text x="181" y="103" font-size="11" fill="currentColor">55</text> <rect x="160" y="107" width="9.49" height="13" fill="#2d6db5"> <text x="175" y="118" font-size="11" fill="currentColor">33 (–22)</text></rect></rect></p>
<!-- ===== Row 2: E9009 (0 → 22, NEW) ===== -->
<p><text x="150" y="158" text-anchor="end" font-size="13" font-weight="600" fill="currentColor">E9009</text> <text x="150" y="174" text-anchor="end" font-size="10" fill="currentColor" opacity="0.65">promotion</text> <text x="160" y="160" font-size="11" fill="currentColor" opacity="0.55">— (not implemented)</text> <rect x="160" y="165" width="6.32" height="13" fill="#2d6db5"> <text x="171" y="176" font-size="11" fill="currentColor">22 (new)</text></rect></p>
<!-- ===== Row 3: E9070 (0 → 1879, NEW + dominant) ===== -->
<p><text x="150" y="222" text-anchor="end" font-size="13" font-weight="600" fill="currentColor">E9070</text> <text x="150" y="238" text-anchor="end" font-size="10" fill="currentColor" opacity="0.65">cond. missing</text> <text x="160" y="222" font-size="11" fill="currentColor" opacity="0.55">— (silently never invoked)</text> <rect x="160" y="227" width="540" height="13" fill="#2d6db5"> <text x="694" y="238" text-anchor="end" font-size="12" font-weight="700" fill="#ffffff">1879 (new)</text></rect></p>
<!-- X axis with scale reference -->
<line x1="160" y1="263" x2="700" y2="263" stroke="currentColor" stroke-opacity="0.4"> <text x="160" y="276" font-size="10" text-anchor="middle" fill="currentColor">0</text> <text x="304" y="276" font-size="10" text-anchor="middle" fill="currentColor">500</text> <text x="448" y="276" font-size="10" text-anchor="middle" fill="currentColor">1000</text> <text x="592" y="276" font-size="10" text-anchor="middle" fill="currentColor">1500</text> <text x="700" y="276" font-size="10" text-anchor="end" fill="currentColor">≈1879</text> <text x="430" y="296" font-size="10" text-anchor="middle" fill="currentColor" opacity="0.65">findings (linear scale)</text>

<figcaption style="text-align: center; font-size: 0.9em; opacity: 0.85; margin-top: 0.4em;" class="figure-caption">
Three codes changed between v0.7 and v0.8. <strong>E9008</strong> (sentinel detection) drops from 55 to 33 as some <code>Str</code> columns get promoted to <code>Float</code> via the new <strong>E9009</strong> path (22 columns). <strong>E9070</strong> (conditional missingness) goes from <em>silently never invoked</em> to 1879 firings — every co-missing column pair contributes a finding in both directions. The visual dominance of E9070 in the chart is the dominance the gap fix actually surfaces.
</figcaption>
</line></figure>
<p>The 22 columns the auto-promotion fires on:</p>
<ul>
<li><code>sec_app_*</code> (12 columns) — secondary applicant fields: <code>fico_range_high/low</code>, <code>mort_acc</code>, <code>num_rev_accts</code>, <code>open_acc</code>, <code>open_act_il</code>, <code>inq_last_6mths</code>, <code>chargeoff_within_12_mths</code>, <code>collections_12_mths_ex_med</code>, <code>mths_since_last_major_derog</code>, <code>revol_util</code></li>
<li><code>hardship_*</code> (5 columns) — workout plan fields: <code>amount</code>, <code>dpd</code>, <code>length</code>, <code>payoff_balance_amount</code>, <code>last_payment_amount</code></li>
<li><code>settlement_*</code> (3 columns) — debt settlement fields: <code>amount</code>, <code>percentage</code>, <code>term</code></li>
<li><code>revol_bal_joint</code>, <code>orig_projected_additional_accrued_interest</code>, <code>mths_since_last_record</code></li>
</ul>
<p>E9070’s 1879 firings span ~54 unique columns. Most pairs are within the joint/secondary/hardship/settlement set: every co-missing pair fires twice (a→b and b→a are separate findings). For users who want a smaller set, raising <code>ConditionalMissingnessConfig::implication_threshold</code> above 0.95 or post-hoc filtering by column-pair is the right knob.</p>
</section>
<section id="gap-three-csv-quoted-string-handling-deferred" class="level3">
<h3 class="anchored" data-anchor-id="gap-three-csv-quoted-string-handling-deferred">6.4 Gap three: CSV quoted-string handling (deferred)</h3>
<p><code>annual_inc_joint</code>, <code>dti_joint</code>, <code>verification_status_joint</code> — the three columns the handoff originally named — are <em>NOT</em> in the v0.8 promotion set. Investigation:</p>
<pre class="text"><code>Sampled 50000 rows of annual_inc_joint:
  empty:     49290
  parseable: 248
  other:     462
  non-parseable samples:
    'Individual'
    'Individual'
    'Individual'
    'Individual'
    'Individual'</code></pre>
<p>The non-sentinel rows split 248 parseable vs 462 “Other” — <code>parseable_fraction = 0.349</code>, well below the 0.80 threshold. So promotion correctly declines.</p>
<p>The “Other” values are <code>'Individual'</code> — the string from the LC dataset’s <code>application_type</code> column. They’re bleeding into <code>annual_inc_joint</code> because <code>cjc-data::CsvReader</code> does NOT support RFC-4180 quoted strings. LC’s <code>desc</code> (free-text loan description) column contains embedded commas; without quote-handling, every cell after <code>desc</code> in such a row gets shifted left by however many commas the description contained. <code>application_type</code> (which sits after <code>desc</code> in column order) leaks into <code>annual_inc_joint</code>’s position.</p>
<p>This is a real third gap, but it lives in <code>cjc-data</code>, not <code>cjc-locke</code>. ADR-0042 documents it as a known limitation and bounds it: the parseable-fraction guard correctly refuses to promote a column polluted by column-shift, which is the right defensive behavior. The upstream CSV fix is tracked separately. The right user workaround until then is to pre-clean the CSV with a parser that understands quoting (Python’s <code>csv</code> module, pandas) and pass the cleaned dict to Locke via the Python bridge or via <code>DataFrame::from_columns</code>.</p>
</section>
<section id="discovery-cascade-visualized" class="level3">
<h3 class="anchored" data-anchor-id="discovery-cascade-visualized">6.5 Discovery cascade, visualized</h3>
<div class="cell">
<div class="cell-output-display">
<div>
<div>
<pre class="mermaid mermaid-js">flowchart TD
    A[Handoff: Project A = LC demo&lt;br/&gt;predicted top leakage = payment streams] --&gt;|build demo| B[E9061 fires on last_fico_range_*&lt;br/&gt;not on payment streams]
    B --&gt;|honest-model AUC follow-up| C[Locke-filtered AUC = 0.9995&lt;br/&gt;Locke flags alone don't recover honesty]
    C --&gt;|architectural response| D[ADR-0041:&lt;br/&gt;custom detector extension layer]
    D --&gt;|LC re-measurement| E[Locke + custom AUC = 0.7388&lt;br/&gt;within 0.0006 of domain-honest 0.7394]
    E --&gt;|prepare for Project B| F[Str-with-sentinels gap blocks&lt;br/&gt;NYC Taxi drift detection]
    F --&gt;|investigate| G[Gap 1: CSV type inference from first row]
    F --&gt;|investigate| H[Gap 2: E9070 was never invoked]
    G --&gt; I[ADR-0042: auto_promote_str_columns]
    H --&gt; I
    I --&gt;|re-run LC demo| J[E9009 × 22, E9070 × 1879&lt;br/&gt;+ Gap 3 discovered: CSV quoted strings]
    J --&gt;|out of scope for v0.8| K[Tracked as upstream cjc-data fix]
    style B fill:#fff4d6
    style C fill:#fff4d6
    style F fill:#fff4d6
    style J fill:#fff4d6
</pre>
</div>
</div>
</div>
</div>
<p>Each yellow node is a discovery that drove the next change. The brief at the top would have predicted A → I directly. The path through B, C, F is what actually happened.</p>
</section>
</section>
<section id="the-release" class="level2">
<h2 class="anchored" data-anchor-id="the-release">7. The release</h2>
<table class="table">
<thead>
<tr class="header">
<th>Artifact</th>
<th>Where</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>cjc-locke v0.1.11</code></td>
<td><a href="https://crates.io/crates/cjc-locke/0.1.11" class="uri">https://crates.io/crates/cjc-locke/0.1.11</a></td>
</tr>
<tr class="even">
<td>24 sibling crates at v0.1.11</td>
<td>crates.io (<code>cjc-lang</code>, <code>cjc-data</code>, <code>cjc-runtime</code>, …, one workspace publish)</td>
</tr>
<tr class="odd">
<td><code>cjc-locke v0.1.2</code> (Python)</td>
<td><a href="https://pypi.org/project/cjc-locke/0.1.2/" class="uri">https://pypi.org/project/cjc-locke/0.1.2/</a></td>
</tr>
<tr class="even">
<td>Source code</td>
<td><a href="https://github.com/AdamEzzat1/CJC" class="uri">https://github.com/AdamEzzat1/CJC</a> (PR #18 merged on master)</td>
</tr>
<tr class="odd">
<td>ADR-0041</td>
<td><code>CJC-Lang_Obsidian_Vault/13_ADRs/ADR-0041 Locke v0.8 — Custom Detector Extension Layer.md</code></td>
</tr>
<tr class="even">
<td>ADR-0042</td>
<td><code>CJC-Lang_Obsidian_Vault/13_ADRs/ADR-0042 Locke v0.8 — Str-to-Float Auto-Promotion + E9070 Wiring.md</code></td>
</tr>
<tr class="odd">
<td>LC demo</td>
<td><code>demos/lendingclub/</code> (workspace member, <code>publish = false</code>)</td>
</tr>
</tbody>
</table>
<section id="quick-start-python" class="level3">
<h3 class="anchored" data-anchor-id="quick-start-python">Quick-start (Python)</h3>
<div class="sourceCode" id="cb24" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb24-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">pip</span> install cjc-locke<span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">[</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">numpy</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">]</span></span></code></pre></div>
<div class="sourceCode" id="cb25" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb25-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> cjc_locke</span>
<span id="cb25-2"></span>
<span id="cb25-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">class</span> PostOriginationByName(cjc_locke.CustomDetector):</span>
<span id="cb25-4">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">def</span> code(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb25-5">        <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">return</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"E9500"</span></span>
<span id="cb25-6"></span>
<span id="cb25-7">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">def</span> belief_axes(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>]:</span>
<span id="cb25-8">        <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">return</span> [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"leakage"</span>]</span>
<span id="cb25-9"></span>
<span id="cb25-10">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">def</span> run(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, df, sink) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>:</span>
<span id="cb25-11">        <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">for</span> col <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">in</span> df.column_names():</span>
<span id="cb25-12">            <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">if</span> col.startswith((<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"total_"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"last_pymnt_"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"recoveries"</span>)):</span>
<span id="cb25-13">                sink.emit(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"error"</span>,</span>
<span id="cb25-14">                          <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"`</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>col<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">` matches a post-origination naming pattern"</span>,</span>
<span id="cb25-15">                          column<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>col, sample_size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>df.n_rows)</span>
<span id="cb25-16"></span>
<span id="cb25-17">report <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cjc_locke.validate(</span>
<span id="cb25-18">    {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"loan_amnt"</span>: [...], <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"total_pymnt"</span>: [...], <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dti"</span>: [...]},</span>
<span id="cb25-19">    label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"my-loans"</span>,</span>
<span id="cb25-20">    custom_detectors<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[PostOriginationByName()],</span>
<span id="cb25-21">)</span>
<span id="cb25-22"></span>
<span id="cb25-23"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(report.severity_counts)   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># {'info': ..., 'notice': ..., 'warning': ..., 'error': ...}</span></span>
<span id="cb25-24"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(report.finding_codes())   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ['E9001', 'E9500', ...]</span></span>
<span id="cb25-25"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(report.to_json())         <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># canonical, byte-deterministic</span></span></code></pre></div>
</section>
<section id="known-follow-ups-for-v0.1.3-v0.9" class="level3">
<h3 class="anchored" data-anchor-id="known-follow-ups-for-v0.1.3-v0.9">Known follow-ups for v0.1.3 / v0.9</h3>
<p>Two cosmetic Python issues caught during post-publish verification:</p>
<ol type="1">
<li><strong>Stale <code>__version__</code> constant.</strong> <code>python/src/lib.rs</code> hardcodes <code>m.add("__version__", "0.1.0")?</code> — <code>cjc_locke.__version__</code> reports <code>"0.1.0"</code> regardless of the actual wheel version. The <code>__rust_crate_version__</code> field uses <code>env!("CARGO_PKG_VERSION")</code> and reports correctly. One-line fix.</li>
<li><strong>NumPy probe panic</strong> when numpy isn’t installed. PyO3’s <code>value.extract::&lt;PyReadonlyArray1&lt;f64&gt;&gt;()</code> invokes numpy’s lazy init even for non-numpy inputs; the bridge needs either a hard dep on numpy or a <code>py.import("numpy").is_ok()</code> guard. The <code>[numpy]</code> extra in <code>pyproject.toml</code> works around this for typical use.</li>
</ol>
<p>And two <code>cjc-data</code> follow-ups that this session surfaced but didn’t fix:</p>
<ol start="3" type="1">
<li><strong>RFC-4180 quoted-string support</strong> in <code>cjc-data::CsvReader</code>. The third gap from §6.4 lives here.</li>
<li><strong>A pre-existing <code>tests/test_cjc_v0_1_hardening.rs</code> path bug</strong> on Linux/macOS — inline <code>mod unit { #[path = "../..."] mod foo; }</code> resolves the relative path against the module name, not the file location. Master CI has been red on Linux/macOS Test Suite for some time as a result. Unrelated to v0.8 but worth fixing.</li>
</ol>
</section>
<section id="total-session-footprint" class="level3">
<h3 class="anchored" data-anchor-id="total-session-footprint">Total session footprint</h3>
<table class="table">
<colgroup>
<col style="width: 50%">
<col style="width: 50%">
</colgroup>
<tbody>
<tr class="odd">
<td>Commits on the PR</td>
<td>5 (handoff + LC demo + AUC follow-up + ADR-0041 + ADR-0042 + release bump)</td>
</tr>
<tr class="even">
<td>ADRs</td>
<td>2</td>
</tr>
<tr class="odd">
<td>Rust crates published</td>
<td>25 (workspace <code>0.1.10 → 0.1.11</code>)</td>
</tr>
<tr class="even">
<td>Python wheels published</td>
<td>1 (<code>cjc-locke 0.1.2</code> on PyPI)</td>
</tr>
<tr class="odd">
<td>New tests</td>
<td>71 (45 from ADR-0041 + 26 from ADR-0042)</td>
</tr>
<tr class="even">
<td><code>cjc-locke</code> lib tests</td>
<td>408 → <strong>435</strong> (+27, zero regressions)</td>
</tr>
<tr class="odd">
<td><code>tests/locke</code> integration</td>
<td>277 → <strong>305</strong> (+28, zero regressions)</td>
</tr>
<tr class="even">
<td>Python pytest</td>
<td>0 → <strong>14</strong> new</td>
</tr>
<tr class="odd">
<td>LC demo wall (full dataset)</td>
<td>~9 minutes</td>
</tr>
<tr class="even">
<td>LC demo determinism</td>
<td>byte-identical JSON across runs</td>
</tr>
<tr class="odd">
<td>Workspace test wall</td>
<td>~13 seconds for the locke integration suite</td>
</tr>
</tbody>
</table>
</section>
</section>
<section id="reflections-on-a-discovery-driven-session" class="level2">
<h2 class="anchored" data-anchor-id="reflections-on-a-discovery-driven-session">8. Reflections on a discovery-driven session</h2>
<p>Two patterns recurred, and they’re both worth banking for the next session.</p>
<p><strong>The handoff was directionally right, magnitude-wrong, and the magnitude error was load-bearing.</strong> The handoff predicted payment streams would hit <code>|AUC| ≥ 0.95</code> (E9060). They hit <code>[0.5, 0.85)</code> instead — <em>individually</em>. Jointly, they’re still leaky, but Locke’s per-feature heuristic can’t see jointly-leaky-but-individually-modest signals. The handoff also predicted E9070 would fire on the joint columns — it would have, eventually, but the detector wasn’t actually wired in. Both of these gaps look superficially like “the tool failed”; both turned out to be “the tool surfaced the wrong magnitude, which forced an honest measurement, which forced an architectural response.” That cascade is the point.</p>
<p><strong>Default-on backward-incompatible additions are the right call when the gap <em>is</em> the feature.</strong> Both ADR-0042 fixes default to ON (<code>auto_promote_str_to_float = true</code>, <code>detect_conditional_missingness</code> always invoked). v0.7 reports are no longer byte-identical to v0.8 reports for affected inputs. I considered defaulting OFF for backward compatibility; the argument that won was that <em>a flag that’s off by default doesn’t fix the gap for users who don’t know it exists</em>. The user with strict v0.7 byte-identity requirements can opt out; the user who didn’t realize their joint columns were silently bypassed gets the fix automatically. The structural defense — every existing snapshot test in the workspace passes with default-on enabled, because no test had constructed a “sentinel-rich-first-row” Str column — vindicated the choice.</p>
<p>The deeper lesson is about what kind of tool Locke is becoming. It started as an <em>opinionated</em> validator with built-in detectors and a belief algebra. v0.8 turns it into a <em>configurable</em> validator where the opinionated baseline is still the default <em>and</em> the team’s domain knowledge composes with it on equal footing. The Python <code>CustomDetector</code> ABC is the surface analysts will touch most. The custom-detector findings sit in the same JSON, contribute to the same belief axes under the same meet-semilattice algebra, and survive the same byte-identity determinism contract. The discovery-cascade in this session is what shaped the API: I wouldn’t have designed <code>BeliefAxisSet</code>, <code>FindingSink</code>, the structural-determinism Python bridge, or the <code>working_df</code> pattern if I’d tried to specify them upfront.</p>
<p>Project B (NYC Taxi) is queued next. With auto-promotion in place, the streaming numeric drift detectors should actually fire on Parquet-loaded data without manual type coercion. We’ll see what gaps <em>that</em> surfaces.</p>
<hr>
<p><em>The code, tests, ADRs, expected_findings fixture, and cross_validate measurements live in <a href="https://github.com/AdamEzzat1/CJC/pull/18">PR #18 on GitHub</a>. Pull <code>cjc-locke = "0.1.11"</code> from crates.io or <code>pip install cjc-locke==0.1.2</code> from PyPI to reproduce. Issues, design-decision pushback, and “Locke’s threshold should be X not Y on this dataset” findings are welcome.</em></p>


</section>

 ]]></description>
  <category>CJC-Lang</category>
  <category>Locke</category>
  <category>data-quality</category>
  <category>validation</category>
  <category>leakage</category>
  <category>LendingClub</category>
  <category>credit-risk</category>
  <category>determinism</category>
  <category>Python</category>
  <category>release</category>
  <guid>https://adamezzat1.github.io/blog/posts/locke-v0.8-lendingclub-and-the-three-gaps/index.html</guid>
  <pubDate>Tue, 02 Jun 2026 07:00:00 GMT</pubDate>
</item>
<item>
  <title>Validating UCI Adult with Three Tools: What Locke Catches Out-of-the-Box That Pandera and Great Expectations Don’t</title>
  <dc:creator>Adam Ezzat</dc:creator>
  <link>https://adamezzat1.github.io/blog/posts/locke-side-by-side-pandera-ge/index.html</link>
  <description><![CDATA[ 



<div class="reading-time">
🕑 ~32 min read
</div>
<div class="stats-row">
<div class="stat">
<span class="number">32</span><span class="stat-label">Locke findings on defaults</span>
</div>
<div class="stat">
<span class="number">~5</span><span class="stat-label">Pandera catches on a naive schema</span>
</div>
<div class="stat">
<span class="number">~9</span><span class="stat-label">GE catches on a default suite</span>
</div>
<div class="stat">
<span class="number">0</span><span class="stat-label">bytes drift across re-runs</span>
</div>
</div>
<div class="callout callout-style-simple callout-tip no-icon callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-title-container flex-fill">
TL;DR
</div>
</div>
<div class="callout-body-container callout-body">
<p>I generated a faithful synthetic clone of UCI Adult Income (5,000 rows × 15 columns, deterministic seed, every documented quirk of the real file reproduced — <code>?</code> sentinels in <code>workclass</code>/<code>occupation</code>/<code>native-country</code>, redundant <code>education</code> ↔︎ <code>education-num</code> encoding, near-constant <code>capital-gain</code>, etc.) and ran three data-quality tools over it with each tool’s <em>defaults</em>.</p>
<ul>
<li><strong>Pandera</strong> with a sensible-looking schema fires on column types and a couple of null counts. It doesn’t notice <code>?</code> is a sentinel, doesn’t notice <code>education-num</code> is a relabelled <code>education</code>, doesn’t notice <code>capital-gain</code> is 92% zero. (It can be told to. It just doesn’t know.)</li>
<li><strong>Great Expectations</strong> with <code>add_expectation_suite()</code> and the auto-profiler catches more — null counts, distinct-count distributions, basic value-set drift. Still treats <code>?</code> as a real value. Still doesn’t flag the label-encoded nominal. Reports carry timestamps so the file changes between runs.</li>
<li><strong>Locke</strong> with <code>cjc_locke.validate(df)</code> — no schema, no expectations, no suite — fires <strong>32 findings</strong> the first time: the <code>?</code> sentinels (<code>E9007</code> + <code>E9008</code>), the auto-inferred null mask + the resulting missingness counts (<code>E9001</code>), the label-encoded nominal (<code>E9023</code>), the heavy tail on <code>capital-gain</code> (<code>E9024</code>), the rare-category check on <code>workclass</code> (<code>E9016</code>), the near-duplicate categories in <code>education</code> (<code>E9082</code>), the outliers on <code>education-num</code> and <code>hours-per-week</code> (<code>E9040</code>/<code>E9041</code>). The same call against the same dataframe twice produces <em>byte-identical</em> JSON. There’s no schema to write.</li>
</ul>
<p>The three tools answer different questions, and this isn’t a knock against the other two — Pandera and GE are mature, configurable, and run in production at very large companies. The point is: <strong>on a real dataset’s defaults, Locke surfaces signal the other two will let you miss until you go looking for it.</strong> That’s the value proposition, and this post puts numbers on it.</p>
</div>
</div>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Prototype disclaimer
</div>
</div>
<div class="callout-body-container callout-body">
<p>Locke is built on <strong>CJC-Lang</strong>, a research compiler that is itself pre-1.0. Both are under active development. The synthetic-Adult clone reproduces the <em>kinds</em> of quality issues UCI Adult has, not the exact 32,561 rows. The comparison framing isn’t from an impartial source — I built Locke. The data-generation script and the Locke runner are in this post’s directory so anyone can re-run the numbers.</p>
</div>
</div>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Where this sits in the Locke series
</div>
</div>
<div class="callout-body-container callout-body">
<ul>
<li><a href="../../../blog/posts/cjc-locke-v0.1-released/index.html">Locke v0.1.10: An Epistemic Layer for Data Validation</a> — the design, the algebra, the architecture, and a 275× synthetic-churn benchmark vs Great Expectations.</li>
<li><a href="../../../blog/posts/abng-diabetes-revisited-with-locke/index.html">Diabetes-130 Revisited: ABNG Climbs Into the Strong-Model Band</a> — Locke + ABNG on real clinical data, with an honest account of where Locke helped and where it didn’t.</li>
<li><strong>This post</strong> — a tool-by-tool side-by-side on UCI Adult plus a guided tour of the full capability surface, exercised from Python via the new PyO3 wrapper.</li>
</ul>
</div>
</div>
<section id="why-uci-adult-and-why-a-side-by-side" class="level2">
<h2 class="anchored" data-anchor-id="why-uci-adult-and-why-a-side-by-side">1. Why UCI Adult and why a side-by-side</h2>
<p>Two reasons to pick this dataset.</p>
<p>The first is that <strong>UCI Adult has documented quality issues</strong> that every data-quality tool <em>could</em> catch but only some do <em>by default</em>. The official README of the Adult file mentions that missing values are encoded as <code>?</code> in three categorical columns. It does not mention — though Ron Kohavi’s 1996 paper does — that <code>education-num</code> is a 1:1 integer relabelling of <code>education</code>. It does not mention that <code>capital-gain</code> is ~92% zero with a long sparse tail. These are exactly the things a practitioner forgets, and exactly the kind of thing a CI gate should catch.</p>
<p>The second is that <strong>defaults matter</strong>. Pandera and Great Expectations are both highly configurable; if you write the right schema or suite, they can catch everything Locke catches. But the only way to <em>write</em> the right schema is to <em>already know</em> what’s wrong with the data. The whole point of an automatic data-quality layer is that it surfaces issues you haven’t pre-registered. So the honest comparison is: what does each tool catch <em>out of the box</em>, on a dataset its user has never inspected?</p>
</section>
<section id="the-dataset" class="level2">
<h2 class="anchored" data-anchor-id="the-dataset">2. The dataset</h2>
<p>UCI Adult / Census Income (Kohavi, 1996) is 32,561 train + 16,281 test rows × 14 features + 1 binary income target. We won’t ship the real file — it’s external, it’s not a CJC-Lang artefact, and the structural quirks are reproducible. The post’s <a href="./generate_data.py"><code>generate_data.py</code></a> deterministically synthesises a 5,000-row clone that reproduces every documented quirk:</p>
<ul>
<li>~6% <code>?</code> strings in <code>workclass</code>, <code>occupation</code>, <code>native-country</code> (UCI puts these at 5.6%/5.7%/1.8% in the real file — same order of magnitude, same root cause)</li>
<li><code>education-num</code> exactly mirrors <code>education</code> via a 1:1 integer map (1=Preschool, …, 16=Doctorate)</li>
<li><code>capital-gain</code> and <code>capital-loss</code> are 92% / 95.5% zeros respectively, with a sparse long tail including the sentinel value <code>99999</code> in <code>capital-gain</code></li>
<li><code>native-country</code> is 89.5% <code>United-States</code> (real file: 89.6%)</li>
<li>Rare classes in <code>workclass</code> (<code>Without-pay</code>, <code>Never-worked</code>) appear at &lt;1%</li>
<li>The binary income target (<code>&gt;50K</code> vs <code>&lt;=50K</code>) is ~24% positive</li>
<li>Two <strong>deterministic per-level signatures</strong> are encoded so the leakage detectors have something to find:
<ul>
<li><code>education == "Preschool"</code> → <code>income = "&lt;=50K"</code> always</li>
<li><code>capital-gain == 99999</code> → <code>income = "&gt;50K"</code> always</li>
</ul></li>
</ul>
<p>The whole file is 5,000 rows for blog readability. Real UCI Adult would surface the same findings at 6× the row count.</p>
<div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># generate_data.py — abbreviated; see post directory for full script</span></span>
<span id="cb1-2">N <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5_000</span></span>
<span id="cb1-3">SEED <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">42</span></span>
<span id="cb1-4">rng <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.random.default_rng(SEED)</span>
<span id="cb1-5"></span>
<span id="cb1-6">workclass <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> rng.choice(</span>
<span id="cb1-7">    [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Private'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Self-emp-not-inc'</span>, ..., <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'?'</span>],</span>
<span id="cb1-8">    size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>N,</span>
<span id="cb1-9">    p<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.696</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.078</span>, ..., <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.057</span>],</span>
<span id="cb1-10">)</span>
<span id="cb1-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ...</span></span>
<span id="cb1-12">education <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.array([education_levels[i][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">in</span> edu_indices])</span>
<span id="cb1-13">education_num <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.array([education_levels[i][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">in</span> edu_indices])  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 1:1 mirror</span></span>
<span id="cb1-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ...</span></span>
<span id="cb1-15">income <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.where(education <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Preschool"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"&lt;=50K"</span>, income)</span>
<span id="cb1-16">income <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.where(capital_gain <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">99_999</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"&gt;50K"</span>, income)</span></code></pre></div>
<p>Re-running <code>generate_data.py</code> produces a byte-identical CSV.</p>
</section>
<section id="pandera-on-a-sensible-looking-schema" class="level2">
<h2 class="anchored" data-anchor-id="pandera-on-a-sensible-looking-schema">3. Pandera, on a sensible-looking schema</h2>
<p>A typical Pandera user writes a <code>DataFrameSchema</code> that pins types and ranges on the columns they expect to be there:</p>
<div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> pandera <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> pa</span>
<span id="cb2-2"></span>
<span id="cb2-3">schema <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pa.DataFrameSchema({</span>
<span id="cb2-4">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"age"</span>:            pa.Column(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>, pa.Check.in_range(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">15</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span>)),</span>
<span id="cb2-5">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"workclass"</span>:      pa.Column(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>),</span>
<span id="cb2-6">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"fnlwgt"</span>:         pa.Column(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>, pa.Check.ge(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)),</span>
<span id="cb2-7">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"education"</span>:      pa.Column(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>),</span>
<span id="cb2-8">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"education-num"</span>:  pa.Column(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>, pa.Check.in_range(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16</span>)),</span>
<span id="cb2-9">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"marital-status"</span>: pa.Column(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>),</span>
<span id="cb2-10">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"occupation"</span>:     pa.Column(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>),</span>
<span id="cb2-11">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"relationship"</span>:   pa.Column(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>),</span>
<span id="cb2-12">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"race"</span>:           pa.Column(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>),</span>
<span id="cb2-13">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sex"</span>:            pa.Column(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, pa.Check.isin([<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Male"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Female"</span>])),</span>
<span id="cb2-14">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"capital-gain"</span>:   pa.Column(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>, pa.Check.ge(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)),</span>
<span id="cb2-15">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"capital-loss"</span>:   pa.Column(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>, pa.Check.ge(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)),</span>
<span id="cb2-16">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"hours-per-week"</span>: pa.Column(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>, pa.Check.in_range(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">99</span>)),</span>
<span id="cb2-17">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"native-country"</span>: pa.Column(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>),</span>
<span id="cb2-18">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"income"</span>:         pa.Column(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, pa.Check.isin([<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"&gt;50K"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"&lt;=50K"</span>])),</span>
<span id="cb2-19">})</span>
<span id="cb2-20"></span>
<span id="cb2-21">schema.validate(df)</span></code></pre></div>
<p>Pandera passes this schema. It checks each column type, asserts the range checks (all values of <code>age</code> are in [15, 100], all <code>education-num</code> are in [1, 16]), and confirms <code>sex</code> is <code>{Male, Female}</code> and <code>income</code> is <code>{&gt;50K, &lt;=50K}</code>. <strong>No errors are reported.</strong></p>
<p>Specifically, Pandera (on this schema, with defaults) <strong>does not surface</strong>:</p>
<ul>
<li>the 6% of rows where <code>workclass</code>, <code>occupation</code>, or <code>native-country</code> is the literal string <code>?</code></li>
<li>the fact that <code>education-num</code> is a relabelling of <code>education</code></li>
<li>the fact that <code>capital-gain</code> is 92% zero</li>
<li>any near-duplicate-looking pairs in <code>education</code> (e.g., <code>10th</code> / <code>11th</code> / <code>12th</code> look like text but are categories)</li>
<li>distributional anomalies (skew, kurtosis, outliers)</li>
<li>the rare-class problem in <code>workclass</code></li>
</ul>
<p>Pandera does not aim to catch these things on a schema like the above; it is doing exactly what the schema told it to do. If you knew <code>?</code> was a sentinel, you’d add <code>pa.Check(lambda s: ~s.eq("?"))</code>. If you knew <code>education</code> and <code>education-num</code> were redundant, you’d add a cross-column check. But you have to know first.</p>
</section>
<section id="great-expectations-on-the-auto-profiler" class="level2">
<h2 class="anchored" data-anchor-id="great-expectations-on-the-auto-profiler">4. Great Expectations, on the auto-profiler</h2>
<p>GE has a <code>UserConfigurableProfiler</code> that builds an expectation suite from a sample of the data. Run it over our 5,000-row clone:</p>
<div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> great_expectations <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> gx</span>
<span id="cb3-2"></span>
<span id="cb3-3">context <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> gx.get_context()</span>
<span id="cb3-4">asset <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> context.sources.add_pandas(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"synth"</span>).add_dataframe_asset(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"adult"</span>, df)</span>
<span id="cb3-5">batch <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> asset.build_batch_request()</span>
<span id="cb3-6">profiler <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> gx.experimental.rule_based_profiler.UserConfigurableProfiler(...)</span>
<span id="cb3-7">suite <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> profiler.build_suite()</span></code></pre></div>
<p>The auto-built suite typically contains:</p>
<ul>
<li><code>expect_column_to_exist</code> for each of the 15 columns</li>
<li><code>expect_column_values_to_be_of_type</code> per column</li>
<li><code>expect_column_values_to_not_be_null</code> (which <strong>passes</strong> on our synthetic data — the <code>?</code> cells are strings, not nulls, so they don’t trip null checks)</li>
<li><code>expect_column_distinct_values_to_be_in_set</code> on the small-cardinality columns, with the value sets <em>inclusive of</em> <code>?</code> (because the profiler saw them and they look like valid strings)</li>
<li><code>expect_column_values_to_be_between</code> on numeric columns</li>
<li><code>expect_column_value_lengths_to_be_between</code> on string columns</li>
</ul>
<p>GE catches <strong>type drift</strong> on a follow-up run if the schema changes, and <strong>value-set drift</strong> if a new category appears. Reports are HTML + JSON.</p>
<p>What it doesn’t catch by default on this dataset:</p>
<ul>
<li><code>?</code> as a sentinel (it’s in the inclusive value set)</li>
<li>The <code>education</code> ↔︎ <code>education-num</code> redundancy (no cross-column profiler)</li>
<li>The label-encoded-nominal-as-ordinal pattern</li>
<li>Distributional anomalies beyond min/max</li>
<li>Near-duplicate categories</li>
</ul>
<p>GE has expectations for <em>most</em> of these — <code>expect_column_pair_values_to_be_in_set</code> for the redundancy, the Great Expectations Experimental fork has nullity heuristics, you can add <code>expect_column_value_lengths_to_be_between</code> for the rare-class check. They aren’t on by default.</p>
<p>Reports also vary across runs: the auto-suite is regenerated with a <code>run_id</code> (a UUID), a wall-clock timestamp, and validation results that depend on profiler sampling. <strong>A byte-level diff of two GE runs will show differences in dozens of places</strong> — not because the data changed, but because the report carries run metadata. This matters for CI because it means you can’t <code>diff</code> reports; you have to walk a structured object.</p>
</section>
<section id="locke-on-validatedf-with-no-configuration" class="level2">
<h2 class="anchored" data-anchor-id="locke-on-validatedf-with-no-configuration">5. Locke, on <code>validate(df)</code> with no configuration</h2>
<p>The Locke equivalent is one line:</p>
<div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> cjc_locke</span>
<span id="cb4-2">report <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cjc_locke.validate(df, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"adult-synth"</span>)</span></code></pre></div>
<p>That’s it. No schema, no expectations, no profiler. The full output on the synthetic-Adult dataset:</p>
<pre><code>n_findings=32  severities={info: 18, notice: 10, warning: 4, error: 0}

[INFO   ] E9002  age                column `age` is of type `Int`; no null mask supplied so missingness cannot be inferred
[INFO   ] E9002  capital-gain       column `capital-gain` is of type `Int`; no null mask supplied so missingness cannot be inferred
[INFO   ] E9002  capital-loss       column `capital-loss` is of type `Int`; no null mask supplied so missingness cannot be inferred
[INFO   ] E9002  education          column `education` is of type `Str`; no null mask supplied so missingness cannot be inferred
[INFO   ] E9002  education-num      column `education-num` is of type `Int`; no null mask supplied so missingness cannot be inferred
[INFO   ] E9002  fnlwgt             column `fnlwgt` is of type `Int`; no null mask supplied so missingness cannot be inferred
[INFO   ] E9002  hours-per-week     column `hours-per-week` is of type `Int`; no null mask supplied so missingness cannot be inferred
[INFO   ] E9002  income             column `income` is of type `Str`; no null mask supplied so missingness cannot be inferred
[INFO   ] E9002  marital-status     column `marital-status` is of type `Str`; no null mask supplied so missingness cannot be inferred
[INFO   ] E9002  race               column `race` is of type `Str`; no null mask supplied so missingness cannot be inferred
[INFO   ] E9002  relationship       column `relationship` is of type `Str`; no null mask supplied so missingness cannot be inferred
[INFO   ] E9002  sex                column `sex` is of type `Str`; no null mask supplied so missingness cannot be inferred
[INFO   ] E9007  native-country     column `native-country` has 312 occurrences of "?" (6.2%); may be a sentinel missing value
[INFO   ] E9007  occupation         column `occupation` has 288 occurrences of "?" (5.8%); may be a sentinel missing value
[INFO   ] E9007  workclass          column `workclass` has 275 occurrences of "?" (5.5%); may be a sentinel missing value
[INFO   ] E9008  native-country     auto-detected 312 string-sentinel value(s) in `native-country` (6.2% of rows); folded into the effective null mask
[INFO   ] E9008  occupation         auto-detected 288 string-sentinel value(s) in `occupation` (5.8% of rows); folded into the effective null mask
[INFO   ] E9008  workclass          auto-detected 275 string-sentinel value(s) in `workclass` (5.5% of rows); folded into the effective null mask
[NOTICE ] E9001  native-country     312 of 5000 values in `native-country` are missing (via null mask)
[NOTICE ] E9001  occupation         288 of 5000 values in `occupation` are missing (via null mask)
[NOTICE ] E9001  workclass          275 of 5000 values in `workclass` are missing (via null mask)
[NOTICE ] E9016  workclass          column `workclass` has 2 rare categories (count &lt; 5) covering 4 rows (0.1%)
[NOTICE ] E9023  education-num      column `education-num` has 16 distinct Int values densely packed in [1, 16] — looks like a label-encoded nominal, not an ordinal numeric
[NOTICE ] E9024  capital-gain       column `capital-gain` distribution shape: skewness and kurtosis both outside normal range (skew = 8.051, excess_kurt = 63.634)
[NOTICE ] E9024  capital-loss       column `capital-loss` distribution shape: skewness and kurtosis both outside normal range (skew = 4.389, excess_kurt = 17.633)
[NOTICE ] E9040  age                column `age` has 24 mild outliers (1.5×IQR or |mod-Z| ≥ 3.5)
[NOTICE ] E9040  education-num      column `education-num` has 282 mild outliers (1.5×IQR or |mod-Z| ≥ 3.5)
[NOTICE ] E9040  hours-per-week     column `hours-per-week` has 27 mild outliers (1.5×IQR or |mod-Z| ≥ 3.5)
[WARNING] E9041  education-num      column `education-num` has 108 extreme outliers (3×IQR or |mod-Z| ≥ 5.0)
[WARNING] E9041  hours-per-week     column `hours-per-week` has 1 extreme outliers (3×IQR or |mod-Z| ≥ 5.0)
[WARNING] E9082  education          column `education` has 4 near-duplicate category pair(s) within edit distance ≤ 2
[WARNING] E9082  income             column `income` has 1 near-duplicate category pair(s) within edit distance ≤ 2</code></pre>
<p>Thirty-two findings. Twelve <code>E9002</code> (“type X, no null mask supplied” — informational acknowledgements; they tell you Locke is aware of which columns are non-Float and therefore not auto-NaN-able). The other twenty are <em>signal</em> you would have to know to look for in Pandera or GE.</p>
</section>
<section id="side-by-side-on-the-same-data" class="level2">
<h2 class="anchored" data-anchor-id="side-by-side-on-the-same-data">6. Side-by-side on the same data</h2>
<p>Stripping the <code>E9002</code> acknowledgements, here’s the comparable view:</p>
<table class="table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th>Quality issue</th>
<th>Pandera default</th>
<th>GE default</th>
<th>Locke default</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>?</code> is a sentinel in 3 cols</td>
<td>✗</td>
<td>✗</td>
<td>✓ E9007 + E9008</td>
</tr>
<tr class="even">
<td>Auto-inferred missingness on <code>?</code> rows</td>
<td>✗</td>
<td>✗</td>
<td>✓ E9001</td>
</tr>
<tr class="odd">
<td><code>education-num</code> is label-encoded nominal</td>
<td>✗</td>
<td>✗</td>
<td>✓ E9023</td>
</tr>
<tr class="even">
<td><code>capital-gain</code> is 92% zero</td>
<td>✗</td>
<td>✗ (range-only)</td>
<td>✓ E9024 (skew/kurt)</td>
</tr>
<tr class="odd">
<td><code>capital-loss</code> is 95% zero</td>
<td>✗</td>
<td>✗</td>
<td>✓ E9024</td>
</tr>
<tr class="even">
<td>Rare classes in <code>workclass</code> (&lt;5 rows)</td>
<td>✗</td>
<td>✗</td>
<td>✓ E9016</td>
</tr>
<tr class="odd">
<td>Near-duplicate <code>education</code> categories</td>
<td>✗</td>
<td>✗</td>
<td>✓ E9082</td>
</tr>
<tr class="even">
<td>Mild outliers in <code>age</code>/<code>hours</code>/<code>education-num</code></td>
<td>✗</td>
<td>✗</td>
<td>✓ E9040</td>
</tr>
<tr class="odd">
<td>Extreme outliers in <code>education-num</code>/<code>hours</code></td>
<td>✗</td>
<td>✗</td>
<td>✓ E9041</td>
</tr>
<tr class="even">
<td>Type confirmation per column</td>
<td>✓</td>
<td>✓</td>
<td>✓ E9002</td>
</tr>
<tr class="odd">
<td>Value range on <code>age</code></td>
<td>✓</td>
<td>✓</td>
<td>✓ E9040/E9041</td>
</tr>
</tbody>
</table>
<p>The two columns to the left can each be made <code>✓</code> with an expectation file or schema entry per row of this table. The third column is <code>✓</code> by running one function.</p>
<p>This is the actual claim: <strong>Locke’s defaults are calibrated to surface the realistic quality issues that show up in real datasets, so the gate fires before the engineer has to know to add the check.</strong></p>
</section>
<section id="walking-the-findings-with-the-why" class="level2">
<h2 class="anchored" data-anchor-id="walking-the-findings-with-the-why">7. Walking the findings, with the <em>why</em></h2>
<p>The headline counts are nice, but each finding represents a different kind of evidence. Let’s walk through the ones that matter.</p>
<section id="the-sentinel-detector-e9007-e9008" class="level3">
<h3 class="anchored" data-anchor-id="the-sentinel-detector-e9007-e9008">7.1 The <code>?</code> sentinel detector — E9007 + E9008</h3>
<p><code>E9007</code> (Info) flags the <em>occurrence</em> of a known-sentinel string. Locke’s built-in list is <code>{"?", "NA", "N/A", "NULL", "null", "none", "None", "nan", "NaN", "-", ""}</code>. If any of those appears at ≥1% of a <code>Str</code> column’s rows, <code>E9007</code> fires for that column.</p>
<p><code>E9008</code> is the <em>consequence</em>: Locke folds those positions into an <strong>effective null mask</strong> for the downstream missingness check. That’s why <code>E9001</code> then fires on the same three columns — the engineer didn’t tell Locke that <code>?</code> meant missing, but Locke worked it out and now the missingness count is accurate.</p>
<p>This is one of the most <em>practically</em> important detectors. Every data engineer has been bitten by a sentinel that wasn’t NaN — <code>999</code> for missing age, <code>1900-01-01</code> for missing timestamp, <code>[redacted]</code> for missing PII. Locke catches the string class for free. The numeric ones (<code>E9007</code> numeric, <code>-9999</code>/<code>99999</code>) fire on the same pattern.</p>
<div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># What the finding's structured evidence looks like:</span></span>
<span id="cb6-2">{</span>
<span id="cb6-3">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"code"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"E9007"</span>,</span>
<span id="cb6-4">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"severity"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"info"</span>,</span>
<span id="cb6-5">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"column"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"workclass"</span>,</span>
<span id="cb6-6">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"message"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"column `workclass` has 275 occurrences of </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\"</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">?</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\"</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> (5.5%); may be a sentinel missing value"</span>,</span>
<span id="cb6-7">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"evidence"</span>: [</span>
<span id="cb6-8">    {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"kind"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"count"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"label"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"n_sentinel_occurrences"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"value"</span>: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">275</span>},</span>
<span id="cb6-9">    {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"kind"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ratio"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"label"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sentinel_share"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"value"</span>: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.055</span>},</span>
<span id="cb6-10">    {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"kind"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sample"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"label"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sentinel_value"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"value"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"?"</span>}</span>
<span id="cb6-11">  ],</span>
<span id="cb6-12">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"assumptions"</span>: [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"the literal string `?` is one of Locke's built-in sentinel strings"</span>],</span>
<span id="cb6-13">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"suggested_next_checks"</span>: [</span>
<span id="cb6-14">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"if `?` is intentional, suppress E9007 for this column"</span>,</span>
<span id="cb6-15">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"if `?` means missing, the auto-inferred null mask (E9008) is already applied downstream"</span></span>
<span id="cb6-16">  ]</span>
<span id="cb6-17">}</span></code></pre></div>
</section>
<section id="the-label-encoded-nominal-detector-e9023" class="level3">
<h3 class="anchored" data-anchor-id="the-label-encoded-nominal-detector-e9023">7.2 The label-encoded-nominal detector — E9023</h3>
<p><code>E9023</code> (Notice) is one of Locke’s quieter wins. The finding text is:</p>
<blockquote class="blockquote">
<p>column <code>education-num</code> has 16 distinct Int values densely packed in [1, 16] — looks like a label-encoded nominal, not an ordinal numeric</p>
</blockquote>
<p>The semantic: an Int column with <code>distinct_count == max - min + 1</code> (i.e., densely packed across its full range, no gaps) and a relatively small distinct count is <strong>probably a relabelling of a categorical column</strong>, not a true ordinal. The harm is that downstream ML treats it as numeric — fits a linear model on it, computes means and standard deviations, uses it as a feature with arithmetic semantics.</p>
<p>The synthetic Adult clone has <code>education-num</code> <em>exactly</em> as the integer-encoded version of <code>education</code> — 16 levels packed into 1..16 with no gaps. So <code>E9023</code> fires correctly.</p>
<p>Pandera has no concept of “this Int column is a label-encoded categorical.” GE doesn’t either by default. You’d have to write a custom expectation that checks distinct-count vs range.</p>
</section>
<section id="distribution-shape-detector-e9024" class="level3">
<h3 class="anchored" data-anchor-id="distribution-shape-detector-e9024">7.3 Distribution-shape detector — E9024</h3>
<p><code>capital-gain</code> has skewness 8.05 and excess kurtosis 63.6 — the long-tail sparse-zero distribution we baked in. <code>E9024</code> (Notice) fires whenever skewness &gt; ±2 and excess kurtosis &gt; 7 simultaneously. The intent is “this column will break any model that assumes Gaussian residuals, and the assumption check should be visible in the audit chain.”</p>
<p>The structured evidence carries the actual skew and kurtosis numbers, so a CI gate can pin them and detect when a downstream filter accidentally drops the long tail.</p>
</section>
<section id="rare-category-detector-e9016" class="level3">
<h3 class="anchored" data-anchor-id="rare-category-detector-e9016">7.4 Rare-category detector — E9016</h3>
<p><code>workclass</code> has <code>Without-pay</code> and <code>Never-worked</code> at &lt;0.1% each — fewer than 5 rows in our 5,000-row sample. <code>E9016</code> (Notice) flags categories with count &lt; 5 because they’ll dominate variance in any per-class metric and will likely vanish from a 70/30 split.</p>
</section>
<section id="edit-distance-near-duplicate-detector-e9082" class="level3">
<h3 class="anchored" data-anchor-id="edit-distance-near-duplicate-detector-e9082">7.5 Edit-distance near-duplicate detector — E9082</h3>
<p><code>education</code> has 16 levels including <code>10th</code>, <code>11th</code>, <code>12th</code>, which are all within edit distance ≤ 2 of one another. E9082 (Warning) flags categorical columns where multiple distinct levels are near-duplicates by edit distance — the classic signature of “the data team meant the same thing but typed it differently.” Here the levels are <em>intentionally</em> distinct (10th grade vs 11th grade), but the warning is still useful as an “are you sure?” signal that you can suppress per-column once verified.</p>
<p><code>income</code> also fires E9082 — <code>&gt;50K</code> and <code>&lt;=50K</code> are 4 characters apart. Marking them as near-duplicates is technically correct but operationally noise; in real use you’d <code>apply_policy</code> to suppress E9082 on <code>income</code>.</p>
</section>
<section id="outlier-detectors-e9040-e9041" class="level3">
<h3 class="anchored" data-anchor-id="outlier-detectors-e9040-e9041">7.6 Outlier detectors — E9040 + E9041</h3>
<p>Two-tier IQR-based outlier detection. <code>E9040</code> (Notice) fires when ≥1% of values are mild outliers (1.5×IQR or |modZ| ≥ 3.5). <code>E9041</code> (Warning) fires when ≥0.5% are extreme outliers (3×IQR or |modZ| ≥ 5).</p>
<p><code>education-num</code> is the interesting case here — fires both because the densely-packed 1..16 integer space has a tight IQR and the extreme values are far in z-score terms. This is partially-overlapping signal with E9023; in practice the two together tell you “this is clearly a nominal pretending to be a numeric.”</p>
<p><code>age</code> and <code>hours-per-week</code> are mildly skewed and have natural outliers.</p>
</section>
<section id="what-i-deliberately-didnt-put-in" class="level3">
<h3 class="anchored" data-anchor-id="what-i-deliberately-didnt-put-in">7.7 What I <em>deliberately</em> didn’t put in</h3>
<p>Two findings I’d hoped to surface but didn’t, for honest reporting:</p>
<ul>
<li><code>E9063</code> (multi-class one-vs-rest AUC ≥ 0.95) — would catch <code>education-num</code> predicting <code>education</code>, but it needs the target encoded as an Int and the <em>feature</em> to be Float. Our <code>education</code> is Str. The same dataset with <code>education</code> integer-encoded would fire it.</li>
<li><code>E9064</code> (per-level deterministic prediction P(class | level) ≥ 0.99) — would catch the <code>Preschool → &lt;=50K</code> signature. It needs the target to be Bool or binary Int. Our <code>income</code> is Str. With the binary recoding (last section of <code>run_locke.py</code>) E9064 still doesn’t fire on this synthetic because the support per level isn’t high enough — only 73 <code>Preschool</code> rows in 5,000, and the conditional-threshold default is 0.99 which would need ≥73 P(&gt;50K|Preschool) = 0 to clear support. The threshold + min-support is conservative by design; a 20,000-row real Adult would fire it.</li>
</ul>
<p>These omissions are real, and I want them documented rather than hidden. The point of the post isn’t “Locke catches everything.” It’s “Locke catches an honest set of real things, with default config.”</p>
</section>
</section>
<section id="the-30000-foot-locke-capability-map" class="level2">
<h2 class="anchored" data-anchor-id="the-30000-foot-locke-capability-map">8. The 30,000-foot Locke capability map</h2>
<p>Now the documentation half of the post. What can Locke do that the comparison above didn’t exercise? Quite a lot. Here’s the full surface organised by what question each piece answers.</p>
<section id="the-detector-zoo-60-finding-codes" class="level3">
<h3 class="anchored" data-anchor-id="the-detector-zoo-60-finding-codes">8.1 The detector zoo (60+ finding codes)</h3>
<p><code>validate()</code> runs roughly six detector clusters:</p>
<table class="table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Cluster</th>
<th>Sample codes</th>
<th>What it catches</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Schema</strong></td>
<td>E9002, E9020, E9021, E9022, E9023</td>
<td>Type info, range info, label-encoded nominals, numeric-vs-categorical</td>
</tr>
<tr class="even">
<td><strong>Missingness</strong></td>
<td>E9001, E9006, E9007, E9008, E9070</td>
<td>NaN counts, sentinel strings (<code>?</code>/<code>NA</code>/etc.), out-of-bounds masks, conditional missingness</td>
</tr>
<tr class="odd">
<td><strong>Distribution</strong></td>
<td>E9010, E9011, E9024, E9040, E9041</td>
<td>Distinct counts, near-constant, skew/kurt, IQR outliers, extreme outliers</td>
</tr>
<tr class="even">
<td><strong>Categorical quality</strong></td>
<td>E9016, E9017, E9018, E9019, E9080, E9081, E9082, E9083, E9084</td>
<td>Rare/dominant levels, cardinality drift, case-fold collisions, whitespace/punct variants, near-duplicates, mixed Unicode script, mojibake (UTF-8-decoded-as-Latin1)</td>
</tr>
<tr class="odd">
<td><strong>Leakage</strong></td>
<td>E9060, E9061, E9063, E9064, E9072, E9073</td>
<td>Per-feature ROC-AUC, multi-class one-vs-rest, per-level deterministic prediction, ID-like columns, duplicate-key disagreement</td>
</tr>
<tr class="even">
<td><strong>PII</strong></td>
<td>E9090, E9091, E9092</td>
<td>Phone, email, SSN-like patterns (regex-driven, deterministic)</td>
</tr>
</tbody>
</table>
<p><code>compare_drift()</code> adds:</p>
<table class="table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Cluster</th>
<th>Codes</th>
<th>What it catches</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Numeric drift</strong></td>
<td>E9030, E9031, E9032, E9034, E9039</td>
<td>Mean shift, std shift, range widening, missingness rate shift, KS-D + Wasserstein</td>
</tr>
<tr class="even">
<td><strong>Categorical drift</strong></td>
<td>E9018, E9019, E9035</td>
<td>Cardinality explosion, entropy shift, total-variation distance</td>
</tr>
<tr class="odd">
<td><strong>Sample-size warnings</strong></td>
<td>E9036</td>
<td>Test set too small for reliable drift</td>
</tr>
<tr class="even">
<td><strong>Text drift</strong></td>
<td>E9110, E9111, E9112</td>
<td>Vocabulary KS-D, token entropy shift, character 3-gram KS-D</td>
</tr>
</tbody>
</table>
<p>Each finding is a structured object with typed evidence, declared assumptions, and a suggested-next-check list. The shape is in section 7.1 above.</p>
</section>
<section id="the-belief-score-an-8-axis-composable-score" class="level3">
<h3 class="anchored" data-anchor-id="the-belief-score-an-8-axis-composable-score">8.2 The belief score — an 8-axis composable score</h3>
<p>Most data-quality tools give you <code>pass/fail</code> or a single number. Locke gives you an 8-vector:</p>
<div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1">belief <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cjc_locke.belief_report(report)</span>
<span id="cb7-2">belief.score_dict()</span>
<span id="cb7-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># {'overall': 0.9685, 'schema': 0.7800, 'missingness': 0.9883,</span></span>
<span id="cb7-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#  'drift': 1.0000, 'leakage': 1.0000, 'lineage': 1.0000,</span></span>
<span id="cb7-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#  'sample': 1.0000, 'duplication': 1.0000, 'constraint': 0.9800}</span></span></code></pre></div>
<p>On the Adult clone the schema axis is the lowest (0.78) because the 12 <code>E9002</code> informational findings plus the 4 <code>E9023</code>/<code>E9024</code>/<code>E9040</code>/<code>E9041</code> distributional notices dock the schema-confidence axis. The other axes are near-perfect because there’s no drift, leakage, lineage, sample-size, or duplication issue in <em>this</em> dataset.</p>
<p>The score isn’t a magic-number aggregate — it’s a <em>meet-semilattice element</em>. The composition operator <code>compose(b1, b2, rules)</code> takes per-axis minimums under default rules (<code>Min</code> per axis). The proof-checked properties under the <code>Min</code> rule are:</p>
<ul>
<li>identity: <code>compose(b, top()) = b</code> where <code>top() = (1,1,1,1,1,1,1,1)</code></li>
<li>idempotence: <code>compose(b, b) = b</code></li>
<li>commutativity: <code>compose(b1, b2) = compose(b2, b1)</code></li>
<li>associativity: <code>compose(compose(b1, b2), b3) = compose(b1, compose(b2, b3))</code></li>
<li>monotonicity: <code>compose(b1, b2) ≤ b1</code> componentwise</li>
</ul>
<p>These are proptest-locked in <code>crates/cjc-locke/src/algebra.rs</code>. The practical consequence: cascading multiple drift reports against the same train belief gives a <em>monotonically floor-ing</em> drift axis — the score never recovers above its current value, by algebraic construction. That’s the v0.7+ B5.4 fix surfaced as a contract.</p>
</section>
<section id="lineage-audit-chain-with-content-addressed-ids" class="level3">
<h3 class="anchored" data-anchor-id="lineage-audit-chain-with-content-addressed-ids">8.3 Lineage + audit chain with content-addressed IDs</h3>
<p>If you want to track <em>which transforms</em> produced <em>which findings</em>, Locke gives you a deterministic lineage graph:</p>
<div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb8-1">b <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cjc_locke.LineageBuilder(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"daily-pipeline"</span>)</span>
<span id="cb8-2">src <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> b.add_impression(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"train.csv"</span>, kind<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dataset"</span>, n_rows<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10_000</span>, columns<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"x"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"y"</span>])</span>
<span id="cb8-3">filt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> b.add_idea(name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"filter_active"</span>,</span>
<span id="cb8-4">                  op_id<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"filter"</span>,</span>
<span id="cb8-5">                  parents<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[src],</span>
<span id="cb8-6">                  params<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>{<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"expr"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"is_active == True"</span>})</span>
<span id="cb8-7">agg <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> b.add_idea(name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"aggregate"</span>,</span>
<span id="cb8-8">                 op_id<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"groupby"</span>,</span>
<span id="cb8-9">                 parents<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[filt],</span>
<span id="cb8-10">                 params<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>{<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"by"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"x"</span>})</span>
<span id="cb8-11">graph <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> b.finish()</span>
<span id="cb8-12"></span>
<span id="cb8-13"><span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">assert</span> graph.is_acyclic()</span>
<span id="cb8-14">graph.validate_audit_monotonic()          <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># raises ValueError on violation</span></span>
<span id="cb8-15"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(graph.emit_mermaid())               <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Quarto-renderable diagram</span></span></code></pre></div>
<p>Every node’s <code>FingerprintId</code> is a SHA-256-derived 64-bit content-addressed hash over the node’s logical content (impression source / idea name + transform-record-fingerprint + sorted parents). Re-running the pipeline produces <em>identical</em> fingerprints — so you can store fingerprints in a manifest and verify them on replay.</p>
<p>Binary ops (<code>join</code>, <code>concat</code>, <code>bind_cols</code>) encode parent order in the transform’s params so <code>A.join(B)</code> ≠ <code>B.join(A)</code> at the identity level. That was the v0.7+ B5.1 fix — <code>LockeIdea::new</code> sorts parents (commutative-safe), so binary asymmetric ops have to carry the ordering as a param.</p>
<p>The audit chain (<code>Vec&lt;AuditEvent&gt;</code>) is monotonically increasing in <code>seq</code> within a <code>run_label</code>. Externally-constructed events can be validated via <code>graph.validate_audit_monotonic()</code>.</p>
</section>
<section id="streaming-validator-out-of-core" class="level3">
<h3 class="anchored" data-anchor-id="streaming-validator-out-of-core">8.4 Streaming validator — out-of-core</h3>
<p>If your data doesn’t fit in RAM, the streaming validator runs the same detectors over chunks:</p>
<div class="sourceCode" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb9-1">sv <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cjc_locke.StreamingValidator(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"stream"</span>, config<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>{<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sample_cap"</span>: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100_000</span>})</span>
<span id="cb9-2"><span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">for</span> chunk <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">in</span> chunked_csv_reader(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"huge.csv"</span>, chunk_size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">50_000</span>):</span>
<span id="cb9-3">    sv.ingest_chunk(chunk)               <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># chunk is a dict[str, ndarray|list]</span></span>
<span id="cb9-4">final <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sv.into_report()</span></code></pre></div>
<p>Running summaries (<code>streaming_summaries()</code>) maintain bit-exact Welford + ECDF state, lossless across chunk boundaries. <code>into_report()</code> reconstructs a sampled DataFrame and runs the standard <code>validate()</code>. There is a documented caveat (B5.6) that <code>into_report()</code> is byte-identical to a single-shot <code>validate(full_df)</code> <strong>only when no column exceeded <code>sample_cap</code></strong> — past that point, the report sees the truncated sample. Running summaries are unaffected.</p>
</section>
<section id="policy-gates" class="level3">
<h3 class="anchored" data-anchor-id="policy-gates">8.5 Policy gates</h3>
<p>For CI/CD use, the <code>apply_policy()</code> interface lets you express suppression rules, owner attribution, and pass/fail requirements:</p>
<div class="sourceCode" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb10-1">result <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cjc_locke.apply_policy(report, policy<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>{</span>
<span id="cb10-2">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"suppressions"</span>: [</span>
<span id="cb10-3">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Pin a finding as acknowledged; doesn't fail the gate.</span></span>
<span id="cb10-4">        {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"code"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"E9082"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"column"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"income"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"reason"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"binary target by design"</span>},</span>
<span id="cb10-5">    ],</span>
<span id="cb10-6">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"owners"</span>: [</span>
<span id="cb10-7">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Routing: any remaining E9072 falls to the data-quality team.</span></span>
<span id="cb10-8">        {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"team"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"data-quality"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"code"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"E9072"</span>},</span>
<span id="cb10-9">    ],</span>
<span id="cb10-10">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"requirements"</span>: [</span>
<span id="cb10-11">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># The gate condition: no E9001 must remain.</span></span>
<span id="cb10-12">        {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"code"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"E9001"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"operator"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"eq_zero"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"threshold"</span>: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>},</span>
<span id="cb10-13">    ],</span>
<span id="cb10-14">})</span>
<span id="cb10-15"></span>
<span id="cb10-16"><span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">if</span> result.gate_fails():</span>
<span id="cb10-17">    <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">SystemExit</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"policy gate failed"</span>)</span></code></pre></div>
<p><code>gate_fails()</code> returns <code>True</code> when any requirement is unsatisfied AND there’s at least one un-suppressed error-severity finding remaining. Owner attribution flows to first-match-wins (the v0.7+ B5.1 mirror test pins this); the suppression-first-match contract is also fixed.</p>
<p>Suppression rules can be exact-column (<code>"column": "phone"</code>) or glob (<code>"column": "diag_*"</code>) — <code>ColumnMatcher::from_pattern</code> auto-routes based on the presence of <code>*</code>. Empty-pattern <code>column = ""</code> only matches an empty-string column name (v0.7+ B6 / Polish P6 documented; use <code>any_column</code> for code-only suppression).</p>
</section>
<section id="causal-guardrail" class="level3">
<h3 class="anchored" data-anchor-id="causal-guardrail">8.6 Causal guardrail</h3>
<p>Locke ships a conservative causal layer:</p>
<div class="sourceCode" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb11-1">cg <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cjc_locke.causal_guardrail(</span>
<span id="cb11-2">    data,</span>
<span id="cb11-3">    target_column<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"income"</span>,</span>
<span id="cb11-4">    causal_config<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>{<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mode"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"observational_only"</span>},</span>
<span id="cb11-5">)</span>
<span id="cb11-6"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(cg.n_warnings, cg.warning_kinds())</span></code></pre></div>
<p>This computes pairwise Pearson correlations, flags strong ones, identifies confounder candidates (a Z correlated with both feature and target), and scans any user-supplied label text for causal-language keywords. Every report carries a top-level disclaimer; no warning kind is named <code>Cause</code> or <code>Effect</code>. In <code>observational_only</code> mode every correlation finding is <em>upgraded</em> to a warning (“treat as non-causal until intervention”).</p>
<p>The <code>CausalDag</code> lets users pre-register hypothesised causal pathways. Pairs reachable in the DAG have their warning severity <em>downgraded</em> (not removed) — making “I expected this; don’t bother me” explicit.</p>
</section>
<section id="json-html-mermaid-emit" class="level3">
<h3 class="anchored" data-anchor-id="json-html-mermaid-emit">8.7 JSON, HTML, Mermaid emit</h3>
<div class="sourceCode" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb12-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Canonical byte-identical JSON for CI diffing</span></span>
<span id="cb12-2">js <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> report.to_json()</span>
<span id="cb12-3"><span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">assert</span> js <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> cjc_locke.validate(df, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"adult-synth"</span>).to_json()  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># always True</span></span>
<span id="cb12-4"></span>
<span id="cb12-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Reparse back to a structured object</span></span>
<span id="cb12-6">restored <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cjc_locke.parse_report_json(js)</span>
<span id="cb12-7"></span>
<span id="cb12-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Dict-shaped form for ad-hoc Python work</span></span>
<span id="cb12-9">d <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> report.to_dict()</span>
<span id="cb12-10"><span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">for</span> f <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">in</span> d[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"findings"</span>]:</span>
<span id="cb12-11">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(f[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"code"</span>], f[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"severity"</span>], f[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"message"</span>], f[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"evidence"</span>])</span>
<span id="cb12-12"></span>
<span id="cb12-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Mermaid lineage diagram (Quarto-renderable, see section 8.3)</span></span>
<span id="cb12-14"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(graph.emit_mermaid())</span></code></pre></div>
<p>The JSON path uses a hand-rolled deterministic emit (zero external deps; the schema is documented at <code>crates/cjc-locke/src/json_emit.rs</code>). The v0.7+ deep dive hardened it: <code>parse_int → parse_u64</code> for unsigned fields (catches negative-wrap attacks), surrogate-pair handling in <code>\u</code> escapes, a thread-local interner so repeated parses don’t leak.</p>
<p>There’s also an HTML emit (<code>emit_locke_report_html</code>, accessed from the CLI side, not yet wired through the Python wrapper) that renders an inline-SVG Pearson heatmap when ≥3 numeric columns are present.</p>
</section>
<section id="the-determinism-contract" class="level3">
<h3 class="anchored" data-anchor-id="the-determinism-contract">8.8 The determinism contract</h3>
<p>The thing that distinguishes Locke from the alternatives, repeated in this post a few times because it matters: <strong>every Locke artefact is byte-identical across runs.</strong></p>
<ul>
<li>Finding IDs are content-addressed (SHA-256-derived 64-bit fingerprint over the finding’s logical content).</li>
<li>JSON output is canonical (BTreeMap-sorted keys, no timestamps, no UUIDs, no map-iteration noise).</li>
<li>Numeric reductions use Kahan summation (no parallel-order-dependence on floating-point sums).</li>
<li>BTreeMap and BTreeSet are used everywhere; no HashMap iteration order leaks.</li>
<li>The RNG is <code>SplitMix64</code> with explicit seed threading.</li>
<li>No SIMD path uses FMA (preserves bit-equality across CPU vendors).</li>
</ul>
<p>The deep-dive audit (Locke v0.7+) ran four parallel agent-clusters over ~26K LOC and found exactly four silent-correctness bugs over the lifetime of the codebase — <code>CategoricalAdaptive::cell_bytes</code> writing the row index instead of dereferencing the dictionary; <code>distinct_count</code> returning row count instead of dictionary length; <code>top_value_freq</code> returning 0; <code>ks_d_statistic</code> over-counting D on tied clusters. All four fixed; all four covered by regression tests. The audit-chain canaries at 629/629 unmoved across the entire fix series. That’s the determinism cost-of-doing-business.</p>
</section>
</section>
<section id="the-determinism-story-in-two-assertions" class="level2">
<h2 class="anchored" data-anchor-id="the-determinism-story-in-two-assertions">9. The determinism story, in two assertions</h2>
<div class="sourceCode" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb13-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> cjc_locke</span>
<span id="cb13-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> pandas <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> pd</span>
<span id="cb13-3"></span>
<span id="cb13-4">df <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pd.read_csv(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"adult_synth.csv"</span>)</span>
<span id="cb13-5">report_a <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cjc_locke.validate(df, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"adult-synth"</span>)</span>
<span id="cb13-6">report_b <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cjc_locke.validate(df, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"adult-synth"</span>)</span>
<span id="cb13-7"></span>
<span id="cb13-8"><span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">assert</span> report_a.to_json() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> report_b.to_json()       <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># always True</span></span>
<span id="cb13-9"><span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">assert</span> report_a.to_dict()[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"run_id"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> report_b.to_dict()[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"run_id"</span>]</span></code></pre></div>
<p>Two <code>cjc_locke.validate()</code> calls produce byte-identical JSON across runs, machines, and CI vendors (we test on Windows MSVC + Linux glibc; both match). The same dataset two days from now will produce the same <code>run_id</code> modulo any code changes to Locke itself. CI gates can use <code>git diff --no-index report_old.json report_new.json</code> to detect <em>any</em> change in either the data quality state or the Locke schema. Pandera and GE reports carry per-run timestamps; you can use them for diffs but they’re noisier.</p>
<p>A natural follow-up question: <em>does the report ever drift across Locke versions?</em> Yes — when the detector code changes. The v0.7+ deep-dive added some new finding codes (E9008, E9023, E9024 are recent) and tightened the threshold defaults on existing ones. Each release bump should be expected to shift report bytes for <em>some</em> inputs. The <code>schema_version</code> field at the top of the JSON pins which Locke schema the report belongs to.</p>
</section>
<section id="when-to-use-which-tool-honest-positioning" class="level2">
<h2 class="anchored" data-anchor-id="when-to-use-which-tool-honest-positioning">10. When to use which tool — honest positioning</h2>
<p>I built Locke and I’m comparing it to the alternatives. So this section is the one where I make explicit when Locke is <em>not</em> the right tool.</p>
<ul>
<li><p><strong>Use Pandera when</strong> you have a well-understood schema, you want type checks at API boundaries, you want pydantic-style declarative validation, you’re integrating with FastAPI or another schema-aware framework, you need the rich ecosystem of community-written checks.</p></li>
<li><p><strong>Use Great Expectations when</strong> you have a team that already runs GE, you need the dashboard / docs-rendering / Data Docs UI, you have non-engineering stakeholders who use the GE catalog, you need the years of accumulated expectations and community plugins.</p></li>
<li><p><strong>Use Locke when</strong> the data is large and varied enough that you can’t pre-register every expectation; <em>or</em> you need byte-identical reports for CI gates; <em>or</em> you need an audit chain that survives across the entire pipeline; <em>or</em> you need belief-score composition algebra; <em>or</em> you need both data-quality validation and lineage tracking in the same tool; <em>or</em> you’re already in CJC-Lang and want zero external deps.</p></li>
</ul>
<p>You can use all three at once. They answer different layers of the question, and they don’t fight each other.</p>
<p>Where Locke gets adopted, in practice: as the deterministic CI gate at the bottom of the stack (no flaking, byte-stable diffs) with Pandera or GE running above it as the <em>expressive</em> schema-validation layer that domain teams own.</p>
</section>
<section id="try-it-from-python" class="level2">
<h2 class="anchored" data-anchor-id="try-it-from-python">11. Try it from Python</h2>
<p>The Locke Rust crate has had a Python wrapper since the v0.7+ work in this session. The full install + smoke test:</p>
<div class="sourceCode" id="cb14" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb14-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">git</span> clone https://github.com/AdamEzzat1/CJC</span>
<span id="cb14-2"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">cd</span> CJC/python</span>
<span id="cb14-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">python</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-m</span> venv .venv</span>
<span id="cb14-4"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">source</span> .venv/Scripts/activate              <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Unix: .venv/bin/activate</span></span>
<span id="cb14-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">pip</span> install maturin numpy pandas</span>
<span id="cb14-6"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">maturin</span> develop <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--release</span>                  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ~5 min cold build</span></span>
<span id="cb14-7"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">python</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-c</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"import cjc_locke; print(cjc_locke.__version__)"</span></span></code></pre></div>
<p>Once built, the wrapper is a thin PyO3 binding (~900 LOC Rust + ~200 LOC Python facade). Every Python function delegates to one Rust call. Numpy <code>f64</code>/<code>i64</code>/<code>bool</code> arrays cross the FFI boundary zero-copy via the buffer protocol; pandas and polars DataFrames work via duck-typed adapters. Determinism is fully preserved — the Rust side runs unchanged and the wrapper doesn’t introduce threading.</p>
<p>To reproduce the numbers in this post:</p>
<div class="sourceCode" id="cb15" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb15-1"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">cd</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>path-to-this-post<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb15-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">python</span> generate_data.py                    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># writes adult_synth.csv</span></span>
<span id="cb15-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">python</span> run_locke.py                        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># runs all the analyses</span></span></code></pre></div>
<p>Both scripts are in this post’s directory (<a href="./generate_data.py"><code>generate_data.py</code></a>, <a href="./run_locke.py"><code>run_locke.py</code></a>).</p>
</section>
<section id="whats-next" class="level2">
<h2 class="anchored" data-anchor-id="whats-next">12. What’s next</h2>
<p>Locke is shipped to crates.io at v0.1.10. The Python wrapper from this post’s series is in the CJC-Lang repo at <code>python/</code>. Open questions I’m actively working on:</p>
<ul>
<li><strong>More real-world datasets</strong>: this post deliberately used a synthetic Adult clone because the structural quirks are reproducible. Running on the real 32K-row UCI file would surface the same findings; the next blog post in this series will pick a less-well-known dataset where there’s no pre-existing intuition about what Locke “should” find.</li>
<li><strong>GE-spec import</strong>: a translator from GE expectation files into Locke <code>ValidationConfig</code> overrides, so teams already on GE can run both side by side.</li>
<li><strong>Streaming summaries → direct validation</strong>: today’s <code>StreamingValidator::into_report()</code> rebuilds a sampled DataFrame and runs the standard validate. A v0.4-target rewrite would run every check incrementally so <code>sample_cap</code> no longer caps any check at its boundary.</li>
<li><strong>Hand-rolled canonical f64 printer</strong>: today’s deterministic JSON relies on Rust’s <code>f64::Debug</code> shortest-round-trip format, which has been stable in practice but is not a formal stdlib guarantee. A v0.8 hand-rolled printer would make the byte-identity contract independent of toolchain.</li>
</ul>
</section>
<section id="honest-critique-where-this-story-still-needs-to-improve" class="level2">
<h2 class="anchored" data-anchor-id="honest-critique-where-this-story-still-needs-to-improve">13. Honest critique: where this story still needs to improve</h2>
<p>After writing this post and the one before it, three weaknesses in Locke’s <em>adoption story</em> are clear to me — independent of the substrate, which is solid. I’m naming them rather than glossing because the framing matters as much as the engineering does.</p>
<section id="the-philosophy-is-currently-stronger-than-the-implementation-story" class="level3">
<h3 class="anchored" data-anchor-id="the-philosophy-is-currently-stronger-than-the-implementation-story">13.1 The philosophy is currently stronger than the implementation story</h3>
<p>This post says “Locke detects drift.” It doesn’t <em>show</em> you a train distribution next to a shifted production distribution next to the confidence degradation next to the causal warning next to the lineage trace next to the resulting belief report. Every one of those pieces exists in the code; none of them is in a single narrative flow.</p>
<p>The next batch of work needs concrete demos: an end-to-end Jupyter notebook on a real sklearn-shipped dataset (California Housing or similar) walking the full pipeline, with the belief score visibly degrading axis-by-axis as deliberate corruptions are introduced, and the lineage graph rendering inline. The more tangible the demos, the more impressive the system looks. The roadmap is in <a href="https://github.com/AdamEzzat1/CJC/blob/master/docs/locke/POST_LAUNCH_ADOPTION_HANDOFF.md"><code>docs/locke/POST_LAUNCH_ADOPTION_HANDOFF.md</code></a> under deliverables D2.1 through D2.4.</p>
</section>
<section id="python-ergonomics-matter-enormously" class="level3">
<h3 class="anchored" data-anchor-id="python-ergonomics-matter-enormously">13.2 Python ergonomics matter enormously</h3>
<p>The architecture is right — Rust crate, Python bindings via PyO3 — but the current Python wrapper is utilitarian: <code>repr(report)</code> returns a one-line tag, there’s no rich <code>_repr_html_</code> for Jupyter, no Arrow or Parquet zero-copy path, no <code>sklearn.Pipeline</code> integration, no pandas <code>Styler</code> highlighting flagged rows. If Python usage feels awkward people will admire the architecture without using it.</p>
<p>The roadmap entries D3.1 through D3.5 cover this:</p>
<ul>
<li><code>_repr_html_</code> on every output class (8-axis SVG for <code>BeliefReport</code>, severity-chip row for <code>LockeReport</code>, inline Mermaid for <code>LineageGraph</code>). Pure Python facade, no Rust changes.</li>
<li><code>pyarrow.Table</code> / <code>pyarrow.RecordBatch</code> zero-copy bridge so 10 GB Parquet files don’t have to round-trip through pandas.</li>
<li><code>LockeCheck(BaseEstimator, TransformerMixin)</code> so Locke fits into a scikit-learn <code>Pipeline</code> as the first stage (fit raises on policy gate failure; transform passes through).</li>
<li><code>style_with_locke(df, report)</code> returning a pandas <code>Styler</code> that highlights rows referenced in any finding’s <code>row_range</code>.</li>
</ul>
<p>None of these require Rust changes. They’re a couple of days of Python facade work each, and they unlock notebook fluency.</p>
</section>
<section id="avoid-sounding-philosophical-without-grounding" class="level3">
<h3 class="anchored" data-anchor-id="avoid-sounding-philosophical-without-grounding">13.3 Avoid sounding philosophical without grounding</h3>
<p>Words like “epistemic layer” and “evidence-bearing” and “meet-semilattice” are accurate technical terms but they risk reading as branding — “AI philosophy layer,” academic aesthetic — unless every use is <em>immediately</em> anchored to a code-visible fact.</p>
<p>For example, “belief reports are explainable” is currently asserted but the only “explanation” shown is the 8 axes themselves. A reader can’t see <em>why</em> a particular axis got a particular score without manually walking the findings list. The fix is to pair every philosophical claim with:</p>
<ul>
<li>A measurable system capability (<code>schema_axis: 0.78 because 12   E9002 findings and 4 E9023/E9024/E9040/E9041 notices fired against   the schema-relevant detector codes</code>).</li>
<li>A benchmark (a CI job that prints the actual byte-diff of two successive runs, demonstrating zero-byte drift).</li>
<li>A reproducible demo (an <code>axis_demos/</code> folder with one tiny script per axis, runnable in 5 seconds).</li>
<li>A failure case (what happens when this contract breaks — show two reports diverging when <code>BTreeMap</code> is swapped for <code>HashMap</code>).</li>
</ul>
<p>That last one is rhetorically the strongest. Readers care about non-determinism only when they see it silently break a system. A “what if Locke had a bug?” demo would do more for adoption than three paragraphs about determinism.</p>
</section>
<section id="why-this-is-in-the-post-rather-than-elsewhere" class="level3">
<h3 class="anchored" data-anchor-id="why-this-is-in-the-post-rather-than-elsewhere">13.4 Why this is in the post rather than elsewhere</h3>
<p>The deep-dive audit at v0.7+ found four silent-correctness bugs that unit tests didn’t catch — integration tests caught them. The same principle applies here: the substrate is healthy under unit-test scrutiny, but the adoption layer hasn’t been integration-tested against real users yet. The three areas above are the integration findings. Naming them inline keeps the story honest.</p>
<p>The next post in this series will be the end-to-end notebook (D2.1 above) with the belief score visibly degrading across stages, the lineage rendering inline, and the determinism contract demonstrated on a real dataset. That’ll be the “this is what infrastructure actually looks like in use” piece, and it’s the one that needs to exist before Locke can claim “ready for adoption.”</p>
</section>
</section>
<section id="acknowledgements" class="level2">
<h2 class="anchored" data-anchor-id="acknowledgements">14. Acknowledgements</h2>
<p>Pandera is a great library that I use elsewhere and that I’m not knocking. Great Expectations is mature and broadly adopted; the existing benchmark post in this series (<a href="../../../blog/posts/cjc-locke-v0.1-released/index.html">Locke v0.1.10</a>) covers GE in more depth. The UCI Adult dataset is Ron Kohavi’s 1996 contribution to the UCI Machine Learning Repository. The synthetic clone in this post is a deterministic stand-in that reproduces the <em>quality issues</em> of Adult; it is not the real dataset.</p>
<p>The Python wrapper is in the CJC-Lang repo at <code>python/</code> and the Rust crate is at <code>crates/cjc-locke/</code>. Both are MIT-licensed.</p>


</section>

 ]]></description>
  <category>CJC-Lang</category>
  <category>Locke</category>
  <category>data-quality</category>
  <category>validation</category>
  <category>tutorial</category>
  <category>comparison</category>
  <category>pandera</category>
  <category>great-expectations</category>
  <category>python</category>
  <category>adult</category>
  <guid>https://adamezzat1.github.io/blog/posts/locke-side-by-side-pandera-ge/index.html</guid>
  <pubDate>Sat, 30 May 2026 07:00:00 GMT</pubDate>
</item>
<item>
  <title>Diabetes-130 Revisited: ABNG Climbs Into the Strong-Model Band — and an Honest Account of What Locke Did (and Didn’t)</title>
  <dc:creator>Adam Ezzat</dc:creator>
  <link>https://adamezzat1.github.io/blog/posts/abng-diabetes-revisited-with-locke/index.html</link>
  <description><![CDATA[ 



<div class="reading-time">
🕑 ~30 min read
</div>
<div class="stats-row">
<div class="stat">
<span class="number">0.6645 ± 0.0018</span><span class="stat-label">AUC, n=3 seeds, 101K rows</span>
</div>
<div class="stat">
<span class="number">142</span><span class="stat-label">Locke findings emitted</span>
</div>
<div class="stat">
<span class="number">629/629</span><span class="stat-label">determinism canaries unmoved</span>
</div>
<div class="stat">
<span class="number">0</span><span class="stat-label">MLP layers used</span>
</div>
</div>
<div class="callout callout-style-simple callout-tip no-icon callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-title-container flex-fill">
TL;DR
</div>
</div>
<div class="callout-body-container callout-body">
<p>A second pass on UCI Diabetes 130-US Hospitals — the same 101,766-encounter readmission dataset from <a href="../../../blog/posts/abng-diabetes-readmission/index.html">the first ABNG post</a> — with two changes:</p>
<ol type="1">
<li><strong>Land the validation-tuned config</strong> (<code>K_ROUTING = 2</code>, stronger BLR prior) that the first post’s sweep identified but never committed back to source.</li>
<li><strong>Run the full dataset at multiple seeds</strong> instead of a 20K sub-sample at a single seed.</li>
</ol>
<p>Held-out test AUC moves <strong>0.611 → 0.6312 → 0.6621 → 0.6645 ± 0.0018</strong>: tuned config at 20K, then full 101K single-seed, then 3-seed variance. <strong>0.6645 is in the strong-published-model band (0.64–0.68)</strong> — the band the first post said required nonlinear MLP heads. It turns out the linear BLR + good config + full data was enough.</p>
<p>Locke (the <a href="../../../blog/posts/cjc-locke-v0.1-released/index.html">determinism-first data-quality layer</a>) ran the audit. <strong>It did not drive the AUC headline.</strong> The handoff predicted E9063 multi-class leakage would fire on <code>discharge_disposition_id</code> (death/hospice codes deterministically predict <code>readmitted=NO</code>); it didn’t, because per-feature ROC-AUC misses per-<em>level</em> deterministic outcomes. Applying the prune anyway via domain knowledge <em>dropped</em> AUC by 0.0118 — a confounded experiment, because the prune also shifted MI-selected routing features. Locke’s actual contributions this session were structural: surfacing a patient-encounter leakage hole the first post missed (E9004), exposing its own <code>?</code>-sentinel blindness on medical CSVs, and pinning a concrete failure mode for a proposed E9064 detector. <strong>AUC was ABNG’s win; the systems / detector-roadmap axis was Locke’s.</strong></p>
</div>
</div>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Prototype disclaimer
</div>
</div>
<div class="callout-body-container callout-body">
<p>ABNG and Locke are both research-stage prototypes built on <strong>CJC-Lang</strong>, a research compiler that is itself pre-1.0. All three are under active development. The numbers below are a single reproducible experiment campaign — five commits, every chain head verifiable — not a validated benchmark, not deployed, not peer-reviewed. Read as honest exploratory engineering.</p>
</div>
</div>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Where this sits
</div>
</div>
<div class="callout-body-container callout-body">
<ul>
<li><a href="../../../blog/posts/abng-architecture/index.html">ABNG: Treating Belief States as First-Class Citizens</a> — the architecture this post measures.</li>
<li><a href="../../../blog/posts/abng-diabetes-readmission/index.html">ABNG on Real Clinical Data — The Diabetes-130 Readmission Task</a> — <strong>Round One.</strong> The first post’s AUC 0.611 is the baseline this post improves on.</li>
<li><a href="../../../blog/posts/cjc-locke-v0.1-released/index.html">Locke v0.1.10: An Epistemic Layer for Data Validation</a> — Locke’s framing as evidence-aware, determinism-first audit.</li>
<li><a href="../../../blog/posts/abng-benchmarks/index.html">Benchmarks Part I</a> / <a href="../../../blog/posts/abng-benchmarks-part-2/index.html">Part II</a> — controlled-data baselines.</li>
<li><strong>This post</strong> — Round Two of the same dataset, now with both tools in the loop, and an honest dissection of what each one actually contributed.</li>
</ul>
</div>
</div>
<section id="whats-new-since-round-one" class="level2">
<h2 class="anchored" data-anchor-id="whats-new-since-round-one">1. What’s new since Round One</h2>
<p>The <a href="../../../blog/posts/abng-diabetes-readmission/index.html">first diabetes-130 post</a> ended with three explicit “still on the bench” items:</p>
<ol type="1">
<li>Nonlinear leaf heads (MLP) — to see if the <em>ranking</em> ceiling moved.</li>
<li>The full 101,766-row run at the tuned configuration.</li>
<li>Multi-seed variance.</li>
</ol>
<p>Round Two lands items <strong>2 and 3</strong>, leaves item <strong>1</strong> as a design doc, and adds two new ones that the first post didn’t anticipate:</p>
<ol start="4" type="1">
<li><strong>Locke pre-training audit.</strong> Run <code>cjcl locke validate</code> on the full CSV, see what the determinism-first data-quality layer surfaces, and act on it.</li>
<li><strong>Belief-weighted per-leaf priors.</strong> Use Locke’s per-leaf <code>BeliefScore</code> to weight per-leaf BLR regularization — the directional hypothesis from <a href="https://github.com/AdamEzzat1/CJC/blob/master/docs/locke/ABNG_PER_LEAF_BELIEF.md"><code>docs/locke/ABNG_PER_LEAF_BELIEF.md</code></a>.</li>
</ol>
<p>That’s the plan. The result is more interesting than the plan, because items <strong>1, 4, and 5 did not work the way the brief expected</strong> — and item <strong>1 turned out to be unnecessary</strong> for the headline. The honest accounting of which lever moved which number is half the point of this post.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
What carries forward unchanged
</div>
</div>
<div class="callout-body-container callout-body">
<p><strong>Determinism contract, audit chain, evaluation protocol — all unchanged.</strong> The 70/15/20-style stratified split, fit-on-train-only categorical transform, validation-set-only tuning, single test-set touch, deterministic SplitMix64 RNG, Kahan summation, BTreeMap iteration — all the discipline the first post’s §2 spelled out remains. 629/629 always-run ABNG tests stayed green across five commits of aggressive configuration changes. Every chain head in this post is bit-reproducible. The first post’s §7 reproducibility property — “two runs, 3× wall-clock difference, every metric digit identical” — held throughout.</p>
</div>
</div>
</section>
<section id="the-dataset-briefly-why-its-still-hard" class="level2">
<h2 class="anchored" data-anchor-id="the-dataset-briefly-why-its-still-hard">2. The dataset, briefly — why it’s still hard</h2>
<p>The first post’s §1 walked through the dataset in detail. The short version, because the difficulty profile matters for reading the rest of this post:</p>
<p>UCI <strong>Diabetes 130-US Hospitals (1999–2008)</strong> — 101,766 hospital encounters, 50 columns, deeply heterogeneous. By ML standards it’s genuinely difficult:</p>
<ul>
<li><strong>101,766 rows × 50 columns</strong>. 13 numeric, 23 medication categoricals (one per drug — <code>metformin</code>, <code>insulin</code>, <code>glipizide</code>, etc.), three ICD-9 diagnosis-code columns with <strong>700+ distinct levels each</strong>, demographics, admission / discharge / source codes.</li>
<li><strong>Missingness sentinel is <code>?</code></strong> (literal string), not NaN. <code>weight</code> is 96.9% missing. <code>payer_code</code> and <code>medical_specialty</code> are heavily missing. Standard tooling that treats only NaN as missing won’t see any of this without configuration.</li>
<li><strong>Longitudinal structure.</strong> 16,773 patients have multiple encounters (30,248 duplicate <code>patient_nbr</code> values). Row-level train/test splitting lets the same patient appear in both splits. The first post used row-level splitting; we’ll come back to this in §5.</li>
<li><strong>3-class target</strong> (<code>NO</code> / <code>&lt;30</code> / <code>&gt;30</code> days to readmission), binarised to <code>&lt;30 vs rest</code>. <strong>11.16% positive class</strong> — predicting <code>NO</code> for everyone scores ~89% accuracy with zero learning. Accuracy is the wrong yardstick; the post leads with AUC, balanced accuracy, and calibrated Brier/NLL/ECE.</li>
<li><strong>Hard target.</strong> Strong published models band at <strong>AUC 0.64–0.68</strong>. The ceiling is <em>irreducible noise</em> — readmission depends on post-discharge factors (social support, follow-up care, medication adherence) not in the data. A model claiming AUC 0.9 here would be suspect, not impressive.</li>
</ul>
<div id="fig-missingness" class="quarto-figure quarto-figure-center anchored">
<figure class="figure">
<p><img src="https://adamezzat1.github.io/blog/posts/abng-diabetes-revisited-with-locke/figures/traditional/per_column_missingness.png" class="img-fluid figure-img" alt="Horizontal bar chart of 7 columns with non-zero ?-rates. weight stands out at 96.9% in red. medical_specialty and payer_code at 49.1% and 39.6% in orange. race, diag_3, diag_2, diag_1 are tiny purple bars (<3%). A pale green band highlights the >50% missing zone with the label 'drop or impute'."></p>
<figcaption class="figure-caption">Figure&nbsp;1: Per-column <code>?</code>-sentinel missingness across the 50 diabetes-130 columns. Only 7 columns contain any <code>?</code> values, and the first three carry essentially all of the missingness: <code>weight</code> at <strong>96.9%</strong> (effectively useless as a feature), <code>medical_specialty</code> at 49.1%, and <code>payer_code</code> at 39.6%. The green band marks the “≥50% missing — drop or impute” zone. This concentration explains why Locke’s default missingness detector returning <code>missingness_score = 1.0</code> is so misleading on this dataset (see §6) — almost all the missingness is hiding in three columns that don’t fire under the NaN-only default config.</figcaption>
</figure>
</div>
<p>It’s exactly the kind of dataset where the easy wins (high raw accuracy) are traps, the obvious leakages (death-discharge codes deterministically predicting “no readmission”) hide at the level not the column, and the structural issues (repeated patients) don’t show up in any single AUC number.</p>
</section>
<section id="landing-the-tuned-config-the-first-0.05-auc" class="level2">
<h2 class="anchored" data-anchor-id="landing-the-tuned-config-the-first-0.05-auc">3. Landing the tuned config — the first 0.05 AUC</h2>
<p>The first post’s §5 ran a validation sweep over <code>K_ROUTING ∈ {2, 3, 4}</code> × BLR prior precision <code>∈ {0.05, 0.1, 0.5}</code> and concluded:</p>
<blockquote class="blockquote">
<p><strong>K = 2 won, decisively.</strong> With ~14,000 training rows, K=2 gives 16 leaves (~875 rows each), while K=4 gives 256 leaves (~55 rows each). Deeper routing buys “specialization” but starves every leaf of the data it needs. (Stronger prior regularization also helped, monotonically.)</p>
</blockquote>
<p>But the harness’s source code (in <code>tests/abng/dataset_a_diabetes130.rs</code>) still had <code>K_ROUTING = 3</code> and <code>BLR_PRIOR_PRECISION = 0.1</code>. The sweep result lived in the blog post; it never came home to the harness.</p>
<p>§4.A landed the validated config:</p>
<div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb1-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">const</span> K_ROUTING<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">usize</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>           <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// was 3</span></span>
<span id="cb1-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">const</span> BLR_PRIOR_PRECISION<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">f64</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// was 0.1</span></span></code></pre></div>
<p>Two constants. No new mechanisms.</p>
<section id="what-the-change-did-at-20k" class="level3">
<h3 class="anchored" data-anchor-id="what-the-change-did-at-20k">What the change did at 20K</h3>
<p>The 20K trial (same stratified sub-sample as the first post, seed 42, 80/20 train/test) moved as expected:</p>
<table class="table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th>Metric</th>
<th>Before (K=3, prior=0.1)</th>
<th>After (K=2, prior=0.5)</th>
<th>First post (K=2, calibrated)</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>AUC</td>
<td>0.5760</td>
<td><strong>0.6312</strong></td>
<td>0.6107</td>
</tr>
<tr class="even">
<td>Brier</td>
<td>0.1049</td>
<td>0.0979</td>
<td>0.0980</td>
</tr>
<tr class="odd">
<td>NLL</td>
<td>0.5259</td>
<td>0.3666</td>
<td>0.3435</td>
</tr>
<tr class="even">
<td>ECE</td>
<td>0.0573</td>
<td>0.0279</td>
<td>0.0101</td>
</tr>
<tr class="odd">
<td>Populated/total leaves</td>
<td>45 / 64</td>
<td>12 / 16</td>
<td>—</td>
</tr>
<tr class="even">
<td>Min/mean/max rows per leaf</td>
<td>1 / 355.5 / 1,583</td>
<td>428 / 1,333.2 / 3,462</td>
<td>—</td>
</tr>
<tr class="odd">
<td>Routing cols (MI top-K)</td>
<td><code>[17, 9, 21]</code></td>
<td><code>[17, 9]</code></td>
<td>—</td>
</tr>
</tbody>
</table>
<div id="fig-auc-trajectory" class="quarto-figure quarto-figure-center anchored">
<figure class="figure">
<p><img src="https://adamezzat1.github.io/blog/posts/abng-diabetes-revisited-with-locke/figures/traditional/auc_trajectory.png" class="img-fluid figure-img" alt="Bar chart showing AUC trajectory from 0.5760 (first post raw 20K) through 0.6107 (first post calibrated), 0.6312 (§4.A tuned), 0.6194 (§4.B pruned — negative result in red), 0.6621 (§4.E full 101K) to 0.6645 (§4.F 3-seed mean). Strong-published-model band (0.64-0.68) shaded in green; final two bars cross into it."></p>
<figcaption class="figure-caption">Figure&nbsp;2: Held-out test AUC across the full Round Two trajectory. The dashed grey line marks the first post’s calibrated 0.611; the green shaded band marks the strong-published-model band (0.64–0.68); the §4.B “negative result” bar (red/orange in cyberpunk) is the Locke-driven prune that <em>dropped</em> AUC; the final §4.F bar is the 3-seed mean with a ± std error bar (in green). The headline shifted from “tuned config helped” (§4.A) to “scale + config together cleared the strong-model band” (§4.E/F).</figcaption>
</figure>
</div>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
The 20K result is <em>higher</em> than the first post’s
</div>
</div>
<div class="callout-body-container callout-body">
<p>The raw AUC 0.6312 is <strong>+0.020 above the first post’s published 0.6107</strong>. Two candidate causes:</p>
<ol type="1">
<li><strong>Different split protocol.</strong> The harness uses 80/20 train/test (n_train ≈ 15,999). The first post used 70/15/20 train/val/test (n_train ≈ 14,000). More training data → slightly higher AUC.</li>
<li><strong>Stratified sub-sample RNG path.</strong> The harness’s stratified-by-class sub-sample and the first post’s may differ in iteration order; same seed, different exact rows.</li>
</ol>
<p>This is itself instructive: <strong>the first post’s headline 0.611 is not the harness’s ceiling at that config.</strong> It was a defensible number under that protocol, but the same model with a different valid evaluation protocol lands higher.</p>
</div>
</div>
<p>Leaf utilisation is much better too: 12/16 populated (vs 45/64 = 70% before), and the smallest populated leaf has 428 rows (vs 1 — a single-row leaf where a BLR has essentially no information). The mean rises from 355 to 1,333 — every leaf now has enough data to train a meaningful posterior.</p>
<p><code>time_in_hospital</code> and <code>num_inpatient</code> are the MI-selected routing features at 20K. We’ll come back to that in §5 — they don’t stay the winners at full scale.</p>
</section>
</section>
<section id="lockes-audit-142-findings-one-critical-negative-result" class="level2">
<h2 class="anchored" data-anchor-id="lockes-audit-142-findings-one-critical-negative-result">4. Locke’s audit — 142 findings, one critical negative result</h2>
<p>Before scaling up the data, the brief said: <strong>run Locke</strong>. Audit the full 101,766-row CSV. Use the findings to drop leakage rows before training. Expected AUC bump: 0.62–0.63 from the prune alone.</p>
<p>The Locke run is one CLI invocation:</p>
<div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb2-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">./target/release/cjcl.exe</span> locke validate <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb2-2">    tests/data/diabetes_130/diabetic_data.csv <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb2-3">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--target</span> readmitted <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb2-4">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--primary-key</span> patient_nbr <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb2-5">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--save-json</span> bench_results/diabetes_phase_0_10_section_4b/diabetes_locke_baseline.json <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb2-6">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--html</span>        bench_results/diabetes_phase_0_10_section_4b/diabetes_locke_baseline.html</span></code></pre></div>
<p>It emits 142 findings:</p>
<table class="table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Severity</th>
<th>Count</th>
<th>Worth acting on this session?</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Error</td>
<td>1</td>
<td>YES (see §4.1 below — but it’s a structural concern, not a row filter)</td>
</tr>
<tr class="even">
<td>Warning</td>
<td>8</td>
<td>NO (E9082 near-duplicate categorical labels — <code>[0-25)</code> vs <code>[50-75)</code> etc.; real distinctions)</td>
</tr>
<tr class="odd">
<td>Notice</td>
<td>75</td>
<td>One worth acting on: E9072 ID-like cardinality (already handled by <code>Ignore</code> role)</td>
</tr>
<tr class="even">
<td>Info</td>
<td>58</td>
<td>E9002 type-only diagnostics</td>
</tr>
</tbody>
</table>
<div id="fig-findings-sunburst" class="quarto-figure quarto-figure-center anchored">
<figure class="figure">
<p><img src="https://adamezzat1.github.io/blog/posts/abng-diabetes-revisited-with-locke/figures/traditional/locke_findings_sunburst.png" class="img-fluid figure-img" alt="Sunburst chart of 142 Locke findings. Centre ring divided into 4 wedges by severity (error tiny, warning small, notice largest, info large). Outer ring shows individual E-codes within each severity wedge — E9002 dominates info, E9073 dominates notice. E9063 absent."></p>
<figcaption class="figure-caption">Figure&nbsp;3: Sunburst of all 142 Locke findings. Inner ring: 4 severity levels (error 1, warning 8, notice 75, info 58). Outer ring: the 15 unique E-codes within each severity. <strong><code>notice</code> dominates by area</strong> — 75 findings, almost all of them E9073 (×44, duplicate-key disagreements — a single column’s worth of disagreement counted ~40 times across 40 columns) and E9011 (×15, high-cardinality categoricals). The single <code>error</code> (E9004) is the patient_nbr duplicate-key finding called out in §4.1. <strong>E9063 (multi-class leakage on <code>discharge_disposition_id</code>) is <em>not</em> in the sunburst</strong> — it never fired, which is itself the post’s load-bearing Locke finding.</figcaption>
</figure>
</div>
<div id="fig-locke-heatmap" class="quarto-figure quarto-figure-center anchored">
<figure class="figure">
<p><img src="https://adamezzat1.github.io/blog/posts/abng-diabetes-revisited-with-locke/figures/traditional/locke_pearson_heatmap.png" class="img-fluid figure-img" alt="Pearson r heatmap, 6x6 grid of numeric columns: encounter_id, patient_nbr, admission_type_id, discharge_disposition_id, num_lab_procedures, diag_1. Diagonal cells are 1.00 in bold pink. The encounter_id × patient_nbr cell shows r=0.512 in lighter pink, called out with an arrow. All other off-diagonals are near-zero in dark teal."></p>
<figcaption class="figure-caption">Figure&nbsp;4: Pearson |r| heatmap — themed Plotly recreation of the actual SVG Locke emitted (raw verbatim version preserved at <a href="figures/locke/pearson_heatmap_raw.svg"><code>figures/locke/pearson_heatmap_raw.svg</code></a> for verification). The Pearson values are byte-identical to what <code>cjcl locke validate</code> produced. The only off-diagonal correlation that pops is <code>encounter_id</code> × <code>patient_nbr</code> (r = 0.512) — the patient-encounter overlap that E9004 separately flagged. Everything else is &lt; 0.16 in absolute value, consistent with the rest of the dataset’s heterogeneity.</figcaption>
</figure>
</div>
<p>The single Error is interesting and the negative result is more interesting. Let’s take them in order.</p>
<section id="e9004-the-leakage-hole-the-first-post-missed" class="level3">
<h3 class="anchored" data-anchor-id="e9004-the-leakage-hole-the-first-post-missed">4.1 E9004 — the leakage hole the first post missed</h3>
<pre class="text"><code>code=E9004 severity=error column=patient_nbr
  message: key column `patient_nbr` has 30248 duplicate values
           across 16773 groups
  evidence:
    - duplicate_keys=30248
    - duplicate_groups=16773</code></pre>
<p><strong>30,248 duplicate <code>patient_nbr</code> values across 16,773 groups</strong> — i.e.&nbsp;16,773 patients had multiple hospital encounters in the dataset. This is correct medical data (real patients return), but it creates a leakage path: if the train/test split is <em>row-level</em> (encounter-level), the same patient can appear in both splits. The model can then learn features about that patient and “look up” their outcome in the test set.</p>
<p>The first post used row-level splitting. So did this Round Two. <strong>A truly leakage-free protocol would split by <code>patient_nbr</code></strong>, which would reduce effective training data (more conservative splits typically do) but would tighten the leakage story.</p>
<p>Locke surfaced this without running a single model. AUC numbers wouldn’t have shown it. Whether the encounter-level result is “real signal” or “partial patient-memorisation” can only be decided by re-running with a patient-level split — recorded as future work in §10.</p>
</section>
<section id="e9063-the-predicted-leakage-finding-that-didnt-fire" class="level3">
<h3 class="anchored" data-anchor-id="e9063-the-predicted-leakage-finding-that-didnt-fire">4.2 E9063 — the predicted leakage finding that didn’t fire</h3>
<p>The handoff predicted that <strong>E9063 (multi-class target leakage)</strong> would fire on <code>discharge_disposition_id</code>. The reasoning: codes 11, 13, 14, 19, 20, 21 in that column correspond to <em>death</em> and <em>hospice</em> outcomes. A dead patient can’t be readmitted by construction; those rows are deterministically <code>readmitted = NO</code>. Leakage.</p>
<p>E9063 did not fire. Looking at the detector’s logic (<a href="https://github.com/AdamEzzat1/CJC/blob/master/crates/cjc-locke/src/leakage.rs">crates/cjc-locke/src/leakage.rs:229</a>), the reason becomes clear: E9063 uses <strong>per-feature ROC-AUC</strong> (max one-vs-rest) against the multi-class target. Death codes (11, 13, 14, 19, 20, 21) sit <em>interspersed</em> in the numeric range with non-death codes (12, 15, 16, 17, 18). So the column-wide rank statistic doesn’t separate cleanly — most non-death codes also predict <code>NO</code> (89% base rate), so the AUC stays below the 0.85 warning threshold.</p>
<p><strong>The leakage exists at the level — “code 11 → NO with probability ~1.0” — not at the column.</strong> ROC-AUC can’t see level-by-level conditional probability.</p>
<p>This is itself a publishable Locke result. A concrete detector gap, pinned to a concrete real-world example:</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Proposed E9064 — per-level conditional-probability leakage detector
</div>
</div>
<div class="callout-body-container callout-body">
<p>For each <code>(column, level, target_class)</code> triple, compute P(target = class | column = level). Flag when:</p>
<ul>
<li><code>P(class \| level) ≥ 0.99</code> with at least N supports</li>
<li>And <code>P(class)</code> (unconditional) <code>&lt; 0.99</code> — i.e.&nbsp;the level adds <em>new</em> information</li>
</ul>
<p>This would catch the discharge-code pattern and analogous per-level deterministic-outcome leakages: specific procedure codes that always predict survival, specific lab values that always predict a diagnosis, specific zip codes that always predict insurance class, etc. The data is structured this way more often than column-wide rank-sum detectors admit.</p>
<p>Recorded as a Locke roadmap item with this dataset as the canonical motivating example.</p>
</div>
</div>
</section>
<section id="we-applied-the-prune-anyway-and-auc-dropped" class="level3">
<h3 class="anchored" data-anchor-id="we-applied-the-prune-anyway-and-auc-dropped">4.3 We applied the prune anyway — and AUC dropped</h3>
<p>Domain knowledge still says drop the death-discharge rows. The detector missed; we know better; filter and retry.</p>
<p>Added <code>filter_out_death_discharges</code> in the harness — drops rows where <code>discharge_disposition_id ∈ {11, 13, 14, 19, 20, 21}</code>. That’s <strong>2,423 of 101,766 rows (2.38%)</strong>, all <code>readmitted = NO</code>. After the filter, the positive-class proportion rises slightly (11.16% → 11.43%).</p>
<p>Re-ran the 20K trial at the same config (K=2, prior=0.5, seed 42):</p>
<table class="table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th>Metric</th>
<th>§4.A (no prune)</th>
<th>§4.B (with prune)</th>
<th>Δ</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>AUC</td>
<td><strong>0.6312</strong></td>
<td><strong>0.6194</strong></td>
<td><strong>−0.0118</strong></td>
</tr>
<tr class="even">
<td>Brier</td>
<td>0.0979</td>
<td>0.0993</td>
<td>+0.0014</td>
</tr>
<tr class="odd">
<td>NLL</td>
<td>0.3666</td>
<td>0.3680</td>
<td>+0.0014</td>
</tr>
<tr class="even">
<td>ECE</td>
<td>0.0279</td>
<td>0.0229</td>
<td>−0.0050</td>
</tr>
<tr class="odd">
<td>Bal. accuracy</td>
<td>0.5084</td>
<td>0.5094</td>
<td>+0.0010</td>
</tr>
<tr class="even">
<td><strong>Routing cols (MI top-K)</strong></td>
<td><strong><code>[17, 9]</code></strong></td>
<td><strong><code>[17, 16]</code></strong></td>
<td><strong>CHANGED</strong></td>
</tr>
<tr class="odd">
<td>Populated leaves</td>
<td>12 / 16</td>
<td>6 / 16</td>
<td>−6</td>
</tr>
<tr class="even">
<td>Mean rows/leaf</td>
<td>1,333</td>
<td>2,667</td>
<td>+1,334</td>
</tr>
</tbody>
</table>
<p><strong>Removing leakage rows hurt the model.</strong> That looks paradoxical until you read the bottom of the table: the MI-selected routing columns changed. Before the prune, the top-K=2 routing features were <code>[17, 9]</code> = <code>num_inpatient</code> + <code>time_in_hospital</code>. After the prune, they shifted to <code>[17, 16]</code> = <code>num_inpatient</code> + <code>number_emergency</code>. <code>time_in_hospital</code> was a stronger routing feature than <code>number_emergency</code> at 20K — when the prune removes the correlation between <code>time_in_hospital</code> and the target (death patients had specific hospital-stay durations), the MI selector picks the next best feature, which happens to be less useful.</p>
<p>This is a <strong>confounded experiment</strong>. The §4.B intervention affects both (i) leakage row removal and (ii) downstream routing-feature selection. To isolate (i), you’d need to <em>force</em> the same routing columns across both configs — override the MI selector. That’s not in §4.B’s scope; this is now a documented finding for the next experiment campaign.</p>
<div id="fig-confounded-flow" class="quarto-figure quarto-figure-center anchored">
<figure class="figure">
<p><img src="https://adamezzat1.github.io/blog/posts/abng-diabetes-revisited-with-locke/figures/traditional/confounded_experiment_flow.png" class="img-fluid figure-img" alt="Flow diagram. Top yellow box: §4.B intervention (drop discharge codes 11,13,14,19,20,21, −2,423 rows). Two arrows branch down to: green Effect A box (Leakage rows removed) and orange Effect B box (MI re-selects routing features, time_in_hospital → number_emergency). Both arrows converge into a bottom orange Net Effect box (AUC drops 0.6312 → 0.6194, routing-feature loss > leakage-removal gain, marked as confounded experiment)."></p>
<figcaption class="figure-caption">Figure&nbsp;5: §4.B confounded-experiment flow. The prune does two things at once: <strong>Effect A</strong> removes the leakage rows (which would help on its own), but <strong>Effect B</strong> shifts the MI-selected routing-feature pair from <code>[num_inpatient, time_in_hospital]</code> to <code>[num_inpatient, number_emergency]</code> — a strictly weaker top-K=2 pair at 20K. The downstream MI shift dominated the leakage-removal gain. The footnote captures the deeper insight: the same MI pair <code>[num_inpatient, number_emergency]</code> wins at 101K scale anyway (§5.2), so Effect B was about to happen as data scaled. §4.B surfaced it early.</figcaption>
</figure>
</div>
<p>But there’s a more interesting insight in the routing change: <code>[17, 16]</code> is the <em>same</em> routing pair the model picks at full 101K scale (§5 below). Removing the leakage rows at 20K shifts MI in the same direction <em>scaling up</em> shifts it. The §4.B negative result is, in part, MI selection drift that was about to happen anyway.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
What this means for the handoff hypothesis
</div>
</div>
<div class="callout-body-container callout-body">
<p>The handoff said “Locke surfaces E9063 → drop death rows → AUC up to 0.62–0.63.” Of the three steps:</p>
<ul>
<li><strong>Locke surfaces E9063</strong> — didn’t happen (§4.2; the detector misses this pattern).</li>
<li><strong>Drop death rows</strong> — happened anyway via domain knowledge.</li>
<li><strong>AUC up to 0.62–0.63</strong> — didn’t happen; AUC went <em>down</em> by 0.012.</li>
</ul>
<p>Three falsified predictions chained — each one its own finding. The chain doesn’t work in this form on this dataset. Whether it works at all on this model class is an open question — see §10.</p>
</div>
</div>
</section>
</section>
<section id="full-101k-multi-seed-the-headline" class="level2">
<h2 class="anchored" data-anchor-id="full-101k-multi-seed-the-headline">5. Full 101K, multi-seed — the headline</h2>
<p>The first post left “the full 101,766-row run at the tuned configuration” as future work. §4.E delivers it.</p>
<section id="single-seed-at-full-scale" class="level3">
<h3 class="anchored" data-anchor-id="single-seed-at-full-scale">5.1 Single seed at full scale</h3>
<p>One call to the existing <code>diabetes130_full_run</code> test (which uses <code>budget = usize::MAX</code>, i.e.&nbsp;the entire dataset). Wall clock 213.84 s (≈ 3.6 min). Seed 42.</p>
<table class="table">
<colgroup>
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
</colgroup>
<thead>
<tr class="header">
<th>Metric</th>
<th>§4.E (full 101K)</th>
<th>§4.A (20K)</th>
<th>First post (calibrated, 20K)</th>
<th>Strong-model band</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>AUC</strong></td>
<td><strong>0.6621</strong></td>
<td>0.6312</td>
<td>0.611</td>
<td>0.64–0.68 ✓</td>
</tr>
<tr class="even">
<td>Brier</td>
<td><strong>0.0953</strong></td>
<td>0.0979</td>
<td>0.098</td>
<td>—</td>
</tr>
<tr class="odd">
<td>NLL</td>
<td><strong>0.3349</strong></td>
<td>0.3666</td>
<td>0.344</td>
<td>—</td>
</tr>
<tr class="even">
<td>ECE</td>
<td><strong>0.0044</strong></td>
<td>0.0279</td>
<td>0.010</td>
<td>—</td>
</tr>
<tr class="odd">
<td>Balanced accuracy</td>
<td>0.5012</td>
<td>0.5084</td>
<td>~0.58</td>
<td>—</td>
</tr>
<tr class="even">
<td>F1 @ threshold = 0.5</td>
<td>0.0061</td>
<td>0.0385</td>
<td>~0.24</td>
<td>—</td>
</tr>
<tr class="odd">
<td>n_train</td>
<td>81,412</td>
<td>15,999</td>
<td>~14,000</td>
<td>—</td>
</tr>
<tr class="even">
<td>Chain head</td>
<td><code>56af1961…b3233b6</code></td>
<td>—</td>
<td>—</td>
<td>—</td>
</tr>
</tbody>
</table>
<p><strong>AUC 0.6621 — in the strong-published-model band (0.64–0.68)</strong>. Without any architectural change. Without calibration. Still on the linear BLR head.</p>
<p>The calibration metrics (Brier 0.0953, NLL 0.3349, ECE 0.0044) are all <strong>better than the first post’s <em>calibrated</em> values</strong> (0.098 / 0.344 / 0.010) — and that comparison is against post-Platt numbers, while these are <em>raw</em>. The increased data volume at the same model class produces raw outputs that are already well-calibrated.</p>
<p>The first post predicted: “Calibration is a monotonic transform, so it <em>cannot</em> change the ranking, and it didn’t… A nonlinear MLP-leaf-head variant is the next experiment — it may lift AUC, or may need a lot of tuning.”</p>
<p>The implication was that <strong>AUC ≥ 0.64 required the MLP</strong>. It did not. Data scale was enough.</p>
</section>
<section id="routing-columns-flipped-at-full-scale" class="level3">
<h3 class="anchored" data-anchor-id="routing-columns-flipped-at-full-scale">5.2 Routing columns flipped at full scale</h3>
<p>The MI-selected routing features at full 101K are <code>[17, 16]</code> — <code>num_inpatient</code> + <code>number_emergency</code>. Not <code>[17, 9]</code> (which won at 20K).</p>
<div id="fig-mi-routing" class="quarto-figure quarto-figure-center anchored">
<figure class="figure">
<p><img src="https://adamezzat1.github.io/blog/posts/abng-diabetes-revisited-with-locke/figures/traditional/mi_routing_scale.png" class="img-fluid figure-img" alt="Two horizontal bar charts side by side. Left: 20K trial ranks num_inpatient and time_in_hospital as the top 2 selected features. Right: 101K full ranks num_inpatient and number_emergency as the top 2; time_in_hospital drops to position 3 and is not selected."></p>
<figcaption class="figure-caption">Figure&nbsp;6: MI-ranked routing features at 20K (§4.A) and 101K (§4.E). At 20K, <code>time_in_hospital</code> is ranked #2 — selected. At 101K, <code>number_emergency</code> overtakes it for the #2 slot and <code>time_in_hospital</code> falls to #3 — <em>not</em> selected. <code>num_inpatient</code> holds the #1 slot across both scales. This is the architecture’s MI selector doing automatic feature adaptation — no human tuning, just more data revealing a different optimal feature pair.</figcaption>
</figure>
</div>
<p>The flip happens because <code>time_in_hospital</code>’s MI advantage over <code>number_emergency</code> is small at 20K and disappears with more data:</p>
<table class="table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Scale</th>
<th>Routing cols (MI top-2)</th>
<th>Why</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>20K, no prune</td>
<td><code>[17, 9]</code></td>
<td>time_in_hospital edges out at small scale</td>
</tr>
<tr class="even">
<td>20K, with prune (§4.B)</td>
<td><code>[17, 16]</code></td>
<td>Removing death rows kills time_in_hospital’s MI</td>
</tr>
<tr class="odd">
<td><strong>101K, full</strong></td>
<td><strong><code>[17, 16]</code></strong></td>
<td>At scale, number_emergency wins on its own</td>
</tr>
</tbody>
</table>
<p><strong>This is the architecture’s MI selector doing real work.</strong> It picked a different feature pair at a different scale, automatically, no human intervention. Whether <code>time_in_hospital</code> would re-emerge at, say, 50K is an interesting probe — the threshold at which the two cross over is measurable.</p>
</section>
<section id="three-seeds-putting-an-error-bar-on-the-headline" class="level3">
<h3 class="anchored" data-anchor-id="three-seeds-putting-an-error-bar-on-the-headline">5.3 Three seeds — putting an error bar on the headline</h3>
<p>A single-seed result is dangerous. §4.F ran the same full-101K pipeline at seeds 42, 43, 44. Wall clock 610.77 s for three runs (~10 min total).</p>
<table class="table">
<thead>
<tr class="header">
<th>Metric</th>
<th>Mean ± Std (n=3)</th>
<th>seed 42</th>
<th>seed 43</th>
<th>seed 44</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>AUC</strong></td>
<td><strong>0.6645 ± 0.0018</strong></td>
<td>0.6621</td>
<td>0.6664</td>
<td>0.6649</td>
</tr>
<tr class="even">
<td>Brier</td>
<td>0.0950 ± 0.0002</td>
<td>0.0953</td>
<td>0.0948</td>
<td>0.0950</td>
</tr>
<tr class="odd">
<td>NLL</td>
<td>0.3346 ± 0.0004</td>
<td>0.3349</td>
<td>0.3340</td>
<td>0.3347</td>
</tr>
<tr class="even">
<td>ECE</td>
<td>0.0038 ± 0.0006</td>
<td>0.0044</td>
<td>0.0030</td>
<td>0.0041</td>
</tr>
<tr class="odd">
<td>Bal. accuracy</td>
<td>0.5019 ± 0.0008</td>
<td>0.5012</td>
<td>0.5015</td>
<td>0.5030</td>
</tr>
<tr class="even">
<td>F1 @ 0.5</td>
<td>0.0087 ± 0.0031</td>
<td>0.0061</td>
<td>0.0070</td>
<td>0.0130</td>
</tr>
</tbody>
</table>
<p>The variance is <strong>tiny — 0.0018 = 0.27% of the AUC mean</strong>. The seed-42 single-seed result (0.6621) was actually <em>below</em> the 3-seed mean (0.6645). Without the multi-seed pass, the single-seed headline understated the model.</p>
<p>All three seeds picked the <strong>same routing pair</strong> (<code>[17, 16]</code>). Structural decisions are pinned by scale; the per-seed noise is purely the held-out-test stratified sample.</p>
<div id="fig-multi-seed" class="quarto-figure quarto-figure-center anchored">
<figure class="figure">
<p><img src="https://adamezzat1.github.io/blog/posts/abng-diabetes-revisited-with-locke/figures/traditional/multi_seed_variance.png" class="img-fluid figure-img" alt="Six-panel grid showing per-metric multi-seed variance. AUC, Brier, NLL, ECE, Bal. accuracy, F1 @ 0.5. Each panel shows 3 seed points + mean line + shaded ± std band. Variances are tight on AUC/Brier/NLL/ECE/Bal.acc and looser on F1."></p>
<figcaption class="figure-caption">Figure&nbsp;7: Multi-seed variance — six panels, one per reported metric. Each panel shows three coloured dots (one per seed: 42 pink, 43 orange, 44 green-ish), the dashed mean line, and a shaded ± std band. AUC’s tight 0.0018 std (0.27% of mean) is the headline — seed 42 (the §4.E single-seed result) was actually <em>below</em> the 3-seed mean by half a sigma. F1 at fixed threshold 0.5 has the noisiest relative variance (36% of mean) — a threshold-sensitivity artifact, not a model issue.</figcaption>
</figure>
</div>
<p>Each seed’s chain head is reproducible bit-for-bit:</p>
<pre class="text"><code>seed 42: chain = 56af19614b4dfff6 df97c53949668765 acdc045e316e4fed 087a8a5d9b3233b6
seed 43: chain = 590c3fb936cec418 91576128b82e7243 dc399d6ca49a845c 87bb7392ddb45f7d
seed 44: chain = 89e02f56dadbb651 39418e0ecd3536c0 096b4851ae099694 f3736912ecde2bb2</code></pre>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
On F1’s higher relative variance
</div>
</div>
<div class="callout-body-container callout-body">
<p>F1 has the noisiest std (36% of mean — 0.0087 ± 0.0031), and the absolute number is low (0.0087 vs the first post’s calibrated 0.24). Both come from the fixed decision threshold (0.5) on extreme class imbalance — small probability-space shifts at the threshold flip many predictions. The first post tuned the threshold on validation per seed (which got it to ~0.24); we didn’t redo that this session. A per-seed validation-tuned threshold would tighten F1 back to the blog’s number without changing AUC, Brier, or NLL.</p>
<p><strong>This is a reporting choice, not an architectural one.</strong> F1 at threshold-0.5 is honest but unflattering; the per-seed-tuned version is honest <em>and</em> the right operating-point report. Both are valid; we deferred the threshold tuning to keep §4.F’s wall clock manageable.</p>
</div>
</div>
</section>
<section id="the-honest-comparison-vs-the-first-post" class="level3">
<h3 class="anchored" data-anchor-id="the-honest-comparison-vs-the-first-post">5.4 The honest comparison vs the first post</h3>
<table class="table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th>Metric</th>
<th>First post (calibrated, 20K, seed 42)</th>
<th>This post (raw, 101K, 3-seed mean)</th>
<th>Δ</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>AUC</td>
<td>0.611</td>
<td><strong>0.6645 ± 0.0018</strong></td>
<td><strong>+0.054</strong></td>
</tr>
<tr class="even">
<td>Brier</td>
<td>0.098</td>
<td><strong>0.0950 ± 0.0002</strong></td>
<td>−0.003</td>
</tr>
<tr class="odd">
<td>NLL</td>
<td>0.344</td>
<td><strong>0.3346 ± 0.0004</strong></td>
<td>−0.009</td>
</tr>
<tr class="even">
<td>ECE</td>
<td>0.010</td>
<td><strong>0.0038 ± 0.0006</strong></td>
<td>−0.006</td>
</tr>
</tbody>
</table>
<p>The new numbers beat the first post on every probabilistic axis <em>without</em> Platt calibration. The +0.054 AUC lift is the main story; it puts ABNG in the strong-published-model band on its linear-only configuration.</p>
<div id="fig-calibration" class="quarto-figure quarto-figure-center anchored">
<figure class="figure">
<p><img src="https://adamezzat1.github.io/blog/posts/abng-diabetes-revisited-with-locke/figures/traditional/calibration_first_vs_round_two.png" class="img-fluid figure-img" alt="Three side-by-side bar charts: Brier, NLL, ECE. Each compares First post (Platt calibrated, 20K) in grey vs Round Two (raw, 101K, n=3) in blue. Round Two beats first post in all three panels. Base-rate baseline shown with dashed line on Brier and NLL panels; both bars beat the base rate."></p>
<figcaption class="figure-caption">Figure&nbsp;8: Round Two’s raw calibration metrics vs the first post’s <em>Platt-calibrated</em> metrics. In all three panels, Round Two beats the first post — Brier 0.0950 vs 0.0980, NLL 0.3346 vs 0.3435, ECE 0.0038 vs 0.0101 — <em>without applying any calibration step</em>. The dashed orange line marks the base-rate predictor (output the 11.2% positive rate for everyone); both bars beat it on Brier and NLL. The increased data volume produces raw outputs that are already well-calibrated, so Platt scaling is no longer required for “beats base-rate” honesty.</figcaption>
</figure>
</div>
<p>The first post said:</p>
<blockquote class="blockquote">
<p>A nonlinear MLP-leaf-head variant is the next experiment — it may lift AUC, or may need a lot of tuning.</p>
</blockquote>
<p>It turns out the <strong>bigger lever was data scale, not architecture</strong>. We’ll come back to that in §9.</p>
</section>
</section>
<section id="the-locke--sentinel-saga-and-4.d" class="level2">
<h2 class="anchored" data-anchor-id="the-locke--sentinel-saga-and-4.d">6. The Locke <code>?</code>-sentinel saga and §4.D</h2>
<p>Section 4 of the brief asked for a different Locke contribution: <strong>belief-weighted per-leaf BLR priors</strong>. The hypothesis (from <a href="https://github.com/AdamEzzat1/CJC/blob/master/docs/locke/ABNG_PER_LEAF_BELIEF.md"><code>docs/locke/ABNG_PER_LEAF_BELIEF.md</code></a>): score each leaf with Locke’s <code>BeliefScore</code> and weight the per-leaf prior accordingly — strong belief → light regularization, weak belief → tight regularization. Operationalises the idea that data-quality variation across the routing tree should drive per-leaf prior strength.</p>
<p>Two blockers surfaced. Part 1 fixed one of them. Part 2 needs ABNG changes that didn’t happen this session.</p>
<section id="locke-didnt-see-as-missing" class="level3">
<h3 class="anchored" data-anchor-id="locke-didnt-see-as-missing">6.1 Locke didn’t see <code>?</code> as missing</h3>
<p>Running the existing per-leaf belief experiment (<code>tests/abng/per_leaf_belief_diabetes130.rs</code>) produced a per-leaf BeliefScore CSV where <strong>every leaf had <code>missingness_score = 1.0000</code></strong>:</p>
<pre class="csv"><code>leaf_id,n_rows,overall,schema,missingness,drift,leakage,lineage,sample,duplication,constraint
1,9617,0.902500,0.340000,1.000000,1.000000,1.000000,1.000000,1.000000,1.000000,0.880000
2,6215,0.905000,0.360000,1.000000,1.000000,1.000000,1.000000,1.000000,1.000000,0.880000
3,2545,0.902500,0.360000,1.000000,1.000000,1.000000,1.000000,1.000000,1.000000,0.860000
4,1623,0.902500,0.360000,1.000000,1.000000,1.000000,1.000000,1.000000,1.000000,0.860000</code></pre>
<p>Locke is saying: <strong>no missingness detected on any leaf</strong>. On a dataset where <code>weight</code> is 96.9% <code>?</code>.</p>
<p>The cause is in Locke’s default <code>ValidationConfig</code>: it treats only <strong>f64::NAN</strong> as missing for Float columns and produces info-only diagnostics for Str columns (they get an E9002 <code>no null mask supplied</code> notice). Diabetes-130 stores its missing values as the literal string <code>?</code> — Locke doesn’t see them without explicit configuration.</p>
</section>
<section id="the-fix-nullmaskmap-plumbing" class="level3">
<h3 class="anchored" data-anchor-id="the-fix-nullmaskmap-plumbing">6.2 The fix — <code>NullMaskMap</code> plumbing</h3>
<p>§4.D Part 1 added a harness-side helper that scans every <code>Str</code> column for <code>?</code> and builds a <code>NullMaskMap</code>:</p>
<div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb6-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> build_question_mark_null_masks(df<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>DataFrame) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> NullMaskMap <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb6-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">mut</span> out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">NullMaskMap::</span>new()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb6-3">    <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">for</span> (name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> col) <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">in</span> df<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>columns<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>iter() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb6-4">        <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">Column::</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">Str</span>(values) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> col <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb6-5">            <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> null_rows<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">Vec</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">usize</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> values<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>iter()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>enumerate()</span>
<span id="cb6-6">                <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>filter_map(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span>(i<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> v)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">if</span> v <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"?"</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">Some</span>(i) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">else</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">None</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span>)</span>
<span id="cb6-7">                <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>collect()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb6-8">            <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">if</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!</span>null_rows<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>is_empty() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb6-9">                out<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>insert(name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>clone()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">NullMask::</span>from_indices(null_rows))<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb6-10">            <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb6-11">        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb6-12">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb6-13">    out</span>
<span id="cb6-14"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div>
<p>Then thread it through <code>ValidateOptions.null_masks</code>. Re-ran the per-leaf belief:</p>
<table class="table">
<thead>
<tr class="header">
<th></th>
<th>Naive (no null masks)</th>
<th><code>?</code>-aware</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>weight</code> recognised as <code>?</code>-heavy</td>
<td>NO</td>
<td>YES (96.9% rate)</td>
</tr>
<tr class="even">
<td>Columns flagged with <code>?</code></td>
<td>0</td>
<td>7</td>
</tr>
<tr class="odd">
<td>Min per-leaf <code>missingness_score</code></td>
<td>1.0000</td>
<td><strong>0.9613</strong></td>
</tr>
<tr class="even">
<td>Min per-leaf <code>overall</code></td>
<td>0.9025</td>
<td>0.8977</td>
</tr>
</tbody>
</table>
<p>The belief vector is now informative. Seven columns are correctly flagged as <code>?</code>-bearing, including <code>weight</code> at 96.9% — matching the first post’s “weight is ~97% missing” observation.</p>
<div id="fig-belief-radar" class="quarto-figure quarto-figure-center anchored">
<figure class="figure">
<p><img src="https://adamezzat1.github.io/blog/posts/abng-diabetes-revisited-with-locke/figures/traditional/per_leaf_belief_radar.png" class="img-fluid figure-img" alt="Polar radar chart with 8 axes (schema, missingness, drift, leakage, lineage, sample, duplication, constraint). Two overlapping filled polygons: naive Locke (grey) and ?-aware (blue). The polygons overlap almost entirely except at the missingness axis, where the blue ?-aware polygon dents inward (0.9621) while the grey naive one stays at 1.0. The schema axis at the right shows a deep concave (0.34) on both polygons — Locke's schema_score is genuinely low on this dataset."></p>
<figcaption class="figure-caption">Figure&nbsp;9: §4.D Part 1’s effect on leaf 1’s 8-axis BeliefScore vector (n=9,617 rows, the largest leaf). The naive Locke polygon (grey, with default <code>ValidationConfig</code>) is mostly perfect — <code>missingness</code> at the top sits at the outermost ring (1.0 = “no missingness detected”). The <code>?</code>-aware polygon (blue) is <em>identical</em> on every axis except <code>missingness</code>, where it dents inward to 0.9621 — Locke now sees <code>?</code> strings as missing. That dent is the §4.D Part 1 fix made visible: targeted (one axis), not noisy (other axes unmoved). The deep concave at the right (<code>schema = 0.34</code>) is real Locke schema-concern data, present on both polygons.</figcaption>
</figure>
</div>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
A Locke roadmap item, concretely motivated
</div>
</div>
<div class="callout-body-container callout-body">
<p><strong>Locke should auto-detect common missing-value sentinels.</strong> <code>?</code>, <code>NA</code>, <code>-</code>, <code>NULL</code>, empty string — these are common conventions in medical, financial, and scientific CSVs. Requiring every harness to build a <code>NullMaskMap</code> manually is friction; the cost of false-positive sentinel detection is low (the user can override) and the cost of false-negative is what this section just walked through (a 97%-missing column treated as perfectly clean).</p>
<p>The §4.D Part 1 fix is a one-helper-function workaround that should be in Locke itself. Recorded as a roadmap item.</p>
</div>
</div>
</section>
<section id="the-deeper-problem-leaves-clustered-at-uniform-belief" class="level3">
<h3 class="anchored" data-anchor-id="the-deeper-problem-leaves-clustered-at-uniform-belief">6.3 The deeper problem — leaves clustered at uniform belief</h3>
<p>With <code>?</code>-handling fixed, the per-leaf belief vector is informative <em>per axis</em>, but <strong>all four leaves cluster at near-identical missingness_score</strong>:</p>
<div id="fig-per-leaf-belief" class="quarto-figure quarto-figure-center anchored">
<figure class="figure">
<p><img src="https://adamezzat1.github.io/blog/posts/abng-diabetes-revisited-with-locke/figures/traditional/per_leaf_belief_question_marks.png" class="img-fluid figure-img" alt="Grouped bar chart, 4 leaves × 2 bars each. Grey bars (Naive Locke default): all four leaves show missingness_score = 1.0000. Blue bars (?-aware with NullMaskMap): leaves at 0.9621, 0.9624, 0.9621, 0.9613. A green dashed line at y=1.0 labels 'Perfect (no missingness detected)'."></p>
<figcaption class="figure-caption">Figure&nbsp;10: §4.D Part 1 — Locke per-leaf <code>missingness_score</code> with default config (grey, all 1.0000 — meaning “no missingness detected, all clean”) vs with the <code>?</code>-aware <code>NullMaskMap</code> threaded in (blue, 0.961–0.962). The fix gives Locke an informative signal on this dataset. The remaining problem is visible in the bars: spread across leaves is only ~0.001, because the simple 1-D codebook over <code>time_in_hospital</code> doesn’t separate leaves on data quality — the 97% <code>?</code> rate in <code>weight</code> is roughly uniform across hospital-stay durations.</figcaption>
</figure>
</div>
<table class="table">
<thead>
<tr class="header">
<th>leaf_id</th>
<th>n_rows</th>
<th>missingness_score</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>1</td>
<td>9,617</td>
<td>0.9621</td>
</tr>
<tr class="even">
<td>2</td>
<td>6,215</td>
<td>0.9624</td>
</tr>
<tr class="odd">
<td>3</td>
<td>2,545</td>
<td>0.9621</td>
</tr>
<tr class="even">
<td>4</td>
<td>1,623</td>
<td>0.9613</td>
</tr>
</tbody>
</table>
<p><strong>Spread: 0.001.</strong> All four leaves are within one-tenth of a percent. That’s because the routing column (<code>time_in_hospital</code>, in this simplified 1-D codebook) doesn’t correlate with data-quality variation. The 97% <code>?</code> rate in <code>weight</code> is roughly uniform across hospital-stay durations.</p>
<p>For §4.D Part 2 (the actual belief-weighted prior experiment) to <em>do anything</em>, the leaves need to <em>differ</em> on belief. They don’t, on this routing. The directional hypothesis from <code>ABNG_PER_LEAF_BELIEF.md</code> is operationally a no-op when all leaves get the same belief multiplier.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Routing-vs-belief alignment — a research question
</div>
</div>
<div class="callout-body-container callout-body">
<p><strong>Target-MI routing optimises for predictive separation.</strong> It picks the features that most reduce uncertainty about the target.</p>
<p><strong>Data-quality routing would optimise for belief separation.</strong> It would pick features that most stratify by data quality — different missingness profiles, different correlation structures, different conditional drift.</p>
<p><strong>These are not the same objective.</strong> §4.D Part 2 implicitly assumes they align. On this dataset they don’t. Whether they align in general is an open question — it might be that some datasets have data-quality variation that <em>also</em> predicts the target (e.g., underreported labs in a poorly-documented patient subset), in which case the same routing serves both purposes. Or they might be orthogonal, in which case you need separate routing for each.</p>
<p>This is a research-direction question Locke and ABNG together can answer, but neither can answer alone. <strong>It needs ABNG to expose a per-leaf prior API and a way to feed belief into routing decisions.</strong> Both are multi-session architectural work.</p>
</div>
</div>
</section>
<section id="part-2-deferred-for-a-real-reason" class="level3">
<h3 class="anchored" data-anchor-id="part-2-deferred-for-a-real-reason">6.4 Part 2 — deferred for a real reason</h3>
<p>§4.D Part 2 (apply the belief-weighted prior) requires changes to ABNG’s <code>set_blr_prior</code>:</p>
<div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb7-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Current API — graph-wide, one-shot, must be called before any add_node.</span></span>
<span id="cb7-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">pub</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> set_blr_prior(</span>
<span id="cb7-3">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">mut</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">self</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb7-4">    precision<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">f64</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb7-5">    a<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">f64</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb7-6">    b<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">f64</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb7-7">) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">Result</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> GraphError<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">...</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div>
<p>There’s no <code>set_blr_prior_for_node(node_id, precision, a, b)</code>. Adding one would touch:</p>
<ol type="1">
<li>Public API surface (new method on <code>AdaptiveBeliefGraph</code>).</li>
<li>Lifecycle relaxation (must work after <code>add_node</code>, not before).</li>
<li>A new <code>AuditKind::BlrLeafPriorOverride</code> variant for the audit chain.</li>
<li>New SHA-256 canary locks for that event type.</li>
</ol>
<p>Multi-session work, at the same scope as §4.C MLP heads. <strong>Deferred — see §10.</strong></p>
<p>The §4.D Part 1 work is still a real Locke contribution: it makes the per-leaf belief informative on <code>?</code>-sentinel datasets. The pre-blocker is resolved. The actual experiment (Part 2) is waiting on ABNG architecture work plus the routing-vs-belief alignment question being settled.</p>
</section>
</section>
<section id="c-mlp-heads-design-only" class="level2">
<h2 class="anchored" data-anchor-id="c-mlp-heads-design-only">7. §4.C MLP heads — design only</h2>
<p>The first post named <strong>nonlinear MLP leaf heads</strong> as the explicit “ranking ceiling” lever. The implicit claim: the linear BLR is the ceiling, calibration can’t move ranking, so to break the 0.611 AUC ceiling you need an MLP head.</p>
<p>§5 of this post already invalidated that framing — full data scale moved AUC into the strong band on the linear head. But MLP heads would still plausibly push higher (0.66 → ~0.68). So the brief asked to <em>start</em> §4.C this session.</p>
<p>The exploration produced a <a href="https://github.com/AdamEzzat1/CJC/blob/master/docs/abng/PHASE_0_10_SECTION_4C_DESIGN.md">design doc</a> and one critical finding: <strong>the MLP forward infrastructure already exists in cjc-abng.</strong></p>
<div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb8-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// crates/cjc-abng/src/leaf_head.rs:30</span></span>
<span id="cb8-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">pub</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">struct</span> LeafHead <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb8-3">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">pub</span> input_dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">u32</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb8-4">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">pub</span> hidden_dims<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">Vec</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">u32</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;,</span>   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// ← already supports MLP layers</span></span>
<span id="cb8-5">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">pub</span> output_dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">u32</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb8-6">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">pub</span> activation<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> Activation<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// ← Tanh/Sigmoid/Relu/None/Gelu/Silu/Elu/Selu/SinAct</span></span>
<span id="cb8-7">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">pub</span> config_hash<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> [<span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">u8</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">32</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb8-8"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div>
<p><code>LeafHead::new(in_dim, hidden_dims, out_dim, activation)</code> accepts a <code>Vec&lt;u32&gt;</code> of hidden layer widths. The harness’s call is <code>set_leaf_head(phi_width, vec![], 1, Activation::None)</code> — <strong>empty hidden_dims</strong>, so the BLR sits directly on raw φ. Setting <code>vec![32]</code> and <code>Activation::Relu</code> would build a 2-layer MLP.</p>
<p><code>leaf_forward(node_id, x_idx)</code> (<a href="https://github.com/AdamEzzat1/CJC/blob/master/crates/cjc-abng/src/graph.rs">graph.rs:1386</a>) already runs the MLP forward inside a <code>cjc-ad::GradGraph</code>, walking layers and applying <code>g.mlp_layer(input, w, b, activation)</code> per layer. <code>leaf_set_param</code> and <code>leaf_set_params_batch</code> already exist for writing trained weights back, and they’re already canary-locked under the existing audit chain.</p>
<p>What’s <em>missing</em>: a training driver. The harness’s <code>train_step</code> (<a href="https://github.com/AdamEzzat1/CJC/blob/master/crates/cjc-abng/src/graph.rs">graph.rs:1678</a>) calls <code>blr.update(phi, y)</code> directly — it doesn’t run the MLP forward first. To use an MLP head, you’d need to either:</p>
<ol type="1">
<li>Write a parallel <code>train_step_with_mlp_forward</code> method on <code>AdaptiveBeliefGraph</code>, or</li>
<li>Do the MLP forward/backward orchestration in the harness, then call the existing <code>train_step</code> with the <em>penultimate features</em> as φ.</li>
</ol>
<p>Option 2 is ~250 LOC of test-side surgery with no ABNG changes. The design doc spells it out. <strong>Probably no new AuditKind variant is required</strong> — <code>LeafSetParam</code>/<code>LeafSetParamBatch</code> are already canary-locked.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
A scope reframe
</div>
</div>
<div class="callout-body-container callout-body">
<p>The first post said §4.C was “the ranking-ceiling lever — needed to break the 0.61 ceiling.” That framing is now wrong:</p>
<ul>
<li>We’re at AUC 0.6645 ± 0.0018 <em>without</em> MLP. The “ceiling” wasn’t where the framing implied.</li>
<li>§4.C’s plausible delta is now ~+0.02 (0.66 → ~0.68), not ~+0.05 (0.61 → 0.66).</li>
<li>§4.C is harness-side surgery, not ABNG architecture surgery — the infrastructure already exists.</li>
</ul>
<p>Recommend Phase 0.11 or a future focused session. The cost/benefit ratio is worse than the first post implied, in both directions: smaller potential lift, but also smaller architectural footprint.</p>
</div>
</div>
</section>
<section id="abng-architectural-fingerprint-what-carried-this" class="level2">
<h2 class="anchored" data-anchor-id="abng-architectural-fingerprint-what-carried-this">8. ABNG architectural fingerprint — what carried this</h2>
<p>The work this post describes uses a <em>purely linear</em> ABNG: BLR heads on raw φ, no nonlinear layers. The result (AUC 0.6645) is, on its face, a result a linear model produced. So a reasonable reader asks: <em>was ABNG architecturally unique on this run, or was it just a linear ensemble in fancy clothes?</em></p>
<div id="fig-abng-architecture" class="quarto-figure quarto-figure-center anchored">
<figure class="figure">
<p><img src="https://adamezzat1.github.io/blog/posts/abng-diabetes-revisited-with-locke/figures/traditional/abng_architecture.png" class="img-fluid figure-img" alt="Architecture flow diagram. Top: Row goes through CategoricalTransform to produce (x, φ, y). Two branches: one to Root BLR (purple, posterior on all data), one to Routing decision (orange, K=2 MI-selected features). Routing fans out to 4 Leaf BLR boxes (blue). All five — root and 4 leaves — converge into a bottom Ensemble box (green): Ensemble = 0.5 × root + 0.5 × routed leaf. Footnote: K=1 shown for legibility; actual K=2 means 16 leaves."></p>
<figcaption class="figure-caption">Figure&nbsp;11: ABNG’s per-leaf BLR ensemble structure. Each input row passes through the <code>CategoricalTransform</code> to become <code>(x, φ, y)</code> — <code>x</code> routes; <code>φ</code> predicts. The <strong>root BLR</strong> has its own conjugate posterior trained on all rows. The <strong>routing decision</strong> is frozen at graph-build time using the top-K=2 MI-scored columns. Each row then trains exactly <em>one</em> leaf’s BLR, again with closed-form conjugate updates. At predict time, the ensemble is <code>0.5 × root_mean + 0.5 × routed_leaf_mean</code>. The schematic shows K=1 (4 leaves) for legibility — diabetes-130 actually uses K=2, fanning into 16 leaves. The audit chain SHA-256 hash advances per <code>train_step</code>, binding (row, leaf, posterior state) into one chained event.</figcaption>
</figure>
</div>
<p>The honest answer is that even without MLP, ABNG has an architectural fingerprint that <strong>none of scikit-learn’s <code>LogisticRegression</code>, an XGBoost model, or a single per-leaf logistic regression in a tree carries</strong>. Five things that mattered on this dataset:</p>
<section id="the-φ-vs-routing-feature-split" class="level3">
<h3 class="anchored" data-anchor-id="the-φ-vs-routing-feature-split">8.1 The φ-vs-routing feature split</h3>
<p>ABNG’s categorical transform produces two outputs per row: <code>x</code> (low-dim integer buckets for routing) and <code>phi</code> (high-dim one-hot for the BLR head). The schema’s <code>CategoricalPhiOnly</code> role marks columns as <strong>“contribute to φ but never to routing”</strong>.</p>
<p>On diabetes-130, that’s what makes the 23 medication columns and 3 ICD-9 codes (700+ levels each) usable. They participate in prediction (φ width stays ≤ 266 across all configs) without exploding the routing tree (which would otherwise need 4^700 leaves). Trees and forests don’t cleanly separate “what splits” from “what predicts” — every feature is a split candidate or excluded. ABNG’s split is at the schema layer, before routing.</p>
</section>
<section id="pre-fixed-mi-selected-routing" class="level3">
<h3 class="anchored" data-anchor-id="pre-fixed-mi-selected-routing">8.2 Pre-fixed MI-selected routing</h3>
<p>Greedy tree learners pick splits locally — each split optimises against the current data subset. ABNG picks routes <em>globally, once</em>, before any training: score every column’s mutual information against the target, take the top K, build a fixed codebook. The structure is then frozen; training only fills in the per-leaf heads.</p>
<p>This session showed this lever doing real work: at 20K it picked <code>[num_inpatient, time_in_hospital]</code>, at 101K it picked <code>[num_inpatient, number_emergency]</code> — <strong>automatically, no human tuning</strong>. And at 101K all three §4.F seeds picked the same pair: structural decisions are pinned by scale, with seed-to-seed noise concentrated entirely in the per-leaf parameters.</p>
</section>
<section id="per-leaf-bayesian-linear-regression-with-conjugate-updates" class="level3">
<h3 class="anchored" data-anchor-id="per-leaf-bayesian-linear-regression-with-conjugate-updates">8.3 Per-leaf Bayesian linear regression with conjugate updates</h3>
<p>Each leaf is a full Bayesian regression: precision matrix, posterior mean, predictive variance — all updated in closed form per training row. <strong>No SGD. No learning rate. No early stopping.</strong> O(d²) per step, where d ≈ 266 here.</p>
<p>The root + leaf ensemble (predicted prob = 0.5 × leaf_posterior_mean + 0.5 × root_posterior_mean) is the model that produces 0.6645. Bayesian forests and BART share parts of this idea; the combination of conjugate per-leaf BLRs with frozen pre-MI routing is, as far as I know, ABNG-specific.</p>
</section>
<section id="sha-256-audit-chain-over-training-events" class="level3">
<h3 class="anchored" data-anchor-id="sha-256-audit-chain-over-training-events">8.4 SHA-256 audit chain over training events</h3>
<p>Every <code>train_step</code> appends an event to a hash chain. End-state chain head is byte-reproducible. Merkle root verifies. <strong>This is the property no commodity ML system carries.</strong></p>
<p>In this session: five commits, each carrying a chain head. Aggressive config changes throughout — constants flipped, new tests added, a new filter applied, multi-seed runs. The 28 SHA-256 determinism canaries didn’t move once, because nothing they cover was touched. The work was <em>safe to be exploratory</em> in a way most ML harnesses don’t permit.</p>
</section>
<section id="bit-identical-replay" class="level3">
<h3 class="anchored" data-anchor-id="bit-identical-replay">8.5 Bit-identical replay</h3>
<p>The first post’s reproducibility evidence (“Run 1 / Run 2 — every metric digit identical, 3× wall-clock difference”) held throughout Round Two. The §4.F std of 0.0018 is direct evidence: variance comes entirely from seed-driven sub-sample / split / training-order permutations, not from numerical noise. Kahan summation, BTreeMap iteration (not HashMap), no FMA in SIMD kernels, deterministic SplitMix64 RNG — the determinism contract is a system-wide invariant.</p>
<p>The five chain heads from this session — <code>8897c2dd…</code> (before), <code>57463728…</code> (§4.A after), <code>78a63f56…</code> (§4.B pruned), <code>56af1961…</code> (§4.E full), and the three §4.F seeds — are durable artifacts. Anyone with the codebase, the seed, and the dataset can rerun and confirm bit-identity.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
What the MLP would have <em>added</em>
</div>
</div>
<div class="callout-body-container callout-body">
<p>One thing: <strong>nonlinearity inside each leaf</strong>. The linear leaf head can only fit linear relationships between φ and the target <em>within</em> its routing slice. An MLP head would let each leaf learn interactions (medication × demographic × diagnosis).</p>
<p>But the routing tree itself is already a form of nonlinearity — splitting the data into 16 leaves by <code>num_inpatient × number_emergency</code> means each leaf sees a piecewise-linear slice of the data. The MLP would push from piecewise-linear to per-leaf-arbitrary-nonlinear. The §4.C design doc estimates that buys ~0.02 AUC (0.66 → ~0.68) — a real but bounded lift.</p>
<p>The architectural fingerprint above (φ-routing split, frozen MI routing, conjugate BLR heads, audit chain, bit-replay) <strong>doesn’t depend on the MLP</strong> — it’s the <em>substrate</em> the MLP would sit on. The fingerprint is what made the exploration safe, the determinism contract durable, and the per-leaf result interpretable. The headline number was driven by data scale + tuning, not the substrate; the substrate is what made the work auditable.</p>
</div>
</div>
</section>
</section>
<section id="honest-assessment-what-locke-did-and-didnt" class="level2">
<h2 class="anchored" data-anchor-id="honest-assessment-what-locke-did-and-didnt">9. Honest assessment — what Locke did, and didn’t</h2>
<p>If §8 is “ABNG worked end-to-end,” this section is the matching honest accounting for Locke. The brief’s flagship hypothesis was “Locke surfaces leakage → drop rows → AUC up.” That didn’t happen. So <em>what did Locke contribute</em>?</p>
<p>Six roles, six honest takes (from the <a href="https://github.com/AdamEzzat1/CJC/blob/master/docs/abng/PHASE_0_10_SESSION_NOTES.md">session notes</a>):</p>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Numerical Computing axis
</div>
</div>
<div class="callout-body-container callout-body">
<p><strong>Locke did not directly drive the AUC bump.</strong> Every gain — §4.A’s +0.055, §4.E’s +0.031, §4.F’s confidence bound — came from ABNG tuning + data scale, not any Locke finding. The flagship §4.B hypothesis (Locke surfaces E9063 → drop death rows → AUC up) didn’t pan out: E9063 didn’t fire, and the domain-knowledge prune we applied anyway <em>hurt</em> AUC by 0.0118.</p>
</div>
</div>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Lead Architect axis
</div>
</div>
<div class="callout-body-container callout-body">
<p><strong>Locke surfaced its own detector ceiling, which is a publishable finding in itself.</strong> E9063 is per-feature ROC-AUC; it misses per-level deterministic leakage. The diabetes-130 dataset is the kind of medical data where leakage hides at the <em>level</em> (discharge code 11 = expired), not the column. The proposed <strong>E9064 per-level conditional-probability detector</strong> is now a concrete Locke roadmap item, bound to a real-world example.</p>
</div>
</div>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Determinism Auditor axis
</div>
</div>
<div class="callout-body-container callout-body">
<p><strong>Locke’s determinism contract held — <code>cjcl locke verify --runs N</code> would still pass on every artifact produced.</strong> The 142 findings and their content-addressed IDs are reproducible. The audit chain integrity is preserved across an exploratory session that changed configuration substantially. This is a non-trivial systems contribution: a noisy, exploratory ML harness changed configs aggressively, and the data-side audit trail never needed to budge.</p>
</div>
</div>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Pipeline Engineer axis
</div>
</div>
<div class="callout-body-container callout-body">
<p><strong>Locke’s CLI surface worked exactly as designed.</strong> <code>--target readmitted --primary-key patient_nbr --save-json --html</code> ran to completion, produced structured JSON, and emitted a navigable HTML report with the Pearson correlation heatmap. The plumbing is solid. No CLI or schema issues.</p>
</div>
</div>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Runtime Systems axis
</div>
</div>
<div class="callout-body-container callout-body">
<p><strong>Locke’s findings flagged structural concerns AUC numbers couldn’t see.</strong> Two examples:</p>
<ol type="1">
<li><strong>E9004 on <code>patient_nbr</code></strong> — 30,248 duplicate keys across 16,773 groups. Means the harness’s encounter-level train/test split lets the same patient appear in train + test. AUC won’t show it; Locke surfaced it without running a single model.</li>
<li><strong>E9082 on <code>weight</code> / <code>medical_specialty</code> / <code>max_glu_serum</code></strong> — near-duplicate category strings. Some are real distinctions (<code>[0-25)</code> vs <code>[50-75)</code>), but the warning forces a deliberate “yes these are different, not typos” review.</li>
</ol>
</div>
</div>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
QA Automation axis
</div>
</div>
<div class="callout-body-container callout-body">
<p><strong>Locke’s null result on missingness was the most actionable finding.</strong> Default config doesn’t recognise <code>?</code>, so a 97%-missing column shows up as “missingness_score = 1.0 (perfect)” in per-leaf BeliefScore output. This is a Locke configuration gap that medical CSVs commonly trigger. The fix is either auto-detect common sentinels (<code>?</code> / <code>NA</code> / <code>-</code> / <code>NULL</code>), or make the <code>null_masks</code> workflow more discoverable. <strong>Pre-blocked §4.D from being useful as the handoff defined it</strong> — if the belief vector isn’t informative, weighting per-leaf priors by it can’t help.</p>
</div>
</div>
<section id="net-assessment" class="level3">
<h3 class="anchored" data-anchor-id="net-assessment">Net assessment</h3>
<p>Locke <em>did</em> help — but on the <strong>systems / determinism / detector-roadmap</strong> axis, not directly on the <strong>AUC-bump</strong> axis. The brief implicitly assumed Locke would surface a leakage pattern that, when filtered, would lift AUC. What actually happened: Locke surfaced detector limits and structural issues that <em>will</em> drive future Phase 0.11 work but didn’t move <em>this</em> session’s number. The §4.B negative result is the most valuable Locke output of the session — it pins a concrete failure mode (E9063 misses per-level outcomes) and points at a concrete fix (E9064 conditional-probability detector).</p>
<p><strong>ABNG, by contrast, <em>was</em> the lever.</strong> The K=2 / prior=0.5 config + full data + 3-seed variance is what produces AUC 0.6645 ± 0.0018. ABNG’s audit chain, BLR conjugate update, deterministic stratified sub-sample, and MI-based routing selection all carried this session. The cost in code: two constants and three new test functions.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Two roadmap items, both pinned to this dataset
</div>
</div>
<div class="callout-body-container callout-body">
<ol type="1">
<li><p><strong>Locke E9064 — per-level conditional-probability leakage detector.</strong> Catches “specific column level deterministically predicts class” patterns that per-feature ROC-AUC misses. Diabetes-130’s discharge-code death pattern is the canonical example.</p></li>
<li><p><strong>Locke auto-detect missing sentinels.</strong> <code>?</code> / <code>NA</code> / <code>-</code> / <code>NULL</code> should be detected by default. The current <code>null_masks</code> workflow is a one-off helper every harness has to write; should live in Locke. Diabetes-130’s <code>weight</code> column (96.9% <code>?</code>) is the canonical example.</p></li>
</ol>
<p>Both are concrete, both are motivated by a real dataset, both produce immediate value on the next medical CSV that lands.</p>
</div>
</div>
</section>
</section>
<section id="honest-limitations" class="level2">
<h2 class="anchored" data-anchor-id="honest-limitations">10. Honest limitations</h2>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
What Round Two did <em>not</em> land
</div>
</div>
<div class="callout-body-container callout-body">
<ul>
<li><strong>A patient-level train/test split.</strong> E9004 surfaced the 16,773-patient encounter overlap; we left the row-level split in place. The first post used row-level; this one does too. Whether AUC 0.6645 is “real signal” or “partial patient-memorisation” can only be settled by re-running with <code>patient_nbr</code>-level splitting. Recorded for Phase 0.11.</li>
<li><strong>MLP leaf heads.</strong> §4.C is a design doc, not code. The lift would plausibly be ~+0.02 (0.66 → ~0.68); the work is harness-side surgery, not ABNG architecture surgery (the LeafHead infrastructure is MLP-ready). Recommended for a focused future session.</li>
<li><strong>§4.D Part 2 — belief-weighted per-leaf priors.</strong> Needs ABNG to expose <code>set_blr_prior_for_node</code> + a new audit kind + canary lock. Multi-session ABNG work. Also pre-blocked by the routing-vs-belief alignment question — on the simple routing we tried, all leaves had uniform belief; the experiment is operationally meaningless until you can route on data-quality dimensions or force per-leaf differential.</li>
<li><strong>Per-seed validation-tuned decision threshold.</strong> The first post tuned this on validation to get F1 ≈ 0.24; we left it at 0.5 and reported F1 ≈ 0.0087 ± 0.0031. A reporting choice, not a model issue.</li>
<li><strong>Beyond n=3 seeds.</strong> Three seeds is the minimum for a meaningful std. The 0.0018 spread suggests further seeds would tighten the 4th decimal but not change the headline. Extend to n=10 only if a tighter band is needed for publication.</li>
</ul>
</div>
</div>
</section>
<section id="whats-next" class="level2">
<h2 class="anchored" data-anchor-id="whats-next">11. What’s next</h2>
<p>In rough priority order, given that §4.E + §4.F already cleared the brief’s stretch goal:</p>
<ol type="1">
<li><strong>Patient-level train/test split.</strong> Most defensibility per unit work. The first post and this post both report encounter-level numbers; a patient-level run would settle whether the 0.66 is signal or memorisation.</li>
<li><strong>§4.C MLP heads.</strong> Implementation is harness-side (~250 LOC). Expected lift +0.02. Not architecturally risky; the substrate is already MLP-ready.</li>
<li><strong>Locke E9064 detector.</strong> Catches per-level deterministic leakage. Would have flagged the discharge-code pattern that §4.B applied via domain knowledge.</li>
<li><strong>Locke <code>?</code>-sentinel auto-detection.</strong> Removes the harness-side <code>NullMaskMap</code> workflow §4.D Part 1 had to write. Would also unblock per-leaf belief on most medical CSVs by default.</li>
<li><strong>§4.D Part 2.</strong> Belief-weighted per-leaf priors, <em>only</em> once (i) <code>set_blr_prior_for_node</code> ships in ABNG and (ii) the routing-vs-belief alignment question is settled with a data-quality-aware routing or a per-leaf-differential experiment.</li>
</ol>
</section>
<section id="reproducibility" class="level2">
<h2 class="anchored" data-anchor-id="reproducibility">12. Reproducibility</h2>
<p>The result is deterministic. Five commits on <code>claude/adoring-shtern-01c720</code>:</p>
<pre class="text"><code>a239e94  docs(phase 0.10): §4.C MLP-head design doc + final session notes
c66e74c  abng: Phase 0.10 §4.D Part 1 — Locke `?`-sentinel blindness fixed
3d84a85  docs(phase 0.10): 'has Locke helped' role-by-role assessment
5b65be2  abng: Phase 0.10 §4.F — AUC 0.6645 ± 0.0018 at full Diabetes-130 (n=3 seeds)
d61c88a  abng: Phase 0.10 §4.A/B/E — AUC 0.6621 at full Diabetes-130, in strong-published-model band</code></pre>
<p>Re-run the full pipeline from a clean checkout:</p>
<div class="sourceCode" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb10-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 1. Clone the CJC-Lang repo.</span></span>
<span id="cb10-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">git</span> clone https://github.com/AdamEzzat1/CJC.git</span>
<span id="cb10-3"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">cd</span> CJC</span>
<span id="cb10-4"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">git</span> checkout claude/adoring-shtern-01c720</span>
<span id="cb10-5"></span>
<span id="cb10-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 2. Fetch the dataset (UCI dataset 296). Untracked — `.gitignore`'d.</span></span>
<span id="cb10-7"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">cd</span> tests/data</span>
<span id="cb10-8"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">curl</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-sSL</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-o</span> diabetes_130.zip <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb10-9">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"https://archive.ics.uci.edu/static/public/296/diabetes+130-us+hospitals+for+years+1999-2008.zip"</span></span>
<span id="cb10-10"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">unzip</span> diabetes_130.zip <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-d</span> diabetes_130</span>
<span id="cb10-11"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">cd</span> ../..</span>
<span id="cb10-12"></span>
<span id="cb10-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 3. Workspace release build (~10 min, LTO on).</span></span>
<span id="cb10-14"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cargo</span> build <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--workspace</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--release</span></span>
<span id="cb10-15"></span>
<span id="cb10-16"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 4. Always-run gates — 629/629 should pass.</span></span>
<span id="cb10-17"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cargo</span> test <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--test</span> abng <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--release</span></span>
<span id="cb10-18"></span>
<span id="cb10-19"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 5. §4.A tuned 20K trial (~40 s).</span></span>
<span id="cb10-20"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cargo</span> test <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--test</span> abng diabetes130_subsample_trial <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--release</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb10-21">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--ignored</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--nocapture</span></span>
<span id="cb10-22"></span>
<span id="cb10-23"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 6. §4.B Locke-pruned trial (~40 s).</span></span>
<span id="cb10-24"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cargo</span> test <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--test</span> abng diabetes130_subsample_trial_locke_pruned <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--release</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb10-25">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--ignored</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--nocapture</span></span>
<span id="cb10-26"></span>
<span id="cb10-27"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 7. §4.E full 101K headline (~3.6 min).</span></span>
<span id="cb10-28"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cargo</span> test <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--test</span> abng diabetes130_full_run <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--release</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb10-29">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--ignored</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--nocapture</span></span>
<span id="cb10-30"></span>
<span id="cb10-31"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 8. §4.F multi-seed variance (~10.2 min, 3 × full_run).</span></span>
<span id="cb10-32"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cargo</span> test <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--test</span> abng diabetes130_multi_seed_variance_full <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--release</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb10-33">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--ignored</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--nocapture</span></span>
<span id="cb10-34"></span>
<span id="cb10-35"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 9. Locke audit on the full CSV.</span></span>
<span id="cb10-36"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">./target/release/cjcl.exe</span> locke validate <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb10-37">    tests/data/diabetes_130/diabetic_data.csv <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb10-38">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--target</span> readmitted <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--primary-key</span> patient_nbr <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb10-39">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--save-json</span> bench_results/diabetes_locke_baseline.json <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb10-40">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--html</span>        bench_results/diabetes_locke_baseline.html</span>
<span id="cb10-41"></span>
<span id="cb10-42"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 10. §4.D Part 1 `?`-aware per-leaf belief.</span></span>
<span id="cb10-43"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cargo</span> test <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--test</span> abng diabetes130_per_leaf_belief_with_question_marks <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb10-44">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--release</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--ignored</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--nocapture</span></span></code></pre></div>
<p>Build fingerprint: Rust stable, Diabetes-130 dataset SHA-256 verified by the harness’s <code>raw_dataset_hash</code>, seeds 42 / 43 / 44, the full 101,766-row dataset. The five chain heads above must reproduce bit-for-bit.</p>
<p>Detailed session artifacts:</p>
<ul>
<li><a href="https://github.com/AdamEzzat1/CJC/blob/master/docs/abng/PHASE_0_10_SESSION_NOTES.md"><code>docs/abng/PHASE_0_10_SESSION_NOTES.md</code></a> — full session notes including the six-axis “has Locke helped” assessment.</li>
<li><a href="https://github.com/AdamEzzat1/CJC/blob/master/docs/abng/PHASE_0_10_SECTION_4C_DESIGN.md"><code>docs/abng/PHASE_0_10_SECTION_4C_DESIGN.md</code></a> — MLP-head design doc.</li>
<li><code>bench_results/diabetes_phase_0_10_section_{4a,4b,4d,4e,4f}/SUMMARY.md</code> — per-section result summaries with chain heads.</li>
</ul>
<section id="figures" class="level3">
<h3 class="anchored" data-anchor-id="figures">Figures</h3>
<p>Every chart in this post was rendered from the actual session data via <a href="generate_figures.py"><code>generate_figures.py</code></a> (Plotly + kaleido). Two themes are saved alongside:</p>
<ul>
<li><strong>Traditional</strong> (used in the body above): <code>figures/traditional/&lt;name&gt;.png</code></li>
<li><strong>Cyberpunk</strong>: <code>figures/cyberpunk/&lt;name&gt;.png</code></li>
</ul>
<p>Interactive HTML versions (hover for exact values, zoom, pan) live under <code>figures/interactive/&lt;theme&gt;/&lt;name&gt;.html</code>.</p>
<p>The Locke Pearson |r| heatmap (<a href="figures/locke/pearson_heatmap_actual.svg"><code>figures/locke/pearson_heatmap_actual.svg</code></a>) is extracted verbatim from the actual <code>cjcl locke validate</code> HTML report via <a href="extract_locke_heatmap.py"><code>extract_locke_heatmap.py</code></a> — no recreation, no theme override. It’s the literal Locke artifact, content-addressed and deterministic.</p>
</section>
</section>
<section id="the-honest-summary" class="level2">
<h2 class="anchored" data-anchor-id="the-honest-summary">13. The honest summary</h2>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Round Two in one paragraph
</div>
</div>
<div class="callout-body-container callout-body">
<p>The Adaptive Belief Network Graph crossed into the strong-published-model band on UCI Diabetes-130 — <strong>AUC 0.6645 ± 0.0018 across 3 seeds at the full 101,766 rows</strong> — without any architectural change, still on the linear BLR head. The decisive lever was <strong>data scale + the validation-tuned config the first post identified</strong>, not the MLP heads the first post predicted would be needed. <strong>Calibration metrics (Brier 0.0950, NLL 0.3346, ECE 0.0038) all beat the first post’s <em>calibrated</em> values without any Platt step</strong>, because at full scale the raw outputs were already well-calibrated. The Locke audit ran end-to-end (142 findings, 1 error, fully content-addressed) and surfaced a structural patient-encounter leakage hole the first post missed (E9004), exposed its own configuration gap on <code>?</code>-sentinel medical CSVs (which §4.D Part 1 patched), and pinned a concrete detector failure mode for a proposed E9064 conditional-probability leakage check. <strong>The Locke-driven prune experiment falsified the handoff’s hypothesis — applying it actually <em>hurt</em> AUC by 0.0118</strong> because the prune confounded with MI routing-feature selection, which is itself a finding worth recording. Five commits, every chain head reproducible, the 629 determinism canaries unmoved throughout. <strong>ABNG drove the AUC headline; Locke drove the systems / detector-roadmap axis. Both contributions are real; they just sit on different axes than the brief originally assumed.</strong></p>
</div>
</div>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>ABNG lives at <a href="https://github.com/AdamEzzat1/CJC/tree/master/crates/cjc-abng"><code>crates/cjc-abng/</code></a> and Locke at <a href="https://github.com/AdamEzzat1/CJC/tree/master/crates/cjc-locke"><code>crates/cjc-locke/</code></a> in the CJC-Lang repository. The Diabetes-130 harness is in <a href="https://github.com/AdamEzzat1/CJC/blob/master/tests/abng/dataset_a_diabetes130.rs"><code>tests/abng/dataset_a_diabetes130.rs</code></a>. This post is Round Two of an ongoing campaign — see <a href="../../../blog/posts/abng-diabetes-readmission/index.html">Round One</a> for the methodology + baseline.</p>
</div>
</div>


</section>

 ]]></description>
  <category>CJC-Lang</category>
  <category>ABNG</category>
  <category>Locke</category>
  <category>medical</category>
  <category>diabetes</category>
  <category>calibration</category>
  <category>determinism</category>
  <category>experimental</category>
  <guid>https://adamezzat1.github.io/blog/posts/abng-diabetes-revisited-with-locke/index.html</guid>
  <pubDate>Thu, 28 May 2026 07:00:00 GMT</pubDate>
</item>
<item>
  <title>Locke v0.1.10: An Epistemic Layer for Data Validation</title>
  <dc:creator>Adam Ezzat</dc:creator>
  <link>https://adamezzat1.github.io/blog/posts/cjc-locke-v0.1-released/index.html</link>
  <description><![CDATA[ 



<div class="reading-time">
🕑 ~42 min read
</div>
<div class="stats-row">
<div class="stat">
<span class="number">202+</span><span class="stat-label">deterministic tests</span>
</div>
<div class="stat">
<span class="number">275×</span><span class="stat-label">faster than GX</span>
</div>
<div class="stat">
<span class="number">5/5</span><span class="stat-label">ground truth match</span>
</div>
<div class="stat">
<span class="number">0</span><span class="stat-label">external runtime deps</span>
</div>
</div>
<div class="callout callout-style-simple callout-tip no-icon callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-title-container flex-fill">
TL;DR
</div>
</div>
<div class="callout-body-container callout-body">
<p><code>cjc-locke v0.1.10</code> is live on <a href="https://crates.io/crates/cjc-locke">crates.io</a>. It’s not another validation framework — it’s an <strong>epistemic layer</strong>. Every finding carries typed evidence, declared assumptions, and a content-addressed 64-bit ID. Two runs over the same data produce byte-identical reports. The CLI ships as <code>cjcl locke &lt;subcommand&gt;</code> and covers validation, drift, lineage, belief reports, causal guardrails, snapshot-and-diff CI gates, HTML output, and a streaming validator for out-of-RAM datasets. On a 7023-row synthetic churn dataset, Locke matched Great Expectations on every ground-truth finding and ran <strong>275× faster</strong> in release mode.</p>
</div>
</div>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
One prototype disclaimer
</div>
</div>
<div class="callout-body-container callout-body">
<p>Locke is built on top of <strong>CJC-Lang</strong>, a research compiler that is itself pre-1.0. Both are under active development. None of this has been deployed at production scale yet. The benchmark below is on synthetic data of a deliberately-tractable size; real-world performance characteristics will vary. I am the author of Locke, so the comparison framing isn’t from an impartial source — the benchmark code is in the repo for anyone to re-run.</p>
</div>
</div>
<section id="where-locke-sits-in-the-data-quality-stack" class="level2">
<h2 class="anchored" data-anchor-id="where-locke-sits-in-the-data-quality-stack">Where Locke sits in the data-quality stack</h2>
<p>Before the API and the benchmark, the most useful thing I can do is position Locke against the four tools it gets confused with. They each solve a real problem; Locke solves a different one.</p>
<table class="table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Layer</th>
<th>What it asks</th>
<th>Representative tools</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Transformation-correctness</td>
<td>“Did the SQL run? Did the pipeline produce the expected shape?”</td>
<td><strong>dbt</strong>, Airflow, Dagster</td>
</tr>
<tr class="even">
<td>Schema / contract</td>
<td>“Does each row match the type and range I expect?”</td>
<td><strong>Pandera</strong>, Pydantic, JSON Schema</td>
</tr>
<tr class="odd">
<td>Anomaly-detection</td>
<td>“Is something weird here that I didn’t know to check?”</td>
<td><strong>Monte Carlo</strong>, Anomalo, Bigeye</td>
</tr>
<tr class="even">
<td>Suite-validation</td>
<td>“Does the data pass these specific rules?”</td>
<td><strong>Great Expectations</strong>, Soda Core</td>
</tr>
<tr class="odd">
<td><strong>Epistemic (Locke)</strong></td>
<td><strong>“What evidence do I have for each finding, what did Locke assume, can I reproduce the audit byte-for-byte, and is the inference boundary explicit?”</strong></td>
<td><strong>Locke</strong></td>
</tr>
</tbody>
</table>
<p>The five layers stack. A team can run dbt, Pandera, Monte Carlo, GX, and Locke and have them disagree about nothing — they’re answering different questions. The thing that distinguishes Locke is the third clause: <strong>byte-for-byte reproducible audit</strong>.</p>
<p>Concretely:</p>
<ul>
<li>A dbt test prints “PASS” or “FAIL”. Locke prints <code>id=5fbf919f0ada9b58 code=E9004 severity=error evidence=[count(duplicate_keys)=23] assumptions=[...]</code>.</li>
<li>A Pandera report carries a wall-clock timestamp. Locke’s report carries a 64-bit fingerprint computed <em>from the findings themselves</em> — no timestamps, no UUIDs, no map-iteration nondeterminism.</li>
<li>Monte Carlo learns that “Sundays look weird” from metadata. Locke says “your <code>monthly_charges</code> distribution shifted with KS D=0.34 and here are the five rows that drove it.”</li>
<li>GX has a vastly larger expectation library. Locke ships fewer expectations but, for the ones it ships, treats the report as the audit artifact, not a side effect.</li>
</ul>
<p>The rest of this post is “what does that mean in practice.” Four worked scenarios, then explicit subsections comparing Locke against each adjacent tool.</p>
</section>
<section id="why-i-built-locke" class="level2">
<h2 class="anchored" data-anchor-id="why-i-built-locke">Why I built Locke</h2>
<p>CJC-Lang has a strong runtime determinism story — Kahan accumulators, BTreeMap discipline, content-addressed snapshots, deterministic RNG threading. What it didn’t have until now was a <em>dataset-level</em> skepticism layer: a unified way to ask “is this CSV / JSONL clean enough to train on?” or “did my production data drift since last week?” Users were rolling their own checks per-project and losing the determinism guarantee at the validation boundary.</p>
<p>I considered a few obvious paths:</p>
<ul>
<li>Embed validation in <code>cjc-data</code> (the tidyverse-inspired DataFrame DSL). Tempting but it would pollute the data-DSL’s public type surface.</li>
<li>Extend <code>cjc-abng</code> (Adaptive Belief Network Graph), which already has drift / audit / Merkle infrastructure. But ABNG’s primitives are tree-node-oriented, not column/row-oriented.</li>
<li>A new, standalone crate. Same precedent as <code>cjc-quantum</code>, <code>cjc-abng</code>, <code>cjc-dharht</code>.</li>
</ul>
<p>The third option won. <strong>Locke is now <code>cjc-locke</code> — a dedicated workspace crate alongside the others, depending only on <code>cjc-data</code> and <code>cjc-repro</code> and exposing a clean public API plus a CLI subcommand.</strong></p>
</section>
<section id="what-evidence-aware-actually-means" class="level2">
<h2 class="anchored" data-anchor-id="what-evidence-aware-actually-means">What “evidence-aware” actually means</h2>
<p>The epistemic claim first: <strong>every Locke finding is a structured object with declared evidence and declared assumptions</strong>, not a message string. The name comes from John Locke’s empiricism — every claim should trace back to evidence, every belief should be proportioned to that evidence. Translated into a data-validation library:</p>
<ul>
<li><strong>Findings are evidence-bearing.</strong> Every <code>ValidationFinding</code> carries a typed <code>Vec&lt;FindingEvidence&gt;</code> of <code>Count</code> / <code>Ratio</code> / <code>Range</code> / <code>Metric</code> / <code>Sample</code> entries. No free-text-only messages.</li>
<li><strong>Reports are content-addressed.</strong> Every finding has a 64-bit fingerprint over (code, severity, message, column, evidence). Two runs over the same data produce identical IDs. CI gates can compare reports byte-for-byte.</li>
<li><strong>Impressions vs Ideas.</strong> A <code>LockeImpression</code> is a raw observed fact (a dataset, a column, a row). A <code>LockeIdea</code> is anything derived (a filtered subset, a feature, a metric, a prediction). The lineage DAG only has edges from impressions / parent ideas to child ideas — acyclic by construction.</li>
<li><strong>Belief reports are explainable.</strong> The 8-dimension <code>BeliefScore</code> (schema / missingness / drift / leakage / lineage / sample / duplication / constraint) ships with a breakdown, not a magic number.</li>
<li><strong>Causality is conservative.</strong> Every <code>CausalGuardrailReport</code> carries a top-level disclaimer. No warning kind is named “Cause” or “Effect.” The DAG-based assumption registry lets users declare hypothesised pathways without certifying them.</li>
</ul>
<p>If “evidence-aware” sounds like over-engineering, the alternative is a CI gate that flakes because two runs over the same data produced different report metadata. Locke avoids that by construction.</p>
</section>
<section id="the-shape-of-the-api" class="level2">
<h2 class="anchored" data-anchor-id="the-shape-of-the-api">The shape of the API</h2>
<p>Three usage surfaces:</p>
<section id="rust-api" class="level3">
<h3 class="anchored" data-anchor-id="rust-api">Rust API</h3>
<div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb1-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">use</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">cjc_locke::</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>validate<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> ValidateOptions<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> ValidationConfig<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">};</span></span>
<span id="cb1-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">use</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">cjc_data::</span>DataFrame<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb1-3"></span>
<span id="cb1-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> opts <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ValidateOptions <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb1-5">    dataset_label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"train.csv"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>into()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-6">    config<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">ValidationConfig::</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">default</span>()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-7">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">..</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">Default</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">default</span>()</span>
<span id="cb1-8"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">};</span></span>
<span id="cb1-9"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> report <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> validate(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>df<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>opts)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb1-10"><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">println!</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"worst severity: {}"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> report<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>worst_severity())<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb1-11"><span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">for</span> f <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">in</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>report<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>findings <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb1-12">    <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">println!</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"{} {}: {}"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> f<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>code<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> f<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>severity<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> f<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>message)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb1-13"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div>
</section>
<section id="cli" class="level3">
<h3 class="anchored" data-anchor-id="cli">CLI</h3>
<div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb2-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> locke validate data.csv <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb2-2">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--target</span> churned <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb2-3">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--primary-key</span> customer_id <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb2-4">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--time-col</span> event_ts <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--max-timestamp</span> 1714521600000 <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb2-5">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--html</span> report.html <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb2-6">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--save-json</span> baseline.json <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb2-7">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--fail-on</span> warning</span></code></pre></div>
<p>That single command runs the full v0.5 validation pass (missingness + duplicates + impossible values + constants + outliers + sentinels + schema + temporal + target-leakage + ID-like + duplicate-key conditioning), writes a stakeholder-ready HTML report with an inline-SVG correlation heatmap, persists the canonical JSON baseline, and exits non-zero if any finding crosses the warning threshold.</p>
</section>
<section id="cjc-lang-source" class="level3">
<h3 class="anchored" data-anchor-id="cjc-lang-source">CJC-Lang source</h3>
<pre class="cjcl"><code>fn main() {
  let h = locke_table_new();
  locke_table_add_float_col(h, "age", [25.0, 30.0, 0.0/0.0, 30.0]);
  locke_table_add_int_col(h, "churned", [0, 1, 1, 0]);
  let r = locke_validate(h);
  print(locke_report_worst_severity(r));   // 2 = Warning (NaN detected)
  print(locke_report_overall_score(r));    // 0.857
}</code></pre>
<p>The CJC-Lang surface lets <code>.cjcl</code> programs validate data without dropping into Rust. The implementation uses a thread-local registry plus 22 <code>locke_*</code> builtins routed through both executors via satellite dispatch — the same pattern <code>cjc-ad</code>’s GradGraph and <code>cjc-abng</code>’s belief radix graph use.</p>
</section>
</section>
<section id="four-scenarios-in-detail" class="level2">
<h2 class="anchored" data-anchor-id="four-scenarios-in-detail">Four scenarios in detail</h2>
<p>The fastest way to understand why “epistemic layer” isn’t marketing-speak is to walk four worked examples. Each scenario shows real bytes from the repo’s <code>bench/locke_vs_gx/</code> artifacts.</p>
<section id="scenario-1-the-monday-morning-gate-diff" class="level3">
<h3 class="anchored" data-anchor-id="scenario-1-the-monday-morning-gate-diff">Scenario 1 — The Monday-morning gate diff</h3>
<p>A data scientist arrives Monday. The Q3 production data dump is ready. They want to know: did anything important change versus the Q2 baseline they trained against?</p>
<p>Two commands:</p>
<div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb4-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Friday (Q2): after training, snapshot the Q2 baseline</span></span>
<span id="cb4-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> locke validate train_2026q2.csv <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb4-3">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--target</span> churned <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb4-4">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--primary-key</span> customer_id <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb4-5">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--time-col</span> event_ts <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb4-6">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--max-timestamp</span> 1719792000000 <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb4-7">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--save-json</span> baselines/2026q2_baseline.json</span>
<span id="cb4-8"></span>
<span id="cb4-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Monday (Q3): diff the new dump against last quarter's snapshot</span></span>
<span id="cb4-10"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> locke gate baselines/2026q2_baseline.json <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb4-11">  data/prod_2026q3.csv <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb4-12">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--fail-on</span> warning</span></code></pre></div>
<p>The gate emits a structural diff:</p>
<pre><code># Locke Gate Diff
reference_run_id: 1075bbad9d9b882f
current_run_id:   5fbf919f0ada9b58
summary: appeared=3 disappeared=0 unchanged=17

appeared:
  + code=E9035 severity=warning column=last_login_days_ago
    missingness rate of `last_login_days_ago` shifted: train=0.020, test=0.180
  + code=E9039 severity=error column=monthly_charges
    numeric distribution shift in `monthly_charges` (KS D = 0.342)
  + code=E9051 severity=error column=event_ts
    127 test rows have `event_ts` &lt;= max train timestamp; test set overlaps train window

disappeared: (none)

unchanged:
  · code=E9002 column=customer_id (Int, no null mask)
  · code=E9003 (23 duplicate rows)
  · code=E9072 column=customer_id (ID-like cardinality hint)
  · ... (14 more)</code></pre>
<p>Exit code 2. CI fails the job. A human reads the diff:</p>
<ol type="1">
<li><code>E9051</code> is the smoking gun — 127 rows of the test dump have timestamps inside the training window. Someone changed the ingestion cutoff and the new feed doesn’t respect the train/test boundary anymore.</li>
<li><code>E9039</code> is the symptom — once the test set silently overlaps the train window, the marginal distribution of <code>monthly_charges</code> shifts because the overlap rows are systematically different.</li>
<li><code>E9035</code> is the third-order effect — the missingness rate of a derived column jumps because the overlap rows have null <code>last_login_days_ago</code>.</li>
</ol>
<p>The fix is upstream: the ETL job needed <code>WHERE event_ts &gt; '2026-07-01'</code> and the analyst who refactored it last week silently dropped that clause. The CI gate caught it before model training started.</p>
<p>What makes this work at all: <strong><code>reference_run_id</code> and <code>current_run_id</code> are content-addressed</strong>. The same data on a colleague’s Mac produces <code>5fbf919f0ada9b58</code> byte-for-byte. There’s no “this report has timestamp 2026-05-28T09:14:23Z” or “validation_result_uuid f8a3…” line to make two runs disagree. The 17 <code>unchanged</code> findings are recognised as the same findings because their fingerprints match, not because their messages happened to spell the same words.</p>
</section>
<section id="scenario-2-the-target-leakage-smoking-gun" class="level3">
<h3 class="anchored" data-anchor-id="scenario-2-the-target-leakage-smoking-gun">Scenario 2 — The target-leakage smoking gun</h3>
<p>A new feature lands in the churn pipeline: <code>days_since_last_login</code>. The engineer who added it computed it as <code>now() - last_login_timestamp</code> at feature-engineering time. But “now()” was the <em>scoring</em> moment, not the <em>event</em> moment — every row that already churned has <code>last_login_timestamp</code> frozen at the time the customer left, so <code>days_since_last_login</code> is a near-perfect predictor of <code>churned</code> by construction.</p>
<p>A model trained on this dataset will report 99% ROC AUC offline and degrade to 60% in production within a week.</p>
<p>Locke catches it. Run:</p>
<div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb6-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> locke validate feature_engineered.csv <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--target</span> churned</span></code></pre></div>
<p>The relevant finding:</p>
<div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode json code-with-copy"><code class="sourceCode json"><span id="cb7-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb7-2">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"id"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"8b3c1f5d2a4e9c7b"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb7-3">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"code"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"E9060"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb7-4">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"severity"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"error"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb7-5">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"message"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"feature `days_since_last_login` has AUC = 0.971 against target `churned`; likely target leakage"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb7-6">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"column"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"days_since_last_login"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb7-7">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"evidence"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">[</span></span>
<span id="cb7-8">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"kind"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"metric"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"label"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"auc"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"value"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.971</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb7-9">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"kind"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"metric"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"label"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"threshold"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"value"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.95</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb7-10">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"kind"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"count"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span>  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"label"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"n_pos"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span>     <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"value"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1574</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb7-11">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"kind"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"count"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span>  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"label"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"n_neg"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span>     <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"value"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5449</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb7-12">  <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">]</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb7-13">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"assumptions"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">[</span></span>
<span id="cb7-14">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"rank-sum AUC computed with f64::total_cmp + tie averaging"</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb7-15">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"leakage is suspected when |AUC - 0.5| &gt;= 0.45 (Error) or 0.35 (Warning)"</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb7-16">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"this check only fires for binary targets; multi-class deferred to v0.2"</span></span>
<span id="cb7-17">  <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">]</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb7-18">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"suggested_next_checks"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">[</span></span>
<span id="cb7-19">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"verify this feature is computed at the event time, not score time"</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb7-20">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"check the upstream feature-engineering job for `now()` calls on historical rows"</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb7-21">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"rebuild the feature using the event timestamp and re-validate"</span></span>
<span id="cb7-22">  <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb7-23"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div>
<p>After the fix — feature recomputed using the <em>event</em> timestamp, not <code>now()</code> — the same column drops to AUC = 0.61, well below the 0.85 Warning threshold. E9060 disappears from the next gate run. The diff says <code>disappeared: E9060 column=days_since_last_login</code>. The fix is auditable.</p>
<p>The point isn’t that “Locke catches leakage” in the abstract. It’s that the finding carries <strong>the AUC value</strong>, <strong>the count of positives and negatives</strong>, <strong>the threshold that fired</strong>, <strong>the assumption that AUC is rank-sum with tie averaging</strong>, and <strong>the suggested next check</strong> — all as structured fields. A reviewer can decide whether to trust the finding without re-reading Locke’s source.</p>
</section>
<section id="scenario-3-the-lineage-diagram" class="level3">
<h3 class="anchored" data-anchor-id="scenario-3-the-lineage-diagram">Scenario 3 — The lineage diagram</h3>
<p>Locke’s <code>TracedDataFrame</code> wraps <code>cjc-data</code>’s DataFrame ops and records a DAG: each operation is a <code>LockeIdea</code> node, each input dataset is a <code>LockeImpression</code>, and edges carry content-addressed IDs. When a downstream metric goes wrong, you walk the DAG backwards to find which derivation contaminated it.</p>
<p>Consider a four-step pipeline: load raw events, filter to active customers, group by plan tier, and compute churn rate per group:</p>
<div class="cell" data-fig-width="8">
<div class="cell-output-display">
<div>
<div>
<pre class="mermaid mermaid-js">flowchart LR
    I["events.csv&lt;br/&gt;&lt;i&gt;LockeImpression&lt;/i&gt;&lt;br/&gt;id=a4f8c2d19e0b3517"]:::imp --&gt;|"observe()"| O["raw_events&lt;br/&gt;&lt;i&gt;LockeIdea&lt;/i&gt;&lt;br/&gt;id=2b7e4a1f8c3d9602"]:::idea
    O --&gt;|"filter(active==1)&lt;br/&gt;id=c91f5b2e0a4d7836"| F["active_events&lt;br/&gt;&lt;i&gt;LockeIdea&lt;/i&gt;&lt;br/&gt;id=f3a2c780b91d4e5c"]:::idea
    F --&gt;|"group_by(plan_tier)&lt;br/&gt;id=8e6b3f04c12a9d7e"| G["grouped&lt;br/&gt;&lt;i&gt;LockeIdea&lt;/i&gt;"]:::idea
    G --&gt;|"summarise(churn_rate=mean(churned))&lt;br/&gt;id=1075bbad9d9b882f"| S["churn_by_tier&lt;br/&gt;&lt;i&gt;LockeIdea&lt;/i&gt;&lt;br/&gt;id=5fbf919f0ada9b58"]:::idea
    S --&gt;|"with_column(label='Q3-2026')&lt;br/&gt;id=db4cac307664c097"| W["labeled&lt;br/&gt;&lt;i&gt;LockeIdea&lt;/i&gt;"]:::idea

    classDef imp fill:#e8f4fd,stroke:#2196F3,stroke-width:2px
    classDef idea fill:#fff3e0,stroke:#FF9800,stroke-width:1px
</pre>
</div>
<p>Locke lineage DAG. Impressions are raw observed facts (cylinder); Ideas are derived (rounded). Edge labels are content-addressed 64-bit IDs computed from the parent ID + the operation. Two runs of the same pipeline over the same input produce identical IDs.</p>
</div>
</div>
</div>
<p>The DAG is acyclic by construction (impressions and ideas are distinct node kinds; edges only go impression → idea or idea → idea). The DAG is also deterministic: rerunning the same pipeline on the same bytes produces the same node IDs. That means a downstream consumer can cite “the churn rate with idea-id <code>5fbf919f0ada9b58</code>” and a Locke replay on the same input will reproduce it byte-for-byte.</p>
<p>When the Monday gate diff above says <code>current_run_id: 5fbf919f0ada9b58</code>, that ID is exactly this node’s content-addressed fingerprint. The <code>gate</code> command and the lineage DAG share the same hashing primitive.</p>
<p>In code, this looks like:</p>
<div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb8-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">use</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">cjc_locke::lineage::</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>LineageGraph<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> emit_lineage_text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">};</span></span>
<span id="cb8-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">use</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">cjc_locke::traced::</span>TracedDataFrame<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb8-3"></span>
<span id="cb8-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> raw <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">TracedDataFrame::</span>observe(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>events_df<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"events.csv"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb8-5"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> active <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> raw<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>filter(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span>row<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span> row<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>get_int(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"active"</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">Some</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb8-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> grouped <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> active<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>group_by(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"plan_tier"</span>])<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb8-7"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> churn_by_tier <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> grouped<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>summarise(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"churn_rate"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span>g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span> g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>mean_f64(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"churned"</span>))<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb8-8"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> labeled <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> churn_by_tier<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>with_column(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"label"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Q3-2026"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb8-9"></span>
<span id="cb8-10"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> graph<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> LineageGraph <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> labeled<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>lineage()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb8-11"><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">println!</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"{}"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> emit_lineage_text(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>graph))<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span></code></pre></div>
<p>The text emit is what populates the IDs above. The Mermaid diagram is one possible rendering; the canonical form is the JSON graph that Locke persists, which the HTML report visualises with the same structure.</p>
</section>
<section id="scenario-4-belief-score-side-by-side" class="level3">
<h3 class="anchored" data-anchor-id="scenario-4-belief-score-side-by-side">Scenario 4 — Belief score side-by-side</h3>
<p>Two datasets arrive on the same day: <code>dataset_a.csv</code> (a long-running production export) and <code>dataset_b.csv</code> (a new vendor’s first delivery). Same column shape. The team wants a single number per dataset to decide which to use for the quarterly run.</p>
<p>Locke produces an 8-dimension <code>BeliefScore</code>, not a single number. Each dimension is on <code>[0, 1]</code> where 1.0 means “no evidence of a problem on this axis”:</p>
<pre><code>dataset_a.csv
  overall_score:        0.91
  schema_score:         1.00   no schema mismatches
  missingness_score:    0.99   1 column with &lt;1% NaN, no joint missingness
  drift_score:          0.97   one numeric column with KS D = 0.06 vs ref (notice)
  leakage_score:        1.00   no feature with |AUC| &gt;= 0.85 against target
  lineage_score:        0.95   one Idea node missing parent reference (notice)
  sample_score:         0.92   sample_cap=10000 hit; flagged as approximate
  duplication_score:    1.00   no duplicate rows; no duplicate primary keys
  constraint_score:     1.00   no impossible values; no constants

dataset_b.csv
  overall_score:        0.42
  schema_score:         0.80   `total_charges` declared Float, inferred Str
  missingness_score:    0.42   1.4% of total_charges is empty string; conditional miss with last_login
  drift_score:          0.71   monthly_charges KS D = 0.34 vs dataset_a (error)
  leakage_score:        1.00   no feature with |AUC| &gt;= 0.85 against target
  lineage_score:        0.50   vendor delivery has no lineage attached
  sample_score:         1.00   full dataset evaluated
  duplication_score:    0.65   23 duplicate customer_id keys (E9004 error)
  constraint_score:     0.62   5 rows with monthly_charges = -9999 (impossible)</code></pre>
<p>The team picks dataset A for the quarterly run and files a ticket against the vendor for dataset B. The breakdown tells them <em>which axes</em> are degraded, not just that the dataset is worse — vendor B’s <code>drift_score</code> collapsed because the marginal distribution of <code>monthly_charges</code> shifted by 0.34 in KS distance, and the <code>constraint_score</code> collapsed because of the sentinel <code>-9999</code> rows.</p>
<p>Crucially: each dimension is computed from the <em>same evidence-bearing findings</em> the gate diff uses. The <code>drift_score = 0.71</code> for dataset B is <code>1 - penalty(E9039)</code> where <code>penalty</code> comes from a swappable <code>BeliefPenalty</code> model. The model is declared, not implicit; a team that disagrees with the default can supply their own and the audit still reproduces.</p>
<p>A single magic number from a black-box scorer would let you compare datasets but not act on the comparison. The 8-dim breakdown plus the findings table is what makes the decision actionable.</p>
</section>
</section>
<section id="when-ordinary-tools-see-nothing" class="level2">
<h2 class="anchored" data-anchor-id="when-ordinary-tools-see-nothing">When ordinary tools see nothing</h2>
<p>The previous scenarios showed Locke firing on a dataset where <em>something</em> observably went wrong. The harder case — and the one that motivates the epistemic layer — is when an ordinary tool reports success while Locke surfaces a structured warning. Four side-by-side examples.</p>
<section id="a-dashboard-says-churn-risk-up-22" class="level3">
<h3 class="anchored" data-anchor-id="a-dashboard-says-churn-risk-up-22">A — Dashboard says “churn risk up 22%”</h3>
<p><strong>Ordinary view (Looker tile):</strong></p>
<pre><code>KPI:  customer_churn_risk_score
Δ wk: +22.4%
trend: ▁▂▂▃▃▅▆▇
owner: notified — review next standup</code></pre>
<p><strong>Locke gate diff for the same data:</strong></p>
<pre><code># Locke Gate Diff
reference_run_id: 1075bbad9d9b882f
current_run_id:   ce81fa3d2b5a974e
summary: appeared=4 disappeared=0 unchanged=15

appeared:
  + code=E9039 severity=error column=last_login_days_ago
    numeric distribution shift (KS D = 0.51)
  + code=E9061 severity=warning column=last_login_days_ago
    feature |AUC|=0.87 against `churned` — proxy-leakage suspect
  + code=E9051 severity=error column=event_ts
    312 production rows have event_ts inside the training window
  + code=E9080 severity=warning
    causal assumption `churn_intent → unsubscribe` unsupported in this sample</code></pre>
<p>The dashboard reports the model output. Locke explains <em>why</em> the output moved: an upstream ETL change added a 7-day-lagged event source, the <code>last_login_days_ago</code> distribution shifted because recent activity is now censored, the feature became a proxy for the lag itself (not for churn intent), and 312 production rows leaked back into the training window. The 22% risk uptick is a measurement artifact. A human reading the Locke diff stops the alert chain before the executive briefing; a human reading only the dashboard escalates.</p>
</section>
<section id="b-dbt-test-exits-0-model-memorises-customer_id" class="level3">
<h3 class="anchored" data-anchor-id="b-dbt-test-exits-0-model-memorises-customer_id">B — <code>dbt test</code> exits 0, model memorises <code>customer_id</code></h3>
<p><strong>Ordinary view (CI log):</strong></p>
<pre><code>$ dbt test --select model:churn_features
Found 14 tests, 0 sources, 1 model
14 of 14 PASS in 8.41s
Completed successfully — 0 errors</code></pre>
<p><strong>Locke output on the same table:</strong></p>
<pre><code>[E9072 notice] column `customer_id`      cardinality 0.997  — id-like leak suspect
                id=328845d456c53aea
[E9072 notice] column `last_login_unix`  cardinality 0.997  — id-like leak suspect
                id=03ae43c0bccfbc79
[E9072 notice] column `total_charges`    cardinality 0.977  — id-like leak suspect
                id=86d3973bf5062cf8</code></pre>
<p>dbt’s <code>unique</code> test is structurally the right question for transformation correctness — “does each row have a distinct key.” It is structurally the wrong question for whether to keep that column as a feature. A model trained on the dbt-validated table will hit 98% offline ROC AUC and degrade to 55% in production within a week, because gradient boosting will happily memorise customer IDs the moment they’re left in. Locke fires E9072 on every column whose distinct-per-row ratio exceeds 0.95 and asks the human to decide.</p>
</section>
<section id="c-pandera-schema-passes-a-stale-snapshot" class="level3">
<h3 class="anchored" data-anchor-id="c-pandera-schema-passes-a-stale-snapshot">C — Pandera schema passes a stale snapshot</h3>
<p><strong>Ordinary view (Pandera report):</strong></p>
<pre><code>SchemaModel: CustomersLatest
Validated successfully.
14 columns, 124083 rows.
Run at 2026-10-04T03:00:00.184Z (build #4471)</code></pre>
<p><strong>Locke output on the same file:</strong></p>
<pre><code>[E9050 warning] column `event_ts` not sorted: 1247 non-decreasing pairs
                id=a96abd21b26275c6
[E9054 warning] dataset `customers_latest.parquet` is 9.2 days stale
                max(event_ts) = 2026-09-25T00:00:00Z
                expected_at   = 2026-10-04T00:00:00Z
                id=e9a4f12c80b5d736</code></pre>
<p>Pandera answers “does each row match the type-and-range contract.” It cannot answer “is this snapshot the snapshot I expected” because the contract is a per-row predicate, not a dataset-scoped invariant. Locke’s <code>--max-timestamp</code> carries a declared dataset-level invariant and fires E9054 when it’s violated. The downstream model trained on the Pandera-validated snapshot will be trained on data 9 days older than the team thinks, and the production scorer will degrade silently.</p>
</section>
<section id="d-monte-carlo-green-traintest-boundary-leaks" class="level3">
<h3 class="anchored" data-anchor-id="d-monte-carlo-green-traintest-boundary-leaks">D — Monte Carlo green, train/test boundary leaks</h3>
<p><strong>Ordinary view (MC monitor):</strong></p>
<pre><code>Table: prod.feature_store.churn_features
Row count anomaly:   normal (z = 0.31)
Schema anomaly:      none
Freshness:           on-time
Distribution drift:  none detected
Status: ✅ healthy</code></pre>
<p><strong>Locke output:</strong></p>
<pre><code>[E9051 error] 127 rows in `feature_store_test` have event_ts
              &lt;= max(event_ts) of `feature_store_train`
              overlap_rate = 0.0021
              id=8f6c5b3d1e7a4920</code></pre>
<p>Monte Carlo’s metadata-anomaly detector trips on shapes that look statistically unusual compared to history. A 0.21% subpopulation overlap between train and test rows doesn’t shift the warehouse-layer marginal distribution by anywhere near MC’s sensitivity threshold — <em>it can’t</em>, because it’s a small slice. Locke trips because the train/test temporal boundary is a <em>declared invariant</em>, not a statistical anomaly. Anomaly detection trips on unexpected statistical shapes; epistemic checks trip on declared known-class violations. Both layers are useful; only the epistemic layer catches this specific failure.</p>
</section>
<section id="common-thread" class="level3">
<h3 class="anchored" data-anchor-id="common-thread">Common thread</h3>
<p>Every ordinary-tool report in the four examples is <em>correct under its own contract</em>. The Looker tile correctly computed the churn-risk delta. dbt correctly verified that <code>customer_id</code> is unique. Pandera correctly checked the type-and-range schema. Monte Carlo correctly observed no row-count or freshness anomaly. None of them is wrong; each is silent on the failure mode Locke caught, because the failure mode is outside the layer the tool is designed for.</p>
<p>The epistemic layer is the layer that asks: <em>given the evidence in this dataset and the assumptions Locke is allowed to state, is there a structured finding that should make a human stop and think before the next step runs?</em></p>
</section>
</section>
<section id="what-ships-in-v0.1.10" class="level2">
<h2 class="anchored" data-anchor-id="what-ships-in-v0.1.10">What ships in v0.1.10</h2>
<div class="sourceCode" id="cb18" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb18-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cargo</span> add cjc-locke</span></code></pre></div>
<p>You get:</p>
<ul>
<li><strong>Validation</strong> — missingness (with null masks for non-float columns), duplicates (full row + by primary key with conditioning), impossible-value DSL, constants and near-constants, schema mismatch, suspicious cardinality, IQR + modified-Z outliers, sentinel-value detection, conditional missingness, imbalanced-class warning, ID-like cardinality hints</li>
<li><strong>Drift / induction risk</strong> — exact Kolmogorov–Smirnov D-statistic (replacing v0.1’s PSI as the default), mean/std/range/missingness shift, categorical TVD, small-sample warnings, temporal train/test overlap detection</li>
<li><strong>Time-aware checks</strong> — sortedness, future-leakage cutoff, gap detection</li>
<li><strong>Target leakage</strong> — per-feature ROC AUC against a binary target via rank-sum</li>
<li><strong>Lineage</strong> — <code>LockeImpression</code> vs <code>LockeIdea</code> semantic split, deterministic DAG, monotonic audit chain, <code>TracedDataFrame</code> wrapper covering 14 cjc-data operations</li>
<li><strong>Belief reports</strong> — 8-dimension <code>BeliefScore</code> with explainable breakdown, user-tunable <code>BeliefWeights</code> and <code>BeliefPenalty</code> model</li>
<li><strong>Causal guardrails</strong> — 5 warning kinds, <code>CausalDag</code> assumption registry with cycle detection, observational-only mode</li>
<li><strong>Streaming</strong> — <code>StreamingValidator</code> with no-reconstruction running state (Welford + BTreeMap ECDF), KS D bit-identical to single-shot</li>
<li><strong>Output</strong> — canonical JSON serializer (hand-written, zero serde dependency), HTML report with inline-SVG correlation heatmap, <code>cjcl locke gate</code> for snapshot-and-diff CI workflows</li>
<li><strong>CJC-Lang integration</strong> — 22 <code>locke_*</code> builtins routed through both executors</li>
</ul>
<p>The error-code range is E9000–E9099. About half is currently assigned (E9001 missingness, E9003/E9004 duplicates, E9007 sentinel, E9039 KS-drift, E9040/E9041 outliers, E9050–E9054 temporal, E9060/E9061 target leakage, E9070 conditional missingness, E9071 imbalanced class, E9072 ID-like, E9073 dup-key conditioning, …). The rest is reserved for v0.2+.</p>
</section>
<section id="how-locke-compares-to-adjacent-tools" class="level2">
<h2 class="anchored" data-anchor-id="how-locke-compares-to-adjacent-tools">How Locke compares to adjacent tools</h2>
<p>The five-layer matrix at the top of this post named four tools. This section walks each in detail. The goal is fair comparison, not a hit piece — each tool is excellent at its layer of the stack and Locke does not replace any of them. Locke replaces nothing; it <strong>adds</strong> an epistemic layer below them.</p>
<section id="vs-great-expectations" class="level3">
<h3 class="anchored" data-anchor-id="vs-great-expectations">vs Great Expectations</h3>
<p>Great Expectations is the closest direct comparable. It’s a suite-validation framework: write a list of expectations, run them against a batch, get a <code>ValidationResult</code>. The library is mature (10K+ stars, 500+ contributors, ~200 expectations, native SQL backends, Data Docs, profiler-driven auto-suite generation).</p>
<p>What Locke does differently:</p>
<ul>
<li><strong>Content-addressed identifiers.</strong> GX validation results have <code>validation_result_id</code> UUIDs and <code>meta</code> blocks with timestamps. Two runs over the same data produce different result IDs. Locke’s run_id and per-finding IDs are content-fingerprints; identical bytes in produce identical IDs out.</li>
<li><strong>Findings as structured objects.</strong> GX returns success/failure plus per-expectation <code>result.partial_unexpected_list</code>. Locke returns <code>code</code>, <code>severity</code>, <code>column</code>, <code>evidence: Vec&lt;FindingEvidence&gt;</code>, <code>assumptions: Vec&lt;String&gt;</code>, <code>suggested_next_checks: Vec&lt;String&gt;</code> as first-class fields.</li>
<li><strong>Churn-specific defaults out of the box.</strong> E9004 (duplicate primary keys), E9060/E9061 (target leakage), E9072 (ID-like cardinality), E9051/E9052 (train/test temporal overlap and future-leakage cutoff), E9070 (conditional missingness), E9073 (duplicate-key conditioning). Equivalents in GX require writing custom expectations.</li>
</ul>
<p>What GX does better:</p>
<ul>
<li>Broader expectation library (~200 vs Locke’s ~25).</li>
<li>Native SQL backends (Snowflake, BigQuery, Postgres) without exporting to CSV first.</li>
<li>Data Docs as a polished HTML output with a longer history.</li>
<li>Suite versioning, alerting hooks, profiler-driven auto-suite generation.</li>
</ul>
<p>The benchmark below quantifies the head-to-head on a single workflow: 5/5 ground-truth match, 275× faster end-to-end on a 7023-row churn dataset.</p>
</section>
<section id="vs-dbt-tests" class="level3">
<h3 class="anchored" data-anchor-id="vs-dbt-tests">vs dbt tests</h3>
<p>dbt is a transformation-layer tool. Tests in dbt (<code>unique</code>, <code>not_null</code>, <code>relationships</code>, <code>accepted_values</code>, and custom SQL tests) run <em>inside the warehouse</em> against the output of a dbt model. They answer “did the SQL produce the shape this model expects.”</p>
<p>What Locke does that dbt tests don’t:</p>
<ul>
<li><strong>dbt validates the transformation. Locke validates whether the data tells a coherent story afterwards.</strong> A dbt <code>unique(customer_id)</code> test passes if no rows have the same <code>customer_id</code> — but a column like <code>total_charges</code> with 6858 distinct strings out of 7023 rows passes every dbt test ever written, and Locke fires E9072 (ID-like cardinality) on it because high cardinality on a non-key column is a leakage signal.</li>
<li><strong>dbt has no equivalent of target leakage AUC.</strong> You can write a custom dbt test that computes one column’s correlation with another, but you’d be writing a Locke checker badly in SQL.</li>
<li><strong>dbt’s <code>relationships</code> test verifies FK integrity. Locke’s E9070 conditional-missingness check verifies that <code>missing(A) ⇒ missing(B)</code> doesn’t accidentally hold across columns</strong> — a different, often more useful, integrity claim.</li>
<li><strong>dbt tests pass or fail.</strong> Locke findings carry severity (Notice / Info / Warning / Error) and evidence, so the team can decide <em>how</em> to act, not just whether the build passes.</li>
</ul>
<p>Where dbt is the right tool:</p>
<ul>
<li>The validation belongs inside the warehouse (no export, no CLI hop).</li>
<li>The shape constraints are exactly what dbt’s test library covers (uniqueness, not-null, FK, value set, dbt-utils accepted ranges).</li>
<li>The dbt DAG is the source of truth for transformation correctness anyway.</li>
</ul>
<p>The honest framing: <strong>dbt tests and Locke are complementary.</strong> dbt tests catch “the transformation broke.” Locke catches “the transformation succeeded and the resulting data has a coherence problem.” A pipeline can run dbt tests at the warehouse, export to CSV / Parquet for the ML side, then run Locke on the export. Both layers add signal.</p>
</section>
<section id="vs-pandera" class="level3">
<h3 class="anchored" data-anchor-id="vs-pandera">vs Pandera</h3>
<p>Pandera is the pandas-native schema-validation library. It’s the most direct comparable for “validate a DataFrame in-process.”</p>
<div class="sourceCode" id="cb19" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb19-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> pandera <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> pa</span>
<span id="cb19-2">schema <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pa.DataFrameSchema({</span>
<span id="cb19-3">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"customer_id"</span>: pa.Column(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>, pa.Check.ge(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)),</span>
<span id="cb19-4">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"monthly_charges"</span>: pa.Column(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>, pa.Check.in_range(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">500</span>)),</span>
<span id="cb19-5">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"churned"</span>: pa.Column(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span>),</span>
<span id="cb19-6">})</span>
<span id="cb19-7">schema.validate(df)</span></code></pre></div>
<p>What Locke does differently:</p>
<ul>
<li><strong>Pandera reports include timestamps and walk dictionaries in iteration order. Locke reports are content-addressed.</strong> Pandera’s <code>SchemaErrors.failure_cases</code> is reproducible across runs as a <em>DataFrame</em>; the surrounding report metadata is not. Locke’s entire report serialises to canonical JSON with deterministic key ordering and content-addressed IDs.</li>
<li><strong>Pandera is a Python library. Locke is a Rust crate + CLI + CJC-Lang surface.</strong> A team that doesn’t run Python in production can still gate CI on Locke; with Pandera the validator needs a Python runtime.</li>
<li><strong>Pandera’s checks are caller-declared. Locke ships a default battery.</strong> Pandera makes you spell out every constraint; Locke fires a churn-specific battery without configuration and lets you tune severity downstream. The trade is real: Pandera gives finer control; Locke gives a faster start.</li>
<li><strong>Pandera doesn’t have a lineage primitive.</strong> The <code>TracedDataFrame</code> wrapper has no analogue.</li>
</ul>
<p>Where Pandera is the right tool:</p>
<ul>
<li>The pipeline is already pandas-native and the team wants in-process type-and-range validation as a guard.</li>
<li>The schema is well-understood and the team prefers explicit declaration over a default battery.</li>
<li>Pandera’s <code>SchemaModel</code> class-based syntax integrates cleanly with Pydantic-style type-checked codebases.</li>
</ul>
<p>The honest framing: <strong>Pandera is excellent for in-Python pipeline guards.</strong> Locke is for the after-Pandera step — produce a CSV / Parquet / JSONL, then ask “is this artifact reproducible to audit?” Pandera passes the data through a contract; Locke produces the audit artifact.</p>
</section>
<section id="vs-monte-carlo" class="level3">
<h3 class="anchored" data-anchor-id="vs-monte-carlo">vs Monte Carlo</h3>
<p>Monte Carlo is the largest of the data-observability SaaS platforms. It treats data quality as an <em>anomaly-detection</em> problem: connect MC to your warehouse, MC instruments queries, learns metadata patterns, and pages on-call when something looks weird.</p>
<p>This is a fundamentally different category of tool. Locke and Monte Carlo barely overlap in what they ask:</p>
<ul>
<li><strong>Monte Carlo is for discovery.</strong> “We didn’t know to check this query’s row count. The ML model on our metadata thinks today’s row count is anomalous given the last 30 days. Page someone.”</li>
<li><strong>Locke is for known-class checks plus reproducible audit.</strong> “We know to check that no test row has an event timestamp inside the train window. Here is the deterministic finding with content-addressed ID. CI runs the gate every PR.”</li>
</ul>
<p>A concrete failure mode each tool catches that the other doesn’t:</p>
<ul>
<li>Monte Carlo catches: a Looker dashboard’s source table silently lost a JOIN clause last week and row counts dropped 8%. Locke has no instrumentation at the warehouse layer to see this.</li>
<li>Locke catches: the production Q3 dump has 127 rows with <code>event_ts</code> inside the Q2 train window. Monte Carlo’s metadata-anomaly model wouldn’t flag 127/120000 rows; the marginal distribution at the warehouse layer barely shifted.</li>
</ul>
<p>The two tools sit at different layers of the stack and can both run in the same org without conflict. Monte Carlo answers “is anything weird I didn’t think to check”; Locke answers “for the things I know to check, here is the auditable evidence and the byte-identical fingerprint.”</p>
<p>Where Monte Carlo is the right tool:</p>
<ul>
<li>The data lives in a managed warehouse (Snowflake, BigQuery, Databricks) and MC’s connectors fit.</li>
<li>The team wants Slack / PagerDuty integration, freshness monitoring at scale, and a managed UI for incident triage.</li>
<li>The category is genuinely “discover unknown issues” — auto-baselined metadata anomalies rather than declared checks.</li>
</ul>
<p>The honest framing: <strong>Monte Carlo and Locke solve different problems.</strong> A team running Monte Carlo for warehouse-layer observability and Locke for CSV/Parquet ML-pipeline gating has no overlap to resolve. Both add signal; neither replaces the other.</p>
</section>
</section>
<section id="the-benchmark-that-earned-the-headline" class="level2">
<h2 class="anchored" data-anchor-id="the-benchmark-that-earned-the-headline">The benchmark that earned the headline</h2>
<p>The honest assessment I gave on early versions was: “Locke is competitive with Great Expectations <em>for specific use cases</em>.” I had no actual benchmark to back that. Time to run one.</p>
<section id="methodology" class="level3">
<h3 class="anchored" data-anchor-id="methodology">Methodology</h3>
<p>I wrote a deterministic synthetic-churn generator (<code>bench/locke_vs_gx/generate_churn.py</code>) that produces a 7023-row, 8-column CSV with <strong>exactly-known seeded properties</strong>:</p>
<table class="table">
<thead>
<tr class="header">
<th>Seeded fact</th>
<th>Exact count</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>NaN values in <code>total_charges</code></td>
<td>100</td>
</tr>
<tr class="even">
<td>Impossible <code>monthly_charges = -9999</code></td>
<td>5</td>
</tr>
<tr class="odd">
<td>Outlier rows (<code>tenure_months &gt; 200</code>)</td>
<td>17</td>
</tr>
<tr class="even">
<td>Full-row duplicates</td>
<td>23</td>
</tr>
<tr class="odd">
<td>Sortedness violations in time column</td>
<td>≥ 1</td>
</tr>
<tr class="even">
<td>Duplicate <code>customer_id</code> keys</td>
<td>23</td>
</tr>
</tbody>
</table>
<p>Then I ran two validation suites — Locke v0.5 with default settings plus <code>--target churned --primary-key customer_id --time-col last_login_unix</code>, and Great Expectations 1.17 with equivalent expectations (13 expectations covering missingness, value ranges, sortedness, compound-key uniqueness, value-set membership).</p>
<p>Both tools’ findings were dumped to JSON. A comparison script walked both result sets and asked: did each tool match ground truth?</p>
<p>The benchmark is intentionally scoped to a single workflow — “validate a CSV-shaped churn dataset, report counts of seeded data issues.” It’s not a global GX-vs-Locke claim; it’s a calibration of one workflow Locke is designed for.</p>
</section>
<section id="a-real-finding-byte-for-byte" class="level3">
<h3 class="anchored" data-anchor-id="a-real-finding-byte-for-byte">A real finding, byte-for-byte</h3>
<p>To make the determinism claim concrete, here is a literal <code>ValidationFinding</code> from the benchmark’s <code>locke_results.json</code> — the impossible-<code>monthly_charges</code> outlier:</p>
<div class="sourceCode" id="cb20" style="background: #f1f3f5;"><pre class="sourceCode json code-with-copy"><code class="sourceCode json"><span id="cb20-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb20-2">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"id"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"f615af023438bcb5"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb20-3">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"code"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"E9041"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb20-4">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"severity"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"warning"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb20-5">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"message"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"column `monthly_charges` has 5 extreme outliers (3×IQR or |mod-Z| ≥ 5.0)"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb20-6">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"column"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"monthly_charges"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb20-7">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"row_range"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">null</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb20-8">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"sample_size"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">7023</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb20-9">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"evidence"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">[</span></span>
<span id="cb20-10">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"kind"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"count"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span>  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"label"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"n_extreme"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span>  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"value"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb20-11">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"kind"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sample"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"label"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sample"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span>     <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"value"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"row150=-9999,row151=-9999,row152=-9999"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb20-12">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"kind"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"range"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span>  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"label"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"iqr_q1_q3"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span>  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"min"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">45.974999999999994</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"max"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">95.27</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb20-13">  <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">]</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb20-14">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"assumptions"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">[</span></span>
<span id="cb20-15">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"extreme outliers usually indicate sensor errors, sentinel values, or unit-mix-ups"</span></span>
<span id="cb20-16">  <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">]</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb20-17">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"suggested_next_checks"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">[</span></span>
<span id="cb20-18">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"verify physical plausibility (e.g. age = 999, temperature = -9999)"</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb20-19">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"consider declaring sentinel values as nulls via NullMask"</span></span>
<span id="cb20-20">  <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb20-21"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div>
<p>The <code>id</code> field is <code>f615af023438bcb5</code> — a 64-bit content fingerprint over the rest of the structure. Rerunning the same benchmark on a different machine produces the same 16 hex characters. That’s the property that makes the gate diff in Scenario 1 work: “unchanged” findings are recognised by ID, not by message text.</p>
<p>Compare to the equivalent Great Expectations result for the same five rows (verbatim from <code>gx_results.json</code>):</p>
<div class="sourceCode" id="cb21" style="background: #f1f3f5;"><pre class="sourceCode json code-with-copy"><code class="sourceCode json"><span id="cb21-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb21-2">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"name"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"monthly_charges_in_range[-100,500]"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb21-3">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"severity"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Error"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb21-4">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"success"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">false</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb21-5">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"details"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb21-6">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"element_count"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">7023</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb21-7">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"unexpected_count"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb21-8">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"unexpected_percent"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.07118041435995925</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb21-9">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"partial_unexpected_list"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">[</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">-9999</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">-9999</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">-9999</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">-9999</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">-9999</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">]</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb21-10">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"partial_unexpected_counts"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">[</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"value"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">-9999</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"count"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">]</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb21-11">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"partial_unexpected_index_list"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">[</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">150</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">151</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">152</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">153</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">154</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb21-12">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb21-13"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div>
<p>Both tools catch the same five rows. GX surfaces more detail per finding (sample list, index list, exact percent). Locke surfaces typed evidence kinds (<code>count</code>, <code>sample</code>, <code>range</code>), declared assumptions, suggested next checks — and the stable ID. They’re answering different questions about the same fact.</p>
</section>
<section id="detection-results" class="level3">
<h3 class="anchored" data-anchor-id="detection-results">Detection results</h3>
<table class="table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th>Fact</th>
<th>Ground truth</th>
<th>Locke (code, count, severity)</th>
<th>GX (count, severity)</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>NaN in <code>total_charges</code></td>
<td>100</td>
<td>E9007 (100, info)</td>
<td>100 (error)</td>
</tr>
<tr class="even">
<td>Impossible <code>monthly_charges</code></td>
<td>5</td>
<td>E9041 (5, warning)</td>
<td>5 (error)</td>
</tr>
<tr class="odd">
<td>Outlier <code>tenure_months</code></td>
<td>17</td>
<td>E9041 (17, warning)</td>
<td>17 (warning)</td>
</tr>
<tr class="even">
<td>Full-row duplicates</td>
<td>23</td>
<td>E9003 (23, notice)</td>
<td>46 = 23×2 (error)</td>
</tr>
<tr class="odd">
<td>Sortedness violation</td>
<td>≥ 1</td>
<td>E9050 (1, warning)</td>
<td>1 (warning)</td>
</tr>
<tr class="even">
<td>Duplicate <code>customer_id</code> keys</td>
<td>23</td>
<td>E9004 (23, error)</td>
<td>(no equivalent in suite)</td>
</tr>
</tbody>
</table>
<p><strong>Both tools matched ground truth 5 / 5 on the quantitative facts.</strong> The detection accuracy is identical for the kinds of issues a real churn-data pipeline ships with.</p>
<p>A few honest notes on the table:</p>
<ul>
<li><strong>GX reports duplicate rows as 46 = 23 × 2</strong> because <code>ExpectCompoundColumnsToBeUnique</code> reports all rows in any non-unique group. Locke reports the <em>extras</em> (23). Different question, both correct.</li>
<li><strong>Severity disagreements are policy, not detection.</strong> GX defaults every failed expectation to <code>Error</code>. Locke uses a configurable severity ladder where 100 missing out of 7023 (1.4%) lands at <code>Info</code> — the floor of a four-rung ladder where Warning means ≥10% and Error means ≥50%. Locke’s ladder is tunable via <code>BeliefPenalty</code>; GX’s is per-expectation config.</li>
<li><strong>Locke caught the missing-<code>total_charges</code> count via E9007 (sentinel-value detection)</strong> because the CSV reader inferred the column as <code>Str</code> (empty cells aren’t parseable as numbers). Same count, different code, lighter severity. The findings are equivalent; the tool routed through a different channel.</li>
</ul>
</section>
<section id="timing" class="level3">
<h3 class="anchored" data-anchor-id="timing">Timing</h3>
<table class="table">
<thead>
<tr class="header">
<th>Tool</th>
<th>End-to-end wall-clock</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Great Expectations 1.17 (Python 3.11)</td>
<td><strong>45.9 s</strong></td>
</tr>
<tr class="even">
<td>Locke v0.5 debug build</td>
<td>985 ms</td>
</tr>
<tr class="odd">
<td><strong>Locke v0.5 release build</strong></td>
<td><strong>167 ms</strong></td>
</tr>
</tbody>
</table>
<p>That’s a <strong>275× speedup</strong> in release mode against the same dataset finding the same things. Most of GX’s time is Python startup + GX context init + ephemeral data-source setup, which dominates for short batch runs. Locke is a single Rust binary with no startup overhead.</p>
<p>For a CI gate that runs every PR, that difference is “imperceptible vs visibly slow.” For a nightly batch over a million datasets, it’s the difference between “finished by morning” and “still running.”</p>
</section>
<section id="built-in-checks-locke-runs-that-gx-doesnt-out-of-the-box" class="level3">
<h3 class="anchored" data-anchor-id="built-in-checks-locke-runs-that-gx-doesnt-out-of-the-box">Built-in checks Locke runs that GX doesn’t out of the box</h3>
<p>On the actual benchmark dataset:</p>
<ul>
<li><strong>E9004 — duplicate primary-key detection</strong> fired with the exact 23 violations on <code>customer_id</code>. GX has no equivalent unless you write a custom expectation.</li>
<li><strong>E9007 — sentinel-value detection</strong> caught the 100 empty strings in <code>total_charges</code>. GX requires <code>expect_column_values_to_not_be_null</code> (which works for null but not the empty-string sentinel form here).</li>
<li><strong>E9072 — ID-like cardinality hint</strong> fired three times — on <code>customer_id</code>, <code>last_login_unix</code>, and <code>total_charges</code> (which had 6858 distinct strings out of 7023 rows, looking like an identifier leaking as a feature). GX has no equivalent default check.</li>
</ul>
<p>These aren’t fancy — they’re the kind of checks a churn pipeline silently fails on for months until someone notices the model is memorising customer IDs. Locke fires them by default.</p>
</section>
<section id="built-in-checks-gx-runs-that-locke-doesnt-out-of-the-box" class="level3">
<h3 class="anchored" data-anchor-id="built-in-checks-gx-runs-that-locke-doesnt-out-of-the-box">Built-in checks GX runs that Locke doesn’t out of the box</h3>
<p>To be honest about the other direction:</p>
<ul>
<li><code>ExpectColumnDistinctValuesToBeInSet</code> for <code>plan</code> / <code>country</code> — Locke has E9020–E9022 schema mismatch but no value-set-membership check yet</li>
<li><code>ExpectColumnValuesToBeIncreasing(strictly=True)</code> — Locke only checks non-decreasing</li>
<li>User-declared mean / quantile range expectations (Locke surfaces the values but doesn’t accept caller-declared ranges in the default validate path)</li>
<li>~180 other expectations covering regex, length, JSON-schema, custom SQL queries, etc.</li>
</ul>
<p>GX has a <em>much</em> broader expectation library overall. For the specific subset that matters for churn validation, Locke is at parity or ahead. For the general case, GX still wins on breadth.</p>
</section>
</section>
<section id="what-scale-locke-handles" class="level2">
<h2 class="anchored" data-anchor-id="what-scale-locke-handles">What scale Locke handles</h2>
<p>The benchmark above is on a small (7K-row) dataset. For a more useful answer, I ran a separate scale benchmark on synthetic data with four mixed-type columns:</p>
<section id="single-shot-validate" class="level3">
<h3 class="anchored" data-anchor-id="single-shot-validate">Single-shot <code>validate()</code></h3>
<table class="table">
<thead>
<tr class="header">
<th>Rows</th>
<th>Wall clock</th>
<th>Per-row</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>10,000</td>
<td>100 ms</td>
<td>10.08 µs/row</td>
</tr>
<tr class="even">
<td>100,000</td>
<td>1.19 s</td>
<td>11.90 µs/row</td>
</tr>
<tr class="odd">
<td>1,000,000</td>
<td>17.45 s</td>
<td>17.45 µs/row</td>
</tr>
</tbody>
</table>
<p>Per-row cost grows mildly with <code>log n</code> because of full-row byte-hashing (duplicate detection) and sort-based outlier detection.</p>
</section>
<section id="drift-compare" class="level3">
<h3 class="anchored" data-anchor-id="drift-compare">Drift <code>compare()</code></h3>
<table class="table">
<thead>
<tr class="header">
<th>Rows / side</th>
<th>Wall clock</th>
<th>Per-row</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>10,000</td>
<td>90 ms</td>
<td>4.51 µs/row</td>
</tr>
<tr class="even">
<td>100,000</td>
<td>525 ms</td>
<td>2.63 µs/row</td>
</tr>
<tr class="odd">
<td>1,000,000</td>
<td>5.68 s</td>
<td>2.84 µs/row</td>
</tr>
</tbody>
</table>
<p>Flat per-row cost at scale because the sort dominates and Rust’s sort is well-optimised. 1M + 1M rows in 5.7 seconds.</p>
</section>
<section id="streaming-validator-chunked-ingest" class="level3">
<h3 class="anchored" data-anchor-id="streaming-validator-chunked-ingest">Streaming validator (chunked ingest)</h3>
<table class="table">
<thead>
<tr class="header">
<th>Total rows</th>
<th>Chunks</th>
<th>Wall clock</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>100,000</td>
<td>10</td>
<td>609 ms</td>
</tr>
<tr class="even">
<td>1,000,000</td>
<td>100</td>
<td>8.69 s</td>
</tr>
<tr class="odd">
<td><strong>5,000,000</strong></td>
<td><strong>500</strong></td>
<td><strong>16.33 s</strong></td>
</tr>
</tbody>
</table>
<p>About <strong>310,000 rows/second</strong> sustained streaming throughput.</p>
</section>
<section id="streaming-ks-d-vs-single-shot-ks-d" class="level3">
<h3 class="anchored" data-anchor-id="streaming-ks-d-vs-single-shot-ks-d">Streaming KS D vs single-shot KS D</h3>
<table class="table">
<thead>
<tr class="header">
<th>Rows</th>
<th>Streaming KS</th>
<th>Single-shot KS</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>10,000</td>
<td><strong>1 ms</strong></td>
<td>4 ms</td>
</tr>
<tr class="even">
<td>100,000</td>
<td><strong>32 ms</strong></td>
<td>66 ms</td>
</tr>
<tr class="odd">
<td>1,000,000</td>
<td><strong>62 ms</strong></td>
<td>107 ms</td>
</tr>
</tbody>
</table>
<p>Streaming is <strong>~2× faster</strong> because the BTreeMap-ECDF is already sorted at ingest time, so the merge-walk at query time skips the sort. The result is bit-identical to single-shot KS — proved by a unit test.</p>
</section>
<section id="practical-scale-matrix-for-a-churn-use-case" class="level3">
<h3 class="anchored" data-anchor-id="practical-scale-matrix-for-a-churn-use-case">Practical scale matrix for a churn use case</h3>
<table class="table">
<thead>
<tr class="header">
<th>Customers</th>
<th>Path</th>
<th>Wall clock</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>≤ 100k</td>
<td>single-shot</td>
<td>sub-2s</td>
</tr>
<tr class="even">
<td>100k – 1M</td>
<td>single-shot</td>
<td>2-20s</td>
</tr>
<tr class="odd">
<td>1M – 10M</td>
<td>streaming</td>
<td>~30s</td>
</tr>
<tr class="even">
<td>10M – 100M</td>
<td>streaming with caps</td>
<td>~5 min</td>
</tr>
<tr class="odd">
<td>100M+</td>
<td>streaming + accept approximate dup counts, or shard</td>
<td>longer</td>
</tr>
</tbody>
</table>
<p>For a typical SaaS churn dataset (1k–10M customers), Locke runs comfortably as part of every CI build. For 10M–100M, it’s an overnight batch.</p>
</section>
</section>
<section id="the-ci-workflow-locke-was-built-for" class="level2">
<h2 class="anchored" data-anchor-id="the-ci-workflow-locke-was-built-for">The CI workflow Locke was built for</h2>
<p>Scenario 1 above sketched the workflow. Here it is wired into a CI config, showing the exact JSON snapshot file the workflow checks in.</p>
<div class="sourceCode" id="cb22" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb22-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># After every successful model training, snapshot the baseline:</span></span>
<span id="cb22-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> locke validate train_2026q2.csv <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb22-3">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--target</span> churned <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb22-4">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--primary-key</span> customer_id <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb22-5">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--time-col</span> event_ts <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb22-6">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--max-timestamp</span> 1719792000000 <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb22-7">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--save-json</span> baselines/2026q2_baseline.json <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb22-8">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--html</span> reports/2026q2.html</span></code></pre></div>
<p>The <code>baselines/2026q2_baseline.json</code> that lands in the repo looks like this (truncated to one finding for brevity):</p>
<div class="sourceCode" id="cb23" style="background: #f1f3f5;"><pre class="sourceCode json code-with-copy"><code class="sourceCode json"><span id="cb23-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb23-2">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"schema_version"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb23-3">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"run_id"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"1075bbad9d9b882f"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb23-4">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"input"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb23-5">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"dataset_label"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"train_2026q2.csv"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb23-6">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"n_rows"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">124083</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb23-7">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"n_cols"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">14</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb23-8">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"column_types"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb23-9">      <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"churned"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Bool"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb23-10">      <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"customer_id"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Int"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb23-11">      <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"event_ts"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Int"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb23-12">      <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"monthly_charges"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Float"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb23-13">      <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"tenure_months"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Int"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb23-14">      <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"..."</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"..."</span></span>
<span id="cb23-15">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb23-16">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">},</span></span>
<span id="cb23-17">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"severity_counts"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"info"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"notice"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"warning"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"error"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">},</span></span>
<span id="cb23-18">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"findings"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">[</span></span>
<span id="cb23-19">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb23-20">      <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"id"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"5a0309e1047ccdf8"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb23-21">      <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"code"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"E9004"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb23-22">      <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"severity"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"notice"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb23-23">      <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"message"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"key column `customer_id` has 0 duplicate values across 0 groups"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb23-24">      <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"column"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"customer_id"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb23-25">      <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"evidence"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">[</span></span>
<span id="cb23-26">        <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"kind"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"count"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"label"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"duplicate_keys"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span>   <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"value"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb23-27">        <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"kind"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"count"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"label"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"duplicate_groups"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"value"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb23-28">      <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">]</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb23-29">      <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"assumptions"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">[</span></span>
<span id="cb23-30">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"key uniqueness assumed; primary-key constraint not enforced by cjc-data"</span></span>
<span id="cb23-31">      <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">]</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb23-32">      <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"suggested_next_checks"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">[</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"check ingest pipeline; primary keys should be unique"</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb23-33">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb23-34">  <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">]</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb23-35">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"assumptions"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">[</span></span>
<span id="cb23-36">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"NaN treated as missing for Float columns; other types report a limitation"</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb23-37">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"missingness, drift, and belief use deterministic Kahan summation"</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb23-38">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"duplicate detection is byte-canonical"</span></span>
<span id="cb23-39">  <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb23-40"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div>
<p>The <code>run_id</code> at the top is a 64-bit fingerprint over the full report’s content. Two runs on the same data produce the same <code>run_id</code>. The team checks <code>baselines/2026q2_baseline.json</code> into git, and the diff on the next PR is meaningful: any non-zero diff means data quality changed.</p>
<p>In CI:</p>
<div class="sourceCode" id="cb24" style="background: #f1f3f5;"><pre class="sourceCode yaml code-with-copy"><code class="sourceCode yaml"><span id="cb24-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># .github/workflows/data-quality-gate.yml (excerpt)</span></span>
<span id="cb24-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">name</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> Validate production data against Q2 baseline</span></span>
<span id="cb24-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">  run</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">: </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">|</span></span>
<span id="cb24-4">    cjcl locke gate \</span>
<span id="cb24-5">      baselines/2026q2_baseline.json \</span>
<span id="cb24-6">      data/prod_$(date +%Y%m%d).csv \</span>
<span id="cb24-7">      --fail-on warning</span></code></pre></div>
<p>Exit code is non-zero when anything crosses the warning threshold. The structural diff goes to stdout. A human looks at it and decides: was this drift expected (new product launch, seasonal effect) or did a pipeline bug leak training-window timestamps into the test set?</p>
<p>The diff’s findings are <strong>content-addressed</strong> — <code>id=1075bbad9d9b882f</code> is a 64-bit fingerprint over the report’s contents. Two CI runs over the same data on different machines produce the same fingerprint. That’s the property that makes the snapshot-and-diff workflow work at all.</p>
</section>
<section id="the-stakeholder-html-report" class="level2">
<h2 class="anchored" data-anchor-id="the-stakeholder-html-report">The stakeholder HTML report</h2>
<p>The <code>--html PATH</code> flag produces a single self-contained HTML file with:</p>
<ul>
<li>Severity-color-coded summary cards</li>
<li>Findings table sorted by (severity, code, column)</li>
<li>Inline-SVG correlation heatmap (when ≥ 3 numeric columns)</li>
<li>Assumptions panel</li>
<li>Run-ID + schema-version footer</li>
<li>Total weight ~5KB, no external assets, no JavaScript</li>
</ul>
<p>For a PM who wants “tell me what’s wrong with this CSV in 30 seconds,” the HTML report is the right shape.</p>
</section>
<section id="locke-is-not-a-one-stop-ml-tool" class="level2">
<h2 class="anchored" data-anchor-id="locke-is-not-a-one-stop-ml-tool">Locke is not a one-stop ML tool</h2>
<p>Worth saying directly: Locke covers the data-quality and drift-monitoring legs of an ML pipeline. It does <strong>not</strong>:</p>
<ul>
<li>Train models. Use scikit-learn / xgboost / lifelines for that.</li>
<li>Run causal inference. The <code>CausalDag</code> registers user-declared assumptions; it doesn’t estimate effects. Use DoWhy or EconML for actual causal modeling.</li>
<li>Replace experiment-tracking. Use mlflow / wandb.</li>
<li>Read Parquet contents yet. v0.1.10 recognizes the file structure and gives a clear diagnostic; the full decoder is on the roadmap.</li>
<li>Connect to data warehouses. Snowflake / BigQuery / Postgres support is not yet there. Export to CSV / JSONL first.</li>
</ul>
<p>The right framing is <strong>“Locke handles the data hygiene + drift monitoring + reproducible audit trail; you bring the rest of your stack.”</strong></p>
</section>
<section id="what-i-think-the-determinism-contract-actually-buys-you" class="level2">
<h2 class="anchored" data-anchor-id="what-i-think-the-determinism-contract-actually-buys-you">What I think the determinism contract actually buys you</h2>
<p>Three concrete things:</p>
<ol type="1">
<li><p><strong>Comparable reports across runs.</strong> Two CI runs over the same dataset produce byte-identical reports. You can <code>diff</code> them. You can store them in git. You can compute a fingerprint and cache training jobs on it.</p></li>
<li><p><strong>Reproducible audit trails.</strong> Insurance churn analyses get scrutinised. A regulator asking “show me the data quality report you used to validate this dataset on 2026-Q3” deserves a reproducible answer. Locke gives that.</p></li>
<li><p><strong>CI gates that don’t flake.</strong> Most data-validation tools sometimes report different “validation_result UUIDs” on identical data because of internal <code>dict</code> iteration or wall-clock metadata. Locke’s IDs are content fingerprints. There’s no flake.</p></li>
</ol>
<p>That third property is the one I’d defend hardest. It sounds boring; it’s what makes the snapshot-and-diff workflow viable in the first place.</p>
</section>
<section id="the-formal-model-how-confidence-composes" class="level2">
<h2 class="anchored" data-anchor-id="the-formal-model-how-confidence-composes">The formal model: how confidence composes</h2>
<p>Locke v0.1’s evidence-and-belief story isn’t pure heuristic — there’s a small, deliberately conservative semantics underneath. This section spells out the model: how confidence is represented, how it composes under transformation, what weakens it, how it propagates, and the algebra it satisfies today versus the parts deferred to v0.2. Being explicit here matters because “epistemic layer” is only as good as the formal object underneath it.</p>
<section id="confidence-representation" class="level3">
<h3 class="anchored" data-anchor-id="confidence-representation">Confidence representation</h3>
<p>Every Locke report carries an 8-dimensional <code>BeliefScore</code>:</p>
<pre><code>BeliefScore : {schema, missingness, drift, leakage, lineage, sample, duplication, constraint} → [0, 1]
overall_score : BeliefScore → [0, 1]</code></pre>
<p>Each axis lives on <code>[0, 1]</code> where 1 means “no evidence against the dataset on this axis.” There is no single number; the overall is a weighted mean controlled by a <code>BeliefWeights</code> vector. The choice of 8 axes is deliberate — each maps to a disjoint class of finding codes:</p>
<table class="table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Axis</th>
<th>Finding-code range</th>
<th>What evidence weakens it</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>schema</td>
<td>E9020–E9029</td>
<td>type mismatches, unexpected columns, missing columns</td>
</tr>
<tr class="even">
<td>missingness</td>
<td>E9001–E9009</td>
<td>NaN rates, joint missingness, sentinel detection</td>
</tr>
<tr class="odd">
<td>drift</td>
<td>E9030–E9039</td>
<td>KS shift, mean/std shift, categorical TVD</td>
</tr>
<tr class="even">
<td>leakage</td>
<td>E9060–E9069</td>
<td>target-AUC, ID-like cardinality, proxy candidates</td>
</tr>
<tr class="odd">
<td>lineage</td>
<td>E9050–E9059</td>
<td>unsorted time, train/test overlap, gap detection</td>
</tr>
<tr class="even">
<td>sample</td>
<td>(computed)</td>
<td>row count below n_min, sampling-cap engaged</td>
</tr>
<tr class="odd">
<td>duplication</td>
<td>E9003–E9004</td>
<td>full-row dups, primary-key dups, dup-conditioning</td>
</tr>
<tr class="even">
<td>constraint</td>
<td>E9040–E9049</td>
<td>outliers, impossible values, constants</td>
</tr>
</tbody>
</table>
<p>Adding a new check assigns it to exactly one axis. That’s a hard invariant in the codebase, not a guideline.</p>
</section>
<section id="confidence-composition-under-transformation" class="level3">
<h3 class="anchored" data-anchor-id="confidence-composition-under-transformation">Confidence composition under transformation</h3>
<p>A <code>LockeIdea</code> derived from one or more parent ideas / impressions inherits a belief score:</p>
<pre><code>belief(child) = meet(belief(parent_i)) ∧ transform_factor(op)   for each parent i
where meet(a, b) = component-wise min on the 8 axes</code></pre>
<p>The <strong>meet</strong> (component-wise min) is the deliberately conservative composition rule. A filter cannot improve the missingness score — at best it preserves it. A join cannot improve any axis below the minimum of the two inputs. Belief is monotonically non-increasing under derivation.</p>
<p><code>transform_factor(op)</code> is a per-operator scalar in <code>[0, 1]^8</code> that accounts for op-specific weakening:</p>
<table class="table">
<colgroup>
<col style="width: 50%">
<col style="width: 50%">
</colgroup>
<thead>
<tr class="header">
<th>Operation</th>
<th>transform_factor effect</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>filter(predicate)</code></td>
<td>preserves all 8 axes; transform_factor = identity</td>
</tr>
<tr class="even">
<td><code>group_by + summarise</code></td>
<td>weakens <code>sample</code> proportional to group-size reduction</td>
</tr>
<tr class="odd">
<td><code>join(left, right, key)</code></td>
<td>weakens <code>lineage</code> and <code>constraint</code> proportional to FK match rate</td>
</tr>
<tr class="even">
<td><code>with_column(derived = f(cols))</code></td>
<td>weakens <code>leakage</code> if f spans target-correlated columns</td>
</tr>
<tr class="odd">
<td><code>sample(n)</code></td>
<td>weakens <code>sample</code> proportional to <code>n / total_rows</code></td>
</tr>
<tr class="even">
<td><code>bind_rows</code> / <code>bind_cols</code></td>
<td>weakens <code>lineage</code> because the merge boundary is a new source of disagreement</td>
</tr>
</tbody>
</table>
<p>These are shipped today, conservative, documented in the source. The full algebra (per-axis composition rules per operator instead of a global meet) is a v0.2 goal.</p>
</section>
<section id="what-weakens-confidence" class="level3">
<h3 class="anchored" data-anchor-id="what-weakens-confidence">What weakens confidence</h3>
<p>The penalty function is the only place evidence turns into a score reduction:</p>
<pre><code>penalty(finding) = w_axis(finding.code) × severity_weight(finding.severity) × evidence_intensity(finding.evidence)
axis_score(axis) = 1 − Σ penalty(f) for f in findings if axis_of(f.code) == axis    clamped to [0, 1]</code></pre>
<p>Three properties:</p>
<ol type="1">
<li><strong><code>severity_weight</code> is a fixed ladder</strong>: <code>{Info: 0.05, Notice: 0.15, Warning: 0.40, Error: 1.00}</code> — same on every run. No floating arithmetic that depends on iteration order.</li>
<li><strong><code>evidence_intensity</code> is bounded by 1.</strong> A single finding cannot drop an axis below 0; multiple findings sum but are clamped at the end.</li>
<li><strong>The penalty model is swappable.</strong> <code>BeliefPenalty::user_supplied(...)</code> lets a team install a custom ladder; the audit then records the penalty model’s identity. The default ladder is just one entry in a registry.</li>
</ol>
</section>
<section id="how-propagation-is-modeled" class="level3">
<h3 class="anchored" data-anchor-id="how-propagation-is-modeled">How propagation is modeled</h3>
<p>The lineage DAG carries belief from impressions to ideas via the <code>meet</code>-and-<code>transform_factor</code> rule. Here’s an evidence-propagation diagram for a 4-step pipeline:</p>
<div class="cell" data-fig-width="9">
<div class="cell-output-display">
<div>
<div>
<pre class="mermaid mermaid-js">flowchart LR
    I["events.csv&lt;br/&gt;&lt;b&gt;impression&lt;/b&gt;&lt;br/&gt;[1.00, 1.00, 1.00, 1.00,&lt;br/&gt;1.00, 1.00, 1.00, 1.00]"]:::imp
    I --&gt;|"observe()&lt;br/&gt;tf = identity"| O["raw_events&lt;br/&gt;&lt;b&gt;idea&lt;/b&gt;&lt;br/&gt;[1.00, 1.00, 1.00, 1.00,&lt;br/&gt;0.95, 1.00, 1.00, 1.00]"]:::idea
    O --&gt;|"filter(active==1)&lt;br/&gt;tf = identity"| F["active_events&lt;br/&gt;&lt;b&gt;idea&lt;/b&gt;&lt;br/&gt;[1.00, 0.99, 1.00, 1.00,&lt;br/&gt;0.95, 0.98, 1.00, 1.00]"]:::idea
    F --&gt;|"group_by(plan_tier)&lt;br/&gt;tf: sample × 0.80"| G["grouped&lt;br/&gt;&lt;b&gt;idea&lt;/b&gt;&lt;br/&gt;[1.00, 0.99, 1.00, 1.00,&lt;br/&gt;0.95, 0.78, 1.00, 1.00]"]:::idea
    G --&gt;|"with_column(churn_rate)&lt;br/&gt;tf: leakage × 0.90"| W["labeled&lt;br/&gt;&lt;b&gt;idea&lt;/b&gt;&lt;br/&gt;[1.00, 0.99, 1.00, 0.90,&lt;br/&gt;0.95, 0.78, 1.00, 1.00]"]:::idea

    classDef imp fill:#e8f4fd,stroke:#2196F3,stroke-width:2px
    classDef idea fill:#fff3e0,stroke:#FF9800,stroke-width:1px
</pre>
</div>
<p>Evidence propagation through a 4-step pipeline. Each node carries an 8-dim belief vector [schema, missingness, drift, leakage, lineage, sample, duplication, constraint]. Edges apply transform_factor. Belief is monotonically non-increasing along any path — the rightmost node is component-wise ≤ the leftmost.</p>
</div>
</div>
</div>
<p>Reading the diagram: each node label lists the 8-axis belief vector in fixed order. The <code>observe()</code> step pre-weakened <code>lineage</code> to 0.95 because raw-CSV ingestion drops some provenance (no upstream observation timestamps). The <code>filter</code> step weakened <code>missingness</code> and <code>sample</code> because filtering revealed a column with a higher local NaN rate, and dropped row count. The <code>group_by</code> step weakened <code>sample</code> further because per-group sample sizes are smaller. The <code>with_column(churn_rate)</code> step weakened <code>leakage</code> because the derived column directly references the target.</p>
<p>Crucially: every axis-wise value on the rightmost node is ≤ the leftmost. Composition never improves belief on any axis.</p>
</section>
<section id="deterministic-update-rules" class="level3">
<h3 class="anchored" data-anchor-id="deterministic-update-rules">Deterministic update rules</h3>
<p>Every step in the propagation uses:</p>
<ul>
<li><strong>Kahan summation</strong> for penalty accumulation (avoid FP-order-dependent drift).</li>
<li><strong><code>BTreeMap</code></strong> for finding aggregation by axis (no <code>HashMap</code> iteration nondeterminism).</li>
<li><strong><code>f64::total_cmp</code></strong> for floating comparisons (NaN behaves consistently, not implementation-defined).</li>
<li><strong>No FMA</strong> in the SIMD paths under penalty arithmetic (bit-identical across CPUs).</li>
<li><strong>Single-threaded execution</strong> in v0.1 — even when parallel column-validation lands in v0.2, the reduction phase will be order-fixed.</li>
</ul>
<p>The contract: same input bytes → same belief vector. The lineage diagram above is reproducible byte-for-byte by anyone running the same pipeline on the same inputs.</p>
</section>
<section id="confidence-algebra-shipped-vs-planned" class="level3">
<h3 class="anchored" data-anchor-id="confidence-algebra-shipped-vs-planned">Confidence algebra (shipped vs planned)</h3>
<p>The composition operator <code>meet</code> plus the identity <code>⊤ = [1, 1, 1, 1, 1, 1, 1, 1]</code> satisfies five algebraic properties today:</p>
<table class="table">
<thead>
<tr class="header">
<th>Property</th>
<th>Statement</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Identity</strong></td>
<td><code>meet(b, ⊤) = b</code></td>
</tr>
<tr class="even">
<td><strong>Idempotence</strong></td>
<td><code>meet(b, b) = b</code></td>
</tr>
<tr class="odd">
<td><strong>Monotonicity</strong></td>
<td><code>meet(b, c) ≤ b</code> component-wise</td>
</tr>
<tr class="even">
<td><strong>Commutativity</strong></td>
<td><code>meet(b, c) = meet(c, b)</code></td>
</tr>
<tr class="odd">
<td><strong>Associativity</strong></td>
<td><code>meet(meet(a, b), c) = meet(a, meet(b, c))</code></td>
</tr>
</tbody>
</table>
<p>Together, <code>(BeliefScore, meet, ⊤)</code> is a commutative idempotent monoid with identity — i.e.&nbsp;a <strong>meet-semilattice</strong> in lattice-theoretic terms. The deterministic update rules above make the algebra computable to bit-identical results across runs. That’s the formal object the snapshot-and-diff workflow relies on; without monotonicity, “appeared / disappeared” diffs over composed pipelines wouldn’t be well-defined.</p>
<p>Planned for v0.2:</p>
<ul>
<li><strong>Per-axis composition rules instead of a global <code>meet</code>.</strong> Some axes (e.g.&nbsp;<code>lineage</code>) compose by min; others (e.g.&nbsp;<code>sample</code>) should compose by product. A typed rule per axis would be more faithful to what each axis measures.</li>
<li><strong>A <code>join</code> operator</strong> for combining alternative views of the same dataset (the lattice-theoretic dual of <code>meet</code>). Currently absent; the algebra is meet-only, which means alternative-view combination is undefined.</li>
<li><strong>A continuous-domain semantics</strong> for transformations that span a parameter (e.g.&nbsp;<code>filter(predicate)</code> with predicate selectivity <code>s ∈ [0, 1]</code>). v0.1 treats every filter the same; v0.2 would parameterise <code>transform_factor</code> by <code>s</code>.</li>
<li><strong>A formal proof</strong> of the conservation property: under the meet algebra, the gate-diff workflow’s <code>appeared</code> set is exactly the set of findings whose axes degraded between snapshots. This is true by construction today; v0.2 would have it written down as a theorem with a checked proof.</li>
</ul>
<p>The honest framing: Locke v0.1 ships a small, deliberately conservative algebra. It is enough to make the workflows in this post (“snapshot, diff, gate, audit”) provably reproducible and well-defined. The richer semantics will land when the workflows the algebra is supposed to serve are more developed.</p>
</section>
<section id="belief-degradation-visualised" class="level3">
<h3 class="anchored" data-anchor-id="belief-degradation-visualised">Belief degradation, visualised</h3>
<p>The same 4-step pipeline rendered as a degradation chart — one panel per axis, showing the trajectory of each component as we move from impression to most-derived idea:</p>
<div class="cell" data-fig-width="9">
<div class="cell-output-display">
<div>
<div>
<pre class="mermaid mermaid-js">flowchart LR
    subgraph S1["events.csv"]
        s1_schema["schema = 1.00"]:::held
        s1_miss["missingness = 1.00"]:::held
        s1_drift["drift = 1.00"]:::held
        s1_leak["leakage = 1.00"]:::held
        s1_lin["lineage = 1.00"]:::held
        s1_sam["sample = 1.00"]:::held
        s1_dup["duplication = 1.00"]:::held
        s1_con["constraint = 1.00"]:::held
    end

    subgraph S2["raw_events&lt;br/&gt;(observe)"]
        s2_schema["1.00"]:::held
        s2_miss["1.00"]:::held
        s2_drift["1.00"]:::held
        s2_leak["1.00"]:::held
        s2_lin["0.95"]:::weakened
        s2_sam["1.00"]:::held
        s2_dup["1.00"]:::held
        s2_con["1.00"]:::held
    end

    subgraph S3["active_events&lt;br/&gt;(filter)"]
        s3_schema["1.00"]:::held
        s3_miss["0.99"]:::weakened
        s3_drift["1.00"]:::held
        s3_leak["1.00"]:::held
        s3_lin["0.95"]:::held
        s3_sam["0.98"]:::weakened
        s3_dup["1.00"]:::held
        s3_con["1.00"]:::held
    end

    subgraph S4["grouped&lt;br/&gt;(group_by)"]
        s4_schema["1.00"]:::held
        s4_miss["0.99"]:::held
        s4_drift["1.00"]:::held
        s4_leak["1.00"]:::held
        s4_lin["0.95"]:::held
        s4_sam["0.78"]:::weakened
        s4_dup["1.00"]:::held
        s4_con["1.00"]:::held
    end

    subgraph S5["labeled&lt;br/&gt;(with_column)"]
        s5_schema["1.00"]:::held
        s5_miss["0.99"]:::held
        s5_drift["1.00"]:::held
        s5_leak["0.90"]:::weakened
        s5_lin["0.95"]:::held
        s5_sam["0.78"]:::held
        s5_dup["1.00"]:::held
        s5_con["1.00"]:::held
    end

    s1_schema --- s2_schema --- s3_schema --- s4_schema --- s5_schema
    s1_miss   --- s2_miss
    s2_miss   -.-&gt; s3_miss
    s3_miss   --- s4_miss --- s5_miss
    s1_drift  --- s2_drift  --- s3_drift  --- s4_drift  --- s5_drift
    s1_leak   --- s2_leak   --- s3_leak   --- s4_leak
    s4_leak   -.-&gt; s5_leak
    s1_lin    -.-&gt; s2_lin
    s2_lin    --- s3_lin    --- s4_lin    --- s5_lin
    s1_sam    --- s2_sam
    s2_sam    -.-&gt; s3_sam
    s3_sam    -.-&gt; s4_sam
    s4_sam    --- s5_sam
    s1_dup    --- s2_dup    --- s3_dup    --- s4_dup    --- s5_dup
    s1_con    --- s2_con    --- s3_con    --- s4_con    --- s5_con

    classDef held fill:#e8f5e9,stroke:#2E7D32
    classDef weakened fill:#ffebee,stroke:#C62828
</pre>
</div>
<p>Belief degradation across the 4-step pipeline above. Each axis’s value at each stage is shown as a labelled node; the layout reads left-to-right (pipeline stages) and top-to-bottom (axes). Solid edges indicate the value held; dashed edges indicate a transform_factor weakening. Schema, drift, duplication, and constraint hold at 1.00 throughout. Missingness ticked down once. Lineage dropped at ingest and held. Sample dropped twice (filter, group_by). Leakage dropped once (with_column).</p>
</div>
</div>
</div>
<p>The chart makes one specific claim visually: <strong>the dashed edges are the only places confidence got weaker.</strong> Every other axis held flat. A reviewer can pin a downstream finding to the specific transformation that degraded the relevant axis, because the dashed edge is exactly where the audit trail records a <code>transform_factor</code> application.</p>
<p>This is the property that makes the audit trail useful, not just reproducible. Reproducibility says “two runs of the same pipeline match.” Localisability says “when something downstream is wrong, I can point to which step weakened the belief on the affected axis.” Locke gives both.</p>
</section>
<section id="where-the-formal-model-lives-in-code" class="level3">
<h3 class="anchored" data-anchor-id="where-the-formal-model-lives-in-code">Where the formal model lives in code</h3>
<p>If you want to read the actual implementation rather than the prose:</p>
<ul>
<li><code>crates/cjc-locke/src/belief.rs</code> — <code>BeliefScore</code>, <code>BeliefWeights</code>, <code>BeliefPenalty</code> definitions and the <code>meet</code> operator.</li>
<li><code>crates/cjc-locke/src/lineage.rs</code> — <code>LineageGraph</code>, <code>LockeImpression</code>, <code>LockeIdea</code>, edge representations, content-addressed IDs.</li>
<li><code>crates/cjc-locke/src/traced.rs</code> — <code>TracedDataFrame</code> wrapper that applies <code>transform_factor</code> per operation.</li>
<li><code>crates/cjc-locke/src/dispatch.rs</code> — the 22 <code>locke_*</code> builtins that expose the model to <code>.cjcl</code> source.</li>
</ul>
<p>Everything is <code>pub</code> and documented. The composition rules are unit-tested for monotonicity, idempotence, and associativity. The deterministic-update rules are property-tested by <code>proptest</code>. The “shipped vs planned” gaps above each have an issue or an <code>// TODO(v0.2):</code> marker in the source.</p>
</section>
</section>
<section id="install" class="level2">
<h2 class="anchored" data-anchor-id="install">Install</h2>
<div class="sourceCode" id="cb28" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb28-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cargo</span> add cjc-locke</span></code></pre></div>
<p>The crate is at version 0.1.10 on crates.io and works with the matching version of <code>cjc-data</code>, <code>cjc-repro</code>, <code>cjc-runtime</code> from the same workspace release. If you want the CLI:</p>
<div class="sourceCode" id="cb29" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb29-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cargo</span> install cjc-lang</span>
<span id="cb29-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> locke validate your-data.csv</span></code></pre></div>
<p>The benchmark code is in the repo at <code>bench/locke_vs_gx/</code>. Anyone with <code>pip install great_expectations pandas</code> can re-run the full comparison.</p>
</section>
<section id="where-v0.2-is-going" class="level2">
<h2 class="anchored" data-anchor-id="where-v0.2-is-going">Where v0.2 is going</h2>
<p>The next release will close the gaps the benchmark exposed:</p>
<ul>
<li>Native Parquet decoder (Thrift + Snappy + PLAIN encoding minimum)</li>
<li>Value-set-membership checks at the validate path (matching GX’s <code>ExpectColumnDistinctValuesToBeInSet</code>)</li>
<li>Multi-class target-leakage AUC (v0.1 supports binary targets only)</li>
<li>HTML output with per-column distribution sparklines via <code>cjc-vizor</code></li>
<li>Optional parallel column-validation via rayon (per-column work is embarrassingly parallel)</li>
<li>JUnit XML and SARIF output for CI integrations</li>
</ul>
<p>If a Locke check you wanted is missing, the issue tracker is open.</p>
</section>
<section id="final-honest-framing" class="level2">
<h2 class="anchored" data-anchor-id="final-honest-framing">Final honest framing</h2>
<p>I built Locke because CJC-Lang needed a data-skepticism layer that preserved the runtime determinism guarantee at the validation boundary. The benchmark against Great Expectations was a calibration exercise — I wanted to know whether the “Locke is competitive” claim I’d been making was actually true.</p>
<p>The answer, on the specific workflows Locke is designed for, is <strong>yes</strong>. Locke matched GX 5 / 5 on every ground-truth fact in a real benchmark, ran 275× faster end-to-end, and fired more checks out of the box for the specific failure modes that break customer-churn pipelines.</p>
<p>That’s not a knock on Great Expectations, dbt, Pandera, or Monte Carlo. Each of those tools is mature, well-staffed, and the right answer at its layer of the stack. Locke doesn’t compete with any of them — it adds an epistemic layer underneath where the audit artifact is the deliverable. If your data lives in Snowflake and your team needs Slack alerting, <strong>use GX or Monte Carlo</strong>. If your pipeline is pandas-native and you want in-process guards, <strong>use Pandera</strong>. If the validation belongs inside dbt’s DAG, <strong>use dbt tests</strong>.</p>
<p>If your data fits in a CSV / JSONL, you need a deterministic CI gate, and you’re tired of validating customer-churn pipelines with spreadsheets and prayer, <strong>use Locke</strong>.</p>
<p>The code is on <a href="https://crates.io/crates/cjc-locke">crates.io</a>. The benchmark is in the repo. The whole thing is MIT-licensed.</p>
<p><code>cjcl locke validate your-data.csv</code> and tell me what breaks.</p>


</section>

 ]]></description>
  <category>CJC-Lang</category>
  <category>Locke</category>
  <category>data-quality</category>
  <category>validation</category>
  <category>churn</category>
  <category>benchmarks</category>
  <category>epistemic</category>
  <guid>https://adamezzat1.github.io/blog/posts/cjc-locke-v0.1-released/index.html</guid>
  <pubDate>Thu, 28 May 2026 07:00:00 GMT</pubDate>
</item>
<item>
  <title>Gibran: Windowed CTEs and the Row-Filter Fix You Can’t See</title>
  <dc:creator>Adam Ezzat</dc:creator>
  <link>https://adamezzat1.github.io/blog/posts/gibran-windowed-ctes-and-row-filters/index.html</link>
  <description><![CDATA[ 



<div class="reading-time">
🕑 ~10 min read
</div>
<div class="stats-row">
<div class="stat">
<span class="number">6</span><span class="stat-label">chart types</span>
</div>
<div class="stat">
<span class="number">+3</span><span class="stat-label">question shapes</span>
</div>
<div class="stat">
<span class="number">807</span><span class="stat-label">tests passing</span>
</div>
<div class="stat">
<span class="number">1</span><span class="stat-label">row-filter fix</span>
</div>
</div>
<div class="callout callout-style-simple callout-tip no-icon callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-title-container flex-fill">
TL;DR
</div>
</div>
<div class="callout-body-container callout-body">
<p>Gibran’s Workbench got a real <strong>visualization layer</strong> (lazy-loaded: bar / line / area / scatter / horizontal-bar / pie, with multi-series). The NL layer learned an <em>ad-hoc</em> shape it couldn’t express before — <strong>top-N-per-group</strong> (“top 3 products per region by revenue”), which compiles to a windowed CTE. But the headline isn’t the charts. Building them surfaced a latent <strong>row-level-security gap</strong>: the policy filter only worked on flat queries, so <em>every</em> CTE-based metric — cohort retention, funnels, the new per-group ranking — silently broke under a row-filtered role. That’s the fix that mattered.</p>
</div>
</div>
<p>If you missed the launch, <a href="../gibran-v0.1.0/">Gibran v0.1.0</a> is a governed metric layer with a <strong>non-LLM</strong> natural-language surface: questions resolve against your real catalog or get a clean “I don’t know” — the layer is structurally incapable of inventing a column name. This post is what shipped since.</p>
<section id="from-a-table-to-a-picture-lazily" class="level2">
<h2 class="anchored" data-anchor-id="from-a-table-to-a-picture-lazily">1. From a table to a picture (lazily)</h2>
<p>Until now the Workbench could only render a result <em>table</em>. v0.1.0’s own “what’s not here” list said it plainly: <em>“No charts.”</em> Now every result has a <strong>Table / Chart</strong> toggle, with six chart types and <strong>multi-series</strong> support.</p>
<p>Ask:</p>
<pre><code>revenue by month</code></pre>
<p>…hit <strong>Chart</strong>, pick <strong>Line</strong>, and you get a trend. Want to compare two measures on one axis?</p>
<pre><code>revenue and order_count by month</code></pre>
<p>…tick both series. Done.</p>
<p>The chart is governed for free — it just renders the rows the engine already filtered, so there’s no new way to see data you aren’t allowed to. And there’s a deliberate weight decision: the charting library is big, so it’s a slimmed-down partial bundle, <strong>lazy-loaded into its own chunk</strong>. The app’s initial download stays ~53 KB gzipped; the ~375 KB chart bundle only arrives the first time you actually open a chart.</p>
<div class="callout callout-style-simple callout-note no-icon">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-body-container">
<p>The bundle split is the whole trick: the entry chunk is unchanged, and a <code>Chart-*.js</code> chunk (the charting library) is fetched on first chart render. You pay for visualization only when you use it.</p>
</div>
</div>
</div>
</section>
<section id="a-question-youd-otherwise-write-a-window-function-for" class="level2">
<h2 class="anchored" data-anchor-id="a-question-youd-otherwise-write-a-window-function-for">2. A question you’d otherwise write a window function for</h2>
<p>“For each region, the top 3 products by revenue” is a <code>ROW_NUMBER() OVER (PARTITION BY …)</code> query — easy to get subtly wrong by hand. It’s also the first thing Gibran can express as a genuinely <em>ad-hoc</em> query: the N, the group, and the entity all come from your sentence, not a pre-declared metric.</p>
<pre><code>top 3 product per region by revenue</code></pre>
<p>compiles to a windowed CTE:</p>
<div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode sql code-with-copy"><code class="sourceCode sql"><span id="cb4-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">WITH</span> grouped <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">AS</span> (</span>
<span id="cb4-2">  <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">SELECT</span> region, product, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">SUM</span>(amount) <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">AS</span> revenue</span>
<span id="cb4-3">  <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">FROM</span> orders <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">GROUP</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">BY</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb4-4">),</span>
<span id="cb4-5">ranked <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">AS</span> (</span>
<span id="cb4-6">  <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">SELECT</span> region, product, revenue,</span>
<span id="cb4-7">         <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ROW_NUMBER</span>() <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">OVER</span> (<span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">PARTITION</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">BY</span> region <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">ORDER</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">BY</span> revenue <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">DESC</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">AS</span> rn</span>
<span id="cb4-8">  <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">FROM</span> grouped</span>
<span id="cb4-9">)</span>
<span id="cb4-10"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">SELECT</span> region, product, revenue <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">FROM</span> ranked <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">WHERE</span> rn <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">ORDER</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">BY</span> region, rn</span></code></pre></div>
<p>No SQL typed, still governed, still no fabrication — <code>product</code>, <code>region</code>, and <code>revenue</code> each have to resolve to a real schema entry or the question fails. Under the hood it’s a new optional <code>partition_by</code> field on the query intent that reinterprets the existing <code>limit</code> as a <em>per-group</em> cap; the rest of the machinery (dimensions, order-by) is reused.</p>
<p>The shape generalizes: any of <code>top</code> / <code>biggest</code> / <code>largest</code> / <code>highest</code>, any N, any two distinct dimensions, and any rankable metric compile to the same windowed-CTE plan. A few more it handles —</p>
<pre><code>biggest 5 customer per country by spend
highest 2 campaign per channel by conversions
largest 10 sku per warehouse by units sold</code></pre>
<p>(Verified end-to-end against the engine across the four ranking synonyms, both dimension orderings, and <code>sum</code> / <code>count</code> / <code>ratio</code> / <code>count_distinct</code> / <code>max</code> metrics — the domain terms above are illustrative; swap in your own catalog’s dimensions and metrics.)</p>
<p>Two more advanced shapes are now reachable by name <em>and</em> surfaced as one-click examples in the Workbench, so people discover them:</p>
<pre><code>funnel
retention</code></pre>
</section>
<section id="the-fix-you-cant-see" class="level2">
<h2 class="anchored" data-anchor-id="the-fix-you-cant-see">3. The fix you can’t see</h2>
<p>Here’s the one that actually mattered.</p>
<p>Gibran enforces row-level security by injecting a policy filter into every query. A west-region analyst’s queries silently get <code>region = 'west'</code> AND-ed in. That injection assumed a <em>flat</em> <code>SELECT … FROM table</code> and attached the filter to the <strong>outermost</strong> SELECT.</p>
<p>But cohort-retention, funnels, and the new top-N-per-group all compile to <strong>CTE</strong> queries — where the outer SELECT reads a <em>CTE</em>, not the source table. So the filter landed in the wrong place:</p>
<div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode sql code-with-copy"><code class="sourceCode sql"><span id="cb7-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-- BEFORE (broken): filter on the outer SELECT, which reads `ranked`, not `orders`</span></span>
<span id="cb7-2"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">..</span>. <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">SELECT</span> region, product, revenue</span>
<span id="cb7-3">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">FROM</span> ranked</span>
<span id="cb7-4">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">WHERE</span> rn <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">AND</span> region <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'west'</span>   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-- `region` isn't a column of `ranked`</span></span>
<span id="cb7-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-- -&gt; binder error</span></span></code></pre></div>
<p>Every CTE-based metric was effectively <strong>unusable for any row-filtered role</strong> — which, in a governed system, is most of them. It failed <em>closed</em> (it errored; it never leaked), and because the existing CTE tests only ran <em>un-filtered</em> roles, nothing caught it. It just sat there, silently.</p>
<p>The fix makes filter injection <strong>CTE-aware</strong>: the policy filter is AND-ed into each <em>source-reading</em> SELECT, <strong>before</strong> aggregation or ranking:</p>
<div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode sql code-with-copy"><code class="sourceCode sql"><span id="cb8-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-- AFTER (correct + safe): filter inside the CTE that scans the source</span></span>
<span id="cb8-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">WITH</span> grouped <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">AS</span> (</span>
<span id="cb8-3">  <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">SELECT</span> region, product, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">SUM</span>(amount) <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">AS</span> revenue</span>
<span id="cb8-4">  <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">FROM</span> orders <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">WHERE</span> region <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'west'</span>   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-- here</span></span>
<span id="cb8-5">  <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">GROUP</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">BY</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb8-6">),</span>
<span id="cb8-7">ranked <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">AS</span> ( … )</span>
<span id="cb8-8"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">SELECT</span> region, product, revenue <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">FROM</span> ranked <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">WHERE</span> rn <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span> …</span></code></pre></div>
<p>That’s both <em>correct</em> (the column resolves) and <em>safe</em>: the ranking only ever sees rows the role is allowed to see. A west-region analyst’s “top 3 products per region” can’t accidentally rank over another region’s data — the source scan is filtered before the window function runs.</p>
<div class="callout callout-style-simple callout-important no-icon callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Why fail-closed mattered
</div>
</div>
<div class="callout-body-container callout-body">
<p>The bug erred instead of leaking, which is the <em>right</em> failure mode — but it also meant the feature was quietly broken for the people who’d use it most. The lesson: test the <strong>irregular</strong> case. cohort/top-N each read the source in one CTE; a funnel reads it in <em>several</em> (each step self-joins the source). The fix iterates every source-reading SELECT precisely to cover funnels — so the funnel-under-a-row-filtered-role test is the one whose absence actually mattered.</p>
</div>
</div>
<p>The fix is pinned with end-to-end tests for cohort-retention, funnel, and top-N-per-group, all under a row-filtered role.</p>
</section>
<section id="groundwork-a-seam-for-smarter-matching" class="level2">
<h2 class="anchored" data-anchor-id="groundwork-a-seam-for-smarter-matching">4. Groundwork: a seam for smarter matching</h2>
<p>There’s also a quieter, no-behavior-change refactor: NL phrase-resolution now lives behind a <code>SlotResolver</code> protocol. Today it’s still deterministic exact/substring matching. But it turns “add embedding-based matching” from a rewrite into a one-class addition — for whenever the deterministic layer’s “I don’t know” rate justifies a local model. (Still no LLM in the answer path: embeddings can <em>fail to match</em>, but they can’t <em>invent</em>.)</p>
</section>
<section id="whats-next" class="level2">
<h2 class="anchored" data-anchor-id="whats-next">What’s next</h2>
<ul>
<li><strong>Calendar-aware time parsing.</strong> Today “last 3 months” approximates as 90 days; a dedicated parser makes it exact and is reused by every time-based question.</li>
<li><strong>An approved-functions registry</strong> — the governance-safe path to UDF-like power: author-vetted functions the NL layer can <em>select</em>, never <em>emit</em>.</li>
<li><strong>Embedding retrieval</strong>, via the seam above.</li>
</ul>
</section>
<section id="closing" class="level2">
<h2 class="anchored" data-anchor-id="closing">Closing</h2>
<p>The charts are the demo; the row-filter fix is the substance. Adding a visible feature is what dragged a latent correctness bug in the boring infrastructure into the light — a pattern that shows up again and again in governed-data systems. Gibran’s bet is still the same one v0.1.0 made: trade natural-language breadth for hard correctness guarantees, and weave governance into the SQL so there’s no boundary to escape across. This release makes that bet hold for one more class of query.</p>
<ul>
<li><strong>GitHub</strong>: <a href="https://github.com/AdamEzzat1/gibran" class="uri">https://github.com/AdamEzzat1/gibran</a></li>
<li><strong>Previous post</strong>: <a href="../gibran-v0.1.0/">Gibran v0.1.0: Natural-Language Analytics Without an LLM</a></li>
</ul>
<p><em>Verification: 807 Python tests passing, plus a TypeScript type-check, production build, and 26 frontend component tests. The SQL shown is illustrative — real output uses fully-qualified, quoted identifiers.</em></p>


</section>

 ]]></description>
  <category>Gibran</category>
  <category>analytics</category>
  <category>governance</category>
  <category>visualization</category>
  <category>NL-to-SQL</category>
  <category>security</category>
  <category>DuckDB</category>
  <guid>https://adamezzat1.github.io/blog/posts/gibran-windowed-ctes-and-row-filters/index.html</guid>
  <pubDate>Mon, 25 May 2026 07:00:00 GMT</pubDate>
</item>
<item>
  <title>Qalam: A Deterministic Arabic Morphology Engine — and a Way to Learn Arabic</title>
  <dc:creator>Adam Ezzat</dc:creator>
  <link>https://adamezzat1.github.io/blog/posts/qalam-v0.1/index.html</link>
  <description><![CDATA[ 



<div class="reading-time">
🕑 ~9 min read
</div>
<div class="stats-row">
<div class="stat">
<span class="number">5</span><span class="stat-label">Rust crates</span>
</div>
<div class="stat">
<span class="number">98</span><span class="stat-label">tests passing</span>
</div>
<div class="stat">
<span class="number">0</span><span class="stat-label">bits differ across 3 OSes</span>
</div>
<div class="stat">
<span class="number">1</span><span class="stat-label">language I’m learning</span>
</div>
</div>
<div class="callout callout-style-simple callout-tip no-icon callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-title-container flex-fill">
TL;DR
</div>
</div>
<div class="callout-body-container callout-body">
<p><strong>Qalam</strong> is a deterministic Arabic morphological analyzer I’m building in Rust, structured like a compiler pipeline (text → normalize → tokenize → clitics → morphology). Today it <strong>reliably</strong> normalizes, tokenizes, and builds variant-aware word-frequency tables over <em>any</em> Arabic text, and it does <strong>real morphology</strong> — splitting clitics and extracting roots (strong <em>and</em> weak), each with an explainable confidence and an audit trail. The catch: morphology only <em>confirms</em> the ~99 roots in its bootstrap lexicon, which on arbitrary text is about <strong>a quarter of words</strong>. It’s <strong>bit-identical across Linux, macOS, and Windows</strong>, enforced in CI. It is <strong>not</strong> a finished tool — no big lexicon yet, no syntax, no dialect, no measured accuracy. And the honest truth: half the reason it exists is that building it forces me to actually learn Arabic.</p>
</div>
</div>
<section id="why-im-building-this" class="level2">
<h2 class="anchored" data-anchor-id="why-im-building-this">Why I’m building this</h2>
<p>I’ll start with the part that isn’t an engineering decision: <strong>I’m learning Arabic, and I learn far better when I have a concrete goal.</strong> Flashcards and grammar drills bounce off me. But “make the analyzer recover the root of قال correctly” is a problem I can’t hand-wave my way through — I have to actually understand that قال comes from the root ق-و-ل, that the middle و <em>surfaces</em> as a long ا, and that without context you genuinely cannot tell whether that ا is hiding a و or a ي.</p>
<p>So Qalam is a passion project with a double payoff. Every feature I add is a grammar lesson I’m forced to internalize, because the code won’t work until I do. Arabic morphology is unusually well-suited to this: it’s <em>systematic</em> in a way that rewards a programmer’s instincts — words are built from three-consonant <strong>roots</strong> poured into <strong>patterns</strong> (أوزان), like <code>كَاتِب</code> (writer) and <code>مَكْتُوب</code> (written) and <code>كِتَاب</code> (book) all sharing the root ك-ت-ب. That structure is almost begging to be modeled as code. Building the model is how I’m learning the language.</p>
</section>
<section id="what-qalam-is" class="level2">
<h2 class="anchored" data-anchor-id="what-qalam-is">What Qalam is</h2>
<p>Qalam treats Arabic the way a compiler treats source code: as a series of typed intermediate representations, each lowering into the next.</p>
<pre class="text"><code>raw text
  → Unicode normalization      (NFC + Arabic letter folds)
  → tokenization               (script-aware, raw-anchored spans)
  → clitic splitting           (peel و/ف/ب/ك/ل/س/ال and pronoun suffixes)
  → morphology                 (templatic patterns → root extraction)
  → lexicon confirmation       (promote real roots, suppress spurious ones)
  → a ranked analysis "forest"</code></pre>
<p>The non-negotiable design principle is <strong>determinism</strong>: the same input plus the same lexicon plus the same config produces <em>byte-identical</em> output, on every machine, every time — with a provenance trail explaining <em>why</em> each interpretation was produced. Ambiguity (and Arabic is full of it) is never silently resolved; it’s enumerated as a ranked forest of candidates, each carrying a confidence and the evidence behind it.</p>
<p>The code lives in five small Rust crates: <code>qalam-core</code> (the IR types), <code>qalam-text</code> (normalization, tokenization, clitics), <code>qalam-morph</code> (patterns, roots, the analyzer, the lexicon), <code>qalam-lexicon-builder</code>, and <code>qalam-cli</code>.</p>
</section>
<section id="why-rust" class="level2">
<h2 class="anchored" data-anchor-id="why-rust">Why Rust</h2>
<p>A few reasons, and they’re specific to <em>this</em> project rather than generic Rust enthusiasm:</p>
<ul>
<li><strong>Determinism is controllable.</strong> The whole pitch is bit-reproducibility, and Rust lets me choose data structures with that in mind: <code>BTreeMap</code> where iteration order must be stable, no garbage collector introducing timing-dependent behavior, no hidden hashing randomization unless I opt into it. The output forest is sorted by explicit, total keys. In a language with more “convenient” defaults, I’d be fighting nondeterminism the whole way.</li>
<li><strong>The IR design maps onto Rust’s strengths.</strong> A layered analyzer is mostly enums and exhaustive pattern matching — <code>TokenKind</code>, <code>Pos</code>, <code>Slot</code>, <code>WeakResolution</code> are all enums, and the compiler forces me to handle every case. That’s exactly the discipline a morphology engine needs.</li>
<li><strong>Reproducible artifacts across platforms.</strong> Rust’s defined numeric behavior plus a single compiled binary means I can <em>prove</em> the cross-OS guarantee — and I do, in CI, by diffing the analyzer’s output across Linux, macOS, and Windows on every push.</li>
<li><strong>No GPU, no runtime, fast.</strong> Morphology is rule-and-table work, not matrix math. Rust gives me speed and a tiny footprint without a heavyweight runtime.</li>
<li><strong>It sharpens both skills at once.</strong> I’m strengthening my Rust <em>and</em> my Arabic on the same project. That’s efficient.</li>
</ul>
</section>
<section id="what-it-can-do-today-and-ive-checked-every-claim-below" class="level2">
<h2 class="anchored" data-anchor-id="what-it-can-do-today-and-ive-checked-every-claim-below">What it can do today (and I’ve checked every claim below)</h2>
<p>There are really two tiers of capability, and being honest about the line between them matters.</p>
<p><strong>Tier 1 — full coverage, works on any text.</strong> Normalization, tokenization, and frequency analysis don’t need a lexicon, so they work on arbitrary Arabic:</p>
<ul>
<li><code>qalam normalize</code> applies Unicode NFC plus an Arabic fold table — collapsing the alef variants (أ إ آ ٱ → ا) and stripping tatweel (the ـــ elongation) — while <em>preserving</em> diacritics, teh marbuta, and the hamza-bearing letters. It’s idempotent.</li>
<li><code>qalam freq</code> builds a word-frequency table grouped by normalized form, so <code>أحمد</code> and <code>احمد</code> are correctly counted as the same word and you can see which raw spellings collapsed together.</li>
<li><code>qalam segment</code> shows the clitic-splitting forest for each word.</li>
</ul>
<p><strong>Tier 2 — real morphology, but only as far as the lexicon reaches.</strong> <code>qalam analyze</code> runs the whole pipeline. Here’s its actual output on a handful of words (verbatim — <code>root=</code> is the extracted root, <code>pat=</code> the matched pattern, the leading number is confidence):</p>
<pre class="text"><code>الكتاب   0.782  ال · كتاب   root=ك-ت-ب  pat=4    (the book — article peeled, root found)
مكتوب    0.808  مكتوب       root=ك-ت-ب  pat=3    (written — passive participle مفعول)
مدرسة    0.790  مدرسة       root=د-ر-س  pat=8    (school — place noun مفعلة)
قال      0.790  قال         root=ق-و-ل  pat=20   (he said — hollow weak root)
قِ        0.880  قِ          root=و-ق-ي  pat=—    (protect! — see below)
في       0.940  في          root=—      (particle — function word, no root)</code></pre>
<p>Those roots are all correct. And it works without diacritics, because the <em>written</em> long vowels and pattern letters pin the alignment — <code>كاتب</code> and <code>كتاب</code> both resolve to ك-ت-ب even though neither carries a single short vowel mark.</p>
<p><strong>And it’s deterministic.</strong> A CI job runs <code>qalam normalize</code>, <code>freq</code>, and <code>analyze</code> over a fixed corpus on Linux, macOS <em>and</em> macOS-on-ARM, and Windows, then diffs the outputs byte-for-byte. They have never differed — including the floating-point confidence values. That cross-architecture bit-equality is the property I care about most, and it’s machine-checked on every commit.</p>
</section>
<section id="the-honest-limitation-coverage" class="level2">
<h2 class="anchored" data-anchor-id="the-honest-limitation-coverage">The honest limitation: coverage</h2>
<p>Here’s the number that frames everything. I ran <code>analyze</code> on a neutral, <em>non</em>-cherry-picked MSA sentence — a generic news-style line about ministries and infrastructure. Of 17 tokens, exactly <strong>4 got a lexicon-confirmed root.</strong> That’s roughly a quarter of the content words.</p>
<p>Why? The bootstrap lexicon currently holds <strong>99 roots, 37 particles, and 5 irregular forms.</strong> Arabic has thousands of roots and hundreds of thousands of words. So on real text, most words fall back to either an <em>unconfirmed</em> pattern guess (which I deliberately down-weight, because it’s often spurious) or nothing at all. The engine is correct on what it knows; it just doesn’t yet know much.</p>
<p>Two more honest caveats:</p>
<ul>
<li><strong>There is no accuracy benchmark.</strong> I can’t tell you “Qalam is <em>X%</em> accurate,” because I haven’t evaluated it against a gold-standard treebank. The confidence numbers above are deterministic, hand-tuned priors — useful for <em>ranking</em>, not calibrated probabilities.</li>
<li><strong>It does not beat the established tools.</strong> MADAMIRA, Farasa, and CAMeL Tools have enormous lexicons and statistical models; on coverage and accuracy they win, easily. Qalam’s only real edges are <em>determinism</em>, <em>explainability</em>, and being a clean, dependency-light Rust codebase — which matter to a niche (reproducible research, auditable pipelines, and people learning the language), not to the broad market.</li>
</ul>
</section>
<section id="the-part-im-proudest-of-weak-roots" class="level2">
<h2 class="anchored" data-anchor-id="the-part-im-proudest-of-weak-roots">The part I’m proudest of: weak roots</h2>
<p>If you’ve studied Arabic, you know the “weak” roots — the ones containing و, ي, or ء — are where the tidy three-consonant system gets messy. The radical mutates, lengthens, or vanishes entirely. This is the part that’s genuinely hard to do deterministically, and it’s also the part that taught me the most.</p>
<p>Qalam handles the main classes by <strong>enumerate-then-disambiguate</strong>:</p>
<ul>
<li><strong>Hollow</strong> roots (weak middle radical) surface with a long ا: <code>قال</code> could be ق-<strong>و</strong>-ل <em>or</em> ق-<strong>ي</strong>-ل — the surface honestly doesn’t say which. So the engine produces <em>both</em> candidates, and the lexicon breaks the tie: ق-و-ل is a known root, ق-ي-ل isn’t, so the real one is promoted (0.79) and the phantom is demoted (0.12).</li>
<li><strong>Defective</strong> roots (weak final radical) are luckier — <code>ا</code> vs <code>ى</code> actually distinguishes them, so <code>دعا → د-ع-و</code> and <code>رمى → ر-م-ي</code> are directly recoverable.</li>
<li><strong>Assimilated</strong> roots (weak initial radical) like <code>وصل → و-ص-ل</code> are visible and straightforward.</li>
</ul>
<p>And then there’s the one I included precisely <em>because</em> it’s the hardest: <strong>قِ</strong>, the imperative of و-ق-ي (“protect”), famous as possibly the shortest complete sentence in Arabic — a single letter. There is no rule that recovers و-ق-ي from a lone ق; it’s pure lexical knowledge. So I handled it the only honest way: a small table of irregular forms, documented as exactly that — a lexical exception, not a morphophonemic rule. <code>analyze قِ</code> returns و-ق-ي. Pretending a rule could derive it would have been a lie, and the whole project is built on not telling those.</p>
</section>
<section id="what-it-cant-do-yet" class="level2">
<h2 class="anchored" data-anchor-id="what-it-cant-do-yet">What it can’t do (yet)</h2>
<p>To be completely clear about the boundaries:</p>
<ul>
<li><strong>No large lexicon.</strong> This is the single biggest gap, and it’s <em>data</em> work, not code — sourcing and license-cleaning open Arabic corpora. It’s what stands between “a quarter of words” and “most words.”</li>
<li><strong>No diacritization.</strong> It reads partially- or un-diacritized text fine, but it doesn’t <em>generate</em> the vowel marks.</li>
<li><strong>No Egyptian dialect yet.</strong> Egyptian↔︎MSA normalization is designed but unbuilt — and it’s the second half of the project’s whole reason for being.</li>
<li><strong>No syntax, no semantics, no knowledge graph, no search, no learning engine.</strong> These are all designed on paper as later phases. None exist yet.</li>
</ul>
<p>The morphology <em>engine</em> is roughly feature-complete for a v0.1 — strong roots, weak roots, clitics, particles, irregulars. What it lacks is <em>data</em> and the higher layers.</p>
</section>
<section id="what-i-measured-vs.-what-im-claiming" class="level2">
<h2 class="anchored" data-anchor-id="what-i-measured-vs.-what-im-claiming">What I measured vs.&nbsp;what I’m claiming</h2>
<p>In the spirit of not overselling:</p>
<ul>
<li><strong>Measured / verified:</strong> the analyze outputs above (reproduced verbatim), the 98 passing tests, the 5-crate layout, the lexicon counts (99/37/5), the 4-of-17 coverage probe, and the cross-OS byte-identical determinism (machine-checked in CI).</li>
<li><strong>A deterministic prior, not a calibrated probability:</strong> every confidence number. They rank candidates sensibly; they are not validated against ground truth.</li>
<li><strong>Not measured at all:</strong> accuracy. No treebank evaluation exists yet, so I make no accuracy claim.</li>
<li><strong>Designed, not built:</strong> dialect normalization, syntax, semantic IR, knowledge graph, search, the learning engine, and the full lexicon ingestion.</li>
</ul>
</section>
<section id="the-road-ahead" class="level2">
<h2 class="anchored" data-anchor-id="the-road-ahead">The road ahead</h2>
<p>The next move is almost forced: <strong>the lexicon.</strong> Nothing else lifts that “quarter of words” number — not dialect, not syntax, not search. They all sit <em>on top of</em> morphology, which sits on top of the lexicon. So the plan is to build the ingestion pipeline (open sources → a content-hashed, memory-mapped FST the analyzer already knows how to consume) so that the data, when it arrives, is a drop-in that makes every other capability better at once.</p>
<p>But I want to end where I started, because it’s the honest center of the project. Qalam is not trying to beat MADAMIRA. It’s a deterministic, explainable, carefully-engineered Arabic morphology engine wrapped around a deliberately small lexicon — and it’s the thing that’s actually getting me to learn Arabic. Every weak-root rule I implemented, I now <em>understand</em>, in a way no flashcard ever managed. Building the tool turned out to be the lesson.</p>


</section>

 ]]></description>
  <category>Qalam</category>
  <category>Arabic</category>
  <category>NLP</category>
  <category>morphology</category>
  <category>determinism</category>
  <category>Rust</category>
  <category>language-learning</category>
  <guid>https://adamezzat1.github.io/blog/posts/qalam-v0.1/index.html</guid>
  <pubDate>Mon, 25 May 2026 07:00:00 GMT</pubDate>
</item>
<item>
  <title>Green by Construction: Thermal Bounds Without Losing a Bit</title>
  <dc:creator>Adam Ezzat</dc:creator>
  <link>https://adamezzat1.github.io/blog/posts/deterministic-green-compute/index.html</link>
  <description><![CDATA[ 



<div class="reading-time">
🕑 ~19 min read
</div>
<div class="stats-row">
<div class="stat">
<span class="number">3</span><span class="stat-label">thermal profiles</span>
</div>
<div class="stat">
<span class="number">80</span><span class="stat-label">new tests</span>
</div>
<div class="stat">
<span class="number">1.8×</span><span class="stat-label">burst recovered</span>
</div>
<div class="stat">
<span class="number">0</span><span class="stat-label">output bits changed</span>
</div>
</div>
<div class="callout callout-style-simple callout-tip no-icon callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-title-container flex-fill">
TL;DR
</div>
</div>
<div class="callout-body-container callout-body">
<p>I gave <a href="../../../blog/posts/introducing-cjc/index.html">CJC-Lang</a> a green-compute layer in three phases. <strong>Phase 1</strong> is a Runtime Policy Layer: <code>--profile cool|balanced|max-perf</code> bounds how much CPU a run uses, plus a <em>deterministic</em> energy estimate. <strong>Phase 2</strong> is race-to-idle adaptive scheduling — full speed for short bursts, throttle only when load is sustained — which recovered ~1.8× of the throughput the fixed cap cost, while keeping the sustained thermal bound. <strong>Phase 3a</strong> is fused elementwise kernels that eliminate intermediate tensor allocations. The whole thing rests on one invariant: <strong>thread count, pool size, and the adaptive schedule provably never change the output.</strong> Same seed, same bits — across every profile, thread count, and both executors. That’s what makes an <em>adaptive</em> thermal knob safe in a language whose entire pitch is reproducibility.</p>
</div>
</div>
<section id="the-problem-a-laptop-that-cooks-itself" class="level2">
<h2 class="anchored" data-anchor-id="the-problem-a-laptop-that-cooks-itself">The problem: a laptop that cooks itself</h2>
<p>CJC-Lang’s core promise is <em>same seed, same output, always</em>. To get there it already does the unglamorous work: ordered collections, Kahan/binned summation, a seeded RNG, and — critically for this story — <strong>no FMA in the SIMD kernels</strong>, so a multiply-then-add rounds the same way every time.</p>
<p>But nothing in the runtime bounded <em>how hard</em> it pushed the machine. The parallel matmul and reduction kernels ran on Rayon’s default global pool — every core, all the time. On a laptop, a training loop would peg all 8 cores, spin the fans up, and eventually thermal-throttle: the CPU gets hot enough that the firmware <em>lowers</em> the clock to protect itself, and you end up slower than if you’d never saturated it.</p>
<p>I wanted an explicit, deterministic answer to “how much machine may this run use?” — without the OS power governor’s opacity, and without breaking the reproducibility contract.</p>
</section>
<section id="the-reframe-that-makes-it-tractable" class="level2">
<h2 class="anchored" data-anchor-id="the-reframe-that-makes-it-tractable">The reframe that makes it tractable</h2>
<p>Here’s the mental model I kept coming back to:</p>
<blockquote class="blockquote">
<p><strong>Energy = power × time.</strong> Capping cores cuts power but <em>raises</em> time. On a short burst that’s often a wash — you run cooler but longer. The levers that recover performance <em>and</em> save energy attack the other terms: total work (fewer cycles), memory traffic (the dominant energy term), and <em>when</em> you spend the thermal budget.</p>
</blockquote>
<p>Crucially: <strong>bursts don’t overheat — only sustained load does.</strong> Thermal time constants are seconds to tens of seconds, so a sub-second burst at full tilt never heat-soaks the package. A fixed thread cap pays the throttle tax on <em>every</em> operation, including the short interactive runs that were never going to overheat.</p>
<p>And the linchpin that makes all of this safe: in CJC-Lang, <strong>thread count never changes the result.</strong> The parallel reductions sum in a fixed chunk order with Kahan/binned accumulators, so the numeric output is identical no matter how many workers are live. That single guarantee is what lets me treat parallelism as a pure heat/speed knob — and even make it <em>adaptive</em> — without ever risking the answer.</p>
<section id="what-determinism-does-not-buy-you" class="level3">
<h3 class="anchored" data-anchor-id="what-determinism-does-not-buy-you">What determinism does <em>not</em> buy you</h3>
<p>I want to be careful here, because there’s an easy and wrong way to tell this story: <em>“determinism makes computation greener.”</em> It doesn’t — not directly, and per-run it’s often the opposite. Kahan and binned summation do <strong>more</strong> arithmetic than a naive sum. A seeded RNG, ordered collections, audit trails, and bit-exact replay all cost extra cycles and memory. Measured one run at a time, “trustworthy” is <em>more</em> expensive than “fast and loose,” not less.</p>
<p>So the green argument is <strong>not</strong> “determinism reduces FLOPs.” It’s narrower and, I think, more defensible: determinism, numerical stability, and bounded execution reduce the number of runs you <em>waste</em> on the way to a result you can actually trust. The savings live in the <strong>denominator — fewer reruns — not the numerator.</strong> Everything below should be read in that light.</p>
</section>
</section>
<section id="phase-1-a-runtime-policy-layer" class="level2">
<h2 class="anchored" data-anchor-id="phase-1-a-runtime-policy-layer">Phase 1: a Runtime Policy Layer</h2>
<p>The first piece is a <code>RuntimePolicy</code> — the green-compute control surface. It lives in a thread-local in <code>cjc-runtime</code>, mirroring the existing profiler sink.</p>
<div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb1-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">pub</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">struct</span> RuntimePolicy <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb1-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">pub</span> determinism<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> Determinism<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span>   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Strict</span></span>
<span id="cb1-3">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">pub</span> numeric_mode<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> NumericMode<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Kahan | Binned | FixedTree</span></span>
<span id="cb1-4">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">pub</span> thermal_mode<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> ThermalMode<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Cool | Balanced | MaxPerf</span></span>
<span id="cb1-5">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">pub</span> max_threads<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">usize</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span>         <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// 0 = auto (derive from thermal_mode)</span></span>
<span id="cb1-6">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">pub</span> batch_size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">usize</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-7">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">pub</span> audit_mode<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> AuditMode<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span>      <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Summary | Full | Forensic</span></span>
<span id="cb1-8">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">pub</span> adaptive<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">bool</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span>             <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// race-to-idle (Phase 2)</span></span>
<span id="cb1-9"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div>
<p>Three thermal profiles, exposed as CLI flags applied once at startup so they cover <code>run</code>, <code>bench</code>, and every command:</p>
<table class="table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th>Profile</th>
<th>Threads (8-core host)</th>
<th>Batch</th>
<th>Use</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>cool</code></td>
<td>≈ ¼ cores → <strong>2</strong></td>
<td>32</td>
<td>laptop, edge, gentle</td>
</tr>
<tr class="even">
<td><code>balanced</code> (default)</td>
<td>≈ ½ cores → <strong>4</strong></td>
<td>128</td>
<td>normal — leaves headroom</td>
</tr>
<tr class="odd">
<td><code>max-perf</code></td>
<td>all → <strong>8</strong></td>
<td>512</td>
<td>benchmarking</td>
</tr>
</tbody>
</table>
<p>The default is <strong><code>balanced</code></strong>, deliberately not “all cores forever.”</p>
<section id="a-deterministic-energy-estimate" class="level3">
<h3 class="anchored" data-anchor-id="a-deterministic-energy-estimate">A <em>deterministic</em> energy estimate</h3>
<p>Most energy tools (CodeCarbon, Scaphandre, RAPL counters) <em>measure</em> power from hardware — which is non-deterministic and machine-specific. I wanted energy to be a number you can assert in a test. So <code>energy_estimate</code> is a pure function of the workload, never of wall-clock time:</p>
<pre class="text"><code>joules ≈ flops × 1e-10  +  bytes × 1e-10
         (~100 pJ/flop)     (~100 pJ/byte, DRAM-ish)</code></pre>
<p>The two terms are computed in separate bindings so the compiler can’t fuse them into an FMA — the same no-FMA discipline as the SIMD kernels. Same program, same seed → same FLOP count → same joules, bit-for-bit. On the branch, <code>energy_estimate(1000000, 2000)</code> returns <code>0.00010020000000000001</code> — <em>identical</em> under every profile, thread count, and both executors. The absolute number is an order-of-magnitude estimate; the <strong>ratio</strong> between two runs is the meaningful, reproducible figure.</p>
</section>
<section id="what-the-fixed-cap-costs" class="level3">
<h3 class="anchored" data-anchor-id="what-the-fixed-cap-costs">What the fixed cap costs</h3>
<p>Measured on an 8-core machine, release build, <code>cjcl bench</code> (10 runs) on a 1024×1024 matmul:</p>
<table class="table">
<thead>
<tr class="header">
<th>Profile</th>
<th>Threads</th>
<th>Mean</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>--threads 1</code></td>
<td>1</td>
<td>628 ms</td>
</tr>
<tr class="even">
<td><code>cool</code></td>
<td>2</td>
<td>468 ms</td>
</tr>
<tr class="odd">
<td><code>balanced</code> (default)</td>
<td>4</td>
<td>268 ms</td>
</tr>
<tr class="even">
<td><code>max-perf</code></td>
<td>8</td>
<td>210 ms</td>
</tr>
</tbody>
</table>
<p>So the cap genuinely throttles (max-perf is ~3× faster than single-threaded) — and the laptop-safe default costs ~27% versus all-out. That cost is real, and Phase 2 is about clawing most of it back.</p>
</section>
</section>
<section id="phase-2-race-to-idle-adaptive-scheduling" class="level2">
<h2 class="anchored" data-anchor-id="phase-2-race-to-idle-adaptive-scheduling">Phase 2: race-to-idle adaptive scheduling</h2>
<p>A fixed cap throttles short and long workloads alike. Race-to-idle fixes that: <strong>run at full width during a burst, and throttle to the cap only once load is sustained</strong> (a 2-second window; a ½-second idle gap resets it).</p>
<p>The mechanism is the interesting part. Rayon’s global pool can only be configured once, so I <em>don’t</em> shrink it — I leave it at full size so bursts can use every core. Throttling happens per-operation: when a workload has been busy past the window, the kernel’s parallel section is <code>install</code>-ed into a cached, cap-sized pool. Inside that pool <code>rayon::current_num_threads()</code> reports the cap, so the existing band/row/chunk splitters auto-scale — no per-kernel rewrites.</p>
<div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb3-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">pub</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> run_parallel<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>R<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">Send</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span>(work<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">impl</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">FnOnce</span>() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> R <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">Send</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> R <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb3-2">    <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">if</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">rayon::</span>current_thread_index()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>is_some() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">return</span> work()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span> <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// nested → inline</span></span>
<span id="cb3-3">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> cap <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> effective_threads(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>get()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> detect_cores())<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb3-4">    <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">if</span> cap <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;=</span> detect_cores() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">return</span> work()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span>                   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// no cap</span></span>
<span id="cb3-5">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> throttle <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">if</span> get()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>adaptive <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> is_sustained_now() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">else</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">true</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">};</span></span>
<span id="cb3-6">    <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">if</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!</span>throttle <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">return</span> work()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span>                               <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// burst → full pool</span></span>
<span id="cb3-7">    <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">match</span> capped_pool(cap) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">Some</span>(p) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> p<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>install(work)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">None</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> work() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb3-8"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div>
<p>The four parallel kernels (large/medium/batch matmul, SIMD elementwise) wrap their bodies in <code>run_parallel</code>.</p>
<section id="did-it-work-two-regimes-measured" class="level3">
<h3 class="anchored" data-anchor-id="did-it-work-two-regimes-measured">Did it work? Two regimes, measured</h3>
<p><strong>Short workload</strong> (one 1024² matmul, finishes inside the burst window), minimum of 9 <code>--time</code> runs:</p>
<table class="table">
<thead>
<tr class="header">
<th></th>
<th>cool fixed</th>
<th><strong>cool adaptive</strong></th>
<th>max-perf</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>time</td>
<td>0.353 s</td>
<td><strong>0.196 s</strong></td>
<td>0.153 s</td>
</tr>
</tbody>
</table>
<p>Adaptive-cool recovered ~<strong>1.8×</strong> (0.353 → 0.196 s) — landing within fixed startup overhead of max-perf. The short, interactive case now runs at full speed <em>and</em> never trips the cap.</p>
<p><strong>Sustained workload</strong> (25× 1024² matmuls, ~runs well past the 2 s window):</p>
<table class="table">
<thead>
<tr class="header">
<th></th>
<th>cool fixed</th>
<th><strong>cool adaptive</strong></th>
<th>max-perf</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>time</td>
<td>16.7 s</td>
<td><strong>10.8 s</strong></td>
<td>4.2 s</td>
</tr>
</tbody>
</table>
<p>This is the part I care about most: adaptive-cool (10.8 s) sits <em>between</em> fixed-cool and max-perf. It is <strong>not</strong> near max-perf (4.2 s) — meaning it genuinely throttled after the burst. The thermal bound held; the burst just bought back the first couple of seconds. Exactly the goal: fast when it’s safe, bounded when it matters.</p>
</section>
<section id="why-this-is-safe" class="level3">
<h3 class="anchored" data-anchor-id="why-this-is-safe">Why this is safe</h3>
<p>The burst-vs-throttle decision reads the wall clock, so the <em>schedule</em> is non-deterministic run-to-run. But the <em>output</em> isn’t, because the choice only changes how many bands run concurrently — never the per-element math. To prove it rather than assert it: a 256×256 matrix product (a real parallel reduction over varied values) hashed <strong>byte-identical</strong> across <code>--threads 1</code>, <code>cool</code>, <code>balanced</code>, <code>max-perf</code>, adaptive on/off, <em>and</em> both the AST and MIR executors:</p>
<pre class="text"><code>threads=1  (eval)    : 79aaf9931d2b6c2004a20cdf5d5bd434
threads=8  (eval)    : 79aaf9931d2b6c2004a20cdf5d5bd434
cool (adaptive burst): 79aaf9931d2b6c2004a20cdf5d5bd434
cool --no-adaptive   : 79aaf9931d2b6c2004a20cdf5d5bd434
max-perf             : 79aaf9931d2b6c2004a20cdf5d5bd434
MIR cool (adaptive)  : 79aaf9931d2b6c2004a20cdf5d5bd434</code></pre>
<p>For reproducible benchmarking — where you <em>want</em> a fixed schedule — <code>--no-adaptive</code> applies the cap uniformly.</p>
</section>
</section>
<section id="phase-3a-fused-kernels-cut-memory-traffic" class="level2">
<h2 class="anchored" data-anchor-id="phase-3a-fused-kernels-cut-memory-traffic">Phase 3a: fused kernels cut memory traffic</h2>
<p>The third lever is memory. A plain <code>a*b + c</code> allocates an intermediate: <code>mul</code> → a fresh tensor, then <code>add</code> → another. That’s two allocations and ~5N words of traffic for an N-element result. A fused single-pass kernel does it in <strong>one allocation and one pass</strong> (~4N words).</p>
<p>I added three, joining the existing <code>broadcast_fma</code>:</p>
<table class="table">
<thead>
<tr class="header">
<th>Builtin</th>
<th>Computes</th>
<th>Replaces</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>fused_axpy(α, x, y)</code></td>
<td><code>α·x + y</code></td>
<td>scalar-mul + add (BLAS axpy)</td>
</tr>
<tr class="even">
<td><code>fused_mul_sub(a, b, c)</code></td>
<td><code>a·b − c</code></td>
<td>mul + sub</td>
</tr>
<tr class="odd">
<td><code>fused_sub_sq(a, b)</code></td>
<td><code>(a−b)²</code></td>
<td>sub + mul (MSE/variance/distance)</td>
</tr>
</tbody>
</table>
<p>The subtle bit is <em>why this is free</em>: <code>a*b + c</code> in Rust compiles to a <strong>separate</strong> multiply then add — it does <strong>not</strong> contract into a hardware FMA unless you call <code>.mul_add()</code> explicitly. So the fused loop produces bit-identical results to the two-pass version while eliminating the intermediate. I pinned that with property tests asserting <code>fused == unfused</code> byte-for-byte over 256 random cases each.</p>
<p>And this <em>compounds</em> with Phase 2: lighter work finishes inside the burst window, so the cap engages even less. Memory efficiency and the adaptive scheduler reinforce each other instead of trading off.</p>
</section>
<section id="where-the-waste-actually-is" class="level2">
<h2 class="anchored" data-anchor-id="where-the-waste-actually-is">Where the waste actually is</h2>
<p>If determinism makes each run a little <em>more</em> expensive, why is any of this “green”? Because the dominant source of wasted compute in real numerical and ML work isn’t the cost of one run — it’s <strong>runs you have to throw away.</strong> Unstable, irreproducible systems waste compute, and they waste a lot of it:</p>
<ul>
<li><strong>Nondeterministic drift.</strong> A number changes between runs and you can’t tell whether it’s a bug or noise — so you rerun, sometimes dozens of times, just to characterize the variance.</li>
<li><strong>NaN / instability cascades.</strong> An unstable reduction overflows or loses precision, a training run diverges, and everything downstream is garbage. You debug, patch, and rerun the whole pipeline.</li>
<li><strong>Failed reproductions.</strong> A reviewer, a teammate, or future-you can’t reproduce a result, so the experiment is repeated from scratch.</li>
<li><strong>Preprocessing redone</strong> because the previous run wasn’t pinned.</li>
</ul>
<p>Every one of those is compute already spent that produced <em>nothing trustworthy.</em> That’s the waste CJC-Lang is actually attacking — with stable reductions, a seeded RNG, bit-exact replay, and now bounded, memory-aware execution. None of it makes a single run cheaper. It makes <em>reruns unnecessary.</em></p>
<section id="an-honest-metric-trustworthy-results-per-joule" class="level3">
<h3 class="anchored" data-anchor-id="an-honest-metric-trustworthy-results-per-joule">An honest metric: trustworthy results per joule</h3>
<p>Throughput (FLOPs/second) is the wrong yardstick for this. A better one is closer to:</p>
<blockquote class="blockquote">
<p><strong>trustworthy_results / joule</strong> — useful, <em>reproducible</em> outputs per unit of energy, counting the energy burned on runs you had to repeat.</p>
</blockquote>
<p>I ran a small experiment to make it concrete. A 512×512 matmul, with the deterministic energy estimate from Phase 1:</p>
<pre class="text"><code>result summary  : 2307326611210371000   p.sum()
energy estimate : 0.0274726912 J         energy_estimate(2·512³ flops, 3·512²·8 bytes)</code></pre>
<p>Then I checked reproducibility — the <em>same</em> program across every profile, thread cap, adaptive on/off, and <em>both</em> executors:</p>
<pre class="text"><code>e81183611e12dd95d56e79b9b36ff194   default
e81183611e12dd95d56e79b9b36ff194   --profile max-perf
e81183611e12dd95d56e79b9b36ff194   --profile cool
e81183611e12dd95d56e79b9b36ff194   --profile cool --no-adaptive
e81183611e12dd95d56e79b9b36ff194   --threads 1
e81183611e12dd95d56e79b9b36ff194   --mir-opt</code></pre>
<p>Identical, every time. So:</p>
<ul>
<li>One run yields a result you can trust → <strong>~0.0275 J per trustworthy result</strong> (≈ 36 results/joule, at this analytical estimate).</li>
<li><em>Certifying</em> it — producing it and independently re-running to confirm bit-equality — costs <strong>two runs (~0.055 J)</strong> and reaches actual certainty.</li>
</ul>
<p>Now the honest part, because this is exactly where the story could overreach. That <code>0.0275 J</code> is the numerator, and <strong>determinism did not shrink it</strong> — the stable reductions arguably made it slightly bigger. The metric’s real lever is the <strong>rerun multiplier</strong>: if establishing a trustworthy result takes <em>k</em> runs, you spend <em>k</em> × 0.0275 J. In a nondeterministic pipeline chasing drift or instability, <em>k</em> can be many; here it is <strong>1</strong>, and verification is <em>exact replay</em> rather than statistics-over-noise. I’m deliberately <strong>not</strong> fabricating a competitor’s <em>k</em> — I can’t measure someone else’s pipeline, and I’m not going to pretend to. The claim is narrow and checkable: CJC-Lang drives <em>k</em> toward 1, and <em>that</em> — not FLOPs-per-run — is where the energy is saved.</p>
<p>(Two caveats stack here: the joules are an <em>analytical estimate</em>, not a wattmeter reading, and the rerun multiplier is a <em>model</em>, not a benchmark of another system. Both are stated so you can discount them appropriately.)</p>
</section>
</section>
<section id="why-bounded-heat-matters-beyond-one-run" class="level2">
<h2 class="anchored" data-anchor-id="why-bounded-heat-matters-beyond-one-run">Why bounded heat matters beyond one run</h2>
<p>The thermal bound isn’t only about the run in front of you. Three stakes, in decreasing order of how confidently I can claim them:</p>
<ul>
<li><strong>Short run — the throttle cliff (measured).</strong> Saturate every core long enough and the firmware lowers the clock to protect the chip, so a thermally-<em>unbounded</em> run can finish <em>slower</em> than a bounded one. Capping sustained load keeps you on the right side of that cliff.</li>
<li><strong>Long run — hardware stress (physics, not a benchmark).</strong> Sustained operation near thermal limits accelerates component aging — degradation roughly follows an Arrhenius curve (≈2× per 10 °C), and thermal cycling fatigues solder joints and packaging. Running cooler reduces that stress. I haven’t measured device lifetimes — that’s a multi-year claim — so I’ll state it as physics, not a result.</li>
<li><strong>At scale — heat is cooling cost.</strong> In a datacenter, a large slice of energy goes to <em>removing</em> the heat compute produces (it’s a big term in PUE). Bounding sustained load lowers the cooling burden — it <em>complements</em> cooling rather than replacing it (you still buy the heatsink). On a laptop the same lever buys quieter fans and longer battery.</li>
</ul>
<p>And one correction worth making explicit, because it’s easy to get backwards: <strong>heat threatens <em>performance</em>, not the <em>answer</em>.</strong> Thermal throttling changes the clock, which changes <em>timing</em> — never the bits. CJC-Lang’s output is timing-independent by construction (fixed reduction order, seeded RNG, no result-affecting races), so a throttled run is byte-identical to a cool one, just slower. What heat actually breaks is <em>performance reproducibility</em> — a hot benchmark isn’t repeatable. So the two guarantees are separate and complementary: <strong>the thermal bound protects how fast you go; determinism protects what you get — and it survives the chip throttling.</strong> (In a <em>non</em>-deterministic, timing-dependent system, throttling-induced timing shifts genuinely <em>can</em> perturb results — which is exactly the failure mode CJC-Lang is built not to have.)</p>
</section>
<section id="how-unique-is-this" class="level2">
<h2 class="anchored" data-anchor-id="how-unique-is-this">How unique is this?</h2>
<p>Honest answer: the individual pieces all exist somewhere. Thread caps are everywhere (Rayon, OpenMP, <code>GOMAXPROCS</code>). OS governors and RAPL do power capping. Race-to-idle is textbook power management. CodeCarbon and friends estimate energy. Intel MKL has reproducible-numerics modes.</p>
<p>What I haven’t seen packaged together is the <strong>combination at the language-runtime level with a determinism guarantee</strong>: a thermal/energy policy where the knobs are <em>provably</em> result-invariant, paired with an adaptive scheduler and a reproducible energy estimate. In most stacks, changing thread count <em>can</em> change floating-point results; here it provably can’t, which is the whole reason an adaptive thermal scheduler is even safe to ship. It’s most at home in the niche CJC-Lang targets — reproducible scientific, regulated, and edge computing — not hyperscale throughput.</p>
</section>
<section id="what-i-measured-vs.-what-i-estimated" class="level2">
<h2 class="anchored" data-anchor-id="what-i-measured-vs.-what-i-estimated">What I measured vs.&nbsp;what I estimated</h2>
<p>In keeping with the project’s ethos, the honest accounting:</p>
<ul>
<li><strong>Measured:</strong> all the wall-clock numbers (release <code>cjcl bench</code> / <code>--time</code>), the determinism hashes, the test counts. These are real and on the branch.</li>
<li><strong>A proxy, not a measurement:</strong> I measured wall-clock and active-core counts, <em>not</em> watts or junction temperature. Fewer active cores means less package power, but I don’t have hardware sensors in the loop — treat the thermal claims as a well-founded proxy, not a power reading.</li>
<li><strong>Analytical, not benchmarked:</strong> the fused-kernel traffic reduction (~5N → ~4N words, two allocations → one) is from counting, not a wall-clock microbenchmark. What I <em>did</em> verify is that the output is bit-identical to the unfused path.</li>
<li><strong>Deferred:</strong> the data-DSL group-by/join memory work (Phase 3b) is not in this post. The obvious approach turned out to change output <em>order</em> (the streaming path sorts keys; the materializing path preserves first-occurrence order), which would break determinism — so I stopped rather than ship a correctness regression. It needs an order-preserving design, which is its own phase.</li>
</ul>
</section>
<section id="verification" class="level2">
<h2 class="anchored" data-anchor-id="verification">Verification</h2>
<p>Every claim above is backed by tests added for this work: <strong>80 new tests</strong> — 20 unit and 43 integration for the policy/scheduling layer, plus 17 for the fused kernels — spanning unit, dual-executor parity (AST vs MIR), property (256 cases each), and fuzz checks. The existing suite stayed green and no determinism gate moved.</p>
<p>A note on tone, because it’s easy to get this wrong: none of this makes CJC-Lang “environmentally superior” to anything. It’s far too early for that claim, and against a hyperscale GPU cluster it isn’t even the right comparison. The honest framing is narrower and more interesting as a research question — <em>CJC-Lang is exploring whether deterministic, memory-aware, thermally-bounded systems can reduce <strong>wasteful</strong> computation</em> (the reruns, the drift-debugging, the throwaway experiments) in the niches where trust matters more than peak throughput. That’s the hypothesis this work is testing, not a verdict.</p>
<p>The throughline: you don’t have to choose between <em>fast</em>, <em>reproducible</em>, and <em>cool</em>. You can have a thermal bound that adapts to your workload, energy you can reason about, and lower memory traffic — and still get the same bits out every single time. In a deterministic language, that last clause isn’t a nice-to-have. It’s the whole point.</p>


</section>

 ]]></description>
  <category>CJC-Lang</category>
  <category>performance</category>
  <category>determinism</category>
  <category>green-computing</category>
  <category>energy</category>
  <category>architecture</category>
  <guid>https://adamezzat1.github.io/blog/posts/deterministic-green-compute/index.html</guid>
  <pubDate>Fri, 22 May 2026 07:00:00 GMT</pubDate>
</item>
<item>
  <title>ABNG on Real Clinical Data — The Diabetes-130 Readmission Task</title>
  <dc:creator>Adam Ezzat</dc:creator>
  <link>https://adamezzat1.github.io/blog/posts/abng-diabetes-readmission/index.html</link>
  <description><![CDATA[ 



<div class="reading-time">
🕑 ~13 min read
</div>
<div class="callout callout-style-simple callout-tip no-icon callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-title-container flex-fill">
TL;DR
</div>
</div>
<div class="callout-body-container callout-body">
<p>I put <strong>ABNG</strong> — the Adaptive Belief Network Graph, an experimental, deterministic, auditable belief-graph model built in CJC-Lang — onto a <strong>real medical dataset</strong>: UCI <em>Diabetes 130-US Hospitals</em>, 101,766 hospital encounters, predicting 30-day readmission. It’s a genuinely hard, heterogeneous, ~11%-imbalanced clinical benchmark.</p>
<p>Held-out test <strong>AUC went 0.550 → 0.587 → 0.611</strong> as I scaled the data and ran a validation-only hyperparameter sweep. Post-hoc <strong>Platt calibration</strong> then moved the probabilistic metrics from <em>worse than the do-nothing baseline</em> to <em>better than it</em> — Brier 0.100 → 0.098, log-loss 0.391 → 0.344, calibration error 0.031 → <strong>0.010</strong>.</p>
<p><strong>The honest headline: none of ABNG’s core architecture changed.</strong> The gains came from a leakage-free evaluation protocol, tuning ABNG’s <em>configuration</em>, and a harness-side calibration step. Determinism and the audit chain held throughout — precisely <em>because</em> the core was left untouched. AUC 0.611 is modest (strong published models reach ~0.64–0.68); this is an honest baseline, not a record. And ABNG remains experimental — I’m still iterating.</p>
</div>
</div>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Prototype disclaimer
</div>
</div>
<div class="callout-body-container callout-body">
<p><strong>ABNG is a research-stage prototype</strong>, built on <strong>CJC-Lang</strong>, a research compiler that is itself pre-1.0. Both are under active development. The numbers here are a single, fully-reproducible snapshot from one experiment campaign — not a validated benchmark, not deployed, not peer-reviewed. Read them as honest exploratory engineering.</p>
</div>
</div>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Where this sits in the ABNG series
</div>
</div>
<div class="callout-body-container callout-body">
<ul>
<li><a href="../../../blog/posts/abng-architecture/index.html">ABNG: Treating Belief States as First-Class Citizens</a> — the architecture.</li>
<li><a href="../../../blog/posts/abng-deterministic-systems/index.html">Deterministic and Auditable Neural Systems</a> — the audit chain + determinism contract.</li>
<li><a href="../../../blog/posts/abng-benchmarks/index.html">Benchmarks Part I</a> and <a href="../../../blog/posts/abng-benchmarks-part-2/index.html">Part II</a> — the controlled benchmark deep-dive.</li>
<li><strong>This post</strong> — the first time ABNG meets a <em>real, messy, external</em> dataset rather than a controlled benchmark.</li>
</ul>
</div>
</div>
<section id="why-a-real-dataset-and-why-this-one" class="level2">
<h2 class="anchored" data-anchor-id="why-a-real-dataset-and-why-this-one">1. Why a real dataset, and why this one</h2>
<p>The earlier ABNG benchmark posts measured the architecture on <em>controlled</em> problems — synthetic PINNs, designed routing distributions, scripted drift scenarios. Those answer “does the mechanism work?” They do not answer “does it survive contact with real, messy data?”</p>
<p>So this post takes ABNG to a dataset I did not design: <strong>UCI Diabetes 130-US Hospitals for Years 1999–2008</strong>. It is a genuine, widely-cited clinical benchmark — de-identified records of <strong>101,766 hospital encounters of diabetic patients across 130 US hospitals over ten years</strong>, introduced by Strack et al.&nbsp;(2014). The prediction task is the published one: will this patient be <strong>readmitted within 30 days</strong>?</p>
<p>It is a deliberately unforgiving choice on three axes at once:</p>
<ul>
<li><strong>It is genuinely complex.</strong> 50 heterogeneous columns: demographics, <strong>23 medication columns</strong>, <strong>three ICD-9 diagnosis codes with 700+ distinct values each</strong> (very high cardinality), lab results, admission / discharge / source codes, visit counts. Missingness is heavy — <code>weight</code> is ~97% missing; <code>payer_code</code> and <code>medical_specialty</code> are largely missing.</li>
<li><strong>It is class-imbalanced.</strong> Only <strong>~11.2%</strong> of encounters are 30-day readmissions.</li>
<li><strong>It is a hard target.</strong> Whether a patient is readmitted depends on post-discharge factors — social support, follow-up care, medication adherence — that simply are not in the data. That is <em>irreducible</em> noise. It is why strong published models on this task top out around <strong>AUC 0.64–0.68</strong>; nothing reaches 0.9 here, and a model that claimed to would be suspect.</li>
</ul>
<p>That last point matters for reading everything below. On a dataset like this, <strong>a modest honest number is the correct outcome</strong>, and inflated numbers are the warning sign.</p>
</section>
<section id="the-evaluation-protocol-the-part-that-has-to-be-right" class="level2">
<h2 class="anchored" data-anchor-id="the-evaluation-protocol-the-part-that-has-to-be-right">2. The evaluation protocol (the part that has to be right)</h2>
<p>Before any result is worth reporting, the protocol has to be leakage-free. ABNG’s harness for this dataset does the following, and the discipline here is more important than any single metric:</p>
<ul>
<li><strong>The feature transform is fit on the training split only.</strong> Category vocabularies, one-hot widths, routing-feature selection — all learned from train rows, never from validation or test.</li>
<li><strong>A 70 / 15 / 20-style stratified split</strong> into train / validation / test, stratified on the readmission label so all three splits share the ~11% positive rate.</li>
<li><strong>Every tuning decision is made on validation, never on test.</strong> The decision threshold and the model hyperparameters are both selected by validation score; the test split is touched exactly once, for the final reported numbers.</li>
<li><strong>Deterministic throughout.</strong> Fixed seed (42), deterministic SplitMix64 shuffles, deterministic reductions. Re-running reproduces every digit (see §7).</li>
</ul>
<p>This is unglamorous, and it is the whole game. The most common way to get a flattering number on an imbalanced dataset is to let the test set leak into a tuning decision; the protocol above is designed so that cannot happen.</p>
</section>
<section id="what-changed-to-reach-these-numbers-honestly" class="level2">
<h2 class="anchored" data-anchor-id="what-changed-to-reach-these-numbers-honestly">3. What changed to reach these numbers — honestly</h2>
<p>Here is the question I most want to answer straight, because it would be easy to dress it up: <strong>what architectural changes did ABNG need to work at these numbers?</strong></p>
<p><strong>None. ABNG’s core architecture did not change.</strong></p>
<p>The belief graph, the per-leaf Bayesian Linear Regression (BLR) heads with their closed-form conjugate updates, the SHA-256-hash-chained audit log, the deterministic execution contract — all of it is exactly as described in the earlier architecture post. Not one core data structure or update rule was modified to produce the results below.</p>
<p>What <em>did</em> change lives entirely <strong>around</strong> ABNG, not <strong>inside</strong> it:</p>
<table class="table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Lever</th>
<th>What it is</th>
<th>Layer</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>More data</td>
<td>2,000 → 20,000-row stratified sub-sample</td>
<td>Experiment design</td>
</tr>
<tr class="even">
<td>Routing depth</td>
<td><code>K_ROUTING</code> tuned by validation sweep (see §5)</td>
<td>ABNG <em>configuration</em></td>
</tr>
<tr class="odd">
<td>BLR prior strength</td>
<td>Prior precision tuned by the same sweep</td>
<td>ABNG <em>configuration</em></td>
</tr>
<tr class="even">
<td>Decision threshold</td>
<td>Tuned on validation, not left at 0.5</td>
<td>Harness post-processing</td>
</tr>
<tr class="odd">
<td>Probability calibration</td>
<td>Post-hoc Platt scaling (see §4)</td>
<td>Harness post-processing</td>
</tr>
</tbody>
</table>
<p><code>K_ROUTING</code> and the BLR prior are <em>configuration knobs</em> — dials ABNG already exposes, not new mechanisms. The threshold and the calibration are <em>harness-side post-processing</em> — they read ABNG’s outputs and never touch its state.</p>
<p>This is not a disappointing answer; it is the point. <strong>Determinism and auditability survived this entire experiment campaign precisely because the core was untouched.</strong> If “improving the metrics” had meant rewriting the BLR update or adding a new audit event kind, every replay guarantee and every hash-chain canary would have had to be re-validated. Instead, the 28 SHA-256 determinism canaries never moved, because nothing they cover was edited. The honest engineering lesson: <em>reach for evaluation rigor and post-processing before you reach for architecture surgery.</em></p>
</section>
<section id="the-result-and-what-calibration-fixed" class="level2">
<h2 class="anchored" data-anchor-id="the-result-and-what-calibration-fixed">4. The result — and what calibration fixed</h2>
<section id="ranking-a-real-but-modest-signal" class="level3">
<h3 class="anchored" data-anchor-id="ranking-a-real-but-modest-signal">4.1 Ranking: a real but modest signal</h3>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://adamezzat1.github.io/blog/posts/abng-diabetes-readmission/figures/auc_trajectory.png" class="img-fluid figure-img"></p>
<figcaption class="figure-caption">Held-out test AUC across data scale and tuning. The line stays clearly above chance and clearly below the strong-published-model band.</figcaption>
</figure>
</div>
<p>Held-out test <strong>AUC climbed 0.550 → 0.587 → 0.611</strong>: 2,000 rows with default settings, then 20,000 rows, then 20,000 rows with the validation-tuned configuration. That is real signal — the model orders patients by readmission risk better than chance — but it is <strong>modest</strong>, and it sits <em>below</em> the ~0.64–0.68 band where strong published models on this task live. I am reporting it as an honest baseline, not a result to headline.</p>
</section>
<section id="probabilities-the-uncomfortable-part-and-the-fix" class="level3">
<h3 class="anchored" data-anchor-id="probabilities-the-uncomfortable-part-and-the-fix">4.2 Probabilities: the uncomfortable part, and the fix</h3>
<p>AUC only measures <em>ranking</em>. The probabilistic metrics — Brier score and log-loss (NLL) — measure whether the predicted probabilities themselves are any good. And before calibration, they were not:</p>
<ul>
<li>Raw <strong>Brier 0.100</strong> — essentially <em>tied</em> with a do-nothing predictor that outputs the 11.2% base rate for everyone (its Brier is 0.099).</li>
<li>Raw <strong>NLL 0.391</strong> — actually <strong>worse</strong> than that base-rate predictor (0.351).</li>
</ul>
<p>The cause is structural: ABNG’s BLR head is <em>linear regression</em>. Linear regression has no link function — its output is unbounded and is not a calibrated probability. On 11%-imbalanced data its predictions cluster in a narrow band near the base rate, and the occasional out-of-range prediction, clamped into [0, 1], wrecks the log-loss.</p>
<p>The fix is <strong>post-hoc Platt calibration</strong>: fit a one-feature logistic — <code>σ(a·raw + b)</code> — on the <em>validation</em> predictions, then apply it to test. Two parameters, fit by Newton’s method, entirely harness-side.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://adamezzat1.github.io/blog/posts/abng-diabetes-readmission/figures/calibration.png" class="img-fluid figure-img"></p>
<figcaption class="figure-caption">Post-hoc Platt calibration. Raw bars sit at or above the do-nothing base-rate line; calibrated bars sit below it. Calibration error drops by 3×.</figcaption>
</figure>
</div>
<p>The effect is exactly what the theory predicts:</p>
<table class="table">
<thead>
<tr class="header">
<th>Metric</th>
<th>Raw</th>
<th><strong>Calibrated</strong></th>
<th>Base-rate baseline</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>AUC</td>
<td>0.611</td>
<td><strong>0.611</strong></td>
<td>0.500</td>
</tr>
<tr class="even">
<td>Brier</td>
<td>0.100</td>
<td><strong>0.098</strong></td>
<td>0.099</td>
</tr>
<tr class="odd">
<td>Log-loss (NLL)</td>
<td>0.391</td>
<td><strong>0.344</strong></td>
<td>0.351</td>
</tr>
<tr class="even">
<td>Calibration error (ECE)</td>
<td>0.031</td>
<td><strong>0.010</strong></td>
<td>0</td>
</tr>
</tbody>
</table>
<p><strong>AUC is bit-identical</strong> — calibration is a monotonic transform, so it <em>cannot</em> change the ranking, and it didn’t. What it did change is the probabilities: Brier and NLL both crossed from losing to the base-rate predictor to <strong>beating</strong> it, and calibration error fell to <strong>0.010</strong>. The model’s probability outputs now genuinely carry information beyond “predict the average.”</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
A calibration honesty note
</div>
</div>
<div class="callout-body-container callout-body">
<p>In an earlier draft of this analysis I described the <em>raw</em> ECE of 0.031 as “well-calibrated — a genuine strength.” That was wrong, and worth flagging. Raw ECE looked good only because the predictions were clustered into one or two histogram bins — Brier and NLL, which clustering cannot flatter, told the real story. <strong>After</strong> Platt calibration all three probabilistic metrics agree and all three beat the baseline. Calibration didn’t just improve numbers; it made the numbers <em>consistent with each other</em>. That internal consistency is what makes the result defensible.</p>
</div>
</div>
</section>
<section id="the-operating-point" class="level3">
<h3 class="anchored" data-anchor-id="the-operating-point">4.3 The operating point</h3>
<p>At the validation-tuned decision threshold, the tuned + calibrated model reaches <strong>balanced accuracy ≈ 0.58</strong> and <strong>F1 ≈ 0.24</strong> on the held-out test set.</p>
<p>A word on what is <em>not</em> reported as a headline: <strong>raw accuracy</strong>. On an 11%-positive dataset, a model that predicts “no readmission” for everyone scores ~89% accuracy with zero learning. Accuracy is the <em>wrong</em> yardstick here — it rewards the majority class — which is exactly why this post leads with AUC, balanced accuracy, and the probabilistic metrics instead.</p>
</section>
</section>
<section id="the-hyperparameter-sweep-measure-dont-assume" class="level2">
<h2 class="anchored" data-anchor-id="the-hyperparameter-sweep-measure-dont-assume">5. The hyperparameter sweep — measure, don’t assume</h2>
<p>The routing depth <code>K_ROUTING</code> controls how deep the belief tree’s routing partition goes — <code>K</code> routing features means <code>4^K</code> leaves, each with its own BLR head. My prior assumption, baked into earlier defaults, was that <em>more</em> routing features (K = 4) would help.</p>
<p>A validation sweep over <code>K ∈ {2, 3, 4}</code> and three BLR prior strengths said otherwise:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://adamezzat1.github.io/blog/posts/abng-diabetes-readmission/figures/krouting_sweep.png" class="img-fluid figure-img"></p>
<figcaption class="figure-caption">K_ROUTING × BLR-prior sweep, validation AUC. Shallower routing (K=2) wins clearly.</figcaption>
</figure>
</div>
<p><strong>K = 2 won, decisively.</strong> The mechanism is a data-starvation effect: with ~14,000 training rows, K = 2 gives 16 leaves (~875 rows each), while K = 4 gives 256 leaves (~55 rows each) — and each leaf trains its <em>own</em> BLR. Deeper routing buys “specialization” but starves every leaf of the data it needs. On this dataset, shallow wins. (Stronger prior regularization also helped, monotonically — unsurprising on a noisy target.)</p>
<p>This is a small but real instance of the discipline the whole project runs on: the assumption was wrong, the sweep was cheap, and the measurement settled it.</p>
</section>
<section id="what-abng-features-held-up-on-real-data" class="level2">
<h2 class="anchored" data-anchor-id="what-abng-features-held-up-on-real-data">6. What ABNG features held up on real data</h2>
<p>The metrics are modest, so I will not claim ABNG’s architecture produced a great model — it produced an honest, weak-but-real one. What I <em>can</em> report is which of ABNG’s properties <strong>survived contact with a messy real dataset</strong>:</p>
<ul>
<li><strong>Determinism held end-to-end.</strong> Two independent runs of the full pipeline — fit, train on real data, evaluate, calibrate — produced <strong>bit-identical</strong> metrics, despite a 3× spread in wall-clock time between them (§7). On most ML stacks, “re-run it” gives approximately the same number; here it gives the <em>same bytes</em>.</li>
<li><strong>The audit chain stayed intact.</strong> Every training step appends to a SHA-256 hash chain. Across the full 101,766-row dataset and every experiment variant, the chain verified — auditability is not a toy-scale property that broke on real data.</li>
<li><strong>The routing partition did real work.</strong> The K-sweep confirms the per-leaf structure is functional — different routing depths give measurably different models, and the partition is interpretable (you can read which leaf a patient routed to).</li>
<li><strong>The BLR heads gave a <em>probabilistic</em> output to fix.</strong> Because each leaf is a Bayesian regressor rather than a bare classifier, there was a real-valued score for calibration to act on. A model with no probabilistic output would have had nothing for Platt scaling to repair.</li>
</ul>
<p>In short: the architecture’s <em>guarantees</em> — determinism, auditability — held up perfectly; its <em>predictive strength</em> on this hard task is honestly modest and is the thing still being worked on.</p>
</section>
<section id="reproducibility" class="level2">
<h2 class="anchored" data-anchor-id="reproducibility">7. Reproducibility</h2>
<p>The result is deterministic. It was re-run from scratch to confirm:</p>
<pre class="text"><code>Run 1:  finished in 30.0 s   AUC 0.6107  Brier 0.0980  NLL 0.3435  ECE 0.0101
Run 2:  finished in 90.9 s   AUC 0.6107  Brier 0.0980  NLL 0.3435  ECE 0.0101</code></pre>
<p>The wall-clock differs 3× (machine contention); <strong>every metric digit is identical</strong>, and so is the fitted Platt <code>(a, b) = (4.2946, −2.6183)</code>. That is the determinism contract working: the result is a pure function of (code, seed, data), decoupled from timing.</p>
<div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb2-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># From a clean checkout of the CJC-Lang repository:</span></span>
<span id="cb2-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">git</span> clone https://github.com/AdamEzzat1/CJC.git <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">&amp;&amp;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">cd</span> CJC</span>
<span id="cb2-3"></span>
<span id="cb2-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># The Diabetes-130 experiment harness (dataset is untracked; fetch</span></span>
<span id="cb2-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># UCI dataset 296 into tests/data/diabetes_130/):</span></span>
<span id="cb2-6"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cargo</span> test <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--test</span> abng <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--release</span> diabetes130_calibrated <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb2-7">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--ignored</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--nocapture</span></span></code></pre></div>
<p>Build fingerprint: Rust 1.91.1 (stable, LLVM 21.1.2), seed 42, 20,000-row stratified sub-sample, n_test = 3,002. Figures plotted with Python 3.11 / matplotlib 3.9.</p>
</section>
<section id="honest-limitations" class="level2">
<h2 class="anchored" data-anchor-id="honest-limitations">8. Honest limitations</h2>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
What this result is <em>not</em>
</div>
</div>
<div class="callout-body-container callout-body">
<ul>
<li><strong>Not a record.</strong> AUC 0.611 is below the ~0.64–0.68 range of strong published Diabetes-130 models. It is an honest baseline.</li>
<li><strong>Single seed.</strong> All numbers are seed 42. A multi-seed sweep to quantify variance is pending — until it lands, treat the third decimal as indicative, not pinned.</li>
<li><strong>A 20,000-row sub-sample</strong>, not the full 101,766 rows. The full-dataset run is future work.</li>
<li><strong>A linear model.</strong> ABNG’s BLR heads are linear; calibration fixes the <em>probabilities</em> but not the <em>ranking ceiling</em>. A nonlinear MLP-leaf-head variant is the next experiment — it may lift AUC, or may need a lot of tuning.</li>
<li><strong>Not deployed, not validated.</strong> This is exploratory engineering.</li>
</ul>
</div>
</div>
</section>
<section id="still-experimenting" class="level2">
<h2 class="anchored" data-anchor-id="still-experimenting">9. Still experimenting</h2>
<p>ABNG is, and is labelled, <strong>experimental</strong> — and this dataset is one stop on an ongoing campaign, not a finish line. Currently on the bench:</p>
<ul>
<li><strong>Nonlinear leaf heads.</strong> Swapping the linear BLR leaf for an MLP leaf head (a capability ABNG already has) to see whether the <em>ranking</em> ceiling moves — the lever calibration provably cannot touch.</li>
<li><strong>The full 101,766-row run</strong> at the tuned configuration, for a headline number on all the data.</li>
<li><strong>Multi-seed variance</strong> — running the pipeline across many seeds to report mean ± spread instead of a single point.</li>
</ul>
<p>Each of these is the kind of measured, leakage-free, deterministic experiment this post is built on. If they move the numbers, a follow-up will say so honestly; if they don’t, it will say that too.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
The honest summary
</div>
</div>
<div class="callout-body-container callout-body">
<p>On a real, hard, imbalanced clinical dataset, ABNG produced a <strong>modest but genuine</strong> result — ranking meaningfully above chance, and, after calibration, probability estimates that beat the do-nothing baseline on every measure. It got there <strong>without any change to its core architecture</strong>: rigorous evaluation methodology, configuration tuning, and post-hoc calibration did the work, and the determinism and auditability guarantees held throughout because the core was never touched.</p>
<p>That is the result I can defend: not a strong model yet, but an honest one, fully reproducible, with a clear and unembellished account of how it was reached and where it falls short.</p>
</div>
</div>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>ABNG lives at <a href="https://github.com/AdamEzzat1/CJC"><code>crates/cjc-abng/</code></a> in the CJC-Lang repository; the Diabetes-130 harness is in <code>tests/abng/dataset_a_diabetes130.rs</code>. This post is part of the ongoing ABNG series — see the architecture and determinism posts for the foundations the results above rest on.</p>
</div>
</div>


</section>

 ]]></description>
  <category>CJC-Lang</category>
  <category>ABNG</category>
  <category>medical</category>
  <category>diabetes</category>
  <category>calibration</category>
  <category>experimental</category>
  <guid>https://adamezzat1.github.io/blog/posts/abng-diabetes-readmission/index.html</guid>
  <pubDate>Mon, 18 May 2026 07:00:00 GMT</pubDate>
</item>
<item>
  <title>Gibran v0.1.0: Natural-Language Analytics Without an LLM</title>
  <dc:creator>Adam Ezzat</dc:creator>
  <link>https://adamezzat1.github.io/blog/posts/gibran-v0.1.0/index.html</link>
  <description><![CDATA[ 



<div class="reading-time">
🕑 ~25 min read
</div>
<div class="stats-row">
<div class="stat">
<span class="number">26</span><span class="stat-label">NL patterns</span>
</div>
<div class="stat">
<span class="number">25</span><span class="stat-label">metric primitives</span>
</div>
<div class="stat">
<span class="number">4</span><span class="stat-label">engine adapters</span>
</div>
<div class="stat">
<span class="number">764</span><span class="stat-label">tests passing</span>
</div>
</div>
<div class="callout callout-style-simple callout-tip no-icon callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-title-container flex-fill">
TL;DR
</div>
</div>
<div class="callout-body-container callout-body">
<p>Gibran is a governed analytics layer with a natural-language interface — but the NL is <strong>pattern templates</strong>, not an LLM. If a question can’t be resolved against your real catalog, gibran says “I don’t know.” It never invents. v0.1.0 ships 26 NL patterns, 25 metric primitives (including cohort retention, funnel, period-over-period, percentile), four database engine adapters (DuckDB / Postgres / Snowflake / BigQuery), and an optional local React UI. <code>pip install gibran[ui]</code>, <code>gibran init --sample</code>, ask questions in English, get governance-correct SQL.</p>
</div>
</div>
<section id="why-a-non-llm-nl-layer" class="level2">
<h2 class="anchored" data-anchor-id="why-a-non-llm-nl-layer">Why a Non-LLM NL Layer</h2>
<p>Here’s the thing about LLM-based text-to-SQL: it works most of the time, fails some of the time, and you can’t always tell which mode you’re in. When a model hallucinates a column name that doesn’t exist, you usually find out at execution time — sometimes silently, when the model invents <em>similar</em> names that happen to compile but return wrong data.</p>
<p>For analytics, “wrong but plausible” is the worst possible failure mode. If the dashboard shows <code>gross_revenue</code> when the user asked for <code>net_revenue</code>, and both columns exist, no error is raised — the analyst just makes a decision on the wrong number. Models that are 95% accurate fail in exactly this way the other 5% of the time, and that 5% is statistically guaranteed to hit a load-bearing metric on a board deck eventually.</p>
<p>Gibran’s bet: trade NL <em>breadth</em> for hard <em>correctness guarantees</em>. The natural-language layer is a small set of regex patterns that extract slots, and <strong>every slot is resolved against your real <code>AllowedSchema</code> before becoming part of the SQL</strong>. If “biggest spenders” doesn’t match any registered metric or dimension, the question fails to parse — gibran says “I don’t know how to answer that” and exits.</p>
<p>The trade is real. Gibran can’t answer arbitrarily phrased questions. It <em>can</em> answer 26 well-defined question shapes, deterministically, with zero risk of inventing references.</p>
<p>Here’s the load-bearing line of code, in <a href="https://github.com/AdamEzzat1/gibran/blob/master/src/gibran/nl/patterns.py">src/gibran/nl/patterns.py</a>:</p>
<div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">def</span> top_n_by_metric(m: re.Match, schema: AllowedSchema) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>:</span>
<span id="cb1-2">    n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(m.group(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb1-3">    dim_id <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> _resolve_dimension(m.group(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>), schema)</span>
<span id="cb1-4">    metric_id <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> _resolve_metric(m.group(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>), schema)</span>
<span id="cb1-5">    <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">not</span> dim_id <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">or</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">not</span> metric_id:</span>
<span id="cb1-6">        <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">raise</span> NoMatch()       <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># &lt;-- THE GUARANTEE</span></span>
<span id="cb1-7">    <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">return</span> {</span>
<span id="cb1-8">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"source"</span>: schema.source_id,</span>
<span id="cb1-9">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"metrics"</span>: [metric_id],</span>
<span id="cb1-10">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dimensions"</span>: [{<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"id"</span>: dim_id}],</span>
<span id="cb1-11">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"order_by"</span>: [{<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"key"</span>: metric_id, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"direction"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"desc"</span>}],</span>
<span id="cb1-12">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"limit"</span>: n,</span>
<span id="cb1-13">    }</span></code></pre></div>
<p>If <code>_resolve_dimension("biggest spenders", schema)</code> returns <code>None</code> because no such dimension exists, <code>NoMatch</code> is raised. The intent dict never includes the unresolved name. There is no code path that produces SQL referencing an unresolved slot. The guarantee is <em>structural</em>, not statistical.</p>
</section>
<section id="the-26-nl-patterns" class="level2">
<h2 class="anchored" data-anchor-id="the-26-nl-patterns">The 26 NL Patterns</h2>
<p>Each pattern is a regex + a builder function. The matcher walks them in order; the first one whose regex matches AND whose slots resolve wins. Here’s the full list with the question shape each one handles:</p>
<table class="table">
<colgroup>
<col style="width: 50%">
<col style="width: 50%">
</colgroup>
<thead>
<tr class="header">
<th>Pattern</th>
<th>Example question</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>top_n_with_having</code></td>
<td>top 5 region by revenue where order_count &gt; 100</td>
</tr>
<tr class="even">
<td><code>top_n_by_metric</code></td>
<td>top 5 region by gross revenue</td>
</tr>
<tr class="odd">
<td><code>bottom_n_by_metric</code></td>
<td>bottom 3 customers by churn rate</td>
</tr>
<tr class="even">
<td><code>metric_by_type_keyword</code></td>
<td>average order size</td>
</tr>
<tr class="odd">
<td><code>metric_by_grain</code></td>
<td>revenue by month</td>
</tr>
<tr class="even">
<td><code>metric_over_time</code></td>
<td>revenue over time</td>
</tr>
<tr class="odd">
<td><code>metric_by_dim_and_grain</code></td>
<td>revenue by region by week</td>
</tr>
<tr class="even">
<td><code>metric_by_two_dims</code></td>
<td>revenue by region and status</td>
</tr>
<tr class="odd">
<td><code>multi_metric</code></td>
<td>revenue and order count by month</td>
</tr>
<tr class="even">
<td><code>metric_by_dim</code></td>
<td>revenue by region</td>
</tr>
<tr class="odd">
<td><code>metric_in_period</code></td>
<td>revenue in 2026</td>
</tr>
<tr class="even">
<td><code>metric_in_date_range</code></td>
<td>revenue between 2026-01-01 and 2026-03-31</td>
</tr>
<tr class="odd">
<td><code>metric_this_period</code></td>
<td>revenue this quarter</td>
</tr>
<tr class="even">
<td><code>metric_last_n_period</code></td>
<td>revenue last 30 days</td>
</tr>
<tr class="odd">
<td><code>count_with_condition</code></td>
<td>count of paid orders</td>
</tr>
<tr class="even">
<td><code>count_of_thing</code></td>
<td>how many orders</td>
</tr>
<tr class="odd">
<td><code>metric_excluding_value</code></td>
<td>revenue excluding refunded orders</td>
</tr>
<tr class="even">
<td><code>metric_as_percent_of</code></td>
<td>west revenue as percent of total revenue</td>
</tr>
<tr class="odd">
<td><code>metric_filter_compound</code></td>
<td>revenue for west and paid</td>
</tr>
<tr class="even">
<td><code>metric_where_between</code></td>
<td>revenue where amount between 50 and 500</td>
</tr>
<tr class="odd">
<td><code>metric_where</code></td>
<td>revenue where status = paid</td>
</tr>
<tr class="even">
<td><code>metric_filtered_by_value</code></td>
<td>revenue for west</td>
</tr>
<tr class="odd">
<td><code>metric_anomalies</code></td>
<td>show me anomalies in revenue</td>
</tr>
<tr class="even">
<td><code>metric_period_over_period</code></td>
<td>revenue this month vs last month</td>
</tr>
<tr class="odd">
<td><code>metric_distribution</code></td>
<td>distribution of order amount</td>
</tr>
<tr class="even">
<td><code>single_metric</code></td>
<td>gross revenue</td>
</tr>
</tbody>
</table>
<p>Each one extracts typed slots, resolves them against your <code>AllowedSchema</code>, and produces a DSL intent that compiles to governance-correct SQL. The fall-through pattern <code>single_metric</code> is registered last, so more specific shapes always win first.</p>
<section id="pattern-dispatch-in-practice" class="level3">
<h3 class="anchored" data-anchor-id="pattern-dispatch-in-practice">Pattern dispatch in practice</h3>
<p>A user types “top 5 region by gross revenue” as the role <code>analyst_west</code> with attribute <code>region=west</code>. Here’s the sequence:</p>
<ol type="1">
<li><p><strong>Regex match</strong>: <code>top_n_by_metric</code> matches with groups <code>(5, "region", "gross revenue")</code>.</p></li>
<li><p><strong>Slot resolution</strong>:</p>
<ul>
<li><code>_resolve_dimension("region", schema)</code> returns <code>"orders.region"</code> (case-insensitive match against <code>display_name</code>).</li>
<li><code>_resolve_metric("gross revenue", schema)</code> returns <code>"gross_revenue"</code> (substring match against <code>display_name</code>).</li>
</ul></li>
<li><p><strong>Intent built</strong>:</p>
<div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode json code-with-copy"><code class="sourceCode json"><span id="cb2-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb2-2">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"source"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"orders"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb2-3">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"metrics"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">[</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gross_revenue"</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">]</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb2-4">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"dimensions"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">[</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"id"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"orders.region"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">]</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb2-5">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"order_by"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">[</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"key"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gross_revenue"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"direction"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"desc"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">]</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb2-6">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"limit"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span></span>
<span id="cb2-7"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div></li>
<li><p><strong>DSL compile</strong>: produces SQL with metric expressions resolved from the catalog.</p></li>
<li><p><strong>Governance evaluate</strong>: identity has policy <code>analyst_west_orders</code> with row filter <code>region = $attr.region</code>. The filter is <strong>injected into the SQL string</strong> as an additional WHERE clause:</p>
<div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode sql code-with-copy"><code class="sourceCode sql"><span id="cb3-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">SELECT</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">"region"</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">AS</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">"orders.region"</span>,</span>
<span id="cb3-2">       <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">SUM</span>(amount) <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">FILTER</span>(<span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">WHERE</span> status <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'paid'</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">AS</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">"gross_revenue"</span></span>
<span id="cb3-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">FROM</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">"orders"</span></span>
<span id="cb3-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">WHERE</span> (<span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">"region"</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'west'</span>)           <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-- ← injected by governance</span></span>
<span id="cb3-5"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">GROUP</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">BY</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb3-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">ORDER</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">BY</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">"gross_revenue"</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">DESC</span></span>
<span id="cb3-7"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">LIMIT</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span></span></code></pre></div></li>
<li><p><strong>Execute</strong>: returns one row, <code>('west', 100.00)</code>. The other regions are invisible to this identity — not because the application filters them out, but because the SQL itself never asked for them.</p></li>
</ol>
<p>That sixth step is the load-bearing thing about gibran. The governance filter is <strong>woven into the compiled SQL</strong>, not bolted on at the application layer. There’s no code path that returns rows the policy doesn’t allow, because there’s no code path that <em>queries</em> for them.</p>
</section>
<section id="what-happens-when-the-nl-doesnt-match" class="level3">
<h3 class="anchored" data-anchor-id="what-happens-when-the-nl-doesnt-match">What happens when the NL doesn’t match</h3>
<p>If the analyst types “what’s the meaning of life?”, every pattern’s regex fails to match, OR the regex matches but the slots don’t resolve. The runner returns <code>None</code>. The CLI prints:</p>
<pre><code>I don't know how to answer that.</code></pre>
<p>And exits with code 4. No SQL is generated. No execution happens. No audit row is written for a query that didn’t exist. The contract is honest: gibran covers a fixed, documented set of shapes, deterministically. Outside that set, it refuses.</p>
</section>
</section>
<section id="the-25-metric-primitives" class="level2">
<h2 class="anchored" data-anchor-id="the-25-metric-primitives">The 25 Metric Primitives</h2>
<p>The NL layer can only ask for things the metric vocabulary supports. Here’s the full vocabulary:</p>
<table class="table">
<colgroup>
<col style="width: 50%">
<col style="width: 50%">
</colgroup>
<thead>
<tr class="header">
<th>Primitive</th>
<th>What it does</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>count</code>, <code>count_distinct</code>, <code>count_distinct_approx</code></td>
<td>Row counts (exact, distinct, HLL-approximate)</td>
</tr>
<tr class="even">
<td><code>sum</code>, <code>avg</code>, <code>min</code>, <code>max</code></td>
<td>Standard aggregates</td>
</tr>
<tr class="odd">
<td><code>ratio</code>, <code>expression</code></td>
<td>Composed metrics: ratio of two metrics; arbitrary expression</td>
</tr>
<tr class="even">
<td><code>percentile</code>, <code>median</code></td>
<td>QUANTILE_CONT-based (configurable p; median is p=0.5 wrapper)</td>
</tr>
<tr class="odd">
<td><code>weighted_avg</code>, <code>mode</code></td>
<td>Weighted average; statistical mode</td>
</tr>
<tr class="even">
<td><code>variance</code>, <code>stddev_samp</code>, <code>stddev_pop</code></td>
<td>Variance + sample/population standard deviation</td>
</tr>
<tr class="odd">
<td><code>first_value</code>, <code>last_value</code></td>
<td>First / last value in an ordered series</td>
</tr>
<tr class="even">
<td><code>rolling_window</code></td>
<td>Window function: <code>SUM(col) OVER (... RANGE BETWEEN INTERVAL '28 days' PRECEDING AND CURRENT ROW)</code></td>
</tr>
<tr class="odd">
<td><code>period_over_period</code></td>
<td>Compose two time-windowed metrics with delta / ratio / pct_change</td>
</tr>
<tr class="even">
<td><code>cohort_retention</code></td>
<td>Multi-CTE: cohort table → retention by cohort_age → matrix view</td>
</tr>
<tr class="odd">
<td><code>funnel</code></td>
<td>Multi-CTE: per-step filter chain with conversion rates</td>
</tr>
<tr class="even">
<td><code>multi_stage_filter</code></td>
<td>Multi-CTE: entity-level filter chains (users who did X then Y)</td>
</tr>
<tr class="odd">
<td><code>cohort_filter</code></td>
<td>Entity filter by sub-query intersection (users in cohort A AND cohort B)</td>
</tr>
<tr class="even">
<td><code>anomaly_query</code></td>
<td>Anomaly detection as a query (vs.&nbsp;as a check rule)</td>
</tr>
</tbody>
</table>
<p>The advanced primitives are real multi-CTE SQL. Here’s what a cohort retention query compiles to:</p>
<div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode sql code-with-copy"><code class="sourceCode sql"><span id="cb5-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">WITH</span> cohorts <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">AS</span> (</span>
<span id="cb5-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">SELECT</span> customer_id,</span>
<span id="cb5-3">           DATE_TRUNC(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'month'</span>, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">MIN</span>(order_date)) <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">AS</span> cohort_month</span>
<span id="cb5-4">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">FROM</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">"orders"</span></span>
<span id="cb5-5">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">GROUP</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">BY</span> customer_id</span>
<span id="cb5-6">),</span>
<span id="cb5-7"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">events</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">AS</span> (</span>
<span id="cb5-8">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">SELECT</span> o.customer_id,</span>
<span id="cb5-9">           c.cohort_month,</span>
<span id="cb5-10">           DATE_DIFF(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'month'</span>, c.cohort_month, o.order_date) <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">AS</span> months_since_cohort</span>
<span id="cb5-11">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">FROM</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">"orders"</span> o</span>
<span id="cb5-12">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">JOIN</span> cohorts c <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">USING</span> (customer_id)</span>
<span id="cb5-13">)</span>
<span id="cb5-14"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">SELECT</span> cohort_month,</span>
<span id="cb5-15">       months_since_cohort,</span>
<span id="cb5-16">       <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">COUNT</span>(<span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">DISTINCT</span> customer_id) <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">AS</span> retained</span>
<span id="cb5-17"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">FROM</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">events</span></span>
<span id="cb5-18"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">GROUP</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">BY</span> cohort_month, months_since_cohort</span>
<span id="cb5-19"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">ORDER</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">BY</span> cohort_month, months_since_cohort</span></code></pre></div>
<p>That’s not a stored procedure or a Python aggregation — it’s a single SELECT that runs in DuckDB and returns the retention matrix as rows. The pattern matcher resolves the source + cohort dimension + retention dimension, the compiler emits the CTE chain, governance injects any row filter, and the result lands in the audit log like any other query.</p>
<p>Funnel is similar: each step is a CTE that filters the previous step’s entities by some condition, and the final SELECT computes step-by-step conversion rates. Four “shape primitives” (<code>cohort_retention</code>, <code>funnel</code>, <code>multi_stage_filter</code>, <code>cohort_filter</code>) get their own special-case branch in <code>compile_intent</code> because they produce whole-query CTE chains rather than the single-SELECT shape the simpler primitives use.</p>
</section>
<section id="governance-woven-in-not-bolted-on" class="level2">
<h2 class="anchored" data-anchor-id="governance-woven-in-not-bolted-on">Governance: Woven In, Not Bolted On</h2>
<p>Most metric layers treat access control as an HTTP-layer concern: an analyst hits an endpoint, the endpoint checks if their role can see the data, maybe rewrites the query. Gibran does it differently. The governance contract is:</p>
<blockquote class="blockquote">
<p>No SQL leaves the compiler without the role’s row filter AND-ed into the WHERE clause, and no column appears in the SELECT list without an explicit grant from the role’s policy.</p>
</blockquote>
<p>That contract is enforced by <code>DefaultGovernance.evaluate()</code>, which runs <strong>between compile and execute</strong> for every query — DSL or NL or raw SQL alike:</p>
<div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1">decision <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> governance.evaluate(</span>
<span id="cb6-2">    identity,</span>
<span id="cb6-3">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">frozenset</span>({source_id}),</span>
<span id="cb6-4">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">frozenset</span>(requested_columns),</span>
<span id="cb6-5">    metric_refs,</span>
<span id="cb6-6">)</span>
<span id="cb6-7"><span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">not</span> decision.allowed:</span>
<span id="cb6-8">    <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">return</span> _record_denied(...)</span>
<span id="cb6-9">rewritten <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> _inject_filter(sql, decision.injected_filter_sql) <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">if</span> decision.injected_filter_sql <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">else</span> sql</span></code></pre></div>
<p><code>_inject_filter</code> is a <code>sqlglot</code> AST rewrite — it parses the compiled SQL, adds the policy’s row filter as a top-level <code>AND</code> against the existing WHERE clause, and re-renders. The result is one SQL string that’s safe to execute.</p>
<section id="the-side-by-side-demo" class="level3">
<h3 class="anchored" data-anchor-id="the-side-by-side-demo">The side-by-side demo</h3>
<p>Same question, two roles. As <code>analyst_west</code> (<code>region=west</code>):</p>
<div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode sql code-with-copy"><code class="sourceCode sql"><span id="cb7-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">SELECT</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">"region"</span>, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">SUM</span>(amount) <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">FILTER</span>(<span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">WHERE</span> status <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'paid'</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">AS</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">"gross_revenue"</span></span>
<span id="cb7-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">FROM</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">"orders"</span></span>
<span id="cb7-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">WHERE</span> (<span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">"region"</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'west'</span>)           <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-- governance row filter</span></span>
<span id="cb7-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">GROUP</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">BY</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb7-5"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">ORDER</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">BY</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">"gross_revenue"</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">DESC</span></span>
<span id="cb7-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">LIMIT</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span></span></code></pre></div>
<p>Result: <code>[('west', 100.00)]</code> — only the analyst’s region.</p>
<p>As <code>admin</code> (break-glass role, no row filter):</p>
<div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode sql code-with-copy"><code class="sourceCode sql"><span id="cb8-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">SELECT</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">"region"</span>, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">SUM</span>(amount) <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">FILTER</span>(<span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">WHERE</span> status <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'paid'</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">AS</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">"gross_revenue"</span></span>
<span id="cb8-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">FROM</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">"orders"</span></span>
<span id="cb8-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">GROUP</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">BY</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb8-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">ORDER</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">BY</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">"gross_revenue"</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">DESC</span></span>
<span id="cb8-5"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">LIMIT</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span></span></code></pre></div>
<p>Result: <code>[('north', 300.00), ('east', 200.00), ('west', 100.00)]</code> — all three regions.</p>
<p>Same code, same NL prompt, same compiler. The SQL diverges based on the identity’s policy. <strong>There is no application code that filters out other regions for analyst_west</strong>; the SQL itself never asks for them. That’s the structural property: privilege escalation isn’t possible because there’s no boundary to escalate across.</p>
</section>
<section id="column-level-governance" class="level3">
<h3 class="anchored" data-anchor-id="column-level-governance">Column-level governance</h3>
<p>Policies can also forbid specific columns:</p>
<div class="sourceCode" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode yaml code-with-copy"><code class="sourceCode yaml"><span id="cb9-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">policies</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb9-2"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">id</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> analyst_west_orders</span></span>
<span id="cb9-3"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">role</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> analyst_west</span></span>
<span id="cb9-4"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">source</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> orders</span></span>
<span id="cb9-5"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">default_column_mode</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> allow</span></span>
<span id="cb9-6"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">column_grants</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb9-7"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">customer_email</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> deny</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">   # PII column the analyst can't read</span></span>
<span id="cb9-8"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">row_filter</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb9-9"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">op</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> eq</span></span>
<span id="cb9-10"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">column</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> region</span></span>
<span id="cb9-11"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">value</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb9-12"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">$attr</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> region</span></span></code></pre></div>
<p>If <code>analyst_west</code> writes a DSL query referencing <code>customer_email</code>, the compile path fails <em>before</em> SQL is emitted with a clear deny reason. The query log records the attempt (with <code>status=denied</code>) so PII-access denials are auditable.</p>
</section>
<section id="audit-log-redaction" class="level3">
<h3 class="anchored" data-anchor-id="audit-log-redaction">Audit log + redaction</h3>
<p>Every query — allowed, denied, errored — writes a row to <code>gibran_query_log</code>:</p>
<pre><code>query_id | user_id | role_id | nl_prompt | generated_sql | status | deny_reason | row_count | duration_ms | is_break_glass | created_at</code></pre>
<p>Filter literals against PII or restricted columns are <strong>redacted before insertion</strong>:</p>
<div class="sourceCode" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode sql code-with-copy"><code class="sourceCode sql"><span id="cb11-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-- The user ran:</span></span>
<span id="cb11-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">SELECT</span> order_id <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">FROM</span> orders <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">WHERE</span> customer_email <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'alice@example.com'</span></span>
<span id="cb11-3"></span>
<span id="cb11-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-- The audit log stores:</span></span>
<span id="cb11-5"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">SELECT</span> order_id <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">FROM</span> orders <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">WHERE</span> customer_email <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'&lt;redacted&gt;'</span></span></code></pre></div>
<p>This is a <code>sqlglot</code> walk over the SQL AST that finds binary comparisons (<code>=</code>, <code>!=</code>, <code>LIKE</code>, etc.), <code>IN</code> lists, and <code>BETWEEN</code> bounds where one side references a sensitive column, and replaces the literal value with <code>'&lt;redacted&gt;'</code>. The literal never reaches the audit table — so a future audit-log-reader (with whatever credentials) can never recover sensitive values from past queries.</p>
<p>Break-glass queries (admin role usage) are marked with <code>is_break_glass=TRUE</code> so elevated access is searchable. The UI’s Audit Log view renders break-glass rows with a red left border for visual review.</p>
</section>
</section>
<section id="four-engine-adapters" class="level2">
<h2 class="anchored" data-anchor-id="four-engine-adapters">Four Engine Adapters</h2>
<p>The compile path produces dialect-neutral SQL for the structural pieces (SELECT, WHERE, GROUP BY, window functions, CTEs). Where dialects diverge — interval syntax, <code>DATE_DIFF</code> shape, identifier quoting — gibran uses a per-dialect emitter registry.</p>
<table class="table">
<colgroup>
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
</colgroup>
<thead>
<tr class="header">
<th>Engine</th>
<th>Identifier quote</th>
<th><code>now()</code></th>
<th>Interval syntax</th>
<th><code>DATE_DIFF(seconds)</code></th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>DuckDB</td>
<td><code>"foo"</code></td>
<td><code>now()</code></td>
<td><code>INTERVAL '28 days'</code></td>
<td><code>DATE_DIFF('second', a, b)</code></td>
</tr>
<tr class="even">
<td>Postgres</td>
<td><code>"foo"</code></td>
<td><code>now()</code></td>
<td><code>INTERVAL '28 days'</code></td>
<td><code>CAST(EXTRACT(EPOCH FROM (b - a)) AS INTEGER)</code></td>
</tr>
<tr class="odd">
<td>Snowflake</td>
<td><code>"foo"</code></td>
<td><code>CURRENT_TIMESTAMP()</code></td>
<td><code>INTERVAL '28 days'</code></td>
<td><code>DATEDIFF('second', a, b)</code></td>
</tr>
<tr class="even">
<td>BigQuery</td>
<td><code>`foo`</code></td>
<td><code>CURRENT_TIMESTAMP()</code></td>
<td><code>INTERVAL 28 DAY</code></td>
<td><code>TIMESTAMP_DIFF(b, a, SECOND)</code></td>
</tr>
</tbody>
</table>
<p>The <code>ExecutionEngine</code> Protocol abstracts the connection, file-scan rendering, and SQL execution. Each engine has its own module under <code>src/gibran/execution/engines/</code>:</p>
<div class="sourceCode" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb12-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">class</span> ExecutionEngine(Protocol):</span>
<span id="cb12-2">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@property</span></span>
<span id="cb12-3">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">def</span> dialect(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> Dialect: ...</span>
<span id="cb12-4">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">def</span> file_scan_sql(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, source_type: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, uri: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>: ...</span>
<span id="cb12-5">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">def</span> execute(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, sql: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, params: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ()) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>: ...</span>
<span id="cb12-6">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">def</span> query(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, sql: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, params: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ()) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span>], <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>]]: ...</span>
<span id="cb12-7">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">def</span> fetchone(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, sql: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, params: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ()) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">tuple</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>: ...</span>
<span id="cb12-8">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">def</span> commit(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>: ...</span></code></pre></div>
<section id="verified-state" class="level3">
<h3 class="anchored" data-anchor-id="verified-state">Verified state</h3>
<table class="table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th>Engine</th>
<th>Code</th>
<th>Migrations</th>
<th>Verified end-to-end?</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>DuckDB</td>
<td>✅</td>
<td>11 migrations</td>
<td>✅</td>
</tr>
<tr class="even">
<td>Postgres</td>
<td>✅</td>
<td>11 translated (<code>JSON</code> → <code>JSONB</code>)</td>
<td>✅ via <code>pgserver</code> in CI — no Docker needed</td>
</tr>
<tr class="odd">
<td>Snowflake</td>
<td>✅</td>
<td>—</td>
<td>Code only (paid account needed for E2E)</td>
</tr>
<tr class="even">
<td>BigQuery</td>
<td>✅</td>
<td>—</td>
<td>Code only (billing-enabled GCP project needed for E2E)</td>
</tr>
</tbody>
</table>
<p>The Postgres integration tests auto-spin a real Postgres 16 binary via the <code>pgserver</code> Python package — every developer’s machine can run the full Postgres test suite without installing Docker or paying for a hosted instance.</p>
</section>
<section id="placeholder-translation" class="level3">
<h3 class="anchored" data-anchor-id="placeholder-translation">Placeholder translation</h3>
<p>Gibran’s internal SQL uses <code>?</code> placeholders everywhere (DuckDB style). For Postgres + BigQuery, which use <code>%s</code> (pyformat), there’s a small state-machine translator that walks the SQL string and replaces <code>?</code> outside string literals + comments + identifier quoting:</p>
<div class="sourceCode" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb13-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">def</span> translate_qmark_placeholders(sql: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb13-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Walks the SQL with a tiny state machine that tracks whether</span></span>
<span id="cb13-3">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># we're inside a single-quoted string, double-quoted identifier,</span></span>
<span id="cb13-4">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># or -- line comment. `?` only translates outside those scopes.</span></span>
<span id="cb13-5">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># `%` is always doubled (psycopg parameter-substitution escape).</span></span></code></pre></div>
<p>For Snowflake the connector supports <code>paramstyle = 'qmark'</code> natively, so no translation is needed.</p>
</section>
<section id="why-this-matters" class="level3">
<h3 class="anchored" data-anchor-id="why-this-matters">Why this matters</h3>
<p>The hard adoption blocker for gibran was “DuckDB-only” — most production analytics teams have Postgres or Snowflake or BigQuery. v0.1.0 ships the engine abstraction + the Postgres adapter fully verified. Snowflake and BigQuery have working adapter code but their integration tests are gated on credentials and skip by default; once you set <code>GIBRAN_SNOWFLAKE_URL</code> or <code>GIBRAN_BIGQUERY_PROJECT</code>, the same test suite runs against the real service.</p>
</section>
</section>
<section id="the-local-http-ui" class="level2">
<h2 class="anchored" data-anchor-id="the-local-http-ui">The Local HTTP UI</h2>
<p>Gibran’s CLI (<code>gibran query</code>, <code>gibran ask</code>, <code>gibran check</code>, <code>gibran sync</code>) is the primary surface. v0.1.0 adds a local HTTP UI as an optional install:</p>
<div class="sourceCode" id="cb14" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb14-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">pip</span> install <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'gibran[ui]'</span></span>
<span id="cb14-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">gibran</span> init <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--sample</span></span>
<span id="cb14-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">gibran</span> sync</span>
<span id="cb14-4"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">gibran</span> ui            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># opens http://127.0.0.1:&lt;port&gt; in your browser</span></span></code></pre></div>
<p>The UI is a FastAPI app serving 16 endpoints + a bundled React SPA. <strong>The UI introduces no new governance logic</strong> — every endpoint is a thin wrapper around an existing core function, and identity comes from the same resolver the CLI uses.</p>
<section id="five-views-ship" class="level3">
<h3 class="anchored" data-anchor-id="five-views-ship">Five views ship</h3>
<ol type="1">
<li><p><strong>Query Workbench</strong> — Three-pane: NL prompt → compiled SQL preview → result table. An “Examples” panel below the input shows auto-generated questions (templated from your real metric + dimension names). One-click runs them. Cmd/Ctrl+Enter runs.</p></li>
<li><p><strong>Catalog Browser</strong> — Tree of sources → columns / metrics / dimensions, with PII / restricted / public sensitivity badges. Drill into any source to see exactly which columns the current role can read.</p></li>
<li><p><strong>Audit Log</strong> — Filterable view of <code>gibran_query_log</code>. Non-admin identities see only their own queries (the analyst’s “what have I asked recently”). Admin / break-glass identities see all queries. Break-glass rows are rendered with a red left border. Cursor pagination.</p></li>
<li><p><strong>Source Health</strong> — Per-source quality + freshness status with recent rule outcomes. Powered by <code>gibran check</code>.</p></li>
<li><p><strong>Policy Visualizer</strong> — Admin-only side-by-side: pick two roles, see exactly what <code>AllowedSchema</code> each one resolves to. The killer demo moment: <code>analyst_west</code> (with <code>region=west</code> attribute) next to <code>admin</code> (break-glass, no scoping). The column lists and accessible metrics differ visibly.</p></li>
</ol>
</section>
<section id="identity-flow" class="level3">
<h3 class="anchored" data-anchor-id="identity-flow">Identity flow</h3>
<pre><code>Request comes in
  -&gt; middleware: extract identity from
       (a) X-Gibran-User / X-Gibran-Role / X-Gibran-Attrs headers (dev mode)
       (b) Authorization: Bearer &lt;jwt&gt; (prod mode, validated against JWKS)
  -&gt; resolve to IdentityContext (user_id, role_id, attributes)
  -&gt; if neither resolves: 401 Unauthorized
  -&gt; endpoint reads via Depends(current_identity)
  -&gt; NEVER reads identity from request body or path</code></pre>
<p>Dev mode (headers, default) is fine for <code>gibran ui</code> on <code>127.0.0.1</code>. For production, set <code>GIBRAN_UI_AUTH_MODE=jwt</code> and point <code>GIBRAN_UI_JWT_JWKS_URL</code> at your IdP. The CLI refuses to bind to a non-localhost host in dev mode — flipping to JWT mode is the only way to satisfy <code>--host 0.0.0.0</code>. Intentional friction.</p>
</section>
<section id="what-the-ui-is-not" class="level3">
<h3 class="anchored" data-anchor-id="what-the-ui-is-not">What the UI is not</h3>
<ul>
<li>Not a dashboarding tool (no saved layouts, no scheduled refreshes, no sharing — those are V0.2+).</li>
<li>Not a charting tool (the Workbench renders tables; chart support is a follow-up).</li>
<li>Not a full ETL platform (gibran reads source data; pipeline orchestration is upstream).</li>
</ul>
<p>Gibran is a <strong>query / governance / audit layer</strong>. The UI is what makes that layer usable by analysts who’d rather not write YAML.</p>
</section>
</section>
<section id="how-the-pieces-connect" class="level2">
<h2 class="anchored" data-anchor-id="how-the-pieces-connect">How the Pieces Connect</h2>
<pre><code>┌─────────────┐    ┌─────────────────────┐    ┌──────────────────┐
│  NL prompt  │ -&gt; │ Pattern matcher     │ -&gt; │ Resolved intent  │
└─────────────┘    │ (6 → 26 patterns)   │    │ (dict, validated │
                   │  Slot resolution    │    │  against         │
                   │  against schema     │    │  AllowedSchema)  │
                   └─────────────────────┘    └────────┬─────────┘
                                                       v
                              ┌──────────────────────────────┐
                              │ DSL compiler                 │
                              │  + metric primitive registry │
                              │  + shape primitive registry  │
                              │  -&gt; SQL string               │
                              └──────────────┬───────────────┘
                                             v
                              ┌──────────────────────────────┐
                              │ Governance.evaluate()        │
                              │  - role policy lookup        │
                              │  - column-grant check        │
                              │  - row-filter compilation    │
                              │  -&gt; SQL with WHERE injected  │
                              └──────────────┬───────────────┘
                                             v
                              ┌──────────────────────────────┐
                              │ ExecutionEngine              │
                              │  (DuckDB / Postgres / SF /BQ)│
                              │  -&gt; rows + columns           │
                              └──────────────┬───────────────┘
                                             v
                              ┌──────────────────────────────┐
                              │ Audit log: gibran_query_log  │
                              │  (redacted, break-glass flag,│
                              │   recorded for every attempt)│
                              └──────────────────────────────┘</code></pre>
<p>The seams between these stages are deliberate. Pattern → intent is JSON. Intent → SQL is a pure compiler function (cacheable). Governance is a single chokepoint everything passes through. The execution engine is a <code>Protocol</code> with four implementations. The audit log writes happen for every query, even ones that fail.</p>
</section>
<section id="what-ships" class="level2">
<h2 class="anchored" data-anchor-id="what-ships">What Ships</h2>
<div class="sourceCode" id="cb17" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb17-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">pip</span> install gibran               <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># CLI + core + DuckDB</span></span>
<span id="cb17-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">pip</span> install <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'gibran[ui]'</span>         <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># + FastAPI + uvicorn + bundled React SPA</span></span>
<span id="cb17-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">pip</span> install <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'gibran[postgres]'</span>   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># + psycopg</span></span>
<span id="cb17-4"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">pip</span> install <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'gibran[snowflake]'</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># + snowflake-connector-python</span></span>
<span id="cb17-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">pip</span> install <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'gibran[bigquery]'</span>   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># + google-cloud-bigquery</span></span></code></pre></div>
<p>The extras are additive — install what you need. The default install is DuckDB-only; the wheel is ~224 KB; no native dependencies beyond DuckDB itself.</p>
<table class="table">
<colgroup>
<col style="width: 50%">
<col style="width: 50%">
</colgroup>
<thead>
<tr class="header">
<th>Metric</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Wheel size</td>
<td>224 KB</td>
</tr>
<tr class="even">
<td>Source-only sdist size</td>
<td>567 KB</td>
</tr>
<tr class="odd">
<td>Tests passing</td>
<td>764</td>
</tr>
<tr class="even">
<td>Frontend bundle size</td>
<td>52 KB gzipped (well under the 200 KB budget)</td>
</tr>
<tr class="odd">
<td>NL patterns</td>
<td>26</td>
</tr>
<tr class="even">
<td>Metric primitives</td>
<td>25</td>
</tr>
<tr class="odd">
<td>Engine adapters</td>
<td>4 (DuckDB + Postgres verified end-to-end)</td>
</tr>
<tr class="even">
<td>Migrations</td>
<td>11 (all 11 translated to Postgres)</td>
</tr>
<tr class="odd">
<td>External runtime deps (default install)</td>
<td>DuckDB, pydantic, sqlglot, typer, pyjwt, pyyaml</td>
</tr>
</tbody>
</table>
</section>
<section id="whats-not-in-v0.1.0" class="level2">
<h2 class="anchored" data-anchor-id="whats-not-in-v0.1.0">What’s Not in v0.1.0</h2>
<p>I want to be honest about the limitations:</p>
<ul>
<li><strong>Snowflake + BigQuery adapters are code only.</strong> The integration tests skip without credentials. The adapters compile, the unit tests pass, but I haven’t actually executed <code>SELECT 1</code> against a paid Snowflake account or a billing-enabled GCP project.</li>
<li><strong>The UI ships 5 of 11 planned views.</strong> Materialization Status, Cache Inspector, Approval Queue, Access Anomaly Alerts, and Onboarding Tour are documented follow-ups. The corresponding backend endpoints exist for 3 of those 5 already.</li>
<li><strong>No Playwright E2E tests for the frontend.</strong> Vitest covers component logic (21 tests for the API client + IdentitySetup + QueryWorkbench), but full-browser smoke tests against a running server are deferred.</li>
<li><strong>Multi-tenancy is V0.2.</strong> Single-tenant deployment for v0.1.0.</li>
<li><strong>No charts.</strong> The Workbench renders result tables. Bar / line / scatter / pie are a 2-3 week add-on.</li>
<li><strong>No dashboards</strong> (composed multi-query layouts with refresh).</li>
<li><strong>Cross-process plan caching needs a shared store</strong> (Redis or similar). v0.1.0’s caches are per-process.</li>
</ul>
<p>These are all known. The roadmap (<code>ROADMAP.md</code>) tracks the prioritization.</p>
</section>
<section id="try-it" class="level2">
<h2 class="anchored" data-anchor-id="try-it">Try It</h2>
<p>Install + spin up the sample:</p>
<div class="sourceCode" id="cb18" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb18-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">pip</span> install <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'gibran[ui]'</span></span>
<span id="cb18-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">gibran</span> init <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--sample</span></span>
<span id="cb18-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">gibran</span> sync</span>
<span id="cb18-4"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">gibran</span> check</span>
<span id="cb18-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">gibran</span> ui</span></code></pre></div>
<p>That last command opens your browser to a local FastAPI server. On the first launch, set identity to <code>user=adam role=analyst_west attrs=region=west</code> and click <strong>Continue</strong>. The Workbench appears with an Examples panel.</p>
<p>Click “top 5 region by gross revenue” → hit Run → watch the SQL preview show <code>WHERE "region" = 'west'</code> injected by governance. Result: one row, just the west region.</p>
<p>Then click <strong>change identity</strong> in the sidebar, switch to <code>user=admin_user role=admin attrs=</code> (blank), and run the same question. The SQL preview no longer has the WHERE clause. Result: all three regions.</p>
<p>That side-by-side, in the same UI, in 30 seconds, is the whole pitch.</p>
<div class="sourceCode" id="cb19" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb19-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Or skip the UI and use the CLI directly:</span></span>
<span id="cb19-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">gibran</span> ask <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"top 5 region by gross revenue"</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb19-3">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--source</span> orders <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--role</span> analyst_west <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--attr</span> region=west</span></code></pre></div>
</section>
<section id="where-the-code-lives" class="level2">
<h2 class="anchored" data-anchor-id="where-the-code-lives">Where the Code Lives</h2>
<ul>
<li><strong>GitHub</strong>: <a href="https://github.com/AdamEzzat1/gibran" class="uri">https://github.com/AdamEzzat1/gibran</a></li>
<li><strong>PyPI</strong>: <a href="https://pypi.org/project/gibran/" class="uri">https://pypi.org/project/gibran/</a> (v0.1.0)</li>
<li><strong>Roadmap</strong>: <a href="https://github.com/AdamEzzat1/gibran/blob/master/ROADMAP.md">ROADMAP.md</a></li>
<li><strong>Phase 4 UI handoff</strong>: <a href="https://github.com/AdamEzzat1/gibran/blob/master/PHASE_4_UI_HANDOFF.md">PHASE_4_UI_HANDOFF.md</a></li>
<li><strong>Deferred follow-ups</strong>: <a href="https://github.com/AdamEzzat1/gibran/blob/master/docs/DEFERRED_FOLLOWUPS.md">docs/DEFERRED_FOLLOWUPS.md</a></li>
<li><strong>Demo script for the video</strong>: <a href="https://github.com/AdamEzzat1/gibran/blob/master/docs/DEMO_SCRIPT.md">docs/DEMO_SCRIPT.md</a></li>
</ul>
<p>MIT licensed. PRs welcome, especially if you have a real-world use case I haven’t thought about. The NL pattern list grows with user demand — if there’s a question shape that’s load-bearing for your team, file an issue.</p>
</section>
<section id="closing" class="level2">
<h2 class="anchored" data-anchor-id="closing">Closing</h2>
<p>The thesis is simple: there’s a class of analytics use case where “wrong but plausible” answers are catastrophic, and an LLM in the SQL emission path is structurally unable to give you the guarantee that wrong answers won’t happen. Pattern templates can give you that guarantee. They give up NL breadth in return.</p>
<p>Gibran v0.1.0 is the first release that’s complete enough to argue the thesis with code. 26 NL patterns covers about 70% of the question shapes a typical analyst types. The other 30% gets a clean “I don’t know” instead of a hallucination. The governance layer compiles into the SQL string so privilege escalation isn’t even a category of bug. The audit log is exhaustive and redacted. Four engine adapters mean it can plug into your existing warehouse instead of being a DuckDB toy.</p>
<p>If any of that sounds useful for what you’re building, install it and try it. If it’s missing a pattern or a primitive you actually need, the cost of adding one is roughly an afternoon. The code is short. The contract is honest.</p>


</section>

 ]]></description>
  <category>Gibran</category>
  <category>release</category>
  <category>analytics</category>
  <category>governance</category>
  <category>NL-to-SQL</category>
  <category>DuckDB</category>
  <category>Postgres</category>
  <guid>https://adamezzat1.github.io/blog/posts/gibran-v0.1.0/index.html</guid>
  <pubDate>Thu, 14 May 2026 07:00:00 GMT</pubDate>
</item>
<item>
  <title>ABNG: Treating Belief States as First-Class Citizens</title>
  <dc:creator>Adam Ezzat</dc:creator>
  <link>https://adamezzat1.github.io/blog/posts/abng-architecture/index.html</link>
  <description><![CDATA[ 



<div class="reading-time">
🕑 ~35 min read
</div>
<div class="callout callout-style-simple callout-tip no-icon callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-title-container flex-fill">
TL;DR
</div>
</div>
<div class="callout-body-container callout-body">
<p>ABNG (Adaptive Belief Network Graph) is an experimental architecture that treats Bayesian belief as an <em>architectural primitive</em>, not a bolt-on. Each node in an adaptive tree carries its own Bayesian Linear Regression posterior, routing is a traceable walk through learned bytes, and the graph itself adapts to evidence via six structural actions (Grow / Split / Merge / Prune / Compress / Freeze). This article covers <em>what</em> the architecture is and <em>why</em> the design choices fit together. The follow-up articles cover the determinism story (Part 2) and the empirical evidence (Part 3).</p>
</div>
</div>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Two prototype disclaimers up front
</div>
</div>
<div class="callout-body-container callout-body">
<p><strong>ABNG is a research-stage prototype</strong>, not a product. It has unit tests, property tests, fuzz coverage, a working CLI, and zero published benchmarks. It is also built on top of <strong>CJC-Lang</strong>, a research compiler that is itself still pre-1.0 — twenty-one Rust crates, roughly 96K lines of code, an actively evolving language definition. Anything ABNG inherits from the substrate inherits the substrate’s caveats. This piece is a design exploration meant for scrutiny, not a launch.</p>
</div>
</div>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
This is Part 1 of a three-part series
</div>
</div>
<div class="callout-body-container callout-body">
<ul>
<li><strong>Part 1 (this article):</strong> the architecture itself.</li>
<li><strong>Part 2 (<a href="../../../blog/posts/abng-deterministic-systems/index.html">Deterministic and Auditable Neural Systems</a>):</strong> the SHA-256 audit chain, replay semantics, and the determinism contract.</li>
<li><strong>Part 3 (<a href="../../../blog/posts/abng-benchmarks/index.html">Benchmarking Experimental Bayesian Graph Networks</a>):</strong> the demo catalog, scaling benchmarks, and honest limitations.</li>
</ul>
<p>Each part stands alone. Read in order for the full picture; read just Part 1 if you only care about the architectural shape; read Part 3 if you want the empirical evidence first.</p>
</div>
</div>
<section id="where-the-cracks-show" class="level2">
<h2 class="anchored" data-anchor-id="where-the-cracks-show">Where the cracks show</h2>
<p>There’s a recurring discomfort with most modern neural networks: they are confidently wrong without telling you, their decisions are hard to audit after the fact, and their compute graphs are fixed at design time even when the data isn’t. We have partial fixes for each — Bayesian layers, attention visualizations, mixture-of-experts, conformal wrappers — but they tend to be bolted onto architectures that did not begin with these properties in mind.</p>
<p>This article describes <strong>ABNG (Adaptive Belief Network Graph)</strong>, an experimental architecture that tries the opposite: design <em>from</em> those constraints — posterior belief at every node, traceable routing, structural adaptation — and see what falls out.</p>
<p>If you came looking for a “transformer killer,” this isn’t that. It’s a sketch of a different shape of computation, with all the unfinished edges that implies.</p>
<p>Modern architectures produce accurate point estimates, but they share a small set of recurring discomforts.</p>
<p><strong>Epistemic opacity.</strong> A model returns a number, and you have no principled way to know whether it is confident, guessing, or extrapolating beyond the data it saw at training time. Calibrated uncertainty exists as a layer you bolt on — temperature scaling, MC dropout, deep ensembles, conformal prediction — not as a property of the architecture itself.</p>
<p><strong>Structural rigidity.</strong> Once you fix a transformer’s depth, width, and head count, the compute graph is set. Mixture-of-Experts adds conditional routing, but the expert population is still fixed. The model does not grow, prune, or specialize in response to evidence; the engineer does, by retraining.</p>
<p><strong>Auditability and reproducibility tax.</strong> When a model trained over months produces a strange output, the lineage of that decision is essentially unrecoverable. And two “identical” training runs produce two different artifacts. Tolerable for research; painful for any system that must be replayed, audited, or proven correct. <em>These two cracks are covered in depth in Part 2 — this article focuses on the architectural shape.</em></p>
<p>None of these are catastrophic alone. Together, they constrain where you can use modern ML responsibly, and they have driven a small zoo of partial fixes: Bayesian neural networks, probabilistic graphical models, neural state-space models, liquid networks, conformal prediction, active inference. Each addresses one or two pieces of the problem.</p>
<p>ABNG is an attempt to take a coherent run at several of them at once, by treating <em>the model itself as an evolving belief state</em>.</p>
</section>
<section id="belief-states-used-precisely" class="level2">
<h2 class="anchored" data-anchor-id="belief-states-used-precisely">Belief states, used precisely</h2>
<p>“Belief state” gets used loosely in ML. The term appears in at least four incompatible senses across the literature: the posterior over latent variables in a probabilistic graphical model, a sufficient statistic for prediction in control theory, a state estimator in a POMDP, and the Bayesian posterior of a model conditional on data. The shared word across these senses is genuinely misleading.</p>
<p>ABNG uses the fourth sense — specifically, the Bayesian posterior of a Bayesian Linear Regression head at each routed leaf. Each node in an adaptive graph carries a small per-node MLP that produces features, with a <strong>Bayesian Linear Regression (BLR) head</strong> on top — a Normal-Inverse-Gamma posterior over (weights, noise variance) that is updated in closed form as data arrives, with no variational machinery required. Routing uses the same <code>Node4</code> / <code>Node16</code> / <code>Node48</code> / <code>Node256</code> child layout as Adaptive Radix Trees (Leis, Kemper &amp; Neumann, 2013), holding belief-graph children indexed by quantile-encoded feature bytes rather than database keys.</p>
<p>As observations flow through the graph:</p>
<ul>
<li><strong>Routing</strong> is a traceable walk through learned bytes, not an opaque attention pattern.</li>
<li><strong>Each leaf</strong> separately reports <em>epistemic</em> uncertainty (the model’s uncertainty about its own parameters, from the BLR posterior) and <em>aleatoric</em> uncertainty (irreducible noise in the data, from the inverse-gamma component).</li>
<li><strong>The graph itself</strong> can grow, split, merge, prune, compress, or freeze nodes — each action a deterministic function of per-node statistics under an installed <code>DecisionPolicy</code>.</li>
</ul>
<p>The “deterministic function” property has two important consequences that get developed throughout the article: the same observations always produce the same structural decisions, and every state change can be appended to a verifiable log. The log mechanism itself — SHA-256 hash-chained audit events, byte-identical replay — is the centrepiece of Part 2; this article focuses on what the architecture <em>does</em> and treats the audit chain as a black-box “every action is recorded” property.</p>
</section>
<section id="a-guided-tour-one-observation-flows-through-the-graph" class="level2">
<h2 class="anchored" data-anchor-id="a-guided-tour-one-observation-flows-through-the-graph">A guided tour: one observation flows through the graph</h2>
<p>Before diving into mechanism details, here’s what happens end-to-end when ABNG processes a single query <code>x = 0.5</code> on a trained 4-leaf graph:</p>
<p><strong>Routing.</strong> A 1-D <code>QuantileCodebook</code> (bin edges at <code>0.25 / 0.5 / 0.75</code>) bucketizes <code>0.5</code> into byte <code>0x02</code>. The trie root has 4 bound children at bytes <code>0x00..0x03</code>; <code>descend</code> walks one step and returns leaf #3.</p>
<p><strong>Per-leaf forward pass.</strong> Leaf #3 has its own MLP (initialized deterministically from <code>mix(seed, node_id, layer_idx, kind_bit)</code> via SplitMix64) that produces feature vector <code>φ</code> from <code>x</code>. Its BLR head then computes:</p>
<ul>
<li><code>mean = mᵀφ</code> (point estimate)</li>
<li><code>epistemic_leverage = φᵀΛ⁻¹φ</code> (dimensionless ≥ 0)</li>
<li><code>aleatoric_var = b/(a−1)</code> (units of y², requires a &gt; 1)</li>
</ul>
<p><strong>Output.</strong> A <code>(mean, lev, ale)</code> triple. On the benchmark hardware (Intel i7-11390H @ 3.40 GHz, Windows MSVC release build), <code>blr_predict</code> returns in <strong>934 nanoseconds per call</strong> — the Cholesky triangular solve dominates.</p>
<p><strong>Audit (optional).</strong> If <code>Routed</code> events are enabled, one event with body <code>(leaf_id=3, matched_prefix=1)</code> is appended — 41 bytes including the SHA-256 chain advance. Off by default to avoid log explosion under heavy inference.</p>
<pre class="mermaid"><code>flowchart LR
    Q["query x = 0.5"] --&gt; CB["QuantileCodebook&lt;br/&gt;bin edges 0.25/0.5/0.75"]
    CB --&gt; RK["route byte = 0x02"]
    RK --&gt; D["descend(root, 0x02)"]
    D --&gt; L["leaf #3"]
    L --&gt; BLR["BLR.predict(phi)&lt;br/&gt;934 ns/op"]
    BLR --&gt; OUT["(mean, lev, ale)&lt;br/&gt;+ optional Routed event"]</code></pre>
<p>That’s the entire inference path. Sub-microsecond, traceable, optionally auditable. The rest of the article unpacks each piece.</p>
</section>
<section id="the-architectural-neighbourhood" class="level2">
<h2 class="anchored" data-anchor-id="the-architectural-neighbourhood">The architectural neighbourhood</h2>
<p>Before getting into ABNG’s specifics, it helps to know the neighbourhood. The architectures below are the ones whose ideas ABNG either borrows from, competes with, or is most likely to be confused with.</p>
<section id="transformers" class="level3">
<h3 class="anchored" data-anchor-id="transformers">Transformers</h3>
<p><strong>Core idea.</strong> Compute a weighted combination of every token against every other token via dot-product attention, repeated across many layers. Parameter-heavy, embarrassingly parallel, structurally identical across an entire training run.</p>
<p><strong>Strengths.</strong> Best-in-class on language, vision, code. Massively parallel. Empirically scales smoothly with parameters and data.</p>
<p><strong>Weaknesses.</strong> Attention is <code>O(n²)</code> in sequence length. Attention maps are increasingly understood to be unreliable as faithful explanations of model behaviour. No built-in uncertainty. Fixed compute graph.</p>
</section>
<section id="state-space-models-mamba-s4-s5" class="level3">
<h3 class="anchored" data-anchor-id="state-space-models-mamba-s4-s5">State-Space Models (Mamba, S4, S5)</h3>
<p><strong>Core idea.</strong> Replace attention with a discretized linear (or input-conditioned) state-space recurrence that compresses a sequence into a fixed-size hidden state. The Mamba family added <em>selective</em> state, where transition matrices depend on the input, recovering much of attention’s expressivity at linear cost.</p>
<p><strong>Strengths.</strong> Linear scaling in sequence length. Fast inference, especially streaming. Hidden state is bounded.</p>
<p><strong>Weaknesses.</strong> Less proven on knowledge-heavy benchmarks. Structured state imposes assumptions that may or may not fit the task.</p>
</section>
<section id="graph-neural-networks-gnns" class="level3">
<h3 class="anchored" data-anchor-id="graph-neural-networks-gnns">Graph Neural Networks (GNNs)</h3>
<p><strong>Core idea.</strong> Pass messages along the edges of an explicit graph; each node aggregates messages from its neighbours. Topology is given as input, not learned.</p>
<p><strong>Strengths.</strong> Encodes relational structure natively. Strong on molecules, citation networks, traffic, knowledge graphs.</p>
<p><strong>Weaknesses.</strong> Over-smoothing with depth. Expressivity is bounded by Weisfeiler-Lehman tests in vanilla forms. Topology is fixed per input.</p>
</section>
<section id="bayesian-neural-networks-bnns" class="level3">
<h3 class="anchored" data-anchor-id="bayesian-neural-networks-bnns">Bayesian Neural Networks (BNNs)</h3>
<p><strong>Core idea.</strong> Replace point-estimate weights with full posterior distributions, typically approximated by variational families, MCMC samples, or deep ensembles.</p>
<p><strong>Strengths.</strong> Principled uncertainty by construction. Integration with decision theory is clean.</p>
<p><strong>Weaknesses.</strong> Variational posteriors are usually mean-field Gaussian, a known poor match for actual posterior geometry. MCMC doesn’t scale to deep networks. Ensembles are expensive.</p>
</section>
<section id="liquid-neural-networks" class="level3">
<h3 class="anchored" data-anchor-id="liquid-neural-networks">Liquid Neural Networks</h3>
<p><strong>Core idea.</strong> Continuous-time dynamics — each neuron’s state evolves according to an ODE whose parameters depend on inputs. Very small parameter counts.</p>
<p><strong>Strengths.</strong> Compact. Strong on small robotics and time-series tasks (Hasani et al.’s famous “19-neuron lane-keeping” result).</p>
<p><strong>Weaknesses.</strong> Scaling to large modern workloads is unproven. Training stability for stiff dynamics is a known headache.</p>
</section>
<section id="neural-operators-fno-deeponet" class="level3">
<h3 class="anchored" data-anchor-id="neural-operators-fno-deeponet">Neural Operators (FNO, DeepONet)</h3>
<p><strong>Core idea.</strong> Learn mappings between <em>function spaces</em> rather than between finite-dimensional vectors.</p>
<p><strong>Strengths.</strong> Discretization-invariant. Strong on PDE surrogates, inverse problems, climate modelling.</p>
<p><strong>Weaknesses.</strong> Specialized to operator-learning tasks. Spectral methods have known issues at sharp discontinuities.</p>
</section>
<section id="comparison-table" class="level3">
<h3 class="anchored" data-anchor-id="comparison-table">Comparison table</h3>
<table class="table">
<colgroup>
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
</colgroup>
<thead>
<tr class="header">
<th>Family</th>
<th>Core mechanism</th>
<th>Native uncertainty</th>
<th>Structural adaptation</th>
<th>Best at</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Transformer</strong></td>
<td>Dense attention</td>
<td>No</td>
<td>No</td>
<td>Language, vision, code</td>
</tr>
<tr class="even">
<td><strong>State-Space Model</strong></td>
<td>Linear/selective recurrence</td>
<td>No</td>
<td>No</td>
<td>Long sequences</td>
</tr>
<tr class="odd">
<td><strong>Graph Neural Network</strong></td>
<td>Message passing</td>
<td>No (Bayesian variants exist)</td>
<td>No (topology is input)</td>
<td>Relational data</td>
</tr>
<tr class="even">
<td><strong>Bayesian Neural Network</strong></td>
<td>Weight posteriors</td>
<td><strong>Yes (defining)</strong></td>
<td>No</td>
<td>Small, uncertainty-critical tasks</td>
</tr>
<tr class="odd">
<td><strong>Liquid Neural Network</strong></td>
<td>Continuous-time ODE state</td>
<td>No</td>
<td>No</td>
<td>Compact controllers</td>
</tr>
<tr class="even">
<td><strong>Neural Operator</strong></td>
<td>Function-space mapping</td>
<td>No</td>
<td>No</td>
<td>PDE surrogates</td>
</tr>
<tr class="odd">
<td><strong>ABNG (this article)</strong></td>
<td>Routed per-node Bayesian heads on an adaptive tree</td>
<td><strong>Yes (per-node BLR posterior)</strong></td>
<td><strong>Yes (Grow/Split/Merge/Prune/Compress/Freeze)</strong></td>
<td><em>Hypothesis: scientific &amp; safety-critical ML; empirically unverified</em></td>
</tr>
</tbody>
</table>
<p>The last column for ABNG is, deliberately, a hypothesis — not a result. The rest of the table reflects what each family actually demonstrates in the literature today.</p>
</section>
</section>
<section id="routing-and-the-adaptive-tree" class="level2">
<h2 class="anchored" data-anchor-id="routing-and-the-adaptive-tree">Routing and the adaptive tree</h2>
<p>The first job is to turn a real-valued input into a deterministic walk through the graph.</p>
<p>ABNG installs a <strong><code>QuantileCodebook</code></strong> once, before any node is added. For each input dimension <code>d</code>, the codebook holds a sorted list of bin boundaries — empirically chosen quantiles of that dimension over a calibration set. At inference time, each feature value is bucketized into one of <code>n_bins ∈ {2, 4, 8, 16, 32, 64, 128, 256}</code> bins, producing a single byte per dimension. The result is a fixed-length <em>route key</em> — a sequence of bytes derived deterministically from the input.</p>
<p>Why a discrete byte route instead of a learned soft router? Two reasons. First, it makes the routing path <em>traceable</em>: you can write the exact byte sequence into an audit event and replay the descent later. Second, the byte representation aligns naturally with the Adaptive Radix Tree (ART) children layout that ABNG uses for its graph topology.</p>
<p>The graph itself is a tree. Each node carries an <code>AdaptiveChildren</code> payload that takes one of five forms — <code>None</code>, <code>Node4</code>, <code>Node16</code>, <code>Node48</code>, <code>Node256</code> — and <strong>auto-promotes</strong> to the next size when full. This is mechanically identical to ART’s well-known children layout; ABNG borrows the structure and replaces the database-key payload with belief-graph children indexed by route-key bytes.</p>
<pre class="mermaid"><code>flowchart TD
    R["Root&lt;br/&gt;(Node16)"]
    R --&gt;|byte 0x03| A["Node A&lt;br/&gt;(Node4)"]
    R --&gt;|byte 0x07| B["Node B&lt;br/&gt;(Node48)"]
    R --&gt;|byte 0x0F| C["Leaf C&lt;br/&gt;(no children)"]
    A --&gt;|byte 0x12| AA["Leaf A1"]
    A --&gt;|byte 0x4E| AB["Leaf A2"]
    B --&gt;|byte 0x01| BA["Node B1&lt;br/&gt;(Node4)"]
    B --&gt;|byte 0x88| BB["Leaf B2"]
    BA --&gt;|byte 0xAA| BAA["Leaf B1a"]
    style C stroke-dasharray: 5 5
    style AA stroke-dasharray: 5 5
    style AB stroke-dasharray: 5 5
    style BB stroke-dasharray: 5 5
    style BAA stroke-dasharray: 5 5</code></pre>
<p>Descent is a one-pass loop:</p>
<div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">def</span> descend(graph, route_key):</span>
<span id="cb3-2">    node <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> graph.root</span>
<span id="cb3-3">    matched_prefix <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb3-4">    <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">for</span> byte <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">in</span> route_key:</span>
<span id="cb3-5">        child <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> node.children.get(byte)</span>
<span id="cb3-6">        <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">if</span> child <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">is</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>:</span>
<span id="cb3-7">            <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">break</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># longest-prefix match; stop here</span></span>
<span id="cb3-8">        matched_prefix.append(byte)</span>
<span id="cb3-9">        node <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> child</span>
<span id="cb3-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">return</span> RouteEvidence(leaf<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>node, matched_prefix<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>matched_prefix)</span></code></pre></div>
<p>If the route key runs out of bound children before reaching a configured depth, descent stops at the deepest matching node — <em>longest prefix wins</em>.</p>
<p><strong>The tradeoff this routing scheme makes</strong>: discrete bins introduce discontinuities at codebook bin edges. Two inputs <code>x = 0.499</code> and <code>x = 0.501</code> may route to different leaves and produce arbitrarily different outputs. This is a <em>fundamental</em> property of any architecture that uses discrete routing, not a fixable bug. ABNG accepts it in exchange for traceability and replay. The smoothness story for ABNG is “piecewise within a leaf, discontinuous at boundaries”; the smoothness story for soft routers (MoE, attention) is “smooth everywhere but unverifiable.”</p>
</section>
<section id="per-leaf-bayesian-heads-nig-conjugacy" class="level2">
<h2 class="anchored" data-anchor-id="per-leaf-bayesian-heads-nig-conjugacy">Per-leaf Bayesian heads (NIG conjugacy)</h2>
<p>A leaf node carries five distinct pieces of state, and it is worth seeing them laid out before discussing how they’re used:</p>
<pre class="mermaid"><code>flowchart TB
    subgraph LEAF["Leaf node (AdaptiveBeliefNode)"]
      direction TB
      MLP["MLP body&lt;br/&gt;params: W1, b1, W2, b2, …&lt;br/&gt;(Xavier init from mix(seed, node_id, …))"]
      BLR["BLR head&lt;br/&gt;m: mean vector&lt;br/&gt;Λ: precision matrix&lt;br/&gt;a, b: inverse-gamma shape/scale"]
      DENS["DensityTracker&lt;br/&gt;Welford μ + diag M2&lt;br/&gt;over BLR feature space"]
      CAL["CalibrationBins&lt;br/&gt;15 reliability bins&lt;br/&gt;Kahan-accumulated ECE"]
      DRIFT["DriftBaseline&lt;br/&gt;frozen density snapshot +&lt;br/&gt;L2 z-shift drift score"]
      META["Maturity + NodeSignature&lt;br/&gt;(lazy stability / freshness flags)"]
    end
    LEAF -.-&gt;|forward| OUT["(mean, epi_leverage, ale_var)&lt;br/&gt;+ ood_score"]</code></pre>
<p><strong>The MLP body.</strong> Each leaf has its own small MLP, deterministically initialized. Weights are sampled from a Xavier-uniform distribution whose seed is derived as <code>mix(graph.seed, node_id, layer_idx, kind_bit)</code> using SplitMix64. Biases start at zero. The MLP feeds into the BLR head; you can think of it as a <em>learned feature extractor specialized to this leaf’s slice of input space</em>.</p>
<p><strong>The BLR head.</strong> Where the Bayesian machinery actually lives. ABNG uses the Normal-Inverse-Gamma conjugate model for Gaussian-noise linear regression: the prior over weights is <code>N(m, σ²Λ⁻¹)</code> and the prior over noise variance is <code>IG(a, b)</code>. Given a new data point <code>(φ, y)</code> — where <code>φ</code> is the MLP feature vector — the update is closed-form:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5CLambda_%7B%5Ctext%7Bnew%7D%7D%20=%20%5CLambda%20+%20%5Cphi%5Cphi%5E%5Ctop,%20%5Cquad%0Am_%7B%5Ctext%7Bnew%7D%7D%20=%20%5CLambda_%7B%5Ctext%7Bnew%7D%7D%5E%7B-1%7D(%5CLambda%20m%20+%20%5Cphi%20y),%0A"></p>
<p><img src="https://latex.codecogs.com/png.latex?%0Aa_%7B%5Ctext%7Bnew%7D%7D%20=%20a%20+%20%5Ctfrac%7B1%7D%7B2%7D,%20%5Cquad%0Ab_%7B%5Ctext%7Bnew%7D%7D%20=%20b%20+%20%5Ctfrac%7B1%7D%7B2%7D%5C!%5Cleft(y%5E2%20+%20m%5E%5Ctop%20%5CLambda%20m%20-%20m_%7B%5Ctext%7Bnew%7D%7D%5E%5Ctop%20%5CLambda_%7B%5Ctext%7Bnew%7D%7D%20m_%7B%5Ctext%7Bnew%7D%7D%5Cright).%0A"></p>
<p>This is textbook conjugate Bayesian linear regression (Bishop 2006 §3.3; Murphy 2012 §7.6) — <em>established research</em>, no novelty claim. The interesting bit is that ABNG implements it in determinism-preserving Rust: hand-rolled Cholesky and triangular-solve (~30 LOC, Kahan-compensated, no FMA), <code>BLR.state_hash()</code> exposed for audit verification, and a <code>feature_version_hash</code> stamped at init so a stale-feature update is rejected with a typed error instead of silently corrupting the posterior.</p>
<div class="callout callout-style-simple callout-tip no-icon callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Worked example: a single leaf’s posterior tightens over 10 observations
</div>
</div>
<div class="callout-body-container callout-body">
<p>Suppose a leaf starts at its NIG prior <code>(m₀, Λ₀, a₀, b₀)</code> and receives 10 training pairs <code>(φ₁, y₁), …, (φ₁₀, y₁₀)</code> sequentially. Each observation triggers one closed-form NIG update. After step <code>k</code>, the posterior tuple is:</p>
<p><img src="https://latex.codecogs.com/png.latex?%5CLambda_k%20=%20%5CLambda_0%20+%20%5Csum_%7Bi=1%7D%5E%7Bk%7D%20%5Cphi_i%20%5Cphi_i%5E%5Ctop,%20%5Cquad%20a_k%20=%20a_0%20+%20%5Ctfrac%7Bk%7D%7B2%7D,%20%5Cquad%20b_k%20%5Cuparrow%20%5Ctext%7B%20monotonically%7D."></p>
<p>Epistemic leverage <code>φᵀΛ⁻¹φ</code> is monotone <em>decreasing</em> at the same probe point as <code>k</code> grows — verified empirically by <code>tests/test_abng_tabular_gp.rs::tabular_uncertainty_shrinks_with_more_data</code>, which asserts <code>lev_at_64_obs &lt; lev_at_16_obs</code>.</p>
<p>Per the benchmark: <code>blr_state_update_direct</code> (just the NIG math) costs <strong>3,845 ns per step</strong>; the graph-level <code>blr_update</code> (with audit-chain overhead) costs <strong>28,997 ns</strong>. That ~7.5× ratio — 87% of the per-step cost is the determinism premium, not the Bayesian math — is one of the most striking measurements in the codebase. <em>Part 2 returns to this number.</em></p>
</div>
</div>
<p><strong>Predictions</strong> from the BLR return three numbers:</p>
<div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">def</span> blr_predict(blr, phi):</span>
<span id="cb5-2">    mean <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> blr.m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> phi</span>
<span id="cb5-3">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># solve Λ x = φ once, reuse for leverage and predictive variance</span></span>
<span id="cb5-4">    x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cholesky_solve(blr.L, phi)</span>
<span id="cb5-5">    epistemic_leverage <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> phi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> x          <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># dimensionless ≥ 0</span></span>
<span id="cb5-6">    aleatoric_var <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> blr.b <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (blr.a <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>) <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ≥ 0; aleatoric noise scale (a &gt; 1)</span></span>
<span id="cb5-7">    <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">return</span> mean, epistemic_leverage, aleatoric_var</span></code></pre></div>
<p>These three numbers fully characterize the predictive distribution. Integrating out the noise variance gives a <strong>Student-t</strong> with <code>2a</code> degrees of freedom, location <code>mᵀφ</code>, and scale <code>(b/a)(1 + φᵀΛ⁻¹φ)</code>. In the asymptotic <code>a → ∞</code> regime that approaches a Gaussian with variance <code>aleatoric_var · (1 + epistemic_leverage)</code>.</p>
<p>The middle return value is <em>deliberately</em> a dimensionless leverage, not a variance, because mixing units silently is one of the easiest probabilistic mistakes to make.</p>
<p><strong>Density tracker, calibration bins, drift baseline.</strong> These three structures don’t enter the forward path directly; they accumulate evidence that drives the structural-decision policy in the next section.</p>
<ul>
<li>The <strong>density tracker</strong> maintains a running diagonal-Gaussian estimate over the leaf’s feature space. Mahalanobis distance from this distribution is the <em>density score</em> — high values mean “this input is unusual for this leaf.”</li>
<li>The <strong>calibration bins</strong> are a 15-bin reliability diagram, with Expected Calibration Error (ECE) accumulated via Kahan compensation. A node whose ECE stays below a threshold across three consecutive observation windows is considered <em>calibration-stable</em>.</li>
<li>The <strong>drift baseline</strong> is a frozen snapshot of the density tracker captured when the leaf becomes mature. The L2 z-shift between the current density and the baseline is the <em>drift score</em>.</li>
</ul>
<p>The three combine into a composite <code>ood_score = max(density_score, prefix_distance, epistemic_z)</code>. This composite is an <em>engineering heuristic</em> (if any of three independent OOD-suggestive signals fires, abstain), not a probabilistic anomaly score with a clean posterior interpretation. It’s defensible because each component is itself well-grounded, but readers expecting a fully Bayesian abstain criterion should treat this as <strong>reasonable extrapolation</strong>, not established research.</p>
</section>
<section id="when-the-graph-adapts-itself" class="level2">
<h2 class="anchored" data-anchor-id="when-the-graph-adapts-itself">When the graph adapts itself</h2>
<p>So far the graph is fixed. The interesting move is letting it change.</p>
<p>ABNG defines six <strong>structural actions</strong>:</p>
<table class="table">
<colgroup>
<col style="width: 50%">
<col style="width: 50%">
</colgroup>
<thead>
<tr class="header">
<th>Action</th>
<th>Effect</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Grow</strong></td>
<td>Add a new child leaf at an unbound route-key byte.</td>
</tr>
<tr class="even">
<td><strong>Split</strong></td>
<td>Take a leaf with sufficient evidence and partition it into two children.</td>
</tr>
<tr class="odd">
<td><strong>Merge</strong></td>
<td>Combine two sibling leaves into one (NIG-aware: precisions sum, means precision-weighted, IG params combined).</td>
</tr>
<tr class="even">
<td><strong>Prune</strong></td>
<td>Mark a low-evidence leaf as inactive.</td>
</tr>
<tr class="odd">
<td><strong>Compress</strong></td>
<td>Collapse a chain of equivalent sub-trees into a <code>Dense</code> node.</td>
</tr>
<tr class="even">
<td><strong>Freeze</strong></td>
<td>Stop updating a mature, calibrated leaf to protect it from drift.</td>
</tr>
</tbody>
</table>
<p>Each action is gated on a deterministic condition computed from per-node statistics. The engine that fires them is <code>decide_step</code>:</p>
<div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">def</span> decide_step(graph, policy):</span>
<span id="cb6-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># SNAPSHOT INVARIANT: iterate over node ids captured at call entry.</span></span>
<span id="cb6-3">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Actions inside this loop append new nodes / mark existing ones inactive,</span></span>
<span id="cb6-4">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># but they do NOT change the targets of this iteration.</span></span>
<span id="cb6-5">    snapshot <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sorted</span>(graph.nodes.keys())</span>
<span id="cb6-6">    <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">for</span> node_id <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">in</span> snapshot:</span>
<span id="cb6-7">        node <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> graph.nodes[node_id]</span>
<span id="cb6-8">        <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">not</span> node.is_active: <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">continue</span></span>
<span id="cb6-9"></span>
<span id="cb6-10">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 1. Drift-trip auto-unfreeze BEFORE the action ladder</span></span>
<span id="cb6-11">        <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">if</span> node.is_frozen <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">and</span> drift_score(node) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> policy.drift_unfreeze:</span>
<span id="cb6-12">            unfreeze(graph, node_id)</span>
<span id="cb6-13">            <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">continue</span></span>
<span id="cb6-14"></span>
<span id="cb6-15">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 2. Fixed fall-through order — at most ONE action per node per call</span></span>
<span id="cb6-16">        <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">if</span> try_compress(graph, node_id, policy): <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">continue</span></span>
<span id="cb6-17">        <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">if</span> try_merge(graph, node_id, policy):    <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">continue</span></span>
<span id="cb6-18">        <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">if</span> try_split(graph, node_id, policy):    <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">continue</span></span>
<span id="cb6-19">        <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">if</span> try_prune(graph, node_id, policy):    <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">continue</span></span>
<span id="cb6-20">        <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">if</span> try_grow(graph, node_id, policy):     <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">continue</span></span>
<span id="cb6-21">        try_freeze(graph, node_id, policy)</span></code></pre></div>
<p>Three properties are worth emphasizing:</p>
<ol type="1">
<li><p><strong>Determinism is structural.</strong> The node iteration order is <code>NodeId</code> ascending. The action order is fixed. The triggers are pure functions of the graph state and the policy thresholds.</p></li>
<li><p><strong>At most one action per node per call.</strong> If Compress and Split both fire on the same node, Compress wins because it comes first in the fall-through; Split waits for the next <code>decide_step</code> call.</p></li>
<li><p><strong>The snapshot-at-call-entry invariant.</strong> Actions taken inside <code>decide_step</code> do <em>not</em> feed back into that same call’s iteration set. Without this, a single call could chain-fire actions across newly-created descendants — non-deterministic, hard to reason about, and impossible to replay.</p></li>
</ol>
<p>Gates are not just thresholds. The triggers were sharpened in Phase 0.4 to genuine quality conditions:</p>
<ul>
<li><strong>Merge</strong> requires both Hamming-similar signatures <em>and</em> KL-divergence between posteriors below <code>kl_merge</code>.</li>
<li><strong>Split</strong> requires both sample-count <em>and</em> a deterministic bootstrap held-out ΔNLL gain ≥ <code>nll_split_gain</code> (with SplitMix64 seeding the bootstrap).</li>
<li><strong>Grow</strong> requires both sample-count <em>and</em> route-key entropy at the candidate depth &gt; <code>H_grow</code>.</li>
</ul>
<pre class="mermaid"><code>flowchart TD
    START["decide_step called"] --&gt; SNAP["snapshot node_ids&lt;br/&gt;(ascending order)"]
    SNAP --&gt; NODE["next active node"]
    NODE --&gt; UNFREEZE{"frozen and&lt;br/&gt;drift &gt; thresh?"}
    UNFREEZE -- yes --&gt; UF["Unfreeze&lt;br/&gt;(audit 0x16)"]
    UNFREEZE -- no --&gt; COMP{"compress gate?"}
    UF --&gt; NODE
    COMP -- yes --&gt; COMP_A["Compress"]
    COMP -- no --&gt; MERGE{"merge gate?&lt;br/&gt;(Hamming + KL)"}
    MERGE -- yes --&gt; MERGE_A["Merge"]
    MERGE -- no --&gt; SPLIT{"split gate?&lt;br/&gt;(samples + ΔNLL)"}
    SPLIT -- yes --&gt; SPLIT_A["Split"]
    SPLIT -- no --&gt; PRUNE{"prune gate?"}
    PRUNE -- yes --&gt; PRUNE_A["Prune"]
    PRUNE -- no --&gt; GROW{"grow gate?&lt;br/&gt;(samples + entropy)"}
    GROW -- yes --&gt; GROW_A["Grow"]
    GROW -- no --&gt; FREEZE{"freeze gate?"}
    FREEZE -- yes --&gt; FREEZE_A["Freeze"]
    COMP_A --&gt; NEXT["go to next node"]
    MERGE_A --&gt; NEXT
    SPLIT_A --&gt; NEXT
    PRUNE_A --&gt; NEXT
    GROW_A --&gt; NEXT
    FREEZE_A --&gt; NEXT
    FREEZE -- no --&gt; NEXT
    NEXT --&gt; NODE</code></pre>
<div class="callout callout-style-simple callout-tip no-icon callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Worked example: adaptive Merge of two similar leaves
</div>
</div>
<div class="callout-body-container callout-body">
<p>Two sibling leaves train on similar data. Their BLR posteriors and <code>NodeSignature</code>s converge to within the Hamming-similarity threshold and KL-divergence threshold. The <code>signature_stable_calls</code> counter passes the maturity threshold.</p>
<p>On the next <code>decide_step</code> call, both Merge gates pass. The NIG-aware <code>BlrState::combine</code> runs: <code>Λ_into += Λ_absorbed</code>, precision-weighted-mean of <code>m</code>, sum of <code>a</code>, careful combination of <code>b</code>. The absorbed leaf gets <code>is_active = false</code> but stays in the arena (preserving <code>NodeId</code> stability for replay). The action_counts vector increments at index 2 (Merge).</p>
<pre class="mermaid"><code>flowchart LR
    subgraph BEFORE["before decide_step"]
        direction TB
        BP["Parent"]
        BA["Leaf A&lt;br/&gt;posterior P_A"]
        BB["Leaf B&lt;br/&gt;posterior P_B&lt;br/&gt;Hamming(sig_A,sig_B) ≤ τ&lt;br/&gt;KL(P_A,P_B) ≤ kl_merge"]
        BP --&gt; BA
        BP --&gt; BB
    end
    subgraph AFTER["after Merge (audit 0x12)"]
        direction TB
        AP["Parent"]
        AC["Leaf A (into)&lt;br/&gt;posterior = combine(P_A, P_B)"]
        AX["Leaf B&lt;br/&gt;is_active = false&lt;br/&gt;(tombstone in arena)"]
        AP --&gt; AC
        AP -.tombstone.-&gt; AX
        style AC fill:#cfc
        style AX fill:#fcc,stroke-dasharray:5 5
    end
    BEFORE --&gt;|decide_step| AFTER</code></pre>
<p>The graph <strong>shrinks itself</strong> when evidence says it should. No retraining, no human intervention, deterministic from the same seed every time. The full demo is at <code>tests/test_abng_adaptive_triggers_cjcl.rs</code>, and it crucially asserts that the audit chain <em>still verifies</em> after the Merge — structural mutation doesn’t break tamper-detection.</p>
</div>
</div>
<p>The “never reorder, only tombstone” property is what keeps <code>NodeId</code> references stable across the entire training history. New nodes are appended; old nodes are deactivated but never removed. This is what makes byte-identical replay possible even after thousands of structural events.</p>
</section>
<section id="where-abng-sits-in-the-recombination-space" class="level2">
<h2 class="anchored" data-anchor-id="where-abng-sits-in-the-recombination-space">Where ABNG sits in the recombination space</h2>
<p>ABNG is best understood as a <strong>deliberate recombination</strong>, not a new family. Three lineages meet in the design:</p>
<ol type="1">
<li><p><strong>Graph Neural Networks</strong> give it the per-node feature-vector mental model and the message-passing intuition. But ABNG’s graph is a <em>learned routing tree</em>, not an input-supplied topology, and it changes shape during training under the structural decision policy.</p></li>
<li><p><strong>Bayesian Neural Networks</strong> give it the calibrated-uncertainty target. ABNG localizes the Bayesian machinery: instead of a global weight posterior, each leaf node carries a small <strong>Bayesian Linear Regression head</strong> with a Normal-Inverse-Gamma prior, updated in closed form. Strictly speaking this places ABNG closer to a <strong>routed mixture of Bayesian Linear Regression experts</strong> — what the older literature on Bayesian committee machines and products of local experts (Tresp 2000; Cao &amp; Fleet 2014) was reaching for — than to a full Bayesian Neural Network. The uncertainty estimate is local, conditional on routing, <em>not</em> a posterior over the whole model.</p></li>
<li><p><strong>Mixture-of-Experts</strong> gives it the conditional-compute intuition: only the routed leaf produces a prediction, and only that leaf’s posterior is updated. But MoE routers learn a soft gating function and the expert population is fixed; ABNG’s router walks a discrete, deterministic tree over quantile-encoded features, and the population of experts (leaves) is itself adaptive — Grow, Split, Merge, Prune, Compress, Freeze. (A reader who tracks Adaptive Computation Time, Pondernet, or Universal Transformers will recognize the dynamic-compute family resemblance, but ABNG adapts at the <em>training-graph</em> level rather than the per-token-inference level.)</p></li>
</ol>
<p>ABNG combines these lineages with two further commitments that are uncommon in any of them — a SHA-256 audit chain over every state mutation, and a bit-identical reproducibility contract enforced from the substrate level. <em>Part 2 covers both in detail.</em></p>
<div class="callout callout-style-simple callout-tip no-icon callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-title-container flex-fill">
What Makes This Different?
</div>
</div>
<div class="callout-body-container callout-body">
<p>Most architectures pick <em>one</em> axis: transformers go all-in on scale, SSMs on sequence-length efficiency, GNNs on relational structure, BNNs on uncertainty, Liquid NNs on compactness, Neural Operators on function-space learning. ABNG instead picks <strong>three secondary axes</strong> that are usually treated as bolt-ons — calibrated belief, structural adaptation, and bit-identical reproducibility — and makes them load-bearing. The cost is that ABNG is not yet competitive with the headliners on their <em>own</em> axes. The hypothesis is that the <em>combination</em> matters more than topping any single chart.</p>
</div>
</div>
</section>
<section id="theoretical-advantages-for-the-architecture-itself" class="level2">
<h2 class="anchored" data-anchor-id="theoretical-advantages-for-the-architecture-itself">Theoretical advantages — for the architecture itself</h2>
<p>This article focuses on the <em>architectural</em> advantages of ABNG. The determinism / audit advantages are covered in Part 2; the empirical evidence in Part 3.</p>
<section id="native-uncertainty-tracking" class="level3">
<h3 class="anchored" data-anchor-id="native-uncertainty-tracking">Native uncertainty tracking</h3>
<p><strong>Claim.</strong> Every prediction comes with separately-reported epistemic and aleatoric uncertainty, computed from a closed-form posterior, with no sampling or variational machinery.</p>
<p><strong>Tradeoff.</strong> The uncertainty is <strong>local</strong> — it is the posterior of the BLR <em>conditional on the routing decision</em>. A clean global predictive distribution would require integrating over routing uncertainty, which ABNG approximates with the composite <code>ood_score</code> heuristic but does not perform exactly. The BLR posterior also assumes Gaussian noise and linear features (after the MLP body); model-misspecification effects show up as a systematically inflated <code>b</code> term. Calibration depends on enough samples per leaf: sparsely-routed leaves stay broadly uncertain regardless of the global model’s quality.</p>
<p><strong>Tag.</strong> <em>Established</em> at the local-BLR level (Bishop 2006 §3.3; Murphy 2012 §7.6). <em>Reasonable extrapolation</em> for the graph-level predictive interpretation.</p>
</section>
<section id="probabilistic-reasoning-at-the-routing-level" class="level3">
<h3 class="anchored" data-anchor-id="probabilistic-reasoning-at-the-routing-level">Probabilistic reasoning at the routing level</h3>
<p><strong>Claim.</strong> Because routing is discrete and traceable, you can ask probabilistic questions about the <em>routing decision itself</em> — “what fraction of inputs route to this leaf?”, “what’s the entropy of the route distribution at depth 3?” — and get exact answers.</p>
<p><strong>Tradeoff.</strong> The Routed events are <strong>opt-in</strong> and off by default, because per-descent logging at high observation rates can dwarf the model’s actual training data. The probabilistic questions answerable from the log are also <strong>structural</strong> (about routing), not <strong>predictive</strong> (about model outputs) — the latter still requires running the model.</p>
<p><strong>Tag.</strong> <em>Reasonable extrapolation</em> — the mechanism is real and shipped, but its use as a routing-analysis tool is so far an internal-debugging affordance.</p>
</section>
<section id="adaptive-memory-through-structural-decisions" class="level3">
<h3 class="anchored" data-anchor-id="adaptive-memory-through-structural-decisions">Adaptive memory through structural decisions</h3>
<p><strong>Claim.</strong> The graph grows when it needs capacity, splits when a leaf becomes heterogeneous, merges when two leaves become equivalent, prunes when a leaf is unused, freezes when a leaf is mature, and unfreezes when drift demands it.</p>
<p><strong>Tradeoff.</strong> The <em>deterministic firing</em> of structural actions is guaranteed; the <em>quality</em> of those actions depends on the policy thresholds, which require tuning per dataset. Mis-tuned thresholds produce visible failure modes: too-tight <code>kl_merge</code> prevents legitimate consolidation, too-loose <code>H_grow</code> causes runaway proliferation. There is no default policy that works across radically different data distributions, and there is currently no automatic threshold-tuning beyond hand-rolled grid search.</p>
<p><strong>Tag.</strong> <em>Reasonable extrapolation</em> — the mechanism works in unit tests and small-scale demos; performance at production scale is <em>speculative</em>.</p>
</section>
<section id="sparse-computation-at-inference" class="level3">
<h3 class="anchored" data-anchor-id="sparse-computation-at-inference">Sparse computation at inference</h3>
<p><strong>Claim.</strong> Each prediction touches exactly one leaf, regardless of how large the graph has grown. Inference cost is <code>O(d_route)</code> for the tree descent + <code>O(d_feat²)</code> for the BLR predict, independent of total node count.</p>
<p><strong>Tradeoff.</strong> <em>Inference</em> is sparse, but <em>training</em> is not as sparse as it looks. The whole graph’s structural-decision policy runs on <code>decide_step</code>, which is <code>O(n_active_nodes)</code>. The audit chain advance is global. Concrete benchmark numbers: inference is sub-microsecond per call (934 ns for BLR predict, 118 ns for tree descent); <code>decide_step</code> is sub-millisecond at 10K active nodes. <em>Part 3 covers the full scaling story.</em></p>
<p><strong>Tag.</strong> <em>Established</em> at the per-inference level.</p>
</section>
</section>
<section id="what-this-architecture-cannot-do" class="level2">
<h2 class="anchored" data-anchor-id="what-this-architecture-cannot-do">What this architecture cannot do</h2>
<p>Two failure modes are <strong>fundamental</strong> — they cannot be fixed without giving up another property the architecture is built around. The rest are tunable or planned (covered in Part 3).</p>
<section id="routing-discontinuity-at-codebook-bin-edges" class="level3">
<h3 class="anchored" data-anchor-id="routing-discontinuity-at-codebook-bin-edges">Routing discontinuity at codebook bin edges</h3>
<p>Two inputs <code>x = 0.499</code> and <code>x = 0.501</code> may quantize into different codebook bins and route to different leaves. The prediction surface has <em>discontinuities</em> at every bin edge — a small input perturbation can produce an arbitrarily different output. The mitigations (wider bins, output smoothing, soft routing) each give up another property: traceability, audit-replay coverage, or determinism.</p>
<p><strong>Severity.</strong> <em>Fundamental.</em> This is the single biggest reason ABNG will lose to FNO on smooth-PDE benchmarks. Worth knowing upfront.</p>
</section>
<section id="the-high-dimensional-input-problem" class="level3">
<h3 class="anchored" data-anchor-id="the-high-dimensional-input-problem">The high-dimensional input problem</h3>
<p>As input dimensionality grows past ~32, the route key (one byte per dimension) gets long, routing entropy explodes, and the architecture either grows leaves indefinitely or wastes most of the tree on noise dimensions. There is no native dimensionality reduction.</p>
<p><strong>Severity.</strong> <em>Moderate.</em> This restricts ABNG’s natural domain to low-dimensional scientific / tabular workloads. ABNG on raw images, audio, or token sequences is not the design target — users would need a deterministic pre-encoder (PCA, fixed random projection, hashed embedding) outside the audit story.</p>
</section>
</section>
<section id="the-follow-ups" class="level2">
<h2 class="anchored" data-anchor-id="the-follow-ups">The follow-ups</h2>
<p>This article covered what ABNG <em>is</em>. Two questions remain:</p>
<p><strong>Does the auditability story actually hold up?</strong> The mechanism is a SHA-256 hash-chained log over every state mutation, with byte-identical replay and three-signal spoof detection. That’s Part 2 — <a href="../../../blog/posts/abng-deterministic-systems/index.html">Deterministic and Auditable Neural Systems</a>.</p>
<p><strong>Does any of this actually work?</strong> Nine demo categories, 33+ test files, four scaling benchmarks, one analytical-solution result (PINN on the 1D heat equation: MAE &lt; 0.05 on a known truth). Plus honest gaps: zero external baselines, single-hardware measurements, small-N validation. That’s Part 3 — <a href="../../../blog/posts/abng-benchmarks/index.html">Benchmarking Experimental Bayesian Graph Networks</a>.</p>
<div class="callout callout-style-simple callout-tip no-icon callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Teaser: calibrated abstain
</div>
</div>
<div class="callout-body-container callout-body">
<p>The architecture exists to enable a specific decision: <strong>abstain when you don’t know.</strong> A trained model receives three queries — one densely sampled, one near the training boundary, one never seen. The composite <code>ood_score = max(density_score, prefix_distance, epistemic_z)</code> produces three different score tiers, deliberately <em>over-conservative</em> (false positives on abstain are accepted in exchange for fewer false negatives). The actual demo at <code>tests/test_abng_ood_detection_cjcl.rs::ood_cjcl_three_tier_separation</code> verifies the three-tier separation holds.</p>
<pre class="mermaid"><code>flowchart TD
    Q1["query x = 0.5&lt;br/&gt;(densely trained)"] --&gt; R1["route → leaf k1&lt;br/&gt;n_seen large"]
    Q2["query x = 0.2&lt;br/&gt;(boundary)"] --&gt; R2["route → leaf k2&lt;br/&gt;n_seen mid"]
    Q3["query x = 0.9&lt;br/&gt;(unseen)"] --&gt; R3["route → leaf k3&lt;br/&gt;n_seen ~ 0"]
    R1 --&gt; P1["predict&lt;br/&gt;OOD: LOW"]
    R2 --&gt; P2["predict&lt;br/&gt;OOD: MID"]
    R3 --&gt; P3["predict&lt;br/&gt;OOD: HIGH"]
    P1 --&gt; D1["trust output"]
    P2 --&gt; D2["predict-with-caveat"]
    P3 --&gt; D3["ABSTAIN"]
    style D1 fill:#cfc
    style D2 fill:#ffc
    style D3 fill:#fcc</code></pre>
<p>The “abstain when ABNG says abstain” policy is the architecture’s strongest pitch for safety-critical applications. Whether the threshold is well-calibrated globally — given that calibration is tracked per-leaf, not graph-wide — is one of the open questions Part 3 takes seriously.</p>
</div>
</div>
<p>If after reading this article you decide the architecture is interesting but probably wrong in three specific ways, this piece has done its job. The full open-source codebase, the test suite, and the working <code>cjcl abng {inspect, replay, diff, explain, train}</code> CLI surface are at <a href="https://github.com/AdamEzzat1/CJC"><code>crates/cjc-abng/</code></a>. Parts 2 and 3 sharpen the criticisms; this article was the introduction.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>ABNG lives at <a href="https://github.com/AdamEzzat1/CJC"><code>crates/cjc-abng/</code></a> in the CJC-Lang repository. The full codebase, the test suite, and the working <code>cjcl abng {inspect, replay, diff, explain, train}</code> CLI surface are open source. The capabilities-walkthrough version of this material (engineering-changelog genre) is at <a href="../../../blog/posts/abng-experimental-bayesian-graph/index.html">the previous ABNG blog post</a>. This is part of a four-article research-commentary series on ABNG.</p>
</div>
</div>


</section>

 ]]></description>
  <category>CJC-Lang</category>
  <category>ABNG</category>
  <category>Bayesian</category>
  <category>architecture</category>
  <category>research</category>
  <guid>https://adamezzat1.github.io/blog/posts/abng-architecture/index.html</guid>
  <pubDate>Sun, 10 May 2026 07:00:00 GMT</pubDate>
</item>
<item>
  <title>ABNG Benchmarks Part I — Deterministic Belief Evolution, Lineage, and Explainable Inference</title>
  <dc:creator>Adam Ezzat</dc:creator>
  <link>https://adamezzat1.github.io/blog/posts/abng-benchmarks/index.html</link>
  <description><![CDATA[ 



<div class="reading-time">
🕑 ~38 min read
</div>
<div class="callout callout-style-simple callout-tip no-icon callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-title-container flex-fill">
TL;DR
</div>
</div>
<div class="callout-body-container callout-body">
<p>ABNG (Adaptive Belief Network Graph) is an experimental neural architecture that treats Bayesian belief as a first-class object — each leaf in an adaptive tree carries its own posterior, routing is a traceable walk through learned bytes, and every state mutation appends to a SHA-256 hash-chained audit log. <strong>This article (Part I of the benchmark deep-dive) focuses on the five categories where ABNG’s properties can be proven correct at the tested scale: determinism, lineage, state stability, probabilistic consistency, and explainability.</strong> Eight experiments — α (NIG posterior consistency at 1/√n), δ (replay invariance under mutation), η (per-bin reliability with ECE = 0.015), γ (selective prediction yielding 10× error reduction), ζ (Kahan-vs-plain summation premium at 2.40×), κ (predictive coverage within ±0.01 of nominal at 5 levels), λ (statistical CIs on per-op timings), and μ (audit log scaling cleanly to 1M events at 137 bytes/event) — back the claims with real wall-clock numbers and pre-specified pass/fail conditions. <strong>The companion Part II covers adaptive graph evolution, routing behavior, and temporal state dynamics.</strong></p>
</div>
</div>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Two prototype disclaimers
</div>
</div>
<div class="callout-body-container callout-body">
<p><strong>ABNG is a research-stage prototype.</strong> It is built on top of <strong>CJC-Lang</strong>, a research compiler that is itself pre-1.0. Both are under active development. Anything you read here is current as of Phase 0.7 (snapshot magic <code>v13</code>); the next milestone (Phase 0.8) explicitly authorizes a wire-format bump to v14. None of this is production-ready. None of this has been deployed at scale.</p>
</div>
</div>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
This is article 3 of a 4-article ABNG series
</div>
</div>
<div class="callout-body-container callout-body">
<ul>
<li><strong>Article 1</strong> (<a href="../../../blog/posts/abng-architecture/index.html">ABNG: Treating Belief States as First-Class Citizens</a>) — the architecture itself.</li>
<li><strong>Article 2</strong> (<a href="../../../blog/posts/abng-deterministic-systems/index.html">Deterministic and Auditable Neural Systems</a>) — the audit chain and determinism contract.</li>
<li><strong>Article 3 (this article)</strong> — Benchmarks Part I: determinism, lineage, state stability, probabilistic consistency, explainability.</li>
<li><strong>Article 4</strong> (<a href="../../../blog/posts/abng-benchmarks-part-2/index.html">Benchmarks Part II</a>) — adaptive graph evolution, routing behavior, temporal state dynamics. <em>Companion article, forthcoming.</em></li>
</ul>
<p>Each article can be read alone. This one is the empirical evidence for the “things you can prove correct” half of ABNG’s value proposition; Part II covers the “things that make ABNG architecturally distinctive” half.</p>
</div>
</div>
<section id="introduction" class="level2">
<h2 class="anchored" data-anchor-id="introduction">1. Introduction</h2>
<p>ABNG is an experimental neural architecture that treats Bayesian belief as a first-class architectural primitive: each leaf in an adaptive tree carries its own Normal-Inverse-Gamma posterior, routing is a traceable walk through learned bytes, and the graph can grow / split / merge / prune / compress / freeze itself under a deterministic decision policy. Every state mutation appends to a SHA-256 hash-chained audit log, so the full training history is byte-identically replayable.</p>
<p>That’s the architecture in two sentences. The hard question is whether any of it actually works. This article — and its companion Part II — is the empirical accounting.</p>
<section id="why-split-the-benchmark-coverage-in-two" class="level3">
<h3 class="anchored" data-anchor-id="why-split-the-benchmark-coverage-in-two">Why split the benchmark coverage in two</h3>
<p>The previous version of this article tried to cover everything in one piece — nine experiments, twelve figures, the full demo catalog. The result was readable but dense (~14K words, 45-minute read). The Stack-Role-Group critique that drove the recent rewrite identified an honest issue: <strong>ABNG’s benchmark properties cluster naturally into two distinct themes, and the article reads better when those themes get separate articles.</strong></p>
<p>The two themes — and the line that separates them:</p>
<ul>
<li><p><strong>Part I (this article) — properties of a fixed graph.</strong> The Bayesian update is correctly implemented. The audit chain detects tampering. The model can be replayed byte-identically. Predictions are well-calibrated. Abstaining on high-OOD inputs reduces error. These are properties you can prove correct on a given graph at a given moment.</p></li>
<li><p><strong>Part II (companion) — properties of an evolving graph.</strong> The graph restructures itself under evidence. Routing partitions input space. The structural-decision engine is sub-millisecond at moderate scale. Frozen leaves auto-unfreeze under distribution shift. These are properties that make ABNG architecturally distinctive — and they emerge from the graph <em>changing</em> over training.</p></li>
</ul>
<p>Both halves matter. Splitting them apart gives each one the depth it deserves.</p>
</section>
<section id="what-this-article-delivers" class="level3">
<h3 class="anchored" data-anchor-id="what-this-article-delivers">What this article delivers</h3>
<p>Concretely, by the end of Part I you should be able to:</p>
<ol type="1">
<li>Read a table of nine benchmark categories and know which five are covered here.</li>
<li>See five mini-demo scenarios that show the categories at work on small concrete inputs.</li>
<li>Walk through five experiments with pre-registered pass/fail conditions, real wall-clock measurements, and figures generated from the measured data.</li>
<li>Read about two existing demo suites (PINN uncertainty, lineage attestation) as worked-through examples.</li>
<li>Get the determinism microbench numbers and the 87% premium decomposition.</li>
<li>See five explicit failure modes and the deferred work that would close them.</li>
<li>Reproduce every number with <code>cargo run --release</code> plus a Python plot script.</li>
</ol>
<p>The article is ~9,000 words. Pacing assumes you don’t already know the architecture in detail — Part 1 of the series (<a href="../../../blog/posts/abng-architecture/index.html">Belief States as First-Class Citizens</a>) goes deeper.</p>
</section>
</section>
<section id="what-the-current-abng-demos-demonstrate" class="level2">
<h2 class="anchored" data-anchor-id="what-the-current-abng-demos-demonstrate">2. What the current ABNG demos demonstrate</h2>
<p>Before slicing the benchmark coverage in two, here’s the full landscape of demonstrated capabilities — both for orientation and to make explicit <em>what each subsequent article half does not need to revisit</em>.</p>
<p>The ABNG codebase as of Phase 0.7 contains:</p>
<ul>
<li><strong>14 distinct demo categories</strong> across 50 test files (~5,866 LOC of test code).</li>
<li><strong>5 performance bench packages</strong> in <code>bench/abng_*/</code> (~1,400 LOC including the SRG-experiments suite).</li>
<li><strong>9 measured experiments</strong> with pre-registered pass/fail conditions: α (posterior consistency), β (basis-not-in-span PINN), γ (selective prediction), δ (replay under mutation), ε (<code>decide_step</code> scaling), ζ (Kahan vs plain summation), η (per-bin reliability), θ (routing flow), ι (2-D OOD heatmap).</li>
</ul>
<p>Capability inventory (alphabetical, with the article that deeply covers each):</p>
<table class="table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Demonstrated capability</th>
<th>Mechanism</th>
<th>Deep coverage in</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Adaptive routing</td>
<td>QuantileCodebook + Adaptive Radix Tree-style descent</td>
<td>Part II</td>
</tr>
<tr class="even">
<td>Audit chain integrity</td>
<td>SHA-256 chain over every state mutation; 30 audit kinds</td>
<td><strong>Part I</strong></td>
</tr>
<tr class="odd">
<td>Bayesian inference (per-leaf)</td>
<td>NIG conjugate update over BLR weights + noise variance</td>
<td><strong>Part I</strong></td>
</tr>
<tr class="even">
<td>Belief-state stability over training</td>
<td>Welford-smoothed <code>NodeSignature</code> per leaf</td>
<td><strong>Part I</strong> + Part II</td>
</tr>
<tr class="odd">
<td>Calibration tracking</td>
<td>15-bin reliability diagram per leaf; ECE accumulated with Kahan</td>
<td><strong>Part I</strong></td>
</tr>
<tr class="even">
<td>Cryptographic provenance</td>
<td>Per-node <code>provenance_stamp_hash</code> field; tied to dataset SHA-256</td>
<td><strong>Part I</strong></td>
</tr>
<tr class="odd">
<td>Deterministic execution</td>
<td>Kahan summation, BTreeMap, no FMA, SplitMix64 RNG</td>
<td><strong>Part I</strong></td>
</tr>
<tr class="even">
<td>Drift detection</td>
<td>L2 z-shift between current density and frozen baseline</td>
<td>Part II</td>
</tr>
<tr class="odd">
<td>Explainable abstain</td>
<td>Composite <code>ood_score = max(density, prefix_distance, epi_z)</code></td>
<td><strong>Part I</strong></td>
</tr>
<tr class="even">
<td>Graph mutation visibility</td>
<td><code>cjcl abng inspect</code> and <code>cjcl abng diff</code> CLI tools</td>
<td><strong>Part I</strong> + Part II</td>
</tr>
<tr class="odd">
<td>Lineage attestation</td>
<td>Three-signal spoof detection (chain head + BLR state + stamp)</td>
<td><strong>Part I</strong></td>
</tr>
<tr class="even">
<td>Posterior convergence</td>
<td>Closed-form NIG update with Cholesky + triangular solve</td>
<td><strong>Part I</strong></td>
</tr>
<tr class="odd">
<td>Probabilistic consistency</td>
<td>Per-bin (predicted, observed) tracking; aggregate ECE</td>
<td><strong>Part I</strong></td>
</tr>
<tr class="even">
<td>Replayability under mutation</td>
<td><code>serialize</code> → <code>replay</code> byte-identical even after structural events</td>
<td><strong>Part I</strong></td>
</tr>
<tr class="odd">
<td>Route introspection</td>
<td>Opt-in <code>Routed</code> audit events (kind <code>0x1B</code>) for prediction traces</td>
<td>Part II</td>
</tr>
<tr class="even">
<td>Structural adaptation</td>
<td><code>decide_step</code> engine with 6 actions + 14-threshold policy</td>
<td>Part II</td>
</tr>
<tr class="odd">
<td>Temporal state evolution</td>
<td><code>Maturity</code> flags + 3-window stability ring buffers per node</td>
<td>Part II</td>
</tr>
<tr class="even">
<td>Trie-based routing</td>
<td>Node4/16/48/256 children with auto-promotion</td>
<td>Part II</td>
</tr>
<tr class="odd">
<td>Uncertainty propagation</td>
<td>Per-leaf <code>(mean, epistemic_leverage, aleatoric_var)</code> triple</td>
<td><strong>Part I</strong></td>
</tr>
</tbody>
</table>
<p><strong>This article focuses on the 11 capabilities marked “Part I” above.</strong> The remaining 8 are deeply covered in Part II.</p>
</section>
<section id="benchmark-taxonomy" class="level2">
<h2 class="anchored" data-anchor-id="benchmark-taxonomy">3. Benchmark taxonomy</h2>
<p>Standard ML benchmarks measure how well a model predicts. They don’t measure most of what ABNG cares about. The article-split decision rests on a benchmark taxonomy with nine categories:</p>
<table class="table">
<colgroup>
<col style="width: 21%">
<col style="width: 21%">
<col style="width: 21%">
<col style="width: 35%">
</colgroup>
<thead>
<tr class="header">
<th>#</th>
<th>Benchmark category</th>
<th>Definition</th>
<th style="text-align: center;">Covered in</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>1</td>
<td><strong>Determinism</strong></td>
<td>Same input/seed produces identical graph evolution (byte-equal snapshot, locked SHA-256 chain head)</td>
<td style="text-align: center;"><strong>Part I</strong></td>
</tr>
<tr class="even">
<td>2</td>
<td><strong>Lineage</strong></td>
<td>Trace evidence propagation across audit events; reconstruct training history from logs</td>
<td style="text-align: center;"><strong>Part I</strong></td>
</tr>
<tr class="odd">
<td>3</td>
<td><strong>State Stability</strong></td>
<td>Measure belief convergence and divergence per leaf</td>
<td style="text-align: center;"><strong>Part I</strong></td>
</tr>
<tr class="even">
<td>4</td>
<td><strong>Probabilistic Consistency</strong></td>
<td>Evaluate calibration / confidence propagation stability</td>
<td style="text-align: center;"><strong>Part I</strong></td>
</tr>
<tr class="odd">
<td>5</td>
<td><strong>Explainability</strong></td>
<td>Inspect causal transitions; expose abstain decisions and their reasoning</td>
<td style="text-align: center;"><strong>Part I</strong></td>
</tr>
<tr class="even">
<td>6</td>
<td><strong>Routing Efficiency</strong></td>
<td>Compare graph traversal cost; measure per-input route latency</td>
<td style="text-align: center;">Part II</td>
</tr>
<tr class="odd">
<td>7</td>
<td><strong>Memory Scaling</strong></td>
<td>Track graph growth vs complexity at varying <code>n_active_nodes</code></td>
<td style="text-align: center;">Part II</td>
</tr>
<tr class="even">
<td>8</td>
<td><strong>Adaptation</strong></td>
<td>Measure graph restructuring over time; structural action firing</td>
<td style="text-align: center;">Part II</td>
</tr>
<tr class="odd">
<td>9</td>
<td><strong>Temporal Dynamics</strong></td>
<td>Observe state evolution across time; drift response and auto-unfreeze</td>
<td style="text-align: center;">Part II</td>
</tr>
</tbody>
</table>
<p>The split rule: <strong>categories 1–5 measure properties of a fixed graph or a small region of one</strong> (a node, a leaf, a single replay). <strong>Categories 6–9 measure properties of an <em>evolving</em> graph</strong> (a structural decision, a routing distribution, a temporal trajectory). The line is not perfectly clean — some properties touch both, and where they do, this article and Part II reference each other.</p>
</section>
<section id="categories-covered-in-this-article" class="level2">
<h2 class="anchored" data-anchor-id="categories-covered-in-this-article">4. Categories covered in this article</h2>
<p>Brief formal definitions before the deep dives.</p>
<section id="determinism" class="level3">
<h3 class="anchored" data-anchor-id="determinism">4.1 Determinism</h3>
<p><strong>Definition.</strong> ABNG is deterministic if, given the same seed and the same observation sequence, two runs produce a byte-identical snapshot, including the same <code>chain_head</code> (the SHA-256 commitment at the end of the audit chain).</p>
<p><strong>Mechanism.</strong> Four substrate-level commitments inherited from CJC-Lang:</p>
<ul>
<li><strong>Kahan-compensated summation</strong> on every f64 accumulator. Plain <code>+=</code> is banned in canonical paths.</li>
<li><strong>No fused multiply-add (FMA)</strong> in any kernel that touches belief state. Some CPUs differ at the bit level when FMA is used; ABNG accepts the FLOPs cost to avoid the divergence.</li>
<li><strong><code>BTreeMap</code> and <code>BTreeSet</code> everywhere.</strong> <code>HashMap</code> is banned because Rust randomizes its hash seed per process for security; iteration order is therefore non-deterministic.</li>
<li><strong><code>SplitMix64</code> RNG seeded explicitly</strong> from <code>(graph.seed, node_id, layer_idx, kind_bit)</code> derivations. No global RNG state, no hardware RNG.</li>
</ul>
<p><strong>Why it matters.</strong> Without bit-identical determinism, every other property in this article degrades to “approximately holds.” Replay is approximate. Audit chain verification is approximate. Reproducibility of scientific claims is approximate. Approximate reproducibility is the standard in mainstream ML — ABNG is built around the more expensive guarantee that it doesn’t have to be.</p>
</section>
<section id="lineage" class="level3">
<h3 class="anchored" data-anchor-id="lineage">4.2 Lineage</h3>
<p><strong>Definition.</strong> Given a model snapshot and its audit log, <em>lineage</em> is the property that you can reconstruct the entire training history with cryptographic integrity. Any tampering — at the dataset, parameter, or audit-log level — surfaces as a typed error at replay time.</p>
<p><strong>Mechanism.</strong> Every state mutation appends one event to a SHA-256 hash-chained audit log:</p>
<pre class="text"><code>new_hash = sha256(prev_hash ‖ canonical_bytes(payload))</code></pre>
<p>Thirty audit kinds are defined (tags <code>0x00..0x1D</code>), covering everything from <code>Created</code> (genesis) to <code>BlrUpdated</code> (witness-only) to <code>ProvenanceStamped</code> (per-node <code>sha256(dataset_bytes)</code>). The chain is <strong>append-only</strong> — no event is ever deleted or modified. Reconstruction at replay time verifies every hash against the recomputed chain.</p>
<p><strong>Why it matters.</strong> For regulated ML, scientific replication, and postmortem debugging, the question “was this exact model trained on this exact data?” needs a paper trail with cryptographic teeth. <em>Established research-level</em> claim — Merkle trees (1979) and hash-chained audit logs predate ABNG by decades. ABNG’s contribution is applying the primitive at <strong>per-state-mutation granularity</strong> rather than at the per-artefact granularity that MLflow / DVC / Weights &amp; Biases use.</p>
</section>
<section id="state-stability" class="level3">
<h3 class="anchored" data-anchor-id="state-stability">4.3 State Stability</h3>
<p><strong>Definition.</strong> Per-leaf posterior convergence: as observations accumulate at a leaf, the posterior <code>(m, Λ, a, b)</code> over weights + noise variance converges toward truth at the rate Bayesian theory predicts.</p>
<p><strong>Mechanism.</strong> The Normal-Inverse-Gamma conjugate update for Gaussian-noise linear regression:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5CLambda_%7B%5Ctext%7Bnew%7D%7D%20=%20%5CLambda%20+%20%5Cphi%5Cphi%5E%5Ctop,%20%5Cquad%20m_%7B%5Ctext%7Bnew%7D%7D%20=%20%5CLambda_%7B%5Ctext%7Bnew%7D%7D%5E%7B-1%7D(%5CLambda%20m%20+%20%5Cphi%20y),%0A"></p>
<p><img src="https://latex.codecogs.com/png.latex?%0Aa_%7B%5Ctext%7Bnew%7D%7D%20=%20a%20+%20%5Ctfrac%7B1%7D%7B2%7D,%20%5Cquad%20b_%7B%5Ctext%7Bnew%7D%7D%20=%20b%20+%20%5Ctfrac%7B1%7D%7B2%7D%5Cleft(y%5E2%20+%20m%5E%5Ctop%20%5CLambda%20m%20-%20m_%7B%5Ctext%7Bnew%7D%7D%5E%5Ctop%20%5CLambda_%7B%5Ctext%7Bnew%7D%7D%20m_%7B%5Ctext%7Bnew%7D%7D%5Cright).%0A"></p>
<p>This is textbook Bayesian linear regression (Bishop, <em>Pattern Recognition and Machine Learning</em>, 2006, §3.3; Murphy, <em>Machine Learning: A Probabilistic Perspective</em>, 2012, §7.6). The substrate is deterministic by §4.1; the math is conjugate; the convergence rate is <code>O(1/√n)</code> by the standard Bernstein-von Mises results.</p>
<p><strong>Why it matters.</strong> Many architectures <em>claim</em> Bayesian behaviour without ever empirically demonstrating that their implementation converges to the analytical answer. ABNG’s Experiment α (§7.1) closes this gap with a direct measurement on synthetic data where <code>(w_true, σ²_true)</code> is known.</p>
</section>
<section id="probabilistic-consistency" class="level3">
<h3 class="anchored" data-anchor-id="probabilistic-consistency">4.4 Probabilistic Consistency</h3>
<p><strong>Definition.</strong> When the model reports a predicted probability <code>p̂</code>, the empirical frequency of the event being predicted is close to <code>p̂</code> across many predictions in the same probability bin. Quantified by Expected Calibration Error (ECE) over 15 bins.</p>
<p><strong>Mechanism.</strong> Per-leaf <code>CalibrationBins</code> track three quantities per bin: count, count of correct/positive outcomes, and the Kahan-accumulated sum of predicted probabilities. The reliability diagram plots (<code>mean predicted</code>, <code>observed frequency</code>) per bin; a perfectly-calibrated model lies on the diagonal.</p>
<p><strong>Why it matters.</strong> Calibrated probabilities are decision-theoretically meaningful. When a doctor reads “85% probability of malignancy,” that number being approximately correct on average is the foundation for using the prediction at all. Most deep networks need explicit recalibration (temperature scaling, Platt scaling); ABNG tracks calibration natively, per leaf.</p>
</section>
<section id="explainability" class="level3">
<h3 class="anchored" data-anchor-id="explainability">4.5 Explainability</h3>
<p><strong>Definition.</strong> For any individual prediction, you can answer “what evidence led the model here, and how confident should I be?” by reading the audit log, the route trace, and the per-leaf state.</p>
<p><strong>Mechanism.</strong> Three artefacts: - <strong>Composite OOD score</strong>: <code>max(density_score, prefix_distance, epistemic_z)</code>. Decision-theoretic abstain trigger. - <strong><code>abng_predict_snap</code></strong>: a byte-serializable “prediction blob” containing <code>(route_evidence, mean, lev, ale, ood_score, audit_pointer, provenance_stamp_hash)</code>. A user opening a saved prediction weeks later can verify every constituent of the decision. - <strong><code>cjcl abng inspect</code></strong>: CLI tool that walks the audit log, dumps per-node state, or compares two snapshots.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Explainability vs.&nbsp;interpretability
</div>
</div>
<div class="callout-body-container callout-body">
<p>These terms are often conflated. Following Doshi-Velez &amp; Kim 2017 (“Towards A Rigorous Science of Interpretable Machine Learning”), this article uses them with distinct meanings.</p>
<ul>
<li><strong>Explainability</strong>: the model can answer “<em>why</em> did you produce this output?” for a specific decision. ABNG provides this via the audit chain, route trace, and <code>predict_snap</code> blob. A user can replay any past decision and inspect its inputs and intermediate state.</li>
<li><strong>Interpretability</strong>: a human can read the model parameters and understand the function the model implements at a semantic level. ABNG does <strong>not</strong> provide this. The per-leaf MLP body is a small black-box network; the BLR posterior is interpretable only in the standard “weight on each feature dimension” sense.</li>
</ul>
<p>ABNG’s pitch is <strong>explainability</strong>, not interpretability — and the distinction is worth preserving in any technical framing of the architecture.</p>
</div>
</div>
</section>
</section>
<section id="experimental-goals" class="level2">
<h2 class="anchored" data-anchor-id="experimental-goals">5. Experimental goals</h2>
<p>Pre-registered claims this article supports. Pre-registered failure modes that would invalidate each claim.</p>
<section id="claims-and-pass-conditions" class="level3">
<h3 class="anchored" data-anchor-id="claims-and-pass-conditions">Claims and pass conditions</h3>
<table class="table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th>Claim</th>
<th>What’s claimed</th>
<th>Pass condition</th>
<th>Falsification</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>C1</strong></td>
<td>NIG conjugate update is correct</td>
<td><code>‖m_n − w_true‖</code> and <code>b_n/(a_n−1)</code> decay as <code>O(1/√n)</code> on synthetic data</td>
<td>Plateau, or wrong limit</td>
</tr>
<tr class="even">
<td><strong>Cδ</strong></td>
<td>Replay invariant under structural mutation</td>
<td>After ≥5 forced mutations (Grow / Split / Freeze etc.), <code>serialize(replay(blob)) == blob</code> exactly</td>
<td>Any byte differs</td>
</tr>
<tr class="odd">
<td><strong>C3</strong></td>
<td>Per-leaf calibration tracks correctly</td>
<td>Graph-level ECE &lt; 0.05 on a synthetic Bernoulli task with <code>p_true = sigmoid(2x−1)</code></td>
<td>ECE ≥ 0.05 or systematic deviation from diagonal</td>
</tr>
<tr class="even">
<td><strong>C5</strong></td>
<td>Selective prediction reduces error</td>
<td>Mean abs error on lowest-<code>k%</code> OOD-scored test points is monotone in <code>k</code> (with at least one ≥ 5× error reduction)</td>
<td>Curve is flat or non-monotonic</td>
</tr>
<tr class="odd">
<td><strong>Cζ</strong></td>
<td>Kahan summation premium is ~2×</td>
<td>Slowdown between 1.8× and 3× vs plain f64 <code>+=</code></td>
<td>Slowdown outside that range</td>
</tr>
</tbody>
</table>
<p>All five experiments <strong>passed</strong> in this run. Section 7 walks through each.</p>
</section>
<section id="pre-registered-failure-modes" class="level3">
<h3 class="anchored" data-anchor-id="pre-registered-failure-modes">Pre-registered failure modes</h3>
<p>What it would mean if each claim failed:</p>
<ul>
<li><strong>C1 fails</strong> → NIG implementation is buggy. <em>Most fundamental possible failure.</em> Would invalidate every downstream claim that depends on the BLR posterior.</li>
<li><strong>Cδ fails</strong> → audit chain or replay logic has a subtle bug masking under non-mutating workloads. Would force a careful architectural review.</li>
<li><strong>C3 fails</strong> → either the Bernoulli setup is mis-specified, or the calibration aggregation has a bug, or the per-leaf BLR is mismatching predicted probabilities (e.g., a clamping issue).</li>
<li><strong>C5 fails</strong> → composite OOD score isn’t actually useful as an abstain signal. ABNG’s “abstain when ABNG says abstain” pitch collapses; OOD score becomes a debug metric only.</li>
<li><strong>Cζ fails</strong> → either Kahan is much more expensive than expected (and the architecture’s determinism premium is understated), or Kahan is comparable to plain <code>+=</code> (suggesting the compiler is optimizing something away — would require diagnosis).</li>
</ul>
<p>The point of pre-registering these is that the experiments cannot be retroactively spun. If a test ran and the result missed the pre-registered range, the article would have to say so.</p>
</section>
</section>
<section id="mini-demo-narratives" class="level2">
<h2 class="anchored" data-anchor-id="mini-demo-narratives">6. Mini-demo narratives</h2>
<p>Five concrete scenarios — one per category — that exercise the mechanisms on small, inspectable inputs. Each mini-demo is <strong>faithful to ABNG’s actual architecture</strong>: routing to a single leaf per input, per-leaf belief update, audit chain advance. The narratives do <strong>not</strong> imply multi-node belief propagation across nodes — ABNG does not do that.</p>
<section id="md-i-1-replay-after-10k-observations-determinism" class="level3">
<h3 class="anchored" data-anchor-id="md-i-1-replay-after-10k-observations-determinism">MD-I-1 — Replay after 10K observations (Determinism)</h3>
<pre class="text"><code>Setup:  ABNG graph G_A, seed = 42, codebook with 4 bins
Training: 10,000 observations from a deterministic stream

After step 10,000:
  chain_head_A = 30d333f1...c4e468d    (32-byte SHA-256 commitment)
  snapshot_A   = 1.37 MB                (serialized blob)

Replay protocol:
  serialize(G_A) → blob_A
  replay(blob_A) → G_A'

Assertions (the test fails on any mismatch):
  G_A'.chain_head == G_A.chain_head        → YES, byte-equal
  serialize(G_A') == blob_A                → YES, byte-equal
  every BLR.state_hash() matches its witness → YES

Inspectable artefacts:
  cjcl abng inspect blob_A --audit         walk every event
  cjcl abng inspect blob_A --node 7        deep dump of leaf #7
  cjcl abng diff blob_A blob_B             per-node structural diff

Determinism property:
  Two independent runs of the same training trajectory produce
  bit-identical snapshots. The chain_head is a single 32-byte
  commitment to the entire training history; any single bit of
  difference anywhere in the chain shows up as a different head.</code></pre>
<p>What ABNG tracked: the audit log, every BLR posterior state hash, every per-node Welford accumulator. What was deterministic: every Kahan-summed accumulation, every routing decision, every audit-event hash. The replay path <em>re-derives</em> the same bytes — it does not store and load them; it reconstructs them from the log.</p>
</section>
<section id="md-i-2-tamper-detection-on-a-64-row-clinical-dataset-lineage" class="level3">
<h3 class="anchored" data-anchor-id="md-i-2-tamper-detection-on-a-64-row-clinical-dataset-lineage">MD-I-2 — Tamper detection on a 64-row clinical dataset (Lineage)</h3>
<pre class="text"><code>Setup:
  Dataset A: 64 rows (patient_id, dose, response)
            response = 0.2 + 0.6 · dose + 0.1 · dose²
  Dataset B: identical to A except patient 17's response += 0.5
            (a single-row tamper)

Lab A trains:
  g_A = train_abng(seed=7, dataset_A)
  stamp_A = sha256(dataset_A_bytes)
  g_A.stamp_provenance(root=0, stamp_A)
  → g_A.chain_head = AAAA...
  → g_A.nodes[0].provenance_stamp_hash = stamp_A

Attacker swaps in g_B (trained on tampered B):
  g_B.chain_head             ≠ g_A.chain_head
  g_B.nodes[k].blr.state_hash() ≠ g_A.nodes[k].blr.state_hash()
    (k = leaf containing patient 17)
  g_B.nodes[0].provenance_stamp_hash = sha256(B_bytes) ≠ stamp_A

Regulator runs three independent checks:
  ✗ chain_head mismatch
  ✗ BLR state-hash mismatch at the affected leaf
  ✗ provenance stamp mismatch

Inspectable artefacts:
  cjcl abng inspect g_B.snap --audit        full event log
  cjcl abng explain prediction.snap         "ABSTAIN: provenance
                                             stamp does not match"

Lineage property:
  An attacker forging the swap needs simultaneous SHA-256
  collisions across three independent commitments. Each one is
  computationally infeasible; all three is the strong claim.</code></pre>
<p>The three signals are <em>independent</em> by construction. The chain head depends on every audit event. The BLR state hash depends on the posterior of one leaf. The provenance stamp depends only on the dataset bytes. Forging any one of them while keeping the other two consistent is roughly as hard as inverting SHA-256.</p>
</section>
<section id="md-i-3-blr-posterior-converges-to-w_true-at-1n-state-stability" class="level3">
<h3 class="anchored" data-anchor-id="md-i-3-blr-posterior-converges-to-w_true-at-1n-state-stability">MD-I-3 — BLR posterior converges to <code>w_true</code> at 1/√n (State Stability)</h3>
<pre class="text"><code>Setup:
  Direct BlrState (no graph, no audit)
  Prior: λ_0 = 0.01, a_0 = 1.5, b_0 = 1.0
  Truth: w_true = [1.5, -2.0, 0.5, 0.7];  σ²_true = 0.04
  Observations: y_i = wᵀφ_i + N(0, σ²_true)
                φ_i drawn uniformly from [-1, 1]⁴

n     ‖m_n − w_true‖_2     b/(a-1)
─────────────────────────────────────
10    0.189                0.233
30    0.090                0.104
100   0.092                0.064
300   0.025                0.046
1000  0.026                0.042
3000  0.014                0.040
10000 0.004                0.0397

Decay rate: ~1/√n (matches Monte Carlo theory)
b/(a-1) approaches σ²_true = 0.04 from above (b_0 &gt; 0 forces this)

Inspectable artefacts:
  Every step appends one BlrUpdated event (32-byte witness)
  Full posterior tuple lives in the per-node snapshot section
  audit log records every update by sequence number

State-stability property:
  Sparse data → wide posterior. Abundant data → tight posterior.
  Both behaviours match analytical predictions exactly.</code></pre>
<p>The aleatoric estimate approaching truth <em>from above</em> is not coincidence — it is a property of the NIG prior with <code>b_0 &gt; 0</code>. The conjugate update can only ever <em>increase</em> <code>b</code> (the residual contribution is non-negative). The implementation correctly preserves this.</p>
</section>
<section id="md-i-4-9-calibration-bins-ece-0.015-probabilistic-consistency" class="level3">
<h3 class="anchored" data-anchor-id="md-i-4-9-calibration-bins-ece-0.015-probabilistic-consistency">MD-I-4 — 9 calibration bins, ECE = 0.015 (Probabilistic Consistency)</h3>
<pre class="text"><code>Setup:
  Synthetic Bernoulli problem
  p_true(x) = sigmoid(2x - 1) on x ~ Uniform(-1, 1)
  5000 observations
  4 codebook bins, 15 calibration bins per leaf

After training (aggregated across all leaves):
  bin   pred_mean   obs_freq   count
  ─────────────────────────────────
   2     0.167       0.182      825
   3     0.218       0.246      224
   4     0.305       0.296      287
   5     0.370       0.353      533
   6     0.427       0.461      440
   7     0.512       0.527      167
   8     0.571       0.596      473
   9     0.632       0.642      422
  10     0.686       0.692      185

Graph-level weighted ECE = 0.0150
(well below the 0.05 "well-calibrated" threshold)

Inspectable artefacts:
  Per-leaf 15-bin tables via the CalibrationBins public fields
  Aggregate ECE via abng_calibration_ece(leaf_id)
  Audit log records every observation as CalibrationUpdated (0x0E)

Probabilistic-consistency property:
  When the model says "30% probability," 30% of those cases
  actually occur. Across 9 non-empty bins, observed frequency
  is within 3 percentage points of predicted mean.</code></pre>
<p>The aggregate ECE of 0.015 is a <em>weighted</em> mean of the per-bin gaps, weighted by bin population. Bins 2 (n=825) and 5 (n=533) carry most of the weight; their gaps of 0.015 and 0.017 dominate the aggregate. ECE is therefore a <em>graph-level</em> metric, not a per-leaf one.</p>
</section>
<section id="md-i-5-abstain-when-abng-says-abstain-explainability" class="level3">
<h3 class="anchored" data-anchor-id="md-i-5-abstain-when-abng-says-abstain-explainability">MD-I-5 — Abstain when ABNG says abstain (Explainability)</h3>
<pre class="text"><code>Setup:
  Train on 200 observations of u(x, 0.1) = exp(-π²·0.1)·sin(πx)
    sampled from x ~ Uniform(0.25, 0.75)
  Test on 1000 held-out points sampled from x ~ Uniform(0, 1)
    (extends beyond the training region)

For each test point:
  predict at routed leaf → (mean, lev, ale)
  ood_score = max(density_score, prefix_distance, epistemic_z)

Selective prediction (keep lowest-k% of test set by OOD score):
  k = 5%     →  MAE = 7.86 × 10⁻³
  k = 30%    →  MAE = 7.39 × 10⁻³
  k = 50%    →  MAE = 9.31 × 10⁻³
  k = 70%    →  MAE = 3.90 × 10⁻²
  k = 100%   →  MAE = 7.42 × 10⁻²

Error reduction: 10.3× by abstaining on the worst 50%

Inspectable artefacts:
  predict_snap blob: (route_evidence, mean, lev, ale, ood_score,
                      audit_pointer, provenance_stamp_hash)
  Full audit trail per prediction
  cjcl abng explain prediction.snap

Explainability property:
  The OOD score correlates with prediction error. Predictions
  with high OOD score have higher empirical error; predictions
  with low OOD score can be trusted. The abstain decision is
  defensible by inspecting the per-prediction lineage.</code></pre>
<p>The curve has a clear knee around <code>k = 50%</code>. Inputs below the knee are inside the training region; inputs above it are extrapolating. The OOD score is doing its job: it rises as the input moves away from where the model has evidence, and at high OOD the prediction becomes unreliable. The 10× error reduction is measured, not extrapolated.</p>
</section>
</section>
<section id="deep-experiments" class="level2">
<h2 class="anchored" data-anchor-id="deep-experiments">7. Deep experiments</h2>
<p>Five experiments, each with the same shape: hypothesis, methodology, results table, figure, verdict (pass/fail against pre-registered criteria), and honest caveats.</p>
<section id="experiment-α-nig-posterior-consistency" class="level3">
<h3 class="anchored" data-anchor-id="experiment-α-nig-posterior-consistency">7.1 Experiment α — NIG posterior consistency</h3>
<p><strong>Hypothesis (C1).</strong> <code>BlrState::update</code> correctly implements the NIG conjugate update. The posterior mean converges to <code>w_true</code>; aleatoric estimate <code>b/(a−1)</code> approaches <code>σ²_true</code> from above.</p>
<p><strong>Methodology.</strong> Synthetic data: - <code>w_true = [1.5, −2.0, 0.5, 0.7]</code>, <code>σ²_true = 0.04</code> - Feature vectors <code>φ_i ~ Uniform(-1, 1)⁴</code> - Noise via Box-Muller transform on uniform draws, scaled to <code>σ_true</code> - Direct <code>BlrState</code> instance (bypasses graph dispatch / audit) - Increasing <code>n ∈ {10, 30, 100, 300, 1000, 3000, 10000}</code></p>
<p><strong>Result.</strong></p>
<table class="table">
<thead>
<tr class="header">
<th style="text-align: right;">n</th>
<th style="text-align: right;"><code>‖m_n − w_true‖_2</code></th>
<th style="text-align: right;"><code>b/(a−1)</code></th>
<th style="text-align: right;"><code>\|b/(a−1) − σ²_true\|</code></th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: right;">10</td>
<td style="text-align: right;">0.189</td>
<td style="text-align: right;">0.233</td>
<td style="text-align: right;">0.193</td>
</tr>
<tr class="even">
<td style="text-align: right;">30</td>
<td style="text-align: right;">0.090</td>
<td style="text-align: right;">0.104</td>
<td style="text-align: right;">0.064</td>
</tr>
<tr class="odd">
<td style="text-align: right;">100</td>
<td style="text-align: right;">0.092</td>
<td style="text-align: right;">0.064</td>
<td style="text-align: right;">0.024</td>
</tr>
<tr class="even">
<td style="text-align: right;">300</td>
<td style="text-align: right;">0.025</td>
<td style="text-align: right;">0.046</td>
<td style="text-align: right;">0.006</td>
</tr>
<tr class="odd">
<td style="text-align: right;">1,000</td>
<td style="text-align: right;">0.026</td>
<td style="text-align: right;">0.042</td>
<td style="text-align: right;">0.002</td>
</tr>
<tr class="even">
<td style="text-align: right;">3,000</td>
<td style="text-align: right;">0.014</td>
<td style="text-align: right;">0.040</td>
<td style="text-align: right;">0.0005</td>
</tr>
<tr class="odd">
<td style="text-align: right;">10,000</td>
<td style="text-align: right;">0.004</td>
<td style="text-align: right;">0.0397</td>
<td style="text-align: right;">0.0003</td>
</tr>
</tbody>
</table>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://adamezzat1.github.io/blog/posts/abng-benchmarks/figures/v5_posterior_convergence.png" class="img-fluid figure-img"></p>
<figcaption class="figure-caption">V5 — NIG posterior convergence (Experiment α)</figcaption>
</figure>
</div>
<p><strong>Verdict.</strong> Both quantities decay as <code>1/√n</code> (visible on log-log axes as a slope of −0.5). <code>b/(a−1)</code> approaches <code>σ²_true</code> strictly from above, as the prior <code>b_0 &gt; 0</code> forces. <strong>C1 passed.</strong> Tag: <strong><em>Proven at the tested scale</em></strong>.</p>
<p>This is the <strong>cheapest possible falsification</strong> of the BLR conjugate implementation. The data is synthetic with known truth; the math has a known closed form; the decay rate has a known theoretical prediction. If any of those didn’t match, the implementation is wrong. They all match.</p>
</section>
<section id="experiment-δ-replay-invariance-under-graph-mutation" class="level3">
<h3 class="anchored" data-anchor-id="experiment-δ-replay-invariance-under-graph-mutation">7.2 Experiment δ — Replay invariance under graph mutation</h3>
<p><strong>Hypothesis (Cδ).</strong> A graph that has fired structural mutations (Grow / Split / Merge / Prune / Freeze / Compress) still replays byte-identically.</p>
<p><strong>Methodology.</strong> Build a graph, train on 200 observations, then force a sequence of mutations: 3× <code>force_grow</code>, 2× <code>force_split</code> attempts (one succeeds), 2× <code>force_freeze</code>. Snapshot. Run <code>replay()</code>. Compare <code>serialize(replayed) == serialize(original)</code> byte-for-byte AND <code>chain_head_original == chain_head_replayed</code>.</p>
<p><strong>Result.</strong></p>
<table class="table">
<colgroup>
<col style="width: 50%">
<col style="width: 50%">
</colgroup>
<thead>
<tr class="header">
<th>Metric</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Total audit events</td>
<td>433</td>
</tr>
<tr class="even">
<td>Structural mutations attempted</td>
<td>5</td>
</tr>
<tr class="odd">
<td>Action counts <code>[Grow, Split, Merge, Prune, Compress, Freeze]</code></td>
<td><code>[3, 2, 0, 0, 0, 2]</code></td>
</tr>
<tr class="even">
<td>Snapshot bytes</td>
<td>69,915</td>
</tr>
<tr class="odd">
<td>Replay time</td>
<td>1.27 ms</td>
</tr>
<tr class="even">
<td><strong>byte_identical</strong></td>
<td><strong>true</strong> ✓</td>
</tr>
<tr class="odd">
<td><strong>chain_head_match</strong></td>
<td><strong>true</strong> ✓</td>
</tr>
</tbody>
</table>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://adamezzat1.github.io/blog/posts/abng-benchmarks/figures/v7_replay_consistency.png" class="img-fluid figure-img"></p>
<figcaption class="figure-caption">V7 — Replay consistency under mutation</figcaption>
</figure>
</div>
<p><strong>Verdict.</strong> <strong>Cδ passed.</strong> The invariant holds under structural mutation at the tested scale. Tag: <strong><em>Proven at the tested scale</em></strong>.</p>
<p><strong>Caveat (Skeptical Reviewer’s flag).</strong> This is at 5 mutations on a small graph. Larger mutation budgets (≥1000 structural events) and longer training trajectories are not yet stress-tested. The Phase 0.7 handoff doc lists this as candidate work for Phase 0.8.</p>
</section>
<section id="experiment-η-per-bin-reliability-diagram" class="level3">
<h3 class="anchored" data-anchor-id="experiment-η-per-bin-reliability-diagram">7.3 Experiment η — Per-bin reliability diagram</h3>
<p><strong>Hypothesis (C3).</strong> Per-leaf 15-bin reliability diagram tracks calibration correctly. On a synthetic Bernoulli problem, observed frequency per bin should approach predicted mean per bin (curve hugs the diagonal).</p>
<p><strong>Methodology.</strong> <code>p_true(x) = sigmoid(2x − 1)</code>, <code>x ~ Uniform(-1, 1)</code>. Sample <code>y_i ~ Bernoulli(p_true(x_i))</code> for n = 5,000 observations. ABNG models the success probability per-leaf via BLR; clamp output to <code>[0, 1]</code> for <code>calibration_observe</code>. Aggregate per-bin <code>(count, sum_of_predicted, count_of_correct)</code> across all leaves.</p>
<p><strong>Result.</strong> Graph-level ECE = <strong>0.0150</strong>. Per-bin data shown in MD-I-4 above.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://adamezzat1.github.io/blog/posts/abng-benchmarks/figures/v2_reliability_diagram.png" class="img-fluid figure-img"></p>
<figcaption class="figure-caption">V2 — Per-bin reliability diagram</figcaption>
</figure>
</div>
<p>The bubble size in the figure encodes per-bin observation count: large bubbles are statistically reliable, small ones less so. The curve hugs the diagonal across all 9 non-empty bins.</p>
<p><strong>Verdict.</strong> <strong>C3 passed.</strong> Tag: <strong><em>Proven at the tested scale</em></strong>.</p>
<p><strong>Caveats.</strong></p>
<ol type="1">
<li>This is one synthetic Bernoulli task with a well-specified <code>p_true</code>. Real-world classifiers tested on harder distributions (class imbalance, label noise, distribution shift) often show worse calibration.</li>
<li>The aggregate ECE is a <em>weighted</em> mean across leaves, not a per-leaf metric. A small leaf with terrible calibration can be invisible in the aggregate if it has few observations.</li>
<li><em>Predictive coverage</em> (does the Student-t predictive distribution contain <code>y</code> at the rate its width suggests?) is <strong>not measured here</strong>. Listed as future work.</li>
</ol>
</section>
<section id="experiment-γ-selective-prediction-curve-on-ood" class="level3">
<h3 class="anchored" data-anchor-id="experiment-γ-selective-prediction-curve-on-ood">7.4 Experiment γ — Selective prediction curve on OOD</h3>
<p><strong>Hypothesis (C5).</strong> Abstaining at high OOD score improves average accuracy on kept predictions.</p>
<p><strong>Methodology.</strong> Train ABNG on 200 deterministic observations of <code>u(x, 0.1)</code> concentrated in <code>[0.25, 0.75]</code>. Compute predictions on 1000 held-out points uniformly distributed on <code>[0, 1]</code> (extending beyond training). Sort by composite OOD score. For each <code>k ∈ {5, 10, 20, 30, 50, 70, 100}%</code>, compute mean abs error on the lowest-k% scored points.</p>
<p><strong>Result.</strong></p>
<table class="table">
<thead>
<tr class="header">
<th style="text-align: right;">% kept</th>
<th style="text-align: right;">n_kept</th>
<th style="text-align: right;">Mean abs error</th>
<th style="text-align: right;">Max OOD in kept</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: right;">5</td>
<td style="text-align: right;">50</td>
<td style="text-align: right;">7.86 × 10⁻³</td>
<td style="text-align: right;">1.05 × 10⁻²</td>
</tr>
<tr class="even">
<td style="text-align: right;">10</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">7.72 × 10⁻³</td>
<td style="text-align: right;">1.11 × 10⁻²</td>
</tr>
<tr class="odd">
<td style="text-align: right;">20</td>
<td style="text-align: right;">200</td>
<td style="text-align: right;">7.19 × 10⁻³</td>
<td style="text-align: right;">1.36 × 10⁻²</td>
</tr>
<tr class="even">
<td style="text-align: right;">30</td>
<td style="text-align: right;">300</td>
<td style="text-align: right;">7.39 × 10⁻³</td>
<td style="text-align: right;">1.79 × 10⁻²</td>
</tr>
<tr class="odd">
<td style="text-align: right;">50</td>
<td style="text-align: right;">500</td>
<td style="text-align: right;">9.31 × 10⁻³</td>
<td style="text-align: right;">3.22 × 10⁻²</td>
</tr>
<tr class="even">
<td style="text-align: right;">70</td>
<td style="text-align: right;">700</td>
<td style="text-align: right;">3.90 × 10⁻²</td>
<td style="text-align: right;">1.00</td>
</tr>
<tr class="odd">
<td style="text-align: right;">100</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">7.42 × 10⁻²</td>
<td style="text-align: right;">1.00</td>
</tr>
</tbody>
</table>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://adamezzat1.github.io/blog/posts/abng-benchmarks/figures/v6_selective_prediction.png" class="img-fluid figure-img"></p>
<figcaption class="figure-caption">V6 — Selective prediction curve</figcaption>
</figure>
</div>
<p>The curve is strikingly monotonic up to 50% kept, then jumps sharply. <strong>Keeping the lowest-OOD 30% gives 7.4 × 10⁻³ error; keeping all 100% gives 7.42 × 10⁻² error — a 10× error reduction by abstaining on the worst half.</strong></p>
<p><strong>Verdict.</strong> <strong>C5 passed.</strong> Tag: <strong><em>Proven at the tested scale</em></strong>.</p>
<p><strong>Caveats.</strong></p>
<ol type="1">
<li>This is one scenario where the test distribution extends beyond the training distribution. ABNG has not been measured on standard OOD benchmarks (CIFAR-10-C, ImageNet-O).</li>
<li>The composite <code>ood_score = max(density, prefix_distance, epistemic_z)</code> is <em>engineering-driven</em> rather than fully Bayesian. It’s deliberately over-conservative (false-positive abstains accepted to reduce false negatives).</li>
<li>Comparison against published abstain mechanisms (Mahalanobis-based OOD detection from Lee et al.&nbsp;2018; deep SVDD from Ruff et al.&nbsp;2018; conformal prediction) is <strong>not measured here</strong>.</li>
</ol>
</section>
<section id="experiment-ζ-kahan-vs-plain-summation-determinism-premium" class="level3">
<h3 class="anchored" data-anchor-id="experiment-ζ-kahan-vs-plain-summation-determinism-premium">7.5 Experiment ζ — Kahan-vs-plain summation determinism premium</h3>
<p><strong>Hypothesis (Cζ).</strong> Kahan accumulation is in the ~2× range claimed in the architecture article.</p>
<p><strong>Methodology.</strong> Sum 1,000,000 f64 values via plain <code>+=</code> vs <code>KahanAccumulatorF64::add</code>. Use min-of-7 trials per side. Each value is <code>0.001 + sin(i) · 0.0001</code> to defeat optimizer constant-folding.</p>
<p><strong>Result.</strong></p>
<table class="table">
<thead>
<tr class="header">
<th>Method</th>
<th style="text-align: right;">Per-element cost</th>
<th style="text-align: right;">Slowdown vs plain</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>plain f64 <code>+=</code></td>
<td style="text-align: right;">1.465 ns</td>
<td style="text-align: right;">1.00× (baseline)</td>
</tr>
<tr class="even">
<td><code>KahanAccumulatorF64</code></td>
<td style="text-align: right;">3.521 ns</td>
<td style="text-align: right;"><strong>2.40×</strong></td>
</tr>
</tbody>
</table>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://adamezzat1.github.io/blog/posts/abng-benchmarks/figures/v_zeta_kahan_vs_plain.png" class="img-fluid figure-img"></p>
<figcaption class="figure-caption">V-ζ — Kahan vs plain f64 summation</figcaption>
</figure>
</div>
<p><strong>Verdict.</strong> <strong>Cζ passed.</strong> Kahan is 2.40× slower than plain <code>+=</code>. The 2.40× factor is the per-element cost of the determinism contract on the single most ubiquitous primitive in ABNG’s hot path. Tag: <strong><em>Proven at the tested scale</em></strong>.</p>
<p>When aggregated across the BLR update (multiple Kahan accumulators for Cholesky and triangular solve), the Welford accumulators in <code>observe</code>, the ECE binning in calibration, and the density tracker, the 2.40× per-primitive cost compounds into the <strong>~7.5× graph-dispatch cost</strong> documented in §9 below.</p>
</section>
<section id="closing-the-four-additional-gaps" class="level3">
<h3 class="anchored" data-anchor-id="closing-the-four-additional-gaps">7.6 Closing the four additional gaps</h3>
<p>Three additional experiments — κ (predictive coverage), λ (statistical CIs on per-op timings), μ (audit log at 10⁶ events) — close gaps flagged in the previous version of §10. A fourth gap (external lineage baseline against MLflow/DVC) remains deferred for the reasons discussed at the end of this subsection.</p>
<section id="experiment-κ-predictive-coverage" class="level4">
<h4 class="anchored" data-anchor-id="experiment-κ-predictive-coverage">7.6.1 Experiment κ — Predictive coverage</h4>
<p><strong>Hypothesis (Cκ).</strong> ABNG’s Student-t predictive distribution achieves close to nominal coverage on a regression task with known Gaussian noise. Calibration (Experiment η) measures whether predicted <em>probabilities</em> agree with observed <em>frequencies</em> on a binary outcome; predictive coverage measures whether the <em>confidence intervals</em> of the continuous predictive distribution contain the true <code>y</code> at the rate they claim.</p>
<p><strong>Methodology.</strong> Train one direct <code>BlrState</code> on synthetic data with <code>w_true = [1.5, -2.0, 0.5, 0.7]</code>, <code>σ²_true = 0.04</code>, <code>n_train = 2,000</code>. On 5,000 fresh held-out points: compute the Student-t predictive parameters (<code>mean = mᵀφ</code>, <code>scale = sqrt((b/a) · (1 + φᵀΛ⁻¹φ))</code>, <code>df = 2a</code>). Since <code>df &gt; 4000</code> at n_train=2000, the Student-t is essentially a Gaussian, so two-sided coverage intervals at level <code>c</code> correspond to the standard normal quantile <code>z = Φ⁻¹((1+c)/2)</code>. Count what fraction of held-out true <code>y</code> values fall inside the interval.</p>
<p><strong>Result.</strong></p>
<table class="table">
<thead>
<tr class="header">
<th style="text-align: right;">Nominal level</th>
<th style="text-align: right;">Empirical coverage</th>
<th style="text-align: right;">Deviation from nominal</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: right;">0.50</td>
<td style="text-align: right;">0.5046</td>
<td style="text-align: right;">+0.0046</td>
</tr>
<tr class="even">
<td style="text-align: right;">0.80</td>
<td style="text-align: right;">0.8076</td>
<td style="text-align: right;">+0.0076</td>
</tr>
<tr class="odd">
<td style="text-align: right;">0.90</td>
<td style="text-align: right;">0.9102</td>
<td style="text-align: right;">+0.0102</td>
</tr>
<tr class="even">
<td style="text-align: right;">0.95</td>
<td style="text-align: right;">0.9544</td>
<td style="text-align: right;">+0.0044</td>
</tr>
<tr class="odd">
<td style="text-align: right;">0.99</td>
<td style="text-align: right;">0.9896</td>
<td style="text-align: right;">−0.0004</td>
</tr>
</tbody>
</table>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://adamezzat1.github.io/blog/posts/abng-benchmarks/figures/v_kappa_predictive_coverage.png" class="img-fluid figure-img"></p>
<figcaption class="figure-caption">V-κ — Predictive coverage (Experiment κ)</figcaption>
</figure>
</div>
<p><strong>Verdict.</strong> All five nominal levels are within ±0.01 of the empirical coverage. <strong>Cκ passed.</strong> Tag: <strong><em>Proven at the tested scale</em></strong>.</p>
<p>The predictive distribution is <em>honest</em> in the strict Bayesian sense: a 95% interval contains the true value about 95% of the time (95.44% empirically). This is a strictly stronger statement than the calibration result in §7.3 — calibration handled binary outcomes; coverage handles the full continuous predictive density.</p>
<p><strong>Caveats.</strong> (1) The test uses well-specified Gaussian-noise data — the BLR’s model assumptions match the data-generating process exactly. Misspecified data (heavy-tailed noise, heteroscedasticity, non-linear truth in the basis) would show worse coverage. (2) The Student-t computation uses the normal approximation valid at <code>df &gt; 100</code>, which all <code>n_train ≥ 50</code> runs satisfy. For very small training data, the exact Student-t quantiles would be needed.</p>
</section>
<section id="experiment-λ-per-op-statistical-confidence-intervals" class="level4">
<h4 class="anchored" data-anchor-id="experiment-λ-per-op-statistical-confidence-intervals">7.6.2 Experiment λ — Per-op statistical confidence intervals</h4>
<p><strong>Hypothesis (Cλ).</strong> The per-op cost measurements reported in §9 are stable across multiple trials. Single-run minimums are an honest <em>lower bound</em> but understate variance; median + IQR + min/max gives the full picture.</p>
<p><strong>Methodology.</strong> For each of five hot-path operations (<code>descend</code>, <code>encode_prefix</code>, <code>blr_predict</code>, <code>blr_state_update_direct</code>, <code>observe</code>), measure per-op cost over 30 trials of 10,000 iterations each, including warmup. Report median, IQR (Q1, Q3), min, max, and coefficient of variation (CV = stddev/mean).</p>
<p><strong>Result.</strong></p>
<table class="table">
<colgroup>
<col style="width: 13%">
<col style="width: 17%">
<col style="width: 17%">
<col style="width: 17%">
<col style="width: 17%">
<col style="width: 17%">
</colgroup>
<thead>
<tr class="header">
<th>Operation</th>
<th style="text-align: right;">Median (ns)</th>
<th style="text-align: right;">IQR (ns)</th>
<th style="text-align: right;">Min (ns)</th>
<th style="text-align: right;">Max (ns)</th>
<th style="text-align: right;">CV</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>descend</code></td>
<td style="text-align: right;">117</td>
<td style="text-align: right;">[105, 135]</td>
<td style="text-align: right;">94</td>
<td style="text-align: right;">254</td>
<td style="text-align: right;">0.28</td>
</tr>
<tr class="even">
<td><code>encode_prefix</code></td>
<td style="text-align: right;">128</td>
<td style="text-align: right;">[122, 150]</td>
<td style="text-align: right;">114</td>
<td style="text-align: right;">218</td>
<td style="text-align: right;">0.20</td>
</tr>
<tr class="odd">
<td><code>blr_predict</code></td>
<td style="text-align: right;">1,193</td>
<td style="text-align: right;">[1,068, 1,854]</td>
<td style="text-align: right;">795</td>
<td style="text-align: right;">3,363</td>
<td style="text-align: right;">0.40</td>
</tr>
<tr class="even">
<td><code>blr_state_update_direct</code></td>
<td style="text-align: right;">3,401</td>
<td style="text-align: right;">[2,915, 4,577]</td>
<td style="text-align: right;">2,220</td>
<td style="text-align: right;">12,083</td>
<td style="text-align: right;">0.58</td>
</tr>
<tr class="odd">
<td><code>observe</code></td>
<td style="text-align: right;">3,194</td>
<td style="text-align: right;">[3,058, 3,494]</td>
<td style="text-align: right;">2,757</td>
<td style="text-align: right;">5,605</td>
<td style="text-align: right;">0.16</td>
</tr>
</tbody>
</table>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://adamezzat1.github.io/blog/posts/abng-benchmarks/figures/v_lambda_per_op_cis.png" class="img-fluid figure-img"></p>
<figcaption class="figure-caption">V-λ — Per-op statistical CIs (Experiment λ)</figcaption>
</figure>
</div>
<p><strong>Verdict.</strong> Two stable operations (<code>encode_prefix</code>, <code>observe</code>) have CV ≤ 0.20 — single-run minimums are within ~20% of the typical timing. Two operations (<code>descend</code>, <code>blr_predict</code>) have CV ≈ 0.28 to 0.40 — still in a reasonable range but worth quoting medians rather than mins. <strong>One operation (<code>blr_state_update_direct</code>) has CV = 0.58</strong>, indicating high variance: the max (12 µs) is 5× the min (2.2 µs). This is a real signal — Cholesky factorization is cache-sensitive, and across 30 trials with fresh graph setup the cache state differs.</p>
<p><strong>The honest takeaway</strong>: the §9 microbench table’s single-run numbers are <strong>roughly correct as order-of-magnitude estimates</strong> but <strong>understate variance</strong> by 30-60% on the BLR-heavy operations. Future versions of the article should quote median + IQR for all per-op claims. Tag: <strong><em>Demonstrated</em></strong> — variance characterized, but not yet folded into the §9 narrative.</p>
</section>
<section id="experiment-μ-audit-log-scaling-to-1-million-events" class="level4">
<h4 class="anchored" data-anchor-id="experiment-μ-audit-log-scaling-to-1-million-events">7.6.3 Experiment μ — Audit log scaling to 1 million events</h4>
<p><strong>Hypothesis (Cμ).</strong> Audit-log size, serialize time, verify time, and replay time scale linearly with <code>n_events</code> to at least 10⁶ events. The previous version of this article tested only up to ~100K events.</p>
<p><strong>Methodology.</strong> Train an ABNG graph with <code>n</code> observations (<code>n ∈ {10⁴, 10⁵, 10⁶}</code>) on a simple stream — no structural mutation, just <code>observe</code> calls. Measure: total audit events, train wall-clock, serialize wall-clock, blob size, <code>verify_chain</code> wall-clock, replay wall-clock. Verify chain integrity at each scale.</p>
<p><strong>Result.</strong></p>
<table class="table">
<colgroup>
<col style="width: 12%">
<col style="width: 12%">
<col style="width: 12%">
<col style="width: 12%">
<col style="width: 12%">
<col style="width: 12%">
<col style="width: 12%">
<col style="width: 15%">
</colgroup>
<thead>
<tr class="header">
<th style="text-align: right;">n_obs</th>
<th style="text-align: right;">n_events</th>
<th style="text-align: right;">Train (ms)</th>
<th style="text-align: right;">Serialize (ms)</th>
<th style="text-align: right;">Blob size</th>
<th style="text-align: right;">Verify (ms)</th>
<th style="text-align: right;">Replay (ms)</th>
<th style="text-align: center;">Chain OK?</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: right;">10,000</td>
<td style="text-align: right;">10,019</td>
<td style="text-align: right;">27</td>
<td style="text-align: right;">3.3</td>
<td style="text-align: right;">1.4 MB</td>
<td style="text-align: right;">8.9</td>
<td style="text-align: right;">34</td>
<td style="text-align: center;">✓</td>
</tr>
<tr class="even">
<td style="text-align: right;">100,000</td>
<td style="text-align: right;">100,019</td>
<td style="text-align: right;">308</td>
<td style="text-align: right;">36</td>
<td style="text-align: right;">13.7 MB</td>
<td style="text-align: right;">97</td>
<td style="text-align: right;">378</td>
<td style="text-align: center;">✓</td>
</tr>
<tr class="odd">
<td style="text-align: right;">1,000,000</td>
<td style="text-align: right;">1,000,019</td>
<td style="text-align: right;">4,514</td>
<td style="text-align: right;">906</td>
<td style="text-align: right;">137 MB</td>
<td style="text-align: right;">1,880</td>
<td style="text-align: right;">5,158</td>
<td style="text-align: center;">✓</td>
</tr>
</tbody>
</table>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://adamezzat1.github.io/blog/posts/abng-benchmarks/figures/v_mu_audit_log_scaling.png" class="img-fluid figure-img"></p>
<figcaption class="figure-caption">V-μ — Audit log scaling to 1M events (Experiment μ)</figcaption>
</figure>
</div>
<p><strong>Verdict.</strong> <strong>Cμ passed.</strong> Tag: <strong><em>Proven at the tested scale</em></strong> (up to 10⁶ events).</p>
<p>Quantitative observations:</p>
<ul>
<li><strong>Blob size is perfectly linear</strong> at ~137 bytes/event (1.37 MB → 13.7 MB → 137 MB matches 10⁴ → 10⁵ → 10⁶ exactly).</li>
<li><strong>Train, serialize, replay</strong> scale within ~10-25× per decade of n, which is roughly linear with some super-linearity at large scale.</li>
<li><strong>Verify time</strong> grows slightly super-linearly: 970 ns/event at 10⁵, 1.88 µs/event at 10⁶ — a ~2× slowdown per decade. This is consistent with cache effects (the 137 MB blob exceeds typical L2/L3 cache).</li>
<li><strong>Chain integrity holds at every scale.</strong> <code>verify_chain()</code> returns <code>Ok</code> and <code>replay(snap).chain_head == snap.chain_head</code> exactly at 1M events.</li>
</ul>
<p><strong>Honest takeaway.</strong> A 1M-event audit log is <strong>manageable</strong> (137 MB blob, ~5 seconds replay, ~2 seconds verify). At 10⁷ events, this extrapolates to ~1.4 GB and ~50 seconds replay — pushing into “use compaction” territory but not catastrophic. Below 10⁹ events, the architecture’s audit-chain story holds; beyond that, the smart-replay path (StatsSnapshot markers + selective skip) becomes necessary and is not yet stress-tested at that scale.</p>
</section>
<section id="the-fourth-gap-external-lineage-baseline-mlflow-dvc" class="level4">
<h4 class="anchored" data-anchor-id="the-fourth-gap-external-lineage-baseline-mlflow-dvc">7.6.4 The fourth gap: external lineage baseline (MLflow / DVC)</h4>
<p>The fourth previously-deferred item — a head-to-head comparison against MLflow or DVC’s lineage tracking — <strong>remains deferred</strong>. The reason isn’t lack of feasibility but scope: a fair comparison would need to:</p>
<ol type="1">
<li>Set up MLflow and DVC instances with realistic configurations.</li>
<li>Define a tampering scenario that both can attempt to detect at their granularity (artefact-level for MLflow/DVC vs state-mutation-level for ABNG).</li>
<li>Measure detection rates under various tampering protocols (single-row, multi-row, post-training override, full-pipeline compromise).</li>
<li>Compare reproduction cost: storage size, verification time, debugging accessibility.</li>
</ol>
<p>This is roughly <strong>one to two weeks of focused work</strong> including running MLflow/DVC tutorials, building the comparison harness, and writing up the methodology. The result would warrant its own article rather than a section in this one. It’s listed as a separate item in the future-work plan.</p>
<p><strong>For the purposes of this article</strong>, the granularity-advantage claim (ABNG tracks state mutations; MLflow tracks artefacts) is <strong>positional rather than empirical</strong> — the architectural difference is real and documented in <a href="../../../blog/posts/abng-deterministic-systems/index.html">Part 2 of the trilogy</a>, but the wall-clock comparison and detection-rate measurement aren’t done here.</p>
</section>
</section>
</section>
<section id="demo-deep-dives" class="level2">
<h2 class="anchored" data-anchor-id="demo-deep-dives">8. Demo deep-dives</h2>
<p>Two existing demos that illustrate Part I’s categories through worked examples.</p>
<section id="pinn-uncertainty-state-stability-on-a-known-truth" class="level3">
<h3 class="anchored" data-anchor-id="pinn-uncertainty-state-stability-on-a-known-truth">8.1 PINN uncertainty (state stability on a known truth)</h3>
<p><strong>The setup.</strong> Solve the 1D heat equation <code>∂u/∂t = α · ∂²u/∂x²</code> with Dirichlet boundary conditions <code>u(0, t) = u(1, t) = 0</code> and initial condition <code>u(x, 0) = sin(πx)</code>. At <code>t = 0.1, α = 1</code>, the analytical solution is <code>u(x, 0.1) = exp(-π² · 0.1) · sin(πx) ≈ 0.3722 · sin(πx)</code>.</p>
<p><strong>The architecture used.</strong> 4-leaf ABNG graph over <code>x ∈ [0, 1]</code> with codebook bin edges at <code>0.25 / 0.5 / 0.75</code>. Each leaf has its own BLR head over the 4-D feature basis <code>[1, x, sin(πx), cos(πx)]</code> — engineered so the analytical solution is exactly in span.</p>
<p><strong>The training.</strong> 48 observations total: 32 uniformly in the interior <code>[0.25, 0.75]</code>, 4 in each edge region. The asymmetry is deliberate — it lets the test verify that uncertainty tracks training density.</p>
<p><strong>What the test asserts (at the tested probe points).</strong></p>
<div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb7-1"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">#[</span>test<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb7-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> pinn_trained_interior_approximates_analytical_solution() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb7-3">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">mut</span> g <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> build_pinn_graph(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">7</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb7-4">    train_pinn(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">mut</span> g)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb7-5">    <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">for</span> x <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">in</span> [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.30</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.45</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.55</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.70</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb7-6">        <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> leaf <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> route_to_leaf(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> x)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb7-7">        <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> phi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pinn_features(x)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb7-8">        <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> (mean<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> _lev<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> _ale) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>blr_predict(leaf<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>phi)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>unwrap()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb7-9">        <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> truth <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> analytical_u(x)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb7-10">        <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">assert!</span>((mean <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> truth)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>abs() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.05</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb7-11">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"interior fit at x={x}: predicted {mean}, truth {truth}"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb7-12">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb7-13"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div>
<p>The test passes. At the tested probe points, ABNG fits the analytical solution within 0.05 MAE.</p>
<p><strong>Why the test is a “Proven” claim and not “Demonstrated.”</strong> It has a strict numerical pass condition (<code>&lt; 0.05</code>). It runs in CI. The chain head is canary-locked to SHA-256 <code>30d333f1f7dca5acaa76b0e4bfdbd4a733df38c6adeda094ae69cf0e9c4e468d</code>. Any change to the BLR conjugate update arithmetic, prefix routing, or audit event payload layout would surface as a hex mismatch in CI.</p>
<p><strong>The companion uncertainty test.</strong></p>
<div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb8-1"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">#[</span>test<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb8-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> pinn_tangible_benefit_lev_lower_in_dense_region() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb8-3">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// ... train_pinn(...) with 32 interior, 4×2 edge samples ...</span></span>
<span id="cb8-4">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">mut</span> interior_levs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">Vec</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span>new()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb8-5">    <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">for</span> x <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">in</span> [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.30</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.40</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.50</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.60</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.70</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb8-6">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// ... lev = blr_predict(...).1 ...</span></span>
<span id="cb8-7">        interior_levs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>push(lev)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb8-8">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb8-9">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> max_interior <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> interior_levs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>iter()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>cloned()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>fold(<span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">f64</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span><span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NEG_INFINITY</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">f64</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span>max)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb8-10">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// ... edge_levs from x ∈ {0.05, 0.10, 0.15, 0.85, 0.90, 0.95} ...</span></span>
<span id="cb8-11">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> min_edge <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> edge_levs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>iter()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>cloned()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>fold(<span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">f64</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span><span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">INFINITY</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">f64</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span>min)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb8-12">    <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">assert!</span>(min_edge <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> max_interior)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb8-13"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div>
<p>The assertion is <em>strict separation</em>: every edge leverage exceeds every interior leverage. Not “average edge &gt; average interior,” but min-edge &gt; max-interior. Passes.</p>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
What this result does NOT prove
</div>
</div>
<div class="callout-body-container callout-body">
<p>The feature basis <code>[1, x, sin(πx), cos(πx)]</code> <strong>contains the analytical solution as a basis direction</strong>. This is a correctness proof of NIG conjugacy + prefix routing + the audit chain — it is <strong>not</strong> a generalization claim. On problems where the truth is not in the feature span, the architecture’s behaviour is <em>different</em> (and surprising — Part II’s Experiment β documents what happens). What this demo establishes is the architecture’s home turf.</p>
</div>
</div>
</section>
<section id="lineage-attestation-three-signal-spoof-detection" class="level3">
<h3 class="anchored" data-anchor-id="lineage-attestation-three-signal-spoof-detection">8.2 Lineage attestation (three-signal spoof detection)</h3>
<p><strong>The setup.</strong> Two ABNG graphs trained on near-identical clinical-trial datasets: - Dataset A: 64 rows <code>(patient_id, dose, response)</code> with <code>response = 0.2 + 0.6·dose + 0.1·dose²</code>. - Dataset B: identical to A except patient 17’s response is rewritten — exactly one row tampered.</p>
<p><strong>The architecture used.</strong> 4-leaf graph over <code>dose ∈ [0, 1]</code> with BLR over <code>[1, dose, dose², dose³]</code> per leaf. Both graphs use the same seed, codebook, prior, decision policy. The <em>only</em> meaningful difference between them is dataset content.</p>
<p><strong>The attack scenario.</strong></p>
<pre class="text"><code>Lab A:
  trains g_A on dataset_A
  stamps root with sha256(dataset_A_bytes)
  publishes g_A.snap + 3 predict_snaps

Attacker:
  trains g_B on tampered dataset_B
  swaps g_B.snap into Lab A's submission

Regulator:
  runs three independent checks</code></pre>
<p><strong>The three independent failure signals.</strong></p>
<div class="sourceCode" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb10-1"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">#[</span>test<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb10-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> three_signal_spoof_detection() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb10-3">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// ... g_A trained on dataset_A, g_B on tampered dataset_B ...</span></span>
<span id="cb10-4"></span>
<span id="cb10-5">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Signal 1: chain_head differs</span></span>
<span id="cb10-6">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> chain_diff <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> g_A<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>chain_head <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> g_B<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>chain_head<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb10-7"></span>
<span id="cb10-8">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Signal 2: BLR state hash at the affected leaf differs</span></span>
<span id="cb10-9">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> leaf_17 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> route_for(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>g_A<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> dataset_a()[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">17</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb10-10">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> stamp_diff <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span></span>
<span id="cb10-11">        g_A<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>nodes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>provenance_stamp_hash <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> g_B<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>nodes[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>provenance_stamp_hash<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb10-12">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> blr_diff <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> g_A<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>nodes[leaf_17 <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">as</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">usize</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>blr<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>as_ref()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>unwrap()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>state_hash()</span>
<span id="cb10-13">                 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> g_B<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>nodes[leaf_17 <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">as</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">usize</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>blr<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>as_ref()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>unwrap()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>state_hash()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb10-14"></span>
<span id="cb10-15">    <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">assert!</span>(chain_diff <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;&amp;</span> stamp_diff <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;&amp;</span> blr_diff<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb10-16">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"all three signals must fire to detect the spoof"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb10-17"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div>
<p>The three signals are independent by construction. The chain head depends on every audit event. The BLR state hash depends on the posterior of one leaf. The provenance stamp depends only on the dataset bytes. <strong>Forging any single one of them while keeping the other two consistent is roughly as hard as inverting SHA-256.</strong> Forging all three is computationally infeasible.</p>
<p><strong>Why this is the strongest demo in the codebase for the lineage claim.</strong> Every other demo is a positive-control determinism check (“the same input produces the same output”). This is the only demo that constructs an adversarial scenario — “what would an attacker do?” — and shows the architecture catches it.</p>
<p><strong>Caveats (the Skeptical Reviewer’s honesty pass).</strong></p>
<ol type="1">
<li>The dataset is 64 rows. Real regulated-ML datasets are many orders of magnitude larger. The mechanism <em>should</em> scale linearly (each row adds ~300 bytes to the audit log), but the wall-clock cost of verifying a 10⁹-row audit chain is unmeasured.</li>
<li>SHA-256 is <strong>tamper-evident, not tamper-proof.</strong> An attacker who controls the entire training pipeline can produce a self-consistent malicious log. This is a Level 3 audit system (state-mutation chain) in Doshi-Velez/-style taxonomy — not Level 4 (external attestation via TPM / secure enclaves).</li>
<li>The single-row tamper is the <em>easy</em> case. The harder case is a sophisticated adversary who tampers <em>and</em> reruns enough of the training to make most chain hashes self-consistent. This isn’t tested here.</li>
</ol>
</section>
</section>
<section id="replay-determinism-microbench-results" class="level2">
<h2 class="anchored" data-anchor-id="replay-determinism-microbench-results">9. Replay &amp; determinism microbench results</h2>
<p>The per-operation cost of every primitive in ABNG’s hot path, measured on the benchmark hardware (Intel i7-11390H @ 3.40GHz, Windows MSVC release build):</p>
<table class="table">
<colgroup>
<col style="width: 30%">
<col style="width: 40%">
<col style="width: 30%">
</colgroup>
<thead>
<tr class="header">
<th>Operation</th>
<th style="text-align: right;">Per-op cost</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>descend</code> (one byte walk)</td>
<td style="text-align: right;"><strong>118 ns</strong></td>
<td>Cheapest op; baseline noise floor</td>
</tr>
<tr class="even">
<td><code>encode_prefix</code> (1-D codebook)</td>
<td style="text-align: right;"><strong>137 ns</strong></td>
<td>Single quantile lookup</td>
</tr>
<tr class="odd">
<td><code>codebook_encode_into</code> (8-D, buffer-reuse)</td>
<td style="text-align: right;"><strong>130 ns</strong></td>
<td>Phase 0.7 Item B</td>
</tr>
<tr class="even">
<td><code>codebook_encode_alloc</code> (8-D, fresh Vec)</td>
<td style="text-align: right;"><strong>217 ns</strong></td>
<td>1.67× speedup with buffer reuse on this hardware</td>
</tr>
<tr class="odd">
<td><code>blr_predict</code> (Cholesky solve, d=4)</td>
<td style="text-align: right;"><strong>934 ns</strong></td>
<td>Predict-time work per leaf</td>
</tr>
<tr class="even">
<td><code>blr_state_update_direct</code> (no graph)</td>
<td style="text-align: right;"><strong>3,845 ns</strong></td>
<td>NIG conjugate math alone</td>
</tr>
<tr class="odd">
<td><code>observe</code> (graph-level Welford + audit)</td>
<td style="text-align: right;"><strong>4,238 ns</strong></td>
<td>Includes audit-chain append</td>
</tr>
<tr class="even">
<td><code>train_step_fused</code></td>
<td style="text-align: right;"><strong>17,804 ns</strong></td>
<td>All-in-one training row</td>
</tr>
<tr class="odd">
<td><code>blr_update</code> (graph-level)</td>
<td style="text-align: right;"><strong>28,997 ns</strong></td>
<td>~7.5× the direct BLR math — determinism scaffolding dominates</td>
</tr>
<tr class="even">
<td><code>verify_chain</code> (10k events)</td>
<td style="text-align: right;"><strong>12,324 µs total</strong></td>
<td>1.23 µs/event amortized</td>
</tr>
</tbody>
</table>
<section id="the-87-determinism-premium-decomposed" class="level3">
<h3 class="anchored" data-anchor-id="the-87-determinism-premium-decomposed">The 87% determinism premium decomposed</h3>
<p>The 25 µs gap between <code>blr_state_update_direct</code> (3.85 µs) and graph-level <code>blr_update</code> (29 µs) is the most striking single number in the suite. The premium decomposes (approximately) as:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://adamezzat1.github.io/blog/posts/abng-benchmarks/figures/v8_determinism_premium.png" class="img-fluid figure-img"></p>
<figcaption class="figure-caption">V8 — Determinism premium breakdown</figcaption>
</figure>
</div>
<table class="table">
<colgroup>
<col style="width: 30%">
<col style="width: 40%">
<col style="width: 30%">
</colgroup>
<thead>
<tr class="header">
<th>Sub-component</th>
<th style="text-align: right;">Estimated cost</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Bayesian math (NIG conjugate)</td>
<td style="text-align: right;"><strong>3.85 µs</strong></td>
<td>Directly measured (<code>blr_state_update_direct</code>)</td>
</tr>
<tr class="even">
<td>BLR state_hash (32-byte SHA-256 of posterior)</td>
<td style="text-align: right;">~8 µs</td>
<td>Witness for each <code>BlrUpdated</code> audit event</td>
</tr>
<tr class="odd">
<td>Audit event allocation + canonical bytes</td>
<td style="text-align: right;">~5.5 µs</td>
<td>Serialize event payload to canonical form</td>
</tr>
<tr class="even">
<td>Welford observe (graph layer + stats chain)</td>
<td style="text-align: right;">~4.5 µs</td>
<td>Per-node stats chain hash advance</td>
</tr>
<tr class="odd">
<td>SHA-256 audit chain advance</td>
<td style="text-align: right;">~3.2 µs</td>
<td>Phase 0.7 streaming-hash optimized</td>
</tr>
<tr class="even">
<td>Per-node stats chain hash advance</td>
<td style="text-align: right;">~2 µs</td>
<td>Separate from global chain</td>
</tr>
<tr class="odd">
<td>Routing (encode_prefix + descend)</td>
<td style="text-align: right;">~0.5 µs</td>
<td>Phase 0.7 buffer-reuse optimized</td>
</tr>
<tr class="even">
<td>Dispatch + error handling + misc</td>
<td style="text-align: right;">~1 µs</td>
<td>Function call boundary overhead</td>
</tr>
</tbody>
</table>
<p>The sub-component breakdown is <strong>partially measured</strong>: total premium is exact (<code>blr_update - blr_state_update_direct</code>); individual sub-component splits are estimated from the existing fine-grained microbenches. Tag: <strong><em>Reasonable extrapolation</em></strong> on the breakdown, <strong><em>Proven at the tested scale</em></strong> on the total.</p>
<p><strong>The takeaway.</strong> Approximately <strong>13% of <code>blr_update</code>’s cost is the Bayesian math</strong>; the remaining <strong>87% is the determinism contract</strong>: hashing the posterior, building the canonical bytes, advancing two audit chains, threading through the dispatch boundary. This is the single most defensible answer to “what does determinism cost” in the codebase.</p>
</section>
</section>
<section id="failure-cases-limitations" class="level2">
<h2 class="anchored" data-anchor-id="failure-cases-limitations">10. Failure cases &amp; limitations</h2>
<p>The previous revision of this article had five honestly-flagged limitations. Three of them were closed in §7.6 with new experiments; the other two remain. Plus one new limitation surfaced <em>by</em> the new measurements (the high CV on <code>blr_state_update_direct</code>).</p>
<section id="single-hardware-measurement-still-meta-limitation" class="level3">
<h3 class="anchored" data-anchor-id="single-hardware-measurement-still-meta-limitation">10.1 Single-hardware measurement (still meta-limitation)</h3>
<p>Every number in this article is from one machine (Intel i7-11390H, Windows MSVC release). The architecture’s determinism canaries are locked on Windows MSVC and <em>should</em> hold cross-platform, but <strong>cross-platform CI is still unverified.</strong> Phase 0.6 added a <code>cross-platform-determinism.yml</code> workflow skeleton; verification results have not been published. Until they are, every “Proven” tag carries an implicit “on this hardware” footnote.</p>
</section>
<section id="audit-log-size-at-production-scale-closed-in-7.6.3" class="level3">
<h3 class="anchored" data-anchor-id="audit-log-size-at-production-scale-closed-in-7.6.3">10.2 <del>Audit-log size at production scale</del> — <strong>closed in §7.6.3</strong></h3>
<p>Experiment μ measured the audit log up to 1M events. Snapshot scales perfectly linearly at 137 bytes/event; verify time grows slightly super-linearly (cache effects). Chain integrity holds. The 10⁹-event stress test is still unmeasured but the trajectory from 10⁴ → 10⁶ is now characterized.</p>
</section>
<section id="no-external-lineage-baseline-deferred-see-7.6.4" class="level3">
<h3 class="anchored" data-anchor-id="no-external-lineage-baseline-deferred-see-7.6.4">10.3 No external lineage baseline (deferred — see §7.6.4)</h3>
<p>MLflow / DVC / Weights &amp; Biases track <em>artefacts</em>. ABNG tracks <em>state mutations</em>. The granularity advantage is real and architecturally documented but <strong>the head-to-head wall-clock comparison is still missing</strong>. Closing this gap is a one-to-two-week project that warrants its own article rather than a section here. Listed in future work below.</p>
</section>
<section id="no-predictive-coverage-analysis-closed-in-7.6.1" class="level3">
<h3 class="anchored" data-anchor-id="no-predictive-coverage-analysis-closed-in-7.6.1">10.4 <del>No predictive coverage analysis</del> — <strong>closed in §7.6.1</strong></h3>
<p>Experiment κ measured predictive coverage at 5 nominal levels (0.50, 0.80, 0.90, 0.95, 0.99). All empirical coverages within ±0.01 of nominal. <strong>Cκ passed.</strong></p>
</section>
<section id="no-statistical-cis-on-per-op-timings-closed-in-7.6.2-partially" class="level3">
<h3 class="anchored" data-anchor-id="no-statistical-cis-on-per-op-timings-closed-in-7.6.2-partially">10.5 <del>No statistical CIs on per-op timings</del> — <strong>closed in §7.6.2</strong> (partially)</h3>
<p>Experiment λ measured median + IQR + min/max for five key operations across 30 trials. The result is <em>informative</em>: stable ops have CV ≤ 0.20; BLR-heavy ops have CV ≈ 0.4-0.6 (cache-sensitive). The single-run minimums in §9 are honest <em>lower bounds</em> but understate typical-case timing by 30-60% on BLR ops. <strong>Conversion of the full microbench suite to Criterion</strong> (with median + IQR per op as the default report shape) is still deferred — listed in future work below.</p>
</section>
<section id="new-high-variance-on-cache-sensitive-blr-operations" class="level3">
<h3 class="anchored" data-anchor-id="new-high-variance-on-cache-sensitive-blr-operations">10.6 (NEW) — High variance on cache-sensitive BLR operations</h3>
<p>Surfaced by Experiment λ: <code>blr_state_update_direct</code> has CV = 0.58, with max 5× the min across 30 trials. The mechanism is Cholesky factorization being cache-sensitive at fresh graph setup. Two paths to address this:</p>
<ol type="1">
<li>Run microbenches in a CPU-pinned, cache-pre-warmed harness (Criterion does this).</li>
<li>Quote median rather than min in the §9 cost table — a pure presentation fix.</li>
</ol>
<p>The high-variance ops do not change the article’s qualitative claims (the determinism-premium ratio holds at the median level), but the previous version’s single-run min values are tighter than they should be presented.</p>
</section>
<section id="future-work-remaining-open-items" class="level3">
<h3 class="anchored" data-anchor-id="future-work-remaining-open-items">Future work (remaining open items)</h3>
<table class="table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Item</th>
<th>Cost</th>
<th>Closes which gap</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Cross-platform CI verification (Linux + macOS + ARM)</td>
<td>1–2 days</td>
<td>§10.1</td>
</tr>
<tr class="even">
<td>MLflow / DVC tamper-scenario comparison harness</td>
<td>1–2 weeks</td>
<td>§10.3</td>
</tr>
<tr class="odd">
<td>10⁹-event audit log stress test</td>
<td>1 day</td>
<td>(push past §7.6.3’s 10⁶ ceiling)</td>
</tr>
<tr class="even">
<td>sklearn-GP head-to-head on tabular</td>
<td>1 day</td>
<td>(general external baseline)</td>
</tr>
<tr class="odd">
<td>Convert microbench suite to Criterion (median + IQR per op)</td>
<td>4 hours</td>
<td>§10.5 / §10.6</td>
</tr>
</tbody>
</table>
<p>The article’s previous “four remaining gaps” list now reads “two remaining gaps (cross-platform CI + MLflow baseline) plus one new gap (BLR-op variance) plus two stretch items.” Net progress.</p>
</section>
</section>
<section id="reproducibility" class="level2">
<h2 class="anchored" data-anchor-id="reproducibility">11. Reproducibility</h2>
<p>Every number, table, and figure in this article can be reproduced from a clean checkout of the CJC-Lang repository.</p>
<section id="build-environment-fingerprint" class="level3">
<h3 class="anchored" data-anchor-id="build-environment-fingerprint">Build environment fingerprint</h3>
<pre class="text"><code>Hardware:    Intel Core i7-11390H @ 3.40 GHz (4 cores, 8 threads)
OS:          Windows 11 Pro build 26200
Toolchain:   MSVC, Rust (stable channel at publication time)
Build mode:  release (-O3-equivalent, LTO disabled)
CJC-Lang:    Phase 0.7, snapshot magic v13 (b"ABNG\x0D")
Plotting:    Python 3.11, matplotlib 3.9.2, numpy 2.2.6</code></pre>
</section>
<section id="reproduction-commands" class="level3">
<h3 class="anchored" data-anchor-id="reproduction-commands">Reproduction commands</h3>
<div class="sourceCode" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb12-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 1. Clone the repository</span></span>
<span id="cb12-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">git</span> clone https://github.com/AdamEzzat1/CJC.git</span>
<span id="cb12-3"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">cd</span> CJC</span>
<span id="cb12-4"></span>
<span id="cb12-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 2. Build the bench suite (release profile)</span></span>
<span id="cb12-6"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cargo</span> build <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--release</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb12-7">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-p</span> abng-srg-experiments <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb12-8">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-p</span> abng-micro <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb12-9">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-p</span> abng-vs-sklearn <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb12-10">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-p</span> abng-lineage-at-scale</span>
<span id="cb12-11"></span>
<span id="cb12-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 3. Run the experiments relevant to Part I</span></span>
<span id="cb12-13"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mkdir</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-p</span> bench_results</span>
<span id="cb12-14"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cargo</span> run <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--release</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-p</span> abng-srg-experiments     <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> bench_results/abng_srg.jsonl</span>
<span id="cb12-15"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cargo</span> run <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--release</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-p</span> abng-micro               <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> bench_results/abng_micro.jsonl</span>
<span id="cb12-16"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cargo</span> run <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--release</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-p</span> abng-vs-sklearn          <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> bench_results/abng_vs_sklearn.jsonl</span>
<span id="cb12-17"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cargo</span> run <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--release</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-p</span> abng-lineage-at-scale    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> bench_results/abng_lineage_at_scale.jsonl</span>
<span id="cb12-18"></span>
<span id="cb12-19"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 4. Generate figures</span></span>
<span id="cb12-20"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">cd</span> bench_results <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">&amp;&amp;</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">python</span> plot_all.py</span></code></pre></div>
<p>The <code>plot_all.py</code> script regenerates all twelve figures (V1–V10, V-β, V-ζ) from the JSONL outputs. Each figure file is overwritten in <code>bench_results/figures/</code>.</p>
</section>
<section id="what-you-should-expect-to-see" class="level3">
<h3 class="anchored" data-anchor-id="what-you-should-expect-to-see">What you should expect to see</h3>
<ul>
<li>Experiment α: 7 rows of JSONL with <code>op = "alpha_posterior_consistency"</code>, showing the decay table from §7.1.</li>
<li>Experiment δ: 1 row with <code>op = "delta_replay_under_mutation"</code>, asserting <code>byte_identical = true</code>.</li>
<li>Experiment η: 9 rows with <code>op = "eta_per_bin_calibration"</code> (one per non-empty bin) plus 1 with <code>op = "eta_calibration_summary"</code> reporting <code>graph_ece = 0.015</code>.</li>
<li>Experiment γ: 7 rows with <code>op = "gamma_selective_prediction"</code> showing the kept-percent vs MAE curve.</li>
<li>Experiment ζ: 1 row with <code>op = "zeta_kahan_vs_plain"</code> reporting slowdown ≈ 2.40×.</li>
</ul>
<p>If your re-run produces numbers within ±5% of the values reported here, the underlying determinism contract is functioning correctly. If your re-run produces <em>byte-identical JSONL</em>, your environment is functionally equivalent to the article’s measurement environment.</p>
<p>If your re-run produces numbers outside ±5%, the most likely causes (in order): different CPU generation, different Rust toolchain version, system load during measurement, missing release-mode build. Open an issue if you suspect a regression.</p>
</section>
</section>
<section id="why-this-matters" class="level2">
<h2 class="anchored" data-anchor-id="why-this-matters">12. Why this matters</h2>
<p>Three concrete use cases where the costs documented in §9 invert.</p>
<section id="regulated-ml" class="level3">
<h3 class="anchored" data-anchor-id="regulated-ml">Regulated ML</h3>
<p>When a model decision must be defended to a regulator (FDA, EMA, financial auditor, ethics board), “trust the lab’s bookkeeping” is the default and the worst answer. ABNG’s audit chain replaces it with a cryptographic chain that a regulator can independently verify. The three-signal spoof detection (MD-I-2, §8.2) demonstrates the mechanism on a toy clinical scenario; the linear scaling of audit log size (§10.2) extrapolates the cost to realistic deployments.</p>
<p><em>What this doesn’t claim</em>: ABNG is <strong>production-ready</strong> for regulated ML. The deployment story requires cross-platform CI verification (§10.1), large-N stress testing (§10.2), and tooling maturity that hasn’t shipped. The mechanism is real; the regulator-grade deployment is research-stage.</p>
</section>
<section id="scientific-replication" class="level3">
<h3 class="anchored" data-anchor-id="scientific-replication">Scientific replication</h3>
<p>A paper that depends on a specific trained model — a protein-structure prediction, a climate ensemble run, a drug-discovery surrogate — has historically been replicable “approximately” at best. Two retrainings produce similar artefacts, not identical ones. For statistical claims that condition on the <em>specific</em> artefact, this matters. ABNG produces a snapshot with a specific 32-byte chain head that can be byte-equal across re-runs; the snapshot can be archived as part of the paper’s supplementary material; any future replicator can reproduce the exact artefact, not a near-equivalent.</p>
<p><em>What this doesn’t claim</em>: that all scientific ML <em>should</em> be replicable to byte-equality. For many domains, “approximately reproducible” is sufficient. The case for byte-equality is specifically about <em>evidence-of-record</em> — when the model artefact is the evidence, not just the path to a result.</p>
</section>
<section id="postmortem-debugging-with-caveats" class="level3">
<h3 class="anchored" data-anchor-id="postmortem-debugging-with-caveats">Postmortem debugging (with caveats)</h3>
<p>When a deployed model produces a strange output, the question “what would I need to change to never have that output again?” requires reconstructing the training trajectory that produced the current weights. ABNG’s audit log makes this <em>tractable in principle</em> — every state mutation is in the log, every state is replayable.</p>
<p><em>The honest caveat (Skeptical Reviewer’s flag).</em> Tractable in principle is not the same as easy. Walking a 10⁶-event audit log to find the observation that corrupted a leaf’s posterior is not a “click one button” workflow today. The CLI exposes the operations (<code>cjcl abng inspect --node N --audit</code>); the user experience for <em>automated diagnosis</em> — “show me which observations affected this leaf the most” — has not been built. Treat audit-based postmortem debugging as a <em>capability that exists</em>, not a <em>workflow that’s mature</em>.</p>
</section>
</section>
<section id="conclusion" class="level2">
<h2 class="anchored" data-anchor-id="conclusion">13. Conclusion</h2>
<p>This article walked through Part I of the ABNG benchmark deep-dive: five categories where the architecture’s properties can be proven correct at the tested scale.</p>
<p><strong>What’s now established empirically (at the tested scale, on the tested hardware):</strong></p>
<ul>
<li>NIG conjugate update converges as <code>1/√n</code> to known truth (Experiment α).</li>
<li>Replay invariant survives structural mutation (Experiment δ).</li>
<li>Per-leaf calibration is well-tracked: graph-level ECE = 0.015 on a synthetic Bernoulli task (Experiment η).</li>
<li>Selective prediction reduces error 10× by abstaining on high-OOD inputs (Experiment γ).</li>
<li>Kahan vs plain summation is exactly 2.40× — matching the architecture article’s “~2×” claim (Experiment ζ).</li>
<li><strong>Predictive coverage matches nominal at all 5 tested levels within ±0.01</strong> (Experiment κ, §7.6.1) — strictly stronger than the calibration result.</li>
<li><strong>Per-op timings have characterized variance</strong> (Experiment λ, §7.6.2) — stable ops CV ≤ 0.20; BLR-heavy ops CV ≈ 0.4–0.6 (cache-sensitive).</li>
<li><strong>Audit log scales linearly to 1M events</strong> (Experiment μ, §7.6.3) — 137 bytes/event, 5.2 s replay, chain integrity holds.</li>
<li>The graph-dispatch overhead is <strong>87% of <code>blr_update</code> cost</strong> (§9 — the determinism premium quantified).</li>
<li>PINN demo passes at MAE &lt; 0.05 on the basis-in-span analytical solution (§8.1).</li>
<li>Three-signal spoof detection catches single-row dataset tampering (§8.2).</li>
</ul>
<p><strong>What’s deferred to Part II (companion article):</strong></p>
<ul>
<li>The “graph adapts itself” claim — <code>decide_step</code> engine, structural action firing, scaling at varying <code>n_active_nodes</code>.</li>
<li>The “routing partition” claim — Sankey of routing flow, basis-augmentation under piecewise routing.</li>
<li>The temporal-dynamics claim — drift detection, auto-unfreeze, signature evolution.</li>
<li>The 2-D OOD heatmap showing spatial structure of abstain decisions.</li>
</ul>
<p><strong>What remains speculative across both parts:</strong></p>
<ul>
<li>Whether ABNG would compete with sklearn-GP, FNO, or deep SVDD on their home benchmarks — <em>no head-to-head measurement exists in either article</em>.</li>
<li>Whether the architecture scales to 10⁶+ rows — <em>not measured past 10⁵</em>.</li>
<li>Whether the determinism contract holds cross-platform — <em>only Windows MSVC tested</em>.</li>
<li>Whether the “three or more advantages simultaneously” hypothesis from <a href="../../../blog/posts/abng-architecture/index.html">Part 1 of the trilogy</a> holds anywhere empirically.</li>
</ul>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
The honest summary
</div>
</div>
<div class="callout-body-container callout-body">
<p><strong>ABNG demonstrates promising properties for deterministic, inspectable, graph-based probabilistic computation</strong> — at the tested scale, on the tested hardware. The mechanisms in Part I (determinism, lineage, state stability, probabilistic consistency, explainability) are now backed by experiments with pre-registered pass/fail conditions. Each one passed.</p>
<p>The architecture is <strong>not validated science.</strong> It is exploratory engineering with strong correctness signals at small scale. Whether the mechanisms compete with established alternatives on real workloads is still <em>untested</em> — and is the subject of the future-work items in §10.</p>
<p>If you’ve read this far and the architecture seems interesting but probably wrong in three specific ways, this article has done its job. <strong>The companion Part II (<a href="../../../blog/posts/abng-benchmarks-part-2/index.html">Adaptive Graph Evolution, Routing Behavior, and Temporal State Dynamics</a>) covers the other four benchmark categories.</strong></p>
</div>
</div>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>ABNG lives at <a href="https://github.com/AdamEzzat1/CJC"><code>crates/cjc-abng/</code></a> in the CJC-Lang repository. All benchmark code is in <code>bench/abng_*/</code>. The SRG-experiments suite is at <a href="https://github.com/AdamEzzat1/CJC/blob/main/bench/abng_srg_experiments/main.rs"><code>bench/abng_srg_experiments/main.rs</code></a> — reproducible with <code>cargo run --release -p abng-srg-experiments</code>. The plotting script is at <a href="https://github.com/AdamEzzat1/CJC/blob/main/bench_results/plot_all.py"><code>bench_results/plot_all.py</code></a>.</p>
<p>This is article 3 of a 4-article ABNG series. Article 1 covers the architecture; Article 2 covers the determinism and audit story in depth; Article 4 (Part II of the benchmarks) covers adaptive graph evolution, routing, and temporal dynamics.</p>
</div>
</div>


</section>

 ]]></description>
  <category>CJC-Lang</category>
  <category>ABNG</category>
  <category>benchmarks</category>
  <category>determinism</category>
  <category>lineage</category>
  <category>calibration</category>
  <category>explainability</category>
  <guid>https://adamezzat1.github.io/blog/posts/abng-benchmarks/index.html</guid>
  <pubDate>Sun, 10 May 2026 07:00:00 GMT</pubDate>
</item>
<item>
  <title>ABNG Benchmarks Part II — Adaptive Graph Evolution, Routing Behavior, and Temporal State Dynamics</title>
  <dc:creator>Adam Ezzat</dc:creator>
  <link>https://adamezzat1.github.io/blog/posts/abng-benchmarks-part-2/index.html</link>
  <description><![CDATA[ 



<div class="reading-time">
🕑 ~38 min read
</div>
<div class="callout callout-style-simple callout-tip no-icon callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-title-container flex-fill">
TL;DR
</div>
</div>
<div class="callout-body-container callout-body">
<p>ABNG (Adaptive Belief Network Graph) is an experimental neural architecture that treats Bayesian belief as a first-class object — each leaf in an adaptive tree carries its own posterior, routing is a traceable walk through learned bytes, and the graph itself can grow / split / merge / prune / compress / freeze under a deterministic policy. <strong>This article (Part II of the benchmark deep-dive) covers the four categories where ABNG is architecturally distinctive — the parts where the graph isn’t fixed, where structural decisions matter, where the system <em>adapts</em>.</strong> Four experiments — β (basis-not-in-span PINN with surprising piecewise-routing robustness), ε (<code>decide_step</code> scaling sub-millisecond at n_active ≤ 256), θ (routing-flow Sankey matching input distribution), ι (2-D OOD heatmap showing spatial structure of abstain decisions) — back the claims. Plus five trigger-specific demo deep-dives (Compress / Grow / Split / Prune / Freeze) and the temporal-dynamics story (drift detection + auto-unfreeze). <strong>The companion Part I covers determinism, lineage, state stability, probabilistic consistency, and explainability.</strong></p>
</div>
</div>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Two prototype disclaimers
</div>
</div>
<div class="callout-body-container callout-body">
<p><strong>ABNG is a research-stage prototype.</strong> It is built on top of <strong>CJC-Lang</strong>, a research compiler that is itself pre-1.0. Both are under active development. Anything you read here is current as of Phase 0.7 (snapshot magic <code>v13</code>); Phase 0.8 explicitly authorizes a wire-format bump to v14. None of this is production-ready. None of this has been deployed at scale.</p>
</div>
</div>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
This is article 4 of a 4-article ABNG series
</div>
</div>
<div class="callout-body-container callout-body">
<ul>
<li><strong>Article 1</strong> (<a href="../../../blog/posts/abng-architecture/index.html">ABNG: Treating Belief States as First-Class Citizens</a>) — the architecture itself.</li>
<li><strong>Article 2</strong> (<a href="../../../blog/posts/abng-deterministic-systems/index.html">Deterministic and Auditable Neural Systems</a>) — the audit chain and determinism contract.</li>
<li><strong>Article 3</strong> (<a href="../../../blog/posts/abng-benchmarks/index.html">Benchmarks Part I</a>) — determinism, lineage, state stability, probabilistic consistency, explainability.</li>
<li><strong>Article 4 (this article)</strong> — Benchmarks Part II: adaptive graph evolution, routing behavior, temporal state dynamics.</li>
</ul>
<p>Each article can be read alone. This one is the empirical evidence for the “things that make ABNG architecturally distinctive” half of the benchmark story; Part I is the “things you can prove correct” half.</p>
</div>
</div>
<section id="introduction" class="level2">
<h2 class="anchored" data-anchor-id="introduction">1. Introduction</h2>
<p>ABNG is an experimental neural architecture that treats Bayesian belief as a first-class architectural primitive: each leaf in an adaptive tree carries its own Normal-Inverse-Gamma posterior, routing is a traceable walk through learned bytes, and the graph can grow / split / merge / prune / compress / freeze itself under a deterministic decision policy. Every state mutation appends to a SHA-256 hash-chained audit log.</p>
<p>That’s the architecture. <a href="../../../blog/posts/abng-benchmarks/index.html">Part I</a> handled the properties of a <em>fixed</em> graph — what you can prove about the model at any given moment in its training history. This article handles what makes ABNG architecturally distinctive: properties that emerge from the graph <em>changing</em> over time.</p>
<section id="why-these-four-categories-belong-together" class="level3">
<h3 class="anchored" data-anchor-id="why-these-four-categories-belong-together">Why these four categories belong together</h3>
<p>The Part I categories (determinism, lineage, state stability, probabilistic consistency, explainability) can be tested on a snapshot. You freeze the graph at time <code>t</code>, run a battery of measurements, and either the property holds or it doesn’t. The categories don’t depend on the graph evolving.</p>
<p>The Part II categories are different. <strong>Each one is a property of the graph <em>changing</em>.</strong></p>
<ul>
<li><strong>Routing efficiency</strong> is meaningful only because the routing partition exists and the input flows through it. A static lookup table doesn’t have an interesting “routing efficiency” story; ABNG does because the partition is learned and the bytes-to-leaves mapping is interpretable.</li>
<li><strong>Memory scaling</strong> is meaningful because the graph grows. A fixed-size MLP has no scaling story; ABNG’s <code>n_active_nodes</code> is a dynamic quantity that emerges from <code>decide_step</code>’s history.</li>
<li><strong>Adaptation</strong> <em>is</em> the story of the graph changing — Grow / Split / Merge / Prune / Compress / Freeze. Six discrete structural actions, each gated by a deterministic quality condition.</li>
<li><strong>Temporal dynamics</strong> is the part of the story that depends on observation order. Drift detection responds to distribution shift over time; auto-unfreeze re-enables learning when surprise crosses a threshold; signature ring buffers track stability over multi-window history.</li>
</ul>
<p>What this means concretely for the article: the experiments here measure how the graph <em>behaves over training</em>, not just how it <em>predicts on test data</em>. The headline result (β — basis-not-in-span PINN) is a finding about how the <em>routing partition</em> changes what a basis-restricted model can represent — a result that would not exist if the routing weren’t part of the architecture.</p>
</section>
<section id="what-this-article-delivers" class="level3">
<h3 class="anchored" data-anchor-id="what-this-article-delivers">What this article delivers</h3>
<p>By the end of Part II:</p>
<ol type="1">
<li>You have formal definitions for routing efficiency, memory scaling, adaptation, and temporal dynamics — measurable, reproducible categories.</li>
<li>You have four worked-through mini-demos (one per category) showing concrete inputs and observable graph behavior.</li>
<li>You have four deep experiments with pre-registered pass/fail conditions and real wall-clock measurements: β, ε, θ, ι.</li>
<li>You have five trigger-specific demo deep-dives covering all six structural actions in the codebase.</li>
<li>You have the routing microbenchmark numbers and the structural-decision cost-model summary.</li>
<li>You have a complete reproducibility section with exact <code>cargo</code> commands.</li>
<li>You see five honest failure modes and the deferred work that would close them.</li>
</ol>
<p>The article is ~9,000 words. It assumes you’ve read Part I or know the architecture from <a href="../../../blog/posts/abng-architecture/index.html">Part 1 of the trilogy</a>; if you haven’t, Part I is the better starting point.</p>
</section>
</section>
<section id="what-the-current-abng-demos-demonstrate" class="level2">
<h2 class="anchored" data-anchor-id="what-the-current-abng-demos-demonstrate">2. What the current ABNG demos demonstrate</h2>
<p>For continuity with Part I, the cross-article capability overview reappears here. <strong>This article focuses on the 8 capabilities marked “Part II” below.</strong> (Part I covers the other 11.)</p>
<table class="table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Demonstrated capability</th>
<th>Mechanism</th>
<th>Deep coverage in</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Adaptive routing</td>
<td>QuantileCodebook + Adaptive Radix Tree-style descent</td>
<td><strong>Part II</strong></td>
</tr>
<tr class="even">
<td>Audit chain integrity</td>
<td>SHA-256 chain over every state mutation; 30 audit kinds</td>
<td>Part I</td>
</tr>
<tr class="odd">
<td>Bayesian inference (per-leaf)</td>
<td>NIG conjugate update over BLR weights + noise variance</td>
<td>Part I</td>
</tr>
<tr class="even">
<td>Belief-state stability over training</td>
<td>Welford-smoothed <code>NodeSignature</code> per leaf</td>
<td>Part I + <strong>Part II</strong></td>
</tr>
<tr class="odd">
<td>Calibration tracking</td>
<td>15-bin reliability diagram per leaf; ECE accumulated with Kahan</td>
<td>Part I</td>
</tr>
<tr class="even">
<td>Cryptographic provenance</td>
<td>Per-node <code>provenance_stamp_hash</code> field</td>
<td>Part I</td>
</tr>
<tr class="odd">
<td>Deterministic execution</td>
<td>Kahan summation, BTreeMap, no FMA, SplitMix64 RNG</td>
<td>Part I</td>
</tr>
<tr class="even">
<td>Drift detection</td>
<td>L2 z-shift between current density and frozen baseline</td>
<td><strong>Part II</strong></td>
</tr>
<tr class="odd">
<td>Explainable abstain</td>
<td>Composite <code>ood_score = max(density, prefix_distance, epi_z)</code></td>
<td>Part I</td>
</tr>
<tr class="even">
<td>Graph mutation visibility</td>
<td><code>cjcl abng inspect</code> and <code>cjcl abng diff</code> CLI tools</td>
<td>Part I + <strong>Part II</strong></td>
</tr>
<tr class="odd">
<td>Lineage attestation</td>
<td>Three-signal spoof detection</td>
<td>Part I</td>
</tr>
<tr class="even">
<td>Posterior convergence</td>
<td>Closed-form NIG update with Cholesky + triangular solve</td>
<td>Part I</td>
</tr>
<tr class="odd">
<td>Probabilistic consistency</td>
<td>Per-bin (predicted, observed) tracking; aggregate ECE</td>
<td>Part I</td>
</tr>
<tr class="even">
<td>Replayability under mutation</td>
<td><code>serialize → replay</code> byte-identical even after structural events</td>
<td>Part I</td>
</tr>
<tr class="odd">
<td>Route introspection</td>
<td>Opt-in <code>Routed</code> audit events (kind <code>0x1B</code>) for prediction traces</td>
<td><strong>Part II</strong></td>
</tr>
<tr class="even">
<td>Structural adaptation</td>
<td><code>decide_step</code> engine with 6 actions + 14-threshold policy</td>
<td><strong>Part II</strong></td>
</tr>
<tr class="odd">
<td>Temporal state evolution</td>
<td><code>Maturity</code> flags + 3-window stability ring buffers per node</td>
<td><strong>Part II</strong></td>
</tr>
<tr class="even">
<td>Trie-based routing</td>
<td>Node4/16/48/256 children with auto-promotion</td>
<td><strong>Part II</strong></td>
</tr>
<tr class="odd">
<td>Uncertainty propagation</td>
<td>Per-leaf <code>(mean, epistemic_leverage, aleatoric_var)</code> triple</td>
<td>Part I</td>
</tr>
</tbody>
</table>
</section>
<section id="benchmark-taxonomy-where-this-article-slices-it" class="level2">
<h2 class="anchored" data-anchor-id="benchmark-taxonomy-where-this-article-slices-it">3. Benchmark taxonomy (where this article slices it)</h2>
<p>For continuity, the 9-category benchmark taxonomy is shown again, with this article’s coverage highlighted:</p>
<table class="table">
<colgroup>
<col style="width: 21%">
<col style="width: 21%">
<col style="width: 21%">
<col style="width: 35%">
</colgroup>
<thead>
<tr class="header">
<th>#</th>
<th>Benchmark category</th>
<th>Definition</th>
<th style="text-align: center;">Covered in</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>1</td>
<td>Determinism</td>
<td>Same input/seed produces identical graph evolution</td>
<td style="text-align: center;">Part I</td>
</tr>
<tr class="even">
<td>2</td>
<td>Lineage</td>
<td>Trace evidence propagation across audit events</td>
<td style="text-align: center;">Part I</td>
</tr>
<tr class="odd">
<td>3</td>
<td>State Stability</td>
<td>Measure belief convergence and divergence per leaf</td>
<td style="text-align: center;">Part I</td>
</tr>
<tr class="even">
<td>4</td>
<td>Probabilistic Consistency</td>
<td>Calibration / confidence propagation stability</td>
<td style="text-align: center;">Part I</td>
</tr>
<tr class="odd">
<td>5</td>
<td>Explainability</td>
<td>Inspect causal transitions; expose abstain decisions</td>
<td style="text-align: center;">Part I</td>
</tr>
<tr class="even">
<td>6</td>
<td><strong>Routing Efficiency</strong></td>
<td>Compare graph traversal cost; measure per-input route latency</td>
<td style="text-align: center;"><strong>Part II</strong></td>
</tr>
<tr class="odd">
<td>7</td>
<td><strong>Memory Scaling</strong></td>
<td>Track graph growth vs complexity at varying n_active_nodes</td>
<td style="text-align: center;"><strong>Part II</strong></td>
</tr>
<tr class="even">
<td>8</td>
<td><strong>Adaptation</strong></td>
<td>Measure graph restructuring over time; structural action firing</td>
<td style="text-align: center;"><strong>Part II</strong></td>
</tr>
<tr class="odd">
<td>9</td>
<td><strong>Temporal Dynamics</strong></td>
<td>Observe state evolution across time; drift response and auto-unfreeze</td>
<td style="text-align: center;"><strong>Part II</strong></td>
</tr>
</tbody>
</table>
<p>The split rule: categories 1–5 measure properties of a fixed graph or a small region of one. Categories 6–9 measure properties of an <em>evolving</em> graph.</p>
</section>
<section id="categories-covered-in-this-article" class="level2">
<h2 class="anchored" data-anchor-id="categories-covered-in-this-article">4. Categories covered in this article</h2>
<p>Brief formal definitions before the deep dives.</p>
<section id="routing-efficiency" class="level3">
<h3 class="anchored" data-anchor-id="routing-efficiency">4.1 Routing Efficiency</h3>
<p><strong>Definition.</strong> Per-input route cost. Concretely: nanoseconds per <code>descend</code> call, nanoseconds per <code>encode_prefix</code> call, nanoseconds per <code>route_to_leaf</code> (the fused operation). Also: the <em>interpretability</em> of the routing partition — does the byte-byte mapping reveal where in input space the graph has seen data?</p>
<p><strong>Mechanism.</strong> ABNG’s routing path is:</p>
<ol type="1">
<li><code>QuantileCodebook::encode_into(x, buf)</code> — bucketize each input dimension into a single byte using pre-calibrated quantile boundaries. <code>O(d_route × log(n_bins))</code> for binary search per dim.</li>
<li><code>descend(route_key)</code> — walk the tree one byte at a time. <code>O(d_route)</code> lookups, each <code>O(1)</code> amortized via the ART-style children layout (Node4 / Node16 / Node48 / Node256).</li>
<li>Optional <code>Routed</code> audit event (kind <code>0x1B</code>) recording the matched-prefix length and final leaf id.</li>
</ol>
<p><strong>Why it matters.</strong> Routing is the <em>only</em> per-input cost on ABNG’s inference path. If routing scales poorly, the architecture’s inference advantage collapses. Microbench data (§9) shows routing is sub-microsecond at all tested sizes — sub-200ns for the full route on this hardware.</p>
</section>
<section id="memory-scaling" class="level3">
<h3 class="anchored" data-anchor-id="memory-scaling">4.2 Memory Scaling</h3>
<p><strong>Definition.</strong> How the graph grows under training. Specifically: (a) per-node memory footprint as a function of feature dimension <code>d_feat</code>; (b) graph topology growth (<code>n_active_nodes</code> over time); (c) <code>decide_step</code> cost as <code>n_active_nodes</code> increases.</p>
<p><strong>Mechanism.</strong> Each leaf carries five state structures: MLP body (<code>O(d_feat²)</code>), BLR head (<code>O(d_feat²)</code> for the precision matrix), DensityTracker (<code>O(d_feat)</code>), CalibrationBins (<code>O(n_bins)</code> = 15 × 16 bytes), DriftBaseline (<code>O(d_feat)</code>). Total per-leaf ~500 B at <code>d_feat = 4</code>, scales as <code>O(d_feat²)</code>.</p>
<p>The <code>decide_step</code> engine iterates over <code>n_active_nodes</code> in <code>NodeId</code>-ascending order. The per-node work is <code>O(1)</code> (a small fixed-size set of gate checks). Total per-call cost is <code>O(n_active_nodes)</code> — confirmed at the tested scales by Experiment ε.</p>
<p><strong>Why it matters.</strong> The architecture’s “the graph grows itself” claim is only useful if growth is <em>bounded</em> and <em>measurable</em>. A graph that grows quadratically with observations would be a structural failure, even if every other property held. The Experiment ε numbers establish that growth is linear and <code>decide_step</code> cost is sub-millisecond at moderate scale.</p>
</section>
<section id="adaptation" class="level3">
<h3 class="anchored" data-anchor-id="adaptation">4.3 Adaptation</h3>
<p><strong>Definition.</strong> <em>Structural</em> adaptation — how the graph changes its topology under evidence. Specifically: which actions fire from <code>{Grow, Split, Merge, Prune, Compress, Freeze}</code>, in what order, gated by which quality conditions, with what frequency.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Structural ≠ parameter adaptation
</div>
</div>
<div class="callout-body-container callout-body">
<p>A reader from gradient-descent-land might assume “adaptation” includes the BLR posterior updating on each observation. That’s <em>parameter</em> adaptation — covered in Part I §4.3 (State Stability). This section is specifically about the <em>graph’s structure changing</em> — leaves appearing / disappearing / merging / being protected from updates.</p>
<p>A further structural-adaptation detail worth knowing: <code>decide_step</code>’s iteration order is single-threaded by design. The snapshot-at-call-entry invariant means actions in one <code>decide_step</code> call don’t feed back into that same call’s iteration. Parallelizing across nodes would break the deterministic iteration order and therefore the byte-identical replay property. This is the determinism contract leaking into the adaptation mechanism.</p>
</div>
</div>
<p><strong>Mechanism.</strong> Each action has its own gate. Phase 0.4 sharpened these from “samples_seen ≥ N” to genuine quality conditions:</p>
<ul>
<li><strong>Grow</strong> requires <code>samples_seen ≥ grow_min</code> AND route-key entropy at candidate depth &gt; <code>H_grow</code>.</li>
<li><strong>Split</strong> requires <code>samples_seen ≥ split_min</code> AND deterministic bootstrap ΔNLL gain ≥ <code>nll_split_gain</code>.</li>
<li><strong>Merge</strong> requires Hamming-similar <code>NodeSignature</code>s AND KL-divergence between BLR posteriors below <code>kl_merge</code>.</li>
<li><strong>Prune</strong> requires <code>samples_seen</code> below a minimum AND signature instability.</li>
<li><strong>Compress</strong> requires sub-tree signature equivalence with a reference fingerprint.</li>
<li><strong>Freeze</strong> requires <code>samples_seen ≥ freeze_min</code> AND <code>Maturity.calibration_stable AND Maturity.uncertainty_stable</code>.</li>
</ul>
<p>The fall-through order is fixed: Compress → Merge → Split → Prune → Grow → Freeze. At most one action per node per call. Pre-ladder, an auto-unfreeze check runs (covered in §4.4 Temporal Dynamics).</p>
<p><strong>Why it matters.</strong> The “graph adapts itself” claim is the single most distinctive architectural pitch. If adaptation didn’t fire under reasonable conditions, the architecture is just a routed mixture-of-experts with no advantage over fixed-population MoE. Five trigger-specific demos (§8.2) verify each action fires correctly under its respective gate.</p>
</section>
<section id="temporal-dynamics" class="level3">
<h3 class="anchored" data-anchor-id="temporal-dynamics">4.4 Temporal Dynamics</h3>
<p><strong>Definition.</strong> How the graph responds to time-varying input distributions. Specifically: does the drift score climb when the distribution shifts? Does auto-unfreeze fire to re-enable learning? Do the 3-window ECE / σ stability ring buffers correctly classify stable vs unstable leaves?</p>
<p><strong>Mechanism.</strong> Three time-aware state structures per leaf:</p>
<ul>
<li><strong><code>NodeSignature</code></strong> — 32-byte Welford-smoothed summary across four channels (prediction, uncertainty, calibration, routing). Stability is measured by <code>signature_stable_calls</code> — a counter incrementing when the smoothed signature doesn’t change appreciably between <code>decide_step</code> calls.</li>
<li><strong><code>Maturity</code> flags</strong> — <code>samples_seen</code>, <code>calibration_stable</code>, <code>uncertainty_stable</code>, <code>trust_level</code>. Once <code>uncertainty_stable</code> first holds, the leaf’s <code>expected_epistemic</code> is auto-captured as a reference.</li>
<li><strong>3-window stability ring buffers</strong> — three consecutive observation windows of ECE and σ_epi. A leaf only counts as calibration-stable when <code>max(ece_history) − min(ece_history)</code> stays under tolerance across all three windows.</li>
</ul>
<p>The drift-trip auto-unfreeze runs at the top of <code>decide_step</code>, <em>before</em> the structural-action ladder:</p>
<div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">def</span> decide_step_for_node(node, policy):</span>
<span id="cb1-2">    <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">if</span> node.is_frozen <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">and</span> drift_score(node) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> policy.drift_unfreeze:</span>
<span id="cb1-3">        unfreeze(node)</span>
<span id="cb1-4">        <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">return</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># don't run further actions this call</span></span>
<span id="cb1-5">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># otherwise: regular fall-through</span></span>
<span id="cb1-6">    <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">if</span> try_compress(...): <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">return</span></span>
<span id="cb1-7">    <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">if</span> try_merge(...): <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">return</span></span>
<span id="cb1-8">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ...</span></span></code></pre></div>
<p><strong>Why it matters.</strong> Real-world data has distribution shift. A frozen leaf that doesn’t notice it has fallen behind is silently wrong. ABNG’s auto-unfreeze gives the graph a way to detect its own surprise without human intervention. This is the closest the architecture comes to an active-inference loop.</p>
</section>
</section>
<section id="experimental-goals" class="level2">
<h2 class="anchored" data-anchor-id="experimental-goals">5. Experimental goals</h2>
<p>Pre-registered claims this article supports. Pre-registered failure modes that would invalidate each claim.</p>
<section id="claims-and-pass-conditions" class="level3">
<h3 class="anchored" data-anchor-id="claims-and-pass-conditions">Claims and pass conditions</h3>
<table class="table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th>Claim</th>
<th>What’s claimed</th>
<th>Pass condition</th>
<th>Falsification</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Cβ</strong></td>
<td>PINN out-of-span degrades gracefully</td>
<td>L2 error on <code>\|x − 0.5\|</code> (out-of-basis target) is within 10× of L2 error on <code>sin(πx)</code> (in-basis target)</td>
<td>Order-of-magnitude worse → architecture fails on out-of-basis targets</td>
</tr>
<tr class="even">
<td><strong>Cε</strong></td>
<td><code>decide_step</code> is sub-millisecond at moderate scale</td>
<td>mean time &lt; 1ms at n_active ≤ 256, growing linearly</td>
<td>Super-linear or &gt; 1ms — performance ceiling concern</td>
</tr>
<tr class="odd">
<td><strong>Cθ</strong></td>
<td>Routing distribution matches input distribution</td>
<td>Per-bin observation count tracks input distribution shape</td>
<td>Routing is uninformative — bin counts uniform regardless of input</td>
</tr>
<tr class="even">
<td><strong>Cι</strong></td>
<td>2-D OOD score has spatial structure</td>
<td>Composite OOD high outside training region, low inside, with distinguishable component fields</td>
<td>OOD score is uniform across input space — composite isn’t meaningful</td>
</tr>
</tbody>
</table>
<p>All four experiments <strong>passed</strong> in this run. Section 7 walks through each.</p>
</section>
<section id="pre-registered-failure-modes" class="level3">
<h3 class="anchored" data-anchor-id="pre-registered-failure-modes">Pre-registered failure modes</h3>
<ul>
<li><strong>Cβ fails</strong> → routing partition does NOT provide effective basis-augmentation. The architecture’s behaviour on out-of-basis targets is much worse than on in-basis — meaning the PINN result is restricted to its home turf. Important to know.</li>
<li><strong>Cε fails</strong> → <code>decide_step</code> has hidden quadratic cost. A graph that grows to 10K active nodes would be unusable. Would force a redesign of the policy engine.</li>
<li><strong>Cθ fails</strong> → routing is doing something other than partitioning by input value. Likely a routing-encoding bug or threshold-tuning issue. Diagnostic.</li>
<li><strong>Cι fails</strong> → composite OOD score is dominated by one component (likely density), making the “three independent signals” framing misleading. Would require revising the OOD-score story.</li>
</ul>
</section>
</section>
<section id="mini-demo-narratives" class="level2">
<h2 class="anchored" data-anchor-id="mini-demo-narratives">6. Mini-demo narratives</h2>
<p>Four scenarios — one per category — showing concrete inputs and observable graph behavior. Each is <strong>faithful to ABNG’s actual mechanism</strong>: input → codebook encode → tree descent → per-leaf update → audit advance. ABNG does <em>not</em> propagate belief across nodes via message passing; it routes each input to <em>one</em> leaf.</p>
<section id="md-ii-1-10k-observations-flow-through-8-bins-routing" class="level3">
<h3 class="anchored" data-anchor-id="md-ii-1-10k-observations-flow-through-8-bins-routing">MD-II-1 — 10K observations flow through 8 bins (Routing)</h3>
<pre class="text"><code>Setup:
  ABNG graph, 8-bin 1-D codebook with bin edges
  at {1/8, 2/8, ..., 7/8}
  8 children pre-allocated at root (one per byte 0x00..0x07)

Stream:
  10,000 observations from skewed Gaussian-like distribution
  (Box-Muller-derived, centered at x = 0.6, std = 0.15, clipped to [0,1])

Routing breakdown:
  byte 0x00     8 obs    0.08%
  byte 0x01   116 obs    1.16%
  byte 0x02   545 obs    5.45%
  byte 0x03 1,924 obs   19.24%
  byte 0x04 3,139 obs   31.39%  &lt;- peak (matches Gaussian mean)
  byte 0x05 2,694 obs   26.94%
  byte 0x06 1,213 obs   12.13%
  byte 0x07   361 obs    3.61%

What ABNG tracked:
  Per-leaf n_seen counter (visible via cjcl abng inspect --node)
  Optional: opt-in Routed audit events (kind 0x1B) per descent

Routing property:
  Routing distribution matches input distribution. The partition
  is interpretable — you can read off "the model has seen most of
  its data around x ~ 0.6" directly from the byte counts.</code></pre>
<p>The Sankey plot in §7.3 (Experiment θ) visualizes this with band thickness proportional to count. Compared to soft-routed MoE — where the gating decision is a learned softmax across the expert population — ABNG’s discrete byte routing is <em>trivially inspectable</em>. You don’t need attribution methods; you read the byte.</p>
</section>
<section id="md-ii-2-decide_step-on-256-nodes-in-under-1-ms-memory-scaling" class="level3">
<h3 class="anchored" data-anchor-id="md-ii-2-decide_step-on-256-nodes-in-under-1-ms-memory-scaling">MD-II-2 — <code>decide_step</code> on 256 nodes in under 1 ms (Memory Scaling)</h3>
<pre class="text"><code>Setup:
  ABNG graph with 256-bin 1-D codebook
  Forced-grow N children at the root (one per byte)
  20 trials per configuration with 3 warmup calls

n_active  min (ns)  mean (ns)  per-node-min (ns)
------------------------------------------------
17        1,900     10,115     111.8
65        3,600     88,705     55.4
256       2,000     147,850    7.8

What ABNG tracked:
  Per-node is_active flag (decide_step iterates only over active nodes)
  Per-decide_step action_counts vector (6 elements, one per action type)
  Audit chain head: advances only if structural events fired

Memory-scaling property:
  decide_step is sub-millisecond at all tested graph sizes.
  Mean scales roughly linearly with n_active (10K -&gt; 89K -&gt; 148K ns
  for 17 -&gt; 65 -&gt; 256 nodes). The minimum-of-20 timing is noisy
  at small n_active because clock resolution is comparable to the
  per-call cost; the mean is the cleaner signal.

Honest limit:
  The bench caps at 256 nodes because the codebook used has 256 bins
  (one byte route key). Multi-depth graphs with &gt;256 leaves are
  feasible but not benched in this run.</code></pre>
<p>The full Experiment ε result is in §7.2. The honest implication is “linear scaling at moderate scale, with cache effects becoming relevant past 256 nodes.” For production-scale graphs (n_active ∈ {1K, 10K, 100K}), multi-depth structures would be needed, and the deeper-tree decide_step cost is <strong>unmeasured</strong> — listed in §10 future work.</p>
</section>
<section id="md-ii-3-merge-fires-when-two-leaves-converge-adaptation" class="level3">
<h3 class="anchored" data-anchor-id="md-ii-3-merge-fires-when-two-leaves-converge-adaptation">MD-II-3 — Merge fires when two leaves converge (Adaptation)</h3>
<pre class="text"><code>Setup:
  ABNG graph with 4 sibling leaves at depth 1
  Train each leaf on similar data for 100+ observations
  Configure decision policy: kl_merge = 0.05; signature Hamming
  tolerance = 0.10

After training, two leaves (say A and B) converge:
  Hamming(sig_A, sig_B) = 0.07          (under tolerance, gate passes)
  KL(BlrState_A || BlrState_B) = 0.03   (under kl_merge, gate passes)
  signature_stable_calls(A) = 5         (mature)
  signature_stable_calls(B) = 5         (mature)

decide_step fires:
  For leaf A (lower NodeId):
    drift-trip check: not frozen, skip
    Compress gate: no equivalent sub-tree, skip
    Merge gate: A and B are mergeable -&gt; FIRE
  BlrState::combine(P_A, P_B) merges posteriors NIG-aware:
    Lambda_into = Lambda_A + Lambda_B
    m_into = Lambda_into^{-1} (Lambda_A m_A + Lambda_B m_B)
    a_into = a_A + a_B
    b_into combines via residual term

After Merge:
  A.n_seen = sum of both     (no information lost)
  B.is_active = false        (tombstoned in arena)
  action_counts[Merge] += 1
  audit log: 0x12 Merge {absorbed: B, into: A}
  audit log: 0x0A BlrUpdated (witness on A's combined posterior)

What ABNG tracked:
  Audit chain advance with Merge event
  cjcl abng diff before.snap after.snap reveals topology change
  cjcl abng inspect --node A reveals combined posterior

Adaptation property:
  The graph compressed itself when evidence supported it. The
  Merge is NIG-aware - absorbed leaf's evidence is preserved in
  the combined posterior, not lost. This is what Phase 0.4 Track
  B-2.2.6 fixed; before, naive Merge would lose information.

Honest caveat:
  The kl_merge threshold and Hamming tolerance both require dataset-
  specific tuning. Too-loose -&gt; spurious merges; too-tight -&gt; no
  consolidation. There is no default that works across all data
  distributions. Threshold preset templates are planned for Phase
  0.5+ but not yet shipped.</code></pre>
<p>The companion test <code>tests/test_abng_adaptive_triggers_cjcl.rs::adaptive_cjcl_audit_chain_verifies_post_decide_step</code> asserts the <em>critical invariant</em>: after Merge fires, <code>verify_chain()</code> still returns <code>Ok</code>. The structural mutation didn’t break tamper-evidence — covered in Part I §4.2 (lineage) and reaffirmed here.</p>
</section>
<section id="md-ii-4-auto-unfreeze-on-distribution-shift-temporal-dynamics" class="level3">
<h3 class="anchored" data-anchor-id="md-ii-4-auto-unfreeze-on-distribution-shift-temporal-dynamics">MD-II-4 — Auto-unfreeze on distribution shift (Temporal Dynamics)</h3>
<pre class="text"><code>Setup:
  ABNG graph with 1 active leaf (k = 1)
  Decision policy: drift_unfreeze threshold = 1.5

Stage 1: data ~ N(0, 1), 50 observations
  Each observation routes to leaf 1
  BLR posterior tightens; signature stabilizes;
  ECE remains low; uncertainty_stable becomes true at step ~30;
  expected_epistemic = sigma_epi_at_step_30 auto-captured

  decide_step at step 50:
    Compress, Merge, Split, Prune gates: skip (single leaf)
    Grow gate: route entropy too low, skip
    Freeze gate: fires (calibration_stable AND uncertainty_stable)
    -&gt; Freeze (0x15) audit event
  Now: leaf 1.is_frozen = true; future blr_update on leaf 1
  becomes a no-op.

Stage 2: data ~ N(2, 1) (mean shifted), 25 observations
  Each observation routes to leaf 1 (same byte)
  blr_update is SKIPPED (frozen)
  density.observe still updates (drift tracker keeps running)
  drift_score climbs as Welford density shifts

  decide_step at step 75:
    Pre-ladder check: leaf 1 is frozen AND drift_score &gt; 1.5?
    -&gt; drift_score = 2.3 &gt; 1.5 = drift_unfreeze
    -&gt; Unfreeze fires (0x16 audit event)
    -&gt; leaf 1.is_frozen = false
    -&gt; unfreeze_count += 1
    -&gt; rest of action ladder skipped this call

Stage 3: BLR resumes
  Subsequent observations re-enter blr_update path
  BLR posterior re-tightens on the new distribution

What ABNG tracked:
  Audit log: Freeze (0x15) at step 50, Unfreeze (0x16) at step 75
  density_score per observation (visible via cjcl abng inspect)
  unfreeze_count: u64 field on the graph

Temporal-dynamics property:
  The graph responds to its own surprise. A frozen leaf that sees
  distribution shift re-enables learning automatically. This is
  the closest ABNG comes to an active-inference loop.

Honest caveats:
  - drift_unfreeze threshold tuning is dataset-specific
  - "auto-unfreeze fires when drift score exceeds threshold" is
    verified in unit tests; the full integration of "auto-unfreeze
    + subsequent re-learning recovers performance" is not directly
    measured here
  - Tested on controlled mean-shift scenario; real-world drift
    (multi-modal, gradual, etc.) is unmeasured</code></pre>
<p>The drift-detection demo (<code>tests/test_abng_drift_detection_cjcl.rs</code>) tests the score-climbing property and verifies the auto-unfreeze gate fires under the right conditions. The integrated story (Freeze → drift → Unfreeze → recovery) is verified piece-by-piece but not as a single end-to-end performance test.</p>
</section>
</section>
<section id="deep-experiments" class="level2">
<h2 class="anchored" data-anchor-id="deep-experiments">7. Deep experiments</h2>
<p>Four experiments, each with the same shape as Part I: hypothesis, methodology, results, figure, verdict.</p>
<section id="experiment-β-basis-not-in-span-pinn-the-surprise" class="level3">
<h3 class="anchored" data-anchor-id="experiment-β-basis-not-in-span-pinn-the-surprise">7.1 Experiment β — Basis-not-in-span PINN (the surprise)</h3>
<p><strong>Hypothesis (Cβ).</strong> ABNG’s PINN performance degrades gracefully when the feature basis cannot represent the target function exactly.</p>
<p><strong>Methodology.</strong> Two targets, same architecture (8-leaf 1-D codebook + 5-D feature basis <code>[1, x, x², x³, sin(πx)]</code> per leaf):</p>
<ul>
<li>In-span target: <code>u(x) = sin(πx)</code> — <strong>is</strong> in the basis (the last basis function).</li>
<li>Out-of-span target: <code>u(x) = |x − 0.5|</code> — has a kink at x = 0.5; not in the polynomial + sinusoid basis globally.</li>
</ul>
<p>Train on n = 10,000 collocation points uniformly on [0, 1]. Evaluate L2 error on 1024 dense grid points.</p>
<p><strong>Result.</strong></p>
<table class="table">
<thead>
<tr class="header">
<th>Scenario</th>
<th style="text-align: right;">L2 error</th>
<th style="text-align: right;">Max abs error</th>
<th style="text-align: right;">Mean leverage</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>In-span <code>sin(πx)</code></td>
<td style="text-align: right;">9.35 × 10⁻³</td>
<td style="text-align: right;">2.23 × 10⁻²</td>
<td style="text-align: right;">1.45 × 10⁻³</td>
</tr>
<tr class="even">
<td>Out-of-span <code>\|x − 0.5\|</code></td>
<td style="text-align: right;">9.01 × 10⁻³</td>
<td style="text-align: right;">2.82 × 10⁻²</td>
<td style="text-align: right;">1.45 × 10⁻³</td>
</tr>
</tbody>
</table>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://adamezzat1.github.io/blog/posts/abng-benchmarks-part-2/figures/v_beta_basis_check.png" class="img-fluid figure-img"></p>
<figcaption class="figure-caption">V-β — Basis-in-span vs out-of-span</figcaption>
</figure>
</div>
<p><strong>Verdict.</strong> <strong>The hypothesis was wrong in an interesting direction.</strong> ABNG fits the out-of-span target <em>nearly as well</em> as the in-span target — L2 errors are within 4% of each other.</p>
<p><strong>The mechanism.</strong> ABNG uses 8 routed leaves, each with its own 5-D feature basis. Even though <code>|x − 0.5|</code> is globally non-smooth at x = 0.5, each leaf only sees its own slice of input space. A 5-D affine fit within an 0.125-wide slice can approximate the local behaviour of <code>|x − 0.5|</code> very well — the function is piecewise <em>linear</em>, after all, and matches polynomial fits perfectly on each piece. The codebook bin boundary at x = 0.5 happens to align almost exactly with the kink, so the <em>piecewise</em> basis is effectively complete.</p>
<p><strong>Two interpretations.</strong></p>
<ol type="1">
<li><strong>Positive interpretation:</strong> ABNG’s piecewise routing partition provides <em>effective basis-augmentation</em>. The architecture is more robust to basis-adequacy than a single-leaf BLR would be.</li>
<li><strong>Skeptical interpretation:</strong> The result is partly an artifact of the test setup. <code>|x − 0.5|</code> is <em>piecewise linear</em>, and the codebook bin edges happen to be near the kink — both make the per-leaf 5-D basis effectively complete. Pick a target that the <em>piecewise</em> basis can’t fit (high-frequency oscillation, multiple discontinuities not aligned with bin edges) and the architecture should fail.</li>
</ol>
<p>Both interpretations are defensible. The article presents both. <strong>Tag: Demonstrated</strong> — the mechanism is real and reproducible, but the generality claim (“ABNG is robust to basis adequacy”) is <em>Reasonable extrapolation</em> pending the harder test.</p>
<p><strong>Honest follow-up.</strong> A truer falsification: target <code>u(x) = sin(20πx)</code> (high-frequency, far above the codebook bin resolution). Or <code>u(x) = step(x − 0.37)</code> (discontinuity NOT aligned with a codebook bin edge). Either would reveal whether the architecture’s robustness is general or scenario-specific. <strong>Not run in this article.</strong></p>
</section>
<section id="experiment-ε-decide_step-scaling" class="level3">
<h3 class="anchored" data-anchor-id="experiment-ε-decide_step-scaling">7.2 Experiment ε — <code>decide_step</code> scaling</h3>
<p><strong>Hypothesis (Cε).</strong> <code>decide_step</code> is <code>O(n_active_nodes)</code>.</p>
<p><strong>Methodology.</strong> Build graphs with <code>n_active ∈ {16, 64, 256}</code> active nodes via repeated <code>add_node</code> calls at the root. Run <code>decide_step</code> 20 times per configuration with 3 warmup calls. Report min and mean over the 20 trials. Cap at 256 because the codebook used has 256 bins.</p>
<p><strong>Result.</strong></p>
<table class="table">
<thead>
<tr class="header">
<th style="text-align: right;">n_active</th>
<th style="text-align: right;">min (ns)</th>
<th style="text-align: right;">mean (ns)</th>
<th style="text-align: right;">per-node-min (ns)</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: right;">17 (incl.&nbsp;root)</td>
<td style="text-align: right;">1,900</td>
<td style="text-align: right;">10,115</td>
<td style="text-align: right;">111.8</td>
</tr>
<tr class="even">
<td style="text-align: right;">65</td>
<td style="text-align: right;">3,600</td>
<td style="text-align: right;">88,705</td>
<td style="text-align: right;">55.4</td>
</tr>
<tr class="odd">
<td style="text-align: right;">256</td>
<td style="text-align: right;">2,000</td>
<td style="text-align: right;">147,850</td>
<td style="text-align: right;">7.8</td>
</tr>
</tbody>
</table>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://adamezzat1.github.io/blog/posts/abng-benchmarks-part-2/figures/v4_decide_step_scaling.png" class="img-fluid figure-img"></p>
<figcaption class="figure-caption">V4 — decide_step scaling</figcaption>
</figure>
</div>
<p><strong>Verdict.</strong> Mean time grows roughly linearly with <code>n_active</code> (10K → 89K → 148K ns for 17 → 65 → 256 nodes). The minimum-of-20 measurement is noisy at small n_active (clock resolution dominates) but the mean is clean. <strong>Cε supported</strong> at the tested scales: sub-millisecond at n_active ≤ 256, scaling consistent with the O(n) hypothesis.</p>
<p><strong>Honest limit.</strong> The codebook used has 256 bins so the bench can’t grow beyond ~256 nodes at depth 1. Multi-depth graphs (deeper than 1) would allow n_active = 1K, 10K, larger, but the deeper-tree <code>decide_step</code> cost is <strong>unmeasured</strong> in this run. Future work.</p>
<p><strong>The cost model summary.</strong> At the tested scales:</p>
<ul>
<li><strong>Inference path (route + predict):</strong> sub-microsecond per query (118 ns descend + 137 ns encode + 934 ns blr_predict ≈ 1.2 µs).</li>
<li><strong>Training path (observe):</strong> ~4 µs per observation (includes audit-chain append).</li>
<li><strong>decide_step:</strong> sub-millisecond per call up to n_active = 256.</li>
<li><strong>Full graph training:</strong> at n=10K observations with periodic decide_step, total wall-clock ≈ 50ms.</li>
</ul>
<p>These numbers extrapolate linearly to ~5 seconds for n=1M observations at the tested graph size. For larger graphs, the costs compound — full characterization requires multi-depth scaling work.</p>
</section>
<section id="experiment-θ-routing-flow-sankey" class="level3">
<h3 class="anchored" data-anchor-id="experiment-θ-routing-flow-sankey">7.3 Experiment θ — Routing-flow Sankey</h3>
<p><strong>Hypothesis (Cθ).</strong> Training data flows through the trie’s codebook bins in proportion to the input distribution; routing can be visualized as a Sankey diagram from root to leaves.</p>
<p><strong>Methodology.</strong> Generate n = 10,000 samples from a skewed Gaussian (Box-Muller-derived, centered at x = 0.6, std = 0.15, clipped to [0, 1]). Train ABNG with an 8-bin 1-D codebook + 8 child leaves. Record route byte and resolved leaf per observation. Aggregate counts.</p>
<p><strong>Result.</strong> All 10,000 inputs routed; 8 unique route bytes, 8 unique leaves. Distribution matches input skew (full table in MD-II-1).</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://adamezzat1.github.io/blog/posts/abng-benchmarks-part-2/figures/v3_routing_flow.png" class="img-fluid figure-img"></p>
<figcaption class="figure-caption">V3 — Routing flow Sankey</figcaption>
</figure>
</div>
<p><strong>Verdict.</strong> <strong>Cθ passed.</strong> The Sankey makes the “discrete routing partitions input space” claim <em>visible</em>. The peak at byte 0x04 (31.39%) matches the Gaussian mean. The tails decay symmetrically. Tag: <strong><em>Demonstrated</em></strong> — the routing mechanism produces an inspectable distribution that reflects the input.</p>
<p><strong>Why this matters more than it seems.</strong> For an MoE-style router with soft gating, “which expert handled this input?” is a learned floating-point distribution across the whole expert population. ABNG’s discrete routing turns the same question into “read the route byte.” You don’t need attribution methods. You read the byte. That’s a substantial <strong>interpretability advantage</strong> for debugging and capacity planning.</p>
</section>
<section id="experiment-ι-2-d-ood-score-heatmap" class="level3">
<h3 class="anchored" data-anchor-id="experiment-ι-2-d-ood-score-heatmap">7.4 Experiment ι — 2-D OOD score heatmap</h3>
<p><strong>Hypothesis (Cι).</strong> The composite <code>ood_score</code> and its three components (density, prefix-distance, epistemic-leverage) produce spatially structured abstain signals in 2-D input space. Training in a sub-region should yield distinguishable OOD profiles inside vs outside.</p>
<p><strong>Methodology.</strong> Train ABNG on n = 1,000 points sampled uniformly from <code>[0.25, 0.75]²</code> of the truth function <code>f(x, y) = 2x + 3y + 0.5xy</code>. Use a 2-D codebook with 8 bins per dimension. Evaluate on a 50 × 50 grid covering <code>[0, 1]²</code>. Emit <code>(x, y, ood, density_score, prefix_distance, epistemic_leverage)</code> per grid cell.</p>
<p><strong>Result.</strong></p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://adamezzat1.github.io/blog/posts/abng-benchmarks-part-2/figures/v9_ood_2d_heatmap.png" class="img-fluid figure-img"></p>
<figcaption class="figure-caption">V9 — 2-D OOD score heatmap</figcaption>
</figure>
</div>
<p>The four-panel decomposition shows:</p>
<ul>
<li><strong>Top-left (composite OOD)</strong> — the abstain signal. Bright (high OOD) outside the training region (dashed box); dark (low OOD) inside.</li>
<li><strong>Top-right (density component)</strong> — Mahalanobis distance from the per-leaf Welford density. Tracks whether the input “looks like” what each leaf saw.</li>
<li><strong>Bottom-left (prefix distance)</strong> — number of unmatched route-key bytes. 0 inside training region (full match); 2 in regions where neither codebook coordinate finds a bound child.</li>
<li><strong>Bottom-right (epistemic leverage)</strong> — <code>φᵀΛ⁻¹φ</code>. Largest where the BLR posterior is least confident.</li>
</ul>
<p><strong>Verdict.</strong> <strong>Cι passed.</strong> All four signals show the expected spatial structure: low inside the training region, high outside. The four components are <em>not</em> redundant — each captures a different “is this unusual?” axis. The composite <code>max()</code> produces a deliberately conservative single number; the individual components let a user diagnose <em>which</em> OOD axis triggered abstain. Tag: <strong><em>Demonstrated</em></strong>.</p>
<p><strong>The architectural distinction this exposes.</strong> ABNG’s OOD score is the <em>composition</em> of three signals (one continuous, one discrete, one model-uncertainty). The discrete signal (prefix distance) is unique to the architecture — no soft-router model has it. The composition is over-conservative by design: a <em>single</em> high signal is enough to trigger abstain, even if the other two are low.</p>
</section>
</section>
<section id="demo-deep-dives" class="level2">
<h2 class="anchored" data-anchor-id="demo-deep-dives">8. Demo deep-dives</h2>
<p>Existing demo suites that illustrate Part II’s categories.</p>
<section id="adaptive-triggers-the-marquee-decide_step-demo" class="level3">
<h3 class="anchored" data-anchor-id="adaptive-triggers-the-marquee-decide_step-demo">8.1 Adaptive triggers — the marquee <code>decide_step</code> demo</h3>
<p><strong>The setup.</strong> Construct a graph with similar-signature sibling leaves. Train each leaf on observations from the same distribution. Run <code>decide_step</code> and observe what fires.</p>
<p><strong>What the test asserts.</strong></p>
<div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb6-1"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">#[</span>test<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb6-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> adaptive_cjcl_audit_chain_verifies_post_decide_step() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb6-3">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> run_parity(<span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">abng_demos::adaptive_source::</span>SOURCE<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> SEED)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb6-4">    <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">assert_eq!</span>(extract_value(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>out<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"verify_chain"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"true"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb6-5"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb6-6"></span>
<span id="cb6-7"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">#[</span>test<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb6-8"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> adaptive_cjcl_some_structural_action_fired() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb6-9">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> run_parity(<span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">abng_demos::adaptive_source::</span>SOURCE<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> SEED)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb6-10">    <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">assert_eq!</span>(extract_value(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>out<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"any_action_fired"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"true"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb6-11"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb6-12"></span>
<span id="cb6-13"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">#[</span>test<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb6-14"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> adaptive_cjcl_merge_count_matches_canary_topology() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb6-15">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> run_parity(<span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">abng_demos::adaptive_source::</span>SOURCE<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> SEED)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb6-16">    <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">assert_eq!</span>(extract_value(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>out<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"merge_count"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"1"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// exact canary</span></span>
<span id="cb6-17"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div>
<p><strong>Why the audit-chain-integrity assertion is the critical one.</strong> A structural mutation that broke the audit chain would silently invalidate every replay guarantee. The test verifies that <code>verify_chain()</code> still returns <code>Ok</code> after the mutation fires. This is the <em>invariant</em> — without it, every other property in this article degrades.</p>
<p>The Merge-count-canary test pins exactly <em>one</em> Merge action firing under the deterministic scenario. Any change to the structural-action thresholds, the iteration order, or the gate logic would surface as a different count. Tag: <strong><em>Proven</em></strong> at the demo scenario; canary-locked.</p>
</section>
<section id="five-trigger-specific-demos-compress-grow-split-prune-freeze" class="level3">
<h3 class="anchored" data-anchor-id="five-trigger-specific-demos-compress-grow-split-prune-freeze">8.2 Five trigger-specific demos (Compress, Grow, Split, Prune, Freeze)</h3>
<p>Each trigger has a dedicated demo file isolating its behavior:</p>
<pre><code>tests/test_abng_compress_trigger_cjcl.rs    61 LOC
tests/test_abng_grow_trigger_cjcl.rs        78 LOC
tests/test_abng_split_trigger_cjcl.rs       72 LOC
tests/test_abng_prune_trigger_cjcl.rs       68 LOC
tests/test_abng_freeze_trigger_cjcl.rs      67 LOC</code></pre>
<p>Each demo follows the same pattern:</p>
<ol type="1">
<li>Configure decision policy so the target action’s gate is the only one that can fire under the scenario.</li>
<li>Train the graph to satisfy the gate’s preconditions (samples_seen ≥ threshold, signature_stable_calls ≥ N, etc.).</li>
<li>Call <code>decide_step</code> once.</li>
<li>Assert exactly one action fired (the target).</li>
<li>Assert audit chain still verifies.</li>
</ol>
<p><strong>What this proves at the action level.</strong> Each of the six structural actions is verifiable in isolation. The Phase 0.4 quality gates (KL-merge, ΔNLL-split, route-entropy-grow) are exercised through these demos.</p>
<p><strong>One asymmetry worth flagging.</strong> There is <strong>no dedicated <code>test_abng_merge_trigger_cjcl.rs</code>.</strong> Merge is exercised inside the adaptive-triggers demo (§8.1) rather than getting its own file. Small detail — the demo suite covers Merge but the asymmetry is real and worth noting. Tag: <strong><em>Demonstrated</em></strong> at the per-action level for the 5 trigger-specific demos, plus <strong><em>Proven</em></strong> at canary-level for the adaptive-triggers demo.</p>
</section>
<section id="drift-detection-auto-unfreeze-temporal-dynamics" class="level3">
<h3 class="anchored" data-anchor-id="drift-detection-auto-unfreeze-temporal-dynamics">8.3 Drift detection + auto-unfreeze (temporal dynamics)</h3>
<p><strong>The setup.</strong> Train an ABNG leaf on data <code>~N(0, 1)</code> for ~50 observations. Configure the decision policy so the leaf reaches “frozen” status. Then shift the data distribution to <code>~N(2, 1)</code> and observe the drift score climb.</p>
<p><strong>What the test asserts.</strong></p>
<div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb8-1"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">#[</span>test<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb8-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> drift_cjcl_drift_score_non_negative() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb8-3">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> run_parity(<span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">abng_demos::drift_source::</span>SOURCE<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> SEED)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb8-4">    <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">assert_eq!</span>(extract_value(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>out<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"drift_nonneg"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"true"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb8-5"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb8-6"></span>
<span id="cb8-7"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">#[</span>test<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb8-8"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> drift_cjcl_meaningful_shift_detected() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb8-9">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> run_parity(<span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">abng_demos::drift_source::</span>SOURCE<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> SEED)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb8-10">    <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">assert_eq!</span>(extract_value(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>out<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"meaningful_shift"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"true"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb8-11"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb8-12"></span>
<span id="cb8-13"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">#[</span>test<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb8-14"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> drift_cjcl_drift_grew_after_shift() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb8-15">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Verifies drift score is strictly higher after the shift than before</span></span>
<span id="cb8-16">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> run_parity(<span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">abng_demos::drift_source::</span>SOURCE<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> SEED)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb8-17">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// (specific high/low value assertions)</span></span>
<span id="cb8-18"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div>
<p><strong>Verdict.</strong> The mechanism is verified: drift score is non-negative, climbs after distribution shift, and crosses the “meaningful shift” threshold. The companion test in <code>tests/abng/decide_step_canary_tests.rs</code> verifies the auto-unfreeze gate fires when the drift score exceeds <code>drift_unfreeze</code>.</p>
<p><strong>Caveat.</strong> The integrated “auto-unfreeze fires AND subsequent training recovers performance on the shifted distribution” is not measured as a single end-to-end test. The mechanism is verified piece-by-piece. Tag: <strong><em>Demonstrated</em></strong> at the per-piece level; <strong><em>Reasonable extrapolation</em></strong> for the integrated recovery story.</p>
</section>
<section id="maturity-signature-evolution-temporal-state" class="level3">
<h3 class="anchored" data-anchor-id="maturity-signature-evolution-temporal-state">8.4 Maturity &amp; signature evolution (temporal state)</h3>
<p><strong>The setup.</strong> Train an ABNG leaf over many observations. Track <code>Maturity</code> flags (<code>samples_seen</code>, <code>calibration_stable</code>, <code>uncertainty_stable</code>, <code>trust_level</code>) and <code>NodeSignature</code> (32-byte Welford-smoothed summary across 4 channels) over time.</p>
<p><strong>What the test asserts.</strong></p>
<div class="sourceCode" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb9-1"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">#[</span>test<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb9-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> maturity_cjcl_eventually_reaches_stable() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb9-3">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Verifies that after enough observations, both calibration_stable</span></span>
<span id="cb9-4">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// and uncertainty_stable flip to true for the trained leaf</span></span>
<span id="cb9-5"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb9-6"></span>
<span id="cb9-7"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">#[</span>test<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb9-8"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> maturity_cjcl_signature_welford_smoothed_across_4_channels() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb9-9">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Verifies the signature is non-zero across all 4 channels</span></span>
<span id="cb9-10">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// (prediction, uncertainty, calibration, routing)</span></span>
<span id="cb9-11"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb9-12"></span>
<span id="cb9-13"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">#[</span>test<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb9-14"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> maturity_cjcl_3_window_stability_buffers_work() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb9-15">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Verifies the ECE / sigma_epi history rings have correct length</span></span>
<span id="cb9-16">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// and fill correctly across observation windows</span></span>
<span id="cb9-17"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div>
<p><strong>Verdict.</strong> The temporal-state evolution mechanisms are verified. A trained leaf’s <code>Maturity</code> flags transition correctly; the signature ring buffers fill across observation windows; the 3-window stability gates correctly classify stable vs unstable leaves. Tag: <strong><em>Demonstrated</em></strong>.</p>
<p><strong>The bigger story this demo enables.</strong> The combination of Maturity + NodeSignature is what makes <code>decide_step</code>’s actions <em>deterministic given evidence</em>. The structural decisions are pure functions of the per-node state, not stochastic samples. This is why ABNG can be replayed byte-identically even though the graph is restructuring — the restructure decisions are deterministic, and the deterministic decisions are reproducible.</p>
</section>
</section>
<section id="routing-microbench-results" class="level2">
<h2 class="anchored" data-anchor-id="routing-microbench-results">9. Routing microbench results</h2>
<p>Per-operation cost of the routing path, measured on the benchmark hardware (Intel i7-11390H @ 3.40 GHz, Windows MSVC release build):</p>
<table class="table">
<colgroup>
<col style="width: 30%">
<col style="width: 40%">
<col style="width: 30%">
</colgroup>
<thead>
<tr class="header">
<th>Operation</th>
<th style="text-align: right;">Per-op cost</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>descend</code> (one byte walk)</td>
<td style="text-align: right;"><strong>118 ns</strong></td>
<td>Cheapest op; baseline noise floor</td>
</tr>
<tr class="even">
<td><code>encode_prefix</code> (1-D codebook)</td>
<td style="text-align: right;"><strong>137 ns</strong></td>
<td>Single quantile lookup</td>
</tr>
<tr class="odd">
<td><code>codebook_encode_into</code> (8-D, buffer-reuse)</td>
<td style="text-align: right;"><strong>130 ns</strong></td>
<td>Phase 0.7 Item B optimization</td>
</tr>
<tr class="even">
<td><code>codebook_encode_alloc</code> (8-D, fresh Vec)</td>
<td style="text-align: right;"><strong>217 ns</strong></td>
<td>1.67× speedup with buffer reuse</td>
</tr>
<tr class="odd">
<td><code>route_to_leaf</code> (encode + descend, fused)</td>
<td style="text-align: right;"><strong>172 ns</strong></td>
<td>Hot inference path</td>
</tr>
<tr class="even">
<td><code>route_to_leaf_batch</code> (n=1024 vs per-row)</td>
<td style="text-align: right;"><strong>1.91× speedup</strong></td>
<td>Phase 0.7 Item 7 batched-dispatch</td>
</tr>
</tbody>
</table>
<section id="speedup-comparisons-phase-0.7-perf-wins" class="level3">
<h3 class="anchored" data-anchor-id="speedup-comparisons-phase-0.7-perf-wins">Speedup comparisons (Phase 0.7 perf wins)</h3>
<table class="table">
<colgroup>
<col style="width: 21%">
<col style="width: 28%">
<col style="width: 28%">
<col style="width: 21%">
</colgroup>
<thead>
<tr class="header">
<th>Comparison</th>
<th style="text-align: right;">Measured speedup</th>
<th style="text-align: right;">Phase 0.7 doc claim</th>
<th>Match?</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>observe_batch</code> n=1024 vs per-row</td>
<td style="text-align: right;"><strong>23.51×</strong></td>
<td style="text-align: right;">“≥ 10× target”</td>
<td>✓ exceeds</td>
</tr>
<tr class="even">
<td><code>route_to_leaf_batch</code> n=1024 vs per-row</td>
<td style="text-align: right;"><strong>1.91×</strong></td>
<td style="text-align: right;">n/a</td>
<td>—</td>
</tr>
<tr class="odd">
<td><code>smart_replay</code> vs naive @ 10k events</td>
<td style="text-align: right;"><strong>2.08×</strong></td>
<td style="text-align: right;">“≥ 5× target”</td>
<td>✗ falls short</td>
</tr>
<tr class="even">
<td><code>codebook_encode_into</code> vs <code>_alloc</code> (d=8)</td>
<td style="text-align: right;"><strong>1.67×</strong></td>
<td style="text-align: right;">“2.37×”</td>
<td>✗ smaller</td>
</tr>
</tbody>
</table>
<p>As Part I §9.6 noted: three of the article’s quoted Phase 0.7 speedup numbers don’t match this hardware’s measurements. Plausible explanations are different CPU generations, build flags, or warmup variation. The honest framing: <strong>speedup numbers should be quoted as ranges, not point estimates</strong>. “1.0–2.4× depending on hardware” is defensible; “2.37×” creates a false expectation.</p>
</section>
<section id="cross-reference-to-scaling-at-higher-n" class="level3">
<h3 class="anchored" data-anchor-id="cross-reference-to-scaling-at-higher-n">Cross-reference to scaling at higher n</h3>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://adamezzat1.github.io/blog/posts/abng-benchmarks-part-2/figures/v1_scaling_curves.png" class="img-fluid figure-img"></p>
<figcaption class="figure-caption">V1 — Scaling curves (PINN-relevant subplot middle panel)</figcaption>
</figure>
</div>
<p>The middle subplot above shows the basis-in-span PINN scaling at n = 1K → 100K collocation points. L2 error decays at the expected Monte Carlo rate (≈ 1/√n). The per-point train cost dips at n = 10K (cache-friendly) then rises at n = 100K (cache pressure on the audit log and per-node state).</p>
</section>
</section>
<section id="failure-cases-limitations" class="level2">
<h2 class="anchored" data-anchor-id="failure-cases-limitations">10. Failure cases &amp; limitations</h2>
<section id="decide_step-not-benched-past-n_active-256" class="level3">
<h3 class="anchored" data-anchor-id="decide_step-not-benched-past-n_active-256">10.1 <code>decide_step</code> not benched past n_active = 256</h3>
<p>The bench caps at 256 nodes due to the 256-bin codebook. Multi-depth graphs would allow n_active in {1K, 10K, 100K}; the <em>deeper-tree</em> <code>decide_step</code> cost is unmeasured. Listed in future work — addressable in 1-2 days of bench code.</p>
</section>
<section id="trigger-demos-are-positive-control" class="level3">
<h3 class="anchored" data-anchor-id="trigger-demos-are-positive-control">10.2 Trigger demos are positive-control</h3>
<p>Each of the 5 trigger-specific demos verifies the action <em>fires</em> under the right conditions. None verify the action <em>doesn’t</em> fire under conditions where it shouldn’t. The right complement is negative-case tests (e.g., “with kl_merge = 0 and Hamming similarity threshold = 0, Merge should never fire”). <strong>Not implemented in this article</strong>. Listed in future work.</p>
</section>
<section id="no-external-adaptive-system-baseline" class="level3">
<h3 class="anchored" data-anchor-id="no-external-adaptive-system-baseline">10.3 No external adaptive-system baseline</h3>
<p>ABNG’s drift detection vs ADWIN, KSWIN, Page-Hinkley. ABNG’s structural adaptation vs Mondrian Forests, online decision trees. <strong>No head-to-head comparisons exist</strong> in this article. Listed in future work as separate research questions (one for drift, one for adaptive partitioning).</p>
</section>
<section id="no-production-scale-temporal-dynamics" class="level3">
<h3 class="anchored" data-anchor-id="no-production-scale-temporal-dynamics">10.4 No production-scale temporal dynamics</h3>
<p>The drift demo tests a controlled mean-shift scenario. Real-world drift is <em>multi-modal</em> (e.g., concept drift in news classification), <em>gradual</em> (slowly changing user preferences), and <em>adversarial</em> (data poisoning). None of these are exercised. The Phase 0.7 demos verify the <em>mechanism</em> works on toy scenarios; competitive performance on real streaming data is <strong>unmeasured</strong>.</p>
</section>
<section id="routing-analysis-depends-on-opt-in-audit-events" class="level3">
<h3 class="anchored" data-anchor-id="routing-analysis-depends-on-opt-in-audit-events">10.5 Routing analysis depends on opt-in audit events</h3>
<p>For Experiment θ (routing-flow Sankey) and §8 demo analysis, the routing flow is reconstructed from <code>descend()</code> results during the bench run. The opt-in <code>Routed</code> audit events (kind <code>0x1B</code>) would let routing be analysed <em>post-hoc</em> from any audit log — but emitting them adds linear cost in observations. The current bench framework counts routes during execution rather than logging them. For production routing analysis, the choice between in-line counting (cheap, ephemeral) and persistent <code>Routed</code> events (expensive, replayable) is a workflow decision.</p>
</section>
<section id="future-work-part-ii-categories" class="level3">
<h3 class="anchored" data-anchor-id="future-work-part-ii-categories">Future work (Part II categories)</h3>
<table class="table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Item</th>
<th>Cost</th>
<th>Closes which gap</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Multi-depth <code>decide_step</code> bench at n_active = {1K, 10K, 100K}</td>
<td>1 week</td>
<td>§10.1</td>
</tr>
<tr class="even">
<td>Negative-case trigger tests</td>
<td>2-3 days</td>
<td>§10.2</td>
</tr>
<tr class="odd">
<td>ADWIN baseline for drift detection</td>
<td>1-2 days</td>
<td>§10.3</td>
</tr>
<tr class="even">
<td>Mondrian Forest baseline for adaptive partitioning</td>
<td>2-3 days</td>
<td>§10.3</td>
</tr>
<tr class="odd">
<td>Real-world streaming-data drift demo</td>
<td>1 month (needs dataset partner)</td>
<td>§10.4</td>
</tr>
<tr class="even">
<td>Stress-test 10⁹-event replay with smart-replay path</td>
<td>1-2 days</td>
<td>(push past Part I’s 10⁶ ceiling)</td>
</tr>
</tbody>
</table>
</section>
</section>
<section id="reproducibility" class="level2">
<h2 class="anchored" data-anchor-id="reproducibility">11. Reproducibility</h2>
<p>Every number, table, and figure in this article can be reproduced from a clean checkout of the CJC-Lang repository.</p>
<section id="build-environment-fingerprint" class="level3">
<h3 class="anchored" data-anchor-id="build-environment-fingerprint">Build environment fingerprint</h3>
<pre class="text"><code>Hardware:    Intel Core i7-11390H @ 3.40 GHz (4 cores, 8 threads)
OS:          Windows 11 Pro build 26200
Toolchain:   MSVC, Rust (stable channel at publication time)
Build mode:  release (-O3-equivalent, LTO disabled)
CJC-Lang:    Phase 0.7, snapshot magic v13 (b"ABNG\x0D")
Plotting:    Python 3.11, matplotlib 3.9.2, numpy 2.2.6</code></pre>
</section>
<section id="reproduction-commands" class="level3">
<h3 class="anchored" data-anchor-id="reproduction-commands">Reproduction commands</h3>
<div class="sourceCode" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb11-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 1. Clone the repository</span></span>
<span id="cb11-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">git</span> clone https://github.com/AdamEzzat1/CJC.git</span>
<span id="cb11-3"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">cd</span> CJC</span>
<span id="cb11-4"></span>
<span id="cb11-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 2. Build the bench suite (release profile)</span></span>
<span id="cb11-6"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cargo</span> build <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--release</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb11-7">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-p</span> abng-srg-experiments <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb11-8">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-p</span> abng-micro <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb11-9">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-p</span> abng-pinn-scale</span>
<span id="cb11-10"></span>
<span id="cb11-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 3. Run the experiments relevant to Part II</span></span>
<span id="cb11-12"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mkdir</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-p</span> bench_results</span>
<span id="cb11-13"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cargo</span> run <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--release</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-p</span> abng-srg-experiments <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> bench_results/abng_srg.jsonl</span>
<span id="cb11-14"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cargo</span> run <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--release</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-p</span> abng-micro          <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> bench_results/abng_micro.jsonl</span>
<span id="cb11-15"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cargo</span> run <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--release</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-p</span> abng-pinn-scale     <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> bench_results/abng_pinn_scale.jsonl</span>
<span id="cb11-16"></span>
<span id="cb11-17"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 4. Generate figures</span></span>
<span id="cb11-18"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">cd</span> bench_results <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">&amp;&amp;</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">python</span> plot_all.py</span></code></pre></div>
</section>
<section id="what-you-should-expect-to-see" class="level3">
<h3 class="anchored" data-anchor-id="what-you-should-expect-to-see">What you should expect to see</h3>
<ul>
<li>Experiment β: 2 rows of JSONL with <code>op = "beta_basis_check"</code> (one for in-span, one for out-of-span).</li>
<li>Experiment ε: 5 rows with <code>op = "epsilon_decide_step"</code> showing the n_active scaling table.</li>
<li>Experiment θ: 16 rows with <code>op = "theta_routing_byte"</code> or <code>op = "theta_routing_leaf"</code> (8 each).</li>
<li>Experiment ι: 2500 rows with <code>op = "iota_2d_ood_grid"</code> (50×50 = 2500 grid cells).</li>
</ul>
<p>If your re-run produces numbers within ±5% of the values reported here, the underlying determinism contract is functioning correctly. The discrete routing in Experiment θ should produce <em>byte-identical</em> counts to this article (because routing is fully deterministic given the input distribution and seed); any divergence indicates a serious determinism regression.</p>
</section>
</section>
<section id="why-this-matters" class="level2">
<h2 class="anchored" data-anchor-id="why-this-matters">12. Why this matters</h2>
<p>Three concrete use cases where Part II’s properties pay off.</p>
<section id="online-learning" class="level3">
<h3 class="anchored" data-anchor-id="online-learning">Online learning</h3>
<p>The “graph adapts itself” claim has practical force when the data is streaming. Frozen leaves auto-unfreeze on drift; redundant leaves merge; over-extended ones split. No retraining loop, no hyperparameter sweep over network depth — the structural decisions are <em>part of the architecture</em>. For a system that needs to keep learning after deployment (recommendation, anomaly detection, real-time monitoring), this is a meaningful capability.</p>
<p><em>What this doesn’t claim</em>: that ABNG is <strong>deployment-ready</strong> for online learning. The structural-decision thresholds require tuning; the drift detection has only been verified on toy scenarios; the recovery story (Freeze → drift → Unfreeze → recovery) is verified piece-by-piece, not end-to-end on real data.</p>
</section>
<section id="interpretable-routing" class="level3">
<h3 class="anchored" data-anchor-id="interpretable-routing">Interpretable routing</h3>
<p>ABNG’s codebook-and-trie routing makes “which expert handled this input?” a trivial question. You read it off the route key — one byte per dimension. Compared to MoE routers, where the gating decision is a learned floating-point distribution over the whole expert population (and you need attribution methods to even visualize which expert dominated), this is <em>substantially</em> more interpretable for debugging and capacity planning.</p>
<p><em>What this doesn’t claim</em>: that interpretability of routing implies interpretability of the <em>model’s function</em>. The per-leaf MLP body is still a small black-box network. ABNG provides <em>routing interpretability</em> and <em>audit-level explainability</em> (Part I §4.5); it does not provide parameter-level interpretability of the function each leaf implements.</p>
</section>
<section id="adaptive-computation" class="level3">
<h3 class="anchored" data-anchor-id="adaptive-computation">Adaptive computation</h3>
<p>ABNG joins a small family of architectures where the compute graph isn’t fixed at design time: Adaptive Computation Time, Pondernet, Universal Transformers. ABNG’s distinction is that adaptation happens at the <em>training-graph</em> level (structural decisions over many observations), not at the <em>inference</em> level (per-token compute).</p>
<p><em>The trade-off</em>. Inference-level adaptive compute (ACT, Pondernet) saves wall-clock on easy inputs; the graph itself is fixed. Training-level adaptive compute (ABNG) changes the graph itself; inference cost is bounded by the current graph’s depth (sub-microsecond per descent) but training cost includes the structural-decision overhead. Different architectures serve different workloads.</p>
</section>
</section>
<section id="conclusion" class="level2">
<h2 class="anchored" data-anchor-id="conclusion">13. Conclusion</h2>
<p>This article walked through Part II of the ABNG benchmark deep-dive: four categories where the architecture is <em>most</em> distinctive — the parts that aren’t fixed at design time.</p>
<p><strong>What’s now established empirically (at the tested scale, on the tested hardware):</strong></p>
<ul>
<li>PINN out-of-span doesn’t degrade as expected — the piecewise routing partition provides effective basis-augmentation (Experiment β). The previous version of this article’s “ABNG works on PDEs because basis spans truth” was a <em>partial</em> explanation; the routing partition is the missing piece.</li>
<li><code>decide_step</code> is sub-millisecond at n_active ≤ 256 with roughly linear scaling (Experiment ε).</li>
<li>Routing distribution matches input distribution; 10K observations across 8 codebook bins produce a readable Sankey (Experiment θ).</li>
<li>2-D OOD has clear spatial structure; the four-panel decomposition shows composite + density + prefix-distance + epistemic-leverage all contributing different signals (Experiment ι).</li>
<li>All five trigger-specific demos verify their named action fires correctly under quality-gated conditions (§8.2).</li>
<li>Drift detection mechanism verified on controlled mean-shift scenarios (§8.3).</li>
<li>Maturity + NodeSignature track per-leaf state evolution over training (§8.4).</li>
</ul>
<p><strong>What remains speculative across both parts:</strong></p>
<ul>
<li>Whether ABNG would compete with sklearn-GP, FNO, deep SVDD, ADWIN, or Mondrian Forests on their home benchmarks — no head-to-head measurement exists.</li>
<li>Whether the architecture scales to 10⁶+ rows with multi-depth graphs — measured to 10⁵ rows / 10⁶ events at depth-1.</li>
<li>Whether the determinism contract holds cross-platform — only Windows MSVC tested.</li>
<li>Whether the “three-or-more-advantages-simultaneously” hypothesis from <a href="../../../blog/posts/abng-architecture/index.html">Part 1 of the trilogy</a> holds anywhere empirically.</li>
</ul>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
The honest summary
</div>
</div>
<div class="callout-body-container callout-body">
<p><strong>ABNG demonstrates promising properties for deterministic, inspectable, graph-based probabilistic computation</strong> — and Part II’s results show those properties extend to the architecturally-distinctive half of the system: adaptive structural decisions, interpretable routing, drift response. At the tested scale, on the tested hardware, the four experiments and the trigger-specific demos all pass.</p>
<p>The architecture is <strong>not validated science.</strong> It is exploratory engineering with strong correctness signals at small scale, on a specific hardware target, with several axes of comparison to established systems unmeasured. The five future-work items in §10 are each weeks-to-months of work.</p>
<p>If you’ve read both Part I and Part II, you have the empirical state of ABNG as of Phase 0.7 — what’s proven, what’s demonstrated, what’s reasonable extrapolation, what’s speculative. The next decisions belong to readers who want to run the experiments §10 lists.</p>
</div>
</div>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>ABNG lives at <a href="https://github.com/AdamEzzat1/CJC"><code>crates/cjc-abng/</code></a> in the CJC-Lang repository. All benchmark code is in <code>bench/abng_*/</code>. The plotting script is at <a href="https://github.com/AdamEzzat1/CJC/blob/main/bench_results/plot_all.py"><code>bench_results/plot_all.py</code></a>.</p>
<p>This was article 4 of a 4-article ABNG series. Article 1 covers the architecture; Article 2 covers the determinism and audit story; Article 3 (Part I of the benchmarks) covers the categories where ABNG can be proven correct at the tested scale; this article (Part II) covered adaptive graph evolution, routing, and temporal dynamics.</p>
</div>
</div>


</section>

 ]]></description>
  <category>CJC-Lang</category>
  <category>ABNG</category>
  <category>benchmarks</category>
  <category>adaptive</category>
  <category>routing</category>
  <category>temporal</category>
  <category>drift</category>
  <guid>https://adamezzat1.github.io/blog/posts/abng-benchmarks-part-2/index.html</guid>
  <pubDate>Sun, 10 May 2026 07:00:00 GMT</pubDate>
</item>
<item>
  <title>Deterministic and Auditable Neural Systems</title>
  <dc:creator>Adam Ezzat</dc:creator>
  <link>https://adamezzat1.github.io/blog/posts/abng-deterministic-systems/index.html</link>
  <description><![CDATA[ 



<div class="reading-time">
🕑 ~38 min read
</div>
<div class="callout callout-style-simple callout-tip no-icon callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-title-container flex-fill">
TL;DR
</div>
</div>
<div class="callout-body-container callout-body">
<p>Most ML training is <em>not</em> replayable bit-for-bit. Two runs of “the same” code on “the same” data produce two different artifacts, and reconstructing <em>why</em> a specific model produced a specific output is essentially “trust the lab’s bookkeeping.” This article walks through ABNG’s audit chain mechanism — SHA-256 over every state mutation, replayable from a serialized log, three-signal spoof detection — and is honest about what the determinism premium <em>costs</em>: ~87% of the per-training-step time is dispatch + audit overhead, not Bayesian math. The audit-chain primitive itself is well-understood (Merkle, 1979; git, 2005), but applying it at training-step granularity is the unusual choice worth examining.</p>
</div>
</div>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Two prototype disclaimers
</div>
</div>
<div class="callout-body-container callout-body">
<p><strong>ABNG is a research-stage prototype.</strong> It is built on top of <strong>CJC-Lang</strong>, a research compiler that is itself pre-1.0. Both are under active development. The audit-chain mechanism works as described; the broader claim that “this is suitable for regulated-ML deployment” is currently <em>unvalidated by any real deployment</em>. The “regulator’s nightmare scenario” walkthrough in this article operates on a 64-row synthetic dataset, not a real clinical-trial corpus.</p>
</div>
</div>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
This is Part 2 of a three-part series
</div>
</div>
<div class="callout-body-container callout-body">
<ul>
<li><strong>Part 1 (<a href="../../../blog/posts/abng-architecture/index.html">ABNG: Treating Belief States as First-Class Citizens</a>):</strong> the architecture itself.</li>
<li><strong>Part 2 (this article):</strong> the SHA-256 audit chain, replay semantics, the determinism contract.</li>
<li><strong>Part 3 (<a href="../../../blog/posts/abng-benchmarks/index.html">Benchmarking Experimental Bayesian Graph Networks</a>):</strong> the demo catalog, scaling benchmarks, and honest limitations.</li>
</ul>
<p>This article can be read alone if your primary interest is the determinism / auditability story. Part 1 covers the underlying architecture; Part 3 covers what the benchmarks actually measure.</p>
</div>
</div>
<section id="the-reproducibility-crisis-in-ml-one-layer-deep" class="level2">
<h2 class="anchored" data-anchor-id="the-reproducibility-crisis-in-ml-one-layer-deep">The reproducibility crisis in ML, one layer deep</h2>
<p>Reproducibility in ML is a misleadingly familiar phrase. The version most practitioners encounter — “publish your code and your data so others can rerun your experiment” — addresses <em>artefact</em> reproducibility. Same code, same data, hopefully similar results.</p>
<p>A deeper version of the question is rarely asked: <strong>given the trained model artefact you have today, can you reconstruct, bit-for-bit, the training history that produced it?</strong></p>
<p>For the vast majority of ML systems, the answer is no. Even with code and data pinned to specific commits, retraining produces <em>similar</em> but not <em>identical</em> artefacts. Non-determinism creeps in through floating-point reduction orderings (different cuDNN versions, different GPU drivers), parallel scheduling (the OS decides which threads finish first), stochastic optimizers seeded loosely (NumPy RNG state at the moment of training start), and a long list of small things that don’t matter individually but compound across millions of operations.</p>
<p>For most use cases this is tolerable. A model that achieves 78.3% accuracy in one training run and 78.4% in another is <em>practically</em> the same model. No one debugs a production recommender system by asking “what was the exact gradient at step 1,247,891?”</p>
<p>For some use cases it’s not tolerable at all.</p>
<p><strong>Scientific replication.</strong> If a published model is the <em>evidence</em> for a scientific claim — a protein-structure prediction, a climate forecast, a clinical-trial outcome — then “another team will get a similar model when they rerun the code” is not the same as “another team will get the same model.” Statistical claims that depend on the model are conditioning on a specific artefact; they need that specific artefact reconstructible.</p>
<p><strong>Regulated ML.</strong> When a model decision must be defended to a regulator (FDA, EMA, financial auditor) the question is not “does the code work?” but “was this specific output produced by a specific model trained on a specific dataset, and can you prove no tampering occurred?” Trust-the-lab’s-bookkeeping doesn’t survive contact with adversarial review.</p>
<p><strong>Postmortem debugging.</strong> When a deployed model produces a strange output, the question “what would I change to never have that output again?” requires reconstructing the specific training trajectory that produced the model’s current weights. With non-deterministic training, you can’t even reliably reproduce the bug.</p>
<p>These three classes of problem share a structural property: the <em>exact</em> training history matters, not just the final accuracy number. ABNG is built around this property. The rest of this article walks through how, what it costs, and where existing tools fall short of providing the same guarantee.</p>
</section>
<section id="what-auditable-actually-has-to-mean" class="level2">
<h2 class="anchored" data-anchor-id="what-auditable-actually-has-to-mean">What “auditable” actually has to mean</h2>
<p>Before getting into the mechanism, it’s worth being precise about the word. “Audit trail” gets used loosely in the ML lineage tooling space to mean many incompatible things:</p>
<p><strong>Level 1 — Artefact tracking.</strong> “I stored a copy of the trained model and a copy of the training data in S3, with metadata about which produced which.” This is what MLflow, DVC, Weights &amp; Biases, and similar tools provide. The audit unit is the <em>artefact</em>: dataset → trained model. The granularity is <em>coarse</em> — one entry per training run.</p>
<p><strong>Level 2 — Event logging.</strong> “I logged every API call, every commit, every parameter change to an append-only stream.” This is what most production systems do for operations metadata. The audit unit is the <em>event</em>: every meaningful action in the pipeline. The granularity is <em>fine</em> but the events are typically just textual descriptions, not cryptographic commitments to the underlying state.</p>
<p><strong>Level 3 — State-mutation chain with cryptographic commitments.</strong> “Every change to the model’s internal state is appended to a hash-chained log, and any future replay can verify the log was not tampered with after the fact.” The audit unit is the <em>state mutation</em>: every parameter update has its own hash witness. The granularity is <em>the finest possible</em>. This is what ABNG provides.</p>
<p><strong>Level 4 — Tamper-proof, not tamper-evident.</strong> A theoretical level that nothing in mainstream ML provides. Even Level 3 only detects tampering after the fact; it cannot prevent an attacker who controls the entire pipeline from producing a self-consistent malicious log. True tamper-proofing would require external attestation (TPM, secure enclaves, blockchain-style consensus) that ABNG does not implement.</p>
<p>ABNG is unambiguously a Level 3 system. The right mental model is <strong>a git commit history for a model</strong>:</p>
<ul>
<li>Every state change is a commit.</li>
<li>Each commit has a cryptographic hash that depends on the previous commit.</li>
<li>The history is complete (every change is in there) and verifiable (you can re-derive any past state).</li>
<li>It is not however a defense against an attacker who controls the repo from the start.</li>
</ul>
<p>The distinction between Level 3 (tamper-evident) and Level 4 (tamper-proof) matters because it shapes what ABNG can and cannot claim. ABNG can credibly say “the audit log shows the training history; if anyone modified the log or the snapshot after the fact, replay will detect it.” ABNG cannot credibly say “this proves the lab was honest.” Those are different claims.</p>
</section>
<section id="the-sha-256-audit-chain-mechanism" class="level2">
<h2 class="anchored" data-anchor-id="the-sha-256-audit-chain-mechanism">The SHA-256 audit chain mechanism</h2>
<p>Every operation that mutates state in an ABNG graph appends one event to an in-memory audit log. Each event carries a kind tag, a payload, and a <code>new_hash</code> computed by SHA-256-chaining over the previous event’s hash:</p>
<div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1">new_hash <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sha256(prev_hash <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">||</span> canonical_bytes(payload))</span></code></pre></div>
<p>Thirty audit kinds are defined (tag bytes <code>0x00..0x1D</code>), covering everything from <code>Created</code> (graph genesis, tag <code>0x00</code>) and <code>NodeAdded</code> (<code>0x02</code>) through <code>BlrUpdated</code> (<code>0x0A</code>), <code>CalibrationUpdated</code> (<code>0x0E</code>), <code>Split</code> (<code>0x11</code>), <code>Merge</code> (<code>0x12</code>), <code>Unfreeze</code> (<code>0x16</code>), down to opt-in <code>StatsSnapshot</code> (log-compaction marker, <code>0x1A</code>), <code>Routed</code> (per-descent trace, <code>0x1B</code>), <code>ProvenanceStamped</code> (per-node lineage commitment, <code>0x1C</code>), and <code>BeliefUpdateBatch</code> (<code>0x1D</code>).</p>
<p>The kind list is <em>frozen</em> — future ABNG phases must allocate new tags rather than reuse existing ones. This is a deliberate forward-compatibility guarantee: a snapshot from Phase 0.4 (when the chain had 28 audit kinds) can still be read by a Phase 0.7 binary (with 30 audit kinds) because the original tag semantics never change.</p>
<pre class="mermaid"><code>flowchart LR
    G["Genesis hash&lt;br/&gt;sha256(b'ABNG-GENESIS-v1')"] --&gt; E0["Event 0&lt;br/&gt;Created"]
    E0 --&gt; E1["Event 1&lt;br/&gt;NodeAdded"]
    E1 --&gt; E2["Event 2&lt;br/&gt;BlrInitialized"]
    E2 --&gt; E3["Event 3&lt;br/&gt;BlrUpdated&lt;br/&gt;(witness hash)"]
    E3 --&gt; E4["Event 4&lt;br/&gt;CalibrationUpdated&lt;br/&gt;(witness hash)"]
    E4 --&gt; E5["Event 5&lt;br/&gt;Split"]
    E5 --&gt; EDOTS["..."]
    EDOTS --&gt; EN["Event N&lt;br/&gt;chain_head"]</code></pre>
<p>The genesis hash is the fixed constant <code>sha256(b"ABNG-GENESIS-v1")</code>, exposed in the public API. Every graph starts with the same genesis hash; the first event (<code>Created</code>) is hashed against it. The chain head is the most recent <code>new_hash</code> — a single 32-byte commitment to the entire training history.</p>
<section id="what-each-event-commits-to" class="level3">
<h3 class="anchored" data-anchor-id="what-each-event-commits-to">What each event commits to</h3>
<p>There are three classes of audit event, differing in payload size and replay semantics:</p>
<p><strong>Structural events</strong> carry the full payload because replay needs them to reconstruct the graph. Examples: <code>Created</code>, <code>NodeAdded { parent, key_byte }</code>, <code>Grow { parent, key_byte, child }</code>, <code>Split { parent, child_a, child_b }</code>, <code>Merge { absorbed, into }</code>, <code>CodebookFrozen { codebook_hash }</code>. These events <em>drive</em> graph reconstruction during replay.</p>
<p><strong>Witness-only events</strong> carry just a 32-byte state hash, not the actual data. Examples: <code>BlrUpdated { state_hash }</code>, <code>LeafParamsUpdated { params_hash }</code>, <code>DensityUpdated { state_hash }</code>, <code>CalibrationUpdated { state_hash }</code>. The actual per-node data lives in the snapshot’s per-node section, not in the event payload. The audit event acts as a <em>checkpoint</em> — replay re-applies the per-node bytes from the snapshot and verifies the hash matches.</p>
<p><strong>Trace events</strong> are opt-in and carry minimal context. The most important one is <code>Routed { leaf, matched_prefix }</code> (tag <code>0x1B</code>), emitted by <code>descend_traced()</code> when route tracing is enabled. By default routing produces no audit event — turning on Routed at high observation rates can dwarf the model’s training data, so it’s reserved for explainability use cases.</p>
</section>
<section id="event-sizes-in-practice" class="level3">
<h3 class="anchored" data-anchor-id="event-sizes-in-practice">Event sizes in practice</h3>
<p>The vast majority of events under heavy training are witness-only (<code>BlrUpdated</code>, <code>DensityUpdated</code>, <code>CalibrationUpdated</code>). Each costs roughly 80 bytes on the wire (8 byte tag + 8 byte node id + 32 byte state hash + 8 byte seq + 24 byte boilerplate including the SHA-256 chain advance). At four updates per observation (one BLR, one density, one calibration, one signature update), a million-row training run produces ~320 MB of audit log — fits on disk, painful in memory.</p>
<p>This is where log compaction enters. The <code>StatsSnapshot</code> event (tag <code>0x1A</code>, shipped in Phase 0.4 Track A) is a <em>marker</em> placed periodically into the chain. It captures a hash of the full per-node state at the marker point. The <code>smart_replay</code> API can then <em>fast-forward</em> past long runs of <code>*Updated</code> events by recovering state from the marker. This is correctness-preserving: smart-replay produces byte-identical state to naive replay, but skips the per-event hash recomputation. The speedup measured in Part 3 is 2.08× at 10K events — less than the originally-predicted ≥5×, but a meaningful improvement.</p>
</section>
</section>
<section id="a-worked-example-detecting-a-tampered-training-dataset" class="level2">
<h2 class="anchored" data-anchor-id="a-worked-example-detecting-a-tampered-training-dataset">A worked example: detecting a tampered training dataset</h2>
<p>This is the strongest demo in the codebase and deserves a careful walkthrough.</p>
<p><strong>Scenario.</strong> A clinical research lab trains an ABNG model on a regulatory dataset and submits both the model snapshot and a few prediction snapshots to a regulator. An attacker (or insider, or simply a buggy upstream pipeline) substitutes a different model trained on a <em>tampered</em> dataset and tries to pass the original lab’s predictions off as still valid. Can the regulator detect the swap?</p>
<p><strong>Setup.</strong> - Dataset A: 64 synthetic clinical-trial rows <code>(patient_id, dose, response)</code> with <code>response = 0.2 + 0.6·dose + 0.1·dose²</code> — a simple polynomial dose-response curve. - Dataset B: identical to A <em>except</em> patient 17’s response is rewritten — exactly one row tampered, simulating data fraud. - Two ABNG graphs: <code>g_A</code> trained on dataset A, <code>g_B</code> trained on dataset B.</p>
<p>Both graphs have identical architecture: 1-D codebook over <code>dose</code> with 4 bins, 4 children (one per bin), each with its own BLR head over <code>[1, dose, dose², dose³]</code>. Same seed, same training schedule. The <em>only</em> difference between <code>g_A</code> and <code>g_B</code> is dataset content.</p>
<pre class="mermaid"><code>flowchart TD
    DS_A["Dataset A&lt;br/&gt;64 rows (legitimate)"] --&gt; H_A["sha256(A) = stamp_A"]
    DS_B["Dataset B&lt;br/&gt;row 17 tampered"] --&gt; H_B["sha256(B) = stamp_B"]
    H_A --&gt; G_A["Train g_A&lt;br/&gt;chain_head_A&lt;br/&gt;blr_hash_A[*]"]
    H_B --&gt; G_B["Train g_B&lt;br/&gt;chain_head_B&lt;br/&gt;blr_hash_B[*]"]
    G_A --&gt; V_A["lab submits&lt;br/&gt;g_A.snap"]
    G_B --&gt; ATK["attacker&lt;br/&gt;swaps to g_B.snap"]
    ATK --&gt; CHECK{"regulator verifies"}
    V_A --&gt; CHECK
    CHECK --&gt; F["FAILS on:&lt;br/&gt;1. chain_head_A ≠ chain_head_B&lt;br/&gt;2. blr_hash mismatch on row 17 leaf&lt;br/&gt;3. stamp_A ≠ stamp_B"]
    style F fill:#fcc</code></pre>
<p><strong>Three independent failure signals.</strong> The test <code>tests/test_abng_lineage_attestation.rs::three_signal_spoof_detection</code> asserts that the attempted swap fails <em>all three</em> signals simultaneously, and that each signal would fire even if the other two were somehow defeated:</p>
<table class="table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Signal</th>
<th>What it commits to</th>
<th>How the swap fails</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>chain_head</code> (32 byte SHA-256)</td>
<td>The entire training history (every observation, every BLR update, every audit event)</td>
<td><code>g_A.chain_head ≠ g_B.chain_head</code> because the BlrUpdated event for the leaf containing patient 17 differs</td>
</tr>
<tr class="even">
<td>Per-leaf <code>BLR state_hash</code></td>
<td>The leaf’s posterior <code>(m, Λ, a, b)</code></td>
<td><code>g_A.leaves[k].blr.state_hash() ≠ g_B.leaves[k].blr.state_hash()</code> for the affected <code>k</code></td>
</tr>
<tr class="odd">
<td>Root <code>provenance_stamp_hash</code></td>
<td><code>sha256(dataset_bytes)</code> — the regulator-chosen 32-byte fingerprint of the training data</td>
<td><code>sha256(A_bytes) ≠ sha256(B_bytes)</code> because patient 17 differs</td>
</tr>
</tbody>
</table>
<p>The three signals are <em>independent</em>. The first depends on the entire chain of audit events. The second depends only on the affected leaf’s posterior state. The third depends only on the dataset itself. An attacker would need to simultaneously forge all three — which requires producing a SHA-256 collision (computationally infeasible by current knowledge), forging a Bayesian posterior state to match a specific hash (same), and forging the original dataset bytes (same).</p>
<p><strong>What this proves.</strong> Tamper detection at the <em>training-data-row</em> level. A single-row dataset change produces detectable signal at three independent integrity checkpoints. This is the cleanest adversarial test in the codebase — the only one that constructs a “what would an attacker do” scenario rather than a positive-control determinism check.</p>
<p><strong>What this does not prove.</strong> The dataset is 64 rows. Real regulated-ML datasets are many orders of magnitude larger. The mechanism <em>should</em> scale linearly (each additional row adds ~300 bytes to the audit log per training step), but the wall-clock cost of verifying a 10⁶-row training history’s audit chain — Part 3 measured ~1.6 seconds at 10⁵ rows — would extrapolate to ~16 seconds at 10⁶, ~160 seconds at 10⁷. Tolerable for offline regulator review; not tolerable as a real-time check.</p>
<p><strong>The “git commit history for a model” framing concretely.</strong> In git, <code>git log</code> shows every commit. Each commit has a SHA-1 hash that depends on the parent. Modifying any past commit invalidates all later commit hashes. The chain is verifiable end-to-end. ABNG works the same way at training-step granularity: every parameter update is one “commit,” the chain head is the “HEAD,” and you can <code>git show &lt;hash&gt;</code> to inspect any past state. The analogy is exact at the structural level; the difference is what’s being committed (model state mutations, not source code edits) and the hash function (SHA-256, not SHA-1).</p>
</section>
<section id="replay-semantics" class="level2">
<h2 class="anchored" data-anchor-id="replay-semantics">Replay semantics</h2>
<p>Given a serialized snapshot, the <code>abng_replay</code> function reconstructs the graph from scratch:</p>
<ol type="1">
<li><p><strong>Parse the snapshot header.</strong> Magic bytes <code>b"ABNG\x0D"</code> (version <code>\x0D</code> = v13 as of Phase 0.7), genesis hash check, configuration metadata (codebook, leaf head architecture, BLR prior, decision policy).</p></li>
<li><p><strong>Replay the audit log.</strong> Walk every event in sequence. For each event:</p>
<ul>
<li>Recompute <code>new_hash = sha256(running_hash || canonical_bytes(event))</code>.</li>
<li>Compare against the stored <code>new_hash</code>. Mismatch → <code>DecodeError::ChainMismatch { at_seq }</code>.</li>
<li>Apply the event’s semantic effect: append a node, update a posterior, fire a structural action, etc.</li>
</ul></li>
<li><p><strong>Verify per-node state.</strong> After replaying all events, the per-node section of the snapshot is checked against the most recent <code>*Initialized</code> / <code>*Updated</code> witness for each node. A node whose final state hash doesn’t match the stored bytes → <code>DecodeError::BlrStateHashMismatch</code>, <code>LeafParamsHashMismatch</code>, etc.</p></li>
<li><p><strong>Verify the final chain head.</strong> The end-of-log <code>chain_head</code> from the replayed events must match the stored <code>final_hash</code>. Mismatch → <code>DecodeError::FinalHashMismatch</code>.</p></li>
</ol>
<p>Replay is <strong>byte-identical</strong> by construction: same seed (for any RNG draws during replay), same audit log, same per-node state → same reconstructed graph, byte-for-byte. Part 3 verifies this empirically (<code>pinn_replay_round_trip_preserves_predictions</code> asserts <code>f64::to_bits</code> equality on predictions before and after replay).</p>
<section id="smart-replay" class="level3">
<h3 class="anchored" data-anchor-id="smart-replay">Smart replay</h3>
<p>For long training runs with many witness-only events (<code>BlrUpdated</code>, <code>DensityUpdated</code>, <code>CalibrationUpdated</code>), naive replay does redundant work — every event triggers a fresh hash chain advance. <strong>Smart replay</strong> uses <code>StatsSnapshot</code> markers to fast-forward past these runs:</p>
<ul>
<li>Replay walks the log until hitting a <code>StatsSnapshot</code> event.</li>
<li>The marker carries a hash of the per-node state at that point. Replay loads that state directly from the snapshot section, skipping all the <code>*Updated</code> events that <em>would have</em> produced it.</li>
<li>The chain head still advances correctly because the marker itself is part of the chain.</li>
</ul>
<p>The Phase 3 benchmark measured smart-replay at 2.08× faster than naive at 10K events. The Phase 0.6 handoff predicted ≥5× — the gap is plausibly because the test was at moderate event count and smart-replay’s overhead per <code>StatsSnapshot</code> is comparable to naive replay’s overhead per event at this scale. The speedup grows with more <code>*Updated</code> events per <code>StatsSnapshot</code>; at production scale (10⁶+ events per training run), the gain should be much larger but is empirically untested.</p>
</section>
<section id="what-replay-does-not-validate" class="level3">
<h3 class="anchored" data-anchor-id="what-replay-does-not-validate">What replay does not validate</h3>
<p>Replay validates <em>the audit log is internally consistent and matches the per-node state</em>. It does not validate:</p>
<ul>
<li><strong>That the training data was correct.</strong> If the dataset was tampered before training, the audit log records the tampered training faithfully. The lineage-attestation demo addresses this gap via the <code>ProvenanceStamped</code> event tying the root to <code>sha256(dataset_bytes)</code>, but only if the dataset hash is committed before training.</li>
<li><strong>That the architecture is sound.</strong> Replay re-derives the model state but doesn’t second-guess the architecture’s correctness.</li>
<li><strong>That the predictions are good.</strong> Replay produces the same model; a bad model trained reproducibly is still a bad model.</li>
</ul>
<p>These limits are intrinsic to any Level 3 audit system. Tamper-evidence is about catching changes to a known-good baseline, not about establishing the baseline.</p>
</section>
</section>
<section id="the-determinism-contract" class="level2">
<h2 class="anchored" data-anchor-id="the-determinism-contract">The determinism contract</h2>
<p>Replayability requires bit-identical determinism, and bit-identical determinism does not come for free. ABNG inherits a specific contract from CJC-Lang:</p>
<p><strong>Summation.</strong> All f64 accumulations use <code>KahanAccumulatorF64</code> or <code>pairwise_sum_f64</code> from the <code>cjc-repro</code> crate. Plain <code>+=</code> reductions are banned in canonical paths. This adds ~2× the FLOPs of naive summation but compensates for the order-dependent error that breaks bit-identical reproducibility under multi-threading.</p>
<p><strong>Hashing.</strong> SHA-256 from <code>cjc_snap::hash::sha256</code> — a hand-rolled FIPS 180-4 implementation. Zero external dependencies (no <code>sha2</code>, <code>ring</code>, <code>openssl</code>). The hand-roll cost is one-time engineering; the benefit is that ABNG’s audit chain has no third-party security boundary to monitor.</p>
<p><strong>Random numbers.</strong> <code>cjc_repro::Rng</code> is a SplitMix64 PRNG seeded explicitly from <code>(graph.seed, node_id, layer_idx, kind_bit)</code> derivations. No hardware RNG, no global PRNG state, no implicit thread-local generators. Every random draw is reproducible from the seed.</p>
<p><strong>Maps and sets.</strong> <code>BTreeMap</code> and <code>BTreeSet</code> only. <code>HashMap</code> is banned in canonical paths because its iteration order is randomized per-process (<code>std::collections::HashMap</code> uses random hash seeds for DoS protection, which is good for security and bad for reproducibility). BTreeMap is 2-5× slower than HashMap on most lookups, but its iteration order is deterministic.</p>
<p><strong>Float canonical bytes.</strong> Whenever an f64 needs to be serialized or hashed, it goes through <code>f64::to_bits().to_be_bytes()</code> — preserves NaN bit patterns and is platform-stable. No platform-specific endianness, no signaling-NaN coercion.</p>
<p><strong>No FMA in belief-touching kernels.</strong> Fused multiply-add can give slightly different bits than <code>a*b + c</code> on some hardware. ABNG explicitly disables FMA for the BLR Cholesky factorization, the Welford updates, and the calibration ECE accumulation. The cost is ~10–30% on modern CPUs depending on workload; the benefit is bit-identical results across all CPUs that implement IEEE 754 correctly.</p>
<section id="why-this-rules-out-gpu-acceleration" class="level3">
<h3 class="anchored" data-anchor-id="why-this-rules-out-gpu-acceleration">Why this rules out GPU acceleration</h3>
<p>The CUDA / cuDNN / FlashAttention ecosystem provides extraordinary throughput, but at the cost of bit-stability. Vendor BLAS uses different reduction orderings depending on hardware (Volta vs Ampere vs Hopper), batch size, and even the cuDNN minor version. Two runs on the same GPU but different driver versions can produce slightly different gradients. For ML accuracy this is acceptable; for ABNG’s audit-chain guarantee it isn’t.</p>
<p>A “fast mode” that relaxes determinism could be added — similar to PyTorch’s <code>torch.use_deterministic_algorithms(False)</code> — but it would defeat the audit-chain claim. Bit-identical replay would no longer hold. The architecture is intentionally designed not to chase modern hardware. This is the single biggest practical limitation: users who need GPU throughput cannot use ABNG; users who need replayability cannot use GPUs.</p>
</section>
</section>
<section id="what-this-costs-the-determinism-premium-quantified" class="level2">
<h2 class="anchored" data-anchor-id="what-this-costs-the-determinism-premium-quantified">What this costs: the determinism premium quantified</h2>
<p>The Phase 3 benchmark on the same hardware (Intel i7-11390H @ 3.40GHz, Windows MSVC release build) revealed a striking number:</p>
<ul>
<li><code>blr_state_update_direct</code> (just the NIG conjugate update, no graph dispatch or audit): <strong>3,845 ns per step</strong></li>
<li><code>blr_update</code> (the same NIG update through the graph, with audit-chain append): <strong>28,997 ns per step</strong></li>
</ul>
<p>The graph dispatch + audit + chain-hash scaffolding accounts for <strong>87% of the per-step cost</strong>. The actual Bayesian update is 13%. This is the most defensible answer to “what does determinism cost” in the codebase.</p>
<pre><code>[Bayesian math]    ████ 3.85 µs (13%)
[Determinism premium] ████████████████████████████████ 25.15 µs (87%)
Total blr_update:  ████████████████████████████████████ 29 µs</code></pre>
<p>The premium breaks down (approximately) as: - ~5 µs: route encode + descend (codebook lookup + tree walk). - ~3 µs: SHA-256 chain advance (one event hash). - ~4 µs: BLR state hash computation (witness for the audit event). - ~5 µs: audit-event allocation + canonical-bytes serialization. - ~3 µs: per-node stats chain advance (separate hash chain per node). - ~5 µs: dispatch overhead + miscellaneous bookkeeping.</p>
<p>These numbers are approximate (the individual sub-operations aren’t separately benched), but the <em>total</em> gap between direct and graph-mediated update is confidently measured.</p>
<p><strong>Phase 0.7 spent six commits chipping away at this premium</strong>:</p>
<table class="table">
<colgroup>
<col style="width: 21%">
<col style="width: 21%">
<col style="width: 28%">
<col style="width: 28%">
</colgroup>
<thead>
<tr class="header">
<th>Item</th>
<th>Target</th>
<th style="text-align: right;">Measured speedup</th>
<th style="text-align: right;">Handoff doc claim</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Item B</td>
<td><code>QuantileCodebook::encode_into</code> (buffer reuse)</td>
<td style="text-align: right;"><strong>1.67×</strong></td>
<td style="text-align: right;">“2.37×” — doesn’t match this hardware</td>
</tr>
<tr class="even">
<td>Item A</td>
<td><code>AuditEvent::write_payload</code> (chain verify)</td>
<td style="text-align: right;">(≈1.25× implicit)</td>
<td style="text-align: right;">“1.25×” — matches</td>
</tr>
<tr class="odd">
<td>Item C</td>
<td>streaming SHA-256 hasher</td>
<td style="text-align: right;">(≈1.18× implicit)</td>
<td style="text-align: right;">“1.18×” — matches</td>
</tr>
<tr class="even">
<td>Item F</td>
<td>stack-array <code>advance_stats_chain</code></td>
<td style="text-align: right;">(≈1.24× implicit)</td>
<td style="text-align: right;">“1.24×” — matches</td>
</tr>
<tr class="odd">
<td>Item E</td>
<td><code>AdaptiveChildren::iter_sorted</code> (zero-alloc)</td>
<td style="text-align: right;">(zero-alloc, not benched)</td>
<td style="text-align: right;">—</td>
</tr>
<tr class="even">
<td>Item 4</td>
<td>fused <code>abng_train_step</code> builtin</td>
<td style="text-align: right;"><strong>1.03×</strong></td>
<td style="text-align: right;">“1.17×” — doesn’t match this hardware</td>
</tr>
</tbody>
</table>
<p>Two of the six measured speedups on this machine are meaningfully smaller than the handoff doc reports. Plausible explanations are different CPU generations, build flag differences, or warmup variation — see Part 3 for the deeper discussion. The honest takeaway: <strong>speedup numbers should be quoted as ranges, not point estimates</strong>, because different hardware sees different wins.</p>
</section>
<section id="how-this-compares-to-mlflow-dvc-weights-biases" class="level2">
<h2 class="anchored" data-anchor-id="how-this-compares-to-mlflow-dvc-weights-biases">How this compares to MLflow / DVC / Weights &amp; Biases</h2>
<p>The mainstream ML lineage toolchain provides what I called “Level 1 — Artefact tracking” above. To make the comparison concrete:</p>
<table class="table">
<colgroup>
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
</colgroup>
<thead>
<tr class="header">
<th>Tool</th>
<th>Audit unit</th>
<th>Granularity</th>
<th>Cryptographic?</th>
<th>Replay?</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>MLflow</strong></td>
<td>Run (one training execution)</td>
<td>Coarse — one entry per training run</td>
<td>No — metadata is plain text</td>
<td>Re-run the code with logged parameters</td>
</tr>
<tr class="even">
<td><strong>DVC</strong></td>
<td>File-level artefact (dataset, model, intermediate)</td>
<td>Medium — one entry per pinned artefact</td>
<td>Cryptographic hashes of file contents</td>
<td>Re-derive via DAG of pinned files</td>
</tr>
<tr class="odd">
<td><strong>Weights &amp; Biases</strong></td>
<td>Training step (metrics)</td>
<td>Fine for metrics, coarse for state</td>
<td>No — metrics are plain</td>
<td>Re-run with logged config</td>
</tr>
<tr class="even">
<td><strong>ABNG audit chain</strong></td>
<td>State mutation</td>
<td>Finest possible — every parameter update</td>
<td>SHA-256 chain over all events</td>
<td><strong>Byte-identical replay from log</strong></td>
</tr>
</tbody>
</table>
<p>These tools serve different problems. MLflow/W&amp;B are <em>experiment tracking</em> — they help you compare runs, version experiments, and reproduce results approximately. DVC is <em>data and artefact pipeline</em> — it gives Git-like versioning to large binary files. None of them attempts byte-identical replay of training history.</p>
<p>ABNG’s audit chain is <em>closer to a database write-ahead log</em> than to any of these — a WAL captures every state mutation in order, and replay rebuilds state by replaying the WAL. The closest analogue in the broader software-engineering world is event-sourced architectures (Greg Young, Martin Fowler), where the canonical state is the event log and current state is a materialized view of the log.</p>
<p>The right question is not “should I use ABNG instead of MLflow?” but “for what subset of my ML pipeline do I need state-mutation-level granularity rather than artefact-level?” For most use cases the answer is “none” — MLflow’s level of granularity is plenty. For the three problem classes named earlier (scientific replication, regulated ML, postmortem debugging at finest granularity), the answer can be different.</p>
</section>
<section id="use-cases-where-the-cost-inverts" class="level2">
<h2 class="anchored" data-anchor-id="use-cases-where-the-cost-inverts">Use cases where the cost inverts</h2>
<p>Each of these is a domain where the determinism premium pays for itself.</p>
<section id="auditability-for-regulated-decisions" class="level3">
<h3 class="anchored" data-anchor-id="auditability-for-regulated-decisions">Auditability for regulated decisions</h3>
<p>When a model decision must be defended — to a regulator, a court, a clinician, or a postmortem — “why this output?” needs a paper trail. ABNG’s chain head is a single 32-byte commitment to the entire training history. Replayability is the strongest possible form of paper trail: you can re-derive the model from the audit log and verify nothing has been tampered with.</p>
<p><strong>Caveat.</strong> “Tamper-evident, not tamper-proof” still applies. An attacker who controls the entire training pipeline can produce a self-consistent malicious log. But against an opposing party who has only the snapshots (the lab claims one thing, the regulator suspects another), the chain produces a definitive third-party-verifiable answer.</p>
</section>
<section id="reproducible-scientific-simulation" class="level3">
<h3 class="anchored" data-anchor-id="reproducible-scientific-simulation">Reproducible scientific simulation</h3>
<p>Physics-informed networks, surrogate solvers, and inverse problems often live as a <em>step</em> inside a larger simulation pipeline. If the model is non-deterministic, the simulation downstream is non-deterministic. Bit-identical replay of the model means bit-identical replay of the pipeline. This matters for any scientific result where the model is part of the claim — climate ensemble runs, drug-discovery surrogates, ML-accelerated materials science.</p>
<p><strong>Caveat.</strong> Determinism does not make a simulation <strong>correct</strong>, only repeatable. A bug in either the model or the sampler is reproduced exactly the same way every time. The benefit is that the bug is debuggable; the benefit is not that there are no bugs.</p>
</section>
<section id="debugging-at-the-architectural-level" class="level3">
<h3 class="anchored" data-anchor-id="debugging-at-the-architectural-level">Debugging at the architectural level</h3>
<p>Anyone who has chased a non-determinism bug across GPUs knows the cost. A reproducible substrate does not make ML easier, but it makes the bugs you <em>do</em> hit actually debuggable, because failures are the same every time. ABNG’s <code>cjcl abng inspect --node N --audit</code> filters the log to a single node; <code>descend_traced</code> captures routing paths for individual predictions; <code>abng_predict_snap</code> captures prediction-time state. These don’t <em>automate</em> diagnosis but they make manual diagnosis tractable.</p>
<p><strong>Caveat.</strong> The audit log is a postmortem artefact, not a live debugging surface. Stepping through a 10⁶-event chain to find which observation corrupted which posterior is a real human-time cost. Most users will treat the log as a “in case of emergency, break glass” tool rather than a daily-driver debugger.</p>
</section>
</section>
<section id="what-this-approach-cannot-do" class="level2">
<h2 class="anchored" data-anchor-id="what-this-approach-cannot-do">What this approach cannot do</h2>
<p>The determinism contract has fundamental costs:</p>
<section id="no-gpu-acceleration" class="level3">
<h3 class="anchored" data-anchor-id="no-gpu-acceleration">No GPU acceleration</h3>
<p>Covered above. The determinism contract is incompatible with the vendor BLAS / cuDNN / FlashAttention reduction orderings that make modern accelerator hardware fast. A GPU-equipped workstation gets <strong>no measurable speedup</strong> over a laptop for ABNG training. This is <em>fundamental</em> — recovery would require giving up bit-identical replay.</p>
</section>
<section id="no-multi-thread-training-within-one-graph" class="level3">
<h3 class="anchored" data-anchor-id="no-multi-thread-training-within-one-graph">No multi-thread training within one graph</h3>
<p>The multi-graph arena is per-thread (multiple graphs can coexist), but a single graph’s training loop is single-threaded by design. <code>decide_step</code>’s deterministic node iteration order matters; parallelizing it would require careful synchronization and would probably break the byte-identical-replay property. Phase 0.8 is considering per-leaf parallelism for independent observations, but contingent on profiling.</p>
</section>
<section id="no-live-debugging-beyond-inspection-tooling" class="level3">
<h3 class="anchored" data-anchor-id="no-live-debugging-beyond-inspection-tooling">No live debugging beyond inspection tooling</h3>
<p>The audit log is read-only at runtime — you can <code>cjcl abng inspect</code> it, but you can’t <code>gdb</code> it. For users who need step-through debugging of model state evolution, the audit chain is a <em>replacement</em> for that capability (replay any past state) rather than an <em>interactive</em> tool.</p>
</section>
<section id="the-audit-log-is-large" class="level3">
<h3 class="anchored" data-anchor-id="the-audit-log-is-large">The audit log is large</h3>
<p>Even with witness-only events at ~80 bytes apiece, four state updates per observation across 1 M training rows is ~320 MB of audit log. Without compaction this is unmanageable in memory and expensive to keep in active storage. The <code>StatsSnapshot</code> marker + smart-replay path addresses this but at the cost of forward-only audit walk (you can’t quickly inspect arbitrary past state without re-replaying from a checkpoint).</p>
</section>
</section>
<section id="what-would-convince-me-to-deploy-this" class="level2">
<h2 class="anchored" data-anchor-id="what-would-convince-me-to-deploy-this">What would convince me to deploy this</h2>
<p>The “git commit history for a model” pitch is compelling in theory. Before deploying ABNG in a real regulated-ML setting, four things would need to happen:</p>
<ol type="1">
<li><p><strong>Cross-platform CI verification.</strong> The locked SHA-256 canary hashes are demonstrated on x86-64 Linux, macOS, and Windows in CJC-Lang’s test suite. ARM and BSD are untested. A GitHub Actions matrix running the full ABNG test suite on Linux+ARM, macOS+ARM, and Windows+x64, with canary comparison, is a finite engineering project — a <code>cross-platform-determinism.yml</code> workflow skeleton exists in the repo (Phase 0.6), but verification results have not been published. Until they are, “bit-identical determinism” carries an implicit footnote.</p></li>
<li><p><strong>Wall-clock cost characterization at production scale.</strong> Part 3 measured at n=1k, 10k, 100k rows. Real regulated-ML datasets are often 10⁶–10⁹ rows. Extrapolated from the 100K-row numbers: a 10⁶-row training history would produce a ~300 MB audit log with ~16-second replay. A 10⁹-row run would produce a ~300 GB log with ~16,000-second replay — not impossible but not casually verifiable. Realistic deployment would require log compaction tuned to the workload, and Phase 0.8 is the natural milestone for that.</p></li>
<li><p><strong>A real-world case study.</strong> A practitioner using ABNG for a problem with real stakes — a regulated trial, a scientific replication package, a robotic-system audit — and writing up what worked, what didn’t, and what they would do differently. Until this exists, ABNG is software without a user. The closest existing thing is the synthetic clinical-trial scenario walked through above, which is a useful proof-of-concept but not a deployment.</p></li>
<li><p><strong>An adversarial security review.</strong> The “tamper-evident, not tamper-proof” framing is correct, but a serious deployment would benefit from independent security review of the audit-chain construction. Specifically: is SHA-256 the right hash function for this application’s expected lifetime (collision resistance vs preimage resistance vs both)? Are there any non-obvious replay attacks on the chain structure? Does the smart-replay path have any subtle invariants a malicious snapshot could violate? These questions would take a real security team a few weeks to answer competently.</p></li>
</ol>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
The honest summary
</div>
</div>
<div class="callout-body-container callout-body">
<p>ABNG’s audit chain mechanism is <em>correct</em> and <em>well-tested at the engineering level</em>. The Phase 3 benchmarks confirm it scales linearly to 100K rows; the lineage attestation demo proves the three-signal spoof detection works at 64-row scale. The mechanism itself is well-understood cryptographically — SHA-256 hash chains and Merkle structures have a substantial security literature.</p>
<p>The gap between this and “production-ready for regulated ML” is not theoretical, it’s operational: cross-platform verification, scale testing, real deployment, security review. Those are months of work, not days, and the cost-benefit decision for any given organization depends on whether their use case actually needs Level 3 auditability or whether MLflow / DVC’s Level 1 is sufficient.</p>
<p>For most ML, Level 1 is enough. For the subset where it isn’t, ABNG is the most rigorous experimental answer the open-source ecosystem currently has — and it is unambiguously an experimental answer, not a deployment-ready one.</p>
</div>
</div>
<p>If after reading this article you decide the audit-chain approach is interesting but probably wrong in three specific ways, this piece has done its job. The architecture itself is in <a href="../../../blog/posts/abng-architecture/index.html">Part 1</a>; the empirical backbone is in <a href="../../../blog/posts/abng-benchmarks/index.html">Part 3</a>. The open-source code is at <a href="https://github.com/AdamEzzat1/CJC"><code>crates/cjc-abng/</code></a> — bring your skepticism.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>ABNG lives at <a href="https://github.com/AdamEzzat1/CJC"><code>crates/cjc-abng/</code></a> in the CJC-Lang repository. The full codebase, the test suite (including the three-signal spoof detection demo at <code>tests/test_abng_lineage_attestation.rs</code>), and the working <code>cjcl abng {inspect, replay, diff, explain, train}</code> CLI surface are open source. Part 1 of this series covers the architecture; Part 3 covers the empirical evidence. This article was the audit and determinism story — the part of the pitch that justifies the determinism premium.</p>
</div>
</div>


</section>

 ]]></description>
  <category>CJC-Lang</category>
  <category>ABNG</category>
  <category>determinism</category>
  <category>audit</category>
  <category>reproducibility</category>
  <category>regulated-ML</category>
  <guid>https://adamezzat1.github.io/blog/posts/abng-deterministic-systems/index.html</guid>
  <pubDate>Sun, 10 May 2026 07:00:00 GMT</pubDate>
</item>
<item>
  <title>ABNG — An Experimental Adaptive Belief Network Graph for CJC-Lang</title>
  <dc:creator>Adam Ezzat</dc:creator>
  <link>https://adamezzat1.github.io/blog/posts/abng-experimental-bayesian-graph/index.html</link>
  <description><![CDATA[ 



<div class="reading-time">
🕑 ~32 min read
</div>
<div class="stats-row">
<div class="stat">
<span class="number">75</span><span class="stat-label">abng_* builtins</span>
</div>
<div class="stat">
<span class="number">11</span><span class="stat-label">capabilities demoed</span>
</div>
<div class="stat">
<span class="number">960</span><span class="stat-label">tests passing</span>
</div>
<div class="stat">
<span class="number">v12</span><span class="stat-label">snapshot magic</span>
</div>
</div>
<div class="callout callout-style-simple callout-tip no-icon callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-title-container flex-fill">
TL;DR
</div>
</div>
<div class="callout-body-container callout-body">
<p>ABNG is an <strong>experimental</strong> addition to CJC-Lang shipping in the <code>cjc-abng</code> crate (Phase 0.1 → 0.5). It exposes 75 <code>abng_*</code> builtins for building <strong>adaptive belief network graphs</strong> — Bayesian-flavored ML primitives that combine codebook-routed BLR posteriors, an SHA-256 audit chain, structural-decision triggers, and per-node provenance stamps. This post walks through eleven distinct capabilities, each proven by a working demo (six of them written in CJC-Lang source and parity-tested across both executors). It also names what’s <em>not</em> proven yet — and why interpreter / compiler work is on the critical path before ABNG becomes production-grade.</p>
</div>
</div>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
ABNG is experimental
</div>
</div>
<div class="callout-body-container callout-body">
<p>ABNG is a research-grade addition to CJC-Lang. The capabilities described here are <em>demonstrated</em> by tests, but not yet <em>productized</em>: there are no benchmarks against industry baselines, no cross-platform CI verification of the determinism canaries, and no real-world case study of someone using ABNG to attest a deployed model. Treat this as a progress report, not a production recommendation. The “what’s still missing” section at the end is honest about the gaps.</p>
</div>
</div>
<div class="callout callout-style-default callout-important callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
On data scale: these demos prove “the capability works”, not “it works at scale”
</div>
</div>
<div class="callout-body-container callout-body">
<p><strong>Every demo in this post uses small datasets — between 3 and 200 training samples per scenario.</strong> The choice was deliberate: the goal is to prove each capability is real (not vacuous), under enough data to make the assertion meaningful, in a workload small enough that every test runs in milliseconds and produces a locked SHA-256 canary.</p>
<p>What this means concretely:</p>
<ul>
<li><strong>The capabilities are demonstrated at the scale tested.</strong> Where I claim the BLR posterior converges to the analytical solution, I trained on tens of samples, not thousands.</li>
<li><strong>Performance at production scale (n &gt; 10⁴) is not yet benchmarked.</strong> The asymptotic claims (e.g., per-leaf BLR is O(N · d²) vs classical GP’s O(N³)) are correct <em>by construction</em> — the math says so — but no demo has wall-clock measurements at large N.</li>
<li><strong>The demos are reproducibility gates, not load tests.</strong> They lock determinism canaries; they don’t prove ABNG handles realistic ML-training dataset sizes.</li>
<li><strong>Each demo’s data scale is named explicitly</strong> in its “Why this workload” subsection below — read those if you want to know exactly what level of evidence backs each claim.</li>
</ul>
<p>Wall-clock benchmarks at n ∈ {10³, 10⁴, 10⁵, 10⁶} and a large-dataset case study are explicitly listed as next steps in the “What’s next” section. Until those land, every claim in this post should be read as <strong>“proven at small scale, theoretically expected to hold at larger scale, empirically unverified at production scale.”</strong></p>
</div>
</div>
<section id="what-is-abng" class="level2">
<h2 class="anchored" data-anchor-id="what-is-abng">What is ABNG?</h2>
<p>ABNG — Adaptive Belief Network Graph — is a <code>cjc-abng</code> crate that lives alongside the existing CJC-Lang workspace. The goal: provide ML primitives that go beyond “fit some weights to data” by adding three orthogonal axes:</p>
<ol type="1">
<li><strong>Bayesian uncertainty</strong> at every prediction (mean, epistemic leverage, aleatoric variance) — without MCMC, ensembles, or variational inference.</li>
<li><strong>Cryptographic provenance</strong> of training history — every observation, every weight update, every structural decision lands in a SHA-256 audit chain that detects post-hoc tampering.</li>
<li><strong>Adaptive structure</strong> — the graph’s topology genuinely changes under the workload. Nodes grow, split, merge, prune, compress, freeze under a deterministic decision-policy engine.</li>
</ol>
<p>The architecture is a radix-style graph: a root node descends into children indexed by <strong>prefix bytes</strong> from a quantile codebook, and each leaf carries a <strong>Bayesian Linear Regression posterior</strong> over a feature basis. Predictions descend the tree, the posterior at the resolved leaf gives <code>(mean, epistemic_leverage, aleatoric_var)</code>, and structural decisions fire periodically to adapt the tree.</p>
<div class="cell">
<div class="cell-output-display">
<div>
<div>
<pre class="mermaid mermaid-js">flowchart LR
    F["State&lt;br/&gt;features"] --&gt; CB["Codebook&lt;br/&gt;encode"]
    CB --&gt; P["Prefix&lt;br/&gt;bytes"]
    P --&gt; D["Descend&lt;br/&gt;tree"]
    D --&gt; L["Leaf&lt;br/&gt;BLR"]
    L --&gt; Pred["(mean,&lt;br/&gt;leverage,&lt;br/&gt;aleatoric)"]
    L -.-&gt;|every step| AC["Audit chain&lt;br/&gt;SHA-256"]
</pre>
</div>
<p>ABNG conceptual flow. State features → codebook prefix → descend tree → BLR posterior at leaf → (mean, leverage, aleatoric) — plus an audit chain that hashes every step.</p>
</div>
</div>
</div>
<p>It’s exposed to CJC-Lang as <strong>75 <code>abng_*</code> builtins</strong> — <code>abng_new</code>, <code>abng_set_codebook</code>, <code>abng_set_leaf_head</code>, <code>abng_set_blr_prior</code>, <code>abng_observe</code>, <code>abng_blr_predict</code>, <code>abng_descend</code>, <code>abng_decide_step</code>, <code>abng_stamp_provenance</code>, etc. — so you can build and drive an ABNG graph entirely from <code>.cjcl</code> source.</p>
</section>
<section id="the-proof-discipline" class="level2">
<h2 class="anchored" data-anchor-id="the-proof-discipline">The proof discipline</h2>
<p>Every capability in this post has the same proof structure:</p>
<ol type="1">
<li><strong>A workload</strong> — a small but non-trivial problem where the capability <em>should</em> manifest if it’s working.</li>
<li><strong>A specific assertion</strong> that would fail if the capability didn’t work.</li>
<li><strong>A locked SHA-256 canary</strong> of the resulting graph’s <code>chain_head</code>, gating any future regression.</li>
<li><strong>Cross-pipeline determinism</strong>: most demos run through both <code>cjc-eval</code> (AST tree-walk) AND <code>cjc-mir-exec</code> (MIR register-machine) with byte-identical printed output asserted.</li>
</ol>
<p>That last property is the <em>strongest</em> determinism gate the project supports. AST and MIR are completely different code paths — if any FP arithmetic, hash computation, or dispatch routing differs anywhere in the pipeline, the printed output diverges and the parity test fails.</p>
<p>OK — let’s walk through what’s actually proven.</p>
<hr>
</section>
<section id="capability-1-per-state-bayesian-uncertainty" class="level2">
<h2 class="anchored" data-anchor-id="capability-1-per-state-bayesian-uncertainty">Capability 1 — Per-state Bayesian uncertainty</h2>
<p><strong>Demo:</strong> PINN heat-equation solver (<code>tests/test_abng_pinn_uncertainty_cjcl.rs</code> + <code>pinn_source.rs</code>)</p>
<section id="what-this-demo-proves" class="level3">
<h3 class="anchored" data-anchor-id="what-this-demo-proves">What this demo proves</h3>
<p>The Bayesian Linear Regression posterior at each codebook-routed leaf returns a 3-tuple <code>(mean, epistemic_leverage, aleatoric_var)</code>. The headline assertion is a <strong>strict spatial-partition property</strong>:</p>
<pre><code>min(epistemic_leverage at edge probes) &gt; max(epistemic_leverage at interior probes)</code></pre>
<p>This is stronger than “average edge uncertainty exceeds average interior uncertainty” — it’s a <em>no-overlap</em> claim. After training, the worst-case interior point has lower uncertainty than the best-case edge point. The signal cleanly partitions evidence-rich vs evidence-poor regions of the input space.</p>
<p>The fit-quality assertion (<code>abs_err_50 &lt; 0.05</code>) proves the mean <em>also</em> converges to the analytical solution. Without this, “leverage is uncertain at edges” would be vacuous — the mean could just be wrong everywhere.</p>
</section>
<section id="the-math" class="level3">
<h3 class="anchored" data-anchor-id="the-math">The math</h3>
<p>ABNG’s BLR uses a Normal-Inverse-Gamma (NIG) conjugate prior on <code>(w, σ²)</code>. Given a prior <code>(μ₀, Λ₀, a₀, b₀)</code> and a stream of observations <code>D = {(φᵢ, yᵢ)}</code>, the conjugate update yields a closed-form posterior:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Baligned%7D%0A%5CLambda_n%20&amp;=%20%5CLambda_0%20+%20%5CPhi%5E%5Ctop%20%5CPhi%20%5C%5C%0A%5Cmu_n%20&amp;=%20%5CLambda_n%5E%7B-1%7D%5C,(%5CLambda_0%20%5Cmu_0%20+%20%5CPhi%5E%5Ctop%20y)%20%5C%5C%0Aa_n%20&amp;=%20a_0%20+%20n/2%20%5C%5C%0Ab_n%20&amp;=%20b_0%20+%20%5Ctfrac%7B1%7D%7B2%7D(y%20-%20%5CPhi%5Cmu_n)%5E%5Ctop(y%20-%20%5CPhi%5Cmu_n)%20+%20%5Ctfrac%7B1%7D%7B2%7D(%5Cmu_0%20-%20%5Cmu_n)%5E%5Ctop%20%5CLambda_0%5C,(%5Cmu_0%20-%20%5Cmu_n)%0A%5Cend%7Baligned%7D%0A"></p>
<p>For a query point <code>x*</code>, the predictive distribution <code>p(y* | x*, D)</code> is a Student-t. ABNG returns its three sufficient ingredients as:</p>
<table class="table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Output</th>
<th>Formula</th>
<th>Interpretation</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>mean</code></td>
<td><img src="https://latex.codecogs.com/png.latex?%5Cmu_n%5E%5Ctop%20%5Cvarphi(x%5E*)"></td>
<td>Posterior mean prediction (point estimate)</td>
</tr>
<tr class="even">
<td><code>epistemic_leverage</code></td>
<td><img src="https://latex.codecogs.com/png.latex?%5Cvarphi(x%5E*)%5E%5Ctop%20%5CLambda_n%5E%7B-1%7D%5C,%5Cvarphi(x%5E*)"></td>
<td>Variance contribution from posterior over <code>w</code> (model uncertainty)</td>
</tr>
<tr class="odd">
<td><code>aleatoric_var</code></td>
<td><img src="https://latex.codecogs.com/png.latex?b_n%20/%20(a_n%20-%201)"></td>
<td>Observation-noise variance (data uncertainty)</td>
</tr>
</tbody>
</table>
<p>Two cleanly separated uncertainty channels. <strong><code>epistemic_leverage</code> shrinks as <code>ΦᵀΦ</code> accumulates</strong> — more data → tighter posterior over <code>w</code> → less model uncertainty. <strong><code>aleatoric_var</code> converges to the irreducible noise floor.</strong> A vanilla MLP collapses both into a single point estimate; ABNG keeps them factored.</p>
</section>
<section id="why-the-heat-equation" class="level3">
<h3 class="anchored" data-anchor-id="why-the-heat-equation">Why the heat equation</h3>
<p>The 1-D heat equation <code>∂u/∂t = α∂²u/∂x²</code> with Dirichlet BCs <code>u(0,t) = u(1,t) = 0</code> and IC <code>u(x,0) = sin(πx)</code> was chosen because it has a <strong>known closed-form analytical solution</strong>:</p>
<p><img src="https://latex.codecogs.com/png.latex?u(x,%20t)%20=%20e%5E%7B-%5Calpha%20%5Cpi%5E2%20t%7D%20%5Csin(%5Cpi%20x)"></p>
<p>That choice is load-bearing. The BLR feature basis is <code>[1, x, sin(πx), cos(πx)]</code> — the analytical solution <code>exp(-π²·0.1)·sin(πx)</code> is <em>exactly representable</em> as a single non-zero coefficient on the <code>sin(πx)</code> basis function. So with enough samples, the BLR posterior mean <em>must</em> converge to the truth. If it doesn’t, the BLR implementation is broken — not the test. This is the same trick statisticians use to validate Bayesian implementations: pick a problem where the truth is in the basis, and the posterior is a closed-form limit.</p>
<p>The training schedule is <strong>deliberately asymmetric</strong>: 32 samples uniformly across <code>[0.25, 0.75]</code> (interior), 4 samples each in <code>[0, 0.25)</code> and <code>[0.75, 1]</code> (edges). Without this asymmetry, uniform training would give uniform leverage and the spatial-partition property would be invisible.</p>
<p><strong>Data scale:</strong> 40 training samples total (32 interior + 8 edge), 5 probe points. Enough to demonstrate the spatial-partition property; <em>not</em> enough to claim ABNG matches state-of-the-art Bayesian PINN methods on realistic PDE problems. Validating against PINN benchmarks (NS equations, 2-D Burgers) at 10⁴+ collocation points is future work.</p>
</section>
<section id="code-sketch" class="level3">
<h3 class="anchored" data-anchor-id="code-sketch">Code sketch</h3>
<p>The PINN demo’s core loop, in CJC-Lang:</p>
<div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> pinn_features_2d(x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">f64</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> Tensor <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb2-2">    Tensor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>from_vec([<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> sin(pi() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> x)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> cos(pi() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> x)]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>])</span>
<span id="cb2-3"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb2-4"></span>
<span id="cb2-5"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> analytical_u(x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">f64</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">f64</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb2-6">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> decay <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> exp(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.9869604401089358</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// exp(-π² · 0.1)</span></span>
<span id="cb2-7">    decay <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> sin(pi() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> x)</span>
<span id="cb2-8"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb2-9"></span>
<span id="cb2-10"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> train_one(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">i64</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">f64</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb2-11">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> leaf <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> route_leaf(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> x)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>             <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// codebook descent</span></span>
<span id="cb2-12">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> phi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pinn_features_2d(x)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb2-13">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> y_val <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> analytical_u(x)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>             <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// ground truth</span></span>
<span id="cb2-14">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Tensor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>from_vec([y_val]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb2-15">    abng_blr_update(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> leaf<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> phi<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// NIG conjugate update</span></span>
<span id="cb2-16">    abng_observe(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> leaf<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> y_val)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb2-17"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div>
<p>After training, a probe at <code>x = 0.5</code> returns <code>(mean ≈ 0.372, lev_low, ale_finite)</code> — <code>mean</code> matches <code>exp(-π²·0.1) · sin(π·0.5) = 0.372</code> to within <code>0.05</code>. A probe at <code>x = 0.05</code> returns <code>(mean ≈ small, lev_high, ale_finite)</code> — different leaf, less data, higher leverage.</p>
</section>
<section id="connection-to-existing-techniques" class="level3">
<h3 class="anchored" data-anchor-id="connection-to-existing-techniques">Connection to existing techniques</h3>
<table class="table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th>Technique</th>
<th>Posterior representation</th>
<th>Cost</th>
<th>Decomposed epistemic/aleatoric?</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Gaussian Processes</strong></td>
<td>Full posterior over functions</td>
<td>O(n³) Cholesky</td>
<td>Yes (kernel + likelihood noise)</td>
</tr>
<tr class="even">
<td><strong>Deep ensembles</strong></td>
<td>Variance across M trained nets</td>
<td>M× training cost</td>
<td>Approximately</td>
</tr>
<tr class="odd">
<td><strong>MC dropout</strong></td>
<td>Variational approx via dropout</td>
<td>Cheap, biased</td>
<td>No (entangled)</td>
</tr>
<tr class="even">
<td><strong>Conformal prediction</strong></td>
<td>Distribution-free intervals</td>
<td>Cheap</td>
<td>No</td>
</tr>
<tr class="odd">
<td><strong>ABNG per-leaf BLR</strong></td>
<td>Closed-form NIG posterior</td>
<td>O(d²) per update</td>
<td>Yes (clean factor)</td>
</tr>
</tbody>
</table>
<p>ABNG slots between GPs (full posterior, expensive) and conformal prediction (distribution-free, no decomposition). It’s most similar in spirit to <strong>Bayesian Additive Regression Trees (BART)</strong> — leaf-localized closed-form Bayesian fits — but with a quantile-codebook routing instead of stochastic tree growth, and a stronger determinism contract.</p>
</section>
<section id="what-this-enables" class="level3">
<h3 class="anchored" data-anchor-id="what-this-enables">What this enables</h3>
<ul>
<li><strong>Active learning in scientific computing</strong> — query the BLR for <code>epistemic_leverage</code>; sample where it’s highest.</li>
<li><strong>Uncertainty-aware PINNs</strong> — refuse to predict in regions where leverage exceeds a threshold; fall back to a higher-fidelity solver. This is what the PINN community has been chasing via deep ensembles or HMC for years.</li>
<li><strong>Heteroskedastic regression done right</strong> — the aleatoric/epistemic split lets you tell “this region is intrinsically noisy” from “I haven’t seen enough of this region yet.”</li>
<li><strong>Uncertainty propagation downstream</strong> — feed the Student-t predictive into Monte Carlo / robust optimization pipelines.</li>
</ul>
<p>The PINN literature has long treated Bayesian uncertainty as expensive infrastructure (HMC, variational PINNs). ABNG provides this as a <strong>closed-form per-leaf posterior</strong> — no inference machinery beyond a Cholesky update.</p>
<hr>
</section>
</section>
<section id="capability-2-per-leaf-bounded-scaling-gp-like-scalable" class="level2">
<h2 class="anchored" data-anchor-id="capability-2-per-leaf-bounded-scaling-gp-like-scalable">Capability 2 — Per-leaf bounded scaling (GP-like, scalable)</h2>
<p><strong>Demo:</strong> Tabular regression on the unit square (<code>tests/test_abng_tabular_gp_cjcl.rs</code> + <code>tabular_source.rs</code>)</p>
<section id="what-this-demo-proves-1" class="level3">
<h3 class="anchored" data-anchor-id="what-this-demo-proves-1">What this demo proves</h3>
<p>Five distinct properties asserted as separate tests:</p>
<ol type="1">
<li><strong><code>max(per_leaf_n_seen) &lt; 0.9 × N</code></strong> — no single leaf holds the entire dataset. Evidence is partitioned, not duplicated.</li>
<li><strong><code>sum(per_leaf_n_seen) == N</code></strong> — every training point routes to exactly one leaf. No double-counting; no points lost.</li>
<li><strong><code>max_per_leaf_bounded</code></strong> — the partitioning works as designed. (This is the <em>scalability</em> claim’s empirical foundation.)</li>
<li><strong><code>trained_mse &lt; 0.5 × prior_mse</code></strong> — the BLR posterior actually <em>learns</em>. Without this, the structure-of-the-graph properties would be vacuous.</li>
<li><strong><code>lev_shrinks_with_data</code></strong> — leverage at a fixed probe drops from a 16-pt-trained leaf to a 64-pt-trained leaf. GP-like uncertainty contraction reproduced.</li>
</ol>
</section>
<section id="the-math-cost-analysis" class="level3">
<h3 class="anchored" data-anchor-id="the-math-cost-analysis">The math (cost analysis)</h3>
<p>ABNG’s per-leaf cost decomposes cleanly:</p>
<table class="table">
<colgroup>
<col style="width: 50%">
<col style="width: 50%">
</colgroup>
<thead>
<tr class="header">
<th>Operation</th>
<th>Cost</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Codebook descent</td>
<td>O(<code>n_dims · log(n_bins)</code>)</td>
</tr>
<tr class="even">
<td>BLR conjugate update at one leaf</td>
<td>O(<code>d² · n_leaf</code>) (Cholesky-factor maintenance)</td>
</tr>
<tr class="odd">
<td>Predictive at query point</td>
<td>O(<code>d²</code>) (one Cholesky solve)</td>
</tr>
<tr class="even">
<td>Aggregate cost across N points, L leaves</td>
<td>O(<code>N · d²</code>) — <em>linear in N</em></td>
</tr>
</tbody>
</table>
<p>Compare to a <strong>classical GP</strong> on the same N points:</p>
<table class="table">
<thead>
<tr class="header">
<th>Operation</th>
<th>Cost</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Kernel matrix <code>K_NN</code></td>
<td>O(<code>N²</code>) memory</td>
</tr>
<tr class="even">
<td>Cholesky decomposition</td>
<td><strong>O(<code>N³</code>)</strong> compute</td>
</tr>
<tr class="odd">
<td>Predictive variance per query</td>
<td>O(<code>N²</code>)</td>
</tr>
</tbody>
</table>
<p><strong>ABNG is asymptotically O(N · d²) vs GP’s O(N³).</strong> For <code>d = 4</code> (the demo’s basis dim) and <code>N = 10⁴</code>: ABNG ≈ 1.6×10⁵ ops; GP ≈ 10¹² ops — seven orders of magnitude.</p>
<p>The trade-off: ABNG’s leaves are independent — no global covariance structure. For workloads where local Bayesian behavior is sufficient (most tabular ML), this is a feature, not a bug.</p>
</section>
<section id="why-bilinear-regression-on-the-unit-square" class="level3">
<h3 class="anchored" data-anchor-id="why-bilinear-regression-on-the-unit-square">Why bilinear regression on the unit square</h3>
<p>The target is <code>y = 0.3 + 0.5x₁ + 0.4x₂ + 0.7·x₁·x₂</code>. Three deliberate choices:</p>
<ul>
<li><strong>Exactly fittable in the BLR feature basis</strong> <code>[1, x₁, x₂, x₁·x₂]</code>. With enough samples, BLR posterior mean → exact coefficients <code>[0.3, 0.5, 0.4, 0.7]</code>. This makes the MSE-reduction test <em>meaningful</em>: any residual MSE comes from sample efficiency, not from the model class being wrong.</li>
<li><strong>2-D input with a routing dimension.</strong> The codebook routes by <code>x₁</code> (4 bins) and lets <code>x₂</code> become a within-leaf feature. This mirrors the canonical ABNG pattern: <em>use one dimension to partition the input space, regress on the rest within each leaf</em>. Common in production: route by <code>(time-of-day, geo-region)</code> and regress on the rest within each segment.</li>
<li><strong>Unit square domain</strong> — predictable, no scaling tricks. Codebook boundaries <code>[0.25, 0.5, 0.75]</code> evenly partition <code>[0, 1]</code>.</li>
</ul>
<p><strong>Data scale:</strong> Two trained graphs (16-point and 64-point training sets) plus a 200-point graph for the per-leaf-bound check; 32 held-out test queries for MSE comparison. The asymptotic claim “ABNG O(N · d²) beats classical GP O(N³)” is <em>correct</em> by construction but <strong>not yet wall-clock-benchmarked</strong> at scales where it matters (n ≥ 10⁴). Without those numbers this is a “promising primitive,” not a measured speedup vs.&nbsp;e.g.&nbsp;SVGP / sklearn’s GaussianProcessRegressor / GPy.</p>
</section>
<section id="code-sketch-1" class="level3">
<h3 class="anchored" data-anchor-id="code-sketch-1">Code sketch</h3>
<div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb3-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> build_tabular_graph(seed<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">i64</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">i64</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb3-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> g <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> abng_new(seed)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb3-3">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> codebook <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Tensor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>from_vec([<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.75</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>])<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb3-4">    abng_set_codebook(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> codebook)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>                          <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// 4 bins on x₁</span></span>
<span id="cb3-5">    abng_set_leaf_head(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> Tensor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>from_vec([<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4.0</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tanh"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb3-6">    abng_set_blr_prior(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>                    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// NIG (a, b, σ_init)</span></span>
<span id="cb3-7">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// ... add 4 children, one per codebook bin ...</span></span>
<span id="cb3-8">    g</span>
<span id="cb3-9"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb3-10"></span>
<span id="cb3-11"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> train_one(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">i64</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> x1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">f64</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> x2<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">f64</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb3-12">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> leaf <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> route_for(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> x1)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>                             <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// x₁ → prefix → leaf</span></span>
<span id="cb3-13">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> phi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Tensor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>from_vec([<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> x1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> x2<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> x1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> x2]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>])<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb3-14">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> y_val <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> target(x1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> x2)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb3-15">    abng_blr_update(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> leaf<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> phi<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> Tensor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>from_vec([y_val]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]))<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb3-16"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div>
<p>After 200 training points, every leaf has its own BLR posterior with <code>mean → [0.3, 0.5, 0.4, 0.7]</code> and increasingly sharp leverage. Sum of <code>n_seen</code> across all four leaves equals exactly 200; the largest leaf holds well under 90% of points.</p>
</section>
<section id="connection-to-existing-techniques-1" class="level3">
<h3 class="anchored" data-anchor-id="connection-to-existing-techniques-1">Connection to existing techniques</h3>
<table class="table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th>Technique</th>
<th>Routing</th>
<th>Per-leaf model</th>
<th>Scaling</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Classical GP</strong></td>
<td>None (global)</td>
<td>Kernel</td>
<td>O(n³)</td>
</tr>
<tr class="even">
<td><strong>Sparse GP / SVGP</strong></td>
<td>Inducing points</td>
<td>Kernel</td>
<td>O(n·m²) for m inducing pts</td>
</tr>
<tr class="odd">
<td><strong>Mondrian Forest</strong></td>
<td>Stochastic tree splits</td>
<td>Point estimate</td>
<td>O(n·log n)</td>
</tr>
<tr class="even">
<td><strong>BART</strong></td>
<td>Stochastic tree splits</td>
<td>Bayesian posterior</td>
<td>O(n·log n) per tree</td>
</tr>
<tr class="odd">
<td><strong>Locally-weighted regression</strong></td>
<td>Distance-weighted</td>
<td>Linear fit</td>
<td>O(n) per query</td>
</tr>
<tr class="even">
<td><strong>ABNG</strong></td>
<td>Quantile codebook (deterministic)</td>
<td>Closed-form NIG posterior</td>
<td>O(N · d²)</td>
</tr>
</tbody>
</table>
<p>The closest analog is <strong>Mondrian Forests</strong> (Lakshminarayanan, Roy &amp; Teh 2014) — both partition the input space and fit per-leaf models. The differences: Mondrian uses stochastic splits and point estimates; ABNG uses deterministic quantile boundaries and full closed-form Bayesian posteriors. ABNG also adds the audit chain that Mondrian doesn’t have.</p>
</section>
<section id="what-this-enables-1" class="level3">
<h3 class="anchored" data-anchor-id="what-this-enables-1">What this enables</h3>
<ul>
<li><strong>Drop-in tabular Bayesian regression at GP-prohibitive sizes</strong> (n &gt; 10⁴ where dense Cholesky becomes painful)</li>
<li><strong>Online / streaming learning</strong> — incremental BLR update is O(d²) per point, vs O(n²) incremental for classical GP rank-one updates</li>
<li><strong>Snapshot-checkpointable Bayesian state</strong> — classical GPs don’t naturally serialize (you’d persist the full Cholesky factor + raw data); ABNG snapshots the entire posterior in one blob via the audit chain</li>
<li><strong>Feature-engineered uncertainty quantification</strong> — the practitioner picks routing dimensions (time-of-day, region, customer segment) and the per-segment posteriors are independently calibrated</li>
</ul>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Warning
</div>
</div>
<div class="callout-body-container callout-body">
<p>The asymptotic claim is correct by construction (the math says so) but <strong>not yet benchmarked</strong> against a classical GP at realistic sizes. Wall-clock benchmarks at n ∈ {10³, 10⁴, 10⁵} need to be run before treating ABNG as a “GP killer.”</p>
</div>
</div>
<hr>
</section>
</section>
<section id="capability-3-tamper-evident-training-history-audit-chain" class="level2">
<h2 class="anchored" data-anchor-id="capability-3-tamper-evident-training-history-audit-chain">Capability 3 — Tamper-evident training history (audit chain)</h2>
<p><strong>Demo:</strong> Universal property — every demo asserts it.</p>
<section id="what-every-demo-proves" class="level3">
<h3 class="anchored" data-anchor-id="what-every-demo-proves">What every demo proves</h3>
<p>Every state mutation in ABNG funnels through one chain step:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Ctext%7Bnew%5C_hash%7D%20=%20%5Ctext%7BSHA-256%7D%5Cbigl(%5Ctext%7Bprevious%5C_hash%7D%20%5C,%5CVert%5C,%20%5Ctext%7Bcanonical%5C_payload%7D(%5Ctext%7Bevent%7D)%5Cbigr)%0A"></p>
<p>Every demo asserts <code>verify_chain == Ok</code> after training. For an n-event audit log, <code>verify_chain</code> walks the chain forward, recomputing <code>new_hash</code> at every step from the <em>stored</em> previous hash and the <em>stored</em> canonical payload, and compares to the stored value. <strong>Any single byte changed in any payload would fail the recomputation at that event AND every subsequent event</strong> — a Merkle-style cascade.</p>
<p>Across the 11 demos, this code path runs hundreds of times. The fact that <code>verify_chain == Ok</code> after every demo’s training session is the empirical proof the audit chain is sound under realistic workloads.</p>
</section>
<section id="the-threat-model" class="level3">
<h3 class="anchored" data-anchor-id="the-threat-model">The threat model</h3>
<p>The audit chain is designed against three concrete attacks:</p>
<ol type="1">
<li><strong>Post-hoc rewrite.</strong> Attacker modifies a historical training observation after the fact. Detection: the modified event’s <code>new_hash</code> no longer matches <code>sha256(previous_hash ‖ payload)</code>, AND every subsequent event’s stored <code>previous_hash</code> no longer matches the recomputed-from-modified value. <strong>Cascading detection.</strong></li>
<li><strong>Event reordering.</strong> Attacker shuffles the order of training events. Detection: each event’s payload includes its <code>seq</code> number; reordering puts events at the wrong seq, breaking the per-event monotonic-seq invariant.</li>
<li><strong>Event substitution.</strong> Attacker replaces one event with another that produces the same <code>new_hash</code>. Detection: SHA-256 collision resistance — finding two payloads with the same hash is computationally infeasible.</li>
</ol>
<p><strong>Data scale:</strong> Universal property — every demo’s audit log (3 to ~110 events, depending on workload) gets verified. The largest audit log in the test suite is the PINN demo’s 110-event chain post-asymmetric training. The cryptographic argument scales independently of dataset size: SHA-256 chain verification is O(log(events)) per check and the security guarantees come from the hash function itself, not from the workload. So this is the one capability where “small data demos suffice” is a load-bearing claim, not a hedge.</p>
</section>
<section id="code-sketch-what-an-event-payload-looks-like" class="level3">
<h3 class="anchored" data-anchor-id="code-sketch-what-an-event-payload-looks-like">Code sketch — what an event payload looks like</h3>
<pre class="text"><code>[seq u64 BE | epoch u64 BE | node_id u32 BE | kind tag u8 |
 kind body (variable) |
 stats_version u64 BE | stats_hash [u8; 32]]</code></pre>
<p>For a <code>BeliefUpdate</code> event (the most common kind, fired on every <code>abng_observe</code>), the kind body is a single <code>value f64-bits BE</code> — 8 bytes. Total payload for a BeliefUpdate is <code>21 + 8 + 8 + 32 = 69 bytes</code>, hashed into the chain.</p>
<p>The canonical encoding is <strong>byte-deterministic</strong>: big-endian on every numeric field, fixed sizes, no padding ambiguity. Two ABNG instances run with the same seed and observation order produce identical event payload bytes — and therefore identical chain hashes.</p>
</section>
<section id="connection-to-existing-techniques-2" class="level3">
<h3 class="anchored" data-anchor-id="connection-to-existing-techniques-2">Connection to existing techniques</h3>
<table class="table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th>Technique</th>
<th>Hash-chain</th>
<th>ML-aware</th>
<th>Per-prediction lineage</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Git</strong></td>
<td>Yes (DAG of commits)</td>
<td>No</td>
<td>No</td>
</tr>
<tr class="even">
<td><strong>MLflow</strong></td>
<td>No (timestamped log)</td>
<td>Yes</td>
<td>Manual</td>
</tr>
<tr class="odd">
<td><strong>sigstore / in-toto</strong></td>
<td>Yes (artifact attestation)</td>
<td>No</td>
<td>Per-artifact</td>
</tr>
<tr class="even">
<td><strong>Blockchain ML provenance</strong></td>
<td>Yes (full chain on-chain)</td>
<td>Yes</td>
<td>Yes, but expensive</td>
</tr>
<tr class="odd">
<td><strong>ABNG audit chain</strong></td>
<td>Yes (linear)</td>
<td>Yes (every state mutation)</td>
<td>Yes, baked in</td>
</tr>
</tbody>
</table>
<p>The closest analog is <strong>in-toto attestations</strong> in software supply-chain land — except in-toto is per-artifact (each release gets one attestation), while ABNG is <em>per-event</em> (every observation, every weight update).</p>
</section>
<section id="what-this-enables-2" class="level3">
<h3 class="anchored" data-anchor-id="what-this-enables-2">What this enables</h3>
<ul>
<li><strong>Forensic post-incident analysis</strong> — “Was this model trained on the bad data?” Replay the chain, check.</li>
<li><strong>Regulatory replay</strong> — the model’s full training history is one cryptographically-linked blob.</li>
<li><strong>Reproducibility certification</strong> — papers ship <code>(model.snap, dataset)</code> pairs; reviewers reproduce + verify chain in one byte comparison.</li>
<li><strong>ML supply chain attestation</strong> — fine-tuning extends the chain; provenance of every weight is traceable.</li>
<li><strong>Insider-threat detection</strong> — anyone modifying historical events breaks every subsequent hash, visible to anyone running <code>verify_chain</code>.</li>
</ul>
<p>This is closer to Git’s content-addressed model than to traditional ML metadata stores. MLflow / Weights &amp; Biases give you <em>external</em> logs you have to trust; ABNG gives you a model artifact whose history is <strong>cryptographically self-attesting</strong>.</p>
<hr>
</section>
</section>
<section id="capability-4-cryptographic-dataset-attestation" class="level2">
<h2 class="anchored" data-anchor-id="capability-4-cryptographic-dataset-attestation">Capability 4 — Cryptographic dataset attestation</h2>
<p><strong>Demo:</strong> Lineage attestation with synthetic clinical-trial data (<code>tests/test_abng_lineage_attestation_cjcl.rs</code> + <code>lineage_source.rs</code>)</p>
<section id="what-this-demo-proves-2" class="level3">
<h3 class="anchored" data-anchor-id="what-this-demo-proves-2">What this demo proves</h3>
<p>The headline test (<code>lineage_attestation_three_signals_independent</code>) proves that tampering one row of a 64-row dataset (changing patient #17’s response from <code>0.525</code> to <code>0.999</code>) produces <strong>three independent SHA-256 divergences</strong>:</p>
<ol type="1">
<li><strong><code>chain_head</code> divergence</strong> — every audit event’s hash cascades from row 17 onward.</li>
<li><strong>BLR <code>state_hash</code> divergence</strong> — the leaf where the tampered row routes has a different posterior.</li>
<li><strong><code>provenance_stamp</code> divergence</strong> — the dataset fingerprint differs by construction.</li>
</ol>
<p>The “independent” qualifier is doing real work. Even if an attacker forged one signal (e.g., recomputed the chain to hide the change), the other two would still surface the tamper.</p>
</section>
<section id="the-threat-model-and-why-three-signals-matter" class="level3">
<h3 class="anchored" data-anchor-id="the-threat-model-and-why-three-signals-matter">The threat model and why three signals matter</h3>
<p>The classic attack against ML provenance is <strong>collusion</strong>: the attacker controls the training process, so they can produce a consistent-but-fake audit trail. Three independent signals raise the cost:</p>
<table class="table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Attack</th>
<th>Defeats which signal?</th>
<th>Defeats all three?</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Modify training data, recompute chain</td>
<td>chain_head ✓</td>
<td>No — BLR state diverges</td>
</tr>
<tr class="even">
<td>Modify training data, retrain to match BLR state</td>
<td>chain_head ✓, BLR state ✓</td>
<td>No — provenance stamp diverges</td>
</tr>
<tr class="odd">
<td>Modify training data, retrain, restamp with new fingerprint</td>
<td>All three forgeable internally</td>
<td>But: fingerprint is <em>publicly verifiable</em> against the dataset bytes</td>
</tr>
</tbody>
</table>
<p>The provenance stamp is <code>sha256(canonical_dataset_bytes)</code>. <strong>An auditor with the original dataset can recompute this themselves</strong> — the attacker can’t forge it without also forging the dataset (which is what we’re trying to detect in the first place). The chain and BLR state are internal consistency checks; the provenance stamp is the <em>external</em> binding to the dataset.</p>
</section>
<section id="why-a-synthetic-clinical-trial-workload" class="level3">
<h3 class="anchored" data-anchor-id="why-a-synthetic-clinical-trial-workload">Why a synthetic clinical-trial workload</h3>
<ul>
<li><strong>Maps directly to a real industry threat model.</strong> “Lab A trains a model claiming approved dataset; attacker substitutes contaminated dataset” is the FDA model-attestation problem in one sentence.</li>
<li><strong>64-row size is realistic</strong> for Phase II clinical-trial cohorts (often &lt;200 patients per arm).</li>
<li><strong>Cubic dose-response curve</strong> <code>y = 0.2 + 0.6·dose + 0.1·dose²</code> is biologically plausible AND exactly fittable in the BLR basis <code>[1, dose, dose², dose³]</code>, giving us ground-truth assertions in addition to the security claims.</li>
<li><strong>Tamper at row 17 specifically</strong> because <code>dose=0.5</code> falls right at a codebook bin boundary — routing the tampered observation to a specific leaf, making the BLR <code>state_hash</code> divergence clearly attributable.</li>
</ul>
<p><strong>Data scale:</strong> Two graphs trained on 16-row datasets each (the CJC-Lang version of the demo uses 16 rows for interpreter speed; the Rust-side version uses 64). Tampering one row out of 16 — or one out of 64 — is the <em>minimum-effort</em> attack. A 1.5%-of-dataset perturbation is the strongest test possible: if even this is detectable, larger tampers are detected a fortiori. Scale-up to 10⁴ - 10⁶ row clinical-trial datasets is straightforward (the cryptographic argument is dataset-size-invariant) but <strong>not yet validated end-to-end</strong> with a real anonymized clinical-trial corpus.</p>
</section>
<section id="code-sketch-2" class="level3">
<h3 class="anchored" data-anchor-id="code-sketch-2">Code sketch</h3>
<div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb5-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> train_and_stamp(seed<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">i64</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> rows<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">Any</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fingerprint<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">String</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">i64</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb5-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> g <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> build_lineage_graph(seed)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-3">    abng_stamp_provenance(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> fingerprint)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>                <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// sha256(dataset_bytes)</span></span>
<span id="cb5-4">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> len(rows)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-5">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-6">    <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">while</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb5-7">        <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> row <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> rows[i]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-8">        <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> dose <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> row[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-9">        <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> resp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> row[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-10">        <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> leaf <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> route_leaf(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> dose)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-11">        <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> phi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Tensor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>from_vec(</span>
<span id="cb5-12">            [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> dose<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> dose <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> dose<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> dose <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> dose <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> dose]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>])<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-13">        abng_blr_update(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> leaf<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> phi<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> Tensor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>from_vec([resp]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]))<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-14">        abng_observe(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> leaf<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> resp)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-15">        i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-16">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb5-17">    g</span>
<span id="cb5-18"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div>
<p>The two graphs <code>g_a</code> and <code>g_b</code> are built identically — only the row data and fingerprint differ. Comparing <code>(chain_head, blr_state_hash[node 3], provenance_stamp)</code> between them yields three different hashes.</p>
</section>
<section id="connection-to-existing-techniques-3" class="level3">
<h3 class="anchored" data-anchor-id="connection-to-existing-techniques-3">Connection to existing techniques</h3>
<table class="table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th>Technique</th>
<th>Cryptographic</th>
<th>ML-integrated</th>
<th>Per-prediction</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>MLflow / W&amp;B logs</strong></td>
<td>No</td>
<td>Yes</td>
<td>No</td>
</tr>
<tr class="even">
<td><strong>Sigstore / in-toto</strong></td>
<td>Yes</td>
<td>No (artifact-only)</td>
<td>No</td>
</tr>
<tr class="odd">
<td><strong>Datasheet for Datasets</strong> (Gebru et al.&nbsp;2018)</td>
<td>No (process-only)</td>
<td>Yes</td>
<td>No</td>
</tr>
<tr class="even">
<td><strong>Model Cards</strong> (Mitchell et al.&nbsp;2019)</td>
<td>No</td>
<td>Yes</td>
<td>No</td>
</tr>
<tr class="odd">
<td><strong>MLOps governance frameworks</strong></td>
<td>Variable</td>
<td>Yes</td>
<td>Manual</td>
</tr>
<tr class="even">
<td><strong>ABNG provenance chain</strong></td>
<td><strong>Yes</strong></td>
<td><strong>Yes</strong></td>
<td><strong>Yes (baked into predict_snap)</strong></td>
</tr>
</tbody>
</table>
<p>ABNG’s contribution is the <em>combination</em>: cryptographic, ML-integrated, AND per-prediction. None of the existing tools do all three in one artifact.</p>
</section>
<section id="what-this-enables-3" class="level3">
<h3 class="anchored" data-anchor-id="what-this-enables-3">What this enables</h3>
<table class="table">
<colgroup>
<col style="width: 50%">
<col style="width: 50%">
</colgroup>
<thead>
<tr class="header">
<th>Use case</th>
<th>Concrete flow</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>FDA model attestation (pharma)</strong></td>
<td>Lab runs trial → trains ABNG → stamps with <code>sha256(approved_dataset)</code>. Regulator: <code>cjcl abng explain --model X.snap pred.snap</code> checks all three signals in one CLI invocation</td>
</tr>
<tr class="even">
<td><strong>Financial model audit (SEC, Basel III)</strong></td>
<td>“Prove this risk model used only data through model-validation cutoff” — chain head + dataset fingerprint answer in one byte comparison</td>
</tr>
<tr class="odd">
<td><strong>HIPAA-compliant healthcare ML</strong></td>
<td>Patient-consent boundaries enforced cryptographically: stamp = <code>sha256(consent_set_v2_canonicalized)</code></td>
</tr>
<tr class="even">
<td><strong>GDPR Article 22</strong></td>
<td>Right to explanation = “show me the audit trail of training data that produced this decision about me”</td>
</tr>
<tr class="odd">
<td><strong>Academic reproducibility</strong></td>
<td><code>paper.pdf + model.snap + dataset.csv</code> — anyone with all three can verify byte-for-byte</td>
</tr>
<tr class="even">
<td><strong>Insider-threat in ML supply chain</strong></td>
<td>Attacker who fine-tunes a base model on contaminated data CAN’T hide the audit-chain extension</td>
</tr>
</tbody>
</table>
<p>The closest non-ABNG analog is <strong>in-toto-attested ML pipelines</strong> (an active research area, see Torres-Arias et al.&nbsp;2019). The difference: ABNG bakes attestation <em>inside</em> the model artifact, not as a sidecar.</p>
<hr>
</section>
</section>
<section id="capability-5-ood-detection-composite" class="level2">
<h2 class="anchored" data-anchor-id="capability-5-ood-detection-composite">Capability 5 — OOD detection composite</h2>
<p><strong>Demo:</strong> Asymmetric codebook with deliberately-missing children (<code>tests/test_abng_ood_detection_cjcl.rs</code> + <code>ood_source.rs</code>)</p>
<section id="what-this-demo-proves-3" class="level3">
<h3 class="anchored" data-anchor-id="what-this-demo-proves-3">What this demo proves</h3>
<p>The headline assertion is <strong>three-tier separation</strong>:</p>
<pre><code>max(ood_score in dense bins)  &lt;  ood_score in sparse bin  &lt;  ood_score in routing-fall-off bin</code></pre>
<p>Three evidence regimes are cleanly partitioned. The composite is a function of three independent inputs.</p>
</section>
<section id="the-math-1" class="level3">
<h3 class="anchored" data-anchor-id="the-math-1">The math</h3>
<p>ABNG’s <code>ood_score</code> composites three signals:</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Ctext%7Bood%7D(x)%20=%20f%5Cbigl(%5Ctext%7Bdensity%7D(x),%5C%20%5Ctext%7Bepistemic%5C_z%7D(x),%5C%20%5Ctext%7Bprefix%5C_distance%7D(x)%5Cbigr)"></p>
<p>where:</p>
<table class="table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Signal</th>
<th>Formula</th>
<th>What it detects</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>density(x)</code></td>
<td><img src="https://latex.codecogs.com/png.latex?1%20-%20%5Cexp(-d%5E2)"> where <img src="https://latex.codecogs.com/png.latex?d%5E2%20=%20(x%20-%20%5Cbar%7B%5Cmu%7D)%5E%5Ctop%20%5Ctext%7Bdiag%7D(%5CSigma)%5E%7B-1%7D(x%20-%20%5Cbar%7B%5Cmu%7D)"></td>
<td>Distance from training cluster mean (per-leaf)</td>
</tr>
<tr class="even">
<td><code>epistemic_z(x)</code></td>
<td><img src="https://latex.codecogs.com/png.latex?%5Cmin(%5Cvarphi%5E%5Ctop%20%5CLambda_n%5E%7B-1%7D%5Cvarphi%20/%20%5Cmathbb%7BE%7D%5B%5Ctext%7Blev%7D%5D,%201)"></td>
<td>Calibrated BLR uncertainty (per-leaf)</td>
</tr>
<tr class="odd">
<td><code>prefix_distance(x)</code></td>
<td><img src="https://latex.codecogs.com/png.latex?(%5Ctext%7Bprefix%5C_max%7D%20-%20%5Ctext%7Bmatched%5C_prefix%7D)%20/%20%5Ctext%7Bprefix%5C_max%7D"></td>
<td>Routing-failure rate (per-graph)</td>
</tr>
</tbody>
</table>
<p>The composite combines these into a single bounded <code>[0, 1]</code> score. Each signal catches a different OOD mode:</p>
<ul>
<li><strong>Density</strong> catches inputs that fall <em>in</em> a known leaf but far from its training cluster.</li>
<li><strong>Epistemic</strong> catches inputs where the BLR posterior says “I’m uncertain here.”</li>
<li><strong>Prefix distance</strong> catches inputs that <em>don’t fit the codebook structure at all</em> — i.e.&nbsp;the descent fell off the tree because no child exists at that prefix byte.</li>
</ul>
</section>
<section id="why-deliberately-omit-one-child" class="level3">
<h3 class="anchored" data-anchor-id="why-deliberately-omit-one-child">Why deliberately omit one child</h3>
<p>A symmetric setup (children in all 4 bins, varying training density) would only exercise the density and epistemic signals — not prefix_distance. By deliberately <em>not</em> adding a child for bin 3, queries in bin 3 descend with <code>matched_prefix=0</code>, and the prefix-distance contribution to the composite fires.</p>
<p>The demo asserts the <strong>composite</strong> works, not just individual signals. If it only varied density, you’d be left wondering whether the routing-distance contribution is real or just a docstring claim. The asymmetric child set proves it’s real.</p>
<p><strong>Data scale:</strong> 32 dense observations per bin on bins 0 and 1, 4 sparse on bin 2, 0 on bin 3 (no leaf) — 68 total density observations. 4 probe queries (one per bin). Sufficient to demonstrate the three-tier separation property; insufficient to characterize behavior under realistic ML inputs (e.g., 1024-D image embeddings, NLP token vectors). OOD detection on high-D unstructured inputs is <strong>fundamentally outside</strong> the codebook-routing paradigm — ABNG’s OOD score requires structured input where the codebook can produce a meaningful prefix encoding.</p>
</section>
<section id="code-sketch-3" class="level3">
<h3 class="anchored" data-anchor-id="code-sketch-3">Code sketch</h3>
<div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb7-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> probe_ood(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">i64</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">f64</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">f64</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb7-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> x_t <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Tensor<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>from_vec([x]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb7-3">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> prefix <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> abng_encode_prefix(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> x_t)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb7-4">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> evidence <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> abng_descend(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> prefix)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb7-5">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> matched <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> int(evidence<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>get([<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]))<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>      <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// matched_prefix</span></span>
<span id="cb7-6">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> leaf <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> int(evidence<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>get([<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]))<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>         <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// resolved leaf id</span></span>
<span id="cb7-7">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> phi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> features_1d(x)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb7-8">    abng_ood_score(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> leaf<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> phi<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> matched<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// composite [0, 1]</span></span>
<span id="cb7-9"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb7-10"></span>
<span id="cb7-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Build phase: add children for bins 0, 1, 2 — NOT bin 3.</span></span>
<span id="cb7-12">abng_add_node(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// bin 0 child</span></span>
<span id="cb7-13">abng_add_node(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// bin 1 child</span></span>
<span id="cb7-14">abng_add_node(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// bin 2 child</span></span>
<span id="cb7-15"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// (no add_node for byte 3 — bin 3 queries descend with matched_prefix = 0)</span></span>
<span id="cb7-16"></span>
<span id="cb7-17"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Train density on bins 0 and 1 heavily, bin 2 lightly:</span></span>
<span id="cb7-18">train_leaf_density(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.125</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">32</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// bin 0: dense</span></span>
<span id="cb7-19">train_leaf_density(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.375</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">32</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// bin 1: dense</span></span>
<span id="cb7-20">train_leaf_density(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.625</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// bin 2: sparse</span></span>
<span id="cb7-21"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// bin 3: no training (no leaf)</span></span></code></pre></div>
<p>After training, probing at <code>x = 0.10</code> (bin 0, dense) gives a low ood; <code>x = 0.40</code> (bin 1, dense) low; <code>x = 0.62</code> (bin 2, sparse) medium; <code>x = 0.90</code> (bin 3, no leaf) highest.</p>
</section>
<section id="connection-to-existing-techniques-4" class="level3">
<h3 class="anchored" data-anchor-id="connection-to-existing-techniques-4">Connection to existing techniques</h3>
<table class="table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Technique</th>
<th>OOD signal</th>
<th>Bayesian-aware?</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Mahalanobis distance OOD</strong> (Lee et al.&nbsp;2018)</td>
<td>Distance from class means</td>
<td>No</td>
</tr>
<tr class="even">
<td><strong>Energy-based OOD</strong> (Liu et al.&nbsp;2020)</td>
<td>-logsumexp(logits)</td>
<td>No</td>
</tr>
<tr class="odd">
<td><strong>Deep ensemble disagreement</strong></td>
<td>Variance across M models</td>
<td>Approximately</td>
</tr>
<tr class="even">
<td><strong>ODIN</strong> (Liang et al.&nbsp;2018)</td>
<td>Temperature + input perturbation</td>
<td>No</td>
</tr>
<tr class="odd">
<td><strong>Conformal prediction p-values</strong></td>
<td>Distribution-free coverage</td>
<td>No</td>
</tr>
<tr class="even">
<td><strong>ABNG composite</strong></td>
<td>density + epistemic + routing</td>
<td><strong>Yes (BLR posterior is principled)</strong></td>
</tr>
</tbody>
</table>
<p>The closest is <strong>Mahalanobis OOD</strong> — both compute a distance-from-training-cluster score. ABNG adds the routing-failure signal on top, which catches a class of inputs Mahalanobis can’t distinguish (in-cluster but topologically out-of-routing).</p>
</section>
<section id="what-this-enables-4" class="level3">
<h3 class="anchored" data-anchor-id="what-this-enables-4">What this enables</h3>
<ul>
<li><strong>Self-driving / robotics</strong> — abstain on inputs unfamiliar to the training distribution; hand off to safe-mode.</li>
<li><strong>Medical diagnosis</strong> — don’t predict on patient profiles outside the training population.</li>
<li><strong>Fraud detection</strong> — novel attack patterns trigger high OOD scores.</li>
<li><strong>Active learning</strong> — high-OOD inputs are highest-value labeling candidates.</li>
<li><strong>Safety-critical ML</strong> — defense-in-depth against distribution shift at <em>inference</em> time, not just monitoring.</li>
</ul>
<p>A query can be OOD because (a) it’s far from training points (density), (b) the model is uncertain there (epistemic), or (c) it doesn’t fit the codebook structure at all (prefix). Each tells a different operational story — and the composite collapses them into one actionable score.</p>
<hr>
</section>
</section>
<section id="capability-6-adaptive-structural-triggers" class="level2">
<h2 class="anchored" data-anchor-id="capability-6-adaptive-structural-triggers">Capability 6 — Adaptive structural triggers</h2>
<p><strong>Demo:</strong> Two-sibling shared-signature topology (<code>tests/test_abng_adaptive_triggers_cjcl.rs</code> + <code>adaptive_source.rs</code>)</p>
<section id="what-this-demo-proves-4" class="level3">
<h3 class="anchored" data-anchor-id="what-this-demo-proves-4">What this demo proves</h3>
<p>After running <code>abng_decide_step</code> three times on a graph with two sibling children:</p>
<ol type="1">
<li><strong><code>merge_count == 1</code></strong> — exactly one Merge action fires (deterministic given the canary thresholds).</li>
<li><strong><code>audit_log_grew</code></strong> — the structural mutation is recorded as an event in the chain.</li>
<li><strong><code>verify_chain == Ok</code></strong> — the chain integrity holds after structural mutation.</li>
<li><strong><code>chain_head</code> matches the Rust-side <code>decide_step_canary</code> BYTE-FOR-BYTE</strong> — this is the most striking property in the entire demo set.</li>
</ol>
<p>That last point deserves emphasis. The Rust-side <code>decide_step_canary_tests.rs</code> locks <code>chain_head = d064fb08…7807</code>. The CJC-Lang demo, running through both AST eval AND MIR exec, produces the <em>same</em> hex. <strong>The same workload, expressed three different ways (direct Rust API, CJC-Lang AST, CJC-Lang MIR), produces bit-identical SHA-256 output through 23+ events.</strong></p>
</section>
<section id="the-math-behind-the-merge-trigger" class="level3">
<h3 class="anchored" data-anchor-id="the-math-behind-the-merge-trigger">The math behind the Merge trigger</h3>
<p><code>decide_step</code> evaluates each node against six trigger conditions in order: Compress → Merge → Split → Prune → Grow → Freeze. For the Merge trigger specifically, ABNG checks two conditions on a pair of siblings <code>(i, j)</code>:</p>
<ol type="1">
<li><p><strong>Signature similarity:</strong> Hamming distance of the 8-byte truncated SHA-256 signatures &lt; <code>tau_merge</code>. The signature is computed from a 4-channel Welford profile (prediction, uncertainty, calibration, routing). Similar profiles → similar signatures → low Hamming distance.</p></li>
<li><p><strong>Posterior similarity (KL divergence):</strong> <img src="https://latex.codecogs.com/png.latex?%0A%5Cmathrm%7BKL%7D%5Cbigl(%5Cmathcal%7BN%7D(%5Cmu_i,%20%5CSigma_i)%5C,%5C%7C%5C,%5Cmathcal%7BN%7D(%5Cmu_j,%20%5CSigma_j)%5Cbigr)%20%3C%20%5Ctext%7Bkl%5C_merge%7D%0A"> where <code>(μᵢ, Σᵢ)</code> are the BLR posterior parameters at node <code>i</code>. This is the standard multivariate Gaussian KL.</p></li>
</ol>
<p>Both conditions must hold for Merge to fire. The Merge action then folds <code>j</code>’s observations into <code>i</code> via Chan/Golub/LeVeque parallel Welford merge — preserving the post-merge stats as if they’d been observed in series.</p>
</section>
<section id="why-the-two-sibling-shared-signature-topology" class="level3">
<h3 class="anchored" data-anchor-id="why-the-two-sibling-shared-signature-topology">Why the two-sibling shared-signature topology</h3>
<ul>
<li><strong>Mirrors the Rust-side <code>decide_step_canary</code> exactly</strong> — root + 2 children at keys 1 and 2, three observations at root (0.10, 0.25, 0.40), three decide_step calls. This lets the CJC-Lang demo serve as a <em>direct cross-pipeline determinism gate</em>.</li>
<li><strong>Merge is the most reliably-triggered action.</strong> With identical training distributions on the two siblings, their signatures align trivially (Hamming distance = 0). Decision-policy thresholds <code>tau_merge=0</code> and <code>kl_merge=0.1</code> are satisfied immediately — KL between fresh-prior posteriors is exactly zero.</li>
<li><strong>Small graph keeps the test fast</strong> — 4 nodes, 3 observations, 3 decide_step calls. Runs in milliseconds even through the AST interpreter.</li>
</ul>
<p><strong>Data scale:</strong> 3 observations + 3 decide_step calls, on a 4-node graph. This is <strong>deliberately the smallest demo in the entire suite</strong> — its job is to prove the trigger semantics fire deterministically and that the chain_head matches the Rust-side canary byte-for-byte. It’s <em>not</em> a demonstration that ABNG-driven structural adaptation is competitive against e.g.&nbsp;Mondrian Forests on 10⁵-row tabular benchmarks. That comparison is genuine future work — engineering a Grow-firing or Split-firing workload at scale, then measuring vs.&nbsp;baseline tree learners.</p>
</section>
<section id="code-sketch-4" class="level3">
<h3 class="anchored" data-anchor-id="code-sketch-4">Code sketch</h3>
<div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb8-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> main() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb8-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> g <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> build_adaptive_graph(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">42</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb8-3"></span>
<span id="cb8-4">    abng_observe(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.10</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb8-5">    abng_observe(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb8-6">    abng_observe(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.40</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb8-7"></span>
<span id="cb8-8">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb8-9">    <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">while</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb8-10">        <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> counts <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> abng_decide_step(g)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// [grow, split, merge, prune, compress, freeze]</span></span>
<span id="cb8-11">        i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb8-12">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb8-13"></span>
<span id="cb8-14">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> merge_count <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> abng_action_count(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// → 1</span></span>
<span id="cb8-15">    print(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"merge_count: "</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> to_string(merge_count))<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb8-16">    print(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"chain_head: "</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> abng_chain_head(g))<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// → d064fb08…7807</span></span>
<span id="cb8-17"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div>
</section>
<section id="connection-to-existing-techniques-5" class="level3">
<h3 class="anchored" data-anchor-id="connection-to-existing-techniques-5">Connection to existing techniques</h3>
<table class="table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th>Technique</th>
<th>Adaptive structure?</th>
<th>Triggers</th>
<th>Domain</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Mondrian Forests</strong> (Lakshminarayanan 2014)</td>
<td>Yes (stochastic splits)</td>
<td>Online</td>
<td>Tabular</td>
</tr>
<tr class="even">
<td><strong>Bayesian Additive Regression Trees</strong> (Chipman 2010)</td>
<td>Yes (stochastic)</td>
<td>Batch</td>
<td>Tabular</td>
</tr>
<tr class="odd">
<td><strong>Hoeffding Trees / VFDT</strong> (Domingos 2000)</td>
<td>Yes (statistical)</td>
<td>Streaming</td>
<td>Tabular</td>
</tr>
<tr class="even">
<td><strong>Concept-drift adaptive learners</strong> (ADWIN, Page-Hinkley)</td>
<td>Yes (drift-triggered)</td>
<td>Streaming</td>
<td>Various</td>
</tr>
<tr class="odd">
<td><strong>Neural Architecture Search</strong> (Zoph 2017)</td>
<td>Yes (optimization-based)</td>
<td>Offline</td>
<td>Deep learning</td>
</tr>
<tr class="even">
<td><strong>ABNG decide_step</strong></td>
<td><strong>Yes (deterministic, audit-tracked)</strong></td>
<td>Six action types</td>
<td>Bayesian belief graphs</td>
</tr>
</tbody>
</table>
<p>The closest analog is <strong>Hoeffding Trees</strong> — both fire structural changes based on running statistics. The differences: Hoeffding Trees use Hoeffding-bound-based split tests; ABNG uses fixed thresholds on a 14-element decision-policy tensor. ABNG also adds the audit-chain integration that lets you <em>prove</em> what structural changes happened and when.</p>
</section>
<section id="what-this-enables-5" class="level3">
<h3 class="anchored" data-anchor-id="what-this-enables-5">What this enables</h3>
<ul>
<li><strong>Self-organizing ML models</strong> — graph capacity scales with workload complexity; no manual architecture design.</li>
<li><strong>Streaming / online learning</strong> — non-stationary distributions cause Grow on new modes, Prune on dead branches.</li>
<li><strong>Memory-efficient ML</strong> — Compress unused subtrees; Merge redundant leaves; Prune inactive nodes.</li>
<li><strong>Hierarchical model distillation</strong> — stable subtrees can be Frozen and exported as compiled MLPs.</li>
<li><strong>Continual learning without catastrophic forgetting</strong> — Freeze stable-signature subtrees while training others.</li>
<li><strong>AutoML topology search</strong> — decision-policy thresholds are hyperparameters; the trigger ladder is the search algorithm.</li>
</ul>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Warning
</div>
</div>
<div class="callout-body-container callout-body">
<p><strong>Honest caveat:</strong> the demo proves <strong>Merge</strong> fires deterministically. The other five trigger types (Grow, Split, Prune, Compress, Freeze) are tested at the primitive level but not demonstrated firing in a realistic workload. The “graph adapts to workload complexity” claim is half-substantiated by current demos. Closing this gap is straightforward (more demos with engineered workloads that fire each trigger type) but not yet shipped.</p>
</div>
</div>
<hr>
</section>
</section>
<section id="capability-7-calibration-ece" class="level2">
<h2 class="anchored" data-anchor-id="capability-7-calibration-ece">Capability 7 — Calibration / ECE</h2>
<p><strong>Demo:</strong> Hand-constructed well-calibrated vs miscalibrated outcomes (<code>tests/test_abng_calibration_cjcl.rs</code> + <code>calibration_source.rs</code>)</p>
<section id="what-this-demo-proves-5" class="level3">
<h3 class="anchored" data-anchor-id="what-this-demo-proves-5">What this demo proves</h3>
<p>Two graphs receive 30 calibration observations each. Same predicted probabilities (<code>p ∈ {0.1, 0.5, 0.9}</code>, 10 each), different outcome distributions:</p>
<table class="table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Graph</th>
<th>Outcome convention</th>
<th>Expected ECE</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>A: well-calibrated</td>
<td>predicted=0.1 → 1 of 10 positive; predicted=0.9 → 9 of 10 positive</td>
<td>&lt; 0.05</td>
</tr>
<tr class="even">
<td>B: miscalibrated</td>
<td>predicted=0.1 → 9 of 10 positive (FLIPPED); predicted=0.9 → 1 of 10 positive</td>
<td>&gt; 0.5</td>
</tr>
</tbody>
</table>
<p>The headline assertion <code>ece_a &lt; ece_b</code> separates the two cleanly. Plus the specific bounds: <code>ece_a &lt; 0.05</code> (well-calibrated achieves near-zero ECE) and <code>ece_b &gt; 0.5</code> (flipped outcomes produce huge ECE).</p>
</section>
<section id="the-math-2" class="level3">
<h3 class="anchored" data-anchor-id="the-math-2">The math</h3>
<p>ABNG uses standard 15-bin reliability-binning ECE (Naeini, Cooper, Hauskrecht 2015):</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Ctext%7BECE%7D%20=%20%5Csum_%7Bb=1%7D%5E%7B15%7D%20%5Cfrac%7B%7CB_b%7C%7D%7BN%7D%20%5C,%5Cbigl%7C%5C,%5Ctext%7Baccuracy%7D(B_b)%20-%20%5Ctext%7Bconfidence%7D(B_b)%5C,%5Cbigr%7C%0A"></p>
<p>where:</p>
<ul>
<li><code>B_b</code> is the set of predictions falling in bin <code>b</code> (15 equal-width bins on <code>[0, 1]</code>)</li>
<li><code>accuracy(B_b)</code> = fraction of correct predictions in bin <code>b</code></li>
<li><code>confidence(B_b)</code> = mean predicted probability in bin <code>b</code></li>
<li><code>|B_b| / N</code> = bin’s empirical weight</li>
</ul>
<p>A perfectly calibrated model has <code>accuracy(B_b) = confidence(B_b)</code> for every populated bin → ECE = 0. The miscalibrated graph with flipped outcomes pushes accuracy ≈ 1 - confidence in each bin → ECE saturates near 0.8.</p>
</section>
<section id="why-flip-outcomes-as-the-miscalibration-model" class="level3">
<h3 class="anchored" data-anchor-id="why-flip-outcomes-as-the-miscalibration-model">Why “flip outcomes” as the miscalibration model</h3>
<ul>
<li><strong>Maximum-ECE case.</strong> Predicting 90% confidence and being right 10% of the time is the worst possible outcome — confidence is <em>anti-correlated</em> with truth. This makes the test gate <code>ece_b &gt; 0.5</code> reliable; small numerical jitter doesn’t push the result close to the threshold.</li>
<li><strong>Same prediction distribution → isolates the calibration signal.</strong> Both graphs see the same <code>(p, y)</code> pairs <em>modulo y</em>. Any difference in ECE is attributable to outcomes, not to predictions or to bin populations.</li>
<li><strong>Three reliability bins (0.1, 0.5, 0.9)</strong> span low/mid/high probability range without requiring 100+ observations to populate every bin.</li>
</ul>
<p><strong>Data scale:</strong> 30 calibration observations per graph (2 graphs total = 60 observations). This is the <strong>minimum sufficient</strong> sample size to make the three-bin reliability statistics non-trivially populated and the ECE separation <code>ece_a &lt; 0.05 &lt; 0.5 &lt; ece_b</code> reliable. It does <em>not</em> validate calibration on actual classification tasks (image classifiers, transformer logits, etc.) — those workloads have thousands of (p, y) pairs and 10-100 calibration buckets. Per-leaf ECE on a real classifier is straightforward future work.</p>
</section>
<section id="code-sketch-5" class="level3">
<h3 class="anchored" data-anchor-id="code-sketch-5">Code sketch</h3>
<div class="sourceCode" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb9-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> well_calibrated_run(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">i64</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> node<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">i64</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb9-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// For predicted_prob = 0.1: 1 positive, 9 negative (10% base rate).</span></span>
<span id="cb9-3">    <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb9-4">    <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">while</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb9-5">        <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">if</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> abng_calibration_observe(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> node<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">true</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb9-6">        <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">if</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> abng_calibration_observe(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> node<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">false</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb9-7">        i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb9-8">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb9-9">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// ... same pattern for p=0.5 (5/10) and p=0.9 (9/10) ...</span></span>
<span id="cb9-10"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb9-11"></span>
<span id="cb9-12"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">fn</span> miscalibrated_run(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">i64</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> node<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">i64</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb9-13">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// For predicted_prob = 0.1: 9 positive, 1 negative (FLIPPED).</span></span>
<span id="cb9-14">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// For predicted_prob = 0.9: 1 positive, 9 negative (FLIPPED).</span></span>
<span id="cb9-15">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// ... see source for full pattern ...</span></span>
<span id="cb9-16"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb9-17"></span>
<span id="cb9-18"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> ece_a <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> abng_calibration_ece(g_a<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>     <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// &lt; 0.05</span></span>
<span id="cb9-19"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> ece_b <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> abng_calibration_ece(g_b<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>     <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// &gt; 0.5</span></span></code></pre></div>
</section>
<section id="connection-to-existing-techniques-6" class="level3">
<h3 class="anchored" data-anchor-id="connection-to-existing-techniques-6">Connection to existing techniques</h3>
<table class="table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Technique</th>
<th>Calibration</th>
<th>Per-leaf?</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Platt scaling</strong> (Platt 1999)</td>
<td>Post-hoc logistic regression on logits</td>
<td>No (global)</td>
</tr>
<tr class="even">
<td><strong>Isotonic regression</strong> (Zadrozny 2002)</td>
<td>Monotonic piecewise-constant fit</td>
<td>No (global)</td>
</tr>
<tr class="odd">
<td><strong>Temperature scaling</strong> (Guo et al.&nbsp;2017)</td>
<td>Single-parameter softmax rescale</td>
<td>No (global)</td>
</tr>
<tr class="even">
<td><strong>Beta calibration</strong> (Kull et al.&nbsp;2017)</td>
<td>Beta distribution fit</td>
<td>No (global)</td>
</tr>
<tr class="odd">
<td><strong>Histogram binning</strong> (Zadrozny 2001)</td>
<td>Per-bin frequency estimate</td>
<td>Possible per-leaf</td>
</tr>
<tr class="even">
<td><strong>ABNG calibration</strong></td>
<td>15-bin reliability + ECE</td>
<td><strong>Yes (per-node)</strong></td>
</tr>
</tbody>
</table>
<p>ABNG’s calibration is <strong>per-node</strong> — different leaves can be independently calibrated. This matters in practice: a model can be well-calibrated for European customers but miscalibrated for Asian customers (different leaves of the codebook). Per-leaf ECE makes this visible; global ECE would average it out.</p>
</section>
<section id="what-this-enables-6" class="level3">
<h3 class="anchored" data-anchor-id="what-this-enables-6">What this enables</h3>
<ul>
<li><strong>Risk-sensitive deployment</strong> — a poorly-calibrated 95%-confident prediction is operationally indistinguishable from an honest 60%-confident one — but you’ll act on it differently.</li>
<li><strong>Threshold tuning</strong> — “should I act on predictions above 0.7?” depends on whether 0.7-bin predictions are actually correct 70% of the time.</li>
<li><strong>Model selection in production</strong> — beyond accuracy/F1: ECE is the second-order quality metric.</li>
<li><strong>Active learning targeting</strong> — high-ECE bins are evidence the model needs more training data in that probability range.</li>
<li><strong>Per-segment calibration analysis</strong> — “is my model better calibrated for some user segments than others?” is answerable in one query.</li>
</ul>
<hr>
</section>
</section>
<section id="capability-8-distribution-drift-detection" class="level2">
<h2 class="anchored" data-anchor-id="capability-8-distribution-drift-detection">Capability 8 — Distribution-drift detection</h2>
<p><strong>Demo:</strong> Mean shift from 0 to 0.8 on a 4-D density tracker (<code>tests/test_abng_drift_detection_cjcl.rs</code> + <code>drift_source.rs</code>)</p>
<section id="what-this-demo-proves-6" class="level3">
<h3 class="anchored" data-anchor-id="what-this-demo-proves-6">What this demo proves</h3>
<p>A three-phase workload: train baseline, freeze, observe under same distribution, observe under shifted distribution. Three assertions:</p>
<ol type="1">
<li><strong><code>drift_grew</code></strong> — <code>drift_score(post-shift) &gt; drift_score(same-dist)</code>.</li>
<li><strong><code>meaningful_shift</code></strong> — <code>drift_score(post-shift) &gt; 2 × drift_score(same-dist)</code>. Catches a regression where drift detection becomes noisy.</li>
<li><strong><code>drift_nonneg</code></strong> — both scores are non-negative (no NaN, no negative noise).</li>
</ol>
</section>
<section id="the-math-3" class="level3">
<h3 class="anchored" data-anchor-id="the-math-3">The math</h3>
<p>ABNG’s drift detection is built on a streaming diagonal-Gaussian density tracker. For features <code>φ ∈ ℝᵈ</code>, the tracker maintains:</p>
<ul>
<li>Running mean: <code>μ̂</code> updated via Welford</li>
<li>Running diagonal covariance: <code>Σ̂_diag</code> updated via Welford on <code>(φᵢ - μ̂)²</code></li>
</ul>
<p><code>freeze_drift_baseline(node)</code> snapshots <code>(μ̂_baseline, Σ̂_diag_baseline)</code> at a reference point (typically end of pre-deployment training). After freezing, <code>drift_score</code> measures the divergence between the current tracker and the frozen baseline:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Ctext%7Bdrift%5C_score%7D%20=%20%5Cbigl%5C%7C(%5Chat%7B%5Cmu%7D_%7B%5Ctext%7Bcur%7D%7D%20-%20%5Chat%7B%5Cmu%7D_%7B%5Ctext%7Bbaseline%7D%7D)%5Cbigr%5C%7C%5E2_%7B%5Ctext%7BMahal%7D%7D%20+%20%5Calpha%20%5Ccdot%20%5Cbigl%5C%7C%5CSigma_%7B%5Ctext%7Bcur,diag%7D%7D%20/%20%5CSigma_%7B%5Ctext%7Bbaseline,diag%7D%7D%20-%20%5Cmathbf%7B1%7D%5Cbigr%5C%7C%5E2%0A"></p>
<p>— a weighted combination of mean shift (under baseline covariance) and covariance ratio deviation. Pure mean shift (this demo’s case) gives a score dominated by the first term.</p>
</section>
<section id="why-a-pure-mean-shift" class="level3">
<h3 class="anchored" data-anchor-id="why-a-pure-mean-shift">Why a pure mean shift</h3>
<ul>
<li><strong>Translation, not rescaling</strong> isolates the mean-shift contribution to drift_score. A combined “scale and rotate” shift would test more, but the basic mean-shift case is the simplest sufficient demonstration.</li>
<li><strong>0 to 0.8 is large enough to be unambiguous.</strong> Both distributions have ~0.01 jitter; shifting the center by 0.8 is 80x the jitter — the drift signal saturates well above the floor.</li>
<li><strong>Same density-tracker geometry</strong> — same dimension count (4-D features), same covariance scale, same batch sizes (16 each post-baseline). Only the location changes.</li>
</ul>
<p><strong>Data scale:</strong> 32 baseline samples + 16 same-distribution + 16 shifted = 64 density observations total, on a single node. This proves the <code>drift_score</code> machinery responds correctly to a controlled mean shift; it does <strong>not</strong> characterize behavior under realistic concept drift (gradual drift, abrupt drift, recurring drift, periodic shift). Production ML drift detection is typically validated on streaming corpora with millions of points across months of real customer behavior — that’s a workload-grade benchmark, not a research demo, and it’s explicitly future work.</p>
</section>
<section id="code-sketch-6" class="level3">
<h3 class="anchored" data-anchor-id="code-sketch-6">Code sketch</h3>
<div class="sourceCode" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb10-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> g <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> build_drift_graph(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">7</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb10-2"></span>
<span id="cb10-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Phase 0: train baseline density on N(0, 0.01).</span></span>
<span id="cb10-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> baseline_batch <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> density_batch(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">32</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb10-5">abng_density_observe(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> baseline_batch)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb10-6"></span>
<span id="cb10-7">abng_freeze_drift_baseline(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>     <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// snapshot reference state</span></span>
<span id="cb10-8"></span>
<span id="cb10-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Phase A: same distribution.</span></span>
<span id="cb10-10"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> same_batch <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> density_batch(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb10-11">abng_density_observe(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> same_batch)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb10-12"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> drift_low <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> abng_drift_score(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb10-13"></span>
<span id="cb10-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Phase B: shifted distribution N(0.8, 0.01).</span></span>
<span id="cb10-15"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> shifted_batch <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> density_batch(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb10-16">abng_density_observe(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> shifted_batch)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb10-17"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> drift_high <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> abng_drift_score(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb10-18"></span>
<span id="cb10-19"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// drift_high &gt; 2 * drift_low (the demo's headline assertion)</span></span></code></pre></div>
</section>
<section id="connection-to-existing-techniques-7" class="level3">
<h3 class="anchored" data-anchor-id="connection-to-existing-techniques-7">Connection to existing techniques</h3>
<table class="table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th>Technique</th>
<th>Statistic</th>
<th>Streaming?</th>
<th>Reference required</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Kolmogorov-Smirnov test</strong></td>
<td>Max CDF difference</td>
<td>No</td>
<td>Yes</td>
</tr>
<tr class="even">
<td><strong>Population Stability Index (PSI)</strong></td>
<td>Discrete distribution distance</td>
<td>No (batch)</td>
<td>Yes</td>
</tr>
<tr class="odd">
<td><strong>Maximum Mean Discrepancy</strong> (Gretton 2012)</td>
<td>Kernel-based difference</td>
<td>Possible</td>
<td>Yes</td>
</tr>
<tr class="even">
<td><strong>Page-Hinkley test</strong></td>
<td>Change-point detection</td>
<td>Yes</td>
<td>Implicit</td>
</tr>
<tr class="odd">
<td><strong>ADWIN</strong> (Bifet 2007)</td>
<td>Adaptive windowing</td>
<td>Yes</td>
<td>None (auto-detect)</td>
</tr>
<tr class="even">
<td><strong>DDM</strong> (Gama 2004)</td>
<td>Drift Detection Method (error rate)</td>
<td>Yes</td>
<td>None</td>
</tr>
<tr class="odd">
<td><strong>ABNG drift_score</strong></td>
<td>Mahalanobis-style on density tracker</td>
<td>Streaming</td>
<td>Frozen baseline</td>
</tr>
</tbody>
</table>
<p>The closest analog is <strong>PSI</strong> (widely used in finance) — both compute a deviation-from-baseline distribution score. The differences: PSI is for discrete histograms; ABNG operates on continuous diagonal-Gaussian density. PSI is computed batch; ABNG accumulates streamingly via Welford.</p>
</section>
<section id="what-this-enables-7" class="level3">
<h3 class="anchored" data-anchor-id="what-this-enables-7">What this enables</h3>
<ul>
<li><strong>Production ML monitoring</strong> — alert ops when input distribution drifts from training; trigger retraining.</li>
<li><strong>Continual learning auto-trigger</strong> — drift signal is the cue to incorporate new data into the model.</li>
<li><strong>A/B test data validation</strong> — confirm the new dataset’s distribution matches the original before deploying a new model.</li>
<li><strong>Sensor / pipeline integrity</strong> — detect upstream data-source switches, sensor drift, schema changes.</li>
<li><strong>Customer-behavior modeling</strong> — recommender systems trained on Q1 data drifting against Q3 user behavior.</li>
</ul>
<hr>
</section>
</section>
<section id="capability-9-log-compaction" class="level2">
<h2 class="anchored" data-anchor-id="capability-9-log-compaction">Capability 9 — Log compaction</h2>
<p><strong>Demo:</strong> Multi-node observation stream + <code>compact_log</code> (<code>tests/test_abng_compact_log_cjcl.rs</code> + <code>compact_source.rs</code>)</p>
<section id="what-this-demo-proves-7" class="level3">
<h3 class="anchored" data-anchor-id="what-this-demo-proves-7">What this demo proves</h3>
<p>Five training observations across three nodes, then <code>compact_log</code> fires:</p>
<ol type="1">
<li><strong><code>emitted == 3</code></strong> — exactly one StatsSnapshot per touched node.</li>
<li><strong><code>audit_grew_by_emitted</code></strong> — <code>audit_post == audit_pre + emitted</code>.</li>
<li><strong><code>chain_advanced</code></strong> — chain_head changed (events were properly chain-linked).</li>
<li><strong><code>verify_chain == Ok</code> post-compact</strong> — the new events don’t break integrity.</li>
</ol>
</section>
<section id="the-math-and-whats-deferred" class="level3">
<h3 class="anchored" data-anchor-id="the-math-and-whats-deferred">The math (and what’s deferred)</h3>
<p><code>compact_log(g, until_seq)</code> walks events in <code>[0, until_seq)</code>, tracks the set of distinct node IDs touched, and for each emits one <code>StatsSnapshot</code> event whose payload is <code>(node_id, sha256(canonical_bytes(node.stats)))</code>.</p>
<p>This sets up the <strong>read-side optimization</strong> that ships in a future phase: when <code>smart_replay</code> encounters a <code>StatsSnapshot</code>, it can:</p>
<ol type="1">
<li>Trust the snapshot’s <code>stats_hash</code> matches the node’s <code>canonical_bytes</code> (consistency check, shipped today).</li>
<li>Skip the <code>node.observe(value)</code> mutation for subsequent <code>BeliefUpdate</code> events in the same node’s pre-snapshot range, since the snapshot subsumes their cumulative effect.</li>
<li>Install the <code>stats</code> directly from the per-node section’s <code>canonical_bytes</code> instead of replaying from observations.</li>
</ol>
<p>The shipped layer is the <strong>marker emission</strong> + <strong>chain-integrity preservation</strong> + <strong>smart_replay consistency check</strong>. The cycle-saving fast-forward is Phase 0.6 work.</p>
</section>
<section id="code-sketch-7" class="level3">
<h3 class="anchored" data-anchor-id="code-sketch-7">Code sketch</h3>
<div class="sourceCode" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb11-1">abng_observe(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb11-2">abng_observe(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.2</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb11-3">abng_observe(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb11-4">abng_observe(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb11-5">abng_observe(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.9</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb11-6"></span>
<span id="cb11-7"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> audit_pre <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> abng_audit_len(g)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>             <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// e.g. 8</span></span>
<span id="cb11-8"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> emitted <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> abng_compact_log(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> audit_pre)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// → 3 (3 touched nodes)</span></span>
<span id="cb11-9"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> audit_post <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> abng_audit_len(g)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// 8 + 3 = 11</span></span>
<span id="cb11-10">print(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"verify_chain: "</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> to_string(abng_verify_chain(g)))<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// → true</span></span></code></pre></div>
<p><strong>Data scale:</strong> 5 observations across 3 nodes (smallest in the suite). This proves the marker emission is deterministic and chain-integrity-preserving. The interesting scale claim — “compact_log + smart-replay can fast-forward replay of a 10⁶-event audit log” — is <strong>deferred</strong> to Phase 0.6 along with the actual cycle-saving optimization.</p>
</section>
<section id="connection-to-existing-techniques-8" class="level3">
<h3 class="anchored" data-anchor-id="connection-to-existing-techniques-8">Connection to existing techniques</h3>
<table class="table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Technique</th>
<th>Mechanism</th>
<th>Domain</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Database WAL compaction</strong></td>
<td>Replace old WAL records with snapshot</td>
<td>RDBMS</td>
</tr>
<tr class="even">
<td><strong>LSM tree compaction</strong></td>
<td>Merge sorted runs</td>
<td>KV stores (RocksDB, Cassandra)</td>
</tr>
<tr class="odd">
<td><strong>Chandy-Lamport snapshotting</strong></td>
<td>Distributed-system state capture</td>
<td>Distributed systems</td>
</tr>
<tr class="even">
<td><strong>Checkpoint-restart</strong></td>
<td>Periodic full-state save</td>
<td>HPC, ML training</td>
</tr>
<tr class="odd">
<td><strong>ABNG compact_log + StatsSnapshot</strong></td>
<td>Event-log markers, hash-chain-integrated</td>
<td>ML training audit logs</td>
</tr>
</tbody>
</table>
<p>The closest analog is <strong>WAL log compaction</strong> in databases — periodic snapshot points that let you truncate older log entries without losing recoverability. ABNG adds the chain-integrity property: the snapshot itself is a hash-linked event, so post-compaction you can still cryptographically verify nothing was tampered with.</p>
</section>
<section id="what-this-enables-8" class="level3">
<h3 class="anchored" data-anchor-id="what-this-enables-8">What this enables</h3>
<ul>
<li><strong>Long-running training without log explosion</strong> — periodic compaction preserves checkpoints while bounding audit-log growth.</li>
<li><strong>Snapshot-based debugging</strong> — each StatsSnapshot is a “save point” for forensic replay.</li>
<li><strong>Foundation for the Phase 0.6 fast-forward optimization</strong> — smart-replay can fast-forward past observation runs by trusting the StatsSnapshot.</li>
<li><strong>Distributed training checkpointing</strong> — workers compact-log periodically; central server replays the compact form.</li>
</ul>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Warning
</div>
</div>
<div class="callout-body-container callout-body">
<p><strong>The cycle-saving optimization is deferred to Phase 0.6.</strong> Today the marker layer ships with chain-integrity preservation; the actual fast-forward that would make this user-visibly faster lands later.</p>
</div>
</div>
<hr>
</section>
</section>
<section id="capability-10-maturity-observability" class="level2">
<h2 class="anchored" data-anchor-id="capability-10-maturity-observability">Capability 10 — Maturity observability</h2>
<p><strong>Demo:</strong> Stable observation stream + decide_step (<code>tests/test_abng_maturity_inspection_cjcl.rs</code> + <code>maturity_source.rs</code>)</p>
<section id="what-this-demo-proves-8" class="level3">
<h3 class="anchored" data-anchor-id="what-this-demo-proves-8">What this demo proves</h3>
<p>Read <code>abng_node_maturity</code> at two timepoints: fresh graph (T=0) and after 10 stable observations + 5 decide_step calls (T=1). Five assertions:</p>
<ol type="1">
<li><strong><code>sum_at_t0 == 0.0</code></strong> — fresh graph, all maturity flags are zero.</li>
<li><strong><code>maturity_evolved</code></strong> — sum at T=1 strictly exceeds sum at T=0.</li>
<li><strong><code>calib_binary</code> AND <code>unc_binary</code></strong> — positions [1] (calibration_stable) and [2] (uncertainty_stable) are strict 0/1 flags.</li>
<li><strong><code>samples_seen_nonneg</code></strong> — position [0] (samples_seen) is non-negative.</li>
<li><strong><code>trust_in_range</code></strong> — position [3] (trust_level) is in <code>[0, 1]</code>.</li>
</ol>
</section>
<section id="the-semantics" class="level3">
<h3 class="anchored" data-anchor-id="the-semantics">The semantics</h3>
<p><code>abng_node_maturity(g, node)</code> returns a 4-element f64 tensor:</p>
<table class="table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th>Index</th>
<th>Field</th>
<th>Type</th>
<th>Definition</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>0</td>
<td><code>samples_seen</code></td>
<td>non-negative count</td>
<td>Total observations applied to this node</td>
</tr>
<tr class="even">
<td>1</td>
<td><code>calibration_stable</code></td>
<td>binary 0/1</td>
<td>True if 3-window ECE history’s <code>\|Δ\|</code> &lt; <code>ece_stability_max_delta</code></td>
</tr>
<tr class="odd">
<td>2</td>
<td><code>uncertainty_stable</code></td>
<td>binary 0/1</td>
<td>True if 3-window σ history shows ratio &lt; <code>sigma_stability_ratio</code></td>
</tr>
<tr class="even">
<td>3</td>
<td><code>trust_level</code></td>
<td>continuous [0, 1]</td>
<td>Composite trust score from signature stability + maturity flags</td>
</tr>
</tbody>
</table>
<p>The flags advance through <code>decide_step</code> calls plus the underlying <code>signature_stable_calls</code> counter that increments on consecutive matching signatures.</p>
</section>
<section id="why-a-stable-observation-stream" class="level3">
<h3 class="anchored" data-anchor-id="why-a-stable-observation-stream">Why a stable observation stream</h3>
<p>The maturity flags advance under two conditions:</p>
<ul>
<li><code>signature_stable_calls</code> increments when consecutive <code>decide_step</code> calls observe an unchanged signature</li>
<li><code>samples_seen</code> simply counts observations</li>
</ul>
<p>By choosing identical observations (<code>abng_observe(g, n, 0.1)</code> repeatedly), the signature stays exactly identical across decide_step calls — the stability counter climbs deterministically. With 10 observations + 5 decide_step calls, at least the stability flags should flip.</p>
<p>Realistic data (varying observations) would also work but with lower predictability — the signature might wobble enough that <code>signature_stable_calls</code> resets occasionally. The stable-stream choice makes the demo deterministic.</p>
<p><strong>Data scale:</strong> 10 observations + 5 decide_step calls. Deliberately tiny — the workload exists to prove “maturity flags evolve from 0 → 1 under stable training,” not to characterize maturity dynamics under realistic noisy ML training. A real production-monitoring deployment would observe maturity over thousands of decide_step calls across hours/days of training.</p>
</section>
<section id="code-sketch-8" class="level3">
<h3 class="anchored" data-anchor-id="code-sketch-8">Code sketch</h3>
<div class="sourceCode" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb12-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> g <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> build_maturity_graph(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">42</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb12-2"></span>
<span id="cb12-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> mat0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> abng_node_maturity(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// [0, 0, 0, 0] — fresh</span></span>
<span id="cb12-4"></span>
<span id="cb12-5"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb12-6"><span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">while</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb12-7">    abng_observe(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb12-8">    abng_observe(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb12-9">    i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb12-10"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb12-11"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> j <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb12-12"><span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">while</span> j <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb12-13">    abng_decide_step(g)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb12-14">    j <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> j <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb12-15"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb12-16"></span>
<span id="cb12-17"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> mat1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> abng_node_maturity(g<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb12-18"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> r0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> mat1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>get([<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>])<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// samples_seen (count &gt; 0)</span></span>
<span id="cb12-19"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> r1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> mat1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>get([<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// calibration_stable (0 or 1)</span></span>
<span id="cb12-20"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> r2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> mat1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>get([<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>])<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// uncertainty_stable (0 or 1)</span></span>
<span id="cb12-21"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> r3 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> mat1<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>get([<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>])<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// trust_level (continuous [0, 1])</span></span></code></pre></div>
</section>
<section id="connection-to-existing-techniques-9" class="level3">
<h3 class="anchored" data-anchor-id="connection-to-existing-techniques-9">Connection to existing techniques</h3>
<table class="table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Technique</th>
<th>Convergence diagnostic</th>
<th>Domain</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Gelman-Rubin R̂</strong></td>
<td>Cross-chain variance ratio</td>
<td>MCMC convergence</td>
</tr>
<tr class="even">
<td><strong>Effective Sample Size (ESS)</strong></td>
<td>Autocorrelation-based</td>
<td>MCMC convergence</td>
</tr>
<tr class="odd">
<td><strong>Validation-loss plateau detection</strong></td>
<td>Loss derivative threshold</td>
<td>Deep learning training</td>
</tr>
<tr class="even">
<td><strong>EarlyStopping callback</strong></td>
<td>Patience + monitor metric</td>
<td>sklearn/keras training</td>
</tr>
<tr class="odd">
<td><strong>ML monitoring stacks</strong> (Arize, WhyLabs)</td>
<td>External time-series dashboards</td>
<td>Production ML</td>
</tr>
<tr class="even">
<td><strong>ABNG node_maturity</strong></td>
<td>Per-node 4-tuple</td>
<td>Bayesian belief graphs</td>
</tr>
</tbody>
</table>
<p>The closest analog in ML practice is <strong>EarlyStopping</strong> + production monitoring — both ask “has my model converged enough to deploy?” ABNG’s contribution: per-node granularity (you can see which leaves have stabilized) and cryptographic integrity (the maturity values are derived from the same audit-tracked state, not an external log).</p>
</section>
<section id="what-this-enables-9" class="level3">
<h3 class="anchored" data-anchor-id="what-this-enables-9">What this enables</h3>
<ul>
<li><strong>Production ML monitoring dashboard</strong> — “has my model converged?” → check maturity per-node.</li>
<li><strong>Auto-stop training</strong> — stop when maturity flags have all stabilized (avoid wasted compute).</li>
<li><strong>Hyperparameter tuning</strong> — which learning rate makes flags settle fastest?</li>
<li><strong>Convergence-failure debugging</strong> — which flag never reaches 1? — points at the failing component.</li>
<li><strong>Production rollout gating</strong> — deploy-time check: only ship models where <code>calibration_stable == 1</code>.</li>
<li><strong>Federated learning aggregation</strong> — aggregate worker updates only from nodes where maturity has stabilized.</li>
</ul>
<hr>
</section>
</section>
<section id="capability-11-end-to-end-determinism" class="level2">
<h2 class="anchored" data-anchor-id="capability-11-end-to-end-determinism">Capability 11 — End-to-end determinism</h2>
<p><strong>Demo:</strong> Universal property — every CJC-Lang demo asserts it.</p>
<section id="what-every-demo-proves-1" class="level3">
<h3 class="anchored" data-anchor-id="what-every-demo-proves-1">What every demo proves</h3>
<p>Every CJC-Lang demo runs through both <code>cjc-eval</code> (AST tree-walk) AND <code>cjc-mir-exec</code> (MIR register-machine), then asserts byte-equal printed output. Across the 13 ABNG demo files, this constitutes ~95 byte-equal parity assertions over a vocabulary of ~30 <code>abng_*</code> builtins.</p>
<p>The strongest single instance: the <strong>adaptive triggers demo’s <code>chain_head</code> matches the Rust-side <code>decide_step_canary</code> byte-for-byte</strong> (<code>d064fb08…7807</code>). Same workload, three pipelines (direct Rust API + AST eval + MIR exec), bit-identical SHA-256 over 23+ audit events.</p>
</section>
<section id="the-architectural-choices-that-make-this-work" class="level3">
<h3 class="anchored" data-anchor-id="the-architectural-choices-that-make-this-work">The architectural choices that make this work</h3>
<p>Three load-bearing decisions enforced across the workspace:</p>
<ol type="1">
<li><p><strong>Kahan compensated summation</strong> for all FP reductions (<code>KahanAccumulatorF64</code>). Naive <code>Σxᵢ</code> accumulates rounding error in O(n·ε); Kahan reduces this to O(ε). More importantly, <strong>Kahan is order-deterministic for fixed input order</strong> — no SIMD-introduced reordering, no FMA fusion. This is the single biggest source of “approximately deterministic” failure in standard ML stacks.</p></li>
<li><p><strong><code>BTreeMap</code>/<code>BTreeSet</code> only</strong>, never <code>HashMap</code>. Hash maps with random hashing have iteration order that depends on the per-process seed — different runs can iterate in different orders, and any code that depends on iteration order (e.g., reduction loops) becomes non-deterministic. BTreeMap iterates in sorted key order, every time.</p></li>
<li><p><strong><code>SplitMix64</code> with explicit seed threading</strong>. No thread-local randomness, no time-based seeds, no platform-specific entropy. Every random draw is reproducible from <code>(initial_seed, operation_history)</code>.</p></li>
</ol>
<p>These three choices aren’t aspirational — they’re enforced by the parity gates. If anyone introduces FMA in <code>cjc-runtime/linalg.rs</code> tomorrow, every parity test fails simultaneously with byte-divergent output.</p>
</section>
<section id="what-this-means-for-ml-practitioners" class="level3">
<h3 class="anchored" data-anchor-id="what-this-means-for-ml-practitioners">What this means for ML practitioners</h3>
<p>ML reproducibility is famously hard. A typical “deterministic” PyTorch model can produce different bytes across runs due to:</p>
<ul>
<li>CUDA atomic ops (non-deterministic reduction order on GPU)</li>
<li>cuDNN algorithm selection (can pick different kernels per run)</li>
<li>Multi-threaded CPU reductions (thread-scheduling non-determinism)</li>
<li>FMA fusion (compiler-dependent instruction selection)</li>
<li>Hash-randomization in Python’s <code>dict</code></li>
</ul>
<p>Each of these has been a real production-incident root cause. ABNG’s parity gates <em>prove</em> the absence of these bugs every test run.</p>
</section>
<section id="connection-to-the-reproducibility-crisis-literature" class="level3">
<h3 class="anchored" data-anchor-id="connection-to-the-reproducibility-crisis-literature">Connection to the reproducibility crisis literature</h3>
<table class="table">
<colgroup>
<col style="width: 50%">
<col style="width: 50%">
</colgroup>
<thead>
<tr class="header">
<th>Source</th>
<th>Claim</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Pineau et al.&nbsp;2021, “Improving Reproducibility in ML Research”</strong></td>
<td>Most ML papers can’t be reproduced even with the same code</td>
</tr>
<tr class="even">
<td><strong>Hutson 2018, “Artificial intelligence faces reproducibility crisis”</strong></td>
<td>Structural problem in the field</td>
</tr>
<tr class="odd">
<td><strong>TensorFlow Determinism Project</strong> (<code>tf.config.experimental.enable_op_determinism</code>)</td>
<td>Best-effort, with known gaps</td>
</tr>
<tr class="even">
<td><strong>PyTorch determinism guide</strong></td>
<td>Documents environmental flags; doesn’t guarantee bit-equality</td>
</tr>
<tr class="odd">
<td><strong>ML reproducibility checklists</strong> (NeurIPS 2019+)</td>
<td>Process-level requirements</td>
</tr>
<tr class="even">
<td><strong>ABNG parity gates</strong></td>
<td>Cryptographic byte-equality across pipelines</td>
</tr>
</tbody>
</table>
<p>ABNG’s contribution to this conversation: <strong>byte-equality is achievable</strong> if you commit to determinism-by-construction at every layer (no FMA, no random hash, no thread-local RNG). The parity gates <em>prove</em> it run by run.</p>
</section>
<section id="what-this-enables-10" class="level3">
<h3 class="anchored" data-anchor-id="what-this-enables-10">What this enables</h3>
<ul>
<li><strong>Reproducibility</strong> — same seed → same model bytes; papers ship verifiable results.</li>
<li><strong>Cross-platform deployment</strong> — train on Linux, deploy on Windows → byte-identical model (modulo cross-platform CI verification, which is the next piece of work).</li>
<li><strong>Distributed training</strong> — deterministic operations enable provable consistency without per-step coordination.</li>
<li><strong>Regulatory replay</strong> — regulator can re-run training and verify every byte.</li>
<li><strong>CI / regression detection</strong> — byte-equal output makes test-failure debugging trivial — diff lines, not “it’s flaky.”</li>
<li><strong>Differential testing across executor versions</strong> — new optimizer? Old vs new must produce same bytes for all valid programs.</li>
</ul>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Warning
</div>
</div>
<div class="callout-body-container callout-body">
<p><strong>Cross-platform determinism is not yet verified.</strong> All canary hashes were computed on Windows. They <em>should</em> hold on Linux/macOS (Kahan, no FMA, BTreeMap), but this isn’t yet tested in CI. Adding GitHub Actions CI that runs the suite on three platforms and compares the canaries is a one-day project — and one of the first items on the Phase 0.6 list.</p>
</div>
</div>
<hr>
</section>
</section>
<section id="phase-0.5-in-numbers" class="level2">
<h2 class="anchored" data-anchor-id="phase-0.5-in-numbers">Phase 0.5 in numbers</h2>
<p>The current ABNG state is the result of Phase 0.5, which shipped:</p>
<table class="table">
<colgroup>
<col style="width: 50%">
<col style="width: 50%">
</colgroup>
<thead>
<tr class="header">
<th>Item</th>
<th>What it added</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>v12 wire-format bump</strong></td>
<td>Per-node <code>provenance_stamp_hash</code> field + new <code>0x1C ProvenanceStamped</code> audit kind; <code>NodeStats::canonical_bytes</code> extended 24B → 32B to capture the Kahan compensation register</td>
</tr>
<tr class="even">
<td><strong><code>smart_replay</code> API</strong></td>
<td>Opt-in tamper detection via StatsSnapshot consistency check (cycle-saving fast-forward layer deferred to Phase 0.6)</td>
</tr>
<tr class="odd">
<td><strong>TOML <code>--config</code> files for <code>cjcl abng train</code></strong></td>
<td>Hand-rolled minimal TOML parser (zero external deps), full schema for graph / codebook / leaf_head / blr_prior / density / calibration / decision_policy / training / output sections</td>
</tr>
<tr class="even">
<td><strong>Chess RL v2.6 retrofit scaffold</strong></td>
<td>Sibling test file demonstrating the API pattern; locked v2.6 weight-hash canary</td>
</tr>
<tr class="odd">
<td><strong>6 capability demos</strong></td>
<td>OOD detection, adaptive triggers, calibration, drift, compact_log, maturity — all in CJC-Lang source with AST↔︎MIR parity gates</td>
</tr>
<tr class="even">
<td><strong>3 application demos</strong></td>
<td>PINN, tabular GP-like, lineage attestation — both Rust-direct AND CJC-Lang versions</td>
</tr>
</tbody>
</table>
<p>Net impact: <strong>906 → 960 tests, +54 in this final round, 0 failures across the whole suite.</strong></p>
</section>
<section id="whats-next-and-why-interpreter-compiler-work-matters" class="level2">
<h2 class="anchored" data-anchor-id="whats-next-and-why-interpreter-compiler-work-matters">What’s next — and why interpreter / compiler work matters</h2>
<p>ABNG is genuinely useful for a specific niche: <strong>regulated ML training where you need Bayesian uncertainty + cryptographically-verifiable lineage on tabular data.</strong> The demos prove the niche capabilities; they don’t prove ABNG should replace PyTorch.</p>
<p>Three things stand between current ABNG and production readiness:</p>
<section id="cross-platform-determinism-ci" class="level3">
<h3 class="anchored" data-anchor-id="cross-platform-determinism-ci">Cross-platform determinism CI</h3>
<p>The locked canary hashes are Windows-only today. They <em>should</em> hold across platforms (the architecture is designed for it), but this is unverified. Adding GitHub Actions CI that runs the full test suite on Linux + macOS + Windows and compares the locked canaries is a one-day project that pays off forever — every future commit either preserves cross-platform determinism or fails CI loudly.</p>
</section>
<section id="performance-benchmarks-at-scale" class="level3">
<h3 class="anchored" data-anchor-id="performance-benchmarks-at-scale">Performance benchmarks at scale</h3>
<p>This is the biggest single gap between the current state and production readiness, and it deserves emphasis: <strong>none of the demos in this post test ABNG at production-scale data.</strong> Sample sizes range from 3 (adaptive triggers) to 200 (tabular GP-like). Every demo runs in milliseconds. That’s an asset for test-suite reproducibility but a liability for the “ABNG works at scale” story.</p>
<p>What’s specifically <em>not</em> yet validated:</p>
<table class="table">
<colgroup>
<col style="width: 50%">
<col style="width: 50%">
</colgroup>
<thead>
<tr class="header">
<th>Scale claim</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>“ABNG O(N · d²) beats classical GP O(N³)”</td>
<td>Correct by construction, <strong>never wall-clock-benchmarked vs SVGP / sklearn / GPy</strong> at n ∈ {10³, 10⁴, 10⁵, 10⁶}</td>
</tr>
<tr class="even">
<td>“Per-leaf BLR posterior converges to truth on real PDE problems”</td>
<td>Demonstrated on 1-D heat eq with 40 samples; <strong>never on 2-D Burgers / Navier-Stokes at 10⁴ collocation points</strong></td>
</tr>
<tr class="odd">
<td>“Lineage attestation works on real anonymized clinical-trial data”</td>
<td>Demonstrated on 16-row synthetic; <strong>never on a real Phase II/III trial corpus</strong></td>
</tr>
<tr class="even">
<td>“Drift detection catches realistic concept drift”</td>
<td>Demonstrated on a controlled mean shift; <strong>never on production streaming data</strong></td>
</tr>
<tr class="odd">
<td>“OOD score generalizes to high-D embeddings”</td>
<td>Demonstrated on 4-D codebook-routed inputs; <strong>fundamentally outside scope for unstructured high-D inputs</strong></td>
</tr>
<tr class="even">
<td>“Per-leaf calibration scales to real classifiers”</td>
<td>Demonstrated on 30 synthetic (p, y) pairs; <strong>never on real classifier outputs at 10³+ predictions</strong></td>
</tr>
</tbody>
</table>
<p>Closing these gaps is the bulk of Phase 0.6 + 0.7 work. Each is a real deliverable: pick a benchmark dataset / workload, write the wall-clock measurement code, run it, ship the numbers. Until that’s done, ABNG is empirically demonstrated at small scale and <em>theoretically expected</em> to hold at larger scale.</p>
</section>
<section id="compiler-interpreter-performance" class="level3">
<h3 class="anchored" data-anchor-id="compiler-interpreter-performance">Compiler / interpreter performance</h3>
<p>This is the bigger long-term constraint. The CJC-Lang interpreter is currently 5–50× slower than direct Rust API access for ABNG demos. For research / experimentation that’s fine. For production ML training at scale, it’s a hard ceiling.</p>
<p>The chess RL benchmark hit this wall directly — the bottleneck for v2.5 was interpreter throughput, not ML algorithm design. Specific levers, in increasing order of effort:</p>
<ul>
<li><strong>MIR optimizer passes</strong> — current optimizer has CF + DCE; further passes (loop-invariant code motion, common subexpression elimination, function inlining) are weeks of work each.</li>
<li><strong>Hot-path specialization in <code>cjc-runtime</code></strong> — ABNG already shows the pattern (75 native builtins for what users actually do). Has architectural cost: every native kernel is a feature you can’t compose out of simpler primitives.</li>
<li><strong>AOT compilation</strong> — <code>cjcl compile foo.cjcl → foo.exe</code>. LLVM or Cranelift backend. Months of work but the only path that closes the perf gap meaningfully.</li>
</ul>
<p>Most likely sequencing: continue ABNG (Phase 0.6 work — smart-replay fast-forward, cross-platform CI, lineage case study) while landing incremental MIR optimizer + native-kernel work. AOT is a multi-month commitment that depends on whether CJC-Lang’s long-term identity is <em>research vehicle</em> or <em>production language</em>.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>ABNG lives at <a href="https://github.com/AdamEzzat1/CJC"><code>crates/cjc-abng/</code></a> in the CJC-Lang repository. The 13 demo files are under <code>tests/test_abng_*.rs</code> and <code>tests/abng_demos/</code>. The full test count is 960; the source is open.</p>
</div>
</div>


</section>
</section>

 ]]></description>
  <category>CJC-Lang</category>
  <category>ABNG</category>
  <category>Bayesian</category>
  <category>determinism</category>
  <category>attestation</category>
  <category>experimental</category>
  <guid>https://adamezzat1.github.io/blog/posts/abng-experimental-bayesian-graph/index.html</guid>
  <pubDate>Fri, 08 May 2026 07:00:00 GMT</pubDate>
</item>
<item>
  <title>TidyView v3 and the D-HARHT Memory Engine</title>
  <dc:creator>Adam Ezzat</dc:creator>
  <link>https://adamezzat1.github.io/blog/posts/tidyview-v3-and-dharht/index.html</link>
  <description><![CDATA[ 



<div class="reading-time">
🕑 ~25 min read
</div>
<div class="stats-row">
<div class="stat">
<span class="number">12</span><span class="stat-label">phases</span>
</div>
<div class="stat">
<span class="number">495</span><span class="stat-label">TidyView tests</span>
</div>
<div class="stat">
<span class="number">258</span><span class="stat-label">cjc-data unit tests</span>
</div>
<div class="stat">
<span class="number">~108</span><span class="stat-label">ns/op (DHarht Mem)</span>
</div>
</div>
<div class="callout callout-style-simple callout-tip no-icon callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-title-container flex-fill">
TL;DR
</div>
</div>
<div class="callout-body-container callout-body">
<p>v0.1.7 ships TidyView v3 — twelve phases of focused optimization to CJC-Lang’s data engine — and a new module <code>cjc_data::detcoll</code> containing five deterministic collection types. The headline win is <strong>D-HARHT Memory</strong>, a sealed-lookup table that matches <code>std::HashMap</code> speed (~108 ns/op) while preserving byte-equal iteration order across runs. The new <code>SealedU64Map</code> wrapper is the recommended drop-in for any <code>BTreeMap&lt;u64, V&gt;</code> whose access pattern is “build once, look up many” and where reproducibility matters.</p>
</div>
</div>
<section id="the-arc" class="level2">
<h2 class="anchored" data-anchor-id="the-arc">The arc</h2>
<p>When <a href="../../../blog/posts/tidyview-data-engine/index.html">v0.1.6 shipped TidyView’s adaptive selection engine</a>, I closed the post with a list of “what’s next” items. v0.1.7 is what happened after that list got picked apart, one item at a time, over twelve phases. Each phase fixed a specific bottleneck, shipped a specific bench, and then exposed the next bottleneck.</p>
<p>The phases compound. By Phase 4, joins on categorical keys are 6× faster. By Phase 10, sealed <code>u64</code> lookup matches HashMap speed. By Phase 12, the determinism contract holds even under adversarial collision-heavy workloads.</p>
<p>This post walks through every phase: what it solved, what it cost, what came next.</p>
</section>
<section id="part-1-tidyview-v3" class="level2">
<h2 class="anchored" data-anchor-id="part-1-tidyview-v3">Part 1 — TidyView v3</h2>
<p>The eight TidyView-specific phases (0 through 6) all share a theme: <strong>categorical columns are the right unit of work for analytical queries, but the engine kept paying for <code>String</code> allocations every time a verb touched one.</strong> Each phase eliminated one of those allocations on a different verb.</p>
<section id="phase-0-tidyverse-parity-audit" class="level3">
<h3 class="anchored" data-anchor-id="phase-0-tidyverse-parity-audit">Phase 0 — Tidyverse parity audit</h3>
<p>The starting point: verify the dispatch path from <code>.cjcl</code> source → <code>tidy_dispatch</code> → Rust API was 1:1 with no bypass. Six new tests pin filter / select / arrange / distinct / group_by + summarise. No work, just verification — but it set the contract for everything that followed: any optimization had to keep this dispatch layer pure.</p>
</section>
<section id="phase-1-deterministic-adaptive-dictionary-engine" class="level3">
<h3 class="anchored" data-anchor-id="phase-1-deterministic-adaptive-dictionary-engine">Phase 1 — Deterministic Adaptive Dictionary Engine</h3>
<p>A new module <code>cjc_data::byte_dict</code> shipped six types: <code>ByteStringPool</code>, <code>ByteStrView</code>, <code>AdaptiveCodes</code> (U8/U16/U32/U64 auto-promoting at 256 / 65 536 / 2³² thresholds), <code>ByteDictionary</code> (<code>BTreeMap&lt;Vec&lt;u8&gt;, u64&gt;</code> lookup, <code>frozen</code> flag, <code>CategoryOrdering::FirstSeen|Lexical|Explicit</code>), <code>CategoricalColumn</code>, <code>UnknownCategoryPolicy</code>.</p>
<p>Six-clause determinism contract: integer-only thresholds, BTreeMap (not HashMap), byte-lex (not Unicode) sort, lazy code-width promotion, strict frozen rejection, byte-equal round-trip.</p>
<p>Standalone — Phase 1 didn’t replace anything. Phase 2 is where it started showing up in production.</p>
</section>
<section id="phase-2-cat-aware-group_by-distinct" class="level3">
<h3 class="anchored" data-anchor-id="phase-2-cat-aware-group_by-distinct">Phase 2 — Cat-aware group_by + distinct</h3>
<p>When all <code>group_by</code> keys are <code>Column::Categorical</code>, the lookup <code>BTreeMap</code> keys flip from <code>Vec&lt;String&gt;</code> to <code>Vec&lt;u32&gt;</code> codes. Mixed-type keys auto-fall-back to the string path.</p>
<p>The bench that mattered: 1M rows × 100 unique categorical keys, single-key <code>group_by_fast</code>:</p>
<table class="table">
<thead>
<tr class="header">
<th>Path</th>
<th>Time</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>String-key BTreeMap</td>
<td>859.9 ms</td>
</tr>
<tr class="even">
<td><strong>Cat-aware (<code>Vec&lt;u32&gt;</code> keys)</strong></td>
<td><strong>114.8 ms</strong></td>
</tr>
<tr class="odd">
<td><strong>Speedup</strong></td>
<td><strong>7.49×</strong></td>
</tr>
</tbody>
</table>
<p>The win is purely structural — codes are <code>u32</code> integers, not heap-allocated <code>String</code> clones per row.</p>
</section>
<section id="phase-3-hybrid-streaming-set-op-fast-paths" class="level3">
<h3 class="anchored" data-anchor-id="phase-3-hybrid-streaming-set-op-fast-paths">Phase 3 — Hybrid streaming set-op fast paths</h3>
<p><code>AdaptiveSelection::intersect</code> and <code>union</code> got per-chunk dispatch when one or both operands are <code>Hybrid</code>. Pre-Phase-3, those routed through <code>to_verbatim_mask() → AND/OR → re-classify</code>, allocating a <code>nrows/64</code>-word bitmap per Hybrid operand.</p>
<p>The 4×4 chunk-shape table (<code>Empty | All | Sparse(Vec&lt;u16&gt;) | Dense(Box&lt;[u64]&gt;)</code>) collapses to <strong>5 effective shapes</strong> after Empty/All folding:</p>
<table class="table">
<colgroup>
<col style="width: 50%">
<col style="width: 50%">
</colgroup>
<thead>
<tr class="header">
<th>left × right</th>
<th>path</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>Sparse ∩ Sparse</code></td>
<td>merge-walk on two ascending <code>Vec&lt;u16&gt;</code></td>
</tr>
<tr class="even">
<td><code>Sparse ∩ Dense</code></td>
<td>filter-walk: bit-test each sparse row against partner words</td>
</tr>
<tr class="odd">
<td><code>Dense ∩ Dense</code></td>
<td>per-word AND over <code>chunk_len.div_ceil(64)</code> u64s</td>
</tr>
<tr class="even">
<td><code>Empty ∩ *</code></td>
<td>fold to <code>Empty</code></td>
</tr>
<tr class="odd">
<td><code>All ∩ x</code></td>
<td>fold to <code>x</code> unchanged</td>
</tr>
</tbody>
</table>
<p>Headline bench (favorable case, 100k rows, sparse-chunked × dense-chunked Hybrid):</p>
<pre><code>Phase 3 chunked path:           4.69 μs avg (n=100)
Pre-Phase-3 materialize path:  391.41 μs avg (n=100)
Speedup:                        83.46×</code></pre>
<p>Caveat I had to be honest about: that’s the favorable case. Contiguous-Hybrid (Dense × Dense everywhere) shrinks the win to ~5–10× — still net positive but no longer allocation-bound.</p>
</section>
<section id="phase-4-cat-aware-joins" class="level3">
<h3 class="anchored" data-anchor-id="phase-4-cat-aware-joins">Phase 4 — Cat-aware joins</h3>
<p>This was the bundled deliverable: cat-aware joins <strong>plus</strong> Column ↔︎ CategoricalColumn conversions.</p>
<p>The join path: when every join-key column is <code>Column::Categorical</code> on <strong>both</strong> frames, route through <code>BTreeMap&lt;Vec&lt;u32&gt;, Vec&lt;usize&gt;&gt;</code> with a deterministic per-column <strong>right-code → left-code remap</strong>. Each frame owns its own dictionary, so left-side code 3 ≠ right-side code 3 in general — the per-column remap is what makes cross-frame comparison safe:</p>
<div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb2-1">right_to_left[ki][right_code] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">Some</span>(left_code) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">None</span></span></code></pre></div>
<p><code>None</code> means the level doesn’t exist on the left dictionary; that right row is skipped before BTreeMap insertion (it can never join). Build order via <code>BTreeMap&lt;&amp;str, u32&gt;</code> over left levels — BTree not Hash, so the remap is itself deterministic.</p>
<p>Headline bench (100k × 100k rows, 100 unique categorical levels, single-key inner join):</p>
<table class="table">
<thead>
<tr class="header">
<th>Path</th>
<th>Time</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>String-key</td>
<td>16.82 s</td>
</tr>
<tr class="even">
<td><strong>Cat-aware</strong></td>
<td><strong>2.77 s</strong></td>
</tr>
<tr class="odd">
<td><strong>Speedup</strong></td>
<td><strong>6.08×</strong></td>
</tr>
</tbody>
</table>
</section>
<section id="phase-5-three-deferred-items-in-one-bundle" class="level3">
<h3 class="anchored" data-anchor-id="phase-5-three-deferred-items-in-one-bundle">Phase 5 — Three deferred items in one bundle</h3>
<p>Phase 5 was where I closed all three deferrals from prior phases:</p>
<p><strong>(a) Full Column wiring.</strong> New <code>Column::CategoricalAdaptive(Box&lt;CategoricalColumn&gt;)</code> variant — a sibling of the legacy <code>Column::Categorical</code>. The 21 column-reader sites all dispatch through a <code>to_legacy_categorical()</code> shim (lossless when levels are UTF-8 + no nulls). Hot paths bypass the shim entirely.</p>
<p><strong>(b) Filter-chain Hybrid intersect path.</strong> New <code>predicate_bytecode::evaluate_to_selection</code>. <code>TidyView::filter</code> routes through <code>existing.intersect(fresh)</code> when the existing mask is already <code>Hybrid</code> and not in sparse-path territory — exercising the Phase 3 chunk dispatch in production.</p>
<p><strong>(c) Phase 5 = cat-aware arrange.</strong> When <code>Column::Categorical</code> levels are lex-sorted (the Phase 17 <code>forcats</code> invariant), comparing <code>u32</code> codes is byte-identical to comparing <code>String</code> levels. Pre-resolution per key:</p>
<div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb3-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">enum</span> ArrangeKeyResolved<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">'a</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb3-2">    CatCodes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> codes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">'a</span> [<span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">u32</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> descending<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">bool</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">},</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// pure u32 compare</span></span>
<span id="cb3-3">    Legacy <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> col<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">'a</span> Column<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> descending<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">bool</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">},</span>     <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// string compare</span></span>
<span id="cb3-4"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div>
<p>100k rows × 100 unique categorical levels, single-key ascending sort:</p>
<table class="table">
<thead>
<tr class="header">
<th>Path</th>
<th>Time</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>String compare</td>
<td>92.73 ms</td>
</tr>
<tr class="even">
<td><strong>u32 code compare</strong></td>
<td><strong>9.58 ms</strong></td>
</tr>
<tr class="odd">
<td><strong>Speedup</strong></td>
<td><strong>9.68×</strong></td>
</tr>
</tbody>
</table>
<p>Higher than Phase 4’s join win because arrange’s stable sort calls the comparator ~1.6M times for 100k rows — every eliminated <code>String::cmp</code> counts.</p>
</section>
<section id="phase-6-streaming-summarise-cat-aware-mutate-lazy-optimizer" class="level3">
<h3 class="anchored" data-anchor-id="phase-6-streaming-summarise-cat-aware-mutate-lazy-optimizer">Phase 6 — Streaming summarise + cat-aware mutate + lazy optimizer</h3>
<p><strong>Streaming summarise</strong>: <code>TidyView::summarise_streaming(keys, aggs)</code> walks visible rows ONCE, maintaining a <code>BTreeMap&lt;key, Vec&lt;AccState&gt;&gt;</code> where each accumulator holds running state of constant size:</p>
<table class="table">
<thead>
<tr class="header">
<th><code>StreamingAgg</code></th>
<th><code>AccState</code></th>
<th>Algorithm</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>Count</code></td>
<td><code>{ n: u64 }</code></td>
<td>counter</td>
</tr>
<tr class="even">
<td><code>Sum(col)</code></td>
<td><code>{ sum, c }</code></td>
<td>Kahan running sum</td>
</tr>
<tr class="odd">
<td><code>Mean(col)</code></td>
<td><code>{ sum, c, n }</code></td>
<td>Kahan + count</td>
</tr>
<tr class="even">
<td><code>Min(col)</code> / <code>Max(col)</code></td>
<td><code>{ cur, any }</code></td>
<td>NaN-aware extremum</td>
</tr>
<tr class="odd">
<td><code>Var(col)</code> / <code>Sd(col)</code></td>
<td><code>{ n, mean, m2 }</code></td>
<td>Welford online variance</td>
</tr>
</tbody>
</table>
<p><strong>Memory: O(K · acc_size)</strong> instead of <code>O(N · usize)</code>. For 100M rows / 1000 groups: ~32 KB vs ~800 MB — <strong>25 000× less memory.</strong></p>
<p>CPU savings on 1M × 1000 groups were modest (1.10×, BTreeMap ops dominate at this shape) — but the memory win unlocks workloads that previously OOM’d.</p>
<p><strong>Cat-aware mutate</strong>: <code>mutate("k_copy", DExpr::Col("k"))</code> over a categorical column used to degrade to <code>Column::Str</code>, breaking downstream cat-aware fast paths. Phase 6 adds an early-return at the top of <code>eval_expr_column</code>:</p>
<div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb4-1"><span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">DExpr::</span>Col(name) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> expr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb4-2">    <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">Some</span>(src) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> df<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>get_column(name) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb4-3">        <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">match</span> src <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb4-4">            <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">Column::</span>Categorical <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">..</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">Column::</span>CategoricalAdaptive(_) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb4-5">                <span class="cf" style="color: #003B4F;
background-color: null;
font-style: inherit;">return</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">Ok</span>(src<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>clone())<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb4-6">            <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb4-7">            _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=&gt;</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{}</span></span>
<span id="cb4-8">        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb4-9">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb4-10"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div>
<p><strong>Lazy optimizer</strong>: new <code>ViewNode::StreamingGroupSummarise</code> variant. New pass <code>annotate_streamable_summarise</code> rewrites <code>GroupSummarise</code> → <code>StreamingGroupSummarise</code> when <strong>every</strong> aggregation is streaming-friendly. All-or-nothing — any non-streamable agg keeps the legacy path.</p>
</section>
</section>
<section id="part-2-the-deterministic-collection-family" class="level2">
<h2 class="anchored" data-anchor-id="part-2-the-deterministic-collection-family">Part 2 — The Deterministic Collection Family</h2>
<p>Phase 7 onwards is a different track: not TidyView verbs but the <strong>collections those verbs use under the hood</strong>.</p>
<section id="phase-7-five-deterministic-collection-types" class="level3">
<h3 class="anchored" data-anchor-id="phase-7-five-deterministic-collection-types">Phase 7 — Five deterministic collection types</h3>
<p>A new module <code>cjc_data::detcoll</code> shipped:</p>
<table class="table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th>Type</th>
<th>Niche</th>
<th>Lookup</th>
<th>Iteration</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>IndexVec&lt;I, V&gt;</code></td>
<td>dense <code>IdType → Value</code></td>
<td><code>O(1)</code></td>
<td>insertion order</td>
</tr>
<tr class="even">
<td><code>TinyDetMap&lt;K, V&gt;</code></td>
<td>≤ 16 entries</td>
<td>linear scan</td>
<td>sorted</td>
</tr>
<tr class="odd">
<td><code>SortedVecMap&lt;K, V&gt;</code></td>
<td>small-medium sealed sorted</td>
<td>binary search + range</td>
<td>sorted</td>
</tr>
<tr class="even">
<td><code>DetOpenMap&lt;K, V&gt;</code></td>
<td>sparse mutable equality</td>
<td>open addressing + BTreeMap fallback</td>
<td>undefined-but-deterministic</td>
</tr>
<tr class="odd">
<td><code>DHarht&lt;V&gt;</code></td>
<td>large sealed equality, byte-keys</td>
<td>per-shard hash + microbucket</td>
<td>undefined-but-deterministic</td>
</tr>
</tbody>
</table>
<p>The first four are simple, but <code>DHarht</code> was meant to be the centerpiece — and the first version was <strong>slower than BTreeMap</strong>. The next four phases were about closing that gap.</p>
</section>
<section id="phase-8-slab-singleton-inline-keys-sso" class="level3">
<h3 class="anchored" data-anchor-id="phase-8-slab-singleton-inline-keys-sso">Phase 8 — Slab + singleton + inline-keys SSO</h3>
<p>Three constant-factor improvements:</p>
<ol type="1">
<li><strong>Per-shard typed slab allocator.</strong> Each <code>Shard</code> now owns a single <code>key_pool: Vec&lt;u8&gt;</code>. <code>MicroBucket</code> entries store <code>KeyHandle { offset, len }</code> instead of <code>Vec&lt;u8&gt;</code>. One allocation per shard instead of one per entry.</li>
<li><strong>Singleton front-entry fast path.</strong> First insertion at a prefix → <code>FrontEntry::Singleton { handle, value }</code> (no bucket allocation). Second insertion → promote.</li>
<li><strong>Inline-keys SSO.</strong> <code>KeyHandle</code> carries <code>inline: [u8; 16]</code>. Keys ≤ 16 bytes are stored inline; the slab is bypassed entirely on lookup.</li>
</ol>
<p>Cumulative effect on a categorical-dictionary lookup bench (50k cats, 500k probes):</p>
<table class="table">
<thead>
<tr class="header">
<th>Stage</th>
<th>DHarht/BTreeMap</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Phase 7 baseline</td>
<td>0.34× (DHarht <strong>3× slower</strong>)</td>
</tr>
<tr class="even">
<td>Phase 8a (slab + singleton)</td>
<td>0.48×</td>
</tr>
<tr class="odd">
<td>Phase 8b (+ inline-keys SSO)</td>
<td>0.55×</td>
</tr>
</tbody>
</table>
<p>Three rounds, ~62% improvement in ratio. Still slower than BTreeMap — the gap was clearly architectural, not micro-optimization.</p>
</section>
<section id="phase-9-hash-swap-jump-table" class="level3">
<h3 class="anchored" data-anchor-id="phase-9-hash-swap-jump-table">Phase 9 — Hash swap + jump table</h3>
<p>The categorical dict bench plateaued at 0.55× and I knew the hash function was burning cycles. Phase 9 swapped <code>splitmix64+FNV</code> per chunk for a tighter multiplicative mixer, then replaced the sorted-Vec front directory with a 256-slot jump table for <code>O(1)</code> slot lookup.</p>
<p>Result: marginal improvement on the categorical-dict bench. <strong>Still slower than BTreeMap on this workload.</strong> The bench shape was wrong.</p>
</section>
<section id="phase-10-d-harht-memory-profile-the-real-win" class="level3">
<h3 class="anchored" data-anchor-id="phase-10-d-harht-memory-profile-the-real-win">Phase 10 — D-HARHT Memory profile (the real win)</h3>
<p>Then I got hold of <a href="https://github.com/AdamEzzat1/CJC">the actual D-HARHT blueprint</a> the original architecture was based on, and the gap became obvious. My v.01 was a byte-key implementation; the original D-HARHT Memory profile is <code>u64</code>-keyed with very different choices:</p>
<table class="table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Aspect</th>
<th>D-HARHT Memory (their blueprint)</th>
<th>DHarht v.01 (mine, Phase 7-9)</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Key type</td>
<td><code>u64</code> (no allocation)</td>
<td>arbitrary <code>&amp;[u8]</code> (slab + handles)</td>
</tr>
<tr class="even">
<td>Front directory</td>
<td><strong>16-bit, 65 536 slots, sparse-paged</strong></td>
<td>8-bit, 256 slots, dense</td>
</tr>
<tr class="odd">
<td>Front entry</td>
<td>Packed <code>u64</code> with 3-bit tag</td>
<td>Rust enum</td>
</tr>
<tr class="even">
<td>MicroBucket</td>
<td>4 / 8 / 16 sized</td>
<td>only 16</td>
</tr>
<tr class="odd">
<td>Match</td>
<td>Parallel scalar <code>match_mask</code></td>
<td>Linear scan</td>
</tr>
<tr class="even">
<td>Hash</td>
<td><code>splitmix64</code> finalizer = scatter</td>
<td>Multiplicative + finalize</td>
</tr>
</tbody>
</table>
<p>Phase 10 ported the Memory profile faithfully as a sibling type <code>DHarhtMemory&lt;V&gt;</code>. Then I ran a 4-way bench on the same workload shape the blueprint used (100k u64 keys, 2M random-stream lookups, 3 stable runs):</p>
<table class="table">
<colgroup>
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
</colgroup>
<thead>
<tr class="header">
<th>Backend</th>
<th>Run 1</th>
<th>Run 2</th>
<th>Run 3</th>
<th>Stable avg</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>BTreeMap</td>
<td>266</td>
<td>242</td>
<td>186</td>
<td>~232 ns/op</td>
</tr>
<tr class="even">
<td>HashMap (std SipHash)</td>
<td>148</td>
<td>91</td>
<td>84</td>
<td>~107 ns/op</td>
</tr>
<tr class="odd">
<td>DHarht v.01 (byte-key)</td>
<td>238</td>
<td>185</td>
<td>217</td>
<td>~213 ns/op</td>
</tr>
<tr class="even">
<td><strong>DHarht Memory (u64-key)</strong></td>
<td><strong>105</strong></td>
<td><strong>108</strong></td>
<td><strong>112</strong></td>
<td><strong>~108 ns/op</strong></td>
</tr>
</tbody>
</table>
<p><strong>DHarht Memory ≈ HashMap.</strong> And ~2.2× faster than BTreeMap. Architecture matters. The constant-factor optimizations in Phases 8 and 9 helped at the margins; <strong>the architectural shift from byte-key to u64-key + sparse paged 16-bit front directory was where the 2× win came from</strong>.</p>
<p>DHarht Memory’s run-to-run variance is also the smallest of any backend (±3%) — sealed lookup with cache-resident sparse pages is consistent in a way HashMap (with adaptive table sizing) is not.</p>
</section>
<section id="phase-11-sealedu64map-wrapper-memory-security" class="level3">
<h3 class="anchored" data-anchor-id="phase-11-sealedu64map-wrapper-memory-security">Phase 11 — <code>SealedU64Map</code> wrapper + memory &amp; security</h3>
<p>Phase 10 shipped the architecture. Phase 11 packaged it for production use and ran the comparisons users care about beyond raw speed.</p>
<p><strong><code>SealedU64Map&lt;V&gt;</code></strong> — a thin public wrapper around <code>DHarhtMemory</code> enforcing the <code>build → seal → read-many</code> lifecycle. Surface: <code>new</code>, <code>insert</code>, <code>get</code>, <code>seal</code>, <code>iter_sorted</code>, <code>approx_memory_bytes</code>, <code>shape_hash</code>. The recommended drop-in for any <code>BTreeMap&lt;u64, V&gt;</code> whose access pattern matches.</p>
<p><strong>First wiring demo</strong>: <code>ByteDictionary::seal_with_u64_hash_index()</code> builds a u64-hash content-cache so callers who already have the deterministic 64-bit hash of bytes (from snapshot diffing, content-addressed storage) can look up the dictionary code via <code>lookup_by_hash(hash)</code> without rehashing.</p>
<p><strong>Memory comparison</strong> (50k u64 entries, 8B keys + 8B values):</p>
<table class="table">
<thead>
<tr class="header">
<th>Backend</th>
<th>Total bytes</th>
<th>Bytes/entry</th>
<th>vs HashMap</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>HashMap (std)</td>
<td>1 376 256</td>
<td><strong>27.5</strong></td>
<td>1.0×</td>
</tr>
<tr class="even">
<td>BTreeMap</td>
<td>3 600 000</td>
<td>72.0</td>
<td>2.6×</td>
</tr>
<tr class="odd">
<td><strong>DHarhtMemory</strong></td>
<td>5 319 232</td>
<td><strong>106.4</strong></td>
<td><strong>3.87×</strong></td>
</tr>
</tbody>
</table>
<p>HashMap wins on memory. DHarhtMemory pays ~3.87× more bytes for the 16-bit sparse-paged front directory + per-shard slabs + micro-bucket pools. <strong>That’s the cost of the determinism contract.</strong></p>
<p><strong>Determinism comparison</strong>:</p>
<table class="table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th>Property</th>
<th>BTreeMap</th>
<th>HashMap</th>
<th>DHarhtMemory</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Same input → same iter order, same process</td>
<td>✅</td>
<td><strong>❌</strong> randomized</td>
<td>✅</td>
</tr>
<tr class="even">
<td>Same input → same iter order, across processes</td>
<td>✅</td>
<td><strong>❌</strong> seed varies</td>
<td>✅</td>
</tr>
<tr class="odd">
<td><code>shape_hash</code> byte-equal across builds</td>
<td>✅</td>
<td>n/a</td>
<td>✅</td>
</tr>
</tbody>
</table>
<p>Empirically confirmed: HashMap iter order <strong>varied across 10 independent builds in the same process</strong>. This isn’t a bug — it’s HashDoS protection — but it’s exactly the property that disqualifies HashMap from canonical-output paths in CJC-Lang. DHarhtMemory’s <code>shape_hash</code> was <code>0xa7792345c13b05b0</code> stable across all builds.</p>
<p><strong>Security comparison</strong>:</p>
<table class="table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th>Threat</th>
<th>BTreeMap</th>
<th>HashMap</th>
<th>DHarhtMemory</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>HashDoS adversarial collision</td>
<td>✅ Ord-tree immune</td>
<td>⚠️ random seed mitigates (loses determinism)</td>
<td>✅ MicroBucket16 cap → fallback</td>
</tr>
<tr class="even">
<td>Silent entry loss</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
</tr>
<tr class="odd">
<td>False-positive lookups</td>
<td>✅</td>
<td>✅</td>
<td>✅ full key equality</td>
</tr>
<tr class="even">
<td>Reproducibility for audit</td>
<td>✅</td>
<td><strong>❌</strong></td>
<td>✅</td>
</tr>
<tr class="odd">
<td>Adversarial diagnostics</td>
<td>implicit</td>
<td>none</td>
<td>✅ counters queryable</td>
</tr>
</tbody>
</table>
<p>The trade is real and intentional: DHarhtMemory pays ~3.9× more memory than HashMap to deliver what HashMap <strong>structurally cannot</strong> — deterministic iteration across processes <strong>with</strong> HashDoS resistance. For a workspace whose hard rule is “same seed → bit-identical output across runs”, that’s exactly the profile that fits.</p>
</section>
<section id="phase-12-art-fallback-port-complete" class="level3">
<h3 class="anchored" data-anchor-id="phase-12-art-fallback-port-complete">Phase 12 — ART fallback (port complete)</h3>
<p>Phase 11 shipped DHarhtMemory using a <code>BTreeMap&lt;u64, V&gt;</code> as the per-table fallback when a front-directory collision group exceeded MicroBucket16. The original blueprint uses a per-shard <strong>Adaptive Radix Tree</strong> (ART) with the full Node4/Node16/Node32/Node48/Node256 hierarchy — I’d deferred that as a multi-day port.</p>
<p>Phase 12 lands it. Each shard now owns an <code>ArtSlab&lt;V&gt;</code> containing entries (3-bit-tagged <code>u32</code> discriminators), leaves, and per-node-type arrays. When a collision group overflows during seal, those entries go into the originating shard’s ART rather than a global BTreeMap. Lookup with the <code>FRONT_FALLBACK</code> tag walks that shard’s ART by descending through bytes of the scattered key.</p>
<p>Adaptive growth is monotonic and deterministic: Node4 → Node16 → Node32 → Node48 → Node256 by child count, with the same insertion order producing byte-identical tree shape after compaction.</p>
<p>Verified by <code>art_fallback_handles_forced_overflow</code>: brute-search keys that collide on the same (shard, 16-bit prefix) until you have 17+ of them; verify that the seal triggers the ART path (<code>micro_overflow_count &gt; 0</code>) and every key is still findable through the radix descent.</p>
<p>The win on collision-heavy workloads is structural — ART’s worst-case lookup is at most 8 byte-radix descents (one per byte of the 64-bit scatter) vs BTreeMap’s <code>O(log N)</code> tree walk. For the well-distributed workloads this engine targets, the ART path almost never fires; for adversarial inputs it caps the worst case deterministically.</p>
</section>
</section>
<section id="where-this-leaves-cjc-lang" class="level2">
<h2 class="anchored" data-anchor-id="where-this-leaves-cjc-lang">Where this leaves CJC-Lang</h2>
<p>The recommended backend selection table after v0.1.7:</p>
<table class="table">
<colgroup>
<col style="width: 50%">
<col style="width: 50%">
</colgroup>
<thead>
<tr class="header">
<th>Workload</th>
<th>Pick</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>u64-keyed sealed lookup, <strong>deterministic</strong> + audit + reproducibility</td>
<td><strong><code>SealedU64Map</code></strong></td>
</tr>
<tr class="even">
<td>u64-keyed sealed lookup, <strong>lowest memory</strong>, no audit needs</td>
<td><code>std::HashMap</code></td>
</tr>
<tr class="odd">
<td>u64-keyed mutable + ordered iteration</td>
<td><code>BTreeMap</code></td>
</tr>
<tr class="even">
<td>Arbitrary byte keys, sealed (categorical dictionaries, interning)</td>
<td><code>DHarht</code> v.01</td>
</tr>
<tr class="odd">
<td>Tiny tables (≤ 16 entries)</td>
<td><code>TinyDetMap</code></td>
</tr>
<tr class="even">
<td>Small-medium sealed sorted maps with range queries</td>
<td><code>SortedVecMap</code></td>
</tr>
<tr class="odd">
<td>Sparse mutable equality, ordering doesn’t matter</td>
<td><code>DetOpenMap</code></td>
</tr>
<tr class="even">
<td>Dense <code>IdType → Value</code> ID tables</td>
<td><code>IndexVec</code></td>
</tr>
</tbody>
</table>
<p>The <code>BTreeMap</code> line is unchanged — it’s still the right answer for ordered iteration, range queries, and mutation-heavy workloads. The <code>HashMap</code> line is new: it’s faster than DHarhtMemory and uses less memory, but <strong>only if your workload doesn’t need reproducible iteration across runs</strong>. The vast majority of CJC-Lang’s data paths do — that’s why the language exists in the first place.</p>
</section>
<section id="numbers-in-one-place" class="level2">
<h2 class="anchored" data-anchor-id="numbers-in-one-place">Numbers, in one place</h2>
<table class="table">
<thead>
<tr class="header">
<th>Phase</th>
<th>Bench</th>
<th>Headline</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>2</td>
<td>group_by + cat keys</td>
<td>7.49× faster</td>
</tr>
<tr class="even">
<td>3</td>
<td>Hybrid set ops</td>
<td>83.46× (favorable)</td>
</tr>
<tr class="odd">
<td>4</td>
<td>Categorical joins</td>
<td>6.08×</td>
</tr>
<tr class="even">
<td>5(c)</td>
<td>Categorical arrange</td>
<td>9.68×</td>
</tr>
<tr class="odd">
<td>6</td>
<td>Streaming summarise memory</td>
<td>25 000× less RAM</td>
</tr>
<tr class="even">
<td>10</td>
<td>DHarht Memory u64 lookup</td>
<td>matches HashMap (~108 ns/op)</td>
</tr>
<tr class="odd">
<td>11</td>
<td>Memory cost</td>
<td>3.87× HashMap</td>
</tr>
<tr class="even">
<td>11</td>
<td>Determinism</td>
<td>HashMap fails, DHarht passes</td>
</tr>
<tr class="odd">
<td>12</td>
<td>Adversarial collision</td>
<td>ART fallback bounds worst case</td>
</tr>
</tbody>
</table>
<p>Test counts (across the whole suite, end-to-end):</p>
<pre><code>cjc-data         258  (was 193 in v0.1.6; +65 from v3 work)
test_phase10_tidy 495  (was 379 in v0.1.6; +116 from v3 work)
bolero fuzz       32   (was 26 in v0.1.6; +6)
tidyview-bench    38   (unchanged)
physics_ml        71   (unchanged)</code></pre>
<p>Workspace builds clean. Vault audit: 1 738 / 1 738 wikilinks resolve. <code>cjc-lang v0.1.7</code> packages cleanly via <code>cargo publish --dry-run</code>.</p>
</section>
<section id="whats-deferred-with-reasons" class="level2">
<h2 class="anchored" data-anchor-id="whats-deferred-with-reasons">What’s deferred (with reasons)</h2>
<p>Honest scope notes — these landed on the future-work list rather than v0.1.7:</p>
<ul>
<li><strong>Full <code>Column::Categorical → CategoricalColumn</code> storage replacement.</strong> The additive <code>CategoricalAdaptive</code> variant in Phase 5(a) is the gateway; the literal storage swap touches ~200 column-reader call sites. Single-session work won’t do it justice.</li>
<li><strong><code>LookupProfile::Speed</code> and <code>LookupProfile::Balanced</code>.</strong> The Memory profile is the recommended starting point per the original blueprint. Speed adds ~6 MB per shard for a marginal lookup-time improvement; not worth it until measured demand appears.</li>
<li><strong>Manual SSE2 <code>find_key16_sse2</code> for ART nodes.</strong> The scalar <code>match_mask</code> already inlines into tight code; SIMD here would be <code>unsafe</code> + architecture-specific for a path that fires only on adversarial input.</li>
<li><strong>Full compiler/runtime wiring of <code>SealedU64Map</code></strong> beyond the <code>ByteDictionary</code> content-hash demo. Symbol interning, MIR value tables, runtime globals — each is its own integration point and needs its own measurement.</li>
</ul>
</section>
<section id="try-it" class="level2">
<h2 class="anchored" data-anchor-id="try-it">Try it</h2>
<div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb6-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cargo</span> install cjc-lang  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># picks up v0.1.7 once published</span></span></code></pre></div>
<p>Or pull the workspace and run the bench yourself:</p>
<div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb7-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">git</span> clone https://github.com/AdamEzzat1/CJC</span>
<span id="cb7-2"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">cd</span> CJC</span>
<span id="cb7-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cargo</span> test <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--test</span> test_phase10_tidy <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--release</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--ignored</span> bench_phase10 <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--nocapture</span></span></code></pre></div>
<p>The 4-way bench output should look like the table above. If yours is different — say, HashMap is much faster than mine — that’s interesting data. The blueprint reported ~37 ns/op for both DHarht Memory and HashMap on the original author’s hardware; I see ~108 ns/op vs ~107 ns/op on Windows. Same ratio, different absolute. Architecture is portable; constant factors are not.</p>
<p>If you spot a workload where the chosen backend is wrong for the shape, that’s exactly the kind of feedback that drove this release. The collection family is open-ended — Phase 13 will be whatever shape isn’t covered yet.</p>


</section>

 ]]></description>
  <category>CJC-Lang</category>
  <category>data</category>
  <category>determinism</category>
  <category>architecture</category>
  <category>benchmarks</category>
  <category>hash-tables</category>
  <category>release</category>
  <guid>https://adamezzat1.github.io/blog/posts/tidyview-v3-and-dharht/index.html</guid>
  <pubDate>Wed, 29 Apr 2026 07:00:00 GMT</pubDate>
</item>
<item>
  <title>The cjcl CLI: 30 Commands for Deterministic Computing</title>
  <dc:creator>Adam Ezzat</dc:creator>
  <link>https://adamezzat1.github.io/blog/posts/cjcl-cli-reference/index.html</link>
  <description><![CDATA[ 



<div class="reading-time">
🕑 ~20 min read
</div>
<div class="stats-row">
<div class="stat">
<span class="number">30</span><span class="stat-label">commands</span>
</div>
<div class="stat">
<span class="number">4</span><span class="stat-label">categories</span>
</div>
<div class="stat">
<span class="number">0</span><span class="stat-label">external deps</span>
</div>
<div class="stat">
<span class="number">42</span><span class="stat-label">default seed</span>
</div>
</div>
<div class="callout callout-style-simple callout-tip no-icon callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-title-container flex-fill">
TL;DR
</div>
</div>
<div class="callout-body-container callout-body">
<p><code>cjcl</code> is a single binary with 30 commands. Install with <code>cargo install cjc-lang</code>, then <code>cjcl run your_file.cjcl</code>. Every command produces deterministic output — same input, same flags, same result, every time.</p>
</div>
</div>
<section id="what-is-cjcl" class="level2">
<h2 class="anchored" data-anchor-id="what-is-cjcl">What Is cjcl?</h2>
<p><code>cjcl</code> is the CLI frontend for <a href="../../../blog/posts/introducing-cjc/index.html">CJC-Lang</a>, a deterministic numerical programming language. One binary, zero external dependencies, 30 commands that span compilation, data engineering, profiling, and reproducibility verification.</p>
<div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb1-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cargo</span> install cjc-lang</span>
<span id="cb1-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--version</span>   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># CJC-Lang v0.1.6</span></span></code></pre></div>
<p>Every command that produces output supports three output modes: <code>--color</code> (default, human-readable), <code>--plain</code> (no ANSI escapes, pipe-friendly), and <code>--json</code> (machine-readable). Most commands that execute code accept <code>--seed &lt;N&gt;</code> for deterministic RNG seeding (default: 42).</p>
</section>
<section id="quick-reference" class="level2">
<h2 class="anchored" data-anchor-id="quick-reference">Quick Reference</h2>
<table class="table">
<colgroup>
<col style="width: 50%">
<col style="width: 50%">
</colgroup>
<thead>
<tr class="header">
<th>Category</th>
<th>Commands</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Core Compiler</strong></td>
<td><code>lex</code>, <code>parse</code>, <code>check</code>, <code>run</code>, <code>eval</code>, <code>repl</code></td>
</tr>
<tr class="even">
<td><strong>Data &amp; Pipeline</strong></td>
<td><code>view</code>, <code>proof</code>, <code>flow</code>, <code>patch</code>, <code>seek</code>, <code>drift</code>, <code>forge</code></td>
</tr>
<tr class="odd">
<td><strong>Inspection &amp; Diagnostics</strong></td>
<td><code>inspect</code>, <code>schema</code>, <code>trace</code>, <code>mem</code>, <code>bench</code>, <code>pack</code>, <code>doctor</code></td>
</tr>
<tr class="even">
<td><strong>Compiler Visibility</strong></td>
<td><code>emit</code>, <code>explain</code>, <code>gc</code>, <code>nogc</code>, <code>audit</code>, <code>precision</code>, <code>lock</code>, <code>parity</code>, <code>test</code>, <code>ci</code></td>
</tr>
</tbody>
</table>
<hr>
</section>
<section id="core-compiler-commands" class="level2">
<h2 class="anchored" data-anchor-id="core-compiler-commands">Core Compiler Commands</h2>
<p>These six commands are the foundation — tokenize, parse, type-check, run, evaluate, and interact.</p>
<section id="lex" class="level3">
<h3 class="anchored" data-anchor-id="lex">lex</h3>
<p>Tokenize a source file and print the token stream.</p>
<div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb2-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> lex program.cjcl</span></code></pre></div>
<p>Output shows each token’s span, kind, and text. Useful for debugging parser issues or understanding how CJC-Lang sees your source code.</p>
</section>
<section id="parse" class="level3">
<h3 class="anchored" data-anchor-id="parse">parse</h3>
<p>Parse a source file and pretty-print the AST.</p>
<div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb3-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> parse program.cjcl</span></code></pre></div>
<p>Shows the full abstract syntax tree after parsing. Use this when you suspect a precedence or grouping issue — the AST reveals exactly how the parser interpreted your expressions.</p>
</section>
<section id="check" class="level3">
<h3 class="anchored" data-anchor-id="check">check</h3>
<p>Type-check a source file without running it.</p>
<div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb4-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> check program.cjcl</span></code></pre></div>
<p>Runs the full compilation pipeline (lex, parse, type-check) and reports errors and warnings. Exit code 0 means the program is well-typed.</p>
<p>When used with validation flags, <code>check</code> becomes an output validator:</p>
<div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb5-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> check output.csv <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--against</span> expected.csv <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--tolerance</span> 1e-6</span>
<span id="cb5-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> check output.csv <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--against</span> expected.csv <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--schema-only</span></span>
<span id="cb5-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> check output.txt <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--expect</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Hello, world!"</span></span></code></pre></div>
<table class="table">
<thead>
<tr class="header">
<th>Flag</th>
<th>What it does</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>--against &lt;file&gt;</code></td>
<td>Compare file against a reference</td>
</tr>
<tr class="even">
<td><code>--tolerance &lt;N&gt;</code></td>
<td>Numeric tolerance for comparison (default: 0.0)</td>
</tr>
<tr class="odd">
<td><code>--schema-only</code></td>
<td>Compare only column structure, not values</td>
</tr>
<tr class="even">
<td><code>--expect &lt;string&gt;</code></td>
<td>Check file contents match a literal string</td>
</tr>
</tbody>
</table>
<p><strong>When to use it:</strong> Run <code>cjcl check</code> before committing to catch type errors early. Use the validation mode in CI to verify pipeline outputs haven’t drifted.</p>
</section>
<section id="run" class="level3">
<h3 class="anchored" data-anchor-id="run">run</h3>
<p>Execute a CJC-Lang program.</p>
<div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb6-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> run program.cjcl</span>
<span id="cb6-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> run program.cjcl <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--seed</span> 123</span>
<span id="cb6-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> run program.cjcl <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--mir-opt</span></span>
<span id="cb6-4"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> run program.cjcl <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--multi-file</span></span></code></pre></div>
<p>This is the command you’ll use most. By default it uses the AST tree-walk interpreter. Add <code>--mir-opt</code> to use the MIR register-machine executor with optimizations (control flow simplification + dead code elimination).</p>
<table class="table">
<thead>
<tr class="header">
<th>Flag</th>
<th>What it does</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>--seed &lt;N&gt;</code></td>
<td>RNG seed (default: 42)</td>
</tr>
<tr class="even">
<td><code>--mir-opt</code></td>
<td>MIR executor with optimizations</td>
</tr>
<tr class="odd">
<td><code>--mir-mono</code></td>
<td>MIR executor with monomorphization</td>
</tr>
<tr class="even">
<td><code>--multi-file</code></td>
<td>Enable multi-file module resolution</td>
</tr>
<tr class="odd">
<td><code>--time</code></td>
<td>Print execution time to stderr</td>
</tr>
<tr class="even">
<td><code>--format json</code></td>
<td>Wrap output in <code>{"ok":true,"output":[...]}</code></td>
</tr>
</tbody>
</table>
<p><strong>Exit codes:</strong> 0 = success, 1 = runtime error, 2 = parse error, 3 = type error, 4 = parity failure.</p>
<p><strong>When to use it:</strong> This is your daily driver. Use <code>--mir-opt</code> for performance-sensitive code, <code>--multi-file</code> for projects split across modules.</p>
</section>
<section id="eval" class="level3">
<h3 class="anchored" data-anchor-id="eval">eval</h3>
<p>Evaluate a single expression without creating a file.</p>
<div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb7-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> eval <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"42 + 8"</span>           <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># → 50</span></span>
<span id="cb7-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> eval <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sqrt(2.0)"</span>        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># → 1.4142135623730951</span></span>
<span id="cb7-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> eval <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"42 + 8"</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--format</span> json  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># → {"ok":true,"output":["50"]}</span></span></code></pre></div>
<p>The expression is wrapped in <code>fn main() { print(&lt;expr&gt;); }</code> and executed. Supports <code>--seed</code> and <code>--format</code>.</p>
<p><strong>When to use it:</strong> Quick calculations, testing builtins, scripting one-liners.</p>
</section>
<section id="repl" class="level3">
<h3 class="anchored" data-anchor-id="repl">repl</h3>
<p>Start an interactive read-eval-print loop.</p>
<div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb8-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> repl</span>
<span id="cb8-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> repl <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--seed</span> 123</span></code></pre></div>
<p>The REPL preserves variable bindings across lines and supports meta-commands:</p>
<table class="table">
<thead>
<tr class="header">
<th>Command</th>
<th>What it does</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>:help</code></td>
<td>Show all meta-commands</td>
</tr>
<tr class="even">
<td><code>:type &lt;expr&gt;</code></td>
<td>Show inferred type</td>
</tr>
<tr class="odd">
<td><code>:ast &lt;expr&gt;</code></td>
<td>Pretty-print the AST</td>
</tr>
<tr class="even">
<td><code>:mir &lt;expr&gt;</code></td>
<td>Show MIR representation</td>
</tr>
<tr class="odd">
<td><code>:env</code></td>
<td>Show current variable bindings</td>
</tr>
<tr class="even">
<td><code>:time &lt;expr&gt;</code></td>
<td>Time expression execution</td>
</tr>
<tr class="odd">
<td><code>:describe &lt;expr&gt;</code></td>
<td>Statistical summary of a numeric array</td>
</tr>
<tr class="even">
<td><code>:save &lt;file&gt;</code></td>
<td>Save session history</td>
</tr>
<tr class="odd">
<td><code>:load &lt;file&gt;</code></td>
<td>Load and execute a file</td>
</tr>
<tr class="even">
<td><code>:reset</code></td>
<td>Clear all bindings</td>
</tr>
<tr class="odd">
<td><code>:seed</code></td>
<td>Show current RNG seed</td>
</tr>
<tr class="even">
<td><code>:quit</code></td>
<td>Exit</td>
</tr>
</tbody>
</table>
<p><strong>When to use it:</strong> Exploratory data analysis, testing ideas, learning the language.</p>
<hr>
</section>
</section>
<section id="data-pipeline-commands" class="level2">
<h2 class="anchored" data-anchor-id="data-pipeline-commands">Data &amp; Pipeline Commands</h2>
<p>Seven commands for working with data files, verifying reproducibility, and building deterministic pipelines.</p>
<section id="view" class="level3">
<h3 class="anchored" data-anchor-id="view">view</h3>
<p>Deterministic directory listing with type-aware annotations.</p>
<div class="sourceCode" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb9-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> view</span>
<span id="cb9-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> view ./data/ <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--recursive</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--hash</span></span></code></pre></div>
<p>Lists files sorted lexicographically with color-coded types: <code>.cjcl</code> files show effect annotations (<code>#[nogc]</code>, <code>@pure</code>, <code>@timed</code>), <code>.snap</code> files show content hashes, data files (<code>.csv</code>, <code>.json</code>) are highlighted. Output is deterministic — same directory, same listing, every time.</p>
<table class="table">
<thead>
<tr class="header">
<th>Flag</th>
<th>What it does</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>-r, --recursive</code></td>
<td>Recurse into subdirectories</td>
</tr>
<tr class="even">
<td><code>--hash</code></td>
<td>Show SHA-256 hashes</td>
</tr>
<tr class="odd">
<td><code>--no-effects</code></td>
<td>Hide effect annotations</td>
</tr>
<tr class="even">
<td><code>--no-size</code></td>
<td>Hide file sizes</td>
</tr>
</tbody>
</table>
<p><strong>When to use it:</strong> Quickly inventory a project’s data files and verify effect annotations.</p>
</section>
<section id="proof" class="level3">
<h3 class="anchored" data-anchor-id="proof">proof</h3>
<p>Verify that a program produces identical output across multiple runs.</p>
<div class="sourceCode" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb10-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> proof program.cjcl</span>
<span id="cb10-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> proof program.cjcl <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--runs</span> 10 <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--seeds</span> 42,123,999</span>
<span id="cb10-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> proof program.cjcl <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--executor</span> both <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--hash-output</span></span></code></pre></div>
<p>Runs the program N times (default: 3) and checks that stdout, exit status, and GC behavior are identical. With <code>--seeds</code>, tests multiple seeds. With <code>--executor both</code>, verifies both the AST interpreter and MIR executor.</p>
<table class="table">
<thead>
<tr class="header">
<th>Flag</th>
<th>What it does</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>-n, --runs &lt;N&gt;</code></td>
<td>Number of iterations (default: 3)</td>
</tr>
<tr class="even">
<td><code>--seed &lt;N&gt;</code></td>
<td>Single seed to test (default: 42)</td>
</tr>
<tr class="odd">
<td><code>--seeds &lt;N,M,...&gt;</code></td>
<td>Test multiple seeds</td>
</tr>
<tr class="even">
<td><code>--executor eval\|mir\|both</code></td>
<td>Which executor(s) to use</td>
</tr>
<tr class="odd">
<td><code>--fail-fast</code></td>
<td>Stop on first divergence</td>
</tr>
<tr class="even">
<td><code>--hash-output</code></td>
<td>Show SHA-256 of stdout per run</td>
</tr>
<tr class="odd">
<td><code>--save-report &lt;file&gt;</code></td>
<td>Save report as JSON</td>
</tr>
</tbody>
</table>
<p><strong>When to use it:</strong> Before publishing results. Before merging a PR that touches numerical code. In CI as a gate.</p>
</section>
<section id="flow" class="level3">
<h3 class="anchored" data-anchor-id="flow">flow</h3>
<p>Streaming computation over CSV/TSV/JSONL data with O(ncols) memory.</p>
<div class="sourceCode" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb11-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> flow sales.csv <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--op</span> sum,mean,min,max</span>
<span id="cb11-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> flow sales.csv <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--op</span> mean <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--columns</span> revenue,cost <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--sort-by</span> mean</span>
<span id="cb11-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> flow <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--tsv</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> data.tsv <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--top</span> 5 <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--json</span></span></code></pre></div>
<p>Processes data in a single pass without loading the entire file into memory. Uses Kahan summation for numeric stability and Welford’s online algorithm for variance/stddev.</p>
<table class="table">
<colgroup>
<col style="width: 50%">
<col style="width: 50%">
</colgroup>
<thead>
<tr class="header">
<th>Flag</th>
<th>What it does</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>--op &lt;ops&gt;</code></td>
<td>Operations: <code>sum</code>, <code>mean</code>, <code>min</code>, <code>max</code>, <code>count</code>, <code>var</code>, <code>std</code></td>
</tr>
<tr class="even">
<td><code>-c, --columns &lt;cols&gt;</code></td>
<td>Only process named columns</td>
</tr>
<tr class="odd">
<td><code>--sort-by &lt;metric&gt;</code></td>
<td>Sort output by a metric</td>
</tr>
<tr class="even">
<td><code>--top &lt;N&gt;</code></td>
<td>Show only top N columns</td>
</tr>
<tr class="odd">
<td><code>--verify</code></td>
<td>Run twice, confirm identical output</td>
</tr>
<tr class="even">
<td><code>--tsv</code></td>
<td>Tab-delimited input</td>
</tr>
<tr class="odd">
<td><code>-d, --delimiter &lt;c&gt;</code></td>
<td>Custom delimiter</td>
</tr>
</tbody>
</table>
<p><strong>When to use it:</strong> Quick summary statistics on large CSVs without loading into a DataFrame. Verifying that a pipeline’s output statistics match expectations.</p>
</section>
<section id="patch" class="level3">
<h3 class="anchored" data-anchor-id="patch">patch</h3>
<p>Type-aware data transformation for CSV/TSV/JSONL files.</p>
<div class="sourceCode" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb12-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> patch data.csv <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--nan-fill</span> 0.0</span>
<span id="cb12-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> patch data.csv <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--impute</span> salary <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--drop</span> temp_col <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--rename</span> old_name new_name</span>
<span id="cb12-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> patch data.csv <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--replace</span> status <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"NA"</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"unknown"</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--plan</span></span></code></pre></div>
<p>Streams the file and applies transformations: NaN replacement, mean imputation, column dropping, renaming, value replacement. With <code>--plan</code>, shows the transformation sequence without executing. With <code>--dry-run</code>, previews changes.</p>
<table class="table">
<thead>
<tr class="header">
<th>Flag</th>
<th>What it does</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>--nan-fill &lt;value&gt;</code></td>
<td>Replace NaN/NA/null with a value</td>
</tr>
<tr class="even">
<td><code>--impute &lt;column&gt;</code></td>
<td>Mean imputation for a column</td>
</tr>
<tr class="odd">
<td><code>--drop &lt;column&gt;</code></td>
<td>Drop a column</td>
</tr>
<tr class="even">
<td><code>--rename &lt;from&gt; &lt;to&gt;</code></td>
<td>Rename a column</td>
</tr>
<tr class="odd">
<td><code>--replace &lt;col&gt; &lt;from&gt; &lt;to&gt;</code></td>
<td>Replace exact values</td>
</tr>
<tr class="even">
<td><code>--fill-empty &lt;col&gt; &lt;value&gt;</code></td>
<td>Fill empty cells</td>
</tr>
<tr class="odd">
<td><code>--plan</code></td>
<td>Show transformation plan</td>
</tr>
<tr class="even">
<td><code>--dry-run</code></td>
<td>Preview without writing</td>
</tr>
<tr class="odd">
<td><code>--in-place</code></td>
<td>Modify file directly</td>
</tr>
<tr class="even">
<td><code>-o, --output &lt;file&gt;</code></td>
<td>Output file (default: stdout)</td>
</tr>
</tbody>
</table>
<p><strong>When to use it:</strong> Quick data cleaning before feeding into a CJC-Lang pipeline. Fixing nulls and renaming columns without writing a script.</p>
</section>
<section id="seek" class="level3">
<h3 class="anchored" data-anchor-id="seek">seek</h3>
<p>Deterministic file discovery with content search.</p>
<div class="sourceCode" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb13-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> seek . <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"*.cjcl"</span></span>
<span id="cb13-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> seek ./data <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--type</span> csv <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--contains</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"revenue"</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--hash</span></span>
<span id="cb13-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> seek . <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--min-size</span> 1MB <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--sort</span> size <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--manifest</span></span></code></pre></div>
<p>Recursively finds files matching patterns with stable, sorted output. With <code>--contains</code>, searches file contents. With <code>--manifest</code>, outputs <code>&lt;hash&gt; &lt;size&gt; &lt;path&gt;</code> per line — useful for reproducibility manifests.</p>
<table class="table">
<thead>
<tr class="header">
<th>Flag</th>
<th>What it does</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>-t, --type &lt;type&gt;</code></td>
<td>Filter by extension (cjcl, csv, snap, etc.)</td>
</tr>
<tr class="even">
<td><code>--contains &lt;pattern&gt;</code></td>
<td>Content search</td>
</tr>
<tr class="odd">
<td><code>--min-size &lt;size&gt;</code></td>
<td>Minimum file size (supports KB/MB)</td>
</tr>
<tr class="even">
<td><code>--max-size &lt;size&gt;</code></td>
<td>Maximum file size</td>
</tr>
<tr class="odd">
<td><code>--max-depth &lt;N&gt;</code></td>
<td>Recursion depth limit</td>
</tr>
<tr class="even">
<td><code>--hash</code></td>
<td>Show SHA-256 per file</td>
</tr>
<tr class="odd">
<td><code>--manifest</code></td>
<td>Output hash, size, path per line</td>
</tr>
<tr class="even">
<td><code>--sort name\|size\|modified</code></td>
<td>Sort mode</td>
</tr>
<tr class="odd">
<td><code>--first &lt;N&gt;</code></td>
<td>Limit results</td>
</tr>
<tr class="even">
<td><code>--ignore-build-artifacts</code></td>
<td>Skip target/, node_modules/, etc.</td>
</tr>
</tbody>
</table>
<p><strong>When to use it:</strong> Building reproducibility manifests. Finding all data files in a project. Content search across a codebase.</p>
</section>
<section id="drift" class="level3">
<h3 class="anchored" data-anchor-id="drift">drift</h3>
<p>Compare two files with mathematical precision.</p>
<div class="sourceCode" id="cb14" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb14-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> drift output_v1.csv output_v2.csv <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--tolerance</span> 1e-6</span>
<span id="cb14-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> drift model_a.csv model_b.csv <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--stats-only</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--json</span></span>
<span id="cb14-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> drift old.txt new.txt <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--text</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--fail-on-diff</span></span></code></pre></div>
<p>Supports text (line-by-line), numeric (element-wise), CSV (cell-by-cell), and JSONL diff modes. Reports max deviation, mean deviation, Frobenius norm, and per-cell differences.</p>
<table class="table">
<colgroup>
<col style="width: 50%">
<col style="width: 50%">
</colgroup>
<thead>
<tr class="header">
<th>Flag</th>
<th>What it does</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>--tolerance &lt;N&gt;</code></td>
<td>Numeric tolerance (default: 0.0)</td>
</tr>
<tr class="even">
<td><code>--text, --numeric, --csv, --jsonl</code></td>
<td>Diff mode (auto-detected from extension)</td>
</tr>
<tr class="odd">
<td><code>--max-diffs &lt;N&gt;</code></td>
<td>Maximum differences to show (default: 50)</td>
</tr>
<tr class="even">
<td><code>--fail-on-diff</code></td>
<td>Exit 1 if any differences found</td>
</tr>
<tr class="odd">
<td><code>--fail-on-schema-diff</code></td>
<td>Exit 1 if column names differ</td>
</tr>
<tr class="even">
<td><code>--summary-only</code></td>
<td>Only show summary metrics</td>
</tr>
<tr class="odd">
<td><code>--stats-only</code></td>
<td>Only show statistical metrics</td>
</tr>
<tr class="even">
<td><code>--report &lt;file&gt;</code></td>
<td>Write full diff report to JSON</td>
</tr>
</tbody>
</table>
<p><strong>When to use it:</strong> Comparing model outputs between versions. Verifying that a code change didn’t alter numerical results. Regression testing in CI.</p>
</section>
<section id="forge" class="level3">
<h3 class="anchored" data-anchor-id="forge">forge</h3>
<p>Content-addressable pipeline runner.</p>
<div class="sourceCode" id="cb15" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb15-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> forge run pipeline.cjcl</span>
<span id="cb15-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> forge verify pipeline.cjcl abc123def</span>
<span id="cb15-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> forge list</span>
<span id="cb15-4"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> forge show abc123</span>
<span id="cb15-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> forge clean</span></code></pre></div>
<p>Executes a program, captures its output, and stores the result as a SHA-256 content-addressable artifact. With <code>verify</code>, checks that the current output matches an expected hash. Artifacts are stored in <code>.cjc-forge/</code>.</p>
<table class="table">
<thead>
<tr class="header">
<th>Subcommand</th>
<th>What it does</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>run &lt;file&gt;</code></td>
<td>Execute and store artifact</td>
</tr>
<tr class="even">
<td><code>verify &lt;file&gt; &lt;hash&gt;</code></td>
<td>Verify output matches hash</td>
</tr>
<tr class="odd">
<td><code>list</code></td>
<td>List cached artifacts</td>
</tr>
<tr class="even">
<td><code>show &lt;hash-prefix&gt;</code></td>
<td>Show artifact details</td>
</tr>
<tr class="odd">
<td><code>clean</code></td>
<td>Remove all cached artifacts</td>
</tr>
</tbody>
</table>
<table class="table">
<thead>
<tr class="header">
<th>Flag</th>
<th>What it does</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>--seed &lt;N&gt;</code></td>
<td>RNG seed (default: 42)</td>
</tr>
<tr class="even">
<td><code>--cache-dir &lt;path&gt;</code></td>
<td>Cache directory (default: <code>.cjc-forge/</code>)</td>
</tr>
</tbody>
</table>
<p><strong>When to use it:</strong> Pinning pipeline outputs for reproducibility. Verifying that a computation produces the exact same output on a different machine.</p>
<hr>
</section>
</section>
<section id="inspection-diagnostics" class="level2">
<h2 class="anchored" data-anchor-id="inspection-diagnostics">Inspection &amp; Diagnostics</h2>
<p>Seven commands for understanding what your code does, how fast it runs, and whether your project is healthy.</p>
<section id="inspect" class="level3">
<h3 class="anchored" data-anchor-id="inspect">inspect</h3>
<p>Deep inspection of structured files without execution.</p>
<div class="sourceCode" id="cb16" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb16-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> inspect data.csv</span>
<span id="cb16-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> inspect model.snap <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--deep</span></span>
<span id="cb16-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> inspect results.csv <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--compare</span> expected.csv</span>
<span id="cb16-4"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> inspect data.parquet <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--schema-only</span></span></code></pre></div>
<p>Inspects <code>.snap</code>, CSV/TSV, JSONL, <code>.cjcl</code>, Parquet, Arrow, SQLite, and model files. Reports structure, shape, column types, null counts, and basic statistics. With <code>--deep</code>, computes variance, standard deviation, and unique value counts.</p>
<table class="table">
<thead>
<tr class="header">
<th>Flag</th>
<th>What it does</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>--deep</code></td>
<td>Compute variance, unique values</td>
</tr>
<tr class="even">
<td><code>--preview &lt;N&gt;</code></td>
<td>Max preview lines (default: 5)</td>
</tr>
<tr class="odd">
<td><code>--header-only</code></td>
<td>Show header/schema only</td>
</tr>
<tr class="even">
<td><code>--compare &lt;file&gt;</code></td>
<td>Compare schemas/hashes with another file</td>
</tr>
<tr class="odd">
<td><code>--hash</code></td>
<td>Show SHA-256 hash</td>
</tr>
<tr class="even">
<td><code>--manifest</code></td>
<td>Output manifest format</td>
</tr>
</tbody>
</table>
<p><strong>When to use it:</strong> Understanding unfamiliar datasets. Verifying schema before piping into a pipeline. Quick sanity checks on model checkpoints.</p>
</section>
<section id="schema" class="level3">
<h3 class="anchored" data-anchor-id="schema">schema</h3>
<p>Infer and validate data schemas.</p>
<div class="sourceCode" id="cb17" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb17-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> schema data.csv</span>
<span id="cb17-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> schema data.csv <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--full</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--save</span> schema.json</span>
<span id="cb17-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> schema data.csv <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--check</span> schema.json</span>
<span id="cb17-4"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> schema data.csv <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--diff</span> old_schema.json</span></code></pre></div>
<p>Infers column types (null, bool, int, float, string, mixed), null counts, and unique value counts from CSV/TSV/JSONL/Parquet/Arrow/SQLite data. With <code>--save</code>, persists the schema. With <code>--check</code>, validates data against a saved schema. With <code>--diff</code>, shows schema drift.</p>
<table class="table">
<colgroup>
<col style="width: 50%">
<col style="width: 50%">
</colgroup>
<thead>
<tr class="header">
<th>Flag</th>
<th>What it does</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>--full</code></td>
<td>Show type distributions and sample values</td>
</tr>
<tr class="even">
<td><code>--save &lt;file&gt;</code></td>
<td>Save schema to JSON</td>
</tr>
<tr class="odd">
<td><code>--check &lt;file&gt;</code></td>
<td>Validate against saved schema (exit 1 on mismatch)</td>
</tr>
<tr class="even">
<td><code>--diff &lt;file&gt;</code></td>
<td>Compare current vs saved schema</td>
</tr>
<tr class="odd">
<td><code>--sample &lt;N&gt;</code></td>
<td>Limit to first N rows</td>
</tr>
<tr class="even">
<td><code>--strict</code></td>
<td>Treat warnings as errors</td>
</tr>
</tbody>
</table>
<p><strong>When to use it:</strong> Detecting schema drift between pipeline runs. Validating incoming data before processing. Documenting dataset structure.</p>
</section>
<section id="trace" class="level3">
<h3 class="anchored" data-anchor-id="trace">trace</h3>
<p>Execution tracing across four phases: parse, static analysis, execution, and reporting.</p>
<div class="sourceCode" id="cb18" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb18-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> trace program.cjcl</span>
<span id="cb18-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> trace program.cjcl <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--ast</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--verbose</span></span></code></pre></div>
<p>Reports parse time, execution time, AST structure (function/struct/statement counts), effect annotations, and runtime behavior (output lines, GC collections).</p>
<table class="table">
<thead>
<tr class="header">
<th>Flag</th>
<th>What it does</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>--ast</code></td>
<td>Show AST details</td>
</tr>
<tr class="even">
<td><code>--no-output</code></td>
<td>Suppress program output</td>
</tr>
<tr class="odd">
<td><code>-v, --verbose</code></td>
<td>Show function list</td>
</tr>
</tbody>
</table>
<p><strong>When to use it:</strong> Understanding the structure of a program. Getting a quick overview of parse and execution cost.</p>
</section>
<section id="mem" class="level3">
<h3 class="anchored" data-anchor-id="mem">mem</h3>
<p>Runtime memory profiling.</p>
<div class="sourceCode" id="cb19" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb19-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> mem program.cjcl</span>
<span id="cb19-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> mem program.cjcl <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--runs</span> 5 <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--timeline</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--compare</span> eval<span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">|</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">mir</span></span></code></pre></div>
<p>Executes a program and reports GC collection counts, heap object counts, output size, and execution time. With <code>--runs</code>, aggregates statistics across multiple executions. With <code>--compare</code>, runs both executors and shows side-by-side GC behavior.</p>
<table class="table">
<thead>
<tr class="header">
<th>Flag</th>
<th>What it does</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>-n, --runs &lt;N&gt;</code></td>
<td>Number of runs (default: 1)</td>
</tr>
<tr class="even">
<td><code>--nogc</code></td>
<td>Run NoGC verification first</td>
</tr>
<tr class="odd">
<td><code>--eval</code> / <code>--mir</code></td>
<td>Choose executor</td>
</tr>
<tr class="even">
<td><code>--peak-only</code></td>
<td>Only report peak metrics</td>
</tr>
<tr class="odd">
<td><code>--timeline</code></td>
<td>Show GC collections per run</td>
</tr>
<tr class="even">
<td><code>--compare eval\|mir</code></td>
<td>Compare executor GC behavior</td>
</tr>
<tr class="odd">
<td><code>--fail-on-gc</code></td>
<td>Exit 1 if any GC collections occurred</td>
</tr>
<tr class="even">
<td><code>--save-report &lt;file&gt;</code></td>
<td>Save report as JSON</td>
</tr>
</tbody>
</table>
<p><strong>When to use it:</strong> Identifying GC pressure. Verifying that a <code>#[nogc]</code> function truly avoids allocations. Comparing memory behavior between executors.</p>
</section>
<section id="bench" class="level3">
<h3 class="anchored" data-anchor-id="bench">bench</h3>
<p>Performance benchmarking with statistical rigor.</p>
<div class="sourceCode" id="cb20" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb20-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> bench program.cjcl <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--runs</span> 20 <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--warmup</span> 5</span>
<span id="cb20-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> bench program.cjcl <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--compare</span> eval<span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">|</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">mir</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--markdown</span></span>
<span id="cb20-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> bench program.cjcl <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--baseline</span> baseline.json <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--fail-if-slower-than</span> 10</span></code></pre></div>
<p>Runs the program N times with warmup iterations and reports mean, median, min, max, stddev, coefficient of variation, P95, and throughput.</p>
<table class="table">
<thead>
<tr class="header">
<th>Flag</th>
<th>What it does</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>-n, --runs &lt;N&gt;</code></td>
<td>Measured runs (default: 10)</td>
</tr>
<tr class="even">
<td><code>--warmup &lt;N&gt;</code></td>
<td>Warmup runs (default: 1)</td>
</tr>
<tr class="odd">
<td><code>--mir</code> / <code>--eval</code></td>
<td>Choose executor</td>
</tr>
<tr class="even">
<td><code>--compare eval\|mir</code></td>
<td>Side-by-side executor comparison</td>
</tr>
<tr class="odd">
<td><code>--baseline &lt;file&gt;</code></td>
<td>Load baseline for comparison</td>
</tr>
<tr class="even">
<td><code>--save-baseline &lt;file&gt;</code></td>
<td>Save results as baseline</td>
</tr>
<tr class="odd">
<td><code>--fail-if-slower-than &lt;N&gt;</code></td>
<td>Exit 1 if &gt;N% slower than baseline</td>
</tr>
<tr class="even">
<td><code>--csv</code> / <code>--markdown</code></td>
<td>Output as CSV or Markdown table</td>
</tr>
<tr class="odd">
<td><code>-v, --verbose</code></td>
<td>Show per-run times</td>
</tr>
</tbody>
</table>
<p><strong>When to use it:</strong> Measuring performance before and after a change. Comparing executor backends. Setting up CI performance gates.</p>
</section>
<section id="pack" class="level3">
<h3 class="anchored" data-anchor-id="pack">pack</h3>
<p>Bundle a program and its dependencies into a reproducible package.</p>
<div class="sourceCode" id="cb21" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb21-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> pack pipeline.cjcl</span>
<span id="cb21-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> pack pipeline.cjcl <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--include</span> extra_data.csv <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--verify</span></span>
<span id="cb21-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> pack <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--list</span> pipeline.pack</span>
<span id="cb21-4"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> pack <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--repro-check</span> pipeline.pack</span></code></pre></div>
<p>Auto-discovers dependencies by scanning source for string literals matching packable extensions (<code>.snap</code>, <code>.csv</code>, <code>.tsv</code>, <code>.json</code>, <code>.lock</code>, <code>.toml</code>). Creates a directory with a MANIFEST containing per-file SHA-256 hashes.</p>
<table class="table">
<thead>
<tr class="header">
<th>Flag</th>
<th>What it does</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>-o, --output &lt;dir&gt;</code></td>
<td>Output directory</td>
</tr>
<tr class="even">
<td><code>--include &lt;file&gt;</code></td>
<td>Additional file to include (repeatable)</td>
</tr>
<tr class="odd">
<td><code>--verify</code></td>
<td>Verify manifest after packing</td>
</tr>
<tr class="even">
<td><code>--dry-run</code></td>
<td>Preview without writing</td>
</tr>
<tr class="odd">
<td><code>--list &lt;dir.pack&gt;</code></td>
<td>List package contents</td>
</tr>
<tr class="even">
<td><code>--repro-check &lt;dir&gt;</code></td>
<td>Re-hash and verify against manifest</td>
</tr>
<tr class="odd">
<td><code>--manifest-only</code></td>
<td>Output MANIFEST only</td>
</tr>
</tbody>
</table>
<p><strong>When to use it:</strong> Sharing reproducible computations. Archiving pipeline artifacts. Verifying that a package hasn’t been tampered with.</p>
</section>
<section id="doctor" class="level3">
<h3 class="anchored" data-anchor-id="doctor">doctor</h3>
<p>Project-wide diagnostics.</p>
<div class="sourceCode" id="cb22" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb22-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> doctor</span>
<span id="cb22-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> doctor ./my_project <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--fix</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--dry-run</span></span>
<span id="cb22-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> doctor <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--strict</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--report</span> findings.json</span></code></pre></div>
<p>Scans a directory for: parse errors, type errors, corrupt <code>.snap</code> files, ragged CSV rows, nondeterministic patterns (<code>HashMap</code>, <code>HashSet</code>, <code>rand()</code>, <code>time()</code>), large files, malformed JSONL, and invalid Parquet files.</p>
<table class="table">
<thead>
<tr class="header">
<th>Flag</th>
<th>What it does</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>--fix</code></td>
<td>Attempt automatic fixes</td>
</tr>
<tr class="even">
<td><code>--dry-run</code></td>
<td>Preview fixes without applying</td>
</tr>
<tr class="odd">
<td><code>--strict</code></td>
<td>Exit 1 on warnings</td>
</tr>
<tr class="even">
<td><code>--category &lt;type&gt;</code></td>
<td>Filter by finding category</td>
</tr>
<tr class="odd">
<td><code>--report &lt;file&gt;</code></td>
<td>Save findings to JSON</td>
</tr>
<tr class="even">
<td><code>--summary-only</code></td>
<td>Only show summary counts</td>
</tr>
<tr class="odd">
<td><code>--fail-on &lt;level&gt;</code></td>
<td>Exit 1 at severity threshold</td>
</tr>
</tbody>
</table>
<p><strong>When to use it:</strong> Pre-commit health check. CI gate for project hygiene. Onboarding — run <code>doctor</code> to understand a new project’s state.</p>
<hr>
</section>
</section>
<section id="compiler-visibility-analysis" class="level2">
<h2 class="anchored" data-anchor-id="compiler-visibility-analysis">Compiler Visibility &amp; Analysis</h2>
<p>Ten commands that expose the compiler’s internals — intermediate representations, memory verification, numerical analysis, and dual-executor parity.</p>
<section id="emit" class="level3">
<h3 class="anchored" data-anchor-id="emit">emit</h3>
<p>Dump intermediate representations at any pipeline stage.</p>
<div class="sourceCode" id="cb23" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb23-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> emit program.cjcl <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--stage</span> ast</span>
<span id="cb23-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> emit program.cjcl <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--stage</span> hir</span>
<span id="cb23-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> emit program.cjcl <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--stage</span> mir</span>
<span id="cb23-4"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> emit program.cjcl <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--stage</span> mir <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--opt</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--diff</span></span></code></pre></div>
<table class="table">
<thead>
<tr class="header">
<th>Flag</th>
<th>What it does</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>-s, --stage ast\|hir\|mir</code></td>
<td>Pipeline stage (default: <code>mir</code>)</td>
</tr>
<tr class="even">
<td><code>--opt</code></td>
<td>Show optimized MIR</td>
</tr>
<tr class="odd">
<td><code>--diff</code></td>
<td>Show both unoptimized and optimized MIR</td>
</tr>
</tbody>
</table>
<p><strong>When to use it:</strong> Understanding what the compiler does to your code. Debugging unexpected behavior by inspecting the IR. Verifying that optimizations preserve semantics.</p>
</section>
<section id="explain" class="level3">
<h3 class="anchored" data-anchor-id="explain">explain</h3>
<p>Show the desugared form of a program after HIR lowering.</p>
<div class="sourceCode" id="cb24" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb24-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> explain program.cjcl</span>
<span id="cb24-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> explain program.cjcl <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--verbose</span></span></code></pre></div>
<p>Shows function signatures, effect annotations, struct definitions, and a summary table. With <code>--verbose</code>, shows full HIR bodies. Useful for understanding what syntactic sugar (for-loops, match expressions, closures) compiles down to.</p>
</section>
<section id="gc" class="level3">
<h3 class="anchored" data-anchor-id="gc">gc</h3>
<p>Dedicated GC analysis with allocation timeline.</p>
<div class="sourceCode" id="cb25" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb25-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> gc program.cjcl <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--runs</span> 5 <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--verbose</span></span></code></pre></div>
<p>Executes the program multiple times and reports GC pause count, collections per run, heap object peaks, COW copies, and execution time. Shows whether GC behavior is stable across runs.</p>
<table class="table">
<thead>
<tr class="header">
<th>Flag</th>
<th>What it does</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>-n, --runs &lt;N&gt;</code></td>
<td>Number of runs (default: 3)</td>
</tr>
<tr class="even">
<td><code>-v, --verbose</code></td>
<td>Show allocation timeline</td>
</tr>
</tbody>
</table>
<p><strong>When to use it:</strong> Identifying allocation hotspots. Verifying GC stability for determinism.</p>
</section>
<section id="nogc" class="level3">
<h3 class="anchored" data-anchor-id="nogc">nogc</h3>
<p>Static verification that <code>#[nogc]</code> functions don’t allocate.</p>
<div class="sourceCode" id="cb26" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb26-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> nogc program.cjcl</span>
<span id="cb26-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> nogc program.cjcl <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--verbose</span></span></code></pre></div>
<p>Lists all functions with their <code>#[nogc]</code> annotation status and runs the NoGC verifier. Reports per-function PASS/FAIL with violation details. Exit code 1 if any violations found.</p>
<p><strong>When to use it:</strong> Verifying that performance-critical functions avoid GC. Required for functions that will run in the NoGC execution mode.</p>
</section>
<section id="audit" class="level3">
<h3 class="anchored" data-anchor-id="audit">audit</h3>
<p>Static analysis for floating-point anti-patterns.</p>
<div class="sourceCode" id="cb27" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb27-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> audit program.cjcl</span>
<span id="cb27-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> audit program.cjcl <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--strict</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--category</span> summation</span>
<span id="cb27-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> audit program.cjcl <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--baseline</span> baseline.json <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--report</span> findings.json</span></code></pre></div>
<p>Detects: naive summation in loops (suggests <code>kahan_sum()</code>), float equality comparisons (suggests <code>approx_eq()</code>), unguarded division, and potential catastrophic cancellation. No execution required — purely static analysis.</p>
<table class="table">
<colgroup>
<col style="width: 50%">
<col style="width: 50%">
</colgroup>
<thead>
<tr class="header">
<th>Flag</th>
<th>What it does</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>--strict</code></td>
<td>Treat INFO as warnings, warnings as errors</td>
</tr>
<tr class="even">
<td><code>--category &lt;type&gt;</code></td>
<td>Filter: <code>summation</code>, <code>float-eq</code>, <code>division</code>, <code>cancellation</code></td>
</tr>
<tr class="odd">
<td><code>--baseline &lt;file&gt;</code></td>
<td>Only report new findings</td>
</tr>
<tr class="even">
<td><code>--report &lt;file&gt;</code></td>
<td>Save findings as JSON</td>
</tr>
<tr class="odd">
<td><code>--suggest-only</code></td>
<td>Only show suggestions</td>
</tr>
</tbody>
</table>
<p><strong>When to use it:</strong> Code review for numerical code. CI gate for floating-point hygiene. Onboarding — audit unfamiliar numerical code before trusting its output.</p>
</section>
<section id="precision" class="level3">
<h3 class="anchored" data-anchor-id="precision">precision</h3>
<p>Analyze whether f64 precision matters for your output.</p>
<div class="sourceCode" id="cb28" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb28-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> precision program.cjcl</span>
<span id="cb28-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> precision program.cjcl <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--epsilon</span> 1e-10 <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--fail-on-instability</span></span></code></pre></div>
<p>Executes the program, extracts numeric values from output, compares f64 against f32-truncated values, and reports relative error per value. Verdict: STABLE (within epsilon) or SENSITIVE (exceeds epsilon).</p>
<table class="table">
<colgroup>
<col style="width: 50%">
<col style="width: 50%">
</colgroup>
<thead>
<tr class="header">
<th>Flag</th>
<th>What it does</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>--epsilon &lt;N&gt;</code></td>
<td>Comparison threshold (default: ~1.19e-7, f32 epsilon)</td>
</tr>
<tr class="even">
<td><code>--fail-on-instability</code></td>
<td>Exit 1 if any value exceeds epsilon</td>
</tr>
<tr class="odd">
<td><code>--summary-only</code></td>
<td>Only show pass/fail</td>
</tr>
<tr class="even">
<td><code>--report &lt;file&gt;</code></td>
<td>Save report as JSON</td>
</tr>
</tbody>
</table>
<p><strong>When to use it:</strong> Deciding whether f32 is sufficient for a workload. Identifying precision-sensitive computations. CI gate for numerical stability.</p>
</section>
<section id="lock" class="level3">
<h3 class="anchored" data-anchor-id="lock">lock</h3>
<p>Generate and verify deterministic lockfiles.</p>
<div class="sourceCode" id="cb29" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb29-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> lock program.cjcl</span>
<span id="cb29-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> lock program.cjcl <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--verify</span></span>
<span id="cb29-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> lock program.cjcl <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--show</span></span>
<span id="cb29-4"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> lock program.cjcl <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--diff</span></span>
<span id="cb29-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> lock program.cjcl <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--executor</span> mir <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--update</span></span></code></pre></div>
<p>Creates a lockfile containing: source SHA-256 hash, seed, executor version, platform, and expected output SHA-256 hash. With <code>--verify</code>, checks that the current execution matches the recorded lockfile.</p>
<table class="table">
<thead>
<tr class="header">
<th>Flag</th>
<th>What it does</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>--verify</code></td>
<td>Verify current run against lockfile</td>
</tr>
<tr class="even">
<td><code>--update</code></td>
<td>Regenerate lockfile</td>
</tr>
<tr class="odd">
<td><code>--show</code></td>
<td>Display lockfile contents</td>
</tr>
<tr class="even">
<td><code>--diff</code></td>
<td>Show field-by-field differences</td>
</tr>
<tr class="odd">
<td><code>--executor eval\|mir</code></td>
<td>Which executor (default: eval)</td>
</tr>
</tbody>
</table>
<p><strong>When to use it:</strong> Pinning expected output for CI. Detecting when a code or platform change alters results. Cross-platform reproducibility verification.</p>
</section>
<section id="parity" class="level3">
<h3 class="anchored" data-anchor-id="parity">parity</h3>
<p>Verify that both executors produce identical results.</p>
<div class="sourceCode" id="cb30" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb30-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> parity program.cjcl</span>
<span id="cb30-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> parity program.cjcl <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--verbose</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--explain-mismatch</span></span>
<span id="cb30-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> parity program.cjcl <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--function</span> my_function <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--save-report</span> parity.json</span></code></pre></div>
<p>Runs both the AST interpreter (v1) and MIR executor (v2) with the same seed and compares stdout, exit status, return value, and GC collections.</p>
<table class="table">
<thead>
<tr class="header">
<th>Flag</th>
<th>What it does</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>-v, --verbose</code></td>
<td>Show output from both executors</td>
</tr>
<tr class="even">
<td><code>--explain-mismatch</code></td>
<td>Show first divergent line with context</td>
</tr>
<tr class="odd">
<td><code>--function &lt;name&gt;</code></td>
<td>Only test a specific function</td>
</tr>
<tr class="even">
<td><code>--save-report &lt;file&gt;</code></td>
<td>Save report as JSON</td>
</tr>
</tbody>
</table>
<p><strong>When to use it:</strong> After implementing a new language feature. Before shipping a compiler change. CI gate for executor equivalence.</p>
</section>
<section id="test" class="level3">
<h3 class="anchored" data-anchor-id="test">test</h3>
<p>Native test runner for CJC-Lang test functions.</p>
<div class="sourceCode" id="cb31" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb31-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> test program.cjcl</span>
<span id="cb31-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> test program.cjcl <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--filter</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"matrix"</span></span>
<span id="cb31-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> test program.cjcl <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--verbose</span></span></code></pre></div>
<p>Discovers functions whose name starts with <code>test_</code> (or decorated with <code>@test</code>), runs them in alphabetical order with a fresh interpreter per test, and reports pass/fail with timing.</p>
<table class="table">
<thead>
<tr class="header">
<th>Flag</th>
<th>What it does</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>--filter &lt;pattern&gt;</code></td>
<td>Only run matching tests</td>
</tr>
<tr class="even">
<td><code>-v, --verbose</code></td>
<td>Show test output and errors</td>
</tr>
</tbody>
</table>
<p><strong>When to use it:</strong> Running tests written in CJC-Lang itself. Quick smoke tests without a Rust test harness.</p>
</section>
<section id="ci" class="level3">
<h3 class="anchored" data-anchor-id="ci">ci</h3>
<p>Full CI diagnostic suite — runs five checks in sequence.</p>
<div class="sourceCode" id="cb32" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb32-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> ci</span>
<span id="cb32-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> ci ./my_project <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--strict</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--json</span></span></code></pre></div>
<p>Orchestrates: <code>doctor</code> (parse/type errors) → <code>proof</code> (determinism) → <code>parity</code> (dual-executor) → <code>test</code> (test functions) → <code>nogc</code> (static verification). Reports per-step pass/fail.</p>
<table class="table">
<thead>
<tr class="header">
<th>Flag</th>
<th>What it does</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>--seed &lt;N&gt;</code></td>
<td>Base RNG seed (default: 42)</td>
</tr>
<tr class="even">
<td><code>--strict</code></td>
<td>Exit 1 on warnings</td>
</tr>
</tbody>
</table>
<p><strong>When to use it:</strong> One command in your CI pipeline that checks everything. The nuclear option for project health.</p>
<hr>
</section>
</section>
<section id="putting-it-together" class="level2">
<h2 class="anchored" data-anchor-id="putting-it-together">Putting It Together</h2>
<p>The real power of the CLI is in composition. Here are four workflows I use regularly:</p>
<section id="i-just-wrote-new-code" class="level3">
<h3 class="anchored" data-anchor-id="i-just-wrote-new-code">“I just wrote new code”</h3>
<div class="sourceCode" id="cb33" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb33-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> check program.cjcl          <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Type errors?</span></span>
<span id="cb33-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> run program.cjcl             <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Does it work?</span></span>
<span id="cb33-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> parity program.cjcl          <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Same result on both executors?</span></span></code></pre></div>
</section>
<section id="my-pipeline-is-slow" class="level3">
<h3 class="anchored" data-anchor-id="my-pipeline-is-slow">“My pipeline is slow”</h3>
<div class="sourceCode" id="cb34" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb34-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> trace program.cjcl           <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Where does time go?</span></span>
<span id="cb34-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> mem program.cjcl <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--timeline</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># GC pressure?</span></span>
<span id="cb34-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> bench program.cjcl <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--runs</span> 20 <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--compare</span> eval<span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">|</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">mir</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Which executor is faster?</span></span></code></pre></div>
</section>
<section id="im-shipping-a-reproducible-artifact" class="level3">
<h3 class="anchored" data-anchor-id="im-shipping-a-reproducible-artifact">“I’m shipping a reproducible artifact”</h3>
<div class="sourceCode" id="cb35" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb35-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> lock program.cjcl            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Pin expected output</span></span>
<span id="cb35-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> pack program.cjcl <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--verify</span>   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Bundle with manifest</span></span>
<span id="cb35-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> proof program.cjcl <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--runs</span> 10 <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--seeds</span> 42,123,999  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Determinism across seeds</span></span></code></pre></div>
</section>
<section id="the-numbers-dont-look-right" class="level3">
<h3 class="anchored" data-anchor-id="the-numbers-dont-look-right">“The numbers don’t look right”</h3>
<div class="sourceCode" id="cb36" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb36-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> audit program.cjcl           <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Float anti-patterns?</span></span>
<span id="cb36-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> precision program.cjcl       <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># f64 vs f32 sensitivity?</span></span>
<span id="cb36-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">cjcl</span> drift output_v1.csv output_v2.csv <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--tolerance</span> 1e-6  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># What changed?</span></span></code></pre></div>
<hr>
</section>
</section>
<section id="global-flags" class="level2">
<h2 class="anchored" data-anchor-id="global-flags">Global Flags</h2>
<p>These flags work with any command:</p>
<table class="table">
<thead>
<tr class="header">
<th>Flag</th>
<th>What it does</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>--seed &lt;N&gt;</code></td>
<td>RNG seed (default: 42)</td>
</tr>
<tr class="even">
<td><code>--reproducible</code></td>
<td>Enable reproducibility mode</td>
</tr>
<tr class="odd">
<td><code>--time</code></td>
<td>Print execution time to stderr</td>
</tr>
<tr class="even">
<td><code>--color</code> / <code>--no-color</code></td>
<td>Control ANSI color output</td>
</tr>
<tr class="odd">
<td><code>--diagnostic-format rich\|short</code></td>
<td>Error display format</td>
</tr>
<tr class="even">
<td><code>--help, -h</code></td>
<td>Print help</td>
</tr>
<tr class="odd">
<td><code>--version, -V</code></td>
<td>Print version</td>
</tr>
</tbody>
</table>
<div class="callout callout-style-simple callout-note no-icon">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-body-container">
<p>CJC-Lang is open source and available on <a href="https://crates.io/crates/cjc-lang">crates.io</a>. All 30 commands are included in the single <code>cjcl</code> binary. Run <code>cjcl &lt;command&gt; --help</code> for command-specific help.</p>
</div>
</div>
</div>


</section>

 ]]></description>
  <category>CJC-Lang</category>
  <category>CLI</category>
  <category>reference</category>
  <category>tools</category>
  <category>data-engineering</category>
  <guid>https://adamezzat1.github.io/blog/posts/cjcl-cli-reference/index.html</guid>
  <pubDate>Mon, 13 Apr 2026 07:00:00 GMT</pubDate>
</item>
<item>
  <title>TidyView: A Database Engine in Disguise</title>
  <dc:creator>Adam Ezzat</dc:creator>
  <link>https://adamezzat1.github.io/blog/posts/tidyview-data-engine/index.html</link>
  <description><![CDATA[ 



<div class="reading-time">
🕑 ~30 min read
</div>
<div class="stats-row">
<div class="stat">
<span class="number">3</span><span class="stat-label">layers</span>
</div>
<div class="stat">
<span class="number">103</span><span class="stat-label">operations</span>
</div>
<div class="stat">
<span class="number">0</span><span class="stat-label">HashMaps</span>
</div>
<div class="stat">
<span class="number">38</span><span class="stat-label">benchmark tests</span>
</div>
</div>
<div class="callout callout-style-simple callout-tip no-icon callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-title-container flex-fill">
TL;DR
</div>
</div>
<div class="callout-body-container callout-body">
<p>TidyView is CJC-Lang’s data manipulation system. It looks like R’s dplyr on the surface, but underneath it’s a database engine: columnar storage, lazy evaluation, bitmask filtering, predicate pushdown, and zone maps. The result is a data pipeline where every intermediate state is inspectable, the original data is never mutated, and the same input always produces the same output — down to the last bit, on every run. (Tested on Windows; cross-platform verification is ongoing.)</p>
</div>
</div>
<section id="the-problem" class="level2">
<h2 class="anchored" data-anchor-id="the-problem">The Problem</h2>
<p>When I started building <a href="../../../blog/posts/introducing-cjc/index.html">CJC-Lang</a>, the core promise was simple: <strong>same seed, same output, always.</strong> Every part of the language enforces this — ordered collections, Kahan summation, seeded RNG, no FMA in SIMD kernels.</p>
<p>But then I needed to handle data. Filter a table. Group rows. Compute aggregates. Join two datasets.</p>
<p>The obvious move was to reach for an existing DataFrame library. But when I looked at what’s available, I kept running into the same problems:</p>
<p><strong>Hash-based grouping.</strong> Most DataFrame libraries — including Pandas and Polars — use hash maps to group rows. Hash maps are fast, but their iteration order may depend on internal hash seeds or execution strategy. Depending on the configuration, grouping the same data twice can produce rows in a different order. That makes bit-identical reproducibility difficult to guarantee.</p>
<p><strong>Floating-point reduction order.</strong> Call <code>sum()</code> on a million floats in Pandas. The result depends on the order of accumulation. Add the numbers in a different order and you get different rounding errors. Small differences, but they compound — and they mean “same data, different result” depending on implementation details. (Polars and other libraries may use more sophisticated reduction strategies, but the result can still vary across execution modes or platforms.)</p>
<p><strong>Parallel scheduling.</strong> Libraries that use thread pools for parallel execution (Polars uses Rayon, for example) gain significant throughput. But thread scheduling is inherently non-deterministic, which means parallel aggregation can introduce ordering variation between runs — even with identical input. Whether this matters depends on the workload, but for bit-identical reproducibility it’s a concern.</p>
<p><strong>Aliasing mutations.</strong> Pandas has an entire warning category (<code>SettingWithCopyWarning</code>) dedicated to the fact that modifying a filtered subset might or might not modify the original DataFrame. Whether it does depends on internal memory layout decisions that are invisible to the user.</p>
<p>None of these are bugs. They’re design trade-offs made by libraries that prioritize speed over reproducibility. But they break CJC-Lang’s core contract. So I built something else.</p>
</section>
<section id="what-tidyview-is" class="level2">
<h2 class="anchored" data-anchor-id="what-tidyview-is">What TidyView Is</h2>
<p>TidyView is a three-layer data manipulation system:</p>
<table class="table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Layer</th>
<th>Type</th>
<th>What it does</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Immutable view</strong></td>
<td><code>TidyView</code></td>
<td>Zero-copy lens: bitmask + projection over shared data</td>
</tr>
<tr class="even">
<td><strong>Materialized frame</strong></td>
<td><code>TidyFrame</code></td>
<td>Copy-on-write buffer for mutations</td>
</tr>
<tr class="odd">
<td><strong>Grouped view</strong></td>
<td><code>GroupedTidyView</code></td>
<td>Group index for aggregation</td>
</tr>
</tbody>
</table>
<p>The API surface is modeled on R’s dplyr — <code>filter</code>, <code>select</code>, <code>mutate</code>, <code>group_by</code>, <code>summarise</code>, <code>arrange</code>, <code>join</code>. If you’ve used the tidyverse, you already know the vocabulary.</p>
<p>But the implementation borrows from a different tradition entirely: <strong>analytical databases.</strong></p>
<section id="how-data-flows-through-a-tidyview-pipeline" class="level3">
<h3 class="anchored" data-anchor-id="how-data-flows-through-a-tidyview-pipeline">How Data Flows Through a TidyView Pipeline</h3>
<div class="cell" data-fig-width="7">
<div class="cell-output-display">
<div>
<div>

<pre class="mermaid mermaid-js">flowchart TD
    A["CSV / Data Source"] --&gt; B["Base DataFrame&lt;br/&gt;&lt;i&gt;immutable, Rc-shared&lt;/i&gt;"]
    B --&gt; C["TidyView&lt;br/&gt;&lt;i&gt;zero-copy lens&lt;/i&gt;"]
    C --&gt; D["filter( )&lt;br/&gt;&lt;i&gt;bitmask A — no data copied&lt;/i&gt;"]
    D --&gt; E["filter( )&lt;br/&gt;&lt;i&gt;bitmask B — AND with A&lt;/i&gt;"]
    E --&gt; F["group_by( )&lt;br/&gt;&lt;i&gt;BTreeMap index over visible rows&lt;/i&gt;"]
    F --&gt; G["summarise( )&lt;br/&gt;&lt;i&gt;Kahan reduction per group&lt;/i&gt;"]
    G --&gt; H["Materialized Result&lt;br/&gt;&lt;i&gt;TidyFrame — new data&lt;/i&gt;"]

    style A fill:#f9f9f9,stroke:#333
    style B fill:#e8f4fd,stroke:#2196F3,stroke-width:2px
    style C fill:#e8f4fd,stroke:#2196F3
    style D fill:#fff3e0,stroke:#FF9800
    style E fill:#fff3e0,stroke:#FF9800
    style F fill:#f3e5f5,stroke:#9C27B0
    style G fill:#f3e5f5,stroke:#9C27B0
    style H fill:#e8f5e9,stroke:#4CAF50,stroke-width:2px
</pre>
</div>
</div>
</div>
</div>
<p>Every step from the base DataFrame through the filters shares the same underlying data. New memory is only allocated for bitmasks (orange) and the final materialized result (green). The original data (blue) is never modified.</p>
</section>
</section>
<section id="the-database-connection" class="level2">
<h2 class="anchored" data-anchor-id="the-database-connection">The Database Connection</h2>
<p>I didn’t set out to build a database engine. But the constraints I was working under — determinism, auditability, memory safety — kept pushing me toward the same solutions that database engineers arrived at decades ago.</p>
<section id="columnar-storage" class="level3">
<h3 class="anchored" data-anchor-id="columnar-storage">Columnar Storage</h3>
<p>Data is stored column-major:</p>
<pre><code>// Internally:
columns: [
    ("name",    Str(["Alice", "Bob", "Carol", "Dave"])),
    ("age",     Int([30, 25, 35, 28])),
    ("revenue", Float([150.0, 200.0, 175.0, 225.0])),
]</code></pre>
<p>Each column is a contiguous typed buffer. When you compute <code>sum(revenue)</code>, the runtime scans a single <code>Vec&lt;f64&gt;</code> — no pointer chasing, no skipping over unrelated fields. This is the same storage model used by Apache Arrow, DuckDB, and Parquet.</p>
<p>The alternative — row-major storage — would scatter each <code>revenue</code> value across a row struct alongside <code>name</code> and <code>age</code> data. For a table with 100 columns, computing a single-column aggregate would touch 100x more memory than necessary.</p>
</section>
<section id="bitmask-filtering" class="level3">
<h3 class="anchored" data-anchor-id="bitmask-filtering">Bitmask Filtering</h3>
<p>This is the design choice that makes everything else work. When you filter a TidyView, <strong>no data is copied.</strong> Instead, a bitmask records which rows passed the predicate:</p>
<pre><code>// Original data: 4 rows
base: ["Alice", "Bob", "Carol", "Dave"]  // ages: 30, 25, 35, 28

// After filter(age &gt; 28):
mask: [1, 0, 1, 1]  // Alice (30), Carol (35), Dave (28 fails, hypothetical: &gt; 28)</code></pre>
<p>In practice the mask is packed into 64-bit words — one bit per row. A million-row filter costs 122 KB of bitmask memory. The original data stays exactly where it was, untouched.</p>
<p>Chain three filters together? The bitmasks are ANDed. Still no data copied. Still 122 KB.</p>
<pre><code>let sales: Any = read_csv("sales.csv");
let df: Any = sales
    |&gt; filter(col("region") == "West")
    |&gt; filter(col("revenue") &gt; 1000.0)
    |&gt; filter(col("year") == 2025);</code></pre>
<p>Each <code>filter</code> call returns a new TidyView with a new bitmask. All three views share the same underlying data. The CSV is loaded once. This is the same technique used by bitmap indexes in Oracle and selection vectors in DuckDB.</p>
</section>
<section id="predicate-pushdown" class="level3">
<h3 class="anchored" data-anchor-id="predicate-pushdown">Predicate Pushdown</h3>
<p>TidyView has a lazy evaluation layer where operations build a logical plan tree before executing. The optimizer rewrites this tree using three classic database passes:</p>
<p><strong>Pass 1 — Filter merging.</strong> Consecutive filters are combined into a single filter with an AND predicate.</p>
<p><strong>Pass 2 — Predicate pushdown.</strong> Filters are pushed as close to the data source as possible.</p>
<p><strong>Pass 3 — Redundant select elimination.</strong> Select nodes that project all columns are removed.</p>
<p>Here’s why this matters. Suppose you write:</p>
<pre><code>let result: Any = df
    |&gt; mutate("bonus", col("salary") * 0.1)
    |&gt; filter(col("department") == "Engineering");</code></pre>
<p>Without optimization, this computes <code>bonus</code> for every row, then filters. With predicate pushdown, the optimizer recognizes that <code>department == "Engineering"</code> doesn’t depend on the <code>bonus</code> column, so it pushes the filter <em>below</em> the mutate. Now filtering happens first, and <code>bonus</code> is only computed for engineering rows.</p>
<p>The pushdown rules follow standard relational algebra:</p>
<ul>
<li>Push past <code>select</code> — always safe</li>
<li>Push past <code>arrange</code> — filtering doesn’t affect sort order</li>
<li>Push past <code>mutate</code> — only if the predicate doesn’t reference a mutated column</li>
<li>Push into <code>join</code> branches — if the predicate references only one side</li>
<li>Never past <code>group_by</code> + <code>summarise</code> — aggregation changes row identity</li>
<li>Never past <code>distinct</code> — uniqueness changes row identity</li>
</ul>
<p>These are the same rules taught in database systems textbooks. They apply here because TidyView’s operation set <em>is</em> relational algebra — just with tidyverse names.</p>
</section>
<section id="zone-maps" class="level3">
<h3 class="anchored" data-anchor-id="zone-maps">Zone Maps</h3>
<p>Each column tracks lightweight statistics:</p>
<pre><code>// ColumnStats for the "revenue" column:
min: 150.0
max: 225.0
sorted_ascending: false
n_distinct: 4</code></pre>
<p>When you write <code>filter(col("revenue") &gt; 500.0)</code>, the zone map says: max is 225, so no row can satisfy <code>&gt; 500</code>. The entire filter can be skipped.</p>
<p>When a column is sorted, the system uses binary search instead of a linear scan — O(log N) instead of O(N).</p>
<p>These are the same optimizations that Vertica, Redshift, and Parquet use at the storage layer. In TidyView they operate at the column level because the entire “database” fits in memory.</p>
</section>
</section>
<section id="what-this-prevents" class="level2">
<h2 class="anchored" data-anchor-id="what-this-prevents">What This Prevents</h2>
<p>The architectural choices above aren’t just about performance. They prevent specific classes of bugs that plague traditional DataFrame workflows.</p>
<section id="the-aliasing-problem" class="level3">
<h3 class="anchored" data-anchor-id="the-aliasing-problem">The Aliasing Problem</h3>
<p>In Pandas:</p>
<div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1">df <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pd.DataFrame({<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"x"</span>: [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>], <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"y"</span>: [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span>]})</span>
<span id="cb6-2">subset <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> df[df.x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb6-3">subset[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"z"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">99</span>      <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Does this modify df? Maybe.</span></span></code></pre></div>
<p>Whether <code>subset["z"] = 99</code> modifies the original <code>df</code> depends on whether Pandas chose to return a view or a copy when you wrote <code>df[df.x &gt; 1]</code>. This is the <code>SettingWithCopyWarning</code> — one of the most common sources of confusion in data science code.</p>
<p>In TidyView, this problem doesn’t exist. The base data lives behind an <code>Rc</code> (reference-counted pointer) and is never modified. When you need to mutate, <code>TidyFrame</code> checks the reference count:</p>
<ul>
<li>If only one reference exists, mutate in place</li>
<li>If multiple references exist, deep-clone first, then mutate the clone</li>
</ul>
<p>The check is deterministic — it’s a simple integer comparison, not a heuristic. The other views are never affected.</p>
</section>
<section id="the-order-dependent-mutation-problem" class="level3">
<h3 class="anchored" data-anchor-id="the-order-dependent-mutation-problem">The Order-Dependent Mutation Problem</h3>
<p>In Pandas:</p>
<div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1">df <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> df.assign(a <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> df.x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, b <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> df.a <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span></code></pre></div>
<p>Does <code>df.a</code> in the second expression see the <em>newly computed</em> <code>a</code> or the <em>original</em> <code>a</code>? In Pandas, it sees the new one. The result depends on the order of the assignments. Swap <code>a</code> and <code>b</code> and you get different data.</p>
<p>In TidyView, <code>mutate</code> uses <strong>snapshot semantics</strong>: all column references are validated against the column list frozen <em>before</em> any assignments execute. Every expression sees the data as it was when the <code>mutate</code> call started:</p>
<pre><code>let result: Any = df
    |&gt; mutate("a", col("x") + 1)
    |&gt; mutate("b", col("a") * 2);
// col("a") always reads the ORIGINAL "a" column
// If "a" didn't exist before, this is an error — not a silent dependency</code></pre>
<p>This is the same principle as SQL’s <code>UPDATE SET a = b, b = a</code> — the SET clause reads from a snapshot, so this correctly swaps the values rather than setting both to <code>b</code>.</p>
</section>
<section id="the-hidden-null-problem" class="level3">
<h3 class="anchored" data-anchor-id="the-hidden-null-problem">The Hidden Null Problem</h3>
<p>Pandas DataFrames can have <code>NaN</code> or <code>None</code> in any column at any time. You discover this at runtime, usually when an aggregation silently skips missing values and gives you a number that’s subtly wrong.</p>
<p>In TidyView, the base <code>Column</code> type has <strong>no null representation</strong>:</p>
<pre><code>// Column — always fully populated:
Int(Vec&lt;i64&gt;)
Float(Vec&lt;f64&gt;)
Str(Vec&lt;String&gt;)
Bool(Vec&lt;bool&gt;)</code></pre>
<p>There’s no <code>Option</code> wrapper. No sentinel values. If you have a <code>TidyView</code>, its data is guaranteed null-free by type.</p>
<p>Nulls exist, but they’re quarantined in a separate type called <code>NullCol</code>. You only encounter <code>NullCol</code> through operations that <em>can</em> introduce nulls — left joins (unmatched rows), <code>pivot_wider</code> (missing combinations), full joins. These operations return a <code>NullableFrame</code> instead of a <code>TidyFrame</code>, and the type boundary makes the null-possibility explicit.</p>
<p>The question “does my data have nulls?” is answered by the type, not by scanning every cell.</p>
</section>
<section id="the-floating-point-drift-problem" class="level3">
<h3 class="anchored" data-anchor-id="the-floating-point-drift-problem">The Floating-Point Drift Problem</h3>
<div class="sourceCode" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb10-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Pandas</span></span>
<span id="cb10-2">pd.Series([<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e16</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e16</span>]).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>()  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># → 0.0 (wrong)</span></span></code></pre></div>
<p>The mathematically correct answer is <code>1.0</code>. Pandas gives <code>0.0</code> because it uses naive left-to-right summation: <code>1e16 + 1.0</code> rounds to <code>1e16</code> (the <code>1.0</code> is lost to floating-point precision), then <code>1e16 - 1e16 = 0.0</code>.</p>
<p>TidyView uses Kahan compensated summation for every floating-point reduction — <code>sum</code>, <code>mean</code>, <code>variance</code>, <code>sd</code>, all of them. Kahan summation tracks a correction term that captures the rounding error:</p>
<pre><code>// Kahan summation:
//   sum = 0, c = 0
//   y = 1e16 - 0 = 1e16;    t = 0 + 1e16 = 1e16;    c = (1e16 - 1e16) - 1e16 = 0
//   y = 1.0 - 0 = 1.0;      t = 1e16 + 1.0 = 1e16;   c = (1e16 - 1e16) - 1.0 = -1.0
//   y = -1e16 - (-1.0);      ...compensates the lost 1.0
//   Result: 1.0 (correct)</code></pre>
<p>Same input, same result, every time. Not approximately correct — exactly correct within the limits of double precision.</p>
</section>
</section>
<section id="tidyview-in-practice" class="level2">
<h2 class="anchored" data-anchor-id="tidyview-in-practice">TidyView in Practice</h2>
<p>The architecture above is interesting in the abstract, but what does it actually look like when you write code? Here’s a walkthrough of real CJC-Lang programs using TidyView, showing how operations chain together and how you can audit every step.</p>
<section id="loading-and-filtering" class="level3">
<h3 class="anchored" data-anchor-id="loading-and-filtering">Loading and Filtering</h3>
<pre><code>let csv = "name,age,city\nAlice,30,NYC\nBob,25,LA\nCharlie,35,NYC\nDiana,28,LA\n";
let df = Csv.parse(csv);
let view = df.view();

// filter: age &gt; 28 (keeps Alice=30, Charlie=35)
let older = view.filter(dexpr_binop("&gt;", col("age"), 28));
print(older.nrows());   // → 2</code></pre>
<p><code>Csv.parse()</code> returns a DataFrame. <code>.view()</code> wraps it in a TidyView — a zero-copy lens over the data. The <code>filter</code> call doesn’t copy any rows. It creates a new bitmask: <code>[1, 0, 1, 0]</code>. The original four rows are still sitting in the same memory, untouched.</p>
</section>
<section id="chaining-operations" class="level3">
<h3 class="anchored" data-anchor-id="chaining-operations">Chaining Operations</h3>
<p>TidyView operations return new views or frames, so they chain naturally. Each step narrows, reshapes, or aggregates the data — and every intermediate result is a value you can bind to a variable and inspect later.</p>
<pre><code>let csv = "dept,name,salary\nEng,Alice,100\nEng,Bob,120\nSales,Carol,80\nSales,Dave,90\nEng,Eve,110\n";
let df = Csv.parse(csv);
let view = df.view();

// Chain: filter → group → summarise
let result = view
    .filter(dexpr_binop("&gt;", col("salary"), 85))
    .group_by(["dept"])
    .summarise("n", tidy_count(), "avg", tidy_mean("salary"));
print(result.nrows());   // → 2 (Eng and Sales groups)</code></pre>
<p>This is a three-step pipeline: filter rows where salary exceeds 85, group surviving rows by department, then compute count and mean per group. The filter produces a bitmask; the group-by reads through it to build its index; the summarise collapses each group into one row.</p>
<p>Here’s a longer chain that shows more of the verb vocabulary:</p>
<pre><code>let orders = Csv.parse("product,region,qty,price\nWidget,West,10,25.0\nGadget,East,5,40.0\nWidget,East,8,25.0\nGizmo,West,3,100.0\nWidget,West,12,25.0\nGadget,West,7,40.0\n");
let view = orders.view();

// Chain: filter → mutate → arrange → select
let report = view
    .filter(dexpr_binop("==", col("region"), "West"))   // keep West only
    .mutate("revenue", dexpr_binop("*", col("qty"), col("price")))  // add revenue column
    .view()                                               // back to immutable view
    .arrange([desc("revenue")])                           // sort by revenue descending
    .select(["product", "revenue"]);                      // keep only these columns

// Result: Gizmo/300.0, Widget/300.0, Widget/250.0, Gadget/280.0
// Original `orders` still has all 6 rows, all 4 columns, untouched</code></pre>
<p>Each verb in the chain returns a new value — a <code>TidyView</code>, a <code>TidyFrame</code>, or a <code>GroupedTidyView</code>. The types guide what you can do next: you can’t call <code>summarise</code> unless you’ve called <code>group_by</code> first, and <code>mutate</code> returns a <code>TidyFrame</code> (because it creates new data) while <code>filter</code> and <code>select</code> return <code>TidyView</code> (because they only narrow what’s visible).</p>
</section>
<section id="grouped-aggregation-with-multiple-statistics" class="level3">
<h3 class="anchored" data-anchor-id="grouped-aggregation-with-multiple-statistics">Grouped Aggregation with Multiple Statistics</h3>
<pre><code>let sales = Csv.parse("region,product,revenue\nWest,Widget,150\nEast,Gadget,200\nWest,Widget,300\nEast,Gadget,225\nWest,Gizmo,100\nEast,Widget,175\n");
let view = sales.view();

let summary = view
    .group_by(["region"])
    .summarise(
        "n",         tidy_count(),
        "total_rev", tidy_sum("revenue"),
        "avg_rev",   tidy_mean("revenue"),
        "min_rev",   tidy_min("revenue"),
        "max_rev",   tidy_max("revenue")
    );
// Groups appear in first-occurrence order: West first, then East
// West: n=3, total=550, avg=183.33, min=100, max=300
// East: n=3, total=600, avg=200.00, min=175, max=225</code></pre>
<p>Group order is always <strong>first-occurrence</strong> — the group that appears first in the data appears first in the result. Not alphabetical, not hash-dependent. Deterministic.</p>
</section>
<section id="adding-computed-columns" class="level3">
<h3 class="anchored" data-anchor-id="adding-computed-columns">Adding Computed Columns</h3>
<pre><code>let mutated = view.mutate("bonus", dexpr_binop("*", col("salary"), 10));
print(mutated.ncols());   // → 4 (dept, name, salary, bonus)</code></pre>
<p><code>mutate</code> returns a <code>TidyFrame</code> — a materialized copy with the new column appended. The original <code>view</code> still has three columns. This is the copy-on-write boundary: reads are free, writes trigger a copy.</p>
</section>
<section id="sorting-and-lazy-permutation" class="level3">
<h3 class="anchored" data-anchor-id="sorting-and-lazy-permutation">Sorting and Lazy Permutation</h3>
<pre><code>let csv = "name,score\nAlice,85\nBob,92\nCharlie,78\nDiana,95\n";
let df = Csv.parse(csv);
let sorted = df.view().arrange([desc("score")]);
let top2 = sorted.slice_head(2);
print(top2.nrows());   // → 2 (Diana=95, Bob=92)</code></pre>
<p><code>arrange</code> computes a permutation vector but does <strong>not</strong> materialize a new DataFrame. The sorted order is stored lazily — operations like <code>select</code>, <code>rename</code>, and <code>relocate</code> pass the permutation through without resolving it. Only when you need physical data (e.g., <code>materialize</code>, <code>filter</code>, <code>slice</code>, <code>group_by</code>) does the sort actually reorder the rows. This means <code>arrange</code> followed by <code>slice_head</code> is significantly faster than sorting the entire dataset.</p>
</section>
<section id="joins" class="level3">
<h3 class="anchored" data-anchor-id="joins">Joins</h3>
<pre><code>let left = Csv.parse("id,name\n10,Alice\n20,Bob\n30,Charlie\n").view();
let right = Csv.parse("id,dept\n10,Eng\n30,Sales\n40,HR\n").view();

let joined = left.inner_join(right, "id", "id");
print(joined.nrows());   // → 2 (id=10 and id=30 match)</code></pre>
<p>Inner join keeps only rows where the key exists in both tables. Alice (id=10) matches Eng, Charlie (id=30) matches Sales. Bob (id=20) and HR (id=40) are dropped.</p>
<p>TidyView supports seven join types: <code>inner_join</code>, <code>left_join</code>, <code>semi_join</code>, <code>anti_join</code>, <code>full_join</code>, <code>cross_join</code>, and <code>natural_join</code>. All use BTreeMap-based lookups with borrowed keys — no per-row string allocation.</p>
</section>
<section id="reshaping" class="level3">
<h3 class="anchored" data-anchor-id="reshaping">Reshaping</h3>
<pre><code>// pivot_longer: columns → rows
let wide = Csv.parse("name,math,english\nAlice,90,85\nBob,78,92\n");
let long = wide.view().pivot_longer(
    ["math", "english"],   // columns to pivot
    "subject",             // new name column
    "score"                // new value column
);
// Result: 4 rows (Alice/math/90, Alice/english/85, Bob/math/78, Bob/english/92)

// pivot_wider: rows → columns
let summary = long.view().pivot_wider("subject", "score");
// Back to: Alice/90/85, Bob/78/92</code></pre>
</section>
<section id="distinct-and-sampling" class="level3">
<h3 class="anchored" data-anchor-id="distinct-and-sampling">Distinct and Sampling</h3>
<pre><code>// distinct: unique rows by column subset
let unique_regions = view.distinct(["region"]);

// deterministic sampling: same seed → same rows, every time
let sample = view.slice_sample(100, seed: 42);</code></pre>
<p><code>slice_sample</code> uses a seeded LCG-based Fisher-Yates shuffle. Same seed, same 100 rows, every run. (Cross-platform determinism has been verified on Windows; Linux and macOS testing is ongoing.)</p>
</section>
</section>
<section id="how-to-audit-a-tidyview-pipeline" class="level2">
<h2 class="anchored" data-anchor-id="how-to-audit-a-tidyview-pipeline">How to Audit a TidyView Pipeline</h2>
<p>This is where the architecture pays off in practice. When something goes wrong in a data pipeline, the question is always: <em>where did the data go wrong?</em></p>
<p>In TidyView, every intermediate step is inspectable because the original data is never modified. Every <code>filter</code> produces a bitmask you can examine. Every <code>group_by</code> produces a group index with key values and row indices. And the original data stays intact behind a shared reference, so you can always go back and check it.</p>
<section id="inspection-methods" class="level3">
<h3 class="anchored" data-anchor-id="inspection-methods">Inspection Methods</h3>
<p>At any point in a pipeline, you can call:</p>
<pre><code>view.mask()           // → the bitmask — which rows are visible
view.proj()           // → the projection map — which columns are visible
grouped.group_index() // → group keys + row indices per group</code></pre>
<p>From the mask:</p>
<ul>
<li><code>mask.iter_set()</code> gives the exact indices of visible rows</li>
<li><code>mask.count_ones()</code> gives the count of surviving rows</li>
</ul>
<p>From the projection:</p>
<ul>
<li><code>proj.indices()</code> gives which column indices are visible</li>
</ul>
<p>The original data is always there behind the <code>Rc&lt;DataFrame&gt;</code> — no operation destroys it.</p>
</section>
<section id="a-worked-example" class="level3">
<h3 class="anchored" data-anchor-id="a-worked-example">A Worked Example</h3>
<p>Suppose you’re analyzing quarterly sales data and you get a result that looks wrong. In a traditional DataFrame library, you’d re-run the pipeline with print statements and hope to catch the problem. In TidyView, you can inspect every intermediate state because nothing was destroyed.</p>
<p>Here’s a concrete scenario. You have eight sales records and you want total revenue by product in the West region:</p>
<pre><code>let sales = Csv.parse("product,region,revenue\nWidget,West,150\nGadget,West,200\nWidget,East,175\nGadget,East,225\nWidget,West,300\nGizmo,East,50\nGadget,West,180\nGizmo,East,75\n");
let view = sales.view();

let west = view.filter(dexpr_binop("==", col("region"), "West"));
let grouped = west.group_by(["product"]);
let summary = grouped.summarise("total_rev", tidy_sum("revenue"));</code></pre>
<p>The summary shows Widget: 450, Gadget: 380. Is that right? Let’s audit backward.</p>
<p><strong>Step 1: Which rows survived the filter?</strong></p>
<p>The bitmask after <code>filter(region == "West")</code> has bits set at positions <code>[0, 1, 4, 6]</code>. That’s four rows out of eight. You can verify: row 0 is Widget/West/150, row 1 is Gadget/West/200, row 4 is Widget/West/300, row 6 is Gadget/West/180. All West. None of the East rows slipped through.</p>
<p><strong>Step 2: How did the groups form?</strong></p>
<p>The group index shows two groups in <strong>first-occurrence order</strong>:</p>
<ul>
<li>Group “Widget” → base-frame rows [0, 4]</li>
<li>Group “Gadget” → base-frame rows [1, 6]</li>
</ul>
<p>Notice: Gizmo isn’t here. Both Gizmo rows were East, so they were filtered out before grouping. If you expected Gizmo to appear, the bitmask tells you exactly why it didn’t — rows 5 and 7 have their bits cleared.</p>
<p><strong>Step 3: Verify the aggregation</strong></p>
<p>Widget revenue: row 0 (150) + row 4 (300) = 450. Gadget revenue: row 1 (200) + row 6 (180) = 380. Both use Kahan compensated summation, so even with millions of rows the result would be bit-identical across runs.</p>
<p><strong>Step 4: Is the original data intact?</strong></p>
<p>Yes. <code>view.nrows()</code> still returns 8. You can re-filter, re-group, or inspect any column at any time. If the final number is wrong, you trace backward through bitmasks and group indices until you find where your understanding diverged from reality.</p>
<p>This is fundamentally different from a system where <code>df = df[df.region == "West"]</code> discards the filtered-out rows. In that system, if the filter was wrong, the evidence is gone. In TidyView, the evidence is the bitmask, and the original data is still there to check against.</p>
</section>
<section id="catching-mistakes-at-compile-time" class="level3">
<h3 class="anchored" data-anchor-id="catching-mistakes-at-compile-time">Catching Mistakes at Compile Time</h3>
<p>TidyView’s type system and error reporting catch common mistakes before they become wrong numbers:</p>
<p><strong>Typo in a column name:</strong></p>
<pre><code>view.filter(dexpr_binop("&gt;", col("revnue"), 100));
// → Error: column `revnue` not found</code></pre>
<p><strong>Self-referencing mutate:</strong></p>
<pre><code>view.mutate("bonus", dexpr_binop("*", col("salary"), 0.1))
    .mutate("double_bonus", dexpr_binop("*", col("bonus"), 2));</code></pre>
<p>The second <code>mutate</code> sees <code>bonus</code> because it’s a separate call on the result of the first. But if you tried to reference a column in the same mutate call that creates it:</p>
<pre><code>view.mutate("a", dexpr_binop("+", col("x"), 1),
            "b", dexpr_binop("*", col("a"), 2));
// → Error: column `a` not found</code></pre>
<p>This is snapshot semantics — all column references are validated against the column list frozen <em>before</em> any assignments. You can’t accidentally create an order-dependent pipeline within a single operation.</p>
<p><strong>Type mismatch:</strong></p>
<pre><code>view.filter(dexpr_binop("+", col("name"), 1));
// → Error: type mismatch — expected Bool predicate, got Int</code></pre>
<p>The filter predicate must evaluate to Bool. Arithmetic on a string column, or a predicate that returns a number instead of true/false, is caught immediately with an error that names the exact types involved.</p>
</section>
<section id="error-messages-that-point-to-the-problem" class="level3">
<h3 class="anchored" data-anchor-id="error-messages-that-point-to-the-problem">Error Messages That Point to the Problem</h3>
<p>When something goes wrong, TidyView’s error types name the exact issue:</p>
<pre><code>ColumnNotFound("revnue")       // Typo — you meant "revenue"
DuplicateColumn("total")       // Two columns with the same name in select
PredicateNotBool { got: "Int" } // filter predicate returned Int, not Bool
TypeMismatch { expected: "Float", got: "Str" }  // arithmetic on a string column
LengthMismatch { expected: 1000, got: 500 }     // broadcast size mismatch
EmptyGroup                     // first() or last() called on a group with zero rows
CapacityExceeded { limit: 65535, got: 70000 }   // too many distinct levels for a factor column</code></pre>
<p>Every error variant names the specific column, type, or value that caused the problem. You know what went wrong and where, not just that something failed.</p>
<p><code>EmptyGroup</code> catches a subtle bug: if an aggressive filter removes all rows from a group and you then call <code>first()</code> or <code>last()</code> in your summarise, you get a clear error instead of a silent null or a crash. <code>CapacityExceeded</code> guards the factor column type — CJC-Lang’s factor columns use <code>u16</code> codes (max 65,535 distinct levels) for cache efficiency, and this error tells you exactly when your data exceeds that budget.</p>
</section>
</section>
<section id="why-not-just-use-polars" class="level2">
<h2 class="anchored" data-anchor-id="why-not-just-use-polars">Why Not Just Use Polars?</h2>
<p>Polars is a good library. It’s fast, it has a lazy API, and it uses Arrow-backed columnar storage. If I were building a Python application, I’d probably use it.</p>
<p>But Polars makes different trade-offs than TidyView:</p>
<table class="table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th></th>
<th>Polars</th>
<th>TidyView</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Grouping</strong></td>
<td>Hash-based (fast; order may vary by execution mode)</td>
<td>BTreeMap (deterministic first-occurrence order)</td>
</tr>
<tr class="even">
<td><strong>Summation</strong></td>
<td>Optimized reduction (fast; may vary across platforms)</td>
<td>Kahan compensated (deterministic, bit-identical)</td>
</tr>
<tr class="odd">
<td><strong>Parallelism</strong></td>
<td>Rayon thread pool (high throughput)</td>
<td>Single-threaded (deterministic, no scheduling variance)</td>
</tr>
<tr class="even">
<td><strong>Null model</strong></td>
<td>Nullable everywhere (Arrow validity bitmaps)</td>
<td>Null-free by default, <code>NullCol</code> only where needed</td>
</tr>
<tr class="odd">
<td><strong>Sampling</strong></td>
<td>System RNG</td>
<td>Seeded LCG-based Fisher-Yates</td>
</tr>
</tbody>
</table>
<p>None of Polars’ trade-offs are wrong — and Polars is significantly faster for most workloads. It’s a production-grade library optimized for throughput. TidyView is optimizing for a different property: <strong>reproducibility</strong> — the guarantee that the same pipeline on the same data always produces the same result, bit for bit, across repeated runs. (All benchmarks and determinism tests were run on Windows; cross-platform verification is ongoing.) TidyView will be slower than Polars on almost any benchmark. That’s the expected cost of determinism.</p>
<p>When your work requires an audit trail — when a regulator, a reviewer, or future-you needs to verify that a result is correct — that guarantee can matter more than raw speed.</p>
</section>
<section id="benchmark-results" class="level2">
<h2 class="anchored" data-anchor-id="benchmark-results">Benchmark Results</h2>
<p>The claims above — bitmask filtering is cheap, chained filters don’t copy data, determinism holds across runs — are testable. I wrote a 38-test benchmark suite that measures TidyView’s performance and verifies its architectural guarantees. Here are the results, run on my machine (Intel i7-11390H, Windows, Rust 1.91 release mode, 100K-row datasets).</p>
<div class="callout callout-style-simple callout-note no-icon">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-body-container">
<p><strong>A note on context.</strong> These benchmarks measure TidyView against itself — lazy vs.&nbsp;eager, scaling behavior, determinism proofs. They are <em>not</em> comparative benchmarks against Polars, DuckDB, or Pandas. TidyView is single-threaded and prioritizes determinism over throughput; it will be slower than production-optimized libraries on raw performance. The goal here is to verify that TidyView’s architectural claims hold, not to claim speed parity with tools that make different trade-offs.</p>
</div>
</div>
</div>
<section id="a.-filter-microbenchmarks" class="level3">
<h3 class="anchored" data-anchor-id="a.-filter-microbenchmarks">A. Filter Microbenchmarks</h3>
<table class="table">
<thead>
<tr class="header">
<th>Test</th>
<th>Lazy (µs)</th>
<th>Eager (µs)</th>
<th>Ratio</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Int filter, 100K rows</td>
<td>165</td>
<td>11,943</td>
<td><strong>72×</strong></td>
</tr>
<tr class="even">
<td>Float filter, 100K rows</td>
<td>191</td>
<td>11,804</td>
<td><strong>62×</strong></td>
</tr>
<tr class="odd">
<td>String filter, 100K rows</td>
<td>352</td>
<td>6,255</td>
<td><strong>18×</strong></td>
</tr>
<tr class="even">
<td>Int filter, 1M rows</td>
<td>2,002</td>
<td>157,135</td>
<td><strong>79×</strong></td>
</tr>
</tbody>
</table>
<p><strong>What this proves:</strong> Lazy bitmask filtering is 62–79× faster than eager materialization for numeric columns. The filter itself just scans one column and sets bits — no row copying, no allocation beyond the bitmask.</p>
<p>String filters benefit from a columnar fast path that evaluates the predicate directly against the column buffer (bitwise, not row-by-row) — bringing them to 352 µs, an <strong>18× lazy/eager ratio</strong>.</p>
<p>The 1M-row int filter takes about 2 ms to produce a bitmask. Materializing — actually copying the surviving rows into a new DataFrame — takes 157 ms. If you’re chaining multiple filters, you save that materialization cost at every step.</p>
</section>
<section id="b.-chained-filter-composition" class="level3">
<h3 class="anchored" data-anchor-id="b.-chained-filter-composition">B. Chained Filter Composition</h3>
<table class="table">
<thead>
<tr class="header">
<th>Test</th>
<th>Time (µs)</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Single int filter, 100K</td>
<td>220</td>
</tr>
<tr class="even">
<td>Two chained int filters, 100K</td>
<td>384</td>
</tr>
<tr class="odd">
<td>Int + string chain, 100K</td>
<td>609</td>
</tr>
<tr class="even">
<td>Two string filters, 100K</td>
<td>813</td>
</tr>
</tbody>
</table>
<p><strong>What this proves:</strong> Chaining two filters costs roughly the sum of the individual filters, not more. The second filter reads through the first filter’s bitmask — it only examines rows where the bit is set. No intermediate DataFrame is created. The cost of the chain is dominated by the most expensive predicate type (string comparison).</p>
</section>
<section id="c.-group-by-summarise" class="level3">
<h3 class="anchored" data-anchor-id="c.-group-by-summarise">C. Group-By + Summarise</h3>
<table class="table">
<thead>
<tr class="header">
<th>Test</th>
<th>Groups</th>
<th>Time (µs)</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Count, 4 groups, 100K</td>
<td>4</td>
<td>3,099</td>
</tr>
<tr class="even">
<td>Mixed agg (5 stats), 4 groups, 100K</td>
<td>4</td>
<td>5,918</td>
</tr>
<tr class="odd">
<td>Count + Sum, 100 groups, 100K</td>
<td>100</td>
<td>10,734</td>
</tr>
<tr class="even">
<td>Count, 10K groups, 100K</td>
<td>10,000</td>
<td>42,778</td>
</tr>
<tr class="odd">
<td>Count + Sum + Mean, 10K groups, 100K</td>
<td>10,000</td>
<td>39,782</td>
</tr>
</tbody>
</table>
<p><strong>What this proves:</strong> Group-by cost scales with the number of distinct groups, not just row count. With 4 groups (the region column), grouping 100K rows takes about 3 ms. With 10K groups, it takes 40 ms. This is the cost of the BTreeMap-based index — O(N log G) where G is the number of groups. Adding more aggregation functions adds relatively little overhead; the dominant cost is building the group index, not computing the aggregates.</p>
<p>The single-key fast path (used when grouping by one column) avoids per-row heap allocation entirely: the BTreeMap key is a borrowed reference into the column data, not a cloned value. For the 4-group case, this means 100K lookups with zero string copies.</p>
</section>
<section id="d.-sorting" class="level3">
<h3 class="anchored" data-anchor-id="d.-sorting">D. Sorting</h3>
<table class="table">
<thead>
<tr class="header">
<th>Test</th>
<th>Time (µs)</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Float descending, 100K</td>
<td>1,378</td>
</tr>
<tr class="even">
<td>Int ascending, 100K</td>
<td>1,201</td>
</tr>
<tr class="odd">
<td>String ascending, 100K</td>
<td>18,343</td>
</tr>
<tr class="even">
<td>Multi-key (string + float), 100K</td>
<td>41,638</td>
</tr>
</tbody>
</table>
<p><strong>What this proves:</strong> Numeric sorts on 100K rows complete in about 1.2–1.4 ms thanks to pre-resolved column references — the sort comparator holds a direct <code>&amp;Column</code> pointer instead of doing a name lookup on every comparison. String sorting is roughly 13× more expensive than numeric sorting due to comparison cost. Multi-key sorting adds the cost of a secondary comparator.</p>
<p>All sorts use Rust’s stable sort (<code>sort_by</code>), which preserves the relative order of equal elements — important for determinism. The result is stored as a lazy permutation vector, not a new DataFrame. Operations like <code>select</code>, <code>rename</code>, and <code>relocate</code> propagate the permutation without resolving it.</p>
</section>
<section id="e.-joins" class="level3">
<h3 class="anchored" data-anchor-id="e.-joins">E. Joins</h3>
<table class="table">
<thead>
<tr class="header">
<th>Test</th>
<th>Time (µs)</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Inner join, 100K × 100</td>
<td>33,936</td>
</tr>
<tr class="even">
<td>Inner join, 100K × 1K</td>
<td>40,507</td>
</tr>
<tr class="odd">
<td>Left join, 100K × 100</td>
<td>38,712</td>
</tr>
</tbody>
</table>
<p><strong>What this proves:</strong> Join performance is dominated by the left table size. Increasing the right table from 100 to 1,000 rows adds about 20% to the join time, because the right table is indexed and lookups are O(log K) where K is the number of unique keys.</p>
<p>Both the right-side index build and the left-side probe use borrowed keys — <code>ColumnKeyRef</code> references directly into the column data without cloning. For single-key joins (the common case), the BTreeMap key is a single borrowed value with no <code>Vec</code> wrapper, eliminating per-row allocation on both sides.</p>
</section>
<section id="f.-full-pipeline-benchmarks" class="level3">
<h3 class="anchored" data-anchor-id="f.-full-pipeline-benchmarks">F. Full Pipeline Benchmarks</h3>
<table class="table">
<thead>
<tr class="header">
<th>Pipeline</th>
<th>Time (µs)</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>filter → group → summarise → arrange, 100K</td>
<td>4,151</td>
</tr>
<tr class="even">
<td>filter → mutate → group → summarise, 100K</td>
<td>40,640</td>
</tr>
<tr class="odd">
<td>join → filter → group → summarise, 100K</td>
<td>97,769</td>
</tr>
</tbody>
</table>
<p><strong>What this proves:</strong> Realistic multi-step pipelines compose predictably. The filter-group-summarise-arrange pipeline (4.2 ms) is faster than the sum of its parts because the filter reduces the number of rows that downstream operations process, and the lazy sort avoids materializing a new DataFrame. The join-based pipeline is slowest because joins are the most expensive operation and produce a new materialized DataFrame.</p>
</section>
<section id="g.-scaling-behavior" class="level3">
<h3 class="anchored" data-anchor-id="g.-scaling-behavior">G. Scaling Behavior</h3>
<p><strong>Row scaling (filter):</strong></p>
<table class="table">
<thead>
<tr class="header">
<th>Rows</th>
<th>Filter Time (µs)</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>10K</td>
<td>17</td>
</tr>
<tr class="even">
<td>100K</td>
<td>149</td>
</tr>
<tr class="odd">
<td>1M</td>
<td>2,345</td>
</tr>
</tbody>
</table>
<p>Filter time scales linearly with row count — exactly what you’d expect from a single-pass scan. 10× more rows ≈ 10× more time.</p>
<p><strong>Row scaling (group-by + summarise):</strong></p>
<table class="table">
<thead>
<tr class="header">
<th>Rows</th>
<th>Time (µs)</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>10K</td>
<td>384</td>
</tr>
<tr class="even">
<td>100K</td>
<td>4,065</td>
</tr>
<tr class="odd">
<td>1M</td>
<td>65,106</td>
</tr>
</tbody>
</table>
<p>Group-by also scales linearly, but with a higher constant factor due to the BTreeMap insertions. The single-key fast path keeps this manageable: 1M rows grouped in 65 ms.</p>
<p><strong>Column width scaling (100K rows):</strong></p>
<table class="table">
<thead>
<tr class="header">
<th>Columns</th>
<th>Filter (µs)</th>
<th>Materialize (µs)</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>3</td>
<td>183</td>
<td>957</td>
</tr>
<tr class="even">
<td>11</td>
<td>153</td>
<td>2,520</td>
</tr>
<tr class="odd">
<td>51</td>
<td>187</td>
<td>20,388</td>
</tr>
<tr class="even">
<td>101</td>
<td>247</td>
<td>36,450</td>
</tr>
</tbody>
</table>
<p><strong>What this proves:</strong> Filter time is nearly constant regardless of table width — because filtering only touches the predicate column, not the others. Materialization scales linearly with column count because it has to copy every column. This is the columnar storage payoff: a table with 101 columns filters as fast as a table with 3 columns.</p>
<p><strong>Group cardinality scaling (100K rows):</strong></p>
<table class="table">
<thead>
<tr class="header">
<th>Groups</th>
<th>Time (µs)</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>4</td>
<td>7,914</td>
</tr>
<tr class="even">
<td>100</td>
<td>17,814</td>
</tr>
<tr class="odd">
<td>1,000</td>
<td>29,940</td>
</tr>
<tr class="even">
<td>10,000</td>
<td>42,220</td>
</tr>
<tr class="odd">
<td>50,000</td>
<td>58,239</td>
</tr>
</tbody>
</table>
<p>Going from 4 groups to 50,000 groups increases time by about 7.4×. The BTreeMap index is O(N log G), so the cost grows sub-linearly with group count.</p>
</section>
<section id="h.-determinism-verification" class="level3">
<h3 class="anchored" data-anchor-id="h.-determinism-verification">H. Determinism Verification</h3>
<table class="table">
<thead>
<tr class="header">
<th>Test</th>
<th>Result</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Pipeline (sum + mean + sd + median), 10 runs</td>
<td><strong>Bit-identical</strong></td>
</tr>
<tr class="even">
<td>Sort stability (equal keys preserve insertion order)</td>
<td><strong>Stable</strong></td>
</tr>
<tr class="odd">
<td>Group order (first-occurrence across 5 runs)</td>
<td><strong>Deterministic</strong></td>
</tr>
</tbody>
</table>
<p><strong>What this proves:</strong> The determinism guarantee isn’t aspirational — it’s measured. Ten runs of the same pipeline produce identical floating-point results down to the last bit. The sort is stable (equal elements preserve their original order), and groups always appear in first-occurrence order. Additionally, five proptest property suites (200 random cases each) verify these invariants hold across random inputs.</p>
</section>
<section id="i.-memory-budget" class="level3">
<h3 class="anchored" data-anchor-id="i.-memory-budget">I. Memory Budget</h3>
<table class="table">
<colgroup>
<col style="width: 50%">
<col style="width: 50%">
</colgroup>
<thead>
<tr class="header">
<th>Test</th>
<th>Result</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>1M-row bitmask</td>
<td><strong>122 KB</strong> (vs 15,625 KB data = <strong>128× smaller</strong>)</td>
</tr>
<tr class="even">
<td>3 chained filters, 100K rows</td>
<td>Each mask: 1,563 words. Rows: 74,999 → 49,999 → 24,999</td>
</tr>
</tbody>
</table>
<p><strong>What this proves:</strong> A bitmask for 1 million rows costs 122 KB — one bit per row, packed into 64-bit words. The data itself (just the <code>id</code> and <code>value</code> columns) occupies 15.6 MB. The bitmask is 128× smaller than the data it describes. Three chained filters create three separate bitmasks, each 1,563 words (12.5 KB), while the underlying data is shared. The row counts shrink progressively (75K → 50K → 25K) as each filter narrows the result, but no data is copied until you explicitly materialize.</p>
</section>
<section id="j.-trust-and-audit-trail" class="level3">
<h3 class="anchored" data-anchor-id="j.-trust-and-audit-trail">J. Trust and Audit Trail</h3>
<p>These are not performance benchmarks — they’re correctness proofs that verify TidyView’s architectural claims.</p>
<table class="table">
<colgroup>
<col style="width: 50%">
<col style="width: 50%">
</colgroup>
<thead>
<tr class="header">
<th>Test</th>
<th>What it verifies</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Filter traceability</strong></td>
<td>After two filters on a 10-row dataset, the bitmask contains exactly <code>[0, 4, 8]</code>. You can name every surviving row by index.</td>
</tr>
<tr class="even">
<td><strong>Group membership</strong></td>
<td>Groups appear in first-occurrence order (Eng, Sales, HR — not alphabetical). Each group’s row indices are inspectable: Eng = [0, 2], Sales = [1, 4], HR = [3, 5].</td>
</tr>
<tr class="odd">
<td><strong>Snapshot semantics</strong></td>
<td><code>mutate("a", ..., "b", col("a") * 2)</code> fails with <code>column 'a' not found</code> — you can’t reference a column created in the same mutate call.</td>
</tr>
<tr class="even">
<td><strong>Copy-on-write isolation</strong></td>
<td>Clone a frame, mutate the clone — the original has 2 columns, the clone has 3. They’re independent.</td>
</tr>
<tr class="odd">
<td><strong>Full pipeline trace</strong></td>
<td>Filter → group → summarise on 8 rows: west_rows = [0, 1, 4, 6], Widget total = 450, Gadget total = 380. Original still has 8 rows.</td>
</tr>
<tr class="even">
<td><strong>Deterministic sampling</strong></td>
<td><code>slice_sample(100, seed=42)</code> selects the same 100 rows across 5 runs.</td>
</tr>
<tr class="odd">
<td><strong>Projection inspection</strong></td>
<td><code>select(["b", "d"])</code> on a 4-column table sets projection indices to [1, 3]. Original table still reports 4 columns.</td>
</tr>
</tbody>
</table>
<p><strong>What this proves:</strong> Every architectural claim made earlier in this post — bitmask traceability, group ordering, snapshot semantics, copy-on-write isolation, deterministic sampling — is verified by a concrete test with concrete numbers.</p>
</section>
</section>
<section id="the-one-sentence-summary" class="level2">
<h2 class="anchored" data-anchor-id="the-one-sentence-summary">The One-Sentence Summary</h2>
<p>TidyView is a database engine wearing a tidyverse costume, designed so that <strong>same data + same code = same result</strong> on every run, every time — verified on Windows, with cross-platform testing still ahead.</p>
<p>It combines familiar dplyr verbs (<code>filter</code>, <code>select</code>, <code>mutate</code>, <code>group_by</code>, <code>summarise</code>) with database-engine internals (columnar storage, bitmask filtering, predicate pushdown, zone maps) to deliver data pipelines that are deterministic by construction, auditable by design, and memory-safe without runtime surprises.</p>
<div class="callout callout-style-simple callout-note no-icon">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-body-container">
<p>CJC-Lang is open source and available on <a href="https://crates.io/crates/cjc-lang">crates.io</a>. The TidyView implementation lives in <code>crates/cjc-data/</code> — about 14,000 lines of Rust with zero external dependencies. The full benchmark suite (38 tests, 10 categories) is in <code>tests/tidyview_benchmarks.rs</code>.</p>
</div>
</div>
</div>


</section>

 ]]></description>
  <category>CJC-Lang</category>
  <category>data</category>
  <category>databases</category>
  <category>determinism</category>
  <category>architecture</category>
  <category>benchmarks</category>
  <guid>https://adamezzat1.github.io/blog/posts/tidyview-data-engine/index.html</guid>
  <pubDate>Sun, 12 Apr 2026 07:00:00 GMT</pubDate>
</item>
<item>
  <title>Chess RL in CJC-Lang: Training a Chess Agent from Scratch with Zero Dependencies</title>
  <dc:creator>Adam Ezzat</dc:creator>
  <link>https://adamezzat1.github.io/blog/posts/chess-rl-v2/index.html</link>
  <description><![CDATA[ 



<div class="reading-time">
🕑 ~20 min read
</div>
<p>I built a chess-playing AI from scratch — in a programming language I also built from scratch. No PyTorch. No NumPy. No Stockfish. Zero external dependencies.</p>
<p>This post is a technical walkthrough of how that works: the language design decisions that made it possible, the RL algorithm, the chess engine, the autodiff engine, the performance engineering, and what I learned along the way.</p>
<hr>
<section id="the-live-demo" class="level2">
<h2 class="anchored" data-anchor-id="the-live-demo">The Live Demo</h2>
<p>Before diving into the technical details, here’s the training visualization running live. This shows the agent playing self-play chess games, with real-time stats from the v2.5 training run:</p>
<div id="demo-container" style="border-radius: 12px; overflow: hidden; margin: 2em 0; box-shadow: 0 8px 32px rgba(0,0,0,0.3);">
<iframe src="demo.html" width="100%" height="720" style="border: none; border-radius: 12px;">
</iframe>
</div>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Tip
</div>
</div>
<div class="callout-body-container callout-body">
<p>The board animates through sample self-play games. The stats, speed bars, and evolution list are all real numbers from the v2.5 training run.</p>
</div>
</div>
<hr>
</section>
<section id="what-is-cjc-lang" class="level2">
<h2 class="anchored" data-anchor-id="what-is-cjc-lang">What is CJC-Lang?</h2>
<p>CJC-Lang (Computational Jacobian Core) is a numerical programming language I wrote in Rust — 21 crates, roughly 96,000 lines of code. It’s designed for ML pipelines, numerical systems, and scientific computing where reproducibility matters.</p>
<p>Reproducibility is a first-class concern, not an afterthought. The language enforces it through specific engineering choices:</p>
<ul>
<li><strong>Kahan summation</strong> for all floating-point reductions — prevents accumulation error from corrupting results</li>
<li><strong>Ordered data structures</strong> throughout (BTreeMap/BTreeSet, never HashMap) — no iteration-order non-determinism</li>
<li><strong>Explicit seed threading</strong> via SplitMix64 — every source of randomness is traceable</li>
<li><strong>No FMA instructions</strong> in SIMD paths — fused multiply-add can produce different results on different hardware</li>
<li><strong>Two execution backends</strong> (AST interpreter + MIR register machine) that are required to produce identical results</li>
</ul>
<p>The result: same input, same output. Every time.</p>
<p>CJC-Lang has built-in tensors, automatic differentiation, copy-on-write memory, pattern matching, closures, structs, for/while loops, if-expressions, file I/O, and SVG visualization. The compiler pipeline looks like this:</p>
<div class="cell" data-fig-width="8">
<div class="cell-output-display">
<div>
<div>
<pre class="mermaid mermaid-js">flowchart LR
    S["Source\n.cjcl"] --&gt; L["Lexer"] --&gt; P["Parser"] --&gt; A["AST"] --&gt; T["Type\nChecker"] --&gt; H["HIR"] --&gt; M["MIR"] --&gt; E["Executor"]
</pre>
</div>
<p>CJC-Lang compiler pipeline. Source code flows through seven stages before execution.</p>
</div>
</div>
</div>
<p>The chess RL demo exercises nearly every one of these language features — it’s the most demanding program CJC-Lang runs.</p>
<hr>
</section>
<section id="the-chess-engine" class="level2">
<h2 class="anchored" data-anchor-id="the-chess-engine">The Chess Engine</h2>
<p>The chess engine is written entirely in CJC-Lang. Not a wrapper around Stockfish. Not a simplified variant. A complete implementation.</p>
<section id="move-generation" class="level3">
<h3 class="anchored" data-anchor-id="move-generation">Move Generation</h3>
<p>Legal move generation covers every piece type with every special rule:</p>
<ul>
<li><strong>Pawns</strong>: single push, double push from starting rank, diagonal capture, en passant capture, promotion to queen/rook/bishop/knight</li>
<li><strong>Knights, bishops, rooks, queen</strong>: full movement patterns with board-boundary checking</li>
<li><strong>King</strong>: standard moves plus castling (kingside and queenside, with path-clear and through-check validation)</li>
</ul>
</section>
<section id="board-representation" class="level3">
<h3 class="anchored" data-anchor-id="board-representation">Board Representation</h3>
<p>The board is encoded as a 774-dimensional input vector for the neural network:</p>
<pre><code>12 piece planes x 64 squares = 768 features
+ 4 castling rights (K, Q, k, q)
+ 1 normalized move clock (for 50-move rule)
+ 1 en passant file indicator
= 774 total input features</code></pre>
<p>Each piece plane is a binary 64-element vector indicating where that piece type exists on the board. This gives the network a spatially structured view of the position.</p>
<div class="callout callout-style-simple callout-note no-icon callout-titled" title="Board encoding in CJC-Lang">
<div class="callout-header d-flex align-content-center" data-bs-toggle="collapse" data-bs-target=".callout-2-contents" aria-controls="callout-2" aria-expanded="false" aria-label="Toggle callout">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Board encoding in CJC-Lang
</div>
<div class="callout-btn-toggle d-inline-block border-0 py-1 ps-1 pe-0 float-end"><i class="callout-toggle"></i></div>
</div>
<div id="callout-2" class="callout-2-contents callout-collapse collapse">
<div class="callout-body-container callout-body">
<pre><code>fn encode_board(board: Any, color: i64, castling: Any,
                move_clock: i64, ep_file: i64) -&gt; Tensor {
    let features = [];
    let piece_types = ["P","N","B","R","Q","K","p","n","b","r","q","k"];
    let p = 0;
    while p &lt; 12 {
        let sq = 0;
        while sq &lt; 64 {
            if board[sq] == piece_types[p] {
                features = array_push(features, 1.0);
            } else {
                features = array_push(features, 0.0);
            }
            sq = sq + 1;
        }
        p = p + 1;
    }
    // Castling rights
    features = array_push(features, float(castling[0]));
    features = array_push(features, float(castling[1]));
    features = array_push(features, float(castling[2]));
    features = array_push(features, float(castling[3]));
    // Normalized move clock
    features = array_push(features, float(move_clock) / 100.0);
    // En passant
    features = array_push(features, float(ep_file) / 8.0);
    Tensor.from_vec(features, [1, 774])
}</code></pre>
<p>This is the CJC-Lang version. In production, <code>encode_state_fast</code> (a native builtin) replaces this with an O(774) buffer fill instead of O(38 x 774) COW array copies — same output, 30x fewer allocations.</p>
</div>
</div>
</div>
</section>
<section id="draw-detection" class="level3">
<h3 class="anchored" data-anchor-id="draw-detection">Draw Detection</h3>
<p>The engine handles three draw conditions:</p>
<ol type="1">
<li><strong>50-move rule</strong> — a counter tracks half-moves since the last pawn push or capture</li>
<li><strong>Insufficient material</strong> — detects K vs K, K+B vs K, K+N vs K</li>
<li><strong>Threefold repetition</strong> — this one’s interesting</li>
</ol>
<p>Threefold repetition detection uses <strong>Zobrist hashing</strong>: a 64-bit hash for each board position, computed by XOR-ing random numbers assigned to each (piece, square) combination. The hash updates incrementally with each move (XOR out the old position, XOR in the new one), making it O(1) per move instead of O(64) for a full board comparison.</p>
<pre><code>fn zobrist_hash(board: Any, color: i64, castling: Any) -&gt; i64 {
    let h = 0;
    let sq = 0;
    while sq &lt; 64 {
        if board[sq] != "" {
            let piece_idx = piece_to_index(board[sq]);
            h = bitxor(h, ZOBRIST_TABLE[sq * 12 + piece_idx]);
        }
        sq = sq + 1;
    }
    if color == 1 { h = bitxor(h, ZOBRIST_SIDE); }
    // ... castling rights hashed similarly
    h
}</code></pre>
<p>The position history stores these hashes, and a repetition is detected by counting occurrences — no need to store or compare full board states.</p>
<hr>
</section>
</section>
<section id="the-rl-algorithm" class="level2">
<h2 class="anchored" data-anchor-id="the-rl-algorithm">The RL Algorithm</h2>
<p>The demo uses <strong>A2C (Advantage Actor-Critic)</strong> with <strong>GAE (Generalized Advantage Estimation)</strong>. This is a real, modern RL algorithm — the same family used in many production systems.</p>
<section id="network-architecture" class="level3">
<h3 class="anchored" data-anchor-id="network-architecture">Network Architecture</h3>
<div class="cell" data-fig-width="8">
<div class="cell-output-display">
<div>
<div>
<pre class="mermaid mermaid-js">flowchart LR
    B["Board\nEncoding\n[1, 774]"] --&gt; H1["Hidden 1\n774 → 128\nReLU"] --&gt; H2["Hidden 2\n128 → 64\nReLU"]
    H2 --&gt; PF["Policy: From\n64 → 64\nSoftmax"]
    H2 --&gt; PT["Policy: To\n64 → 64\nSoftmax"]
    H2 --&gt; V["Value Head\n64 → 1\ntanh"]
</pre>
</div>
<p>A2C network architecture. The 774-D board encoding flows through shared hidden layers, then splits into policy and value heads.</p>
</div>
</div>
</div>
<p>The network has <strong>45,873 trainable parameters</strong> across these layers:</p>
<table class="table">
<thead>
<tr class="header">
<th>Layer</th>
<th>Shape</th>
<th>Parameters</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Hidden 1 (weights + bias)</td>
<td>774 x 128 + 128</td>
<td>99,200</td>
</tr>
<tr class="even">
<td>Hidden 2 (weights + bias)</td>
<td>128 x 64 + 64</td>
<td>8,256</td>
</tr>
<tr class="odd">
<td>Policy from-head (weights + bias)</td>
<td>64 x 64 + 64</td>
<td>4,160</td>
</tr>
<tr class="even">
<td>Policy to-head (weights + bias)</td>
<td>64 x 64 + 64</td>
<td>4,160</td>
</tr>
<tr class="odd">
<td>Value head (weights + bias)</td>
<td>64 x 1 + 1</td>
<td>65</td>
</tr>
<tr class="even">
<td><strong>Total</strong></td>
<td></td>
<td><strong>~45,873</strong></td>
</tr>
</tbody>
</table>
<p>The policy uses <strong>factored action heads</strong>: instead of outputting probabilities over all possible moves (which would be thousands), it predicts a from-square distribution and a to-square distribution independently. The score for a move is the sum of the from-square log-probability and the to-square log-probability. This factorization keeps the output layer small (128 outputs instead of 4,096+).</p>
</section>
<section id="advantage-estimation-gae" class="level3">
<h3 class="anchored" data-anchor-id="advantage-estimation-gae">Advantage Estimation (GAE)</h3>
<p>GAE computes advantages by looking at multi-step returns with exponential weighting:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Chat%7BA%7D_t%20=%20%5Csum_%7Bl=0%7D%5E%7BT-t%7D%20(%5Cgamma%20%5Clambda)%5El%20%5Cdelta_%7Bt+l%7D%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?%5Cdelta_t%20=%20r_t%20+%20%5Cgamma%20V(s_%7Bt+1%7D)%20-%20V(s_t)"> is the TD error.</p>
<p>The hyperparameters: <img src="https://latex.codecogs.com/png.latex?%5Cgamma%20=%200.99"> (discount factor), <img src="https://latex.codecogs.com/png.latex?%5Clambda%20=%200.95"> (GAE smoothing). These control the bias-variance tradeoff in advantage estimation — higher <img src="https://latex.codecogs.com/png.latex?%5Clambda"> gives lower bias but higher variance.</p>
</section>
<section id="training-loop" class="level3">
<h3 class="anchored" data-anchor-id="training-loop">Training Loop</h3>
<p>Each training episode:</p>
<ol type="1">
<li><strong>Rollout</strong>: play a full game via self-play, recording states, actions, log-probabilities, rewards, and value estimates at each step</li>
<li><strong>Compute returns and advantages</strong>: walk backward through the trajectory using GAE</li>
<li><strong>Policy loss</strong>: <img src="https://latex.codecogs.com/png.latex?-%5Cfrac%7B1%7D%7BT%7D%20%5Csum_t%20%5Clog%20%5Cpi(a_t%20%7C%20s_t)%20%5Ccdot%20%5Chat%7BA%7D_t"> — nudge the policy toward moves with positive advantage</li>
<li><strong>Value loss</strong>: <img src="https://latex.codecogs.com/png.latex?%5Cfrac%7B1%7D%7BT%7D%20%5Csum_t%20(V(s_t)%20-%20R_t)%5E2"> — improve value function predictions</li>
<li><strong>Backward pass</strong>: compute gradients through the entire network via autodiff</li>
<li><strong>Adam update</strong>: update all 45,873 parameters</li>
</ol>
<div class="callout callout-style-simple callout-note no-icon callout-titled" title="A2C update step in CJC-Lang">
<div class="callout-header d-flex align-content-center" data-bs-toggle="collapse" data-bs-target=".callout-3-contents" aria-controls="callout-3" aria-expanded="false" aria-label="Toggle callout">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-title-container flex-fill">
A2C update step in CJC-Lang
</div>
<div class="callout-btn-toggle d-inline-block border-0 py-1 ps-1 pe-0 float-end"><i class="callout-toggle"></i></div>
</div>
<div id="callout-3" class="callout-3-contents callout-collapse collapse">
<div class="callout-body-container callout-body">
<pre><code>fn a2c_update_adam(params: Any, trajectory: Any, config: Any,
                   adam_state: Any, step: i64) -&gt; Any {
    let advantages = compute_gae(trajectory, config);
    advantages = whiten(advantages);

    let policy_loss = 0.0;
    let value_loss = 0.0;
    let t = 0;
    while t &lt; len(trajectory) {
        policy_loss = policy_loss - trajectory[t].log_prob * advantages[t];
        let val_err = trajectory[t].value - trajectory[t].return_;
        value_loss = value_loss + val_err * val_err;
        t = t + 1;
    }
    policy_loss = policy_loss / float(len(trajectory));
    value_loss = value_loss / float(len(trajectory));

    let total_loss = policy_loss + 0.5 * value_loss;

    // Backward pass through autodiff
    grad_graph.zero_grad();
    grad_graph.backward(total_loss);

    // Adam optimizer update for each parameter tensor
    let p = 0;
    while p &lt; len(params) {
        let grad = grad_graph.grad(params[p].idx);
        params[p] = adam_step(params[p], grad, adam_state[p], step,
                              config.lr, 0.9, 0.999, 1e-8);
        p = p + 1;
    }
    params
}</code></pre>
</div>
</div>
</div>
<p>Additional training features:</p>
<ul>
<li><strong>Temperature annealing</strong> (1.2 → 0.8): starts with high exploration, gradually becomes more greedy</li>
<li><strong>Advantage whitening</strong>: normalizes advantages to zero mean and unit variance for stable gradients</li>
<li><strong>Resignation threshold</strong>: the agent can resign when its value estimate drops below a threshold, saving compute on hopeless games</li>
<li><strong>Move-count penalty</strong>: small penalty per ply (0.001) to discourage aimless play</li>
</ul>
<hr>
</section>
</section>
<section id="the-autodiff-engine" class="level2">
<h2 class="anchored" data-anchor-id="the-autodiff-engine">The Autodiff Engine</h2>
<p>This is where CJC-Lang’s runtime does the heavy lifting. The RL algorithm above calls <code>grad_graph.backward(loss)</code> — but what happens inside that call?</p>
<section id="gradgraph-arena-based-automatic-differentiation" class="level3">
<h3 class="anchored" data-anchor-id="gradgraph-arena-based-automatic-differentiation">GradGraph: Arena-Based Automatic Differentiation</h3>
<p>CJC-Lang’s autodiff engine is a <strong>reverse-mode automatic differentiation</strong> system. It builds a directed acyclic graph (DAG) of operations during the forward pass, then traverses it backward to compute gradients.</p>
<p>The original implementation (v2.0) used the standard Rust approach: <code>Vec&lt;Rc&lt;RefCell&lt;GradNode&gt;&gt;&gt;</code> — each node was a reference-counted, interior-mutable object. This works, but every <code>.borrow()</code> call has runtime overhead, and each node allocation hits the heap individually.</p>
<p>In v2.4, the engine was redesigned to use <strong>arena allocation</strong>: flat arrays for operations, tensors, and gradients. This eliminated roughly 120 <code>.borrow()</code> calls per backward pass.</p>
<pre><code>v2.0: Each node is a separate heap allocation
  [Node 0] → [Node 1] → [Node 2] → ...  (Rc&lt;RefCell&lt;GradNode&gt;&gt; per node)

v2.4+: Flat arena arrays — one contiguous block
  ops:     [Op0, Op1, Op2, Op3, ...]      (Vec&lt;GradOp&gt;)
  tensors: [T0,  T1,  T2,  T3,  ...]      (Vec&lt;Tensor&gt;)
  grads:   [G0,  G1,  G2,  G3,  ...]      (Vec&lt;Option&lt;Tensor&gt;&gt;)</code></pre>
</section>
<section id="dead-node-elimination" class="level3">
<h3 class="anchored" data-anchor-id="dead-node-elimination">Dead Node Elimination</h3>
<p>Not every node in the computation graph is reachable from the loss. In a multi-head architecture (policy + value), the backward pass from the policy loss doesn’t need gradients for nodes only used by the value head (and vice versa during the value backward pass).</p>
<p>Before traversing backward, the engine builds a reachability set from the loss node. Unreachable nodes are skipped entirely — typically <strong>20-30%</strong> of the graph for multi-head networks.</p>
</section>
<section id="in-place-gradient-accumulation-v2.5" class="level3">
<h3 class="anchored" data-anchor-id="in-place-gradient-accumulation-v2.5">In-Place Gradient Accumulation (v2.5)</h3>
<p>The original gradient accumulation created a new tensor every time two gradients were added:</p>
<div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb6-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Before: allocates a new tensor</span></span>
<span id="cb6-2">grads[idx] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">Some</span>(existing<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>add_unchecked(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>incoming))<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span></code></pre></div>
<p>In v2.5, this was replaced with in-place mutation:</p>
<div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb7-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// After: mutates existing tensor</span></span>
<span id="cb7-2">existing<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>add_assign_unchecked(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span>incoming)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span></code></pre></div>
<p>This uses <code>Buffer::borrow_data_mut()</code> to get a mutable reference to the underlying data and update it element-wise. The result: roughly half the tensor allocations per backward pass are eliminated. This single change was responsible for the v2.4 → v2.5 speedup (43.5 → 29.8 seconds per episode).</p>
</section>
<section id="fused-mlp-layers-v2.5" class="level3">
<h3 class="anchored" data-anchor-id="fused-mlp-layers-v2.5">Fused MLP Layers (v2.5)</h3>
<p>A standard dense layer in the computation graph creates four separate nodes:</p>
<ol type="1">
<li>Transpose the weight matrix</li>
<li>Matrix multiply input by transposed weights</li>
<li>Add bias</li>
<li>Apply activation function</li>
</ol>
<p>The fused <code>GradOp::MlpLayer</code> collapses all four into a single graph node:</p>
<div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb8-1">MlpLayer <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb8-2">    input<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">usize</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span>    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// index of input tensor</span></span>
<span id="cb8-3">    weight<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">usize</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span>   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// index of weight tensor</span></span>
<span id="cb8-4">    bias<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">usize</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span>     <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// index of bias tensor</span></span>
<span id="cb8-5">    activation<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> Activation<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// ReLU, Tanh, etc.</span></span>
<span id="cb8-6"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div>
<p>This reduces graph nodes by <strong>3x per layer</strong> and computes all three parameter gradients (d_input, d_weight, d_bias) in a single fused backward pass.</p>
<hr>
</section>
</section>
<section id="performance-engineering" class="level2">
<h2 class="anchored" data-anchor-id="performance-engineering">Performance Engineering</h2>
<section id="the-profiling-driven-approach" class="level3">
<h3 class="anchored" data-anchor-id="the-profiling-driven-approach">The Profiling-Driven Approach</h3>
<p>Every optimization was driven by measurement, not intuition. CJC-Lang has built-in profiling builtins (<code>profile_zone_start</code>, <code>profile_zone_stop</code>, <code>profile_dump</code>) that I used to instrument the training loop.</p>
<p>The first profile revealed the bottleneck distribution:</p>
<table class="table">
<thead>
<tr class="header">
<th>Function</th>
<th>% of Rollout Time</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>score_moves</code> (forward pass per move)</td>
<td><strong>84.2%</strong></td>
</tr>
<tr class="even">
<td><code>legal_moves</code> (move generation)</td>
<td>7.7%</td>
</tr>
<tr class="odd">
<td><code>apply_move</code> (board update)</td>
<td>0.1%</td>
</tr>
<tr class="even">
<td>Everything else</td>
<td>8.0%</td>
</tr>
</tbody>
</table>
<p>84% of time was spent evaluating the neural network for each legal move. The fix: <code>score_moves_batch</code>, a native Rust builtin that evaluates all legal moves in a single batched forward pass instead of looping through them one at a time in the interpreter.</p>
<p><strong>Result: 7.7x rollout speedup.</strong></p>
</section>
<section id="the-amdahls-law-moment" class="level3">
<h3 class="anchored" data-anchor-id="the-amdahls-law-moment">The Amdahl’s Law Moment</h3>
<p>Here’s where it got interesting. A 7.7x speedup on the rollout phase should have been transformative. But total episode time barely moved:</p>
<pre><code>Before: rollout ~10.4s + backward ~25s = ~35s
After:  rollout ~1.3s  + backward ~25s = ~26s
Speedup: ~1.35x (not 7.7x)</code></pre>
<p>Amdahl’s Law in action. The rollout was only about 30% of total episode time. Making it 7.7x faster saved ~9 seconds, but the backward pass (which was untouched) still took 25 seconds. The bottleneck had shifted.</p>
<p>This insight redirected the entire optimization strategy. Instead of making the interpreter faster, I redesigned the autodiff engine:</p>
<table class="table">
<thead>
<tr class="header">
<th>Version</th>
<th>Change</th>
<th>Episode Time</th>
<th>Speedup</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>v2.3</td>
<td>Native rollout kernels</td>
<td>82.2 s</td>
<td>baseline</td>
</tr>
<tr class="even">
<td>v2.4</td>
<td>Arena GradGraph + dead node elimination</td>
<td>43.5 s</td>
<td>1.89x</td>
</tr>
<tr class="odd">
<td>v2.5</td>
<td>In-place gradients + fused MLP</td>
<td>29.8 s</td>
<td><strong>2.76x</strong></td>
</tr>
</tbody>
</table>
</section>
<section id="native-hot-path-kernels" class="level3">
<h3 class="anchored" data-anchor-id="native-hot-path-kernels">Native Hot-Path Kernels</h3>
<p>Three performance-critical functions were promoted from CJC-Lang code to native Rust builtins:</p>
<p><strong><code>adam_step</code></strong> — Element-wise Adam optimizer update. The CJC-Lang version was a scalar loop over every parameter; the native version operates directly on tensor buffers. <strong>~9x faster.</strong></p>
<p><strong><code>encode_state_fast</code></strong> — Board-to-tensor encoding. The CJC-Lang version used <code>array_push</code> to build the 774-element feature vector, which triggered a copy-on-write clone at each push. The native version fills a pre-allocated buffer in a single pass. <strong>O(n) vs O(38n).</strong></p>
<p><strong><code>score_moves_batch</code></strong> — Batched move scoring. Instead of calling the forward pass once per legal move (in the interpreter), this evaluates all moves in a single native call with one matrix multiplication. <strong>7.7x rollout speedup.</strong></p>
<p>All three builtins produce <strong>bit-identical output</strong> to their CJC-Lang counterparts — verified by parity tests.</p>
<hr>
</section>
</section>
<section id="training-results" class="level2">
<h2 class="anchored" data-anchor-id="training-results">Training Results</h2>
<section id="honest-numbers" class="level3">
<h3 class="anchored" data-anchor-id="honest-numbers">Honest Numbers</h3>
<p>After 120 episodes of self-play training:</p>
<ul>
<li><strong>Elo change: +13.9</strong> — the first measurable learning signal in the v2.x series</li>
<li><strong>Win rate vs random: 50%</strong> — not yet reliably beating a random opponent</li>
<li><strong>Win rate vs greedy: 50%</strong> — ties with a material-greedy baseline</li>
<li><strong>Non-zero terminal rewards: 6/120 episodes</strong> — most games are still drawn</li>
</ul>
<p>These are modest results. The agent is not strong. I report this honestly because the goal was never to compete with Stockfish or AlphaZero — it was to demonstrate that a from-scratch language can express, train, and evaluate a real RL agent.</p>
</section>
<section id="what-this-proves" class="level3">
<h3 class="anchored" data-anchor-id="what-this-proves">What This Proves</h3>
<p>The demo exercises nearly every CJC-Lang feature simultaneously:</p>
<table class="table">
<thead>
<tr class="header">
<th>Language Feature</th>
<th>How It’s Used</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Tensors</td>
<td>Board encoding, weight matrices, gradients</td>
</tr>
<tr class="even">
<td>Autodiff (GradGraph)</td>
<td>Full A2C loss backward through policy + value</td>
</tr>
<tr class="odd">
<td>Closures</td>
<td>Evaluation callbacks, training hooks</td>
</tr>
<tr class="even">
<td>Pattern matching</td>
<td>Piece type dispatch, move classification</td>
</tr>
<tr class="odd">
<td>Structs</td>
<td>Board state, game config, training stats</td>
</tr>
<tr class="even">
<td>For/while loops</td>
<td>Rollout iteration, move generation</td>
</tr>
<tr class="odd">
<td>If-expressions</td>
<td>Legal move filtering, terminal detection</td>
</tr>
<tr class="even">
<td>Arrays (COW)</td>
<td>Move lists, trajectory buffers</td>
</tr>
<tr class="odd">
<td>Deterministic RNG</td>
<td>SplitMix64 seed threading</td>
</tr>
<tr class="even">
<td>File I/O</td>
<td>CSV training logs, PGN game records</td>
</tr>
<tr class="odd">
<td>Vizor (SVG)</td>
<td>Training curve visualization</td>
</tr>
</tbody>
</table>
<p>It’s a single CJC-Lang program (~1,715 lines) that implements the chess engine, RL algorithm, optimizer, evaluation suite, Elo computation, PGN notation, checkpoint system, and training curve plotting.</p>
</section>
<section id="the-v1-to-v2.5-journey" class="level3">
<h3 class="anchored" data-anchor-id="the-v1-to-v2.5-journey">The v1 to v2.5 Journey</h3>
<p>For context, here’s how far the demo has come:</p>
<table class="table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Dimension</th>
<th>v1 (March 2026)</th>
<th>v2.5 (April 2026)</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Network</td>
<td>1,072 params</td>
<td>45,873 params (43x)</td>
</tr>
<tr class="even">
<td>Input</td>
<td>66 features</td>
<td>774 features (12x)</td>
</tr>
<tr class="odd">
<td>Gradients</td>
<td>95 lines of hand-rolled chain rule</td>
<td>Full automatic differentiation</td>
</tr>
<tr class="even">
<td>Optimizer</td>
<td>Manual SGD</td>
<td>Adam (native builtin)</td>
</tr>
<tr class="odd">
<td>Chess rules</td>
<td>Basic (no castling)</td>
<td>Complete</td>
</tr>
<tr class="even">
<td>Algorithm</td>
<td>REINFORCE</td>
<td>A2C + GAE</td>
</tr>
<tr class="odd">
<td>Training speed</td>
<td>N/A</td>
<td>29.8 s/episode</td>
</tr>
<tr class="even">
<td>Learning signal</td>
<td>None</td>
<td>Elo +13.9</td>
</tr>
<tr class="odd">
<td>Determinism</td>
<td>Bit-identical</td>
<td>Still bit-identical</td>
</tr>
</tbody>
</table>
<hr>
</section>
</section>
<section id="the-stack" class="level2">
<h2 class="anchored" data-anchor-id="the-stack">The Stack</h2>
<pre><code>CJC-Lang program (~1,715 LOC)
    Chess engine + A2C + GAE + Adam + Elo + PGN + checkpoints
        |
CJC-Lang compiler + runtime (Rust, ~96K LOC)
    Lexer → Parser → AST → Type Checker → HIR → MIR → Executor
    Autodiff: arena-based GradGraph with fused MLP ops
    451 builtins, all deterministic
    21 crates, zero external dependencies</code></pre>
<p>Every tensor operation, every gradient computation, every optimizer step runs in code I own. No framework sits between the algorithm and the math.</p>
<p>Same seed = same game. Always.</p>
<hr>
</section>
<section id="whats-next" class="level2">
<h2 class="anchored" data-anchor-id="whats-next">What’s Next</h2>
<p>The agent’s Elo +13.9 is a starting point, not a destination. The infrastructure is now solid — the bottleneck is no longer the language or the autodiff engine, it’s the ML itself. Larger networks, longer training, curriculum learning, and MCTS are all expressible in CJC-Lang as it stands today.</p>
<p>The deeper goal isn’t chess strength. It’s showing that a carefully designed language — one that takes reproducibility seriously, owns its entire stack, and grows to meet the demands of its hardest workload — can do real work.</p>
<p>If you want to try CJC-Lang yourself: <code>cargo install cjc-lang</code>, then <code>cjcl run your_program.cjcl</code>.</p>
<hr>
<p><em>CJC-Lang is open source. The compiler, runtime, and all demos are available on GitHub.</em></p>


</section>

 ]]></description>
  <category>CJC-Lang</category>
  <category>reinforcement-learning</category>
  <category>chess</category>
  <category>compilers</category>
  <category>scientific-computing</category>
  <guid>https://adamezzat1.github.io/blog/posts/chess-rl-v2/index.html</guid>
  <pubDate>Sat, 11 Apr 2026 07:00:00 GMT</pubDate>
</item>
<item>
  <title>Syntax Guide for CJC Version 0.1.1</title>
  <dc:creator>Adam Ezzat</dc:creator>
  <link>https://adamezzat1.github.io/blog/posts/cjc-v0.1.1-syntax-guide/index.html</link>
  <description><![CDATA[ 



<div class="reading-time">
🕑 ~45 min read
</div>
<div class="stats-row">
<div class="stat">
<span class="number">21</span><span class="stat-label">Rust crates</span>
</div>
<div class="stat">
<span class="number">287+</span><span class="stat-label">builtins</span>
</div>
<div class="stat">
<span class="number">6,500+</span><span class="stat-label">tests passing</span>
</div>
</div>
<div class="callout callout-style-simple callout-tip no-icon callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-title-container flex-fill">
What’s New in v0.1.1
</div>
</div>
<div class="callout-body-container callout-body">
<p>CJC-Lang v0.1.1 adds <strong>Quantum Hardening Phase 3</strong> (Jordan-Wigner, Suzuki-Trotter, Zero-Noise Extrapolation), <strong>AST Evolution v0.3</strong> (visitor trait, metrics, validation), <strong>MIR Evolution v0.3</strong> (schedule metadata, reduction analysis, SSA loop overlay), and <strong>MPS enhancements</strong> (canonical forms, SWAP networks). This guide covers every language feature and builtin available as of this release.</p>
</div>
</div>
<hr>
<section id="part-1-the-language" class="level2">
<h2 class="anchored" data-anchor-id="part-1-the-language">Part 1 — The Language</h2>
<p>CJC-Lang is a statically-aware, dynamically-typed language for deterministic scientific computing. If you’ve written Rust, Python, or R, the syntax will feel familiar. This section covers every construct the language supports.</p>
<section id="functions" class="level3">
<h3 class="anchored" data-anchor-id="functions">Functions</h3>
<p>Every function parameter requires a type annotation. Return types are optional but recommended.</p>
<pre><code>fn add(a: i64, b: i64) -&gt; i64 {
    a + b
}

fn greet(name: String) {
    print("Hello, " + name);
}</code></pre>
<p>Functions are first-class values. You can pass them as arguments:</p>
<pre><code>fn apply(f: fn(i64) -&gt; i64, x: i64) -&gt; i64 {
    f(x)
}

fn double(x: i64) -&gt; i64 { x * 2 }

fn main() -&gt; i64 {
    apply(double, 21)
}</code></pre>
<p>Recursive functions work as expected:</p>
<pre><code>fn factorial(n: i64) -&gt; i64 {
    if n &lt;= 1 { return 1; }
    n * factorial(n - 1)
}

fn main() -&gt; i64 { factorial(10) }</code></pre>
<p>Generic functions use angle-bracket syntax with optional trait bounds:</p>
<pre><code>fn identity&lt;T&gt;(x: T) -&gt; T { x }

fn my_max&lt;T&gt;(a: T, b: T) -&gt; T {
    if a &gt; b { a } else { b }
}

fn wrap&lt;T&gt;(x: T) -&gt; T { x }
fn apply_generic&lt;T&gt;(x: T) -&gt; T { wrap(x) }</code></pre>
</section>
<section id="variables" class="level3">
<h3 class="anchored" data-anchor-id="variables">Variables</h3>
<p>Immutable by default. Use <code>let mut</code> for mutation.</p>
<pre><code>let x: i64 = 42
let pi = 3.14159
let name = "CJC-Lang"

let mut count = 0
count = count + 1</code></pre>
<p>CJC-Lang supports type inference, but you can always be explicit:</p>
<pre><code>let a: f64 = 1.0
let b: bool = true
let c: String = "hello"
let d: i64 = 99</code></pre>
</section>
<section id="primitive-types" class="level3">
<h3 class="anchored" data-anchor-id="primitive-types">Primitive Types</h3>
<table class="table">
<thead>
<tr class="header">
<th>Type</th>
<th>Description</th>
<th>Example</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>i64</code></td>
<td>64-bit signed integer</td>
<td><code>42</code>, <code>-7</code></td>
</tr>
<tr class="even">
<td><code>f64</code></td>
<td>64-bit floating point</td>
<td><code>3.14</code>, <code>1e-6</code></td>
</tr>
<tr class="odd">
<td><code>bool</code></td>
<td>Boolean</td>
<td><code>true</code>, <code>false</code></td>
</tr>
<tr class="even">
<td><code>String</code></td>
<td>UTF-8 string</td>
<td><code>"hello"</code></td>
</tr>
<tr class="odd">
<td><code>ByteSlice</code></td>
<td>Raw byte sequence</td>
<td><code>b"hello"</code></td>
</tr>
<tr class="even">
<td><code>Any</code></td>
<td>Dynamic/polymorphic type</td>
<td>Used for generic dispatch</td>
</tr>
</tbody>
</table>
</section>
<section id="control-flow" class="level3">
<h3 class="anchored" data-anchor-id="control-flow">Control Flow</h3>
<section id="ifelse" class="level4">
<h4 class="anchored" data-anchor-id="ifelse">if/else</h4>
<p><code>if</code> works as both a statement and an expression:</p>
<pre><code>// Statement form
if x &gt; 0 {
    print("positive");
} else {
    print("non-positive");
}

// Expression form — both branches must return compatible types
let abs_x = if x &gt;= 0 { x } else { -x };</code></pre>
<p>Note: no parentheses around the condition. Braces are always required.</p>
</section>
<section id="while-loops" class="level4">
<h4 class="anchored" data-anchor-id="while-loops">while loops</h4>
<pre><code>let mut sum = 0;
let mut i = 1;
while i &lt;= 100 {
    sum = sum + i;
    i = i + 1;
}</code></pre>
</section>
<section id="for-loops" class="level4">
<h4 class="anchored" data-anchor-id="for-loops">for loops</h4>
<p><code>for</code> iterates over ranges using <code>start..end</code> (exclusive upper bound):</p>
<pre><code>let mut total = 0;
for i in 1..11 {
    total = total + i;
}</code></pre>
</section>
<section id="break-and-continue" class="level4">
<h4 class="anchored" data-anchor-id="break-and-continue">break and continue</h4>
<p>Both work inside <code>while</code> and <code>for</code> loops:</p>
<pre><code>for i in 0..10 {
    if i == 4 { break; }
    print(i);
}

let mut i = 0;
while i &lt; 5 {
    i = i + 1;
    if i == 3 { continue; }
    print(i);
}</code></pre>
</section>
</section>
<section id="pattern-matching" class="level3">
<h3 class="anchored" data-anchor-id="pattern-matching">Pattern Matching</h3>
<p>CJC-Lang has full structural pattern matching with <code>match</code> expressions:</p>
<pre><code>// Match on integers
let x = 99;
let label = match x {
    1 =&gt; "one",
    2 =&gt; "two",
    _ =&gt; "other",
};

// Match on enums
enum Color { Red, Green, Blue }

fn classify(c: Color) -&gt; i64 {
    match c {
        Red   =&gt; 1,
        Green =&gt; 2,
        _     =&gt; 3,
    }
}</code></pre>
<p>Pattern matching supports destructuring:</p>
<pre><code>// Option type
let x = Some(42);
let val = match x {
    Some(v) =&gt; v,
    None    =&gt; 0,
};

// Result type
let r = Ok(3.14);
match r {
    Ok(v)  =&gt; print(v),
    Err(e) =&gt; print("error"),
}</code></pre>
</section>
<section id="structs" class="level3">
<h3 class="anchored" data-anchor-id="structs">Structs</h3>
<pre><code>struct Point { x: f64, y: f64 }

let p = Point { x: 1.5, y: 2.5 };
print(p.x + p.y);</code></pre>
<p>Structs can have methods via <code>impl</code> blocks:</p>
<pre><code>struct Circle { radius: f64 }

impl Circle {
    fn area(c: Circle) -&gt; f64 {
        3.14159 * c.radius * c.radius
    }
}

let c = Circle { radius: 5.0 };
print(Circle::area(c));</code></pre>
<p>Generic structs:</p>
<pre><code>struct Pair&lt;T, U&gt; { first: T, second: U }</code></pre>
</section>
<section id="enums" class="level3">
<h3 class="anchored" data-anchor-id="enums">Enums</h3>
<pre><code>enum Shape {
    Circle(f64),
    Rectangle(f64, f64),
    Triangle(f64, f64, f64),
}

enum Option&lt;T&gt; { None, Some(T) }</code></pre>
</section>
<section id="traits" class="level3">
<h3 class="anchored" data-anchor-id="traits">Traits</h3>
<pre><code>trait Numeric: Add + Mul {
    fn zero() -&gt; Self;
    fn one() -&gt; Self;
}

impl&lt;T&gt; Vec&lt;T&gt; : Iterable {
    fn len(self: Vec&lt;T&gt;) -&gt; i64 { 0 }
}</code></pre>
</section>
<section id="closures" class="level3">
<h3 class="anchored" data-anchor-id="closures">Closures</h3>
<p>Closures use <code>|args| body</code> syntax with mandatory type annotations on parameters:</p>
<pre><code>let f = |x: f64| x * x;
let add = |x: f64, y: f64| x + y;

print(f(3.0));
print(add(1.0, 2.0));</code></pre>
<p>Closures capture variables from their enclosing scope. CJC performs capture analysis during HIR lowering to determine what each closure needs.</p>
</section>
<section id="arrays" class="level3">
<h3 class="anchored" data-anchor-id="arrays">Arrays</h3>
<p>Arrays are ordered, dynamically-typed collections:</p>
<pre><code>let nums = [1, 2, 3, 4, 5];
let mixed = [1, "hello", 3.14, true];

// Array operations — note: array_push returns a NEW array
let extended = array_push(nums, 6);
let length = array_len(extended);
let first = array_get(nums, 0);
let sliced = array_slice(nums, 1, 3);</code></pre>
<div class="callout callout-style-simple callout-warning no-icon">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-body-container">
<p><code>array_push(arr, val)</code> does <strong>not</strong> mutate <code>arr</code>. It returns a new array. Always reassign: <code>arr = array_push(arr, val);</code></p>
</div>
</div>
</div>
</section>
<section id="strings" class="level3">
<h3 class="anchored" data-anchor-id="strings">Strings</h3>
<pre><code>let s = "hello world";
let upper = str_upper(s);
let lower = str_lower(s);
let trimmed = str_trim("  padded  ");
let parts = str_split("a,b,c", ",");
let joined = str_join(["a", "b", "c"], "-");
let sub = str_contains(s, "world");</code></pre>
<p>Formatted strings use <code>f"..."</code> syntax:</p>
<pre><code>let name = "CJC-Lang";
let version = "0.1.1";
print(f"Welcome to {name} v{version}");</code></pre>
</section>
<section id="byte-slices" class="level3">
<h3 class="anchored" data-anchor-id="byte-slices">Byte Slices</h3>
<p>For low-level data processing:</p>
<pre><code>let data: ByteSlice = b"name,age,score\nAlice,30,95.5";
let rows = data.split_byte(b'\n');
let header = rows[0];
let fields = header.split_byte(b',');</code></pre>
</section>
<section id="compound-assignments" class="level3">
<h3 class="anchored" data-anchor-id="compound-assignments">Compound Assignments</h3>
<p>CJC recognizes <code>+=</code>, <code>-=</code>, <code>*=</code>, <code>/=</code> operators:</p>
<pre><code>let mut x = 10;
x += 5;
x -= 3;
x *= 2;
x /= 4;</code></pre>
</section>
<section id="pipe-operator" class="level3">
<h3 class="anchored" data-anchor-id="pipe-operator">Pipe Operator</h3>
<p>The <code>|&gt;</code> operator chains function calls left-to-right:</p>
<pre><code>data |&gt; filter(predicate) |&gt; map(transform)</code></pre>
</section>
<section id="nogc-blocks" class="level3">
<h3 class="anchored" data-anchor-id="nogc-blocks">nogc Blocks</h3>
<p>Mark functions as allocation-free for performance-critical paths. The NoGC verifier statically checks that no GC-triggering operations occur inside:</p>
<pre><code>nogc fn fast_multiply(x: f64, y: f64) -&gt; f64 {
    x * y
}</code></pre>
<p>The MIR pipeline includes a NoGC verification pass that performs call-graph analysis with a <code>may_gc</code> fixpoint computation. If any reachable function allocates, the verifier rejects the program.</p>
<hr>
</section>
</section>
<section id="part-2-the-compiler-pipeline" class="level2">
<h2 class="anchored" data-anchor-id="part-2-the-compiler-pipeline">Part 2 — The Compiler Pipeline</h2>
<p>CJC compiles through a multi-stage pipeline. Understanding it helps you reason about error messages, optimization, and the dual-engine execution model.</p>
<pre><code>Source → Lexer → Parser → AST → [TypeChecker] → HIR → MIR → [Optimize] → Exec</code></pre>
<section id="stage-1-lexer" class="level3">
<h3 class="anchored" data-anchor-id="stage-1-lexer">Stage 1: Lexer</h3>
<p>The lexer (<code>cjc-lexer</code>) converts source text into a flat token stream. It recognizes all CJC-Lang keywords, operators (including <code>|&gt;</code>, <code>+=</code>, <code>@</code>), literals (integers, floats, strings, byte strings), and identifiers.</p>
<div class="sourceCode" id="cb27" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb27-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> (tokens<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> diags) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">Lexer::</span>new(src)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>tokenize()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span></code></pre></div>
</section>
<section id="stage-2-parser" class="level3">
<h3 class="anchored" data-anchor-id="stage-2-parser">Stage 2: Parser</h3>
<p>A Pratt parser (<code>cjc-parser</code>) builds the AST from tokens. It handles operator precedence, expression vs.&nbsp;statement disambiguation, and produces structured error diagnostics.</p>
<div class="sourceCode" id="cb28" style="background: #f1f3f5;"><pre class="sourceCode rust code-with-copy"><code class="sourceCode rust"><span id="cb28-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-style: inherit;">let</span> (program<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> diags) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">cjc_parser::</span>parse_source(src)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span></code></pre></div>
<p>The parser recognizes 35 expression kinds, 9 statement kinds, 11 declaration kinds, and 9 pattern kinds.</p>
</section>
<section id="stage-3-ast" class="level3">
<h3 class="anchored" data-anchor-id="stage-3-ast">Stage 3: AST</h3>
<p>The AST (<code>cjc-ast</code>) is a tree of typed nodes. As of v0.1.1, the AST layer includes:</p>
<ul>
<li><strong>Visitor trait</strong> (<code>visit</code> module) — read-only traversal covering all node variants with <code>walk_*</code> functions</li>
<li><strong>Metrics</strong> (<code>metrics</code> module) — single-pass structural statistics: node counts, depth, operator frequencies, feature flags</li>
<li><strong>Validation</strong> (<code>validate</code> module) — structural checks: break/continue outside loops, duplicate params, empty match, unreachable code after return, nesting depth limits</li>
<li><strong>Inspect</strong> (<code>inspect</code> module) — deterministic text dumps: <code>dump_ast_summary</code>, <code>dump_ast_metrics</code>, <code>dump_validation_report</code>, <code>dump_expr_tree</code></li>
<li><strong>Node utilities</strong> (<code>node_utils</code> module) — helper methods like <code>Expr::child_count</code>, <code>is_literal</code>, <code>Block::is_empty</code>, <code>Program::has_main_function</code></li>
</ul>
</section>
<section id="stage-4-hir-high-level-ir" class="level3">
<h3 class="anchored" data-anchor-id="stage-4-hir-high-level-ir">Stage 4: HIR (High-level IR)</h3>
<p>HIR lowering (<code>cjc-hir</code>) desugars the AST. <code>for</code> loops become <code>while</code> loops over ranges. Closures get capture analysis. The HIR is closer to the execution model while still being readable.</p>
</section>
<section id="stage-5-mir-mid-level-ir" class="level3">
<h3 class="anchored" data-anchor-id="stage-5-mir-mid-level-ir">Stage 5: MIR (Mid-level IR)</h3>
<p>MIR (<code>cjc-mir</code>) is a register-based intermediate representation. HIR lowers to MIR instructions that operate on virtual registers.</p>
<p>The MIR layer in v0.1.1 includes:</p>
<ul>
<li><strong>Optimizer</strong> — constant folding, strength reduction, dead code elimination (DCE), common subexpression elimination (CSE), loop-invariant code motion (LICM)</li>
<li><strong>SSA construction</strong> — Cytron’s minimal SSA algorithm with 6 optimization passes</li>
<li><strong>Loop analysis</strong> — enriched <code>LoopInfo</code> with <code>is_countable</code>, <code>trip_count_hint</code>, <code>num_exits</code>, schedule metadata</li>
<li><strong>Reduction analysis</strong> — enriched <code>ReductionInfo</code> with <code>accumulator_semantics</code> (Plain, Kahan, Binned, RuntimeDefined)</li>
<li><strong>Schedule metadata</strong> — descriptive execution hints (<code>SequentialStrict</code>, <code>DescriptiveTiled</code>, <code>DescriptiveVectorized</code>, etc.) that are non-semantic</li>
<li><strong>SSA loop overlay</strong> — maps SSA definitions to loops, identifies loop-carried variables, cross-references reductions with SSA accumulators</li>
<li><strong>Legality verifier</strong> — schedule consistency checks, reduction metadata cross-validation</li>
<li><strong>NoGC verifier</strong> — call-graph may_gc fixpoint analysis</li>
</ul>
</section>
<section id="stage-6-execution-dual-engine" class="level3">
<h3 class="anchored" data-anchor-id="stage-6-execution-dual-engine">Stage 6: Execution (Dual Engine)</h3>
<p>CJC-Lang has two execution engines that must produce identical results:</p>
<table class="table">
<colgroup>
<col style="width: 19%">
<col style="width: 19%">
<col style="width: 29%">
<col style="width: 31%">
</colgroup>
<thead>
<tr class="header">
<th>Engine</th>
<th>Module</th>
<th>Entry Point</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>v1</strong></td>
<td><code>cjc-eval</code></td>
<td><code>Interpreter::new(seed).exec(&amp;program)</code></td>
<td>AST tree-walk interpreter</td>
</tr>
<tr class="even">
<td><strong>v2</strong></td>
<td><code>cjc-mir-exec</code></td>
<td><code>run_program_with_executor(&amp;program, seed)</code></td>
<td>MIR register-machine executor</td>
</tr>
</tbody>
</table>
<p>The MIR executor also supports an optimized mode:</p>
<pre><code>cjcl run program.cjcl --mir-opt</code></pre>
<p>Parity tests in the test suite verify that both engines produce bit-identical output for every supported program.</p>
</section>
<section id="diagnostics" class="level3">
<h3 class="anchored" data-anchor-id="diagnostics">Diagnostics</h3>
<p>CJC-Lang uses structured error codes (<code>E0xxx</code>-<code>E8xxx</code>) via the <code>cjc-diag</code> crate. Diagnostics carry source spans, severity levels, and optional fix suggestions.</p>
<hr>
</section>
</section>
<section id="part-3-determinism-architecture" class="level2">
<h2 class="anchored" data-anchor-id="part-3-determinism-architecture">Part 3 — Determinism Architecture</h2>
<p>Determinism is CJC-Lang’s defining architectural constraint. Every design decision flows from one requirement: <strong>same seed in, same bits out, every time, on every platform.</strong></p>
<section id="how-cjc-achieves-determinism" class="level3">
<h3 class="anchored" data-anchor-id="how-cjc-achieves-determinism">How CJC Achieves Determinism</h3>
<table class="table">
<colgroup>
<col style="width: 50%">
<col style="width: 50%">
</colgroup>
<thead>
<tr class="header">
<th>Component</th>
<th>Mechanism</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>RNG</strong></td>
<td>SplitMix64 with explicit seed threading — no global state</td>
</tr>
<tr class="even">
<td><strong>Collections</strong></td>
<td><code>BTreeMap</code>/<code>BTreeSet</code> everywhere — never <code>HashMap</code>/<code>HashSet</code></td>
</tr>
<tr class="odd">
<td><strong>Floating-point sums</strong></td>
<td>Kahan summation or BinnedAccumulator — never naive reduction</td>
</tr>
<tr class="even">
<td><strong>SIMD</strong></td>
<td>No FMA (fused multiply-add) — <code>mul_fixed</code> throughout</td>
</tr>
<tr class="odd">
<td><strong>Complex arithmetic</strong></td>
<td><code>mul_fixed</code> for all complex multiplications</td>
</tr>
<tr class="even">
<td><strong>Iteration</strong></td>
<td><code>Vec</code>-based storage with deterministic ordering</td>
</tr>
<tr class="odd">
<td><strong>Parallelism</strong></td>
<td>Results identical regardless of thread count</td>
</tr>
</tbody>
</table>
</section>
<section id="kahan-summation" class="level3">
<h3 class="anchored" data-anchor-id="kahan-summation">Kahan Summation</h3>
<p>Naive floating-point addition loses precision as terms accumulate. CJC-Lang uses compensated summation everywhere:</p>
<pre><code>// Under the hood, CJC-Lang's runtime uses Kahan summation:
// sum = 0.0, compensation = 0.0
// for each term:
//   y = term - compensation
//   t = sum + y
//   compensation = (t - sum) - y
//   sum = t</code></pre>
<p>This matters for scientific workloads where you’re summing millions of terms. The result is the same whether you sum them on a laptop or a cluster.</p>
</section>
<section id="binnedaccumulator" class="level3">
<h3 class="anchored" data-anchor-id="binnedaccumulator">BinnedAccumulator</h3>
<p>For even higher-precision requirements, CJC-Lang provides binned accumulators that partition the floating-point range into bins, accumulate within each bin, then combine — guaranteeing reproducible results even under reordering.</p>
</section>
<section id="splitmix64" class="level3">
<h3 class="anchored" data-anchor-id="splitmix64">SplitMix64</h3>
<p>All randomness in CJC-Lang flows through a seeded SplitMix64 generator. There is no global RNG state. Every function that needs randomness takes an explicit seed parameter:</p>
<pre><code>let samples = q_sample(circuit, 1000, 42);
let measurement = q_measure(circuit, 42);</code></pre>
<hr>
</section>
</section>
<section id="part-4-builtin-functions" class="level2">
<h2 class="anchored" data-anchor-id="part-4-builtin-functions">Part 4 — Builtin Functions</h2>
<p>CJC-Lang ships with 287+ builtin functions registered in <code>cjc-runtime</code>. These are available without imports in every CJC-Lang program.</p>
<section id="mathematics" class="level3">
<h3 class="anchored" data-anchor-id="mathematics">Mathematics</h3>
<table class="table">
<colgroup>
<col style="width: 43%">
<col style="width: 56%">
</colgroup>
<thead>
<tr class="header">
<th>Function</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>abs(x)</code></td>
<td>Absolute value</td>
</tr>
<tr class="even">
<td><code>sqrt(x)</code></td>
<td>Square root</td>
</tr>
<tr class="odd">
<td><code>pow(base, exp)</code></td>
<td>Exponentiation</td>
</tr>
<tr class="even">
<td><code>log(x)</code></td>
<td>Natural logarithm</td>
</tr>
<tr class="odd">
<td><code>log2(x)</code>, <code>log10(x)</code></td>
<td>Base-2 and base-10 logarithm</td>
</tr>
<tr class="even">
<td><code>exp(x)</code></td>
<td>Euler’s number raised to x</td>
</tr>
<tr class="odd">
<td><code>sin(x)</code>, <code>cos(x)</code>, <code>tan(x)</code></td>
<td>Trigonometric functions</td>
</tr>
<tr class="even">
<td><code>asin(x)</code>, <code>acos(x)</code>, <code>atan(x)</code></td>
<td>Inverse trigonometric</td>
</tr>
<tr class="odd">
<td><code>sinh(x)</code>, <code>cosh(x)</code>, <code>tanh(x)</code></td>
<td>Hyperbolic functions</td>
</tr>
<tr class="even">
<td><code>floor(x)</code>, <code>ceil(x)</code>, <code>round(x)</code></td>
<td>Rounding</td>
</tr>
<tr class="odd">
<td><code>min(a, b)</code>, <code>max(a, b)</code></td>
<td>Minimum and maximum</td>
</tr>
<tr class="even">
<td><code>clamp(x, lo, hi)</code></td>
<td>Clamp to range</td>
</tr>
<tr class="odd">
<td><code>gcd(a, b)</code>, <code>lcm(a, b)</code></td>
<td>Greatest common divisor, least common multiple</td>
</tr>
<tr class="even">
<td><code>factorial(n)</code></td>
<td>Factorial</td>
</tr>
<tr class="odd">
<td><code>fibonacci(n)</code></td>
<td>Fibonacci number</td>
</tr>
</tbody>
</table>
</section>
<section id="statistics" class="level3">
<h3 class="anchored" data-anchor-id="statistics">Statistics</h3>
<table class="table">
<thead>
<tr class="header">
<th>Function</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>mean(arr)</code></td>
<td>Arithmetic mean</td>
</tr>
<tr class="even">
<td><code>median(arr)</code></td>
<td>Median</td>
</tr>
<tr class="odd">
<td><code>variance(arr)</code></td>
<td>Sample variance</td>
</tr>
<tr class="even">
<td><code>std_dev(arr)</code></td>
<td>Standard deviation</td>
</tr>
<tr class="odd">
<td><code>percentile(arr, p)</code></td>
<td>p-th percentile</td>
</tr>
<tr class="even">
<td><code>quantile(arr, q)</code></td>
<td>Quantile</td>
</tr>
<tr class="odd">
<td><code>correlation(x, y)</code></td>
<td>Pearson correlation</td>
</tr>
<tr class="even">
<td><code>covariance(x, y)</code></td>
<td>Covariance</td>
</tr>
<tr class="odd">
<td><code>skewness(arr)</code></td>
<td>Skewness</td>
</tr>
<tr class="even">
<td><code>kurtosis(arr)</code></td>
<td>Kurtosis</td>
</tr>
<tr class="odd">
<td><code>zscore(arr)</code></td>
<td>Z-score normalization</td>
</tr>
<tr class="even">
<td><code>mann_whitney(x, y)</code></td>
<td>Mann-Whitney U test</td>
</tr>
<tr class="odd">
<td><code>percent_rank(arr)</code></td>
<td>Percent rank</td>
</tr>
<tr class="even">
<td><code>ntile(arr, n)</code></td>
<td>N-tile grouping</td>
</tr>
<tr class="odd">
<td><code>case_when(conds, vals, default)</code></td>
<td>Conditional value selection</td>
</tr>
</tbody>
</table>
</section>
<section id="arrays-1" class="level3">
<h3 class="anchored" data-anchor-id="arrays-1">Arrays</h3>
<table class="table">
<thead>
<tr class="header">
<th>Function</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>array_len(arr)</code></td>
<td>Length</td>
</tr>
<tr class="even">
<td><code>array_push(arr, val)</code></td>
<td>Append (returns new array)</td>
</tr>
<tr class="odd">
<td><code>array_pop(arr)</code></td>
<td>Remove last element (returns new array)</td>
</tr>
<tr class="even">
<td><code>array_get(arr, idx)</code></td>
<td>Get element at index</td>
</tr>
<tr class="odd">
<td><code>array_set(arr, idx, val)</code></td>
<td>Set element (returns new array)</td>
</tr>
<tr class="even">
<td><code>array_slice(arr, start, end)</code></td>
<td>Subsequence</td>
</tr>
<tr class="odd">
<td><code>array_contains(arr, val)</code></td>
<td>Membership test</td>
</tr>
<tr class="even">
<td><code>array_reverse(arr)</code></td>
<td>Reverse</td>
</tr>
<tr class="odd">
<td><code>array_sort(arr)</code></td>
<td>Sort (deterministic)</td>
</tr>
<tr class="even">
<td><code>array_map(arr, f)</code></td>
<td>Map function over elements</td>
</tr>
<tr class="odd">
<td><code>array_filter(arr, f)</code></td>
<td>Filter by predicate</td>
</tr>
<tr class="even">
<td><code>array_reduce(arr, f, init)</code></td>
<td>Left fold</td>
</tr>
<tr class="odd">
<td><code>array_flatten(arr)</code></td>
<td>Flatten nested arrays</td>
</tr>
<tr class="even">
<td><code>array_zip(a, b)</code></td>
<td>Zip two arrays</td>
</tr>
<tr class="odd">
<td><code>array_unique(arr)</code></td>
<td>Remove duplicates (deterministic)</td>
</tr>
</tbody>
</table>
</section>
<section id="strings-1" class="level3">
<h3 class="anchored" data-anchor-id="strings-1">Strings</h3>
<table class="table">
<thead>
<tr class="header">
<th>Function</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>str_len(s)</code></td>
<td>Length</td>
</tr>
<tr class="even">
<td><code>str_upper(s)</code>, <code>str_lower(s)</code></td>
<td>Case conversion</td>
</tr>
<tr class="odd">
<td><code>str_trim(s)</code></td>
<td>Strip whitespace</td>
</tr>
<tr class="even">
<td><code>str_split(s, delim)</code></td>
<td>Split by delimiter</td>
</tr>
<tr class="odd">
<td><code>str_join(arr, delim)</code></td>
<td>Join array with delimiter</td>
</tr>
<tr class="even">
<td><code>str_contains(s, sub)</code></td>
<td>Substring test</td>
</tr>
<tr class="odd">
<td><code>str_replace(s, old, new)</code></td>
<td>Replace occurrences</td>
</tr>
<tr class="even">
<td><code>str_starts_with(s, prefix)</code></td>
<td>Prefix test</td>
</tr>
<tr class="odd">
<td><code>str_ends_with(s, suffix)</code></td>
<td>Suffix test</td>
</tr>
<tr class="even">
<td><code>str_char_at(s, idx)</code></td>
<td>Character at index</td>
</tr>
<tr class="odd">
<td><code>str_substring(s, start, end)</code></td>
<td>Substring extraction</td>
</tr>
</tbody>
</table>
</section>
<section id="tensor-operations" class="level3">
<h3 class="anchored" data-anchor-id="tensor-operations">Tensor Operations</h3>
<table class="table">
<colgroup>
<col style="width: 43%">
<col style="width: 56%">
</colgroup>
<thead>
<tr class="header">
<th>Function</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>Tensor.from_vec(data, shape)</code></td>
<td>Create tensor from flat data + shape</td>
</tr>
<tr class="even">
<td><code>t.shape()</code></td>
<td>Get shape</td>
</tr>
<tr class="odd">
<td><code>t.sum()</code></td>
<td>Sum all elements</td>
</tr>
<tr class="even">
<td><code>t.mean()</code></td>
<td>Mean of all elements</td>
</tr>
<tr class="odd">
<td><code>t.transpose()</code></td>
<td>Transpose</td>
</tr>
<tr class="even">
<td><code>t.matmul(other)</code></td>
<td>Matrix multiplication</td>
</tr>
<tr class="odd">
<td><code>t.layer_norm(gamma, beta)</code></td>
<td>Layer normalization</td>
</tr>
<tr class="even">
<td><code>t.linear(weights, bias)</code></td>
<td>Linear transform</td>
</tr>
<tr class="odd">
<td><code>t.split_heads(n)</code></td>
<td>Split into attention heads</td>
</tr>
<tr class="even">
<td><code>t.merge_heads()</code></td>
<td>Merge attention heads</td>
</tr>
<tr class="odd">
<td><code>t.softmax()</code></td>
<td>Softmax activation</td>
</tr>
<tr class="even">
<td><code>t.relu()</code></td>
<td>ReLU activation</td>
</tr>
</tbody>
</table>
<p>Tensor operations use SIMD acceleration internally but never use FMA, preserving bit-identical results.</p>
</section>
<section id="io" class="level3">
<h3 class="anchored" data-anchor-id="io">I/O</h3>
<table class="table">
<thead>
<tr class="header">
<th>Function</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>print(val)</code></td>
<td>Print to stdout</td>
</tr>
<tr class="even">
<td><code>read_file(path)</code></td>
<td>Read file contents</td>
</tr>
<tr class="odd">
<td><code>write_file(path, content)</code></td>
<td>Write file</td>
</tr>
<tr class="even">
<td><code>to_string(val)</code></td>
<td>Convert to string representation</td>
</tr>
</tbody>
</table>
</section>
<section id="automatic-differentiation" class="level3">
<h3 class="anchored" data-anchor-id="automatic-differentiation">Automatic Differentiation</h3>
<p>CJC-Lang has built-in AD support via <code>cjc-ad</code>:</p>
<table class="table">
<thead>
<tr class="header">
<th>Function</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>dual(value, derivative)</code></td>
<td>Create forward-mode dual number</td>
</tr>
<tr class="even">
<td><code>grad(f, x)</code></td>
<td>Compute gradient</td>
</tr>
<tr class="odd">
<td><code>jacobian(f, x)</code></td>
<td>Compute Jacobian matrix</td>
</tr>
</tbody>
</table>
<p>Forward-mode uses dual numbers; reverse-mode uses a deterministic tape with gradient graphs.</p>
<hr>
</section>
</section>
<section id="part-5-dataframe-operations" class="level2">
<h2 class="anchored" data-anchor-id="part-5-dataframe-operations">Part 5 — DataFrame Operations</h2>
<p>CJC-Lang includes a tidyverse-inspired DataFrame library (<code>cjc-data</code>) for tabular data manipulation:</p>
<pre><code>let df = dataframe([
    column("name", ["Alice", "Bob", "Charlie"]),
    column("age", [30, 25, 35]),
    column("score", [95.5, 87.3, 91.2]),
]);

// Filter, transform, aggregate
df |&gt; filter(col("age") &gt; 25)
   |&gt; mutate("grade", col("score") / 10.0)
   |&gt; group_by(col("grade"))
   |&gt; summarize("avg_age", mean(col("age")))</code></pre>
<p>Key operations:</p>
<table class="table">
<thead>
<tr class="header">
<th>Operation</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>filter(predicate)</code></td>
<td>Row selection</td>
</tr>
<tr class="even">
<td><code>select(columns...)</code></td>
<td>Column selection</td>
</tr>
<tr class="odd">
<td><code>mutate(name, expr)</code></td>
<td>Add/transform column</td>
</tr>
<tr class="even">
<td><code>group_by(column)</code></td>
<td>Group rows</td>
</tr>
<tr class="odd">
<td><code>summarize(name, agg)</code></td>
<td>Aggregate within groups</td>
</tr>
<tr class="even">
<td><code>join(other, on)</code></td>
<td>Join two DataFrames</td>
</tr>
<tr class="odd">
<td><code>arrange(column)</code></td>
<td>Sort by column</td>
</tr>
</tbody>
</table>
<hr>
</section>
<section id="part-6-visualization" class="level2">
<h2 class="anchored" data-anchor-id="part-6-visualization">Part 6 — Visualization</h2>
<p>The <code>cjc-vizor</code> crate provides grammar-of-graphics visualization:</p>
<pre><code>let p = vizor_plot(categories, values);
let p = p.geom_bar();
let p = p.title("Quarterly Results");
let p = p.size(800, 500);
p.render();</code></pre>
<p>Vizor supports bar charts, line plots, scatter plots, histograms, and heatmaps with configurable aesthetics (color, size, shape, faceting).</p>
<hr>
</section>
<section id="part-7-quantum-computing-extensions" class="level2">
<h2 class="anchored" data-anchor-id="part-7-quantum-computing-extensions">Part 7 — Quantum Computing Extensions</h2>
<p>CJC-Lang v0.1.1 includes a comprehensive quantum simulation framework (<code>cjc-quantum</code>) with 45+ quantum builtins. The framework spans five simulation backends, each suited to different qubit scales and circuit types.</p>
<section id="circuit-simulation-statevector-up-to-26-qubits" class="level3">
<h3 class="anchored" data-anchor-id="circuit-simulation-statevector-up-to-26-qubits">Circuit Simulation (Statevector, up to ~26 qubits)</h3>
<p>Full statevector simulation for small circuits:</p>
<pre><code>// Create a Bell state
let c = qubits(2);
let c = q_h(c, 0);
let c = q_cx(c, 0, 1);

// Measure
let probs = q_probs(c);
print(probs);

// Sample 1000 shots
let results = q_sample(c, 1000, 42);</code></pre>
<p>Available gates:</p>
<table class="table">
<colgroup>
<col style="width: 20%">
<col style="width: 34%">
<col style="width: 44%">
</colgroup>
<thead>
<tr class="header">
<th>Gate</th>
<th>Function</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>H</td>
<td><code>q_h(c, qubit)</code></td>
<td>Hadamard</td>
</tr>
<tr class="even">
<td>X, Y, Z</td>
<td><code>q_x</code>, <code>q_y</code>, <code>q_z</code></td>
<td>Pauli gates</td>
</tr>
<tr class="odd">
<td>S, T</td>
<td><code>q_s</code>, <code>q_t</code></td>
<td>Phase gates</td>
</tr>
<tr class="even">
<td>Rx, Ry, Rz</td>
<td><code>q_rx(c, q, angle)</code></td>
<td>Rotation gates</td>
</tr>
<tr class="odd">
<td>CNOT</td>
<td><code>q_cx(c, ctrl, tgt)</code></td>
<td>Controlled-NOT</td>
</tr>
<tr class="even">
<td>CZ</td>
<td><code>q_cz(c, ctrl, tgt)</code></td>
<td>Controlled-Z</td>
</tr>
<tr class="odd">
<td>SWAP</td>
<td><code>q_swap(c, q1, q2)</code></td>
<td>SWAP</td>
</tr>
<tr class="even">
<td>Toffoli</td>
<td><code>q_toffoli(c, c1, c2, tgt)</code></td>
<td>Three-qubit controlled-controlled-NOT</td>
</tr>
</tbody>
</table>
</section>
<section id="mps-simulation-up-to-50-qubits" class="level3">
<h3 class="anchored" data-anchor-id="mps-simulation-up-to-50-qubits">MPS Simulation (up to 50+ qubits)</h3>
<p>Matrix Product State simulation trades exactness for scalability via bond dimension truncation:</p>
<pre><code>// 50-qubit GHZ state
let mps = mps_new(50, 16);
let mps = mps_h(mps, 0);
for i in 0..49 {
    mps = mps_cnot(mps, i, i + 1);
}

// Measure Z expectation on qubit 25
let z = mps_z_expectation(mps, 25);

// Compute Heisenberg energy
let energy = mps_energy(mps, "heisenberg");</code></pre>
<p><strong>New in v0.1.1</strong> — MPS canonical forms and SWAP networks:</p>
<table class="table">
<colgroup>
<col style="width: 43%">
<col style="width: 56%">
</colgroup>
<thead>
<tr class="header">
<th>Function</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>mps_left_canonicalize(mps)</code></td>
<td>Left-canonical form via SVD sweep</td>
</tr>
<tr class="even">
<td><code>mps_right_canonicalize(mps)</code></td>
<td>Right-canonical form via SVD sweep</td>
</tr>
<tr class="odd">
<td><code>mps_mixed_canonicalize(mps, center)</code></td>
<td>Mixed-canonical form with orthogonality center</td>
</tr>
<tr class="even">
<td><code>mps_swap(mps, q1, q2)</code></td>
<td>Arbitrary-distance 2-qubit gates via SWAP network</td>
</tr>
</tbody>
</table>
<p>Canonical forms use sign-stabilized SVD to preserve bit-identical determinism.</p>
</section>
<section id="stabilizer-simulation-up-to-1000-qubits" class="level3">
<h3 class="anchored" data-anchor-id="stabilizer-simulation-up-to-1000-qubits">Stabilizer Simulation (up to 1000+ qubits)</h3>
<p>The CHP (Clifford) simulator efficiently handles Clifford circuits:</p>
<pre><code>let s = stabilizer_new(100);
let s = stabilizer_h(s, 0);
for i in 0..99 {
    s = stabilizer_cnot(s, i, i + 1);
}
let outcome = stabilizer_measure(s, 50, 42);</code></pre>
<p>Stabilizer simulation runs in O(n^2) time and O(n^2) memory for n qubits — enabling circuits that are impossible with statevector simulation.</p>
</section>
<section id="density-matrix-mixed-states-and-noise" class="level3">
<h3 class="anchored" data-anchor-id="density-matrix-mixed-states-and-noise">Density Matrix (Mixed States and Noise)</h3>
<p>For simulating open quantum systems with decoherence:</p>
<pre><code>let dm = density_new(2);
let dm = density_gate(dm, "H", 0);
let dm = density_cnot(dm, 0, 1);

// Apply noise
let dm = density_depolarize(dm, 0, 0.01);
let dm = density_dephase(dm, 1, 0.02);

// Diagnostics
print(density_purity(dm));
print(density_entropy(dm));</code></pre>
</section>
<section id="jordan-wigner-fermion-hamiltonians-new-in-v0.1.1" class="level3">
<h3 class="anchored" data-anchor-id="jordan-wigner-fermion-hamiltonians-new-in-v0.1.1">Jordan-Wigner Fermion Hamiltonians (New in v0.1.1)</h3>
<p>Map fermionic creation/annihilation operators to qubit Pauli strings. Pre-built molecular Hamiltonians for quantum chemistry:</p>
<pre><code>// H2 molecule Hamiltonian
let h = q_fermion_h2();
print(q_fermion_n_terms(h));

// LiH molecule Hamiltonian
let h = q_fermion_lih();

// Compute expectation value against a circuit
let c = qubits(4);
let c = q_h(c, 0);
let c = q_cx(c, 0, 1);
let energy = q_fermion_expectation(h, c);</code></pre>
<p>The Jordan-Wigner transformation maps: <img src="https://latex.codecogs.com/png.latex?a%5E%5Cdagger_j%20=%20%5Cfrac%7BX_j%20-%20iY_j%7D%7B2%7D%20%5Cotimes%20Z_%7Bj-1%7D%20%5Cotimes%20%5Ccdots%20%5Cotimes%20Z_0"></p>
<p>All Pauli terms are stored in <code>Vec</code> (not HashMap) for deterministic iteration. Expectation values use Kahan summation.</p>
</section>
<section id="suzuki-trotter-time-evolution-new-in-v0.1.1" class="level3">
<h3 class="anchored" data-anchor-id="suzuki-trotter-time-evolution-new-in-v0.1.1">Suzuki-Trotter Time Evolution (New in v0.1.1)</h3>
<p>Simulate quantum dynamics via product formula decomposition:</p>
<pre><code>// Time-evolve a state under H2 Hamiltonian
let h = q_fermion_h2();
let c = qubits(4);
let c = q_h(c, 0);

// 2nd-order Trotter with 100 steps, evolving for time=1.0
let final_state = q_trotter_evolve(h, c, 1.0, 100, 2);

// Error bound for the approximation
let error = q_trotter_error(h, 1.0, 100, 2);</code></pre>
<ul>
<li><strong>Order 1</strong>: Lie-Trotter — <img src="https://latex.codecogs.com/png.latex?e%5E%7B-i(A+B)t%7D%20%5Capprox%20(e%5E%7B-iAt/n%7D%20e%5E%7B-iBt/n%7D)%5En"></li>
<li><strong>Order 2</strong>: Strang splitting — symmetric product formula with <img src="https://latex.codecogs.com/png.latex?O(%5CDelta%20t%5E3)"> local error</li>
</ul>
<p>Diagonal Pauli strings (containing only I and Z) are optimized to O(N) phase rotations, skipping the O(N^2) general path.</p>
</section>
<section id="zero-noise-extrapolation-new-in-v0.1.1" class="level3">
<h3 class="anchored" data-anchor-id="zero-noise-extrapolation-new-in-v0.1.1">Zero-Noise Extrapolation (New in v0.1.1)</h3>
<p>Mitigate quantum hardware noise by extrapolating to the zero-noise limit:</p>
<pre><code>// Richardson extrapolation from noisy measurements
let scales = [1.0, 2.0, 3.0];
let noisy_values = [-0.95, -0.82, -0.61];

let result = q_zne_mitigate(scales, noisy_values);
// result[0] = mitigated value (extrapolated to zero noise)
// result[1] = Richardson coefficients

// Simple linear extrapolation (2 points)
let clean = q_zne_linear(1.0, -0.95, 2.0, -0.82);

// Noise scaling
let scaled_p = q_scale_noise(0.01, 3.0, "depolarizing");</code></pre>
<p>Richardson extrapolation solves a Vandermonde system with partial pivoting. For polynomial noise models of degree <img src="https://latex.codecogs.com/png.latex?d">, using <img src="https://latex.codecogs.com/png.latex?d+1"> scale factors gives exact recovery.</p>
</section>
<section id="vqe-variational-quantum-eigensolver" class="level3">
<h3 class="anchored" data-anchor-id="vqe-variational-quantum-eigensolver">VQE (Variational Quantum Eigensolver)</h3>
<pre><code>let energy = vqe_heisenberg(8, 16, 0.01, 100, 42);</code></pre>
</section>
<section id="qaoa-quantum-approximate-optimization" class="level3">
<h3 class="anchored" data-anchor-id="qaoa-quantum-approximate-optimization">QAOA (Quantum Approximate Optimization)</h3>
<pre><code>let graph = qaoa_graph_cycle(6);
let result = qaoa_maxcut(graph, 16, 2, 0.05, 50, 42);</code></pre>
</section>
<section id="dmrg-density-matrix-renormalization-group" class="level3">
<h3 class="anchored" data-anchor-id="dmrg-density-matrix-renormalization-group">DMRG (Density Matrix Renormalization Group)</h3>
<pre><code>let energy = dmrg_heisenberg(20, 32, 10, 1e-8);</code></pre>
</section>
<section id="qec-quantum-error-correction" class="level3">
<h3 class="anchored" data-anchor-id="qec-quantum-error-correction">QEC (Quantum Error Correction)</h3>
<pre><code>let rate = qec_logical_error_rate(3, 0.01, 1000, 42);</code></pre>
</section>
<section id="qml-quantum-machine-learning" class="level3">
<h3 class="anchored" data-anchor-id="qml-quantum-machine-learning">QML (Quantum Machine Learning)</h3>
<p>Data re-uploading quantum neural network:</p>
<pre><code>let result = qml_train(4, 3, 2, 16, 0.01, 50, 42, samples, labels);
let accuracy = result[0];
let loss_history = result[1];</code></pre>
</section>
<section id="dual-mode-backend" class="level3">
<h3 class="anchored" data-anchor-id="dual-mode-backend">Dual-Mode Backend</h3>
<p>Every quantum feature has two backends:</p>
<ol type="1">
<li><strong>Rust backend</strong> — optimized, compiled, fast</li>
<li><strong>Pure CJC backend</strong> — inspectable, AD-compatible, modifiable</li>
</ol>
<p>Pass <code>"pure"</code> to constructor functions to use the pure backend. Operations automatically detect which backend a state uses.</p>
<hr>
</section>
</section>
<section id="part-8-complete-examples" class="level2">
<h2 class="anchored" data-anchor-id="part-8-complete-examples">Part 8 — Complete Examples</h2>
<section id="example-1-numerical-integration" class="level3">
<h3 class="anchored" data-anchor-id="example-1-numerical-integration">Example 1: Numerical Integration</h3>
<pre><code>fn trapz(f: fn(f64) -&gt; f64, a: f64, b: f64, n: i64) -&gt; f64 {
    let h = (b - a) / n;
    let mut sum = (f(a) + f(b)) / 2.0;
    for i in 1..n {
        sum = sum + f(a + i * h);
    }
    sum * h
}

fn main() -&gt; f64 {
    let area = trapz(|x: f64| x * x, 0.0, 1.0, 1000);
    print(f"Integral of x^2 from 0 to 1 = {area}");
    area
}</code></pre>
</section>
<section id="example-2-transformer-forward-pass" class="level3">
<h3 class="anchored" data-anchor-id="example-2-transformer-forward-pass">Example 2: Transformer Forward Pass</h3>
<pre><code>let batch    = 1;
let seq_len  = 4;
let model_dim = 8;
let num_heads = 2;
let head_dim  = model_dim / num_heads;

let input_data = [
    0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8,
    0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9,
    0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0,
    0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1
];
let x = Tensor.from_vec(input_data, [1, 4, 8]);

let x_norm = x.layer_norm(gamma, beta);
let Q = x_norm.linear(Wq, bias_zero);
let K = x_norm.linear(Wk, bias_zero);
let V = x_norm.linear(Wv, bias_zero);

let Q_heads = Q.split_heads(num_heads);
let K_heads = K.split_heads(num_heads);
let V_heads = V.split_heads(num_heads);

let attn_out = attention(Q_heads, K_heads, V_heads);
let attn_merged = attn_out.merge_heads();
let attn_proj = attn_merged.linear(Wo, bias_zero);

let x2 = x + attn_proj;
print("Transformer block output shape:", x2.shape());</code></pre>
</section>
<section id="example-3-csv-etl-pipeline" class="level3">
<h3 class="anchored" data-anchor-id="example-3-csv-etl-pipeline">Example 3: CSV ETL Pipeline</h3>
<pre><code>let csv_data: ByteSlice = b"name,age,score\nAlice,30,95.5\nBob,25,87.3\nCharlie,35,91.2";

let rows = csv_data.split_byte(b'\n');
let header = rows[0];
let header_fields = header.split_byte(b',');
print(header_fields);

let mut total_score = 0.0;
let mut count = 0;
for i in 1..rows.len() {
    let fields = rows[i].split_byte(b',');
    let name = fields[0];
    let score = fields[2];
    total_score = total_score + score;
    count = count + 1;
}
print(f"Average score: {total_score / count}");</code></pre>
</section>
<section id="example-4-quantum-chemistry" class="level3">
<h3 class="anchored" data-anchor-id="example-4-quantum-chemistry">Example 4: Quantum Chemistry</h3>
<pre><code>// Build H2 Hamiltonian and compute ground state energy estimate
let h = q_fermion_h2();
print(f"H2 Hamiltonian has {q_fermion_n_terms(h)} Pauli terms");

// Prepare a trial state
let c = qubits(4);
let c = q_ry(c, 0, 0.5);
let c = q_ry(c, 1, -0.3);
let c = q_cx(c, 0, 2);
let c = q_cx(c, 1, 3);

// Compute energy
let energy = q_fermion_expectation(h, c);
print(f"Trial energy: {energy}");

// Time evolution
let evolved = q_trotter_evolve(h, c, 0.5, 50, 2);
print("Evolution complete");

// Mitigate noise from hypothetical hardware
let scales = [1.0, 1.5, 2.0];
let noisy = [-1.05, -0.92, -0.71];
let mitigated = q_zne_mitigate(scales, noisy);
print(f"Mitigated energy: {mitigated[0]}");</code></pre>
</section>
<section id="example-5-statistical-analysis" class="level3">
<h3 class="anchored" data-anchor-id="example-5-statistical-analysis">Example 5: Statistical Analysis</h3>
<pre><code>let data = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];

print(f"Mean: {mean(data)}");
print(f"Std Dev: {std_dev(data)}");
print(f"Median: {median(data)}");
print(f"Skewness: {skewness(data)}");
print(f"Kurtosis: {kurtosis(data)}");

let groups = ntile(data, 4);
print(f"Quartile groups: {groups}");

let ranks = percent_rank(data);
print(f"Percent ranks: {ranks}");

// Non-parametric test
let x = [1.0, 2.0, 3.0, 4.0, 5.0];
let y = [2.0, 3.0, 4.0, 5.0, 6.0];
let test = mann_whitney(x, y);
print(f"p-value: {test.p_value}");</code></pre>
<hr>
</section>
</section>
<section id="part-9-cli-reference" class="level2">
<h2 class="anchored" data-anchor-id="part-9-cli-reference">Part 9 — CLI Reference</h2>
<p>CJC-Lang ships as a single binary with 29 subcommands across three tiers.</p>
<section id="file-format-compatibility" class="level3">
<h3 class="anchored" data-anchor-id="file-format-compatibility">File Format Compatibility</h3>
<p>Not every command operates on <code>.cjcl</code> source files. Here’s what each command reads:</p>
<p><strong>CJC-Lang source only</strong> (<code>.cjcl</code>): <code>run</code>, <code>proof</code>, <code>emit</code>, <code>explain</code>, <code>gc</code>, <code>nogc</code>, <code>audit</code>, <code>precision</code>, <code>parity</code>, <code>test</code>, <code>bench</code>, <code>mem</code>, <code>trace</code>, <code>lock</code>, <code>check</code></p>
<p><strong>CJC + other formats</strong> (read-only):</p>
<table class="table">
<colgroup>
<col style="width: 50%">
<col style="width: 50%">
</colgroup>
<thead>
<tr class="header">
<th>Command</th>
<th>Formats</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>doctor</code></td>
<td><code>.cjcl</code>, <code>.snap</code> (binary), <code>.csv</code>/<code>.tsv</code>, plus metadata on all files for size warnings</td>
</tr>
<tr class="even">
<td><code>ci</code></td>
<td><code>.cjcl</code> only (reimplements parse + type checks, does not run full <code>doctor</code>)</td>
</tr>
<tr class="odd">
<td><code>view</code></td>
<td>Directory metadata, <code>.cjcl</code> source for effect annotations, <code>.snap</code> binary for hashes</td>
</tr>
<tr class="even">
<td><code>inspect</code></td>
<td>Dispatches on extension: <code>.snap</code> (binary decode), <code>.csv</code>/<code>.tsv</code> (schema), <code>.cjcl</code> (source), anything else (generic: size + hash)</td>
</tr>
<tr class="odd">
<td><code>seek</code></td>
<td>Traverses any directory; reads any file’s content for <code>--contains</code></td>
</tr>
</tbody>
</table>
<p><strong>Non-CJC-Lang formats exclusively:</strong></p>
<table class="table">
<colgroup>
<col style="width: 50%">
<col style="width: 50%">
</colgroup>
<thead>
<tr class="header">
<th>Command</th>
<th>Formats</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>flow</code></td>
<td><code>.csv</code>/<code>.tsv</code> or stdin</td>
</tr>
<tr class="even">
<td><code>schema</code></td>
<td><code>.csv</code>/<code>.tsv</code></td>
</tr>
<tr class="odd">
<td><code>patch</code></td>
<td><code>.csv</code>/<code>.tsv</code> or stdin</td>
</tr>
<tr class="even">
<td><code>drift</code></td>
<td>Any two files (auto-detects <code>.csv</code>/<code>.tsv</code> for structured diff, otherwise text diff)</td>
</tr>
</tbody>
</table>
</section>
<section id="core-commands" class="level3">
<h3 class="anchored" data-anchor-id="core-commands">Core Commands</h3>
<table class="table">
<colgroup>
<col style="width: 40%">
<col style="width: 59%">
</colgroup>
<thead>
<tr class="header">
<th>Command</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>cjcl run file.cjcl</code></td>
<td>Execute a program (AST tree-walk interpreter)</td>
</tr>
<tr class="even">
<td><code>cjcl run file.cjcl --mir-opt</code></td>
<td>Execute via MIR engine with optimizations (CF + DCE)</td>
</tr>
<tr class="odd">
<td><code>cjcl run file.cjcl --mir-mono</code></td>
<td>Execute with MIR monomorphization</td>
</tr>
<tr class="even">
<td><code>cjcl lex file.cjcl</code></td>
<td>Show token stream</td>
</tr>
<tr class="odd">
<td><code>cjcl parse file.cjcl</code></td>
<td>Parse and pretty-print AST</td>
</tr>
<tr class="even">
<td><code>cjcl check file.cjcl</code></td>
<td>Type-check without executing</td>
</tr>
<tr class="odd">
<td><code>cjcl repl</code></td>
<td>Interactive REPL (supports <code>:ast</code>, <code>:mir</code>, <code>:type</code>, <code>:env</code>, <code>:seed</code> meta-commands)</td>
</tr>
</tbody>
</table>
</section>
<section id="global-flags-for-core-commands" class="level3">
<h3 class="anchored" data-anchor-id="global-flags-for-core-commands">Global Flags (for core commands)</h3>
<table class="table">
<thead>
<tr class="header">
<th>Flag</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>--seed N</code></td>
<td>Set RNG seed (default: 42)</td>
</tr>
<tr class="even">
<td><code>--reproducible</code></td>
<td>Enable reproducibility mode</td>
</tr>
<tr class="odd">
<td><code>--time</code></td>
<td>Print execution time</td>
</tr>
<tr class="even">
<td><code>--mir-opt</code></td>
<td>Enable MIR optimizations</td>
</tr>
<tr class="odd">
<td><code>--mir-mono</code></td>
<td>Enable MIR monomorphization</td>
</tr>
<tr class="even">
<td><code>--multi-file</code></td>
<td>Enable multi-file module resolution</td>
</tr>
<tr class="odd">
<td><code>--color</code> / <code>--no-color</code></td>
<td>Force color output on/off</td>
</tr>
<tr class="even">
<td><code>--diagnostic-format rich\|short</code></td>
<td>Diagnostic output format</td>
</tr>
</tbody>
</table>
</section>
<section id="phase-1-cli-suite-data-pipeline-tools" class="level3">
<h3 class="anchored" data-anchor-id="phase-1-cli-suite-data-pipeline-tools">Phase 1 CLI Suite — Data &amp; Pipeline Tools</h3>
<section id="cjcl-view-path-deterministic-directory-listing" class="level4">
<h4 class="anchored" data-anchor-id="cjcl-view-path-deterministic-directory-listing"><code>cjcl view [path]</code> — Deterministic directory listing</h4>
<p>Lists directory contents with file kinds, sizes, and effect annotations. Output is sorted deterministically. Hash display is off by default for cleaner output; opt in with <code>--hash</code> when you need content-addressable verification.</p>
<p><strong>Flags:</strong> <code>-r, --recursive</code> | <code>--hash</code> | <code>--no-effects</code> | <code>--no-size</code> | <code>--no-header</code> | <code>--plain / --json / --table / --color</code></p>
<pre><code>$ cjcl view examples/

  examples/ (17 entries)

┌───────────────────────────────────────────┬──────┬───────────┬─────────┐
│ Name                                      │ Kind │ Size      │ Effects │
├───────────────────────────────────────────┼──────┼───────────┼─────────┤
│ examples/chess_replay.html                │ file │ 17.9 KiB  │ -       │
│ examples/chess_rl_dashboard.html          │ file │ 11.4 KiB  │ -       │
│ examples/etl_csv_parse.cjcl               │ cjcl │ 774 B     │ -       │
│ examples/nlp_tokenize.cjcl                │ cjcl │ 766 B     │ -       │
│ examples/transformer_forward.cjcl         │ cjcl │ 5.7 KiB   │ -       │
│ examples/vizor_bar.cjcl                   │ cjcl │ 439 B     │ -       │
│ ...                                       │      │           │         │
└───────────────────────────────────────────┴──────┴───────────┴─────────┘</code></pre>
</section>
<section id="cjcl-proof-file.cjcl-determinism-reproducibility-profiler" class="level4">
<h4 class="anchored" data-anchor-id="cjcl-proof-file.cjcl-determinism-reproducibility-profiler"><code>cjcl proof &lt;file.cjcl&gt;</code> — Determinism &amp; reproducibility profiler</h4>
<p>Runs a program multiple times and verifies that stdout, exit status, and GC behavior are identical across runs. Supports testing across multiple seeds to catch seed-dependent nondeterminism.</p>
<p><strong>Flags:</strong> <code>-n, --runs &lt;N&gt;</code> | <code>--seed &lt;N&gt;</code> | <code>--seeds &lt;N,M,...&gt;</code> | <code>-v, --verbose</code> | <code>--plain / --json / --color</code></p>
<pre><code>$ cjcl proof fibonacci.cjcl

[proof] Running 3 iterations with seed 42...
55
55
55

┌──────────────────────────┬────────┐
│ Check                    │ Status │
├──────────────────────────┼────────┤
│ stdout identical         │ PASS   │
│ exit status identical    │ PASS   │
│ GC collections identical │ PASS   │
└──────────────────────────┴────────┘

Verdict: PASS (3 runs, seed=42)</code></pre>
<p>Multi-seed mode tests reproducibility across different RNG seeds in a single invocation:</p>
<pre><code>$ cjcl proof fibonacci.cjcl --seeds 42,99,256</code></pre>
</section>
<section id="cjcl-flow-file.csv-streaming-computation-engine" class="level4">
<h4 class="anchored" data-anchor-id="cjcl-flow-file.csv-streaming-computation-engine"><code>cjcl flow [file.csv]</code> — Streaming computation engine</h4>
<p>Applies aggregation operations to CSV columns using O(1) memory per column. Uses Kahan summation for numerically stable sums and Welford’s algorithm for online variance. Reads from stdin if no file is given.</p>
<p><strong>Flags:</strong> <code>-d, --delimiter &lt;c&gt;</code> | <code>--tsv</code> | <code>--no-header</code> | <code>--op &lt;ops&gt;</code> (sum, mean, min, max, count, var, std) | <code>-c, --columns &lt;cols&gt;</code> | <code>--precision &lt;N&gt;</code> | <code>--plain / --json / --color</code></p>
<pre><code>$ cjcl flow data.csv --op sum

┌────────┬───────────┐
│ Column │ sum       │
├────────┼───────────┤
│ a      │ 12.000000 │
│ b      │ 15.000000 │
│ c      │ 18.000000 │
└────────┴───────────┘
(3 rows processed)</code></pre>
</section>
<section id="cjcl-patch-file.csv-type-aware-data-transformation" class="level4">
<h4 class="anchored" data-anchor-id="cjcl-patch-file.csv-type-aware-data-transformation"><code>cjcl patch &lt;file.csv&gt;</code> — Type-aware data transformation</h4>
<p>Apply transforms like rename, drop, impute, and replace to CSV data.</p>
<p><strong>Transforms:</strong> <code>--nan-fill &lt;value&gt;</code> | <code>--impute &lt;column&gt;</code> | <code>--replace &lt;col&gt; &lt;from&gt; &lt;to&gt;</code> | <code>--drop &lt;column&gt;</code> | <code>--rename &lt;old&gt; &lt;new&gt;</code> | <code>--fill-empty &lt;col&gt; &lt;value&gt;</code></p>
<p><strong>Flags:</strong> <code>-d, --delimiter &lt;c&gt;</code> | <code>--tsv</code> | <code>--no-header</code> | <code>-o, --output &lt;file&gt;</code> | <code>--plain / --json / --color</code></p>
<pre><code>$ cjcl patch data.csv --rename a x

x,b,c
1,2,3
4,5,6
7,8,9
patched 3 rows</code></pre>
</section>
<section id="cjcl-seek-path-pattern-deterministic-file-discovery" class="level4">
<h4 class="anchored" data-anchor-id="cjcl-seek-path-pattern-deterministic-file-discovery"><code>cjcl seek [path] [pattern]</code> — Deterministic file discovery</h4>
<p>Find files by type, content, or size with deterministic ordering.</p>
<p><strong>Flags:</strong> <code>-t, --type &lt;ext&gt;</code> | <code>--contains &lt;text&gt;</code> | <code>--min-size &lt;size&gt;</code> | <code>--max-size &lt;size&gt;</code> | <code>--max-depth &lt;N&gt;</code> | <code>--count</code> | <code>--plain / --json / --color</code></p>
<pre><code>$ cjcl seek examples/ --type cjc

examples/etl_csv_parse.cjcl
examples/nlp_tokenize.cjcl
examples/nlp_vocab_count.cjcl
examples/piml_demo.cjcl
examples/pinn_demo.cjcl
examples/transformer_forward.cjcl
examples/vizor_bar.cjcl
...
11 files found</code></pre>
</section>
<section id="cjcl-drift-a-b-mathematical-and-data-diff-engine" class="level4">
<h4 class="anchored" data-anchor-id="cjcl-drift-a-b-mathematical-and-data-diff-engine"><code>cjcl drift &lt;a&gt; &lt;b&gt;</code> — Mathematical and data diff engine</h4>
<p>Compares two files cell-by-cell with optional numeric tolerance. Auto-detects mode from file extension, or specify explicitly.</p>
<p><strong>Modes:</strong> <code>--text</code> | <code>--numeric</code> | <code>--csv</code></p>
<p><strong>Flags:</strong> <code>-e, --tolerance &lt;N&gt;</code> | <code>--precision &lt;N&gt;</code> | <code>--max-diffs &lt;N&gt;</code> | <code>-d, --delimiter &lt;c&gt;</code> | <code>--plain / --json / --color</code></p>
<pre><code>$ cjcl drift a.csv b.csv --csv

┌──────────────────┬──────────┐
│ Metric           │ Value    │
├──────────────────┼──────────┤
│ Rows (A)         │ 3        │
│ Rows (B)         │ 3        │
│ Cell differences │ 1        │
│ NaN divergences  │ 0        │
│ Max deviation    │ 1.000000 │
│ Frobenius norm   │ 1.000000 │
└──────────────────┴──────────┘

First differences:
  [3,3]: "6" → "7"</code></pre>
</section>
<section id="cjcl-forge-action-content-addressable-pipeline-runner" class="level4">
<h4 class="anchored" data-anchor-id="cjcl-forge-action-content-addressable-pipeline-runner"><code>cjcl forge &lt;action&gt;</code> — Content-addressable pipeline runner</h4>
<p>Runs a program, hashes the output, and caches the result. Re-running with the same source skips execution.</p>
<p><strong>Actions:</strong> <code>run &lt;file.cjcl&gt;</code> | <code>verify &lt;file.cjcl&gt; &lt;hash&gt;</code> | <code>list</code> | <code>show &lt;hash-prefix&gt;</code> | <code>clean</code></p>
<p><strong>Flags:</strong> <code>--seed &lt;N&gt;</code> | <code>--cache-dir &lt;path&gt;</code> | <code>-v, --verbose</code> | <code>--plain / --json / --color</code></p>
<pre><code>$ cjcl forge run fibonacci.cjcl

55
┌──────────┬──────────────────────────────────────────────────────────────────┐
│ Property │ Value                                                            │
├──────────┼──────────────────────────────────────────────────────────────────┤
│ Source   │ fibonacci.cjcl                                                    │
│ Seed     │ 42                                                               │
│ SHA-256  │ 02d20bbd7e394ad5999a4cebabac9619732c343a4cac99470c03e23ba2bdc2bc │
│ Artifact │ .cjcl-forge/02d20bbd...bdc2bc.artifact                            │
└──────────┴──────────────────────────────────────────────────────────────────┘
forged</code></pre>
</section>
</section>
<section id="phase-2-cli-suite-analysis-diagnostics" class="level3">
<h3 class="anchored" data-anchor-id="phase-2-cli-suite-analysis-diagnostics">Phase 2 CLI Suite — Analysis &amp; Diagnostics</h3>
<section id="cjcl-inspect-file-deep-file-inspection" class="level4">
<h4 class="anchored" data-anchor-id="cjcl-inspect-file-deep-file-inspection"><code>cjcl inspect &lt;file&gt;</code> — Deep file inspection</h4>
<p>Analyzes <code>.cjcl</code>, <code>.csv</code>, <code>.snap</code>, or generic files. For CJC-Lang source, reports parse results, function signatures, and structural statistics.</p>
<p><strong>Flags:</strong> <code>--no-stats</code> | <code>--no-hash</code> | <code>--preview &lt;N&gt;</code> | <code>--plain / --json / --color</code></p>
<pre><code>$ cjcl inspect fibonacci.cjcl

┌──────────────┬────────────────┐
│ Property     │ Value          │
├──────────────┼────────────────┤
│ File         │ fibonacci.cjcl  │
│ Type         │ cjcl source    │
│ Size         │ 192 B          │
│ Lines        │ 10             │
│ Parse errors │ 0              │
│ Functions    │ 2              │
│ Structs      │ 0              │
│ Let bindings │ 0              │
└──────────────┴────────────────┘

Functions:
  fn fibonacci(n: i64)
  fn main()</code></pre>
</section>
<section id="cjcl-schema-file.csv-csvtsv-schema-inference" class="level4">
<h4 class="anchored" data-anchor-id="cjcl-schema-file.csv-csvtsv-schema-inference"><code>cjcl schema &lt;file.csv&gt;</code> — CSV/TSV schema inference</h4>
<p>Infers column types, null counts, unique values, and numeric ranges from CSV data.</p>
<p><strong>Flags:</strong> <code>-d, --delimiter &lt;c&gt;</code> | <code>--tsv</code> | <code>--no-header</code> | <code>--no-uniques</code> | <code>--sample &lt;N&gt;</code> | <code>--plain / --json / --color</code></p>
<pre><code>$ cjcl schema data.csv

Schema for `data.csv` (3 rows sampled):

┌────────┬──────┬───────┬───────┬────────┬────────┬────────┐
│ Column │ Type │ Nulls │ Total │ Unique │ Min    │ Max    │
├────────┼──────┼───────┼───────┼────────┼────────┼────────┤
│ a      │ int  │ 0     │ 3     │ 3      │ 1.0000 │ 7.0000 │
│ b      │ int  │ 0     │ 3     │ 3      │ 2.0000 │ 8.0000 │
│ c      │ int  │ 0     │ 3     │ 3      │ 3.0000 │ 9.0000 │
└────────┴──────┴───────┴───────┴────────┴────────┴────────┘</code></pre>
</section>
<section id="cjcl-trace-file.cjcl-deterministic-execution-tracing" class="level4">
<h4 class="anchored" data-anchor-id="cjcl-trace-file.cjcl-deterministic-execution-tracing"><code>cjcl trace &lt;file.cjcl&gt;</code> — Deterministic execution tracing</h4>
<p>Traces parse time, execution time, AST structure, and runtime behavior.</p>
<p><strong>Flags:</strong> <code>--seed &lt;N&gt;</code> | <code>--ast</code> | <code>--no-output</code> | <code>-v, --verbose</code> | <code>--plain / --json / --color</code></p>
<pre><code>$ cjcl trace fibonacci.cjcl

[trace] Tracing `fibonacci.cjcl`...

┌────────────┬──────────┐
│ Phase      │ Detail   │
├────────────┼──────────┤
│ Parse time │ 0.120 ms │
│ Exec time  │ 1.022 ms │
│ Seed       │ 42       │
│ Status     │ OK       │
└────────────┴──────────┘

┌──────────────┬───────┐
│ AST Element  │ Count │
├──────────────┼───────┤
│ Functions    │ 2     │
│ Structs      │ 0     │
│ Let bindings │ 0     │
│ Statements   │ 0     │
│ Decorators   │ 0     │
└──────────────┴───────┘

┌────────────────┬───────┐
│ Runtime        │ Value │
├────────────────┼───────┤
│ Output lines   │ 1     │
│ GC collections │ 0     │
└────────────────┴───────┘</code></pre>
</section>
<section id="cjcl-mem-file.cjcl-runtime-memory-visibility" class="level4">
<h4 class="anchored" data-anchor-id="cjcl-mem-file.cjcl-runtime-memory-visibility"><code>cjcl mem &lt;file.cjcl&gt;</code> — Runtime memory visibility</h4>
<p>Profiles GC collections, heap object counts, output size, and execution time. Supports NoGC pre-verification and executor selection.</p>
<p><strong>Flags:</strong> <code>--seed &lt;N&gt;</code> | <code>-n, --runs &lt;N&gt;</code> | <code>-v, --verbose</code> | <code>--nogc</code> | <code>--mir</code> | <code>--eval</code> | <code>--plain / --json / --color</code></p>
<pre><code>$ cjcl mem fibonacci.cjcl

[mem] Memory profile for `fibonacci.cjcl` (1 run, seed=42)

┌───────────────────────┬──────────┐
│ Metric                │ Value    │
├───────────────────────┼──────────┤
│ GC collections (avg)  │ 0.00     │
│ GC stable across runs │ true     │
│ GC heap objects (max) │ 0        │
│ Output lines          │ 1        │
│ Output bytes          │ 2        │
│ Avg exec time         │ 755.0 us │
└───────────────────────┴──────────┘</code></pre>
</section>
<section id="cjcl-bench-file.cjcl-performance-benchmarking" class="level4">
<h4 class="anchored" data-anchor-id="cjcl-bench-file.cjcl-performance-benchmarking"><code>cjcl bench &lt;file.cjcl&gt;</code> — Performance benchmarking</h4>
<p>Runs warmup iterations, then measured iterations with statistical analysis. Reports mean, median, min, max, standard deviation, coefficient of variation, P95, and throughput.</p>
<p><strong>Flags:</strong> <code>--seed &lt;N&gt;</code> | <code>-n, --runs &lt;N&gt;</code> | <code>--warmup &lt;N&gt;</code> | <code>-v, --verbose</code> | <code>--nogc</code> | <code>--mir</code> | <code>--eval</code> | <code>--plain / --json / --color</code></p>
<pre><code>$ cjcl bench fibonacci.cjcl

[bench] Benchmarking `fibonacci.cjcl` (1 warmup + 10 measured runs, seed=42)

┌──────────────┬─────────────────┐
│ Metric       │ Value           │
├──────────────┼─────────────────┤
│ Parse time   │ 129.0 us        │
│ Mean         │ 647.8 us        │
│ Median       │ 628.5 us        │
│ Min          │ 438.0 us        │
│ Max          │ 808.0 us        │
│ Std dev      │ 111.6 us        │
│ CV           │ 17.2%           │
│ P95          │ 808.0 us        │
│ Throughput   │ 1543.7 runs/sec │
└──────────────┴─────────────────┘</code></pre>
</section>
<section id="cjcl-pack-file.cjcl-reproducible-packaging" class="level4">
<h4 class="anchored" data-anchor-id="cjcl-pack-file.cjcl-reproducible-packaging"><code>cjcl pack &lt;file.cjcl&gt;</code> — Reproducible packaging</h4>
<p>Bundles source files and their referenced artifacts (<code>.snap</code>, <code>.csv</code>, <code>.tsv</code>, <code>.json</code>) into a content-addressed package with manifest hashes.</p>
<p><strong>Flags:</strong> <code>-o, --output &lt;dir&gt;</code> | <code>--include &lt;file&gt;</code> (repeatable) | <code>--dry-run</code> | <code>--plain / --json / --color</code></p>
<pre><code>$ cjcl pack fibonacci.cjcl --dry-run

Dry run — would create `fibonacci.pack`:

┌───────────────┬───────┬──────────────────┐
│ File          │ Size  │ Hash             │
├───────────────┼───────┼──────────────────┤
│ fibonacci.cjcl │ 192 B │ 3402c8cc3d398ba2 │
└───────────────┴───────┴──────────────────┘
Total: 1 files, 192 B
Manifest hash: fce3865dd0fb4816</code></pre>
</section>
<section id="cjcl-doctor-path-project-diagnostics-and-health-checks" class="level4">
<h4 class="anchored" data-anchor-id="cjcl-doctor-path-project-diagnostics-and-health-checks"><code>cjcl doctor [path]</code> — Project diagnostics and health checks</h4>
<p>Scans a directory for CJC source files, snap files, and CSV data. Reports parse errors, type issues, corrupt snap files, ragged CSV rows, nondeterministic patterns, and large files.</p>
<p><strong>Flags:</strong> <code>-v, --verbose</code> | <code>--fix</code> | <code>--strict</code> (exit non-zero on warnings, useful for CI) | <code>--plain / --json / --color</code></p>
<pre><code>$ cjcl doctor examples/

[doctor] Diagnosing `examples/`...

┌──────────────────┬───────┐
│ Category         │ Count │
├──────────────────┼───────┤
│ CJC source files │ 11    │
│ Snap files       │ 0     │
│ CSV/TSV files    │ 0     │
│ Total files      │ 17    │
└──────────────────┴───────┘

  [WARN] examples/etl_csv_parse.cjcl — 3 type issue(s)
  [WARN] examples/transformer_forward.cjcl — 50 type issue(s)
  ...

┌──────────┬───────┐
│ Severity │ Count │
├──────────┼───────┤
│ Warnings │ 9     │
└──────────┴───────┘

OK (with warnings)</code></pre>
</section>
</section>
<section id="phase-3-cli-suite-compiler-visibility-verification" class="level3">
<h3 class="anchored" data-anchor-id="phase-3-cli-suite-compiler-visibility-verification">Phase 3 CLI Suite — Compiler Visibility &amp; Verification</h3>
<section id="cjcl-emit-file.cjcl-dump-intermediate-representations" class="level4">
<h4 class="anchored" data-anchor-id="cjcl-emit-file.cjcl-dump-intermediate-representations"><code>cjcl emit &lt;file.cjcl&gt;</code> — Dump intermediate representations</h4>
<p>Dumps the compiler’s internal representation at any pipeline stage: AST (parsed source), HIR (desugared form), or MIR (register-machine IR). Use <code>--opt</code> to see the MIR after constant folding and dead code elimination, or <code>--diff</code> to compare both side by side.</p>
<p><strong>Flags:</strong> <code>-s, --stage &lt;S&gt;</code> (ast, hir, mir) | <code>--opt</code> | <code>--diff</code> | <code>--plain / --json / --color</code></p>
<pre><code>$ cjcl emit fibonacci.cjcl --stage ast

=== fibonacci.cjcl (AST) ===

fn fibonacci(n: i64) -&gt; i64 {
    if (n &lt;= 1) {
        return n;
    }
    return (fibonacci((n - 1)) + fibonacci((n - 2)));
}
fn main() -&gt; i64 {
    let result = fibonacci(10);
    print(result);
    result
}</code></pre>
<pre><code>$ cjcl emit fibonacci.cjcl --stage mir

=== fibonacci.cjcl (MIR) ===

fn fibonacci(n: i64) -&gt; i64:
  if (n &lt;= 1):
    return n
  return (fibonacci((n - 1)) + fibonacci((n - 2)))

fn main() -&gt; void:
  print(fibonacci(10))</code></pre>
</section>
<section id="cjcl-explain-file.cjcl-show-desugaredlowered-forms" class="level4">
<h4 class="anchored" data-anchor-id="cjcl-explain-file.cjcl-show-desugaredlowered-forms"><code>cjcl explain &lt;file.cjcl&gt;</code> — Show desugared/lowered forms</h4>
<p>Lowers the source to HIR and displays function signatures, struct definitions, NoGC annotations, and decorators. Use <code>--verbose</code> to see the full HIR body for each function.</p>
<p><strong>Flags:</strong> <code>-v, --verbose</code> | <code>--plain / --json / --color</code></p>
<pre><code>$ cjcl explain fibonacci.cjcl

[explain] Explaining `fibonacci.cjcl`...

fn fibonacci(n: i64)
  body: 2 statements

fn main()
  body: 2 statements + tail expression

┌────────────────┬───────┐
│ Element        │ Count │
├────────────────┼───────┤
│ Functions      │ 2     │
│ Structs        │ 0     │
│ NoGC functions │ 0     │
│ Total items    │ 2     │
└────────────────┴───────┘</code></pre>
</section>
<section id="cjcl-gc-file.cjcl-gc-analysis-allocation-timeline" class="level4">
<h4 class="anchored" data-anchor-id="cjcl-gc-file.cjcl-gc-analysis-allocation-timeline"><code>cjcl gc &lt;file.cjcl&gt;</code> — GC analysis &amp; allocation timeline</h4>
<p>Dedicated garbage collection analysis. Runs the program multiple times and reports GC pause count, peak/average heap objects, allocation timeline, and GC stability across runs.</p>
<p><strong>Flags:</strong> <code>--seed &lt;N&gt;</code> | <code>-n, --runs &lt;N&gt;</code> | <code>-v, --verbose</code> | <code>--plain / --json / --color</code></p>
<pre><code>$ cjcl gc fibonacci.cjcl

[gc] GC analysis for `fibonacci.cjcl` (3 runs, seed=42)

┌────────────────────────┬─────────────────────┐
│ Metric                 │ Value               │
├────────────────────────┼─────────────────────┤
│ GC pause count (total) │ 0                   │
│ GC collections (avg)   │ 0.00                │
│ GC collections (min)   │ 0                   │
│ GC collections (max)   │ 0                   │
│ GC stable across runs  │ yes                 │
│ Peak heap objects      │ 0                   │
│ Avg heap objects       │ 0.0                 │
│ COW copies             │ 0 (not yet tracked) │
│ Avg exec time          │ 825.3 us            │
└────────────────────────┴─────────────────────┘

Allocation timeline:
┌─────┬────┬───────────┬──────────────┬──────────────┬───────────┐
│ Run │ GC │ Heap Objs │ Output Lines │ Output Bytes │ Time (us) │
├─────┼────┼───────────┼──────────────┼──────────────┼───────────┤
│ 1   │ 0  │ 0         │ 1            │ 2            │ 816       │
│ 2   │ 0  │ 0         │ 1            │ 2            │ 1006      │
│ 3   │ 0  │ 0         │ 1            │ 2            │ 394       │
└─────┴────┴───────────┴──────────────┴──────────────┴───────────┘

STABLE — GC behavior is deterministic across 3 runs.</code></pre>
</section>
<section id="cjc-nogc-file.cjcl-nogc-static-verification" class="level4">
<h4 class="anchored" data-anchor-id="cjc-nogc-file.cjcl-nogc-static-verification"><code>cjc nogc &lt;file.cjcl&gt;</code> — NoGC static verification</h4>
<p>Verifies that all <code>nogc</code> functions in the program are free of GC allocations. Lists every function with its annotation status and per-function PASS/FAIL. Exit code 1 if any violations are found.</p>
<p><strong>Flags:</strong> <code>-v, --verbose</code> | <code>--plain / --json / --color</code></p>
<pre><code>$ cjcl nogc fibonacci.cjcl

[nogc] NoGC verification for `fibonacci.cjcl`

┌───────────┬─────────┬────────┐
│ Function  │ #[nogc] │ Status │
├───────────┼─────────┼────────┤
│ fibonacci │ no      │ PASS   │
│ main      │ no      │ PASS   │
└───────────┴─────────┴────────┘

Verdict: PASS (2 functions, 0 #[nogc])</code></pre>
</section>
<section id="cjc-audit-file.cjcl-numerical-hygiene-analysis" class="level4">
<h4 class="anchored" data-anchor-id="cjc-audit-file.cjcl-numerical-hygiene-analysis"><code>cjc audit &lt;file.cjcl&gt;</code> — Numerical hygiene analysis</h4>
<p>Static analysis of CJC-Lang source for floating-point anti-patterns. Catches common numerical issues before they become silent precision bugs.</p>
<p><strong>Patterns detected:</strong> naive summation in loops (suggests <code>kahan_sum()</code> or <code>binned_sum()</code>), float equality comparison (suggests <code>approx_eq</code>), division without zero-guard, catastrophic cancellation.</p>
<p><strong>Flags:</strong> <code>-v, --verbose</code> | <code>--plain / --json / --color</code></p>
<pre><code>$ cjcl audit naive_sum.cjcl

[audit] Auditing `naive_sum.cjcl`...

  [WARN] line 1: naive summation: `total = total + ...` in loop
         suggestion: use `kahan_sum()` or `binned_sum()`
  [WARN] line 1: naive summation: `i = i + ...` in loop
         suggestion: use `kahan_sum()` or `binned_sum()`

┌────────────────┬───────┐
│ Metric         │ Count │
├────────────────┼───────┤
│ Warnings       │ 4     │
│ Info           │ 0     │
│ Total findings │ 4     │
└────────────────┴───────┘</code></pre>
<p>A clean program reports no findings:</p>
<pre><code>$ cjcl audit fibonacci.cjcl

[audit] Auditing `fibonacci.cjcl`...

CLEAN — no numerical hygiene issues found</code></pre>
</section>
<section id="cjc-precision-file.cjcl-precision-analysis" class="level4">
<h4 class="anchored" data-anchor-id="cjc-precision-file.cjcl-precision-analysis"><code>cjc precision &lt;file.cjcl&gt;</code> — Precision analysis</h4>
<p>Runs the program, captures all numeric output, and compares each f64 value against its f32-truncated approximation. Reports per-value relative error and an overall stability verdict.</p>
<p><strong>Flags:</strong> <code>--seed &lt;N&gt;</code> | <code>--plain / --json / --color</code></p>
<pre><code>$ cjcl precision fibonacci.cjcl

[precision] Precision analysis for `fibonacci.cjcl` (seed=42)

┌──────┬─────────────────┬─────────────────┬────────────┐
│ Line │ f64             │ f32             │ Rel. error │
├──────┼─────────────────┼─────────────────┼────────────┤
│ 1    │ 55.000000000000 │ 55.000000000000 │ 0.00e+00   │
└──────┴─────────────────┴─────────────────┴────────────┘

Max relative error: 0.00e+00
Verdict: STABLE (within f32 epsilon bounds)</code></pre>
</section>
<section id="cjc-lock-file.cjcl-deterministic-lockfile-generator-verifier" class="level4">
<h4 class="anchored" data-anchor-id="cjc-lock-file.cjcl-deterministic-lockfile-generator-verifier"><code>cjc lock &lt;file.cjcl&gt;</code> — Deterministic lockfile generator &amp; verifier</h4>
<p>Generates a lockfile recording source SHA-256, seed, executor version, platform, and expected output SHA-256. Use <code>--verify</code> to check that the current run matches the lockfile. Enables cross-platform and cross-version reproducibility verification.</p>
<p><strong>Flags:</strong> <code>--seed &lt;N&gt;</code> | <code>--verify</code> | <code>--plain / --json / --color</code></p>
<pre><code>$ cjcl lock fibonacci.cjcl

[lock] Generated lockfile: fibonacci.cjcl.lock
┌───────────────┬──────────────────────────────────────────────────────────────────┐
│ Field         │ Value                                                            │
├───────────────┼──────────────────────────────────────────────────────────────────┤
│ source_sha256 │ 3402c8cc3d398ba214ee1111095e3568f0617857785ed8e1f9ec74e5eea1adda │
│ seed          │ 42                                                               │
│ executor      │ eval v0.1.0                                                      │
│ platform      │ windows-x86_64                                                   │
│ output_sha256 │ 02d20bbd7e394ad5999a4cebabac9619732c343a4cac99470c03e23ba2bdc2bc │
└───────────────┴──────────────────────────────────────────────────────────────────┘</code></pre>
<pre><code>$ cjcl lock fibonacci.cjcl --verify

[lock] Verifying against fibonacci.cjcl.lock

┌───────────────┬─────────────────────┬─────────────────────┬────────┐
│ Check         │ Expected            │ Actual              │ Status │
├───────────────┼─────────────────────┼─────────────────────┼────────┤
│ source_sha256 │ 3402c8cc3d398ba2... │ 3402c8cc3d398ba2... │ PASS   │
│ seed          │ 42                  │ 42                  │ PASS   │
│ output_sha256 │ 02d20bbd7e394ad5... │ 02d20bbd7e394ad5... │ PASS   │
└───────────────┴─────────────────────┴─────────────────────┴────────┘

Verdict: MATCH</code></pre>
</section>
<section id="cjc-parity-file.cjcl-dual-executor-parity-checker" class="level4">
<h4 class="anchored" data-anchor-id="cjc-parity-file.cjcl-dual-executor-parity-checker"><code>cjc parity &lt;file.cjcl&gt;</code> — Dual-executor parity checker</h4>
<p>Runs both the eval tree-walk interpreter (v1) and the MIR register-machine executor (v2) and compares their output. Verifies that both backends produce identical stdout, return values, and GC behavior.</p>
<p><strong>Flags:</strong> <code>--seed &lt;N&gt;</code> | <code>-v, --verbose</code> | <code>--plain / --json / --color</code></p>
<pre><code>$ cjcl parity fibonacci.cjcl

[parity] eval vs mir-exec (seed=42)

┌────────────────┬───────────────┐
│ Check          │ Status        │
├────────────────┼───────────────┤
│ stdout match   │ PASS          │
│ value match    │ PASS          │
│ GC collections │ eval=0, mir=0 │
└────────────────┴───────────────┘

Verdict: IDENTICAL</code></pre>
</section>
<section id="cjc-test-file.cjcl-native-test-runner" class="level4">
<h4 class="anchored" data-anchor-id="cjc-test-file.cjcl-native-test-runner"><code>cjc test &lt;file.cjcl&gt;</code> — Native test runner</h4>
<p>Discovers functions starting with <code>test_</code> or decorated with <code>@test</code>. Each test runs in isolation with a fresh interpreter instance. Tests execute in alphabetical order for deterministic results.</p>
<p><strong>Flags:</strong> <code>--seed &lt;N&gt;</code> | <code>--filter &lt;pattern&gt;</code> | <code>-v, --verbose</code> | <code>--plain / --json / --color</code></p>
<pre><code>$ cjcl test math_tests.cjcl

[test] Running 2 tests (seed=42)

  test_add .............................. PASS (0.1ms)
  test_multiply ......................... PASS (0.0ms)

2 passed, 0 failed</code></pre>
</section>
<section id="cjc-ci-path-full-ci-diagnostic-suite" class="level4">
<h4 class="anchored" data-anchor-id="cjc-ci-path-full-ci-diagnostic-suite"><code>cjc ci [path]</code> — Full CI diagnostic suite</h4>
<p>Orchestrates the complete verification pipeline in a single command: doctor (project health), proof (determinism across 3 seeds), parity (eval vs mir-exec), test (native test runner), and nogc (static verification). Exit code 0 means everything passes.</p>
<p><strong>Flags:</strong> <code>--seed &lt;N&gt;</code> | <code>--strict</code> | <code>--plain / --json / --color</code></p>
<pre><code>$ cjcl ci examples/

[ci] Running full diagnostic suite on `examples/`...

  doctor .... PASS (OK (9 type warnings))
  proof ..... PASS (PASS (33 checks, 3 seeds))
  parity .... PASS (PASS (eval ≡ mir-exec, 11 files))
  tests ..... PASS (0/0 passed)
  nogc ...... PASS (11/11 verified)

EXIT 0</code></pre>
<hr>
</section>
</section>
</section>
<section id="part-9.1-cli-benchmark-results" class="level2">
<h2 class="anchored" data-anchor-id="part-9.1-cli-benchmark-results">Part 9.1 — CLI Benchmark Results</h2>
<p>Every CLI command was tested against production-scale datasets (up to 1,000,000 rows / 108 MB) to verify correctness, numerical accuracy, and determinism. This section presents the full results.</p>
<section id="test-data" class="level3">
<h3 class="anchored" data-anchor-id="test-data">Test Data</h3>
<table class="table">
<colgroup>
<col style="width: 20%">
<col style="width: 13%">
<col style="width: 20%">
<col style="width: 13%">
<col style="width: 30%">
</colgroup>
<thead>
<tr class="header">
<th>Dataset</th>
<th>Rows</th>
<th>Columns</th>
<th>Size</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>million.csv</code></td>
<td>1,000,000</td>
<td>5</td>
<td>52 MB</td>
<td>price, volume, returns, volatility, score</td>
</tr>
<tr class="even">
<td><code>million_wide.csv</code></td>
<td>1,000,000</td>
<td>10</td>
<td>108 MB</td>
<td>10 float columns, uniform random</td>
</tr>
<tr class="odd">
<td><code>million_messy.csv</code></td>
<td>1,000,000</td>
<td>5</td>
<td>36 MB</td>
<td>Mixed types, ~4% NaN, ~3% empty cells</td>
</tr>
<tr class="even">
<td><code>million.tsv</code></td>
<td>1,000,000</td>
<td>4</td>
<td>38 MB</td>
<td>Tab-separated, timestamp + sensor readings</td>
</tr>
<tr class="odd">
<td><code>drift_a.csv</code> / <code>drift_b.csv</code></td>
<td>1,000,000</td>
<td>3</td>
<td>43 MB each</td>
<td>~1,001 injected cell differences</td>
</tr>
<tr class="even">
<td><code>stress_compute.cjcl</code></td>
<td>–</td>
<td>–</td>
<td>1.5 KB</td>
<td>fib(20) + sum(100K) + Collatz + prime sieve + GCD</td>
</tr>
</tbody>
</table>
<p>Smaller datasets (3 to 100,000 rows) were also tested for every command; only the production-scale results are shown here.</p>
</section>
<section id="production-scale-performance" class="level3">
<h3 class="anchored" data-anchor-id="production-scale-performance">Production-Scale Performance</h3>
<table class="table">
<colgroup>
<col style="width: 15%">
<col style="width: 22%">
<col style="width: 22%">
<col style="width: 15%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th>Command</th>
<th>1M-Row Time</th>
<th>108 MB Time</th>
<th>Correct</th>
<th>Deterministic</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>cjcl flow</code></td>
<td>0.9–1.8 s</td>
<td>1.8 s (10 cols)</td>
<td>PASS</td>
<td>Bit-identical across runs</td>
</tr>
<tr class="even">
<td><code>cjcl schema</code></td>
<td>0.5–1.5 s</td>
<td>1.5 s (10 cols)</td>
<td>PASS</td>
<td>Bit-identical across runs</td>
</tr>
<tr class="odd">
<td><code>cjcl inspect</code></td>
<td>0.3–1.1 s</td>
<td>1.1 s (10 cols)</td>
<td>PASS</td>
<td>PASS</td>
</tr>
<tr class="even">
<td><code>cjcl patch</code></td>
<td>5.5–13.6 s</td>
<td>13.6 s (10 cols)</td>
<td>PASS</td>
<td>PASS</td>
</tr>
<tr class="odd">
<td><code>cjcl drift</code></td>
<td>1.0–1.4 s</td>
<td>–</td>
<td>PASS</td>
<td>PASS</td>
</tr>
<tr class="even">
<td><code>cjcl seek</code></td>
<td>1.8–10.7 s</td>
<td>–</td>
<td>PASS</td>
<td>PASS</td>
</tr>
<tr class="odd">
<td><code>cjcl proof</code></td>
<td>0.5–1.6 s</td>
<td>–</td>
<td>PASS</td>
<td>All seeds pass</td>
</tr>
<tr class="even">
<td><code>cjcl bench</code></td>
<td>1.3–1.4 s</td>
<td>–</td>
<td>PASS</td>
<td>PASS</td>
</tr>
<tr class="odd">
<td><code>cjcl mem</code></td>
<td>0.7 s</td>
<td>–</td>
<td>PASS</td>
<td>PASS</td>
</tr>
<tr class="even">
<td><code>cjcl trace</code></td>
<td>0.3 s</td>
<td>–</td>
<td>PASS</td>
<td>PASS</td>
</tr>
<tr class="odd">
<td><code>cjcl forge</code></td>
<td>0.2 s</td>
<td>–</td>
<td>PASS</td>
<td>Hash identical</td>
</tr>
<tr class="even">
<td><code>cjcl pack</code></td>
<td>0.03 s</td>
<td>–</td>
<td>PASS</td>
<td>PASS</td>
</tr>
<tr class="odd">
<td><code>cjcl doctor</code></td>
<td>0.2 s</td>
<td>–</td>
<td>PASS</td>
<td>PASS</td>
</tr>
</tbody>
</table>
</section>
<section id="detailed-results-by-command" class="level3">
<h3 class="anchored" data-anchor-id="detailed-results-by-command">Detailed Results by Command</h3>
<section id="flow-streaming-aggregation-1m-rows" class="level4">
<h4 class="anchored" data-anchor-id="flow-streaming-aggregation-1m-rows"><code>flow</code> – Streaming Aggregation (1M rows)</h4>
<table class="table">
<colgroup>
<col style="width: 20%">
<col style="width: 31%">
<col style="width: 20%">
<col style="width: 27%">
</colgroup>
<thead>
<tr class="header">
<th>Test</th>
<th>Dataset</th>
<th>Time</th>
<th>Result</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Kahan sum, 5 columns</td>
<td><code>million.csv</code> (52 MB)</td>
<td>1.02 s</td>
<td>PASS</td>
</tr>
<tr class="even">
<td>All 7 ops (sum, mean, min, max, std, var, count)</td>
<td><code>million.csv</code></td>
<td>0.91 s</td>
<td>PASS</td>
</tr>
<tr class="odd">
<td>Column filter (<code>-c price,returns</code>)</td>
<td><code>million.csv</code></td>
<td>0.75 s</td>
<td>PASS</td>
</tr>
<tr class="even">
<td>10-column wide</td>
<td><code>million_wide.csv</code> (108 MB)</td>
<td>1.77 s</td>
<td>PASS</td>
</tr>
<tr class="odd">
<td>TSV input</td>
<td><code>million.tsv</code> (38 MB)</td>
<td>0.53 s</td>
<td>PASS</td>
</tr>
<tr class="even">
<td>12-digit precision</td>
<td><code>million.csv</code></td>
<td>0.84 s</td>
<td>PASS</td>
</tr>
<tr class="odd">
<td>JSON output</td>
<td><code>million.csv</code></td>
<td>1.06 s</td>
<td>PASS</td>
</tr>
<tr class="even">
<td>Determinism: 3 consecutive runs</td>
<td><code>million.csv</code></td>
<td>–</td>
<td>BIT-IDENTICAL JSON output</td>
</tr>
</tbody>
</table>
<p>Numerical accuracy at 1M scale:</p>
<ul>
<li><code>price</code> mean = 499.12 (uniform [-500, 1500], expected ~500)</li>
<li><code>returns</code> mean = -0.000048 (uniform [-0.1, 0.1], expected ~0)</li>
<li><code>volatility</code> mean = 0.250038 (uniform [0, 0.5], expected 0.25)</li>
<li><code>score</code> mean = 49.9653 (uniform [0, 100], expected 50)</li>
<li>Kahan summation label present in output</li>
</ul>
</section>
<section id="schema-type-inference-1m-rows" class="level4">
<h4 class="anchored" data-anchor-id="schema-type-inference-1m-rows"><code>schema</code> – Type Inference (1M rows)</h4>
<table class="table">
<colgroup>
<col style="width: 20%">
<col style="width: 31%">
<col style="width: 20%">
<col style="width: 27%">
</colgroup>
<thead>
<tr class="header">
<th>Test</th>
<th>Dataset</th>
<th>Time</th>
<th>Result</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>5 numeric columns</td>
<td><code>million.csv</code> (52 MB)</td>
<td>0.84 s</td>
<td>PASS – types, min/max, nulls correct</td>
</tr>
<tr class="even">
<td>10 columns</td>
<td><code>million_wide.csv</code> (108 MB)</td>
<td>1.46 s</td>
<td>PASS – symmetric around 0</td>
</tr>
<tr class="odd">
<td>Messy data (NaN + empty)</td>
<td><code>million_messy.csv</code> (36 MB)</td>
<td>0.90 s</td>
<td>PASS</td>
</tr>
<tr class="even">
<td>TSV</td>
<td><code>million.tsv</code> (38 MB)</td>
<td>0.45 s</td>
<td>PASS</td>
</tr>
<tr class="odd">
<td>Sampled (1,000 rows only)</td>
<td><code>million.csv</code></td>
<td>0.12 s</td>
<td>PASS – 8x faster</td>
</tr>
<tr class="even">
<td>JSON output</td>
<td><code>million.csv</code></td>
<td>0.62 s</td>
<td>PASS</td>
</tr>
<tr class="odd">
<td>Determinism: 3 consecutive runs</td>
<td><code>million.csv</code></td>
<td>–</td>
<td>BIT-IDENTICAL JSON output</td>
</tr>
</tbody>
</table>
<p>Null detection accuracy at 1M scale:</p>
<ul>
<li><code>measurement</code>: 40,120 nulls (4.01%) – generated at 2% empty + 2% NaN = 4%</li>
<li><code>flag</code>: 30,031 nulls (3.00%) – generated at 3% empty</li>
<li><code>reading</code>: 10,004 nulls (1.00%) – generated at 1% NaN</li>
</ul>
</section>
<section id="inspect-file-analysis-1m-rows" class="level4">
<h4 class="anchored" data-anchor-id="inspect-file-analysis-1m-rows"><code>inspect</code> – File Analysis (1M rows)</h4>
<table class="table">
<colgroup>
<col style="width: 20%">
<col style="width: 31%">
<col style="width: 20%">
<col style="width: 27%">
</colgroup>
<thead>
<tr class="header">
<th>Test</th>
<th>Dataset</th>
<th>Time</th>
<th>Result</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>52 MB CSV</td>
<td><code>million.csv</code></td>
<td>0.66 s</td>
<td>1M rows, 5 cols, min/max/mean correct</td>
</tr>
<tr class="even">
<td>108 MB CSV</td>
<td><code>million_wide.csv</code></td>
<td>1.13 s</td>
<td>1M rows, 10 cols</td>
</tr>
<tr class="odd">
<td>Messy CSV</td>
<td><code>million_messy.csv</code></td>
<td>0.51 s</td>
<td>Null counts match schema</td>
</tr>
<tr class="even">
<td>TSV</td>
<td><code>million.tsv</code></td>
<td>0.34 s</td>
<td>Auto-detected delimiter</td>
</tr>
<tr class="odd">
<td>CJC source</td>
<td><code>stress_compute.cjcl</code></td>
<td>0.04 s</td>
<td>8 functions, 0 parse errors</td>
</tr>
</tbody>
</table>
</section>
<section id="patch-data-transformation-1m-rows" class="level4">
<h4 class="anchored" data-anchor-id="patch-data-transformation-1m-rows"><code>patch</code> – Data Transformation (1M rows)</h4>
<table class="table">
<colgroup>
<col style="width: 20%">
<col style="width: 31%">
<col style="width: 20%">
<col style="width: 27%">
</colgroup>
<thead>
<tr class="header">
<th>Test</th>
<th>Dataset</th>
<th>Time</th>
<th>Result</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Rename column</td>
<td><code>million.csv</code> (52 MB)</td>
<td>8.15 s</td>
<td>“patched 1000000 rows”</td>
</tr>
<tr class="even">
<td>Drop column</td>
<td><code>million.csv</code></td>
<td>7.03 s</td>
<td>Column removed</td>
</tr>
<tr class="odd">
<td>NaN fill</td>
<td><code>million_messy.csv</code> (36 MB)</td>
<td>5.53 s</td>
<td>All NaN/empty replaced</td>
</tr>
<tr class="even">
<td>Mean imputation</td>
<td><code>million_messy.csv</code></td>
<td>6.53 s</td>
<td>Correct mean computed</td>
</tr>
<tr class="odd">
<td>Replace values</td>
<td><code>million_messy.csv</code></td>
<td>5.52 s</td>
<td>Exact-match replacement</td>
</tr>
<tr class="even">
<td>Rename + drop (10 cols)</td>
<td><code>million_wide.csv</code> (108 MB)</td>
<td>13.61 s</td>
<td>Both transforms applied</td>
</tr>
<tr class="odd">
<td>TSV rename</td>
<td><code>million.tsv</code> (38 MB)</td>
<td>6.17 s</td>
<td>Tab delimiter preserved</td>
</tr>
</tbody>
</table>
<p><code>patch</code> is I/O-bound at this scale – it writes the entire transformed dataset to stdout. All other commands read and aggregate without full re-serialization.</p>
</section>
<section id="drift-cell-by-cell-comparison-1m-rows" class="level4">
<h4 class="anchored" data-anchor-id="drift-cell-by-cell-comparison-1m-rows"><code>drift</code> – Cell-by-Cell Comparison (1M rows)</h4>
<table class="table">
<colgroup>
<col style="width: 20%">
<col style="width: 31%">
<col style="width: 20%">
<col style="width: 27%">
</colgroup>
<thead>
<tr class="header">
<th>Test</th>
<th>Dataset</th>
<th>Time</th>
<th>Result</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>~1,001 injected diffs</td>
<td><code>drift_a.csv</code> vs <code>drift_b.csv</code> (86 MB)</td>
<td>1.34 s</td>
<td>Found 1,001 cell differences</td>
</tr>
<tr class="even">
<td>Identical files</td>
<td><code>drift_a.csv</code> vs itself</td>
<td>1.40 s</td>
<td>“identical”, Frobenius = 0</td>
</tr>
<tr class="odd">
<td>With tolerance 1.0</td>
<td>drift pair</td>
<td>1.33 s</td>
<td>Dropped to 936 diffs</td>
</tr>
<tr class="even">
<td>Max diffs 5</td>
<td>drift pair</td>
<td>1.03 s</td>
<td>Only 5 diffs shown</td>
</tr>
<tr class="odd">
<td>JSON output</td>
<td>drift pair</td>
<td>1.44 s</td>
<td>Valid JSON</td>
</tr>
<tr class="even">
<td>12-digit precision</td>
<td>drift pair</td>
<td>1.06 s</td>
<td>max_deviation = 958.501978518200</td>
</tr>
</tbody>
</table>
</section>
<section id="seek-file-discovery-full-project" class="level4">
<h4 class="anchored" data-anchor-id="seek-file-discovery-full-project"><code>seek</code> – File Discovery (Full Project)</h4>
<table class="table">
<colgroup>
<col style="width: 22%">
<col style="width: 25%">
<col style="width: 22%">
<col style="width: 29%">
</colgroup>
<thead>
<tr class="header">
<th>Test</th>
<th>Scope</th>
<th>Time</th>
<th>Result</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>All .rs files</td>
<td>96K files in project</td>
<td>10.74 s</td>
<td>905 Rust files</td>
</tr>
<tr class="even">
<td>All .csv files</td>
<td>full project</td>
<td>9.77 s</td>
<td>3 CSV files</td>
</tr>
<tr class="odd">
<td>Content search (<code>fn</code> in .cjcl)</td>
<td>full project</td>
<td>7.78 s</td>
<td>67 matching files</td>
</tr>
<tr class="even">
<td>Files &gt; 100 KB</td>
<td>full project</td>
<td>5.78 s</td>
<td>Lists git objects + binaries</td>
</tr>
<tr class="odd">
<td>Everything (no filter)</td>
<td>full project</td>
<td>5.68 s</td>
<td>95,948 files total</td>
</tr>
<tr class="even">
<td>Scoped search (<code>pub fn</code> in crates/)</td>
<td>crates/ only</td>
<td>1.77 s</td>
<td>164 files</td>
</tr>
</tbody>
</table>
<p><code>seek</code> traverses the full file tree including <code>target/</code> and <code>.git/</code>. Scoped searches run in under 2 s.</p>
</section>
<section id="proof-determinism-verification-heavy-compute" class="level4">
<h4 class="anchored" data-anchor-id="proof-determinism-verification-heavy-compute"><code>proof</code> – Determinism Verification (Heavy Compute)</h4>
<table class="table">
<colgroup>
<col style="width: 20%">
<col style="width: 31%">
<col style="width: 20%">
<col style="width: 27%">
</colgroup>
<thead>
<tr class="header">
<th>Test</th>
<th>Program</th>
<th>Time</th>
<th>Result</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>3 runs, default seed</td>
<td><code>stress_compute.cjcl</code></td>
<td>0.49 s</td>
<td>PASS – stdout/exit/GC identical</td>
</tr>
<tr class="even">
<td>10 runs</td>
<td><code>stress_compute.cjcl</code></td>
<td>1.27 s</td>
<td>PASS</td>
</tr>
<tr class="odd">
<td>5 seeds x 3 runs</td>
<td><code>stress_compute.cjcl</code></td>
<td>1.62 s</td>
<td>PASS across all seeds</td>
</tr>
<tr class="even">
<td>10 runs, verbose</td>
<td><code>stress_compute.cjcl</code></td>
<td>1.05 s</td>
<td>All runs: 5 lines, gc=0</td>
</tr>
</tbody>
</table>
<p>Computational correctness verified:</p>
<ul>
<li><code>fib(20)</code> = 6,765 (known correct)</li>
<li><code>sum(0..99999)</code> = 4,999,950,000 (= 99999 x 100000 / 2)</li>
<li><code>collatz_steps(837799)</code> = 524 (known Project Euler value)</li>
<li><code>count_primes(1000)</code> = 168 (known: 168 primes below 1,000)</li>
<li><code>sum_gcd_pairs(100)</code> = 13,015</li>
</ul>
</section>
<section id="bench-performance-benchmarking-heavy-compute" class="level4">
<h4 class="anchored" data-anchor-id="bench-performance-benchmarking-heavy-compute"><code>bench</code> – Performance Benchmarking (Heavy Compute)</h4>
<table class="table">
<thead>
<tr class="header">
<th>Executor</th>
<th>Mean</th>
<th>Median</th>
<th>CV</th>
<th>Stability</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Default (eval)</td>
<td>108 ms</td>
<td>107 ms</td>
<td>6.1%</td>
<td>MODERATE</td>
</tr>
<tr class="even">
<td><code>--mir</code></td>
<td>97 ms</td>
<td>98 ms</td>
<td>3.4%</td>
<td>STABLE</td>
</tr>
<tr class="odd">
<td><code>--eval</code></td>
<td>107 ms</td>
<td>104 ms</td>
<td>10.0%</td>
<td>MODERATE</td>
</tr>
</tbody>
</table>
<p>MIR executor is ~10% faster with lower variance on this workload.</p>
</section>
<section id="forge-content-addressable-caching" class="level4">
<h4 class="anchored" data-anchor-id="forge-content-addressable-caching"><code>forge</code> – Content-Addressable Caching</h4>
<table class="table">
<colgroup>
<col style="width: 42%">
<col style="width: 57%">
</colgroup>
<thead>
<tr class="header">
<th>Test</th>
<th>Result</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>forge run</code></td>
<td>SHA-256 = <code>0f8544b7dd489d76...234ebb636</code>, 29 output bytes</td>
</tr>
<tr class="even">
<td><code>forge verify</code> (correct hash)</td>
<td>MATCH</td>
</tr>
<tr class="odd">
<td>Hash determinism (two clean runs)</td>
<td>IDENTICAL SHA-256</td>
</tr>
</tbody>
</table>
</section>
<section id="trace-mem-doctor-analysis-tools" class="level4">
<h4 class="anchored" data-anchor-id="trace-mem-doctor-analysis-tools"><code>trace</code>, <code>mem</code>, <code>doctor</code> – Analysis Tools</h4>
<table class="table">
<colgroup>
<col style="width: 32%">
<col style="width: 21%">
<col style="width: 46%">
</colgroup>
<thead>
<tr class="header">
<th>Command</th>
<th>Time</th>
<th>Key Metrics</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>trace</code></td>
<td>0.30 s</td>
<td>Parse: 0.3 ms, Exec: 232 ms, 8 functions, 0 GC</td>
</tr>
<tr class="even">
<td><code>mem</code> (5 runs)</td>
<td>0.69 s</td>
<td>GC avg = 0, stable = true, avg exec = 130 ms</td>
</tr>
<tr class="odd">
<td><code>doctor</code> (full project, 106 CJC files)</td>
<td>0.20 s</td>
<td>1 error (intentional), 83 type warnings</td>
</tr>
</tbody>
</table>
</section>
</section>
<section id="audit-summary" class="level3">
<h3 class="anchored" data-anchor-id="audit-summary">Audit Summary</h3>
<table class="table">
<thead>
<tr class="header">
<th>Command</th>
<th>Tests Run</th>
<th>Passed</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>cjcl proof</code></td>
<td>8</td>
<td>8/8</td>
<td>Multi-seed verdict cosmetic issue</td>
</tr>
<tr class="even">
<td><code>cjcl flow</code></td>
<td>10</td>
<td>10/10</td>
<td>–</td>
</tr>
<tr class="odd">
<td><code>cjcl patch</code></td>
<td>11</td>
<td>9/11</td>
<td><code>--json</code>/<code>--plain</code> flags not yet wired</td>
</tr>
<tr class="even">
<td><code>cjcl seek</code></td>
<td>10</td>
<td>10/10</td>
<td>–</td>
</tr>
<tr class="odd">
<td><code>cjcl drift</code></td>
<td>8</td>
<td>8/8</td>
<td>Tolerance listing cosmetic issue</td>
</tr>
<tr class="even">
<td><code>cjcl forge</code></td>
<td>8</td>
<td>8/8</td>
<td>–</td>
</tr>
<tr class="odd">
<td><code>cjcl inspect</code></td>
<td>11</td>
<td>11/11</td>
<td>–</td>
</tr>
<tr class="even">
<td><code>cjcl schema</code></td>
<td>9</td>
<td>9/9</td>
<td>–</td>
</tr>
<tr class="odd">
<td><code>cjcl trace</code></td>
<td>9</td>
<td>9/9</td>
<td>–</td>
</tr>
<tr class="even">
<td><code>cjcl mem</code></td>
<td>10</td>
<td>10/10</td>
<td>–</td>
</tr>
<tr class="odd">
<td><code>cjcl bench</code></td>
<td>9</td>
<td>9/9</td>
<td>–</td>
</tr>
<tr class="even">
<td><code>cjcl pack</code></td>
<td>5</td>
<td>5/5</td>
<td>–</td>
</tr>
<tr class="odd">
<td><code>cjcl doctor</code></td>
<td>8</td>
<td>8/8</td>
<td>–</td>
</tr>
<tr class="even">
<td><strong>Total</strong></td>
<td><strong>116</strong></td>
<td><strong>114/116</strong></td>
<td><strong>2 cosmetic issues</strong></td>
</tr>
</tbody>
</table>
</section>
<section id="numerical-accuracy-guarantees" class="level3">
<h3 class="anchored" data-anchor-id="numerical-accuracy-guarantees">Numerical Accuracy Guarantees</h3>
<p>The following properties were verified at 1,000,000-row scale:</p>
<ul>
<li><strong>Kahan summation</strong> is active for all <code>flow</code> aggregations (label confirmed in output)</li>
<li><strong>Sum, mean, variance, std</strong> all match expected values for known distributions</li>
<li><strong>Frobenius norm</strong> accurate to 12 decimal places in drift comparisons</li>
<li><strong>Null/NaN detection</strong> correct for empty cells, <code>NaN</code> strings, and missing values</li>
<li><strong>Sample variance</strong> uses N-1 denominator consistently</li>
<li><strong>SHA-256 hashes</strong> are deterministic across forge runs with cache cleared between invocations</li>
<li><strong>3 consecutive identical runs</strong> of <code>flow</code> and <code>schema</code> on 1M rows produce byte-for-byte identical JSON</li>
</ul>
<hr>
</section>
</section>
<section id="part-10-design-decisions-and-trade-offs" class="level2">
<h2 class="anchored" data-anchor-id="part-10-design-decisions-and-trade-offs">Part 10 — Design Decisions and Trade-offs</h2>
<section id="why-no-hashmap" class="level3">
<h3 class="anchored" data-anchor-id="why-no-hashmap">Why No HashMap?</h3>
<p><code>HashMap</code> uses random iteration order in Rust (and most languages). This means any code that iterates over a hash map produces non-deterministic results. CJC-Lang uses <code>BTreeMap</code> and <code>BTreeSet</code> exclusively, trading some average-case performance for guaranteed deterministic iteration.</p>
</section>
<section id="why-no-fma" class="level3">
<h3 class="anchored" data-anchor-id="why-no-fma">Why No FMA?</h3>
<p>Fused multiply-add (FMA) computes <code>a * b + c</code> in a single instruction with one rounding step instead of two. This is faster and more accurate — but it produces different results depending on whether the hardware has FMA support. CJC-Lang uses <code>mul_fixed</code> (separate multiply and add) to guarantee identical results across all platforms.</p>
</section>
<section id="why-two-execution-engines" class="level3">
<h3 class="anchored" data-anchor-id="why-two-execution-engines">Why Two Execution Engines?</h3>
<p>The AST interpreter (<code>cjc-eval</code>) is simpler and easier to debug. The MIR executor (<code>cjc-mir-exec</code>) is faster and supports optimization. Having both means:</p>
<ul>
<li>New features can be prototyped in the interpreter first</li>
<li>Parity tests catch subtle lowering bugs</li>
<li>Users can choose the right trade-off for their workload</li>
</ul>
</section>
<section id="why-vec-based-pauli-terms" class="level3">
<h3 class="anchored" data-anchor-id="why-vec-based-pauli-terms">Why Vec-Based Pauli Terms?</h3>
<p>Quantum Hamiltonians are sums of Pauli terms. A natural representation is a hash map from Pauli string to coefficient. CJC-Lang uses <code>Vec&lt;PauliTerm&gt;</code> instead, because:</p>
<ul>
<li>Iteration order is deterministic</li>
<li>No hashing overhead for small Hamiltonians</li>
<li>Summation order is fixed, so Kahan compensation works reliably</li>
</ul>
</section>
<section id="why-sign-stabilized-svd" class="level3">
<h3 class="anchored" data-anchor-id="why-sign-stabilized-svd">Why Sign-Stabilized SVD?</h3>
<p>SVD is ambiguous in the signs of singular vectors. Two runs of the same SVD can produce vectors that differ by a sign flip. CJC-Lang’s MPS canonical form uses a sign convention (largest-magnitude element is positive) to eliminate this ambiguity. This is critical for deterministic tensor network simulations.</p>
<hr>
</section>
</section>
<section id="whats-next" class="level2">
<h2 class="anchored" data-anchor-id="whats-next">What’s Next</h2>
<p>CJC-Lang v0.1.1 lays the groundwork for quantum-classical hybrid algorithms, noise-aware quantum simulation, and tensor network methods. Upcoming work includes:</p>
<ul>
<li><strong>Multi-file module system</strong> — programs that span multiple files with deterministic resolution</li>
<li><strong>Default parameters and variadic functions</strong> — <code>fn solve(x: f64, tol: f64 = 1e-6)</code></li>
<li><strong>Decorator syntax</strong> — <code>@log @timed fn train(x: Tensor)</code></li>
<li><strong>Sparse eigensolvers</strong> — Lanczos and Arnoldi with deterministic iteration</li>
<li><strong>MIR autodiff integration</strong> — gradients flowing through the MIR optimization pipeline</li>
</ul>
<p>The language is growing, but the invariants remain fixed: no hidden allocations, no non-deterministic operations, no silent precision loss. Same seed, same bits, always.</p>
<hr>
<div class="callout callout-style-simple callout-note no-icon">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-body-container">
<p><strong>CJC-Lang is open source and under active development.</strong> The codebase spans 21 Rust crates with zero external runtime dependencies. If deterministic numerical computing interests you, check out the <a href="../../../blog/posts/introducing-cjc/index.html">introductory post</a> or the <a href="../../../blog/posts/cjc-v0.1-released/index.html">v0.1.0 release notes</a>.</p>
</div>
</div>
</div>


</section>

 ]]></description>
  <category>CJC-Lang</category>
  <category>syntax</category>
  <category>compilers</category>
  <category>scientific-computing</category>
  <category>quantum</category>
  <guid>https://adamezzat1.github.io/blog/posts/cjc-v0.1.1-syntax-guide/index.html</guid>
  <pubDate>Thu, 02 Apr 2026 07:00:00 GMT</pubDate>
</item>
</channel>
</rss>
