v3/docs/adr/ADR-103-witness-temporal-history.md
Status: Accepted
Date: 2026-05-08
Version: [email protected]+ / @claude-flow/[email protected]+
Related: ADR-102 (CI smoke harness), #1867, #1859, #1862, project memory project_verification_process.md
The signed witness manifest at verification.md.json (described in
verification.md) attests that every documented fix in the codebase is
still present, by SHA-256 + marker substring, signed Ed25519 with a
deterministic seed derived from the git commit.
It works well as a snapshot, but has two structural gaps:
The manifest is overwritten every regen. A fix that flips pass → drift → regressed carries no history. When CI says "F12 is regressed", you can
only answer "as of HEAD, marker is missing" — not "introduced between
commit X and Y, on date D, in PR #N". For a project with 80+ tracked
fixes across rapid alpha churn, this turns regression triage into a
manual git-bisect every time.
The regen logic was originally inline in shell heredocs (per the
project_verification_process.md memory) and later extracted to
scripts/regen-witness.mjs. Both forms hard-code ruflo's paths and
fix list. Other projects can't adopt the witness pattern without
copy-pasting and rewriting. Given that several downstream consumers
of ruflo and @claude-flow/cli ship their own fixes, this is a
real adoption blocker.
Each regen appends one line to verification-history.jsonl:
{
"v": 1,
"commit": "<gitCommit at issuance>",
"issuedAt": "<ISO timestamp>",
"branch": "<branch>",
"manifestHash": "<sha256 of canonical manifest>",
"summary": { "totalFixes": N, "verified": M, "missing": K },
"fixes": {
"F1": { "sha256": "...", "markerVerified": true },
"#1867":{ "sha256": "...", "markerVerified": true },
/* ... one entry per fix, keyed by id ... */
}
}
Format choice: JSONL because
pending-insights.jsonl).The file is committed alongside verification.md.json. They must move
as a pair — the manifest is the latest signed snapshot, the JSONL is
the timeline that proves it's the latest.
ruflo-core plugin assetThe witness scripts move to plugins/ruflo-core/scripts/witness/:
| File | Purpose |
|---|---|
lib.mjs | Pure functions: regenerate, appendHistory, loadHistory, findRegressionIntroductions, fixTimeline, diffLatest |
init.mjs | Bootstrap empty manifest + history + fix template into any repo |
regen.mjs | Sign the manifest + append history (the canonical workflow) |
verify.mjs | Validate signature + markers against the live tree |
history.mjs | Query the temporal log: summary, regressions, timeline, list |
Plus exposure as Claude Code surface area:
| File | Purpose |
|---|---|
plugins/ruflo-core/skills/witness/SKILL.md | Workflow doc + anti-patterns |
plugins/ruflo-core/commands/witness.md | Slash-command thin wrapper |
plugins/ruflo-core/agents/witness-curator.md | Agent for adding fixes / interpreting regressions |
The scripts are project-agnostic: they take --manifest, --history,
--fixes, --root flags. They probe <root> and <root>/v3 for
@noble/ed25519, so they work in monorepo and flat layouts.
The ruflo internal entrypoint at scripts/regen-witness.mjs is now a
thin wrapper that hard-codes ruflo's paths and reads the
project-specific fix list from witness-fixes.json. Single source of
truth lives in the plugin.
The witness-verify job in .github/workflows/v3-ci.yml (added by
ADR-102 §3) gains a follow-on step that runs history.mjs summary
to surface transitions since the previous snapshot. The summary
subcommand exits non-zero on any newly-regressed fix — informational
in the current pipeline, but adopters can promote it to a hard gate
in their own CI.
history.mjs regressions walks the JSONL backwards to find, for each
currently-regressed fix, the most recent snapshot where it was passing
(lastPassCommit). The next snapshot's commit (regressedAtCommit)
brackets the regression to a single window. Combined with git log lastPassCommit..regressedAtCommit -- <file>, this collapses bisect
to a small set of commits to read.
appendHistory strips the manifest's desc/marker/file fields
from the per-fix record. Those don't change frequently and would
bloat the JSONL. If a marker is updated mid-stream, the JSONL still
records the SHA-256 + verified-status pair from each regen, which is
the load-bearing signal for regression detection.verify.mjs is parallel to the bundled ruflo verify command but
has no ruflo CLI dependency — adopters who only want the witness
toolkit can install one tiny package (@noble/ed25519) and run it.plugins/ruflo-core/scripts/witness/ and init-ing — no ruflo
install required.verification-history.jsonl).@noble/ed25519 (~15 KB
minified, no transitive deps)./ruvector as a substrate. JSONL is sufficient for
the queries we need today; an HNSW-backed similarity index over
snapshot fingerprints is a follow-up if pattern-based queries
("snapshots most similar to commit X") become valuable.verify.mjs script duplicates a small amount of logic with
v3/@claude-flow/cli/src/commands/verify.ts. Acceptable for now
(the standalone needs to work without the CLI installed); could be
unified later if both move to the plugin.plugins/ruflo-core/skills/witness/SKILL.md — adoption guideplugins/ruflo-core/agents/witness-curator.md — agent definition~/.claude/.../project_verification_process.md — original inline
regen process; superseded by this ADR's plugin-extracted form