Skip to content

Releases

Cernum v1.0.0 — what may be routed to, and who says so

Choose a tag to compare

@happySNAG happySNAG released this 24 Sep 02:08

Cernum v1.0.0 — what may be routed to, and who says so

The release that makes routing a decision with a version on it, gives the fleet somewhere to put
what each machine observed, and qualifies a frontier model per capability rather than per score.

This is the first release to consolidate the two development lines — the Mac mini benchmark line and
the MacBook workspace line — into one canonical V1, and the first to run live frontier qualification
through the sealed workspace engine as part of the release itself.

There is no universal score anywhere in this release, and no winner. There are candidate sets, with
every exclusion and its reason beside it.


Why 1.0.0

v0.2.4 was already a cut tag, and package.json had carried 0.2.4 since 17 September while
package-lock.json still said 0.2.3 — the missing half of that bump, six days stale. This release
resolves it deliberately: both files move 0.2.3/0.2.4 → 1.0.0 as an intentional release-version
change, rather than the lockfile quietly catching up to a version somebody else had already
published.


Routing policy is a value with a version, and both versions ship

Pass 6 and Pass 7 approved the identity exception on a written condition: a candidate admitted under
requestAcceptedIdentityUnverifiable is measured in full and is never a routing target. Pass 8
decided the opposite, under conditions. The naive way to ship that is to edit isRoutable — which
would retroactively change what every sealed record and signed approval said was guaranteed.

So the policy became a value instead.

Version Rule Status
crp1 An identity-admitted route is never a routing target, whatever else is established about it The Pass 6/7 policy, preserved verbatim and still evaluable
crp2 Such a route may be an Ordra candidate, and only when all nine conditions hold Current; supersedes crp1 prospectively

crp2's nine conditions: the route is currently discovered; available on the requested machine;
that discovery is fresh; its qualification evidence is fresh; it has sufficient capability evidence
for the task asked about; its identity admission is current; the spend posture permits execution; no
provider or session state blocks it; and the task did not ask for verified identity.

isRoutable is unchanged, because sealed manifests cite it by name as the thing that keeps the
Pass 6/7 promise. crp1RefusesAdmittedState proves the old rule still refuses what it always
refused by calling that same function rather than a second copy that could drift.

Supersession is not erasure. --policy crp1 re-answers today under the rule that was in force then,
which is what reading a historical decision honestly requires. Verified against real evidence:

crp2                            5 candidates  (3 Claude verified + 2 Codex admitted)
crp1                            3 candidates  (Codex excluded — the Pass 6/7 rule)
crp2 --require-verified-identity 3 candidates  (Codex excluded per query, with reason)

Identity disclosure remains mandatory. A route routable under crp2 still reports its identity
as unverifiable on every candidate, exclusion and surface, and carries a disclosure stating the
permission and its limit in one breath. Being routed does not verify a route and never makes it
citable as "this model did the work".


Zero-cost authorization is not zero-cost evidence

The owner's permission to benchmark free OpenCode routes is recorded verbatim — and it proves
nothing. A permission is not a finding. Refused by name as evidence: the authorization itself, a
published $0 catalogue price however current, a free-tier announcement, a model's own reply about
what it costs, and an inference request sent to prove the route is free (which spends the thing it
is trying to measure).

A confirmation must bind to the exact route and name the account's own billing record that was read,
by whom and when. It expires by calendar at thirty days, and immediately by observation when a
discovery delta sees that route's price or free status move — the second is the one that matters,
because the day a free tier starts charging is not the day anybody re-reads a billing page.


The fleet: an append-only, content-addressed observation store

A record is named by the digest of its own content, so the same observation is one file however many
times it arrives. A file whose bytes no longer digest to its own name is reported by path and is not
read. Bundles travel as one sealed JSON value — no daemon, no protocol, no hard-coded host or path.

An imported observation never becomes a local one. Every record carries the cmk1: key of the
machine that observed it, and that key survives transport unchanged. A machine can know that
another machine saw a route available; it cannot, on that basis, report the route available here.
Local qualification stays bound to machine + weights digest + runtime identity and does not transfer.

Two digests, because they answer two different questions:

  • contentDigest — over the records alone, sorted by their own identity. Stable across exports,
    machines and time. This is what comparability means, and it is what two machines compare.
  • bundleDigest — over everything including who exported and when. Integrity has to move when
    anything moves.

contentDigest is recomputed from the records on read and never believed from the file, so a bundle
doctored to claim it holds what another machine holds does not need to be caught — the forged value
simply never takes effect.

Proven operationally in this release: export, verify, import, idempotent re-import (0 written, 220
already held, origins preserved), whole-bundle refusal of a tampered file, and machine-scoped
availability.


Live qualification

Claude Opus 5.5 — discovered, not assumed

The claude CLI has no model-listing command, so "which models may this account call" has no
enumerable answer on that provider — which is exactly the gap a person fills by typing a plausible
identifier out of a release announcement. It was asked instead: the CLI resolves the bare alias
opus to the latest Opus, and --output-format json reports, in its own metadata rather than in any
model's prose, the identifier that served the request. That named claude-opus-5-5, context
window 1,000,000. An identity smoke test then returned verified.

pack.cernum.workspace.discriminator-one@1, 3 repeats, 18 independent runs.

Model Score asset-container.retitle Median tokens Median wall Allowance
Fable 5.1 18/18 3/3 92,399 67.3s $11.06
Opus 5 16/18 1/3 [P F F] 58,239 34.3s $3.65
Opus 5.5 15/18 0/3 [F F F] 57,478 33.8s $2.92
Sonnet 5 14/18 2/3 [P F P] 289,315 68.5s $3.22
Haiku 4.5 9/18 0/3 299,385 74.2s $1.82

