Skip to content

Releases: toshish/traza

v0.26.0-preview.1

v0.26.0-preview.1 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 06 Sep 21:32
d2d12cc

[0.26.0-preview.1] - 2026-09-06

Preview: an object-storage snapshot archive behind the new, default-off
object-storage cargo feature and the feature-gated traza-object binary.
A verified pin publishes to S3-compatible storage as an explicitly named,
immutable snapshot — manifest written last under a conditional create, every
object read back and SHA-256-verified before it is named — and reads back
three ways: queryable in place by chunk-verified range reads (exact engine
semantics: last-write-wins across segments, tenant scoping, content search,
cursors, payload retrieval), verified full restore into a fresh directory,
and snapshot administration (list, inspect, verify, delete, cleanup).

Added

  • object-storage feature: traza::object_storage (Rust API) and
    traza-object (CLI) for publish/query/restore/delete against S3-compatible
    backends via object_store (env credentials, TLS verifying by default,
    bounded per-request and per-transfer budgets with retries, plain HTTP only
    by explicit opt-in), plus injectable in-memory and fault backends for
    tests.
  • Deletion is fenced by permanent snapshot tombstones: a deleted (or
    swept-after-abandonment) snapshot id is never reusable, which is what
    makes delete and cleanup safe against concurrent or resumed publishers
    across machines without a lock service. Sweeping an abandoned upload
    requires an explicit publisher-quiescent acknowledgment, retires the id,
    and the publisher re-checks the tombstone and its own upload marker
    around its commit.
  • Every remote manifest is bound to the generation manifest it
    archived
    before a query is served — an omitted or substituted segment
    fails the open instead of shrinking answers — and the archived tombstone
    log is strictly re-parsed for pending erasures. Store identity is
    checked on every read, verify, delete and cleanup; publish reports a
    manifest_sha256 to retain externally and enforce via
    --expect-manifest-sha, which is the stated trust root (the bucket is
    not assumed Byzantine-proof).
  • Source pins are validated before anything they name is opened: canonical
    file naming, symlink refusal, and refusal of unmanifested or live-store
    files, so a stale manifest can never silently archive an older subset.
  • segment::RangeSource and Segment::open_from_source: local file opens
    and remote opens now share one implementation, so a remote segment gets
    the full v8 validation story (header CRC, section checksums, timestamp
    grounding, per-read block/content-page CRCs) by construction. Remote
    reads additionally verify a per-chunk SHA-256 table from the snapshot
    manifest, including cache hits, under one global bounded chunk cache.
  • Store::pin_for_object_archive (feature-gated): pin + verify + refuse
    pending erasures — the pin publication requires and independently
    re-checks from the pin's own tombstone log.
  • Operations guide: docs/operations/object-storage.md, stating the scope
    honestly — archives are historical immutable state, not tiering; local
    TTL/erasure never reaches published snapshots (delete the snapshots, and
    mind bucket versioning/object-lock); remote querying saves the archive
    copy's
    disk, not the live store's.

Changed

  • pin_generation now checkpoints and links under one maintenance/seal
    acquisition, closing the between-locks gap in which compaction could
    replace a just-manifested file and fail the pin.
  • Version metadata: crate and UI move to 0.26.0-preview.1. The standalone
    MSRV stays 1.81; the object-storage feature's resolved toolchain floor
    is recorded in package.metadata.traza.object-storage-rust-version.

Fixed

  • Incremental SHA-256 preserves a buffered partial block across small or
    empty updates. Object verification now remains correct across arbitrary
    network chunk boundaries; one-shot digests and the storage format are
    unchanged.

v0.25.0

Choose a tag to compare

@github-actions github-actions released this 06 Sep 10:29
9622f4c

[0.25.0] - 2026-09-06

A lossless storage-efficiency release: format v8 compacts persisted indexes
while preserving spans, payloads, query semantics, and retention settings.

Measured on the three bundled synthetic corpora (paired same-machine
before/after runs, identical ingested bytes; not a forecast for arbitrary
traffic): whole-data-directory size fell 44.2% on generic
(132,663,519 → 73,968,447 bytes for 1M service-trace spans), 31.4% on
llm (107,692,288 → 73,909,976 bytes for 200k LLM-call spans), and
38.1% on pinned-context (4,283,487 → 2,650,416 bytes for 10k
long-context spans). The full normalized export of all 1.21M baseline spans
is byte-identical after migration, and every migrated store verifies intact.
Evidence, latency pairs, and reproduction commands:
docs/benchmarks/storage-v8-comparison.md.

