Skip to content

Verifying Defendability

A claim of defendability is worthless until it is tested. "Defendable" is not a posture you assert in a slide; it is a property you can demonstrate on demand, with a suite that fails loudly the moment the property stops holding. This page sets out how I test the Lodestar stack — the categories, the concrete assertions, and the conformance matrix that ties each test back to a control.

If you have not read what defendable means, start there. This page assumes you already accept the goal and want to know how to prove you have hit it.

Five test categories

Verification splits cleanly into five buckets. Each maps to a mechanism in the stack, and each has a distinct failure signature.

Category What it proves Mechanism under test
Determinism (golden-master) Same inputs produce byte-identical scores Pure scoring engine
Audit-completeness Every governed operation emits an event Append-only log
Budget enforcement Work stops at the ceiling, fail-closed Budget ledger
Drift detection Stale or re-versioned scores are flagged Temporal pinning
Tenant isolation Confidential state never crosses a boundary Per-tenant directories

Golden-master determinism

The scoring engine is a set of pure functions: reproducibility is the whole point. A golden-master test freezes a known input, records the expected output once, and fails on any divergence. Because a Buyer score is a weighted composite of three layers — Need (0.40), Attractiveness (0.25), Winnability (0.35) — the fixture must pin every one of the 18 variables.

import { scoreBuyer } from "../src/scoring/buyer";
import goldenInput from "./fixtures/buyer-001.input.json";
import goldenOutput from "./fixtures/buyer-001.output.json";

test("buyer score is a pure function of its inputs", () => {
  const first = scoreBuyer(goldenInput);
  const second = scoreBuyer(goldenInput);

  // idempotent
  expect(first).toEqual(second);
  // matches the frozen golden master
  expect(first.total).toBe(goldenOutput.total);
  expect(first.band).toBe(goldenOutput.band);
  expect(first.layerScores).toEqual(goldenOutput.layerScores);
});

The total and band are not independent assertions — a band of "High" must correspond to a total in [70, 85). I assert both so a broken band boundary cannot hide behind a correct total.

The honest limit here is worth stating plainly: determinism guarantees process, not correctness. If signal-freshness is computed with the wrong half-life, the golden master will happily lock in the wrong number. What the test buys you is that the error is visible and reproducible — you change the decay function, the golden master breaks, and you are forced to look. That is how you catch a wrong variable, not how you prevent one.

Audit-completeness

The rule is absolute: every governed operation emits exactly one audit event. A test harness that uses the in-memory log writer can assert this directly. Drive an operation, then interrogate the log.

test("buyer_scored emits a complete decision trace", () => {
  const session = newTestSession();        // in-memory audit log + ledger
  session.scoreBuyer(goldenInput);

  const events = session.audit.events();
  expect(events.map(e => e.type)).toContain("buyer_scored");

  const ev = events.find(e => e.type === "buyer_scored")!;
  expect(ev.scoring.variables).toHaveLength(18);   // full trace, no gaps
  expect(ev.scoring.total).toBeGreaterThanOrEqual(0);
  expect(ev.temporal.dataAsOf).toBeDefined();       // provenance pinned
  expect(ev.budget.cost).toBe(5);                   // buyer_scoring = 5 units
});

Completeness has two halves. First, presence: the operation must produce an event at all. Second, fidelity: the event must carry the full variable trace, the temporal pin, and the budget line — a decision trace with a missing variable is a hole in the record. I test both, because a system that logs "buyer scored" with no variables is auditable in name only.

Budget-ceiling enforcement

The ledger checks the ceiling before it records. A budget test asserts that the boundary is fail-closed, not fail-open: the operation over the ceiling must throw and emit budget_exceeded, and it must not have produced a score.

test("work stops at the ceiling and fails closed", () => {
  const session = newTestSession({ ceiling: 8 });   // room for one buyer score

  session.scoreBuyer(goldenInput);                  // +5 => runningTotal 5
  expect(() => session.analyseFirm(goldenFirm))     // firm_analysis = 10
    .toThrow(/budget/i);                            // 5 + 10 > 8

  const types = session.audit.events().map(e => e.type);
  expect(types).toContain("budget_exceeded");
  expect(types).not.toContain("firm_analysis_complete"); // no partial work
});

See budget and bounding for the full cost table. The subtle case a good test catches: runningTotal + cost must exceed the ceiling to be rejected, so an operation that lands the total exactly on the ceiling should be accepted. Test the boundary at ceiling - 1, ceiling, and ceiling + 1; off-by-one errors live here.

Drift detection

Temporal pinning records a pin at score time; the drift check compares it against current state. The recommendation logic has four arms, and each deserves a test: two or more drift reasons recommend reanalyze; model drift alone recommends reanalyze; a single non-model reason recommends monitor; no drift is ok.

test("model version change alone triggers reanalyze", () => {
  const pin = { dataAsOf: "2026-06-01", modelVersion: "1.3.0",
                scoredAt: "2026-06-15", maxAgeDays: 30 };
  const now = { currentDataAsOf: "2026-06-01", modelVersion: "1.4.0",
                today: "2026-06-20" };

  const result = checkDrift(pin, now);
  expect(result.reasons).toEqual(["MODEL"]);
  expect(result.recommendation).toBe("reanalyze");
});

Again the honest limit: drift detection tells you a score is stale. It does not refresh the underlying data — that is a separate operation you must schedule and pay for out of the same ledger.

Tenant isolation

Isolation is a directory boundary, not a policy note, so I test it as one. Shared signal data is visible to every tenant; confidential scores and plans must never appear in another tenant's state directory.

test("confidential scores stay in their tenant directory", () => {
  const a = harness.session({ tenant: "tenant_a" });
  a.scoreBuyer(goldenInput);

  const bFiles = fs.readdirSync(stateDir("tenant_b"));
  expect(bFiles).not.toContain(`${goldenInput.buyerId}.score.json`);
  // and the reverse read is refused, not empty
  expect(() => harness.session({ tenant: "tenant_b" })
    .loadScore(goldenInput.buyerId)).toThrow(/not found|forbidden/i);
});

For the boundary model see multi-tenancy.

The conformance matrix

Individual tests are necessary but not sufficient. What an auditor wants is a single artefact that maps every control to the test that exercises it and the last time that test passed. I generate one from the suite:

Control Mechanism Test id Status
Audit logging (ISO 27001 A.12.4) Append-only JSONL audit.completeness pass
Decision trace (SOC 2 CC7.2) Full variable trace audit.fidelity pass
Reproducibility (ISO 27001 A.14.2) Deterministic engine scoring.golden pass
Data provenance (ISO 27001 A.8.1) Temporal pinning drift.recommend pass
Budget enforcement (SOC 2 CC6.1) Cost ceiling budget.failclosed pass
Tenant isolation (ISO 27001 A.9.4) Per-tenant state tenant.boundary pass

This matrix is the seam between engineering and evidence packages — each row is a control, a mechanism, and a green test that produced a record. Run the suite in CI on every commit and the matrix is never more than one push out of date.

A closing caution: passing the suite proves the mechanisms hold, not that the system is correct or that governance is done. Verification is maintenance, not one-time setup — the tests decay the moment a variable, a weight, or a boundary changes and no one updates the golden master.