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
7 changes: 5 additions & 2 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

114 changes: 114 additions & 0 deletions client/interface/lync/__tests__/indexedDbDurability.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { describe, expect, it } from "bun:test";
import { IDBFactory } from "fake-indexeddb";
import { createIndexedDbEventStore } from "@deepfates/lync/idb-log";
import type { LyncEventBody } from "@deepfates/lync/events";
import { serializeLyncEvent } from "@deepfates/lync/store";

const at = "2026-09-06T21:31:13.000Z";

function event(
id: string,
kind: string,
parents: string[] = [],
payload: Record<string, unknown> = {},
): LyncEventBody {
return {
v: 1,
id,
kind,
at,
author: { actor: "textile-durability-regression" },
parents,
payload,
};
}

describe("installed Lync IndexedDB durability", () => {
it("reopens every resolved curation mutation while an archive batch is suspended", async () => {
const indexedDB = new IDBFactory();
const dbName = "textile-import-curation-overlap";
const store = createIndexedDbEventStore({ dbName, indexedDB });
const importedRoot = event(
"019fcb10-0000-7000-8000-000000000001",
"twitter/tweet",
[],
{ full_text: "Imported archive root" },
);
const importedReply = event(
"019fcb10-0000-7000-8000-000000000002",
"twitter/tweet",
[importedRoot.id],
{ full_text: "Imported archive reply" },
);
const keep = event(
"019fcb10-0000-7000-8000-000000000003",
"lync/mark",
[importedRoot.id],
{ label: "selection", target: importedRoot.id },
);
const note = event(
"019fcb10-0000-7000-8000-000000000004",
"curare/annotation",
[importedRoot.id],
{ label: "note", text: "Keep this source context." },
);
const cluster = event(
"019fcb10-0000-7000-8000-000000000005",
"lync/mark",
[importedRoot.id],
{ label: "cluster", target: importedRoot.id },
);

let announceSuspension!: () => void;
let releaseImport!: () => void;
const suspended = new Promise<void>((resolve) => {
announceSuspension = resolve;
});
const released = new Promise<void>((resolve) => {
releaseImport = resolve;
});
async function* archiveImport() {
yield importedRoot;
announceSuspension();
await released;
yield importedReply;
}

const importing = store.appendMany(archiveImport());
await suspended;

const expectDurableAfterReopen = async (id: string) => {
const reopened = createIndexedDbEventStore({ dbName, indexedDB });
await expect(reopened.byId(id)).resolves.toMatchObject({ body: { id } });
};

try {
await expect(store.append(keep)).resolves.toMatchObject({ status: "added" });
await expectDurableAfterReopen(keep.id);

await expect(store.union(serializeLyncEvent(note))).resolves.toMatchObject({
status: "added",
});
await expectDurableAfterReopen(note.id);

await expect(store.appendMany([cluster])).resolves.toEqual([
expect.objectContaining({ status: "added" }),
]);
await expectDurableAfterReopen(cluster.id);
} finally {
releaseImport();
await importing;
}

const reopened = createIndexedDbEventStore({ dbName, indexedDB });
await expect(reopened.byRoot(importedRoot.id)).resolves.toEqual(
expect.arrayContaining([
expect.objectContaining({ body: expect.objectContaining({ id: importedRoot.id }) }),
expect.objectContaining({ body: expect.objectContaining({ id: importedReply.id }) }),
expect.objectContaining({ body: expect.objectContaining({ id: keep.id }) }),
expect.objectContaining({ body: expect.objectContaining({ id: note.id }) }),
expect.objectContaining({ body: expect.objectContaining({ id: cluster.id }) }),
]),
);
});
});
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
},
"dependencies": {
"@ax-llm/ax": "^14.0.39",
"@deepfates/lync": "file:vendor/deepfates-lync-0.4.3-0a511b6.tgz",
"@deepfates/lync": "file:vendor/deepfates-lync-0.4.3-828a140.tgz",
"@tailwindcss/postcss": "^4.1.17",
"@types/d3-flextree": "^2.1.4",
"@types/express": "^4.17.21",
Expand Down Expand Up @@ -60,6 +60,7 @@
"eslint": "^8.57.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.6",
"fake-indexeddb": "^6.2.4",
"playwright": "^1.59.1",
"typescript": "^5.2.2",
"vite": "^7.3.6",
Expand Down
16 changes: 13 additions & 3 deletions vendor/LYNC-PROVENANCE.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
# Vendored Lync package

`deepfates-lync-0.4.3-0a511b6.tgz` was produced with `pnpm pack`
`deepfates-lync-0.4.3-828a140.tgz` was produced once with `pnpm pack`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Ship the documents linked from the packed README

When consumers read the README from this vendored package, its primary “Choose your path” links point to docs/library.md, docs/cli.md, docs/relay.md, and docs/architecture.md, but inspection of this exact tarball shows that no package/docs/ files are included. The replacement therefore turns the installed package's main documentation entry points into broken links; include the referenced documentation in the packed files or keep the packed README self-contained.

Useful? React with 👍 / 👎.

from the clean `@deepfates/lync` checkout at commit
`0a511b6f918acf87800bf433f34311ee47a9c48b`.
`828a14017ea71519c510b0808a49d6382a36ed33` (tree
`f24e6122012f24beea934a1f639117ab15694725`). Sibling consumers reuse these
exact archive bytes and checksum rather than repacking the source.

- Declared package version: `0.4.3`
- Archive SHA-256: `4de871ce1117cff040fedc069944c3705e314168e01f151ae6d27239f6d1b745`
- Archive SHA-256: `cc90bfc8766ecab7c7ab53298ca0fdff8e3649626879f6cab7300c071e566196`
- Required unpublished surfaces: `@deepfates/lync/indexed-union` and
`@deepfates/lync/presentation`

The source verification gate passed 223 tests across 26 files, typecheck,
executable examples, and its packed-artifact check. Its bounded regressions
cover public mutation completion during an overlapping async batch, reentrant
batch producers, partial file-store writes, and relay retry/reopen after
partial writes. Textile separately exercises the installed archive through an
IndexedDB fresh-store reopen regression and its provider-free browser path.
These checks establish those cases, not a blanket durability guarantee.

This exact local package keeps Textile reproducible before an owner-approved
0.4 registry release. It is a dependency boundary, not a fork: union parsing,
conflict adjudication, topology, suppression, source-byte verification, and
Expand Down
Binary file removed vendor/deepfates-lync-0.4.3-0a511b6.tgz
Binary file not shown.
Binary file added vendor/deepfates-lync-0.4.3-828a140.tgz
Binary file not shown.