Changed

  • Record offsets and trace/attribute postings use delta varints. Metadata
    sections use LZ4 when it produces fewer bytes, with raw passthrough otherwise.
    Record compression and content-addressed payloads retain their existing codecs.
  • Segment headers and all metadata used to exclude query results carry integrity
    checks. Content-index pages are also checked when read after the segment opens.
  • Format v6 and v7 stores migrate automatically to v8, including pins and their
    manifests. Migration preserves filenames and WAL fold boundaries and proves
    complete coverage of legacy records before replacing a segment.

Fixed

  • Corrupted persisted query indexes can no longer silently hide matching spans
    at open. Malformed geometry and noncanonical varints are refused.
  • Legacy offset metadata that omits intact records is refused instead of
    publishing an apparently healthy but incomplete migrated store.

Upgrade

Migration changes files in place through atomic replacements and resumes after
interruption. It is one-way from the first converted file. Stop the old server
and retain a complete cold backup plus a tested compatible binary before opening
existing data with this release. Downgrade requires restoring that backup;
replacing only the executable is insufficient. See
the format and migration contract.

v0.24.2

Choose a tag to compare

@github-actions github-actions released this 01 Sep 02:45
76119c5

[0.24.2] - 2026-08-31

The follow-up an external review of the v0.24.1 line earned: the query
deadline now reaches the sidecar rebuilds and buffered sweeps it missed,
the v7 header's timestamp range is validated against real records instead
of trusted, and the filter benchmark refuses to publish a latency for an
answer it did not verify. Every finding was reproduced red before its fix.

Fixed

  • The query deadline reaches the rollup sidecar rebuild. External
    review of v0.24.1 found two query paths that check the budget per
    segment and then rebuild a missing rollup sidecar INSIDE the segment
    with no check at all — the scoped payload fetch's reachability probe
    and the analytics fold's fast path. A 50k-span rebuild ran to
    completion under a 1 ms budget (~1 s of decode) and answered as though
    the probe had been cheap. The rebuild's record-decode loop now runs
    under the calling request's budget at the standard cadence, with a
    final check before it returns; second-round review found the sibling —
    the supersede prefilter's key-hash rebuild, reachable per emitted span
    on every query and fold — and it is threaded the same way. The
    maintenance TTL sweep passes no budget, exactly as the deadline
    contract exempts it, and the shadowed-tail scan never rebuilds at all
    (a segment without a sidecar ends that scan).
  • Buffered spans no longer escape the query deadline. The same
    review found every buffered-span sweep unchecked: an unflushed
    20k-span corpus answered complete results under a zero budget on the
    unlimited query, fold, session-union, and analytics-rollup paths, and
    the limited query built its whole buffer source before the merge's
    first check. Every buffer sweep on a query path now checks at the
    standard cadence, and every budgeted query path performs one final
    check before returning a completed answer — a request past its budget
    is refused, never rewarded for finishing late. The deadline's cost
    statement in docs/configuration.md is amended to match.
  • The segment header's timestamp range is validated, never trusted.
    The same review demonstrated the harm: the two range words carry no
    checksum, may_contain_timestamps pruned whole segments on them
    before any validation, and editing sixteen header bytes silently hid a
    record the store could still decode — the false-negative predicate the
    corruption contract forbids. Both opens now prove both declared bounds
    against record bytes: the eager open against every record, the lazy
    open by decoding the first block (whose fence-vs-first-record check
    pins the declared min to the actual first record) and reading the last
    record's timestamp — the true max, records being timestamp-sorted —
    which must equal the declared max. Second-round review forced that
    grounding: pinning the min to the first FENCE alone could be colluded
    with, since the fence is unchecksummed metadata too — raising the
    header word and the fence together still silently hid the head window.
    A metadata-only forgery of the range is now refused at open on either
    bound; a forgery must alter record bytes plus the block crc32 to get
    past it, which the validation's own decode catches. An empty segment
    must declare the canonical empty range, and any record read outside
    the declared range still fails loud as corrupt. Deliberate amendment
    #5 in docs/segment-format.md states the validation and its cost — two
    bounded block decodes per lazy open, dropped from the cache after.
  • The canonical benchmark's filter gate gained a semantic oracle.
    Review also noted the bench recorded filter latency on any 200 with
    parseable JSON — a wrong or empty answer would have been timed and
    published as a healthy number. Each filter sample now asserts the
    exact span count the corpus construction defines, the requested
    benchmark.group value on every returned span, and the exact expected
    span ids, before its latency is recorded; a wrong answer aborts the
    run under the same refuse-to-publish rule as the gates.

