NxtSoftLabs
← All writing

Blastline v0.4–v0.8: a standing safety audit, and the barrier that fixed Go

August 20, 2026·6 min read

Blastline launched with three languages replay-verified and a promise: selection is a safe superset, and anything the graph can't vouch for fails open. Four releases later — v0.4 through v0.8 — that promise gets two upgrades. First, it stops resting on a one-time benchmark: every merge to main now re-verifies it, standing, forever. Second, one of the ways the promise could quietly break — a single edit cascading through an interface's entire sibling set — got found and closed, with the fix measured in both directions.

Four releases, briefly

  • v0.4 — cross-repo selection. Point Blastline at a CGraph seam — the fused graph joining two services at their wire contracts — and a provider-side schema edit selects the consumer's tests across the repo boundary.
  • v0.5 — C/C++. The fourth language family, detected by the googletest/ctest convention (*_test.{c,cc,cpp,cxx}, test_*). Replay-verified on CGraph's own C++ codebase: 62/62 co-changed tests selected, at a mean 22.4% of the suite, with every fail-open an honest build-system change (CMakeLists, submodule pointers) rather than a selection miss.
  • v0.6 — base-graph automation. Pure deletions used to map only when you supplied a base graph by hand. The GitHub Action now takes a base-graph-command input, checks out the pushed range's base commit into a worktree, runs your build command there, and hard-fails if it doesn't produce a graph — deletion mapping stops being a manual step. Verified with a deletion self-test on Blastline's own repo: delete src/mcp.ts, and the selection is a subset containing src/mcp.test.ts.

The other two releases are the headline stories, so they get their own sections.

v0.7: the safety audit — benchmarks are a snapshot, this is standing

Blastline's launch benchmarks replayed 20 historical commits per repo and scored every selection against what the author actually co-changed. That's real evidence, but it's a snapshot — it says nothing about the next commit. v0.7 turns the same judgment into a check that runs forever: safety-audit.yml fires on every push to main, and its job is to try to catch Blastline breaking its own contract.

The workflow builds a head graph, and — when the push has a real parent commit — checks that commit into a worktree and builds a base graph too, so scripts/audit.ts can replay the exact selection Blastline would have made for the pushed range. It then runs the full test suite with vitest's JSON reporter, deliberately not trusting the subset it just computed. The judgment itself is one function, computeAudit in src/audit.ts:

export interface AuditOutcome {
  /** failing test files the selection did not contain */
  escapes: string[];
  /**
   * - "full-run"  — selection failed open, the full suite ran by definition
   * - "clean"     — subset selected, full suite green: nothing to miss
   * - "caught"    — tests failed and every one was inside the subset
   * - "escape"    — at least one failing test was outside the subset
   */
  verdict: "full-run" | "clean" | "caught" | "escape";
}

An escape — a test that failed and wasn't in the selection — is the one outcome "safe superset" forbids. Every run appends a record to a JSONL ledger on the safety-ledger branch, publishing with a race-safe fetch-merge-push retry loop that's idempotent by head SHA, so a concurrent run's record is never clobbered. The ledger rolls up into a shields.io endpoint badge — ${escapes} in ${audited} audited merges, green until the first violation, red and staying red once one lands:

export function badgeFromLedger(records: AuditRecord[]) {
  const escapes = records.reduce((n, r) => n + r.escapes.length, 0);
  const audited = records.length;
  return {
    message: `${escapes} in ${audited} audited ${audited === 1 ? "merge" : "merges"}`,
    color: escapes > 0 ? "red" : "brightgreen",
  };
}

The workflow fails loudly on an escape, but only after the record is published — the evidence is never lost to make the alarm quieter. Ordinary test failures that stayed inside the selection don't fail the job at all; caught is success, because that's exactly what a safe superset is for. The badge lives on the repo README, and it's a different kind of proof than a benchmark table: it doesn't say "we checked once," it says "we're still checking."

v0.8: the dispatch barrier — each hop is individually honest, the composition is not

Go's interface-dispatch resolution (CGraph #47, shipped before v0.5) gave Blastline implements and dispatches_to edges so a call through an interface method resolves to every implementer — which is exactly what made Go's replay hit 10/10 selectable co-changed tests on gorilla/mux. But those same edges, walked backwards from a change, had a failure mode: an edited implementation would climb from the method to the interface it satisfies, and from the interface to every other implementer of it, and from each of those to all of their callers.

Traced on a real commit (gorilla/mux 525206d, a one-file getter change): the walk selected 10 of 11 test files via the chain

Get --method_of--> Router --implements--> matcher --method--> matcher.Match --dispatches_to--> Route.Match

Every hop in that chain is a true fact about the code — Get really is a method of Router, Router really does implement matcher, and matcher.Match really does dispatch to Route.Match. But the composition asks a different question than "what does this edit affect": a node reached by walking dispatches_to backwards is an interface contract the changed code implements. Its callers are genuinely at risk — any of them might call through to the changed implementation — but its structural neighborhood (the interface's other implementers, and everything they touch) is not, unless the edit itself is to the contract.

The fix is a barrier on that one edge direction: from a node reached by walking dispatches_to backwards, the walk continues only through consumer relations — callers, importers, the things that use the contract — never back out sideways into sibling implementers. A consumer reached that way resumes the ordinary unrestricted walk from there, and an edited interface (a contract seed, not a contract-reached node) still expands fully, because then the sibling implementers really are affected.

The before/after, replayed on the same binary:

beforeafter
mux mean subset45.9%30.3%
mux co-changed selected10/1110/11 (unchanged — the miss is an empty TODO test with nothing to select)

Selectivity improved by a third with zero loss of correctness on the same replay. And the barrier only fires where dispatches_to edges exist at all: they're absent by construction from the TypeScript, Python, and C++ graphs, so those three languages' replays were unaffected — TS held at 43/43, Python at 4/4, and C++'s mean matched the pre-barrier number to four decimal places, with its own 62/62 co-changed tests still selected.

Where that leaves it

All four v1 language families are now replay-verified with the dispatch barrier in place, and every merge to Blastline's own main re-runs that verification against itself. The full status, every number, and the roadmap are in the repo README; the design and code for both features are in openspec/ and src/audit.ts respectively.

Try it

npm install -g blastline
cgraph --root ./src --out cgraph-out
blastline tests main..HEAD | xargs vitest run

If you're new to the project, start with the launch post for the design and the original benchmarks, or the Blastline project page for the current feature set. The interface-dispatch edges the barrier now guards were added for Go support after CGraph#47; the freshness guarantees the audit's own graphs rely on are covered in stale index? how a code graph proves its answers are fresh.