Skip to content

Releases: MSKazemi/novafabric

v0.105.0 — Mocked replay that fails closed, a diff gate that tells a difference from a failure to compare, and one secret pipeline for every capsule

Choose a tag to compare

@github-actions github-actions released this 10 Oct 04:42

v0.105.0 — Mocked replay that fails closed, a diff gate that tells a difference from a failure to compare, and one secret pipeline for every capsule

Released 2026-10-10. A replay, diff and evidence-integrity release. Mocked replay now serves
much more of a recorded run (async, streamed and Responses API calls, recorded model errors,
recorded MCP results, declared Python tools) and fails closed when the re-run diverges.
nova diff keeps exit 1 for "found a difference" and uses 2 for "cannot compare". Every
capsule writer (nova capture, the framework adapters, @agent, OTLP ingest) now goes through
the same secret scan, residual pass and opt-in seal. This release changes behaviour in several
places. Read Behaviour changes and upgrade notes before
upgrading a CI gate.

Maturity labels follow the project convention: works today, experimental, planned,
future design. Each feature is labelled in the CHANGELOG entry for this
version; experimental features below stay experimental.

What's in it

Security and evidence integrity

Area What changed
One finalization path Framework-adapter, @agent and OTLP-ingested capsules now finalize through nova capture's path (capture/finalize.py). They get manifest redaction, the residual pass, the fail-closed manifest gate, lineage.jsonl and evidence_digests, and they are sealed when a signing profile exists (sealing stays opt-in). nova verify passes on them. A finalization failure never fails the wrapped call. The capsule is left unsealed with metadata.finalization_error.
Residual pass After the last write, every capsule file except capsule.yaml, the proof and .seal/ is rescanned before it is digested and sealed. This covers secrets written into replay.yaml, the C2PA marker or by an ADR-0135 masker. The final manifest is checked once more. If it still matches a rule, the capsule is not sealed. Recorded in the new optional residual_check block of redaction-proof.json. Scanner 0.4.0.
Rule pack 0.7.0 AWS access key ids, AWS secret access keys (only when their key name is next to them), GitHub tokens (ghp_, gho_, ghu_, ghs_, ghr_) and fine-grained PATs (github_pat_) are now detected. 18 rules, up from 14. The run-capsule architecture page lists what the scanner still does not detect, and a test keeps that list honest.
Maskers and capture-health.json ADR-0135 maskers (experimental) now walk the same files as the built-in scanner. capture-health.json is written before the residual pass, so it is scanned and bound by evidence_digests.
Audit log Every audit-log reader and writer resolves the path at call time: NOVAFABRIC_AUDIT_LOG_PATH, else $XDG_DATA_HOME/novafabric/audit.jsonl, else ~/.local/share/novafabric/audit.jsonl. Before, most writers ignored the override.
Sealing and verify nova seal init (experimental, ADR-0301) sets up opt-in sealing in one offline command with a local, self-asserted identity. nova verify reports the signer's trust level (identity_trust) and gains --json. timestamp_ok is null when there is no timestamp. ca_chain_ok is true only when --ca-bundle validated the chain. A key that does not match its certificate no longer produces a seal that can never verify.
Fixes nova export-evidence --timestamp bundles now pass their own verification recipe. nova init --force archives the old signing key instead of overwriting it. nova verify reports, instead of crashing on, a DSSE signature that is not valid base64. Locked dependencies clear every pip-audit finding, and the three npm trees pass the npm-audit gate (one time-boxed waiver for a dev-only chain).

Replay

Area What changed
Fail-closed mocked replay (ADR-0300) A replay that makes more model calls than were recorded, calls another provider or in another order, calls an unsupported surface, makes an unrecorded MCP call or leaves recorded responses unconsumed ends failure (ReplayDivergence, exit 1). --permissive keeps the old warn-and-continue outcome and still records every divergence. New replay_contract block and counters in the replay result.
More surfaces served (ADR-0304) OpenAI chat.completions.create, OpenAI responses.create and Anthropic messages.create are served sync and async, streamed or not. Live network connections made by the replayed process are reported, never blocked. A generated support matrix in replay modes says what is served, refused or not controlled.
Recorded model errors (experimental) A recorded rate limit, 4xx, 5xx, timeout or connection error is raised again as the SDK's own exception class, from an allow-list. A stream that failed part-way is served as its delivered chunks, then the same exception.
Tool results Recorded MCP call_tool results are served one-to-one (ADR-0300). record.tool (experimental, ADR-0306 slice 1) captures declared Python functions and serves their results before the body runs. replay.yaml tool_overrides (experimental, ADR-0306 slice 2) are enforced inside the replay, through the same decision --dry-run prints. Other tools still run live, so run replays in a sandbox.
Logical calls (ADR-0305) The httpx wire record under an SDK call is marked transport and linked to the SDK record. Counts, nova cost, nova diff, replay counters, the dashboard and the exporters read logical calls only.
Refusing what cannot be re-run A framework-adapter, @agent or OTLP-imported capsule has a label, not a program, as its command. mocked replay now refuses it up front (CapsuleNotReplayable) instead of trying to execute the label. intervention records whether a substituted tool result reached the workload (it does not; tools run live).

Diff and CI gate (ADR-0303 and Amendments 1 and 2)

Area What changed
Exit codes 0 no difference (or no gate), 1 the gate found a difference, 2 cannot compare. An unresolvable ref, an unreadable or malformed capsule.yaml or env.lock, and malformed record lines under the gate are all exit 2. --help and the CLI reference list every code.
What counts as a change A paired model call is compared on its provider and every recorded gen_ai.request.* parameter (temperature, seed, max_tokens, …), not only model, messages and response. Nested files under outputs/ are compared.
Output The JSON report carries has_changes and skipped_malformed_lines. --json means --output-format json in every mode. name@version diffs honour --output-format. github-annotation reports added-only or removed-only diffs as errors, and annotation messages are escaped. GET /api/diff returns the counts and has_changes.

Capture

