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
14 changes: 7 additions & 7 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@
"@flags-sdk/vercel": "^1.4.6",
"@hugeicons/core-free-icons": "^4.2.3",
"@hugeicons/react": "^1.1.9",
"@openeditor/core": "0.0.49",
"@openeditor/custom-block": "0.0.49",
"@openeditor/emoji": "0.0.49",
"@openeditor/exporters": "0.0.49",
"@openeditor/react": "0.0.49",
"@openeditor/ui": "0.0.49",
"@openeditor/workspace": "0.0.49",
"@openeditor/core": "0.0.50",
"@openeditor/custom-block": "0.0.50",
"@openeditor/emoji": "0.0.50",
"@openeditor/exporters": "0.0.50",
"@openeditor/react": "0.0.50",
"@openeditor/ui": "0.0.50",
"@openeditor/workspace": "0.0.50",
"@vercel/analytics": "^2.0.1",
"@vercel/sdk": "^1.28.8",
"ai": "7.0.49",
Expand Down
64 changes: 32 additions & 32 deletions bun.lock

Large diffs are not rendered by default.

9 changes: 3 additions & 6 deletions packages/backend/convex/aiWorkspaces.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import { v } from "convex/values";
import {
fingerprintProjectPage,
type OpenEditorProjectSnapshot,
} from "@openeditor/workspace";
import { fingerprintSnapshotPage, type Snapshot } from "@openeditor/workspace";
import type { JsonObject } from "@openeditor/core";
import { query } from "./_generated/server";
import {
Expand Down Expand Up @@ -159,7 +156,7 @@ export const exportDraft = query({
document: document ?? emptyOpenEditorDocument(),
};
});
const project: OpenEditorProjectSnapshot = {
const project: Snapshot = {
id: String(site._id),
revision: String(site.draftRevision),
title: site.name,
Expand All @@ -184,7 +181,7 @@ export const exportDraft = query({
Promise.all(
project.pages.map(async (page) => ({
pageId: page.id,
fingerprint: await fingerprintProjectPage(page),
fingerprint: await fingerprintSnapshotPage(page),
})),
),
]);
Expand Down
6 changes: 1 addition & 5 deletions packages/backend/convex/billingDeletion.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,7 @@ describe("workspace billing deletion", () => {
getUserIdentity: async () => ({ subject: "user-1" }),
},
runQuery: async (_reference: unknown, args: unknown) => {
if (
typeof args === "object" &&
args !== null &&
"model" in args
) {
if (typeof args === "object" && args !== null && "model" in args) {
return {
_id: "member-1",
organizationId: "organization-1",
Expand Down
54 changes: 14 additions & 40 deletions packages/backend/convex/model/aiWorkspaceFingerprint.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { describe, expect, test } from "bun:test";
import {
fingerprintProjectPage,
fingerprintProjectSnapshot,
fingerprintSiteManifest,
type OpenEditorProjectSnapshot,
fingerprintSnapshot,
fingerprintSnapshotManifest,
fingerprintSnapshotPage,
type Snapshot,
} from "@openeditor/workspace";
import { assertAiWorkspaceFingerprints } from "./aiWorkspaceFingerprint";

const document = { type: "doc" as const, version: 1 as const, content: [] };
const current: OpenEditorProjectSnapshot = {
const current: Snapshot = {
id: "site-1",
revision: "4",
title: "Site",
Expand All @@ -26,28 +26,6 @@ const current: OpenEditorProjectSnapshot = {
],
};

function manifest(project: OpenEditorProjectSnapshot) {
return {
format: "openeditor.site" as const,
version: 1 as const,
project: {
id: project.id,
revision: project.revision,
title: project.title,
metadata: project.metadata,
},
pages: project.pages.map((page) => ({
id: page.id,
file: `pages/${page.id}.json`,
title: page.title,
slug: page.slug,
parentId: page.parentId,
order: page.order,
metadata: page.metadata,
})),
};
}

describe("OpenEditor atomic fingerprint checks", () => {
test("accepts the complete unchanged-to-updated trust chain", async () => {
const next = structuredClone(current);
Expand All @@ -56,24 +34,22 @@ describe("OpenEditor atomic fingerprint checks", () => {
assertAiWorkspaceFingerprints({
currentProject: current,
nextProject: next,
expectedProjectFingerprint: await fingerprintProjectSnapshot(current),
expectedSiteFingerprint: await fingerprintSiteManifest(
manifest(current),
),
nextSiteFingerprint: await fingerprintSiteManifest(manifest(next)),
expectedProjectFingerprint: await fingerprintSnapshot(current),
expectedSiteFingerprint: await fingerprintSnapshotManifest(current),
nextSiteFingerprint: await fingerprintSnapshotManifest(next),
pageFingerprints: [
{
pageId: "home",
expectedFingerprint: await fingerprintProjectPage(
expectedFingerprint: await fingerprintSnapshotPage(
current.pages[0]!,
),
nextFingerprint: await fingerprintProjectPage(next.pages[0]!),
nextFingerprint: await fingerprintSnapshotPage(next.pages[0]!),
},
],
}),
).resolves.toMatchObject({
expectedProjectFingerprint: await fingerprintProjectSnapshot(current),
resultProjectFingerprint: await fingerprintProjectSnapshot(next),
expectedProjectFingerprint: await fingerprintSnapshot(current),
resultProjectFingerprint: await fingerprintSnapshot(next),
});
});

Expand All @@ -83,10 +59,8 @@ describe("OpenEditor atomic fingerprint checks", () => {
currentProject: current,
nextProject: current,
expectedProjectFingerprint: "stale",
expectedSiteFingerprint: await fingerprintSiteManifest(
manifest(current),
),
nextSiteFingerprint: await fingerprintSiteManifest(manifest(current)),
expectedSiteFingerprint: await fingerprintSnapshotManifest(current),
nextSiteFingerprint: await fingerprintSnapshotManifest(current),
pageFingerprints: [],
}),
).rejects.toThrow("project fingerprint no longer matches");
Expand Down
48 changes: 11 additions & 37 deletions packages/backend/convex/model/aiWorkspaceFingerprint.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import {
fingerprintProjectPage,
fingerprintProjectSnapshot,
fingerprintSiteManifest,
type OpenEditorProjectSnapshot,
type WorkspaceSiteManifest,
fingerprintSnapshot,
fingerprintSnapshotManifest,
fingerprintSnapshotPage,
type Snapshot,
} from "@openeditor/workspace";
import { AiWorkspaceValidationError } from "./aiWorkspaceBounds";

Expand All @@ -13,35 +12,10 @@ export type AiPageFingerprintPrecondition = {
nextFingerprint?: string;
};

const siteManifest = (
project: OpenEditorProjectSnapshot,
): WorkspaceSiteManifest => ({
format: "openeditor.site",
version: 1,
project: {
id: project.id,
revision: project.revision,
title: project.title,
...(project.metadata !== undefined ? { metadata: project.metadata } : {}),
},
pages: project.pages.map((page) => ({
id: page.id,
file: `pages/${page.id}.json`,
title: page.title,
...(page.slug !== undefined ? { slug: page.slug } : {}),
...(page.route !== undefined ? { route: page.route } : {}),
...(page.parentId !== undefined ? { parentId: page.parentId } : {}),
...(page.order !== undefined ? { order: page.order } : {}),
...(page.metadata !== undefined ? { metadata: page.metadata } : {}),
})),
});

export async function fingerprintAiProjectTrustRoot(
project: OpenEditorProjectSnapshot,
) {
export async function fingerprintAiProjectTrustRoot(project: Snapshot) {
return {
projectFingerprint: await fingerprintProjectSnapshot(project),
siteFingerprint: await fingerprintSiteManifest(siteManifest(project)),
projectFingerprint: await fingerprintSnapshot(project),
siteFingerprint: await fingerprintSnapshotManifest(project),
};
}

Expand All @@ -51,8 +25,8 @@ function fail(message: string): never {

/** Verify the complete OpenEditor trust root inside the write transaction. */
export async function assertAiWorkspaceFingerprints(input: {
currentProject: OpenEditorProjectSnapshot;
nextProject: OpenEditorProjectSnapshot;
currentProject: Snapshot;
nextProject: Snapshot;
expectedProjectFingerprint: string;
expectedSiteFingerprint: string;
nextSiteFingerprint: string;
Expand Down Expand Up @@ -90,7 +64,7 @@ export async function assertAiWorkspaceFingerprints(input: {
if (current) fail(`Created page ${precondition.pageId} already exists`);
} else if (
!current ||
(await fingerprintProjectPage(current)) !==
(await fingerprintSnapshotPage(current)) !==
precondition.expectedFingerprint
) {
fail(`Page ${precondition.pageId} fingerprint no longer matches`);
Expand All @@ -100,7 +74,7 @@ export async function assertAiWorkspaceFingerprints(input: {
const next = nextPages.get(precondition.pageId);
if (
!next ||
(await fingerprintProjectPage(next)) !== precondition.nextFingerprint
(await fingerprintSnapshotPage(next)) !== precondition.nextFingerprint
) {
fail(`Page ${precondition.pageId} next fingerprint is invalid`);
}
Expand Down
6 changes: 3 additions & 3 deletions packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@
"@convex-dev/workpool": "^0.4.9",
"@firecrawl/anydoc": "0.1.9",
"@noble/hashes": "2.0.1",
"@openeditor/core": "0.0.48",
"@openeditor/custom-block": "0.0.48",
"@openeditor/workspace": "0.0.48",
"@openeditor/core": "0.0.50",
"@openeditor/custom-block": "0.0.50",
"@openeditor/workspace": "0.0.50",
"@polar-sh/sdk": "0.49.0",
"ai": "7.0.49",
"better-auth": "^1.6.23",
Expand Down