trace-manifest.org

specification - v5 - beta

trace-manifest v5 — the interop rev (beta)

Status: beta. schemaVersion: 5 is live but explicitly revisable — field names and shapes here may still move as the first external emitter (clew) pushes on them, without a version bump. Loupe reads schema 3–5 today, uploads included. Two v5 samples ship in samples/: sample-v5.trace-manifest.json (FieldKit — SpecAssay-dialect, tiers + parents + rollups, ~31 rows) and clew-style-v5.trace-manifest.json (ledger origins, Swift symbol anchors, nativeStatus). SpecAssay's own Gate still emits v4; it bumps once the beta settles. When it does, this doc folds into the v4 spec.

Why v5

v4 is a single-emitter, single-viewer format: SpecAssay writes it, Loupe reads it, and the requirement→criterion hierarchy is inferred from the US/FR/NFR/AC prefix and the domain token in the ID. That convention is load-bearing, and it only holds for SpecAssay's own naming.

v5 opens the format to a second emitter — Thorsten Schlathölter's clew / Ariadne-Thread, built on CAS-DD (Code-Anchored Spec-Driven Development) — so he can extract clew's data into a manifest and render it through the same glass. Three facts about clew drive the rev:

  1. It models the tier explicitly. In CAS-DD, "a requirement is covered once all of its acceptance criteria are covered" is a real edge, not a naming convention. v5 must carry that edge as data.
  2. Its IDs are ledger-minted. clew mints immutable IDs from a sequence held in a ledger — they survive rewrites. v4's registry: {path, line} assumes an ID sits on a markdown line. v5 must generalize where an ID comes from.
  3. It is code-anchored. Anchors may be spans or symbols, and should survive reflow the same way the IDs do.

Design principles

Top-level shape (v5)

FieldMeaningv5
schemaVersion5
formatAlways "trace-manifest"
emitterNow an object { name, version }. name is the tool ("specassay-check", "clew"); version is its own release string. A bare string is still accepted and read as { name }.
targetNameProject label
repoPathAbsolute path scanned
generatedAtISO-8601 UTC
gate{ ok, failures[] } — the emitter's refuse set
totalsregistryIdCount, acCount, coveredCount
statusCountsCounts for the four core statuses
rowsMatrix rows
extNEW. Reserved object for emitter-specific data a generic viewer ignores. Key by emitter name: ext: { clew: { … } }.

changed · new.

Row shape (v5)

FieldMeaningv5
idDurable ID
typeEmitter dialect kind (AC/FR/NFR/US for SpecAssay). Free-form; the portable altitude is tier.
tierNEW. Portable altitude, decoupled from prefix: "intent" | "requirement" | "criterion". SpecAssay maps US→intent, FR/NFR→requirement, AC→criterion; clew maps its own kinds. Viewers order the descent by tier, falling back to type prefix when absent.
statementBest-effort prose from the source
parentsNEW. [id] — the upward edges (a criterion's requirement, a requirement's intent), declared not inferred. Empty/absent ⇒ fall back to the domain-grouping convention.
originNEW. Where the ID comes from (see below). Generalizes registry.
registry{ path, line } — retained. Equivalent to origin with kind: "registry-line"; readers alias one to the other.
statusOne of the four core statuses
nativeStatusNEW. Optional emitter-native coverage term, when it differs from the core. Informational; color keys on status.
implementationsCoverage anchors (see anchor shape)
proofsProof anchors ({ name, … })
carryingTasksOpen Carries: tasks that excuse tracked-debt / anointed backlog
rollupNEW, courtesy. Precomputed coverage over children (see below).
attestedByOptional operator stamp

origin — generalized ID provenance

"origin": {
  "kind": "registry-line" | "ledger" | "external",
  "path": "specs/PRD.md",   // registry-line: the file…
  "line": 14,               // …and the line
  "ledger": ".clew/ledger", // ledger: the ledger reference…
  "seq": 4271               // …and the immutable sequence number
}

registry-line is SpecAssay's world; ledger is clew's. A viewer links whatever it's given and treats an ID with no resolvable origin as a plain durable token. registry: {path,line} and origin: {kind:"registry-line",path,line} are two spellings of the same fact; emitters may write either, readers accept both.

Anchor shape (implementations / proofs)

{ "path": "src/sync.py", "line": 51, "endLine": 58, "symbol": "reconcile", "sha": "…", "excerpt": "…" }

v4 anchors are { path, line, excerpt } (proofs add name). v5 adds optional endLine, symbol, and sha so a code-anchored emitter can point at a span or a symbol that survives reflow. line remains the common case; everything else is optional enrichment.

rollup — precomputed coverage (option B)

"rollup": {
  "covered": true,
  "children": ["AC-SYNC-01", "AC-SYNC-02"],
  "coveredChildren": 2
}

A requirement is covered when all of its child criteria are covered (proven or excused debt). The children are derivable from the parents edges; rollup carries the emitter's own answer so a viewer need not recompute. The edges are canonical: if rollup.covered contradicts what the edges imply, a viewer trusts the edges and may flag the disagreement.

Status core (unchanged) + native mapping

The four core statuses and their colors are fixed — this is Loupe's contract:

StatusColorMeaning
provengreenNamed carrier exists
tracked-debtamberWork started, proof missing, excused by an open Carries: task
GAPredCriterion with neither proof nor open debt; the gate refuses
backlogbluePlanning altitude; not a silent gap

An emitter whose model doesn't divide the world this way records its own nativeStatus (e.g. "partial", "anchored") and maps it onto one of the four. Loupe never colors on nativeStatus; it's there for round-tripping and tooltips.

Back-compat

Emitter-conformance checklist

The minimum a manifest needs for Loupe to render the intent → build → proof descent truthfully. This is the page to hand a new emitter — the schema doc is the reference; this is the contract.

Everything not on this list is enrichment. A manifest that satisfies it renders; a manifest that adds ext, symbol, sha, nativeStatus, rollup, etc. renders richer.

Worked example — a clew-style row mapped to v5

A ledger-minted requirement and one of its criteria, code-anchored, as clew might emit them:

{
  "schemaVersion": 5,
  "format": "trace-manifest",
  "emitter": { "name": "clew", "version": "0.9.0" },
  "targetName": "ariadne-demo",
  "rows": [
    {
      "id": "REQ-0042",
      "tier": "requirement",
      "type": "requirement",
      "statement": "Offline edits reconcile across devices on reconnect.",
      "status": "proven",
      "origin": { "kind": "ledger", "ledger": ".clew/ledger", "seq": 42 },
      "rollup": { "covered": true, "children": ["CRIT-0043"], "coveredChildren": 1 }
    },
    {
      "id": "CRIT-0043",
      "tier": "criterion",
      "type": "acceptance-criterion",
      "statement": "A change made offline appears on a second device within 2s of reconnect.",
      "status": "proven",
      "parents": ["REQ-0042"],
      "origin": { "kind": "ledger", "ledger": ".clew/ledger", "seq": 43 },
      "implementations": [{ "path": "Sources/Sync/Reconcile.swift", "symbol": "reconcile", "line": 88 }],
      "proofs": [{ "name": "testReconcileWithinBudget", "path": "Tests/SyncTests.swift", "line": 40 }],
      "nativeStatus": "anchored"
    }
  ]
}

Loupe reads this with no knowledge of clew: it orders REQ-0042 above CRIT-0043 by tier, draws the thread along parents, colors both green on status, and links the Swift anchors. The nativeStatus, symbol, and ledger origin ride along untouched.

Non-goals