Area What changed
Runner, image and Slurm job (experimental, ADR-0307) capsule.yaml records host.runner (the runner name; for docker and kubernetes, the image reference and the digest the runtime resolved, never taken from the tag) and, inside a Slurm job, host.slurm from an explicit allow-list (node list stored as a hash).
SDK calls Async, streamed and Responses API calls are recorded with their response. Each SDK record names its API surface. A stream that never delivered a finish reason is recorded with finish_reason: null. Anthropic finish reasons are stored in the schema enum, with the provider's raw value kept in an extension.
Framework adapters (experimental) LlamaIndex, Pydantic AI and LangGraph streams are captured for the whole stream. An abandoned or cancelled stream is recorded as status: partial. LlamaIndex WorkflowHandler runs and Haystack AsyncPipeline runs are captured once and completely. A bare adapter run id now resolves in nova replay, nova diff and nova validate.
Host facts Every capsule writer measures host with one function. Adapter capsules no longer record x86_64, cpu_count: 1 or memory_bytes: 0 regardless of the machine. macOS and Windows record real memory. env.lock names the real NovaFabric version. A command that never started records error.type: WorkloadNotStarted.

CLI, packaging and docs

  • --json is plain JSON. 38 JSON output paths no longer carry ANSI colour codes when colour
    is forced. Eleven commands no longer print notes after their JSON on stdout.
  • --help examples use real flags and IDs. nova classify run, nova eval agent,
    nova eval run, the nova audit commands, nova serve and nova export-examiner pccp are
    corrected.
  • Every documented command resolves. A new guard checks every nova … invocation in
    README.md, llms.txt, docs/ and examples/**/*.md against the real command tree. It found
    and fixed 54 that did not exist.
  • Public claims match the code. CITATION.cff, .zenodo.json, the GitHub Action and the
    Claude Code plugin say secret-scanned and sealable, not "secret-redacted" or "signed", and
    name five replay modes. A test reads the rule count and the replay modes from the code.
  • The wheel ships the dashboard only (ADR-0299). nova serve --experimental no longer
    carries a copy of the website. The public pages live on https://novafabric.ai. The dashboard
    source moved from web/ to ui/dashboard/.
  • Collector. go install github.com/MSKazemi/novafabric/collector/cmd/novafabric-collector@latest
    works: the Go module now declares the path its code lives at.
  • Server mode. Alembic migration v005 grants the application role DELETE on runs,
    capsules and signatures, so governed delete works as novafabric_app under row-level
    security.

Behaviour changes and upgrade notes

Each item below is flagged as a behaviour change...

Read more

v0.104.0 — Secret scanning of every capsule file, run-to-run diff that pairs separate captures, and replayable tool-calling turns

Choose a tag to compare

@github-actions github-actions released this 08 Oct 00:21

v0.104.0 — Secret scanning of every capsule file, run-to-run diff that pairs separate captures, and replayable tool-calling turns

Released 2026-10-08. A correctness and security release. It closes a secret-redaction gap,
makes nova diff compare two separately captured runs, and lets mocked replay serve a recorded
tool-calling turn back. Read the upgrade notes: nova diff --assert-no-regressions can now
fail a CI step that passed before.

Maturity labels follow the project convention: works today, experimental, planned,
future design. Features below are labelled in the CHANGELOG entry for
this version.

What's in it

