ABNG — An Experimental Adaptive Belief Network Graph for CJC-Lang

Bayesian uncertainty, cryptographic dataset attestation, and adaptive structural triggers — proven by demos, honestly bounded by what’s still unfinished.

CJC-Lang
ABNG
Bayesian
determinism
attestation
experimental
ABNG (Adaptive Belief Network Graph) is an experimental cjc-abng crate exposing 75 abng_* builtins to CJC-Lang. This post walks through eleven distinct capabilities — eleven demos, six in CJC-Lang source — and what each one actually proves. Plus the honest gaps: ABNG is still experimental, the interpreter is a known bottleneck, and there’s compiler/interpreter work ahead before this becomes production-grade.
Author

Adam Ezzat

Published

May 8, 2026

Modified

May 8, 2026

🕑 ~32 min read
75abng_* builtins
11capabilities demoed
960tests passing
v12snapshot magic
TL;DR

ABNG is an experimental addition to CJC-Lang shipping in the cjc-abng crate (Phase 0.1 → 0.5). It exposes 75 abng_* builtins for building adaptive belief network graphs — 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 not proven yet — and why interpreter / compiler work is on the critical path before ABNG becomes production-grade.

ABNG is experimental

ABNG is a research-grade addition to CJC-Lang. The capabilities described here are demonstrated by tests, but not yet productized: 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.

On data scale: these demos prove “the capability works”, not “it works at scale”

Every demo in this post uses small datasets — between 3 and 200 training samples per scenario. 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.

What this means concretely:

  • The capabilities are demonstrated at the scale tested. Where I claim the BLR posterior converges to the analytical solution, I trained on tens of samples, not thousands.
  • Performance at production scale (n > 10⁴) is not yet benchmarked. The asymptotic claims (e.g., per-leaf BLR is O(N · d²) vs classical GP’s O(N³)) are correct by construction — the math says so — but no demo has wall-clock measurements at large N.
  • The demos are reproducibility gates, not load tests. They lock determinism canaries; they don’t prove ABNG handles realistic ML-training dataset sizes.
  • Each demo’s data scale is named explicitly in its “Why this workload” subsection below — read those if you want to know exactly what level of evidence backs each claim.

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 “proven at small scale, theoretically expected to hold at larger scale, empirically unverified at production scale.”

What is ABNG?

ABNG — Adaptive Belief Network Graph — is a cjc-abng 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:

  1. Bayesian uncertainty at every prediction (mean, epistemic leverage, aleatoric variance) — without MCMC, ensembles, or variational inference.
  2. Cryptographic provenance of training history — every observation, every weight update, every structural decision lands in a SHA-256 audit chain that detects post-hoc tampering.
  3. Adaptive structure — the graph’s topology genuinely changes under the workload. Nodes grow, split, merge, prune, compress, freeze under a deterministic decision-policy engine.

The architecture is a radix-style graph: a root node descends into children indexed by prefix bytes from a quantile codebook, and each leaf carries a Bayesian Linear Regression posterior over a feature basis. Predictions descend the tree, the posterior at the resolved leaf gives (mean, epistemic_leverage, aleatoric_var), and structural decisions fire periodically to adapt the tree.

flowchart LR
    F["State<br/>features"] --> CB["Codebook<br/>encode"]
    CB --> P["Prefix<br/>bytes"]
    P --> D["Descend<br/>tree"]
    D --> L["Leaf<br/>BLR"]
    L --> Pred["(mean,<br/>leverage,<br/>aleatoric)"]
    L -.->|every step| AC["Audit chain<br/>SHA-256"]

ABNG conceptual flow. State features → codebook prefix → descend tree → BLR posterior at leaf → (mean, leverage, aleatoric) — plus an audit chain that hashes every step.

It’s exposed to CJC-Lang as 75 abng_* builtins — abng_new, abng_set_codebook, abng_set_leaf_head, abng_set_blr_prior, abng_observe, abng_blr_predict, abng_descend, abng_decide_step, abng_stamp_provenance, etc. — so you can build and drive an ABNG graph entirely from .cjcl source.

The proof discipline

Every capability in this post has the same proof structure:

  1. A workload — a small but non-trivial problem where the capability should manifest if it’s working.
  2. A specific assertion that would fail if the capability didn’t work.
  3. A locked SHA-256 canary of the resulting graph’s chain_head, gating any future regression.
  4. Cross-pipeline determinism: most demos run through both cjc-eval (AST tree-walk) AND cjc-mir-exec (MIR register-machine) with byte-identical printed output asserted.

That last property is the strongest 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.

OK — let’s walk through what’s actually proven.


Capability 1 — Per-state Bayesian uncertainty

Demo: PINN heat-equation solver (tests/test_abng_pinn_uncertainty_cjcl.rs + pinn_source.rs)

What this demo proves

The Bayesian Linear Regression posterior at each codebook-routed leaf returns a 3-tuple (mean, epistemic_leverage, aleatoric_var). The headline assertion is a strict spatial-partition property:

min(epistemic_leverage at edge probes) > max(epistemic_leverage at interior probes)

This is stronger than “average edge uncertainty exceeds average interior uncertainty” — it’s a no-overlap 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.

The fit-quality assertion (abs_err_50 < 0.05) proves the mean also converges to the analytical solution. Without this, “leverage is uncertain at edges” would be vacuous — the mean could just be wrong everywhere.

The math

ABNG’s BLR uses a Normal-Inverse-Gamma (NIG) conjugate prior on (w, σ²). Given a prior (μ₀, Λ₀, a₀, b₀) and a stream of observations D = {(φᵢ, yᵢ)}, the conjugate update yields a closed-form posterior:

\[ \begin{aligned} \Lambda_n &= \Lambda_0 + \Phi^\top \Phi \\ \mu_n &= \Lambda_n^{-1}\,(\Lambda_0 \mu_0 + \Phi^\top y) \\ a_n &= a_0 + n/2 \\ b_n &= b_0 + \tfrac{1}{2}(y - \Phi\mu_n)^\top(y - \Phi\mu_n) + \tfrac{1}{2}(\mu_0 - \mu_n)^\top \Lambda_0\,(\mu_0 - \mu_n) \end{aligned} \]

For a query point x*, the predictive distribution p(y* | x*, D) is a Student-t. ABNG returns its three sufficient ingredients as:

Output Formula Interpretation
mean \(\mu_n^\top \varphi(x^*)\) Posterior mean prediction (point estimate)
epistemic_leverage \(\varphi(x^*)^\top \Lambda_n^{-1}\,\varphi(x^*)\) Variance contribution from posterior over w (model uncertainty)
aleatoric_var \(b_n / (a_n - 1)\) Observation-noise variance (data uncertainty)

Two cleanly separated uncertainty channels. epistemic_leverage shrinks as ΦᵀΦ accumulates — more data → tighter posterior over w → less model uncertainty. aleatoric_var converges to the irreducible noise floor. A vanilla MLP collapses both into a single point estimate; ABNG keeps them factored.