Opus 5.5 ran with identity verified on all 18, 0 retries, 0 regressions, 0 scope violations and
nothing throttled. It is 3/3 on five of six cases — identical to Opus 5 — and the entire difference
between 16 and 15 is one run on the case that isolates preserve-unknown reasoning, where Opus 5's
own result was one lucky pass away from the same. On 18 runs that is not a regression, and it is
not reported as one.

What it buys: the same capability profile as Opus 5 at roughly 20% less plan allowance, the same
wall time, and a slightly better time-to-first-token.

Qualified capability families are decided per capability, by the rule that no case may have
never passed — a 15/18 aggregate does not buy qualification in a family where one case failed every
time:

Capability Opus 5.5
multiFileEditing qualified, 6/6
repositoryComprehension qualified, 6/6
regressionAvoidance qualified, 3/3
preservationUnderTransformation not qualified — 1 case never passed
forwardCompatibilityReasoning not qualified — 1 case never passed
destructiveFallbackAvoidance not qualified — 1 case never passed
contractOverVisibleTests not qualified — 1 case never passed
failureInterpretation not qualified — 1 case never passed
scopeDiscipline no evidence — not exercised by this pack

A genuinely new OpenAI route, and what it is not

Discovery found gpt-6-luna and gpt-6-sol advertised to this account, published
2026-09-14 — distinct models, not aliases or renames of gpt-5.6-luna/gpt-5.6-sol (2026-06-23) or
of gpt-6-astra (2026-08-27).

They are advertised on the metered OpenAI API only and are absent from the Codex CLI catalogue,
so they are not subscription-included. Qualifying them would mean real per-token spend against a
personal credential, which was not authorized in this pass. They are recorded as discovered and
unqualified, and they are not routing candidates.

Route changes

opencode/union-alpha is no longer listed by OpenCode and is recorded as removed. One route moved
nothing that describes it. No billing, context or runtime-digest changes.


Ordra candidate sets, by capability

Cost never overrides capability. Routes are grouped with zero-marginal-cost first (local,
free_confirmed, subscriptionIncluded); metered routes are available only under explicit spend
authorization. Among routes that satisfy the requirements, choosing is a scheduling decision this
engine does not make.

Capability family Qualified candidates
multiFileEditing fable-5-1, opus-5, opus-5-5, sonnet-5, gpt-5.6-sol, gpt-6-astra
repositoryComprehension opus-5, opus-5-5, gpt-5.6-sol, gpt-6-astra
regressionAvoidance opus-5, opus-5-5, gpt-5.6-sol, gpt-6-astra
interactingInvariantReasoning fable-5-1, haiku-4-5, opus-5, opus-5-5, sonnet-5, gpt-5.6-sol, gpt-6-astra
preservationUnderTransformation fable-5...
Read more

Cernum v0.2.4 — the preview and the record describe the same request

Choose a tag to compare

@happySNAG happySNAG released this 17 Sep 21:42

Cernum v0.2.4 — the preview and the record describe the same request

A corrective release. Every defect it closes was found by reading cernum smoke's dry-run output
against the record it would have written — before a single live request was sent.

No live Claude, Codex or OpenCode request was made while finding, fixing, testing or releasing any
of this. Every execution path below is exercised against fake executables written to temporary
directories.


The audit

A MacBook Pro dry-run audit of the v0.2.3 smoke path found seven things. They are not seven separate
bugs; they are one shape — a command that described each request twice, and disagreed with itself
about money, identity and what it could measure.

# Found Closed by
1 cernum help smoke ignored the topic and printed the general index, omitting --models, --dry-run and --all-ladder D
2 OPENCODE_NO_PROOF_PATH was stale and false, and was written into discovered.json for every OpenCode model on every run C
3 smokeBinding() hardcoded subscriptionCLI and subscriptionIncluded for every provider A
4 A live OpenCode smoke would spend metered money and record $0 marginal subscription usage A, B
5 The dry-run reported meteredAPI correctly; the live record would have disagreed A
6 --otlp-observer was inert during smoke: adapter options were never passed E
7 A Codex smoke can establish request acceptance with identity unverifiable, never proof E

A · One binding, derived from the registry, validated where it is built

commandSmoke built each request from a local helper that wrote three provider-determined fields as
literals — executionClass: 'subscriptionCLI', billingBasis: 'subscriptionIncluded',
authorizationMode: 'subscriptionCLISession' — for every provider it was ever handed. The preview
beside it derived the same fields from the provider registry and printed them correctly. Nothing
caught the contradiction, because the smoke path was the one binding-building path in this engine
that never ran validateBinding.

  • src/engine/smoke-binding.ts is now the only place a smoke binding is built. Execution class,
    billing basis, pricing and authorization mode all come from provider.ts, and the object is
    validated before it is returned.
  • authorizationModeForProvider() joins executionClassOf and billingBasisOf in the registry,
    so "how is this provider authorised" has one answer rather than one per caller.
  • The preview, the live request, the evidence file and the discovery row all read the same object.
    There is no second description of a run for any of them to disagree with.
  • OpenCode always records meteredAPI. Never subscriptionIncluded, at any effort level.
  • An OpenCode cost Cernum cannot compute is recorded as UNAVAILABLE — with a reason naming why
    it is unavailable — and never as zero. Where you supply prices and the provider reports tokens, the
    charge is computed and labelled estimated, because a figure derived from a published rate is not
    the figure on a bill.
  • Invariant tests walk every executable provider and assert the preview string and the recorded
    result agree on billing basis, execution class and authorization mode.

