Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/isolate-document-durability-state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@inkeep/open-knowledge': patch
---

Open Knowledge servers now keep document durability coordination inside each server instance. Running multiple servers in one process with overlapping document names no longer lets one content root's reconciled base, persistence batch, in-flight flush, agent-write marker, or store failure/divergence state affect another server. Disk reconciliation, agent writes, persistence retries, and the staleness watchdog all consume the same instance-owned state, reducing the risk of incorrect merge anchors, skipped flushes, or misleading durability telemetry when desktop, tests, or embedded hosts create more than one server.
7 changes: 6 additions & 1 deletion packages/app/tests/integration/cc1-broadcast.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,12 @@ describe('CC1 broadcast — L1 integration', () => {
const beforeTextLen = systemDoc?.getText('source').length ?? 0;

expect(() =>
applyExternalChange(server.instance.hocuspocus, SYSTEM_DOC_NAME, '# should be ignored'),
applyExternalChange(
server.instance.durabilityState,
server.instance.hocuspocus,
SYSTEM_DOC_NAME,
'# should be ignored',
),
).not.toThrow();

const afterXmlLen = systemDoc?.getXmlFragment('default').length ?? 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,12 @@ async function runInterleave(clientId: number): Promise<string> {
// External wholesale replace lands on the server and reaches the live
// client only.
writeFileSync(join(server.contentDir, `${docName}.md`), REPLACED_CONTENT, 'utf-8');
applyExternalChange(server.instance.hocuspocus, docName, REPLACED_CONTENT);
applyExternalChange(
server.instance.durabilityState,
server.instance.hocuspocus,
docName,
REPLACED_CONTENT,
);
await pollUntil(() => live.ytext.toString().includes('M6-'), 5000);

// Concurrent stale-anchored insert: the paused client still sees the
Expand Down
14 changes: 12 additions & 2 deletions packages/app/tests/integration/persistence-fan-out.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,12 @@ describe('persistence L2 fan-out integration (US-014, FR-7)', () => {
});

// Simulate a file-watcher external change — registers file-system contributor
applyExternalChange(server.hocuspocus, 'fs-writer-doc', '# Updated from disk\n');
applyExternalChange(
server.durabilityState,
server.hocuspocus,
'fs-writer-doc',
'# Updated from disk\n',
);

const doc = server.hocuspocus.documents.get('fs-writer-doc');
doc?.removeDirectConnection();
Expand Down Expand Up @@ -195,7 +200,12 @@ describe('persistence L2 fan-out integration (US-014, FR-7)', () => {
recordContributor('concurrent-doc', 'agent-s1', 'Session 1', 'agent-s1');

// File-watcher contributor (simulating an external disk change)
applyExternalChange(server.hocuspocus, 'concurrent-doc', '# Updated concurrently\n');
applyExternalChange(
server.durabilityState,
server.hocuspocus,
'concurrent-doc',
'# Updated concurrently\n',
);

const doc = server.hocuspocus.documents.get('concurrent-doc');
doc?.removeDirectConnection();
Expand Down
7 changes: 6 additions & 1 deletion packages/app/tests/stress/bridge-convergence.fuzz.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,12 @@ async function applyOp(
// doc.
writeFileSync(join(server.contentDir, `${docName}.md`), op.newContent, 'utf-8');
try {
applyExternalChange(server.instance.hocuspocus, docName, op.newContent);
applyExternalChange(
server.instance.durabilityState,
server.instance.hocuspocus,
docName,
op.newContent,
);
} catch {
// Non-fatal — no-op early-returns when the doc is unloaded; the
// bridge-merge transact race re-throw is survivable for the fuzz.
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/api-agent-frontmatter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {
applyAgentMarkdownWrite,
applyAgentUndo,
} from './agent-sessions.ts';
import { createApiExtension } from './api-extension.ts';
import { createApiExtension } from './api-extension.test-helper.ts';

interface CapturedResponse {
status: number;
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/api-agent-patch-ytext-truth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
AgentSessionManager,
applyAgentMarkdownWrite,
} from './agent-sessions.ts';
import { createApiExtension } from './api-extension.ts';
import { createApiExtension } from './api-extension.test-helper.ts';
import { getMetrics, resetMetrics } from './metrics.ts';

interface CapturedResponse {
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/api-agent-patch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
AgentSessionManager,
applyAgentMarkdownWrite,
} from './agent-sessions.ts';
import { createApiExtension } from './api-extension.ts';
import { createApiExtension } from './api-extension.test-helper.ts';

interface CapturedResponse {
status: number;
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/api-agent-write-file-index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
AgentSessionManager,
applyAgentMarkdownWrite,
} from './agent-sessions.ts';
import { createApiExtension } from './api-extension.ts';
import { createApiExtension } from './api-extension.test-helper.ts';
import { createContentFilter } from './content-filter.ts';
import type { DiskEvent, FileIndexEntry } from './file-watcher.ts';

Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/api-agent-write-summary.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Readable } from 'node:stream';
import { Hocuspocus } from '@hocuspocus/server';
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
import { AgentSessionManager, applyAgentMarkdownWrite } from './agent-sessions.ts';
import { createApiExtension } from './api-extension.ts';
import { createApiExtension } from './api-extension.test-helper.ts';
import {
__formatContributorsForTests as formatContributorsForTest,
__resetContributorsForTests as resetContributorsForTest,
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/api-asset.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { createServer, type Server } from 'node:http';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
import { createApiExtension } from './api-extension.ts';
import { createApiExtension } from './api-extension.test-helper.ts';
import type { ContentFilter } from './content-filter.ts';
import { listenOnLoopback } from './loopback-rig-test-helpers.ts';

Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/api-backlinks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { Readable } from 'node:stream';
import { describe, expect, test } from 'vitest';
import { createApiExtension } from './api-extension.ts';
import { createApiExtension } from './api-extension.test-helper.ts';
import { BacklinkIndex } from './backlink-index.ts';
import { type ContentFilter, createContentFilter } from './content-filter.ts';
import type { FileIndexEntry } from './file-watcher.ts';
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/api-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { Readable } from 'node:stream';
import { describe, expect, test } from 'vitest';
import { createApiExtension } from './api-extension.ts';
import { createApiExtension } from './api-extension.test-helper.ts';
import { getLocalDir } from './config/paths.ts';
import { acquireServerLock } from './server-lock.ts';

Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/api-create-page.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { join } from 'node:path';
import { Readable } from 'node:stream';
import type { Principal } from '@inkeep/open-knowledge-core';
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
import { createApiExtension } from './api-extension.ts';
import { createApiExtension } from './api-extension.test-helper.ts';
import { BacklinkIndex } from './backlink-index.ts';
import { contributorCount, hasContributor, swapContributors } from './contributor-tracker.ts';
import type { FileIndexEntry } from './file-watcher.ts';
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/api-docname-validation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { Readable } from 'node:stream';
import { Hocuspocus } from '@hocuspocus/server';
import { describe, expect, test } from 'vitest';
import { AgentSessionManager } from './agent-sessions.ts';
import { createApiExtension } from './api-extension.ts';
import { createApiExtension } from './api-extension.test-helper.ts';

interface CapturedResponse {
status: number;
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/api-document-list-ready-gate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { join } from 'node:path';
import { Readable } from 'node:stream';
import { Hocuspocus } from '@hocuspocus/server';
import { describe, expect, test } from 'vitest';
import { createApiExtension } from './api-extension.ts';
import { createApiExtension } from './api-extension.test-helper.ts';
import type { FileIndexEntry } from './file-watcher.ts';

function makeReq(url: string): IncomingMessage {
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/api-empty-docname.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { Readable } from 'node:stream';
import { Hocuspocus } from '@hocuspocus/server';
import { describe, expect, test } from 'vitest';
import { AgentSessionManager } from './agent-sessions.ts';
import { createApiExtension } from './api-extension.ts';
import { createApiExtension } from './api-extension.test-helper.ts';

interface CapturedResponse {
status: number;
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/api-extension.ok-init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ async function bootRig(): Promise<TestRig> {

const { Hocuspocus } = await import('@hocuspocus/server');
const { AgentSessionManager } = await import('./agent-sessions.ts');
const { createApiExtension } = await import('./api-extension.ts');
const { createApiExtension } = await import('./api-extension.test-helper.ts');

const hocuspocus = new Hocuspocus({ quiet: true });
const sessionManager = new AgentSessionManager(hocuspocus);
Expand Down
14 changes: 14 additions & 0 deletions packages/server/src/api-extension.test-helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { ApiExtensionOptions } from './api-extension.ts';
import { createApiExtension as createApiExtensionBase } from './api-extension.ts';
import { DocumentDurabilityState } from './document-durability-state.ts';

export * from './api-extension.ts';

export function createApiExtension(
options: Omit<ApiExtensionOptions, 'durabilityState'>,
): ReturnType<typeof createApiExtensionBase> {
return createApiExtensionBase({
...options,
durabilityState: new DocumentDurabilityState(),
});
}
10 changes: 7 additions & 3 deletions packages/server/src/api-extension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import { mkdtemp, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join, resolve } from 'node:path';
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
import { resumeSyncOnAuthEvent, safeSubdir, sanitizeFilename } from './api-extension.ts';
import {
resumeSyncOnAuthEvent,
safeSubdir,
sanitizeFilename,
} from './api-extension.test-helper.ts';
import type { AuthEvent } from './local-ops/types.ts';
import { listenOnLoopback } from './loopback-rig-test-helpers.ts';
import type { SyncEngine } from './sync-engine.ts';
Expand Down Expand Up @@ -186,7 +190,7 @@ describe('handleUploadAsset', () => {

const { Hocuspocus } = await import('@hocuspocus/server');
const { AgentSessionManager } = await import('./agent-sessions.ts');
const { createApiExtension } = await import('./api-extension.ts');
const { createApiExtension } = await import('./api-extension.test-helper.ts');

const hocuspocus = new Hocuspocus({ quiet: true });
const sessionManager = new AgentSessionManager(hocuspocus);
Expand Down Expand Up @@ -538,7 +542,7 @@ describe('handleUploadAsset — same-dir sha256 dedup (FR-2)', () => {

const { Hocuspocus } = await import('@hocuspocus/server');
const { AgentSessionManager } = await import('./agent-sessions.ts');
const { createApiExtension } = await import('./api-extension.ts');
const { createApiExtension } = await import('./api-extension.test-helper.ts');

const hocuspocus = new Hocuspocus({ quiet: true });
const sessionManager = new AgentSessionManager(hocuspocus);
Expand Down
Loading