Why the heat equation

The 1-D heat equation ∂u/∂t = α∂²u/∂x² with Dirichlet BCs u(0,t) = u(1,t) = 0 and IC u(x,0) = sin(πx) was chosen because it has a known closed-form analytical solution:

\[u(x, t) = e^{-\alpha \pi^2 t} \sin(\pi x)\]

That choice is load-bearing. The BLR feature basis is [1, x, sin(πx), cos(πx)] — the analytical solution exp(-π²·0.1)·sin(πx) is exactly representable as a single non-zero coefficient on the sin(πx) basis function. So with enough samples, the BLR posterior mean must 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.

The training schedule is deliberately asymmetric: 32 samples uniformly across [0.25, 0.75] (interior), 4 samples each in [0, 0.25) and [0.75, 1] (edges). Without this asymmetry, uniform training would give uniform leverage and the spatial-partition property would be invisible.

Data scale: 40 training samples total (32 interior + 8 edge), 5 probe points. Enough to demonstrate the spatial-partition property; not 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.

Code sketch

The PINN demo’s core loop, in CJC-Lang:

fn pinn_features_2d(x: f64) -> Tensor {
    Tensor.from_vec([1.0, x, sin(pi() * x), cos(pi() * x)], [1, 4])
}

fn analytical_u(x: f64) -> f64 {
    let decay = exp(-0.9869604401089358);  // exp(-π² · 0.1)
    decay * sin(pi() * x)
}

fn train_one(g: i64, x: f64) {
    let leaf = route_leaf(g, x);             // codebook descent
    let phi = pinn_features_2d(x);
    let y_val = analytical_u(x);             // ground truth
    let y = Tensor.from_vec([y_val], [1]);
    abng_blr_update(g, leaf, phi, y);        // NIG conjugate update
    abng_observe(g, leaf, y_val);
}

After training, a probe at x = 0.5 returns (mean ≈ 0.372, lev_low, ale_finite) — mean matches exp(-π²·0.1) · sin(π·0.5) = 0.372 to within 0.05. A probe at x = 0.05 returns (mean ≈ small, lev_high, ale_finite) — different leaf, less data, higher leverage.

Connection to existing techniques

Technique Posterior representation Cost Decomposed epistemic/aleatoric?
Gaussian Processes Full posterior over functions O(n³) Cholesky Yes (kernel + likelihood noise)
Deep ensembles Variance across M trained nets M× training cost Approximately
MC dropout Variational approx via dropout Cheap, biased No (entangled)
Conformal prediction Distribution-free intervals Cheap No
ABNG per-leaf BLR Closed-form NIG posterior O(d²) per update Yes (clean factor)

ABNG slots between GPs (full posterior, expensive) and conformal prediction (distribution-free, no decomposition). It’s most similar in spirit to Bayesian Additive Regression Trees (BART) — leaf-localized closed-form Bayesian fits — but with a quantile-codebook routing instead of stochastic tree growth, and a stronger determinism contract.

What this enables

  • Active learning in scientific computing — query the BLR for epistemic_leverage; sample where it’s highest.
  • Uncertainty-aware PINNs — 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.
  • Heteroskedastic regression done right — the aleatoric/epistemic split lets you tell “this region is intrinsically noisy” from “I haven’t seen enough of this region yet.”
  • Uncertainty propagation downstream — feed the Student-t predictive into Monte Carlo / robust optimization pipelines.

The PINN literature has long treated Bayesian uncertainty as expensive infrastructure (HMC, variational PINNs). ABNG provides this as a closed-form per-leaf posterior — no inference machinery beyond a Cholesky update.


Capability 2 — Per-leaf bounded scaling (GP-like, scalable)

Demo: Tabular regression on the unit square (tests/test_abng_tabular_gp_cjcl.rs + tabular_source.rs)

What this demo proves

Five distinct properties asserted as separate tests:

  1. max(per_leaf_n_seen) < 0.9 × N — no single leaf holds the entire dataset. Evidence is partitioned, not duplicated.
  2. sum(per_leaf_n_seen) == N — every training point routes to exactly one leaf. No double-counting; no points lost.
  3. max_per_leaf_bounded — the partitioning works as designed. (This is the scalability claim’s empirical foundation.)
  4. trained_mse < 0.5 × prior_mse — the BLR posterior actually learns. Without this, the structure-of-the-graph properties would be vacuous.
  5. lev_shrinks_with_data — leverage at a fixed probe drops from a 16-pt-trained leaf to a 64-pt-trained leaf. GP-like uncertainty contraction reproduced.

The math (cost analysis)

ABNG’s per-leaf cost decomposes cleanly:

Operation Cost
Codebook descent O(n_dims · log(n_bins))
BLR conjugate update at one leaf O(d² · n_leaf) (Cholesky-factor maintenance)
Predictive at query point O(d²) (one Cholesky solve)
Aggregate cost across N points, L leaves O(N · d²) — linear in N

Compare to a classical GP on the same N points:

Operation Cost
Kernel matrix K_NN O(N²) memory
Cholesky decomposition O(N³) compute
Predictive variance per query O(N²)

ABNG is asymptotically O(N · d²) vs GP’s O(N³). For d = 4 (the demo’s basis dim) and N = 10⁴: ABNG ≈ 1.6×10⁵ ops; GP ≈ 10¹² ops — seven orders of magnitude.

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.

Why bilinear regression on the unit square

The target is y = 0.3 + 0.5x₁ + 0.4x₂ + 0.7·x₁·x₂. Three deliberate choices:

  • Exactly fittable in the BLR feature basis [1, x₁, x₂, x₁·x₂]. With enough samples, BLR posterior mean → exact coefficients [0.3, 0.5, 0.4, 0.7]. This makes the MSE-reduction test meaningful: any residual MSE comes from sample efficiency, not from the model class being wrong.
  • 2-D input with a routing dimension. The codebook routes by x₁ (4 bins) and lets x₂ become a within-leaf feature. This mirrors the canonical ABNG pattern: use one dimension to partition the input space, regress on the rest within each leaf. Common in production: route by (time-of-day, geo-region) and regress on the rest within each segment.
  • Unit square domain — predictable, no scaling tricks. Codebook boundaries [0.25, 0.5, 0.75] evenly partition [0, 1].

Data scale: 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 correct by construction but not yet wall-clock-benchmarked at scales where it matters (n ≥ 10⁴). Without those numbers this is a “promising primitive,” not a measured speedup vs. e.g. SVGP / sklearn’s GaussianProcessRegressor / GPy.

Code sketch