Two further honesty repairs fell out of the same reading:

  • The OpenCode adapter put the finish reason in reportedEffort, so every OpenCode smoke printed
    effort applied, as the provider reported it: stop. A finish reason is not an effort level.
  • The adapter's comment said OpenCode's own cost figure was "preserved in the raw usage instead". It
    was read off the message and dropped. It is carried now, as
    openCodeReportedCostUnitUnverified — the generated types declare no unit, so it is not promoted
    into a cost column.

B · A metered smoke is refused unless it was authorized by name

cernum smoke opencodeCLI --models opencode/union-alpha
cernum: this run is not authorized.
  THE PROVIDER MAY BILL YOU FOR THIS REQUEST AND CERNUM CANNOT SAY HOW MUCH. …
  The amount is UNAVAILABLE — which is not zero, not free, and not estimated. …
  Nothing was sent.
  • With prices (--pricing <file>): --authorize-metered <dollars> is required. The worst case
    for the scope
    is computed from the frozen budgets — every input budget filled, every output budget
    filled at the higher of the output and reasoning rates — and the run is refused if it exceeds the
    ceiling. It is a bound that was checked before sending, not a receipt.
  • Without prices: there is no bound, so a ceiling would be a limit this tool cannot enforce and is
    refused as one. --authorize-unpriced-metered acknowledges in writing that the provider may bill an
    amount Cernum cannot state, and it covers exactly one request. A scope of two is refused: an
    unbounded charge repeated is an unbounded charge multiplied.
  • The authorization is written into the evidence file, with what it covered and what stayed
    unknown.
  • Subscription execution needs no spending authorization — no card is billed — and is still never
    called free: it consumes a finite plan allowance, and that is recorded.
  • A dry-run reports the requirement instead of refusing. The point of a preview is that a person
    reads the requirement before they are standing in front of it.

C · OpenCode evidence says what is true in this release

OPENCODE_NO_PROOF_PATH said Cernum "has no OpenCode execution adapter, so there is no authorized
request it could make and record". True in v0.2.2. False from v0.2.3, when the adapter shipped. The
sentence stayed — and it is a discovery string, written into discovered.json beside every
OpenCode model on every run, so the store filled with a claim the same build could disprove.

  • OPENCODE_PROOF_PATH replaces it and names the route, its cost, and its limits: discovery means
    discovered/unproven; an authorized successful request proves execution; it proves
    identity only as far as the returned providerID/modelID support; a reply naming no model
    leaves the candidate unproven.
  • Contaminated rows are superseded, never deleted. supersedeStaleOpenCodeEvidence corrects the
    evidence on read, preserves the original text verbatim in supersededEvidence, and stamps
    evidenceCorrectedBy: v0.2.4. It changes no availability — correcting a sentence proves nothing
    — backdates no timestamp, and is idempotent.
  • Seven verdict paths tested against fake executables: Union Alpha success, missing identity,
    model mismatch, refusal, timeout, malformed JSON, and a secret echoed in an error message.

And the reason the correction had nowhere to go

Correcting on read is only half of it: the correction reaches disk when the store is next written,
and the one command that rewrites the store was deleting the rows instead.

cernum discover <provider> dropped every row for each named provider and wrote back only what came
out of that run. So on a machine where OpenCode is not installed — or is installed and signed out, or
simply answered nothing — cernum discover opencodeCLI deleted the entire record for that
provider, including a proven row that a metered smoke had paid for.
Nothing warned. The store is
the evidence, so nothing could recover it.

A tool that did not answer is a fact about today, not a retraction of what was established before.
Rows for a provider that returned nothing are now kept, and said to be kept, with their
timestamps untouched — a run that learned nothing does not get to refresh anybody's freshness either,
so a stale proof still ages out on its own schedule.

D · Every spelling of "tell me about this command" is safe and complete

v0.2.3 stopped cernum smoke --help from sending six live requests. It did not touch cernum help smoke, which parsed the topic into the positional list and called a function that took no
arguments. So the most-typed spelling printed the general index, in which smoke had a two-line
entry naming --evidence and nothing else — while --models, --dry-run and --all-ladder were
all implemented. A person looking for the safe way to preview a spending command was shown the least
informative page the program has.

  • cernum help smoke, cernum smoke --help and cernum smoke -h now print byte-for-byte the same
    page
    , from the same table, through the same renderer. A test asserts the three cannot drift.
  • That page documents the provider argument, --models, --all-ladder, --max-attempts,
    --dry-run, the full metered-authorization contract, --otlp-observer, and what a Codex smoke can
    and cannot establish about identity.
  • help <topic> refuses an unknown topic rather than printing the index and hoping.
  • Unknown options refuse even when --help is also present. Printing help would be safe and would
    also answer a question nobody asked: the flag it could not read may be the one carrying the scope or
    the ceiling.
  • Malformed values refuse. --models with no value, a bare flag given one, an empty value, and
    --max-attempts banana — which used to become NaN, fail NaN > 0, and silently not apply the
    cap
    , on the one command whose reason for having a cap is to keep a spending run small.
  • 18 side-effect tests spawn the real command with recording fakes first on PATH and assert the
    fakes were never invoked.

E · Smoke telemetry, and what it still cannot establish

--otlp-observer was wired into run and resume and nowhere else. commandSmoke built its
adapter with buildAdapter(provider) and no options at all, so even had the flag been accepted
there it would have started a collector that exported nothing.

  • Adapter options are passed through smoke execution. --otlp-observer <dir> works for Codex smoke:
    loopback only, ephe...
Read more

Cernum v0.2.3 — asking a question must not cost money

Choose a tag to compare

@happySNAG happySNAG released this 17 Sep 18:36

Cernum v0.2.3 — asking a question must not cost money

A corrective release, and the first one that makes OpenCode a provider Cernum can actually run.

Two things happened on a MacBook Pro. A person asked a command what it did and was charged for the
question. And when they went to prove the model they actually wanted, they found Cernum could
discover it but had never been able to send it a single request.


