Releases: SmooAI/audit
Release list
v0.4.1
Patch Changes
-
4e17d0e: Gate each registry's publish on that registry, not on a parsed stdout flag
Every non-npm publish step was gated on
steps.changesets.outputs.published, which changesets/action derives by parsing publish stdout. That fails open two ways, both seen for real on 2026-08-20: if npm published in an earlier run, a retry finds nothing new so the flag is false and all four remaining registries skip — a green run that ships nothing; and@changesets/cli3.x renamed the very line the action parses, which silently switched every non-npm publish off in a sibling repo while its releases stayed green.Each step now gates on whether its own registry already carries
package.json's version, so a retry publishes exactly what is missing, and a final step fails the run if npm shipped a version the others didn't. NuGet is reported but not asserted — its index lags an accepted push by minutes to tens of minutes, and a guard that reddens successful releases gets deleted.
v0.4.0
Minor Changes
- ccc4f4e: Give every language the same emit posture: retry with backoff, and never fail
silently.- Retry on transient failure in Python, Rust, Go, and .NET — previously
TypeScript only. Transport errors and HTTP 5xx are retried with exponential
backoff; a 4xx is surfaced immediately. The retried POST carries the same
canonical bytes, since ingest dedupes on the event hash. The defaults now live
inspec/parity-corpus.json'sretryPolicy(3 attempts, 100 ms base,
doubling) and every language's tests assert their client against it, rather
than five copies of the same magic numbers. - BREAKING (Python):
swallow_errorsnow defaults toFalse. It defaulted
toTrue, so a misconfigured endpoint or an expired token dropped every audit
event and reported success — fail-open on the exact path that carries the
record. Passswallow_errors=Trueto keep the old behaviour;on_errornow
fires whether or not the error is swallowed. (The old docstring also claimed
this matched the TypeScript client. It never did — TS has always thrown.) - New:
AuditClient.emit_asyncin Python, running the blockingurllib
POST off the event loop viaasyncio.to_thread. - Rust's
AuditClientOptionsgainsmax_retries/retry_backoff_ms(both
Option,None= the shared default) and anAuditClientOptions::new
constructor. Go'sAuditClientgainsMaxRetries/RetryBackoff(zero =
default). .NET'sAuditClientOptionsgainsMaxRetries/RetryBackoffMs. - Go's
Emitstays synchronous on purpose —go client.Emit(ctx, event)is how
Go does async — and now honours context cancellation between retries too.
- Retry on transient failure in Python, Rust, Go, and .NET — previously
v0.3.0
Minor Changes
-
a6a0293: Ship chain verification in all five languages, and prove it with corrupted-chain
corpus vectors.verifyChain— the function that actually DETECTS a broken hash chain — existed
only in TypeScript. Python carried a docstring describing how one would verify;
Rust, Go, and .NET had nothing. A service in four of five languages could seal a
chain it could never audit, which makes "tamper-evident" a claim rather than a
capability.- New:
verify_chain(Python, Rust),VerifyChain(Go),HashChain.Verify
(.NET). All five return the same verdict shape:ok,brokenAt, and a shared
failure code —hash_previous_mismatch(the link is wrong) or
hash_current_mismatch(the event body was edited after sealing). verifyChaingains an optionalgenesisPreviousHash, so a slice continuing an
existing chain can be verified at all. Without it, only a first-of-day chain
was verifiable. TypeScript additionally gains acodefield alongside the
existing human-readablereason(additive;reasonis unchanged).spec/parity-corpus.jsongainschainFixtures: 11 real chains, sealed by the
builder and then genuinely tampered with, each with the verdict every language
must return. All five suites load them. The corpus previously proved sealing
only, which is what let the asymmetry hide.- One fixture asserts the honest limit: deleting events from the TAIL of a chain
still verifies. Replay cannot see it; catching it needs an external anchor.
- New:
v0.2.1
Patch Changes
-
60bc0dc: Fix version sync so released artifacts carry the version they were released as.
version:syncran afterchangeset publish, mutating the manifests in the CI
workspace where nothing ever committed them. Every git tag therefore shipped
0.0.0inpython/pyproject.toml,rust/audit/Cargo.toml,go/version.go
(audit.Version), andSmooAI.Audit.csprojwhile npm, PyPI, crates.io, and
NuGet all showed 0.2.0 — andcargo publish --allow-dirtyexisted only to
tolerate the resulting dirty tree.The sync now runs in the changesets
versionlifecycle, so the bumped manifests
are committed with the release. A newpnpm version:checkfails CI on any drift,
including ago.modmodule path whose/vNsuffix disagrees with the major.
cargo publishis now--locked.
v0.2.0
Minor Changes
-
f05a9ab: Trace correlation: an emitted audit event now carries the W3C trace context of the
request that caused it, so a row in the audit store can be joined to a trace.The ids ride in the ENVELOPE, never inside the event. The wire body is now
{"event":<the sealed event>,"spanId":"…","traceId":"…"}. The bytes under
"event"are exactly the bytes that were hashed — unchanged, byte-for-byte, with
or without a trace active — becausehashCurrentcovers canonical-JSON(event
minushashCurrent) and any new event field would invalidate every stored chain
and every fixture inspec/parity-corpus.json. The corpus is untouched, and each
language asserts it inside an active span as well as outside one. Both ids are
OMITTED when there is no valid span: never"", never an all-zero id.TypeScript:
AuditClient.emit(event, trace?)captures the active context at
emit time behind an optional@opentelemetry/apipeer dependency. Without it
installed (or without a registered SDK) it is a no-op, not a crash.buildEnvelope
/currentTraceContextare exported for consumers on their own transport.Rust: the same, behind a new optional
otelcargo feature (off by default —
the crate does not link OpenTelemetry unless you ask for it).AuditClient::emit
uses the ambient span;emit_with_tracetakes an explicitTraceContextthat
wins per field.TraceContext::current()reads both context homes — atracing
span via tracing-opentelemetry and an OTel-native one — because neither falls
back to the other.Go:
AuditClient.Emit(ctx, event)reads the span context already on thectxit
takes (trace.SpanContextFromContext(ctx).IsValid()before touching the ids), via
the OpenTelemetry trace API only — no SDK, no exporter. Pinned to otel v1.35.0,
the newest release whosegodirective (1.22.0) still builds on the Go 1.22 the
CI matrix pins; v1.36+ declare go 1.23.Python: the ids come from the ambient span behind a guarded
from opentelemetry import traceimport, exposed as the optionalotelextra
(pip install smooai-audit[otel]). Without it installed, correlation is a no-op —
opentelemetry-apiis never a hard dependency..NET: reads
Activity.Current— the BCL type the OpenTelemetry .NET SDK itself
populates — so no new package reference. Non-W3C or unstarted activities report
nothing.
v0.1.1
Patch Changes
- 50f514b: Rust: feature-gate the HTTP
AuditClientbehind a default-onclientfeature (reqwest is now optional). Consumers that only need the schema + canonical JSON + hash chain — e.g. a service that publishes audit events onto its own transport such as NATS — can depend withdefault-features = falseto drop the reqwest + async-runtime pull.
v0.1.0
Minor Changes
- df7c377: Initial scaffold of
@smooai/audit— a polyglot client SDK (TypeScript, Python, Rust, Go, .NET) for tamper-evident, SQL-queryable audit logging. Ships the intended public surface: a canonicalAuditEventschema,canonicalJson, a per-org-per-day SHA-256 hash chain (computeEventHash/buildHashChain), and anAuditClientemit client. Implementations are stubbed (TODO(audit-impl)) pending the shared parity corpus.