fn build_tabular_graph(seed: i64) -> i64 {
    let g = abng_new(seed);
    let codebook = Tensor.from_vec([0.25, 0.5, 0.75], [1, 3]);
    abng_set_codebook(g, codebook);                          // 4 bins on x₁
    abng_set_leaf_head(g, 2, Tensor.from_vec([4.0], [1]), 1, "tanh");
    abng_set_blr_prior(g, 2.0, 1.0, 0.5);                    // NIG (a, b, σ_init)
    // ... add 4 children, one per codebook bin ...
    g
}

fn train_one(g: i64, x1: f64, x2: f64) {
    let leaf = route_for(g, x1);                             // x₁ → prefix → leaf
    let phi = Tensor.from_vec([1.0, x1, x2, x1 * x2], [1, 4]);
    let y_val = target(x1, x2);
    abng_blr_update(g, leaf, phi, Tensor.from_vec([y_val], [1]));
}

After 200 training points, every leaf has its own BLR posterior with mean → [0.3, 0.5, 0.4, 0.7] and increasingly sharp leverage. Sum of n_seen across all four leaves equals exactly 200; the largest leaf holds well under 90% of points.

Connection to existing techniques

Technique Routing Per-leaf model Scaling
Classical GP None (global) Kernel O(n³)
Sparse GP / SVGP Inducing points Kernel O(n·m²) for m inducing pts
Mondrian Forest Stochastic tree splits Point estimate O(n·log n)
BART Stochastic tree splits Bayesian posterior O(n·log n) per tree
Locally-weighted regression Distance-weighted Linear fit O(n) per query
ABNG Quantile codebook (deterministic) Closed-form NIG posterior O(N · d²)

