Skip to content

Coherence runtime: sever dead C2, type the command surface, and fix two quadratics (v0.9.30) - #1225

Merged
xiaolai merged 164 commits into
mainfrom
merge/coherence-runtime
Aug 7, 2026
Merged

Coherence runtime: sever dead C2, type the command surface, and fix two quadratics (v0.9.30)#1225
xiaolai merged 164 commits into
mainfrom
merge/coherence-runtime

Conversation

@xiaolai

@xiaolai xiaolai commented Aug 6, 2026

Copy link
Copy Markdown
Owner

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_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 recorded NEEDS REVISION with 3 Critical findings. Preserved with full history on coherence/group-commit-2pc and re-commissioned as its own plan rather than built to make unreachable code correct.

Closed a live data-integrity hole. future_format was 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> to CommandError; the baseline's coherence entries are deleted, not zeroed. Total legacy commands 120 → 99.

Two quadratics, both measured:

Before After
rebuild_from (N durable commits → one transaction) 176.3 ms 3.7 ms
breakdown refresh (DAG loaded per file → per scan) 376.7 ms 85.4 ms

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 git binary from reconciling at all.

Verification

pnpm check:all green; 2,129 Rust tests; clippy -D warnings clean; 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.md as design scope.

xiaolai added 30 commits July 19, 2026 21:07
…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.
xiaolai added 28 commits August 6, 2026 11:23
# 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.
@xiaolai
xiaolai merged commit 07e0fd0 into main Aug 7, 2026
17 checks passed
@xiaolai
xiaolai deleted the merge/coherence-runtime branch August 7, 2026 01:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant