',
+ '[',
+ ']',
+ '[[',
+ '\\',
+ ' \n',
+] as const;
+
+function prng(seed: number): () => number {
+ let state = seed >>> 0;
+ return () => {
+ state = (state + 0x6d2b79f5) >>> 0;
+ let t = state;
+ t = Math.imul(t ^ (t >>> 15), t | 1);
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
+ };
+}
+
+function randomEdit(source: string, random: () => number): string {
+ const at = Math.floor(random() * (source.length + 1));
+ const roll = random();
+ if (roll < 0.55) {
+ const token = INSERTS[Math.floor(random() * INSERTS.length)] as string;
+ return source.slice(0, at) + token + source.slice(at);
+ }
+ if (roll < 0.9) {
+ const length = 1 + Math.floor(random() * 12);
+ return source.slice(0, at) + source.slice(at + length);
+ }
+ const length = 1 + Math.floor(random() * 6);
+ const token = INSERTS[Math.floor(random() * INSERTS.length)] as string;
+ return source.slice(0, at) + token + source.slice(at + length);
+}
+
+function plainSpans(spans: readonly PmSourceSpan[]) {
+ return spans.map(({ from, to, sourceStart, sourceEnd, type, depth, mapped }) => ({
+ from,
+ to,
+ sourceStart,
+ sourceEnd,
+ type,
+ depth,
+ mapped,
+ }));
+}
+
+function fullOrNull(source: string): Projection | null {
+ try {
+ return buildProjection(source, md);
+ } catch {
+ return null;
+ }
+}
+
+function expectMatchesFullParse(base: Projection, source: string, label: string): Projection {
+ const update = reprojectChanged(base, source, md);
+ const full = buildProjection(source, md);
+ if (update === null) return full;
+ const got = update.projection;
+ expect(got.source, label).toBe(source);
+ expect(got.bodyOffset, label).toBe(full.bodyOffset);
+ expect(got.map.precision, label).toBe('full');
+ expect(got.doc.toJSON(), label).toEqual(full.doc.toJSON());
+ expect(got.doc.eq(full.doc), label).toBe(true);
+ expect(plainSpans(got.map.spans), label).toEqual(plainSpans(full.map.spans));
+ expect(got.map.sourceLength, label).toBe(full.map.sourceLength);
+ expect(got.map.docSize, label).toBe(full.map.docSize);
+ return got;
+}
+
+describe('reprojectChanged — a window reparse equals a full parse', () => {
+ for (const [name, fixture] of [
+ ['hazards', HAZARDS],
+ ['frontmatter', WITH_FRONTMATTER],
+ ] as const) {
+ it(`agrees with buildProjection on 600 single random edits to ${name}`, () => {
+ const random = prng(0xc0ffee);
+ const base = buildProjection(fixture, md);
+ for (let i = 0; i < 600; i++) {
+ const next = randomEdit(fixture, random);
+ if (fullOrNull(next) === null) continue;
+ expectMatchesFullParse(base, next, `${name} edit ${i}: ${JSON.stringify(next)}`);
+ }
+ });
+
+ it(`agrees with buildProjection across a chain of 400 edits to ${name}`, () => {
+ const random = prng(0xbadf00d);
+ let source: string = fixture;
+ let base = buildProjection(source, md);
+ for (let i = 0; i < 400; i++) {
+ const next = randomEdit(source, random);
+ if (fullOrNull(next) === null) continue;
+ base = expectMatchesFullParse(base, next, `${name} chain ${i}: ${JSON.stringify(next)}`);
+ source = next;
+ }
+ });
+ }
+
+ it('agrees with buildProjection on edits across a large realistic document', () => {
+ const random = prng(0x5eed);
+ const source = loadLargeRealistic();
+ const base = buildProjection(source, md);
+ for (let i = 0; i < 60; i++) {
+ const next = randomEdit(source, random);
+ if (fullOrNull(next) === null) continue;
+ expectMatchesFullParse(base, next, `large edit ${i}`);
+ }
+ });
+});
+
+describe('reprojectChanged — it reparses a window, not the document', () => {
+ it('handles typing inside a paragraph of a large document without a full parse', () => {
+ const source = loadLargeRealistic();
+ const base = buildProjection(source, md);
+ const paragraphs = base.map.blocks.filter((span) => span.type === 'paragraph');
+ let windowed = 0;
+ for (let i = 0; i < 40; i++) {
+ const span = paragraphs[Math.floor((i / 40) * paragraphs.length)] as PmSourceSpan;
+ const at = base.bodyOffset + span.sourceEnd;
+ const update = reprojectChanged(base, `${source.slice(0, at)}x${source.slice(at)}`, md);
+ if (update === null) continue;
+ windowed++;
+ expect(update.after.to - update.after.from).toBeLessThanOrEqual(5);
+ }
+ expect(windowed).toBeGreaterThanOrEqual(36);
+ });
+
+ it('keeps every untouched block node by identity', () => {
+ const base = buildProjection(HAZARDS, md);
+ const at = HAZARDS.indexOf('Trailing paragraph.');
+ const update = reprojectChanged(base, `${HAZARDS.slice(0, at)}More. ${HAZARDS.slice(at)}`, md);
+ expect(update).not.toBeNull();
+ if (update === null) return;
+ for (let i = 0; i < update.before.from; i++) {
+ expect(update.projection.doc.child(i)).toBe(base.doc.child(i));
+ }
+ });
+});
+
+describe('reprojectChanged — it declines what a window cannot prove', () => {
+ it('declines a change to the frontmatter', () => {
+ const base = buildProjection(WITH_FRONTMATTER, md);
+ expect(reprojectChanged(base, WITH_FRONTMATTER.replace('Doc', 'Docs'), md)).toBeNull();
+ });
+
+ it('declines when a link reference definition exists or appears', () => {
+ const base = buildProjection(HAZARDS, md);
+ expect(reprojectChanged(base, `${HAZARDS}\n[x]: https://example.com\n`, md)).toBeNull();
+ const withDefinition = buildProjection(`[x]: https://example.com\n\n${HAZARDS}`, md);
+ const source = withDefinition.source.replace('Trailing', 'Trailing [x]');
+ expect(reprojectChanged(withDefinition, source, md)).toBeNull();
+ });
+
+ it('declines a block-precision base', () => {
+ const base = buildProjection(HAZARDS, md);
+ const blockOnly = { ...base, map: { ...base.map, precision: 'block' as const } };
+ expect(reprojectChanged(blockOnly, `${HAZARDS}x`, md)).toBeNull();
+ });
+});
diff --git a/packages/core/src/projection/incremental-projection.ts b/packages/core/src/projection/incremental-projection.ts
new file mode 100644
index 000000000..20c43200a
--- /dev/null
+++ b/packages/core/src/projection/incremental-projection.ts
@@ -0,0 +1,277 @@
+import { Fragment, type Node as PmNode } from '@tiptap/pm/model';
+import { stripFrontmatter } from '../extensions/frontmatter.ts';
+import type { MarkdownManager } from '../markdown/index.ts';
+import { preprocessForParse } from '../markdown/pipeline.ts';
+import { buildFullSourceMap, type PmSourceSpan } from '../markdown/pm-source-map.ts';
+import type { BlockRange, Projection } from './block-splice.ts';
+
+export interface ProjectionUpdate {
+ projection: Projection;
+ before: BlockRange;
+ after: BlockRange;
+}
+
+const DEFINITION_LINE = /^ {0,3}\[[^\]\n]+\]:/m;
+const ANCHOR_STEPS = [1, 2, 4, 8] as const;
+const FALLBACK_BLOCK = 'rawMdxFallback';
+
+const preprocessedBodies = new WeakMap
();
+
+interface SharedEnds {
+ prefix: number;
+ suffix: number;
+}
+
+function sharedEnds(previous: string, next: string): SharedEnds {
+ const bound = Math.min(previous.length, next.length);
+ let prefix = 0;
+ while (prefix < bound && previous.charCodeAt(prefix) === next.charCodeAt(prefix)) prefix++;
+ let suffix = 0;
+ while (
+ suffix < bound - prefix &&
+ previous.charCodeAt(previous.length - 1 - suffix) === next.charCodeAt(next.length - 1 - suffix)
+ ) {
+ suffix++;
+ }
+ return { prefix, suffix };
+}
+
+function lineStart(source: string, offset: number): number {
+ let at = Math.max(0, Math.min(offset, source.length));
+ while (at > 0 && source[at - 1] !== '\n') at--;
+ return at;
+}
+
+function lineEnd(source: string, offset: number): number {
+ let at = Math.max(0, Math.min(offset, source.length));
+ while (at < source.length && source[at] !== '\n') at++;
+ return at;
+}
+
+function canAnchor(span: PmSourceSpan, node: PmNode): boolean {
+ if (span.sourceEnd <= span.sourceStart) return false;
+ return !(node.type.name === 'paragraph' && node.content.size === 0);
+}
+
+function anchorFrom(
+ blocks: readonly PmSourceSpan[],
+ doc: PmNode,
+ start: number,
+ direction: -1 | 1,
+ steps: number,
+): number {
+ let at = start;
+ let taken = 0;
+ for (;;) {
+ at += direction;
+ if (at < 0 || at >= blocks.length) return at;
+ if (canAnchor(blocks[at] as PmSourceSpan, doc.child(at))) {
+ taken++;
+ if (taken === steps) return at;
+ }
+ }
+}
+
+function firstEndingAtOrAfter(blocks: readonly PmSourceSpan[], offset: number): number {
+ let lo = 0;
+ let hi = blocks.length;
+ while (lo < hi) {
+ const mid = (lo + hi) >> 1;
+ if ((blocks[mid] as PmSourceSpan).sourceEnd >= offset) hi = mid;
+ else lo = mid + 1;
+ }
+ return lo;
+}
+
+function lastStartingAtOrBefore(blocks: readonly PmSourceSpan[], offset: number): number {
+ let lo = 0;
+ let hi = blocks.length;
+ while (lo < hi) {
+ const mid = (lo + hi) >> 1;
+ if ((blocks[mid] as PmSourceSpan).sourceStart <= offset) lo = mid + 1;
+ else hi = mid;
+ }
+ return lo - 1;
+}
+
+function hasFallbackBlock(doc: PmNode): boolean {
+ for (let i = 0; i < doc.childCount; i++) {
+ if (doc.child(i).type.name === FALLBACK_BLOCK) return true;
+ }
+ return false;
+}
+
+function preprocessedBody(projection: Projection, body: string): string {
+ const cached = preprocessedBodies.get(projection);
+ if (cached !== undefined) return cached;
+ const preprocessed = preprocessForParse(body);
+ preprocessedBodies.set(projection, preprocessed);
+ return preprocessed;
+}
+
+type Boundary = Record;
+
+function edgeBoundary(
+ base: unknown,
+ window: unknown,
+ touchesStart: boolean,
+ touchesEnd: boolean,
+): Boundary | null {
+ const head = ((touchesStart ? window : base) ?? {}) as Boundary;
+ const tail = ((touchesEnd ? window : base) ?? {}) as Boundary;
+ const out: Boundary = {};
+ if (head.bom === true) out.bom = true;
+ if (typeof head.leading === 'string') out.leading = head.leading;
+ if (typeof tail.trailing === 'string') out.trailing = tail.trailing;
+ return Object.keys(out).length > 0 ? out : null;
+}
+
+function shiftSpan(span: PmSourceSpan, pm: number, source: number): PmSourceSpan {
+ return {
+ ...span,
+ from: span.from + pm,
+ to: span.to + pm,
+ sourceStart: span.sourceStart + source,
+ sourceEnd: span.sourceEnd + source,
+ };
+}
+
+/* STOP: this must return exactly what buildProjection(source) returns, or null. A window is
+ trusted only when (1) the parser's input outside it is byte-identical before and after, which
+ the preprocessing must prove by splitting cleanly at the window's edges -- JSX tag pairing and
+ code regions are decided document-wide there -- and (2) the unchanged block on each side
+ parses back to the identical node over the identical bytes. Frontmatter, link and footnote
+ definitions, and MDX fallback recovery are document-wide too, so they are null. A projection
+ that is merely close corrupts the next write. */
+export function reprojectChanged(
+ base: Projection,
+ source: string,
+ md: MarkdownManager,
+): ProjectionUpdate | null {
+ if (base.map.precision !== 'full') return null;
+ const blocks = base.map.blocks;
+ const n = blocks.length;
+ if (n === 0 || n !== base.doc.childCount) return null;
+ if (source === base.source) {
+ return { projection: base, before: { from: 0, to: 0 }, after: { from: 0, to: 0 } };
+ }
+
+ const { frontmatter, body } = stripFrontmatter(source);
+ if (frontmatter !== base.source.slice(0, base.bodyOffset)) return null;
+ const oldBody = base.source.slice(base.bodyOffset);
+ if (base.map.sourceLength !== oldBody.length) return null;
+ if (DEFINITION_LINE.test(oldBody) || DEFINITION_LINE.test(body)) return null;
+ if (hasFallbackBlock(base.doc)) return null;
+
+ const { prefix, suffix } = sharedEnds(oldBody, body);
+ const changeFrom = prefix;
+ const changeTo = oldBody.length - suffix;
+ const delta = body.length - oldBody.length;
+ const first = firstEndingAtOrAfter(blocks, changeFrom);
+ const last = lastStartingAtOrBefore(blocks, changeTo);
+ const schema = base.doc.type.schema;
+ const oldPreprocessed = preprocessedBody(base, oldBody);
+ const newPreprocessed = preprocessForParse(body);
+
+ for (const steps of ANCHOR_STEPS) {
+ const lo = anchorFrom(blocks, base.doc, first, -1, steps);
+ const hi = anchorFrom(blocks, base.doc, last, 1, steps);
+ const touchesStart = lo < 0;
+ const touchesEnd = hi >= n;
+ if (touchesStart && touchesEnd) return null;
+ const loSpan = touchesStart ? null : (blocks[lo] as PmSourceSpan);
+ const hiSpan = touchesEnd ? null : (blocks[hi] as PmSourceSpan);
+ const windowStart = loSpan === null ? 0 : lineStart(oldBody, loSpan.sourceStart);
+ const windowEnd = hiSpan === null ? oldBody.length : lineEnd(oldBody, hiSpan.sourceEnd);
+ if (windowStart > changeFrom || windowEnd < changeTo) return null;
+ const text = body.slice(windowStart, windowEnd + delta);
+ if (text.trim() === '') continue;
+
+ const head = preprocessForParse(oldBody.slice(0, windowStart));
+ const tail = preprocessForParse(oldBody.slice(windowEnd));
+ const oldWindow = preprocessForParse(oldBody.slice(windowStart, windowEnd));
+ if (oldPreprocessed !== head + oldWindow + tail) continue;
+ if (newPreprocessed !== head + preprocessForParse(text) + tail) continue;
+
+ let parsed: ReturnType;
+ try {
+ parsed = md.parseWithSourceMap(text);
+ } catch {
+ return null;
+ }
+ const window = parsed.doc;
+ const nodes: PmNode[] = [];
+ for (let i = 0; i < window.childCount; i++) {
+ const child = window.child(i);
+ nodes.push(child.type.schema === schema ? child : schema.nodeFromJSON(child.toJSON()));
+ }
+ const windowBlocks = parsed.map.blocks;
+ if (windowBlocks.length !== nodes.length || nodes.length === 0) continue;
+
+ if (loSpan !== null) {
+ const first = windowBlocks[0] as PmSourceSpan;
+ if (
+ !(nodes[0] as PmNode).eq(base.doc.child(lo)) ||
+ first.sourceStart !== loSpan.sourceStart - windowStart ||
+ first.sourceEnd !== loSpan.sourceEnd - windowStart
+ ) {
+ continue;
+ }
+ }
+ if (hiSpan !== null) {
+ const last = windowBlocks[windowBlocks.length - 1] as PmSourceSpan;
+ if (
+ !(nodes[nodes.length - 1] as PmNode).eq(base.doc.child(hi)) ||
+ last.sourceStart !== hiSpan.sourceStart - windowStart + delta ||
+ last.sourceEnd !== hiSpan.sourceEnd - windowStart + delta
+ ) {
+ continue;
+ }
+ }
+
+ const beforeFrom = touchesStart ? 0 : lo;
+ const beforeTo = touchesEnd ? n : hi + 1;
+ const docSize = base.doc.content.size;
+ const pmStart = beforeFrom < n ? (blocks[beforeFrom] as PmSourceSpan).from : docSize;
+ const pmEnd = beforeTo < n ? (blocks[beforeTo] as PmSourceSpan).from : docSize;
+ const pmDelta = window.content.size - (pmEnd - pmStart);
+
+ const children: PmNode[] = [];
+ for (let i = 0; i < beforeFrom; i++) children.push(base.doc.child(i));
+ children.push(...nodes);
+ for (let i = beforeTo; i < n; i++) children.push(base.doc.child(i));
+ const doc = base.doc.type.create(
+ {
+ ...base.doc.attrs,
+ sourceDocBoundary: edgeBoundary(
+ base.doc.attrs.sourceDocBoundary,
+ window.attrs.sourceDocBoundary,
+ touchesStart,
+ touchesEnd,
+ ),
+ },
+ Fragment.fromArray(children),
+ );
+
+ const spans: PmSourceSpan[] = [];
+ for (const span of base.map.spans) if (span.from < pmStart) spans.push(span);
+ for (const span of parsed.map.spans) spans.push(shiftSpan(span, pmStart, windowStart));
+ for (const span of base.map.spans) {
+ if (span.from >= pmEnd) spans.push(shiftSpan(span, pmDelta, delta));
+ }
+
+ const projection: Projection = {
+ source,
+ bodyOffset: base.bodyOffset,
+ doc,
+ map: buildFullSourceMap(spans, body, doc.content.size),
+ };
+ preprocessedBodies.set(projection, newPreprocessed);
+ return {
+ projection,
+ before: { from: beforeFrom, to: beforeTo },
+ after: { from: beforeFrom, to: beforeFrom + nodes.length },
+ };
+ }
+ return null;
+}
diff --git a/packages/core/src/utils/apply-by-prefix-suffix.ts b/packages/core/src/utils/apply-by-prefix-suffix.ts
index d7b25e8e3..623c1b512 100644
--- a/packages/core/src/utils/apply-by-prefix-suffix.ts
+++ b/packages/core/src/utils/apply-by-prefix-suffix.ts
@@ -1,9 +1,5 @@
import type * as Y from 'yjs';
-/**
- * Same semantics, one implementation. @see PRECEDENTS.md precedent #9 (minimize CRDT mutation in
- * sync bridges) @see PRECEDENTS.md precedent #10 (XmlFragment-authoritative, Y.Text mirrors)
- */
export function applyByPrefixSuffix(ytext: Y.Text, currentText: string, newText: string): void {
if (currentText === newText) return;
diff --git a/packages/desktop/src/main/index.ts b/packages/desktop/src/main/index.ts
index 02614f863..24d698f5c 100644
--- a/packages/desktop/src/main/index.ts
+++ b/packages/desktop/src/main/index.ts
@@ -2483,6 +2483,8 @@ async function runApplicationMenuRefresh(): Promise {
: undefined,
onNavigateBack: () => sendMenuAction('navigate-back'),
onNavigateForward: () => sendMenuAction('navigate-forward'),
+ onUndo: () => sendMenuAction('undo'),
+ onRedo: () => sendMenuAction('redo'),
noteWindow: focusedWindow !== null && getNoteWindowContext(focusedWindow.id) !== undefined,
activeTarget: currentActiveTarget(),
onOpenInNewWindow: () => {
diff --git a/packages/desktop/src/main/menu.ts b/packages/desktop/src/main/menu.ts
index aa300b07a..c0c53e5ff 100644
--- a/packages/desktop/src/main/menu.ts
+++ b/packages/desktop/src/main/menu.ts
@@ -22,6 +22,8 @@ import { type MenuTranslator, translateEnglish } from './menu-translator.ts';
export interface MenuDeps {
onNavigateBack?(): void;
onNavigateForward?(): void;
+ onUndo?(): void;
+ onRedo?(): void;
appName: string;
showDevToolsMenu: boolean;
terminalCapable: boolean;
@@ -556,8 +558,16 @@ export function buildMenuTemplate(deps: MenuDeps): MenuItemConstructorOptions[]
{
label: translate(NATIVE_MENU_LABELS.menuEdit),
submenu: [
- roleItem('undo'),
- roleItem('redo'),
+ {
+ label: translate(NATIVE_MENU_LABELS.roleUndo),
+ accelerator: 'CmdOrCtrl+Z',
+ click: () => deps.onUndo?.(),
+ },
+ {
+ label: translate(NATIVE_MENU_LABELS.roleRedo),
+ accelerator: process.platform === 'win32' ? 'CmdOrCtrl+Y' : 'Shift+CmdOrCtrl+Z',
+ click: () => deps.onRedo?.(),
+ },
{ type: 'separator' },
roleItem('cut'),
roleItem('copy'),
diff --git a/packages/desktop/src/preload/index.ts b/packages/desktop/src/preload/index.ts
index aa936b7ef..760e9e427 100644
--- a/packages/desktop/src/preload/index.ts
+++ b/packages/desktop/src/preload/index.ts
@@ -264,6 +264,8 @@ const MENU_ACTION_BUFFER_POLICY: Record =
'move-to-trash': 'never-buffer',
'close-active-tab-or-window': 'never-buffer',
'kill-terminal': 'never-buffer',
+ undo: 'never-buffer',
+ redo: 'never-buffer',
'toggle-sidebar': 'parity',
'toggle-source': 'parity',
diff --git a/packages/desktop/tests/integration/m1-smoke.test.ts b/packages/desktop/tests/integration/m1-smoke.test.ts
index 6324b697d..b626aa0eb 100644
--- a/packages/desktop/tests/integration/m1-smoke.test.ts
+++ b/packages/desktop/tests/integration/m1-smoke.test.ts
@@ -262,7 +262,9 @@ describe('M1 smoke', () => {
const coreMembers = extractLiteralUnion(readFileSync(corePath, 'utf-8'), 'OkMenuAction');
expect(coreMembers.size).toBeGreaterThan(0);
- expect(coreMembers.size).toBe(37);
+ expect(coreMembers.size).toBe(39);
+ expect(coreMembers.has('undo')).toBe(true);
+ expect(coreMembers.has('redo')).toBe(true);
expect(coreMembers.has('toggle-show-hidden-files')).toBe(true);
expect(coreMembers.has('toggle-show-ok-folders')).toBe(true);
expect(coreMembers.has('toggle-show-only-markdown-files')).toBe(true);
diff --git a/packages/desktop/tests/main/menu.test.ts b/packages/desktop/tests/main/menu.test.ts
index 251b1b95f..7187855ff 100644
--- a/packages/desktop/tests/main/menu.test.ts
+++ b/packages/desktop/tests/main/menu.test.ts
@@ -1473,6 +1473,35 @@ describe('buildMenuTemplate — Edit → Check spelling while typing', () => {
});
});
+describe('buildMenuTemplate — Edit → Undo and Redo', () => {
+ test('are app items, not native roles, and click through to the deps', () => {
+ const onUndo = vi.fn(() => {});
+ const onRedo = vi.fn(() => {});
+ const template = buildMenuTemplate(makeDeps({ onUndo, onRedo }));
+ const undo = findByLabel(template, 'Undo');
+ const redo = findByLabel(template, 'Redo');
+ expect(undo?.role).toBeUndefined();
+ expect(redo?.role).toBeUndefined();
+ (undo?.click as (() => void) | undefined)?.();
+ (redo?.click as (() => void) | undefined)?.();
+ expect(onUndo).toHaveBeenCalledTimes(1);
+ expect(onRedo).toHaveBeenCalledTimes(1);
+ });
+
+ test('keep the platform accelerators', () => {
+ const accelerators = (platform: NodeJS.Platform) => {
+ const template = buildMenuTemplateForPlatform(platform, makeDeps());
+ return [
+ findByLabel(template, 'Undo')?.accelerator,
+ findByLabel(template, 'Redo')?.accelerator,
+ ];
+ };
+ expect(accelerators('darwin')).toEqual(['CmdOrCtrl+Z', 'Shift+CmdOrCtrl+Z']);
+ expect(accelerators('win32')).toEqual(['CmdOrCtrl+Z', 'CmdOrCtrl+Y']);
+ expect(accelerators('linux')).toEqual(['CmdOrCtrl+Z', 'Shift+CmdOrCtrl+Z']);
+ });
+});
+
describe('Terminal menu — New Terminal Window', () => {
test('appears in the Terminal submenu beside New Terminal', () => {
const template = buildMenuTemplateForPlatform(
diff --git a/packages/server/measure-observer-a-drain.test.ts b/packages/server/measure-observer-a-drain.test.ts
deleted file mode 100644
index 49c47e986..000000000
--- a/packages/server/measure-observer-a-drain.test.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import { describe, expect, test } from 'vitest';
-import { parseDrainMeasurementArgs } from './measure-observer-a-drain.ts';
-
-describe('measure-observer-a-drain argument parsing', () => {
- test('defaults cover all three carets at the S6 scale', () => {
- expect(parseDrainMeasurementArgs([])).toEqual({
- carets: ['start', 'middle', 'end'],
- fixtureMultiple: 3,
- keystrokes: 15,
- });
- });
-
- test('a caret list selects a subset in the order given', () => {
- expect(parseDrainMeasurementArgs(['--caret', 'end,start']).carets).toEqual(['end', 'start']);
- });
-
- test('fixture multiple and keystroke count are read as integers', () => {
- const args = parseDrainMeasurementArgs(['--fixture-multiple', '1', '--keystrokes', '4']);
- expect(args.fixtureMultiple).toBe(1);
- expect(args.keystrokes).toBe(4);
- });
-
- test('a misspelled flag is rejected rather than silently ignored', () => {
- expect(() => parseDrainMeasurementArgs(['--carets', 'start'])).toThrow(/unknown flag --carets/);
- });
-
- test('a misspelled flag in trailing position is reported as unknown, not as missing a value', () => {
- expect(() => parseDrainMeasurementArgs(['--carets'])).toThrow(/unknown flag --carets/);
- });
-
- test('a leading -- separator is normalized away', () => {
- expect(parseDrainMeasurementArgs(['--', '--caret', 'end']).carets).toEqual(['end']);
- });
-
- test('a -- separator anywhere but the leading position is rejected', () => {
- expect(() => parseDrainMeasurementArgs(['--', '--', '--caret', 'end'])).toThrow(
- /unknown flag --/,
- );
- expect(() => parseDrainMeasurementArgs(['--caret', 'end', '--'])).toThrow(/unknown flag --/);
- });
-
- test('a flag with no value is rejected rather than keeping the default', () => {
- expect(() => parseDrainMeasurementArgs(['--caret'])).toThrow(/--caret needs a value/);
- expect(() => parseDrainMeasurementArgs(['--keystrokes'])).toThrow(/--keystrokes needs a value/);
- });
-
- test('a non-decimal numeric literal is rejected rather than coerced', () => {
- expect(() => parseDrainMeasurementArgs(['--fixture-multiple', '0x10'])).toThrow(
- /--fixture-multiple/,
- );
- expect(() => parseDrainMeasurementArgs(['--keystrokes', '1e4'])).toThrow(/--keystrokes/);
- });
-
- test('a fractional count is rejected rather than truncated', () => {
- expect(() => parseDrainMeasurementArgs(['--fixture-multiple', '3.5'])).toThrow(
- /--fixture-multiple/,
- );
- expect(() => parseDrainMeasurementArgs(['--keystrokes', '2.5'])).toThrow(/--keystrokes/);
- });
-
- test('an unrecognized caret name is rejected even when a valid one accompanies it', () => {
- expect(() => parseDrainMeasurementArgs(['--caret', 'middleish'])).toThrow(/middleish/);
- expect(() => parseDrainMeasurementArgs(['--caret', 'start,middleish'])).toThrow(/middleish/);
- });
-
- test('a non-positive fixture multiple is rejected', () => {
- expect(() => parseDrainMeasurementArgs(['--fixture-multiple', '0'])).toThrow(
- /--fixture-multiple/,
- );
- });
-
- test('a non-positive keystroke count is rejected', () => {
- expect(() => parseDrainMeasurementArgs(['--keystrokes', 'x'])).toThrow(/--keystrokes/);
- });
-});
diff --git a/packages/server/measure-observer-a-drain.ts b/packages/server/measure-observer-a-drain.ts
deleted file mode 100644
index a55775576..000000000
--- a/packages/server/measure-observer-a-drain.ts
+++ /dev/null
@@ -1,181 +0,0 @@
-import { arch, cpus, platform } from 'node:os';
-import { resolve } from 'node:path';
-import { pathToFileURL } from 'node:url';
-import { sharedExtensions } from '@inkeep/open-knowledge-core';
-import { getSchema } from '@tiptap/core';
-import * as Y from 'yjs';
-import { loadLargeRealistic } from '../core/src/markdown/fixtures/index.ts';
-import { composeAndWriteRawBody } from './src/bridge-intake.ts';
-import { mdManager } from './src/md-manager.ts';
-import { setupServerObservers } from './src/server-observers.ts';
-
-export type CaretPosition = 'start' | 'middle' | 'end';
-
-export interface DrainMeasurementArgs {
- readonly carets: CaretPosition[];
- readonly fixtureMultiple: number;
- readonly keystrokes: number;
-}
-
-export interface DrainMeasurement {
- readonly caret: CaretPosition;
- readonly capturedAt: string;
- readonly host: { readonly platform: string; readonly arch: string; readonly cpus: number };
- readonly nodeVersion: string;
- readonly bodyBytes: number;
- readonly keystrokes: number;
- readonly totalMs: number;
- readonly perKeystrokeMedianMs: number;
- readonly perKeystrokeMaxMs: number;
- readonly serializeCallsPerDrain: number;
- readonly parseCallsPerDrain: number;
- readonly markerLanded: boolean;
-}
-
-const RESULT_PREFIX = 'OBSERVER_A_DRAIN_RESULT ';
-const ALL_CARETS: CaretPosition[] = ['start', 'middle', 'end'];
-
-export function parseDrainMeasurementArgs(argv: readonly string[]): DrainMeasurementArgs {
- let carets = ALL_CARETS;
- let fixtureMultiple = 3;
- let keystrokes = 15;
- const positiveInteger = (flag: string, raw: string): number => {
- if (!/^[0-9]+$/.test(raw)) throw new Error(`${flag} must be a positive integer, got ${raw}`);
- const parsed = Number(raw);
- if (parsed < 1) throw new Error(`${flag} must be a positive integer, got ${raw}`);
- return parsed;
- };
- const normalized = argv[0] === '--' ? argv.slice(1) : argv;
- for (let i = 0; i < normalized.length; i++) {
- const flag = normalized[i];
- if (flag !== '--caret' && flag !== '--fixture-multiple' && flag !== '--keystrokes') {
- throw new Error(`unknown flag ${flag}; use --caret, --fixture-multiple or --keystrokes`);
- }
- const value = normalized[i + 1];
- if (value === undefined) throw new Error(`${flag} needs a value`);
- if (flag === '--caret') {
- const requested = value.split(',');
- const unknown = requested.filter((c) => !ALL_CARETS.includes(c as CaretPosition));
- if (unknown.length > 0) {
- throw new Error(`--caret does not accept ${unknown.join(',')}; use start, middle or end`);
- }
- carets = requested as CaretPosition[];
- } else if (flag === '--fixture-multiple') {
- fixtureMultiple = positiveInteger(flag, value);
- } else if (flag === '--keystrokes') {
- keystrokes = positiveInteger(flag, value);
- } else {
- const unreachable: never = flag;
- throw new Error(`unknown flag ${String(unreachable)}`);
- }
- i++;
- }
- return { carets, fixtureMultiple, keystrokes };
-}
-
-function collectTextNodes(
- node: Y.XmlFragment | Y.XmlElement,
- found: Y.XmlText[] = [],
-): Y.XmlText[] {
- for (let i = 0; i < node.length; i++) {
- const child = node.get(i);
- if (child instanceof Y.XmlText) found.push(child);
- else if (child instanceof Y.XmlElement) collectTextNodes(child, found);
- }
- return found;
-}
-
-function median(values: readonly number[]): number {
- const sorted = [...values].sort((a, b) => a - b);
- return Number((sorted[Math.floor(sorted.length / 2)] ?? 0).toFixed(1));
-}
-
-export function measureDrain(caret: CaretPosition, args: DrainMeasurementArgs): DrainMeasurement {
- const base = loadLargeRealistic();
- const raw = Array.from({ length: args.fixtureMultiple }, () => base).join('\n');
- const doc = new Y.Doc();
- const xmlFragment = doc.getXmlFragment('default');
- const ytext = doc.getText('source');
-
- doc.transact(() => {
- composeAndWriteRawBody(doc, raw, 'agent');
- });
- const cleanup = setupServerObservers({
- doc,
- xmlFragment,
- ytext,
- mdManager,
- schema: getSchema(sharedExtensions),
- docName: 'measure-observer-a-drain',
- });
-
- const texts = collectTextNodes(xmlFragment);
- const target =
- caret === 'start'
- ? texts[0]
- : caret === 'middle'
- ? texts[Math.floor(texts.length / 2)]
- : texts[texts.length - 1];
- if (!target) throw new Error('fixture produced no editable text node');
-
- const originalSerialize = mdManager.serialize.bind(mdManager);
- const originalParse = mdManager.parseToEditorMdast.bind(mdManager);
- let serializeCalls = 0;
- let parseCalls = 0;
- mdManager.serialize = (json, opts) => {
- serializeCalls++;
- return originalSerialize(json, opts);
- };
- mdManager.parseToEditorMdast = (markdown) => {
- parseCalls++;
- return originalParse(markdown);
- };
-
- const marker = 'MEASURE'.repeat(Math.ceil(args.keystrokes / 7)).slice(0, args.keystrokes);
- const perKeystroke: number[] = [];
- try {
- const startedAt = performance.now();
- for (const char of marker) {
- const at = performance.now();
- doc.transact(() => {
- target.insert(target.length, char);
- });
- perKeystroke.push(performance.now() - at);
- }
- const totalMs = performance.now() - startedAt;
- return {
- caret,
- capturedAt: new Date().toISOString(),
- host: { platform: platform(), arch: arch(), cpus: cpus().length },
- nodeVersion: process.version,
- bodyBytes: raw.length,
- keystrokes: marker.length,
- totalMs: Number(totalMs.toFixed(1)),
- perKeystrokeMedianMs: median(perKeystroke),
- perKeystrokeMaxMs: Number(Math.max(...perKeystroke).toFixed(1)),
- serializeCallsPerDrain: Number((serializeCalls / marker.length).toFixed(2)),
- parseCallsPerDrain: Number((parseCalls / marker.length).toFixed(2)),
- markerLanded: ytext.toString().includes(marker),
- };
- } finally {
- mdManager.serialize = originalSerialize;
- mdManager.parseToEditorMdast = originalParse;
- cleanup?.();
- }
-}
-
-function runCampaign(args: DrainMeasurementArgs): void {
- for (const caret of args.carets) {
- const measurement = measureDrain(caret, args);
- process.stdout.write(`${RESULT_PREFIX}${JSON.stringify(measurement)}\n`);
- }
-}
-
-if (process.argv[1] && pathToFileURL(resolve(process.argv[1])).href === import.meta.url) {
- try {
- runCampaign(parseDrainMeasurementArgs(process.argv.slice(2)));
- } catch (err) {
- process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
- process.exitCode = 1;
- }
-}
diff --git a/packages/server/package.json b/packages/server/package.json
index 6b333012a..43475fbf3 100644
--- a/packages/server/package.json
+++ b/packages/server/package.json
@@ -10,12 +10,6 @@
"development": "./src/index.ts",
"types": "./dist/index.d.mts",
"default": "./dist/index.mjs"
- },
- "./parse-worker": {
- "@inkeep/source": "./src/parse-worker.ts",
- "development": "./src/parse-worker.ts",
- "types": "./dist/parse-worker.d.mts",
- "default": "./dist/parse-worker.mjs"
}
},
"files": [
@@ -38,9 +32,6 @@
"@opentelemetry/sdk-metrics": "^2.0.0",
"@opentelemetry/sdk-trace-base": "^2.0.0",
"@opentelemetry/semantic-conventions": "^1.30.0",
- "@tiptap/core": "^3.22.3",
- "@tiptap/pm": "^3.22.4",
- "@tiptap/y-tiptap": "3.0.3",
"@types/busboy": "^1.5.4",
"busboy": "^1.6.0",
"chokidar": "^5.0.0",
@@ -77,11 +68,9 @@
"test": "vitest run",
"test:network": "vitest run --config vitest.network.config.ts",
"measure:generated-index-sweep": "node --import tsx --conditions=development measure-generated-index-sweep.ts",
- "measure:observer-a-drain": "node --import tsx --conditions=development measure-observer-a-drain.ts",
"typecheck:bash": "tsc -p tsconfig.bash.json"
},
"devDependencies": {
- "@types/mdast": "^4.0.4",
"@types/node": "^24.7.0",
"@types/shell-quote": "^1.7.5",
"@types/ws": "^8.18.1",
diff --git a/packages/server/src/acp/thread-manager.ts b/packages/server/src/acp/thread-manager.ts
index ff8a0a3f6..61932408e 100644
--- a/packages/server/src/acp/thread-manager.ts
+++ b/packages/server/src/acp/thread-manager.ts
@@ -57,7 +57,6 @@ import type { AgentPresenceBroadcaster } from '../agent-presence.ts';
import { observeReadiness } from '../agent-registry-gate.ts';
import {
type AgentSessionManager,
- agentWriteLossDetect,
applyAgentMarkdownWrite,
snapshotBlocks,
} from '../agent-sessions.ts';
@@ -2784,23 +2783,11 @@ export class AcpThreadManager {
clientName: record.info.agent.id,
},
);
- const embedResolver =
- this.opts.resolveEmbed !== undefined
- ? { resolveEmbed: this.opts.resolveEmbed, sourcePath: target.rel }
- : undefined;
const suppliedWriterId = sessionWriterId(session);
try {
session.dc.document.transact(() => {
const beforeBlocks = snapshotBlocks(session.dc.document);
- applyAgentMarkdownWrite(
- session.dc.document,
- content,
- 'replace',
- embedResolver,
- undefined,
- agentWriteLossDetect(session),
- suppliedWriterId,
- );
+ applyAgentMarkdownWrite(session.dc.document, content, 'replace', suppliedWriterId);
const changedBlocks =
changedBlockRange(beforeBlocks, snapshotBlocks(session.dc.document)) ?? undefined;
const activityMap = session.dc.document.getMap('agent-flash');
diff --git a/packages/server/src/agent-activity.ts b/packages/server/src/agent-activity.ts
index bc5a15c25..b538697be 100644
--- a/packages/server/src/agent-activity.ts
+++ b/packages/server/src/agent-activity.ts
@@ -43,7 +43,7 @@ function collectItemsInDeleteSet(
);
}
-export function* walkYTextItems(ytext: Y.Text): IterableIterator- {
+function* walkYTextItems(ytext: Y.Text): IterableIterator
- {
let cursor = (ytext as unknown as { _start: Item | null })._start;
while (cursor !== null) {
yield cursor;
diff --git a/packages/server/src/agent-effect-capture.test.ts b/packages/server/src/agent-effect-capture.test.ts
deleted file mode 100644
index bfa11b927..000000000
--- a/packages/server/src/agent-effect-capture.test.ts
+++ /dev/null
@@ -1,124 +0,0 @@
-import { describe, expect, test, vi } from 'vitest';
-import * as Y from 'yjs';
-import { captureEffect, type EffectValue } from './activity-log.ts';
-import { applyAgentMarkdownWrite } from './agent-sessions.ts';
-import {
- createWiredPreDrainRig,
- WIRED_PENDING_LINE,
- WIRED_STALE_LINE,
-} from './pre-drain-wired.test-helper.ts';
-
-const AGENT_ORIGIN = Object.freeze({ source: 'local', context: { origin: 'agent-write' } });
-const FOREIGN_ORIGIN = Object.freeze({ source: 'local', context: { origin: 'observer-sync' } });
-
-function effectRows(doc: Y.Doc): EffectValue[] {
- return [...doc.getMap('agent-effects').values()];
-}
-
-describe('captureEffect origin keying', () => {
- test('a foreign-origin write landing between arming and the agent transact is not captured', () => {
- const doc = new Y.Doc();
- const ytext = doc.getText('source');
- doc.transact(() => ytext.insert(0, 'seed body\n'), 'setup');
-
- captureEffect(ytext, 'agent-1', AGENT_ORIGIN, 'seed', 'claude');
-
- doc.transact(() => ytext.insert(ytext.length, 'user keystroke\n'), FOREIGN_ORIGIN);
- doc.transact(() => ytext.insert(ytext.length, 'agent bytes\n'), AGENT_ORIGIN);
-
- const rows = effectRows(doc);
- expect(rows.length).toBe(1);
- const delta = JSON.stringify(rows[0]?.delta);
- expect(delta).toContain('agent bytes');
- expect(delta).not.toContain('user keystroke');
- });
-
- test('the disposer disarms a write that produced no delta, so it cannot capture a later one', () => {
- const doc = new Y.Doc();
- const ytext = doc.getText('source');
-
- const dispose = captureEffect(ytext, 'agent-1', AGENT_ORIGIN, 'seed', 'claude');
- dispose();
-
- const dispose2 = captureEffect(ytext, 'agent-1', AGENT_ORIGIN, 'seed', 'claude');
- doc.transact(() => ytext.insert(0, 'second write\n'), AGENT_ORIGIN);
- dispose2();
-
- const rows = effectRows(doc);
- expect(rows.length).toBe(1);
- expect(JSON.stringify(rows[0]?.delta)).toContain('second write');
- });
-});
-
-describe('effect capture across a real pre-drain flush', () => {
- test('the agent-effects row carries the AGENT delta, not the pre-drained user keystroke', async () => {
- vi.useFakeTimers({ toFake: ['Date'] });
- vi.setSystemTime(1_000_000);
- const rig = await createWiredPreDrainRig({ docName: 'effect-attribution.md' });
- try {
- rig.stageUnpropagatedKeystroke();
- expect(rig.serializeFragment()).toContain(WIRED_PENDING_LINE);
- expect(rig.ytextString()).toContain(WIRED_STALE_LINE);
- expect(rig.ytextString()).not.toContain(WIRED_PENDING_LINE);
-
- const document = rig.session.dc.document;
- const dispose = captureEffect(
- document.getText('source'),
- rig.session.agentId,
- rig.session.origin,
- 'seed',
- 'claude',
- );
- try {
- rig.agentWriteWithPreDrain('A fresh agent paragraph.', 'append');
- } finally {
- dispose();
- }
-
- expect(rig.ytextString()).toContain(WIRED_PENDING_LINE);
-
- const rows = effectRows(rig.doc);
- expect(rows.length).toBe(1);
- const row = rows[0];
- expect(row?.sessionId).toBe(rig.session.agentId);
- const delta = JSON.stringify(row?.delta);
- expect(delta).toContain('A fresh agent paragraph.');
- expect(delta).not.toContain(WIRED_PENDING_LINE);
- } finally {
- await rig.cleanup();
- vi.useRealTimers();
- }
- });
-
- test('a declined write leaves no armed observer for the next write on the same session', async () => {
- vi.useFakeTimers({ toFake: ['Date'] });
- vi.setSystemTime(1_000_000);
- const rig = await createWiredPreDrainRig({ docName: 'effect-noop.md' });
- try {
- const document = rig.session.dc.document;
- const disposeNoop = captureEffect(
- document.getText('source'),
- rig.session.agentId,
- rig.session.origin,
- );
- document.transact(() => {
- applyAgentMarkdownWrite(document, '', 'append');
- }, rig.session.origin);
- disposeNoop();
- expect(effectRows(rig.doc).length).toBe(0);
-
- const dispose = captureEffect(
- document.getText('source'),
- rig.session.agentId,
- rig.session.origin,
- );
- rig.agentWrite('Real content.', 'append');
- dispose();
-
- expect(effectRows(rig.doc).length).toBe(1);
- } finally {
- await rig.cleanup();
- vi.useRealTimers();
- }
- });
-});
diff --git a/packages/server/src/agent-sessions-snapshot-blocks.test.ts b/packages/server/src/agent-sessions-snapshot-blocks.test.ts
new file mode 100644
index 000000000..0e7363574
--- /dev/null
+++ b/packages/server/src/agent-sessions-snapshot-blocks.test.ts
@@ -0,0 +1,26 @@
+import type { Document } from '@hocuspocus/server';
+import { describe, expect, test } from 'vitest';
+import * as Y from 'yjs';
+import { snapshotBlocks } from './agent-sessions.ts';
+
+function docWith(source: string): Document {
+ const doc = new Y.Doc() as unknown as Document;
+ doc.getText('source').insert(0, source);
+ return doc;
+}
+
+describe('snapshotBlocks', () => {
+ test('returns one entry per top-level block of the markdown', () => {
+ const blocks = snapshotBlocks(docWith('# Title\n\nFirst.\n\nSecond.\n'));
+ expect(blocks).toEqual(['# Title', 'First.', 'Second.']);
+ });
+
+ test('is empty for an empty document', () => {
+ expect(snapshotBlocks(docWith(''))).toEqual([]);
+ });
+
+ test('skips the frontmatter fence — ordinals address body blocks', () => {
+ const blocks = snapshotBlocks(docWith('---\ntitle: T\n---\n\n# Heading\n\nBody.\n'));
+ expect(blocks).toEqual(['# Heading', 'Body.']);
+ });
+});
diff --git a/packages/server/src/agent-sessions.test.ts b/packages/server/src/agent-sessions.test.ts
index b9d8a20ee..4d96884fe 100644
--- a/packages/server/src/agent-sessions.test.ts
+++ b/packages/server/src/agent-sessions.test.ts
@@ -1,5 +1,5 @@
import type { Document } from '@hocuspocus/server';
-import { sharedExtensions, stripFrontmatter } from '@inkeep/open-knowledge-core';
+import { stripFrontmatter } from '@inkeep/open-knowledge-core';
import { metrics } from '@opentelemetry/api';
import {
AggregationTemporality,
@@ -7,8 +7,6 @@ import {
MeterProvider,
PeriodicExportingMetricReader,
} from '@opentelemetry/sdk-metrics';
-import { getSchema } from '@tiptap/core';
-import { yXmlFragmentToProseMirrorRootNode } from '@tiptap/y-tiptap';
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
import * as Y from 'yjs';
import { sessionWriterId } from './agent-id.ts';
@@ -380,7 +378,7 @@ describe('applyAgentUndo — scope drain semantics (V0-14)', () => {
}
expect(session.um.undoStack.length).toBe(4);
- const undone = applyAgentUndo(session, 'count', undefined, 2);
+ const undone = applyAgentUndo(session, 'count', 2);
expect(undone).toBe(true);
expect(session.um.undoStack.length).toBe(2);
});
@@ -394,7 +392,7 @@ describe('applyAgentUndo — scope drain semantics (V0-14)', () => {
session.dc.document.transact(() => ytext.insert(0, 'y'), session.origin);
expect(session.um.undoStack.length).toBe(2);
- expect(applyAgentUndo(session, 'count', undefined, 99)).toBe(true);
+ expect(applyAgentUndo(session, 'count', 99)).toBe(true);
expect(session.um.undoStack.length).toBe(0);
});
@@ -404,7 +402,7 @@ describe('applyAgentUndo — scope drain semantics (V0-14)', () => {
session.dc.document.transact(() => ytext.insert(0, 'z'), session.origin);
expect(session.um.undoStack.length).toBe(1);
- expect(applyAgentUndo(session, 'count', undefined, 0)).toBe(false);
+ expect(applyAgentUndo(session, 'count', 0)).toBe(false);
expect(session.um.undoStack.length).toBe(1);
});
@@ -414,42 +412,6 @@ describe('applyAgentUndo — scope drain semantics (V0-14)', () => {
expect(applyAgentUndo(session, 'session')).toBe(false);
expect(applyAgentUndo(session, 'last')).toBe(false);
});
-
- test('post-undo XmlFragment uses embedResolver for `![[file]]` refs', async () => {
- const session = await manager.getSession('doc-resolve.md', 'agent-resolve');
- const xmlFragment = session.dc.document.getXmlFragment('default');
- const ytext = session.dc.document.getText('source');
-
- const embedResolver = {
- resolveEmbed: (basename: string) =>
- basename === 'photo.png' ? 'attachments/photo.png' : null,
- sourcePath: 'doc-resolve.md',
- };
-
- session.dc.document.transact(() => {
- applyAgentMarkdownWrite(session.dc.document, '![[photo.png]]\n', 'replace', embedResolver);
- }, session.origin);
- session.um.stopCapturing();
-
- session.dc.document.transact(() => {
- applyAgentMarkdownWrite(session.dc.document, '# Heading\n', 'replace', embedResolver);
- }, session.origin);
-
- expect(ytext.toString()).toContain('# Heading');
-
- const undone = applyAgentUndo(session, 'last', embedResolver);
- expect(undone).toBe(true);
-
- const schema = getSchema(sharedExtensions);
- const pmJson = yXmlFragmentToProseMirrorRootNode(xmlFragment, schema).toJSON();
- const node = pmJson.content?.[0] as
- | { type?: string; attrs?: { componentName?: string; props?: Record } }
- | undefined;
- expect(node?.type).toBe('jsxComponent');
- expect(node?.attrs?.componentName).toBe('WikiEmbedImage');
- expect(node?.attrs?.props?.src).toBe('/attachments/photo.png');
- expect(node?.attrs?.props?.target).toBe('photo.png');
- });
});
describe('applyAgentUndo — Y.Text-is-truth contract (FR-40)', () => {
@@ -571,7 +533,7 @@ describe('empty / whitespace content writes (PRD-6835)', () => {
expect(stripFrontmatter(after).body.trim()).toBe('');
});
- test('replace with empty markdown on a frontmatter-less doc clears to empty (bridge converges)', async () => {
+ test('replace with empty markdown on a frontmatter-less doc clears to empty', async () => {
const session = await manager.getSession('clear-plain.md', 'agent-clear-plain');
const ytext = session.dc.document.getText('source');
@@ -585,12 +547,6 @@ describe('empty / whitespace content writes (PRD-6835)', () => {
}, session.origin);
expect(ytext.toString()).toBe('');
- const schema = getSchema(sharedExtensions);
- const node = yXmlFragmentToProseMirrorRootNode(
- session.dc.document.getXmlFragment('default'),
- schema,
- );
- expect(node.textContent).toBe('');
});
test('append with empty markdown is a no-op (no \\n\\n injection, byte-unchanged)', async () => {
@@ -778,9 +734,6 @@ describe('applyAgentMarkdownWrite — position: "replace" atomic-overwrite contr
first.dc.document,
'# First replacement\n',
'replace',
- undefined,
- undefined,
- undefined,
sessionWriterId(first),
);
}, first.origin);
@@ -791,9 +744,6 @@ describe('applyAgentMarkdownWrite — position: "replace" atomic-overwrite contr
first.dc.document,
'# Peer replacement\n',
'replace',
- undefined,
- undefined,
- undefined,
sessionWriterId({ agentId: 'agent-b' }),
);
},
@@ -1005,9 +955,6 @@ describe('applyAgentMarkdownWrite — position: "replace" atomic-overwrite contr
first.dc.document,
'# First replacement\n',
'replace',
- undefined,
- undefined,
- undefined,
sessionWriterId(first),
);
}, first.origin);
@@ -1020,9 +967,6 @@ describe('applyAgentMarkdownWrite — position: "replace" atomic-overwrite contr
first.dc.document,
'# Peer replacement\n',
'replace',
- undefined,
- undefined,
- undefined,
sessionWriterId({ agentId: 'agent-b' }),
);
},
@@ -1044,9 +988,6 @@ describe('applyAgentMarkdownWrite — position: "replace" atomic-overwrite contr
first.dc.document,
'# First replacement\n',
'replace',
- undefined,
- undefined,
- undefined,
sessionWriterId(first),
);
}, first.origin);
@@ -1059,9 +1000,6 @@ describe('applyAgentMarkdownWrite — position: "replace" atomic-overwrite contr
first.dc.document,
'# Peer replacement\n',
'replace',
- undefined,
- undefined,
- undefined,
sessionWriterId({ agentId: 'agent-b' }),
);
},
@@ -1102,9 +1040,6 @@ describe('applyAgentMarkdownWrite — position: "replace" atomic-overwrite contr
session.dc.document,
'Body without frontmatter.\n',
'replace',
- undefined,
- undefined,
- undefined,
sessionWriterId(session),
);
}, session.origin);
@@ -1118,9 +1053,6 @@ describe('applyAgentMarkdownWrite — position: "replace" atomic-overwrite contr
session.dc.document,
peerPayload,
'replace',
- undefined,
- undefined,
- undefined,
sessionWriterId({ agentId: 'agent-b' }),
);
},
@@ -1137,9 +1069,6 @@ describe('applyAgentMarkdownWrite — position: "replace" atomic-overwrite contr
session.dc.document,
peerPayload,
'replace',
- undefined,
- undefined,
- undefined,
sessionWriterId({ agentId: 'agent-b' }),
);
},
@@ -1183,9 +1112,6 @@ describe('applyAgentMarkdownWrite — position: "replace" atomic-overwrite contr
first.dc.document,
'# First replacement\n',
'replace',
- undefined,
- undefined,
- undefined,
sessionWriterId(first),
);
}, first.origin);
@@ -1198,9 +1124,6 @@ describe('applyAgentMarkdownWrite — position: "replace" atomic-overwrite contr
first.dc.document,
'# Peer replacement\n',
'replace',
- undefined,
- undefined,
- undefined,
sessionWriterId({ agentId: 'agent-b' }),
);
},
@@ -1216,9 +1139,6 @@ describe('applyAgentMarkdownWrite — position: "replace" atomic-overwrite contr
first.dc.document,
payload,
position,
- undefined,
- undefined,
- undefined,
sessionWriterId({ agentId: 'agent-b' }),
);
},
diff --git a/packages/server/src/agent-sessions.ts b/packages/server/src/agent-sessions.ts
index 1c8aa33ed..4dd3e3511 100644
--- a/packages/server/src/agent-sessions.ts
+++ b/packages/server/src/agent-sessions.ts
@@ -1,10 +1,9 @@
/** Each session creates its own frozen LocalTransactionOrigin at birth (precedent #1). */
import type { DirectConnection, Document, Hocuspocus } from '@hocuspocus/server';
import {
- applyPatchToFm,
- detectFmRegion,
parseFrontmatterYaml,
prependFrontmatter,
+ sourceBlockSnapshot,
stripFrontmatter,
unwrapFrontmatterFences,
} from '@inkeep/open-knowledge-core';
@@ -13,20 +12,9 @@ import { splitPayloadFrontmatter } from './payload-frontmatter.ts';
export { colorFromSeed } from '@inkeep/open-knowledge-core';
import * as Y from 'yjs';
-import type { YjsStackItemShape } from './agent-activity.ts';
import { type RawWriterId, UNIDENTIFIED_WRITER_ID } from './agent-id.ts';
-import {
- composeAndWriteRawBody,
- deriveFragmentFromYtext,
- type PrecomputedParse,
- replaceRawBody,
-} from './bridge-intake.ts';
-import {
- type BridgeDeriveLossReporter,
- DERIVE_LOSS_SITE_AGENT_WRITE_INTAKE,
- type DeriveLossDetectOptions,
-} from './bridge-loss-detector.ts';
-import { shouldRunPairedIntakeDetection } from './bridge-loss-suppression.ts';
+import { composeAndWriteRawBody, replaceRawBody } from './bridge-intake.ts';
+import { getLastExternalEditorChangeMs } from './bridge-quiescence.ts';
import { isConfigDoc, isSystemDoc } from './cc1-broadcast.ts';
import { ConcurrentOverwriteRefusedError } from './concurrent-overwrite-refused-error.ts';
import { isDocInConflict } from './conflict-authority.ts';
@@ -39,14 +27,10 @@ import { getDocExtension, stripDocExtension } from './doc-extensions.ts';
import { FrontmatterMalformedError } from './frontmatter-malformed-error.ts';
import { recordFrontmatterEditSurface } from './frontmatter-telemetry.ts';
import { getLogger } from './logger.ts';
+import { mdManager } from './md-manager.ts';
import { incrementAgentSessionEvictions } from './metrics.ts';
-import { precomputeParse } from './parse-pool.ts';
-import {
- getLastExternalEditorChangeMs,
- getPreDrainController,
- type PairedWriteOrigin,
-} from './server-observers.ts';
import { getMeter, setActiveSpanAttributes, withSpanSync } from './telemetry.ts';
+import type { PairedWriteOrigin } from './write-origins.ts';
export type { AgentWriteContentDivergence };
@@ -56,11 +40,6 @@ export interface AgentDirectConnection extends DirectConnection {
document: Document;
}
-/**
- * Agent write origin — typed `PairedWriteOrigin` per precedent #1 extension; the typed marker
- * carries the `paired: true` field that `isPairedWriteOrigin` reads to gate paired-write
- * transactions.
- */
export const AGENT_WRITE_ORIGIN = {
source: 'local',
skipStoreHooks: false,
@@ -79,38 +58,6 @@ function docNameToFile(docName: string): string {
* Y.Text bytes, then route through the sibling primitive matching the caller's intent. The caller
* must wrap this in `session.dc.document.transact(fn, session.origin)` (precedent #24).
*/
-export async function prepareAgentMarkdownParse(
- document: Document,
- markdown: string,
- position: 'append' | 'prepend' | 'replace' | 'patch',
- embedResolver?: {
- resolveEmbed: (basename: string, sourcePath: string) => string | null;
- sourcePath: string;
- },
-): Promise {
- if (isDocInConflict(document)) return undefined;
- const composed = composeAgentWrite(document.getText('source').toString(), markdown, position);
- if (composed === undefined) return undefined;
- return precomputeParse(composed.newContent, embedResolver);
-}
-
-export async function prepareFrontmatterPatchParse(
- document: Document,
- patch: Parameters[1],
-): Promise {
- const snapshot = document.getText('source').toString();
- const { fenced, body } = detectFmRegion(snapshot);
- const result = applyPatchToFm(fenced, patch);
- if (!result.ok || result.nextFenced === fenced) return undefined;
- const needsFenceSeparator = fenced === '' && body !== '' && !body.startsWith('\n');
- return precomputeParse(result.nextFenced + (needsFenceSeparator ? '\n' : '') + body);
-}
-
-export interface AgentWriteLossDetect {
- reporter: BridgeDeriveLossReporter;
- writerId: string | null;
-}
-
export const CONCURRENT_REPLACE_WINDOW_MS = 2_000;
class AgentWriteRecency {
@@ -163,38 +110,10 @@ function assertConcurrentReplaceAllowed(
throw new ConcurrentOverwriteRefusedError(docNameToFile(document.name));
}
-export function agentWriteLossDetect(session: {
- bridgeLossReporter?: BridgeDeriveLossReporter;
- agentId: string;
-}): AgentWriteLossDetect | undefined {
- return session.bridgeLossReporter
- ? { reporter: session.bridgeLossReporter, writerId: session.agentId }
- : undefined;
-}
-
-export function agentWritePreDrain(
- document: Document,
- markdown: string,
- position: 'append' | 'prepend' | 'replace' | 'patch',
-): void {
- const controller = getPreDrainController(document as unknown as Y.Doc);
- if (!controller) return;
- if (composeAgentWrite(document.getText('source').toString(), markdown, position) === undefined) {
- return;
- }
- controller.preDrain({ kind: 'agent-write', writeKind: position });
-}
-
export function applyAgentMarkdownWrite(
document: Document,
markdown: string,
position: 'append' | 'prepend' | 'replace' | 'patch',
- embedResolver?: {
- resolveEmbed: (basename: string, sourcePath: string) => string | null;
- sourcePath: string;
- },
- precomputed?: PrecomputedParse,
- lossDetect?: AgentWriteLossDetect,
suppliedWriterId?: RawWriterId,
): AgentWriteContentDivergence | undefined {
if (isDocInConflict(document)) {
@@ -214,9 +133,6 @@ export function applyAgentMarkdownWrite(
document,
markdown,
position,
- embedResolver,
- precomputed,
- lossDetect,
suppliedWriterId,
);
if (divergence !== undefined) {
@@ -234,10 +150,7 @@ export function applyAgentMarkdownWrite(
}
export function snapshotBlocks(document: Document): string[] {
- return document
- .getXmlFragment('default')
- .toArray()
- .map((child) => child.toString());
+ return sourceBlockSnapshot(document.getText('source').toString(), mdManager);
}
interface ComposedAgentWrite {
@@ -299,12 +212,6 @@ function applyAgentMarkdownWriteInner(
document: Document,
markdown: string,
position: 'append' | 'prepend' | 'replace' | 'patch',
- embedResolver?: {
- resolveEmbed: (basename: string, sourcePath: string) => string | null;
- sourcePath: string;
- },
- precomputed?: PrecomputedParse,
- lossDetect?: AgentWriteLossDetect,
suppliedWriterId?: RawWriterId,
): AgentWriteContentDivergence | undefined {
try {
@@ -316,20 +223,6 @@ function applyAgentMarkdownWriteInner(
}
const { existingFm, finalFm, newContent } = composed;
- const detect: DeriveLossDetectOptions | undefined =
- lossDetect && shouldRunPairedIntakeDetection(AGENT_WRITE_ORIGIN.context.origin)
- ? {
- report: (obs) =>
- lossDetect.reporter(
- document.name,
- obs,
- lossDetect.writerId,
- DERIVE_LOSS_SITE_AGENT_WRITE_INTAKE,
- ),
- baselineFullMd: currentYText,
- }
- : undefined;
-
let frontmatterEdited = false;
if (finalFm !== existingFm) {
const parsed = parseFrontmatterYaml(unwrapFrontmatterFences(finalFm));
@@ -354,9 +247,9 @@ function applyAgentMarkdownWriteInner(
if (frontmatterEdited) recordFrontmatterEditSurface('mcp-write');
if (position === 'replace') {
- replaceRawBody(document, newContent, embedResolver, precomputed, detect);
+ replaceRawBody(document, newContent);
} else {
- composeAndWriteRawBody(document, newContent, 'agent', embedResolver, precomputed, detect);
+ composeAndWriteRawBody(document, newContent, 'agent');
}
const actualYText = document.getText('source').toString();
@@ -390,16 +283,12 @@ function applyAgentMarkdownWriteInner(
/**
* Y.Text-is-truth agent undo, the only sanctioned server-side undo write surface: after
- * `session.um.undo()` Y.Text holds the intended post-undo bytes and XmlFragment derives from them
- * (precedent #38). There is no canonicalize-write-back step, which would defeat that contract.
+ * `session.um.undo()` Y.Text holds the intended post-undo bytes (precedent #38). There is no
+ * canonicalize-write-back step, which would defeat that contract.
*/
export function applyAgentUndo(
session: SessionRecord,
scope: 'last' | 'session' | 'count',
- embedResolver?: {
- resolveEmbed: (basename: string, sourcePath: string) => string | null;
- sourcePath: string;
- },
count?: number,
): boolean {
const undoDoc = session.dc.document;
@@ -415,7 +304,7 @@ export function applyAgentUndo(
},
},
() => {
- const undone = applyAgentUndoInner(session, scope, embedResolver, count);
+ const undone = applyAgentUndoInner(session, scope, count);
setActiveSpanAttributes({ 'agent.undo_effective': undone });
return undone;
},
@@ -425,10 +314,6 @@ export function applyAgentUndo(
function applyAgentUndoInner(
session: SessionRecord,
scope: 'last' | 'session' | 'count',
- embedResolver?: {
- resolveEmbed: (basename: string, sourcePath: string) => string | null;
- sourcePath: string;
- },
count?: number,
): boolean {
const { dc, um, undoOrigin } = session;
@@ -441,28 +326,12 @@ function applyAgentUndoInner(
? Math.min(Math.max(0, count ?? 0), um.undoStack.length)
: um.undoStack.length;
- if (framesToPop === 1 && um.undoStack.length > 0) {
- getPreDrainController(document as unknown as Y.Doc)?.preDrain({
- kind: 'agent-undo',
- stackItem: um.undoStack[um.undoStack.length - 1] as unknown as YjsStackItemShape,
- });
- }
-
let undone = false;
- const reporter = session.bridgeLossReporter;
- const detect: DeriveLossDetectOptions | undefined =
- reporter && shouldRunPairedIntakeDetection(undoOrigin.context.origin)
- ? {
- report: (obs) => reporter(session.docName, obs, session.agentId),
- baselineFullMd: document.getText('source').toString(),
- }
- : undefined;
document.transact(() => {
for (let i = 0; i < framesToPop && um.undoStack.length > 0; i++) {
um.undo();
undone = true;
}
- if (undone) deriveFragmentFromYtext(document, embedResolver, detect);
}, undoOrigin);
log.debug(
@@ -486,7 +355,6 @@ interface SessionRecord {
um: Y.UndoManager;
agentId: string;
docName: string;
- bridgeLossReporter?: BridgeDeriveLossReporter;
lastUsedAt: number;
}
@@ -519,7 +387,6 @@ function createSessionOrigin(
}
function createUndoOrigin(sessionId: string, agentType?: string): PairedWriteOrigin {
- // precedent #1: typed transaction origin; paired: true so observers short-circuit.
const context: Record & { origin: string; paired: true } = {
origin: 'agent-undo',
paired: true as const,
@@ -565,7 +432,6 @@ export class AgentSessionManager {
private hocuspocus: Hocuspocus;
private readonly maxSessions: number;
private readonly minEvictableIdleMs: number;
- private bridgeLossReporter?: BridgeDeriveLossReporter;
private evictions = 0;
constructor(
@@ -573,17 +439,11 @@ export class AgentSessionManager {
options: {
maxSessions?: number;
minEvictableIdleMs?: number;
- bridgeLossReporter?: BridgeDeriveLossReporter;
} = {},
) {
this.hocuspocus = hocuspocus;
this.maxSessions = options.maxSessions ?? MAX_AGENT_SESSIONS;
this.minEvictableIdleMs = options.minEvictableIdleMs ?? MIN_EVICTABLE_IDLE_MS;
- this.bridgeLossReporter = options.bridgeLossReporter;
- }
-
- public attachBridgeLossReporter(reporter: BridgeDeriveLossReporter): void {
- this.bridgeLossReporter = reporter;
}
public get liveSessionCount(): number {
@@ -731,7 +591,6 @@ export class AgentSessionManager {
agentId,
docName,
lastUsedAt: Date.now(),
- bridgeLossReporter: this.bridgeLossReporter,
};
}
diff --git a/packages/server/src/agent-write-loss-detect-coverage.test.ts b/packages/server/src/agent-write-loss-detect-coverage.test.ts
deleted file mode 100644
index 20f19a018..000000000
--- a/packages/server/src/agent-write-loss-detect-coverage.test.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-import { basename, dirname, join } from 'node:path';
-import { fileURLToPath } from 'node:url';
-import { Node, Project, SyntaxKind } from 'ts-morph';
-import { describe, expect, it } from 'vitest';
-import { listAgentWriteSpineFiles } from './agent-write-spine-files.test-helper.ts';
-
-const here = dirname(fileURLToPath(import.meta.url));
-
-function newProject(): Project {
- return new Project({
- skipFileDependencyResolution: true,
- skipLoadingLibFiles: true,
- skipAddingFilesFromTsConfig: true,
- compilerOptions: { noLib: true, allowJs: false },
- });
-}
-
-function calleeName(call: Node): string | null {
- if (!Node.isCallExpression(call)) return null;
- const expr = call.getExpression();
- if (Node.isIdentifier(expr)) return expr.getText();
- if (Node.isPropertyAccessExpression(expr)) return expr.getName();
- return null;
-}
-
-function threadsLossDetect(call: Node): boolean {
- if (!Node.isCallExpression(call)) return false;
- return call
- .getArguments()
- .some((arg) => Node.isCallExpression(arg) && calleeName(arg) === 'agentWriteLossDetect');
-}
-
-describe('agent-write loss-detect coverage', () => {
- it('every applyAgentMarkdownWrite spine call threads agentWriteLossDetect', () => {
- const project = newProject();
- const spineFiles = listAgentWriteSpineFiles(here);
- expect(spineFiles.length).toBeGreaterThan(0);
- const spineCalls = spineFiles.flatMap((path) =>
- project
- .addSourceFileAtPath(join(here, path))
- .getDescendantsOfKind(SyntaxKind.CallExpression)
- .filter((c) => calleeName(c) === 'applyAgentMarkdownWrite'),
- );
- expect(spineCalls.length).toBeGreaterThanOrEqual(6);
- const missing = spineCalls
- .filter((c) => !threadsLossDetect(c))
- .map((c) => `${basename(c.getSourceFile().getFilePath())}:${c.getStartLineNumber()}`);
- expect(missing).toEqual([]);
- });
-
- it('flags a spine call that omits the loss detector (planted positive)', () => {
- const project = newProject();
- const sf = project.createSourceFile(
- 'planted-missing-loss-detect.ts',
- `declare function applyAgentMarkdownWrite(...a: unknown[]): void;
- function h(session: { dc: { document: unknown } }) {
- applyAgentMarkdownWrite(session.dc.document, 'x', 'append');
- }`,
- );
- const call = sf
- .getDescendantsOfKind(SyntaxKind.CallExpression)
- .find((c) => calleeName(c) === 'applyAgentMarkdownWrite');
- expect(call).toBeDefined();
- expect(call && threadsLossDetect(call)).toBe(false);
- });
-
- it('recognizes a spine call that threads the loss detector (positive control)', () => {
- const project = newProject();
- const sf = project.createSourceFile(
- 'planted-with-loss-detect.ts',
- `declare function applyAgentMarkdownWrite(...a: unknown[]): void;
- declare function agentWriteLossDetect(s: unknown): unknown;
- function h(session: { dc: { document: unknown } }) {
- applyAgentMarkdownWrite(
- session.dc.document,
- 'x',
- 'append',
- undefined,
- undefined,
- agentWriteLossDetect(session),
- );
- }`,
- );
- const call = sf
- .getDescendantsOfKind(SyntaxKind.CallExpression)
- .find((c) => calleeName(c) === 'applyAgentMarkdownWrite');
- expect(call).toBeDefined();
- expect(call && threadsLossDetect(call)).toBe(true);
- });
-});
diff --git a/packages/server/src/agent-write-pre-drain-coverage.test.ts b/packages/server/src/agent-write-pre-drain-coverage.test.ts
deleted file mode 100644
index 0252d6890..000000000
--- a/packages/server/src/agent-write-pre-drain-coverage.test.ts
+++ /dev/null
@@ -1,142 +0,0 @@
-import { basename, dirname, join } from 'node:path';
-import { fileURLToPath } from 'node:url';
-import { Node, Project, SyntaxKind } from 'ts-morph';
-import { describe, expect, it } from 'vitest';
-import { listAgentWriteSpineFiles } from './agent-write-spine-files.test-helper.ts';
-
-const here = dirname(fileURLToPath(import.meta.url));
-
-const FULL_BODY_OVERWRITE = new Set(['replace', 'patch']);
-
-function newProject(): Project {
- return new Project({
- skipFileDependencyResolution: true,
- skipLoadingLibFiles: true,
- skipAddingFilesFromTsConfig: true,
- compilerOptions: { noLib: true, allowJs: false },
- });
-}
-
-function calleeName(call: Node): string | null {
- if (!Node.isCallExpression(call)) return null;
- const expr = call.getExpression();
- if (Node.isIdentifier(expr)) return expr.getText();
- if (Node.isPropertyAccessExpression(expr)) return expr.getName();
- return null;
-}
-
-function positionLiterals(call: Node): Set {
- const out = new Set();
- if (!Node.isCallExpression(call)) return out;
- const arg = call.getArguments()[2];
- if (!arg) return out;
- for (const lit of [
- ...(Node.isStringLiteral(arg) ? [arg] : []),
- ...arg.getDescendantsOfKind(SyntaxKind.StringLiteral),
- ]) {
- out.add(lit.getLiteralText());
- }
- return out;
-}
-
-function isFunctionLike(n: Node): boolean {
- return (
- Node.isFunctionDeclaration(n) ||
- Node.isFunctionExpression(n) ||
- Node.isArrowFunction(n) ||
- Node.isMethodDeclaration(n)
- );
-}
-
-function handlerScope(call: Node): Node | undefined {
- for (const anc of call.getAncestors()) {
- if (!isFunctionLike(anc)) continue;
- const parent = anc.getParent();
- if (parent && Node.isCallExpression(parent) && calleeName(parent) === 'transact') continue;
- return anc;
- }
- return undefined;
-}
-
-describe('agent-write pre-drain coverage', () => {
- it('every pre-drainable applyAgentMarkdownWrite spine call is preceded by agentWritePreDrain', () => {
- const project = newProject();
- const spineFiles = listAgentWriteSpineFiles(here);
- expect(spineFiles.length).toBeGreaterThan(0);
- const spineCalls = spineFiles.flatMap((path) =>
- project
- .addSourceFileAtPath(join(here, path))
- .getDescendantsOfKind(SyntaxKind.CallExpression)
- .filter((c) => calleeName(c) === 'applyAgentMarkdownWrite'),
- );
- expect(spineCalls.length).toBeGreaterThanOrEqual(6);
-
- const preDrainable = spineCalls.filter((call) => {
- const positions = positionLiterals(call);
- return positions.size === 0 || [...positions].some((p) => !FULL_BODY_OVERWRITE.has(p));
- });
- expect(preDrainable.length).toBeGreaterThanOrEqual(3);
-
- const missing = preDrainable
- .filter((call) => {
- const scope = handlerScope(call);
- if (!scope) return true;
- return !scope
- .getDescendantsOfKind(SyntaxKind.CallExpression)
- .some((c) => calleeName(c) === 'agentWritePreDrain' && c.getStart() < call.getStart());
- })
- .map((c) => `${basename(c.getSourceFile().getFilePath())}:${c.getStartLineNumber()}`);
- expect(missing).toEqual([]);
- });
-
- it('does not exempt a site that writes at a pre-drainable position (planted positive)', () => {
- const project = newProject();
- const sf = project.createSourceFile(
- 'planted-append-without-pre-drain.ts',
- `declare function applyAgentMarkdownWrite(...a: unknown[]): void;
- function h(doc: unknown) {
- applyAgentMarkdownWrite(doc, 'x', 'append');
- }`,
- );
- const call = sf
- .getDescendantsOfKind(SyntaxKind.CallExpression)
- .find((c) => calleeName(c) === 'applyAgentMarkdownWrite');
- expect(call).toBeDefined();
- const positions = call ? positionLiterals(call) : new Set();
- expect([...positions]).toEqual(['append']);
- expect([...positions].some((p) => !FULL_BODY_OVERWRITE.has(p))).toBe(true);
- });
-
- it('exempts a site that only ever writes a full-body overwrite (negative control)', () => {
- const project = newProject();
- const sf = project.createSourceFile(
- 'planted-patch-only.ts',
- `declare function applyAgentMarkdownWrite(...a: unknown[]): void;
- function h(doc: unknown) {
- applyAgentMarkdownWrite(doc, 'x', 'patch');
- }`,
- );
- const call = sf
- .getDescendantsOfKind(SyntaxKind.CallExpression)
- .find((c) => calleeName(c) === 'applyAgentMarkdownWrite');
- expect(call).toBeDefined();
- const positions = call ? positionLiterals(call) : new Set();
- expect([...positions].some((p) => !FULL_BODY_OVERWRITE.has(p))).toBe(false);
- });
-
- it('refuses to exempt an unanalysable position (fail-closed)', () => {
- const project = newProject();
- const sf = project.createSourceFile(
- 'planted-dynamic-position.ts',
- `declare function applyAgentMarkdownWrite(...a: unknown[]): void;
- function h(doc: unknown, pos: string) {
- applyAgentMarkdownWrite(doc, 'x', pos);
- }`,
- );
- const call = sf
- .getDescendantsOfKind(SyntaxKind.CallExpression)
- .find((c) => calleeName(c) === 'applyAgentMarkdownWrite');
- expect(call).toBeDefined();
- expect(call ? positionLiterals(call).size : -1).toBe(0);
- });
-});
diff --git a/packages/server/src/agent-write-spine-walk-root-coverage-contract.test.ts b/packages/server/src/agent-write-spine-walk-root-coverage-contract.test.ts
index 9ae9c9540..3ed6bb50d 100644
--- a/packages/server/src/agent-write-spine-walk-root-coverage-contract.test.ts
+++ b/packages/server/src/agent-write-spine-walk-root-coverage-contract.test.ts
@@ -98,7 +98,10 @@ describe('agent-write spine walk root coverage contract', () => {
it('holds every spine census to that one walk root', () => {
const censusFiles = listCensusFiles();
- expect(censusFiles.length).toBeGreaterThanOrEqual(2);
+ expect(
+ censusFiles.length,
+ 'no spine census files discovered next to this contract',
+ ).toBeGreaterThanOrEqual(1);
const offRoot = censusFiles
.flatMap((file) => spineWalkRootArguments(file).map((arg) => `${file}: ${arg}`))
.filter((entry) => !entry.endsWith(': here'));
diff --git a/packages/server/src/api-extension.ts b/packages/server/src/api-extension.ts
index 47648f52c..7a4d995c4 100644
--- a/packages/server/src/api-extension.ts
+++ b/packages/server/src/api-extension.ts
@@ -80,11 +80,8 @@ import {
AgentSessionCapacityError,
type AgentSessionManager,
type AgentWriteContentDivergence,
- agentWriteLossDetect,
- agentWritePreDrain,
applyAgentMarkdownWrite,
iconFromClientName,
- prepareAgentMarkdownParse,
snapshotBlocks,
} from './agent-sessions.ts';
import {
@@ -190,7 +187,6 @@ import {
ManagedRenameSourceTypeMismatchError,
} from './apply-managed-rename.ts';
import { composeAndWriteRawBody } from './bridge-intake.ts';
-import type { BridgeDeriveLossReporter } from './bridge-loss-detector.ts';
import { isConfigDoc, isLinkIndexExcludedDoc, isSystemDoc } from './cc1-broadcast.ts';
import {
isReservedProjectStatePath,
@@ -327,7 +323,6 @@ import {
getOrLoadRenameLogIndex,
type RenameLogEntry,
} from './rename-log.ts';
-import type { PairedWriteOrigin } from './server-observers.ts';
import { createAssetService } from './services/assets.ts';
import { createFileOpsService, DuplicateNameExhaustedError } from './services/file-ops.ts';
import { createSearchService } from './services/search.ts';
@@ -342,6 +337,7 @@ import { readSkillInstallModeRaw } from './skill-placements.ts';
import type { SyncEngine } from './sync-engine.ts';
import { getMeter, withSpan, withSpanSync } from './telemetry.ts';
import { computeWriteAdvisoryLinks } from './write-advisory-links.ts';
+import type { PairedWriteOrigin } from './write-origins.ts';
let _renameAttributionCounter: ReturnType['createCounter']> | null =
null;
@@ -399,8 +395,7 @@ export function __resetRenameTelemetryForTesting(): void {
}
/**
- * Exported so the bridge-invariant watcher can enforce by identity (precedent #1) and so server
- * observers can resolve `context.paired` without importing the object transitively.
+ * A typed `PairedWriteOrigin`, compared by identity (precedent #1).
*/
export const MANAGED_RENAME_ORIGIN = {
source: 'local' as const,
@@ -1287,7 +1282,6 @@ export interface ApiExtensionOptions {
getLinkPreviewsEnabled?: () => boolean;
getConfigDiagnostics?: () => ConfigDiagnosticsReport;
resolveEmbed?: (basename: string, sourcePath: string) => string | null;
- getBridgeLossReporter?: () => BridgeDeriveLossReporter | undefined;
getPrincipal?: () => Principal | null;
homeDirOverride?: string;
agentIntegrations?: AgentRegistryHostSeam;
@@ -1457,7 +1451,6 @@ export function createApiExtension(
localOpCliArgs = ['open-knowledge'],
authStreamHeartbeatMs,
projectDir,
- getBridgeLossReporter,
getPrincipal,
homeDirOverride,
agentIntegrations,
@@ -2249,7 +2242,7 @@ export function createApiExtension(
if (result.rewrites === 0) {
return;
}
- composeAndWriteRawBody(document, result.markdown, 'managed-rename', false);
+ composeAndWriteRawBody(document, result.markdown, 'managed-rename');
}, MANAGED_RENAME_ORIGIN);
return result;
}
@@ -2304,7 +2297,7 @@ export function createApiExtension(
if (result.rewrites === 0) {
return;
}
- composeAndWriteRawBody(document, result.markdown, 'managed-rename', false);
+ composeAndWriteRawBody(document, result.markdown, 'managed-rename');
}, MANAGED_RENAME_ORIGIN);
return result;
}
@@ -2565,8 +2558,6 @@ export function createApiExtension(
hocuspocus,
sourceDocName,
contentDir,
- undefined,
- getBridgeLossReporter?.(),
conflicts,
);
if (recentlyRemovedDocs && !isSystemDoc(sourceDocName) && !isConfigDoc(sourceDocName)) {
@@ -2768,8 +2759,6 @@ export function createApiExtension(
hocuspocus,
docName,
contentDir,
- undefined,
- getBridgeLossReporter?.(),
conflicts,
);
const content = readCurrentDocumentContent(docName);
@@ -3309,8 +3298,6 @@ export function createApiExtension(
hocuspocus,
docName,
contentDir,
- options.resolveEmbed,
- getBridgeLossReporter?.(),
conflicts,
);
@@ -3339,18 +3326,12 @@ export function createApiExtension(
colorSeed,
clientName,
);
- agentWritePreDrain(session.dc.document, `${content}\n`, 'append');
session.dc.document.transact(() => {
const beforeBlocks = snapshotBlocks(session.dc.document);
applyAgentMarkdownWrite(
session.dc.document,
`${content}\n`,
'append',
- options.resolveEmbed
- ? { resolveEmbed: options.resolveEmbed, sourcePath: docName }
- : undefined,
- undefined,
- agentWriteLossDetect(session),
suppliedWriterId,
);
@@ -3601,21 +3582,9 @@ export function createApiExtension(
hocuspocus,
resolvedDocName,
contentDir,
- options.resolveEmbed,
- getBridgeLossReporter?.(),
conflicts,
);
- const entryEmbedResolver = options.resolveEmbed
- ? { resolveEmbed: options.resolveEmbed, sourcePath: resolvedDocName }
- : undefined;
- const entryPrecomputed = await prepareAgentMarkdownParse(
- session.dc.document,
- entry.markdown,
- entry.position ?? 'append',
- entryEmbedResolver,
- );
-
let writeDivergence: AgentWriteContentDivergence | undefined;
const disposeEntryEffectCapture = captureEffect(
session.dc.document.getText('source'),
@@ -3624,7 +3593,6 @@ export function createApiExtension(
colorSeed,
clientName,
);
- agentWritePreDrain(session.dc.document, entry.markdown, entry.position ?? 'append');
try {
session.dc.document.transact(() => {
const beforeBlocks = snapshotBlocks(session.dc.document);
@@ -3632,9 +3600,6 @@ export function createApiExtension(
session.dc.document,
entry.markdown,
entry.position ?? 'append',
- entryEmbedResolver,
- entryPrecomputed,
- agentWriteLossDetect(session),
suppliedWriterId,
);
@@ -4781,7 +4746,6 @@ export function createApiExtension(
resolveDocFilePath,
summaryResponseFields,
sessionManager,
- options,
agentPresenceBroadcaster,
buildAgentActor,
flushDiskAndDetectOutcome,
@@ -5133,8 +5097,6 @@ export function createApiExtension(
sessionManager,
durabilityState,
hocuspocus,
- options,
- getBridgeLossReporter,
agentPresenceBroadcaster,
recordContentDivergenceGate,
buildAgentActor,
diff --git a/packages/server/src/api-rollback-actor-identity.test.ts b/packages/server/src/api-rollback-actor-identity.test.ts
index c7767a2fb..b847bd23a 100644
--- a/packages/server/src/api-rollback-actor-identity.test.ts
+++ b/packages/server/src/api-rollback-actor-identity.test.ts
@@ -99,10 +99,6 @@ async function setupRollback(tmpDir: string): Promise {
writeFileSync(resolve(contentDir, `${docName}.md`), newContent);
const yDoc = new Y.Doc();
- const xmlFragment = yDoc.getXmlFragment('default');
- const para = new Y.XmlElement('paragraph');
- para.insert(0, [new Y.XmlText('Version 2 content (modified)')]);
- xmlFragment.insert(0, [para]);
yDoc.getText('source').insert(0, newContent);
const shadowRef: ShadowRef = { current: shadow };
diff --git a/packages/server/src/api-rollback-rename-history.test.ts b/packages/server/src/api-rollback-rename-history.test.ts
index c4666043a..88e3d5586 100644
--- a/packages/server/src/api-rollback-rename-history.test.ts
+++ b/packages/server/src/api-rollback-rename-history.test.ts
@@ -133,7 +133,6 @@ describe('handleRollback — rename history mitigation (US-005)', () => {
const docName = 'b';
const yDoc = new Y.Doc();
- yDoc.getXmlFragment('default');
yDoc.getText('source').insert(0, '# B post-rename\n');
const shadowRef: ShadowRef = { current: shadow };
diff --git a/packages/server/src/bridge-intake.test.ts b/packages/server/src/bridge-intake.test.ts
index eea247be6..25902020e 100644
--- a/packages/server/src/bridge-intake.test.ts
+++ b/packages/server/src/bridge-intake.test.ts
@@ -1,20 +1,14 @@
/**
- * Unit tests for the three sibling write-side primitives in `bridge-intake.ts` — the shared
+ * Unit tests for the two sibling write-side primitives in `bridge-intake.ts` — the shared
* substrate of the Y.Text-is-truth contract (precedent #38).
*/
-import { normalizeBridge, stripFrontmatter } from '@inkeep/open-knowledge-core';
-import { yXmlFragmentToProseMirrorRootNode } from '@tiptap/y-tiptap';
+import { stripFrontmatter } from '@inkeep/open-knowledge-core';
import { beforeEach, describe, expect, test } from 'vitest';
import * as Y from 'yjs';
import { ROLLBACK_ORIGIN } from './api-extension.ts';
-import {
- composeAndWriteRawBody,
- deriveFragmentFromYtext,
- replaceRawBody,
-} from './bridge-intake.ts';
+import { composeAndWriteRawBody, replaceRawBody } from './bridge-intake.ts';
import { FILE_WATCHER_ORIGIN } from './external-change.ts';
-import { mdManager, schema } from './md-manager.ts';
describe('composeAndWriteRawBody — primitive contract', () => {
let doc: Y.Doc;
@@ -92,45 +86,6 @@ describe('composeAndWriteRawBody — primitive contract', () => {
expect(doc.getText('source').toString()).toBe(content);
});
- test('XmlFragment derives from parse(body) — fragment matches structural form', () => {
- doc.transact(() => {
- composeAndWriteRawBody(doc, '# Heading\n\nbody\n', 'agent');
- }, FILE_WATCHER_ORIGIN);
-
- const xmlFragment = doc.getXmlFragment('default');
- expect(xmlFragment.length).toBeGreaterThan(0);
- expect(xmlFragment.length).toBe(2);
- });
-
- test('XmlFragment does NOT contain frontmatter content', () => {
- const content = '---\ntitle: Test\n---\n# Heading\n';
- doc.transact(() => {
- composeAndWriteRawBody(doc, content, 'agent');
- }, FILE_WATCHER_ORIGIN);
-
- const xmlFragment = doc.getXmlFragment('default');
- const xmlString = xmlFragment.toString();
- expect(xmlString).not.toContain('title: Test');
- expect(xmlString).not.toContain('---');
- });
-
- test('bridge invariant holds: normalizeBridge(ytext) === normalizeBridge(serialize(fragment) + fm)', () => {
- const content = '---\ntitle: Test\n---\n# Heading\n\nbody\n';
- doc.transact(() => {
- composeAndWriteRawBody(doc, content, 'agent');
- }, FILE_WATCHER_ORIGIN);
-
- const ytext = doc.getText('source').toString();
- const xmlFragment = doc.getXmlFragment('default');
- const fragmentBody = mdManager.serialize(
- yXmlFragmentToProseMirrorRootNode(xmlFragment, schema).toJSON(),
- );
- const { frontmatter } = stripFrontmatter(ytext);
- const fragmentFull = `${frontmatter}${fragmentBody}`;
-
- expect(normalizeBridge(ytext)).toBe(normalizeBridge(fragmentFull));
- });
-
test('idempotent — second call with same content does not mutate Y.Text', () => {
const content = '# Heading\n\nbody\n';
doc.transact(() => {
@@ -176,32 +131,11 @@ describe('composeAndWriteRawBody — primitive contract', () => {
expect(tx).toBe(1);
});
- test('Y.Text is mutated before XmlFragment (write-order contract per FR-30)', () => {
- const events: string[] = [];
- const xmlFragment = doc.getXmlFragment('default');
- const ytext = doc.getText('source');
- xmlFragment.observeDeep(() => events.push('xml'));
- ytext.observe(() => events.push('ytext'));
-
- doc.transact(() => {
- composeAndWriteRawBody(doc, '# Test\n', 'agent');
- }, FILE_WATCHER_ORIGIN);
-
- expect(events.length).toBeGreaterThanOrEqual(2);
- expect(events.indexOf('ytext')).toBeLessThan(events.indexOf('xml'));
- });
-
- test('writes XmlFragment + Y.Text atomically inside one caller-wrap transact', () => {
- let xmlObserved = false;
+ test('writes Y.Text inside the caller-wrap transact, under the caller origin', () => {
let textObserved = false;
let observedTxOrigin: unknown;
- const xmlFragment = doc.getXmlFragment('default');
const ytext = doc.getText('source');
- xmlFragment.observeDeep((_events, transaction) => {
- xmlObserved = true;
- observedTxOrigin = transaction.origin;
- });
ytext.observe((_event, transaction) => {
textObserved = true;
observedTxOrigin = transaction.origin;
@@ -211,7 +145,6 @@ describe('composeAndWriteRawBody — primitive contract', () => {
composeAndWriteRawBody(doc, '# Test\n', 'agent');
}, FILE_WATCHER_ORIGIN);
- expect(xmlObserved).toBe(true);
expect(textObserved).toBe(true);
expect(observedTxOrigin).toBe(FILE_WATCHER_ORIGIN);
});
@@ -234,26 +167,6 @@ describe('composeAndWriteRawBody — primitive contract', () => {
expect(doc.getText('source').toString()).toBe('');
});
-
- test('embedResolver context is threaded through to mdManager.parseWithFallback', () => {
- let calledWithBasename = '';
- let calledWithSourcePath = '';
- const embedResolver = {
- resolveEmbed: (basename: string, sourcePath: string): string | null => {
- calledWithBasename = basename;
- calledWithSourcePath = sourcePath;
- return `/resolved/${basename}`;
- },
- sourcePath: 'docs/feature.md',
- };
-
- doc.transact(() => {
- composeAndWriteRawBody(doc, '![[photo.png]]\n', 'file-watcher', embedResolver);
- }, FILE_WATCHER_ORIGIN);
-
- expect(calledWithBasename).toBe('photo.png');
- expect(calledWithSourcePath).toBe('docs/feature.md');
- });
});
describe('replaceRawBody — primitive contract', () => {
@@ -297,32 +210,6 @@ describe('replaceRawBody — primitive contract', () => {
expect(doc.getText('source').toString()).toBe(content);
});
- test('XmlFragment derives from parse(body) — fragment matches structural form', () => {
- doc.transact(() => {
- replaceRawBody(doc, '# Heading\n\nbody paragraph\n');
- }, ROLLBACK_ORIGIN);
-
- const xmlFragment = doc.getXmlFragment('default');
- const pmRoot = yXmlFragmentToProseMirrorRootNode(xmlFragment, schema);
- expect(pmRoot.firstChild?.type.name).toBe('heading');
- expect(pmRoot.lastChild?.type.name).toBe('paragraph');
- });
-
- test('bridge invariant holds: normalizeBridge(ytext) === normalizeBridge(serialize(fragment) + fm)', () => {
- const content = '---\ntitle: t\n---\n\n# H\n\nbody\n';
- doc.transact(() => {
- replaceRawBody(doc, content);
- }, ROLLBACK_ORIGIN);
-
- const ytext = doc.getText('source').toString();
- const xmlFragment = doc.getXmlFragment('default');
- const pmRoot = yXmlFragmentToProseMirrorRootNode(xmlFragment, schema);
- const serialized = mdManager.serialize(pmRoot.toJSON());
- const { frontmatter } = stripFrontmatter(content);
- const reconstituted = `${frontmatter}\n\n${serialized}`;
- expect(normalizeBridge(ytext)).toBe(normalizeBridge(reconstituted));
- });
-
test('does not call doc.transact() — caller-wrap is mandatory for atomicity', () => {
let tx = 0;
doc.on('beforeTransaction', () => {
@@ -336,32 +223,11 @@ describe('replaceRawBody — primitive contract', () => {
expect(tx).toBe(1);
});
- test('Y.Text is mutated before XmlFragment (write-order contract per FR-30 D4)', () => {
- const events: string[] = [];
- const xmlFragment = doc.getXmlFragment('default');
- const ytext = doc.getText('source');
- xmlFragment.observeDeep(() => events.push('xml'));
- ytext.observe(() => events.push('ytext'));
-
- doc.transact(() => {
- replaceRawBody(doc, '# Test\n');
- }, ROLLBACK_ORIGIN);
-
- expect(events.length).toBeGreaterThanOrEqual(2);
- expect(events.indexOf('ytext')).toBeLessThan(events.indexOf('xml'));
- });
-
- test('writes XmlFragment + Y.Text atomically inside one caller-wrap transact under ROLLBACK_ORIGIN', () => {
- let xmlObserved = false;
+ test('writes Y.Text inside the caller-wrap transact, under ROLLBACK_ORIGIN', () => {
let textObserved = false;
let observedTxOrigin: unknown;
- const xmlFragment = doc.getXmlFragment('default');
const ytext = doc.getText('source');
- xmlFragment.observeDeep((_events, transaction) => {
- xmlObserved = true;
- observedTxOrigin = transaction.origin;
- });
ytext.observe((_event, transaction) => {
textObserved = true;
observedTxOrigin = transaction.origin;
@@ -371,7 +237,6 @@ describe('replaceRawBody — primitive contract', () => {
replaceRawBody(doc, '# Test\n');
}, ROLLBACK_ORIGIN);
- expect(xmlObserved).toBe(true);
expect(textObserved).toBe(true);
expect(observedTxOrigin).toBe(ROLLBACK_ORIGIN);
});
@@ -435,44 +300,3 @@ describe('replaceRawBody — primitive contract', () => {
expect(doc.getText('source').toString()).toBe('');
});
});
-
-describe('deriveFragmentFromYtext — primitive contract', () => {
- let doc: Y.Doc;
-
- beforeEach(() => {
- doc = new Y.Doc();
- });
-
- test('writes ZERO bytes to Y.Text — distinguishing-feature pin', () => {
- doc.transact(() => {
- composeAndWriteRawBody(doc, '# Heading\n\nbody\n', 'file-watcher');
- }, FILE_WATCHER_ORIGIN);
-
- let textMutations = 0;
- const observer = (): void => {
- textMutations++;
- };
- const ytext = doc.getText('source');
- ytext.observe(observer);
-
- doc.transact(() => {
- deriveFragmentFromYtext(doc);
- }, FILE_WATCHER_ORIGIN);
-
- ytext.unobserve(observer);
- expect(textMutations).toBe(0);
- });
-
- test('preserves Y.Text bytes verbatim across the call', () => {
- const seed = '# Heading\n\nbody\n';
- doc.transact(() => {
- composeAndWriteRawBody(doc, seed, 'file-watcher');
- }, FILE_WATCHER_ORIGIN);
-
- doc.transact(() => {
- deriveFragmentFromYtext(doc);
- }, FILE_WATCHER_ORIGIN);
-
- expect(doc.getText('source').toString()).toBe(seed);
- });
-});
diff --git a/packages/server/src/bridge-intake.ts b/packages/server/src/bridge-intake.ts
index 208dc38da..38bd81cf3 100644
--- a/packages/server/src/bridge-intake.ts
+++ b/packages/server/src/bridge-intake.ts
@@ -1,96 +1,12 @@
/**
- * The three sibling write-side primitives for the Y.Text-is-truth contract (precedent #38):
- * `composeAndWriteRawBody`, `replaceRawBody` and `deriveFragmentFromYtext`, each owning one
- * paired-write semantics. No primitive calls `doc.transact()`; the caller wraps.
+ * The two sibling write-side primitives for the Y.Text-is-truth contract (precedent #38):
+ * `composeAndWriteRawBody` and `replaceRawBody`, each owning one paired-write semantics. No
+ * primitive calls `doc.transact()`; the caller wraps.
*/
-import {
- applyFastDiff,
- composeWithDerivedBody,
- stripFrontmatter,
-} from '@inkeep/open-knowledge-core';
-import type { JSONContent } from '@tiptap/core';
-import { updateYFragment, yXmlFragmentToProseMirrorRootNode } from '@tiptap/y-tiptap';
+import { applyFastDiff } from '@inkeep/open-knowledge-core';
import type * as Y from 'yjs';
-import type { DeriveLossDetectOptions } from './bridge-loss-detector.ts';
-import { mdManager, schema } from './md-manager.ts';
import { withSpanSync } from './telemetry.ts';
-interface EmbedResolverContext {
- resolveEmbed: (basename: string, sourcePath: string) => string | null;
- resolveSize?: (basename: string, sourcePath: string) => number | null;
- sourcePath: string;
-}
-
-type EmbedResolverArg = EmbedResolverContext | false | undefined;
-
-export interface PrecomputedParse {
- rawContent: string;
- parsedJson: JSONContent;
-}
-
-function parseBodyWithPrecompute(
- document: Y.Doc,
- rawContent: string,
- embedResolver: EmbedResolverArg,
- precomputed: PrecomputedParse | undefined,
-): JSONContent {
- const { body } = stripFrontmatter(rawContent);
- if (precomputed !== undefined && precomputed.rawContent === rawContent) {
- return precomputed.parsedJson;
- }
- return withSpanSync(
- 'md.parseWithFallback',
- { attributes: { 'body.bytes': body.length, 'doc.name': document.guid } },
- () => mdManager.parseWithFallback(body, buildParseOpts(embedResolver)),
- );
-}
-
-function buildParseOpts(embedResolver: EmbedResolverArg):
- | {
- resolveEmbed: EmbedResolverContext['resolveEmbed'];
- resolveSize?: EmbedResolverContext['resolveSize'];
- sourcePath: string;
- }
- | undefined {
- return embedResolver
- ? {
- resolveEmbed: embedResolver.resolveEmbed,
- resolveSize: embedResolver.resolveSize,
- sourcePath: embedResolver.sourcePath,
- }
- : undefined;
-}
-
-function serializeFragmentBody(xmlFragment: Y.XmlFragment): string {
- return mdManager.serialize(yXmlFragmentToProseMirrorRootNode(xmlFragment, schema).toJSON());
-}
-
-function reportPairedDeriveLoss(
- detect: DeriveLossDetectOptions,
- pendingBody: string,
- parsedJson: JSONContent,
- xmlFragment: Y.XmlFragment,
- restoreFrontmatter: string,
- parseOpts: ReturnType,
-): void {
- const rebuiltBody = serializeFragmentBody(xmlFragment);
- const ytextDerivedBody = mdManager.serialize(parsedJson);
- const { body: baselineRawBody } = stripFrontmatter(detect.baselineFullMd);
- const baselineBody = mdManager.serialize(mdManager.parseWithFallback(baselineRawBody, parseOpts));
- detect.report({
- pendingBody,
- baselineBody,
- ytextDerivedBody,
- rebuiltBody,
- restorePayload: composeWithDerivedBody(restoreFrontmatter, pendingBody).md,
- });
-}
-
-/**
- * Applies raw composed bytes to Y.Text via an incremental line-aligned diff and derives
- * XmlFragment via parse. Must run inside an outer `doc.transact(..., origin)` block for atomicity
- * and the per-session frozen origin identity (precedent #24).
- */
export type ComposeWriteSurface =
| 'agent'
| 'file-watcher'
@@ -98,13 +14,20 @@ export type ComposeWriteSurface =
| 'undo'
| 'frontmatter';
+/**
+ * Apply raw composed bytes to Y.Text via an incremental line-aligned diff.
+ *
+ * MUST be called inside an outer `doc.transact(..., origin)` block
+ * established by the caller (atomicity + per-session frozen origin object
+ * identity per precedent #24).
+ *
+ * @param document Y.Doc holding the doc's `source` Y.Text.
+ * @param rawContent Full document bytes (frontmatter + body) to write to Y.Text verbatim.
+ */
export function composeAndWriteRawBody(
document: Y.Doc,
rawContent: string,
surface: ComposeWriteSurface,
- embedResolver?: EmbedResolverArg,
- precomputed?: PrecomputedParse,
- detect?: DeriveLossDetectOptions,
): void {
withSpanSync(
'bridge.composeAndWriteRawBody',
@@ -116,44 +39,25 @@ export function composeAndWriteRawBody(
},
},
() => {
- const xmlFragment = document.getXmlFragment('default');
const ytext = document.getText('source');
const currentYText = ytext.toString();
-
- const parsedJson = parseBodyWithPrecompute(document, rawContent, embedResolver, precomputed);
- const pmNode = schema.nodeFromJSON(parsedJson);
-
- const pendingBody = detect ? serializeFragmentBody(xmlFragment) : undefined;
-
if (currentYText !== rawContent) {
applyFastDiff(ytext, currentYText, rawContent);
}
-
- const meta = { mapping: new Map(), isOMark: new Map() };
- updateYFragment(document, xmlFragment, pmNode, meta);
-
- if (detect && pendingBody !== undefined) {
- const { frontmatter: restoreFrontmatter } = stripFrontmatter(detect.baselineFullMd);
- reportPairedDeriveLoss(
- detect,
- pendingBody,
- parsedJson,
- xmlFragment,
- restoreFrontmatter,
- buildParseOpts(embedResolver),
- );
- }
},
);
}
-export function replaceRawBody(
- document: Y.Doc,
- rawContent: string,
- embedResolver?: EmbedResolverArg,
- precomputed?: PrecomputedParse,
- detect?: DeriveLossDetectOptions,
-): void {
+/**
+ * Replace Y.Text wholesale — the rollback semantics.
+ *
+ * MUST be called inside an outer `doc.transact(..., origin)` block
+ * established by the caller (precedent #24).
+ *
+ * @param document Y.Doc holding the doc's `source` Y.Text.
+ * @param rawContent Full document bytes (frontmatter + body) to write to Y.Text verbatim.
+ */
+export function replaceRawBody(document: Y.Doc, rawContent: string): void {
withSpanSync(
'bridge.replaceRawBody',
{
@@ -163,78 +67,12 @@ export function replaceRawBody(
},
},
() => {
- const xmlFragment = document.getXmlFragment('default');
const ytext = document.getText('source');
-
- const parsedJson = parseBodyWithPrecompute(document, rawContent, embedResolver, precomputed);
- const pmNode = schema.nodeFromJSON(parsedJson);
-
- const pendingBody = detect ? serializeFragmentBody(xmlFragment) : undefined;
-
const currentText = ytext.toString();
if (currentText !== rawContent) {
ytext.delete(0, currentText.length);
ytext.insert(0, rawContent);
}
-
- const meta = { mapping: new Map(), isOMark: new Map() };
- updateYFragment(document, xmlFragment, pmNode, meta);
-
- if (detect && pendingBody !== undefined) {
- const { frontmatter: restoreFrontmatter } = stripFrontmatter(detect.baselineFullMd);
- reportPairedDeriveLoss(
- detect,
- pendingBody,
- parsedJson,
- xmlFragment,
- restoreFrontmatter,
- buildParseOpts(embedResolver),
- );
- }
},
);
}
-
-/**
- * Pre-state contract: `Y.UndoManager.undo()` has already mutated ytext to the post-undo bytes
- * (those bytes ARE the user's intended post-undo source form per Y.Text-is-truth, precedent #38).
- */
-export function deriveFragmentFromYtext(
- document: Y.Doc,
- embedResolver?: EmbedResolverArg,
- detect?: DeriveLossDetectOptions,
-): void {
- const xmlFragment = document.getXmlFragment('default');
- const ytext = document.getText('source');
-
- const fullMd = ytext.toString();
- const { frontmatter, body } = stripFrontmatter(fullMd);
- const parseOpts = buildParseOpts(embedResolver);
- const parsedJson = mdManager.parseWithFallback(body, parseOpts);
- const pmNode = schema.nodeFromJSON(parsedJson);
-
- const pendingBody = detect
- ? mdManager.serialize(yXmlFragmentToProseMirrorRootNode(xmlFragment, schema).toJSON())
- : undefined;
-
- const meta = { mapping: new Map(), isOMark: new Map() };
- updateYFragment(document, xmlFragment, pmNode, meta);
-
- if (detect && pendingBody !== undefined) {
- const rebuiltBody = mdManager.serialize(
- yXmlFragmentToProseMirrorRootNode(xmlFragment, schema).toJSON(),
- );
- const ytextDerivedBody = mdManager.serialize(parsedJson as JSONContent);
- const { body: baselineRawBody } = stripFrontmatter(detect.baselineFullMd);
- const baselineBody = mdManager.serialize(
- mdManager.parseWithFallback(baselineRawBody, parseOpts),
- );
- detect.report({
- pendingBody,
- baselineBody,
- ytextDerivedBody,
- rebuiltBody,
- restorePayload: composeWithDerivedBody(frontmatter, pendingBody).md,
- });
- }
-}
diff --git a/packages/server/src/bridge-loss-detector.test.ts b/packages/server/src/bridge-loss-detector.test.ts
index 4fd59b129..cce6179a5 100644
--- a/packages/server/src/bridge-loss-detector.test.ts
+++ b/packages/server/src/bridge-loss-detector.test.ts
@@ -1,74 +1,11 @@
-import { readFileSync } from 'node:fs';
-import { mkdtemp, rm } from 'node:fs/promises';
-import { tmpdir } from 'node:os';
-import { resolve } from 'node:path';
+import { normalizeBridge } from '@inkeep/open-knowledge-core';
+import { describe, expect, it } from 'vitest';
import {
- normalizeBridge,
- pendingContentLines,
- stripFrontmatter,
-} from '@inkeep/open-knowledge-core';
-import { updateYFragment } from '@tiptap/y-tiptap';
-import { afterEach, beforeEach, describe, expect, it } from 'vitest';
-import * as Y from 'yjs';
-import {
- composeAndWriteRawBody,
- deriveFragmentFromYtext,
- replaceRawBody,
-} from './bridge-intake.ts';
-import {
- createBridgeDeriveLossReporter,
- DERIVE_LOSS_SITE_AGENT_UNDO,
- DERIVE_LOSS_SITE_AGENT_WRITE_INTAKE,
- DERIVE_LOSS_SITE_FILE_WATCHER_INTAKE,
type DeriveLossObservation,
detectApplyArmDrop,
detectDeriveLoss,
detectPairedIntakeLoss,
} from './bridge-loss-detector.ts';
-import { DocumentDurabilityState } from './document-durability-state.ts';
-import { applyExternalChange } from './external-change.ts';
-import { LossCaptureRing, lossCaptureCurrentPath, parseLossCaptureLines } from './loss-capture.ts';
-import { mdManager, schema } from './md-manager.ts';
-import { setupServerObservers } from './server-observers.ts';
-import { initShadowRepo, type ShadowHandle, shadowGit } from './shadow-repo.ts';
-import { getDocumentHistory } from './timeline-query.ts';
-
-type RingEvent = ReturnType[number];
-
-async function pollForEvent(
- projectDir: string,
- ring: LossCaptureRing,
- predicate: (e: RingEvent) => boolean,
- timeoutMs = 5000,
-): Promise {
- const start = Date.now();
- while (Date.now() - start < timeoutMs) {
- await ring.drain();
- try {
- const events = parseLossCaptureLines(
- readFileSync(lossCaptureCurrentPath(projectDir), 'utf-8'),
- );
- const found = events.find(predicate);
- if (found) return found;
- } catch {}
- await new Promise((r) => setTimeout(r, 20));
- }
- throw new Error('timed out waiting for loss-ring event');
-}
-
-function buildFragment(doc: Y.Doc, body: string): void {
- const xf = doc.getXmlFragment('default');
- const pm = schema.nodeFromJSON(mdManager.parseWithFallback(body, undefined));
- doc.transact(() => updateYFragment(doc, xf, pm, { mapping: new Map(), isOMark: new Map() }));
-}
-
-function seedDivergedDoc(syncedMd: string, pendingBody: string): Y.Doc {
- const doc = new Y.Doc();
- doc.getText('source').insert(0, syncedMd);
- buildFragment(doc, stripFrontmatter(syncedMd).body);
- buildFragment(doc, pendingBody);
- return doc;
-}
describe('detectDeriveLoss (the twin verdict)', () => {
it('flags a never-propagated fragment line that both twins lack', () => {
@@ -116,139 +53,6 @@ describe('detectDeriveLoss (the twin verdict)', () => {
});
});
-describe('deriveFragmentFromYtext observation', () => {
- it('reports the un-propagated fragment content the derive discards', () => {
- const doc = seedDivergedDoc(
- '# Title\n\nOriginal line',
- '# Title\n\nOriginal line\n\nPending keystroke',
- );
- let captured: DeriveLossObservation | undefined;
- const baselineFullMd = doc.getText('source').toString();
- doc.transact(() => {
- deriveFragmentFromYtext(doc, undefined, {
- report: (obs) => {
- captured = obs;
- },
- baselineFullMd,
- });
- });
- expect(captured).toBeDefined();
- const dropped = detectDeriveLoss(captured as DeriveLossObservation);
- expect(dropped).toContain('Pending keystroke');
- expect((captured as DeriveLossObservation).restorePayload).toContain('Pending keystroke');
- doc.destroy();
- });
-
- it('reports no loss for an ordinary in-sync derive', () => {
- const md = '# Title\n\nOnly line';
- const doc = new Y.Doc();
- doc.getText('source').insert(0, md);
- buildFragment(doc, stripFrontmatter(md).body);
- let captured: DeriveLossObservation | undefined;
- const baselineFullMd = doc.getText('source').toString();
- doc.transact(() => {
- deriveFragmentFromYtext(doc, undefined, {
- report: (obs) => {
- captured = obs;
- },
- baselineFullMd,
- });
- });
- expect(captured).toBeDefined();
- expect(detectDeriveLoss(captured as DeriveLossObservation)).toEqual([]);
- doc.destroy();
- });
-});
-
-describe('createBridgeDeriveLossReporter (real shadow + ring)', () => {
- let tmpDir: string;
-
- beforeEach(async () => {
- tmpDir = await mkdtemp(resolve(tmpdir(), 'ok-derive-loss-test-'));
- });
-
- afterEach(async () => {
- await rm(tmpDir, { recursive: true, force: true });
- });
-
- async function setupShadow(): Promise<{ projectRoot: string; shadow: ShadowHandle }> {
- const projectRoot = resolve(tmpDir, 'project');
- const shadow = await initShadowRepo(projectRoot);
- return { projectRoot, shadow };
- }
-
- it('writes a bridge-derive-loss checkpoint + detector-trip event whose sha resolves', async () => {
- const { projectRoot, shadow } = await setupShadow();
- const ring = new LossCaptureRing({ projectDir: projectRoot, maxBytes: 1_000_000 });
- const reporter = createBridgeDeriveLossReporter({
- shadow: () => shadow,
- ring,
- getBranch: () => 'main',
- contentRoot: '',
- });
-
- const doc = seedDivergedDoc('# Title\n\nOriginal', '# Title\n\nOriginal\n\nPending keystroke');
- const baselineFullMd = doc.getText('source').toString();
- doc.transact(() => {
- deriveFragmentFromYtext(doc, undefined, {
- report: (obs) => reporter('intro', obs, 'agent-1'),
- baselineFullMd,
- });
- });
- doc.destroy();
-
- const trip = await pollForEvent(
- projectRoot,
- ring,
- (e) => e.event === 'detector-trip' && Boolean(e.checkpointSha),
- );
- expect(trip).toBeDefined();
- expect(trip?.direction).toBe('b');
- expect(trip?.docName).toBe('intro');
- expect(typeof trip?.lostLen).toBe('number');
- expect(trip?.digest).toBeTruthy();
-
- const hist = await getDocumentHistory(shadow, { docName: 'intro' }, '');
- const row = hist.entries.find((e) => e.sha === trip?.checkpointSha);
- expect(row?.checkpoint?.kind).toBe('bridge-derive-loss');
- });
-
- it('writes nothing when the derive preserved all content', async () => {
- const { projectRoot, shadow } = await setupShadow();
- const ring = new LossCaptureRing({ projectDir: projectRoot, maxBytes: 1_000_000 });
- const reporter = createBridgeDeriveLossReporter({
- shadow: () => shadow,
- ring,
- getBranch: () => 'main',
- contentRoot: '',
- });
-
- const md = '# Title\n\nOnly line';
- const doc = new Y.Doc();
- doc.getText('source').insert(0, md);
- buildFragment(doc, stripFrontmatter(md).body);
- const baselineFullMd = doc.getText('source').toString();
- doc.transact(() => {
- deriveFragmentFromYtext(doc, undefined, {
- report: (obs) => reporter('intro', obs),
- baselineFullMd,
- });
- });
- doc.destroy();
-
- await new Promise((r) => setTimeout(r, 0));
- await ring.drain();
-
- let events: ReturnType = [];
- try {
- events = parseLossCaptureLines(readFileSync(lossCaptureCurrentPath(projectRoot), 'utf-8'));
- } catch {}
- expect(events.filter((e) => e.event === 'detector-trip')).toEqual([]);
- const hist = await getDocumentHistory(shadow, { docName: 'intro' }, '');
- expect(hist.entries.some((e) => e.checkpoint?.kind === 'bridge-derive-loss')).toBe(false);
- });
-});
-
describe('detectApplyArmDrop (Observer-A apply verdict)', () => {
it('flags a substantive line the applied Y.Text dropped', () => {
const md = '# Title\n\nLine one\n\nLine two\n\nLine three';
@@ -273,315 +77,6 @@ describe('detectApplyArmDrop (Observer-A apply verdict)', () => {
});
});
-describe('Observer-A apply post-condition (real drain)', () => {
- let tmpDir: string;
-
- beforeEach(async () => {
- tmpDir = await mkdtemp(resolve(tmpdir(), 'ok-apply-loss-test-'));
- });
-
- afterEach(async () => {
- await rm(tmpDir, { recursive: true, force: true });
- });
-
- function makeInjector(target: string): (yt: Y.Text) => void {
- let fired = false;
- return (yt) => {
- if (fired) return;
- const idx = yt.toString().indexOf(target);
- if (idx >= 0) {
- yt.delete(idx, target.length);
- fired = true;
- }
- };
- }
-
- it('checkpoints + emits a detector-trip when an apply arm drops content', async () => {
- const projectRoot = resolve(tmpDir, 'project');
- const shadow = await initShadowRepo(projectRoot);
- const ring = new LossCaptureRing({ projectDir: projectRoot, maxBytes: 1_000_000 });
-
- const doc = new Y.Doc();
- const ytext = doc.getText('source');
- const xf = doc.getXmlFragment('default');
- ytext.insert(0, '# Title\n\nLine one\n\nLine two');
- buildFragment(doc, '# Title\n\nLine one\n\nLine two');
-
- const cleanup = setupServerObservers({
- doc,
- xmlFragment: xf,
- ytext,
- mdManager,
- schema,
- docName: 'intro',
- shadow: () => shadow,
- getBranch: () => 'main',
- contentRoot: '',
- lossDetectorEnabled: true,
- lossRing: ring,
- __testApplyLossInjector: makeInjector('Line two'),
- });
-
- buildFragment(doc, '# Title\n\nLine one\n\nLine two\n\nLine three');
-
- const trip = await pollForEvent(
- projectRoot,
- ring,
- (e) => e.event === 'detector-trip' && e.direction === 'a' && Boolean(e.checkpointSha),
- );
- expect(trip.docName).toBe('intro');
- const hist = await getDocumentHistory(shadow, { docName: 'intro' }, '');
- expect(
- hist.entries.some(
- (e) => e.sha === trip.checkpointSha && e.checkpoint?.kind === 'observer-a-apply-loss',
- ),
- ).toBe(true);
-
- cleanup();
- doc.destroy();
- });
-
- it('does not trip when the loss-detector kill-switch is off', async () => {
- const projectRoot = resolve(tmpDir, 'project');
- const shadow = await initShadowRepo(projectRoot);
- const ring = new LossCaptureRing({ projectDir: projectRoot, maxBytes: 1_000_000 });
-
- const doc = new Y.Doc();
- const ytext = doc.getText('source');
- const xf = doc.getXmlFragment('default');
- ytext.insert(0, '# Title\n\nLine one\n\nLine two');
- buildFragment(doc, '# Title\n\nLine one\n\nLine two');
-
- const cleanup = setupServerObservers({
- doc,
- xmlFragment: xf,
- ytext,
- mdManager,
- schema,
- docName: 'intro',
- shadow: () => shadow,
- getBranch: () => 'main',
- contentRoot: '',
- lossDetectorEnabled: false,
- lossRing: ring,
- __testApplyLossInjector: makeInjector('Line two'),
- });
-
- buildFragment(doc, '# Title\n\nLine one\n\nLine two\n\nLine three');
-
- await new Promise((r) => setTimeout(r, 100));
- await ring.drain();
- let events: RingEvent[] = [];
- try {
- events = parseLossCaptureLines(readFileSync(lossCaptureCurrentPath(projectRoot), 'utf-8'));
- } catch {}
- expect(events.filter((e) => e.event === 'detector-trip')).toEqual([]);
-
- cleanup();
- doc.destroy();
- });
-});
-
-describe('paired-intake derive-loss (composeAndWriteRawBody / replaceRawBody, real shadow + ring)', () => {
- let tmpDir: string;
-
- beforeEach(async () => {
- tmpDir = await mkdtemp(resolve(tmpdir(), 'ok-paired-intake-loss-test-'));
- });
-
- afterEach(async () => {
- await rm(tmpDir, { recursive: true, force: true });
- });
-
- async function setupReporter(): Promise<{
- projectRoot: string;
- shadow: ShadowHandle;
- ring: LossCaptureRing;
- reporter: ReturnType;
- }> {
- const projectRoot = resolve(tmpDir, 'project');
- const shadow = await initShadowRepo(projectRoot);
- const ring = new LossCaptureRing({ projectDir: projectRoot, maxBytes: 1_000_000 });
- const reporter = createBridgeDeriveLossReporter({
- shadow: () => shadow,
- ring,
- getBranch: () => 'main',
- contentRoot: '',
- });
- return { projectRoot, shadow, ring, reporter };
- }
-
- async function assertNoTrip(projectRoot: string, ring: LossCaptureRing): Promise {
- await new Promise((r) => setTimeout(r, 0));
- await ring.drain();
- let events: RingEvent[] = [];
- try {
- events = parseLossCaptureLines(readFileSync(lossCaptureCurrentPath(projectRoot), 'utf-8'));
- } catch {}
- expect(events.filter((e) => e.event === 'detector-trip')).toEqual([]);
- }
-
- it('file-watcher intake: a disk write that drops un-propagated fragment content trips + checkpoints', async () => {
- const { projectRoot, shadow, ring, reporter } = await setupReporter();
- const doc = seedDivergedDoc('# Title\n\nOriginal', '# Title\n\nOriginal\n\nPending keystroke');
- const baselineFullMd = doc.getText('source').toString();
- doc.transact(() => {
- composeAndWriteRawBody(
- doc,
- '# Title\n\nOriginal edited on disk',
- 'file-watcher',
- undefined,
- undefined,
- {
- report: (obs) =>
- reporter('intro', obs, 'file-system', DERIVE_LOSS_SITE_FILE_WATCHER_INTAKE),
- baselineFullMd,
- },
- );
- });
- doc.destroy();
-
- const trip = await pollForEvent(
- projectRoot,
- ring,
- (e) =>
- e.event === 'detector-trip' &&
- e.site === DERIVE_LOSS_SITE_FILE_WATCHER_INTAKE &&
- Boolean(e.checkpointSha),
- );
- expect(trip.direction).toBe('b');
- expect(trip.docName).toBe('intro');
- expect(trip.writerId).toBe('file-system');
- expect(typeof trip.lostLen).toBe('number');
- expect(trip.digest).toBeTruthy();
- expect(JSON.stringify(trip)).not.toContain('Pending keystroke');
-
- const hist = await getDocumentHistory(shadow, { docName: 'intro' }, '');
- const row = hist.entries.find((e) => e.sha === trip.checkpointSha);
- expect(row?.checkpoint?.kind).toBe('bridge-derive-loss');
- });
-
- it('file-watcher intake: a clean doc (fragment == Y.Text) does not trip', async () => {
- const { projectRoot, ring, reporter } = await setupReporter();
- const md = '# Title\n\nOnly line';
- const doc = new Y.Doc();
- doc.getText('source').insert(0, md);
- buildFragment(doc, stripFrontmatter(md).body);
- const baselineFullMd = doc.getText('source').toString();
- doc.transact(() => {
- composeAndWriteRawBody(
- doc,
- '# Title\n\nOnly line edited',
- 'file-watcher',
- undefined,
- undefined,
- {
- report: (obs) =>
- reporter('intro', obs, 'file-system', DERIVE_LOSS_SITE_FILE_WATCHER_INTAKE),
- baselineFullMd,
- },
- );
- });
- doc.destroy();
- await assertNoTrip(projectRoot, ring);
- });
-
- it('agent-write intake (replaceRawBody): an overwrite that drops un-propagated content trips with the agent-write site', async () => {
- const { projectRoot, shadow, ring, reporter } = await setupReporter();
- const doc = seedDivergedDoc('# Title\n\nOriginal', '# Title\n\nOriginal\n\nPending keystroke');
- const baselineFullMd = doc.getText('source').toString();
- doc.transact(() => {
- replaceRawBody(doc, '# Title\n\nAgent replacement', undefined, undefined, {
- report: (obs) => reporter('intro', obs, 'agent-1', DERIVE_LOSS_SITE_AGENT_WRITE_INTAKE),
- baselineFullMd,
- });
- });
- doc.destroy();
-
- const trip = await pollForEvent(
- projectRoot,
- ring,
- (e) =>
- e.event === 'detector-trip' &&
- e.site === DERIVE_LOSS_SITE_AGENT_WRITE_INTAKE &&
- Boolean(e.checkpointSha),
- );
- expect(trip.direction).toBe('b');
- expect(trip.writerId).toBe('agent-1');
- const hist = await getDocumentHistory(shadow, { docName: 'intro' }, '');
- expect(
- hist.entries.some(
- (e) => e.sha === trip.checkpointSha && e.checkpoint?.kind === 'bridge-derive-loss',
- ),
- ).toBe(true);
- });
-
- it('agent-write intake: an overwrite that keeps the pending content does not trip', async () => {
- const { projectRoot, ring, reporter } = await setupReporter();
- const doc = seedDivergedDoc('# Title\n\nOriginal', '# Title\n\nOriginal\n\nPending keystroke');
- const baselineFullMd = doc.getText('source').toString();
- doc.transact(() => {
- replaceRawBody(
- doc,
- '# Title\n\nOriginal\n\nPending keystroke\n\nAgent added',
- undefined,
- undefined,
- {
- report: (obs) => reporter('intro', obs, 'agent-1', DERIVE_LOSS_SITE_AGENT_WRITE_INTAKE),
- baselineFullMd,
- },
- );
- });
- doc.destroy();
- await assertNoTrip(projectRoot, ring);
- });
-
- it('applyExternalChange builds + forwards the reporter and the file-watcher detector fires', async () => {
- const { projectRoot, shadow, ring, reporter } = await setupReporter();
- const doc = seedDivergedDoc('# Title\n\nOriginal', '# Title\n\nOriginal\n\nPending keystroke');
- const hocuspocus = {
- documents: { get: (n: string) => (n === 'intro' ? doc : undefined) },
- } as unknown as Parameters[1];
- applyExternalChange(
- new DocumentDurabilityState(),
- hocuspocus,
- 'intro',
- '# Title\n\nOriginal edited on disk',
- undefined,
- undefined,
- reporter,
- );
-
- const trip = await pollForEvent(
- projectRoot,
- ring,
- (e) =>
- e.event === 'detector-trip' &&
- e.site === DERIVE_LOSS_SITE_FILE_WATCHER_INTAKE &&
- Boolean(e.checkpointSha),
- );
- expect(trip.direction).toBe('b');
- expect(trip.docName).toBe('intro');
- const hist = await getDocumentHistory(shadow, { docName: 'intro' }, '');
- expect(
- hist.entries.some(
- (e) => e.sha === trip.checkpointSha && e.checkpoint?.kind === 'bridge-derive-loss',
- ),
- ).toBe(true);
- doc.destroy();
- });
-
- it('a suppress-classified paired write (no detect option) never trips, even on a dirty fragment', async () => {
- const { projectRoot, ring } = await setupReporter();
- const doc = seedDivergedDoc('# Title\n\nOriginal', '# Title\n\nOriginal\n\nPending keystroke');
- doc.transact(() => {
- replaceRawBody(doc, '# Title\n\nRolled back to an older version');
- });
- doc.destroy();
- await assertNoTrip(projectRoot, ring);
- });
-});
-
describe('detectPairedIntakeLoss (the line-predicate floor)', () => {
const INTRA_LINE_STOMP: DeriveLossObservation = {
pendingBody: 'Deploy the staging server now.',
@@ -619,198 +114,3 @@ describe('detectPairedIntakeLoss (the line-predicate floor)', () => {
expect(detectPairedIntakeLoss(obs)).toEqual([]);
});
});
-
-describe('paired-intake floor through the real pipeline (real shadow + ring)', () => {
- let tmpDir: string;
-
- beforeEach(async () => {
- tmpDir = await mkdtemp(resolve(tmpdir(), 'ok-paired-floor-test-'));
- });
-
- afterEach(async () => {
- await rm(tmpDir, { recursive: true, force: true });
- });
-
- const PRE_OP_BODY = '## Guide\n\nDeploy the server now.';
- const PENDING_BODY = '## Guide\n\nDeploy the staging server now.';
- const PENDING_LINE = 'Deploy the staging server now.';
- const REPLACEMENT = '## Guide\n\nRestart the staging cluster later.';
-
- async function setup(): Promise<{
- projectRoot: string;
- shadow: ShadowHandle;
- ring: LossCaptureRing;
- reporter: ReturnType;
- }> {
- const projectRoot = resolve(tmpDir, 'project');
- const shadow = await initShadowRepo(projectRoot);
- const ring = new LossCaptureRing({ projectDir: projectRoot, maxBytes: 1_000_000 });
- const reporter = createBridgeDeriveLossReporter({
- shadow: () => shadow,
- ring,
- getBranch: () => 'main',
- contentRoot: '',
- });
- return { projectRoot, shadow, ring, reporter };
- }
-
- it('agent-write (replaceRawBody): the line predicate trips + checkpoints an intra-line stomp the twin misses', async () => {
- const { projectRoot, shadow, ring, reporter } = await setup();
- const doc = seedDivergedDoc(PRE_OP_BODY, PENDING_BODY);
- const baselineFullMd = doc.getText('source').toString();
- let captured: DeriveLossObservation | undefined;
- doc.transact(() => {
- replaceRawBody(doc, REPLACEMENT, undefined, undefined, {
- report: (obs) => {
- captured = obs;
- reporter('intro', obs, 'agent-1', DERIVE_LOSS_SITE_AGENT_WRITE_INTAKE);
- },
- baselineFullMd,
- });
- });
- doc.destroy();
-
- expect(captured).toBeDefined();
- const obs = captured as DeriveLossObservation;
- expect(detectDeriveLoss(obs)).toEqual([]);
- expect(detectPairedIntakeLoss(obs)).toContain(PENDING_LINE);
-
- const trip = await pollForEvent(
- projectRoot,
- ring,
- (e) =>
- e.event === 'detector-trip' &&
- e.site === DERIVE_LOSS_SITE_AGENT_WRITE_INTAKE &&
- Boolean(e.checkpointSha),
- );
- expect(trip.direction).toBe('b');
- expect(JSON.stringify(trip)).not.toContain(PENDING_LINE);
-
- const hist = await getDocumentHistory(shadow, { docName: 'intro' }, '');
- expect(
- hist.entries.some(
- (e) => e.sha === trip.checkpointSha && e.checkpoint?.kind === 'bridge-derive-loss',
- ),
- ).toBe(true);
- });
-
- it('the checkpoint payload is the pre-derive FRAGMENT serialization (byte-level), not Y.Text', async () => {
- const { projectRoot, shadow, ring, reporter } = await setup();
- const doc = seedDivergedDoc(PRE_OP_BODY, PENDING_BODY);
- const baselineFullMd = doc.getText('source').toString();
- let captured: DeriveLossObservation | undefined;
- doc.transact(() => {
- replaceRawBody(doc, REPLACEMENT, undefined, undefined, {
- report: (obs) => {
- captured = obs;
- reporter('intro', obs, 'agent-1', DERIVE_LOSS_SITE_AGENT_WRITE_INTAKE);
- },
- baselineFullMd,
- });
- });
- doc.destroy();
- const obs = captured as DeriveLossObservation;
-
- const trip = await pollForEvent(
- projectRoot,
- ring,
- (e) => e.event === 'detector-trip' && Boolean(e.checkpointSha),
- );
- const blob = (await shadowGit(shadow).raw('show', `${trip.checkpointSha}:intro`)).toString();
-
- expect(blob).toBe(obs.restorePayload);
- expect(blob).toContain(PENDING_LINE);
- expect(blob).not.toContain('Restart the staging cluster');
- });
-
- it('file-watcher (composeAndWriteRawBody): the line predicate trips + checkpoints an intra-line stomp', async () => {
- const { projectRoot, shadow, ring, reporter } = await setup();
- const doc = seedDivergedDoc(PRE_OP_BODY, PENDING_BODY);
- const baselineFullMd = doc.getText('source').toString();
- let captured: DeriveLossObservation | undefined;
- doc.transact(() => {
- composeAndWriteRawBody(doc, REPLACEMENT, 'file-watcher', undefined, undefined, {
- report: (obs) => {
- captured = obs;
- reporter('intro', obs, 'file-system', DERIVE_LOSS_SITE_FILE_WATCHER_INTAKE);
- },
- baselineFullMd,
- });
- });
- doc.destroy();
- const obs = captured as DeriveLossObservation;
- expect(detectDeriveLoss(obs)).toEqual([]);
- expect(detectPairedIntakeLoss(obs)).toContain(PENDING_LINE);
-
- const trip = await pollForEvent(
- projectRoot,
- ring,
- (e) =>
- e.event === 'detector-trip' &&
- e.site === DERIVE_LOSS_SITE_FILE_WATCHER_INTAKE &&
- Boolean(e.checkpointSha),
- );
- const hist = await getDocumentHistory(shadow, { docName: 'intro' }, '');
- expect(
- hist.entries.some(
- (e) => e.sha === trip.checkpointSha && e.checkpoint?.kind === 'bridge-derive-loss',
- ),
- ).toBe(true);
- });
-
- it('agent-undo (deriveFragmentFromYtext): the line predicate participates in the floor for every derive caller', async () => {
- const { projectRoot, shadow, ring, reporter } = await setup();
- const doc = seedDivergedDoc(
- '## Guide\n\nOriginal.',
- '## Guide\n\nOriginal.\n\nPending line here.',
- );
- const baselineFullMd = doc.getText('source').toString();
- let captured: DeriveLossObservation | undefined;
- doc.transact(() => {
- deriveFragmentFromYtext(doc, undefined, {
- report: (obs) => {
- captured = obs;
- reporter('intro', obs, 'agent-1', DERIVE_LOSS_SITE_AGENT_UNDO);
- },
- baselineFullMd,
- });
- });
- doc.destroy();
- const obs = captured as DeriveLossObservation;
- expect(pendingContentLines(obs.pendingBody, obs.ytextDerivedBody, obs.baselineBody)).toContain(
- 'Pending line here.',
- );
- expect(detectPairedIntakeLoss(obs)).toContain('Pending line here.');
-
- const trip = await pollForEvent(
- projectRoot,
- ring,
- (e) =>
- e.event === 'detector-trip' &&
- e.site === DERIVE_LOSS_SITE_AGENT_UNDO &&
- Boolean(e.checkpointSha),
- );
- const hist = await getDocumentHistory(shadow, { docName: 'intro' }, '');
- expect(
- hist.entries.some(
- (e) => e.sha === trip.checkpointSha && e.checkpoint?.kind === 'bridge-derive-loss',
- ),
- ).toBe(true);
- });
-
- it('a suppress-classified paired write (no detect) never trips, even on an intra-line dirty fragment', async () => {
- const { projectRoot, ring } = await setup();
- const doc = seedDivergedDoc(PRE_OP_BODY, PENDING_BODY);
- doc.transact(() => {
- replaceRawBody(doc, REPLACEMENT);
- });
- doc.destroy();
- await new Promise((r) => setTimeout(r, 0));
- await ring.drain();
- let events: RingEvent[] = [];
- try {
- events = parseLossCaptureLines(readFileSync(lossCaptureCurrentPath(projectRoot), 'utf-8'));
- } catch {}
- expect(events.filter((e) => e.event === 'detector-trip')).toEqual([]);
- });
-});
diff --git a/packages/server/src/bridge-loss-detector.ts b/packages/server/src/bridge-loss-detector.ts
index f87b25007..7ffc0fd9a 100644
--- a/packages/server/src/bridge-loss-detector.ts
+++ b/packages/server/src/bridge-loss-detector.ts
@@ -1,10 +1,4 @@
-import { findDroppedContent, fnv1aDigest, pendingContentLines } from '@inkeep/open-knowledge-core';
-import { getLogger } from './logger.ts';
-import { LOSS_EVENT_DETECTOR_TRIP, type LossCaptureRing } from './loss-capture.ts';
-import { type ShadowHandle, saveInMemoryCheckpoint } from './shadow-repo.ts';
-
-const log = getLogger('bridge-loss-detector');
-const checkpointLog = getLogger('checkpoint');
+import { findDroppedContent, pendingContentLines } from '@inkeep/open-knowledge-core';
export function detectApplyArmDrop(
intendedMd: string,
@@ -59,88 +53,3 @@ export interface DeriveLossDetectOptions {
report: (obs: DeriveLossObservation) => void;
baselineFullMd: string;
}
-
-export const DERIVE_LOSS_SITE_AGENT_UNDO = 'agent-undo-derive';
-export const DERIVE_LOSS_SITE_FILE_WATCHER_INTAKE = 'file-watcher-intake';
-export const DERIVE_LOSS_SITE_AGENT_WRITE_INTAKE = 'agent-write-intake';
-
-export type BridgeDeriveLossReporter = (
- docName: string,
- obs: DeriveLossObservation,
- writerId?: string | null,
- site?: string,
-) => void;
-
-export interface BridgeDeriveLossReporterDeps {
- shadow: () => ShadowHandle | undefined;
- ring?: Pick;
- getBranch: () => string;
- contentRoot: string;
-}
-
-export function createBridgeDeriveLossReporter(
- deps: BridgeDeriveLossReporterDeps,
-): BridgeDeriveLossReporter {
- return (docName, obs, writerId = null, site = DERIVE_LOSS_SITE_AGENT_UNDO) => {
- const dropped = detectPairedIntakeLoss(obs);
- if (dropped.length === 0) return;
- const lostLen = dropped.reduce((n, s) => n + s.length, 0);
- const digest = fnv1aDigest(dropped.join('\n'));
- const shadow = deps.shadow();
- if (!shadow) {
- void deps.ring?.record({
- event: LOSS_EVENT_DETECTOR_TRIP,
- docName,
- writerId,
- direction: 'b',
- site,
- lostLen,
- digest,
- });
- return;
- }
- const branch = deps.getBranch();
- const contentRoot = deps.contentRoot;
- queueMicrotask(() => {
- saveInMemoryCheckpoint(shadow, contentRoot, {
- kind: 'bridge-derive-loss',
- docName,
- contents: obs.restorePayload,
- label: `Before ${site} content-loss @ ${new Date().toISOString()}`,
- branch,
- metadata: { lostSubstrings: dropped },
- })
- .then((sha) => {
- void deps.ring?.record({
- event: LOSS_EVENT_DETECTOR_TRIP,
- docName,
- writerId,
- direction: 'b',
- site,
- lostLen,
- digest,
- checkpointSha: sha,
- });
- console.warn(
- JSON.stringify({
- event: 'bridge-derive-loss-checkpoint-created',
- docName,
- sha,
- kind: 'bridge-derive-loss',
- site,
- timestamp: new Date().toISOString(),
- }),
- );
- })
- .catch((checkpointErr: unknown) => {
- const e =
- checkpointErr instanceof Error ? checkpointErr : new Error(String(checkpointErr));
- log.warn({ docName, err: e }, '[bridge-derive-loss] checkpoint write failed');
- checkpointLog.warn(
- { err: e, 'doc.name': docName, branch, kind: 'bridge-derive-loss' },
- 'checkpoint write failed',
- );
- });
- });
- };
-}
diff --git a/packages/server/src/bridge-loss-suppression.test.ts b/packages/server/src/bridge-loss-suppression.test.ts
deleted file mode 100644
index 011101c29..000000000
--- a/packages/server/src/bridge-loss-suppression.test.ts
+++ /dev/null
@@ -1,141 +0,0 @@
-import { mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
-import { tmpdir } from 'node:os';
-import { dirname, join, sep } from 'node:path';
-import { fileURLToPath } from 'node:url';
-import { afterEach, describe, expect, it } from 'vitest';
-import {
- PAIRED_INTAKE_DETECTION,
- pairedIntakeDetectionMode,
- RESERVED_PAIRED_INTAKE_DETECTION,
- shouldRunPairedIntakeDetection,
-} from './bridge-loss-suppression.ts';
-
-const SRC_DIR = dirname(fileURLToPath(import.meta.url));
-
-function isProductionSource(name: string): boolean {
- return (
- name.endsWith('.ts') &&
- !name.endsWith('.test.ts') &&
- !name.endsWith('.test-helper.ts') &&
- !name.endsWith('.d.ts')
- );
-}
-
-function stripCommentLines(text: string): string {
- return text
- .split('\n')
- .filter((line) => {
- const t = line.trimStart();
- return !(t.startsWith('*') || t.startsWith('//') || t.startsWith('/*') || t.startsWith('*/'));
- })
- .join('\n');
-}
-
-function declaredPairedOrigins(root: string = SRC_DIR): Set {
- const origins = new Set();
- const objLiteral = /\{[^{}]*\}/g;
- for (const rel of readdirSync(root, { recursive: true, encoding: 'utf-8' })) {
- const base = rel.split(sep).pop() ?? rel;
- if (!isProductionSource(base)) continue;
- let text: string;
- try {
- text = stripCommentLines(readFileSync(join(root, rel), 'utf-8'));
- } catch {
- continue;
- }
- for (const m of text.matchAll(objLiteral)) {
- const obj = m[0];
- if (!obj.includes('paired: true')) continue;
- const originMatch = obj.match(/origin:\s*'([^']+)'/);
- if (originMatch?.[1]) origins.add(originMatch[1]);
- }
- }
- return origins;
-}
-
-const scratchDirs: string[] = [];
-afterEach(() => {
- while (scratchDirs.length > 0) {
- const dir = scratchDirs.pop();
- if (dir) rmSync(dir, { recursive: true, force: true });
- }
-});
-
-function plantSyntheticTree(): string {
- const root = mkdtempSync(join(tmpdir(), 'ok-paired-scan-'));
- scratchDirs.push(root);
- writeFileSync(
- join(root, 'top-level-surface.ts'),
- "export const TOP = Object.freeze({ source: 'local', context: { origin: 'planted-top-level', paired: true } });\n",
- 'utf-8',
- );
- mkdirSync(join(root, 'http', 'deeper'), { recursive: true });
- writeFileSync(
- join(root, 'http', 'nested-surface.ts'),
- "export const NESTED = Object.freeze({ source: 'local', context: { origin: 'planted-subdirectory', paired: true } });\n",
- 'utf-8',
- );
- writeFileSync(
- join(root, 'http', 'deeper', 'deepest-surface.ts'),
- "export const DEEP = Object.freeze({ source: 'local', context: { origin: 'planted-two-deep', paired: true } });\n",
- 'utf-8',
- );
- writeFileSync(
- join(root, 'http', 'nested-surface.test.ts'),
- "const FIXTURE = { origin: 'planted-fixture-not-production', paired: true };\n",
- 'utf-8',
- );
- return root;
-}
-
-describe('paired-intake detection classification (fail-closed sweep)', () => {
- it('the scanner finds paired origins declared in subdirectories, not just top-level files', () => {
- const root = plantSyntheticTree();
- const found = declaredPairedOrigins(root);
-
- expect(found.has('planted-top-level')).toBe(true);
- expect(found.has('planted-subdirectory')).toBe(true);
- expect(found.has('planted-two-deep')).toBe(true);
- expect(found.has('planted-fixture-not-production')).toBe(false);
- expect([...found].sort()).toEqual([
- 'planted-subdirectory',
- 'planted-top-level',
- 'planted-two-deep',
- ]);
- });
-
- it('classifies every paired-write origin declared in production source', () => {
- const declared = declaredPairedOrigins();
- expect(declared.size).toBeGreaterThanOrEqual(5);
- const unclassified = [...declared].filter((o) => pairedIntakeDetectionMode(o) === undefined);
- expect(unclassified).toEqual([]);
- });
-
- it('has no phantom classification without a source origin', () => {
- const declared = declaredPairedOrigins();
- const phantom = Object.keys(PAIRED_INTAKE_DETECTION).filter((o) => !declared.has(o));
- expect(phantom).toEqual([]);
- });
-
- it('keeps reserved classifications out of the live map until their constant lands', () => {
- const declared = declaredPairedOrigins();
- for (const reserved of Object.keys(RESERVED_PAIRED_INTAKE_DETECTION)) {
- expect(PAIRED_INTAKE_DETECTION[reserved]).toBeUndefined();
- expect(declared.has(reserved)).toBe(false);
- }
- });
-
- it('flags a synthetic unclassified origin', () => {
- expect(pairedIntakeDetectionMode('brand-new-write-surface')).toBeUndefined();
- expect(shouldRunPairedIntakeDetection('brand-new-write-surface')).toBe(false);
- });
-
- it('runs the detector for content-preserving origins and suppresses replacements', () => {
- expect(shouldRunPairedIntakeDetection('agent-write')).toBe(true);
- expect(shouldRunPairedIntakeDetection('agent-undo')).toBe(true);
- expect(shouldRunPairedIntakeDetection('file-watcher')).toBe(true);
- expect(shouldRunPairedIntakeDetection('rollback-apply')).toBe(false);
- expect(shouldRunPairedIntakeDetection('managed-rename')).toBe(false);
- expect(shouldRunPairedIntakeDetection('park-snapshot')).toBe(false);
- });
-});
diff --git a/packages/server/src/bridge-loss-suppression.ts b/packages/server/src/bridge-loss-suppression.ts
deleted file mode 100644
index 42f25a077..000000000
--- a/packages/server/src/bridge-loss-suppression.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-export type PairedIntakeDetectionMode = 'detect' | 'suppress';
-
-interface PairedIntakeDetectionEntry {
- mode: PairedIntakeDetectionMode;
- why: string;
-}
-
-export const PAIRED_INTAKE_DETECTION: Record = {
- 'agent-write': {
- mode: 'detect',
- why: 'An agent write can race un-propagated WYSIWYG content; the pre-write baseline excludes the write itself, so only a never-propagated keystroke trips.',
- },
- 'agent-undo': {
- mode: 'detect',
- why: 'The Observer-B agent-undo derive; the pre-undo baseline excludes the undo’s own removal, so only a never-propagated keystroke trips.',
- },
- 'file-watcher': {
- mode: 'detect',
- why: 'An external disk write overwriting a dirty open doc drops un-propagated content; the pre-write baseline excludes the incoming change itself.',
- },
- 'rollback-apply': {
- mode: 'suppress',
- why: 'An explicit restore of a historical version; discarding the current state is the user intent, and that state stays a timeline version.',
- },
- 'managed-rename': {
- mode: 'suppress',
- why: 'A rename re-writes the same content at a new path; no content is dropped by construction.',
- },
- 'park-snapshot': {
- mode: 'suppress',
- why: 'A read-only snapshot capture that makes no Y.Doc mutation, so no content can be lost.',
- },
- 'generated-index': {
- mode: 'suppress',
- why: 'A rebuild of a file OK authors: its content is derived from the other documents, and replacing a hand edit is the stated contract of generating it, not a loss.',
- },
-};
-
-export const RESERVED_PAIRED_INTAKE_DETECTION: Record = {
- 'machine-merge': {
- mode: 'detect',
- why: 'Reserved for the conflict spec: a machine merge landing into a dirty doc can drop un-propagated content; classified detect ahead of the constant.',
- },
-};
-
-export function pairedIntakeDetectionMode(
- originContextOrigin: string,
-): PairedIntakeDetectionMode | undefined {
- return PAIRED_INTAKE_DETECTION[originContextOrigin]?.mode;
-}
-
-export function shouldRunPairedIntakeDetection(originContextOrigin: string): boolean {
- return PAIRED_INTAKE_DETECTION[originContextOrigin]?.mode === 'detect';
-}
diff --git a/packages/server/src/bridge-no-wallclock.test.ts b/packages/server/src/bridge-no-wallclock.test.ts
index 3e9dd5fc8..a5e4508da 100644
--- a/packages/server/src/bridge-no-wallclock.test.ts
+++ b/packages/server/src/bridge-no-wallclock.test.ts
@@ -14,10 +14,7 @@ const repoRoot = join(here, '..', '..', '..');
* Files guarded by precedent #13(b). Each must be free of the forbidden
* patterns.
*/
-const GUARDED_FILES = [
- 'packages/server/src/server-observers.ts',
- 'packages/app/src/editor/observers.ts',
-] as const;
+const GUARDED_FILES = ['packages/app/src/editor/observers.ts'] as const;
const FORBIDDEN: ReadonlyArray<{ name: string; regex: RegExp }> = [
{ name: 'setTimeout() call', regex: /\bsetTimeout\s*\(/ },
diff --git a/packages/server/src/bridge-quiescence.test.ts b/packages/server/src/bridge-quiescence.test.ts
index aafb44337..529201834 100644
--- a/packages/server/src/bridge-quiescence.test.ts
+++ b/packages/server/src/bridge-quiescence.test.ts
@@ -7,7 +7,7 @@ import {
getQuiescenceCountersForTests,
isDocQuiescent,
} from './bridge-quiescence.ts';
-import { OBSERVER_SYNC_ORIGIN } from './server-observers.ts';
+import { OBSERVER_SYNC_ORIGIN } from './write-origins.ts';
beforeEach(() => {
__resetQuiescenceForTests();
diff --git a/packages/server/src/bridge-quiescence.ts b/packages/server/src/bridge-quiescence.ts
index 968e0dbb5..a7c75d394 100644
--- a/packages/server/src/bridge-quiescence.ts
+++ b/packages/server/src/bridge-quiescence.ts
@@ -8,6 +8,7 @@ interface DocQuiescenceCounters {
* through this module so the bridge observer file stays clean of timer machinery.
*/
lastUserTxAtMs: number | null;
+ lastExternalEditorChangeAtMs: number | null;
}
const counters = new WeakMap();
@@ -16,7 +17,12 @@ let globalCounter = 0;
function getCounters(doc: Y.Doc): DocQuiescenceCounters {
let c = counters.get(doc);
if (!c) {
- c = { lastUserTxGen: 0, settledGen: 0, lastUserTxAtMs: null };
+ c = {
+ lastUserTxGen: 0,
+ settledGen: 0,
+ lastUserTxAtMs: null,
+ lastExternalEditorChangeAtMs: null,
+ };
counters.set(doc, c);
}
return c;
@@ -33,12 +39,23 @@ function isObserverSelfOrigin(origin: unknown): boolean {
return ctx !== undefined && ctx !== null && ctx.origin === 'observer-sync';
}
+function isConnectionOrigin(origin: unknown): boolean {
+ return (
+ origin !== null &&
+ typeof origin === 'object' &&
+ (origin as { source?: unknown }).source === 'connection'
+ );
+}
+
export function attachQuiescenceTracker(doc: Y.Doc): () => void {
const onAfterTransaction = (tx: Y.Transaction): void => {
if (isObserverSelfOrigin(tx.origin)) return;
const c = getCounters(doc);
c.lastUserTxGen = ++globalCounter;
c.lastUserTxAtMs = Date.now();
+ if (tx.changed.size > 0 && isConnectionOrigin(tx.origin)) {
+ c.lastExternalEditorChangeAtMs = c.lastUserTxAtMs;
+ }
};
const onAfterAllTransactions = (): void => {
getCounters(doc).settledGen = ++globalCounter;
@@ -72,6 +89,10 @@ export function getMsSinceLastUserTx(doc: Y.Doc, nowMs: number = Date.now()): nu
return Math.max(0, nowMs - c.lastUserTxAtMs);
}
+export function getLastExternalEditorChangeMs(doc: Y.Doc): number | undefined {
+ return counters.get(doc)?.lastExternalEditorChangeAtMs ?? undefined;
+}
+
export function getQuiescenceCountersForTests(doc: Y.Doc): DocQuiescenceCounters | undefined {
return counters.get(doc);
}
diff --git a/packages/server/src/bridge-race-rig.test-helper.ts b/packages/server/src/bridge-race-rig.test-helper.ts
deleted file mode 100644
index 960e2b0a3..000000000
--- a/packages/server/src/bridge-race-rig.test-helper.ts
+++ /dev/null
@@ -1,243 +0,0 @@
-/**
- * Faking only Date keeps span timing and the settlement dispatcher (which uses no wall clock,
- * precedent #13(b)) untouched.
- */
-
-import { type MarkdownManager, sharedExtensions } from '@inkeep/open-knowledge-core';
-import { getSchema, type JSONContent } from '@tiptap/core';
-import { updateYFragment, yXmlFragmentToProseMirrorRootNode } from '@tiptap/y-tiptap';
-import { vi } from 'vitest';
-import * as Y from 'yjs';
-import { mdManager as productionMdManager } from './md-manager.ts';
-import type { ObserverDispatchKind, SetupServerObserversOpts } from './server-observers.ts';
-import { setupServerObservers } from './server-observers.ts';
-
-const schema = getSchema(sharedExtensions);
-
-const FRESHNESS_ADVANCE_MS = 3_000;
-
-const RIG_EXTERNAL_ORIGIN = 'bridge-race-rig/external';
-const RIG_FORCE_ORIGIN = 'bridge-race-rig/force-a-round';
-
-const EMPTY_UPDATE_META = () => ({ mapping: new Map(), isOMark: new Map() });
-
-interface StimulusOpts {
- advanceFreshness?: boolean;
-}
-
-interface DrainTraceEntry {
- readonly label: string;
- readonly dispatches: readonly ObserverDispatchKind[];
- readonly bytes: string;
- readonly fragmentMd: string;
- readonly byteChanged: boolean;
-}
-
-export interface BridgeRaceRig {
- readonly doc: Y.Doc;
- readonly xmlFragment: Y.XmlFragment;
- readonly ytext: Y.Text;
- readonly mdManager: MarkdownManager;
- readonly trace: readonly DrainTraceEntry[];
- dispatchLog(): ObserverDispatchKind[];
- traceLines(): string[];
- serializeFragment(): string;
- advanceClock(ms: number): void;
- advancePastFreshness(): void;
- stimulus(label: string, mutate: () => void, opts?: StimulusOpts): DrainTraceEntry;
- seedSource(md: string, opts?: StimulusOpts): DrainTraceEntry;
- externalYtextEdit(
- label: string,
- mutate: (ytext: Y.Text) => void,
- opts?: StimulusOpts,
- ): DrainTraceEntry;
- editFragment(md: string, opts?: StimulusOpts): DrainTraceEntry;
- churnedFragmentEdit(md: string, opts?: StimulusOpts): DrainTraceEntry;
- echoFragmentEdit(baseMd: string, from: string, to: string, opts?: StimulusOpts): DrainTraceEntry;
- dualMutation(
- md: string,
- ytextEdit: (ytext: Y.Text) => void,
- opts?: StimulusOpts,
- ): DrainTraceEntry;
- forceARound(opts?: StimulusOpts): DrainTraceEntry;
- settle(rounds: number): DrainTraceEntry[];
- pairedWrite(label: string, mutate: () => void, origin: unknown): DrainTraceEntry;
- cleanup(): void;
-}
-
-export interface CreateRigOpts {
- docName?: string;
- setupOverrides?: Partial;
-}
-
-function mutateFirstText(node: JSONContent, from: string, to: string): boolean {
- if (typeof node.text === 'string' && node.text === from) {
- node.text = to;
- return true;
- }
- for (const child of node.content ?? []) {
- if (mutateFirstText(child, from, to)) return true;
- }
- return false;
-}
-
-function stripCaptureAttrs(node: JSONContent): JSONContent {
- let next = node;
- if (next.attrs && typeof next.attrs === 'object') {
- const kept: Record = {};
- for (const [k, v] of Object.entries(next.attrs)) {
- if (k.startsWith('source') || k === 'position') continue;
- kept[k] = v;
- }
- next = { ...next, attrs: kept };
- }
- if (Array.isArray(next.content)) {
- next = { ...next, content: next.content.map(stripCaptureAttrs) };
- }
- return next;
-}
-
-export function createBridgeRaceRig(opts: CreateRigOpts = {}): BridgeRaceRig {
- const doc = new Y.Doc();
- const xmlFragment = doc.getXmlFragment('default');
- const ytext = doc.getText('source');
- const mdManager = opts.setupOverrides?.mdManager ?? productionMdManager;
-
- const trace: DrainTraceEntry[] = [];
- const pending: ObserverDispatchKind[] = [];
- let lastBytes = ytext.toString();
-
- const onDispatch = (kind: ObserverDispatchKind): void => {
- pending.push(kind);
- opts.setupOverrides?.onDispatch?.(kind);
- };
-
- const cleanup = setupServerObservers({
- doc,
- xmlFragment,
- ytext,
- mdManager,
- schema,
- docName: opts.docName,
- ...opts.setupOverrides,
- onDispatch,
- });
-
- const serializeFragment = (): string =>
- mdManager.serialize(yXmlFragmentToProseMirrorRootNode(xmlFragment, schema).toJSON());
-
- const advanceClock = (ms: number): void => {
- vi.setSystemTime(Date.now() + ms);
- };
- const advancePastFreshness = (): void => advanceClock(FRESHNESS_ADVANCE_MS);
-
- const record = (label: string): DrainTraceEntry => {
- const bytes = ytext.toString();
- const entry: DrainTraceEntry = {
- label,
- dispatches: pending.slice(),
- bytes,
- fragmentMd: serializeFragment(),
- byteChanged: bytes !== lastBytes,
- };
- lastBytes = bytes;
- pending.length = 0;
- trace.push(entry);
- return entry;
- };
-
- const stimulus = (label: string, mutate: () => void, sopts?: StimulusOpts): DrainTraceEntry => {
- if (sopts?.advanceFreshness !== false) advancePastFreshness();
- pending.length = 0;
- mutate();
- return record(label);
- };
-
- const parseNode = (md: string, churn: boolean): ReturnType => {
- const json = mdManager.parse(md);
- return schema.nodeFromJSON(churn ? stripCaptureAttrs(json) : json);
- };
-
- const populateFragment = (md: string, churn: boolean): void => {
- updateYFragment(doc, xmlFragment, parseNode(md, churn), EMPTY_UPDATE_META());
- };
-
- const rig: BridgeRaceRig = {
- doc,
- xmlFragment,
- ytext,
- mdManager,
- trace,
- dispatchLog: () => trace.flatMap((e) => [...e.dispatches]),
- traceLines: () =>
- trace.map(
- (e) => `${e.label} dispatch=[${e.dispatches.join(',')}] byteChanged=${e.byteChanged}`,
- ),
- serializeFragment,
- advanceClock,
- advancePastFreshness,
- stimulus,
- seedSource: (md, sopts) =>
- stimulus(
- 'seed-source',
- () => {
- doc.transact(() => {
- ytext.delete(0, ytext.length);
- ytext.insert(0, md);
- }, RIG_EXTERNAL_ORIGIN);
- },
- sopts,
- ),
- externalYtextEdit: (label, mutate, sopts) =>
- stimulus(label, () => doc.transact(() => mutate(ytext), RIG_EXTERNAL_ORIGIN), sopts),
- editFragment: (md, sopts) =>
- stimulus('edit-fragment', () => populateFragment(md, false), sopts),
- churnedFragmentEdit: (md, sopts) =>
- stimulus('churned-fragment', () => populateFragment(md, true), sopts),
- echoFragmentEdit: (baseMd, from, to, sopts) =>
- stimulus(
- 'echo-fragment',
- () => {
- const json = mdManager.parse(baseMd);
- if (!mutateFirstText(json, from, to)) {
- throw new Error(`echoFragmentEdit: text leaf '${from}' not found in parse(baseMd)`);
- }
- updateYFragment(doc, xmlFragment, schema.nodeFromJSON(json), EMPTY_UPDATE_META());
- },
- sopts,
- ),
- dualMutation: (md, ytextEdit, sopts) =>
- stimulus(
- 'dual-mutation',
- () => {
- doc.transact(() => {
- updateYFragment(doc, xmlFragment, parseNode(md, false), EMPTY_UPDATE_META());
- ytextEdit(ytext);
- }, RIG_EXTERNAL_ORIGIN);
- },
- sopts,
- ),
- forceARound: (sopts) =>
- stimulus(
- 'force-a-round',
- () => {
- doc.transact(() => {
- const el = new Y.XmlElement('paragraph');
- xmlFragment.push([el]);
- xmlFragment.delete(xmlFragment.length - 1, 1);
- }, RIG_FORCE_ORIGIN);
- },
- sopts,
- ),
- settle: (rounds) => {
- const out: DrainTraceEntry[] = [];
- for (let i = 0; i < rounds; i++) out.push(rig.forceARound());
- return out;
- },
- pairedWrite: (label, mutate, origin) =>
- stimulus(label, () => doc.transact(mutate, origin), { advanceFreshness: false }),
- cleanup,
- };
-
- return rig;
-}
diff --git a/packages/server/src/bridge-race-rig.test.ts b/packages/server/src/bridge-race-rig.test.ts
deleted file mode 100644
index 0c1f059e5..000000000
--- a/packages/server/src/bridge-race-rig.test.ts
+++ /dev/null
@@ -1,110 +0,0 @@
-import {
- MarkdownManager,
- type SerializeCallOptions,
- sharedExtensions,
-} from '@inkeep/open-knowledge-core';
-import type { JSONContent } from '@tiptap/core';
-import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
-import { type BridgeRaceRig, createBridgeRaceRig } from './bridge-race-rig.test-helper.ts';
-
-function makeRecordingManager(): {
- manager: MarkdownManager;
- serializeOpts: Array;
-} {
- const real = new MarkdownManager({
- extensions: sharedExtensions,
- deriveStructuralFreshness: true,
- });
- const serializeOpts: Array = [];
- const manager = new Proxy(real, {
- get(target, prop, receiver) {
- if (prop === 'serialize') {
- return (json: JSONContent, opts?: SerializeCallOptions) => {
- serializeOpts.push(opts);
- return target.serialize(json, opts);
- };
- }
- const value = Reflect.get(target, prop, receiver);
- return typeof value === 'function' ? value.bind(target) : value;
- },
- });
- return { manager, serializeOpts };
-}
-
-describe('bridge-race rig — H1 substrate', () => {
- beforeEach(() => {
- vi.useFakeTimers({ toFake: ['Date'] });
- });
- afterEach(() => {
- vi.useRealTimers();
- });
-
- function driveScenario(): BridgeRaceRig {
- const rig = createBridgeRaceRig({ docName: 'race-rig-smoke.md' });
- rig.seedSource('# Doc\n\nOriginal body.\n');
- rig.editFragment('# Doc\n\nOriginal body.\n\nWysiwyg paragraph.\n');
- rig.dualMutation('# Doc\n\nOriginal body.\n\nWysiwyg paragraph two.\n', (yt) =>
- yt.insert(yt.length, 'Source tail.\n'),
- );
- rig.churnedFragmentEdit(
- '# Doc\n\nOriginal body.\n\nWysiwyg paragraph two.\n\nSource tail.\n\nExtra.\n',
- );
- rig.settle(3);
- return rig;
- }
-
- test('drives a scripted interleaving to a byte fixed point on the production drain', () => {
- const rig = driveScenario();
- try {
- const tail = rig.trace.slice(-2);
- expect(tail.every((e) => e.dispatches.join(',') === 'a' && e.byteChanged === false)).toBe(
- true,
- );
- const last = rig.trace.at(-1);
- expect(last?.bytes).toContain('Original body.');
- // Both bridge representations agree at rest (Y.Text-is-truth, precedent #38).
- expect(rig.serializeFragment()).toBe(last?.fragmentMd);
- } finally {
- rig.cleanup();
- }
- });
-
- test('trace is byte-identical across 3 consecutive runs (determinism contract)', () => {
- const runs: string[][] = [];
- for (let i = 0; i < 3; i++) {
- const rig = driveScenario();
- runs.push(rig.traceLines());
- rig.cleanup();
- }
- expect(runs[1]).toEqual(runs[0]);
- expect(runs[2]).toEqual(runs[0]);
- expect(runs[0].length).toBeGreaterThan(4);
- });
-
- test('the freshness-suppressed Observer A arm is drivable on the rig (P2-1: DRIVABLE)', () => {
- const { manager, serializeOpts } = makeRecordingManager();
- const rig = createBridgeRaceRig({
- docName: 'race-rig-freshness.md',
- setupOverrides: { mdManager: manager },
- });
- try {
- rig.seedSource('# Doc\n\nBody line.\n');
- rig.settle(1);
-
- let before = serializeOpts.length;
- rig.forceARound();
- const freshCalls = serializeOpts.slice(before);
- expect(freshCalls.some((o) => o?.skipFreshnessDerive === false)).toBe(true);
-
- rig.externalYtextEdit('external-hot', (yt) => yt.insert(yt.length, 'Typed tail.\n'), {
- advanceFreshness: false,
- });
- before = serializeOpts.length;
- rig.forceARound({ advanceFreshness: false });
- const hotCalls = serializeOpts.slice(before);
- expect(hotCalls.some((o) => o?.skipFreshnessDerive === true)).toBe(true);
- } finally {
- rig.cleanup();
- }
- });
-});
diff --git a/packages/server/src/bridge-watchdog.test.ts b/packages/server/src/bridge-watchdog.test.ts
deleted file mode 100644
index bd6a99341..000000000
--- a/packages/server/src/bridge-watchdog.test.ts
+++ /dev/null
@@ -1,994 +0,0 @@
-import {
- BridgeInvariantViolationError,
- MarkdownManager,
- normalizeBridge,
- setToleranceTelemetryHook,
- sharedExtensions,
- type ToleranceFireRecord,
-} from '@inkeep/open-knowledge-core';
-import { afterEach, beforeEach, describe, expect, test } from 'vitest';
-import {
- __getSplitBrainRateTupleCountForTests,
- __getViolationRateTupleCountForTests,
- __resetBridgeWatchdogForTests,
- assertBridgeInvariant,
- emitBridgeSplitBrainRederive,
- emitObserverAPathBFired,
- shouldEmitBridgeInvariantViolation,
- shouldEmitBridgeSplitBrainRederive,
- shouldEmitBridgeToleranceApplied,
- shouldEmitObserverAPathBFired,
- shouldThrowOnBridgeInvariantViolation,
-} from './bridge-watchdog.ts';
-import { getMetrics, resetMetrics } from './metrics.ts';
-
-beforeEach(() => {
- __resetBridgeWatchdogForTests();
- resetMetrics();
-});
-
-afterEach(() => {
- delete process.env.OK_BRIDGE_THROW_ON_VIOLATION;
- delete process.env.OK_BRIDGE_VIOLATION_DEBOUNCE_S;
-});
-
-describe('shouldThrowOnBridgeInvariantViolation (affirmative gate polarity)', () => {
- test('undefined NODE_ENV does not throw (Bun production default)', () => {
- expect(shouldThrowOnBridgeInvariantViolation({} as NodeJS.ProcessEnv)).toBe(false);
- });
-
- test('NODE_ENV=production does not throw', () => {
- expect(
- shouldThrowOnBridgeInvariantViolation({ NODE_ENV: 'production' } as NodeJS.ProcessEnv),
- ).toBe(false);
- });
-
- test('NODE_ENV=development does not throw', () => {
- expect(
- shouldThrowOnBridgeInvariantViolation({ NODE_ENV: 'development' } as NodeJS.ProcessEnv),
- ).toBe(false);
- });
-
- test('NODE_ENV=test throws (bun test default)', () => {
- expect(shouldThrowOnBridgeInvariantViolation({ NODE_ENV: 'test' } as NodeJS.ProcessEnv)).toBe(
- true,
- );
- });
-
- test('OK_BRIDGE_THROW_ON_VIOLATION=1 throws regardless of NODE_ENV', () => {
- expect(
- shouldThrowOnBridgeInvariantViolation({
- NODE_ENV: 'production',
- OK_BRIDGE_THROW_ON_VIOLATION: '1',
- } as NodeJS.ProcessEnv),
- ).toBe(true);
- });
-
- test('OK_BRIDGE_THROW_ON_VIOLATION=0 does not throw', () => {
- expect(
- shouldThrowOnBridgeInvariantViolation({
- OK_BRIDGE_THROW_ON_VIOLATION: '0',
- } as NodeJS.ProcessEnv),
- ).toBe(false);
- });
-});
-
-describe('assertBridgeInvariant — no-op for tolerance-equivalent inputs', () => {
- test('byte-equal inputs pass without throwing', () => {
- expect(() => {
- assertBridgeInvariant('# Hello\n', '# Hello\n', { site: 'observer-b' });
- }).not.toThrow();
- expect(getMetrics().bridgeInvariantViolations).toBe(0);
- });
-
- test('CRLF vs LF tolerated (normalize.ts step 2)', () => {
- expect(() => {
- assertBridgeInvariant('# Hello\r\n', '# Hello\n', { site: 'observer-b' });
- }).not.toThrow();
- expect(getMetrics().bridgeInvariantViolations).toBe(0);
- });
-
- test('BOM vs no-BOM tolerated (normalize.ts step 1)', () => {
- expect(() => {
- assertBridgeInvariant('# Hello\n', '# Hello\n', { site: 'observer-b' });
- }).not.toThrow();
- expect(getMetrics().bridgeInvariantViolations).toBe(0);
- });
-
- test('per-line trailing whitespace tolerated (normalize.ts step 4)', () => {
- expect(() => {
- assertBridgeInvariant('# Hello \nbody\n', '# Hello\nbody\n', { site: 'observer-b' });
- }).not.toThrow();
- expect(getMetrics().bridgeInvariantViolations).toBe(0);
- });
-
- test('3+ newline collapse tolerated (NG1 architectural floor)', () => {
- expect(() => {
- assertBridgeInvariant('# H\n\n\n\n# H2\n', '# H\n\n# H2\n', { site: 'observer-b' });
- }).not.toThrow();
- expect(getMetrics().bridgeInvariantViolations).toBe(0);
- });
-
- test('table-row trailing-pipe divergence tolerated (row-no-trailing-pipe)', () => {
- expect(() => {
- assertBridgeInvariant('| a | b\n| - | -\n| 1 | 2\n', '| a | b|\n| - | -|\n| 1 | 2|\n', {
- site: 'observer-b',
- });
- }).not.toThrow();
- expect(getMetrics().bridgeInvariantViolations).toBe(0);
- });
-
- test('touched-cell table divergence is NOT absorbed by the trailing-pipe tolerance', () => {
- expect(() => {
- assertBridgeInvariant(
- '| a | b |\n| - | - |\n| 1 | 2 |\n',
- '| a | b |\n| - | - |\n| 1 | 99 |\n',
- { site: 'observer-b' },
- );
- }).toThrow(BridgeInvariantViolationError);
- });
-});
-
-describe('assertBridgeInvariant — throws under NODE_ENV=test (default for bun test)', () => {
- test('byte-divergence outside tolerance throws', () => {
- expect(() => {
- assertBridgeInvariant('# Foo\n', '# Bar\n', { site: 'observer-b' });
- }).toThrow(BridgeInvariantViolationError);
- });
-
- test('thrown error carries violation shape (site, snapshots, diff)', () => {
- try {
- assertBridgeInvariant('# Foo\n', '# Bar\n', {
- site: 'observer-b',
- docName: 'test/doc.md',
- origin: { context: { origin: 'TEST_ORIGIN' } },
- });
- throw new Error('expected throw');
- } catch (err) {
- expect(err).toBeInstanceOf(BridgeInvariantViolationError);
- const tyErr = err as BridgeInvariantViolationError;
- expect(tyErr.violation.site).toBe('observer-b');
- expect(tyErr.violation.docName).toBe('test/doc.md');
- expect(tyErr.violation.ytextSnapshot).toBe('# Foo\n');
- expect(tyErr.violation.fragmentMdSnapshot).toBe('# Bar\n');
- expect(tyErr.violation.unifiedDiff).toContain('# Foo');
- expect(tyErr.violation.unifiedDiff).toContain('# Bar');
- }
- });
-
- test('throw bypasses telemetry counter (no double-counted event)', () => {
- expect(() => {
- assertBridgeInvariant('# A\n', '# B\n', { site: 'observer-b' });
- }).toThrow();
- expect(getMetrics().bridgeInvariantViolations).toBe(0);
- expect(getMetrics().bridgeInvariantViolationsSuppressed).toBe(0);
- });
-
- test('suppressDevThrow:true emits + increments instead of throwing (persistence policy)', () => {
- const originalWarn = console.warn;
- const warnings: string[] = [];
- console.warn = (...args: unknown[]) => {
- warnings.push(args.map(String).join(' '));
- };
-
- try {
- expect(() => {
- assertBridgeInvariant('# A\n', '# B\n', {
- site: 'persistence',
- docName: 'doc-1',
- suppressDevThrow: true,
- });
- }).not.toThrow();
- } finally {
- console.warn = originalWarn;
- }
-
- expect(getMetrics().bridgeInvariantViolations).toBe(1);
- expect(warnings).toHaveLength(1);
- const event = JSON.parse(warnings[0] ?? '{}');
- expect(event.event).toBe('bridge-invariant-violation');
- expect(event.site).toBe('persistence');
- expect(event['doc.name']).toBe('doc-1');
- });
-
- test('suppressDevThrow:false still throws (default behavior, Observer B contract)', () => {
- expect(() => {
- assertBridgeInvariant('# A\n', '# B\n', {
- site: 'observer-b',
- suppressDevThrow: false,
- });
- }).toThrow(BridgeInvariantViolationError);
- expect(getMetrics().bridgeInvariantViolations).toBe(0);
- });
-});
-
-describe('assertBridgeInvariant — production emit path (rate-limited)', () => {
- let originalNodeEnv: string | undefined;
- beforeEach(() => {
- originalNodeEnv = process.env.NODE_ENV;
- process.env.NODE_ENV = 'production';
- });
- afterEach(() => {
- if (originalNodeEnv === undefined) delete process.env.NODE_ENV;
- else process.env.NODE_ENV = originalNodeEnv;
- });
-
- test('first violation in window emits + increments counter', () => {
- const originalWarn = console.warn;
- const warnings: string[] = [];
- console.warn = (...args: unknown[]) => {
- warnings.push(args.map(String).join(' '));
- };
-
- try {
- assertBridgeInvariant('# A\n', '# B\n', {
- site: 'observer-b',
- docName: 'doc-1',
- nowMs: 1000,
- });
- } finally {
- console.warn = originalWarn;
- }
-
- expect(getMetrics().bridgeInvariantViolations).toBe(1);
- expect(getMetrics().bridgeInvariantViolationsSuppressed).toBe(0);
- expect(warnings).toHaveLength(1);
- const event = JSON.parse(warnings[0] ?? '{}');
- expect(event.event).toBe('bridge-invariant-violation');
- expect(event.site).toBe('observer-b');
- expect(event['doc.name']).toBe('doc-1');
- });
-
- test('repeat violations within debounce window suppressed (counter increments suppressed)', () => {
- const originalWarn = console.warn;
- console.warn = () => {};
-
- try {
- assertBridgeInvariant('# A\n', '# B\n', {
- site: 'observer-b',
- docName: 'doc-1',
- nowMs: 1000,
- });
- assertBridgeInvariant('# A\n', '# C\n', {
- site: 'observer-b',
- docName: 'doc-1',
- nowMs: 1001,
- });
- assertBridgeInvariant('# A\n', '# D\n', {
- site: 'observer-b',
- docName: 'doc-1',
- nowMs: 1002,
- });
- } finally {
- console.warn = originalWarn;
- }
-
- expect(getMetrics().bridgeInvariantViolations).toBe(1);
- expect(getMetrics().bridgeInvariantViolationsSuppressed).toBe(2);
- });
-
- test('different (site, doc) tuples have independent debounce windows', () => {
- const originalWarn = console.warn;
- console.warn = () => {};
-
- try {
- assertBridgeInvariant('# A\n', '# B\n', {
- site: 'observer-b',
- docName: 'doc-1',
- nowMs: 1000,
- });
- assertBridgeInvariant('# A\n', '# B\n', {
- site: 'observer-b',
- docName: 'doc-2',
- nowMs: 1000,
- });
- assertBridgeInvariant('# A\n', '# B\n', {
- site: 'persistence',
- docName: 'doc-1',
- nowMs: 1000,
- });
- } finally {
- console.warn = originalWarn;
- }
-
- expect(getMetrics().bridgeInvariantViolations).toBe(3);
- expect(getMetrics().bridgeInvariantViolationsSuppressed).toBe(0);
- });
-
- test('emission past debounce window resets counter for the tuple', () => {
- const originalWarn = console.warn;
- console.warn = () => {};
-
- try {
- assertBridgeInvariant('# A\n', '# B\n', {
- site: 'observer-b',
- docName: 'doc-1',
- nowMs: 1000,
- });
- assertBridgeInvariant('# A\n', '# C\n', {
- site: 'observer-b',
- docName: 'doc-1',
- nowMs: 1000 + 70_000,
- });
- } finally {
- console.warn = originalWarn;
- }
-
- expect(getMetrics().bridgeInvariantViolations).toBe(2);
- expect(getMetrics().bridgeInvariantViolationsSuppressed).toBe(0);
- });
-
- test('OK_BRIDGE_VIOLATION_DEBOUNCE_S env var configures the debounce', () => {
- const originalWarn = console.warn;
- console.warn = () => {};
- process.env.OK_BRIDGE_VIOLATION_DEBOUNCE_S = '5';
-
- try {
- assertBridgeInvariant('# A\n', '# B\n', {
- site: 'observer-b',
- docName: 'doc-1',
- nowMs: 0,
- });
- assertBridgeInvariant('# A\n', '# C\n', {
- site: 'observer-b',
- docName: 'doc-1',
- nowMs: 2_000,
- });
- assertBridgeInvariant('# A\n', '# D\n', {
- site: 'observer-b',
- docName: 'doc-1',
- nowMs: 6_000,
- });
- } finally {
- console.warn = originalWarn;
- }
-
- expect(getMetrics().bridgeInvariantViolations).toBe(2);
- expect(getMetrics().bridgeInvariantViolationsSuppressed).toBe(1);
- });
-});
-
-describe('shouldEmitBridgeInvariantViolation — gate semantics', () => {
- test('first call returns true', () => {
- expect(shouldEmitBridgeInvariantViolation('observer-b', 'doc-1', 1000)).toBe(true);
- });
-
- test('repeat call inside window returns false', () => {
- shouldEmitBridgeInvariantViolation('observer-b', 'doc-1', 1000);
- expect(shouldEmitBridgeInvariantViolation('observer-b', 'doc-1', 1500)).toBe(false);
- });
-
- test('call after debounce expires returns true', () => {
- shouldEmitBridgeInvariantViolation('observer-b', 'doc-1', 1000);
- expect(shouldEmitBridgeInvariantViolation('observer-b', 'doc-1', 70_000)).toBe(true);
- });
-
- test('docName=undefined uses sentinel slot (separate from any named doc)', () => {
- expect(shouldEmitBridgeInvariantViolation('observer-b', undefined, 1000)).toBe(true);
- expect(shouldEmitBridgeInvariantViolation('observer-b', 'doc-1', 1000)).toBe(true);
- expect(shouldEmitBridgeInvariantViolation('observer-b', undefined, 1500)).toBe(false);
- expect(shouldEmitBridgeInvariantViolation('observer-b', 'doc-1', 1500)).toBe(false);
- });
-});
-
-describe('shouldEmitObserverAPathBFired — per-doc rate-limiter', () => {
- test('first call for a doc returns true', () => {
- expect(shouldEmitObserverAPathBFired('doc-1', 1000)).toBe(true);
- });
-
- test('repeat call inside window returns false', () => {
- shouldEmitObserverAPathBFired('doc-1', 1000);
- expect(shouldEmitObserverAPathBFired('doc-1', 1500)).toBe(false);
- });
-
- test('call after debounce expires returns true', () => {
- shouldEmitObserverAPathBFired('doc-1', 1000);
- expect(shouldEmitObserverAPathBFired('doc-1', 70_000)).toBe(true);
- });
-
- test('different docs have independent windows', () => {
- expect(shouldEmitObserverAPathBFired('doc-1', 1000)).toBe(true);
- expect(shouldEmitObserverAPathBFired('doc-2', 1000)).toBe(true);
- expect(shouldEmitObserverAPathBFired('doc-1', 1500)).toBe(false);
- expect(shouldEmitObserverAPathBFired('doc-2', 1500)).toBe(false);
- });
-
- test('docName=undefined uses __nodoc__ sentinel (distinct from any named doc)', () => {
- expect(shouldEmitObserverAPathBFired(undefined, 1000)).toBe(true);
- expect(shouldEmitObserverAPathBFired('doc-1', 1000)).toBe(true);
- expect(shouldEmitObserverAPathBFired(undefined, 1500)).toBe(false);
- expect(shouldEmitObserverAPathBFired('doc-1', 1500)).toBe(false);
- });
-
- test('emitObserverAPathBFired increments suppressed counter when rate-limited', () => {
- expect(emitObserverAPathBFired('doc-1', 1000)).toBe(true);
- expect(getMetrics().observerAPathBFiresSuppressed).toBe(0);
- expect(emitObserverAPathBFired('doc-1', 1500)).toBe(false);
- expect(getMetrics().observerAPathBFiresSuppressed).toBe(1);
- expect(emitObserverAPathBFired('doc-1', 2000)).toBe(false);
- expect(getMetrics().observerAPathBFiresSuppressed).toBe(2);
- });
-
- test('emitObserverAPathBFired returns true after window resets', () => {
- expect(emitObserverAPathBFired('doc-1', 1000)).toBe(true);
- expect(emitObserverAPathBFired('doc-1', 70_000)).toBe(true);
- expect(getMetrics().observerAPathBFiresSuppressed).toBe(0);
- });
-});
-
-describe('shouldEmitBridgeSplitBrainRederive — per-(site, doc) rate-limiter', () => {
- test('first call for a (site, doc) tuple returns true', () => {
- expect(shouldEmitBridgeSplitBrainRederive('post-merge', 'doc-1', 1000)).toBe(true);
- });
-
- test('repeat call inside window returns false', () => {
- shouldEmitBridgeSplitBrainRederive('post-merge', 'doc-1', 1000);
- expect(shouldEmitBridgeSplitBrainRederive('post-merge', 'doc-1', 1500)).toBe(false);
- });
-
- test('call after debounce expires returns true', () => {
- shouldEmitBridgeSplitBrainRederive('post-merge', 'doc-1', 1000);
- expect(shouldEmitBridgeSplitBrainRederive('post-merge', 'doc-1', 70_000)).toBe(true);
- });
-
- test('sites have independent windows for the same doc', () => {
- expect(shouldEmitBridgeSplitBrainRederive('identity-gate', 'doc-1', 1000)).toBe(true);
- expect(shouldEmitBridgeSplitBrainRederive('post-merge', 'doc-1', 1000)).toBe(true);
- expect(shouldEmitBridgeSplitBrainRederive('identity-gate', 'doc-1', 1500)).toBe(false);
- expect(shouldEmitBridgeSplitBrainRederive('post-merge', 'doc-1', 1500)).toBe(false);
- });
-
- test('different docs have independent windows', () => {
- expect(shouldEmitBridgeSplitBrainRederive('post-merge', 'doc-1', 1000)).toBe(true);
- expect(shouldEmitBridgeSplitBrainRederive('post-merge', 'doc-2', 1000)).toBe(true);
- expect(shouldEmitBridgeSplitBrainRederive('post-merge', 'doc-1', 1500)).toBe(false);
- });
-
- test('docName=undefined uses __nodoc__ sentinel (distinct from any named doc)', () => {
- expect(shouldEmitBridgeSplitBrainRederive('post-merge', undefined, 1000)).toBe(true);
- expect(shouldEmitBridgeSplitBrainRederive('post-merge', 'doc-1', 1000)).toBe(true);
- expect(shouldEmitBridgeSplitBrainRederive('post-merge', undefined, 1500)).toBe(false);
- });
-
- test('emitBridgeSplitBrainRederive increments suppressed counter when rate-limited', () => {
- expect(emitBridgeSplitBrainRederive('post-merge', 'doc-1', 1000)).toBe(true);
- expect(getMetrics().bridgeSplitBrainRederivesSuppressed).toBe(0);
- expect(emitBridgeSplitBrainRederive('post-merge', 'doc-1', 1500)).toBe(false);
- expect(getMetrics().bridgeSplitBrainRederivesSuppressed).toBe(1);
- expect(emitBridgeSplitBrainRederive('post-merge', 'doc-1', 2000)).toBe(false);
- expect(getMetrics().bridgeSplitBrainRederivesSuppressed).toBe(2);
- });
-
- test('emitBridgeSplitBrainRederive returns true after window resets', () => {
- expect(emitBridgeSplitBrainRederive('post-merge', 'doc-1', 1000)).toBe(true);
- expect(emitBridgeSplitBrainRederive('post-merge', 'doc-1', 70_000)).toBe(true);
- expect(getMetrics().bridgeSplitBrainRederivesSuppressed).toBe(0);
- });
-});
-
-describe('bridge-invariant-violation payload redaction (OK_TELEMETRY_VERBOSE opt-in)', () => {
- let originalNodeEnv: string | undefined;
- let originalVerbose: string | undefined;
-
- beforeEach(() => {
- originalNodeEnv = process.env.NODE_ENV;
- originalVerbose = process.env.OK_TELEMETRY_VERBOSE;
- process.env.NODE_ENV = 'production';
- });
-
- afterEach(() => {
- if (originalNodeEnv === undefined) delete process.env.NODE_ENV;
- else process.env.NODE_ENV = originalNodeEnv;
- if (originalVerbose === undefined) delete process.env.OK_TELEMETRY_VERBOSE;
- else process.env.OK_TELEMETRY_VERBOSE = originalVerbose;
- });
-
- function emitOnce(ytextSnapshot: string, fragmentSnapshot: string): Record {
- const originalWarn = console.warn;
- const warnings: string[] = [];
- console.warn = (...args: unknown[]) => {
- warnings.push(args.map(String).join(' '));
- };
- try {
- assertBridgeInvariant(ytextSnapshot, fragmentSnapshot, {
- site: 'observer-b',
- docName: 'doc-1',
- nowMs: 1000,
- });
- } finally {
- console.warn = originalWarn;
- }
- expect(warnings).toHaveLength(1);
- return JSON.parse(warnings[0] ?? '{}') as Record;
- }
-
- test('default emit redacts raw diff; payload carries length + FNV hash only', () => {
- const event = emitOnce('# user-typed body\n', '# canonical fragment body\n');
- expect(event.event).toBe('bridge-invariant-violation');
- expect(event.redacted).toBe(true);
- expect('diff' in event).toBe(false);
- expect(typeof event.ytextHash).toBe('string');
- expect(typeof event.fragmentHash).toBe('string');
- expect(event.ytextLen).toBe('# user-typed body\n'.length);
- expect(event.fragmentLen).toBe('# canonical fragment body\n'.length);
- const serialized = JSON.stringify(event);
- expect(serialized).not.toContain('user-typed body');
- expect(serialized).not.toContain('canonical fragment body');
- });
-
- test('OK_TELEMETRY_VERBOSE=1 includes the truncated diff (opt-in posture)', () => {
- process.env.OK_TELEMETRY_VERBOSE = '1';
- const event = emitOnce('# user-typed body\n', '# canonical fragment body\n');
- expect(event.redacted).toBe(false);
- expect(typeof event.diff).toBe('string');
- expect(String(event.diff)).toContain('user-typed body');
- expect(String(event.diff)).toContain('canonical fragment body');
- expect(typeof event.ytextHash).toBe('string');
- });
-
- test('OK_TELEMETRY_VERBOSE=0 stays redacted (only "1" enables verbose)', () => {
- process.env.OK_TELEMETRY_VERBOSE = '0';
- const event = emitOnce('# user-typed body\n', '# canonical fragment body\n');
- expect(event.redacted).toBe(true);
- expect('diff' in event).toBe(false);
- });
-
- test('FNV-1a hash is stable for the same input across calls', () => {
- const a = emitOnce('# stable A\n', '# stable B\n');
- __resetBridgeWatchdogForTests();
- const b = emitOnce('# stable A\n', '# stable B\n');
- expect(a.ytextHash).toBe(b.ytextHash);
- expect(a.fragmentHash).toBe(b.fragmentHash);
- });
-
- test('different inputs produce different hashes (collision probability is 1/2^32)', () => {
- const a = emitOnce('# alpha\n', '# beta\n');
- __resetBridgeWatchdogForTests();
- const b = emitOnce('# gamma\n', '# delta\n');
- expect(a.ytextHash).not.toBe(b.ytextHash);
- expect(a.fragmentHash).not.toBe(b.fragmentHash);
- });
-});
-
-describe('bridge-tolerance-applied event (FR-41)', () => {
- function captureWarn(fn: () => void): string[] {
- const originalWarn = console.warn;
- const warnings: string[] = [];
- console.warn = (...args: unknown[]) => {
- warnings.push(args.map(String).join(' '));
- };
- try {
- fn();
- } finally {
- console.warn = originalWarn;
- }
- return warnings;
- }
-
- test('CRLF tolerance fires bridge-tolerance-applied with class=crlf', () => {
- const warnings = captureWarn(() => {
- assertBridgeInvariant('# Hello\r\n', '# Hello\n', { site: 'observer-b' });
- });
- const events = warnings.map((w) => JSON.parse(w));
- const toleranceEvents = events.filter((e) => e.event === 'bridge-tolerance-applied');
- expect(toleranceEvents.length).toBeGreaterThanOrEqual(1);
- expect(toleranceEvents.some((e) => e.class === 'crlf')).toBe(true);
- expect(getMetrics().bridgeToleranceApplied.crlf).toBeGreaterThanOrEqual(1);
- });
-
- test('BOM tolerance fires class=bom', () => {
- const warnings = captureWarn(() => {
- assertBridgeInvariant('# Hello\n', '# Hello\n', { site: 'observer-b' });
- });
- const events = warnings.map((w) => JSON.parse(w));
- const toleranceEvents = events.filter((e) => e.event === 'bridge-tolerance-applied');
- expect(toleranceEvents.some((e) => e.class === 'bom')).toBe(true);
- expect(getMetrics().bridgeToleranceApplied.bom).toBeGreaterThanOrEqual(1);
- });
-
- test('byte-equal inputs do NOT emit any tolerance event', () => {
- const warnings = captureWarn(() => {
- assertBridgeInvariant('# Hello\n', '# Hello\n', { site: 'observer-b' });
- });
- expect(warnings).toHaveLength(0);
- expect(getMetrics().bridgeToleranceApplied).toEqual({});
- });
-
- test('multiple tolerance classes in one input emit one event per class', () => {
- const warnings = captureWarn(() => {
- assertBridgeInvariant('# Hello \r\n', '# Hello\n', { site: 'observer-b' });
- });
- const events = warnings.map((w) => JSON.parse(w));
- const toleranceEvents = events.filter((e) => e.event === 'bridge-tolerance-applied');
- const classes = new Set(toleranceEvents.map((e) => e.class));
- expect(classes.has('bom')).toBe(true);
- expect(classes.has('crlf')).toBe(true);
- expect(classes.has('trailing-whitespace')).toBe(true);
- });
-
- test('event payload is bounded-cardinality: only event + class + site fields', () => {
- const warnings = captureWarn(() => {
- assertBridgeInvariant('# Hello\r\n', '# Hello\n', { site: 'observer-b' });
- });
- const events = warnings.map((w) => JSON.parse(w));
- const toleranceEvents = events.filter((e) => e.event === 'bridge-tolerance-applied');
- for (const event of toleranceEvents) {
- const keys = Object.keys(event).sort();
- expect(keys).toEqual(['class', 'event', 'site']);
- expect(typeof event.class).toBe('string');
- expect(typeof event.site).toBe('string');
- expect(event.event).toBe('bridge-tolerance-applied');
- }
- });
-
- test('rate-limiter suppresses repeat emissions per class within window', () => {
- captureWarn(() => {
- assertBridgeInvariant('# A\r\n', '# A\n', {
- site: 'observer-b',
- nowMs: 1000,
- });
- });
- const warnings = captureWarn(() => {
- assertBridgeInvariant('# B\r\n', '# B\n', {
- site: 'observer-b',
- nowMs: 1500,
- });
- });
- const events = warnings.map((w) => JSON.parse(w));
- const crlfEvents = events.filter(
- (e) => e.event === 'bridge-tolerance-applied' && e.class === 'crlf',
- );
- expect(crlfEvents).toHaveLength(0);
- });
-
- test('rate-limiter resets after debounce window expires', () => {
- captureWarn(() => {
- assertBridgeInvariant('# A\r\n', '# A\n', {
- site: 'observer-b',
- nowMs: 1000,
- });
- });
- const warnings = captureWarn(() => {
- assertBridgeInvariant('# B\r\n', '# B\n', {
- site: 'observer-b',
- nowMs: 70_000,
- });
- });
- const events = warnings.map((w) => JSON.parse(w));
- expect(events.some((e) => e.event === 'bridge-tolerance-applied' && e.class === 'crlf')).toBe(
- true,
- );
- });
-
- test('different classes have independent debounce windows', () => {
- const warnings = captureWarn(() => {
- assertBridgeInvariant('# A\r\n', '# A\n', {
- site: 'observer-b',
- nowMs: 1000,
- });
- });
- const events = warnings.map((w) => JSON.parse(w));
- const classes = new Set(
- events.filter((e) => e.event === 'bridge-tolerance-applied').map((e) => e.class),
- );
- expect(classes.has('bom')).toBe(true);
- expect(classes.has('crlf')).toBe(true);
- });
-});
-
-describe('tolerance-telemetry file hook receives the full un-rate-limited list', () => {
- let fires: ToleranceFireRecord[] = [];
-
- beforeEach(() => {
- fires = [];
- setToleranceTelemetryHook((record) => {
- fires.push(record);
- });
- });
-
- afterEach(() => {
- setToleranceTelemetryHook(null);
- });
-
- test('hook fires on both calls while console/metric emit once', () => {
- const originalWarn = console.warn;
- const warnings: string[] = [];
- console.warn = (...args: unknown[]) => {
- warnings.push(args.map(String).join(' '));
- };
-
- try {
- assertBridgeInvariant('# Hello\r\n', '# Hello\n', {
- site: 'observer-b',
- docName: 'doc-1',
- nowMs: 1000,
- });
- assertBridgeInvariant('# Hello\r\n', '# Hello\n', {
- site: 'observer-b',
- docName: 'doc-1',
- nowMs: 1500,
- });
- } finally {
- console.warn = originalWarn;
- }
-
- expect(fires.filter((f) => f.className === 'crlf')).toHaveLength(2);
-
- const crlfWarnings = warnings
- .map((w) => JSON.parse(w))
- .filter((e) => e.event === 'bridge-tolerance-applied' && e.class === 'crlf');
- expect(crlfWarnings).toHaveLength(1);
- expect(getMetrics().bridgeToleranceApplied.crlf).toBe(1);
- });
-});
-
-describe('shouldEmitBridgeToleranceApplied — gate semantics', () => {
- test('first call per (site, class) returns true', () => {
- expect(shouldEmitBridgeToleranceApplied('observer-b', 'crlf', 1000)).toBe(true);
- });
-
- test('repeat call inside window returns false', () => {
- shouldEmitBridgeToleranceApplied('observer-b', 'crlf', 1000);
- expect(shouldEmitBridgeToleranceApplied('observer-b', 'crlf', 1500)).toBe(false);
- });
-
- test('different classes have independent windows', () => {
- expect(shouldEmitBridgeToleranceApplied('observer-b', 'crlf', 1000)).toBe(true);
- expect(shouldEmitBridgeToleranceApplied('observer-b', 'bom', 1000)).toBe(true);
- expect(shouldEmitBridgeToleranceApplied('observer-b', 'crlf', 1500)).toBe(false);
- expect(shouldEmitBridgeToleranceApplied('observer-b', 'bom', 1500)).toBe(false);
- });
-
- test('different sites for the same class have independent windows', () => {
- expect(shouldEmitBridgeToleranceApplied('observer-b', 'crlf', 1000)).toBe(true);
- expect(shouldEmitBridgeToleranceApplied('persistence', 'crlf', 1500)).toBe(true);
- expect(shouldEmitBridgeToleranceApplied('observer-b', 'crlf', 1700)).toBe(false);
- expect(shouldEmitBridgeToleranceApplied('persistence', 'crlf', 1900)).toBe(false);
- });
-
- test('post-debounce-expiry call returns true', () => {
- shouldEmitBridgeToleranceApplied('observer-b', 'crlf', 1000);
- expect(shouldEmitBridgeToleranceApplied('observer-b', 'crlf', 70_000)).toBe(true);
- });
-});
-
-describe('shouldEmitBridgeInvariantViolation — lazy prune of past-window entries', () => {
- test('grows linearly below the prune threshold', () => {
- for (let i = 0; i < 1023; i++) {
- shouldEmitBridgeInvariantViolation('observer-b', `doc-${i}`, 0);
- }
- expect(__getViolationRateTupleCountForTests()).toBe(1023);
- });
-
- test('past-window entries reclaim when threshold is exceeded', () => {
- for (let i = 0; i < 1024; i++) {
- shouldEmitBridgeInvariantViolation('observer-b', `doc-${i}`, 0);
- }
- expect(__getViolationRateTupleCountForTests()).toBe(1024);
-
- shouldEmitBridgeInvariantViolation('observer-b', 'doc-new', 70_000);
- expect(__getViolationRateTupleCountForTests()).toBe(1);
- });
-
- test('in-window entries are preserved during prune (mixed window state)', () => {
- for (let i = 0; i < 1023; i++) {
- shouldEmitBridgeInvariantViolation('observer-b', `doc-old-${i}`, 0);
- }
- shouldEmitBridgeInvariantViolation('observer-b', 'doc-fresh', 30_000);
- expect(__getViolationRateTupleCountForTests()).toBe(1024);
-
- shouldEmitBridgeInvariantViolation('observer-b', 'doc-new', 70_000);
- expect(__getViolationRateTupleCountForTests()).toBe(2);
- expect(shouldEmitBridgeInvariantViolation('observer-b', 'doc-fresh', 71_000)).toBe(false);
- });
-
- test('threshold boundary: exactly 1023 entries does not trigger prune', () => {
- for (let i = 0; i < 1023; i++) {
- shouldEmitBridgeInvariantViolation('observer-b', `doc-${i}`, 0);
- }
- shouldEmitBridgeInvariantViolation('observer-b', 'doc-1024th', 70_000);
- expect(__getViolationRateTupleCountForTests()).toBe(1024);
- });
-
- test('all-in-window: prune walks but reclaims nothing (documents conditional bound)', () => {
- for (let i = 0; i < 1024; i++) {
- shouldEmitBridgeInvariantViolation('observer-b', `doc-${i}`, 1_000);
- }
- expect(__getViolationRateTupleCountForTests()).toBe(1024);
-
- shouldEmitBridgeInvariantViolation('observer-b', 'doc-new', 2_000);
- expect(__getViolationRateTupleCountForTests()).toBe(1025);
- });
-});
-
-describe('shouldEmitBridgeSplitBrainRederive — lazy prune of past-window entries', () => {
- test('grows linearly below the prune threshold', () => {
- for (let i = 0; i < 1023; i++) {
- shouldEmitBridgeSplitBrainRederive('post-merge', `doc-${i}`, 0);
- }
- expect(__getSplitBrainRateTupleCountForTests()).toBe(1023);
- });
-
- test('past-window entries reclaim when threshold is exceeded', () => {
- for (let i = 0; i < 1024; i++) {
- shouldEmitBridgeSplitBrainRederive('post-merge', `doc-${i}`, 0);
- }
- expect(__getSplitBrainRateTupleCountForTests()).toBe(1024);
-
- shouldEmitBridgeSplitBrainRederive('post-merge', 'doc-new', 70_000);
- expect(__getSplitBrainRateTupleCountForTests()).toBe(1);
- });
-
- test('in-window entries are preserved during prune (mixed window state)', () => {
- for (let i = 0; i < 1023; i++) {
- shouldEmitBridgeSplitBrainRederive('post-merge', `doc-old-${i}`, 0);
- }
- shouldEmitBridgeSplitBrainRederive('post-merge', 'doc-fresh', 30_000);
- expect(__getSplitBrainRateTupleCountForTests()).toBe(1024);
-
- shouldEmitBridgeSplitBrainRederive('post-merge', 'doc-new', 70_000);
- expect(__getSplitBrainRateTupleCountForTests()).toBe(2);
- expect(shouldEmitBridgeSplitBrainRederive('post-merge', 'doc-fresh', 71_000)).toBe(false);
- });
-
- test('threshold boundary: exactly 1023 entries does not trigger prune', () => {
- for (let i = 0; i < 1023; i++) {
- shouldEmitBridgeSplitBrainRederive('post-merge', `doc-${i}`, 0);
- }
- shouldEmitBridgeSplitBrainRederive('post-merge', 'doc-1024th', 70_000);
- expect(__getSplitBrainRateTupleCountForTests()).toBe(1024);
- });
-
- test('all-in-window: prune walks but reclaims nothing (documents conditional bound)', () => {
- for (let i = 0; i < 1024; i++) {
- shouldEmitBridgeSplitBrainRederive('post-merge', `doc-${i}`, 1_000);
- }
- expect(__getSplitBrainRateTupleCountForTests()).toBe(1024);
-
- shouldEmitBridgeSplitBrainRederive('post-merge', 'doc-new', 2_000);
- expect(__getSplitBrainRateTupleCountForTests()).toBe(1025);
- });
-
- test('all three sites for the same doc occupy distinct keys (each counted)', () => {
- shouldEmitBridgeSplitBrainRederive('post-merge', 'doc-1', 0);
- shouldEmitBridgeSplitBrainRederive('identity-gate', 'doc-1', 0);
- shouldEmitBridgeSplitBrainRederive('error-recovery', 'doc-1', 0);
- expect(__getSplitBrainRateTupleCountForTests()).toBe(3);
- });
-});
-
-describe('assertBridgeInvariant — return value reflects normalize-equality', () => {
- test('byte-equal inputs return true', () => {
- expect(assertBridgeInvariant('# Hello\n', '# Hello\n', { site: 'observer-b' })).toBe(true);
- });
-
- test('tolerance-equivalent inputs return true (CRLF case)', () => {
- expect(assertBridgeInvariant('# Hello\r\n', '# Hello\n', { site: 'observer-b' })).toBe(true);
- });
-
- test('tolerance-equivalent inputs return true (BOM case)', () => {
- expect(assertBridgeInvariant('# Hello\n', '# Hello\n', { site: 'observer-b' })).toBe(true);
- });
-
- test('non-equivalent inputs with suppressDevThrow return false (no throw)', () => {
- const originalWarn = console.warn;
- console.warn = () => {};
- try {
- const result = assertBridgeInvariant('# Foo\n', '# Bar\n', {
- site: 'persistence',
- docName: 'doc-x',
- suppressDevThrow: true,
- });
- expect(result).toBe(false);
- } finally {
- console.warn = originalWarn;
- }
- });
-
- test('rate-limited (suppressed) emission still returns false', () => {
- const originalNodeEnv = process.env.NODE_ENV;
- process.env.NODE_ENV = 'production';
- const originalWarn = console.warn;
- console.warn = () => {};
- try {
- const r1 = assertBridgeInvariant('# A\n', '# B\n', {
- site: 'observer-b',
- docName: 'doc-1',
- nowMs: 1000,
- });
- const r2 = assertBridgeInvariant('# A\n', '# C\n', {
- site: 'observer-b',
- docName: 'doc-1',
- nowMs: 1500,
- });
- expect(r1).toBe(false);
- expect(r2).toBe(false);
- } finally {
- console.warn = originalWarn;
- if (originalNodeEnv === undefined) delete process.env.NODE_ENV;
- else process.env.NODE_ENV = originalNodeEnv;
- }
- });
-});
-
-describe('assertBridgeInvariant — parse-equivalence fallback (canonicalizeBody opt)', () => {
- const mgr = new MarkdownManager({ extensions: sharedExtensions });
- const canonicalizeBody = (body: string): string => mgr.serialize(mgr.parseWithFallback(body));
-
- const LAZY_RAW = '- item one continues here,\nlazily on the next line.\n';
-
- test('normalize-divergent but parse-equivalent inputs are tolerated (returns true, no throw)', () => {
- const canonical = canonicalizeBody(LAZY_RAW);
- expect(canonical).not.toBe(LAZY_RAW);
- expect(normalizeBridge(canonical)).not.toBe(normalizeBridge(LAZY_RAW));
-
- const result = assertBridgeInvariant(LAZY_RAW, canonical, {
- site: 'persistence',
- docName: 'lazy-doc',
- canonicalizeBody,
- });
- expect(result).toBe(true);
- expect(getMetrics().bridgeInvariantViolations).toBe(0);
- expect(getMetrics().bridgeInvariantViolationsSuppressed).toBe(0);
- });
-
- test('tolerated parse-equivalent pair emits bridge-tolerance-applied with the parse-equivalence class', () => {
- const canonical = canonicalizeBody(LAZY_RAW);
- const originalWarn = console.warn;
- const warnings: string[] = [];
- console.warn = (...args: unknown[]) => {
- warnings.push(args.map(String).join(' '));
- };
- try {
- assertBridgeInvariant(LAZY_RAW, canonical, {
- site: 'persistence',
- docName: 'lazy-doc',
- canonicalizeBody,
- });
- } finally {
- console.warn = originalWarn;
- }
- const toleranceEvents = warnings
- .map((w) => {
- try {
- return JSON.parse(w) as Record;
- } catch {
- return null;
- }
- })
- .filter((e): e is Record => e?.event === 'bridge-tolerance-applied');
- expect(toleranceEvents.map((e) => e.class)).toContain('parse-equivalence');
- });
-
- test('genuinely divergent inputs still throw with canonicalizeBody provided', () => {
- expect(() => {
- assertBridgeInvariant(LAZY_RAW, '# Completely different fragment\n', {
- site: 'observer-b',
- docName: 'diverged-doc',
- canonicalizeBody,
- });
- }).toThrow(BridgeInvariantViolationError);
- });
-
- test('frontmatter divergence is not bridged by body parse-equivalence', () => {
- const left = `---\ntitle: A\n---\n\n${LAZY_RAW}`;
- const right = `---\ntitle: B\n---\n\n${canonicalizeBody(LAZY_RAW)}`;
- expect(() => {
- assertBridgeInvariant(left, right, {
- site: 'observer-b',
- docName: 'fm-diverged-doc',
- canonicalizeBody,
- });
- }).toThrow(BridgeInvariantViolationError);
- });
-
- test('without canonicalizeBody the strict normalize-only behavior is preserved', () => {
- const canonical = canonicalizeBody(LAZY_RAW);
- expect(() => {
- assertBridgeInvariant(LAZY_RAW, canonical, {
- site: 'observer-b',
- docName: 'lazy-doc-strict',
- });
- }).toThrow(BridgeInvariantViolationError);
- });
-});
diff --git a/packages/server/src/bridge-watchdog.ts b/packages/server/src/bridge-watchdog.ts
deleted file mode 100644
index f995d450b..000000000
--- a/packages/server/src/bridge-watchdog.ts
+++ /dev/null
@@ -1,308 +0,0 @@
-/**
- * Lives in its own module because precedent #13(b) bans wall-clock SCHEDULING (`setTimeout`,
- * `setInterval`) in `server-observers.ts` — see `bridge-no-wallclock.test.ts` for the enforced
- * gate's `FORBIDDEN` regex array.
- */
-
-import type { MarkdownManager } from '@inkeep/open-knowledge-core';
-import {
- type BridgeInvariantSite,
- type BridgeInvariantViolation,
- BridgeInvariantViolationError,
- type BridgeToleranceSignal,
- detectAppliedToleranceClasses,
- emitToleranceFire,
- isParseEquivalentBridge,
- locateBridgeDivergence,
- normalizeBridge,
- PARSE_EQUIVALENCE_TOLERANCE,
- toBridgeInvariantLog,
-} from '@inkeep/open-knowledge-core';
-import { getLogger } from './logger.ts';
-import {
- incrementBridgeInvariantViolations,
- incrementBridgeInvariantViolationsSuppressed,
- incrementBridgeSplitBrainRederivesSuppressed,
- incrementBridgeToleranceApplied,
- incrementObserverAPathBFiresSuppressed,
-} from './metrics.ts';
-
-const log = getLogger('bridge-watchdog');
-
-const DEFAULT_DEBOUNCE_S = 60;
-
-const lastEmitMs = new Map();
-
-const MAX_VIOLATION_RATE_TUPLES = 1024;
-
-const lastToleranceEmitMs = new Map();
-
-const lastPathBEmitMs = new Map();
-
-export type BridgeSplitBrainSite =
- | 'identity-gate'
- | 'post-merge'
- | 'error-recovery'
- | 'duplication-guard';
-
-const lastSplitBrainEmitMs = new Map();
-
-function toleranceRateKey(site: BridgeInvariantSite, cls: BridgeToleranceSignal): string {
- return `${site}::${cls}`;
-}
-
-function readDebounceMs(): number {
- const raw = process.env.OK_BRIDGE_VIOLATION_DEBOUNCE_S;
- if (raw === undefined) return DEFAULT_DEBOUNCE_S * 1000;
- const parsed = Number.parseInt(raw, 10);
- if (!Number.isFinite(parsed) || parsed <= 0) return DEFAULT_DEBOUNCE_S * 1000;
- return parsed * 1000;
-}
-
-function rateKey(site: BridgeInvariantSite, docName: string | undefined): string {
- return `${site}::${docName ?? '__nodoc__'}`;
-}
-
-export function shouldEmitBridgeInvariantViolation(
- site: BridgeInvariantSite,
- docName: string | undefined,
- nowMs: number = Date.now(),
-): boolean {
- const key = rateKey(site, docName);
- const last = lastEmitMs.get(key);
- const debounceMs = readDebounceMs();
- if (last !== undefined && nowMs - last < debounceMs) return false;
- if (lastEmitMs.size >= MAX_VIOLATION_RATE_TUPLES) {
- for (const [k, lastMs] of lastEmitMs) {
- if (nowMs - lastMs >= debounceMs) lastEmitMs.delete(k);
- }
- }
- lastEmitMs.set(key, nowMs);
- return true;
-}
-
-export function shouldEmitBridgeToleranceApplied(
- site: BridgeInvariantSite,
- toleranceClass: BridgeToleranceSignal,
- nowMs: number = Date.now(),
-): boolean {
- const key = toleranceRateKey(site, toleranceClass);
- const last = lastToleranceEmitMs.get(key);
- const debounceMs = readDebounceMs();
- if (last !== undefined && nowMs - last < debounceMs) return false;
- lastToleranceEmitMs.set(key, nowMs);
- return true;
-}
-
-export function shouldEmitObserverAPathBFired(
- docName: string | undefined,
- nowMs: number = Date.now(),
-): boolean {
- const key = docName ?? '__nodoc__';
- const last = lastPathBEmitMs.get(key);
- const debounceMs = readDebounceMs();
- if (last !== undefined && nowMs - last < debounceMs) return false;
- if (lastPathBEmitMs.size >= MAX_VIOLATION_RATE_TUPLES) {
- for (const [k, lastMs] of lastPathBEmitMs) {
- if (nowMs - lastMs >= debounceMs) lastPathBEmitMs.delete(k);
- }
- }
- lastPathBEmitMs.set(key, nowMs);
- return true;
-}
-
-export function emitObserverAPathBFired(docName: string | undefined, nowMs?: number): boolean {
- const shouldEmit = shouldEmitObserverAPathBFired(docName, nowMs);
- if (!shouldEmit) {
- incrementObserverAPathBFiresSuppressed();
- } else {
- log.debug(
- { docName },
- '[bridge-watchdog] Observer A Path B fired (slow-path Y.Text divergence merge)',
- );
- }
- return shouldEmit;
-}
-
-export function shouldEmitBridgeSplitBrainRederive(
- site: BridgeSplitBrainSite,
- docName: string | undefined,
- nowMs: number = Date.now(),
-): boolean {
- const key = `${site}::${docName ?? '__nodoc__'}`;
- const last = lastSplitBrainEmitMs.get(key);
- const debounceMs = readDebounceMs();
- if (last !== undefined && nowMs - last < debounceMs) return false;
- if (lastSplitBrainEmitMs.size >= MAX_VIOLATION_RATE_TUPLES) {
- for (const [k, lastMs] of lastSplitBrainEmitMs) {
- if (nowMs - lastMs >= debounceMs) lastSplitBrainEmitMs.delete(k);
- }
- }
- lastSplitBrainEmitMs.set(key, nowMs);
- return true;
-}
-
-export function emitBridgeSplitBrainRederive(
- site: BridgeSplitBrainSite,
- docName: string | undefined,
- nowMs?: number,
-): boolean {
- const shouldEmit = shouldEmitBridgeSplitBrainRederive(site, docName, nowMs);
- if (!shouldEmit) {
- incrementBridgeSplitBrainRederivesSuppressed();
- } else {
- log.debug({ site, docName }, '[bridge-watchdog] split-brain re-derive detected');
- }
- return shouldEmit;
-}
-
-export function __resetBridgeWatchdogForTests(): void {
- lastEmitMs.clear();
- lastToleranceEmitMs.clear();
- lastPathBEmitMs.clear();
- lastSplitBrainEmitMs.clear();
-}
-
-export function __getViolationRateTupleCountForTests(): number {
- return lastEmitMs.size;
-}
-
-export function __getSplitBrainRateTupleCountForTests(): number {
- return lastSplitBrainEmitMs.size;
-}
-
-export function shouldThrowOnBridgeInvariantViolation(
- env: NodeJS.ProcessEnv = process.env,
-): boolean {
- return env.NODE_ENV === 'test' || env.OK_BRIDGE_THROW_ON_VIOLATION === '1';
-}
-
-type DocParseSurface = Pick<
- NonNullable[1]>,
- 'resolveEmbed' | 'resolveSize'
-> & { docName?: string };
-
-export function createDocCanonicalizer(
- mdManager: MarkdownManager,
- opts: DocParseSurface,
-): (body: string) => string {
- const parseOpts =
- opts.resolveEmbed && opts.docName
- ? {
- resolveEmbed: opts.resolveEmbed,
- resolveSize: opts.resolveSize,
- sourcePath: opts.docName,
- }
- : undefined;
- return (body: string): string =>
- mdManager.serialize(mdManager.parseWithFallback(body, parseOpts));
-}
-
-interface AssertBridgeInvariantOpts {
- site: BridgeInvariantSite;
- docName?: string;
- origin?: unknown;
- nowMs?: number;
- suppressDevThrow?: boolean;
- /**
- * Parse-equivalence fallback: when inputs diverge beyond every `normalizeBridge` byte class,
- * canonicalize the ytext body through the caller's own parse-serialize pipeline and accept a
- * match, since the fragment then IS `parse(ytext)` (precedent #38). Bind the doc's own options.
- */
- canonicalizeBody?: (body: string) => string;
-}
-
-export function assertBridgeInvariant(
- ytextSnapshot: string,
- fragmentMdSnapshot: string,
- opts: AssertBridgeInvariantOpts,
-): boolean {
- const reportTolerated = (classes: readonly BridgeToleranceSignal[]): void => {
- const emittedClasses = classes.filter((cls) =>
- shouldEmitBridgeToleranceApplied(opts.site, cls, opts.nowMs),
- );
- if (classes.length > 0) {
- emitToleranceFire(classes, ytextSnapshot, fragmentMdSnapshot, opts.docName);
- }
- if (emittedClasses.length > 0) {
- log.debug(
- { site: opts.site, docName: opts.docName, classes: emittedClasses },
- '[bridge-watchdog] tolerance classes applied',
- );
- }
- for (const cls of emittedClasses) {
- incrementBridgeToleranceApplied(cls);
- console.warn(
- JSON.stringify({
- event: 'bridge-tolerance-applied',
- site: opts.site,
- class: cls,
- }),
- );
- }
- };
-
- const ytextNorm = normalizeBridge(ytextSnapshot);
- const fragNorm = normalizeBridge(fragmentMdSnapshot);
- if (ytextNorm === fragNorm) {
- if (ytextSnapshot !== fragmentMdSnapshot) {
- reportTolerated(detectAppliedToleranceClasses(ytextSnapshot, fragmentMdSnapshot));
- }
- return true;
- }
-
- if (
- opts.canonicalizeBody &&
- isParseEquivalentBridge(ytextSnapshot, fragmentMdSnapshot, opts.canonicalizeBody)
- ) {
- reportTolerated([
- ...detectAppliedToleranceClasses(ytextSnapshot, fragmentMdSnapshot),
- PARSE_EQUIVALENCE_TOLERANCE,
- ]);
- return true;
- }
-
- const violation: BridgeInvariantViolation = {
- site: opts.site,
- origin: opts.origin,
- docName: opts.docName,
- ytextSnapshot,
- fragmentMdSnapshot,
- unifiedDiff: ` ytext: ${ytextNorm.slice(0, 300)}\n frag: ${fragNorm.slice(0, 300)}`,
- stack: new Error().stack,
- };
-
- if (shouldThrowOnBridgeInvariantViolation() && !opts.suppressDevThrow) {
- throw new BridgeInvariantViolationError(violation);
- }
-
- const shouldEmit = shouldEmitBridgeInvariantViolation(opts.site, opts.docName, opts.nowMs);
- if (!shouldEmit) {
- incrementBridgeInvariantViolationsSuppressed();
- return false;
- }
- incrementBridgeInvariantViolations();
- const divergence = locateBridgeDivergence(ytextNorm, fragNorm);
- log.warn(
- {
- site: opts.site,
- docName: opts.docName,
- ytextBytes: ytextSnapshot.length,
- fragmentBytes: fragmentMdSnapshot.length,
- normalizedYtextBytes: ytextNorm.length,
- normalizedFragmentBytes: fragNorm.length,
- firstDivergenceIndex: divergence.index,
- normalizedLine: divergence.normalizedLine,
- normalizedColumn: divergence.normalizedColumn,
- ytextLineKind: divergence.ytextLineKind,
- fragmentLineKind: divergence.fragmentLineKind,
- precedingLineKind: divergence.precedingLineKind,
- },
- `[bridge-watchdog] bridge invariant violation at ${opts.site}${
- opts.docName ? ` for '${opts.docName}'` : ''
- }`,
- );
- const verbose = process.env.OK_TELEMETRY_VERBOSE === '1';
- console.warn(JSON.stringify(toBridgeInvariantLog(violation, { verbose })));
- return false;
-}
diff --git a/packages/server/src/comments/anchor.test.ts b/packages/server/src/comments/anchor.test.ts
index 819feaa92..d809ab647 100644
--- a/packages/server/src/comments/anchor.test.ts
+++ b/packages/server/src/comments/anchor.test.ts
@@ -1,3 +1,4 @@
+import { contextMatchScore } from '@inkeep/open-knowledge-core';
import { describe, expect, test } from 'vitest';
import {
assertAnchorConsistent,
@@ -324,3 +325,53 @@ describe('refind — a passage whose NEIGHBOURS were edited', () => {
expectResolvesTo(editedFar());
});
});
+
+describe('refind — the stored offsets are a hint, and the hint is trusted alone', () => {
+ const quote = 'the garlic paste';
+ const body =
+ 'Intro paragraph that is long enough.\n\nStir well and add ' +
+ quote +
+ ' to the pan.\n\nLater, again: add ' +
+ quote +
+ ' to the pan.\n';
+
+ test('the fixture really does repeat the quote, and the anchor takes the first', () => {
+ const first = body.indexOf(quote);
+ const second = body.indexOf(quote, first + 1);
+ expect(second).toBeGreaterThan(first);
+ expect(createAnchor(body, first, first + quote.length).start).toBe(first);
+ });
+
+ test('CHARACTERIZATION: a twin sliding onto the stored offsets is taken without evidence', () => {
+ const first = body.indexOf(quote);
+ const second = body.indexOf(quote, first + 1);
+ const anchor = createAnchor(body, first, first + quote.length);
+
+ const edited = body.slice(0, 2) + body.slice(2 + (second - first));
+
+ expect(edited.indexOf(quote)).toBe(first - (second - first));
+ expect(edited.indexOf(quote, first)).toBe(first);
+ expect(refind(edited, anchor)).toEqual({
+ status: 'anchored',
+ start: anchor.start,
+ end: anchor.end,
+ });
+ });
+
+ test('the context it skipped scores the other occurrence higher', () => {
+ const first = body.indexOf(quote);
+ const second = body.indexOf(quote, first + 1);
+ const anchor = createAnchor(body, first, first + quote.length);
+ const edited = body.slice(0, 2) + body.slice(2 + (second - first));
+
+ const scoreAt = (start: number): number =>
+ contextMatchScore(
+ edited,
+ { start, end: start + quote.length },
+ { prefix: anchor.prefix, suffix: anchor.suffix },
+ { syntaxIn: 'haystack', syntaxInContext: true },
+ );
+
+ expect(scoreAt(first - (second - first))).toBeGreaterThan(scoreAt(anchor.start));
+ });
+});
diff --git a/packages/server/src/content-filter.ts b/packages/server/src/content-filter.ts
index 164275dc5..7a21f0803 100644
--- a/packages/server/src/content-filter.ts
+++ b/packages/server/src/content-filter.ts
@@ -773,6 +773,7 @@ export function createContentFilter(opts: ContentFilterOptions): ContentFilter {
}
function isRejectedByConfigurableRules(relativePath: string): boolean {
+ if (relativePath === '') return false;
for (const segment of relativePath.split('/')) {
if (BUILTIN_SKIP_DIRS.has(segment)) return true;
}
@@ -892,6 +893,7 @@ export function createContentFilter(opts: ContentFilterOptions): ContentFilter {
},
isPathIgnored(relativePath: string, opts?: ContentFilterPathReadOpts): boolean {
+ if (relativePath === '') return true;
if (isReservedDocName(relativePath)) return true;
if (isSecretBearingFile(relativePath)) return true;
if (pathHasSecretBearingDirSegment(relativePath)) return true;
@@ -1335,6 +1337,7 @@ export async function createContentFilterAsync(opts: ContentFilterOptions): Prom
return isReservedForUserTree(docName);
}
function isRejectedByConfigurableRules(relativePath: string): boolean {
+ if (relativePath === '') return false;
for (const segment of relativePath.split('/')) {
if (BUILTIN_SKIP_DIRS.has(segment)) return true;
}
@@ -1505,6 +1508,7 @@ export async function createContentFilterAsync(opts: ContentFilterOptions): Prom
},
isPathIgnored(relativePath: string, opts?: ContentFilterPathReadOpts): boolean {
+ if (relativePath === '') return true;
if (isReservedDocName(relativePath)) return true;
if (isSecretBearingFile(relativePath)) return true;
if (pathHasSecretBearingDirSegment(relativePath)) return true;
diff --git a/packages/server/src/content/generated-artifact.test.ts b/packages/server/src/content/generated-artifact.test.ts
index 30136311e..408cf3c7a 100644
--- a/packages/server/src/content/generated-artifact.test.ts
+++ b/packages/server/src/content/generated-artifact.test.ts
@@ -1,7 +1,7 @@
import { describe, expect, test } from 'vitest';
import * as Y from 'yjs';
-import type { PairedWriteOrigin } from '../server-observers.ts';
import type { WriterIdentity } from '../shadow-repo.ts';
+import type { PairedWriteOrigin } from '../write-origins.ts';
import { type GeneratedArtifactEnv, writeGeneratedArtifact } from './generated-artifact.ts';
const WRITER: WriterIdentity = {
diff --git a/packages/server/src/content/generated-artifact.ts b/packages/server/src/content/generated-artifact.ts
index 9968d6525..49b0f48cc 100644
--- a/packages/server/src/content/generated-artifact.ts
+++ b/packages/server/src/content/generated-artifact.ts
@@ -1,7 +1,7 @@
import type * as Y from 'yjs';
import { replaceRawBody } from '../bridge-intake.ts';
-import type { PairedWriteOrigin } from '../server-observers.ts';
import type { WriterIdentity } from '../shadow-repo.ts';
+import type { PairedWriteOrigin } from '../write-origins.ts';
export type GeneratedWriteOutcome = 'unchanged' | 'document' | 'disk' | 'blocked-conflict';
diff --git a/packages/server/src/derive-defer-floor.test.ts b/packages/server/src/derive-defer-floor.test.ts
deleted file mode 100644
index ba8298df6..000000000
--- a/packages/server/src/derive-defer-floor.test.ts
+++ /dev/null
@@ -1,210 +0,0 @@
-import { readFileSync } from 'node:fs';
-import { mkdtemp, rm } from 'node:fs/promises';
-import { tmpdir } from 'node:os';
-import { resolve } from 'node:path';
-import type { Hocuspocus } from '@hocuspocus/server';
-import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
-import {
- type BridgeDeriveLossReporter,
- createBridgeDeriveLossReporter,
- DERIVE_LOSS_SITE_AGENT_UNDO,
- DERIVE_LOSS_SITE_AGENT_WRITE_INTAKE,
- DERIVE_LOSS_SITE_FILE_WATCHER_INTAKE,
-} from './bridge-loss-detector.ts';
-import { DocumentDurabilityState } from './document-durability-state.ts';
-import { applyExternalChange } from './external-change.ts';
-import {
- type LossCaptureEvent,
- LossCaptureRing,
- lossCaptureCurrentPath,
- parseLossCaptureLines,
-} from './loss-capture.ts';
-import {
- createWiredPreDrainRig,
- WIRED_PENDING_LINE,
- type WiredPreDrainRig,
-} from './pre-drain-wired.test-helper.ts';
-import { initShadowRepo, type ShadowHandle, shadowGit } from './shadow-repo.ts';
-import { getDocumentHistory } from './timeline-query.ts';
-
-interface FloorHarness {
- readonly wired: WiredPreDrainRig;
- readonly shadow: ShadowHandle;
- readonly ring: LossCaptureRing;
- readonly docName: string;
- deferCount(): number;
- stageDeferredKeystroke(): void;
- awaitDetectorTrip(): Promise;
- cleanup(): Promise;
-}
-
-async function createFloorHarness(docName: string): Promise {
- const tmpDir = await mkdtemp(resolve(tmpdir(), 'ok-defer-floor-'));
- const projectRoot = resolve(tmpDir, 'project');
- const shadow = await initShadowRepo(projectRoot);
- const ring = new LossCaptureRing({ projectDir: projectRoot, maxBytes: 1_000_000 });
- const reporter: BridgeDeriveLossReporter = createBridgeDeriveLossReporter({
- shadow: () => shadow,
- ring,
- getBranch: () => 'main',
- contentRoot: '',
- });
- let defers = 0;
- const wired = await createWiredPreDrainRig({
- docName,
- reporter,
- setupOverrides: {
- onDeriveTimingDefer: () => {
- defers += 1;
- },
- },
- });
-
- return {
- wired,
- shadow,
- ring,
- docName,
- deferCount: () => defers,
- stageDeferredKeystroke: () => {
- wired.stageUnpropagatedKeystroke();
- const before = defers;
- wired.rig.externalYtextEdit(
- 'source-write',
- (yt) => yt.insert(yt.length, '\nAnother source line.\n'),
- { advanceFreshness: false },
- );
- expect(defers).toBeGreaterThan(before);
- expect(wired.serializeFragment()).toContain(WIRED_PENDING_LINE);
- expect(wired.ytextString()).not.toContain(WIRED_PENDING_LINE);
- },
- awaitDetectorTrip: async () => {
- let trip: LossCaptureEvent | undefined;
- for (let i = 0; i < 100 && !trip; i++) {
- await ring.drain();
- try {
- const events = parseLossCaptureLines(
- readFileSync(lossCaptureCurrentPath(projectRoot), 'utf-8'),
- );
- trip = events.find((e) => e.event === 'detector-trip' && Boolean(e.checkpointSha));
- } catch {}
- if (!trip) await new Promise((r) => setTimeout(r, 10));
- }
- return trip;
- },
- cleanup: async () => {
- await wired.cleanup();
- await rm(tmpDir, { recursive: true, force: true });
- },
- };
-}
-
-async function expectRestorableFloorCheckpoint(
- h: FloorHarness,
- expectedSite: string,
-): Promise {
- const trip = await h.awaitDetectorTrip();
- expect(trip).toBeDefined();
- expect(trip?.site).toBe(expectedSite);
- expect(typeof trip?.lostLen).toBe('number');
- expect(JSON.stringify(trip)).not.toContain(WIRED_PENDING_LINE);
-
- const blob = (
- await shadowGit(h.shadow).raw('show', `${trip?.checkpointSha}:${h.docName}`)
- ).toString();
- expect(blob).toContain(WIRED_PENDING_LINE);
-
- const hist = await getDocumentHistory(h.shadow, { docName: h.docName }, '');
- const row = hist.entries.find((e) => e.sha === trip?.checkpointSha);
- expect(row?.checkpoint?.kind).toBe('bridge-derive-loss');
-}
-
-describe('checkpoint floor after a derive-timing defer', () => {
- beforeEach(() => {
- vi.useFakeTimers({ toFake: ['Date'] });
- vi.setSystemTime(1_000_000);
- });
- afterEach(() => {
- vi.useRealTimers();
- });
-
- test('agent append: the deferred keystroke lands on the floor, restorable', async () => {
- const h = await createFloorHarness('floor-agent-append');
- try {
- h.stageDeferredKeystroke();
-
- h.wired.agentWriteWithPreDrain('An appended agent paragraph.', 'append');
-
- expect(h.wired.ytextString()).not.toContain(WIRED_PENDING_LINE);
- expect(h.wired.serializeFragment()).not.toContain(WIRED_PENDING_LINE);
- await expectRestorableFloorCheckpoint(h, DERIVE_LOSS_SITE_AGENT_WRITE_INTAKE);
- } finally {
- await h.cleanup();
- }
- });
-
- test('agent replace: the deferred keystroke lands on the floor, restorable', async () => {
- const h = await createFloorHarness('floor-agent-replace');
- try {
- h.stageDeferredKeystroke();
-
- h.wired.agentWriteWithPreDrain('## Replaced\n\nBrand new body.\n', 'replace');
-
- expect(h.wired.ytextString()).not.toContain(WIRED_PENDING_LINE);
- expect(h.wired.serializeFragment()).not.toContain(WIRED_PENDING_LINE);
- await expectRestorableFloorCheckpoint(h, DERIVE_LOSS_SITE_AGENT_WRITE_INTAKE);
- } finally {
- await h.cleanup();
- }
- });
-
- test('agent undo of a single frame: the deferred keystroke lands on the floor, restorable', async () => {
- const h = await createFloorHarness('floor-agent-undo');
- try {
- h.wired.agentWrite('Agent appended line.', 'append');
- expect(h.wired.ytextString()).toContain('Agent appended line.');
-
- h.stageDeferredKeystroke();
-
- expect(h.wired.agentUndo('last')).toBe(true);
-
- expect(h.wired.ytextString()).not.toContain('Agent appended line.');
- expect(h.wired.ytextString()).not.toContain(WIRED_PENDING_LINE);
- expect(h.wired.serializeFragment()).not.toContain(WIRED_PENDING_LINE);
- await expectRestorableFloorCheckpoint(h, DERIVE_LOSS_SITE_AGENT_UNDO);
- } finally {
- await h.cleanup();
- }
- });
-
- test('file-watcher change: the deferred keystroke lands on the floor, restorable', async () => {
- const h = await createFloorHarness('floor-file-watcher');
- try {
- h.stageDeferredKeystroke();
-
- const hocuspocus = {
- documents: new Map([[h.docName, h.wired.doc]]),
- } as unknown as Hocuspocus;
- applyExternalChange(
- new DocumentDurabilityState(),
- hocuspocus,
- h.docName,
- '## Guide\n\nRewritten from disk.\n',
- undefined,
- undefined,
- createBridgeDeriveLossReporter({
- shadow: () => h.shadow,
- ring: h.ring,
- getBranch: () => 'main',
- contentRoot: '',
- }),
- );
-
- expect(h.wired.ytextString()).not.toContain(WIRED_PENDING_LINE);
- expect(h.wired.serializeFragment()).not.toContain(WIRED_PENDING_LINE);
- await expectRestorableFloorCheckpoint(h, DERIVE_LOSS_SITE_FILE_WATCHER_INTAKE);
- } finally {
- await h.cleanup();
- }
- });
-});
diff --git a/packages/server/src/derive-fixed-point-backstop.test.ts b/packages/server/src/derive-fixed-point-backstop.test.ts
deleted file mode 100644
index 02d4f30d1..000000000
--- a/packages/server/src/derive-fixed-point-backstop.test.ts
+++ /dev/null
@@ -1,398 +0,0 @@
-import { mkdirSync, writeFileSync } from 'node:fs';
-import { mkdtemp, rm } from 'node:fs/promises';
-import { tmpdir } from 'node:os';
-import { resolve } from 'node:path';
-import simpleGit from 'simple-git';
-import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
-import { createBridgeRaceRig } from './bridge-race-rig.test-helper.ts';
-import { LOSS_EVENT_BACKSTOP_TRIP, type LossCaptureEventInput } from './loss-capture.ts';
-import { getMetrics } from './metrics.ts';
-import { initShadowRepo, type ShadowHandle, shadowGit } from './shadow-repo.ts';
-import { getDocumentHistory } from './timeline-query.ts';
-
-const CONTENT_ROOT = 'content/docs';
-
-const CYCLE_FORM_A = '# Cycle\n\nalpha side of the loop \n';
-const CYCLE_FORM_B = '# Cycle\n\nbravo side of the loop \n';
-function cycleForm(i: number): string {
- return i % 2 === 0 ? CYCLE_FORM_A : CYCLE_FORM_B;
-}
-
-function driveCycleUntilTrip(rig: ReturnType, trips: number[]): void {
- for (let i = 0; i < 24 && trips.length === 0; i++) rig.seedSource(cycleForm(i));
-}
-
-describe('re-derive fixed-point backstop (H4)', () => {
- beforeEach(() => {
- vi.useFakeTimers({ toFake: ['Date'] });
- vi.setSystemTime(1_000_000);
- });
- afterEach(() => {
- vi.useRealTimers();
- });
-
- test('a normalize-equal-but-byte-different loop is not treated as converged and trips the backstop loudly', () => {
- const trips: number[] = [];
- const recorded: LossCaptureEventInput[] = [];
- const rig = createBridgeRaceRig({
- docName: 'backstop-trip.md',
- setupOverrides: {
- onReDeriveBackstop: (rounds) => trips.push(rounds),
- lossRing: {
- record: async (input) => {
- recorded.push(input);
- },
- },
- },
- });
- const before = getMetrics().reDeriveBackstopTripped;
- try {
- driveCycleUntilTrip(rig, trips);
-
- expect(trips.length).toBe(1);
- const rounds = trips[0] ?? 0;
- expect(rounds).toBeGreaterThanOrEqual(4);
- expect(getMetrics().reDeriveBackstopTripped).toBe(before + 1);
-
- const evt = recorded.find((e) => e.event === LOSS_EVENT_BACKSTOP_TRIP);
- expect(evt).toBeDefined();
- expect(evt?.direction).toBe('b');
- expect(evt?.site).toBe('rederive-backstop');
- expect(evt?.lostLen).toBeUndefined();
- expect(JSON.stringify(evt)).not.toContain('side of the loop');
- } finally {
- rig.cleanup();
- }
- });
-
- test('a churned-table respell oscillation trips: alternating pipe-dash table forms revisit without converging', () => {
- const trips: number[] = [];
- const rig = createBridgeRaceRig({
- docName: 'table-cycle.md',
- setupOverrides: { onReDeriveBackstop: (r) => trips.push(r) },
- });
- try {
- const tableA = '| a | alpha |\n| - | - |\n| 1 | 2 | \n';
- const tableB = '| a | bravo |\n| - | - |\n| 1 | 2 | \n';
- for (let i = 0; i < 24 && trips.length === 0; i++) {
- rig.seedSource(i % 2 === 0 ? tableA : tableB);
- }
- expect(trips.length).toBe(1);
- } finally {
- rig.cleanup();
- }
- });
-
- test('forward progress never trips: a monotonic non-round-trip edit stream advances without oscillating', () => {
- const trips: number[] = [];
- const rig = createBridgeRaceRig({
- docName: 'progress.md',
- setupOverrides: { onReDeriveBackstop: (r) => trips.push(r) },
- });
- try {
- for (let i = 0; i < 40; i++) rig.seedSource(`# Doc\n\nrunning body line ${i} \n`);
- expect(trips).toEqual([]);
- } finally {
- rig.cleanup();
- }
- });
-
- test('legitimate flows never trip: WYSIWYG typing, round-trip source typing, and a churned-table respell all settle', () => {
- const trips: number[] = [];
- const rig = createBridgeRaceRig({
- docName: 'legit.md',
- setupOverrides: { onReDeriveBackstop: (r) => trips.push(r) },
- });
- try {
- for (let i = 0; i < 20; i++) rig.editFragment(`# Doc\n\nbody line ${i}\n`);
- for (let i = 0; i < 20; i++) rig.seedSource(`# Doc\n\nsource line ${i}\n`);
- for (let i = 0; i < 20; i++) rig.churnedFragmentEdit(`| a | b${i} |\n|---|---|\n| 1 | 2 |\n`);
-
- expect(trips).toEqual([]);
- } finally {
- rig.cleanup();
- }
- });
-
- test("the spike's masking-class fixtures each settle to a fixed point without tripping", () => {
- const maskingFixtures = [
- '# Escape\n\n_leading underscore word\n\n[bracket opener text\n',
- 'Wrap **before ** mid ** after** end.\n',
- '- top\n - nested four\n - deeper eight\n',
- '# Title \n\nParagraph one ends here. \n\nLast.\n',
- '1. one\n1. two\n1. three\n',
- ];
- for (const fixture of maskingFixtures) {
- const trips: number[] = [];
- const rig = createBridgeRaceRig({
- docName: 'masking.md',
- setupOverrides: { onReDeriveBackstop: (r) => trips.push(r) },
- });
- try {
- for (let i = 0; i < 20; i++) rig.churnedFragmentEdit(fixture);
- rig.settle(4);
- expect(trips).toEqual([]);
- } finally {
- rig.cleanup();
- }
- }
- });
-
- test('forced settlement rounds on a converged doc are fixed points, not events', () => {
- const trips: number[] = [];
- const rig = createBridgeRaceRig({
- docName: 'rest.md',
- setupOverrides: { onReDeriveBackstop: (r) => trips.push(r) },
- });
- try {
- rig.seedSource('# Rest\n\nsettled body.\n');
- rig.settle(20);
- expect(trips).toEqual([]);
- } finally {
- rig.cleanup();
- }
- });
-
- test('D2-deferred drains are non-events for the backstop', () => {
- const trips: number[] = [];
- const rig = createBridgeRaceRig({
- docName: 'defer-noevent.md',
- setupOverrides: { onReDeriveBackstop: (r) => trips.push(r) },
- });
- const beforeForceResolve = getMetrics().deriveTimingDeferForceResolved;
- try {
- rig.editFragment(
- '## Guide\n\nIntro.\n\n\n\n\n\nStep one bod\n\n\n\n\n',
- );
- rig.settle(1);
- rig.externalYtextEdit('poke', (yt) => yt.insert(yt.length, '\nTrailing.\n'));
- rig.echoFragmentEdit(rig.ytext.toString(), 'Step one bod', 'Step one body.', {
- advanceFreshness: false,
- });
- for (let i = 0; i < 30; i++) {
- rig.externalYtextEdit('src', (yt) => yt.insert(yt.length, `\nt-${i}\n`), {
- advanceFreshness: false,
- });
- if (getMetrics().deriveTimingDeferForceResolved > beforeForceResolve) break;
- }
- expect(getMetrics().deriveTimingDeferForceResolved).toBeGreaterThan(beforeForceResolve);
- expect(trips).toEqual([]);
- } finally {
- rig.cleanup();
- }
- });
-
- test('freeze scope: the B-direction is frozen while persistence stays live', () => {
- const trips: number[] = [];
- const rig = createBridgeRaceRig({
- docName: 'freeze-b.md',
- setupOverrides: { onReDeriveBackstop: (r) => trips.push(r) },
- });
- try {
- driveCycleUntilTrip(rig, trips);
- expect(trips.length).toBe(1);
- const frozenFragment = rig.serializeFragment();
-
- rig.seedSource('# Cycle\n\na fresh source edit B will not re-derive while frozen \n');
- expect(rig.serializeFragment()).toBe(frozenFragment);
- expect(rig.ytext.toString()).toContain('a fresh source edit B will not re-derive');
- } finally {
- rig.cleanup();
- }
- });
-
- test('freeze scope: the A-direction stays live and a converging drain unfreezes the loop', () => {
- const trips: number[] = [];
- const rig = createBridgeRaceRig({
- docName: 'freeze-a.md',
- setupOverrides: { onReDeriveBackstop: (r) => trips.push(r) },
- });
- try {
- driveCycleUntilTrip(rig, trips);
- expect(trips.length).toBe(1);
-
- rig.editFragment('# Recovered\n\nwysiwyg edit converges the doc\n');
- expect(rig.ytext.toString()).toContain('wysiwyg edit converges the doc');
- rig.seedSource('# After\n\nsource edit re-derives after the unfreeze\n');
- expect(rig.serializeFragment()).toContain('source edit re-derives after the unfreeze');
- } finally {
- rig.cleanup();
- }
- });
-
- test('typing during a freeze persists — the user-edit path and Y.Text stay live while the B loop is frozen', () => {
- const trips: number[] = [];
- const rig = createBridgeRaceRig({
- docName: 'freeze-persists.md',
- setupOverrides: { onReDeriveBackstop: (r) => trips.push(r) },
- });
- try {
- driveCycleUntilTrip(rig, trips);
- expect(trips.length).toBe(1);
- const frozenFragment = rig.serializeFragment();
-
- rig.seedSource('# Cycle\n\ntyped into source while the loop is frozen — not lost \n');
- expect(rig.serializeFragment()).toBe(frozenFragment);
- expect(rig.ytext.toString()).toContain(
- 'typed into source while the loop is frozen — not lost',
- );
-
- rig.editFragment('# Typed\n\nwysiwyg content typed during the freeze reaches Y.Text\n');
- expect(rig.ytext.toString()).toContain(
- 'wysiwyg content typed during the freeze reaches Y.Text',
- );
- } finally {
- rig.cleanup();
- }
- });
-
- test('kill-switch OFF: the loop churns unbounded with no trip; default-ON pinned', () => {
- const off: number[] = [];
- const rigOff = createBridgeRaceRig({
- docName: 'backstop-off.md',
- setupOverrides: { fixedPointBackstopEnabled: false, onReDeriveBackstop: (r) => off.push(r) },
- });
- try {
- for (let i = 0; i < 24; i++) rigOff.seedSource(cycleForm(i));
- expect(off).toEqual([]);
- expect(rigOff.serializeFragment()).toContain('bravo side of the loop');
- } finally {
- rigOff.cleanup();
- }
-
- const on: number[] = [];
- const rigOn = createBridgeRaceRig({
- docName: 'backstop-default.md',
- setupOverrides: { onReDeriveBackstop: (r) => on.push(r) },
- });
- try {
- driveCycleUntilTrip(rigOn, on);
- expect(on.length).toBe(1);
- } finally {
- rigOn.cleanup();
- }
- });
-});
-
-describe('re-derive fixed-point backstop — checkpoint floor', () => {
- let tmpDir: string;
-
- beforeEach(async () => {
- vi.useFakeTimers({ toFake: ['Date'] });
- vi.setSystemTime(1_000_000);
- tmpDir = await mkdtemp(resolve(tmpdir(), 'ok-backstop-'));
- });
- afterEach(async () => {
- vi.useRealTimers();
- await rm(tmpDir, { recursive: true, force: true });
- });
-
- async function setupShadow(): Promise {
- const projectRoot = resolve(tmpDir, 'project');
- const contentDir = resolve(projectRoot, CONTENT_ROOT);
- mkdirSync(contentDir, { recursive: true });
- const git = simpleGit(projectRoot);
- await git.init();
- await git.raw('config', 'user.name', 'Test');
- await git.raw('config', 'user.email', 'test@test.com');
- writeFileSync(resolve(contentDir, 'backstop.md'), '# Seed\n');
- await git.add('.');
- await git.commit('Initial commit');
- return initShadowRepo(projectRoot);
- }
-
- test('a trip writes a resolvable bridge-backstop-trip checkpoint holding the frozen Y.Text', async () => {
- const shadow = await setupShadow();
- const recorded: LossCaptureEventInput[] = [];
- const trips: number[] = [];
- const rig = createBridgeRaceRig({
- docName: 'backstop',
- setupOverrides: {
- onReDeriveBackstop: (r) => trips.push(r),
- shadow: () => shadow,
- getBranch: () => 'main',
- contentRoot: CONTENT_ROOT,
- lossRing: {
- record: async (input) => {
- recorded.push(input);
- },
- },
- },
- });
- try {
- let frozenYText = '';
- for (let i = 0; i < 24; i++) {
- rig.seedSource(cycleForm(i));
- if (!frozenYText && trips.length > 0) {
- frozenYText = rig.ytext.toString();
- break;
- }
- }
- expect(frozenYText).not.toBe('');
-
- await vi.waitFor(() =>
- expect(
- recorded.some(
- (e) => e.event === LOSS_EVENT_BACKSTOP_TRIP && typeof e.checkpointSha === 'string',
- ),
- ).toBe(true),
- );
- const evt = recorded.find(
- (e) => e.event === LOSS_EVENT_BACKSTOP_TRIP && typeof e.checkpointSha === 'string',
- );
- const sha = evt?.checkpointSha;
- expect(sha).toMatch(/^[0-9a-f]{40}$/);
-
- const hist = await getDocumentHistory(shadow, { docName: 'backstop' }, CONTENT_ROOT);
- const row = hist.entries.find((e) => e.sha === sha);
- expect(row?.type).toBe('checkpoint');
- expect(row?.checkpoint?.kind).toBe('bridge-backstop-trip');
-
- const content = (
- await shadowGit(shadow).raw('show', `${sha}:${CONTENT_ROOT}/backstop`)
- ).toString();
- expect(content).toBe(frozenYText);
- } finally {
- rig.cleanup();
- }
- });
-
- test('a checkpoint-write failure still fires a sha-less backstop-trip ring event (never silent)', async () => {
- const recorded: LossCaptureEventInput[] = [];
- const trips: number[] = [];
- const brokenShadow: ShadowHandle = {
- gitDir: resolve(tmpDir, 'no-such-shadow.git'),
- workTree: resolve(tmpDir, 'no-such-worktree'),
- };
- const rig = createBridgeRaceRig({
- docName: 'backstop',
- setupOverrides: {
- onReDeriveBackstop: (r) => trips.push(r),
- shadow: () => brokenShadow,
- getBranch: () => 'main',
- contentRoot: CONTENT_ROOT,
- lossRing: {
- record: async (input) => {
- recorded.push(input);
- },
- },
- },
- });
- const before = getMetrics().reDeriveBackstopTripped;
- try {
- driveCycleUntilTrip(rig, trips);
- expect(trips.length).toBe(1);
- expect(getMetrics().reDeriveBackstopTripped).toBe(before + 1);
-
- await vi.waitFor(() =>
- expect(recorded.some((e) => e.event === LOSS_EVENT_BACKSTOP_TRIP)).toBe(true),
- );
- const evt = recorded.find((e) => e.event === LOSS_EVENT_BACKSTOP_TRIP);
- expect(evt?.direction).toBe('b');
- expect(evt?.site).toBe('rederive-backstop');
- expect(evt?.checkpointSha).toBeUndefined();
- } finally {
- rig.cleanup();
- }
- });
-});
diff --git a/packages/server/src/derive-fixed-point-comparand.test.ts b/packages/server/src/derive-fixed-point-comparand.test.ts
deleted file mode 100644
index f0b02d80a..000000000
--- a/packages/server/src/derive-fixed-point-comparand.test.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
-import { createBridgeRaceRig } from './bridge-race-rig.test-helper.ts';
-
-const CYCLE_FORM_A = '# Cycle\n\nalpha side of the loop \n';
-const CYCLE_FORM_B = '# Cycle\n\nbravo side of the loop \n';
-
-function driveCycle(
- rig: ReturnType,
- trips: number[],
- untilTripCount: number,
-): void {
- for (let i = 0; i < 40 && trips.length < untilTripCount; i++) {
- rig.seedSource(i % 2 === 0 ? CYCLE_FORM_A : CYCLE_FORM_B);
- }
-}
-
-describe('raw-byte fixed point on an A-then-B drain', () => {
- beforeEach(() => {
- vi.useFakeTimers({ toFake: ['Date'] });
- vi.setSystemTime(1_000_000);
- });
- afterEach(() => {
- vi.useRealTimers();
- });
-
- test('a dual-CRDT drain that settles residual-bearing does NOT release a live backstop freeze', () => {
- const trips: number[] = [];
- const rig = createBridgeRaceRig({
- docName: 'ab-comparand.md',
- setupOverrides: { onReDeriveBackstop: (r) => trips.push(r) },
- });
- try {
- driveCycle(rig, trips, 1);
- expect(trips.length).toBe(1);
-
- rig.dualMutation('# Cycle\n\ngamma side of the loop\n', (yt) => {
- yt.insert(yt.length, 'concurrent tail \n');
- });
- expect(rig.ytext.toString()).not.toBe(rig.serializeFragment());
-
- driveCycle(rig, trips, 2);
- expect(trips.length).toBe(1);
- } finally {
- rig.cleanup();
- }
- });
-
- test('a genuinely converged drain still unfreezes', () => {
- const trips: number[] = [];
- const rig = createBridgeRaceRig({
- docName: 'ab-comparand-converge.md',
- setupOverrides: { onReDeriveBackstop: (r) => trips.push(r) },
- });
- try {
- driveCycle(rig, trips, 1);
- expect(trips.length).toBe(1);
-
- rig.editFragment('# Recovered\n\nwysiwyg edit converges the doc\n');
- expect(rig.ytext.toString()).toContain('wysiwyg edit converges the doc');
-
- rig.seedSource('# After\n\nsource edit re-derives after the unfreeze\n');
- expect(rig.serializeFragment()).toContain('source edit re-derives after the unfreeze');
- } finally {
- rig.cleanup();
- }
- });
-});
diff --git a/packages/server/src/derive-pre-drain.test.ts b/packages/server/src/derive-pre-drain.test.ts
deleted file mode 100644
index a9ba40453..000000000
--- a/packages/server/src/derive-pre-drain.test.ts
+++ /dev/null
@@ -1,221 +0,0 @@
-import { readFileSync } from 'node:fs';
-import { mkdtemp, rm } from 'node:fs/promises';
-import { tmpdir } from 'node:os';
-import { resolve } from 'node:path';
-import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
-import { createBridgeDeriveLossReporter } from './bridge-loss-detector.ts';
-import { LossCaptureRing, lossCaptureCurrentPath, parseLossCaptureLines } from './loss-capture.ts';
-import {
- createWiredPreDrainRig,
- WIRED_PENDING_LINE,
- WIRED_STALE_LINE,
-} from './pre-drain-wired.test-helper.ts';
-import { getPreDrainController } from './server-observers.ts';
-import { initShadowRepo, shadowGit } from './shadow-repo.ts';
-import { getDocumentHistory } from './timeline-query.ts';
-
-describe('pre-drain paired-vector arms (H15)', () => {
- beforeEach(() => {
- vi.useFakeTimers({ toFake: ['Date'] });
- vi.setSystemTime(1_000_000);
- });
- afterEach(() => {
- vi.useRealTimers();
- });
-
- test('CROSS-BLOCK undo: the pending keystroke survives in Y.Text and the re-derived fragment', async () => {
- const rig = await createWiredPreDrainRig({ docName: 'cross-undo.md' });
- try {
- rig.agentWrite('Agent appended line.', 'append');
- expect(rig.ytextString()).toContain('Agent appended line.');
-
- rig.stageUnpropagatedKeystroke();
- expect(rig.serializeFragment()).toContain(WIRED_PENDING_LINE);
- expect(rig.ytextString()).not.toContain(WIRED_PENDING_LINE);
-
- const undone = rig.agentUndo('last');
-
- expect(undone).toBe(true);
- expect(rig.ytextString()).toContain(WIRED_PENDING_LINE);
- expect(rig.serializeFragment()).toContain(WIRED_PENDING_LINE);
- expect(rig.ytextString()).not.toContain('Agent appended line.');
- expect(rig.serializeFragment()).not.toContain('Agent appended line.');
- } finally {
- await rig.cleanup();
- }
- });
-
- test('CROSS-BLOCK agent append: the pending keystroke survives and the append lands', async () => {
- const rig = await createWiredPreDrainRig({ docName: 'cross-append.md' });
- try {
- rig.stageUnpropagatedKeystroke();
- expect(rig.serializeFragment()).toContain(WIRED_PENDING_LINE);
- expect(rig.ytextString()).not.toContain(WIRED_PENDING_LINE);
-
- rig.agentWriteWithPreDrain('A fresh agent paragraph.', 'append');
-
- expect(rig.ytextString()).toContain(WIRED_PENDING_LINE);
- expect(rig.serializeFragment()).toContain(WIRED_PENDING_LINE);
- expect(rig.ytextString()).toContain('A fresh agent paragraph.');
- } finally {
- await rig.cleanup();
- }
- });
-
- test('kill-switch OFF: the cross-block keystroke is NOT flushed (left for the floor)', async () => {
- const rig = await createWiredPreDrainRig({
- docName: 'kill-off.md',
- setupOverrides: { preDrainEnabled: false },
- });
- try {
- rig.agentWrite('Agent appended line.', 'append');
- rig.stageUnpropagatedKeystroke();
- expect(rig.serializeFragment()).toContain(WIRED_PENDING_LINE);
-
- rig.agentUndo('last');
-
- expect(rig.ytextString()).not.toContain(WIRED_PENDING_LINE);
- expect(rig.serializeFragment()).not.toContain(WIRED_PENDING_LINE);
- } finally {
- await rig.cleanup();
- }
- });
-
- test('dirty-flag gating: a clean paired op short-circuits without a discriminator pass', async () => {
- const rig = await createWiredPreDrainRig({ docName: 'clean-op.md' });
- try {
- const controller = getPreDrainController(rig.doc);
- expect(controller).toBeDefined();
- const verdict = controller?.preDrain({
- kind: 'agent-write',
- composedBody: 'anything',
- writeKind: 'append',
- });
- expect(verdict?.reason).toBe('skip-no-pending');
- expect(verdict?.preDrain).toBe(false);
- } finally {
- await rig.cleanup();
- }
- });
-
- test('INERT on replace-intent: a whole-doc replace op declines and never flushes', async () => {
- const rig = await createWiredPreDrainRig({ docName: 'replace-inert.md' });
- try {
- rig.stageUnpropagatedKeystroke();
- const before = rig.ytextString();
- expect(before).toContain(WIRED_STALE_LINE);
- expect(before).not.toContain(WIRED_PENDING_LINE);
-
- const verdict = getPreDrainController(rig.doc)?.preDrain({
- kind: 'agent-write',
- composedBody: '## Replaced\n\nBrand new body.\n',
- writeKind: 'replace',
- });
-
- expect(verdict?.preDrain).toBe(false);
- expect(rig.ytextString()).toBe(before);
- expect(rig.ytextString()).not.toContain(WIRED_PENDING_LINE);
- } finally {
- await rig.cleanup();
- }
- });
-
- test('NO-TARGET: an undo with an empty stack neither flushes nor throws', async () => {
- const rig = await createWiredPreDrainRig({ docName: 'no-target.md' });
- try {
- rig.stageUnpropagatedKeystroke();
- const before = rig.ytextString();
-
- const undone = rig.agentUndo('last');
-
- expect(undone).toBe(false);
- expect(rig.ytextString()).toBe(before);
- } finally {
- await rig.cleanup();
- }
- });
-
- test('SAME-BLOCK / overlap: a replace over the pending content checkpoints it byte-level, restorable', async () => {
- const tmpDir = await mkdtemp(resolve(tmpdir(), 'ok-pre-drain-floor-'));
- const projectRoot = resolve(tmpDir, 'project');
- const shadow = await initShadowRepo(projectRoot);
- const ring = new LossCaptureRing({ projectDir: projectRoot, maxBytes: 1_000_000 });
- const reporter = createBridgeDeriveLossReporter({
- shadow: () => shadow,
- ring,
- getBranch: () => 'main',
- contentRoot: '',
- });
- const rig = await createWiredPreDrainRig({ docName: 'overlap', reporter });
- try {
- rig.stageUnpropagatedKeystroke();
- expect(rig.serializeFragment()).toContain(WIRED_PENDING_LINE);
-
- rig.agentWriteWithPreDrain('## Replaced\n\nBrand new body.\n', 'replace');
-
- let trip: ReturnType[number] | undefined;
- for (let i = 0; i < 100 && !trip; i++) {
- await ring.drain();
- try {
- const events = parseLossCaptureLines(
- readFileSync(lossCaptureCurrentPath(projectRoot), 'utf-8'),
- );
- trip = events.find((e) => e.event === 'detector-trip' && Boolean(e.checkpointSha));
- } catch {}
- if (!trip) await new Promise((r) => setTimeout(r, 10));
- }
- expect(trip).toBeDefined();
- expect(typeof trip?.lostLen).toBe('number');
- expect(JSON.stringify(trip)).not.toContain(WIRED_PENDING_LINE);
-
- const blob = (
- await shadowGit(shadow).raw('show', `${trip?.checkpointSha}:overlap`)
- ).toString();
- expect(blob).toContain(WIRED_PENDING_LINE);
-
- const hist = await getDocumentHistory(shadow, { docName: 'overlap' }, '');
- const row = hist.entries.find((e) => e.sha === trip?.checkpointSha);
- expect(row?.checkpoint?.kind).toBe('bridge-derive-loss');
- } finally {
- await rig.cleanup();
- await rm(tmpDir, { recursive: true, force: true });
- }
- });
-});
-
-describe('pre-drain frontmatter-ambiguity decline', () => {
- beforeEach(() => {
- vi.useFakeTimers({ toFake: ['Date'] });
- vi.setSystemTime(1_000_000);
- });
- afterEach(() => {
- vi.useRealTimers();
- });
-
- test('a pending doc-start rule pair declines the flush instead of writing un-adjusted bytes', async () => {
- const rig = await createWiredPreDrainRig({ docName: 'fm-ambiguous-predrain.md' });
- try {
- rig.rig.seedSource('seed body\n');
- rig.rig.externalYtextEdit('poke', (yt) => {
- yt.insert(yt.length, 'trailing\n');
- });
- rig.rig.editFragment('---\n\nx\n\n---\n\nseed body\n', { advanceFreshness: false });
-
- const pending = rig.serializeFragment();
- expect(pending.startsWith('---')).toBe(true);
- expect(rig.ytextString().startsWith('---')).toBe(false);
-
- const verdict = getPreDrainController(rig.doc)?.preDrain({
- kind: 'agent-write',
- composedBody: 'anything',
- writeKind: 'append',
- });
- expect(verdict?.preDrain).toBe(false);
- expect(verdict?.reason).toBe('checkpoint-fm-ambiguous');
-
- expect(rig.ytextString().startsWith('---')).toBe(false);
- } finally {
- await rig.cleanup();
- }
- });
-});
diff --git a/packages/server/src/derive-timing-exhaustion.test.ts b/packages/server/src/derive-timing-exhaustion.test.ts
deleted file mode 100644
index 28c018689..000000000
--- a/packages/server/src/derive-timing-exhaustion.test.ts
+++ /dev/null
@@ -1,201 +0,0 @@
-import { mkdirSync, writeFileSync } from 'node:fs';
-import { mkdtemp, rm } from 'node:fs/promises';
-import { tmpdir } from 'node:os';
-import { resolve } from 'node:path';
-import simpleGit from 'simple-git';
-import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
-import { type BridgeRaceRig, createBridgeRaceRig } from './bridge-race-rig.test-helper.ts';
-import type { LossCaptureEventInput } from './loss-capture.ts';
-import { getMetrics } from './metrics.ts';
-import { initShadowRepo, type ShadowHandle, shadowGit } from './shadow-repo.ts';
-import { getDocumentHistory } from './timeline-query.ts';
-
-const GEN1 =
- '## Guide\n\nIntro paragraph.\n\n\n\n\n\nStep one bod\n\n\n\n\n';
-const PENDING_LINE = 'Step one body.';
-const STALE_LINE = 'Step one bod';
-const CONTENT_ROOT = 'content/docs';
-
-const MAX_DRAINS = 30;
-
-function stageUnpropagatedKeystroke(rig: BridgeRaceRig): void {
- rig.editFragment(GEN1);
- rig.settle(1);
- rig.externalYtextEdit('poke', (yt) => yt.insert(yt.length, '\nTrailing.\n'));
- rig.echoFragmentEdit(rig.ytext.toString(), STALE_LINE, PENDING_LINE, {
- advanceFreshness: false,
- });
-}
-
-function sourceWrite(rig: BridgeRaceRig, text: string): void {
- rig.externalYtextEdit('source-write', (yt) => yt.insert(yt.length, `\n${text}\n`), {
- advanceFreshness: false,
- });
-}
-
-describe('derive-timing defer exhaustion (H2 exhaustion arm)', () => {
- beforeEach(() => {
- vi.useFakeTimers({ toFake: ['Date'] });
- vi.setSystemTime(1_000_000);
- });
- afterEach(() => {
- vi.useRealTimers();
- });
-
- test('sustained deferral preserves the keystroke until the guard force-resolves loudly', () => {
- const recorded: LossCaptureEventInput[] = [];
- const rig = createBridgeRaceRig({
- docName: 'exhaustion-bound.md',
- setupOverrides: {
- lossRing: {
- record: async (input) => {
- recorded.push(input);
- },
- },
- },
- });
- const before = getMetrics().deriveTimingDeferForceResolved;
- try {
- stageUnpropagatedKeystroke(rig);
-
- let forced = false;
- for (let i = 0; i < MAX_DRAINS && !forced; i++) {
- expect(rig.serializeFragment()).toContain(PENDING_LINE);
- sourceWrite(rig, `trailing-${i}`);
- forced = getMetrics().deriveTimingDeferForceResolved > before;
- }
-
- expect(forced).toBe(true);
- expect(rig.serializeFragment()).not.toContain(PENDING_LINE);
- expect(getMetrics().deriveTimingDeferForceResolved).toBe(before + 1);
-
- const evt = recorded.find(
- (e) => e.event === 'checkpoint-write' && e.site === 'derive-timing-exhaustion',
- );
- expect(evt).toBeDefined();
- expect(evt?.direction).toBe('b');
- expect(typeof evt?.lostLen).toBe('number');
- expect(JSON.stringify(evt)).not.toContain(PENDING_LINE);
- } finally {
- rig.cleanup();
- }
- });
-
- test('a deferring doc reaches the bound through pure drains under a frozen clock', () => {
- const rig = createBridgeRaceRig({ docName: 'exhaustion-quiescent.md' });
- const before = getMetrics().deriveTimingDeferForceResolved;
- try {
- stageUnpropagatedKeystroke(rig);
- sourceWrite(rig, 'kick');
- expect(rig.serializeFragment()).toContain(PENDING_LINE);
-
- let forced = false;
- for (let i = 0; i < MAX_DRAINS && !forced; i++) {
- rig.forceARound({ advanceFreshness: false });
- forced = getMetrics().deriveTimingDeferForceResolved > before;
- }
-
- expect(forced).toBe(true);
- expect(rig.serializeFragment()).not.toContain(PENDING_LINE);
- expect(getMetrics().deriveTimingDeferForceResolved).toBe(before + 1);
- } finally {
- rig.cleanup();
- }
- });
-
- test('with the guard off nothing defers, so the exhaustion path never fires', () => {
- const rig = createBridgeRaceRig({
- docName: 'exhaustion-guard-off.md',
- setupOverrides: { deferGuardEnabled: false },
- });
- const before = getMetrics().deriveTimingDeferForceResolved;
- try {
- stageUnpropagatedKeystroke(rig);
- for (let i = 0; i < MAX_DRAINS; i++) sourceWrite(rig, `x-${i}`);
- expect(rig.serializeFragment()).not.toContain(PENDING_LINE);
- expect(getMetrics().deriveTimingDeferForceResolved).toBe(before);
- } finally {
- rig.cleanup();
- }
- });
-});
-
-describe('derive-timing defer exhaustion — checkpoint floor', () => {
- let tmpDir: string;
-
- beforeEach(async () => {
- vi.useFakeTimers({ toFake: ['Date'] });
- vi.setSystemTime(1_000_000);
- tmpDir = await mkdtemp(resolve(tmpdir(), 'ok-defer-exhaustion-'));
- });
- afterEach(async () => {
- vi.useRealTimers();
- await rm(tmpDir, { recursive: true, force: true });
- });
-
- async function setupShadow(): Promise<{ shadow: ShadowHandle }> {
- const projectRoot = resolve(tmpDir, 'project');
- const contentDir = resolve(projectRoot, CONTENT_ROOT);
- mkdirSync(contentDir, { recursive: true });
- const git = simpleGit(projectRoot);
- await git.init();
- await git.raw('config', 'user.name', 'Test');
- await git.raw('config', 'user.email', 'test@test.com');
- writeFileSync(resolve(contentDir, 'exhaustion.md'), '# Seed\n');
- await git.add('.');
- await git.commit('Initial commit');
- return { shadow: await initShadowRepo(projectRoot) };
- }
-
- test('force-resolve writes a resolvable defer-exhaustion-loss checkpoint holding the pre-resolve fragment', async () => {
- const { shadow } = await setupShadow();
- const recorded: LossCaptureEventInput[] = [];
- const rig = createBridgeRaceRig({
- docName: 'exhaustion',
- setupOverrides: {
- shadow: () => shadow,
- getBranch: () => 'main',
- contentRoot: CONTENT_ROOT,
- lossRing: {
- record: async (input) => {
- recorded.push(input);
- },
- },
- },
- });
- const before = getMetrics().deriveTimingDeferForceResolved;
- try {
- stageUnpropagatedKeystroke(rig);
- for (let i = 0; i < MAX_DRAINS; i++) {
- sourceWrite(rig, `t-${i}`);
- if (getMetrics().deriveTimingDeferForceResolved > before) break;
- }
-
- await vi.waitFor(() =>
- expect(
- recorded.some(
- (e) => e.event === 'checkpoint-write' && typeof e.checkpointSha === 'string',
- ),
- ).toBe(true),
- );
-
- const evt = recorded.find(
- (e) => e.event === 'checkpoint-write' && e.site === 'derive-timing-exhaustion',
- );
- const sha = evt?.checkpointSha;
- expect(sha).toMatch(/^[0-9a-f]{40}$/);
-
- const hist = await getDocumentHistory(shadow, { docName: 'exhaustion' }, CONTENT_ROOT);
- const row = hist.entries.find((e) => e.sha === sha);
- expect(row?.type).toBe('checkpoint');
- expect(row?.checkpoint?.kind).toBe('defer-exhaustion-loss');
-
- const content = (
- await shadowGit(shadow).raw('show', `${sha}:${CONTENT_ROOT}/exhaustion`)
- ).toString();
- expect(content).toContain(PENDING_LINE);
- } finally {
- rig.cleanup();
- }
- });
-});
diff --git a/packages/server/src/derive-timing-guard.test.ts b/packages/server/src/derive-timing-guard.test.ts
deleted file mode 100644
index c7c9cd9c4..000000000
--- a/packages/server/src/derive-timing-guard.test.ts
+++ /dev/null
@@ -1,206 +0,0 @@
-import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
-import { type BridgeRaceRig, createBridgeRaceRig } from './bridge-race-rig.test-helper.ts';
-import type { LossCaptureEventInput } from './loss-capture.ts';
-
-const GEN1 =
- '## Guide\n\nIntro paragraph.\n\n\n\n\n\nStep one bod\n\n\n\n\n';
-const PENDING_LINE = 'Step one body.';
-const STALE_LINE = 'Step one bod';
-
-function stageUnpropagatedKeystroke(rig: BridgeRaceRig): void {
- rig.editFragment(GEN1);
- rig.settle(1);
- rig.externalYtextEdit('poke', (yt) => yt.insert(yt.length, '\nTrailing.\n'));
- rig.echoFragmentEdit(rig.ytext.toString(), STALE_LINE, PENDING_LINE, {
- advanceFreshness: false,
- });
-}
-
-function sourceWrite(rig: BridgeRaceRig, text: string): void {
- rig.externalYtextEdit('source-write', (yt) => yt.insert(yt.length, `\n${text}\n`), {
- advanceFreshness: false,
- });
-}
-
-describe('derive-timing defer guard (H2)', () => {
- beforeEach(() => {
- vi.useFakeTimers({ toFake: ['Date'] });
- vi.setSystemTime(1_000_000);
- });
- afterEach(() => {
- vi.useRealTimers();
- });
-
- test('an un-propagated WYSIWYG keystroke survives a drain-shaped re-derive', () => {
- const rig = createBridgeRaceRig({ docName: 'defer-survives.md' });
- try {
- stageUnpropagatedKeystroke(rig);
- expect(rig.serializeFragment()).toContain(PENDING_LINE);
-
- sourceWrite(rig, 'Another source line.');
-
- expect(rig.serializeFragment()).toContain(PENDING_LINE);
- } finally {
- rig.cleanup();
- }
- });
-
- test('with the guard OFF the same drain stomps the keystroke', () => {
- const rig = createBridgeRaceRig({
- docName: 'defer-off.md',
- setupOverrides: { deferGuardEnabled: false },
- });
- try {
- stageUnpropagatedKeystroke(rig);
- expect(rig.serializeFragment()).toContain(PENDING_LINE);
-
- sourceWrite(rig, 'Another source line.');
-
- expect(rig.serializeFragment()).not.toContain(PENDING_LINE);
- } finally {
- rig.cleanup();
- }
- });
-
- test('the guard is default-ON (no explicit flag)', () => {
- const rig = createBridgeRaceRig({ docName: 'defer-default.md' });
- try {
- stageUnpropagatedKeystroke(rig);
- sourceWrite(rig, 'Another source line.');
- expect(rig.serializeFragment()).toContain(PENDING_LINE);
- } finally {
- rig.cleanup();
- }
- });
-
- test('continued WYSIWYG typing carries the deferred keystroke into Y.Text', () => {
- const rig = createBridgeRaceRig({ docName: 'defer-converge.md' });
- try {
- stageUnpropagatedKeystroke(rig);
- sourceWrite(rig, 'Another source line.');
- expect(rig.ytext.toString()).not.toContain(PENDING_LINE);
-
- const typed = rig.serializeFragment().replace('Intro paragraph.', 'Intro paragraph typed.');
- expect(typed).toContain(PENDING_LINE);
- rig.editFragment(typed);
-
- expect(rig.ytext.toString()).toContain(PENDING_LINE);
- expect(rig.ytext.toString()).toContain('Intro paragraph typed.');
- expect(rig.ytext.toString()).toContain('Another source line.');
- expect(rig.serializeFragment()).toContain(PENDING_LINE);
- } finally {
- rig.cleanup();
- }
- });
-
- test('a pure source-editor write near a component does not defer', () => {
- let deferCount = 0;
- const rig = createBridgeRaceRig({
- docName: 'no-false-defer-source.md',
- setupOverrides: {
- onDeriveTimingDefer: () => {
- deferCount += 1;
- },
- },
- });
- try {
- rig.editFragment(GEN1);
- rig.settle(2);
- expect(rig.ytext.toString()).toContain(STALE_LINE);
- sourceWrite(rig, 'New source paragraph.');
- expect(rig.serializeFragment()).toContain('New source paragraph.');
- expect(deferCount).toBe(0);
- } finally {
- rig.cleanup();
- }
- });
-
- test('a Y.Text-only residual (fragment holds less) does not defer', () => {
- let deferCount = 0;
- const rig = createBridgeRaceRig({
- docName: 'no-false-defer-ytext.md',
- setupOverrides: {
- onDeriveTimingDefer: () => {
- deferCount += 1;
- },
- },
- });
- try {
- rig.seedSource('# Title\n\nAlpha paragraph.\n');
- rig.settle(1);
- sourceWrite(rig, 'Beta paragraph.');
- expect(rig.serializeFragment()).toContain('Beta paragraph.');
- expect(deferCount).toBe(0);
- } finally {
- rig.cleanup();
- }
- });
-
- test('a deferring drain does not move the settlement witnesses', () => {
- const snapshots: Array<{ canonicalWitness: string; rawWitness: string }> = [];
- const rig = createBridgeRaceRig({
- docName: 'defer-atomicity.md',
- setupOverrides: {
- onDeriveTimingDefer: (s) => snapshots.push(s),
- },
- });
- try {
- stageUnpropagatedKeystroke(rig);
- sourceWrite(rig, 'First trailing.');
- sourceWrite(rig, 'Second trailing.');
- expect(snapshots.length).toBeGreaterThanOrEqual(2);
- expect(snapshots[1]?.canonicalWitness).toBe(snapshots[0]?.canonicalWitness);
- expect(snapshots[1]?.rawWitness).toBe(snapshots[0]?.rawWitness);
- } finally {
- rig.cleanup();
- }
- });
-
- test('each defer records a distinguishable guard-defer loss-ring event', () => {
- const recorded: LossCaptureEventInput[] = [];
- const rig = createBridgeRaceRig({
- docName: 'defer-ring.md',
- setupOverrides: {
- lossRing: {
- record: async (input) => {
- recorded.push(input);
- },
- },
- },
- });
- try {
- stageUnpropagatedKeystroke(rig);
- sourceWrite(rig, 'Another source line.');
- expect(recorded.length).toBeGreaterThanOrEqual(1);
- const evt = recorded[0];
- expect(evt?.event).toBe('guard-defer');
- expect(evt?.docName).toBe('defer-ring.md');
- expect(evt?.direction).toBe('b');
- expect(typeof evt?.lostLen).toBe('number');
- expect(JSON.stringify(evt)).not.toContain(PENDING_LINE);
- } finally {
- rig.cleanup();
- }
- });
-
- test('a Y.Text-ahead divergence re-derives (does not defer) — direction-aware', () => {
- let deferCount = 0;
- const rig = createBridgeRaceRig({
- docName: 'direction-aware.md',
- setupOverrides: {
- onDeriveTimingDefer: () => {
- deferCount += 1;
- },
- },
- });
- try {
- rig.editFragment(GEN1);
- rig.settle(2);
- sourceWrite(rig, 'Divergent source content.');
- expect(rig.serializeFragment()).toContain('Divergent source content.');
- expect(deferCount).toBe(0);
- } finally {
- rig.cleanup();
- }
- });
-});
diff --git a/packages/server/src/disk-content-intake.ts b/packages/server/src/disk-content-intake.ts
index 0fd185187..1c68a01ba 100644
--- a/packages/server/src/disk-content-intake.ts
+++ b/packages/server/src/disk-content-intake.ts
@@ -1,7 +1,7 @@
import type * as Y from 'yjs';
-import { composeAndWriteRawBody, type PrecomputedParse } from './bridge-intake.ts';
+import { composeAndWriteRawBody } from './bridge-intake.ts';
import type { DeriveLossDetectOptions } from './bridge-loss-detector.ts';
-import type { PairedWriteOrigin } from './server-observers.ts';
+import type { PairedWriteOrigin } from './write-origins.ts';
export const FILE_WATCHER_ORIGIN = {
source: 'local',
@@ -9,16 +9,26 @@ export const FILE_WATCHER_ORIGIN = {
context: { origin: 'file-watcher', paired: true },
} as const satisfies PairedWriteOrigin;
+/* STOP: `detect` must be read before the write and reported after it. Under the single
+ replica the pre-write Y.Text body is the only witness to content that reached no disk,
+ so capturing it after composeAndWriteRawBody reports an empty loss set every time. */
export function applyDiskContentToDoc(
document: Y.Doc,
content: string,
- resolveEmbed?: (basename: string, sourcePath: string) => string | null,
- sourcePath?: string,
- resolveSize?: (basename: string, sourcePath: string) => number | null,
+ _resolveEmbed?: (basename: string, sourcePath: string) => string | null,
+ _sourcePath?: string,
+ _resolveSize?: (basename: string, sourcePath: string) => number | null,
detect?: DeriveLossDetectOptions,
- precomputed?: PrecomputedParse,
): void {
- const embedResolver =
- resolveEmbed && sourcePath ? { resolveEmbed, resolveSize, sourcePath } : undefined;
- composeAndWriteRawBody(document, content, 'file-watcher', embedResolver, precomputed, detect);
+ const pendingBody = detect === undefined ? '' : document.getText('source').toString();
+ composeAndWriteRawBody(document, content, 'file-watcher');
+ if (detect === undefined) return;
+ const appliedBody = document.getText('source').toString();
+ detect.report({
+ pendingBody,
+ baselineBody: detect.baselineFullMd,
+ ytextDerivedBody: appliedBody,
+ rebuiltBody: appliedBody,
+ restorePayload: appliedBody,
+ });
}
diff --git a/packages/server/src/external-change.test.ts b/packages/server/src/external-change.test.ts
index 6b7b35a27..1263cdf66 100644
--- a/packages/server/src/external-change.test.ts
+++ b/packages/server/src/external-change.test.ts
@@ -67,11 +67,6 @@ describe('applyExternalChange — throwing helper', () => {
expect(frontmatter).toContain('title: Test');
expect(frontmatter).toContain('---');
- const xmlFragment = doc.getXmlFragment('default');
- const xmlString = xmlFragment.toString();
- expect(xmlString).not.toContain('title: Test');
- expect(xmlString).not.toContain('tags: [a, b]');
-
await conn.disconnect();
});
@@ -346,16 +341,18 @@ describe('createExternalChangeHandler — error-swallowing factory', () => {
const conn = await hp.openDirectConnection(docName);
const doc = getDoc(conn);
- const originalGetXmlFragment = doc.getXmlFragment.bind(doc);
- doc.getXmlFragment = () => {
- throw new Error('synthetic getXmlFragment failure');
- };
-
doc.getText('source').insert(0, '# Original\n');
const textBefore = doc.getText('source').toString();
+ const originalGetText = doc.getText.bind(doc);
+ doc.getText = () => {
+ throw new Error('synthetic getText failure');
+ };
+
await expect(handler(docName, '# Content\n')).resolves.toBeUndefined();
+ doc.getText = originalGetText;
+
expect(errorSpy).toHaveBeenCalled();
const callArgs = errorSpy.mock.calls[0] ?? [];
expect(String(callArgs[1])).toContain('Failed to apply external change');
@@ -363,7 +360,6 @@ describe('createExternalChangeHandler — error-swallowing factory', () => {
expect(doc.getText('source').toString()).toBe(textBefore);
- doc.getXmlFragment = originalGetXmlFragment;
await conn.disconnect();
} finally {
errorSpy.mockRestore();
@@ -379,8 +375,8 @@ describe('createExternalChangeHandler — error-swallowing factory', () => {
const conn = await hp.openDirectConnection(docName);
const doc = getDoc(conn);
- const originalGetXmlFragment = doc.getXmlFragment.bind(doc);
- doc.getXmlFragment = () => {
+ const originalGetText = doc.getText.bind(doc);
+ doc.getText = () => {
throw new BridgeInvariantViolationError({
site: 'observer-b',
docName,
@@ -395,9 +391,9 @@ describe('createExternalChangeHandler — error-swallowing factory', () => {
BridgeInvariantViolationError,
);
+ doc.getText = originalGetText;
expect(errorSpy).not.toHaveBeenCalled();
- doc.getXmlFragment = originalGetXmlFragment;
await conn.disconnect();
} finally {
errorSpy.mockRestore();
@@ -415,8 +411,8 @@ describe('createExternalChangeHandler — error-swallowing factory', () => {
const conn = await hp.openDirectConnection(docName);
const doc = getDoc(conn);
- const originalGetXmlFragment = doc.getXmlFragment.bind(doc);
- doc.getXmlFragment = () => {
+ const originalGetText = doc.getText.bind(doc);
+ doc.getText = () => {
throw new BridgeMergeContentLossError({
baseline: 'base',
userText: 'user',
@@ -432,9 +428,9 @@ describe('createExternalChangeHandler — error-swallowing factory', () => {
BridgeMergeContentLossError,
);
+ doc.getText = originalGetText;
expect(errorSpy).not.toHaveBeenCalled();
- doc.getXmlFragment = originalGetXmlFragment;
await conn.disconnect();
} finally {
console.error = originalError;
diff --git a/packages/server/src/external-change.ts b/packages/server/src/external-change.ts
index 7259b007c..edef72cf9 100644
--- a/packages/server/src/external-change.ts
+++ b/packages/server/src/external-change.ts
@@ -10,13 +10,6 @@ import {
} from '@inkeep/open-knowledge-core';
import { formatReconcileSubject } from '@inkeep/open-knowledge-core/shadow-repo-layout';
import type * as Y from 'yjs';
-import type { PrecomputedParse } from './bridge-intake.ts';
-import {
- type BridgeDeriveLossReporter,
- DERIVE_LOSS_SITE_FILE_WATCHER_INTAKE,
- type DeriveLossDetectOptions,
-} from './bridge-loss-detector.ts';
-import { shouldRunPairedIntakeDetection } from './bridge-loss-suppression.ts';
import {
isConfigDoc,
isEditableTextDoc,
@@ -44,7 +37,7 @@ import { FILE_SYSTEM_WRITER } from './shadow-repo.ts';
export { FILE_WATCHER_ORIGIN } from './disk-content-intake.ts';
-export function redactedErrorSummary(err: unknown): unknown {
+function redactedErrorSummary(err: unknown): unknown {
const verbose = process.env.OK_TELEMETRY_VERBOSE === '1';
if (err instanceof BridgeMergeContentLossError) return err.toLog({ verbose });
if (err instanceof BridgeInvariantViolationError) {
@@ -58,10 +51,6 @@ export function applyExternalChange(
hocuspocus: Hocuspocus,
docName: string,
content: string,
- resolveEmbed?: (basename: string, sourcePath: string) => string | null,
- resolveSize?: (basename: string, sourcePath: string) => number | null,
- bridgeLossReporter?: BridgeDeriveLossReporter,
- precomputed?: PrecomputedParse,
): void {
if (
isSystemDoc(docName) ||
@@ -80,31 +69,9 @@ export function applyExternalChange(
const priorFm = stripFrontmatter(currentSource).frontmatter;
const { frontmatter: nextFm } = stripFrontmatter(content);
- const detect: DeriveLossDetectOptions | undefined =
- bridgeLossReporter && shouldRunPairedIntakeDetection(FILE_WATCHER_ORIGIN.context.origin)
- ? {
- report: (obs) =>
- bridgeLossReporter(
- docName,
- obs,
- FILE_SYSTEM_WRITER.id,
- DERIVE_LOSS_SITE_FILE_WATCHER_INTAKE,
- ),
- baselineFullMd: currentSource,
- }
- : undefined;
-
try {
document.transact(() => {
- applyDiskContentToDoc(
- document,
- content,
- resolveEmbed,
- docName,
- resolveSize,
- detect,
- precomputed,
- );
+ applyDiskContentToDoc(document, content);
}, FILE_WATCHER_ORIGIN);
} catch (err) {
try {
@@ -144,21 +111,10 @@ export function applyExternalChange(
export function createExternalChangeHandler(
durabilityState: DocumentDurabilityState,
hocuspocus: Hocuspocus,
- resolveEmbed?: (basename: string, sourcePath: string) => string | null,
- resolveSize?: (basename: string, sourcePath: string) => number | null,
- bridgeLossReporter?: BridgeDeriveLossReporter,
): (docName: string, content: string) => Promise {
return async (docName: string, content: string): Promise => {
try {
- applyExternalChange(
- durabilityState,
- hocuspocus,
- docName,
- content,
- resolveEmbed,
- resolveSize,
- bridgeLossReporter,
- );
+ applyExternalChange(durabilityState, hocuspocus, docName, content);
getLogger('file-watcher').info({ docName }, 'applied external change');
} catch (err) {
if (
@@ -291,8 +247,6 @@ export function reconcileDiskBeforeAgentWrite(
hocuspocus: Hocuspocus,
docName: string,
contentDir: string,
- resolveEmbed: ((basename: string, sourcePath: string) => string | null) | undefined,
- bridgeLossReporter: BridgeDeriveLossReporter | undefined,
conflicts: Pick,
): ReconcileBeforeWriteResult {
if (
@@ -413,15 +367,7 @@ export function reconcileDiskBeforeAgentWrite(
`[reconcile] ${docName} insert-group dedup skipped (LCS cell cap); union emitted with possible same-block duplication`,
);
}
- applyExternalChange(
- durabilityState,
- hocuspocus,
- docName,
- ingest,
- resolveEmbed,
- undefined,
- bridgeLossReporter,
- );
+ applyExternalChange(durabilityState, hocuspocus, docName, ingest);
if (outcome.kind === 'merged') {
durabilityState.setReconciledBase(docName, diskContent);
}
diff --git a/packages/server/src/http/agent-write-routes.test.ts b/packages/server/src/http/agent-write-routes.test.ts
index 5416dc972..034421ac1 100644
--- a/packages/server/src/http/agent-write-routes.test.ts
+++ b/packages/server/src/http/agent-write-routes.test.ts
@@ -38,7 +38,6 @@ function buildGroup(overrides: Partial = {}) {
durabilityState: {} as DocumentDurabilityState,
hocuspocus: {} as Hocuspocus,
options: {},
- getBridgeLossReporter: undefined,
agentPresenceBroadcaster: undefined,
recordContentDivergenceGate: notDispatched,
buildAgentActor: notDispatched,
diff --git a/packages/server/src/http/agent-write-routes.ts b/packages/server/src/http/agent-write-routes.ts
index 7cdbc8fc3..4d0da4214 100644
--- a/packages/server/src/http/agent-write-routes.ts
+++ b/packages/server/src/http/agent-write-routes.ts
@@ -38,13 +38,9 @@ import {
AgentSessionCapacityError,
type AgentSessionManager,
type AgentWriteContentDivergence,
- agentWriteLossDetect,
- agentWritePreDrain,
applyAgentMarkdownWrite,
applyAgentUndo,
iconFromClientName,
- prepareAgentMarkdownParse,
- prepareFrontmatterPatchParse,
snapshotBlocks,
} from '../agent-sessions.ts';
import {
@@ -52,8 +48,7 @@ import {
normalizeSummary,
type SummaryResponse,
} from '../agent-write-summary.ts';
-import { composeAndWriteRawBody, type PrecomputedParse, replaceRawBody } from '../bridge-intake.ts';
-import type { BridgeDeriveLossReporter } from '../bridge-loss-detector.ts';
+import { composeAndWriteRawBody, replaceRawBody } from '../bridge-intake.ts';
import { isConfigDoc, isSystemDoc, SYSTEM_DOC_NAME } from '../cc1-broadcast.ts';
import {
ConcurrentOverwriteRefusedError,
@@ -83,13 +78,11 @@ import { type LinkAdvisoryPolicy, projectWriteAdvisoryLinks } from '../link-advi
import { getLogger } from '../logger.ts';
import { validateMermaidFences } from '../mermaid-validator.ts';
import { incrementAgentPatchFindMismatches, incrementAgentWriteCalls } from '../metrics.ts';
-import { precomputeParse } from '../parse-pool.ts';
import {
createAncestorShaSetCache,
getOrLoadRenameLogIndex,
resolveDocPathAtCommit,
} from '../rename-log.ts';
-import type { PairedWriteOrigin } from '../server-observers.ts';
import { createVersionOpsService } from '../services/version-ops.ts';
import {
type ShadowRef,
@@ -99,6 +92,7 @@ import {
} from '../shadow-repo.ts';
import { getMeter, withSpanSync } from '../telemetry.ts';
import { computeWriteAdvisoryLinks } from '../write-advisory-links.ts';
+import type { PairedWriteOrigin } from '../write-origins.ts';
import { type ApiRouteGroup, createApiRouteGroup } from './api-pipeline.ts';
import { errorResponse } from './error-response.ts';
import { getRequestId } from './request-id.ts';
@@ -163,10 +157,6 @@ export interface AgentWriteRouteDeps {
sessionManager: AgentSessionManager;
durabilityState: DocumentDurabilityState;
hocuspocus: Hocuspocus;
- options: {
- resolveEmbed?: (basename: string, sourcePath: string) => string | null;
- };
- getBridgeLossReporter: (() => BridgeDeriveLossReporter | undefined) | undefined;
agentPresenceBroadcaster: AgentPresenceBroadcaster | undefined;
recordContentDivergenceGate: (
handler: 'agent-write-md' | 'agent-write-batch' | 'agent-patch' | 'rollback',
@@ -241,8 +231,6 @@ export function createAgentWriteRoutes(deps: AgentWriteRouteDeps): ApiRouteGroup
sessionManager,
durabilityState,
hocuspocus,
- options,
- getBridgeLossReporter,
agentPresenceBroadcaster,
recordContentDivergenceGate,
buildAgentActor,
@@ -339,19 +327,8 @@ export function createAgentWriteRoutes(deps: AgentWriteRouteDeps): ApiRouteGroup
hocuspocus,
resolvedDocName,
contentDir,
- options.resolveEmbed,
- getBridgeLossReporter?.(),
conflicts,
);
- const writeMdEmbedResolver = options.resolveEmbed
- ? { resolveEmbed: options.resolveEmbed, sourcePath: resolvedDocName }
- : undefined;
- const writeMdPrecomputed = await prepareAgentMarkdownParse(
- session.dc.document,
- body.markdown,
- position,
- writeMdEmbedResolver,
- );
const timestamp = new Date().toISOString();
let writeDivergence: AgentWriteContentDivergence | undefined;
let disposeEffectCapture: (() => void) | undefined;
@@ -373,16 +350,12 @@ export function createAgentWriteRoutes(deps: AgentWriteRouteDeps): ApiRouteGroup
colorSeed,
clientName,
);
- agentWritePreDrain(session.dc.document, body.markdown, position);
session.dc.document.transact(() => {
const beforeBlocks = snapshotBlocks(session.dc.document);
writeDivergence = applyAgentMarkdownWrite(
session.dc.document,
body.markdown,
position,
- writeMdEmbedResolver,
- writeMdPrecomputed,
- agentWriteLossDetect(session),
suppliedWriterId,
);
const changedBlocks =
@@ -573,11 +546,8 @@ export function createAgentWriteRoutes(deps: AgentWriteRouteDeps): ApiRouteGroup
hocuspocus,
resolvedDocName,
contentDir,
- options.resolveEmbed,
- getBridgeLossReporter?.(),
conflicts,
);
- const fmPatchPrecomputed = await prepareFrontmatterPatchParse(session.dc.document, patch);
const timestamp = new Date().toISOString();
let editError: import('@inkeep/open-knowledge-core').FmEditError | undefined;
let applied = false;
@@ -626,13 +596,7 @@ export function createAgentWriteRoutes(deps: AgentWriteRouteDeps): ApiRouteGroup
result.nextFenced,
(needsFenceSeparator ? '\n' : '') + currentBody,
).md;
- composeAndWriteRawBody(
- session.dc.document,
- newFull,
- 'agent',
- undefined,
- fmPatchPrecomputed,
- );
+ composeAndWriteRawBody(session.dc.document, newFull, 'agent');
recordFrontmatterEditSurface('mcp-write');
bodyMutated = true;
}
@@ -828,35 +792,8 @@ export function createAgentWriteRoutes(deps: AgentWriteRouteDeps): ApiRouteGroup
hocuspocus,
docName,
contentDir,
- options.resolveEmbed,
- getBridgeLossReporter?.(),
conflicts,
);
- const patchEmbedResolver = options.resolveEmbed
- ? { resolveEmbed: options.resolveEmbed, sourcePath: docName }
- : undefined;
- let patchPrecomputed: PrecomputedParse | undefined;
- {
- const preSnapshot = session.dc.document.getText('source').toString();
- const { frontmatter: preFm, body: preBody } = stripFrontmatter(preSnapshot);
- const preFull = prependFrontmatter(preFm, preBody);
- const prePos =
- offset == null
- ? preFull.indexOf(find)
- : preFull.slice(offset, offset + find.length) === find
- ? offset
- : -1;
- if (prePos !== -1 && prePos >= preFm.length) {
- const guessFull =
- preFull.slice(0, prePos) + replace + preFull.slice(prePos + find.length);
- patchPrecomputed = await prepareAgentMarkdownParse(
- session.dc.document,
- stripFrontmatter(guessFull).body,
- 'patch',
- patchEmbedResolver,
- );
- }
- }
const timestamp = new Date().toISOString();
let notFound = false;
let staleTarget = false;
@@ -931,9 +868,6 @@ export function createAgentWriteRoutes(deps: AgentWriteRouteDeps): ApiRouteGroup
session.dc.document,
newBody,
'patch',
- patchEmbedResolver,
- patchPrecomputed,
- agentWriteLossDetect(session),
suppliedWriterId,
);
const changedBlocks =
@@ -1177,14 +1111,7 @@ export function createAgentWriteRoutes(deps: AgentWriteRouteDeps): ApiRouteGroup
mode: 'writing',
ts: Date.now(),
});
- undone = applyAgentUndo(
- session,
- scope,
- options.resolveEmbed
- ? { resolveEmbed: options.resolveEmbed, sourcePath: docName }
- : undefined,
- count,
- );
+ undone = applyAgentUndo(session, scope, count);
if (undone) {
recordContributor(
docName,
@@ -1553,16 +1480,12 @@ export function createAgentWriteRoutes(deps: AgentWriteRouteDeps): ApiRouteGroup
return;
}
/**
- * Rollback routes through the `replaceRawBody` sibling primitive (precedent #38,
- * Y.Text-is-truth), which overwrites ytext first and derives the fragment after.
+ * Rollback routes through the `replaceRawBody` sibling primitive, which overwrites
+ * Y.Text whole (precedent #38).
*/
- const rollbackEmbedResolver = options.resolveEmbed
- ? { resolveEmbed: options.resolveEmbed, sourcePath: docName }
- : undefined;
- const rollbackPrecomputed = await precomputeParse(markdown, rollbackEmbedResolver);
let rollbackDivergence: AgentWriteContentDivergence | undefined;
document.transact(() => {
- replaceRawBody(document, markdown, rollbackEmbedResolver, rollbackPrecomputed);
+ replaceRawBody(document, markdown);
rollbackDivergence = evaluateContentDivergence(
document.getText('source').toString(),
markdown,
diff --git a/packages/server/src/http/lint-write-routes.ts b/packages/server/src/http/lint-write-routes.ts
index efd606075..4e480c8f2 100644
--- a/packages/server/src/http/lint-write-routes.ts
+++ b/packages/server/src/http/lint-write-routes.ts
@@ -21,7 +21,6 @@ import type { AgentPresenceBroadcaster } from '../agent-presence.ts';
import {
AgentSessionCapacityError,
type AgentSessionManager,
- agentWriteLossDetect,
applyAgentMarkdownWrite,
iconFromClientName,
} from '../agent-sessions.ts';
@@ -80,9 +79,6 @@ export interface LintWriteRouteDeps {
stored: string | undefined;
};
sessionManager: AgentSessionManager;
- options: {
- resolveEmbed?: (basename: string, sourcePath: string) => string | null;
- };
agentPresenceBroadcaster: AgentPresenceBroadcaster | undefined;
buildAgentActor: (args: {
clientName: string | undefined;
@@ -125,7 +121,6 @@ export function createLintWriteRoutes(deps: LintWriteRouteDeps): ApiRouteGroup {
resolveDocFilePath,
summaryResponseFields,
sessionManager,
- options,
agentPresenceBroadcaster,
buildAgentActor,
flushDiskAndDetectOutcome,
@@ -352,17 +347,7 @@ export function createLintWriteRoutes(deps: LintWriteRouteDeps): ApiRouteGroup {
});
const suppliedWriterId = sessionWriterId(session);
session.dc.document.transact(() => {
- applyAgentMarkdownWrite(
- session.dc.document,
- fixed,
- 'patch',
- options.resolveEmbed
- ? { resolveEmbed: options.resolveEmbed, sourcePath: resolvedDocName }
- : undefined,
- undefined,
- agentWriteLossDetect(session),
- suppliedWriterId,
- );
+ applyAgentMarkdownWrite(session.dc.document, fixed, 'patch', suppliedWriterId);
}, session.origin);
if (actor.kind !== 'anonymous') {
diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts
index db6fed53d..e53c0fc85 100644
--- a/packages/server/src/index.ts
+++ b/packages/server/src/index.ts
@@ -493,18 +493,7 @@ export {
updateServerLockPort,
waitForServerLockDrain,
} from './server-lock.ts';
-export {
- createServerObserverExtension,
- type ServerObserverExtensionOptions,
-} from './server-observer-extension.ts';
-export {
- isPairedWriteOrigin,
- OBSERVER_SYNC_ORIGIN,
- type ObserverDispatchKind,
- type PairedWriteOrigin,
- type SetupServerObserversOpts,
- setupServerObservers,
-} from './server-observers.ts';
+export { createServerObserverExtension } from './server-observer-extension.ts';
export {
buildWipTree,
type CheckpointGcResult,
@@ -654,3 +643,8 @@ export {
} from './tolerance-telemetry-writer.ts';
export { trustSystemCertificates } from './trust-system-ca.ts';
export { PROTOCOL_VERSION, RUNTIME_VERSION, STATE_SCHEMA_VERSION } from './version-constants.ts';
+export {
+ isPairedWriteOrigin,
+ OBSERVER_SYNC_ORIGIN,
+ type PairedWriteOrigin,
+} from './write-origins.ts';
diff --git a/packages/server/src/managed-artifact-persistence.test.ts b/packages/server/src/managed-artifact-persistence.test.ts
index 1329258e1..de7f116dd 100644
--- a/packages/server/src/managed-artifact-persistence.test.ts
+++ b/packages/server/src/managed-artifact-persistence.test.ts
@@ -251,7 +251,6 @@ describe('store/load round-trip', () => {
const fresh = new Y.Doc();
loadManagedArtifactDoc(fresh, projectDocName, ctx);
expect(fresh.getText('source').toString()).toBe('');
- expect(fresh.getXmlFragment('default').length).toBe(0);
});
test('__template__ synthetic doc is INERT in load + store (tombstone, never creates a file)', async () => {
@@ -265,7 +264,6 @@ describe('store/load round-trip', () => {
const fresh = new Y.Doc();
expect(() => loadManagedArtifactDoc(fresh, templateDocName, ctx)).not.toThrow();
expect(fresh.getText('source').toString()).toBe('');
- expect(fresh.getXmlFragment('default').length).toBe(0);
expect(fresh.getMap('lifecycle').get(LINEAGE_EPOCH_KEY)).toBeUndefined();
expect(existsSync(join(projectDir, '__template__', 'notes', 'daily.md'))).toBe(false);
@@ -280,7 +278,7 @@ describe('store/load round-trip', () => {
expect(await storeManagedArtifactDoc(doc, docName, 'agent', ctx)).toBe('no-op');
});
- test('load seeds Y.Text + XmlFragment from disk (paired-write)', () => {
+ test('load seeds Y.Text from disk', () => {
const ctx = makeCtx();
const path = managedArtifactAbsPath(docName, ctx);
mkdirSync(resolve(path, '..'), { recursive: true });
@@ -288,10 +286,38 @@ describe('store/load round-trip', () => {
const doc = new Y.Doc();
loadManagedArtifactDoc(doc, docName, ctx);
expect(doc.getText('source').toString()).toBe(SRC);
- expect(doc.getXmlFragment('default').length).toBeGreaterThan(0);
expect(reconciled.get(docName)).toBe(SRC);
});
+ /**
+ * The emptiness test that decides whether to seed must consult Y.Text, not
+ * only the derived fragment. Y.Text is the source of truth (precedent #38),
+ * so a document already holding source bytes has content by definition —
+ * regardless of whether its fragment has been derived yet. Reading only the
+ * fragment would classify such a doc as empty and seed the file ON TOP of the
+ * live bytes, concatenating disk content into a populated document.
+ *
+ * Constructed directly rather than through a bridge path so the state under
+ * test is unambiguous: Y.Text populated, fragment untouched.
+ */
+ test('load refuses to seed a doc holding Y.Text bytes with an underived fragment', () => {
+ const ctx = makeCtx();
+ const path = managedArtifactAbsPath(docName, ctx);
+ mkdirSync(resolve(path, '..'), { recursive: true });
+ writeFileSync(path, SRC, 'utf-8');
+
+ const doc = new Y.Doc();
+ const live = '# live content typed in source mode\n';
+ doc.getText('source').insert(0, live);
+ expect(doc.getXmlFragment('default').length).toBe(0);
+
+ loadManagedArtifactDoc(doc, docName, ctx);
+
+ expect(doc.getText('source').toString()).toBe(live);
+ expect(doc.getXmlFragment('default').length).toBe(0);
+ expect(doc.getMap('lifecycle').get(LINEAGE_EPOCH_KEY)).toBeUndefined();
+ });
+
test('load is lazy — a missing file seeds nothing (no auto-create)', () => {
const ctx = makeCtx();
const doc = new Y.Doc();
diff --git a/packages/server/src/managed-artifact-persistence.ts b/packages/server/src/managed-artifact-persistence.ts
index d83313c2a..d720fce13 100644
--- a/packages/server/src/managed-artifact-persistence.ts
+++ b/packages/server/src/managed-artifact-persistence.ts
@@ -228,8 +228,9 @@ export function loadManagedArtifactDoc(
const extParsed = parseExternalSkillDocName(documentName);
if (extParsed && externalSkillAbsPath(extParsed.name, extParsed.rel) === null) return;
- const xmlFragment = document.getXmlFragment('default');
- if (xmlFragment.length > 0) return;
+ // (precedent #38) and the only surface, so it is the whole test.
+ const ytext = document.getText('source');
+ if (ytext.length > 0) return;
const filePath = managedArtifactAbsPath(documentName, ctx);
if (!existsSync(filePath)) return;
@@ -243,7 +244,7 @@ export function loadManagedArtifactDoc(
}
document.transact(() => {
- applyDiskContentToDoc(document, raw, undefined, documentName);
+ applyDiskContentToDoc(document, raw);
document.getMap('lifecycle').set(LINEAGE_EPOCH_KEY, crypto.randomUUID());
}, FILE_WATCHER_ORIGIN);
@@ -345,10 +346,10 @@ export async function storeManagedArtifactDoc(
}
if (disk !== null && disk !== lkg && disk !== content) {
incrementManagedArtifactReconcile();
- const detect = ctx.beforeReconcileDivergence?.(document, documentName, content, disk);
+ ctx.beforeReconcileDivergence?.(document, documentName, content, disk);
await stashDiscardedEdit(documentName, content, ctx);
document.transact(() => {
- applyDiskContentToDoc(document, disk, undefined, documentName, undefined, detect);
+ applyDiskContentToDoc(document, disk);
}, FILE_WATCHER_ORIGIN);
ctx.setReconciledBase(documentName, disk);
ctx.lkgCache.set(documentName, disk);
@@ -382,7 +383,7 @@ export function applyExternalManagedArtifactChange(
const lkg = ctx.lkgCache.get(documentName);
if (lkg !== undefined && lkg === raw) return 'no-op';
document.transact(() => {
- applyDiskContentToDoc(document, raw, undefined, documentName);
+ applyDiskContentToDoc(document, raw);
}, FILE_WATCHER_ORIGIN);
ctx.setReconciledBase(documentName, raw);
ctx.lkgCache.set(documentName, raw);
diff --git a/packages/server/src/managed-rename.test.ts b/packages/server/src/managed-rename.test.ts
deleted file mode 100644
index 102b06122..000000000
--- a/packages/server/src/managed-rename.test.ts
+++ /dev/null
@@ -1,111 +0,0 @@
-/**
- * Pins the MANAGED_RENAME_ORIGIN paired-write order: under precedent #38 the write must be
- * ytext-first and fragment-second, so a partial failure leaves ytext new and Observer B re-derives
- * from it. Reversed, a throw after the fragment write silently reverts the rename.
- */
-
-import { applyFastDiff, sharedExtensions, stripFrontmatter } from '@inkeep/open-knowledge-core';
-import { getSchema } from '@tiptap/core';
-import { updateYFragment, yXmlFragmentToProseMirrorRootNode } from '@tiptap/y-tiptap';
-import { beforeEach, describe, expect, test } from 'vitest';
-import * as Y from 'yjs';
-import { MANAGED_RENAME_ORIGIN } from './api-extension.ts';
-import { mdManager } from './md-manager.ts';
-import { setupServerObservers } from './server-observers.ts';
-
-const schema = getSchema(sharedExtensions);
-
-function applyRenameWritesInline(
- doc: Y.Doc,
- newMarkdown: string,
- options: { throwAfterYText?: boolean } = {},
-): void {
- const xmlFragment = doc.getXmlFragment('default');
- const ytext = doc.getText('source');
- doc.transact(() => {
- const currentText = ytext.toString();
- const { body } = stripFrontmatter(newMarkdown);
- const parsedJson = mdManager.parseWithFallback(body);
- const pmNode = schema.nodeFromJSON(parsedJson);
- applyFastDiff(ytext, currentText, newMarkdown);
- if (options.throwAfterYText) {
- throw new Error('synthetic: updateYFragment failed after applyFastDiff');
- }
- updateYFragment(doc, xmlFragment, pmNode, {
- mapping: new Map(),
- isOMark: new Map(),
- });
- }, MANAGED_RENAME_ORIGIN);
-}
-
-describe('MANAGED_RENAME_ORIGIN — paired-write order property', () => {
- let doc: Y.Doc;
-
- beforeEach(() => {
- doc = new Y.Doc();
- const xmlFragment = doc.getXmlFragment('default');
- const ytext = doc.getText('source');
- const seed = '# Old\n\n[[old-page]]\n';
- doc.transact(() => {
- const seedJson = mdManager.parse(seed);
- const seedNode = schema.nodeFromJSON(seedJson);
- updateYFragment(doc, xmlFragment, seedNode, {
- mapping: new Map(),
- isOMark: new Map(),
- });
- ytext.insert(0, seed);
- }, MANAGED_RENAME_ORIGIN);
- });
-
- test('Y.Text is mutated before XmlFragment under MANAGED_RENAME_ORIGIN', () => {
- const events: string[] = [];
- const xmlFragment = doc.getXmlFragment('default');
- const ytext = doc.getText('source');
- xmlFragment.observeDeep(() => events.push('xml'));
- ytext.observe(() => events.push('ytext'));
-
- applyRenameWritesInline(doc, '# New\n\n[[new-page]]\n');
-
- expect(events.length).toBeGreaterThanOrEqual(2);
- expect(events.indexOf('ytext')).toBeLessThan(events.indexOf('xml'));
- });
-
- test('partial failure (throw after applyFastDiff): ytext holds renamed bytes', () => {
- const ytext = doc.getText('source');
-
- expect(() => {
- applyRenameWritesInline(doc, '# New\n\n[[new-page]]\n', { throwAfterYText: true });
- }).toThrow(/synthetic/);
-
- expect(ytext.toString()).toBe('# New\n\n[[new-page]]\n');
- });
-
- test('partial failure recovery: Observer B re-derives fragment from new ytext on next settlement', () => {
- const xmlFragment = doc.getXmlFragment('default');
- const ytext = doc.getText('source');
-
- expect(() => {
- applyRenameWritesInline(doc, '# New\n\n[[new-page]]\n', { throwAfterYText: true });
- }).toThrow(/synthetic/);
-
- const cleanup = setupServerObservers({
- doc,
- xmlFragment,
- ytext,
- mdManager,
- schema,
- });
-
- doc.transact(() => {
- const cur = ytext.toString();
- ytext.insert(cur.length, ' ');
- });
-
- const fragmentJson = yXmlFragmentToProseMirrorRootNode(xmlFragment, schema).toJSON();
- const fragmentBody = mdManager.serialize(fragmentJson);
- expect(fragmentBody).toContain('new-page');
- expect(fragmentBody).not.toContain('old-page');
-
- cleanup();
- });
-});
diff --git a/packages/server/src/map-driven-observer-a.test.ts b/packages/server/src/map-driven-observer-a.test.ts
deleted file mode 100644
index 9ac1445ea..000000000
--- a/packages/server/src/map-driven-observer-a.test.ts
+++ /dev/null
@@ -1,582 +0,0 @@
-import {
- MarkdownManager,
- type SerializeCallOptions,
- sharedExtensions,
-} from '@inkeep/open-knowledge-core';
-import { getSchema, type JSONContent } from '@tiptap/core';
-import { updateYFragment } from '@tiptap/y-tiptap';
-import { describe, expect, test, vi } from 'vitest';
-import * as Y from 'yjs';
-import { AGENT_WRITE_ORIGIN } from './agent-sessions.ts';
-import { composeAndWriteRawBody } from './bridge-intake.ts';
-import { getLogger } from './logger.ts';
-import { computeMapDrivenBodySplice } from './map-driven-splice.ts';
-import { getMetrics } from './metrics.ts';
-import { createCountingManager } from './parse-counting.test-helper.ts';
-import {
- __resetMapDrivenParseErrorWarnForTests,
- __resetMemoComposeFailureWarnForTests,
- OBSERVER_SYNC_ORIGIN,
- setupServerObservers,
-} from './server-observers.ts';
-
-const mdManager = new MarkdownManager({ extensions: sharedExtensions });
-const schema = getSchema(sharedExtensions);
-
-function createTestDoc() {
- const doc = new Y.Doc();
- const xmlFragment = doc.getXmlFragment('default');
- const ytext = doc.getText('source');
- return { doc, xmlFragment, ytext };
-}
-
-function populateFragment(doc: Y.Doc, xmlFragment: Y.XmlFragment, md: string): void {
- const json = mdManager.parse(md);
- const pmNode = schema.nodeFromJSON(json);
- const meta = { mapping: new Map(), isOMark: new Map() };
- updateYFragment(doc, xmlFragment, pmNode, meta);
-}
-
-interface CapturedDelta {
- readonly origin: unknown;
- readonly ops: ReadonlyArray<{ retain?: number; insert?: string | unknown[]; delete?: number }>;
-}
-
-function captureYTextDeltas(ytext: Y.Text): CapturedDelta[] {
- const captured: CapturedDelta[] = [];
- const handler = (event: Y.YTextEvent, transaction: Y.Transaction): void => {
- captured.push({ origin: transaction.origin, ops: event.changes.delta });
- };
- ytext.observe(handler);
- return captured;
-}
-
-describe('map-driven Observer A — default Path A behavior', () => {
- test('(a) single-block edit produces narrow splice covering only the edited block', () => {
- const { doc, xmlFragment, ytext } = createTestDoc();
- populateFragment(doc, xmlFragment, '# Heading\n\nFirst paragraph.\n\nSecond paragraph.\n');
- const cleanup = setupServerObservers({ doc, xmlFragment, ytext, mdManager, schema });
-
- const before = ytext.toString();
- const deltas = captureYTextDeltas(ytext);
-
- populateFragment(
- doc,
- xmlFragment,
- '# Heading\n\nFirst paragraph EDITED.\n\nSecond paragraph.\n',
- );
-
- const observerWrites = deltas.filter((d) => d.origin === OBSERVER_SYNC_ORIGIN);
- expect(observerWrites.length).toBeGreaterThanOrEqual(1);
- const mapDrivenWrite = observerWrites[observerWrites.length - 1];
-
- const retainOps = mapDrivenWrite.ops.filter((op) => op.retain !== undefined);
- const insertOps = mapDrivenWrite.ops.filter((op) => op.insert !== undefined);
- const deleteOps = mapDrivenWrite.ops.filter((op) => op.delete !== undefined);
-
- expect(retainOps.length).toBeLessThanOrEqual(2);
- expect(insertOps.length + deleteOps.length).toBeGreaterThanOrEqual(1);
-
- const headingEnd = before.indexOf('# Heading') + '# Heading'.length;
- const secondParaStart = before.indexOf('Second paragraph');
-
- const leadingRetain = mapDrivenWrite.ops[0]?.retain ?? 0;
- expect(leadingRetain).toBeGreaterThanOrEqual(headingEnd);
- expect(leadingRetain).toBeLessThanOrEqual(before.indexOf('First paragraph') + 1);
-
- let cursorAfterWrite = leadingRetain;
- for (const op of mapDrivenWrite.ops.slice(1)) {
- if (op.delete !== undefined) cursorAfterWrite += op.delete;
- }
- expect(cursorAfterWrite).toBeLessThanOrEqual(secondParaStart);
-
- cleanup();
- });
-
- test('(b) untouched bytes outside the splice are byte-identical pre→post (AC1)', () => {
- const { doc, xmlFragment, ytext } = createTestDoc();
- populateFragment(doc, xmlFragment, '# Heading\n\nFirst.\n\nUntouched bytes here.\n');
- const cleanup = setupServerObservers({ doc, xmlFragment, ytext, mdManager, schema });
-
- const before = ytext.toString();
- const untouchedSnippet = 'Untouched bytes here.';
- const untouchedStartBefore = before.indexOf(untouchedSnippet);
- expect(untouchedStartBefore).toBeGreaterThanOrEqual(0);
- const tailBefore = before.slice(untouchedStartBefore);
-
- populateFragment(doc, xmlFragment, '# Heading\n\nEDITED first.\n\nUntouched bytes here.\n');
-
- const after = ytext.toString();
- const untouchedStartAfter = after.indexOf(untouchedSnippet);
- expect(untouchedStartAfter).toBeGreaterThanOrEqual(0);
- expect(after.slice(untouchedStartAfter)).toBe(tailBefore);
-
- cleanup();
- });
-
- test('(c) contiguous multi-block edit produces splice union covering both edited blocks', () => {
- const { doc, xmlFragment, ytext } = createTestDoc();
- populateFragment(doc, xmlFragment, 'first.\n\nsecond.\n\nthird.\n');
- const cleanup = setupServerObservers({ doc, xmlFragment, ytext, mdManager, schema });
-
- const before = ytext.toString();
- const deltas = captureYTextDeltas(ytext);
-
- populateFragment(doc, xmlFragment, 'first EDITED.\n\nsecond EDITED.\n\nthird.\n');
-
- const observerWrites = deltas.filter((d) => d.origin === OBSERVER_SYNC_ORIGIN);
- const mapDrivenWrite = observerWrites[observerWrites.length - 1];
-
- const leadingRetain = mapDrivenWrite.ops[0]?.retain ?? 0;
- let cursorAfterDeletes = leadingRetain;
- for (const op of mapDrivenWrite.ops.slice(1)) {
- if (op.delete !== undefined) cursorAfterDeletes += op.delete;
- }
- const thirdStart = before.indexOf('third.');
- expect(cursorAfterDeletes).toBeLessThanOrEqual(thirdStart);
-
- const after = ytext.toString();
- expect(after).toContain('first EDITED.');
- expect(after).toContain('second EDITED.');
- expect(after.slice(after.indexOf('third.'))).toBe(before.slice(thirdStart));
-
- cleanup();
- });
-
- test('(d) synthetic-doc name short-circuits to fallback path (no map-driven splice attempted)', () => {
- const { doc, xmlFragment, ytext } = createTestDoc();
- populateFragment(doc, xmlFragment, 'A.\n\nB.\n');
- const cleanup = setupServerObservers({
- doc,
- xmlFragment,
- ytext,
- mdManager,
- schema,
- docName: '__system__',
- });
-
- populateFragment(doc, xmlFragment, 'A EDITED.\n\nB.\n');
-
- expect(ytext.toString()).toContain('A EDITED.');
- expect(ytext.toString()).toContain('B.');
-
- cleanup();
- });
-
- test('(e) edit in paragraph containing ==highlight== degrades to block granularity (documented sub-block limitation)', () => {
- const { doc, xmlFragment, ytext } = createTestDoc();
- populateFragment(doc, xmlFragment, 'Para with ==highlight== inside.\n\nUntouched after.\n');
- const cleanup = setupServerObservers({ doc, xmlFragment, ytext, mdManager, schema });
-
- const before = ytext.toString();
- const untouchedSnippet = 'Untouched after.';
- const tailBefore = before.slice(before.indexOf(untouchedSnippet));
-
- populateFragment(
- doc,
- xmlFragment,
- 'Para with ==highlight== inside EDITED.\n\nUntouched after.\n',
- );
-
- const after = ytext.toString();
- const untouchedStartAfter = after.indexOf(untouchedSnippet);
- expect(untouchedStartAfter).toBeGreaterThanOrEqual(0);
- expect(after.slice(untouchedStartAfter)).toBe(tailBefore);
-
- cleanup();
- });
-
- test('(f) map-driven splice is the default — active with no env configuration', () => {
- expect(process.env.OK_MAP_DRIVEN_OBSERVER_A).toBeUndefined();
-
- const raw = '# Notes\n\n| a | b |\n| - | - |\n| 1 | 2\n';
- const { doc, xmlFragment, ytext } = createTestDoc();
- const cleanup = setupServerObservers({ doc, xmlFragment, ytext, mdManager, schema });
- doc.transact(() => {
- composeAndWriteRawBody(doc, raw, 'agent');
- }, AGENT_WRITE_ORIGIN);
- expect(ytext.toString()).toBe(raw);
-
- populateFragment(doc, xmlFragment, raw.replace('# Notes', '# Notes EDITED'));
-
- expect(ytext.toString()).toContain('# Notes EDITED');
- expect(ytext.toString()).toContain('| 1 | 2\n');
-
- cleanup();
- });
-
- test('(g) fallback: an offset-less block (comment block) falls back to applyIncrementalDiff and still converges', () => {
- const { doc, xmlFragment, ytext } = createTestDoc();
- populateFragment(doc, xmlFragment, '\n\nOriginal.\n');
- const cleanup = setupServerObservers({ doc, xmlFragment, ytext, mdManager, schema });
-
- populateFragment(doc, xmlFragment, '\n\nOriginal.\n\nAdded.\n');
-
- expect(ytext.toString()).toContain('Original.');
- expect(ytext.toString()).toContain('Added.');
-
- cleanup();
- });
-
- describe('dash-count tripwire — concurrent source-form table edits are detected + spliced, never silently dropped', () => {
- const narrowDashBody = 'before\n\n| a | b |\n| - | - |\n| 1 | 2 |\n\nafter\n';
- const wideDashBody = 'before\n\n| a | b |\n| --- | --- |\n| 1 | 2 |\n\nafter\n';
-
- test('computeMapDrivenBodySplice detects a dash-count-only change and emits the splice', () => {
- const splice = computeMapDrivenBodySplice(
- narrowDashBody,
- mdManager.parse(wideDashBody),
- mdManager,
- );
-
- expect(splice).not.toBeNull();
- if (!splice) throw new Error('unreachable');
- const applied =
- narrowDashBody.slice(0, splice.spliceStart) +
- splice.newSlice +
- narrowDashBody.slice(splice.spliceEnd);
- expect(applied).toBe(wideDashBody);
- });
-
- test('Observer A applies a dash-count-only fragment change to Y.Text (detected + spliced, blocks outside untouched)', () => {
- const { doc, xmlFragment, ytext } = createTestDoc();
- populateFragment(doc, xmlFragment, narrowDashBody);
- const cleanup = setupServerObservers({ doc, xmlFragment, ytext, mdManager, schema });
- expect(ytext.toString()).toBe(narrowDashBody);
-
- populateFragment(doc, xmlFragment, wideDashBody);
-
- expect(ytext.toString()).toBe(wideDashBody);
-
- cleanup();
- });
- });
-
- describe('splice-path observability — applied vs fallback(reason) counters', () => {
- function fallbackTotal(m: ReturnType): number {
- return Object.values(m.mapDrivenSpliceFallback).reduce((a, b) => a + (b ?? 0), 0);
- }
-
- test('a successful map-driven splice increments mapDrivenSpliceApplied (no fallback)', () => {
- const raw = '# Heading\n\nFirst.\n\nSecond.\n';
- const { doc, xmlFragment, ytext } = createTestDoc();
- const cleanup = setupServerObservers({ doc, xmlFragment, ytext, mdManager, schema });
- doc.transact(() => {
- composeAndWriteRawBody(doc, raw, 'agent');
- }, AGENT_WRITE_ORIGIN);
- expect(ytext.toString()).toBe(raw);
-
- const before = getMetrics();
- populateFragment(doc, xmlFragment, raw.replace('First.', 'First EDITED.'));
- const after = getMetrics();
-
- expect(after.mapDrivenSpliceApplied - before.mapDrivenSpliceApplied).toBe(1);
- expect(fallbackTotal(after) - fallbackTotal(before)).toBe(0);
-
- cleanup();
- });
-
- test('a synthetic-doc drain increments fallback reason synthetic-doc, not applied', () => {
- const raw = 'A.\n\nB.\n';
- const { doc, xmlFragment, ytext } = createTestDoc();
- const cleanup = setupServerObservers({
- doc,
- xmlFragment,
- ytext,
- mdManager,
- schema,
- docName: '__system__',
- });
- doc.transact(() => {
- composeAndWriteRawBody(doc, raw, 'agent');
- }, AGENT_WRITE_ORIGIN);
-
- const before = getMetrics();
- populateFragment(doc, xmlFragment, 'A EDITED.\n\nB.\n');
- const after = getMetrics();
-
- expect(
- (after.mapDrivenSpliceFallback['synthetic-doc'] ?? 0) -
- (before.mapDrivenSpliceFallback['synthetic-doc'] ?? 0),
- ).toBe(1);
- expect(after.mapDrivenSpliceApplied - before.mapDrivenSpliceApplied).toBe(0);
-
- cleanup();
- });
-
- test('an offset-less block drain increments fallback reason missing-position', () => {
- const raw = '\n\nOriginal.\n';
- const { doc, xmlFragment, ytext } = createTestDoc();
- const cleanup = setupServerObservers({ doc, xmlFragment, ytext, mdManager, schema });
- doc.transact(() => {
- composeAndWriteRawBody(doc, raw, 'agent');
- }, AGENT_WRITE_ORIGIN);
-
- const before = getMetrics();
- populateFragment(doc, xmlFragment, '\n\nOriginal.\n\nAdded.\n');
- const after = getMetrics();
-
- expect(
- (after.mapDrivenSpliceFallback['missing-position'] ?? 0) -
- (before.mapDrivenSpliceFallback['missing-position'] ?? 0),
- ).toBe(1);
- expect(after.mapDrivenSpliceApplied - before.mapDrivenSpliceApplied).toBe(0);
-
- cleanup();
- });
-
- test('a parse/serialize throw inside the splice reports parse-error instead of vanishing', () => {
- const throwingManager = {
- parseToEditorMdast: () => {
- throw new Error('synthetic parser regression');
- },
- serialize: () => '',
- } as unknown as MarkdownManager;
- const reasons: string[] = [];
-
- const splice = computeMapDrivenBodySplice('A.\n', mdManager.parse('A.\n'), throwingManager, {
- onFallback: (reason) => {
- reasons.push(reason);
- },
- });
-
- expect(splice).toBeNull();
- expect(reasons).toEqual(['parse-error']);
- });
-
- test('a sustained parse-error fallback warns once with the error message, then stays counter-only', () => {
- const raw = '# Heading\n\nFirst.\n\nSecond.\n';
- const { doc, xmlFragment, ytext } = createTestDoc();
- const throwingManager = new Proxy(mdManager, {
- get(target, prop) {
- if (prop === 'parseToEditorMdast') {
- return () => {
- throw new Error('synthetic parser regression');
- };
- }
- const value = Reflect.get(target, prop, target);
- return typeof value === 'function' ? value.bind(target) : value;
- },
- });
- __resetMapDrivenParseErrorWarnForTests();
- const warnSpy = vi.spyOn(getLogger('server-observers'), 'warn');
- const cleanup = setupServerObservers({
- doc,
- xmlFragment,
- ytext,
- mdManager: throwingManager,
- schema,
- });
- doc.transact(() => {
- composeAndWriteRawBody(doc, raw, 'agent');
- }, AGENT_WRITE_ORIGIN);
-
- const before = getMetrics();
- populateFragment(doc, xmlFragment, raw.replace('First.', 'First EDITED.'));
- populateFragment(doc, xmlFragment, raw.replace('First.', 'First EDITED TWICE.'));
- const after = getMetrics();
-
- expect(ytext.toString()).toContain('First EDITED TWICE.');
- expect(
- (after.mapDrivenSpliceFallback['parse-error'] ?? 0) -
- (before.mapDrivenSpliceFallback['parse-error'] ?? 0),
- ).toBeGreaterThanOrEqual(2);
- const spliceWarns = warnSpy.mock.calls.filter((args) =>
- String(args[1]).includes('Map-driven splice'),
- );
- expect(spliceWarns).toHaveLength(1);
- expect((spliceWarns[0]?.[0] as { err?: Error } | undefined)?.err?.message).toBe(
- 'synthetic parser regression',
- );
-
- warnSpy.mockRestore();
- cleanup();
- });
-
- test('a memo composition failure warns once with the error, then stays counter-only', () => {
- const raw = '# Heading\n\npreserved \n\nFirst.\n\nSecond.\n\nThird.\n';
- const { doc, xmlFragment, ytext } = createTestDoc();
- const uncloneableManager = new Proxy(mdManager, {
- get(target, prop) {
- if (prop === 'parseToEditorMdast') {
- return (markdown: string) => {
- const tree = target.parseToEditorMdast(markdown);
- for (const child of tree.children) {
- (child as { data?: Record }).data = {
- uncloneable: () => undefined,
- };
- }
- return tree;
- };
- }
- const value = Reflect.get(target, prop, target);
- return typeof value === 'function' ? value.bind(target) : value;
- },
- });
- __resetMemoComposeFailureWarnForTests();
- const warnSpy = vi.spyOn(getLogger('server-observers'), 'warn');
- const cleanup = setupServerObservers({
- doc,
- xmlFragment,
- ytext,
- mdManager: uncloneableManager,
- schema,
- });
- doc.transact(() => {
- composeAndWriteRawBody(doc, raw, 'agent');
- }, AGENT_WRITE_ORIGIN);
-
- const before = getMetrics();
- populateFragment(doc, xmlFragment, raw.replace('First.', 'First EDITED.'));
- populateFragment(doc, xmlFragment, raw.replace('First.', 'First EDITED TWICE.'));
- const after = getMetrics();
-
- expect(ytext.toString()).toContain('First EDITED TWICE.');
- expect(
- (after.mapDrivenSpliceMemoSkips['compose-failed'] ?? 0) -
- (before.mapDrivenSpliceMemoSkips['compose-failed'] ?? 0),
- ).toBeGreaterThanOrEqual(2);
- const memoWarns = warnSpy.mock.calls.filter((args) =>
- String(args[1]).includes('Spliced-body memo composition threw'),
- );
- expect(memoWarns).toHaveLength(1);
- const memoWarn = memoWarns[0]?.[0] as { err?: Error } | undefined;
- expect(memoWarn?.err?.name).toBe('DataCloneError');
-
- warnSpy.mockRestore();
- cleanup();
- });
-
- test('an offset-less block reports missing-position through the pure computer', () => {
- const reasons: string[] = [];
- const splice = computeMapDrivenBodySplice(
- '\n',
- mdManager.parse('\n\nX.\n'),
- mdManager,
- {
- onFallback: (reason) => {
- reasons.push(reason);
- },
- },
- );
-
- expect(splice).toBeNull();
- expect(reasons).toEqual(['missing-position']);
- });
- });
-});
-
-describe('typing-burst parse economy (PRD-8273)', () => {
- function typeChar(doc: Y.Doc, xmlFragment: Y.XmlFragment, ch: string): void {
- doc.transact(() => {
- let node: Y.XmlElement | Y.XmlText | Y.XmlHook | undefined = xmlFragment.get(
- xmlFragment.length - 1,
- );
- while (node instanceof Y.XmlElement && node.length > 0) {
- node = node.get(node.length - 1);
- }
- if (!(node instanceof Y.XmlText)) throw new Error('no text node to type into');
- node.insert(node.length, ch);
- });
- }
-
- test('consecutive keystroke drains parse each body once, not once per drain', () => {
- const { manager: counted, parses } = createCountingManager();
- const { doc, xmlFragment, ytext } = createTestDoc();
- const cleanup = setupServerObservers({
- doc,
- xmlFragment,
- ytext,
- mdManager: counted,
- schema,
- });
-
- doc.transact(() => {
- const pmNode = schema.nodeFromJSON(counted.parse('# H\n\nalpha\n'));
- updateYFragment(doc, xmlFragment, pmNode, { mapping: new Map(), isOMark: new Map() });
- });
-
- const before = parses();
- for (const ch of 'XYZ') typeChar(doc, xmlFragment, ch);
- const burstParses = parses() - before;
-
- expect(ytext.toString()).toContain('alphaXYZ');
-
- expect(burstParses).toBe(3);
-
- cleanup();
- });
-
- test('a keystroke drain serializes the fragment JSON once, not once per consumer', () => {
- const { manager: counted, serializes } = createCountingManager();
- const { doc, xmlFragment, ytext } = createTestDoc();
- const cleanup = setupServerObservers({
- doc,
- xmlFragment,
- ytext,
- mdManager: counted,
- schema,
- });
-
- doc.transact(() => {
- const pmNode = schema.nodeFromJSON(counted.parse('# H\n\nalpha\n'));
- updateYFragment(doc, xmlFragment, pmNode, { mapping: new Map(), isOMark: new Map() });
- });
-
- const before = serializes();
- for (const ch of 'XYZ') typeChar(doc, xmlFragment, ch);
-
- expect(ytext.toString()).toContain('alphaXYZ');
- expect(serializes() - before).toBe(3);
-
- cleanup();
- });
-
- test('a drain inside the freshness window serializes for itself instead of reusing the suppressed body', () => {
- const real = new MarkdownManager({
- extensions: sharedExtensions,
- deriveStructuralFreshness: true,
- });
- const serializeOpts: Array = [];
- const recording = new Proxy(real, {
- get(target, prop, receiver) {
- if (prop === 'serialize') {
- return (json: JSONContent, opts?: SerializeCallOptions) => {
- serializeOpts.push(opts);
- return target.serialize(json, opts);
- };
- }
- const value = Reflect.get(target, prop, receiver);
- return typeof value === 'function' ? value.bind(target) : value;
- },
- });
- const { doc, xmlFragment, ytext } = createTestDoc();
- const cleanup = setupServerObservers({
- doc,
- xmlFragment,
- ytext,
- mdManager: recording,
- schema,
- docName: 'freshness-window',
- });
-
- doc.transact(() => {
- const pmNode = schema.nodeFromJSON(real.parse('# H\n\nalpha\n\nbeta\n'));
- updateYFragment(doc, xmlFragment, pmNode, { mapping: new Map(), isOMark: new Map() });
- });
-
- doc.transact(() => {
- ytext.insert(ytext.length, 'Source tail.\n');
- });
-
- const before = serializeOpts.length;
- typeChar(doc, xmlFragment, 'X');
- const drainCalls = serializeOpts.slice(before);
-
- expect(drainCalls.some((o) => o?.skipFreshnessDerive === true)).toBe(true);
- expect(drainCalls.some((o) => o?.skipFreshnessDerive !== true)).toBe(true);
- expect(ytext.toString()).toContain('X');
-
- cleanup();
- });
-});
diff --git a/packages/server/src/map-driven-splice.test.ts b/packages/server/src/map-driven-splice.test.ts
deleted file mode 100644
index f14719b2a..000000000
--- a/packages/server/src/map-driven-splice.test.ts
+++ /dev/null
@@ -1,537 +0,0 @@
-import { MarkdownManager, sharedExtensions } from '@inkeep/open-knowledge-core';
-import type { JSONContent } from '@tiptap/core';
-import { describe, expect, test } from 'vitest';
-import {
- computeMapDrivenBodySplice,
- createEditorMdastMemo,
- type EditorMdastMemo,
-} from './map-driven-splice.ts';
-import type { MapDrivenSpliceMemoSkipReason } from './metrics.ts';
-import { createCountingManager } from './parse-counting.test-helper.ts';
-
-const mdManager = new MarkdownManager({ extensions: sharedExtensions });
-
-function applySplice(
- oldBody: string,
- splice: { spliceStart: number; spliceEnd: number; newSlice: string },
-): string {
- return oldBody.slice(0, splice.spliceStart) + splice.newSlice + oldBody.slice(splice.spliceEnd);
-}
-
-function pmFromMd(md: string): JSONContent {
- return mdManager.parse(md);
-}
-
-describe('computeMapDrivenBodySplice', () => {
- describe('byte preservation outside the splice', () => {
- test('single-block edit produces splice covering only the edited block', () => {
- const oldBody = '# Heading\n\nFirst paragraph.\n\nSecond paragraph.\n';
- const newBody = '# Heading\n\nFirst paragraph EDITED.\n\nSecond paragraph.\n';
-
- const splice = computeMapDrivenBodySplice(oldBody, pmFromMd(newBody), mdManager);
- expect(splice).not.toBeNull();
- if (!splice) return;
-
- const headingEnd = oldBody.indexOf('# Heading') + '# Heading'.length;
- expect(splice.spliceStart).toBeGreaterThanOrEqual(headingEnd);
- const secondParaStart = oldBody.indexOf('Second paragraph');
- expect(splice.spliceEnd).toBeLessThanOrEqual(secondParaStart);
-
- expect(oldBody.slice(0, splice.spliceStart)).toBe(
- applySplice(oldBody, splice).slice(0, splice.spliceStart),
- );
- const reconstructed = applySplice(oldBody, splice);
- expect(reconstructed.slice(splice.spliceStart + splice.newSlice.length)).toBe(
- oldBody.slice(splice.spliceEnd),
- );
- });
-
- test('result of applying splice equals the canonical newBody serialization', () => {
- const oldBody = '# Heading\n\nFirst.\n\nSecond.\n';
- const newPm = pmFromMd('# Heading\n\nFirst CHANGED.\n\nSecond.\n');
- const splice = computeMapDrivenBodySplice(oldBody, newPm, mdManager);
- expect(splice).not.toBeNull();
- if (!splice) return;
-
- const reconstructed = applySplice(oldBody, splice);
- const canonicalNew = mdManager.serialize(newPm);
- const reconstructedMdast = mdManager.parseToMdast(reconstructed);
- const canonicalMdast = mdManager.parseToMdast(canonicalNew);
- expect(reconstructedMdast.children.length).toBe(canonicalMdast.children.length);
- });
- });
-
- describe('source-form preservation through structural equality', () => {
- test('an untouched block whose canonical form would canonicalize bytes is excluded from splice', () => {
- const oldBody = '*italic one*\n\nuntouched two\n';
- const newPmJson = pmFromMd('*italic one* EDIT\n\nuntouched two\n');
- const splice = computeMapDrivenBodySplice(oldBody, newPmJson, mdManager);
- expect(splice).not.toBeNull();
- if (!splice) return;
-
- const result = applySplice(oldBody, splice);
- expect(result).toContain('untouched two');
- const oldUntouched = oldBody.slice(oldBody.indexOf('untouched two'));
- const newUntouched = result.slice(result.indexOf('untouched two'));
- expect(newUntouched).toBe(oldUntouched);
- });
-
- test('block matching the structural shape but canonicalized in newBody is NOT spliced', () => {
- const oldBody = '*italic*\n\nplain\n';
- const newPmJson = pmFromMd('*italic*\n\nplain CHANGED\n');
- const splice = computeMapDrivenBodySplice(oldBody, newPmJson, mdManager);
- expect(splice).not.toBeNull();
- if (!splice) return;
-
- const result = applySplice(oldBody, splice);
- expect(result.startsWith('*italic*\n\n')).toBe(true);
- });
- });
-
- describe('insertions and deletions at boundaries', () => {
- test('append a new paragraph at end', () => {
- const oldBody = 'First.\n';
- const newPmJson = pmFromMd('First.\n\nSecond.\n');
- const splice = computeMapDrivenBodySplice(oldBody, newPmJson, mdManager);
- expect(splice).not.toBeNull();
- if (!splice) return;
-
- const result = applySplice(oldBody, splice);
- expect(result).toContain('First.');
- expect(result).toContain('Second.');
- expect(result.indexOf('First.')).toBe(0);
- });
-
- test('prepend a new paragraph at start', () => {
- const oldBody = 'Second.\n';
- const newPmJson = pmFromMd('First.\n\nSecond.\n');
- const splice = computeMapDrivenBodySplice(oldBody, newPmJson, mdManager);
- expect(splice).not.toBeNull();
- if (!splice) return;
-
- const result = applySplice(oldBody, splice);
- expect(result).toContain('First.');
- expect(result).toContain('Second.');
- expect(result.indexOf('First.')).toBeLessThan(result.indexOf('Second.'));
- });
-
- test('insert a paragraph in the middle preserves surrounding blocks byte-identically', () => {
- const oldBody = '*Pre*\n\nPost.\n';
- const newPmJson = pmFromMd('*Pre*\n\nMiddle.\n\nPost.\n');
- const splice = computeMapDrivenBodySplice(oldBody, newPmJson, mdManager);
- expect(splice).not.toBeNull();
- if (!splice) return;
-
- const result = applySplice(oldBody, splice);
- expect(result.startsWith('*Pre*')).toBe(true);
- expect(result).toContain('Middle.');
- expect(result.endsWith('Post.\n')).toBe(true);
- });
-
- test('delete a middle block', () => {
- const oldBody = 'A.\n\nB.\n\nC.\n';
- const newPmJson = pmFromMd('A.\n\nC.\n');
- const splice = computeMapDrivenBodySplice(oldBody, newPmJson, mdManager);
- expect(splice).not.toBeNull();
- if (!splice) return;
-
- const result = applySplice(oldBody, splice);
- expect(result).toContain('A.');
- expect(result).toContain('C.');
- expect(result).not.toContain('B.');
- });
- });
-
- describe('synthetic / empty inputs', () => {
- test('empty oldBody + new content produces splice that yields the new content', () => {
- const oldBody = '';
- const newPmJson = pmFromMd('A new paragraph.\n');
- const splice = computeMapDrivenBodySplice(oldBody, newPmJson, mdManager);
- expect(splice).not.toBeNull();
- if (!splice) return;
-
- const result = applySplice(oldBody, splice);
- expect(result).toContain('A new paragraph.');
- });
-
- test('no-change input produces no-op splice', () => {
- const oldBody = 'A.\n\nB.\n';
- const newPmJson = pmFromMd(oldBody);
- const splice = computeMapDrivenBodySplice(oldBody, newPmJson, mdManager);
- expect(splice).not.toBeNull();
- if (!splice) return;
-
- const result = applySplice(oldBody, splice);
- const oldChildren = mdManager.parseToMdast(oldBody).children;
- const resultChildren = mdManager.parseToMdast(result).children;
- expect(resultChildren.length).toBe(oldChildren.length);
- });
- });
-
- describe('contiguous multi-block edits', () => {
- test('editing two adjacent blocks unions their splice ranges', () => {
- const oldBody = 'first.\n\nsecond.\n\nthird.\n';
- const newPmJson = pmFromMd('first EDITED.\n\nsecond EDITED.\n\nthird.\n');
- const splice = computeMapDrivenBodySplice(oldBody, newPmJson, mdManager);
- expect(splice).not.toBeNull();
- if (!splice) return;
-
- const result = applySplice(oldBody, splice);
- expect(result.endsWith('third.\n')).toBe(true);
-
- const thirdStartOld = oldBody.indexOf('third.');
- expect(splice.spliceEnd).toBeLessThanOrEqual(thirdStartOld);
- });
-
- test('non-contiguous multi-block edits collapse into one over-wide splice (documented AC2 degradation)', () => {
- const oldBody = 'first.\n\nmiddle.\n\nthird.\n';
- const newPmJson = pmFromMd('first EDITED.\n\nmiddle.\n\nthird EDITED.\n');
- const splice = computeMapDrivenBodySplice(oldBody, newPmJson, mdManager);
- expect(splice).not.toBeNull();
- if (!splice) return;
-
- const middleStart = oldBody.indexOf('middle.');
- expect(splice.spliceStart).toBeLessThanOrEqual(middleStart);
- expect(splice.spliceEnd).toBeGreaterThanOrEqual(middleStart + 'middle.'.length);
-
- const result = applySplice(oldBody, splice);
- expect(result).toContain('first EDITED.');
- expect(result).toContain('middle.');
- expect(result).toContain('third EDITED.');
- });
- });
-
- describe('robustness to parse failure', () => {
- test('returns null when serialize throws on schema-rejected JSON', () => {
- const oldBody = 'A.\n';
- const malformed = { type: 'not-a-real-node-type' } as JSONContent;
- const splice = computeMapDrivenBodySplice(oldBody, malformed, mdManager);
- expect(splice).toBeNull();
- });
- });
-});
-
-describe('editor-mdast parse memo (PRD-8273)', () => {
- test('a repeated body is parsed once, not once per call', () => {
- const { manager: counted, parses } = createCountingManager();
- const memo = createEditorMdastMemo();
- const bodyA = '# H\n\nalpha\n';
-
- const first = computeMapDrivenBodySplice(bodyA, counted.parse('# H\n\nalphaX\n'), counted, {
- memo,
- });
- expect(first).not.toBeNull();
- if (!first) return;
- const bodyB = applySplice(bodyA, first);
- const afterFirst = parses();
-
- computeMapDrivenBodySplice(bodyB, counted.parse('# H\n\nalphaXY\n'), counted, { memo });
-
- expect(parses() - afterFirst).toBe(1);
- });
-
- test('a body changed out from under the memo misses rather than serving a stale parse', () => {
- const { manager: counted, parses } = createCountingManager();
- const memo = createEditorMdastMemo();
-
- const primed = '# H\n\nalpha\n';
- computeMapDrivenBodySplice(primed, counted.parse('# H\n\nalphaX\n'), counted, { memo });
- const afterPrime = parses();
-
- const external = '# DIFFERENT\n\nomega\n\ntail\n';
- const splice = computeMapDrivenBodySplice(
- external,
- counted.parse('# DIFFERENT\n\nomega EDITED\n\ntail\n'),
- counted,
- { memo },
- );
- expect(splice).not.toBeNull();
- if (!splice) return;
-
- expect(parses() - afterPrime).toBe(2);
-
- const result = applySplice(external, splice);
- expect(result).toContain('omega EDITED');
- expect(result).toContain('# DIFFERENT');
- expect(result.endsWith('tail\n')).toBe(true);
- });
-
- test('a splice built from a memo hit equals one built from a fresh parse', () => {
- const { manager: counted, parses } = createCountingManager();
- const memo = createEditorMdastMemo();
- const bodyA = '# H\n\none\n\ntwo\n\nthree\n';
-
- const first = computeMapDrivenBodySplice(
- bodyA,
- counted.parse('# H\n\none EDITED\n\ntwo\n\nthree\n'),
- counted,
- { memo },
- );
- expect(first).not.toBeNull();
- if (!first) return;
- const bodyB = applySplice(bodyA, first);
-
- const newPm = counted.parse('# H\n\none EDITED\n\ntwo CHANGED\n\nthree\n');
- const before = parses();
- const fromHit = computeMapDrivenBodySplice(bodyB, newPm, counted, { memo });
- expect(parses() - before).toBe(1);
-
- const fromFreshParse = computeMapDrivenBodySplice(bodyB, newPm, counted);
- expect(fromHit).toEqual(fromFreshParse);
- expect(fromHit).not.toBeNull();
- });
-
- test('preserved bytes the serializer would not emit still hit the next drain', () => {
- const { manager: counted, parses } = createCountingManager();
- const memo = createEditorMdastMemo();
- const bodyA = 'one \n\ntwo\n\nthree\n';
-
- const first = computeMapDrivenBodySplice(
- bodyA,
- counted.parse('one \n\ntwo A\n\nthree\n'),
- counted,
- { memo },
- );
- expect(first).not.toBeNull();
- if (!first) return;
- const bodyB = applySplice(bodyA, first);
- expect(bodyB).not.toBe(counted.serialize(counted.parse(bodyB)));
- expect(memo.entry?.body).toBe(bodyB);
-
- const newPm = counted.parse('one \n\ntwo B\n\nthree\n');
- const before = parses();
- const fromHit = computeMapDrivenBodySplice(bodyB, newPm, counted, { memo });
- expect(parses() - before).toBe(1);
- expect(fromHit).toEqual(computeMapDrivenBodySplice(bodyB, newPm, counted));
- });
-
- test('a same-length body with different content misses — the key is bytes, not length', () => {
- const { manager: counted, parses } = createCountingManager();
- const memo = createEditorMdastMemo();
-
- const primed = counted.serialize(counted.parse('one\n\ntwo\n\nthree\n'));
- const actual = 'one two\n\nthreeX\n';
- expect(actual.length).toBe(primed.length);
- expect(actual).not.toBe(primed);
-
- computeMapDrivenBodySplice('zzz\n', counted.parse(primed), counted, { memo });
-
- const newPm = counted.parse('one two\n\nthreeY\n');
- const before = parses();
- const withMemo = computeMapDrivenBodySplice(actual, newPm, counted, { memo });
- expect(parses() - before).toBe(2);
-
- const withoutMemo = computeMapDrivenBodySplice(actual, newPm, counted);
- expect(withMemo).toEqual(withoutMemo);
- expect(withMemo).not.toBeNull();
- });
-});
-
-describe('caller-supplied serialization', () => {
- test('a body serialized from the same PM JSON is reused instead of re-serialized', () => {
- const { manager: counted, serializes } = createCountingManager();
- const memo = createEditorMdastMemo();
- const oldBody = '# H\n\none\n\ntwo\n';
- const newPm = counted.parse('# H\n\none EDITED\n\ntwo\n');
- const body = counted.serialize(newPm);
-
- const before = serializes();
- const reused = computeMapDrivenBodySplice(oldBody, newPm, counted, {
- memo,
- serializedNewPm: { json: newPm, body, opts: undefined },
- });
- expect(serializes() - before).toBe(0);
- expect(reused).toEqual(computeMapDrivenBodySplice(oldBody, newPm, counted));
- });
-
- test('a body carried from different PM JSON is ignored and the splice serializes itself', () => {
- const { manager: counted, serializes } = createCountingManager();
- const oldBody = '# H\n\none\n\ntwo\n';
- const newPm = counted.parse('# H\n\none EDITED\n\ntwo\n');
- const stale = counted.parse('# H\n\nSTALE\n\ntwo\n');
- const staleBody = counted.serialize(stale);
-
- const before = serializes();
- const splice = computeMapDrivenBodySplice(oldBody, newPm, counted, {
- serializedNewPm: { json: stale, body: staleBody, opts: undefined },
- });
- const selfSerializes = serializes() - before;
- expect(selfSerializes).toBe(1);
- expect(splice).toEqual(computeMapDrivenBodySplice(oldBody, newPm, counted));
- });
-});
-
-describe('spliced-body memo fidelity', () => {
- const DIRTY_PREFIX = 'preserved \n\n';
-
- const NARROWABLE_SHAPES = new Set(['list', 'blockquote', 'loose-list']);
-
- const CORPUS: Array<[string, string]> = [
- ['paragraphs', 'one\n\ntwo\n\nthree\n\nfour\n'],
- ['headings', '# H\n\npara\n\n## H2\n\npara two\n\ntail\n'],
- ['list', '# H\n\n- one\n- two\n- three\n\npara\n\ntail\n'],
- ['blockquote', '# H\n\n> one\n>\n> two\n\npara\n\ntail\n'],
- ['table', '# H\n\n| a | b |\n|---|---|\n| 1 | 2 |\n\npara\n\ntail\n'],
- ['code-fence', '# H\n\n```js\nconst a = 1;\n```\n\npara\n\ntail\n'],
- ['setext', 'Title\n=====\n\npara\n\ntail\n'],
- ['definitions', '# H\n\n[a]: http://example.com\n\nsee [a]\n\npara two\n\ntail\n'],
- ['footnote', '# H\n\ntext[^1]\n\n[^1]: note\n\npara two\n\ntail\n'],
- ['dirty-bytes', 'one \n\ntwo\n\nthree\n\nfour\n'],
- ['blank-runs', 'one\n\n\n\ntwo\n\nthree\n\nfour\n'],
- ['thematic-break', 'one\n\n---\n\ntwo\n\nthree\n'],
- ['loose-list', '# H\n\n- one\n\n- two\n\npara\n\ntail\n'],
- [
- 'many-blocks',
- `${Array.from({ length: 60 }, (_, i) => `Paragraph ${i} body text.`).join('\n\n')}\n`,
- ],
- ];
-
- const EDITS: Array<[string, (text: string) => string]> = [
- ['append-char', (text) => `${text}Z`],
- ['prepend-char', (text) => `Z${text}`],
- ['emphasis-marker', (text) => `${text} *em*`],
- ['dash-run', (text) => `${text} ---`],
- ];
-
- function editFirstLeaf(
- json: JSONContent,
- blockIndex: number,
- edit: (text: string) => string,
- ): JSONContent | null {
- const clone = structuredClone(json) as JSONContent;
- const block = clone.content?.[blockIndex];
- if (!block) return null;
- const stack: JSONContent[] = [block];
- while (stack.length > 0) {
- const node = stack.pop();
- if (!node) continue;
- if (typeof node.text === 'string') {
- node.text = edit(node.text);
- return clone;
- }
- for (const child of node.content ?? []) stack.push(child);
- }
- return null;
- }
-
- function appendToFirstLeaf(json: JSONContent, blockIndex: number): JSONContent | null {
- return editFirstLeaf(json, blockIndex, (text) => `${text}Z`);
- }
-
- for (const [label, canonicalDoc] of CORPUS) {
- for (const [form, doc] of [
- ['canonical', canonicalDoc],
- ['non-canonical', `${DIRTY_PREFIX}${canonicalDoc}`],
- ] as const) {
- test(`${label} (${form}): every composed entry equals a fresh parse of the spliced body`, () => {
- const pm = mdManager.parse(doc);
- const blockCount = pm.content?.length ?? 0;
- expect(blockCount).toBeGreaterThan(0);
- const firstEditable = form === 'canonical' ? 0 : 1;
- let composed = 0;
- let attempted = 0;
- const skips: MapDrivenSpliceMemoSkipReason[] = [];
-
- for (let index = firstEditable; index < blockCount; index++) {
- for (const [, edit] of EDITS) {
- const edited = editFirstLeaf(pm, index, edit);
- if (!edited) continue;
- attempted++;
- const memo = createEditorMdastMemo();
- const newBody = mdManager.serialize(edited);
- const splice = computeMapDrivenBodySplice(doc, edited, mdManager, {
- memo,
- onMemoSkip: (reason) => skips.push(reason),
- });
- expect(splice).not.toBeNull();
- if (!splice) continue;
- const applied = applySplice(doc, splice);
- expect(memo.entry).not.toBeNull();
- if (applied === newBody) {
- expect(memo.entry?.body).toBe(newBody);
- continue;
- }
- if (memo.entry?.body !== applied) continue;
- composed++;
- expect(JSON.stringify(memo.entry.children)).toBe(
- JSON.stringify(mdManager.parseToEditorMdast(applied).children),
- );
- }
- }
-
- const narrowedSkips = skips.filter((reason) => reason === 'narrowed').length;
- const alreadyCurrentSkips = skips.filter(
- (reason) => reason === 'entry-already-current',
- ).length;
-
- expect(attempted).toBeGreaterThan(0);
- expect(skips.filter((r) => r !== 'narrowed' && r !== 'entry-already-current')).toEqual([]);
- expect(composed).toBe(attempted - narrowedSkips - alreadyCurrentSkips);
- if (form === 'canonical') {
- expect(alreadyCurrentSkips).toBeGreaterThan(0);
- } else {
- expect(composed).toBeGreaterThan(0);
- }
- if (NARROWABLE_SHAPES.has(label)) {
- expect(narrowedSkips).toBeGreaterThan(0);
- } else {
- expect(narrowedSkips).toBe(0);
- }
- });
- }
- }
-
- test('a container-narrowed splice leaves the constructed entry unwritten', () => {
- const doc = 'para \n\n- one\n- two\n- three\n\ntail\n';
- const pm = mdManager.parse(doc);
- const edited = appendToFirstLeaf(pm, 1);
- expect(edited).not.toBeNull();
- if (!edited) return;
- const memo = createEditorMdastMemo();
- const splice = computeMapDrivenBodySplice(doc, edited, mdManager, { memo });
- expect(splice).not.toBeNull();
- if (!splice) return;
- const applied = applySplice(doc, splice);
- const newBody = mdManager.serialize(edited);
- expect(applied).not.toBe(newBody);
- expect(applied.startsWith('para ')).toBe(true);
- expect(splice.spliceStart).toBeGreaterThan(doc.indexOf('- one'));
- expect(memo.entry?.body).toBe(newBody);
- });
-});
-
-describe('spliced-body memo failure isolation', () => {
- test('a throwing memo write costs the optimization, not the splice', () => {
- const oldBody = 'preserved \n\none\n\ntwo\n\nthree\n';
- const newPm = mdManager.parse('preserved \n\none EDITED\n\ntwo\n\nthree\n');
- const reasons: MapDrivenSpliceMemoSkipReason[] = [];
- const errors: unknown[] = [];
- const composeFailure = new Error('synthetic composition regression');
- let stored: EditorMdastMemo['entry'] = null;
- let writes = 0;
- const hostileMemo = {
- get entry() {
- return stored;
- },
- set entry(value: EditorMdastMemo['entry']) {
- writes++;
- if (writes > 2) throw composeFailure;
- stored = value;
- },
- } as EditorMdastMemo;
-
- const splice = computeMapDrivenBodySplice(oldBody, newPm, mdManager, {
- memo: hostileMemo,
- onMemoSkip: (reason, err) => {
- reasons.push(reason);
- errors.push(err);
- },
- });
-
- expect(splice).not.toBeNull();
- expect(splice).toEqual(computeMapDrivenBodySplice(oldBody, newPm, mdManager));
- expect(writes).toBe(3);
- expect(reasons).toEqual(['compose-failed']);
- expect(errors).toEqual([composeFailure]);
- });
-});
diff --git a/packages/server/src/map-driven-splice.ts b/packages/server/src/map-driven-splice.ts
deleted file mode 100644
index 5227f250f..000000000
--- a/packages/server/src/map-driven-splice.ts
+++ /dev/null
@@ -1,354 +0,0 @@
-import type { MarkdownManager, SerializeCallOptions } from '@inkeep/open-knowledge-core';
-import type { JSONContent } from '@tiptap/core';
-import type { RootContent } from 'mdast';
-import type { MapDrivenSpliceMemoSkipReason } from './metrics.ts';
-
-export interface MapDrivenSplice {
- readonly spliceStart: number;
- readonly spliceEnd: number;
- readonly newSlice: string;
-}
-
-export interface SerializedEditorBody {
- readonly json: JSONContent;
- readonly body: string;
- readonly opts: SerializeCallOptions | undefined;
-}
-
-export function serializeEditorBody(
- mdManager: MarkdownManager,
- json: JSONContent,
- opts?: SerializeCallOptions,
-): SerializedEditorBody {
- return { json, body: mdManager.serialize(json, opts), opts };
-}
-
-function reusableBodyFrom(
- serializedNewPm: SerializedEditorBody | undefined,
- newPmJson: JSONContent,
-): string | undefined {
- if (serializedNewPm === undefined) return undefined;
- if (serializedNewPm.json !== newPmJson) return undefined;
- if (serializedNewPm.opts?.skipFreshnessDerive === true) return undefined;
- return serializedNewPm.body;
-}
-
-export interface MapDrivenSpliceOptions {
- readonly onFallback?: (reason: 'parse-error' | 'missing-position', err?: unknown) => void;
- readonly onMemoHit?: () => void;
- readonly onMemoSkip?: (reason: MapDrivenSpliceMemoSkipReason, err?: unknown) => void;
- readonly memo?: EditorMdastMemo;
- readonly serializedNewPm?: SerializedEditorBody;
-}
-
-export interface EditorMdastMemo {
- entry: { readonly body: string; readonly children: readonly RootContent[] } | null;
-}
-
-export function createEditorMdastMemo(): EditorMdastMemo {
- return { entry: null };
-}
-
-function editorMdastChildren(
- mdManager: MarkdownManager,
- body: string,
- memo: EditorMdastMemo | undefined,
- onHit?: () => void,
-): readonly RootContent[] {
- if (memo?.entry?.body === body) {
- onHit?.();
- return memo.entry.children;
- }
- const children = mdManager.parseToEditorMdast(body).children;
- if (memo !== undefined) memo.entry = { body, children };
- return children;
-}
-
-export function computeMapDrivenBodySplice(
- oldBody: string,
- newPmJson: JSONContent,
- mdManager: MarkdownManager,
- options: MapDrivenSpliceOptions = {},
-): MapDrivenSplice | null {
- const { onFallback, onMemoHit, onMemoSkip, memo, serializedNewPm } = options;
- let oldChildren: readonly RootContent[];
- let newBody: string;
- let newChildren: readonly RootContent[];
- try {
- oldChildren = editorMdastChildren(mdManager, oldBody, memo, onMemoHit);
- newBody = reusableBodyFrom(serializedNewPm, newPmJson) ?? mdManager.serialize(newPmJson);
- newChildren = editorMdastChildren(mdManager, newBody, memo);
- } catch (err) {
- onFallback?.('parse-error', err);
- return null;
- }
-
- if (!allBlocksCarryPositions(oldChildren) || !allBlocksCarryPositions(newChildren)) {
- onFallback?.('missing-position');
- return null;
- }
-
- let walked: ChildrenSplice;
- try {
- walked = computeChildrenSplice(
- oldChildren,
- newChildren,
- {
- start: oldChildren.length > 0 ? blockStartOffset(oldChildren[0]) : 0,
- end: oldBody.length,
- },
- {
- start: newChildren.length > 0 ? blockStartOffset(newChildren[0]) : 0,
- end: newBody.length,
- },
- newBody,
- );
- } catch (err) {
- onFallback?.('missing-position', err);
- return null;
- }
-
- if (memo !== undefined) {
- try {
- memoizeSplicedBody(memo, { oldBody, oldChildren, newBody, newChildren, walked }, onMemoSkip);
- } catch (err) {
- onMemoSkip?.('compose-failed', err);
- }
- }
- return walked.splice;
-}
-
-interface ByteRegion {
- readonly start: number;
- readonly end: number;
-}
-
-type ChildrenSplice =
- | { readonly narrowed: true; readonly splice: MapDrivenSplice }
- | {
- readonly narrowed: false;
- readonly splice: MapDrivenSplice;
- readonly prefixLen: number;
- readonly suffixLen: number;
- readonly newSliceStart: number;
- };
-
-const NARROWABLE_CONTAINER_TYPES = new Set(['blockquote', 'list', 'listItem']);
-
-function computeChildrenSplice(
- oldChildren: readonly RootContent[],
- newChildren: readonly RootContent[],
- oldRegion: ByteRegion,
- newRegion: ByteRegion,
- newBody: string,
-): ChildrenSplice {
- let prefixLen = 0;
- while (
- prefixLen < oldChildren.length &&
- prefixLen < newChildren.length &&
- structurallyEqual(oldChildren[prefixLen], newChildren[prefixLen])
- ) {
- prefixLen++;
- }
-
- let suffixLen = 0;
- while (
- suffixLen < oldChildren.length - prefixLen &&
- suffixLen < newChildren.length - prefixLen &&
- structurallyEqual(
- oldChildren[oldChildren.length - 1 - suffixLen],
- newChildren[newChildren.length - 1 - suffixLen],
- )
- ) {
- suffixLen++;
- }
-
- if (
- oldChildren.length - prefixLen - suffixLen === 1 &&
- newChildren.length - prefixLen - suffixLen === 1
- ) {
- const oldChanged = oldChildren[prefixLen];
- const newChanged = newChildren[prefixLen];
- const narrowed = tryNarrowIntoContainer(oldChanged, newChanged, newBody);
- if (narrowed) return { narrowed: true, splice: narrowed.splice };
- }
-
- const spliceStart = prefixLen > 0 ? blockEndOffset(oldChildren[prefixLen - 1]) : oldRegion.start;
- const spliceEnd =
- suffixLen > 0 ? blockStartOffset(oldChildren[oldChildren.length - suffixLen]) : oldRegion.end;
-
- const newSliceStart =
- prefixLen > 0 ? blockEndOffset(newChildren[prefixLen - 1]) : newRegion.start;
- const newSliceEnd =
- suffixLen > 0 ? blockStartOffset(newChildren[newChildren.length - suffixLen]) : newRegion.end;
-
- return {
- splice: {
- spliceStart,
- spliceEnd,
- newSlice: newBody.slice(newSliceStart, newSliceEnd),
- },
- prefixLen,
- suffixLen,
- newSliceStart,
- narrowed: false,
- };
-}
-
-function tryNarrowIntoContainer(
- oldNode: RootContent,
- newNode: RootContent,
- newBody: string,
-): ChildrenSplice | null {
- if (oldNode.type !== newNode.type || !NARROWABLE_CONTAINER_TYPES.has(oldNode.type)) return null;
- if (!('children' in oldNode) || !('children' in newNode)) return null;
- const oldKids = oldNode.children as readonly RootContent[];
- const newKids = newNode.children as readonly RootContent[];
- if (oldKids.length === 0 || newKids.length === 0) return null;
- if (!allBlocksCarryPositions(oldKids) || !allBlocksCarryPositions(newKids)) return null;
- if (
- stringifyIgnorePosition({ ...oldNode, children: [] }) !==
- stringifyIgnorePosition({ ...newNode, children: [] })
- ) {
- return null;
- }
- return computeChildrenSplice(
- oldKids,
- newKids,
- { start: blockStartOffset(oldNode), end: blockEndOffset(oldNode) },
- { start: blockStartOffset(newNode), end: blockEndOffset(newNode) },
- newBody,
- );
-}
-
-interface SplicedBodyInputs {
- readonly oldBody: string;
- readonly oldChildren: readonly RootContent[];
- readonly newBody: string;
- readonly newChildren: readonly RootContent[];
- readonly walked: ChildrenSplice;
-}
-
-function memoizeSplicedBody(
- memo: EditorMdastMemo,
- inputs: SplicedBodyInputs,
- onSkip: ((reason: MapDrivenSpliceMemoSkipReason, err?: unknown) => void) | undefined,
-): void {
- const { oldBody, oldChildren, newBody, newChildren, walked } = inputs;
- if (walked.narrowed) {
- onSkip?.('narrowed');
- return;
- }
- if (oldChildren.length === 0 || newChildren.length === 0) {
- onSkip?.('empty-children');
- return;
- }
-
- const { splice, prefixLen, suffixLen, newSliceStart } = walked;
- const applied =
- oldBody.slice(0, splice.spliceStart) + splice.newSlice + oldBody.slice(splice.spliceEnd);
- if (applied === newBody) {
- onSkip?.('entry-already-current');
- return;
- }
-
- const mid = cloneShifted(
- newChildren.slice(prefixLen, newChildren.length - suffixLen),
- splice.spliceStart - newSliceStart,
- countNewlines(oldBody, 0, splice.spliceStart) - countNewlines(newBody, 0, newSliceStart),
- );
- if (mid === null) {
- onSkip?.('position-not-numeric');
- return;
- }
-
- const tail = cloneShifted(
- oldChildren.slice(oldChildren.length - suffixLen),
- applied.length - oldBody.length,
- countNewlines(splice.newSlice, 0, splice.newSlice.length) -
- countNewlines(oldBody, splice.spliceStart, splice.spliceEnd),
- );
- if (tail === null) {
- onSkip?.('position-not-numeric');
- return;
- }
-
- memo.entry = { body: applied, children: [...oldChildren.slice(0, prefixLen), ...mid, ...tail] };
-}
-
-function cloneShifted(
- nodes: readonly RootContent[],
- byteDelta: number,
- lineDelta: number,
-): RootContent[] | null {
- const cloned = structuredClone(nodes) as RootContent[];
- return shiftPositions(cloned, byteDelta, lineDelta) ? cloned : null;
-}
-
-function shiftPositions(value: unknown, byteDelta: number, lineDelta: number): boolean {
- if (Array.isArray(value)) {
- for (const entry of value) {
- if (!shiftPositions(entry, byteDelta, lineDelta)) return false;
- }
- return true;
- }
- if (value === null || typeof value !== 'object') return true;
- const node = value as Record;
- const position = node.position as
- | { start?: { offset?: number; line?: number }; end?: { offset?: number; line?: number } }
- | undefined;
- if (position !== undefined) {
- for (const point of [position.start, position.end]) {
- if (point === undefined) continue;
- if (typeof point.offset !== 'number' || typeof point.line !== 'number') return false;
- point.offset += byteDelta;
- point.line += lineDelta;
- }
- }
- for (const key of Object.keys(node)) {
- if (key === 'position') continue;
- if (!shiftPositions(node[key], byteDelta, lineDelta)) return false;
- }
- return true;
-}
-
-function countNewlines(text: string, from: number, to: number): number {
- let count = 0;
- for (let index = from; index < to; index++) {
- if (text.charCodeAt(index) === 10) count++;
- }
- return count;
-}
-
-function allBlocksCarryPositions(children: readonly RootContent[]): boolean {
- for (const child of children) {
- const start = child.position?.start?.offset;
- const end = child.position?.end?.offset;
- if (typeof start !== 'number' || typeof end !== 'number') return false;
- }
- return true;
-}
-
-function blockStartOffset(node: RootContent): number {
- const offset = node.position?.start?.offset;
- if (typeof offset !== 'number') {
- throw new Error('mdast node missing position.start.offset');
- }
- return offset;
-}
-
-function blockEndOffset(node: RootContent): number {
- const offset = node.position?.end?.offset;
- if (typeof offset !== 'number') {
- throw new Error('mdast node missing position.end.offset');
- }
- return offset;
-}
-
-function structurallyEqual(a: RootContent, b: RootContent): boolean {
- return stringifyIgnorePosition(a) === stringifyIgnorePosition(b);
-}
-
-function stringifyIgnorePosition(node: unknown): string {
- return JSON.stringify(node, (key, value) => (key === 'position' ? undefined : value));
-}
diff --git a/packages/server/src/map-driven-splice.unchanged-detector.test.ts b/packages/server/src/map-driven-splice.unchanged-detector.test.ts
deleted file mode 100644
index 219fa70a8..000000000
--- a/packages/server/src/map-driven-splice.unchanged-detector.test.ts
+++ /dev/null
@@ -1,103 +0,0 @@
-import { MarkdownManager, sharedExtensions } from '@inkeep/open-knowledge-core';
-import type { JSONContent } from '@tiptap/core';
-import { describe, expect, test } from 'vitest';
-import { computeMapDrivenBodySplice } from './map-driven-splice.ts';
-
-const mdManager = new MarkdownManager({ extensions: sharedExtensions });
-
-function applySplice(
- oldBody: string,
- splice: { spliceStart: number; spliceEnd: number; newSlice: string },
-): string {
- return oldBody.slice(0, splice.spliceStart) + splice.newSlice + oldBody.slice(splice.spliceEnd);
-}
-
-function editTextNode(pm: JSONContent, marker: string): JSONContent {
- const clone = JSON.parse(JSON.stringify(pm)) as JSONContent;
- let done = false;
- const walk = (node: JSONContent): void => {
- if (done) return;
- if (node.type === 'text' && typeof node.text === 'string' && node.text.includes(marker)) {
- node.text = `${node.text} EDITWORD`;
- done = true;
- return;
- }
- for (const child of node.content ?? []) walk(child);
- };
- walk(clone);
- if (!done) throw new Error(`marker not found in PM doc: ${marker}`);
- return clone;
-}
-
-describe('computeMapDrivenBodySplice unchanged-block detection', () => {
- test('lazy-continuation blockquote untouched by the edit stays outside the splice', () => {
- const oldBody = '> lazy first line\nlazy continuation stays\n\nSeparate paragraph.\n';
- const pm = editTextNode(mdManager.parse(oldBody), 'Separate paragraph.');
-
- const splice = computeMapDrivenBodySplice(oldBody, pm, mdManager);
- expect(splice).not.toBeNull();
- if (!splice) return;
-
- const result = applySplice(oldBody, splice);
- expect(result).toBe(
- '> lazy first line\nlazy continuation stays\n\nSeparate paragraph. EDITWORD\n',
- );
- });
-
- test('same-list edit preserves a sibling item containing a multi-blank run', () => {
- const oldBody = '- item one\n\n para in item\n\n\n wide gap para\n- item two editable\n';
- const pm = editTextNode(mdManager.parse(oldBody), 'item two editable');
-
- const splice = computeMapDrivenBodySplice(oldBody, pm, mdManager);
- expect(splice).not.toBeNull();
- if (!splice) return;
-
- const result = applySplice(oldBody, splice);
- expect(result).toBe(
- '- item one\n\n para in item\n\n\n wide gap para\n- item two editable EDITWORD\n',
- );
- });
-
- test('same-blockquote edit preserves a sibling lazy-continuation paragraph', () => {
- const oldBody = '> lazy first line\nlazy continuation stays\n>\n> editable second para\n';
- const pm = editTextNode(mdManager.parse(oldBody), 'editable second para');
-
- const splice = computeMapDrivenBodySplice(oldBody, pm, mdManager);
- expect(splice).not.toBeNull();
- if (!splice) return;
-
- const result = applySplice(oldBody, splice);
- expect(result).toBe(
- '> lazy first line\nlazy continuation stays\n>\n> editable second para EDITWORD\n',
- );
- });
-
- test('container-data-only change blocks narrowing: bullet-marker flip rewrites the whole list', () => {
- const oldBody = '* item one\n* item two\n';
- const pm = JSON.parse(JSON.stringify(mdManager.parse(oldBody))) as JSONContent;
- const list = pm.content?.find((n) => n.type === 'list');
- if (!list?.attrs) throw new Error('list node with attrs not found');
- expect(list.attrs.bulletMarker).toBe('*');
- list.attrs.bulletMarker = '-';
-
- const splice = computeMapDrivenBodySplice(oldBody, pm, mdManager);
- expect(splice).not.toBeNull();
- if (!splice) return;
-
- const result = applySplice(oldBody, splice);
- expect(result).toBe('- item one\n- item two\n');
- });
-
- test('a REAL source-form change on an otherwise text-identical block is still detected (dash-count tripwire twin)', () => {
- const oldBody = '| A | B |\n| - | - |\n| x | y |\n';
- const newBody = '| A | B |\n| --- | --- |\n| x | y |\n';
- const pm = mdManager.parse(newBody);
-
- const splice = computeMapDrivenBodySplice(oldBody, pm, mdManager);
- expect(splice).not.toBeNull();
- if (!splice) return;
-
- const result = applySplice(oldBody, splice);
- expect(result).toBe(newBody);
- });
-});
diff --git a/packages/server/src/mcp-mount.test.ts b/packages/server/src/mcp-mount.test.ts
index 3a4f88027..9e32b3d35 100644
--- a/packages/server/src/mcp-mount.test.ts
+++ b/packages/server/src/mcp-mount.test.ts
@@ -282,6 +282,18 @@ describe('mountMcpAndApi /mcp guard', () => {
expect(calls).toBe(0);
});
+ test('names the dropped upgrade instead of destroying the socket silently', async () => {
+ vi.mocked(log.warn).mockClear();
+ const { port } = await startMountedServer({ handle: async () => {}, close: async () => {} });
+
+ await expect(requestUnknownUpgrade(port)).resolves.toBe('');
+
+ expect(log.warn).toHaveBeenCalledWith(
+ expect.objectContaining({ host: expect.stringContaining('127.0.0.1') }),
+ expect.stringContaining('upgrade dropped'),
+ );
+ });
+
test('warns before closing a proxied unknown upgrade', async () => {
vi.mocked(log.warn).mockClear();
const { port } = await startMountedServer({ handle: async () => {}, close: async () => {} });
diff --git a/packages/server/src/mcp-mount.ts b/packages/server/src/mcp-mount.ts
index 5a2f52d91..1084d0e44 100644
--- a/packages/server/src/mcp-mount.ts
+++ b/packages/server/src/mcp-mount.ts
@@ -106,6 +106,9 @@ export function mountMcpAndApi(opts: MountMcpAndApiOptions): MountMcpAndApiHandl
});
};
+ /* STOP: every path out of this function destroys the socket, so it must say so. A
+ silent drop here is indistinguishable at the client from the browser never sending
+ the request, which is the difference between a server bug and a client one. */
const onUpgrade = (req: IncomingMessage, socket: Duplex, head: Buffer): void => {
if (collaborationHost.handleUpgrade(req, socket, head)) return;
if (tripsForwardedHeaderTripwire(req, ingressPolicy)) {
@@ -114,6 +117,16 @@ export function mountMcpAndApi(opts: MountMcpAndApiOptions): MountMcpAndApiHandl
'[remote] refused proxied WS upgrade; consent with OK_ALLOW_EXTERNAL=1 + OK_EXTERNAL_URL (or server.allowExternal + server.externalUrl in config)',
);
warnForwardedHeaderRefusalOnce(log, 'ws-upgrade');
+ } else {
+ log.warn(
+ {
+ url: req.url,
+ host: req.headers.host ?? 'none',
+ origin: req.headers.origin ?? 'none',
+ protocol: req.headers['sec-websocket-protocol'] ?? 'none',
+ },
+ `[ws] upgrade dropped: no handler claimed ${req.url ?? '/'}. The collaboration host takes /collab* only; anything else reaching this listener is destroyed here`,
+ );
}
socket.destroy();
};
diff --git a/packages/server/src/md-manager.ts b/packages/server/src/md-manager.ts
index cccf29101..601940c5b 100644
--- a/packages/server/src/md-manager.ts
+++ b/packages/server/src/md-manager.ts
@@ -1,9 +1,6 @@
import { MarkdownManager, sharedExtensions } from '@inkeep/open-knowledge-core';
-import { getSchema } from '@tiptap/core';
export const mdManager = new MarkdownManager({
extensions: sharedExtensions,
deriveStructuralFreshness: true,
});
-
-export const schema = getSchema(sharedExtensions);
diff --git a/packages/server/src/mermaid-persistence.test.ts b/packages/server/src/mermaid-persistence.test.ts
index 3b360b04c..9b1390978 100644
--- a/packages/server/src/mermaid-persistence.test.ts
+++ b/packages/server/src/mermaid-persistence.test.ts
@@ -95,7 +95,6 @@ describe('loadMermaidDoc', () => {
loadMermaidDoc(doc, DOC, ctx);
expect(doc.getText('source').toString()).toBe(SRC);
expect(typeof doc.getMap('lifecycle').get(LINEAGE_EPOCH_KEY)).toBe('string');
- expect(doc.getXmlFragment('default').length).toBe(0);
});
test('lazy: a missing file seeds nothing (admitting a doc never creates disk)', () => {
diff --git a/packages/server/src/metrics.test.ts b/packages/server/src/metrics.test.ts
index dd6e05db0..773cae945 100644
--- a/packages/server/src/metrics.test.ts
+++ b/packages/server/src/metrics.test.ts
@@ -11,8 +11,6 @@ import {
incrementConflict,
incrementMapDrivenSpliceApplied,
incrementMapDrivenSpliceFallback,
- incrementMapDrivenSpliceMemoHit,
- incrementMapDrivenSpliceMemoSkip,
incrementPark,
incrementReconcile,
incrementRescueBuffer,
@@ -124,29 +122,6 @@ describe('reconciliation metrics', () => {
expect(getMetrics().reconcileCount).toBe(2);
});
- test('the map-driven splice memo counters accumulate and snapshot independently', () => {
- resetMetrics();
- incrementMapDrivenSpliceMemoHit();
- incrementMapDrivenSpliceMemoSkip('narrowed');
- incrementMapDrivenSpliceMemoSkip('narrowed');
- incrementMapDrivenSpliceMemoSkip('entry-already-current');
- const first = getMetrics();
- expect(first.mapDrivenSpliceMemoHits).toBe(1);
- expect(first.mapDrivenSpliceMemoSkips).toEqual({ narrowed: 2, 'entry-already-current': 1 });
-
- incrementMapDrivenSpliceMemoHit();
- incrementMapDrivenSpliceMemoSkip('position-not-numeric');
- const second = getMetrics();
- expect(second.mapDrivenSpliceMemoHits).toBe(2);
- expect(second.mapDrivenSpliceMemoSkips).toEqual({
- narrowed: 2,
- 'entry-already-current': 1,
- 'position-not-numeric': 1,
- });
- expect(first.mapDrivenSpliceMemoHits).toBe(1);
- expect(first.mapDrivenSpliceMemoSkips).toEqual({ narrowed: 2, 'entry-already-current': 1 });
- });
-
test('resetMetrics clears all counters', () => {
incrementReconcile();
incrementConflict();
diff --git a/packages/server/src/metrics.ts b/packages/server/src/metrics.ts
index 246464c5e..a1d57c1d4 100644
--- a/packages/server/src/metrics.ts
+++ b/packages/server/src/metrics.ts
@@ -6,7 +6,7 @@ export type MapDrivenSpliceFallbackReason =
| 'parse-error'
| 'missing-position';
-export type MapDrivenSpliceMemoSkipReason =
+type MapDrivenSpliceMemoSkipReason =
| 'narrowed'
| 'empty-children'
| 'entry-already-current'
@@ -318,19 +318,10 @@ export function incrementPersistenceDiskWrite(): void {
counters.persistenceDiskWrites++;
}
-export function incrementServerObserverError(direction: 'a' | 'b'): void {
- if (direction === 'a') counters.serverObserverErrorsA++;
- else counters.serverObserverErrorsB++;
-}
-
export function incrementBridgeMergeContentLoss(): void {
counters.bridgeMergeContentLoss++;
}
-export function incrementBridgeMergeContentGrowth(): void {
- counters.bridgeMergeContentGrowth++;
-}
-
export function incrementAgentWriteCalls(): void {
counters.agentWriteCalls++;
}
@@ -351,26 +342,6 @@ export function incrementBridgeMergeCheckpointCreated(): void {
counters.bridgeMergeCheckpointCreated++;
}
-export function incrementProducerGuardCheckpointCreated(): void {
- counters.producerGuardCheckpointCreated++;
-}
-
-export function incrementProducerGuardFires(): void {
- counters.producerGuardFires++;
-}
-
-export function incrementProducerGuardFiresSuppressed(): void {
- counters.producerGuardFiresSuppressed++;
-}
-
-export function incrementBridgeInvariantViolations(): void {
- counters.bridgeInvariantViolations++;
-}
-
-export function incrementBridgeInvariantViolationsSuppressed(): void {
- counters.bridgeInvariantViolationsSuppressed++;
-}
-
export function incrementPersistenceSkipNonQuiescent(): void {
counters.persistenceSkipNonQuiescent++;
}
@@ -395,19 +366,6 @@ export function incrementAgentPatchFindMismatches(): void {
counters.agentPatchFindMismatches++;
}
-export function incrementBridgeToleranceApplied(toleranceClass: BridgeToleranceSignal): void {
- counters.bridgeToleranceApplied[toleranceClass] =
- (counters.bridgeToleranceApplied[toleranceClass] ?? 0) + 1;
-}
-
-export function incrementObserverAPathBFires(): void {
- counters.observerAPathBFires++;
-}
-
-export function incrementObserverAPathBFiresSuppressed(): void {
- counters.observerAPathBFiresSuppressed++;
-}
-
export function incrementMapDrivenSpliceApplied(): void {
counters.mapDrivenSpliceApplied++;
}
@@ -416,54 +374,6 @@ export function incrementMapDrivenSpliceFallback(reason: MapDrivenSpliceFallback
counters.mapDrivenSpliceFallback[reason] = (counters.mapDrivenSpliceFallback[reason] ?? 0) + 1;
}
-export function incrementMapDrivenSpliceMemoHit(): void {
- counters.mapDrivenSpliceMemoHits++;
-}
-
-export function incrementMapDrivenSpliceMemoSkip(reason: MapDrivenSpliceMemoSkipReason): void {
- counters.mapDrivenSpliceMemoSkips[reason] = (counters.mapDrivenSpliceMemoSkips[reason] ?? 0) + 1;
-}
-
-export function incrementObserverAResidualMergeRuns(): void {
- counters.observerAResidualMergeRuns++;
-}
-
-export function incrementObserverADuplicationRederives(): void {
- counters.observerADuplicationRederives++;
-}
-
-export function incrementObserverADuplicationCheckpointCreated(): void {
- counters.observerADuplicationCheckpointCreated++;
-}
-
-export function incrementObserverAApplyLoss(): void {
- counters.observerAApplyLoss++;
-}
-
-export function incrementObserverAApplyLossCheckpointCreated(): void {
- counters.observerAApplyLossCheckpointCreated++;
-}
-
-export function incrementDeriveTimingDeferForceResolved(): void {
- counters.deriveTimingDeferForceResolved++;
-}
-
-export function incrementPersistenceDeferHold(): void {
- counters.persistenceDeferHold++;
-}
-
-export function incrementPersistenceReconcileLoss(): void {
- counters.persistenceReconcileLoss++;
-}
-
-export function incrementPersistenceReconcileLossCheckpointCreated(): void {
- counters.persistenceReconcileLossCheckpointCreated++;
-}
-
-export function incrementPersistenceReconcileLossDeduped(): void {
- counters.persistenceReconcileLossDeduped++;
-}
-
export function incrementPersistenceDuplicationReset(): void {
counters.persistenceDuplicationReset++;
}
@@ -512,22 +422,6 @@ export function incrementManagedArtifactReconcileDeduped(): void {
counters.managedArtifactReconcileDeduped++;
}
-export function incrementReDeriveBackstopTripped(): void {
- counters.reDeriveBackstopTripped++;
-}
-
-export function incrementBridgeSplitBrainRederives(): void {
- counters.bridgeSplitBrainRederives++;
-}
-
-export function incrementBridgeSplitBrainRederivesSuppressed(): void {
- counters.bridgeSplitBrainRederivesSuppressed++;
-}
-
-export function incrementPersistenceReconciliationFailures(): void {
- counters.persistenceReconciliationFailures++;
-}
-
export function incrementExternalChangeHandlerErrors(): void {
counters.externalChangeHandlerErrors++;
}
@@ -560,10 +454,6 @@ export function incrementPersistenceStalenessForceStoreTimeouts(): void {
counters.persistenceStalenessForceStoreTimeouts++;
}
-export function incrementPersistenceSanityCheckSerializeFailures(): void {
- counters.persistenceSanityCheckSerializeFailures++;
-}
-
export function incrementDeferredStoreFailures(): void {
counters.deferredStoreFailures++;
}
diff --git a/packages/server/src/observer-a-verbatim-fallback-respell.test.ts b/packages/server/src/observer-a-verbatim-fallback-respell.test.ts
deleted file mode 100644
index e44afad35..000000000
--- a/packages/server/src/observer-a-verbatim-fallback-respell.test.ts
+++ /dev/null
@@ -1,112 +0,0 @@
-/**
- * The assertion is on the settled `Y.Text` because that is the authoritative source persisted to
- * disk and converged to every peer (precedent #38).
- */
-
-import { sharedExtensions } from '@inkeep/open-knowledge-core';
-import { getSchema, type JSONContent } from '@tiptap/core';
-import { updateYFragment } from '@tiptap/y-tiptap';
-import { afterEach, beforeEach, expect, test, vi } from 'vitest';
-import { type BridgeRaceRig, createBridgeRaceRig } from './bridge-race-rig.test-helper.ts';
-import { mdManager } from './md-manager.ts';
-
-const schema = getSchema(sharedExtensions);
-
-const STEPS = [
- '',
- '',
- '',
- '',
- 'Content one.',
- '',
- '',
- '',
- '',
- '',
- 'Content two.',
- '',
- '',
- '',
- '',
- '',
-].join('\n');
-
-const INDENTED_STEP = /\n[ \t]+<\/?Step\b/;
-
-const CLIENT_ORIGIN = 'observer-a-verbatim-fallback-respell/client';
-
-let rig: BridgeRaceRig;
-
-beforeEach(() => {
- vi.stubEnv('NODE_ENV', 'production');
- vi.useFakeTimers({ toFake: ['Date'] });
- rig = createBridgeRaceRig({ docName: 'verbatim-fallback-respell' });
-});
-
-afterEach(() => {
- rig?.cleanup();
- vi.useRealTimers();
- vi.unstubAllEnvs();
-});
-
-function fragmentWithDegradedStep(index: number, raw: string): JSONContent {
- const json = mdManager.parse(STEPS) as JSONContent;
- const container = json.content?.[0];
- const children = container?.content;
- if (!container || !children) throw new Error('fixture parse did not yield a JSX container');
- children[index] = {
- type: 'rawMdxFallback',
- attrs: { reason: 'Unregistered component: Step', originalSpan: null },
- content: [{ type: 'text', text: raw }],
- };
- container.attrs = { ...container.attrs, sourceDirty: true };
- return json;
-}
-
-function degradeWhileTyping(json: JSONContent, typeAfter: string, char: string): void {
- const at = rig.ytext.toString().indexOf(typeAfter) + typeAfter.length;
- rig.stimulus('degrade-while-typing', () => {
- rig.doc.transact(() => {
- updateYFragment(rig.doc, rig.xmlFragment, schema.nodeFromJSON(json), {
- mapping: new Map(),
- isOMark: new Map(),
- });
- rig.ytext.insert(at, char);
- }, CLIENT_ORIGIN);
- });
-}
-
-test('a degraded nested block does not re-indent the authored source in Y.Text', () => {
- rig.seedSource(STEPS);
- expect(rig.ytext.toString()).toBe(STEPS);
-
- degradeWhileTyping(
- fragmentWithDegradedStep(1, '\n\nContent two.\n\n'),
- 'Content one.',
- 'Z',
- );
- rig.settle(3);
-
- const settled = rig.ytext.toString();
- expect(settled).not.toMatch(INDENTED_STEP);
- expect(settled).toContain('\n\nContent two.\n\n');
- expect(settled).toContain('Content one.Z');
- expect((settled.match(//g) ?? []).length).toBe(2);
- expect((settled.match(/<\/Step>/g) ?? []).length).toBe(2);
-});
-
-test('the authored bytes survive a degraded FIRST nested block', () => {
- rig.seedSource(STEPS);
-
- degradeWhileTyping(
- fragmentWithDegradedStep(0, '\n\nContent one.\n\n'),
- 'Content two.',
- 'Z',
- );
- rig.settle(3);
-
- const settled = rig.ytext.toString();
- expect(settled).not.toMatch(INDENTED_STEP);
- expect(settled).toContain('Content one.');
- expect(settled).toContain('Content two.Z');
-});
diff --git a/packages/server/src/observer-bridge-spans.test.ts b/packages/server/src/observer-bridge-spans.test.ts
deleted file mode 100644
index 9dca6a1ee..000000000
--- a/packages/server/src/observer-bridge-spans.test.ts
+++ /dev/null
@@ -1,127 +0,0 @@
-import { context, metrics, trace } from '@opentelemetry/api';
-import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks';
-import {
- BasicTracerProvider,
- InMemorySpanExporter,
- type ReadableSpan,
- SimpleSpanProcessor,
-} from '@opentelemetry/sdk-trace-base';
-import { afterEach, beforeEach, describe, expect, it } from 'vitest';
-import * as Y from 'yjs';
-import { composeAndWriteRawBody } from './bridge-intake';
-import { mdManager, schema } from './md-manager';
-import { setupServerObservers } from './server-observers';
-
-let exporter: InMemorySpanExporter;
-let provider: BasicTracerProvider;
-
-function setupExporter(): void {
- exporter = new InMemorySpanExporter();
- provider = new BasicTracerProvider({
- spanProcessors: [new SimpleSpanProcessor(exporter)],
- });
- trace.setGlobalTracerProvider(provider);
- context.setGlobalContextManager(new AsyncLocalStorageContextManager().enable());
-}
-
-async function teardownExporter(): Promise {
- await provider.shutdown();
- trace.disable();
- metrics.disable();
- context.disable();
-}
-
-function spansByName(name: string): ReadableSpan[] {
- return exporter.getFinishedSpans().filter((s) => s.name === name);
-}
-
-const ALLOWED_BRIDGE_ATTRIBUTE_KEYS = new Set([
- 'surface',
- 'body.bytes',
- 'doc.name',
- 'observer.a.path',
- 'observer.dispatch',
- 'merge.bytes_changed',
-]);
-
-beforeEach(() => {
- setupExporter();
-});
-
-afterEach(async () => {
- await teardownExporter();
-});
-
-describe('FR6 / AC10 — server bridge spans', () => {
- it('emits bridge.composeAndWriteRawBody with surface, body.bytes, doc.name on agent write', () => {
- const doc = new Y.Doc();
- doc.gc = false;
- composeAndWriteRawBody(doc, '# Hello\n\nworld\n', 'agent');
- const span = spansByName('bridge.composeAndWriteRawBody')[0];
- expect(span).toBeDefined();
- expect(span?.attributes.surface).toBe('agent');
- expect(span?.attributes['body.bytes']).toBe(15);
- expect(typeof span?.attributes['doc.name']).toBe('string');
- });
-
- it('emits bridge.composeAndWriteRawBody with surface=file-watcher when called from disk path', () => {
- const doc = new Y.Doc();
- doc.gc = false;
- composeAndWriteRawBody(doc, 'body\n', 'file-watcher');
- const span = spansByName('bridge.composeAndWriteRawBody')[0];
- expect(span?.attributes.surface).toBe('file-watcher');
- });
-
- it('emits md.parseWithFallback with body.bytes + doc.name as a child of compose', () => {
- const doc = new Y.Doc();
- doc.gc = false;
- composeAndWriteRawBody(doc, '---\nfoo: bar\n---\nbody\n', 'agent');
- const parse = spansByName('md.parseWithFallback')[0];
- expect(parse).toBeDefined();
- expect(parse?.attributes['body.bytes']).toBe(5);
- });
-
- it('observer.runASync / runBSync / dispatch fire with bounded-cardinality attrs', () => {
- const doc = new Y.Doc();
- doc.gc = false;
- const xmlFragment = doc.getXmlFragment('default');
- const ytext = doc.getText('source');
- const cleanup = setupServerObservers({
- doc,
- xmlFragment,
- ytext,
- mdManager,
- schema,
- docName: 'README',
- });
- try {
- doc.transact(() => {
- ytext.insert(0, '# Hi\n');
- });
- const dispatch = spansByName('observer.dispatch')[0];
- expect(dispatch).toBeDefined();
- const obDispatch = dispatch?.attributes['observer.dispatch'];
- expect(['a', 'b', 'a-then-b', 'none']).toContain(obDispatch as string);
- const runB = spansByName('observer.runBSync')[0];
- expect(runB).toBeDefined();
- expect(runB?.attributes['doc.name']).toBe('README');
- } finally {
- cleanup();
- }
- });
-
- it('no unbounded-cardinality attribute names appear on bridge spans', () => {
- const doc = new Y.Doc();
- doc.gc = false;
- composeAndWriteRawBody(doc, '# T\n', 'agent');
- const compose = spansByName('bridge.composeAndWriteRawBody')[0];
- const parse = spansByName('md.parseWithFallback')[0];
- for (const span of [compose, parse]) {
- if (!span) continue;
- for (const key of Object.keys(span.attributes)) {
- if (key.startsWith('otel.') || key.startsWith('sdk.')) continue;
- expect(ALLOWED_BRIDGE_ATTRIBUTE_KEYS.has(key)).toBe(true);
- }
- }
- });
-});
diff --git a/packages/server/src/paired-intake-detection-wiring.test.ts b/packages/server/src/paired-intake-detection-wiring.test.ts
deleted file mode 100644
index c947fd2b7..000000000
--- a/packages/server/src/paired-intake-detection-wiring.test.ts
+++ /dev/null
@@ -1,208 +0,0 @@
-import { mkdtempSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
-import { tmpdir } from 'node:os';
-import { join } from 'node:path';
-import { Hocuspocus } from '@hocuspocus/server';
-import { sharedExtensions } from '@inkeep/open-knowledge-core';
-import { getSchema } from '@tiptap/core';
-import { updateYFragment } from '@tiptap/y-tiptap';
-import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
-import type * as Y from 'yjs';
-import {
- type BridgeDeriveLossReporter,
- type DeriveLossObservation,
- detectPairedIntakeLoss,
-} from './bridge-loss-detector.ts';
-import {
- PAIRED_INTAKE_DETECTION,
- type PairedIntakeDetectionMode,
-} from './bridge-loss-suppression.ts';
-import { RECONCILE_TEST_CONFLICTS } from './conflict-authority.test-helper.ts';
-import { DocumentDurabilityState } from './document-durability-state.ts';
-import { reconcileDiskBeforeAgentWrite } from './external-change.ts';
-import { mdManager } from './md-manager.ts';
-import { createWiredPreDrainRig, WIRED_PENDING_LINE } from './pre-drain-wired.test-helper.ts';
-
-const schema = getSchema(sharedExtensions);
-
-function withMode(origin: string, mode: PairedIntakeDetectionMode, fn: () => void): void;
-function withMode(
- origin: string,
- mode: PairedIntakeDetectionMode,
- fn: () => Promise,
-): Promise;
-function withMode(
- origin: string,
- mode: PairedIntakeDetectionMode,
- fn: () => void | Promise,
-): void | Promise {
- const entry = PAIRED_INTAKE_DETECTION[origin];
- if (!entry) throw new Error(`unclassified paired origin: ${origin}`);
- const previous = entry.mode;
- entry.mode = mode;
- const restore = () => {
- entry.mode = previous;
- };
- try {
- const out = fn();
- if (out instanceof Promise) return out.finally(restore);
- restore();
- return out;
- } catch (err) {
- restore();
- throw err;
- }
-}
-
-function lossCollector(): { trips: DeriveLossObservation[]; reporter: BridgeDeriveLossReporter } {
- const trips: DeriveLossObservation[] = [];
- return {
- trips,
- reporter: (_docName, obs) => {
- if (detectPairedIntakeLoss(obs).length > 0) trips.push(obs);
- },
- };
-}
-
-describe('paired-intake detection follows the registry at every wired site', () => {
- beforeEach(() => {
- vi.useFakeTimers({ toFake: ['Date'] });
- vi.setSystemTime(1_000_000);
- });
- afterEach(() => {
- vi.useRealTimers();
- });
-
- test('agent-undo: reclassifying to suppress actually stops the undo-derive detection', async () => {
- const on = lossCollector();
- const rigOn = await createWiredPreDrainRig({
- docName: 'undo-detect.md',
- reporter: on.reporter,
- setupOverrides: { preDrainEnabled: false },
- });
- try {
- rigOn.agentWrite('Agent appended line.', 'append');
- rigOn.stageUnpropagatedKeystroke();
- expect(rigOn.serializeFragment()).toContain(WIRED_PENDING_LINE);
- rigOn.agentUndo('last');
- expect(on.trips.length).toBeGreaterThan(0);
- } finally {
- await rigOn.cleanup();
- }
-
- const off = lossCollector();
- const rigOff = await createWiredPreDrainRig({
- docName: 'undo-suppress.md',
- reporter: off.reporter,
- setupOverrides: { preDrainEnabled: false },
- });
- try {
- await withMode('agent-undo', 'suppress', async () => {
- rigOff.agentWrite('Agent appended line.', 'append');
- rigOff.stageUnpropagatedKeystroke();
- expect(rigOff.serializeFragment()).toContain(WIRED_PENDING_LINE);
- rigOff.agentUndo('last');
- });
- expect(off.trips).toEqual([]);
- } finally {
- await rigOff.cleanup();
- }
- });
-
- test('agent-write: reclassifying to suppress actually stops the write-intake detection', async () => {
- const on = lossCollector();
- const rigOn = await createWiredPreDrainRig({
- docName: 'write-detect.md',
- reporter: on.reporter,
- setupOverrides: { preDrainEnabled: false },
- });
- try {
- rigOn.stageUnpropagatedKeystroke();
- rigOn.agentWrite('## Replaced\n\nBrand new body.\n', 'replace');
- expect(on.trips.length).toBeGreaterThan(0);
- } finally {
- await rigOn.cleanup();
- }
-
- const off = lossCollector();
- const rigOff = await createWiredPreDrainRig({
- docName: 'write-suppress.md',
- reporter: off.reporter,
- setupOverrides: { preDrainEnabled: false },
- });
- try {
- await withMode('agent-write', 'suppress', async () => {
- rigOff.stageUnpropagatedKeystroke();
- rigOff.agentWrite('## Replaced\n\nBrand new body.\n', 'replace');
- });
- expect(off.trips).toEqual([]);
- } finally {
- await rigOff.cleanup();
- }
- });
-
- test('file-watcher: reclassifying to suppress actually stops the reconcile-intake detection', async () => {
- const contentDir = realpathSync(mkdtempSync(join(tmpdir(), 'ok-registry-wiring-')));
- const hp = new Hocuspocus({ quiet: true });
- const durabilityState = new DocumentDurabilityState();
- const base = '# Notes\n\nFirst paragraph.\n';
- const pending = 'A keystroke that never reached Y.Text.';
-
- const seed = async (docName: string): Promise => {
- const conn = await hp.openDirectConnection(docName);
- const doc = (conn as unknown as { document: Y.Doc }).document;
- writeFileSync(join(contentDir, `${docName}.md`), base);
- durabilityState.setReconciledBase(docName, base);
- doc.transact(() => {
- doc.getText('source').insert(0, base);
- updateYFragment(
- doc,
- doc.getXmlFragment('default'),
- schema.nodeFromJSON(mdManager.parse(base)),
- { mapping: new Map(), isOMark: new Map() },
- );
- }, 'seed');
- doc.transact(() => {
- updateYFragment(
- doc,
- doc.getXmlFragment('default'),
- schema.nodeFromJSON(mdManager.parse(`${base}\n${pending}\n`)),
- { mapping: new Map(), isOMark: new Map() },
- );
- }, 'wysiwyg');
- writeFileSync(join(contentDir, `${docName}.md`), '# Notes\n\nEdited on disk.\n');
- return doc;
- };
-
- try {
- const on = lossCollector();
- await seed('watcher-detect');
- reconcileDiskBeforeAgentWrite(
- durabilityState,
- hp,
- 'watcher-detect',
- contentDir,
- undefined,
- on.reporter,
- RECONCILE_TEST_CONFLICTS,
- );
- expect(on.trips.length).toBeGreaterThan(0);
-
- const off = lossCollector();
- await seed('watcher-suppress');
- withMode('file-watcher', 'suppress', () => {
- reconcileDiskBeforeAgentWrite(
- durabilityState,
- hp,
- 'watcher-suppress',
- contentDir,
- undefined,
- off.reporter,
- RECONCILE_TEST_CONFLICTS,
- );
- });
- expect(off.trips).toEqual([]);
- } finally {
- rmSync(contentDir, { recursive: true, force: true });
- }
- });
-});
diff --git a/packages/server/src/paired-write-enforcement.test.ts b/packages/server/src/paired-write-enforcement.test.ts
index 7d1ff7050..5b7d678b9 100644
--- a/packages/server/src/paired-write-enforcement.test.ts
+++ b/packages/server/src/paired-write-enforcement.test.ts
@@ -11,11 +11,14 @@ import {
} from 'ts-morph';
import { beforeAll, describe, expect, test } from 'vitest';
-const SANCTIONED_PRIMITIVES = new Set([
- 'composeAndWriteRawBody',
- 'replaceRawBody',
- 'deriveFragmentFromYtext',
-]);
+const SANCTIONED_PRIMITIVES = new Set(['composeAndWriteRawBody', 'replaceRawBody']);
+
+/*
+ * WARN: `undo` is sanctioned ONLY because `Y.UndoManager.undo()` writes
+ * `Y.Text` itself — it is the write, not a bypass of one. Do not widen this
+ * set to admit any other bare method name.
+ */
+const SANCTIONED_WRITER_METHODS = new Set(['undo']);
const TRANSITIVE_PRIMITIVE_CALLERS = new Set([
'applyDiskContentToDoc',
@@ -138,7 +141,11 @@ function bodyCallsSanctionedPrimitive(body: Node | undefined): {
? callee.getName()
: null;
if (calleeName === null) return;
- if (SANCTIONED_PRIMITIVES.has(calleeName) || TRANSITIVE_PRIMITIVE_CALLERS.has(calleeName)) {
+ if (
+ SANCTIONED_PRIMITIVES.has(calleeName) ||
+ TRANSITIVE_PRIMITIVE_CALLERS.has(calleeName) ||
+ SANCTIONED_WRITER_METHODS.has(calleeName)
+ ) {
matched = true;
matchedName = calleeName;
traversal.stop();
@@ -202,12 +209,13 @@ describe('paired-write enforcement', () => {
`${relative(SERVER_SRC_DIR, file)}:${call.line} — paired-write origin "${call.originExpr}" ` +
`does not route through any sanctioned primitive ` +
`(${[...SANCTIONED_PRIMITIVES, ...TRANSITIVE_PRIMITIVE_CALLERS].join(', ')}). ` +
- `Refactor to call composeAndWriteRawBody / replaceRawBody / deriveFragmentFromYtext.`,
+ `Refactor to call composeAndWriteRawBody / replaceRawBody.`,
);
} else {
const known =
SANCTIONED_PRIMITIVES.has(matchedName ?? '') ||
- TRANSITIVE_PRIMITIVE_CALLERS.has(matchedName ?? '');
+ TRANSITIVE_PRIMITIVE_CALLERS.has(matchedName ?? '') ||
+ SANCTIONED_WRITER_METHODS.has(matchedName ?? '');
if (!known) {
failures.push(
`${relative(SERVER_SRC_DIR, file)}:${call.line} — internal classifier bug: ` +
@@ -225,7 +233,7 @@ describe('paired-write enforcement', () => {
}
});
- test('all three sanctioned primitives are exported from bridge-intake.ts', () => {
+ test('both sanctioned primitives are exported from bridge-intake.ts', () => {
const project = new Project({
skipFileDependencyResolution: true,
skipLoadingLibFiles: true,
diff --git a/packages/server/src/paired-write-origin.test.ts b/packages/server/src/paired-write-origin.test.ts
index e2c9d189b..e76a69beb 100644
--- a/packages/server/src/paired-write-origin.test.ts
+++ b/packages/server/src/paired-write-origin.test.ts
@@ -6,7 +6,7 @@ import { describe, test } from 'vitest';
import type { AGENT_WRITE_ORIGIN } from './agent-sessions.ts';
import type { MANAGED_RENAME_ORIGIN, ROLLBACK_ORIGIN } from './api-extension.ts';
import type { FILE_WATCHER_ORIGIN } from './external-change.ts';
-import type { PairedWriteOrigin } from './server-observers.ts';
+import type { PairedWriteOrigin } from './write-origins.ts';
type Assignable = X extends Y ? true : never;
diff --git a/packages/server/src/parse-counting.test-helper.ts b/packages/server/src/parse-counting.test-helper.ts
deleted file mode 100644
index cad9d3219..000000000
--- a/packages/server/src/parse-counting.test-helper.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { MarkdownManager, sharedExtensions } from '@inkeep/open-knowledge-core';
-
-export interface CountingManager {
- readonly manager: MarkdownManager;
- readonly parses: () => number;
- readonly serializes: () => number;
-}
-
-export function createCountingManager(): CountingManager {
- const manager = new MarkdownManager({ extensions: sharedExtensions });
- let calls = 0;
- let serializeCalls = 0;
- const original = manager.parseToEditorMdast.bind(manager);
- manager.parseToEditorMdast = (markdown: string) => {
- calls += 1;
- return original(markdown);
- };
- const originalSerialize = manager.serialize.bind(manager);
- manager.serialize = (json, opts) => {
- serializeCalls += 1;
- return originalSerialize(json, opts);
- };
- return { manager, parses: () => calls, serializes: () => serializeCalls };
-}
diff --git a/packages/server/src/parse-pool.test.ts b/packages/server/src/parse-pool.test.ts
deleted file mode 100644
index 3140f37eb..000000000
--- a/packages/server/src/parse-pool.test.ts
+++ /dev/null
@@ -1,342 +0,0 @@
-import { readFileSync } from 'node:fs';
-import { dirname, resolve } from 'node:path';
-import { fileURLToPath, pathToFileURL } from 'node:url';
-import type { Document } from '@hocuspocus/server';
-import { stripFrontmatter } from '@inkeep/open-knowledge-core';
-import { yXmlFragmentToProseMirrorRootNode } from '@tiptap/y-tiptap';
-import { afterEach, describe, expect, test } from 'vitest';
-import * as Y from 'yjs';
-import { applyAgentMarkdownWrite, prepareAgentMarkdownParse } from './agent-sessions.ts';
-import { composeAndWriteRawBody, replaceRawBody } from './bridge-intake.ts';
-import { mdManager, schema } from './md-manager.ts';
-import {
- _overrideParseTaskTimeoutForTests,
- _overrideParseWorkerUrlForTests,
- destroyParsePool,
- offloadParse,
- PARSE_OFFLOAD_MIN_BYTES,
- precomputeParse,
-} from './parse-pool.ts';
-
-const __dirname = dirname(fileURLToPath(import.meta.url));
-
-const TEST_ORIGIN = {
- source: 'local',
- context: { origin: 'agent', paired: true },
-} as const;
-
-function asDocument(ydoc: Y.Doc, name = 'doc.md'): Document {
- return {
- name,
- awareness: undefined,
- getText: (n: string) => ydoc.getText(n),
- getMap: (n: string) => ydoc.getMap(n),
- getXmlFragment: (n: string) => ydoc.getXmlFragment(n),
- transact: (fn: () => void, origin?: unknown) => ydoc.transact(fn, origin),
- on: ydoc.on.bind(ydoc),
- off: ydoc.off.bind(ydoc),
- } as unknown as Document;
-}
-
-function fragmentJson(ydoc: Y.Doc): string {
- return JSON.stringify(
- yXmlFragmentToProseMirrorRootNode(ydoc.getXmlFragment('default'), schema).toJSON(),
- );
-}
-
-function largeMarkdown(): string {
- const section = [
- '## Heading with **strong** and _emphasis_ and `code`',
- '',
- 'A paragraph with a [link](https://example.com/a) and #tag and [[Wiki Page]].',
- '',
- '- item one',
- '- item two',
- ' - nested',
- '',
- '| A | B |',
- '| --- | --- |',
- '| 1 | 2 |',
- '',
- '```ts',
- 'const x: number = 1;',
- '```',
- '',
- '> quoted text',
- '',
- ].join('\n');
- return section.repeat(Math.ceil((PARSE_OFFLOAD_MIN_BYTES * 4) / section.length));
-}
-
-const EQUIVALENCE_FIXTURES: ReadonlyArray<[string, string]> = [
- ['plain paragraph', 'Just a paragraph.\n'],
- ['heading and setext', '# H1\n\nSetext\n===\n\nBody.\n'],
- ['emphasis nesting', '**bold _nested_ and `code`** tail\n'],
- ['escapes survive', 'not \\*emphasis\\* and a literal \\_underscore\\_\n'],
- ['hard break then spaced text (patched dependency)', 'foo\\\n *bar*\n'],
- ['hard break then whitespace-only text (patched dependency)', 'a\\\n \nb\n'],
- ['task list', '- [ ] open\n- [x] done\n'],
- ['ordered list renumber source', '3. three\n4. four\n'],
- ['table alignment', '| a | b |\n|:--|--:|\n| 1 | 2 |\n'],
- ['fenced code with lang', '```python\nprint("hi")\n```\n'],
- ['math inline and block', 'Euler: $e^{i\\pi}+1=0$\n\n$$\nx^2\n$$\n'],
- ['wikilink and tag', 'See [[Other Page|alias]] and #topic\n'],
- ['raw inline html', 'line
break and span\n'],
- ['jsx component', 'Body text\n'],
- ['broken mdx falls back', 'before\n\n\ntext\n\n'],
- ['reference link', 'See [ref one][r1].\n\n[r1]: https://example.com/r1\n'],
- ['thematic break and blockquote', '---\n\n> quote\n\n---\n'],
- ['crlf-free multi-blank', 'a\n\n\n\nb\n'],
-];
-
-afterEach(async () => {
- _overrideParseWorkerUrlForTests(undefined);
- _overrideParseTaskTimeoutForTests(undefined);
- await destroyParsePool();
-});
-
-describe('worker parse equivalence', () => {
- test('fixture corpus: worker output is byte-identical to inline parse', async () => {
- for (const [label, fixture] of EQUIVALENCE_FIXTURES) {
- const inline = mdManager.parseWithFallback(fixture);
- const offloaded = await offloadParse(fixture);
- expect(JSON.stringify(offloaded), label).toBe(JSON.stringify(inline));
- }
- }, 60_000);
-
- test('large generated doc: worker output is byte-identical to inline parse', async () => {
- const md = largeMarkdown();
- const inline = mdManager.parseWithFallback(md);
- const offloaded = await offloadParse(md);
- expect(JSON.stringify(offloaded)).toBe(JSON.stringify(inline));
- }, 60_000);
-
- test('this file round-trips identically (real-world prose corpus)', async () => {
- const source = readFileSync(resolve(__dirname, 'parse-pool.test.ts'), 'utf8');
- const asMarkdown = `# Source dump\n\n\`\`\`ts\n${source}\n\`\`\`\n`;
- const inline = mdManager.parseWithFallback(asMarkdown);
- const offloaded = await offloadParse(asMarkdown);
- expect(JSON.stringify(offloaded)).toBe(JSON.stringify(inline));
- }, 60_000);
-
- test('patched dependency behavior holds inside the worker', async () => {
- const offloaded = await offloadParse('foo\\\n *bar*');
- const paragraph = offloaded.content?.[0];
- expect(paragraph?.type).toBe('paragraph');
- const types = (paragraph?.content ?? []).map((n) => n.type);
- expect(types).toContain('hardBreak');
- const barNode = (paragraph?.content ?? []).find((n) => n.type === 'text' && n.text === 'bar');
- expect(barNode?.marks?.some((m) => m.type === 'emphasis')).toBe(true);
- }, 60_000);
-
- test('wiki-embed resolution matches inline (two-pass table protocol)', async () => {
- const md = 'Intro paragraph.\n\n![[photo.png]]\n\n![[missing.bin]]\n';
- const resolver = {
- resolveEmbed: (target: string) => (target === 'photo.png' ? 'assets/photo.png' : null),
- resolveSize: (target: string) => (target === 'photo.png' ? 12_345 : null),
- sourcePath: 'docs/page',
- };
- const inline = mdManager.parseWithFallback(md, { ...resolver });
- const offloaded = await offloadParse(md, resolver);
- expect(JSON.stringify(offloaded)).toBe(JSON.stringify(inline));
- }, 60_000);
-
- test('embed-free doc with a resolver present completes in one pass and matches inline', async () => {
- const md = 'No embeds here, just a [link](https://example.com).\n';
- const resolver = {
- resolveEmbed: () => {
- throw new Error('resolver must not be consulted for an embed-free doc');
- },
- sourcePath: 'docs/page',
- };
- const inline = mdManager.parseWithFallback(md, {
- sourcePath: 'docs/page',
- resolveEmbed: () => null,
- });
- const offloaded = await offloadParse(md, resolver);
- expect(JSON.stringify(offloaded)).toBe(JSON.stringify(inline));
- }, 60_000);
-});
-
-describe('precomputeParse threshold and fallback', () => {
- test('small docs stay inline (returns undefined)', async () => {
- const result = await precomputeParse('# tiny\n\nbody\n');
- expect(result).toBeUndefined();
- });
-
- test('large docs offload and carry the exact rawContent', async () => {
- const raw = `---\ntitle: X\n---\n\n${largeMarkdown()}`;
- const result = await precomputeParse(raw);
- expect(result).toBeDefined();
- expect(result?.rawContent).toBe(raw);
- const inline = mdManager.parseWithFallback(stripFrontmatter(raw).body);
- expect(JSON.stringify(result?.parsedJson)).toBe(JSON.stringify(inline));
- }, 60_000);
-
- test('worker file unavailable degrades to undefined (inline fallback)', async () => {
- _overrideParseWorkerUrlForTests(null);
- const result = await precomputeParse(largeMarkdown());
- expect(result).toBeUndefined();
- });
-
- test('worker spawn failure degrades to undefined (inline fallback)', async () => {
- _overrideParseWorkerUrlForTests(pathToFileURL(resolve(__dirname, 'no-such-worker.mjs')));
- const result = await precomputeParse(largeMarkdown());
- expect(result).toBeUndefined();
- }, 60_000);
-
- test('task timeout degrades to undefined, then the pool recovers', async () => {
- _overrideParseTaskTimeoutForTests(1);
- const timedOut = await precomputeParse(largeMarkdown());
- expect(timedOut).toBeUndefined();
- _overrideParseTaskTimeoutForTests(undefined);
- const recovered = await precomputeParse(largeMarkdown());
- expect(recovered).toBeDefined();
- }, 60_000);
-
- test('destroyParsePool terminates workers and the next dispatch respawns', async () => {
- const before = await precomputeParse(largeMarkdown());
- expect(before).toBeDefined();
- await destroyParsePool();
- const after = await precomputeParse(largeMarkdown());
- expect(after).toBeDefined();
- }, 60_000);
-});
-
-describe('bridge-intake byte-identity guard', () => {
- test('a stale precompute is discarded (inline parse applies the real bytes)', () => {
- const raw = '# Real\n\nreal body\n';
- const staleParse = mdManager.parseWithFallback('# Impostor\n\nimpostor body\n');
- const withStale = new Y.Doc();
- withStale.transact(() => {
- composeAndWriteRawBody(withStale, raw, 'agent', undefined, {
- rawContent: '# Impostor\n\nimpostor body\n',
- parsedJson: staleParse,
- });
- }, TEST_ORIGIN);
- const control = new Y.Doc();
- control.transact(() => {
- composeAndWriteRawBody(control, raw, 'agent');
- }, TEST_ORIGIN);
- expect(withStale.getText('source').toString()).toBe(raw);
- expect(fragmentJson(withStale)).toBe(fragmentJson(control));
- });
-
- test('a byte-matching precompute is honored (observable via a divergent parse)', () => {
- const raw = '# Real\n\nreal body\n';
- const divergent = mdManager.parseWithFallback('# Marker heading only\n');
- const doc = new Y.Doc();
- doc.transact(() => {
- replaceRawBody(doc, raw, undefined, { rawContent: raw, parsedJson: divergent });
- }, TEST_ORIGIN);
- expect(doc.getText('source').toString()).toBe(raw);
- expect(fragmentJson(doc)).toContain('Marker heading only');
- });
-});
-
-describe('prepareAgentMarkdownParse end-to-end', () => {
- test('fresh precompute: applied write matches the inline-path control byte-for-byte', async () => {
- const md = largeMarkdown();
- const prepared = new Y.Doc();
- const preparedDoc = asDocument(prepared);
- const precomputed = await prepareAgentMarkdownParse(preparedDoc, md, 'replace');
- expect(precomputed).toBeDefined();
- prepared.transact(() => {
- applyAgentMarkdownWrite(preparedDoc, md, 'replace', undefined, precomputed);
- }, TEST_ORIGIN);
-
- const control = new Y.Doc();
- const controlDoc = asDocument(control);
- control.transact(() => {
- applyAgentMarkdownWrite(controlDoc, md, 'replace');
- }, TEST_ORIGIN);
-
- expect(prepared.getText('source').toString()).toBe(control.getText('source').toString());
- expect(fragmentJson(prepared)).toBe(fragmentJson(control));
- }, 60_000);
-
- test('doc moved during the await: stale precompute discarded, write still correct', async () => {
- const md = largeMarkdown();
- const ydoc = new Y.Doc();
- const doc = asDocument(ydoc);
- const precomputed = await prepareAgentMarkdownParse(doc, md, 'append');
- expect(precomputed).toBeDefined();
- ydoc.transact(() => {
- ydoc.getText('source').insert(0, '# Raced-in heading\n\n');
- }, TEST_ORIGIN);
- ydoc.transact(() => {
- applyAgentMarkdownWrite(doc, md, 'append', undefined, precomputed);
- }, TEST_ORIGIN);
-
- const control = new Y.Doc();
- const controlDoc = asDocument(control);
- control.transact(() => {
- control.getText('source').insert(0, '# Raced-in heading\n\n');
- }, TEST_ORIGIN);
- control.transact(() => {
- applyAgentMarkdownWrite(controlDoc, md, 'append');
- }, TEST_ORIGIN);
-
- expect(ydoc.getText('source').toString()).toBe(control.getText('source').toString());
- expect(fragmentJson(ydoc)).toBe(fragmentJson(control));
- }, 60_000);
-
- test('no-op composition (empty append) returns undefined without dispatching', async () => {
- const ydoc = new Y.Doc();
- const result = await prepareAgentMarkdownParse(asDocument(ydoc), '', 'append');
- expect(result).toBeUndefined();
- });
-});
-
-describe('worker entry ships in every bundle shape', () => {
- test('server tsdown config emits the parse-worker entry', () => {
- const config = readFileSync(resolve(__dirname, '../tsdown.config.ts'), 'utf8');
- expect(config).toMatch(/'parse-worker':\s*'src\/parse-worker\.ts'/);
- });
-
- test('cli tsdown config emits the parse-worker entry next to dist/cli.mjs', () => {
- const config = readFileSync(resolve(__dirname, '../../cli/tsdown.config.ts'), 'utf8');
- expect(config).toMatch(/'parse-worker':\s*'src\/parse-worker\.ts'/);
- });
-
- test('server package.json exports the parse-worker subpath for both conditions', () => {
- const pkg = JSON.parse(readFileSync(resolve(__dirname, '../package.json'), 'utf8')) as {
- exports: Record>;
- };
- expect(pkg.exports['./parse-worker']).toEqual({
- '@inkeep/source': './src/parse-worker.ts',
- development: './src/parse-worker.ts',
- types: './dist/parse-worker.d.mts',
- default: './dist/parse-worker.mjs',
- });
- });
-
- test.each([
- ['@inkeep/open-knowledge-server', '../package.json'],
- ['@inkeep/open-knowledge-core', '../../core/package.json'],
- ])(
- '%s keeps every export subpath on the same four conditions in the same order',
- (_name, rel) => {
- const pkg = JSON.parse(readFileSync(resolve(__dirname, rel), 'utf8')) as {
- exports: Record>;
- };
- const subpaths = Object.entries(pkg.exports);
- expect(subpaths.length).toBeGreaterThan(0);
- for (const [subpath, entry] of subpaths) {
- expect(Object.keys(entry), `${rel} ${subpath} condition order`).toEqual([
- '@inkeep/source',
- 'development',
- 'types',
- 'default',
- ]);
- expect(entry['@inkeep/source'], `${rel} ${subpath} source vs development`).toBe(
- entry.development,
- );
- expect(entry.types, `${rel} ${subpath} types must resolve to a declaration`).toMatch(
- /\.d\.mts$/,
- );
- }
- },
- );
-});
diff --git a/packages/server/src/parse-pool.ts b/packages/server/src/parse-pool.ts
deleted file mode 100644
index 7dc91dcc3..000000000
--- a/packages/server/src/parse-pool.ts
+++ /dev/null
@@ -1,372 +0,0 @@
-import { existsSync } from 'node:fs';
-import { createRequire } from 'node:module';
-import { availableParallelism } from 'node:os';
-import { dirname, join } from 'node:path';
-import { performance } from 'node:perf_hooks';
-import { fileURLToPath, pathToFileURL } from 'node:url';
-import { Worker } from 'node:worker_threads';
-import { stripFrontmatter } from '@inkeep/open-knowledge-core';
-import type { JSONContent } from '@tiptap/core';
-import type { PrecomputedParse } from './bridge-intake.ts';
-import { getLogger } from './logger.ts';
-import type {
- ParseWorkerEmbedResolution,
- ParseWorkerResult,
- ParseWorkerTask,
-} from './parse-worker.ts';
-import { getMeter, onTelemetryShutdown } from './telemetry.ts';
-
-const log = getLogger('parse-pool');
-
-export const PARSE_OFFLOAD_MIN_BYTES = 8 * 1024;
-
-const PARSE_TASK_TIMEOUT_MS = 30_000;
-
-const MAX_PENDING_TASKS = 32;
-
-const WORKER_IDLE_REAP_MS = 30_000;
-
-const POOL_SIZE = Math.max(1, Math.min(4, availableParallelism() - 1));
-
-export interface ParsePoolEmbedResolver {
- resolveEmbed: (basename: string, sourcePath: string) => string | null;
- resolveSize?: (basename: string, sourcePath: string) => number | null;
- sourcePath: string;
-}
-
-type DispatchMode =
- | 'offload'
- | 'inline-small'
- | 'inline-unavailable'
- | 'inline-busy'
- | 'inline-timeout'
- | 'inline-error';
-
-type Meter = ReturnType;
-let dispatchCounter: ReturnType | null = null;
-let taskLatencyHistogram: ReturnType | null = null;
-let gaugesInstalled = false;
-
-onTelemetryShutdown(() => {
- dispatchCounter = null;
- taskLatencyHistogram = null;
- gaugesInstalled = false;
-});
-
-function recordDispatch(mode: DispatchMode): void {
- dispatchCounter ||= getMeter().createCounter('ok.parse_pool.dispatch_total', {
- description:
- 'Bridge-intake parse precompute dispatches by mode: offload (worker parse used) vs the inline-* fallback reasons (small doc, pool unavailable, queue saturated, task timeout, worker error).',
- });
- dispatchCounter.add(1, { mode });
-}
-
-function recordTaskLatency(ms: number): void {
- taskLatencyHistogram ||= getMeter().createHistogram('ok.parse_pool.task_ms', {
- description:
- 'Wall-clock latency of a completed parse-pool offload (both passes for embed-bearing docs), in milliseconds.',
- unit: 'ms',
- });
- taskLatencyHistogram.record(ms);
-}
-
-function installGauges(): void {
- if (gaugesInstalled) return;
- gaugesInstalled = true;
- getMeter()
- .createObservableGauge('ok.parse_pool.queue_depth', {
- description: 'Parse-pool tasks waiting for a free worker.',
- })
- .addCallback((result) => {
- result.observe(queue.length);
- });
- getMeter()
- .createObservableGauge('ok.parse_pool.workers', {
- description: 'Live parse-pool worker threads.',
- })
- .addCallback((result) => {
- result.observe(workers.length);
- });
-}
-
-interface PendingTask {
- task: ParseWorkerTask;
- resolve: (result: ParseWorkerResult) => void;
- reject: (err: Error) => void;
-}
-
-interface PoolWorker {
- worker: Worker;
- current: PendingTask | null;
- timer: NodeJS.Timeout | null;
-}
-
-const workers: PoolWorker[] = [];
-const queue: PendingTask[] = [];
-let nextTaskId = 1;
-let idleReapTimer: NodeJS.Timeout | null = null;
-let workerUrlOverride: URL | null | undefined;
-
-export function _overrideParseWorkerUrlForTests(url: URL | null | undefined): void {
- workerUrlOverride = url;
-}
-
-let taskTimeoutMs = PARSE_TASK_TIMEOUT_MS;
-
-export function _overrideParseTaskTimeoutForTests(ms: number | undefined): void {
- taskTimeoutMs = ms ?? PARSE_TASK_TIMEOUT_MS;
-}
-
-function resolveWorkerUrl(): URL | null {
- if (workerUrlOverride !== undefined) return workerUrlOverride;
- const candidates: URL[] = [
- new URL('./parse-worker.mjs', import.meta.url),
- new URL('./parse-worker.ts', import.meta.url),
- ];
- try {
- const requireFromHere = createRequire(import.meta.url);
- candidates.push(
- pathToFileURL(
- join(
- dirname(requireFromHere.resolve('@inkeep/open-knowledge-server/parse-worker')),
- 'parse-worker.mjs',
- ),
- ),
- );
- } catch {}
- for (const candidate of candidates) {
- try {
- if (existsSync(fileURLToPath(candidate))) return candidate;
- } catch {}
- }
- return null;
-}
-
-function spawnWorker(url: URL): PoolWorker | null {
- try {
- const worker = new Worker(url);
- worker.unref();
- const poolWorker: PoolWorker = { worker, current: null, timer: null };
- worker.on('message', (result: ParseWorkerResult) => {
- completeTask(poolWorker, (pending) => pending.resolve(result));
- });
- worker.on('error', (err: Error) => {
- completeTask(poolWorker, (pending) => pending.reject(err));
- removeWorker(poolWorker);
- });
- worker.on('exit', () => {
- completeTask(poolWorker, (pending) =>
- pending.reject(new Error('parse worker exited mid-task')),
- );
- removeWorker(poolWorker);
- });
- return poolWorker;
- } catch (err) {
- log.warn({ err }, '[parse-pool] failed to spawn parse worker');
- return null;
- }
-}
-
-function completeTask(poolWorker: PoolWorker, settle: (pending: PendingTask) => void): void {
- const pending = poolWorker.current;
- if (pending === null) return;
- poolWorker.current = null;
- if (poolWorker.timer !== null) {
- clearTimeout(poolWorker.timer);
- poolWorker.timer = null;
- }
- settle(pending);
- pumpQueue();
-}
-
-function removeWorker(poolWorker: PoolWorker): void {
- const idx = workers.indexOf(poolWorker);
- if (idx !== -1) workers.splice(idx, 1);
-}
-
-function pumpQueue(): void {
- while (queue.length > 0) {
- let idle = workers.find((w) => w.current === null);
- if (idle === undefined && workers.length < POOL_SIZE) {
- const url = resolveWorkerUrl();
- const spawned = url === null ? null : spawnWorker(url);
- if (spawned !== null) {
- workers.push(spawned);
- idle = spawned;
- }
- }
- if (idle === undefined) break;
- const pending = queue.shift();
- if (pending === undefined) break;
- assignTask(idle, pending);
- }
- scheduleIdleReap();
-}
-
-function assignTask(poolWorker: PoolWorker, pending: PendingTask): void {
- poolWorker.current = pending;
- poolWorker.timer = setTimeout(() => {
- poolWorker.current = null;
- removeWorker(poolWorker);
- void poolWorker.worker.terminate();
- pending.reject(new ParseTaskTimeoutError());
- pumpQueue();
- }, taskTimeoutMs);
- poolWorker.timer.unref();
- poolWorker.worker.postMessage(pending.task);
-}
-
-class ParseTaskTimeoutError extends Error {
- constructor() {
- super(`parse worker task exceeded ${taskTimeoutMs}ms`);
- this.name = 'ParseTaskTimeoutError';
- }
-}
-
-function scheduleIdleReap(): void {
- if (idleReapTimer !== null) return;
- if (workers.length === 0) return;
- idleReapTimer = setTimeout(() => {
- idleReapTimer = null;
- const busy = workers.some((w) => w.current !== null);
- if (busy || queue.length > 0) {
- scheduleIdleReap();
- return;
- }
- for (const poolWorker of workers.splice(0)) {
- void poolWorker.worker.terminate();
- }
- }, WORKER_IDLE_REAP_MS);
- idleReapTimer.unref();
-}
-
-function dispatch(task: Omit): Promise {
- const url = resolveWorkerUrl();
- if (url === null) {
- return Promise.reject(new ParsePoolUnavailableError());
- }
- installGauges();
- const pending: PendingTask = {
- task: { ...task, id: nextTaskId++ },
- resolve: () => {},
- reject: () => {},
- };
- const promise = new Promise