v0.24.1

Choose a tag to compare

@github-actions github-actions released this 31 Aug 14:46
3db112d

[0.24.1] - 2026-08-31

Fixed

  • The compaction keeps-up test's tick deadline is sized for v7 merges.
    The bound that proves a tick merges the backlog it found and returns —
    rather than merging what arrives for as long as the writes do — was
    twenty seconds, sized before v7 made every merge re-encode lz4 blocks.
    The v0.24.0 tag run watched a starved shared runner push one legitimate
    tick past it and fail release preflight, so the v0.24.0 tag exists but
    published no artifacts on any channel; this release is the v7 line's
    first shipped artifact. The tripwire is two minutes now, and the
    comment says which failure the clock is for.

v0.23.0

Choose a tag to compare

@github-actions github-actions released this 31 Aug 05:39
c7ab577

[0.23.0] - 2026-08-26

The release where the production obligations land, and the first artifact to
carry them: everything between v0.22.1 and here ships for the first time,
including the [0.22.2] section below, whose tag was never cut. Generations and
checkpoints with hot backup and one-flag restore; erasure with a receipt that
re-checks every domain by name; tenant identity in the primary key; the eval
entity model; attribution that names the failing step; a compute deadline on
every query and a connection slot no panic can leak; and a six-demo tour that
CI holds green. The format pays for it once: segment formats 1–5 are spent,
and a pre-generation store migrates on first open, automatically.