1. cernum smoke --help sent six live requests

It did not print help. It began six Claude CLI smoke requests against the whole Claude ladder.
At least four completed, consuming roughly $0.127 of subscription allowance at list value. No
evidence file was written and no proof state was recorded, so the spend bought nothing.

Three ordinary decisions in one argument parser, none dangerous alone:

--help was honoured only in the command position cernum --help worked; cernum smoke --help did not
smoke defaulted to a live provider no positional argument meant claudeCLI — and --help produces no positionals
unknown options were accepted and ignored nothing could refuse an argument it did not understand

-h was not recognised as an option at all: it does not start with --, so it was read as a
provider name.

The four completed requests are recorded as an accidental-request incident and explicitly not as
benchmark evidence
— see docs/incidents/2026-09-17-accidental-smoke-requests.md.
No ledger, manifest, ranking or report includes them, and none will be amended to.

What changed

  • Help is answered before dispatch, for every command. --help and -h, in any position, from a
    central command table. A command cannot be added without one, and cannot spend on its way to
    printing one.
  • smoke requires an explicit provider AND an explicit candidate scope. There is no default
    provider and no default candidate set. Naming claudeCLI is not consent to its six-request ladder:
    --models <id,…> names them, or --all-ladder asks for the whole thing deliberately.
  • Unknown options refuse with exit code 2, list what was accepted, and run nothing.
  • --dry-run on every command that spends, printing exactly which requests would be sent.
  • A pre-flight disclosure precedes every live execution: provider, candidates, request count and
    authorization class — and subscriptionIncluded is spelled out as "consumes a finite plan
    allowance you already pay for. Not free."
  • Every effectful command was audited. All 25 now declare their effect class and their options.
    The audit found options read inside helpers (--pricing, --thinking, --observe-only,
    --admit-identity-unverifiable on create) that a glance at the function bodies would have missed.

--root and --endpoint are accepted everywhere, because they say where things are rather than
what to do and wrappers reasonably append them to every call. Everything else is declared by the
command that reads it, so --modles is still refused.


2. OpenCode can now be executed, not just discovered

v0.2.2 correctly stopped calling a catalogue listing a proof — and left OpenCode with no way to
ever be proven
, because Cernum had no OpenCode execution adapter. opencode/union-alpha could be
named in a plan and never run.

How the adapter was built, which matters more than usual

This engine has written a CLI adapter from assumption exactly once, and its tests used the same
assumption, so a wholly broken adapter passed for weeks.

  • The invocation was read from opencode run --help, which sends nothing:
    -m/--model provider/model, --format json, --dir, --variant, --pure.
  • The response envelope was read from the generated types that ship with the tool —
    @opencode-ai/sdk/dist/gen/types.gen.d.ts, where AssistantMessage, TextPart, RetryPart and
    the five error variants are declared by OpenCode itself.
  • No request was sent to any model while writing or testing this, and none is sent by the tests.
    Every fixture is shaped from those declarations and replayed by a fake executable.

What it does

  • Invokes OpenCode non-interactively, with the model named explicitly in OpenCode's own
    provider/model addressing, --format json, --pure, and an empty working directory created
    and removed around each request
    — OpenCode is a coding agent, and a request run in a real
    directory can read what is in it.
  • Captures exit status, answer text, latency, retries, token telemetry (input, output, reasoning,
    cache read/write), cost and finish reason
    where OpenCode reports them.
  • Marks what it does not have. No tokens block means usageProvenance: 'unavailable', never a
    zero. OpenCode's cost is never filed as subscription allowance — it is metered, and saying
    otherwise would report a per-token bill as plan usage.
  • Reads identity from the reply and never from the request. providerID/modelID rejoined as
    OpenCode addresses them. No identity in the reply means reportedModelID stays empty; a different
    model answering is a mismatch, not a result.
  • Classifies failures from the envelope before the exit code, so a ProviderAuthError is
    notAuthenticated and is never retried three times over.

Execution proof and identity are still two different things

A successful authorized request proves execution. What it proves about identity is decided
separately, by what the reply said: proven when OpenCode names the model asked for, unverifiable
when it answers and names nobody, substituted when it names another. A request succeeding is never
allowed to imply who answered it.

Integrated, not bolted on

Smoke, campaign manifests, ledgers, guards, checkpoint/resume and reports all reach OpenCode through
the same adapter factory a campaign uses — smoke used to construct a subscription adapter
unconditionally, which is why engine support would not have reached it. A new authorization mode,
toolManagedCredential, records what is actually true of OpenCode: metered, and holding its own
credential. Neither existing mode was honest — apiKeyEnvironment claims Cernum knows where a key
lives, and subscriptionCLISession would assert a subscription for something billed per token.

A metered OpenCode campaign still refuses to freeze without a pricing snapshot. Cernum will not
estimate from prices it invented.


Tests

1397 unit and parity tests (up from 1354), 22 E2E passing (up from 15), typecheck clean, both
macOS architectures built.

43 tests added. The CLI safety tests spawn the real command as a subprocess with a recording fake
on PATH
and assert the fake was never invoked — a test that accidentally sent a request would be
the failure it is looking for. They fail against v0.2.2.

Six E2E remain blocked, and not by this release. Every campaign-execution test aborts at
disk.freeSpace: free 13.9 GiB, floor 15.00 GiB. That is the machine, not the code: reproduced by
hand outside Playwright and identical on earlier commits. The same guard means no scored campaign
can run on that machine until ~1 GiB is freed.

Checksums

shasum -a 256 -c Cernum-0.2.3-SHA256SUMS.txt --ignore-missing

Opening an unsigned build

Not signed with a Developer ID and not notarized:

xattr -dr com.apple.quarantine /Applications/Cernum.app

