OpenKnowledge

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: true marker to the four origins that atomically write both CRDTs inside one doc.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_ORIGIN is now exported and included in BRIDGE_ENFORCING_ORIGINS.

    Loud-on-content-loss merge (Bucket A). mergeThreeWay now asserts a maximal-unique-line-substring post-condition with a weak order-preservation side-check (assertContentPreservation). Violations throw BridgeMergeContentLossError in tests so regressions surface; production swallows the error, emits a structured bridge-merge-content-loss JSON log, and queues a silent named checkpoint via the new saveInMemoryCheckpoint shadow-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 helpers checkpointVariant + checkpointHeadlineLabel are exported for tests.

    Rescue-buffer consolidation. Reconcile-delete and branch-switch rescue paths now write external-change-rescue checkpoints to refs/checkpoints/<branch>/* via saveInMemoryCheckpoint. /api/rescue + /api/rescue/:docName merge flat-file (shutdown-flush, retained) and timeline-ref (new) sources — response rows carry a source: 'flat' | 'timeline' discriminator.

    Settlement-based observer dispatch (Bucket B). Server Observer A + Observer B now run from doc.on('afterAllTransactions', ...) — one fire per outermost doc.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-clock setTimeout reappears in either bridge-observer file.

    Telemetry. New bridgeMergeContentLoss and bridgeMergeCheckpointCreated counters exposed via the existing GET /api/metrics/reconciliation endpoint. Structured log events (bridge-merge-content-loss, bridge-merge-checkpoint-created) follow the existing JSON-log convention.

    Elevated fuzz coverage. bridge-convergence.fuzz.test.ts now runs 200 seeds per PR (STRESS_FUZZ_PR=1, wired in ci.yml), 10 000 seeds nightly (STRESS_FUZZ_NIGHTLY=1, wired in nightly.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 per specs/2026-04-19-ci-signal-quality/SPEC.md (FR-2 / D-Q1 LOCKED). STRESS_FUZZ_PR and STRESS_FUZZ_NIGHTLY env wirings deleted from both workflows; the fuzz test file is preserved and invoked ad-hoc via bun run measure:fuzz.]

    Fuzz structural quiescence. Tests now use awaitDocQuiescence(doc) instead of wait(ms) around pauseSync/resumeSync — race reproduction is event-ordered, not wall-clock.

    Precedents #1, #11(b), and #13(b) in AGENTS.md are updated to reflect the shipped behavior.

View v0.2.0 on GitHub