Area What changed
Secret scanning (security) Every file ADR-0009 names is now scanned before the capsule is finalized: outputs/**, inputs/**, env.lock, assets.jsonl, lineage.jsonl, plus the manifest. Before, a key the workload printed stayed in outputs/stdout.txt, and a key on the command line stayed in capsule.yaml and in the signed seal payload. The manifest is now redacted before it is written or signed.
Rule pack 0.6.0 (security) OpenAI project, service-account and admin keys (sk-proj-…, sk-svcacct-…, sk-admin-…) were not detected at all; Anthropic keys longer than 87 characters were masked with their tail left visible; Langfuse secret keys (sk-lf-…) were not detected. All three are fixed.
Diff Model calls from two separate captures now pair (span match, then sequence position anchored on identical requests). Before, every call showed as one added plus one removed. Tool calls still match order-independently, then by position with the same tool name, and report arguments_changed.
CI gate --assert-no-regressions exits 1 on any change, including added or removed calls, as documented; the name@version asset path now honours it too.
Replay Capture records the assistant's tool-call requests (OpenAI tool_calls, Anthropic tool_use), and mocked replay serves them back, so a function-calling agent sees its own requests. Tool responses are still not substituted: tools run live. Running out of recorded responses now warns.
Dashboard /api/adapters lists all 11 framework adapters (three were missing).
Docs Comparison page, feature tour, standards table, GitHub Action README, both tutorials and README corrected. Prompts and responses are stored in the capsule (only OTel export of content is opt-in); sealing and RFC 3161 timestamping are opt-in; there are five replay modes; a mocked replay is not a new capsule.

Upgrade notes

  • nova diff --assert-no-regressions may now fail where it passed. It counts added and
    removed calls, and calls from two captures now pair, so a model-behaviour change between
    captures is reported as a change. That is the documented contract; review gated pipelines.
  • A binary file in inputs/ or outputs/ that contains a detected key is dropped from the
    capsule
    and recorded in redaction-proof.json (ADR-0009). Text files are redacted in place.
    Files over 64 MiB are recorded as skipped, not scanned.
  • Redaction-proof targets now list artifact files, and the scanner version is 0.3.0 /
    pack 0.6.0. The proof schema is unchanged (the target kinds already existed).
  • Model-call records may carry message.tool_calls in response choices — the existing
    optional Message.tool_calls field of model-call.schema.json. Absent on text-only turns.
  • Replay results no longer record a --mock-tools flag, which never existed.
  • Capsules written by earlier versions validate and verify unchanged. Re-capture a run to get the
    wider redaction coverage; existing capsules are not rewritten.

Verification

make test-par on the release tree (coverage floor ≥ 90%): 17,885 passed, 147 skipped,
2 xfailed, coverage 93.78%
. ruff check and mypy src (964 files) clean; web/ type-check and
unit tests clean. Three failures and nine errors, all diagnosed:

  • Two release-metadata guards (release notes and the README link to them not yet tracked by
    the public git) — they pass once this release commit tracks the file.
  • One no-op-overhead timing assertion under parallel load; it passes on rerun.
  • ⚠ Not verified locally: the nine JanusGraph container-parity tests need a Docker daemon,
    which the release machine did not have; they run in public CI. New tests: tests/capture/test_redaction_full_coverage.py,
    tests/test_diff_alignment_corpus.py, tests/test_replay_tool_call_requests.py, and an adapter
    registry drift test. The diff and gate fixes were also checked on two real captures of
    examples/blackbox_demo: the model-only change now exits 1 (it exited 0 before).

v0.103.0 — governed delete, opt-in timestamping, spec-conformant seals, and a dashboard that explores capsules

Choose a tag to compare

@github-actions github-actions released this 04 Oct 11:41

v0.103.0 — governed delete, opt-in timestamping, spec-conformant seals, and a dashboard that explores capsules

Released 2026-10-04. The first release since v0.102.1 and the largest since v0.102.0: about
twenty parallel development streams, merged and gated together. It closes real defects in
sealing and verification, makes several defaults safer, and adds a governed delete path, OTLP
log ingest and a much richer dashboard. Six behaviour changes are listed under Upgrade notes
— read them before upgrading a server.

Maturity labels follow the project convention: works today, experimental, planned,
future design. Features below are labelled in the CHANGELOG entry for
this version.

What's in it

Area What changed
Sealing and verification New NovaSeal seals sign the DSSE envelope over the spec PAE, so stock DSSE tooling verifies them (legacy seals still verify). RFC 3161 tokens are bound to the envelope they timestamp. nova verify no longer needs the sealer's own Merkle log, so an independent auditor can verify a capsule. Timestamping is opt-in (ADR-0292).
Encryption at rest Envelopes are bound to their object and plaintext reads from an encrypted store are refused; a digest-pinned legacy inventory and a v1 strict mode ease migration.
Governed delete (ADR-0206 P2) Capsule delete and index delete share one governed path; holds, WORM locks and sealed capsules are refused; a failed rollback can no longer destroy the only copy; the tombstone reaper keeps indexed or unfinished deletes.
Dashboard Runs filter bar with URL view state, capsule explorer (summary, Integrity, Lineage), a Dashboards view with a validate-and-apply write path, evidence-cart export, accessibility fixes from a real-browser axe pass.
Ingest and server OTLP logs ingest into an append-only sidecar; per-org usage budgets and API-key workspace-binding enforcement; keyset pagination for runs, assets and lineage nodes.
CLI nova replay --environment, nova diff --environment / --group-by environment, nova consent attest, verify-attestation and withdraw.
Capture Strict capture mode (opt-in); capture scope follows threads and pool items; the Google ADK adapter runs on a real Runner.
Correctness A custom NOVAFABRIC_HOME is honoured everywhere through one resolver; a failed GDPR erasure no longer reports success; an unpriced model no longer reads as $0.00.

Upgrade notes

  • RFC 3161 timestamping is opt-in. There is no default TSA any more. To keep timestamping,
    set tsa_url: (or tsa_urls:) in novaseal.yaml. Earlier capsules verify unchanged.
  • Deleting a sealed capsule is refused (409 sealed_capsule). Set
    NOVAFABRIC_ALLOW_SEALED_DELETE=1 on the server to restore the old behaviour; it never
    overrides a legal hold or an unexpired WORM lock.
  • Plaintext reads from an encrypted object store fail. Use the legacy inventory and v1
    strict mode described in the server admin guide to migrate.
  • Refused aggregate figures are null, not 0 (ADR-0234 D2) — consumers that summed them
    as zero must handle null.
  • Invalid pagination cursors return 400.
  • New seals use the DSSE spec PAE. Verifiers older than this release cannot check new
    seals; this release still verifies legacy ones.
  • No capsule or evidence-bundle schema change. Local mode needs no server and no network.

Verification

make test-par equivalent on this tree (coverage floor ≥ 90%): 17,823 passed, 148 skipped,
2 xfailed, coverage 93.77%
. Nine tests failed and nine errored on that first run; all were
diagnosed rather than waved through:

  • Version bump left api/openapi-dashboard.yaml stale (installed package metadata still
    read 0.102.1) — regenerated; the same class v0.102.1 recorded.
  • Release notes not yet publicly tracked (two guard tests) — they now are.
  • Seven tests needed python on PATH (daemon worker, nova replay mocked mode, OTel
    emit) — an invocation artefact of running the venv's pytest directly, not a code defect.
  • One no-op-overhead timing assertion (1.0036 s against a 1.0 s budget) under 6-way
    parallel load; it passes on rerun.

After those fixes the eight affected test files rerun green (84 passed). ⚠ Not verified:
the nine JanusGraph container-parity tests need a Docker daemon, which the release machine
did not have; they run in public CI. Ruff and mypy src (963 files) were clean.

v0.102.1 — the release pipeline actually publishes

Choose a tag to compare

@github-actions github-actions released this 27 Sep 13:18

v0.102.1 — the release pipeline actually publishes

Released 2026-09-27. v0.102.0 looked like a clean release — a tag, release notes, green
CI — and shipped almost nothing. No PyPI package, no Helm chart, and a container image that
was published unsigned. The repository had moved from the novafabric organisation to
MSKazemi seven weeks earlier, and every part of publishing that named the old identity had
been quietly broken ever since. Nothing failed until a tag fired. This release repairs the
pipeline, corrects the paths users were being handed, and adds a guard behind each fix so the
next move of the repository cannot repeat it.

What's in it

Area What changed
PyPI publishing The trusted publisher was still registered to the pre-transfer repository path, so the OIDC claim repo:MSKazemi/novafabric… was rejected and 0.102.0 never reached PyPI. Re-registered against the real repository; the stale grant — a standing publish right for a path this project no longer controls — was removed.
Helm chart (breaking for the chart's default image) publish-chart.yml hardcoded oci://ghcr.io/novafabric/charts; a repo-scoped GITHUB_TOKEN cannot write another owner's package, so the push failed outright. The namespace now derives from github.repository_owner, lowercased for GHCR.
Image signing An OCI repository name must be lowercase and github.repository is not. docker/metadata-action lowercases its own input, so the build and push succeeded and hid the problem while cosign failed on parsing reference. The reference is resolved once and reused by signing, attestation and both scans.
Vulnerability gate The same raw reference fed the ADR-0186 trivy gate that blocks a release on fixable CRITICAL findings — it could not resolve its image, so it was not gating. Fixed by the same change.
Documented install paths 18 live references in docs/ops/, deploy/helm/ and deploy/k8s/ still named the unreachable namespace, including the chart's own default image.repository — so helm install produced ImagePullBackOff and every documented docker pull returned 401. Corrected; docs/releases/ was left alone, as those record what was true per release.
CI on the private mirror .github/ is part of the shared tree, so the mirror re-ran the entire suite on billable minutes — CI five times, every one failing, one for 40 minutes. All 35 jobs are now confined to the public repository.
Colourised help assertions Rich emits ANSI escapes inside option names when colour is on, so nine --flag assertions passed locally and failed only in CI. They now assert the rendered text. Two were additionally vacuous — a missing assert keyword meant the check never ran at all.

Upgrade notes

  • No API, schema or CLI change. Every fix is to the release pipeline, its guards, or the
    documentation. Capsules, evidence bundles and the public interface are untouched.
  • If you install the Helm chart or pull the image, update the path you use. The working
    namespace is ghcr.io/mskazemi/…; the old organisation path is not publicly readable.
    Only 0.102.0, 0.102 and latest exist there — earlier tags remain at the unreachable
    path and are not being backfilled.
  • ghcr.io/mskazemi/novafabric:0.102.0 is unsigned. That image shipped before the signing
    fix, so cosign verify cannot succeed for it. This release is the first with a signature.

Verification

Each fix carries a guard, and every guard was attacked before being trusted — mutation red,
restore green:

  • test_registry_namespace_matches_ci.py — no workflow hardcodes a GHCR namespace, live docs
    name the public remote's owner, and no OCI reference is built without lowercasing.
  • test_workflows_run_only_on_the_public_repo.py — checks the guard is effective, not merely
    present: A || B && guard binds as A || (B && guard) and still runs on the mirror.
  • test_release_metadata_is_consistent.py — CITATION.cff, the CHANGELOG heading and the
    release-notes file must agree with pyproject.toml, and the notes file must be publicly
    tracked.
  • test_help_assertions_strip_ansi.py — narrowed after a first version flagged 12 working
    assertions, then hardened against two of its own bypasses.

make test-par (byte-for-byte the CI unit job), run on this tree: 17,306 passed,
0 failed, 59 skipped, 2 xfailed, coverage 93.99%
(≥90% floor), in 13m48s. Public CI was
green on the commit this release is cut from, including the unit job that the
help-assertion fix repairs.

The gate earned its place twice while cutting this release. It caught that the version bump
had left both api/*.yaml contracts stale — the same staleness v0.102.0 shipped with — and it
caught two daemon tests whose Thread.join(timeout=3) does not raise on timeout, so under
20 workers they asserted against an empty list and blamed the signal rather than the
unfinished thread. Neither was re-run until green; both were diagnosed and fixed.

⚠ One gap is documented rather than closed: the local gate runs with FORCE_COLOR unset while
CI enables colour, so the ANSI class is invisible to make test-par at runtime. The guard
catches it statically; runtime parity remains an open decision.

v0.102.0 — thirty ADR slices, a deployable dashboard, and an honesty sweep

Choose a tag to compare

@MSKazemi MSKazemi released this 26 Sep 22:28

v0.102.0 — thirty ADR slices, a deployable dashboard, and an honesty sweep

Released 2026-09-27. Three through-lines. nova serve authenticated but never
authorized, and shipped from a chart whose default exposed signed evidence over plain
HTTP — both are fixed, and the chart now refuses to upgrade until you state your intent.
Thirty accepted-but-unbuilt ADR slices were built, in three batches, every one marked
experimental. And a long sweep went after the worst class of defect this project can
have: a surface that reported a verdict it could not support. Most of what is new here is
experimental and interface-unstable; the corrections are not.

What's in it

Area What you can do now
serve authorizes (ADR-0228, experimental) Possession of the one shared bearer token no longer grants every endpoint — including DELETE /api/runs/{id}, PII erase, seal bypass and POST /api/admin/roles. Scopes are enforced and an unclassified route denies everyone. The roles UI previously let an operator assign a role, see it listed, and believe access was restricted while nothing in serve/ ever read an assignment. Additive; the default local posture is byte-identical.
serve tenancy (ADR-0229, experimental) serve reads eight stores that do not share a tenancy model; each now declares aware/agnostic/unsafe, and the declaration is checked against the store's real schema rather than against itself. Multi-tenant is refused at startup rather than served unscoped. Single-tenant is the default and unchanged.
Secure-by-default chart (ADR-0230, breaking) mode: server with OIDC/RBAC and serve.insecure: false. Setting insecure: true makes helm template fail, not warn. mode: dashboard stays fully supported when deliberately chosen — the problem was the default, not the option.
The dashboard becomes data (ADR-0231–0236, 0239, experimental) Portable versioned dashboard/widget JSON (nova dashboard); a filter-bar grammar whose power ceiling is enforced by the DSL itself; nova query --scope node|root|tree; ratio(a, b) where undefined is not zero; the evidence cart, which resolves once so a collected set attests to a state that existed; and an audit log that records who acted, what changed, and what was refused.
Trust chain, offline (ADR-0055, ADR-0070, experimental) x509 signer-certificate chain validation against an operator CA bundle, RFC 3161 TSA trust-chain verification, and offline CRL revocation checking — nova verify --ca-bundle, --tsa-ca-bundle, --crl-dir/--crl-strict.
The assurance loop closes (ADR-0147, experimental) nova assure-baseline, assure-canary record, assure-alarm check, assure-impact report, assure-run, plus nova replay-equivalence check|regime and nova drift collect|fingerprint. The C3 behavioural-equivalence verdict was built and tested since v0.59.0 with no CLI to reach it; it is reachable now.
Media & computer-use provenance (ADR-0148, experimental) nova capture-ui treats keystrokes as the hazard they are (scan-before-digest, per-capsule salt, stated residual risk); nova provenance binds a C2PA manifest to the exact bytes of a captured media part; nova diff --media compares by exact hash and optionally pHash; nova toolschema conformance|deprecations|track.
Human oversight and consent (ADR-0150, experimental) Decision-context receipts, overrides and rationale, consent receipts, accountability handoffs and acted-on-behalf records — nova hitl, nova consent.
Regulated-domain exporters (ADR-0159, ADR-0208, experimental) nova export-cat (CAT-style lifecycle trail), nova export-adverse-action (ECOA / Reg B specific reasons), validation-independence and retention-posture packs, plus nova server usage reconcile|export and GET /v0/usage/export for chargeback.
The rest of the slice program (experimental) Session index, portable session bundle and sub-range replay; A2A object mapping and Agent Card facet; embodied ODD, sim-to-real, teleop and timing evidence; agentic-science lab and instrument provenance; settlement payment chains; risk-transfer liability/SLA/coverage; shared-store access ledger and cross-run memory derivation; format-migration and LTV re-seal records; transitive nova trust-path; OTel GenAI severity in both directions; and an API deprecation removal gate.
The honesty sweep The SLURM runner had been sealing success for cancelled, timed-out and OOM-killed jobs. nova erasure request reported a GDPR Art.17 erasure it never performed. Energy receipts claimed confidence: measured on hardware with no counter. A DSSE envelope's second signature was neither honoured nor detected. Azure Key Vault seals were silently unverifiable. nova verify now checks the capsule on disk is the one that was sealed. An unpriced model reported $0.00, which reads as free. The published v0.38.0 Scale-S4 latency claim is withdrawn — it was never met. The public decisions index was under-claiming eleven shipped features.
Security, supply chain and onboarding The Kubernetes runner no longer ships your shell's environment into the cluster; the sdist and Docker build context no longer carry the private tree; the runtime image no longer runs as root; the capsule watcher's queue is bounded and degrades to rescan rather than dropping events; npm and gRPC-Go advisories patched with a standing audit gate. Capture adapters for LlamaIndex, Pydantic AI and Haystack (issues #1–#3), three runnable examples, an integrations guide, docs/for-researchers.md, and a CLA.

Upgrade notes

  • BREAKING — --runner kubernetes no longer inherits the submitting environment. A
    workload that implicitly relied on a variable will now start without it. Migration:
    name it explicitly with --runner-option extra_env='{"LOG_LEVEL":"debug"}'. For anything
    secret do not use extra_env — its values land in the Job object — bind a
    service_account to a Kubernetes Secret instead. See docs/operator-guide.md §3.4.
    --runner slurm and --runner docker are unchanged; they already filtered, and their
    open-coded filters were replaced by the shared helper so the three cannot drift apart.
  • BREAKING (Helm chart) — the deployed default is secure now. An upgrade refuses
    until you state your intent. To keep the old behaviour, set mode: dashboard,
    serve.insecure: true, serve.acknowledgeInsecureExposure: i-accept-serving-evidence-over-plain-http and upgradeAcknowledged: true. To take the
    new defaults, confirm you have read this with upgradeAcknowledged: true. A fresh
    install needs neither.
  • Everything else is additive. No change to schemas/run-capsule.*; new facet fields
    and records ride existing extra="allow" facets, so every pre-existing capsule and
    envelope stays valid. serve authorization and tenancy are additive and leave the
    default local posture byte-identical.
  • Most of what is new is labelled experimental — it ships and works, but the
    interface may change before the v1.0 schema freeze. The corrections in the honesty sweep
    are not experimental.

Verification

make test-par (byte-for-byte the CI unit job): 17,282 passed, 0 failed, 59
skipped, 2 xfailed, coverage 93.99%
(≥90% floor), in 19m13s.

The release gate itself caught three defects the faster tier structurally cannot see,
all of them introduced by preparing this release: api/openapi.yaml and
api/openapi-dashboard.yaml embed the package version and had gone stale against the
bump, and README linked a release-notes file that existed on disk but was not yet
tracked by the public git — a dead link for every reader of the published repository.

v0.101.0 — the enterprise-grade program: ten first slices

Choose a tag to compare

@github-actions github-actions released this 07 Aug 04:06

v0.101.0 — the enterprise-grade program: ten first slices

Released 2026-08-07. One theme: before this release, a NovaFabric deployment was
single-instance, single-key, single-region, best-effort-on-restart, and contractually
silent
. Ten design decisions (ADRs 0240–0249) each got their first working slice —
every one additive, off-by-default where it changes behavior, and honest in its docs
about what stays planned.

What's in it

Area What you can do now
Durable jobs (ADR-0242) Replay jobs survive restarts and report correct status across --workers processes; interrupted work reads failed with an explicit error — never silently lost. The novafabric.jobs store/runner (lease-claimed, worker-guarded, bounded retries) is the substrate the other job kinds migrate onto.
Native TLS (ADR-0241) nova server start and nova serve --tls-cert/--tls-key terminate TLS themselves — for HPC nodes and VMs with no reverse proxy. Fail-closed launch validation; proven by a real HTTPS round-trip test.
Writer lease + fencing (ADR-0244) The split-brain-proof core: a Postgres-anchored (SQLite-twinned) single-writer lease whose fencing token bumps on every holder change. Automated promotion stays planned; manual failover is now hardenable.
Per-tenant keys (ADR-0243) Each tenant's capsules can wrap DEKs under that tenant's own KEK; removing one tenant's key makes exactly that tenant's data unreadable — fail-closed, named error, tenant-scoped blast radius.
Jurisdiction routing (ADR-0247) Capsule writes route to the backend registered for the tenant's jurisdiction, fail closed when unroutable; cross-border reads deny by default through the (previously unconsumed) ADR-0077 residency gate. New: docs/ops/data-residency.md.
Step-up auth + audit fix (ADR-0246) Role grant/revoke now writes the hash-chained audit log. Opt-in step-up: destructive calls (role mutations, capsule delete) require fresh OIDC auth or get 401 step_up_required.
Dashboard API contract (ADR-0240) api/openapi-dashboard.yaml — the serve surface (198 routes) generated, drift-guarded, with a shrink-only annotation ratchet (195 pending) and the legal-holds family fully declared + conformance-tested.
SLO catalog (ADR-0248) docs/slo.md — every published performance number labelled gated/measured/target; the three CI gate files read their thresholds from the same catalog, so a claim and its gate cannot disagree; stale measurements expire visibly.
Support policy (ADR-0245) docs/support-policy.md — what "supported" means today (latest tag), what upgrading promises (the N/N+1 gate), the runtime matrix, and the v1.0 LTS intent. The documented release gate now names every CI job, guarded.
Air-gap bundle (ADR-0249) make airgap-bundle + an offline verifier: one tar with a DSSE-signed manifest; a flipped byte fails naming the member; unsigned stowaways are findings.

Upgrade notes

  • No breaking changes. All schema changes are additive (EncryptedBlob.tenant_key_id,
    jobs/lease tables are new); every pre-existing envelope, capsule, and API response shape
    stays valid. New behaviors (TLS, step-up, tenant keys, jurisdiction routing) are opt-in.
  • Replay job state now lives in $NOVAFABRIC_HOME/jobs.db; the old in-memory job table
    simply disappears (it never survived a restart anyway).
  • New env vars, all optional: NOVA_TLS_*, NOVAFABRIC_SERVER_STEP_UP_*,
    NOVA_OBJECT_STORE_TENANT_KEK_DIR, NOVAFABRIC_JOBS_DB.

Verification

make test-par (byte-for-byte the CI unit job): 11,864 passed, 0 failed, coverage
92.85%
(≥90% floor). Seal p99 and capture-overhead p95 gates green. The release-gate
sweep itself caught and fixed five integration misses the per-item tiers could not —
including two new audit event types missing OCSF mappings, flagged by the SIEM totality
guard doing exactly its job.

v0.100.1 — the container image builds again

Choose a tag to compare

@github-actions github-actions released this 05 Aug 20:31

v0.100.1 — the container image builds again

2026-08-05 · Apache-2.0 · pip install novafabric==0.100.1

A patch release with no library changes. It exists because two workflows can only
fail at release time, and tagging v0.100.0 was the first thing in weeks to exercise them.

If you install from PyPI, v0.100.0 already has everything you need — this
release only matters if you pull the container image.


Fixed — the container image has been unbuildable since v0.99.0

v0.99.0 added force-include entries for the canonical JSON Schemas that runtime
validators load (BL-037) but no matching COPY in deploy/docker/Dockerfile, so the
wheel build inside the image failed:

FileNotFoundError: Forced include not found: /build/schemas/export-manifest.schema.json
error: Failed to build `/build`

This is the second instance of the same omission. The identical mistake broke the
image for alembic/ on 2026-07-24, and the Dockerfile's own comment records it.

The class is unpleasant because it is invisible everywhere except a container build:
uv build on a developer machine succeeds, because there the repository root is the
build context. Only Docker's narrower context exposes the gap.

tests/packaging_metadata/test_dockerfile_force_includes.py now asserts that every
force-include source both exists and is copied into the build context, so a third
instance fails in the test suite rather than at a release tag.

Fixed — the MetadataStore Security Gate could not pass

It ran uv sync without --all-extras, so mypy could not resolve psycopg_pool or
alembic.command and reported two missing installs as if they were type errors.

Fifth instance of the class BL-022 named. The guard therefore no longer names one job
at a time — it scans every workflow that runs mypy or pytest. The two benchmark jobs
stay exempt by design: they measure capture overhead, and installing extras changes what
gets imported and skews the numbers.

Fixed — the manual re-publish escape hatch was itself broken

publish-image.yml offers workflow_dispatch and its comment promises "a manual
re-publish from the Actions tab if a tag run needs a retry". But the only tag rule was
type=semver, so a dispatch from a branch derived no tags at all and buildx failed
with tag is needed when pushing to registry — the escape hatch failed in exactly the
situation it exists for.

Branch dispatches now fall back to a branch tag and a short-SHA tag. Dispatching with
--ref v1.2.3 still takes the semver path unchanged.


Upgrading

Nothing to do for library users. For the image:

docker pull ghcr.io/novafabric/novafabric:0.100.1

Known issues

Unchanged from v0.100.0: #23
(server mode register_run() after the packaged Postgres migrations) and
#21. Both are pre-existing and
neither affects local mode.

v0.100.0 — the release that makes the repository usable by outsiders

Choose a tag to compare

@github-actions github-actions released this 05 Aug 20:18

v0.100.0 — the release that makes the repository usable by outsiders

2026-08-05 · Apache-2.0 · pip install novafabric==0.100.0

No runtime behaviour changes for existing capsules. This release is about two things:
a repository a stranger can actually contribute to, and a release pipeline that
delivers.

Read this first if you are upgrading: v0.98.0 through v0.99.0 were tagged but
never reached PyPI — a broken step in the publish workflow blocked the upload for
five consecutive releases. If you installed from PyPI, you have been running 0.97.0
regardless of what the repository said. This release fixes the pipeline and carries
every fix from those versions with it.


Fixed — the release pipeline never published

Generate SBOM (syft) failed on five consecutive release tags starting 2026-07-31.
Because it was a blocking step it took the whole build job down, so the dependent
publish job never ran.

The consequence was invisible and serious: the three defects v0.99.0 fixed for released
installs
— schemas missing from the wheel, ~1.5% of API keys unrotatable, concurrent
captures cross-filing into each other's capsules — never reached a single user.

The step is now continue-on-error with an explicit warning annotation. A supply-chain
nicety must not be able to block the supply chain
; a broken third-party action should
show up as a failed step, not as a silent non-release.

Security — three HIGH-severity advisories cleared

Package From To Cleared
cryptography 48.0.1 50.0.0 PYSEC-2026-3552, PYSEC-2026-3553 (HIGH), PYSEC-2026-3554
aiohttp 3.14.1 3.14.3 PYSEC-2026-3545 (HIGH), plus two moderate

cryptography underpins NovaSeal signing, so the major bump was verified, not
assumed
: 621 tests across seal/, trust/, evidence/ and supplychain/ pass, and
the p99 seal-latency gate holds at 6.44 ms against its 200 ms ceiling. Transitive
updates: sigstore 4.5.0, pyopenssl 26.4.0, msal 1.37.0.

Fixed — Postgres migrations could not run as documented

alembic -c alembic-postgres.ini upgrade head — the command in the runbook — failed with
ModuleNotFoundError: No module named 'psycopg2' for any ordinary postgresql:// DSN.
SQLAlchemy resolves the bare scheme to psycopg2; NovaFabric ships psycopg[binary]
(psycopg 3).

It survived because every other consumer passes the DSN straight to psycopg.connect(),
where it is correct — only the SQLAlchemy path was affected. New
novafabric.metadata_store.dsn.to_sqlalchemy_url normalises bare postgresql:// and
postgres:// to postgresql+psycopg:// and leaves an explicitly named driver alone.

Fixed — CI had been failing for weeks in three different ways

  • integration had not executed a single test since at least 2026-07-30. It installed
    without --all-extras, so alembic was absent and the migration step died first.
  • web could not build the site on any run. It pinned Node 20; Astro 7 requires
    ≥ 22.12. web/package.json also declared >=20.0.0, so the pin agreed with the
    declaration and the declaration was what was wrong.
  • unit had never completed — 30 consecutive runs hit a 15-minute cap. GitHub reports
    a job timeout as cancelled, which reads like an interrupted run, so a permanently
    broken job never looked like one. The suite and the 90% coverage gate had not run in CI
    at all. Raised to 40 minutes; it now completes in ~23 minutes and the coverage gate
    passes at 90.78%.

Each is guarded by a test so it cannot regrow silently.

Added — the contributor surface

151 links in the public documentation pointed at files excluded from the public
repository
— every one a 404 for every visitor, including the RFC-process link
CONTRIBUTING.md tells contributors to read first.

The fix generalised into a rule now enforced by make check-links: a link's target must
be tracked by the public git, not merely exist on disk.
Those differ exactly where it
matters, and checking git membership found nine more instances a path denylist could not.

New public documentation:

Contributor onboarding: CONTRIBUTING.md now opens with a 15-minute path from clone to
pull request before any governance material; a .devcontainer/ removes the
uv sync-without---all-extras failure that breaks ~30 tests for every newcomer;
CODEOWNERS, CONTRIBUTORS.md, YAML issue forms, and published response commitments in
SUPPORT.md. GitHub private vulnerability reporting, secret scanning and push protection
are enabled.

Added — the documentation is on the web

novafabric.ai/docs/ publishes the docs/ tree as 48 pages, read directly from the
repository at build time so the site cannot drift from what maintainers edit. Includes
TechArticle JSON-LD, per-page meta descriptions, sitemap entries, and relative .md
links rewritten to working URLs.


Known issues

  • #23 — server mode: register_run()
    fails after running the packaged Postgres migrations.
    v002_partition_ddl gives runs
    a primary key of (run_id, started_at) while the store writes
    ON CONFLICT (run_id, tenant_id). Pre-existing, not introduced here, and it affects
    server mode only — local mode is unaffected. Every candidate fix changes a documented
    guarantee, so it is deliberately left for a decision rather than patched quietly.
  • #21 — a set of CLI --help
    assertions fail in CI and not locally; unexplained, tracked separately.

Upgrading

pip install --upgrade novafabric
nova --version

No capsule migration is required. The Run Capsule and Evidence Bundle formats are
still not frozen — expect schema changes before v1.0.

Full detail: CHANGELOG.md [0.100.0].

NovaFabric v0.99.0 — 2026-08-05

Choose a tag to compare

@MSKazemi MSKazemi released this 05 Aug 12:44

NovaFabric v0.99.0 — 2026-08-05

pip install novafabric drops from 412 MB to 113 MB. NovaFabric's pitch is
local-first — install it, capture a run, replay it, no server and no cluster.
The install did not match the pitch: a default install pulled 412 MB across 50
packages, most of it for infrastructure a default user could never reach. This
release fixes that, and it is the one change in the packaging-hardening program
that needs an ADR, because it changes what is importable after a plain install.

Also in this release: four defects a source-tree test run could not see

The packaging work below is the headline, but this release also carries four
correctness fixes. Three were live on every released install — and all three
were invisible to the test suite, because the suite runs from the source tree
where repo-root fallbacks resolve and hooks never contend.

Three schema validators were never in the wheel

novafabric.envelope.validator, novafabric.import_blob.service and
novafabric.capsule.validator each loaded their JSON Schema from the repo-root
schemas/ directory, which does not ship. On a plain pip install novafabric,
Event Envelope v1 validation and parent/child capsule validation raised
FileNotFoundError, and batch-import manifest validation resolved a path
outside site-packages.

Fixed by mapping the canonical files into the package at build time
(force-include) and making each loader prefer the packaged path — deliberately
not by copying them under src/, which would recreate the two-schema-trees
problem. Verified by building the wheel and exercising all three in a clean
virtualenv, before and after.

~1.5% of API keys could never be rotated or revoked

key_id came from secrets.token_urlsafe, whose alphabet includes -, so
1 in 65 ids (1.54%, measured over 200,000 draws) began with a hyphen — and every
nova server api-key command takes the id as a positional argument, so Click
parsed it as an option and exited 2. New ids are re-drawn until the first
character is alphanumeric. Keys issued before this fix still work: pass the
id after a trailing --, e.g.
nova server api-key rotate --db-path k.db -- -Jabc123.

Concurrent in-process captures corrupted each other

capture.hooks keeps one module-level hook list and one EventRecorder, and
eight of the nine in-process call sites drove them unguarded. Two overlapping
captures meant the second inherited the first's recorder (events filed into
the wrong capsule
), stacked a second patch layer, and whichever finished first
tore down both. ADR-0224 adds an ownership token, and every adapter now records
metadata.wire_capture as installed, installed-contended or
skipped-concurrent — so a short stream can never be misread as "nothing
happened". Full per-task isolation is phase 2, specified and not yet built.

Nine schema $id collisions

Nine schema pairs declared the same $id while disagreeing about what they
accept; measured, the canonical Run Capsule schema rejected a capsule
nova capture had just written. Canonical OAS v1 target schemas are now
identified .../<name>-v1.schema.json and say in $comment that they are not
yet in force. No producer or capsule changes — ADR-0034 §1 keeps the spec in
pre-freeze draft, so ^1\. is a target, not a contract.

ADR-0024's license policy is enforced, not just written

scripts/license_gate.py + .license-policy.toml resolve every installed
distribution's license and block on policy, closing a gap the ADR had admitted
for fifteen months. It runs on push, PR, and weekly — an upstream relicense
changes nothing in uv.lock but everything for the policy. Disjunction-aware
(a tri-licensed package resolves to its most permissive arm), and Tier D is
forbidden outright.

Highlights

ADR-0222: four dependencies move from core to extras

duckdb, pyarrow, python-louvain and clickhouse-connect are no longer
unconditional dependencies. Every one of their import sites was already behind
an extra — evidence_fabric/* (the scale tier), serve/topology/* (the
opt-in dashboard), lineage/migration/*, and cost/clickhouse_store.py — so a
default install was paying for all four and never able to use them.
python-louvain was also the only path numpy had into a bare install: a
16.7 MB scientific-computing stack, for a community-detection pass in an
experimental dashboard you had not installed. And clickhouse-connect was
pinned twice, in both core and the scale extra, with two version floors free
to drift apart.

Measured into a genuinely clean venv, not projected:

site-packages packages
v0.95.0 412 MB 50
v0.99.0 ~113 MB 40
delta −299 MB (−72.6 %) −10

Also dropped, transitively: numpy, lz4, backports.zstd, urllib3.

Two notes on these numbers, so they are not read as more precise than they
are.
The package count moved from 42 to 40 after this release note was first
written: closing ADR-0222 OQ-2 also took pyjwt and python-multipart out of
core (both are server-only). That is ~300 KB, so it does not move the MB
figure.

The MB column is method-sensitive and should be read as approximate. An
independent re-measure on 2026-08-01 (python -m venv + pip install .) read
116 MB for the whole venv and 100 MB excluding pip/setuptools, against
the 113 MB recorded here. The 3 MB spread is not explained, and the two
measurements were not taken the same way — so treat −72.6 % as the honest
order of the win, not a figure to quote to three significant digits. The
package counts, which are exact and reproducible, are the more reliable half
of this table.

networkx deliberately stays

It is the obvious fifth candidate and it is staying, for a reason worth knowing:
networkx is imported eagerly at CLI start-up (cli/main.py → cli/insights.py
→ lineage/analytics/), so moving it would break nova --help itself. It also
backs the default nova insights and nova lineage commands.

Note the distinction that makes this safe: nova insights uses networkx's own
pure-Python nx.community.louvain_communities. The separate python-louvain
distribution (import community) is used only by the dashboard's topology
extractor — that is the one that moved. Conflating them would have broken
nova insights.

The degradation contract

Moving a dependency is only safe if every core-reachable use of it behaves
sensibly without it. v0.99.0 adopts an explicit contract:

Either fall back to a stdlib-equivalent path producing identical results,
or raise an ImportError naming the exact extra to install. Never a silent
wrong answer.

What you run Without the extra
nova query Falls back to stdlib sqlite3, identical rows, reports index.engine: "sqlite"
nova backup Skips the derived DuckDB topology cache with a stated reason; the backup still succeeds
nova restore Fails loudly — but only if a .duckdb member is actually in the archive. Unverified is not verified
nova insights Report still builds; notes the ignored cost source
ClickHouse accumulator / cost store ImportError: ... pip install novafabric[clickhouse]
evidence_fabric.DuckDBAccumulator ImportError: ... pip install novafabric[scale]

Backup and restore land on opposite sides on purpose: skipping a rebuildable
cache during backup loses no evidence, whereas calling a restore verified when
the store was never opened would be exactly the silent wrong answer the contract
forbids.

This is enforced by tests, not prose — including a parity test that runs the
DuckDB and SQLite query engines over the same capsules across five query shapes
and asserts the rows are equal. Without that, the new [query] extra would be a
correctness dependency rather than a performance one.

[query] is not currently an accelerator. ADR-0222 left the DuckDB-vs-SQLite
crossover unmeasured. Measuring it (ADR-0222 OQ-3,
bench/query/MEASURED_CEILING.md) found no crossover: DuckDB is ~20× slower
at every directory size from 10 to 20,000 capsules, because the index build
inserts row by row rather than using DuckDB's bulk appender. Selecting DuckDB
implicitly therefore made nova query slower for anyone who installed
[query], [scale] or [all], so SQLite is now the default engine
regardless of what is installed
; NOVAFABRIC_QUERY_ENGINE=duckdb opts back
in. Porting the index build to the appender API, after which this should be
re-measured, is tracked separately.

evidence_fabric imports lazily

novafabric.evidence_fabric used to import all six of its exports eagerly, so
any symbol — including the dependency-free EventQueueConsumer — required
duckdb and pyarrow. It now uses a PEP 562 __getattr__, so importing the
package pulls in nothing optional and resolving one backend never drags in
another's dependency. Its docstring also claimed DuckDBAccumulator and
LocalPIITable were "self-contained (no optional deps required)", which was
never true; that is corrected.

Upgrading

Migration note. If you relied on duckdb, pyarrow, clickhouse_connect
or community (python-louvain) being importable after a plain
pip install novafabric, install:

pip install 'novafabric[all]'      # restores the previous install surface

…or pick the narrower extra you actually need:

Extra Provides
novafabric[scale] duckdb, pyarrow, clickhouse-connect (+ the rest of the Evidence Fabric scale tier)
novafabric[serve] duckdb, pyarrow, python-louvain (+ FastAPI/uvicorn)
novafabric[query] duckdb — new. Gives nova query the DuckDB SQL surface, not speed: see the note below
novafabric[clickhouse] clickhouse-connect
novafabric[lineage-migration] pyarrow

These...

Read more

NovaFabric v0.98.3 — 2026-07-31

Choose a tag to compare

@MSKazemi MSKazemi released this 31 Jul 13:24

The "10px type floor" introduced in v0.97.0 never actually worked. text-[var(--text-2xs)] compiles to color: var(--text-2xs) — Tailwind cannot tell a bare var() is a length, so all 116 occurrences set an invalid color and left the font size inherited. The visible symptom: sidebar group headers rendered at 16px, larger than the 12px items they label. Now uses the real text-2xs utility.

Sidebar groups start collapsed, each showing its item count; the group holding the active tab always renders expanded.

Full suite 11,318 passed / 0 failed; e2e 70/70.