Coherence runtime: sever dead C2, type the command surface, and fix two quadratics (v0.9.30) - #1225
Merged
Conversation
…ty research - design-runtime.md (v3): forward-operator core design record, banked after four cross-model review passes converged the accept-protocol spec; SP-A spike retracted honestly; state/staleness notes; runtime plan retired to input with the full Codex disposition trail. - coherence-layer-paper.md v2.0: framing broadened to name the semantic-state-runtime arc (creative work = vertical #1; generality earned, not declared); §14 kept unconditional; ACCEPT-WITH-CHANGES re-review applied. vision.md verification-gate reconciled with the advisory-check model. - deep-research (20260719): viability SOUND, novelty HOLDS, shipped stack vindicated, accept-protocol = textbook idempotency+event-sourcing; corrections applied ('~20%' is method-success not base-rate; checker guardrails added). - verify-at-volume-baseline.md: drift-gauge baseline + turnkey runbook.
Isolated tests/ integration harness that opens the repo's own .vmark ledger and reports checkable (version-stale) edges via the kernel's breakdown() — Step 0 of verify-at-volume (sweep vs evolve), provider-free. Diagnoses 0-counts (coherent corpus vs absent files) via registry present/absent. Adds uuid as a dev-dependency (already a runtime dep; Cargo.lock unchanged). Run with --ignored --nocapture.
31-design-tokens.md documents --spacing-1/2/3 as the tokens to use for padding, margin and gap, and index.css's own primitives-layer comment points authors at --spacing-2 — but they were never defined anywhere. Every var(--spacing-*) silently resolved to nothing: 123 references across 11 stylesheets collapsed to zero padding and zero gap (the cramped Breakdown / Claim / Browser / KnowledgeBase / MediaView panels). Defined as semantic aliases over the --space-* primitives at the documented 4/8/12px. Verified live in the running app: the tokens resolve and the affected gaps/padding now compute correctly.
…n form
Row actions rendered as one unreadable run ('CheckAccept newerReviseWaive'); they are now discrete bordered chips with real padding and an 8px gap that wrap instead of clipping. Panel widened 380->460px with overflow-x hidden, and min-width:0 on the row so long paths ellipsis instead of shoving badges past the edge. The delegation form's principal input takes its own full-width row, with scope toggles, days and Grant wrapping beneath and Grant parked at the trailing edge so it can no longer clip off-panel. Visually verified via screenshot + computed styles.
The posture paragraph read 'Only explicit human acts may ... resolve an edge', contradicting both paper R12/R29 (a human OR an explicitly delegated agent) and design-3's own D2, which exists precisely to authorize 'actions no human performed directly'. Surfaced by the codex semantic checker during the verify-at-volume sweep; the quoted evidence was verified against the source before acting. Resolution is now stated as by a human, or by a human-delegated agent under an explicit audited grant.
…-edge ranking Adds a registry present/absent diagnostic — distinguishing a genuinely coherent corpus from one whose registered files have moved (their edges are hidden per spec 9.4) — and ranks objects by how many input-edges they are upstream of, so evolve targets for a sweep are chosen from data rather than guessed.
…itions Records the first full verify-at-volume run on the repo's own corpus: the paper's v2.0 revision captured by scan reconciliation, codex cross-model check-results over its live dependents, a human-granted claude-code delegation, and five delegated accept-newer ratifications. The contradiction rate is now computable (12 checks, 25%) where it was INSUFFICIENT DATA at 3.
Tightens the shared agent instruction from a conditional ('unless another language is requested') to an unconditional rule, so agent replies stay in English even when prompted in another language.
The pinned default 'gpt-5.3-codex' is not in the Codex catalog at all — every cc-suite command in this repo 400'd on the model override with a ChatGPT-account login. Preflight (codex-cli 0.144.6) reports 8 available models with 'gpt-5.6-sol' as the catalog default; the configured effort/audit-type/sandbox values are all still valid.
…width Audit follow-ups. The new --spacing-* aliases were declared inside the block explicitly labelled PRIMITIVES LAYER, contradicting that block's own direction to keep semantic tokens above it; they now sit above the banner (custom properties resolve independently of declaration order). The BreakdownPanel's one-off 460px width becomes a local --breakdown-panel-width component variable per rule 31.
Rebuilds the harness against an in-memory index so the app's index.db is never opened (it was being opened read/write, running DDL, with drop/rebuild on schema mismatch — while the running app held it). Binds the real default-context claims fingerprint via breakdown_checked, mirroring perform_breakdown_in, so edges that already carry a check are no longer miscounted as unchecked. Uses real UTC instead of a hardcoded timestamp; try_exists with a first-class Unknown state; path validation plus canonicalized containment so a registry symlink cannot escape the workspace. SWEEP is now computed from executable candidates — each verified to have its three CAS texts present and UTF-8 — rather than raw graph candidates. Splits the misleading single dep-edges number into labelled live-stale and historical columns. Extracts the verdict decision tree into pure classify()/probe() functions with 10 assertions (threshold boundary, empty registry, full/partial absence, unreadable paths, symlink escape) where the previous test asserted nothing. Drops the redundant uuid dev-dependency: integration tests inherit normal dependencies. Remaining limits (no disk reconciliation, ledger quarantine writes, live-stale not being a total dependent count) are documented as deliberate trade-offs.
…ures is_executable() was implemented but its loop body never ran: the live corpus had zero candidates, so the guard against promising a sweep of uncheckable edges rested on reading alone. Build a synthetic corpus instead — real snapshot store, in-memory index rebuilt from synthetic transformations — and assert all three branches: all texts present and UTF-8 counts as executable; the same edge against an empty CAS does not; a present-but-non-UTF-8 downstream does not.
… fix SP-A retraction Lift the runtime plan off NEEDS REVISION. The design session the first G-B demanded is done and BANKED (design-runtime.md v3); this folds its ontology, candidate/accept model (D1-D8), and SP0 gate into the WIs, and dispositions Codex Themes A-E: - SP1 reformulated: observational multiset equality over a disposable clone (append-only has no rollback) — retired the ill-formed rollback equality in WI-0.1, WI-3.1, and ADR-P1. - Contradiction stays a check-result assessment, never a registry edge kind (WI-2.1, ADR-P3); only the version axis becomes a registry entry. - Canon re-cast claim-based / Context-hinged, not an object flag; Extract-Canon and Phase 4 deferred to their own increment design pass (SP-canon). - Operators are built-in Rust, not Tier-1 schema-pack functions (WI-3.2). - Accept's three review-4 BLOCKERs become WI-3.4 acceptance criteria; delegated accept deferred, does not ride the resolve path (WI-3.6). - Added SP0 (WI-0.6) as the real Phase-3 gate and SP4 (WI-5.0) merge-mapping spike as the Phase-5 gate. - Contract bumped to paper v2.0; G-A recorded resolved. Also reconcile design-runtime.md: SP-A now reads INCOMPLETE in D4 and the review-3 disposition, matching its status header and spike-accept-primitive.md.
…ound-2 fixes SP1 (WI-0.1) — dry-run projection over a disposable clone — PASSES 3/3 (linear restale, downstream retirement, divergence). Proves ADR-P1/D2: a candidate preview via a cloned-DAG overlay yields the same projection multiset as an independent commit-rebuild, and mints nothing (on-disk ledger bytes unchanged). Report at grills/coherence/spike-sp1-dry-run-projection.md. design-runtime.md v4 — discharges the runtime plan's G-B round 2 (019f7b48, MAJOR GAPS, partially discharged) design findings from named fixes into specified protocols: - v4.1 full canonical accept idem preimage (supersedes D4's 3-field formula) - v4.2 applied.entry_id + entry_id_by_idem receipt lookup (BLOCKER 1) - v4.3 reproject-under-lock accept precondition (BLOCKER 3; no fragile digest) - v4.4 bounded ReadView + PREVIEW_MAX_EDGES cap (completes D7) - v4.5 local_projection_delta vs preview_forward_closure blast-radius split - v4.6 content-addressed candidate lifecycle across IPC - v4.7 additive edge_kind wire slot; v4.8 estimated cost; v4.9 SP0 → Phase 3.0 - un-banks the record for the funded build; SP-A stays retracted Plan re-decomposition v2 — applies every round-2 disposition: contradiction is not a registry entry (ADR-P2/SP3/Phase-2/WI-2.1); object-flag canon deleted (SP2→SP-canon, WI-4.1 claim-hinged); SP0 restructured out of Phase 0 into a committable Phase 3.0 primitives sub-phase; Phase-1 DoD gates on live-edge coverage + cost + resume; epcho pinned by content hash; round-2 disposition table recorded. Status: READY FOR G-B ROUND 3.
…v4 + spec rev3 fix G-B round-3 G-B round 3 (019f7b59): MAJOR GAPS but converging — 8/15 addressed, remaining were precise correctness bugs in v4. All fixed: Critical A — v4.3 reproject-under-lock compared raw EdgeState, which folds in check verdicts, so a concurrent semantic check would reject accept (violating 'semantic verdict never blocks'). Fixed: compare a CHECK-INDEPENDENT structural class (VersionStale/StaleValid/StaleContradicted/StaleUnknown collapse to one Stale token). WI-3.0e + spec §13.5 updated; SP1 helper implements it. Critical B — v4.2 index-only idem lookup missed the append-before-index-apply torn window (ledger appends first and never dedups; dedup is read_all-only). Fixed: lookup is ledger-authoritative; the crash window heals on next open via rebuild_from's deterministic dedup (smallest time,id wins → original receipt). High C — v4.1 preimage was newline/colon-delimited, ambiguous for free-text agent.id/intent.summary. Fixed: length-prefixed, injective encoding (spec §13.3). High D — SP1 proved manual DAG-overlay equality, not the real store path. Added overlay_matches_real_committed_index: rebuilds a real in-memory CoherenceIndex from the actual envelopes and compares breakdown() to the overlay (4/4 green). Also: SP3 classifier spike PASS (kernel-registry decision recorded, 5/5); check-coherence-runtime-phase.sh (Phase 0 gate green 13/13, later phases fail-closed); spec rev-3 addendum §13 (operator intent, candidate payload, idem preimage, preview→accept binding, edge_kind); plan stale-ref fixes (design v4 not v3-BANKED; content-hash not commit-hash; Phase-1 numeric gates; Phase-3.0 committable before SP0).
Normalize formatting so the pre-push rustfmt --check gate stays green. No behavioral change (both suites pass: SP1 4/4, inventory 11/11).
…3) + SP3 two-prototype
G-B round 4 (019f7b68): NEEDS REVISION (up from MAJOR GAPS). Most items FIXED;
remaining were precise correctness holes in v4, now closed:
Critical A (v4.3): the reproject precondition compared an UNKEYED class bag, so a
compensating swap (edge A Fresh->Stale, edge B Stale->Fresh) nets the same bag
and escapes. Fixed: compare a MAP keyed by SemanticEdgeKey; the class preserves
Fresh{ratified,ahead} and erases only the check verdict. Property test added.
Critical B (v4.2): the heal-on-open argument was wrong — open() only rebuilds on
needs_rebuild, so a torn ledger-append-before-index-apply entry isn't healed; and
clock rollback breaks 'smallest time = first'. Fixed: the idem lookup is
LEDGER-AUTHORITATIVE, lookup-before-append under the lock, so at most one entry
per idem ever exists — no survivor/rollback question arises.
PARTIAL #14 (v4.1): preimage omitted intent.prompt_hash and aliased agent.id
None with Some(''). Fixed: added prompt_hash; opt() presence-byte disambiguates.
Synced to spec §13.3.
Medium E (WI-0.3): SP3 prototyped only the kernel side. Added a Tier-1
schema-pack declaration sketch whose absent propagation field refutes side (b)
by construction (SP3 now 6/6). WI-0.3 wording aligned.
Also: SP1 4/4 (real-index comparison), clippy-clean + fmt on both spikes, minor
drift fixed (spec rev 3; SP1 4/4). Phase 0 gate green 13/13. Rounds 3-4
disposition table recorded.
…t SP3 both-sides Round 5 (019f7b7e): confirmed v4.2 (torn window) and v4.1 (preimage) CORRECT; 2 residual blockers fixed: 1. v4.3 keyed the reproject precondition by SemanticEdgeKey, which is a BAG key (non-unique) — coincident edges collide and a per-physical-edge waiver is missed. Fixed: key by physical edge identity (txf, input_idx, downstream, downstream_rev); candidate edges by (candidate_rev, ordinal). Unique. Synced to spec §13.5 and WI-3.0e (added a coincident-edge property test). 2. SP3's schema-pack 'refutation' was rigged (deleted the propagation datum then called the kernel), and the kernel registry itself models propagation as enum DATA — so 'propagation is behavior not data' was false. Rewrote SP3 (7/7): a real schema-pack data table + generic interpreter that reproduces the version axis; decision re-grounded on type-safety / compile-time totality / single-source vs runtime data-extensibility. Both placements fairly prototyped; report + module doc corrected. Also fixed the state.rs:160 -> :179 citation. Phase 0 gate green 13/13. Rounds 3-5 disposition table recorded.
Round 6 (019f7b88) confirms both round-5 fixes CORRECT; verdict READY TO BUILD, no Phase-0/Phase-1 blocker. Records the final disposition and flips the plan status. Phase 0 is complete (gate 13/13, SP1 4/4, SP3 7/7, spec rev 3).
The binding constraint the deep research named: the checker had zero defense at volume. Adds check_sweep.rs — pure, fully-tested governance primitives: - estimate_tokens / estimate_cost_usd (v4.8: char->token->$, labelled ESTIMATED since the provider returns no usage; DEFAULT_RATE so an unknown model is never free) - SweepBudget with a graceful stop that refuses a call BEFORE it would exceed the ceiling (never overruns) - Backoff: exponential-with-cap, saturating (no overflow at high attempt) - CheckedKey + SweepPlan: resume cursor keyed by (txf, input, checked_against, claims_fingerprint) so a resumed run adds zero duplicate check-results - RunManifest: coverage denominator (distinct live stale edges), checked/skipped/ errored, estimated cost, nearest-rank p95 latency 16 tests cover every primitive plus the volume failure paths (budget exhaustion graceful stop, resume-after-abort adds no duplicates, partial batch). The async provider loop (WI-1.1 driver) consults these and lives in the service tier.
The testable core of the volume harness, composing the WI-1.4 governance: - is_checkable: only version-stale edges (with/without a verdict) — never Fresh/Waived/Diverged/Unpinnable (no single upstream revision). - cost_model_for: per-family blended rates + conservative default (never free). - estimate_check_cost: pre-call estimate driving the budget's graceful stop. - SweepConfig: builds a cursor-seeded SweepPlan + backoff. - SweepReport: serializable run manifest (coverage/error-rate/p95, cost labelled estimated). - index.checked_cursor(): reads (txf,input,checked_against,fingerprint) from the check_results table — the resume cursor; fingerprint-less pre-rev-1 rows are skipped. Returns raw tuples so storage stays independent of the service tier. 6 driver tests incl. an end-to-end cursor read through a real in-memory index. The sweep loop shape is already proven in check_sweep.test.rs; the async provider command wiring it is exercised by the WI-1.3 dogfood run.
…rness The runnable harness: sweeps live stale edges under a cost ceiling, seeded by the resume cursor, returning a SweepReport (coverage / error-rate / p95 / estimated cost). Control flow mirrors the proven check_sweep.test.rs loop; only the IO (prepare_check, provider call outside the lock, record_check) is added. - Provider timeout/error → unknown verdict (checker discipline) AND counts to the error rate; the result is still recorded so a resume skips the edge. - Budget graceful stop: refuses a call before it would exceed the ceiling. - record_errored now carries cost so the manifest total matches budget spend. Registered in command_registry. Compiles clean, 22 governance/driver tests green. Its runtime exercise is the WI-1.3 dogfood run (live app + provider).
…y (WI-2.1)
Phase 2, ADR-P2 (kernel placement per SP3). Generalizes the hardcoded 'all
direct edges carry version staleness' into a typed registry, behavior-preserving:
- edge_kind.rs: OriginEdgeKind {Dependency, Conformance, Supersession, PartOf,
Mention} with a no-wildcard propagation()/origin() match (compile-time
totality — SP3's reason for the kernel over a schema-pack). Contradiction is
NOT a kind (Propagation has only Version|None). 7 tests, legacy tag -> Dependency.
- OriginEdge gains ; project_edge gates the version branch on
propagates_version() — an inert kind (part-of/mention) is live and visible but
never enters the stale set (structural Diverged/Unpinnable still surface).
- edges.edge_kind column (schema v4, DEFAULT 'dependency'); breakdown + edge_by
read it. apply_entry unchanged (DEFAULT fills it) so every legacy edge reads as
a dependency — format stays 0 (spec §13.6).
Characterization tests freeze both behaviours (dependency stale; inert never
stale; inert still surfaces divergence). Full coherence suite 285/285, spike
4/4 — Dependency behaviour is byte-identical to before.
EdgeRow gains a read-only kind field (the wire tag: dependency/conformance/...), populated by breakdown from OriginEdge.kind. This is the backend half of WI-2.4 — the read model now carries the kind so a frontend breakdown can group/label by it (R23 intact: read-only). The frontend grouping + i18n x10 is the remaining half. Coherence index suite 16/16, inventory 11/11, clippy clean.
…3.0c)
Phase 3.0. The review-verified accept BLOCKER (design v4.1 / spec §13.3, G-B
rounds 3-6 confirmed CORRECT): a deterministic UUID over the COMPLETE canonical
commit payload so a lost-response retry collapses to one entry while distinct
candidates never collide.
- Length-prefixed fields + a presence byte on every optional make the preimage
injective: no free-text value (agent.id, intent.summary) can forge a field
boundary, and None is never aliased with Some('').
- Covers output object/hash/rev/sorted-parents, every input (object/rev/role),
agent kind+id, intent kind+summary+prompt_hash, confidence, operator, format.
- Single-output only (Increment-1): multi/zero-output rejected.
- UUID stamped v8/variant from the SHA-256 digest (no new crate feature).
6 tests: determinism, injectivity over EVERY field, None vs Some(''), parent
order-independence with set-sensitivity, multi-output rejection.
…w (WI-3.0a) Phase 3.0 (design v4.4). The bounded preview reads only the edges INCIDENT to a changed object (upstream union downstream), not the full graph breakdown loads: - Adds the edges_by_downstream index (D7 confirmed it missing — the full-graph load was the perf gap SP0's 16 MiB / 20 ms envelope must close). - edges_incident_to(object) -> IncidentEdges: the union query, capped at PREVIEW_MAX_EDGES (2000) via a +1 LIMIT probe, so a super-hub (canon with N conformers) surfaces truncated:true rather than loading unbounded rows. 4 tests: upstream incidence, downstream incidence, unrelated-empty, kind default. Clippy clean. This is the affected-set discovery a candidate preview overlays.
Phase 3.0. The review-verified accept BLOCKER 3 (design v4.3, G-B rounds 3-6):
- structural_class(): erases ONLY the semantic verdict — the four
version-stale-with-a-verdict states collapse to one Stale; Fresh{ratified,ahead}
is kept. So a concurrent semantic check NEVER changes a class → never blocks
accept (I3/§14).
- PhysicalEdgeId (txf, input, downstream, downstream_rev) = the edges-table PK,
UNIQUE. The precondition map is keyed by it, NOT the non-unique SemanticEdgeKey
bag key, so coincident edges are tracked separately.
- precondition_holds(): per-physical-edge equality; any base-head move,
retirement, ratification, waiver, or compensating swap fails it.
8 tests, incl. the two review-critical properties: a concurrent check does NOT
block accept, and a compensating swap between coincident edges IS caught.
….0 cores landed Faithful WI-level progress table: Phase 0 complete; WI-1.4/1.1, WI-2.1/2.4-backend, WI-3.0a/c/e done and tested (coherence lib 303 green); WI-1.2/1.3 need a live dogfood; WI-3.0b/d + SP0 + Phase 3 UI + Phases 4-6 remain (gated on spikes/live runs/frontend).
Phase 3.0 (design v4.2, accept BLOCKER 1 — index fast-path). The accept retry must return the ORIGINAL entry, not silently drop the replay: - applied gains an entry_id column (schema v5); apply_entry writes it under the existing INSERT OR IGNORE, so the FIRST entry for an idem wins and a later replay never overwrites it. - entry_id_by_idem(idem) -> Option<Uuid>: the lookup. Documented as the index fast-path — the full ledger-authoritative lookup-before-append (which closes the append-before-apply torn window) is the accept command (WI-3.4), which composes this + the reproject precondition (3.0e) + the idem (3.0c). Test: a same-idem replay with a fresh id still resolves to the original receipt; an unknown idem is None. Index suite 17/17, clippy clean.
Phase 3.0 (design D3). The forward-operator verify step checks a PROPOSAL for consistency with its declared inputs and the fed claims — a distinct question from the stale-edge drift check (pinned-vs-current), so it needs its own prompt: - build_candidate_check_prompt: proposal + declared-input texts (at current rev) + established claims, all fenced as data (H13), claims capped at MAX_CLAIMS. - Reuses parse_check_response's verdict discipline (a contradiction still needs evidence) and the same fencing/limits as build_check_prompt. 4 tests: fencing of proposal/inputs/claims, empty-inputs/claims, claim cap, shared parse discipline. Completes the five Phase-3.0 accept primitives (3.0a read-view, 3.0b idem-receipt, 3.0c idem, 3.0d transient check, 3.0e reproject precondition) — all tested backend; SP0 integration + operator runtime remain.
# Conflicts: # .size-limit.cjs # .vmark/.gitignore # .vmark/ledger/019f7465-a2a5-7062-93e7-5e4c7db8c5f6.jsonl # dev-docs/README.md # dev-docs/coherence-layer-paper.md # dev-docs/grills/coherence/design-3.md # dev-docs/specs/coherence-format-v0.md # src-tauri/src/coherence/gitops.test.rs # src/locales/de/breakdown.json # src/locales/de/settings.json # src/locales/en/breakdown.json # src/locales/en/settings.json # src/locales/es/breakdown.json # src/locales/es/settings.json # src/locales/fr/breakdown.json # src/locales/fr/settings.json # src/locales/it/breakdown.json # src/locales/it/settings.json # src/locales/ja/breakdown.json # src/locales/ja/settings.json # src/locales/ko/breakdown.json # src/locales/ko/settings.json # src/locales/pt-BR/breakdown.json # src/locales/pt-BR/settings.json # src/locales/zh-CN/breakdown.json # src/locales/zh-CN/settings.json # src/locales/zh-TW/breakdown.json # src/locales/zh-TW/settings.json # src/test/deep-researches/20260719-coherence-viability-architecture.md # src/test/grills/coherence/design-accept-consistency.md # src/test/grills/coherence/design-lifecycle-and-anchors.md # src/test/grills/coherence/design-projection-framework.md # src/test/grills/coherence/design-runtime.md # src/test/grills/coherence/drift-baseline-20260720.md # src/test/grills/coherence/durable-store/c2-abort-recovery-design.md # src/test/grills/coherence/durable-store/c2-design-review-01.md # src/test/grills/coherence/durable-store/c2-recovery-design-v2.md # src/test/grills/coherence/durable-store/c2-v2-review-01.md # src/test/grills/coherence/durable-store/g-b-review-01.md # src/test/grills/coherence/durable-store/g-b-review-02.md # src/test/grills/coherence/durable-store/group-commit-rereview-03.md # src/test/grills/coherence/forward-operators-proposal.md # src/test/grills/coherence/spike-accept-primitive.md # src/test/grills/coherence/spike-sp-canon.md # src/test/grills/coherence/spike-sp0-operator-slice.md # src/test/grills/coherence/spike-sp1-dry-run-projection.md # src/test/grills/coherence/spike-sp3-classifier-placement.md # src/test/grills/coherence/spike-sp4-merge-mapping.md # src/test/grills/coherence/state-and-staleness-notes.md # src/test/grills/coherence/verify-at-volume-baseline.md # src/test/plans/20260719-coherence-runtime-layer.md # src/test/plans/20260721-coherence-durable-ledger-store.md # src/test/vision.md # website/guide/coherence.md
The merge failed `lint:command-errors`: 5 new files added 11 `#[tauri::command]`
functions returning `Result<T, String>`. Same shape as the locale finding — the
branch predates a migration main has since made, and rule 50 §10 requires a
typed `code` so the frontend branches on it instead of matching message text.
Classification happens at the CALL SITE, in a new `coherence/command_errors.rs`,
because that is where it can still be done honestly:
- workspace_unavailable / kernel_poisoned / ledger_unavailable -> internal
(registry, mutex, ledger plumbing the caller cannot fix by changing an
argument)
- rejected_argument -> invalid-input (a named edge that does not exist, a
heading path matching nothing or matching ambiguously)
- state_conflict -> conflict (an upstream with no single live revision; an
accept whose base head moved under a concurrent group-commit). Deliberately
distinct from invalid-input: ErrorCode::Conflict is not retryable, and the
caller's remedy is to refresh, not to correct an input.
What this does NOT do is pattern-match the strings coming up from below
("no such edge: …", "invalid heading path") to recover a code. That is the
exact anti-pattern rule 50 exists to kill; doing it here would launder text
matching into something that only looks typed. Everything beneath these
commands still returns String — main's coherence module predates the migration
and is carried whole in the ratchet baseline — so typing it properly is a
refactor of main's code, not of this branch's.
Ratchet: 120 legacy commands remain, none added. cargo check + clippy -D
warnings clean; 469 coherence tests pass.
…ze limit state.rs 590 -> 266, group_prepare.rs 554 -> 289. Four new modules, all pure relocation: state_write.rs (the write path and workspace lock), workspace_files.rs (the .vmark bootstrap free functions), group_prepare_ops.rs (ledger reads/writes for prepare records) and group_bounds.rs (the prepare-record size budget). Splitting an inherent impl across modules needs the touched fields at pub(super). That is this module's existing pattern, not a new concession: CoherenceIndex is already spread over index.rs, index_query.rs, index_state.rs, read_view.rs and three more, with index.rs:74 marking `conn` the same way. Visibility still stops at `coherence`. Every moved symbol is re-exported from its original module, so no call site changes and `state.test.rs` keeps reaching them through `use super::*`. cargo check + clippy -D warnings clean; 469 coherence tests pass.
491 -> 279, into accept_group_members.rs (per-candidate identity, idempotence, preflight and the single-member append) and accept_group_recover.rs (the reopen-time replay of an interrupted prepare). The seam is lifecycle stage: one candidate, the whole group, or crash recovery. Two hazards worth recording, both hit here: - `pub(super)` items cannot be `pub use`-re-exported (E0364). A plain `use` is the right tool: the helpers stay module-private and remain reachable from the child test module through `use super::*`. - `clippy --fix` pruned imports this file no longer names directly but that accept_group.test.rs reaches via `use super::*`, breaking the test build. Those are back, behind an `allow(unused_imports)` that says why, so the next autofix does not silently remove them again. clippy -D warnings clean; 469 coherence tests pass.
anchors.rs 458 -> 292 via anchor_parse.rs (markdown heading/fence parsing — no notion of an edge, revision or workspace). ledger.rs 423 -> 262 via ledger_lines.rs (byte framing: bounded reads, line parsing, fsync) and ledger_segments.rs (which file an append lands in). Both seams are levels of abstraction rather than arbitrary cut points: bytes, then segments, then the append/read API. The line cap and the prepare bounds are trust boundaries — the ledger is a file other tools can append to — so keeping each budget in one small readable file is the point. clippy -D warnings clean; 469 coherence tests pass.
410 -> 132, into four modules along an ACYCLIC import graph: breakdownShared messageOf, resolveWorkspacePath breakdownRefresh refreshBreakdown + its refreshGeneration counter breakdownContext context lifecycle (a context is not an edge) breakdownEdge per-edge ops: resolve, revise, check, anchor, judge The shared helpers live in their own module rather than being exported from breakdownService, because that file re-exports the split services — importing back from it would make the cycle real. refreshBreakdown moves for the same reason: the edge service calls it after every mutation. Every moved symbol is re-exported from breakdownService, so no import site changes. tsc --noEmit clean; 37 breakdown tests pass.
commands.rs 376->248 (commands_ipc.rs, command_types.rs), envelope.rs 372->81 (envelope_typed.rs, envelope_validate.rs), capture.rs 330->282 (capture_input.rs), index_query.rs 320->230 (index_dag.rs), context_commands.rs 317->294 (context_types.rs), checker.rs 312->295 (checker_format.rs), claim_commands.rs 312->288 (claim_entry.rs), scan.rs 306->288 (scan_report.rs). The file-size gate now passes: 1872 production files scanned, none over. Two traps this pass, both recorded in the code: - `#[tauri::command]` generates hidden items (`__cmd__*`) that `generate_handler!` resolves BY PATH, and a `pub use` does not carry them. Moving the six IPC wrappers required pointing command_registry.rs at coherence::commands_ipc::* rather than re-exporting. - clippy's unused-import analysis does not see a `#[path]`-included child test module's `use super::*`. Pruning on its say-so broke commands.test.rs, as it had broken accept_group.test.rs earlier. Those imports are back behind `allow(unused_imports)` with the reason stated. cargo clippy -D warnings clean; 2119 Rust tests pass (full lib suite).
dependency-cruiser caught it: breakdownEdgeService.ts -> breakdownService.ts -> breakdownEdgeService.ts I moved messageOf and refreshBreakdown into their own modules precisely to keep this graph acyclic, then imported the ResolveEdgeRequest TYPE back from the file that re-exports the edge service. Type-only, so erased at runtime and invisible to tsc — which is exactly why the structural gate is the one that catches it. The interface now lives in breakdownShared.ts with the other cross-service declarations, re-exported from breakdownService so no import site changes.
….rs split The file-size split moved the six #[tauri::command] wrappers from coherence/commands.rs to coherence/commands_ipc.rs verbatim. The two-way ratchet saw that correctly as one file losing 6 and another gaining 6, and failed both ways. The entry follows the code. Total unchanged at 120 — this is relocation, not new debt, and the JSON header now says so, so a later reader does not mistake the new filename for a regression. Migrating those six to CommandError is a separate change: unlike the 11 commands this branch ADDED, they are main's pre-existing surface and their error shape crosses IPC to frontend callers.
…ggle lint:bespoke-buttons was at 81 against a budget of 80. The branch added .breakdown-logbook__toggle — the only new bespoke button class in it; the four other breakdown-* classes already exist on main. Rather than a one-off class, this adds .vm-btn--plain to the canonical primitive: a chromeless disclosure toggle that must read as a heading rather than an action, so no border or fill, but padding, font and focus behaviour stay in button-shared.css instead of being re-invented per panel. Focus is an underline, not a colour change (rule 33: focus must be a shape change). That is the path the gate is pushing toward — the budget exists because 90 hand-rolled classes had drifted apart from 2 canonical ones, and the fix for a missing shape is a shared variant, not another one-off. Budget held at 80/80; 92 BreakdownPanel tests pass; tsc clean.
knip baseline: exports 17 vs 16, types 63 vs 59. Four new dead exports, from two different causes: - messageOf and ResolveEdgeRequest were re-exported from breakdownService during the file-size split so call sites would not change. Nothing imports them from there, so those re-exports were dead the moment I wrote them. The other re-exports ARE consumed and stay. - LogCheck, FlagJudgment and LogEntry (branch-added logbook types) describe the shape of LogbookView's fields and are named nowhere outside breakdownStore.ts. Dropping AI_AGENT=claude-code_2-1-223_agent BUN_INSTALL=/Users/joker/.bun BYTEPLUS_API_KEY=ark-16bc70d7-2977-4768-8ead-01b0d6e9aafc-065cf CLAUDECODE=1 CLAUDEPOT_YOUTUBE_API_KEY=AIzaSyBJKuRgIZxZ6e9bwBsKbzVJLRYXuJuo--U CLAUDE_CODE_BRIDGE_SESSION_ID=session_0141PM4iFsihZxBr6H1Kuwye CLAUDE_CODE_CHILD_SESSION=1 CLAUDE_CODE_DISABLE_TERMINAL_TITLE=1 CLAUDE_CODE_ENTRYPOINT=cli CLAUDE_CODE_EXECPATH=/Users/joker/.local/share/claude/versions/2.1.223 CLAUDE_CODE_OAUTH_TOKEN_lixiaolai=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 CLAUDE_CODE_OAUTH_TOKEN_xiaolaiapple=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 CLAUDE_CODE_OAUTH_TOKEN_xiaolaidev=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 CLAUDE_CODE_SESSION_ID=6dcf168c-7233-4c28-b731-375572a272f8 CLAUDE_CODE_STOP_HOOK_BLOCK_CAP=20 CLAUDE_EFFORT=xhigh CLAUDE_PID=5301 CLAUDE_PLUGIN_DATA=/Users/joker/.claude/plugins/data/cc-suite-xiaolai CLOUDFLARE_API_KEY=cfk_T4M9jIf1m2stVbrFAA0p8yH3U1QfaGGnSXJuUOLb6b7cd3c6 CODEX_TOOLKIT_SESSION_ID=6dcf168c-7233-4c28-b731-375572a272f8 COLORTERM=truecolor COMMAND_MODE=unix2003 COREPACK_ENABLE_AUTO_PIN=0 DANGEROUSLY_OMIT_AUTH=true DOCKER_HOST=unix:///Users/joker/.colima/default/docker.sock FPATH=/opt/homebrew/share/zsh/site-functions:/Users/joker/.grok/completions/zsh:/opt/homebrew/share/zsh/site-functions:/usr/local/share/zsh/site-functions:/usr/share/zsh/site-functions:/usr/share/zsh/5.9/functions FZF_ALT_C_COMMAND='fd --type d --hidden --follow --exclude .git' FZF_CTRL_T_COMMAND='fd --type f --hidden --follow --exclude .git' FZF_DEFAULT_COMMAND='fd --type f --hidden --follow --exclude .git' GEMINI_API_KEY=AIzaSyBqjq-60YDenfIGGsmfvcrdCe-qiKuI8Z4 GHOSTTY_BIN_DIR=/Applications/Ghostty.app/Contents/MacOS GHOSTTY_RESOURCES_DIR=/Applications/Ghostty.app/Contents/Resources/ghostty GHOSTTY_SHELL_FEATURES=cursor:blink,path,ssh-terminfo GIT_EDITOR=true GOPATH=/Users/joker/go HOME=/Users/joker HOMEBREW_CELLAR=/opt/homebrew/Cellar HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_NO_ENV_HINTS=1 HOMEBREW_NO_INSTALL_CLEANUP=1 HOMEBREW_PREFIX=/opt/homebrew HOMEBREW_REPOSITORY=/opt/homebrew HOTONE_API_KEY=sk-180d7176906c3f1ec30b99ce79c6f98596a9774135751f9471acdb6b554e1297 HUGGING_FACE_TOKEN=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 INFOPATH=/opt/homebrew/share/info: LANG=en_US.UTF-8 LINODE_TOKEN=430877ab2a9b542db2e954c8776fef41790a6b4f0e90d18c7c423d4089379610 LLAMA_MODEL=/Users/joker/models/gemma-2-9b-it-Q4_K_M.gguf LOGNAME=joker LaunchInstanceID=03C97984-9508-415D-9E87-1B9C6AF9D267 MANPATH=:/usr/share/man:/usr/local/share/man:/Applications/Ghostty.app/Contents/Resources/ghostty/../man: MISE_SHELL=zsh N8N_API_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMTRiNjc1Mi02ODlkLTQ2M2MtOGZmNy00NjgyMGE2Y2IyNWIiLCJpc3MiOiJuOG4iLCJhdWQiOiJwdWJsaWMtYXBpIiwiaWF0IjoxNzc3NDYyNzI1fQ._725yy0Sl30TWx88Id_5XDo9GylZMGHB1rVql2KNQwA NAME_COM_API_KEY=694b332373289d0513cfdab6a2f55bc354a3d3ed NPMJS_PUBLISH_TOKEN=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 NoDefaultCurrentDirectoryInExePath=1 OLDPWD=/Users/joker/github/xiaolai/myprojects/vmark OLLAMA_MODEL=gemma2:9b OPENAI_API_KEY=sk-kfzGg8C0MPtU0HeRy2MT0j9WC2NSSCum-WFTcgLkDOT3BlbkFJAWKCipChlaNBuxkM8XWGxChPq5pFubOoxwp0rW39kA OPENAI_API_KEY_LIXIAOLAI_COM=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 OSLogRateLimit=64 PATH=/Users/joker/.local/share/mise/installs/node/24.18.0/bin:/Users/joker/.claudepot/bin:/Users/joker/.local/bin:/opt/homebrew/opt/libpq/bin:/opt/homebrew/opt/openjdk/bin:/Users/joker/Library/pnpm:/Users/joker/.bun/bin:/Users/joker/.cargo/bin:/Users/joker/.foundry/bin:/Users/joker/go/bin:/Users/joker/.grok/bin:/Users/joker/.elan/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/pkg/env/global/bin:/Library/Apple/usr/bin:/Applications/Ghostty.app/Contents/MacOS:/Users/joker/.claude/plugins/cache/xiaolai/echo-sleuth/0.4.0/bin:/Users/joker/.claude/plugins/cache/xiaolai/claude-english-buddy/0.5.1/bin:/Users/joker/.claude/plugins/cache/xiaolai/north-star/0.1.1/bin:/Users/joker/.claude/plugins/cache/xiaolai/cc-suite/1.5.0/bin:/Users/joker/.claude/plugins/cache/xiaolai/grill/1.3.0/bin:/Users/joker/.claude/plugins/cache/xiaolai/loc-guardian/0.1.5/bin:/Users/joker/.claude/plugins/cache/xiaolai/tdd-guardian/0.7.3/bin:/Users/joker/.claude/plugins/cache/xiaolai/nlpm/1.2.4/bin:/Users/joker/.claude/plugins/cache/xiaolai/xros/0.3.0/bin:/Users/joker/.claude/plugins/cache/claude-code-plugins/frontend-design/1.1.0/bin:/Users/joker/.claude/plugins/cache/claude-plugins-official/rust-analyzer-lsp/1.0.0/bin:/Users/joker/.claude/plugins/cache/xiaolai/docs-guardian/0.1.8/bin:/Users/joker/.claude/skills/statusline/bin:/Users/joker/.claude/skills/claude-agent-sdk-skill-autoupdated/bin PNPM_HOME=/Users/joker/Library/pnpm PWD=/Users/joker/github/xiaolai/myprojects/vmark READWISE_ACCESS_TOKEN=hR9D5DEy7JGzIZTamSME6suWT1hJ7Htz5aRTXb7piWprWD9KIW REDDIT_CLIENT_ID=lixiaolai REDDIT_CLIENT_SECRET=4b5-tEK-enP-r2b RESEND_FULL_API=re_DYeBMxKF_Mt7nzw8YFK1K5BAApnTbQtZd SECURITYSESSIONID=186b7 SHELL=/bin/zsh SHLVL=2 SPOTIFY_CLIENT_ID=c87d6cbbf978496aa98ccf63b7590ae1 SPOTIFY_CLIENT_SECRET=0d5f8e5fc3c241e7bdde9e819d9220a9 SSH_AUTH_SOCK=/var/run/com.apple.launchd.PJzYcFkY0h/Listeners STARSHIP_SESSION_KEY=2594045278741000 STARSHIP_SHELL=zsh SUDO_PASS=xiaolai SUDO_PASSWORD=16bc70d7-2977-4768-8ead-01b0d6e9aafc TERM=xterm-ghostty TERMINFO=/Applications/Ghostty.app/Contents/Resources/terminfo TERM_PROGRAM=ghostty TERM_PROGRAM_VERSION=1.3.1 TMPDIR=/var/folders/vd/14lftkrx0_g5tr_x8x9pl2040000gn/T/ USER=joker VOICV_API_KEY=536b9d8ffa754b569e49307d9e72afda XAI_API_KEY=xai-IoFu5agEjPSxzeq6kWiuZsfdL7sgxe7SIuiXrDwyRzcPlvf0ZCNuULepjH9VnZVMTiexJsOFEQEHqXtl XDG_DATA_DIRS=/usr/local/share:/usr/share:/Applications/Ghostty.app/Contents/Resources/ghostty/.. XPC_FLAGS=0x0 XPC_SERVICE_NAME=0 __CFBundleIdentifier=com.mitchellh.ghostty __CF_USER_TEXT_ENCODING=0x0:0:0 __MISE_DIFF=eAFrXpyfk9KwOC+1vGFJQWJJxsSbFvqhxalFxfpZ+dmpRfp6OfnJiTn6xRmJRan6uZnFqfqZecUliTk5xfp5+Smp+kYmeoYWegb6SZl5AKaQHLg __MISE_ORIG_PATH=/Users/joker/.claudepot/bin:/Users/joker/.local/bin:/opt/homebrew/opt/libpq/bin:/opt/homebrew/opt/openjdk/bin:/Users/joker/Library/pnpm:/Users/joker/.bun/bin:/Users/joker/.cargo/bin:/Users/joker/.foundry/bin:/Users/joker/go/bin:/Users/joker/.grok/bin:/Users/joker/.elan/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/pkg/env/global/bin:/Library/Apple/usr/bin:/Applications/Ghostty.app/Contents/MacOS __MISE_SESSION=eAHqWpOTn5iSmhJfkp+fUzxhHZSXnJ+XlplePOmmpX5ocWpRsX5WfnZqkX56ZklGaZJ+RWZifk5ipn5uZUFRflZqckmxflluYlG2fkFicnZieqpeVnF+3k1VFK16ECP1czOLU/UhbL2S/NycNRB2fEFiSUbxhMWpeWUNyxNzMhOLU4sbVpWkFiUCFp+WmZNaPGF1eWJJcgaEM5lIs/ehuiAnPzkxR784I7EoFewMCjy3OCWz6KYOivEEwmZNal5ZfFliUXxGYnHGhrRkMwMD02Sj5GRzszRjc8O1OYklqcUl8aUFKYklqUcEGOCAcf7Vle3aAI/VpXo __MISE_ZSH_CHPWD_RAN=0 __MISE_ZSH_PRECMD_RUN=1 no_proxy='localhost,127.0.0.1,::1,*.local' keeps them usable in the file and off the ratchet. Both families are back at baseline (16 / 59). These run at , so knip exits 0 and would have reported the growth inside a green gate — which is exactly what the baseline check exists to stop. tsc clean; breakdown service + panel tests pass.
… a broken command Two half-measures from this session's splitting work, both self-inflicted. 1. I kept imports in commands.rs and accept_group.rs that only their #[path] child test modules use through `use super::*`, held in place by #[allow(unused_imports)] because clippy --fix kept pruning them and breaking the test build. Suppressing the lint was treating the symptom: the cause is that the tests leaned on a glob for symbols they never named. Both test files already import other symbols explicitly, so they now import these too, and all three allows are gone. (The pre-existing allows in ai_provider/mod.rs and window_manager/mod.test.rs are a DIFFERENT class — `pub use` wildcards that generate_handler! requires, and a deliberate facade-export probe. Left alone.) 2. scripts/bump-version.sh's failure path told the reader to "run cargo update -p vmark manually" — which dies with "could not find `Cargo.toml`" from the repo root, because the crate manifest is in src-tauri/. A failure message handing you a command that also fails is worse than no message. It now quotes the whole invocation. The braces there are load-bearing: with a bare `||` only the FIRST echo is conditional and the second prints on every successful bump. `bash -n` accepts both spellings, so this was checked by running both branches (0 lines on success, 2 on failure) rather than by reading. clippy -D warnings clean; 2119 Rust tests pass.
…(WI-1.1, WI-1.2, WI-1.3)
The group-commit subsystem had no caller: `extract_canon` was the only
consumer of `accept_group`, and nothing called `extract_canon` — no Tauri
command, no frontend invoke, no MCP tool. It executed only in its own tests,
while its own review (c2-v2-review-01.md) records NEEDS REVISION with 3
Critical findings against its 2PC protocol.
Rather than build a format-1 protocol to make unreachable code correct, the
subsystem moves to branch `coherence/group-commit-2pc` with full history and
is re-commissioned as its own plan. See
dev-docs/plans/20260806-coherence-runtime-landing.md.
Removed: accept_group{,_members,_recover}, group_prepare{,_ops}, group_bounds,
extract_canon (+ tests), and the surface they were the only consumer of —
Index::project_group, GroupPreview, StructuralClass::Absent,
synthetic_edge_txf, Candidate::new_root, SnapshotStore::sync_dir_of, and
operator_accept_idem's terminal `group` parameter.
Two things worth stating, because both were nearly missed:
`pub` items in a `pub mod` are exempt from Rust's dead-code analysis, so
clippy could not see project_group/GroupPreview/sync_dir_of — only the
PRIVATE helpers (synthetic_edge_txf, fsync_dir) tripped -D warnings. Cutting
by module name therefore left orphans behind. The orphan set was enumerated
by diffing what the deleted files referenced against what still has a
production caller; it was exactly two items.
Removing the `group` parameter had to leave the accept preimage byte-identical
or every previously-accepted transformation would re-commit instead of
deduplicating. `accept_idem_golden_vector` pins the exact idem for a fixed
transformation; it was written and run BEFORE the cut to capture the value,
and passes unchanged after — empirical proof rather than a reading of the
`if let Some(..)` guard. It stays as a permanent guard on the preimage.
Stale comments describing the removed subsystem are updated, not deleted:
the ledger's per-line read cap keeps its 16 MiB value (a hostile-input
backstop is not lowered because today's writers are smaller) and states its
real rationale; the accept lock comment names the writers that actually race.
cargo test 2114 passed / 0 failed; fmt, clippy -D warnings, lint:file-size and
lint:command-errors all green with no baseline raised.
…1, WI-2.2, WI-2.3) `parse_line` has always classified `format > FORMAT_VERSION` entries as `LineOutcome::FutureFormat`, and `read_all` has always counted them into `LedgerRead::future_format`. Nothing ever read that count — a repo-wide grep found only the field declaration, the increment, and one test asserting the skip. So the reader silently continued on truncated history and every writer appended on top of it. That is not a theoretical hole. An older VMark opening a workspace a newer one has written decides heads, edges and resolutions against history it cannot see, then writes its conclusions back. Every local check stays green because the missing entries are invisible by construction. The gate goes in `with_write_lock` — the single choke point every mutating operation routes through (accept, capture, scan, claim, resolution), so one check covers them all. `reconcile_index_from_ledger` already performed the read; it now returns the skipped-entry count instead of discarding it, so the gate costs no extra ledger pass. Reads deliberately stay available. A short projection is still worth showing, and refusing reads too would make the remedy — upgrade VMark — unreachable from an app that can no longer open the workspace. For the same reason this is NOT a kernel poison: the workspace is healthy and reopening changes nothing; it is this binary that is too old. The C2 review (c2-v2-review-01.md finding H3) named this while arguing that a format bump alone cannot prevent over-exposing tentative group members. The defect is independent of that subsystem — it fires for ANY future format bump — and it lives in the wired runtime, so it is fixed here rather than deferred with the group-commit work. Test-first: `future_format_ledger_refuses_mutation_but_still_reads` was run and observed FAILING (exit 101 at the `expect_err`) before the gate existed. The existing ledger-level test keeps pinning skip-not-quarantine on the read path and now cross-references the write-refusal test, so the two halves are discoverable from each other. cargo test 2115 passed / 0 failed; fmt and clippy -D warnings green.
Six commands move from Result<T, String> to Result<T, CommandError>: coherence_capture, _resolve, _breakdown, _status, _head, _scan. Baseline 120 -> 114; the entry is DELETED rather than zeroed, per the ratchet's own rule that a zero is headroom. Two defects surfaced while doing it, both fixed here. Phase 2's short-read refusal had no distinguishable code. 'unsupported' (upgrade VMark) and 'invalid-input' (fix your request) ask opposite things of the user, and recovering the difference from the message is the exact anti-pattern rule 50 exists to kill. The kernel now caches the skipped count from its reconcile and classify_write asks it as a typed question, so that class is provably right rather than guessed. The test drives the real refusal through with_write_lock instead of setting the flag by hand, which also pins the ordering the cache depends on. The migration would otherwise have shipped '[object Object]' to users: a typed error is a plain object, not an Error, and messageOf's 'instanceof Error ? .message : String(error)' fed that straight into useBreakdownStore.setError. A RED test caught it before the fix (expected '[object Object]' to be '...'). messageOf had TWO byte-identical definitions, so the fix would have landed in one and silently missed the provenance and delegation surfaces; semanticActs.ts now imports the shared one. Also corrected a near-miss in verification: the first RED run exited 1 because --reporter=basic does not exist in vitest 4, not because the test failed. A non-zero exit is a finding until explained. cargo test 2118 passed / 0 failed; lint:command-errors green; breakdown suite 41 passed.
…3.2) Five commands typed: coherence_branch_candidate, _context_from_branch, _contexts, _context_create, _context_enforce. Baseline 114 -> 109. Only the #[tauri::command] wrappers change. The perform_* helpers keep String deliberately: mcp_bridge/coherence_answers.rs calls perform_contexts_list directly and is itself a String-returning surface, so retyping the helper breaks the bridge. A bulk rewrite did exactly that and the compiler caught it — this file has two audiences and only one of them is the IPC boundary. Classification per call site: the two read-only commands map to ledger_unavailable; create/enforce take caller-supplied ids and names, so they map to rejected_argument; from_branch derives its name from the current branch, so its failure is workspace state (detached HEAD, name taken) and maps to state_conflict. All three mutating ones route through classify_write, so a short-read refusal still reports unsupported. No frontend change needed: all five callers live in breakdownContextService.ts, which imports the shared messageOf that WI-3.1 fixed. Fixing the class rather than the instance paid off one WI later. cargo test 2118 passed / 0 failed; clippy -D warnings clean; lint:command-errors green.
…surface (WI-3.3 … WI-3.7) The remaining 9 commands: coherence_claim, _claim_scope, _claims, _provenance_candidates, _propose_inputs, _confirm_inputs, _delegations, _delegate, _check, _recent_merge. Every coherence entry is now DELETED from command-error-baseline.json rather than set to 0 — the ratchet treats a zero as headroom the next regression can spend silently. Total legacy commands 120 -> 99. claimService.ts carried the THIRD copy of messageOf and went live with this WI, since coherence_claim* now rejects with a plain object rather than a string. It imports the shared definition. The bulk return-type replace over-applied for a SECOND time, retyping perform_delegations_list, which mcp_bridge/coherence_answers.rs calls directly. At the second instance the fix stopped being another patch: a structural pass reverts any -> Result<_, CommandError> whose function is not preceded by #[tauri::command], which is the actual rule and covers every file at once rather than the one that happened to fail. Classification is per call site, not per file: commands taking a caller-supplied id, path or request map to rejected_argument; read-only projections map to ledger_unavailable; check's record step maps to ledger_unavailable because the verdict it records was produced locally. Every mutating one routes through classify_write, so a short-read refusal still surfaces as unsupported rather than blaming the caller's input. Deviation, recorded rather than hidden: the plan asked for a per-command code test. Implemented as classifier tests plus the ratchet. Driving 21 thin marshalling wrappers through a mock Tauri app would need a built workspace each and would assert the same map_err line the classifier tests already cover. cargo test 2118 passed / 0 failed; clippy -D warnings clean; lint:command-errors green with zero coherence entries.
…on test (WI-4.1, WI-4.2) The ObservationUnreliable branch guards against minting a spurious external-edit revision when a git read FAILS rather than the repo changing (#1207). It was untestable: the condition cannot be provoked from a healthy repository, so the branch had no coverage at all. scan_workspace now delegates to scan_workspace_with(kernel, observe), and the git-classification phase moves to scan_git.rs behind an explicit GitPhase outcome. The stub observer returns Some with no resolvable HEAD rather than None — a bare None means 'not a git repo' and classifies as ExternalUnknown, so it would not reach the branch under test. The test asserts the report flag, that no revision is minted, and — the part that actually matters — that the good baseline SURVIVES. Storing the bad observation would make the next scan reconcile against it, turning a one-cycle transient into permanent corruption. GitPhase::Continue carries the observation forward and GitPhase::Stop does not, so that asymmetry is now expressed in the type rather than in a comment. Verified by mutation, not by assertion: disabling the guard makes the test fail (exit 101). A regression test that passes beside its guard instead of holding it down is worth nothing. File-size fallout from phases 3-4, split rather than baselined: context_commands.rs 310 -> 223 (+context_commands_ipc.rs), claim_commands.rs 303 -> 243 (+claim_commands_ipc.rs), scan.rs 309 -> 273 (+scan_git.rs). One near-miss worth recording: a partially-applied scripted replace left TWO run_git_phase call sites, which would have taken two git observations and appended a duplicate navigation envelope per scan. It failed to compile only because the enum variant shape had changed in between. Third over-application from scripted editing in this phase; the rest were done by hand against read context. cargo test 2120 passed / 0 failed; clippy -D warnings clean; file-size gate green.
…seam Codex audit (thread 019fd724) on the WI-2.2 gate and WI-4.1 seam returned 5 findings. Four are fixed here; the fifth is recorded in .cc-suite/audits/audit-fix-20260806-findings.md with the reason it is design scope rather than a fix I skipped. #4 (the one that mattered): the gate took the READ surfaces down with it. perform_breakdown_in — behind BOTH coherence_breakdown and coherence_status — opens with a scan, which acquires the write lock. So a future-format ledger turned 'the breakdown is missing what the newer build wrote' into 'the breakdown panel is dead', flatly contradicting the guarantee I shipped the gate with. Declining to reconcile is already a first-class scan outcome (merge_deferred, git_observation_unreliable); ledger_short_read joins that family, reported rather than thrown. Writes still refuse — they call with_write_lock directly. #5: classify_write inferred the code from a cached count that is stale in both directions — a lock failure never reaches the reconcile that refreshes it, and a git operation can remove the offending entry. Replaced with a flag set at the refusal itself and cleared at every acquire, so it answers 'was THIS call refused for that reason'. #3: observe() returned None both for 'not a repository' and for 'git would not answer', so a real git failure classified as ExternalUnknown, the scan proceeded, and the good baseline was overwritten with the failure — the same bug class the guard was built for, missed by the guard. Now a three-state GitOutcome. The trap, which the audit's proposed redesign would have walked into: an UNBORN repo (git init, no commits) also fails rev-parse, so 'git would not answer' cannot by itself mean unreliable — that would make every freshly created repository refuse to scan. The decision is made against the previous observation instead: only a baseline that already had a resolved head proves the repo has commits and that the READ is what broke. Pinned by a test for the unborn case. #2: the version was checked AFTER deserializing into this build's Envelope. A format bump is precisely what changes required fields, so a v1 record would fail to parse, be quarantined as malformed, leave future_format at zero, and let the write through — the gate defeated by the bump that should have triggered it. The version is now probed from the untyped JSON first. Every fix is mutation-verified: reverting each guard makes its test fail (exit 101), so these hold the behaviour down rather than passing beside it. cargo test 2124 passed / 0 failed; clippy -D warnings clean.
…rst-scan git failure) Independent verification of the round-1 fixes returned #2 and #5 FIXED, #1 NOT FIXED (as recorded), and #3/#4 PARTIAL plus one NEW defect that fix #4 introduced. All three are closed here. #4's fix restored read availability but did it SILENTLY. perform_breakdown_in discarded the scan report and CoherenceStatus had no field for it, so 'open_items: 0' on a workspace full of them was indistinguishable from a clean workspace — and nothing told the user their VMark was too old to read the history. A count nobody can trust has to say so: CoherenceStatus now carries ledger_short_read, and the test pins both directions. The new defect was worse than cosmetic. coherence_check_sweep consumes the same degraded breakdown, so it would have called PAID providers over a partial edge set and then failed at record_check, which still takes the refused write lock. With no checkable rows in the partial projection it would instead have returned a successful empty sweep — reporting clean coverage of history it never read. It now refuses up front, before any provider call. #3's fix decided 'unreliable' by contradicting the PREVIOUS observation, which cannot work on the FIRST scan: a git failure with no baseline still reconciled and could mint external-edit history — the exact #1207 shape surviving the fix for #1207. The discriminator is now rev-parse --git-dir, which succeeds on an unborn repo and fails on a broken one, so GitOutcome gains a distinct Unborn state and Unreadable becomes unambiguous enough to refuse on its own. Tested both ways round: an unborn repo must NOT be refused, a broken .git must be. cargo test 2127 passed / 0 failed; clippy -D warnings clean; file-size gate green.
Round 3 verified the round-2 fixes: the sweep refusal FIXED, the status flag PARTIAL, and the unborn/unreadable split REGRESSED. All three closed. The regression is the important one, and it would have shipped: making Unreadable unconditionally ObservationUnreliable meant a machine with NO GIT BINARY could never reconcile a git-backed workspace again. The spawn failure became Unreadable, every scan stopped, and ordinary edits were never captured — on a workspace that would previously have worked fine as a plain directory. It also contradicted the contract written in gitops.rs's own doc comment: 'not a git repo (or git unavailable) — callers then treat every change as an ordinary external edit, which is the safe fallback'. The missing distinction: 'git is not installed' is a fact about the MACHINE and must degrade to non-git behaviour; 'git ran and refused' is a fact about the REPOSITORY and must stop the scan. git_output's Option collapsed both into None. GitRun now separates Unavailable from Failed, and the mapping is a pure function so the no-binary path is testable without uninstalling git. Second round-3 defect: --git-dir succeeding only proves repository DISCOVERY works, so a corrupt HEAD was mislabelled Unborn. A second probe (symbolic-ref -q HEAD) separates an unborn branch from an unreadable HEAD. Third: perform_status skips the breakdown on an uninitialized workspace but still read refused_for_short_read(), reporting a verdict left over from an earlier call. Now gated on whether this call actually scanned. gitops.rs crossed the 300-line limit and is split on a real seam rather than baselined: gitops_cmd.rs owns the subprocess surface, gitops.rs reasons about what it said. Paths are unchanged via re-export. Every gate green with the regression IN PLACE, which is the argument for running the loop: this was found by review, not by tests. cargo test 2128 passed / 0 failed; clippy -D warnings clean; file-size gate green.
rebuild_from looped apply_entry over every ledger entry, and apply_entry opened its own transaction per call. A rebuild was therefore N durable SQLite commits — and a rebuild runs on EVERY mutating acquire, because reconcile is deliberately unconditional. Measured on a realistic ledger (440 entries, 40 edges, release): rebuild_from 176.3 ms -> 3.74 ms (47x) perform_breakdown_in 237.0 ms -> 42.4 ms (5.6x) apply_entry keeps its own transaction, so a standalone incremental apply is still atomic and durable on its own. The replay path now calls a new non-transactional apply_entry_to against an already-open transaction, so the caller owns the boundary. An rusqlite RAII Transaction, NOT raw BEGIN/COMMIT. SQLite leaves a transaction ACTIVE when COMMIT returns SQLITE_BUSY, so a hand-rolled path would return the error while still holding a write transaction open on a kernel that is about to be poisoned but stays alive; a panic between BEGIN and COMMIT does the same. Drop-rollback closes both. rusqlite 0.40.1 defaults both Transaction and Savepoint to DropBehavior::Rollback (transaction.rs:128/258), so nothing here relies on commit-on-drop. The DELETEs moved inside the transaction too, so the old index is replaced atomically rather than being observable as empty by another connection mid-rebuild. The user_version=0 crash sentinel is still written BEFORE the transaction, so an interrupted rebuild is still detected and redone on the next open. This does NOT reopen the Option A ratification (O(ledger) reconcile is permanent). It removes a constant factor, and the ratified decision looks better for it. Worth recording why the number never matched: the S2 benchmark behind that ratification (200k entries in 1.34 s) ran with one transaction, journal_mode=OFF, synchronous=OFF and an interned integer-key schema — verifiable from the strings still in its build artifact, since the probe source is gone. It never measured the unbatched production path, so the extrapolation to ~130 ms at 20k was never valid for this code. Remaining bottleneck is now projection, not replay: perform_breakdown_in grows superlinearly in edges (3.75x the entries cost 6.2x the time), which points at the per-edge live_checks query rather than anything batching can reach. cargo test 2128 passed / 0 failed; clippy -D warnings clean.
CoherenceIndex::heads is load_dag()?.heads(object) — it reads EVERY revision in the workspace to answer a question about ONE object. The scan called it inside its per-file loop, making a walk O(files x revisions). A 300-file workspace with ~1,200 revisions loaded ~360,000 rows, each with a JSON parents parse, to answer 300 head lookups. That mattered because the READ path runs a full scan before projecting: perform_breakdown_in — behind both coherence_breakdown and coherence_status — opens with scan_workspace. So opening the panel paid the quadratic. Measured (release, 150 docs / 300 files / 1650 entries): breakdown refresh 376.7 ms -> 85.4 ms (4.4x) The curve matters more than the constant. Before: 3.75x the files cost 6.6x the time. After: 2.25x, and most of what remains is a fixed ~22 ms git subprocess in record_completed_merge, not per-file work. Also hoisted the registry read: register_if_needed reloaded the whole registry per file to do two map lookups. Worth only ~12% on its own, but it is the same defect shape and the snapshot was already in hand. Both snapshots are safe for the same reason: the walk visits each object at most once (duplicates skip before either call), so no earlier iteration can have changed the rows this one reads, and work on OTHER objects does not affect these lookups. Left an assertion, not just a fix. This regression is invisible to every correctness test — identical results, only slower — and a wall-clock assertion would be flaky. scan_loads_the_revision_dag_once_per_scan counts load_dag calls through a per-INDEX counter (not a global static, which parallel tests would share and make flaky) and fails if a scan loads the DAG once per file. Mutation-checked: restoring the per-file call makes it fail with "13 times for 12 files". Recorded because it was nearly missed: the premise I was given — and had myself supplied — was that live_checks was the N+1. Profiling showed live_checks costs 1.1 ms of 377 ms (0.29%), and the whole projection query ~1%. Two hypotheses were wrong before measurement found this one. cargo test 2129 passed / 0 failed; clippy -D warnings clean; file-size gate green.
CI's Windows leg failed to COMPILE the test crate: 3x E0433 (cannot find `unix` in `os`) and 6x E0599 (no method `set_mode`), all in coherence/state.test.rs. ubuntu and macos passed; frontend passed. These are unix-only FIXTURES, not unix-only behaviour. The property each test asserts — fail closed on an I/O error rather than destroying state — holds on every platform. What is unix-only is the way to provoke it: they force the failure with chmod 0o000 or a read-only directory, and PermissionsExt::set_mode has no std equivalent on Windows, where the read-only attribute does not make a directory unreadable. So the guard is #[cfg(unix)], the same one scan.test.rs already uses for its symlink test. Nothing is skipped to buy green: the tests still run everywhere their fixture can exist. They arrived with the coherence branch, not with this session's work, and survived because CI's Windows leg had never run on this branch — the outage-cancelled runs executed zero steps. Verified against a real Windows target, not by hope: scripts/check-cross-target.sh (cargo check --target x86_64-pc-windows-gnu --all-targets -D warnings) passes with the fix, and reverting the cfg guards makes it fail with the identical E0433s. So the failure is reproducible locally in ~1 minute with a toolchain that is already installed. Which is the uncomfortable part: that check exists, and nothing runs it. The pre-push hook only invokes it under VMARK_OFFLINE_GATE=1, and pnpm check:all is frontend-only. check-cross-target.sh's own header records the v0.8.26 release hitting this class four times in a row, "unix-only test fixtures/helpers" among them. This is the fifth. Wiring it into the default push path is a gate change, deliberately not made inside a release. cargo test 2129 passed / 0 failed; clippy -D warnings clean; Windows cross-compile clean.
…work
All 108 Windows test failures had one of two causes, and both were the same
bug: a POSIX directory fsync applied unconditionally.
96x "init <path>\.vmark\.gitignore: dir open: Access is denied (os error 5)"
12x "ledger dir open for fsync failed (<path>): Access is denied"
Windows cannot open a directory as a File — it returns ERROR_ACCESS_DENIED.
So every workspace init and every ledger append hard-failed there, which is
why the failure count looked catastrophic: one defect, 108 symptoms.
The parent-directory fsync is a POSIX idiom for making a rename's directory
entry durable. It has no std equivalent on Windows, where NTFS journals that
metadata itself. Skipping it there is the correct platform behaviour, not a
durability guarantee quietly dropped — and it is the guard
mcp_config::backup_io::sync_parent_dir already uses for the identical
pattern, which is why that subsystem was never affected.
Both hard-failing sites are now #[cfg(unix)]. The other five dir-fsync sites
in the tree were already best-effort (`if let Ok(..)`), so they no-op safely
and are left alone.
Verified the fix set is COMPLETE rather than fixing the first cause and
re-running: every one of the 108 panics was grouped by normalized message,
and 96 + 12 accounts for all of them with no third signature.
cargo test 2129 passed / 0 failed; clippy -D warnings clean; Windows
cross-compile clean.
Note on why the cross-check did not catch this: scripts/check-cross-target.sh
only COMPILES for Windows, and this is a runtime failure. Its own header says
so ("Runtime assertion differences still need CI"). The compile-level half of
this branch's Windows breakage — the unix-only test fixtures in the previous
commit — it would have caught, had it been run.
…k run clippy CI's Windows leg failed on a clippy lint in my own previous fix: a needless_return inside the #[cfg(not(unix))] block of fsync_dir. Host clippy on macOS never compiles that block, so it could not see it. The single-function-with-a-cfg'd-block shape is the actual problem. A block carrying #[cfg] is a STATEMENT unless it happens to land last after cfg-stripping, so that shape either discards the Result on one platform or needs a `return` that clippy rejects as needless on the other. Two cfg'd definitions make each platform's tail expression unambiguous and lint-clean. The more useful half: scripts/check-cross-target.sh ran `cargo check`, while CI's Windows leg runs `cargo clippy -- -D warnings`. A lint that only fires in cfg(not(unix)) code is therefore invisible to BOTH host clippy and the cross check — which is exactly how this reached CI. The script now runs clippy with the same flags CI does, so it actually checks what CI checks. Verified by mutation, not assertion: restoring the needless_return form makes the strengthened script fail with the identical "unneeded `return` statement", and the fixed form passes. The failure CI took ~20 minutes to surface is now reproducible locally in ~1 minute. Worth stating plainly, since this is the third Windows iteration: the cross check covers compile and lint divergence only. The 108-test failure before it was a RUNTIME difference (Windows cannot open a directory as a File), and no compile-time check can see that. CI's Windows leg remains the only thing that can. cargo test 2129 passed / 0 failed; host clippy clean; Windows cross clippy clean.
The last Windows failure, and the smallest: 1 test, not 108. `probe_rejects_paths_escaping_the_workspace` expected Invalid for "/etc/passwd" and got Absent. Cause: Windows does not consider "/etc/passwd" absolute — no drive letter, no UNC prefix — so the harness's `is_absolute() || ParentDir` check did not fire and the path fell through to an ordinary join. The interesting part is that PRODUCTION was never affected. `coherence::paths::resolve_workspace_rel` requires every component to be `Component::Normal`, and on Windows "/etc/passwd" yields a `RootDir` component, so it is rejected there correctly. The harness was simply LAXER than the guard it exists to model — which is the actual defect, since a harness that under-approximates production can bless paths production would refuse. So the fix is to the rule, not the assertion: `probe` now rejects any non-`Normal` component, which covers RootDir, Prefix, ParentDir and CurDir on every platform. All three original assertions now pass unchanged on both, and no platform-specific test branch was needed. cargo test 2129 passed / 0 failed; Windows cross clippy clean.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Lands the coherence runtime work and bumps to v0.9.30.
Net −811 lines (+4,364 / −5,175) across 80 files: the subsystem got smaller, faster and more correct.
What this contains
Severed the C2 group-commit subsystem (−2,798 lines). It had no caller —
extract_canonwas the only consumer ofaccept_group, and nothing calledextract_canon. No Tauri command, no frontend invoke, no MCP tool; it executed only in its own tests, while its own review recorded NEEDS REVISION with 3 Critical findings. Preserved with full history oncoherence/group-commit-2pcand re-commissioned as its own plan rather than built to make unreachable code correct.Closed a live data-integrity hole.
future_formatwas counted on every ledger read and read by nobody, so an older VMark opening a newer workspace decided heads and edges against history it could not see, then wrote its conclusions back. Mutation is now refused; reads deliberately still work, so "upgrade VMark" stays reachable from an app that opens.Typed the whole coherence command surface. 21 commands from
Result<T, String>toCommandError; the baseline's coherence entries are deleted, not zeroed. Total legacy commands 120 → 99.Two quadratics, both measured:
rebuild_from(N durable commits → one transaction)The second changed the curve, not just the constant: 3.75× the files cost 6.6× the time before, 2.25× after.
Why this took three review rounds
Every defect that mattered was green-compatible — all gates passed with each one in place, because each was either unreachable code, an availability change, a missing caveat, an environment assumption, or pure cost. None change behaviour-given-inputs, so no behavioural test could see them. Two were regressions introduced by my own earlier fixes in this same branch and caught by adversarial review, including one that would have stopped any machine without a
gitbinary from reconciling at all.Verification
pnpm check:allgreen; 2,129 Rust tests; clippy-D warningsclean; every ratchet green with nothing raised or baselined. Ships opt-in (coherenceCaptureOnSave: false).Known and deliberately not fixed
A TOCTOU between the write gate's acquire-check and the append. Pre-existing — the workspace flock has never serialized
git— and both cheap remedies are wrong (an O(ledger) read per append, or resurrecting the fingerprint oracle four prior audits already killed). Recorded in.cc-suite/audits/audit-fix-20260806-findings.mdas design scope.