Added

  • --query-deadline-ms (default 30000, 0 disables): a wall-clock budget
    one query or aggregation may spend inside the engine, checked at segment
    boundaries and every few thousand decoded records. Past it the request
    answers 400 {"error":"query deadline exceeded: ..."} with no rows — the
    refusal that stops one runaway scan (an unindexable content search, a
    whole-corpus fold) from holding a handler thread for minutes, and never a
    partial aggregate, because a partial answer would look complete. A 200
    still means what it meant
    — every answer served is still the whole answer
    — and durability is untouched: ingest, the tail, exports (bounded by the
    socket timeout), and erasure work are all exempt. The worst measured
    legitimate query in this repo is ~3 s, so the default never touches
    measured use. traza_query_deadline_exceeded_total counts the HTTP
    refusals; an MCP tool that exhausts the budget surfaces the refusal in its
    tool result instead, uncounted.

    • Library surface: Config::query_deadline is a new field (None —
      the unbounded behaviour every embedder had — by default; a Config
      built by struct literal must add it, ..Config::default() need not),
      and Error::DeadlineExceeded is a new variant of the exhaustive
      Error enum, so a downstream exhaustive match must add an arm.
  • A demo tour in examples/ — six scripted proofs, one claim each. Five
    new demos join mcp-demo, and examples/README.md
    indexes the tour: swarm streams a simulated agent platform into a live
    dashboard in real time; crash SIGKILLs the server mid-ingest and proves
    every acknowledged span survived, then corrupts a byte, watches
    /v1/verify name the file, and restores from a hot backup; needle
    ingests a million spans over real HTTP and times the answers; incident
    investigates a runaway session over MCP — diagnose_session, a defused
    prompt injection, promote_failures_to_dataset, and an experiment diff
    proving a fix; vanish erases one tenant's session next to an identical
    twin tenant and walks the receipt, including the backup pin it refuses to
    ignore. Every number each demo prints is measured during the run — a timed
    request, a counter, a field from a live response — and each script asserts
    its own claims and exits non-zero when one fails, which is why ci.sh now
    runs all six at their smoke settings as merge gates.

    • Demos refuse to run against a port another server already holds:
      readiness is the child's own startup line, not a probe that a squatter
      can answer. This guard also landed in mcp-demo.
  • Attribution: the server names the failing step, instead of the reader
    doing it.
    A new diagnose_session MCP tool answers why did this run
    fail
    — the outcome, the step the failure is attributed to, and the
    repetition behind it — with the evidence for each claim travelling beside
    it. It replaces the two prompts that used to teach a model to read a trace
    and judge its shape by eye ("repeated sibling names are a retry storm,
    deep chains are a loop"
    ), on the surface's own rule that a prompt wanting
    a branch is a tool.

    • Every rule fires on telemetry that already exists. An earlier design
      rested on a session.outcome attribute and a relation: "retry-of" link
      — both conventions Traza invented and nothing else emits — which would
      have been a closed loop: seed the convention, detect it, pass CI, and
      answer cause: null on every real store. A declared convention now
      raises confidence and is never a precondition. The discriminators are
      error density, serial fraction, self-similar depth and context growth.
    • Context growth is read from context_tokens, not prompt tokens.
      gen_ai.usage.cache_read_input_tokens and cache_creation_input_tokens
      are recognized, because Anthropic reports input_tokens as the uncached
      remainder
      : a growing conversation reports a prompt count that falls as
      its cache warms, so reading the wrong field inverts the signal on the
      configuration long-running agents actually use. Where the arithmetic is
      unknown the trend is reported as unreadable rather than guessed.
    • It refuses to say more than the data supports. A shape is classified
      only when a discriminator agrees; otherwise it is reported as
      inconclusive with the missing signal named. Ordinary iteration is
      reported as ordinary so a reader can see it was examined and set aside.
      Absent token data can never satisfy a healthy classification. The
      strongest test is silence: the analysis finds no fault anywhere in the
      bundled corpus of healthy workloads.
    • Session outcome is first-class and provenance-tagged: declared when
      a span said so (session.outcome, session.goal — Traza extensions,
      labelled as honestly as llm.cost_usd), derived from the run's own
      spans otherwise, and unknown — never rendered as success — for a run
      still in flight. A run that failed a call and recovered is a success that
      reports its errors, not a failure.
  • promote_failures_to_dataset closes the loop into the M4 eval
    entities: a session's attributed failures become a regression dataset
    version, each example carrying its own copy plus provenance back to the
    span. Re-promoting is idempotent. Gated by an rw token and a new
    --mcp-promote switch, separate from --mcp-annotations because an
    annotation dies with its span while a promoted example deliberately
    outlives its source. The caller names a session, never a span: the
    promoted set is re-derived server-side from the diagnosis, so text injected
    into the telemetry can change whether a promotion happens but never what it
    copies — proven by a test that plants exactly that instruction.

  • A runaway agent scenario in the seed corpus: an agent whose search tool
    fails from the fourth turn on, so every later reflection appends the failure
    to its context and asks again. Its steps are siblings under one root, the
    shape real frameworks emit, and it carries no declared outcome and no retry
    link — so finding it proves the analysis works on what a real pipeline
    sends.

  • Tenant identity in the primary key. Span identity is now
    (tenant, trace_id, span_id) — everywhere: the write buffer's index, WAL
    replay, segment supersede resolution, compaction's last-write-wins merge,
    the tail ring's veils, cursors, the analytics key hashes, annotations and
    erasure. Two tenants sharing a trace id can no longer silently upsert over
    each other, which is the whole reason this ships now: keys cannot be
    retrofitted after the format freeze. The default tenant is the empty
    string and is never serialized, so a single-tenant store writes
    byte-identical WAL frames, segment records and annotation lines to what it
    wrote before tenancy existed — no segment or WAL format bump, proven by a
    serialization-guard test. Tenant scoping reaches every surface the roadmap
    names:

    • Credentials: TRAZA_TOKENS entries take an optional binding,
      scope@tenant:token. A bound credential ingests, queries, tails,
      exports, annotates and erases exactly its own tenant; naming another
      tenant is a 403, and the store-global operator surfaces (/v1/stats,
      /v1/metrics, /v1/verify, checkpoint/flush/backups) refuse bound
      tokens outright. OTLP exporters select a tenant with the traza.tenant
      resource attribute; MCP tools are scoped by the same binding.
    • Sessions are (tenant, session_id) — the same session.id under
      two tenants is two sessions, in the rollup sidecar (format v3,
      self-healing rebuild), the session list, and group_by=session rows.
    • Retention takes per-tenant windows: --tenant-ttl TENANT=SECONDS,
      repeatable. A tenant's cutoff is its override, else the global TTL,
      else never — and the segment retire-whole fast path only runs when a
      global TTL covers everyone, because a whole-segment decision taken from
      configured windows alone would delete an unswept tenant's data with the
      segment.
    • Quota accounting: GET /v1/tenants reports per-tenant spans,
      traces, serialized bytes and offloaded payload bytes from one exact
      fold. Accounting, deliberately not enforcement.
    • Erasure: trace/span/session subjects carry a tenant (empty = the
      default tenant, never "all"), and a new tenant subject erases
      everything a tenant owns — spans, annotations, scores, datasets,
      experiments — with the same barrier, ordering, and receipt discipline
      as M3. Reference-aware payload deletion now spans tenants: a blob two
      tenants share survives one tenant's erasure and the receipt names why.
  • The eval entity model — identity only, no workflow. The addressing the
    product thesis requires, and nothing else: Dataset (stable id, name,
    tenant), DatasetVersion (immutable, content-addressed manifest of
    (example_id, digest) pairs with a parent version for lineage and the
    promotion's provenance — re-POSTing identical content IS the same
    version), Example (stable id across versions; input, optional
    expected output, split...

Read more

v0.22.1

Choose a tag to compare

@github-actions github-actions released this 09 Aug 00:18

[0.22.1] - 2026-08-07

Fixed

  • The release archive's dashboard now sits where the server actually
    probes.
    v0.22.0 packaged the UI at ui/dist inside the archive, but
    the binary-relative search — the packaging convention the source itself
    documents — looks for <binary dir>/ui. Launched from inside the
    extracted directory, the working-directory fallback hid the bug; launched
    from anywhere else, / was a 404. The archive now places the build at
    ui/, and every release archive is smoke-tested from an unrelated
    working directory
    — dashboard 200, span accepted and read back —
    before anything publishes, so this class of bug cannot ship twice.
  • The README's container quickstart no longer discards the token it
    mints.
    The old one-liner generated TRAZA_TOKENS inline, so the server
    started and the reader had no way into their own dashboard. The token now
    lands in a shell variable and is echoed before use.

Added

  • --version / -V on traza-server, printing the crate version.
  • Third-party notices ship with the artifacts. Release archives and
    container images now carry THIRD_PARTY_NOTICES.md: the MIT and OFL
    material for React, Inter and JetBrains Mono in the dashboard build, and
    the dual-licensed Rust crates linked into the binary.
  • The release pipeline fails closed. A preflight job refuses a tag
    whose version disagrees with Cargo.toml, ui/package.json, or this
    file, and runs the full ./ci.sh gate on the tagged commit before
    anything builds; the crates.io job fails loudly when the registry token
    is absent instead of skipping green. A new CI job checks the whole tree
    on the advertised MSRV, holding rust-version to being true rather than
    asserted — and its first runs proved the asserted 1.70 false twice over:
    the locked dependency tree requires 1.71, and the test suite uses
    File::set_times, stabilized in 1.75. rust-version now says 1.75,
    because that is the number the gate can hold.
  • SECURITY.md (private vulnerability reporting, supported versions,
    and the threat model in one paragraph), a code of conduct, issue and PR
    templates, and Dependabot coverage for Cargo, npm, and GitHub Actions.

Changed

  • The container runs as uid 65534, with /data shipped owned by that
    uid so a named volume inherits it — a bind-mounted data directory must be
    writable by 65534 — and images carry OCI source, version, and license
    labels.

v0.22.0

Choose a tag to compare

@github-actions github-actions released this 08 Aug 04:57

[0.22.0] - 2026-08-07

Added

  • An install, not a build. Tagged releases now ship what the README used
    to ask you to compile:
    traza-<version>-{linux-x86_64,linux-aarch64,macos-aarch64}.tar.gz, each
    carrying the traza-server binary (musl-static on Linux — no libc to
    match) with the dashboard already built in ui/dist, alongside
    SHA256SUMS and GitHub build-provenance attestations
    (gh attestation verify). The same binaries become the container image,
    ghcr.io/toshish/traza, built FROM scratch — an engine with two
    dependencies does not need a distribution underneath it. A non-loopback
    bind still refuses to start without TRAZA_TOKENS, in the container as
    anywhere else. The tag also publishes to crates.io when the registry
    token is present.
  • The merge bar runs in public. ./ci.sh — the same script, no drift —
    now runs on Linux and macOS for every push and pull request, and the
    README carries the badge. Until this release the script had only ever
    gated one platform: the laptop it was written on.

Fixed

  • The dashboard now renders trace media wherever the trace actually put
    it, instead of only the one lucky shape it recognized before.
    Found via
    a production vision-review trace whose image showed as an empty frame: the
    conversation view understood only data:/http(s): strings in a part's
    data/uri/url, which missed most real emitters and — worse — poured
    every other locator into the transcript as text (a bare-base64 screenshot
    rendered as eight thousand lines of base64). The messages panel and the
    trace inspector's payload modal now recognize, with tests: bare-base64
    data lifted into a data: URI via the declared MIME type; Anthropic
    source.{base64,url,file} parts; Google GenAI typeless inline_data /
    file_data parts; OpenAI input_audio, image_url-object, and file
    parts; MCP-style tool-result content lists (screenshots included);
    width/height metadata; and parts whose bytes were never captured,
    which now say so with the emitter's unavailable_reason instead of
    presenting an empty frame that reads as a rendering bug.

  • Offloading no longer demotes media to a JSON dump. A
    gen_ai.{input,output}.messages attribute past --payload-threshold-bytes
    becomes a payload reference, and the old conversation view could only show
    it as preview text — "load full" printed the raw JSON, megabytes of base64
    included. The payload body is the original messages JSON, so the
    conversation view now fetches it back (automatically up to 4 MiB, when the
    turn nears the viewport; on demand above that, content-addressed and cached
    across screens) and renders the parsed turns — an offloaded text-to-speech
    span plays its audio again. The trace inspector's payload modal gained the
    same understanding: message payloads render as turns, media payloads as
    media, anything else as code, with the literal bytes one raw toggle away.

  • Every MCP tool now advertises ToolAnnotations. The catalog carried
    names, descriptions and schemas but no readOnlyHint, destructiveHint,
    idempotentHint or openWorldHint — and absent hints are not neutral. Each
    defaults to the pessimistic answer, so nine read-only tools were advertised
    as potentially destructive and open-world. A host that gates on that asks for
    approval on every call; one running non-interactively, with nobody to ask,
    cancels them, which is what a Codex run did. The nine readers now declare
    readOnlyHint: true with openWorldHint: false, and record_annotation
    declares an additive, non-idempotent write. Tool nature is a required
    argument of the builder rather than a step that can be skipped.

  • describe_store reports the server version. initialize carried it in
    serverInfo, but a host reads that once and need not pass it to the model,
    so an agent asked which Traza it was talking to correctly answered that it
    could not tell. The overview block — and the traza://store/overview
    resource that shares it — now names the version.

Changed

  • The measurement records moved out of the repository root into
    docs/benchmarks/
    , and are named for what they
    measure rather than shouted: BENCHMARKS.md is now
    docs/benchmarks/canonical-corpus.md, and INGEST-BENCHMARK.md,
    STORAGE-BENCHMARK.md, QUERY-BENCHMARK.md and
    INDEX-MEM-BENCHMARK.{md,json} are ingest.md, storage.md, query.md
    and index-memory.{md,json}. Six SHOUTING files at the root read as project
    furniture on the level of README and LICENSE; they are generated
    documentation, and they belong with the documentation that cites them.
    Every benchmark binary writes to the new path, and docs/README.md now
    indexes all five records rather than two. Entries below this one name the
    files as they were called at the time.

  • query-bench publishes its record only for the canonical configuration, the
    rule bench already held itself to. Now that the record is a published
    document, an experimental corpus or client count would otherwise have
    overwritten the committed numbers with an answer to a different question.

  • The advertised tool catalog is smaller than before the annotations were
    added
    : 14,895 → 14,296 bytes, with 628 bytes of new annotations inside
    that. The four accepted time formats were restated on ten properties while
    the initialize instructions already spell them out once per session; that
    repetition and some restated prose are gone. Every semantic warning stays —
    word-not-substring, status-is-not-an-attribute, a missing key is kept, and
    the ranking ceiling that sends a caller to slowest_spans.

  • The published history was rewritten once, before the repository went
    public
    , to remove an internal live-deployment analysis that had no
    business in a public tree. Tags v0.1.0 through v0.20.0 are
    bit-identical to the private originals; commits from the v0.21.0
    release onward carry new hashes. Stated here because a lineage that
    quietly changed under its tags would be worse than one that says so.