Skip to content

Selvedge v0.3.11

Latest

Choose a tag to compare

@github-actions github-actions released this 29 Aug 21:12
· 2 commits to main since this release

Selvedge v0.3.11 — Active memory v2 + a tamper-evident log

Abandoned alternatives are first-class, and the log can prove itself. The active-memory release: the expires_when column that shipped dormant in v0.3.8 gets its evaluator, reject joins revert so paths never taken are recorded the same way paths rolled back are, and prior_attempts reads both as stated outcomes instead of inferring them.

Riding along, pulled forward from the verifiable-claims phase: a tamper-evident hash chain over the event log. A store whose differentiator is retaining rejected decisions should be able to show the retention is intact — so selvedge verify now recomputes a SHA-256 chain over every logged event and names the exact sequence number of a row that was edited, deleted, or reordered out-of-band. Honest scope, stated in the module itself: this detects casual and accidental modification and produces an independently verifiable export; it is not proof against a motivated local attacker, who controls the file and can recompute every digest.

Added

  • Tamper-evident hash chain (selvedge-chain/1, design (d) of the design doc). Every logged event gets a SHA-256 chain record in a sidecar event_chain table, written in the same transaction, over a canonicalized 17-field core — every events column except git_commit, which is late-bound by the post-commit hook and independently checkable against git. Legitimate mutators append boundary records instead of breaking the chain: migrate-paths --apply appends an amend record, and the destructive-gated prune appends a tombstone counting the chained rows it deleted — so a gated prune verifies clean while a silent sqlite3 deletion of a chained row fails verification. Zero new dependencies (stdlib hashlib/json), no migration of the events table, genesis is automatic on first write after upgrade.
  • Two new selvedge verify checks. chain_intact (must-fail) recomputes the chain end to end — hashes, prev_hash threading, seq contiguity, tombstone accounting; nothing legitimate can trip it. chain_coverage (warn-only) counts events rows with no chain record — expected on every upgraded install, so it warns without breaking CI. Pre-genesis rows are unchained, not invalid.
  • Chain attestation manifest in selvedge verify --json (chain_manifest): storage mechanism, chain algorithm, canonical-form version, verification-procedure reference — the four fields MCP SEP-3004 §2.7 asks a self-attesting store to publish, producible here precisely because the verifier ships in the package. (Selvedge assessed itself against that draft contract and declares its divergences in the design doc; it does not claim conformance.)
  • expires_when evaluation in selvedge stale. A decision can carry a machine-checkable expiry condition in a closed four-shape grammar — library:NAME>=VERSION, entity:PATH:changes, date:ISO, manual:LABEL — validated at write time on every path; values outside the grammar are rejected, not stored. Evaluation is local-only, no network, no LLM: installed distribution metadata, the event log itself, and the clock. A library: condition whose package isn't locally observable presents as manual_review rather than a guess.
  • change_type="reject". Records "we considered this and decided against it" without writing the change — the counterpart to revert for paths never taken. No new MCP tool. Adoption defended on three surfaces: a worked example in the log_change docstring, a prompt-block sentence telling agents to log rejections, and a reject-specific validator rule nudging the reasoning to name what was chosen instead.
  • stale_when capture nudge. A reject or revert logged with neither stale_when nor expires_when gets a suggestion to record the invalidating condition — the reason a decision was made is also the condition under which it should die. Warns, never rejects. Digest rows whose stale_when has since matched present as re-examine rather than a bare reverted — presentation only; the stored verdict never mutates.
  • selvedge supersede gains -d/--diff, --revisit-after, and --expires-when (#31), with the same semantics as selvedge log, routed through the same size-bound and secret-shape checks as every other write path.

Changed

  • prior_attempts confidence tiers. An attempt closed by an explicit reject or revert now reports confidence: "exact" — the outcome is stated in the log, not inferred; the v0.3.7 proximity heuristic drops to tiebreaker. No API shape change, but results move: callers filtering on proximity_high should also accept exact.
  • PreCompact reminder contracts, pinned. Repeat-fire stability is now asserted byte-for-byte (and the reminder goes quiet once the log_change lands), and the reminder distinguishes "edited with no log" from "log exists but was truncated" — the two cases have different fixes.
  • SessionStart digest selection order is a documented contract, pinned by tests including a seeded fixture for the digest-boundary race (a supersede landing between seed and render yields a digest naming exactly one effective verdict). Deliberately dumb ranking — overdue-ness and recency, nothing semantic.
  • selvedge prompt block now tells agents to log rejections and reverts with their invalidating conditions; the bundled skill is regenerated from it.

Fixed

  • An id-less supersede no longer re-opens every earlier revert on the path (#30). The timestamp fallback in prior_attempts matched a supersede-without-link against every earlier closing removal; it now mirrors log_supersede's own auto-link rule and re-opens at most the single removal it would have auto-linked to at write time.

Notes

  • Test suite 984 → 1114. The phase's soft budget is ≤40 new tests and this release exceeds it deliberately, which the budget discipline requires calling out: the overrun is the tamper-evidence chain pulled forward from the verifiable-claims phase, on top of the expires_when grammar suite and the hook-contract fixtures the v0.3.10 release-thread feedback committed this release to. Coverage 89.4%, flat. The chain checks are mutation-verified — deleting the prev_hash/seq threading checks or the pagination makes the suite red.
  • Schema tax 4445/5450 tokens (was 3705/3800): log_change gained the expires_when parameter and get_stale_decisions documents the three surfacing flags. The core tool surface is 2.2% of a 200k-token window.
  • The chain makes no claim about rows that predate it — including whether they still exist. A chain seal command for opt-in retroactive coverage is future work, per the design doc. Selvedge's own dogfood store crossed its genesis with this release: the decisions behind v0.3.11 are the first chained rows, including a real change_type="reject".

Install: pip install selvedge==0.3.11
PyPI: https://pypi.org/project/selvedge/
Docs: https://github.com/masondelan/selvedge/blob/main/docs/getting-started.md