Cernum v0.2.2 — a listing is not a proof, and a migration is not a move

Choose a tag to compare

@happySNAG happySNAG released this 17 Sep 16:25

Cernum v0.2.2 — a listing is not a proof, and a migration is not a move

A corrective release. Two claims Cernum made about itself were wrong, and both were the kind that
only matters later: one would have let a scored campaign spend money on a model nothing had ever
called, and the other described an untouched backup folder that the code had already emptied.

No campaign, score, ledger, manifest or published result is changed by this release. What changes is
what Cernum asserts.


1. OpenCode models were marked proven on the strength of a catalogue

What v0.2.1 did. Every model opencode models returned was recorded as proven — the one
availability state a campaign builder will select. So this chain ran to completion:

OpenCode is installed → a credential is configured → a listing names 70 models → all 70 are
selectable for a scored, billed campaign.

Not one link in that chain is a model having answered anything.

Where the listing actually comes from, checked on the machine rather than assumed:
~/.cache/opencode/models.json, a cached catalogue holding 7,850 models across 221 providers — a
description of the world, not of your account. opencode models opencode narrows it to one provider
and printed 70 of the 103 entries the catalogue carries there. Some filtering happens; none of it is
a per-model entitlement check, and none of it is a reply from a model. Holding a credential does not
close the gap either: a configured key says a service was signed into, never that a particular model
will answer.

Cernum already refuses exactly this inference for Codex — codex debug models renders a catalogue of
what the client knows about, the service refuses models that appear in it, so the listing proves
nothing about the account. OpenCode was given the opposite treatment by accident.

What v0.2.2 does.

v0.2.1 v0.2.2
opencode/union-alpha after discovery proven unproven
verifiedModelID opencode/union-alpha empty — nothing returned an identifier
Selectable for a campaign yes no
An identifier OpenCode does not name refused refused, and the evidence now adds that being named would not have proven it either

proven now means what it says: a request that was authorized, was sent, came back, and was
written down.
The type itself is documented that way, and the distinction it draws is between an
account-scoped listing — a metered API's /v1/models, answered with your own key — and a
client-side catalogue, which never reaches proven.

There is no OpenCode proof path today, and Cernum says so. adaptersFor binds claudeCLI,
codexCLI, anthropicAPI and openaiAPI and nothing else, so Cernum has no OpenCode execution
adapter and no request it could make. A bare unproven would read like a chore with a command that
fixes it; the status text states plainly that there is none, and cernum smoke opencodeCLI refuses
by name rather than implying otherwise.

If you ran discovery under v0.2.1, your stored evidence says proven. Re-run
cernum discover opencodeCLI under v0.2.2 and those records are rewritten.


2. The rename migration MOVED your data while saying it never deletes

What v0.2.0 and v0.2.1 did. The first launch after the rename carried campaigns/, evidence/,
settings.json and model-lab.log from Model Lab into Cernum using fs.renameSync, with a
copy-then-fs.rmSync fallback across devices. Both take the source away.

The code's own header said "It NEVER deletes", and the v0.2.0 release notes said "The migration
never overwrites and never deletes"
immediately above a sentence describing a move that "copies
before removing the source"
. The two halves contradicted each other, and the reassuring half was the
one people read.

This happened. On a MacBook Pro on 2026-09-17, evidence/ and model-lab.log left the
Model Lab folder on first launch. Nothing was lost — both were intact under Cernum — but the old
folder was not the untouched backup the notes promised.

Why the tests did not catch it. Every migration test asserted the destination was populated. Not
one asserted the source still existed, so "carried across" and "taken away" were indistinguishable
to the suite.

What v0.2.2 does.

  • Copies. Never moves. There is no code path in the migration that renames, unlinks or removes
    anything inside the legacy directory. The one rm in the file removes a partial destination
    the migration itself just created and could not finish.
  • Idempotent, decided by content. A second launch compares both sides by relative path, then
    size, then SHA-256. Identical means already carried across — reported, and not a conflict, so the
    normal state of every migrated machine stops reading as a problem.
  • Conflict-safe. Present on both sides with different contents means both are left alone and
    a person decides. Work done in Cernum after the migration is never copied over.
  • Failure costs nothing. A copy that fails leaves the original untouched and removes its own
    half-written destination, so a later launch does not mistake a partial copy for a conflict.
  • Says so. The result carries sourcePreserved, the log states the old directory was left
    intact, and migrated-from.json records copied rather than moved.

After migrating, your data exists in both places. That is deliberate: a move makes the old
directory an unreliable witness, and duplication buys a second opinion for the price of disk. Delete
the old folder yourself once you are satisfied.

Recovering a machine that already ran v0.2.0 or v0.2.1

If evidence/ or model-lab.log left your Model Lab folder, they are under Cernum. Copy them
back — verifying first, and without touching Cernum's copies:

SUPPORT="$HOME/Library/Application Support"
# Confirm what is missing on the old side and present on the new.
for item in evidence model-lab.log; do
  printf '%-16s legacy:%s  cernum:%s\n' "$item" \
    "$([ -e "$SUPPORT/Model Lab/$item" ] && echo yes || echo MISSING)" \
    "$([ -e "$SUPPORT/Cernum/$item" ] && echo yes || echo missing)"
done
# Copy back only what is missing. -n never overwrites; the Cernum copy is the source and is unchanged.
cp -Rn "$SUPPORT/Cernum/evidence"       "$SUPPORT/Model Lab/evidence"
cp -n  "$SUPPORT/Cernum/model-lab.log"  "$SUPPORT/Model Lab/model-lab.log"
# Prove the restore by hash, not by eye.
diff -r "$SUPPORT/Cernum/evidence" "$SUPPORT/Model Lab/evidence" && echo "evidence/ matches"
shasum -a 256 "$SUPPORT/Cernum/model-lab.log" "$SUPPORT/Model Lab/model-lab.log"

Once both sides match, v0.2.2's migration will report them as already carried across on every
future launch and will not touch either copy again.


What did not change

The scoring policy registry and its digests, every suite identifier, the evidence store manifest
format, all provider cost and billing reporting, campaign execution, and the v0.2.1 executable-lookup
and discovery-routing fixes. Every historical campaign and every frozen evidence manifest is
untouched.

Windows remains on v0.2.0 (issue #1) and is unaffected by either defect fixed here in its published
build — it carries the v0.2.1 routing gap instead.


Verification

Both defects were reproduced against the defective code before being fixed, and every new test was
run against it: 6 of the OpenCode proof tests fail on v0.2.1's proven, and 6 of the migration
tests fail
on v0.2.1's move. 33 tests added across the two areas, covering evidence/,
model-lab.log, existing destinations, same-size-different-content conflicts, repeat launches, work
done after migrating, and a copy that fails mid-way.

Checksums

shasum -a 256 -c Cernum-0.2.2-SHA256SUMS.txt --ignore-missing

Opening an unsigned build

Not signed with a Developer ID and not notarized. Right-click the app → Open → Open, or:

xattr -dr com.apple.quarantine /Applications/Cernum.app

Cernum v0.2.1 — an installed CLI is reported as installed

Choose a tag to compare

@happySNAG happySNAG released this 17 Sep 15:08

Cernum v0.2.1 — an installed CLI is reported as installed

This is a corrective release. It fixes two defects that, together, made v0.2.0 report a provider as
absent on a machine where it was installed, authenticated and working.
There are no new features,
no behaviour changes to campaigns, scoring or evidence, and no change to any stored result.

If you are running v0.2.0, replace it. Nothing it recorded is wrong — but what it told you about
your own machine
could be, and there was no way to tell from the application which one you were
looking at.


What was broken

1. Provider discovery searched only PATH, so an app launched from Finder found nothing

findExecutable read process.env.PATH and stopped there. That is correct for a command you type in
a terminal and wrong for every other way Cernum runs.

A macOS application launched from Finder, the Dock, or open inherits launchd's environment —
on a machine with nothing set, /usr/bin:/bin:/usr/sbin:/sbin — and reads no shell profile.
Everything a CLI installer appended to your .zshrc is invisible to it. So claude in
/usr/local/bin, codex in ~/.local/bin and opencode in ~/.opencode/bin were all reported
notInstalled in the application's own provider view, at the same moment the same CLIs answered
normally in a terminal.

You can still see the old behaviour for yourself against a v0.2.0 bundle:

env -i HOME="$HOME" PATH=/usr/bin:/bin:/usr/sbin:/sbin \
  /Applications/Cernum.app/Contents/Resources/cernum providers

The fix. PATH is searched first and still wins, so a build you deliberately put ahead of
another is still the one that runs. Only when PATH has nothing does the search widen, to the
directories CLI installers actually write to:

~/.opencode/bin · ~/.local/bin · ~/.bun/bin · ~/.npm-global/bin · /opt/homebrew/bin · /usr/local/bin

Each is a single stat of a single filename. Widening where Cernum looks changes nothing about what
Cernum does:
it still drives only a CLI you installed and authenticated yourself, still never
installs one, still never reads its stored session. The absolute path it found is printed on every
status surface, so a match outside PATH is always visible as the unusual location it came from.

2. cernum discover opencodeCLI denied that OpenCode was a provider

The decision about which provider is discovered by which routine was written out twice — once in
the terminal command, once in the desktop application — and the two had drifted. The application
routed OpenCode correctly. The terminal had no branch for it, and answered:

cernum: 'opencodeCLI' is not a provider. Try claudeCLI, codexCLI, anthropicAPI or openaiAPI.

…about a provider the same build supports, two lines below where its own cernum providers output
had just listed it. Because discover is what writes down a proof, Union Alpha could not be
proven from a terminal at all, and so could not enter a campaign manifest.

The fix. One router in the engine, discoverProvider, is now the only place that decision is
made, and both surfaces call it. Adding the missing branch would have fixed the symptom and left the
shape that produced it. The list of discoverable providers is derived from the provider registry
rather than typed, so a provider added later is routed or is caught by a test that walks every
registered id — it cannot become silently unreachable from one surface again.

Also corrected

  • A sentence that had stopped being true. Three notInstalled messages said the executable "is
    not on this machine's PATH"
    . After fix 1 that understates the search and invites you to fix a
    PATH that was never the problem. They now read: "opencode was not found on PATH, nor in the
    directories CLI installers write to."
  • cernum providers and cernum help printed a provider list that omitted opencodeCLI — so the
    command nobody could run was also the command nobody was told to run. Both lists are now derived
    from the same registry.
  • cernum discover validates every provider name before running anything. discover claudeCLI opencode used to discover Claude, then fail, leaving half a run's evidence written.
  • The lockfile's own name and version, stale since the rename, now match package.json.

What did not change

No campaign, score, ledger, manifest, evidence store or published result is affected by this
release.
The defects were in how Cernum reported what is installed on your machine, not in how it
runs a campaign or what it records. Specifically unchanged: the scoring policy registry and its
digests, every suite identifier, the evidence store manifest format, all provider cost and billing
reporting, and the userData migration v0.2.0 introduced.

Your Model Lab data is untouched. ~/Library/Application Support/Model Lab/ and every campaign in
it are left exactly as they are, as they were by v0.2.0.

OpenCode remains an UNTESTED METERED API. It is discoverable, and that is all this release
changes about it. No scored Cernum campaign has been run through OpenCode, no OpenCode result is
published anywhere in this repository, and OpenCode's cost is reported as unavailable — not zero,
not free, not estimated.


Verification

1329 unit and parity tests (up from 1307), 21 E2E, typecheck clean, both macOS architectures
and the Windows pair built from the same commit.

22 tests were added, and each one was run against the defective code first. Eight of them fail on
v0.2.0's findExecutable; one fails on v0.2.0's terminal dispatch. The pre-existing OpenCode tests
all passed throughout the outage, because every one of them injected the executable lookup and called
the discovery routine directly — they tested the destination and never the route. The new tests
install a real executable in a real directory, under the PATH launchd gives an application, and
let discovery find it the way it must find one on a real machine; three of them run
cernum discover opencodeCLI as an actual subprocess.

They cover: OpenCode installed and authenticated · Union Alpha present · Union Alpha absent (reported
refused, never quietly omitted) · --version failure · models failure · a version line that is
not a version · spawn failure · a non-default install directory · PATH still winning · nothing
installed at all · every registered provider routing from both surfaces.


Checksums

Verify a download before you open it:

shasum -a 256 -c Cernum-0.2.1-SHA256SUMS.txt --ignore-missing

Opening an unsigned build

These builds are not signed with a Developer ID and not notarized, so macOS will say it cannot
verify the developer. Right-click the app → Open → Open, or clear the quarantine attribute
yourself:

xattr -dr com.apple.quarantine /Applications/Cernum.app

Cernum v0.2.0 — Test. Discern. Decide.

Choose a tag to compare

@happySNAG happySNAG released this 17 Sep 04:01

Test. Discern. Decide.

Model Lab is now Cernum. This release renames the product, opens it to frontier models, and adopts a second scoring policy generation that makes the evaluator measurably more accurate.

Full notes: docs/RELEASE-NOTES-v0.2.0.md


⚠️ Upgrading from Model Lab — your history is not lost

Application data folders are named after the application, so the rename moved them. The first launch of Cernum carries your campaigns, evidence store, settings and log across automatically and writes a migrated-from.json recording what moved.

It never overwrites and never deletes. If a file already exists on the new side, both copies are left alone and the log says so. A cross-volume move copies before removing the source, so a failure leaves the originals exactly where they were.

Before After
macOS ~/Library/Application Support/Model Lab/ ~/Library/Application Support/Cernum/
Windows %APPDATA%\Model Lab\ %APPDATA%\Cernum\

CERNUM_USER_DATA, CERNUM_CAMPAIGN_ROOT and CERNUM_OLLAMA_ENDPOINT are canonical; the old MODEL_LAB_* names still work.


Frontier models

v0.1.0's README said frontier models were "not supported — there is no API client, no key handling and no outbound internet path." That had been false for some time. It is corrected, and the table now distinguishes what has actually been exercised from what has not:

Provider Status
Ollama (local) ✅ Exercised
Claude subscription (claude CLI) ✅ Exercised — full scored campaigns
Codex subscription (codex CLI) ✅ Exercised — full scored campaigns
OpenCode (opencode CLI) ⚠️ Discovery only — never run a scored campaign
Anthropic API / OpenAI API ⚠️ Implemented, never used in a published campaign

Subscription and metered API support are not the same claim. A subscription request is reported as subscriptionIncluded — a zero marginal charge against a finite allowance — and never as free. A Codex CLI signed in with an API key is refused rather than used.

Union Alpha is available at OpenCode's own address, opencode/union-alpha. Availability is detected, never assumed: discovery asks three read-only subcommands and spends nothing. Cernum holds no pricing for OpenCode, so an OpenCode attempt's cost is reported unavailable — not zero, not estimated.

Muse is not a separate provider. Muse builds are local models over the Ollama-compatible transport on a configurable port (e.g. 11435), which this release verifies.


Scoring policy generation 2

Two corrections that had been built and left unreachable are now selectable as one generation. Measured by replaying all 608 stored responses, with no provider request:

Version 1 Version 2
Evaluator false positives (350 adjudicated rows) 201 33
Evaluator false negatives 4 4
Candidates carrying ≥1 governance violation 10 of 10 0 of 10

Version 1 is preserved exactly — still registered, still canonical, and it reproduces the stored status on 608 of 608 eligible rows. No stored row was modified. Adopting generation 2 moves candidates relative to one another, substantially; a companion restatement is published beside the untouched version-1 artifacts, and no candidate is routed or recommended on it.


Verifying your download

Cernum-0.2.0-SHA256SUMS.txt is attached. After downloading:

shasum -a 256 -c Cernum-0.2.0-SHA256SUMS.txt      # macOS / Linux
certutil -hashfile Cernum-Setup-0.2.0-x64.exe SHA256   # Windows

Builds are unsigned and not notarized. macOS will show "Cernum" Not Opened on first launch — see Opening an unsigned build. You cannot cryptographically verify an artifact's origin; build from source if that matters to you.

Which file

Your machine Download
Mac, Apple Silicon (M1+) Cernum-0.2.0-macos-arm64.dmg
Mac, Intel Cernum-0.2.0-macos-x64.dmg
Windows 10/11 x64 Cernum-Setup-0.2.0-x64.exe (installer) or Cernum-Portable-0.2.0-x64.exe

Apple menu → About This Mac: "Chip: Apple M…" means arm64.

Windows has still not been exercised on physical Windows hardware — the artifacts are produced from the same source and verified, but nothing has been run there.

Model Lab v0.1.0

Choose a tag to compare

@happySNAG happySNAG released this 06 Sep 14:31

Model Lab v0.1.0

The first public release.

Model Lab is a desktop benchmarking environment for comparing the language models that run on your
own computer. You pick some models, pick what to test, and every answer is judged by fixed
deterministic rules — never by another AI. What you get back is a ranking within that one benchmark
with every caveat stated, and a permanent local record of exactly what was asked, what came back, and
why it scored the way it did.

Nothing leaves your machine. No account, no telemetry, no update check.

What it does

  • Benchmarks local models through Ollama, plus a built-in deterministic
    reference model so you can walk the entire workflow before Ollama is even installed.
  • 14 versioned suites, 44 cases, 12 capabilities. A Quick check is 11 attempts per model; the
    Full lab is 46. You can also hand-pick suites.
  • Measures quality, reliability, median and p95 latency, tokens per second, and hard-boundary
    violations
    , and breaks all of it down per capability.
  • Judges deterministically: matching, required and prohibited concepts, refusal classification,
    JSON conformance, ordering, provenance labelling. Anything a rule cannot honestly judge is marked
    needs human review rather than guessed at, and is excluded from the score in both directions.
  • Records a hard boundary as disqualifying. Inventing a personal fact, claiming an action
    happened, silently changing a configuration or exceeding an authorised scope sinks a model to the
    bottom of the ranking regardless of how fluent it was.
  • Issues a recommendation under a fixed, published policy — recommended, not recommended,
    insufficient evidence, or disqualified — that is deliberately hard to earn, and that changes
    nothing anywhere by itself.
  • Keeps an append-only local evidence store. Nothing is ever overwritten; a re-run is a new run.
    Export the whole store as one JSON bundle with a digest at any time.
  • Compares any two past benchmarks, and says plainly when they are not like-for-like.
  • Shows missing data as missing. A value the runtime or the operating system did not report is
    n/a with the reason — never a zero, never an estimate.

Supported platforms

Platform Status
macOS 12+, Apple Silicon (arm64) Reference platform. DMG and ZIP.
macOS 12+, Intel (x64) DMG and ZIP, built from the same source.
Windows 10 / 11 x64 NSIS per-user installer and a portable executable. See Windows verification status below.
Linux Not packaged.

Supported model runtimes

  • Ollama on this computer — any model you have installed. Model Lab detects it, can start it, and
    can ask it to download a model after you confirm by name.
  • Built-in deterministic reference model — always available, no runtime required.

Not supported in this release: hosted API models (Claude, GPT, Gemini and similar), remote Ollama
hosts, and other local runtimes such as llama.cpp, LM Studio or MLX. Non-loopback endpoints are
refused by design, so that the hardware facts recorded with every result describe the machine that
actually ran the model.

Privacy

  • The only network connection is to Ollama on loopback. There is no telemetry, no crash reporting, no
    update check and no account.
  • Every benchmark prompt is a fixed synthetic fixture. Model Lab reads no files, messages, contacts
    or calendars, and contains no code that could.
  • Prompts, answers and results stay in your user application-data folder as plain JSON, and survive
    uninstalling the application.
  • If a model produces internal "thinking", only its length is recorded, never its content.

Known limitations

  • Builds are unsigned. macOS builds are ad-hoc signed but carry no Apple Developer ID and are not
    notarized; Windows builds are unsigned. Expect Gatekeeper and SmartScreen warnings — the README
    walks through both — and note that you cannot cryptographically verify a downloaded artifact.
  • You cannot author benchmark suites in the application. The suites are fixed and versioned so
    that results stay comparable, including with their own past selves.
  • Some sealed fixtures carry an older project's name. The engine was first written for a private
    application; its suite titles, system prompts and the reference model's stored name were sealed
    then and are pinned by the equivalence corpus and by every result already on disk, so they are not
    edited. The interface presents its own names over them, but the attempt drill-down shows the prompt
    verbatim, because that is evidence.
  • Two capabilities are partly human-judged. Conversational warmth and emotional attunement are
    marked needs human review; there is no screen for recording those judgments yet.
  • The suites reflect that origin. They lean towards assistant behaviour — memory honesty,
    privacy, safety boundaries — and are not a general coding, mathematics or translation benchmark.
  • Thermal state is never read, so a thermally throttled run looks simply like a slow run.

Windows verification status

Being precise, because "should work" and "was checked" are different claims:

  • ✅ The NSIS installer and the portable executable build — cross-built from macOS, both
    artifacts produced, with the installer licence page configured.
  • ✅ The engine is platform-independent TypeScript; nothing in src/core/ touches an operating-system
    API.
  • ⏳ Typecheck, the 200 unit and parity tests, the production build and the end-to-end suite on
    Windows are configured in CI (.github/workflows/ci.yml, windows-latest) but have not run
    even once
    — this repository has not been pushed anywhere yet. The first push closes these rows
    on its own.
  • ⚠️ Windows-specific code paths (CIM hardware query, %APPDATA% locations, Ctrl accelerators,
    starting Ollama) have been reviewed by reading the code only. They have never been executed.
  • ❌ Installing, launching and uninstalling the packaged application on a real Windows PC has not
    been done.
    scripts/windows-smoke.ps1 performs exactly that check and has never been run.
    Reports either way are the most useful contribution to this release.

In short: Windows support is written and its artifacts build. It has not been observed running.

Verification for this release

  • 200 unit and parity tests, and 6 end-to-end tests against the built application, all passing on
    macOS.
  • 157 of those parity assertions check byte-for-byte equality with a frozen reference corpus produced
    by an independent second implementation of the same engine. See docs/PARITY.md.
  • macOS arm64 and x64 packaged, with the bundle smoke test passing: structure, Info.plist,
    architecture, ad-hoc signature, and a Finder-equivalent launch storing data in Application Support.
  • Windows installer and portable executable built successfully.
  • The full user journey exercised against real Ollama models: install-to-results, live progress,
    cancellation, results, drill-down, recommendation, history and comparison.

Licence

MIT. Third-party components are listed in THIRD-PARTY-NOTICES.md.