DEV Community

Cover image for I built the flight data recorder for AI agents.
Sam LABBE
Sam LABBE

Posted on Edited on

I built the flight data recorder for AI agents.

Or: why I spent my evenings making AI-agent journals impossible to rewrite.


One Tuesday, at 2:37 PM

A disgruntled employee opens the database behind an AI product and
edits one row. The minutes an agent produced yesterday — the ones a client
is about to receive — now say something else. No alert. No trace. The
dashboard shows a pristine history, because the history has been
rewritten
.

This isn't a thriller plot. It is the default state of every AI-agent
product shipping today: their logs live in mutable databases, and nothing
exists to prove, after the fact, what the agent actually decided
.

I looked for the tool that closes this gap. It didn't exist. So I built it.
It's called NoireBox, it's open source (MIT), and this post is about
what it does, how it does it, and why I believe it's a missing piece.

→ https://noirebox.github.io/noirebox

(Written three weeks ago; updated October 8 — the numbers below are
regenerated, not remembered.)

The founding question

After fifteen years of backend engineering — insurance, renewable energy,
real estate — in systems where every decision must be defensible years
later, I asked the question that started everything:

If someone contests a decision made by an AI agent tomorrow, who can
prove it?

Not "who can tell the story". Not "where is it logged". Who can prove it
— to a client, a lawyer, an auditor, a regulator — without trusting the
vendor, the host, or the administrator?

The answer, everywhere, was: nobody. Observability tooling (Langfuse,
LangSmith, Helicone…) is great for debugging what happened — but its
logs stay mutable, and nothing is exportable as evidence. Debugging and
proving are different jobs.

The principle: a proof, not a promise

NoireBox is a tamper-evident journal for AI agents — a flight data
recorder, like the ones in aircraft. Every event (model call, output,
decision, incident) goes through four mechanisms:

1. Hash chaining. Each event's SHA-256 fingerprint includes the previous
event's. Modify, insert, or delete anything and the chain breaks — and the
verifier tells you which event was tampered with. A numbered notebook:
you can't tear out a page without anyone noticing.

2. Signatures. Every fingerprint is signed with Ed25519, using a key
that never leaves the server (chmod 600) — or one injected by your secret
manager. Regenerating the whole chain without the key? The forgery is
visible on sight.

3. The outside witness. The detail that kills tampering at the source:
NoireBox has an external timestamp authority (RFC 3161 — the same
protocol notaries and audit firms use) sign the chain head's fingerprint at
time T. Rewriting history afterwards becomes arithmetically impossible:
the old token no longer covers the new head. A journal that timestamps
itself proves nothing — that's the suspect writing its own police report.

4. The third-party-verifiable export. One file, plus a standalone
verifier that anyone runs on their own machine, offline, with no
credentials:

$ python verifier/verifier.py export.json
[✓] INTACT — 7375 events verified, attestation valid, 4 anchor tokens (2 against pinned roots, 0 reported-not-verified), head of chain: 6097eca4b7690d90…
Enter fullscreen mode Exit fullscreen mode

Output from the journal running on my machine as I edit these lines —
not an illustration.

And on that Tuesday at 2:37 PM, the same verifier returns a different
verdict:

[3] An attacker rewrites event 2: the minutes now read "REFUSE".
[✗] DETECTED — event 2: invalid hash (content was modified)
Enter fullscreen mode Exit fullscreen mode

