v0.2.0
Part of the OpenKnowledge changelog.
Minor Changes
feat(bridge): correctness guardrail, silent recovery UX, and settlement-based propagation for the dual-CRDT observer bridge (Y.XmlFragment ↔ Y.Text).
Paired-write symmetry (Bucket 0). Adds a typed
context.paired: truemarker to the four origins that atomically write both CRDTs inside onedoc.transact()block —AGENT_WRITE_ORIGIN,FILE_WATCHER_ORIGIN,ROLLBACK_ORIGIN,MANAGED_RENAME_ORIGIN. Server Observer A and Server Observer B now short-circuit symmetrically on paired-write drains via a semantic predicate (context.paired === true), closing the prior Observer-B asymmetry that could re-propagate RGA-level corruption under concurrent typing.MANAGED_RENAME_ORIGINis now exported and included inBRIDGE_ENFORCING_ORIGINS.Loud-on-content-loss merge (Bucket A).
mergeThreeWaynow asserts a maximal-unique-line-substring post-condition with a weak order-preservation side-check (assertContentPreservation). Violations throwBridgeMergeContentLossErrorin tests so regressions surface; production swallows the error, emits a structuredbridge-merge-content-lossJSON log, and queues a silent named checkpoint via the newsaveInMemoryCheckpointshadow-repo primitive so the editor keeps responding. Users can recover the pre-merge state via the existing TimelinePanel — no toast, no banner. The algorithm's academic-proven limits (Khanna-Kunal-Pierce 2007) are turned into observable, recoverable events rather than silent byte loss.TimelinePanel kind-aware rendering. Checkpoint rows render with distinct icon + label per kind:
Save Version(diamond, existing),bridge-merge-loss(amber alert-triangle, "Before concurrent merge @ …"),external-change-rescue(sky file-archive, "External change recovered @ …"). Pure helperscheckpointVariant+checkpointHeadlineLabelare exported for tests.Rescue-buffer consolidation. Reconcile-delete and branch-switch rescue paths now write
external-change-rescuecheckpoints torefs/checkpoints/<branch>/*viasaveInMemoryCheckpoint./api/rescue+/api/rescue/:docNamemerge flat-file (shutdown-flush, retained) and timeline-ref (new) sources — response rows carry asource: 'flat' | 'timeline'discriminator.Settlement-based observer dispatch (Bucket B). Server Observer A + Observer B now run from
doc.on('afterAllTransactions', ...)— one fire per outermostdoc.transact()drain, Observer A before Observer B so any Y.Text write from A is visible to B. The 50 ms wall-clock debounce is gone. Client observer debounce machinery is deleted (per precedent #14, the client is baseline-only). A new grep gate (packages/server/src/bridge-no-wallclock.test.ts) fails CI if wall-clocksetTimeoutreappears in either bridge-observer file.Telemetry. New
bridgeMergeContentLossandbridgeMergeCheckpointCreatedcounters exposed via the existingGET /api/metrics/reconciliationendpoint. Structured log events (bridge-merge-content-loss,bridge-merge-checkpoint-created) follow the existing JSON-log convention.Elevated fuzz coverage.
bridge-convergence.fuzz.test.tsnow runs 200 seeds per PR (STRESS_FUZZ_PR=1, wired inci.yml), 10 000 seeds nightly (STRESS_FUZZ_NIGHTLY=1, wired innightly.yml), and logs the resolved seed count at startup for CI visibility. Default local runs remain 25 seeds to keep the dev loop fast.
[Corrected 2026-04-19 post-ship: automated fuzz tier removed from CI and nightly perspecs/2026-04-19-ci-signal-quality/SPEC.md(FR-2 / D-Q1 LOCKED).STRESS_FUZZ_PRandSTRESS_FUZZ_NIGHTLYenv wirings deleted from both workflows; the fuzz test file is preserved and invoked ad-hoc viabun run measure:fuzz.]Fuzz structural quiescence. Tests now use
awaitDocQuiescence(doc)instead ofwait(ms)aroundpauseSync/resumeSync— race reproduction is event-ordered, not wall-clock.Precedents #1, #11(b), and #13(b) in
AGENTS.mdare updated to reflect the shipped behavior.