The closest analog is Mondrian Forests (Lakshminarayanan, Roy & 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.

What this enables

  • Drop-in tabular Bayesian regression at GP-prohibitive sizes (n > 10⁴ where dense Cholesky becomes painful)
  • Online / streaming learning — incremental BLR update is O(d²) per point, vs O(n²) incremental for classical GP rank-one updates
  • Snapshot-checkpointable Bayesian state — 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
  • Feature-engineered uncertainty quantification — the practitioner picks routing dimensions (time-of-day, region, customer segment) and the per-segment posteriors are independently calibrated
Warning

The asymptotic claim is correct by construction (the math says so) but not yet benchmarked 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.”


Capability 3 — Tamper-evident training history (audit chain)

Demo: Universal property — every demo asserts it.

What every demo proves

Every state mutation in ABNG funnels through one chain step:

\[ \text{new\_hash} = \text{SHA-256}\bigl(\text{previous\_hash} \,\Vert\, \text{canonical\_payload}(\text{event})\bigr) \]

Every demo asserts verify_chain == Ok after training. For an n-event audit log, verify_chain walks the chain forward, recomputing new_hash at every step from the stored previous hash and the stored canonical payload, and compares to the stored value. Any single byte changed in any payload would fail the recomputation at that event AND every subsequent event — a Merkle-style cascade.

Across the 11 demos, this code path runs hundreds of times. The fact that verify_chain == Ok after every demo’s training session is the empirical proof the audit chain is sound under realistic workloads.

The threat model

The audit chain is designed against three concrete attacks:

  1. Post-hoc rewrite. Attacker modifies a historical training observation after the fact. Detection: the modified event’s new_hash no longer matches sha256(previous_hash ‖ payload), AND every subsequent event’s stored previous_hash no longer matches the recomputed-from-modified value. Cascading detection.
  2. Event reordering. Attacker shuffles the order of training events. Detection: each event’s payload includes its seq number; reordering puts events at the wrong seq, breaking the per-event monotonic-seq invariant.
  3. Event substitution. Attacker replaces one event with another that produces the same new_hash. Detection: SHA-256 collision resistance — finding two payloads with the same hash is computationally infeasible.

Data scale: 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.

Code sketch — what an event payload looks like

[seq u64 BE | epoch u64 BE | node_id u32 BE | kind tag u8 |
 kind body (variable) |
 stats_version u64 BE | stats_hash [u8; 32]]

For a BeliefUpdate event (the most common kind, fired on every abng_observe), the kind body is a single value f64-bits BE — 8 bytes. Total payload for a BeliefUpdate is 21 + 8 + 8 + 32 = 69 bytes, hashed into the chain.

The canonical encoding is byte-deterministic: 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.

Connection to existing techniques

Technique Hash-chain ML-aware Per-prediction lineage
Git Yes (DAG of commits) No No
MLflow No (timestamped log) Yes Manual
sigstore / in-toto Yes (artifact attestation) No Per-artifact
Blockchain ML provenance Yes (full chain on-chain) Yes Yes, but expensive
ABNG audit chain Yes (linear) Yes (every state mutation) Yes, baked in

The closest analog is in-toto attestations in software supply-chain land — except in-toto is per-artifact (each release gets one attestation), while ABNG is per-event (every observation, every weight update).

What this enables

  • Forensic post-incident analysis — “Was this model trained on the bad data?” Replay the chain, check.
  • Regulatory replay — the model’s full training history is one cryptographically-linked blob.
  • Reproducibility certification — papers ship (model.snap, dataset) pairs; reviewers reproduce + verify chain in one byte comparison.
  • ML supply chain attestation — fine-tuning extends the chain; provenance of every weight is traceable.
  • Insider-threat detection — anyone modifying historical events breaks every subsequent hash, visible to anyone running verify_chain.

This is closer to Git’s content-addressed model than to traditional ML metadata stores. MLflow / Weights & Biases give you external logs you have to trust; ABNG gives you a model artifact whose history is cryptographically self-attesting.


Capability 4 — Cryptographic dataset attestation

Demo: Lineage attestation with synthetic clinical-trial data (tests/test_abng_lineage_attestation_cjcl.rs + lineage_source.rs)

What this demo proves

The headline test (lineage_attestation_three_signals_independent) proves that tampering one row of a 64-row dataset (changing patient #17’s response from 0.525 to 0.999) produces three independent SHA-256 divergences:

  1. chain_head divergence — every audit event’s hash cascades from row 17 onward.
  2. BLR state_hash divergence — the leaf where the tampered row routes has a different posterior.
  3. provenance_stamp divergence — the dataset fingerprint differs by construction.

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.

The threat model and why three signals matter

The classic attack against ML provenance is collusion: the attacker controls the training process, so they can produce a consistent-but-fake audit trail. Three independent signals raise the cost:

Attack Defeats which signal? Defeats all three?
Modify training data, recompute chain chain_head ✓ No — BLR state diverges
Modify training data, retrain to match BLR state chain_head ✓, BLR state ✓ No — provenance stamp diverges
Modify training data, retrain, restamp with new fingerprint All three forgeable internally But: fingerprint is publicly verifiable against the dataset bytes

The provenance stamp is sha256(canonical_dataset_bytes). An auditor with the original dataset can recompute this themselves — 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 external binding to the dataset.

Why a synthetic clinical-trial workload

  • Maps directly to a real industry threat model. “Lab A trains a model claiming approved dataset; attacker substitutes contaminated dataset” is the FDA model-attestation problem in one sentence.
  • 64-row size is realistic for Phase II clinical-trial cohorts (often <200 patients per arm).
  • Cubic dose-response curve y = 0.2 + 0.6·dose + 0.1·dose² is biologically plausible AND exactly fittable in the BLR basis [1, dose, dose², dose³], giving us ground-truth assertions in addition to the security claims.
  • Tamper at row 17 specifically because dose=0.5 falls right at a codebook bin boundary — routing the tampered observation to a specific leaf, making the BLR state_hash divergence clearly attributable.

Data scale: 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 minimum-effort 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 not yet validated end-to-end with a real anonymized clinical-trial corpus.

Code sketch

fn train_and_stamp(seed: i64, rows: Any, fingerprint: String) -> i64 {
    let g = build_lineage_graph(seed);
    abng_stamp_provenance(g, 0, fingerprint);                // sha256(dataset_bytes)
    let n = len(rows);
    let i = 0;
    while i < n {
        let row = rows[i];
        let dose = row[0];
        let resp = row[1];
        let leaf = route_leaf(g, dose);
        let phi = Tensor.from_vec(
            [1.0, dose, dose * dose, dose * dose * dose], [1, 4]);
        abng_blr_update(g, leaf, phi, Tensor.from_vec([resp], [1]));
        abng_observe(g, leaf, resp);
        i = i + 1;
    }
    g
}

The two graphs g_a and g_b are built identically — only the row data and fingerprint differ. Comparing (chain_head, blr_state_hash[node 3], provenance_stamp) between them yields three different hashes.

Connection to existing techniques

Technique Cryptographic ML-integrated Per-prediction
MLflow / W&B logs No Yes No
Sigstore / in-toto Yes No (artifact-only) No
Datasheet for Datasets (Gebru et al. 2018) No (process-only) Yes No
Model Cards (Mitchell et al. 2019) No Yes No
MLOps governance frameworks Variable Yes Manual
ABNG provenance chain Yes Yes Yes (baked into predict_snap)

ABNG’s contribution is the combination: cryptographic, ML-integrated, AND per-prediction. None of the existing tools do all three in one artifact.

What this enables

Use case Concrete flow
FDA model attestation (pharma) Lab runs trial → trains ABNG → stamps with sha256(approved_dataset). Regulator: cjcl abng explain --model X.snap pred.snap checks all three signals in one CLI invocation
Financial model audit (SEC, Basel III) “Prove this risk model used only data through model-validation cutoff” — chain head + dataset fingerprint answer in one byte comparison
HIPAA-compliant healthcare ML Patient-consent boundaries enforced cryptographically: stamp = sha256(consent_set_v2_canonicalized)
GDPR Article 22 Right to explanation = “show me the audit trail of training data that produced this decision about me”
Academic reproducibility paper.pdf + model.snap + dataset.csv — anyone with all three can verify byte-for-byte
Insider-threat in ML supply chain Attacker who fine-tunes a base model on contaminated data CAN’T hide the audit-chain extension

The closest non-ABNG analog is in-toto-attested ML pipelines (an active research area, see Torres-Arias et al. 2019). The difference: ABNG bakes attestation inside the model artifact, not as a sidecar.


Capability 5 — OOD detection composite

Demo: Asymmetric codebook with deliberately-missing children (tests/test_abng_ood_detection_cjcl.rs + ood_source.rs)

What this demo proves

The headline assertion is three-tier separation:

max(ood_score in dense bins)  <  ood_score in sparse bin  <  ood_score in routing-fall-off bin

Three evidence regimes are cleanly partitioned. The composite is a function of three independent inputs.

The math

ABNG’s ood_score composites three signals:

\[\text{ood}(x) = f\bigl(\text{density}(x),\ \text{epistemic\_z}(x),\ \text{prefix\_distance}(x)\bigr)\]

where:

Signal Formula What it detects
density(x) \(1 - \exp(-d^2)\) where \(d^2 = (x - \bar{\mu})^\top \text{diag}(\Sigma)^{-1}(x - \bar{\mu})\) Distance from training cluster mean (per-leaf)
epistemic_z(x) \(\min(\varphi^\top \Lambda_n^{-1}\varphi / \mathbb{E}[\text{lev}], 1)\) Calibrated BLR uncertainty (per-leaf)
prefix_distance(x) \((\text{prefix\_max} - \text{matched\_prefix}) / \text{prefix\_max}\) Routing-failure rate (per-graph)

The composite combines these into a single bounded [0, 1] score. Each signal catches a different OOD mode:

  • Density catches inputs that fall in a known leaf but far from its training cluster.
  • Epistemic catches inputs where the BLR posterior says “I’m uncertain here.”
  • Prefix distance catches inputs that don’t fit the codebook structure at all — i.e. the descent fell off the tree because no child exists at that prefix byte.

Why deliberately omit one child

A symmetric setup (children in all 4 bins, varying training density) would only exercise the density and epistemic signals — not prefix_distance. By deliberately not adding a child for bin 3, queries in bin 3 descend with matched_prefix=0, and the prefix-distance contribution to the composite fires.

The demo asserts the composite 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.

Data scale: 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 fundamentally outside the codebook-routing paradigm — ABNG’s OOD score requires structured input where the codebook can produce a meaningful prefix encoding.

Code sketch

fn probe_ood(g: i64, x: f64) -> f64 {
    let x_t = Tensor.from_vec([x], [1]);
    let prefix = abng_encode_prefix(g, x_t);
    let evidence = abng_descend(g, prefix);
    let matched = int(evidence.get([0]));      // matched_prefix
    let leaf = int(evidence.get([1]));         // resolved leaf id
    let phi = features_1d(x);
    abng_ood_score(g, leaf, phi, matched, 1)   // composite [0, 1]
}

// Build phase: add children for bins 0, 1, 2 — NOT bin 3.
abng_add_node(g, 0, 0);   // bin 0 child
abng_add_node(g, 0, 1);   // bin 1 child
abng_add_node(g, 0, 2);   // bin 2 child
// (no add_node for byte 3 — bin 3 queries descend with matched_prefix = 0)

// Train density on bins 0 and 1 heavily, bin 2 lightly:
train_leaf_density(g, 1, 0.125, 32);   // bin 0: dense
train_leaf_density(g, 2, 0.375, 32);   // bin 1: dense
train_leaf_density(g, 3, 0.625, 4);    // bin 2: sparse
// bin 3: no training (no leaf)

After training, probing at x = 0.10 (bin 0, dense) gives a low ood; x = 0.40 (bin 1, dense) low; x = 0.62 (bin 2, sparse) medium; x = 0.90 (bin 3, no leaf) highest.

Connection to existing techniques

Technique OOD signal Bayesian-aware?
Mahalanobis distance OOD (Lee et al. 2018) Distance from class means No
Energy-based OOD (Liu et al. 2020) -logsumexp(logits) No
Deep ensemble disagreement Variance across M models Approximately
ODIN (Liang et al. 2018) Temperature + input perturbation No
Conformal prediction p-values Distribution-free coverage No
ABNG composite density + epistemic + routing Yes (BLR posterior is principled)

The closest is Mahalanobis OOD — 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).

What this enables

  • Self-driving / robotics — abstain on inputs unfamiliar to the training distribution; hand off to safe-mode.
  • Medical diagnosis — don’t predict on patient profiles outside the training population.
  • Fraud detection — novel attack patterns trigger high OOD scores.
  • Active learning — high-OOD inputs are highest-value labeling candidates.
  • Safety-critical ML — defense-in-depth against distribution shift at inference time, not just monitoring.

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.


Capability 6 — Adaptive structural triggers

Demo: Two-sibling shared-signature topology (tests/test_abng_adaptive_triggers_cjcl.rs + adaptive_source.rs)

What this demo proves

After running abng_decide_step three times on a graph with two sibling children:

  1. merge_count == 1 — exactly one Merge action fires (deterministic given the canary thresholds).
  2. audit_log_grew — the structural mutation is recorded as an event in the chain.
  3. verify_chain == Ok — the chain integrity holds after structural mutation.
  4. chain_head matches the Rust-side decide_step_canary BYTE-FOR-BYTE — this is the most striking property in the entire demo set.

That last point deserves emphasis. The Rust-side decide_step_canary_tests.rs locks chain_head = d064fb08…7807. The CJC-Lang demo, running through both AST eval AND MIR exec, produces the same hex. 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.

The math behind the Merge trigger

decide_step 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 (i, j):

  1. Signature similarity: Hamming distance of the 8-byte truncated SHA-256 signatures < tau_merge. The signature is computed from a 4-channel Welford profile (prediction, uncertainty, calibration, routing). Similar profiles → similar signatures → low Hamming distance.

  2. Posterior similarity (KL divergence): \[ \mathrm{KL}\bigl(\mathcal{N}(\mu_i, \Sigma_i)\,\|\,\mathcal{N}(\mu_j, \Sigma_j)\bigr) < \text{kl\_merge} \] where (μᵢ, Σᵢ) are the BLR posterior parameters at node i. This is the standard multivariate Gaussian KL.

Both conditions must hold for Merge to fire. The Merge action then folds j’s observations into i via Chan/Golub/LeVeque parallel Welford merge — preserving the post-merge stats as if they’d been observed in series.

Why the two-sibling shared-signature topology

  • Mirrors the Rust-side decide_step_canary exactly — 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 direct cross-pipeline determinism gate.
  • Merge is the most reliably-triggered action. With identical training distributions on the two siblings, their signatures align trivially (Hamming distance = 0). Decision-policy thresholds tau_merge=0 and kl_merge=0.1 are satisfied immediately — KL between fresh-prior posteriors is exactly zero.
  • Small graph keeps the test fast — 4 nodes, 3 observations, 3 decide_step calls. Runs in milliseconds even through the AST interpreter.

Data scale: 3 observations + 3 decide_step calls, on a 4-node graph. This is deliberately the smallest demo in the entire suite — 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 not a demonstration that ABNG-driven structural adaptation is competitive against e.g. 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. baseline tree learners.

Code sketch

fn main() {
    let g = build_adaptive_graph(42);

    abng_observe(g, 0, 0.10);
    abng_observe(g, 0, 0.25);
    abng_observe(g, 0, 0.40);

    let i = 0;
    while i < 3 {
        let counts = abng_decide_step(g);    // [grow, split, merge, prune, compress, freeze]
        i = i + 1;
    }

    let merge_count = abng_action_count(g, 2);  // → 1
    print("merge_count: " + to_string(merge_count));
    print("chain_head: " + abng_chain_head(g));  // → d064fb08…7807
}

Connection to existing techniques

Technique Adaptive structure? Triggers Domain
Mondrian Forests (Lakshminarayanan 2014) Yes (stochastic splits) Online Tabular
Bayesian Additive Regression Trees (Chipman 2010) Yes (stochastic) Batch Tabular
Hoeffding Trees / VFDT (Domingos 2000) Yes (statistical) Streaming Tabular
Concept-drift adaptive learners (ADWIN, Page-Hinkley) Yes (drift-triggered) Streaming Various
Neural Architecture Search (Zoph 2017) Yes (optimization-based) Offline Deep learning
ABNG decide_step Yes (deterministic, audit-tracked) Six action types Bayesian belief graphs

The closest analog is Hoeffding Trees — 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 prove what structural changes happened and when.

What this enables

  • Self-organizing ML models — graph capacity scales with workload complexity; no manual architecture design.
  • Streaming / online learning — non-stationary distributions cause Grow on new modes, Prune on dead branches.
  • Memory-efficient ML — Compress unused subtrees; Merge redundant leaves; Prune inactive nodes.
  • Hierarchical model distillation — stable subtrees can be Frozen and exported as compiled MLPs.
  • Continual learning without catastrophic forgetting — Freeze stable-signature subtrees while training others.
  • AutoML topology search — decision-policy thresholds are hyperparameters; the trigger ladder is the search algorithm.
Warning

Honest caveat: the demo proves Merge 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.


Capability 7 — Calibration / ECE

Demo: Hand-constructed well-calibrated vs miscalibrated outcomes (tests/test_abng_calibration_cjcl.rs + calibration_source.rs)

What this demo proves

Two graphs receive 30 calibration observations each. Same predicted probabilities (p ∈ {0.1, 0.5, 0.9}, 10 each), different outcome distributions:

Graph Outcome convention Expected ECE
A: well-calibrated predicted=0.1 → 1 of 10 positive; predicted=0.9 → 9 of 10 positive < 0.05
B: miscalibrated predicted=0.1 → 9 of 10 positive (FLIPPED); predicted=0.9 → 1 of 10 positive > 0.5

The headline assertion ece_a < ece_b separates the two cleanly. Plus the specific bounds: ece_a < 0.05 (well-calibrated achieves near-zero ECE) and ece_b > 0.5 (flipped outcomes produce huge ECE).

The math

ABNG uses standard 15-bin reliability-binning ECE (Naeini, Cooper, Hauskrecht 2015):

\[ \text{ECE} = \sum_{b=1}^{15} \frac{|B_b|}{N} \,\bigl|\,\text{accuracy}(B_b) - \text{confidence}(B_b)\,\bigr| \]

where:

  • B_b is the set of predictions falling in bin b (15 equal-width bins on [0, 1])
  • accuracy(B_b) = fraction of correct predictions in bin b
  • confidence(B_b) = mean predicted probability in bin b
  • |B_b| / N = bin’s empirical weight

A perfectly calibrated model has accuracy(B_b) = confidence(B_b) for every populated bin → ECE = 0. The miscalibrated graph with flipped outcomes pushes accuracy ≈ 1 - confidence in each bin → ECE saturates near 0.8.

Why “flip outcomes” as the miscalibration model

  • Maximum-ECE case. Predicting 90% confidence and being right 10% of the time is the worst possible outcome — confidence is anti-correlated with truth. This makes the test gate ece_b > 0.5 reliable; small numerical jitter doesn’t push the result close to the threshold.
  • Same prediction distribution → isolates the calibration signal. Both graphs see the same (p, y) pairs modulo y. Any difference in ECE is attributable to outcomes, not to predictions or to bin populations.
  • Three reliability bins (0.1, 0.5, 0.9) span low/mid/high probability range without requiring 100+ observations to populate every bin.

Data scale: 30 calibration observations per graph (2 graphs total = 60 observations). This is the minimum sufficient sample size to make the three-bin reliability statistics non-trivially populated and the ECE separation ece_a < 0.05 < 0.5 < ece_b reliable. It does not 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.

Code sketch

fn well_calibrated_run(g: i64, node: i64) {
    // For predicted_prob = 0.1: 1 positive, 9 negative (10% base rate).
    let i = 0;
    while i < 10 {
        if i < 1 { abng_calibration_observe(g, node, 0.1, true); }
        if i >= 1 { abng_calibration_observe(g, node, 0.1, false); }
        i = i + 1;
    }
    // ... same pattern for p=0.5 (5/10) and p=0.9 (9/10) ...
}

fn miscalibrated_run(g: i64, node: i64) {
    // For predicted_prob = 0.1: 9 positive, 1 negative (FLIPPED).
    // For predicted_prob = 0.9: 1 positive, 9 negative (FLIPPED).
    // ... see source for full pattern ...
}

let ece_a = abng_calibration_ece(g_a, 0);     // < 0.05
let ece_b = abng_calibration_ece(g_b, 0);     // > 0.5

Connection to existing techniques

Technique Calibration Per-leaf?
Platt scaling (Platt 1999) Post-hoc logistic regression on logits No (global)
Isotonic regression (Zadrozny 2002) Monotonic piecewise-constant fit No (global)
Temperature scaling (Guo et al. 2017) Single-parameter softmax rescale No (global)
Beta calibration (Kull et al. 2017) Beta distribution fit No (global)
Histogram binning (Zadrozny 2001) Per-bin frequency estimate Possible per-leaf
ABNG calibration 15-bin reliability + ECE Yes (per-node)

ABNG’s calibration is per-node — 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.

What this enables

  • Risk-sensitive deployment — a poorly-calibrated 95%-confident prediction is operationally indistinguishable from an honest 60%-confident one — but you’ll act on it differently.
  • Threshold tuning — “should I act on predictions above 0.7?” depends on whether 0.7-bin predictions are actually correct 70% of the time.
  • Model selection in production — beyond accuracy/F1: ECE is the second-order quality metric.
  • Active learning targeting — high-ECE bins are evidence the model needs more training data in that probability range.
  • Per-segment calibration analysis — “is my model better calibrated for some user segments than others?” is answerable in one query.

Capability 8 — Distribution-drift detection

Demo: Mean shift from 0 to 0.8 on a 4-D density tracker (tests/test_abng_drift_detection_cjcl.rs + drift_source.rs)

What this demo proves

A three-phase workload: train baseline, freeze, observe under same distribution, observe under shifted distribution. Three assertions:

  1. drift_grew — drift_score(post-shift) > drift_score(same-dist).
  2. meaningful_shift — drift_score(post-shift) > 2 × drift_score(same-dist). Catches a regression where drift detection becomes noisy.
  3. drift_nonneg — both scores are non-negative (no NaN, no negative noise).

The math

ABNG’s drift detection is built on a streaming diagonal-Gaussian density tracker. For features φ ∈ ℝᵈ, the tracker maintains:

  • Running mean: μ̂ updated via Welford
  • Running diagonal covariance: Σ̂_diag updated via Welford on (φᵢ - μ̂)²

freeze_drift_baseline(node) snapshots (μ̂_baseline, Σ̂_diag_baseline) at a reference point (typically end of pre-deployment training). After freezing, drift_score measures the divergence between the current tracker and the frozen baseline:

\[ \text{drift\_score} = \bigl\|(\hat{\mu}_{\text{cur}} - \hat{\mu}_{\text{baseline}})\bigr\|^2_{\text{Mahal}} + \alpha \cdot \bigl\|\Sigma_{\text{cur,diag}} / \Sigma_{\text{baseline,diag}} - \mathbf{1}\bigr\|^2 \]

— 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.

Why a pure mean shift

  • Translation, not rescaling 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.
  • 0 to 0.8 is large enough to be unambiguous. Both distributions have ~0.01 jitter; shifting the center by 0.8 is 80x the jitter — the drift signal saturates well above the floor.
  • Same density-tracker geometry — same dimension count (4-D features), same covariance scale, same batch sizes (16 each post-baseline). Only the location changes.

Data scale: 32 baseline samples + 16 same-distribution + 16 shifted = 64 density observations total, on a single node. This proves the drift_score machinery responds correctly to a controlled mean shift; it does not 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.

Code sketch

let g = build_drift_graph(7);

// Phase 0: train baseline density on N(0, 0.01).
let baseline_batch = density_batch(0.0, 32);
abng_density_observe(g, 0, baseline_batch);

abng_freeze_drift_baseline(g, 0);     // snapshot reference state

// Phase A: same distribution.
let same_batch = density_batch(0.0, 16);
abng_density_observe(g, 0, same_batch);
let drift_low = abng_drift_score(g, 0);

// Phase B: shifted distribution N(0.8, 0.01).
let shifted_batch = density_batch(0.8, 16);
abng_density_observe(g, 0, shifted_batch);
let drift_high = abng_drift_score(g, 0);

// drift_high > 2 * drift_low (the demo's headline assertion)

Connection to existing techniques

Technique Statistic Streaming? Reference required
Kolmogorov-Smirnov test Max CDF difference No Yes
Population Stability Index (PSI) Discrete distribution distance No (batch) Yes
Maximum Mean Discrepancy (Gretton 2012) Kernel-based difference Possible Yes
Page-Hinkley test Change-point detection Yes Implicit
ADWIN (Bifet 2007) Adaptive windowing Yes None (auto-detect)
DDM (Gama 2004) Drift Detection Method (error rate) Yes None
ABNG drift_score Mahalanobis-style on density tracker Streaming Frozen baseline

The closest analog is PSI (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.

What this enables

  • Production ML monitoring — alert ops when input distribution drifts from training; trigger retraining.
  • Continual learning auto-trigger — drift signal is the cue to incorporate new data into the model.
  • A/B test data validation — confirm the new dataset’s distribution matches the original before deploying a new model.
  • Sensor / pipeline integrity — detect upstream data-source switches, sensor drift, schema changes.
  • Customer-behavior modeling — recommender systems trained on Q1 data drifting against Q3 user behavior.

Capability 9 — Log compaction

Demo: Multi-node observation stream + compact_log (tests/test_abng_compact_log_cjcl.rs + compact_source.rs)

What this demo proves

Five training observations across three nodes, then compact_log fires:

  1. emitted == 3 — exactly one StatsSnapshot per touched node.
  2. audit_grew_by_emitted — audit_post == audit_pre + emitted.
  3. chain_advanced — chain_head changed (events were properly chain-linked).
  4. verify_chain == Ok post-compact — the new events don’t break integrity.

The math (and what’s deferred)

compact_log(g, until_seq) walks events in [0, until_seq), tracks the set of distinct node IDs touched, and for each emits one StatsSnapshot event whose payload is (node_id, sha256(canonical_bytes(node.stats))).

This sets up the read-side optimization that ships in a future phase: when smart_replay encounters a StatsSnapshot, it can:

  1. Trust the snapshot’s stats_hash matches the node’s canonical_bytes (consistency check, shipped today).
  2. Skip the node.observe(value) mutation for subsequent BeliefUpdate events in the same node’s pre-snapshot range, since the snapshot subsumes their cumulative effect.
  3. Install the stats directly from the per-node section’s canonical_bytes instead of replaying from observations.

The shipped layer is the marker emission + chain-integrity preservation + smart_replay consistency check. The cycle-saving fast-forward is Phase 0.6 work.

Code sketch

abng_observe(g, 0, 0.1);
abng_observe(g, 0, 0.2);
abng_observe(g, 1, 0.5);
abng_observe(g, 1, 0.6);
abng_observe(g, 2, 0.9);

let audit_pre = abng_audit_len(g);             // e.g. 8
let emitted = abng_compact_log(g, audit_pre);  // → 3 (3 touched nodes)
let audit_post = abng_audit_len(g);            // 8 + 3 = 11
print("verify_chain: " + to_string(abng_verify_chain(g)));  // → true

Data scale: 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 deferred to Phase 0.6 along with the actual cycle-saving optimization.

Connection to existing techniques

Technique Mechanism Domain
Database WAL compaction Replace old WAL records with snapshot RDBMS
LSM tree compaction Merge sorted runs KV stores (RocksDB, Cassandra)
Chandy-Lamport snapshotting Distributed-system state capture Distributed systems
Checkpoint-restart Periodic full-state save HPC, ML training
ABNG compact_log + StatsSnapshot Event-log markers, hash-chain-integrated ML training audit logs

The closest analog is WAL log compaction 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.

What this enables

  • Long-running training without log explosion — periodic compaction preserves checkpoints while bounding audit-log growth.
  • Snapshot-based debugging — each StatsSnapshot is a “save point” for forensic replay.
  • Foundation for the Phase 0.6 fast-forward optimization — smart-replay can fast-forward past observation runs by trusting the StatsSnapshot.
  • Distributed training checkpointing — workers compact-log periodically; central server replays the compact form.
Warning

The cycle-saving optimization is deferred to Phase 0.6. Today the marker layer ships with chain-integrity preservation; the actual fast-forward that would make this user-visibly faster lands later.


Capability 10 — Maturity observability

Demo: Stable observation stream + decide_step (tests/test_abng_maturity_inspection_cjcl.rs + maturity_source.rs)

What this demo proves

Read abng_node_maturity at two timepoints: fresh graph (T=0) and after 10 stable observations + 5 decide_step calls (T=1). Five assertions:

  1. sum_at_t0 == 0.0 — fresh graph, all maturity flags are zero.
  2. maturity_evolved — sum at T=1 strictly exceeds sum at T=0.
  3. calib_binary AND unc_binary — positions [1] (calibration_stable) and [2] (uncertainty_stable) are strict 0/1 flags.
  4. samples_seen_nonneg — position [0] (samples_seen) is non-negative.
  5. trust_in_range — position [3] (trust_level) is in [0, 1].

The semantics

abng_node_maturity(g, node) returns a 4-element f64 tensor:

Index Field Type Definition
0 samples_seen non-negative count Total observations applied to this node
1 calibration_stable binary 0/1 True if 3-window ECE history’s \|Δ\| < ece_stability_max_delta
2 uncertainty_stable binary 0/1 True if 3-window σ history shows ratio < sigma_stability_ratio
3 trust_level continuous [0, 1] Composite trust score from signature stability + maturity flags

The flags advance through decide_step calls plus the underlying signature_stable_calls counter that increments on consecutive matching signatures.

Why a stable observation stream

The maturity flags advance under two conditions:

  • signature_stable_calls increments when consecutive decide_step calls observe an unchanged signature
  • samples_seen simply counts observations

By choosing identical observations (abng_observe(g, n, 0.1) 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.

Realistic data (varying observations) would also work but with lower predictability — the signature might wobble enough that signature_stable_calls resets occasionally. The stable-stream choice makes the demo deterministic.

Data scale: 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.

Code sketch

let g = build_maturity_graph(42);

let mat0 = abng_node_maturity(g, 0);  // [0, 0, 0, 0] — fresh

let i = 0;
while i < 10 {
    abng_observe(g, 0, 0.1);
    abng_observe(g, 1, 0.1);
    i = i + 1;
}
let j = 0;
while j < 5 {
    abng_decide_step(g);
    j = j + 1;
}

let mat1 = abng_node_maturity(g, 0);
let r0 = mat1.get([0]);  // samples_seen (count > 0)
let r1 = mat1.get([1]);  // calibration_stable (0 or 1)
let r2 = mat1.get([2]);  // uncertainty_stable (0 or 1)
let r3 = mat1.get([3]);  // trust_level (continuous [0, 1])

Connection to existing techniques

Technique Convergence diagnostic Domain
Gelman-Rubin R̂ Cross-chain variance ratio MCMC convergence
Effective Sample Size (ESS) Autocorrelation-based MCMC convergence
Validation-loss plateau detection Loss derivative threshold Deep learning training
EarlyStopping callback Patience + monitor metric sklearn/keras training
ML monitoring stacks (Arize, WhyLabs) External time-series dashboards Production ML
ABNG node_maturity Per-node 4-tuple Bayesian belief graphs

The closest analog in ML practice is EarlyStopping + 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).

What this enables

  • Production ML monitoring dashboard — “has my model converged?” → check maturity per-node.
  • Auto-stop training — stop when maturity flags have all stabilized (avoid wasted compute).
  • Hyperparameter tuning — which learning rate makes flags settle fastest?
  • Convergence-failure debugging — which flag never reaches 1? — points at the failing component.
  • Production rollout gating — deploy-time check: only ship models where calibration_stable == 1.
  • Federated learning aggregation — aggregate worker updates only from nodes where maturity has stabilized.

Capability 11 — End-to-end determinism

Demo: Universal property — every CJC-Lang demo asserts it.

What every demo proves

Every CJC-Lang demo runs through both cjc-eval (AST tree-walk) AND cjc-mir-exec (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 abng_* builtins.

The strongest single instance: the adaptive triggers demo’s chain_head matches the Rust-side decide_step_canary byte-for-byte (d064fb08…7807). Same workload, three pipelines (direct Rust API + AST eval + MIR exec), bit-identical SHA-256 over 23+ audit events.

The architectural choices that make this work

Three load-bearing decisions enforced across the workspace:

  1. Kahan compensated summation for all FP reductions (KahanAccumulatorF64). Naive Σxᵢ accumulates rounding error in O(n·ε); Kahan reduces this to O(ε). More importantly, Kahan is order-deterministic for fixed input order — no SIMD-introduced reordering, no FMA fusion. This is the single biggest source of “approximately deterministic” failure in standard ML stacks.

  2. BTreeMap/BTreeSet only, never HashMap. 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.

  3. SplitMix64 with explicit seed threading. No thread-local randomness, no time-based seeds, no platform-specific entropy. Every random draw is reproducible from (initial_seed, operation_history).

These three choices aren’t aspirational — they’re enforced by the parity gates. If anyone introduces FMA in cjc-runtime/linalg.rs tomorrow, every parity test fails simultaneously with byte-divergent output.

What this means for ML practitioners

ML reproducibility is famously hard. A typical “deterministic” PyTorch model can produce different bytes across runs due to:

  • CUDA atomic ops (non-deterministic reduction order on GPU)
  • cuDNN algorithm selection (can pick different kernels per run)
  • Multi-threaded CPU reductions (thread-scheduling non-determinism)
  • FMA fusion (compiler-dependent instruction selection)
  • Hash-randomization in Python’s dict

Each of these has been a real production-incident root cause. ABNG’s parity gates prove the absence of these bugs every test run.

Connection to the reproducibility crisis literature

Source Claim
Pineau et al. 2021, “Improving Reproducibility in ML Research” Most ML papers can’t be reproduced even with the same code
Hutson 2018, “Artificial intelligence faces reproducibility crisis” Structural problem in the field
TensorFlow Determinism Project (tf.config.experimental.enable_op_determinism) Best-effort, with known gaps
PyTorch determinism guide Documents environmental flags; doesn’t guarantee bit-equality
ML reproducibility checklists (NeurIPS 2019+) Process-level requirements
ABNG parity gates Cryptographic byte-equality across pipelines

ABNG’s contribution to this conversation: byte-equality is achievable if you commit to determinism-by-construction at every layer (no FMA, no random hash, no thread-local RNG). The parity gates prove it run by run.

What this enables

  • Reproducibility — same seed → same model bytes; papers ship verifiable results.
  • Cross-platform deployment — train on Linux, deploy on Windows → byte-identical model (modulo cross-platform CI verification, which is the next piece of work).
  • Distributed training — deterministic operations enable provable consistency without per-step coordination.
  • Regulatory replay — regulator can re-run training and verify every byte.
  • CI / regression detection — byte-equal output makes test-failure debugging trivial — diff lines, not “it’s flaky.”
  • Differential testing across executor versions — new optimizer? Old vs new must produce same bytes for all valid programs.
Warning

Cross-platform determinism is not yet verified. All canary hashes were computed on Windows. They should 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.


Phase 0.5 in numbers

The current ABNG state is the result of Phase 0.5, which shipped:

Item What it added
v12 wire-format bump Per-node provenance_stamp_hash field + new 0x1C ProvenanceStamped audit kind; NodeStats::canonical_bytes extended 24B → 32B to capture the Kahan compensation register
smart_replay API Opt-in tamper detection via StatsSnapshot consistency check (cycle-saving fast-forward layer deferred to Phase 0.6)
TOML --config files for cjcl abng train Hand-rolled minimal TOML parser (zero external deps), full schema for graph / codebook / leaf_head / blr_prior / density / calibration / decision_policy / training / output sections
Chess RL v2.6 retrofit scaffold Sibling test file demonstrating the API pattern; locked v2.6 weight-hash canary
6 capability demos OOD detection, adaptive triggers, calibration, drift, compact_log, maturity — all in CJC-Lang source with AST↔︎MIR parity gates
3 application demos PINN, tabular GP-like, lineage attestation — both Rust-direct AND CJC-Lang versions

Net impact: 906 → 960 tests, +54 in this final round, 0 failures across the whole suite.

What’s next — and why interpreter / compiler work matters

ABNG is genuinely useful for a specific niche: regulated ML training where you need Bayesian uncertainty + cryptographically-verifiable lineage on tabular data. The demos prove the niche capabilities; they don’t prove ABNG should replace PyTorch.

Three things stand between current ABNG and production readiness:

Cross-platform determinism CI

The locked canary hashes are Windows-only today. They should 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.

Performance benchmarks at scale

This is the biggest single gap between the current state and production readiness, and it deserves emphasis: none of the demos in this post test ABNG at production-scale data. 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.

What’s specifically not yet validated:

Scale claim Status
“ABNG O(N · d²) beats classical GP O(N³)” Correct by construction, never wall-clock-benchmarked vs SVGP / sklearn / GPy at n ∈ {10³, 10⁴, 10⁵, 10⁶}
“Per-leaf BLR posterior converges to truth on real PDE problems” Demonstrated on 1-D heat eq with 40 samples; never on 2-D Burgers / Navier-Stokes at 10⁴ collocation points
“Lineage attestation works on real anonymized clinical-trial data” Demonstrated on 16-row synthetic; never on a real Phase II/III trial corpus
“Drift detection catches realistic concept drift” Demonstrated on a controlled mean shift; never on production streaming data
“OOD score generalizes to high-D embeddings” Demonstrated on 4-D codebook-routed inputs; fundamentally outside scope for unstructured high-D inputs
“Per-leaf calibration scales to real classifiers” Demonstrated on 30 synthetic (p, y) pairs; never on real classifier outputs at 10³+ predictions

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 theoretically expected to hold at larger scale.

Compiler / interpreter performance

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.

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:

  • MIR optimizer passes — current optimizer has CF + DCE; further passes (loop-invariant code motion, common subexpression elimination, function inlining) are weeks of work each.
  • Hot-path specialization in cjc-runtime — 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.
  • AOT compilation — cjcl compile foo.cjcl → foo.exe. LLVM or Cranelift backend. Months of work but the only path that closes the perf gap meaningfully.

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 research vehicle or production language.

Note

ABNG lives at crates/cjc-abng/ in the CJC-Lang repository. The 13 demo files are under tests/test_abng_*.rs and tests/abng_demos/. The full test count is 960; the source is open.