(Real output from the repo's tamper demo — not an illustration.)

NoireBox never asks you to trust it. It hands you the dossier, and anyone
recomputes the truth themselves.

What about a whole fleet? One seal, a thousand boxes

Each box seals its own chain head individually. But what if you run 1,200
agents? Aggregated anchoring: the chain heads of N boxes form a Merkle
tree
whose root receives a single TSA seal. One seal for the whole
fleet — and each box proves it took part with ~log₂(N) hashes (11 for
1,200), verified offline.

That's the Certificate Transparency model — the system that made HTTPS
certificates auditable worldwide — applied to AI-agent decisions. To my
knowledge, nobody does this.

$ make demo-fleet
[2] Merkle tree: 3 leaves, root 7f2cf1a1e7b9879d…
    cr-reunion         proof: 2 hashes → ✓ covered
    support-juridique  proof: 2 hashes → ✓ covered
    scoring-credit     proof: 2 hashes → ✓ covered
[5] support-juridique regenerates its journal: content rewritten, re-chained cleanly.
[✗] DETECTED — the new head is not covered by the fleet seal.
    old head covered: yes; new one: NO
Enter fullscreen mode Exit fullscreen mode

(The root is different on every run — it commits to the exact state of
every chain at sealing time. That's the point.)

The most important part: the hub doing the aggregation cannot cheat. It can
omit a box (a detectable silence), but it can neither rewrite a sealed
batch nor include a fake head — and it journals the full tree into its
own
NoireBox journal. Every member verifies locally, without
contacting anyone. The arithmetic decides.

The guardrail is a plugin (keep yours)

Already running Lakera, Llama Guard, your own LLM-judge, or your own
regexes? Keep them. Prevention is a fungible layer — everyone has their
own, and it will keep changing. Proof is the universal layer.

Journal your solution's verdicts with a single POST /api/v1/events
(type: "incident") and its catches become tamper-evident and
third-party-verifiable, instead of ending up in rewritable app logs. The
bundled detector (regex + micro-model, French, English and Spanish, ~250–290
KB per language) is a working example of the plugin contract, not an
obligation.

The numbers — all reproducible with one command

Metric Value Reproduce with
Sealing one event (hash + signature + commit) 0.17 ms make bench
Full verification of 100 events ~44 ms make bench
Bundled detectors, FR + EN + ES 293 + 243 + 287 KB make train + make train-en + make train-es
Held-out attack sentences, never seen in training 18/18 across FR, EN, ES the three metrics*.json files
Test suite 244 green make test

Performance is boring by design, as it should be: sealing an event
(hash + signature + commit) costs a fraction of a millisecond, and
verifying a 100-event export takes about a tenth of a second on a laptop.
The bottleneck is SHA-256 and Ed25519 — not clever code.

I imposed one rule on myself: no number that can't be reproduced with one
command.
A metric we can't reproduce is unknown — not a rounder number.
That's also why this post contains no "78M+ events", no customer logos, no
testimonials.

What three weeks of public review added

This post got taken apart in public, and the attacks became the roadmap.
The consolidated answers are six new ADRs, credited to the people who
found the gaps: a receipt schema v0.2.1 where a verdict binds its
check, its evaluator (touch the test suite and the receipts die), the
spec that defines its semantics, the questions asked — anchored outside
the agent's reach — and the state it was judged against (the TOCTOU
field: the executor applies the sealed version as a precondition, a 409
on state move, the divergence sealed — never silent reuse); the
denominator
— a hash chain never catches the entry never written, so
attempts are sealed before outcomes, the invariant is outcomes ≤
attempts
, and an outbound expectation must be anchored or co-signed
before "pending" means anything; flow independence as a testable
invariant
— can one compromise reach both the audit flow and the truth
flow?; custody vs anchoring — when a counterparty exists, their
record beats your timestamp; sealing the belief — the resolved view
captured before the action; and cross-chain reconciliation (ADR 024)
— when one agent consumes another's output, the consumption itself is a
sealable event, the contract is a partial order (edge admissibility, not
a global timeline), and the blast radius of a superseded head is a
precise path set. A journal server that anchors itself on a wall-clock
timer, journals that several processes write and anyone verifies, and a
checker whose own reports are sealed — clean passes included, negative
controls that must bite. The attackers are named in the ADRs, because
credit is the cheapest honesty there is.

What it is not

  • Not a blockchain. One issuer, one verifier. Blockchains solve a problem we don't have — consensus among strangers — at a complexity price we refuse to pay.
  • Not a certification. A building block that feeds your audits; the exact perimeter is written down in the threat model.
  • Not an LLM. A ~250 KB specialist detector that sorts sentences into 5 classes, deterministic and testable — not a black box predicting the next token.

Why now

The EU AI Act (article 12)
requires automatic event logging for high-risk systems. GDPR (art. 5(2),
30) puts the burden of proof on whoever processes the data. ISO 42001
asks for traceability of AI decisions.

European vendors will have to prove — not promise — what their agents
did. Today, almost none of them can. NoireBox is a sovereign building block:
self-hosted, zero telemetry, keys stay yours. Built in the Vosges mountains
🇫🇷 — yes, proof infrastructure can be built from the mountains.

Try it in three commands — or one with Docker 🐳

$ git clone https://github.com/noirebox/noirebox && cd noirebox
$ ./start.sh          # 244 green tests + API on :8768 — /docs is live
$ make demo           # the model → the journal → the auditor → the attacker
Enter fullscreen mode Exit fullscreen mode

Or from PyPI — the ML engine ships inside the wheel:

$ pip install noirebox
$ noirebox serve      # API + OpenAPI docs on :8768/docs
Enter fullscreen mode Exit fullscreen mode

Or with Docker — no Python needed:

docker run -p 8768:8768 ghcr.io/noirebox/noirebox:latest
Enter fullscreen mode Exit fullscreen mode

API + OpenAPI docs on :8768/docs — data persists in ./data. The image is
public on GHCR: pull it anonymously, run it, verify it.

FastAPI, 11 documented routes plus Prometheus metrics, a Python SDK, an MCP
server (4 tools to plug your agents in), a bilingual DPO-ready PDF export, a
VS Code extension (Marketplace and Open VSX), and a self-hosted TSA
(make tsa, OpenSSL, $0, offline) — because a witness you don't control is
a witness less.

What's next

Shipped since this post: the receipt schema v0.2.1 with state binding
(ADR 019), the denominator and its outbound half (ADR 020), flow
independence as a bench (ADR 021), the custody-vs-anchoring taxonomy
(ADR 022), belief-before-action sealing (ADR 023), cross-chain
reconciliation (ADR 024), the two-key pairing enforced, the auto-anchor
timer, multi-writer journals, Spanish detection, and the editor
extension (VS Marketplace and Open VSX). Zero open issues. Next: the
cross-chain edges wired into the reconciler, the independence bench
against outside review, HSM-backed signing. The core is MIT, forever —
verification included.

If this resonates, here's what would help the most:

  • ⭐ A star on the repo — it sounds silly, but it's what makes a project exist
  • 🕵️ Try to break the proof. This is a project whose product is resistance to tampering: the best compliment is an issue describing an attack
  • 🔀 Fork it, PR it, question it — the code is deliberately small (one journal, one plugin, one verifier) so it can be read end to end

And if you know a DPO, a CISO, or an AI vendor preparing for article 12,
send them this post — that's exactly who this black box is for.


NoireBox — every AI decision, sealed forever.

Built solo, in the Vosges mountains, between two missions. Code, threat
model and specs:

github.com/noirebox/noirebox

Top comments (19)

Collapse
 
sinarezaei profile image
Sina Rezaei •

The fleet-level Merkle root raises an interesting distributed-systems question for me: what exactly is the consistency boundary of a fleet seal?

If 1,200 agents are sealing independently, the Merkle root proves which chain heads were included, but those heads may represent different logical times. Imagine a credit decision pipeline where a risk agent seals at T1, a fraud agent at T2, and an underwriting agent at T3. A single fleet root can prove inclusion of all three heads, but it doesn't necessarily prove that the three agents were evaluated against the same world-state.

I'd probably want a fleet seal to carry something like an epoch or monotonic watermark alongside the root:

fleet_epoch
agent_id
chain_head
logical_clock / source_watermark
root_hash
tsa_token
Enter fullscreen mode Exit fullscreen mode

Then the verifier could distinguish "these agents were included in the same audit batch" from "these agents happened to be anchored together."

This becomes especially important when one agent consumes another agent's output. If fraud-agent@epoch=41 is used by underwriting-agent@epoch=39, the individual chains can both be perfectly intact while the cross-agent dependency is temporally inconsistent.

The Merkle tree solves efficient inclusion proofs really nicely. The next interesting layer, to me, is proving the consistency of the snapshot that the tree represents, not just the integrity of its leaves.

Collapse
 
slabb profile image
Sam LABBE • • Edited

You've named the next layer exactly — and the honest contract first: the fleet root is Certificate-Transparency-style inclusion, deliberately. It proves these heads existed at seal time and weren't swapped; it never claimed they saw the same world.

Snapshot consistency is a different proof, and it lives one layer below the root. The instrument isn't an epoch field — a self-stamped watermark is a self-report, and an allocator-issued one re-opens the per-call latency tax from the reid thread. It's the consumption edge: when one agent consumes another's output, that consumption is itself a sealable event carrying the producer's chain head as observed — the dependency enters both journals.

Then your fraud / underwriting case becomes checkable without trusting anyone's clock: the reconciliation layer verifies the consumed head is an ancestor of fraud's currently anchored head, and that underwriting's anchor interval sits after the consumed event — temporal position is derived from the external anchors, a relation between two sealed chains, not a field anyone asserts. An inverted dependency surfaces as a cross-chain finding, same species as an orphan outcome.

The root answers "what existed"; cross-chain reconciliation answers "what depended on what" — and a dependency that inverts time is just another hole in the shape.

Collapse
 
sinarezaei profile image
Sina Rezaei •

Yeah, that makes sense. I like deriving the ordering from the dependency edge rather than introducing another piece of state that agents have to assert.

The case that gets interesting for me is fan-in. An agent rarely consumes exactly one upstream result. Imagine an underwriting agent consuming outputs from risk, fraud, KYC, and a pricing agent. The useful invariant isn't really “underwriting happened after fraud,” but that every consumed head has a valid ancestry and anchor relation before the underwriting decision that depended on it.

You could end up with something like:

risk ────────┐
fraud ───────┤
KYC ─────────┼──> underwriting
pricing ─────┘
Enter fullscreen mode Exit fullscreen mode

Now suppose the underwriting journal contains four consumption edges, but one of the consumed heads later turns out to be an orphaned or superseded branch. The underwriting chain itself can still be perfectly intact. What matters is whether the dependency graph can be reconciled back through each producer's sealed history.

That also makes me wonder whether the reconciliation layer is effectively building a partial order rather than a global timeline. You don't need to answer “what happened first everywhere”; you only need to prove that every dependency edge points from an admissible predecessor to a consumer.

That feels like a much more scalable contract for a fleet than trying to manufacture a single notion of time across all the agents.

Thread Thread
 
slabb profile image
Sam LABBE •

The partial-order framing is exactly the right contract — a fleet that had to agree on a global timeline would be re-deriving consensus just to keep an audit trail, and admissibility per edge is the whole invariant.

Your fan-in orphan case is where it pays off: when a consumed head turns out superseded or orphaned, the finding propagates forward along the dependency graph — every downstream journal that recorded an edge to the dead head inherits the finding, edge by edge, so the blast radius reads as a path set instead of "everything downstream, maybe."

Underwriting's chain staying intact is the point, not a gap: it proves what underwriting believed and when; the producer's supersede event proves the belief went stale; the reconciliation layer correlates the two into a finding with a precise descendant set. Same principle as everywhere else in this design — the graph doesn't manufacture time or truth, it makes every relation checkable after the fact, and every failed check names exactly what it invalidates.

Thread Thread
 
slabb profile image
Sam LABBE •

@sinarezaei — closing the loop: the exchange where you took the receipt apart became ADR 024 — cross-chain reconciliation, your name in its lineage, and as of today the findings ship as code: github.com/noirebox/noirebox/blob/...

What went in, from your side of the conversation:

  • Consumption edges are sealable events — the producer's chain head as observed enters BOTH journals; a half missing on either side is the finding (unrecorded_edge — silence is the starting fact).
  • The contract is a partial order, not a global timeline — edge admissibility, not "what happened first everywhere." And no epoch field anywhere: temporal position is a relation between two sealed chains, derived from anchors. Your move off the epoch is recorded as the rejected alternative, with the reason attached (a self-stamped watermark is a self-report; an allocator-issued one re-opens the per-call latency tax).
  • temporal_inversion — fraud@41 consumed by underwriting@39 — is a finding, same species as orphan_outcome, across chains. A head merely not yet covered by an anchor is underivable, not stale: the queue is the only open window.
  • Fan-in: a supersede event kills the head, and the finding walks the recorded edges — the blast radius is a precise path set (fraud#1 → underwriting#1 → underwriting#2 → claims#1), not "everything downstream, maybe."

And your TOCTOU hole from the same conversation is the receipt's state_binding (v0.2.1): the executor applies the sealed version as a precondition, 409 on state move, the divergence sealed as receipt_expired_by_state_change — never silent reuse of a receipt the world invalidated.

noirebox crosschain --journal fraud=data/fraud.db --journal underwriting=data/uw.db reconciles the graph; the report seals even when everything matched. Keep attacking — the next hole is the next field.

Collapse
 
slabb profile image
Sam LABBE •

This convergence is now written: ADR 024 — your fleet-epoch field is recorded as the REJECTED alternative ("a self-stamped watermark is a self-report"), your fan-in invariant is decision §5 (every consumed head needs a valid ancestry and anchor relation before the decision that depended on it), and the implementation issues are open. You asked the question, the ADR carries your name — come break the edges when they land in the reconciler.

Collapse
 
axiru profile image
Axiru •

Sealing the decision and not just the outcome is the part most logging skips. For a refund or payout tool there are two events to seal: the policy decision (allow, hold or deny, with the reason code and the policy version) and the provider response after the money moved.

How do you handle the case where they disagree? An allow that never reached Stripe because the process died, or a charge that went through with no sealed decision in front of it. Does NoireBox flag the missing half, or is reconciliation left to the caller?

Collapse
 
slabb profile image
Sam LABBE •

Shipped 🎉 — your invariant is noirebox reconcile v0.2 on main (issue #3, closed): decision/outcome pairing, deadlines, and the denominator invariant from last week's threads (outcomes ≤ attempts). Credited in ADR #020.

Collapse
 
slabb profile image
Sam LABBE •

@axiru — follow-up, because the useful half of this thread moved to GitHub and email. Issue #3 shipped and is now closed: noirebox reconcile (v0.1, on main) encodes exactly your invariant — every policy_decision needs a matching provider_response, and the reverse — with three refinements straight from your review:

correlation on decision_id, not the payment intent — one intent can produce several attempts, each decision ownable;
decisions carry a deadline (expected_by / expected_within): pending until the window passes, then unconfirmed; no deadline → open_gap;
unauthorized: true in an outcome payload is its own finding — auditors search for the flag, not for silence.
Full status set: matched · pending · unconfirmed · open_gap · orphan_outcome · unauthorized · late. noirebox reconcile --fail-on-findings gates CI on the failure states, and make demo-payout runs your two failure cases end-to-end — the reconciliation report is itself journaled.

The regenerated v0.1 sample is in your inbox. The standing offer survives: your redacted production shapes remain the best stress test for this config — credited either way, here or in the changelog.

Collapse
 
slabb profile image
Sam LABBE • • Edited

This is exactly the right question — decision vs outcome is where most logging quietly lies.

What NoireBox guarantees today: the integrator seals both events (your policy_decision — allow/hold/deny, reason code, policy version — then the provider_response), and the journal proves the order and the integrity: the decision's hash sits inside the outcome's prev_hash chain, with millisecond timestamps and signatures. After the fact, nobody can claim "the charge ran first" or "that decision was added later" — the sequence is signed and anchored.

What NoireBox deliberately does not do: know what a "missing half" is. The journal is domain-blind by design — an event is a type and a payload, which is why the same box works for payouts, medical AI or legal. So, your two cases:

  • Process dies after sealing the decision, before Stripe: the journal pins it exactly — decision sealed at T, no outcome recorded. It doesn't fabricate knowledge of what happened next, but the gap itself is provable evidence: "here is what the system knew at the moment it stopped." That's the difference between reconstructing an incident from a mutable log vs from a flight recorder.
  • A charge with no sealed decision in front of it: if the caller skips journaling, the journal honestly shows an outcome with nothing before it — detectable by sequence order + a correlation id in the payloads (a one-line query over the export) — but NoireBox won't flag it on its own today. Reconciliation is caller-side.

That last point is a fair critique, and it's on the roadmap: a reconciliation plugin — a scheduled check over the journal that encodes your invariants ("every decision must have an outcome within N minutes") and journals its findings as reconciliation events. Same plugin pattern as the guardrail: the journal stays domain-blind, the domain knowledge stays pluggable.

If you're building a payout flow, the two-event pattern with a correlation id works today — and I'd genuinely like the invariant case sketched as an issue. It would shape the reconciliation plugin, and you'd be credited for it.

Collapse
 
slabb profile image
Sam LABBE • • Edited

Opened an issue to track this → github.com/noirebox/noirebox/issues/3. Thanks for the sharpest question of the thread.

Thread Thread
 
naveen_alavilli profile image
Naveen Alavilli •

This solves a problem worth solving well — tamper-evidence after the write is a real gap and hash-chaining plus an external TSA closes it properly. The boundary worth naming for anyone evaluating it: NoireBox proves nobody edited event N after it was sealed. It can't prove event N was true when it was sealed. If the integrator's own instrumentation misrepresents what the agent actually did — logs "approved by policy X" when the check silently no-opped, say — the chain still verifies clean, because the lie shipped at write time, not after. That's not a flaw in the design, tamper-evidence and correctness-at-source are genuinely different problems, but it's worth being explicit that this tool answers "was the record altered," not "was the record accurate." Both matter for the EU AI Act use case, and only one of them is what a hash chain can give you.

Thread Thread
 
slabb profile image
Sam LABBE •

@naveen_alavilli You've drawn the boundary exactly where we'd draw it — and it deserves your precision: NoireBox answers "was the record altered?" and "when did this claim exist?", never "was the claim true when written?". A lie sealed at write time verifies clean forever; the chain attests a lie with exactly the same force it would attest the truth.

Two things the chain still changes in your "approved by policy X" scenario, though:

1. The lie is pinned. Once sealed, the story can't be quietly rewritten when the audit starts — "that's not what our logs said" is off the table, and the third-party timestamps (RFC 3161 / Bitcoin — ADR 006 + 009) fix when the claim existed. The chain doesn't detect the lie; it makes the lie non-retractable and attributable to a key.

2. Write-time lies become falsifiable when the sources are separated. That's what the reconciliation layer (issue #3, v0.1, shipped) is for: have the policy engine seal its own decision events, the instrumentation seal its claims, and noirebox reconcile correlate them — an outcome ("approved, money moved") with no sealed policy decision in front of it surfaces as orphan_outcome, and noirebox reconcile --fail-on-findings gates CI on it (demo_payout.py runs exactly those two failure cases). A hash chain can't validate truth, but it makes claims from independent writers cross-checkable against each other after the fact — fooling it now requires lying at both sources.

One caveat in the spirit of your comment: one instance = one key pair, so "independent writers" today means independent processes posting their own events, not independent keys — sealer identity is the natural next step. And you're right that the threat model should carry this boundary explicitly rather than leave it implied — PR #19 adds exactly that line. The most useful kind of review — thanks.

Thread Thread
 
slabb profile image
Sam LABBE •

Your boundary is now a named actor in the threat model ("lying source" — tamper-evidence ≠ truth-at-write) and it shaped the belief-capture convention (ADR #023): the resolved view gets sealed before the action, self-report grade stated. Credited.

Collapse
 
slabb profile image
Sam LABBE •

UPDATE (Oct 8, evening) — you took this apart in the comments, and the comments became the roadmap. Your items are shipped on main, tested, your names in the ADRs:

  • @axiru — your reconciliation invariant is noirebox reconcile v0.2: decision/outcome pairing, deadlines, attempt-first (outcomes ≤ attempts, unlogged_attempt), and the outbound half: a pending expectation must be anchored or co-signed by a different known writer, or it's unwitnessed_expectation — a wish with a hash.
  • @naveen_alavilli — your boundary is a named actor in the threat model, and it shaped belief capture (ADR 023): the resolved view sealed BEFORE the action, self-report grade stated.
  • @sunnydachs @anp2network @housharenet @mayailands — receipt schema v0.2.1: check_id (predicate + inputs + version + spec_hash — your "step I had not taken" is taken), evaluator_sha256, state_binding (the TOCTOU field: the executor applies the sealed version as a precondition, 409 on state move, divergence sealed as receipt_expired_by_state_change), two-key pairing ENFORCED (same writer on both sides = the same_key_pairing finding), single-key receipts labeled AND refused by the gate, per-parameter negative controls on lookups, precedence journaled before the incident, legitimate transformations as first-class lineage.

Numbers, reproducible: 24 ADRs, 244 tests, zero open issues. Changelog + ADRs: github.com/noirebox/noirebox/blob/...

Second comment below for the witness-thread crowd 👇

Collapse
 
slabb profile image
Sam LABBE •

UPDATE (Oct 8, part 2) — the witness-thread crowd, your items shipped too:

  • @james @glenallen — ADR 021: the shared-dependency test ("can one compromise reach both?") is a runnable bench (independence.py: store, key, endpoints, anchor cadence — empty list = independent), clock and key independence included, per-writer keys with a multi-writer verifier on main. "A name, not a role" is enforced: an unnamed precedence policy refuses to seal.
  • @arhancanli @david_ilands — the denominator: attempt-first sealing, outcomes ≤ attempts, and the outbound half (david's "wish with a hash" is now unwitnessed_expectation — covered in part 1).
  • @tom_jones — witness ≠ author enforced at BOTH layers: seal time (the receipt refuses self-witnessing) and serve time (ensure_witness_distinct — skip the slot and escalate). Your same-model-twice failover is now structurally refused.
  • @reid @mickyarun — the wall-clock anchor timer shipped (the server anchors its own chain head, 60 min default), custody-vs-anchoring mapped in the deployment docs, and the reconciliation cost note: named, in writing.
  • @james_anderson_h @slabb (the Oct 5-8 threads) — ADR 024, cross-chain reconciliation: consumption edges are sealable events carrying the producer's chain head as observed; the contract is a partial order (edge admissibility, not a global timeline); the blast radius of a superseded head is a precise path set, propagated downstream.

Totals, reproducible: 24 ADRs, 244 tests, zero open issues. The gate ate its own author twice this week (Bandit caught a MEDIUM in my push, CI's ruff caught the rest) — the controls are tested as controls.

pip install noirebox — verification stays free, forever. Keep attacking: the next critic is the next ADR.

Collapse
 
slabb profile image
Sam LABBE •

UPDATE (Oct 6) — three weeks since this post, and most of it came from this thread:

  • v0.10.0 everywhere — PyPI, GHCR, the VS Code Marketplace and Open VSX (the flight recorder is now also an editor extension: seal, verify, audit-pack from the sidebar)
  • @axiru's invariant from this very thread shipped — issue #3 is closed: noirebox reconcile v0.2 encodes the decision/outcome pairing, plus the attempt-first convention and unlogged_attempt (the denominator problem this community wasn't done with: outcomes must never exceed sealed attempts)
  • @naveen_alavilli's boundary became a named actor in the threat model — tamper-evidence ≠ truth-at-write, stated where the auditors read it
  • Last week's launch threads produced ADRs 019-023 (receipt schema v0.2 with two-key pairing, the normalized witness, flow independence as a testable invariant, custody vs anchoring, belief-before-action) — credited by name to the people who found the gaps
  • The journal server now anchors its own chain head on a wall-clock timer, and the verifier ships in the wheel

Current state: 219 tests, 23 ADRs, every published number generated by a
command. Changelog: github.com/noirebox/noirebox/blob/...
— install: pip install noirebox

Collapse
 
slabb profile image
Sam LABBE • • Edited

UPDATE 🐳 — it now runs in one command.

No clone, no Python, no setup:

docker run -p 8768:8768 ghcr.io/noirebox/noirebox:latest
Enter fullscreen mode Exit fullscreen mode

API + OpenAPI docs on :8768/docs, the ML guardrail ships inside the image, and every incident it catches is sealed in the journal before you even open a browser. Pull it anonymously, run it, verify it — that's the whole point of a flight recorder.

Small print, as always: the image is public, the tests are green, the numbers are reproducible. ☕ If the project helps: buymeacoffee.com/samlabbe

Some comments may only be visible to logged-in visitors. Sign in to view all comments.