Rung 2 Track B: verification freshness (last-verified axis) + 1.2.0 review-followup hardening#2
Rung 2 Track B: verification freshness (last-verified axis) + 1.2.0 review-followup hardening#2tachyon-beep wants to merge 24 commits into
Conversation
Package fully renamed charter->plainweave. Updates live member-facing refs: the federation value-add audit (incl. plainweave_* tool names), consumer tickets, vision authority split, federation.md, README/index, the advisory-not-gating concept, PRD-0001, metrics guardrail, and the sibling-import guard (FORBIDDEN_IMPORT_ROOTS) + consumer-ticket test. Leaves generic 'on-charter', the draft-charter skill, historical plan docs, and the evidence-entangled sibling guards (member-diff path + source-grounding) untouched -- those need a baseline refresh / re-grounding against the real plainweave repo, tracked separately. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…cessor Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Revert the always-on top-version verification guard added to _run_migrations (and its _top_version_objects_present helper + _TOP_VERSION_OBJECTS_VERSION constant) as out-of-scope migration-runner hardening. The in-scope v4 block in _schema_presence_floor is retained. Rewrite test_presence_floor_recovers_dropped_table to exercise the real user_version==0 reconcile path (meta claims v4, table dropped, v2/v3 intact -> floor to v3, re-run v4), mirroring the established v3-style test. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…unverified/unavailable)
…ndetermined branch Fix module docstring purity claim to list all three imports (collections.abc + typing + warpline.listing.reason).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… code Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extends test_stale_first_is_secondary_to_an_explicit_sort with a third entity Z (depth=0, stale) alongside X (depth=0, fresh) and Y (depth=1, stale). The new assertion (c) verifies that within the same depth bucket the stale item precedes the fresh one — the gap left unexercised by the previous two-item layout. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… pre-verify envelope
Also relax dogfood real-member parity check from strict path equality to subset check (warpline_paths ⊆ baseline_paths): warpline only tracks code entities, not Makefile/README/docs, so the strict == was a false failure when the selected lacuna commit happened to touch non-entity files. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…+ error symmetry)
Fix 1 (latent crash): list_change_events_for_key_ids built IN-clause
placeholders from raw key_ids but bound sorted(set(key_ids)), raising
sqlite3.ProgrammingError on any duplicate key_id. Derive both from
unique_ids = sorted(set(key_ids)). Regression test added (RED→GREEN).
Fix 2 (doc): _schema_presence_floor docstring referenced v(N>3); after
the v4 migration it should read v(N>4).
Fix 3 (truth-table): lock the precedence that a mixed covers() result on
the latest change (one event None, another True) yields fresh — a positive
cover wins once any True exists.
Fix 4 (error symmetry): empty/blank commit in verify_record now raises
MissingRequiredFieldError(rejected_field="commit") rather than flowing
through resolve_commit("") → BadRevisionError. Present-but-unresolvable
refs still raise BadRevisionError. Test added (RED→GREEN); existing
bad-ref test confirmed GREEN.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… order Replace _latest_covering_event (picks by verified_at recency) with _tightest_covering_event (picks the covering event minimising commits_between to latest_change). When gate records arrive out of git-ancestry order the old helper overstated commits_behind; the new one always reflects the most-advanced proof on record. State classification, reason triples, and fresh/unverified/unavailable paths are unchanged. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…apture The `client is None` branch of `capture_edge_snapshot` unconditionally UPSERTed completeness=SKIPPED and DELETEd edges for the (repo, commit, loomweave) key across two non-atomic commits. For a commit that already held a FULL/DELTA row this downgraded a real edge graph to a 0-edge SKIPPED row — the R3 data-loss class the atomic capture path prevents, but which the absent-client case never reached (warpline-d7d04243b2). Fix (the ticket's preferred, enrich-only/fail-closed option): - store: add get_edge_snapshot(repo_id, commit_sha, source) — exact-key lookup (latest_snapshot is repo-latest-by-id, the wrong key). - snapshot: when a prior FULL/DELTA exists, preserve it untouched and return recapture_skipped=True against it (regardless of staleness — a stale FULL is still a real graph; the read path downgrades stale completeness itself). With no usable prior, write SKIPPED via the single-transaction capture_snapshot_atomic(edges=[]), retiring the old two-commit UPSERT+DELETE write. - commands: append a PRESERVED warning. The envelope is then the honest triple — completeness=FULL/DELTA (real graph) + sei=unavailable (peer down) + warning (not refreshed) — mirroring the if_stale_after short-circuit (edges=0, entities=0, already_current). test_gv_lw_3 realized "loomweave absent -> SKIPPED" as a recapture at the SAME commit holding the FULL, i.e. it asserted the downgrade — contradicting GV-LW-6 (preserve prior, never a degraded/0-edge row). The frozen manifest assert text never said "absent at a commit holding a FULL", so it stays literally true; only the test's incidental same-commit realization moves to a no-prior commit (c2). Manifest JSON untouched; the vector set stays frozen at 19. The preserve invariant is locked by GV-LW-6 plus new unit/CLI tests (full-preserve, delta-preserve, no-prior-atomic, and a CLI PRESERVED-warning test); the old test that pinned the bug is inverted, and present-client edge-replace stays covered by test_capture_snapshot_atomic_replaces_edges. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… + ENVELOPE_KEYS build_envelope always emits the top-level enrichment_reasons key (envelope.py:97), but the static contract fixtures and ENVELOPE_KEYS omitted it, and _assert_frozen_envelope's subset check (ENVELOPE_KEYS <= set(fixture)) silently tolerated the absence — so the static reference envelopes no longer matched the runtime the hub consumes. - Add enrichment_reasons to ENVELOPE_KEYS. - Add the faithful runtime block to both mcp-response fixtures: the reserved-but-honest requirements 'disabled' triple on both, plus the sei 'clean' triple on changed (its enrichment.sei is 'present', and change_list attaches sei_reason); reverify carries requirements only (it passes no enrichment_reasons to build_envelope). - Assert the block in _assert_frozen_envelope, mirroring build_envelope's contract (envelope.py:78-88) + reason() (listing.py:43): every dimension in the closed vocab, every value a canonical reason_class, non-clean carries cause+fix, and requirements is universally 'disabled'. Test-only hygiene; no src change. Closes warpline-fc09bdeddd. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ueError)
listing.reason() enforced its carrier rule (class-membership, and every
non-clean carrier MUST carry both cause and fix) with bare asserts, which
python -O strips. build_envelope only re-checked reason_class membership,
not cause/fix. So under -O a hollow {reason_class: 'disabled'} triple with
no cause/fix would pass validation and ship -- the exact 'unexplained
absence' the honesty doctrine forbids.
- listing.py: promote both reason() asserts to raised ValueError (keep the
clean short-circuit). Closes the helper-built-triple hole.
- envelope.py: build_envelope now rejects a non-clean enrichment_reasons
triple missing cause/fix (clean exempt). Closes the parallel
hand-built-via-kwarg hole, which bypassed reason() even without -O.
- _enrichment.py: sei_reason() is non-Optional -- raises ValueError on an
out-of-vocab state instead of returning None.
- commands.py: delete the four now-dead 'assert sei_triple is not None'
narrowing guards (also -O-strippable); sei_reason's non-Optional return
makes them redundant. Param kept as str (not Literal) to avoid mypy
[arg-type] churn at the call sites.
Tests: flip the reason() AssertionError expectations to ValueError; rewrite
the sei_reason None test to expect ValueError; add coverage that
build_envelope rejects a hollow non-clean triple via the enrichment_reasons=
kwarg.
Internal hardening; frozen warpline.<contract>.v1 envelope and the closed
enrichment vocab unchanged. Verified: full suite green (5 known pre-existing
env failures only), python -O proof passes on all paths, mypy unchanged
(1 pre-existing no-any-return), ruff clean. Closes warpline-d88e223731.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…→Plainweave refs Adds/normalizes the 'not-for-X' Banner naming this member's specific misuse (deconfliction-first, not security/compliance); fixes hardcoded Charter→Plainweave prose. Re-vendored kit; build green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…econcile workspace to plan/verification-freshness Execution-only session on the PDR-0006 deferred follow-ups; no product bet decided/killed/reprioritized, so no new PDR. current-state.md reconciled from the stale 2026-06-24 (main @ v1.2.0) brief to present reality: branch plan/verification-freshness, verification-freshness BUILT-but-unreleased (Track B in CHANGELOG [Unreleased]; reconciliation-debt flag for its missing acceptance PDR), and the follow-up tracker state. metrics.md gains a 2026-06-26 quality-debt-burndown reading (honesty guardrail strengthened: weft-reason invariant survives python -O); no reversal trigger crossed. roadmap.md/vision.md untouched (no horizon or strategy change). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fe9d51b782
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if not entity_change_commits: | ||
| return _unverified("the entity has no recorded change commits to verify") | ||
|
|
||
| latest_change = entity_change_commits[-1] # oldest-first input -> latest is last |
There was a problem hiding this comment.
Use topology, not authored-at order, for latest change
This assumes the last element is the newest change, but the new reverify caller builds entity_change_commits from list_change_events_for_key_ids(), which orders by changed_at; ingestion stores Git author time there. In rebases/cherry-picks or applied patches, a later commit can have an older author date, so verifying C0 and then landing C1 with an earlier author date makes this pick C0 as latest_change and report the entity fresh even though C1 is unverified.
Useful? React with 👍 / 👎.
| commit=str(args.get("commit", "")), | ||
| kind=str(args.get("kind", "")), |
There was a problem hiding this comment.
Reject null kind before storing verification events
When an MCP caller sends kind: null, this coercion turns it into the literal string 'None', so warpline_verification_record succeeds and writes a verification event with kind = 'None' even though the advertised input schema requires a string. Because MCP dispatch does not validate the schema before calling handlers, the handler needs to type-check kind (and similarly avoid stringifying invalid values) rather than persisting malformed provenance.
Useful? React with 👍 / 👎.
| out = _git_optional(repo, ["rev-parse", "--verify", "--quiet", f"{ref}^{{commit}}"]) | ||
| if out is None: | ||
| return None | ||
| return out if len(out) == 40 else None |
There was a problem hiding this comment.
Accept full object IDs instead of assuming SHA-1 length
In a Git repository initialized with --object-format=sha256, git rev-parse --verify HEAD^{commit} returns a 64-character commit ID, so this length check returns None and warpline verify-record --commit HEAD fails as an invalid revision. Since Git object ID length is repository-dependent, the resolved commit should not be rejected solely because it is not 40 characters.
Useful? React with 👍 / 👎.
| parity = ( | ||
| baseline["baseline_executed"] is True | ||
| and baseline_paths == warpline_paths | ||
| and warpline_paths <= baseline_paths |
There was a problem hiding this comment.
Require dogfood parity to find every trackable code path
This subset check only proves Warpline did not invent paths; it no longer proves it found all code paths. In a real-member dogfood run where the baseline sees changed code files a.py and b.py but changed_data only reports a.py, warpline_paths <= baseline_paths remains true as long as any changed key exists, so parity/uplift can pass while missing code changes and corrupt the productization metrics. Filter the baseline to Warpline-trackable paths and compare the expected set for coverage instead of accepting any subset.
Useful? React with 👍 / 👎.
Summary
Merges
plan/verification-freshnessintomain. The headline is Rung 2, Track B — verification freshness: warpline now tracks alast_verifiedaxis sourced from its own gate result, so the reverify worklist answers "changed since last proven-good" (not just "changed since HEAD~1") with a trust-decay signal. The branch also folds in the v1.2.0 release-grade-review follow-up hardening and the docs/product checkpoint.Everything here is advisory, enrich-only, and never gates. The frozen
warpline.<contract>.v1envelope and the closed 6-keyenrichmentvocab are untouched — verification rides the reverify-item schema and adata-block summary, not the enrichment vocab.What's included
Verification freshness (Rung 2, Track B)
verificationblock on the reverify worklist (fresh/stale/unverified/unavailable) with a trust-decay signal, plus averification_summaryrollup.verify-record(CLI) /warpline_verification_record(MCP) — the 2nd mutating tool; writes.weft/warpline/only.verification_eventstable; presence-floor-scoped recovery), git reachability helpers (is_ancestor/commits_between/resolve_commit), purecompose_verification_freshness, and stale-first reverify ordering (same-depth tiebreak).GV-VF-1locks the honesty + never-filter invariants (vector count → 19).v1.2.0 review-followup hardening (from PDR-0006's deferred punch-list)
warpline-fc09bdeddd— frozen-envelope contract fixtures +ENVELOPE_KEYScarryenrichment_reasons(static reference matches the runtime the hub consumes).warpline-d88e223731— the weft-reason honesty invariant now survivespython -O:reason()andbuild_enveloperaiseValueErrorinstead of relying on-O-strippableasserts;sei_reason()is non-Optional. Closes both the helper-built and hand-built-via-kwarg paths.warpline-d7d04243b2— SKIPPED snapshot path preserves a usable prior FULL/DELTA when loomweave is absent at recapture; plus four earlier review follow-ups.Docs / product
CHANGELOG.md[Unreleased]: verification freshness; honesty-Ohardening.current-state.md,metrics.md) reconciled to this branch.9d21d0d).Contract safety
warpline.<contract>.v1envelope: unchanged.enrichmentvocab: unchanged (verification is a reverify-item field +datasummary, never an enrichment key).enrichment_reasonscarrier and the newverificationblock are purely additive.meta.local_only: true,peer_side_effects: [],mutates_paths: [".weft/warpline/"]).Testing
python -Oproof confirms the honesty invariant raisesValueErroron every hollow-triple path (helper-built, hand-built-via-kwarg, out-of-vocabsei_reason).no-any-return, unrelated). ruff: clean. Public-docs hygiene: clean.Notes for the reviewer
vision.md(changing public release status outside the repo).warpline-17242c627b(atomic ROLLBACK coverage + precondition guard).warpline-9eae3eb86a(Charter→Plainweave evidence refresh) is gated on the localplainweavesibling repo and is out of scope here.🤖 Generated with Claude Code