@agent-native/core 0.101.6 → 0.101.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +28 -0
- package/corpus/core/docs/content/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/ar-SA/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/de-DE/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/es-ES/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/fr-FR/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/hi-IN/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/ja-JP/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/ko-KR/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/pt-BR/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/zh-CN/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/zh-TW/deployment.mdx +9 -7
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/artifact-response.ts +255 -12
- package/corpus/core/src/agent/thread-data-builder.ts +84 -11
- package/corpus/core/src/client/review/ReviewCommentComposer.tsx +9 -1
- package/corpus/core/src/client/review/ReviewThreadPanel.tsx +17 -2
- package/corpus/core/src/client/settings/SettingsPanel.tsx +41 -21
- package/corpus/core/src/client/settings/useBuilderStatus.ts +28 -9
- package/corpus/core/src/client/setup-connections/BuilderConnectCard.tsx +2 -7
- package/corpus/core/src/integrations/a2a-continuation-processor.ts +99 -7
- package/corpus/core/src/integrations/adapters/discord.ts +3 -1
- package/corpus/core/src/integrations/adapters/email.ts +4 -1
- package/corpus/core/src/integrations/adapters/google-docs.ts +4 -1
- package/corpus/core/src/integrations/adapters/microsoft-teams.ts +3 -1
- package/corpus/core/src/integrations/adapters/slack.ts +38 -9
- package/corpus/core/src/integrations/adapters/telegram.ts +32 -10
- package/corpus/core/src/integrations/adapters/whatsapp.ts +5 -1
- package/corpus/core/src/integrations/index.ts +1 -0
- package/corpus/core/src/integrations/types.ts +9 -2
- package/corpus/core/src/integrations/webhook-handler.ts +69 -45
- package/corpus/core/src/server/agent-chat/action-filters-a2a.ts +1 -0
- package/corpus/core/src/server/auth.ts +25 -1
- package/corpus/core/src/server/builder-browser.ts +353 -1
- package/corpus/core/src/server/core-routes-plugin.ts +601 -223
- package/corpus/templates/calendar/app/components/calendar/DayView.tsx +80 -1
- package/corpus/templates/calendar/app/components/calendar/EventCard.tsx +28 -3
- package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +5 -1
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +30 -3
- package/corpus/templates/calendar/app/components/calendar/OutOfOfficeEvent.tsx +196 -0
- package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +115 -1
- package/corpus/templates/calendar/app/lib/out-of-office.ts +45 -0
- package/corpus/templates/calendar/changelog/2026-07-14-out-of-office-blocks-now-sit-behind-meetings-and-event-stack.md +6 -0
- package/corpus/templates/content/.agents/skills/content/SKILL.md +31 -0
- package/corpus/templates/content/.agents/skills/document-editing/SKILL.md +35 -21
- package/corpus/templates/content/AGENTS.md +32 -9
- package/corpus/templates/content/actions/_builder-cms-intent-lookup.ts +87 -0
- package/corpus/templates/content/actions/_builder-cms-read-client.ts +173 -25
- package/corpus/templates/content/actions/_builder-cms-source-adapter.ts +1 -0
- package/corpus/templates/content/actions/_builder-cms-write-adapter.ts +427 -18
- package/corpus/templates/content/actions/_builder-cms-write-client.ts +17 -73
- package/corpus/templates/content/actions/_builder-cms-write-settings.ts +27 -7
- package/corpus/templates/content/actions/_builder-source-execution-claim.ts +124 -0
- package/corpus/templates/content/actions/_builder-source-execution-preservation.ts +21 -0
- package/corpus/templates/content/actions/_builder-source-timings.ts +57 -0
- package/corpus/templates/content/actions/_database-source-utils.ts +1071 -170
- package/corpus/templates/content/actions/_database-utils.ts +104 -13
- package/corpus/templates/content/actions/_local-file-documents.ts +53 -1
- package/corpus/templates/content/actions/_preview-document-draft.ts +31 -0
- package/corpus/templates/content/actions/_property-utils.ts +7 -1
- package/corpus/templates/content/actions/cancel-prepared-builder-source-update.ts +309 -0
- package/corpus/templates/content/actions/configure-document-property.ts +11 -0
- package/corpus/templates/content/actions/create-content-database.ts +11 -0
- package/corpus/templates/content/actions/create-document.ts +9 -0
- package/corpus/templates/content/actions/create-inline-content-database.ts +3 -0
- package/corpus/templates/content/actions/execute-builder-source-batch.ts +120 -49
- package/corpus/templates/content/actions/execute-builder-source-execution.ts +608 -264
- package/corpus/templates/content/actions/export-content-source.ts +1 -0
- package/corpus/templates/content/actions/get-content-database.ts +13 -1
- package/corpus/templates/content/actions/get-document.ts +12 -2
- package/corpus/templates/content/actions/get-preview-document-draft.ts +26 -0
- package/corpus/templates/content/actions/import-content-source.ts +6 -0
- package/corpus/templates/content/actions/list-documents.ts +3 -0
- package/corpus/templates/content/actions/materialize-builder-required-fields.ts +550 -0
- package/corpus/templates/content/actions/prepare-builder-source-execution.ts +112 -65
- package/corpus/templates/content/actions/prepare-builder-source-review.ts +463 -170
- package/corpus/templates/content/actions/preview-builder-source-review.ts +99 -0
- package/corpus/templates/content/actions/pull-document.ts +2 -0
- package/corpus/templates/content/actions/search-documents.ts +5 -1
- package/corpus/templates/content/actions/set-content-database-source-write-mode.ts +2 -2
- package/corpus/templates/content/actions/update-document.ts +16 -1
- package/corpus/templates/content/actions/update-preview-document-draft.ts +153 -0
- package/corpus/templates/content/actions/view-screen.ts +15 -2
- package/corpus/templates/content/app/components/editor/DescriptionField.tsx +121 -0
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +14 -0
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +250 -49
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +57 -5
- package/corpus/templates/content/app/components/editor/body-hydration.ts +27 -0
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +2414 -589
- package/corpus/templates/content/app/components/editor/database/settings.tsx +13 -10
- package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +358 -75
- package/corpus/templates/content/app/components/editor/previewDocumentSaveController.ts +124 -29
- package/corpus/templates/content/app/components/editor/previewDocumentSaveRegistry.ts +2 -0
- package/corpus/templates/content/app/global.css +18 -1
- package/corpus/templates/content/app/hooks/use-content-database.ts +68 -0
- package/corpus/templates/content/app/hooks/use-documents.ts +53 -4
- package/corpus/templates/content/app/i18n/zh-TW.ts +30 -0
- package/corpus/templates/content/app/i18n-data.ts +339 -0
- package/corpus/templates/content/changelog/2026-07-10-builder-backed-preview-edits-are-preserved-when-article-bodi.md +6 -0
- package/corpus/templates/content/changelog/2026-07-10-builder-cms-sources-can-now-be-enabled-for-guarded-staged-or.md +6 -0
- package/corpus/templates/content/changelog/2026-07-13-builder-connection-setup-is-now-available-directly-from-conn.md +6 -0
- package/corpus/templates/content/changelog/2026-07-13-builder-reviews-now-hide-already-applied-writes-detect-nativ.md +6 -0
- package/corpus/templates/content/changelog/2026-07-13-required-builder-publishing-fields-can-now-be-added-directly.md +6 -0
- package/corpus/templates/content/changelog/2026-07-13-you-can-now-cancel-a-prepared-builder-update-safely-before-i.md +6 -0
- package/corpus/templates/content/changelog/2026-07-14-builder-account-connection-now-remains-available-when-branch.md +6 -0
- package/corpus/templates/content/changelog/2026-07-14-builder-backed-articles-now-preserve-fifth-and-sixth-level-h.md +6 -0
- package/corpus/templates/content/changelog/2026-07-14-builder-sources-now-review-and-publish-rich-article-updates-.md +6 -0
- package/corpus/templates/content/changelog/2026-07-14-builder-writes-now-stop-retries-when-reconciliation-is-needed.md +6 -0
- package/corpus/templates/content/changelog/2026-07-14-file-and-media-links-now-validate-before-saving.md +6 -0
- package/corpus/templates/content/changelog/2026-07-14-pages-and-databases-can-describe-themselves-with-guidance-fo.md +6 -0
- package/corpus/templates/content/changelog/2026-07-14-slack-follow-ups-now-retain-created-content-identity-and-per.md +6 -0
- package/corpus/templates/content/parity/matrix.md +27 -25
- package/corpus/templates/content/parity/matrix.ts +50 -2
- package/corpus/templates/content/server/db/schema.ts +51 -0
- package/corpus/templates/content/server/lib/document-context.ts +76 -0
- package/corpus/templates/content/server/lib/public-documents.ts +3 -0
- package/corpus/templates/content/server/plugins/auth.ts +1 -0
- package/corpus/templates/content/server/plugins/db.ts +59 -0
- package/corpus/templates/content/server/routes/api/document-agent-context.json.get.ts +5 -0
- package/corpus/templates/content/shared/api.ts +87 -1
- package/corpus/templates/content/shared/builder-mdx.ts +705 -15
- package/corpus/templates/content/shared/content-source.ts +7 -0
- package/corpus/templates/content/shared/nfm.ts +3 -3
- package/corpus/templates/content/shared/properties.ts +2 -0
- package/corpus/templates/design/app/components/design/ReadOnlyDesignBanner.tsx +52 -7
- package/corpus/templates/design/app/components/design/ReviewCommentsPanel.tsx +36 -4
- package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +4 -1
- package/corpus/templates/design/app/i18n/ar-SA.ts +1 -0
- package/corpus/templates/design/app/i18n/de-DE.ts +1 -0
- package/corpus/templates/design/app/i18n/en-US.ts +1 -0
- package/corpus/templates/design/app/i18n/es-ES.ts +1 -0
- package/corpus/templates/design/app/i18n/fr-FR.ts +1 -0
- package/corpus/templates/design/app/i18n/hi-IN.ts +1 -0
- package/corpus/templates/design/app/i18n/ja-JP.ts +1 -0
- package/corpus/templates/design/app/i18n/ko-KR.ts +1 -0
- package/corpus/templates/design/app/i18n/pt-BR.ts +1 -0
- package/corpus/templates/design/app/i18n/zh-CN.ts +1 -0
- package/corpus/templates/design/app/i18n/zh-TW.ts +1 -0
- package/corpus/templates/design/app/pages/design-editor/selection-state.ts +8 -0
- package/corpus/templates/design/app/pages/design-editor/tool-state.ts +11 -0
- package/corpus/templates/design/changelog/2026-07-14-reviewers-can-now-pin-comments-from-the-canvas-toolbar-targe.md +6 -0
- package/corpus/templates/design/changelog/2026-07-15-viewer-comment-controls-now-sit-inside-the-read-only-notice-.md +6 -0
- package/corpus/templates/design/shared/review-anchor.ts +48 -0
- package/corpus/templates/plan/server/plan-mdx.ts +53 -7
- package/dist/a2a/artifact-response.d.ts +15 -0
- package/dist/a2a/artifact-response.d.ts.map +1 -1
- package/dist/a2a/artifact-response.js +204 -5
- package/dist/a2a/artifact-response.js.map +1 -1
- package/dist/agent/thread-data-builder.d.ts +6 -0
- package/dist/agent/thread-data-builder.d.ts.map +1 -1
- package/dist/agent/thread-data-builder.js +71 -8
- package/dist/agent/thread-data-builder.js.map +1 -1
- package/dist/client/review/ReviewCommentComposer.d.ts +2 -1
- package/dist/client/review/ReviewCommentComposer.d.ts.map +1 -1
- package/dist/client/review/ReviewCommentComposer.js +3 -3
- package/dist/client/review/ReviewCommentComposer.js.map +1 -1
- package/dist/client/review/ReviewThreadPanel.d.ts +9 -1
- package/dist/client/review/ReviewThreadPanel.d.ts.map +1 -1
- package/dist/client/review/ReviewThreadPanel.js +6 -4
- package/dist/client/review/ReviewThreadPanel.js.map +1 -1
- package/dist/client/settings/SettingsPanel.d.ts +3 -0
- package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
- package/dist/client/settings/SettingsPanel.js +11 -4
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/settings/useBuilderStatus.d.ts +8 -4
- package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
- package/dist/client/settings/useBuilderStatus.js +19 -6
- package/dist/client/settings/useBuilderStatus.js.map +1 -1
- package/dist/client/setup-connections/BuilderConnectCard.d.ts.map +1 -1
- package/dist/client/setup-connections/BuilderConnectCard.js +2 -5
- package/dist/client/setup-connections/BuilderConnectCard.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/integrations/a2a-continuation-processor.d.ts.map +1 -1
- package/dist/integrations/a2a-continuation-processor.js +72 -6
- package/dist/integrations/a2a-continuation-processor.js.map +1 -1
- package/dist/integrations/adapters/discord.d.ts.map +1 -1
- package/dist/integrations/adapters/discord.js +1 -0
- package/dist/integrations/adapters/discord.js.map +1 -1
- package/dist/integrations/adapters/email.d.ts.map +1 -1
- package/dist/integrations/adapters/email.js +2 -0
- package/dist/integrations/adapters/email.js.map +1 -1
- package/dist/integrations/adapters/google-docs.d.ts.map +1 -1
- package/dist/integrations/adapters/google-docs.js +2 -0
- package/dist/integrations/adapters/google-docs.js.map +1 -1
- package/dist/integrations/adapters/microsoft-teams.d.ts.map +1 -1
- package/dist/integrations/adapters/microsoft-teams.js +1 -0
- package/dist/integrations/adapters/microsoft-teams.js.map +1 -1
- package/dist/integrations/adapters/slack.d.ts.map +1 -1
- package/dist/integrations/adapters/slack.js +21 -6
- package/dist/integrations/adapters/slack.js.map +1 -1
- package/dist/integrations/adapters/telegram.d.ts.map +1 -1
- package/dist/integrations/adapters/telegram.js +11 -2
- package/dist/integrations/adapters/telegram.js.map +1 -1
- package/dist/integrations/adapters/whatsapp.d.ts.map +1 -1
- package/dist/integrations/adapters/whatsapp.js +3 -0
- package/dist/integrations/adapters/whatsapp.js.map +1 -1
- package/dist/integrations/index.d.ts +1 -1
- package/dist/integrations/index.d.ts.map +1 -1
- package/dist/integrations/index.js.map +1 -1
- package/dist/integrations/types.d.ts +8 -2
- package/dist/integrations/types.d.ts.map +1 -1
- package/dist/integrations/types.js.map +1 -1
- package/dist/integrations/webhook-handler.d.ts.map +1 -1
- package/dist/integrations/webhook-handler.js +40 -43
- package/dist/integrations/webhook-handler.js.map +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-chat/action-filters-a2a.d.ts.map +1 -1
- package/dist/server/agent-chat/action-filters-a2a.js +1 -0
- package/dist/server/agent-chat/action-filters-a2a.js.map +1 -1
- package/dist/server/auth.d.ts +1 -0
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +19 -2
- package/dist/server/auth.js.map +1 -1
- package/dist/server/builder-browser.d.ts +78 -0
- package/dist/server/builder-browser.d.ts.map +1 -1
- package/dist/server/builder-browser.js +246 -1
- package/dist/server/builder-browser.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts +41 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +260 -14
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/docs/content/deployment.mdx +9 -7
- package/docs/content/locales/ar-SA/deployment.mdx +9 -7
- package/docs/content/locales/de-DE/deployment.mdx +9 -7
- package/docs/content/locales/es-ES/deployment.mdx +9 -7
- package/docs/content/locales/fr-FR/deployment.mdx +9 -7
- package/docs/content/locales/hi-IN/deployment.mdx +9 -7
- package/docs/content/locales/ja-JP/deployment.mdx +9 -7
- package/docs/content/locales/ko-KR/deployment.mdx +9 -7
- package/docs/content/locales/pt-BR/deployment.mdx +9 -7
- package/docs/content/locales/zh-CN/deployment.mdx +9 -7
- package/docs/content/locales/zh-TW/deployment.mdx +9 -7
- package/package.json +2 -2
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { and, asc, eq, inArray, isNull, sql } from "drizzle-orm";
|
|
2
2
|
|
|
3
3
|
import { getDb, schema } from "../server/db/index.js";
|
|
4
|
+
import { getDocumentContextPath } from "../server/lib/document-context.js";
|
|
4
5
|
import {
|
|
5
6
|
parseDocumentFavorite,
|
|
6
7
|
parseDocumentHideFromSearch,
|
|
@@ -20,6 +21,7 @@ import {
|
|
|
20
21
|
listPropertiesForDatabase,
|
|
21
22
|
serializeDatabase,
|
|
22
23
|
} from "./_property-utils.js";
|
|
24
|
+
export { getDocumentContextPath };
|
|
23
25
|
|
|
24
26
|
export const CONTENT_DATABASE_MAX_READ_LIMIT = 5_000;
|
|
25
27
|
|
|
@@ -34,6 +36,32 @@ type DatabaseMembershipRow = {
|
|
|
34
36
|
bodyHydrationQueueId?: string | null;
|
|
35
37
|
};
|
|
36
38
|
|
|
39
|
+
type DocumentListRow = Omit<typeof schema.documents.$inferSelect, "content">;
|
|
40
|
+
|
|
41
|
+
// Database grids render row metadata and properties. Fetching the document body
|
|
42
|
+
// here would transfer it only for serializeDocument to replace it with an empty
|
|
43
|
+
// string below; opened documents use their dedicated document read path instead.
|
|
44
|
+
export const contentDatabaseListDocumentSelection = {
|
|
45
|
+
id: schema.documents.id,
|
|
46
|
+
parentId: schema.documents.parentId,
|
|
47
|
+
title: schema.documents.title,
|
|
48
|
+
description: schema.documents.description,
|
|
49
|
+
icon: schema.documents.icon,
|
|
50
|
+
position: schema.documents.position,
|
|
51
|
+
isFavorite: schema.documents.isFavorite,
|
|
52
|
+
hideFromSearch: schema.documents.hideFromSearch,
|
|
53
|
+
sourceMode: schema.documents.sourceMode,
|
|
54
|
+
sourceKind: schema.documents.sourceKind,
|
|
55
|
+
sourcePath: schema.documents.sourcePath,
|
|
56
|
+
sourceRootPath: schema.documents.sourceRootPath,
|
|
57
|
+
sourceUpdatedAt: schema.documents.sourceUpdatedAt,
|
|
58
|
+
visibility: schema.documents.visibility,
|
|
59
|
+
ownerEmail: schema.documents.ownerEmail,
|
|
60
|
+
orgId: schema.documents.orgId,
|
|
61
|
+
createdAt: schema.documents.createdAt,
|
|
62
|
+
updatedAt: schema.documents.updatedAt,
|
|
63
|
+
};
|
|
64
|
+
|
|
37
65
|
export function serializeBodyHydration(
|
|
38
66
|
item: typeof schema.contentDatabaseItems.$inferSelect,
|
|
39
67
|
options: { queued?: boolean } = {},
|
|
@@ -44,6 +72,7 @@ export function serializeBodyHydration(
|
|
|
44
72
|
status === "pending" ||
|
|
45
73
|
status === "hydrating" ||
|
|
46
74
|
status === "hydrated" ||
|
|
75
|
+
status === "unavailable" ||
|
|
47
76
|
status === "error"
|
|
48
77
|
? status
|
|
49
78
|
: options.queued
|
|
@@ -121,16 +150,62 @@ export function normalizeContentDatabasePageOptions(options: {
|
|
|
121
150
|
return { limit, offset };
|
|
122
151
|
}
|
|
123
152
|
|
|
153
|
+
export function filterContentDatabaseSourceRowsForPage<
|
|
154
|
+
TRow extends { documentId: string; databaseItemId: string },
|
|
155
|
+
TChangeSet extends {
|
|
156
|
+
documentId: string | null;
|
|
157
|
+
databaseItemId: string | null;
|
|
158
|
+
direction: string;
|
|
159
|
+
state: string;
|
|
160
|
+
executions: Array<{ state: string }>;
|
|
161
|
+
},
|
|
162
|
+
>(args: {
|
|
163
|
+
rows: TRow[];
|
|
164
|
+
changeSets: TChangeSet[];
|
|
165
|
+
visibleDocumentIds: ReadonlySet<string>;
|
|
166
|
+
}) {
|
|
167
|
+
const actionableChangeSets = args.changeSets.filter(
|
|
168
|
+
(changeSet) =>
|
|
169
|
+
changeSet.direction === "outbound" &&
|
|
170
|
+
!changeSet.executions.some(
|
|
171
|
+
(execution) => execution.state === "succeeded",
|
|
172
|
+
) &&
|
|
173
|
+
(changeSet.state === "pending_push" ||
|
|
174
|
+
changeSet.state === "staged_revision" ||
|
|
175
|
+
changeSet.state === "approved"),
|
|
176
|
+
);
|
|
177
|
+
const actionableDocumentIds = new Set(
|
|
178
|
+
actionableChangeSets.flatMap((changeSet) =>
|
|
179
|
+
changeSet.documentId ? [changeSet.documentId] : [],
|
|
180
|
+
),
|
|
181
|
+
);
|
|
182
|
+
const actionableItemIds = new Set(
|
|
183
|
+
actionableChangeSets.flatMap((changeSet) =>
|
|
184
|
+
changeSet.databaseItemId ? [changeSet.databaseItemId] : [],
|
|
185
|
+
),
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
return args.rows.filter(
|
|
189
|
+
(row) =>
|
|
190
|
+
!row.documentId ||
|
|
191
|
+
args.visibleDocumentIds.has(row.documentId) ||
|
|
192
|
+
actionableDocumentIds.has(row.documentId) ||
|
|
193
|
+
actionableItemIds.has(row.databaseItemId),
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
124
197
|
function serializeDocument(
|
|
125
|
-
doc:
|
|
198
|
+
doc: DocumentListRow,
|
|
126
199
|
membership?: DatabaseMembershipRow,
|
|
127
|
-
options: { includeContent?: boolean } = {},
|
|
128
200
|
) {
|
|
129
201
|
return {
|
|
130
202
|
id: doc.id,
|
|
131
203
|
parentId: doc.parentId,
|
|
132
204
|
title: doc.title,
|
|
133
|
-
|
|
205
|
+
// List reads deliberately project no `documents.content`; opened documents
|
|
206
|
+
// use their dedicated read path.
|
|
207
|
+
content: "",
|
|
208
|
+
description: doc.description,
|
|
134
209
|
icon: doc.icon,
|
|
135
210
|
position: doc.position,
|
|
136
211
|
isFavorite: parseDocumentFavorite(doc.isFavorite),
|
|
@@ -160,6 +235,14 @@ export async function getContentDatabaseResponse(
|
|
|
160
235
|
if (!database || database.deletedAt) {
|
|
161
236
|
throw new Error(`Database "${databaseId}" not found`);
|
|
162
237
|
}
|
|
238
|
+
const [databaseDocument] = await db
|
|
239
|
+
.select({
|
|
240
|
+
id: schema.documents.id,
|
|
241
|
+
parentId: schema.documents.parentId,
|
|
242
|
+
description: schema.documents.description,
|
|
243
|
+
})
|
|
244
|
+
.from(schema.documents)
|
|
245
|
+
.where(eq(schema.documents.id, database.documentId));
|
|
163
246
|
|
|
164
247
|
// PURE read: the primary "Content" Blocks field is seeded at create time and
|
|
165
248
|
// by the one-time startup repair — never here. Reading a database (including a
|
|
@@ -185,7 +268,7 @@ export async function getContentDatabaseResponse(
|
|
|
185
268
|
const documents =
|
|
186
269
|
items.length > 0
|
|
187
270
|
? await db
|
|
188
|
-
.select()
|
|
271
|
+
.select(contentDatabaseListDocumentSelection)
|
|
189
272
|
.from(schema.documents)
|
|
190
273
|
.where(
|
|
191
274
|
and(
|
|
@@ -200,7 +283,9 @@ export async function getContentDatabaseResponse(
|
|
|
200
283
|
const documentById = new Map(documents.map((doc) => [doc.id, doc]));
|
|
201
284
|
const propertiesByDocumentId = await listPropertiesForDatabaseDocuments(
|
|
202
285
|
databaseId,
|
|
203
|
-
|
|
286
|
+
// Property serialization uses metadata only; this list projection carries
|
|
287
|
+
// every document field it consumes except the deliberately omitted body.
|
|
288
|
+
documents as Array<typeof schema.documents.$inferSelect>,
|
|
204
289
|
);
|
|
205
290
|
const queuedBodyHydrationItemIds =
|
|
206
291
|
items.length > 0
|
|
@@ -248,17 +333,20 @@ export async function getContentDatabaseResponse(
|
|
|
248
333
|
serializedItems.map((item) => item.document.id),
|
|
249
334
|
);
|
|
250
335
|
// When paginating, scope every DOCUMENT-BACKED source's rows to the visible
|
|
251
|
-
// page
|
|
252
|
-
//
|
|
253
|
-
//
|
|
336
|
+
// page, plus the small set referenced by actionable reviews. The dialog gets
|
|
337
|
+
// change sets independently of the item page and needs those rows to retain
|
|
338
|
+
// the linked provider target instead of misclassifying an off-page update as
|
|
339
|
+
// a create. Federated join rows carry no document (empty documentId), so
|
|
340
|
+
// they're kept intact — only matched ones overlay anyway.
|
|
254
341
|
const pagedSources =
|
|
255
342
|
limit !== null
|
|
256
343
|
? sources.map((source) => ({
|
|
257
344
|
...source,
|
|
258
|
-
rows:
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
345
|
+
rows: filterContentDatabaseSourceRowsForPage({
|
|
346
|
+
rows: source.rows,
|
|
347
|
+
changeSets: source.changeSets,
|
|
348
|
+
visibleDocumentIds: serializedDocumentIds,
|
|
349
|
+
}),
|
|
262
350
|
}))
|
|
263
351
|
: sources;
|
|
264
352
|
const pagedPrimary = pagedSources[0] ?? null;
|
|
@@ -272,7 +360,10 @@ export async function getContentDatabaseResponse(
|
|
|
272
360
|
const itemsWithOverlay = applyFederatedOverlayValues(federatedItems);
|
|
273
361
|
|
|
274
362
|
return {
|
|
275
|
-
database: serializeDatabase(database),
|
|
363
|
+
database: serializeDatabase(database, databaseDocument?.description ?? ""),
|
|
364
|
+
contextPath: databaseDocument
|
|
365
|
+
? await getDocumentContextPath(databaseDocument)
|
|
366
|
+
: [],
|
|
276
367
|
properties: await listPropertiesForDatabase(databaseId),
|
|
277
368
|
items: itemsWithOverlay,
|
|
278
369
|
source: pagedPrimary,
|
|
@@ -154,6 +154,7 @@ function documentFromFolder(folderPath: string, position: number): Document {
|
|
|
154
154
|
parentId: folderParentId(folderPath),
|
|
155
155
|
title: titleFromSegment(basename(folderPath)),
|
|
156
156
|
content: "",
|
|
157
|
+
description: "",
|
|
157
158
|
icon: null,
|
|
158
159
|
position,
|
|
159
160
|
isFavorite: false,
|
|
@@ -189,6 +190,7 @@ function documentFromLocalFile(
|
|
|
189
190
|
parentId: parentFolderId(file.path),
|
|
190
191
|
title: parsed.title,
|
|
191
192
|
content: parsed.content,
|
|
193
|
+
description: parsed.description ?? "",
|
|
192
194
|
icon: parsed.icon ?? null,
|
|
193
195
|
position,
|
|
194
196
|
isFavorite: parsed.isFavorite ?? false,
|
|
@@ -410,6 +412,33 @@ export async function getLocalFileDocument(id: string): Promise<Document> {
|
|
|
410
412
|
return documentFromLocalFile(file, file.content, 0);
|
|
411
413
|
}
|
|
412
414
|
|
|
415
|
+
export async function getLocalDocumentContextPath(id: string) {
|
|
416
|
+
const documents = await listLocalFileDocuments();
|
|
417
|
+
const byId = new Map(documents.map((document) => [document.id, document]));
|
|
418
|
+
const current = byId.get(id) ?? (await getLocalFileDocument(id));
|
|
419
|
+
const path: Array<{
|
|
420
|
+
id: string;
|
|
421
|
+
kind: "page" | "database";
|
|
422
|
+
title: string;
|
|
423
|
+
description: string;
|
|
424
|
+
}> = [];
|
|
425
|
+
const seen = new Set([current.id]);
|
|
426
|
+
let parentId = current.parentId;
|
|
427
|
+
while (parentId && !seen.has(parentId)) {
|
|
428
|
+
seen.add(parentId);
|
|
429
|
+
const parent = byId.get(parentId);
|
|
430
|
+
if (!parent) break;
|
|
431
|
+
path.unshift({
|
|
432
|
+
id: parent.id,
|
|
433
|
+
kind: "page",
|
|
434
|
+
title: parent.title,
|
|
435
|
+
description: parent.description ?? "",
|
|
436
|
+
});
|
|
437
|
+
parentId = parent.parentId;
|
|
438
|
+
}
|
|
439
|
+
return path;
|
|
440
|
+
}
|
|
441
|
+
|
|
413
442
|
function splitFrontmatter(source: string) {
|
|
414
443
|
const match = source.match(FRONTMATTER_RE);
|
|
415
444
|
if (!match) return { frontmatter: "", body: source };
|
|
@@ -517,12 +546,16 @@ function updateFrontmatterFields(
|
|
|
517
546
|
titleChanged: boolean,
|
|
518
547
|
iconChanged: boolean,
|
|
519
548
|
favoriteChanged: boolean,
|
|
549
|
+
descriptionChanged: boolean,
|
|
520
550
|
) {
|
|
521
551
|
if (usesDocsNoBookkeepingProfile(file.profile)) {
|
|
522
552
|
return {
|
|
523
553
|
...(titleChanged ? { title: nextTitle || "Untitled" } : {}),
|
|
524
554
|
...(iconChanged ? { icon: args.icon ?? null } : {}),
|
|
525
555
|
...(favoriteChanged ? { isFavorite: args.isFavorite ?? false } : {}),
|
|
556
|
+
...(descriptionChanged
|
|
557
|
+
? { description: args.description?.trim() ?? "" }
|
|
558
|
+
: {}),
|
|
526
559
|
};
|
|
527
560
|
}
|
|
528
561
|
|
|
@@ -531,6 +564,10 @@ function updateFrontmatterFields(
|
|
|
531
564
|
icon: args.icon !== undefined ? args.icon : current.icon,
|
|
532
565
|
isFavorite:
|
|
533
566
|
args.isFavorite !== undefined ? args.isFavorite : current.isFavorite,
|
|
567
|
+
description:
|
|
568
|
+
args.description !== undefined
|
|
569
|
+
? args.description.trim()
|
|
570
|
+
: current.description,
|
|
534
571
|
updatedAt: new Date().toISOString(),
|
|
535
572
|
};
|
|
536
573
|
}
|
|
@@ -544,6 +581,9 @@ function createFrontmatterFields(
|
|
|
544
581
|
return {
|
|
545
582
|
title,
|
|
546
583
|
...(args.icon !== undefined ? { icon: args.icon || null } : {}),
|
|
584
|
+
...(args.description !== undefined
|
|
585
|
+
? { description: args.description.trim() }
|
|
586
|
+
: {}),
|
|
547
587
|
};
|
|
548
588
|
}
|
|
549
589
|
|
|
@@ -551,6 +591,7 @@ function createFrontmatterFields(
|
|
|
551
591
|
title,
|
|
552
592
|
icon: args.icon || null,
|
|
553
593
|
isFavorite: false,
|
|
594
|
+
description: args.description?.trim() ?? "",
|
|
554
595
|
updatedAt: new Date().toISOString(),
|
|
555
596
|
};
|
|
556
597
|
}
|
|
@@ -576,8 +617,17 @@ export async function updateLocalFileDocument(
|
|
|
576
617
|
const iconChanged = args.icon !== undefined && args.icon !== current.icon;
|
|
577
618
|
const favoriteChanged =
|
|
578
619
|
args.isFavorite !== undefined && args.isFavorite !== current.isFavorite;
|
|
620
|
+
const descriptionChanged =
|
|
621
|
+
args.description !== undefined &&
|
|
622
|
+
args.description.trim() !== current.description;
|
|
579
623
|
|
|
580
|
-
if (
|
|
624
|
+
if (
|
|
625
|
+
!titleChanged &&
|
|
626
|
+
!contentChanged &&
|
|
627
|
+
!iconChanged &&
|
|
628
|
+
!favoriteChanged &&
|
|
629
|
+
!descriptionChanged
|
|
630
|
+
) {
|
|
581
631
|
return current;
|
|
582
632
|
}
|
|
583
633
|
|
|
@@ -591,6 +641,7 @@ export async function updateLocalFileDocument(
|
|
|
591
641
|
titleChanged,
|
|
592
642
|
iconChanged,
|
|
593
643
|
favoriteChanged,
|
|
644
|
+
descriptionChanged,
|
|
594
645
|
),
|
|
595
646
|
nextContent,
|
|
596
647
|
);
|
|
@@ -712,6 +763,7 @@ export async function localContentViewScreenSummary() {
|
|
|
712
763
|
id: document.id,
|
|
713
764
|
parentId: document.parentId,
|
|
714
765
|
title: document.title,
|
|
766
|
+
description: document.description,
|
|
715
767
|
source: document.source,
|
|
716
768
|
})),
|
|
717
769
|
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { and, eq } from "drizzle-orm";
|
|
2
|
+
|
|
3
|
+
import { getDb, schema } from "../server/db/index.js";
|
|
4
|
+
|
|
5
|
+
export async function readPreviewDocumentDraft(
|
|
6
|
+
ownerEmail: string,
|
|
7
|
+
orgId: string,
|
|
8
|
+
documentId: string,
|
|
9
|
+
) {
|
|
10
|
+
const [draft] = await getDb()
|
|
11
|
+
.select({
|
|
12
|
+
documentId: schema.documentPreviewDrafts.documentId,
|
|
13
|
+
title: schema.documentPreviewDrafts.title,
|
|
14
|
+
content: schema.documentPreviewDrafts.content,
|
|
15
|
+
baseDocumentUpdatedAt: schema.documentPreviewDrafts.baseDocumentUpdatedAt,
|
|
16
|
+
loadedContentWasEmpty: schema.documentPreviewDrafts.loadedContentWasEmpty,
|
|
17
|
+
deferredReason: schema.documentPreviewDrafts.deferredReason,
|
|
18
|
+
version: schema.documentPreviewDrafts.version,
|
|
19
|
+
updatedAt: schema.documentPreviewDrafts.updatedAt,
|
|
20
|
+
})
|
|
21
|
+
.from(schema.documentPreviewDrafts)
|
|
22
|
+
.where(
|
|
23
|
+
and(
|
|
24
|
+
eq(schema.documentPreviewDrafts.ownerEmail, ownerEmail),
|
|
25
|
+
eq(schema.documentPreviewDrafts.orgId, orgId),
|
|
26
|
+
eq(schema.documentPreviewDrafts.documentId, documentId),
|
|
27
|
+
),
|
|
28
|
+
)
|
|
29
|
+
.limit(1);
|
|
30
|
+
return draft ?? null;
|
|
31
|
+
}
|
|
@@ -151,11 +151,15 @@ export async function getDatabaseById(
|
|
|
151
151
|
return database ?? null;
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
export function serializeDatabase(
|
|
154
|
+
export function serializeDatabase(
|
|
155
|
+
database: ContentDatabaseRow,
|
|
156
|
+
description = "",
|
|
157
|
+
) {
|
|
155
158
|
return {
|
|
156
159
|
id: database.id,
|
|
157
160
|
documentId: database.documentId,
|
|
158
161
|
title: database.title,
|
|
162
|
+
description,
|
|
159
163
|
viewConfig: parseDatabaseViewConfig(database.viewConfigJson),
|
|
160
164
|
createdAt: database.createdAt,
|
|
161
165
|
updatedAt: database.updatedAt,
|
|
@@ -500,6 +504,7 @@ export async function listPropertiesForDatabase(
|
|
|
500
504
|
databaseId: definition.databaseId,
|
|
501
505
|
name: definition.name,
|
|
502
506
|
type,
|
|
507
|
+
description: definition.description,
|
|
503
508
|
visibility: normalizePropertyVisibility(definition.visibility),
|
|
504
509
|
options,
|
|
505
510
|
position: definition.position,
|
|
@@ -568,6 +573,7 @@ function serializePropertyDefinition(
|
|
|
568
573
|
databaseId: definition.databaseId,
|
|
569
574
|
name: definition.name,
|
|
570
575
|
type,
|
|
576
|
+
description: definition.description,
|
|
571
577
|
visibility: normalizePropertyVisibility(definition.visibility),
|
|
572
578
|
options: parsePropertyOptions(definition.optionsJson),
|
|
573
579
|
position: definition.position,
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import { getRequestUserEmail } from "@agent-native/core/server/request-context";
|
|
3
|
+
import { assertAccess } from "@agent-native/core/sharing";
|
|
4
|
+
import { and, eq, inArray } from "drizzle-orm";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
|
|
7
|
+
import { getDb, schema } from "../server/db/index.js";
|
|
8
|
+
import type {
|
|
9
|
+
CancelPreparedBuilderSourceUpdateRequest,
|
|
10
|
+
CancelPreparedBuilderSourceUpdateResponse,
|
|
11
|
+
} from "../shared/api.js";
|
|
12
|
+
import {
|
|
13
|
+
CANCELLED_BUILDER_EXECUTION_SUMMARY,
|
|
14
|
+
CANCELLED_BUILDER_REVIEW_NOTE_PREFIX,
|
|
15
|
+
resolveDatabaseForSourceMutation,
|
|
16
|
+
} from "./_database-source-utils.js";
|
|
17
|
+
import { getContentDatabaseResponse } from "./_database-utils.js";
|
|
18
|
+
|
|
19
|
+
const PRE_DISPATCH_STATES = new Set(["ready", "write_disabled", "blocked"]);
|
|
20
|
+
|
|
21
|
+
function parseExecutionPayload(payloadJson: string) {
|
|
22
|
+
let payload: unknown;
|
|
23
|
+
try {
|
|
24
|
+
payload = JSON.parse(payloadJson);
|
|
25
|
+
} catch {
|
|
26
|
+
throw new Error(
|
|
27
|
+
"Cannot cancel this Builder update because its execution evidence is unreadable.",
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
"Cannot cancel this Builder update because its execution evidence is ambiguous.",
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
return payload as Record<string, unknown>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isLocalBlockedDryRun(payload: Record<string, unknown>) {
|
|
39
|
+
const dryRun = payload.dryRun;
|
|
40
|
+
return (
|
|
41
|
+
!!dryRun &&
|
|
42
|
+
typeof dryRun === "object" &&
|
|
43
|
+
!Array.isArray(dryRun) &&
|
|
44
|
+
(dryRun as Record<string, unknown>).status === "blocked"
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function assertProvablyPreDispatch(execution: {
|
|
49
|
+
state: string;
|
|
50
|
+
summary: string;
|
|
51
|
+
payloadJson: string;
|
|
52
|
+
attemptToken: string | null;
|
|
53
|
+
}) {
|
|
54
|
+
if (!PRE_DISPATCH_STATES.has(execution.state)) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
`Cannot cancel a Builder execution in state ${execution.state}. Its outcome must be preserved.`,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
if (execution.attemptToken) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
"Cannot cancel this Builder update because a dispatch attempt was recorded.",
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const payload = parseExecutionPayload(execution.payloadJson);
|
|
66
|
+
if (
|
|
67
|
+
Object.prototype.hasOwnProperty.call(payload, "response") ||
|
|
68
|
+
Object.prototype.hasOwnProperty.call(payload, "dispatch")
|
|
69
|
+
) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
"Cannot cancel this Builder update because dispatch or response evidence was recorded.",
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
if (
|
|
75
|
+
execution.state === "blocked" &&
|
|
76
|
+
execution.summary !== CANCELLED_BUILDER_EXECUTION_SUMMARY &&
|
|
77
|
+
!isLocalBlockedDryRun(payload)
|
|
78
|
+
) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
"Cannot cancel this blocked Builder update because it is not provably a local pre-dispatch block.",
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function assertCanonicalClaims(
|
|
86
|
+
executions: Array<{ id: string; idempotencyKey: string }>,
|
|
87
|
+
claims: Array<{ idempotencyKey: string; executionId: string }>,
|
|
88
|
+
) {
|
|
89
|
+
const idempotencyKeys = executions.map((row) => row.idempotencyKey);
|
|
90
|
+
if (new Set(idempotencyKeys).size !== executions.length) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
"Cannot cancel this Builder update because duplicate execution gates share an idempotency key.",
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
if (claims.length !== executions.length) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
"Cannot cancel this Builder update because its canonical execution claim is missing or foreign.",
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
const claimByKey = new Map(
|
|
101
|
+
claims.map((claim) => [claim.idempotencyKey, claim.executionId]),
|
|
102
|
+
);
|
|
103
|
+
for (const execution of executions) {
|
|
104
|
+
if (claimByKey.get(execution.idempotencyKey) !== execution.id) {
|
|
105
|
+
throw new Error(
|
|
106
|
+
"Cannot cancel this Builder update because an execution claim points to a different gate.",
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export default defineAction({
|
|
113
|
+
description:
|
|
114
|
+
"Cancel one exact prepared Builder update only when every execution is provably pre-dispatch. This never calls Builder or deletes audit history.",
|
|
115
|
+
schema: z
|
|
116
|
+
.object({
|
|
117
|
+
databaseId: z.string().optional().describe("Database ID"),
|
|
118
|
+
documentId: z.string().optional().describe("Database document/page ID"),
|
|
119
|
+
sourceId: z.string().describe("Exact Builder source ID"),
|
|
120
|
+
changeSetId: z.string().describe("Exact prepared Builder change-set ID"),
|
|
121
|
+
note: z
|
|
122
|
+
.string()
|
|
123
|
+
.max(500)
|
|
124
|
+
.optional()
|
|
125
|
+
.describe("Optional cancellation note"),
|
|
126
|
+
})
|
|
127
|
+
.refine((value) => value.databaseId || value.documentId, {
|
|
128
|
+
message: "Provide databaseId or documentId.",
|
|
129
|
+
}),
|
|
130
|
+
run: async (
|
|
131
|
+
args: CancelPreparedBuilderSourceUpdateRequest,
|
|
132
|
+
): Promise<CancelPreparedBuilderSourceUpdateResponse> => {
|
|
133
|
+
const database = await resolveDatabaseForSourceMutation(args);
|
|
134
|
+
if (!database) throw new Error("Database not found.");
|
|
135
|
+
await assertAccess("document", database.documentId, "editor");
|
|
136
|
+
|
|
137
|
+
const db = getDb();
|
|
138
|
+
const actor = getRequestUserEmail() ?? "agent-runtime@agent-native.local";
|
|
139
|
+
const now = new Date().toISOString();
|
|
140
|
+
let executionIds: string[] = [];
|
|
141
|
+
let status: "cancelled" | "already_cancelled" = "cancelled";
|
|
142
|
+
|
|
143
|
+
await db.transaction(async (tx) => {
|
|
144
|
+
const [source] = await tx
|
|
145
|
+
.select()
|
|
146
|
+
.from(schema.contentDatabaseSources)
|
|
147
|
+
.where(
|
|
148
|
+
and(
|
|
149
|
+
eq(schema.contentDatabaseSources.id, args.sourceId),
|
|
150
|
+
eq(schema.contentDatabaseSources.databaseId, database.id),
|
|
151
|
+
),
|
|
152
|
+
)
|
|
153
|
+
.limit(1);
|
|
154
|
+
if (!source || source.sourceType !== "builder-cms") {
|
|
155
|
+
throw new Error("The exact Builder source was not found.");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const [changeSet] = await tx
|
|
159
|
+
.select()
|
|
160
|
+
.from(schema.contentDatabaseSourceChangeSets)
|
|
161
|
+
.where(
|
|
162
|
+
and(
|
|
163
|
+
eq(schema.contentDatabaseSourceChangeSets.id, args.changeSetId),
|
|
164
|
+
eq(schema.contentDatabaseSourceChangeSets.sourceId, source.id),
|
|
165
|
+
),
|
|
166
|
+
)
|
|
167
|
+
.limit(1);
|
|
168
|
+
if (!changeSet) throw new Error("Source change-set not found.");
|
|
169
|
+
|
|
170
|
+
const executions = await tx
|
|
171
|
+
.select()
|
|
172
|
+
.from(schema.contentDatabaseSourceExecutions)
|
|
173
|
+
.where(
|
|
174
|
+
and(
|
|
175
|
+
eq(schema.contentDatabaseSourceExecutions.sourceId, source.id),
|
|
176
|
+
eq(
|
|
177
|
+
schema.contentDatabaseSourceExecutions.changeSetId,
|
|
178
|
+
changeSet.id,
|
|
179
|
+
),
|
|
180
|
+
),
|
|
181
|
+
);
|
|
182
|
+
executionIds = executions.map((execution) => execution.id);
|
|
183
|
+
|
|
184
|
+
if (executions.length === 0) {
|
|
185
|
+
throw new Error("No prepared Builder execution was found to cancel.");
|
|
186
|
+
}
|
|
187
|
+
for (const execution of executions) assertProvablyPreDispatch(execution);
|
|
188
|
+
|
|
189
|
+
const idempotencyKeys = [
|
|
190
|
+
...new Set(executions.map((row) => row.idempotencyKey)),
|
|
191
|
+
];
|
|
192
|
+
const claims = await tx
|
|
193
|
+
.select({
|
|
194
|
+
idempotencyKey:
|
|
195
|
+
schema.contentDatabaseSourceExecutionClaims.idempotencyKey,
|
|
196
|
+
executionId: schema.contentDatabaseSourceExecutionClaims.executionId,
|
|
197
|
+
})
|
|
198
|
+
.from(schema.contentDatabaseSourceExecutionClaims)
|
|
199
|
+
.where(
|
|
200
|
+
and(
|
|
201
|
+
eq(
|
|
202
|
+
schema.contentDatabaseSourceExecutionClaims.ownerEmail,
|
|
203
|
+
database.ownerEmail,
|
|
204
|
+
),
|
|
205
|
+
eq(schema.contentDatabaseSourceExecutionClaims.sourceId, source.id),
|
|
206
|
+
inArray(
|
|
207
|
+
schema.contentDatabaseSourceExecutionClaims.idempotencyKey,
|
|
208
|
+
idempotencyKeys,
|
|
209
|
+
),
|
|
210
|
+
),
|
|
211
|
+
);
|
|
212
|
+
assertCanonicalClaims(executions, claims);
|
|
213
|
+
|
|
214
|
+
if (changeSet.state === "rejected") {
|
|
215
|
+
const cancellationReviews = await tx
|
|
216
|
+
.select()
|
|
217
|
+
.from(schema.contentDatabaseSourceChangeReviews)
|
|
218
|
+
.where(
|
|
219
|
+
and(
|
|
220
|
+
eq(schema.contentDatabaseSourceChangeReviews.sourceId, source.id),
|
|
221
|
+
eq(
|
|
222
|
+
schema.contentDatabaseSourceChangeReviews.changeSetId,
|
|
223
|
+
changeSet.id,
|
|
224
|
+
),
|
|
225
|
+
eq(
|
|
226
|
+
schema.contentDatabaseSourceChangeReviews.decision,
|
|
227
|
+
"rejected",
|
|
228
|
+
),
|
|
229
|
+
),
|
|
230
|
+
);
|
|
231
|
+
const wasCancelled =
|
|
232
|
+
executions.length > 0 &&
|
|
233
|
+
executions.every(
|
|
234
|
+
(execution) =>
|
|
235
|
+
execution.state === "blocked" &&
|
|
236
|
+
execution.summary === CANCELLED_BUILDER_EXECUTION_SUMMARY,
|
|
237
|
+
) &&
|
|
238
|
+
cancellationReviews.some((review) =>
|
|
239
|
+
review.note?.startsWith(CANCELLED_BUILDER_REVIEW_NOTE_PREFIX),
|
|
240
|
+
);
|
|
241
|
+
if (!wasCancelled) {
|
|
242
|
+
throw new Error(
|
|
243
|
+
"This Builder change-set was rejected through another review path, not cancelled as a prepared update.",
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
status = "already_cancelled";
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (changeSet.state !== "approved") {
|
|
251
|
+
throw new Error(
|
|
252
|
+
`Only an approved, prepared Builder change-set can be cancelled; current state is ${changeSet.state}.`,
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
await tx
|
|
257
|
+
.update(schema.contentDatabaseSourceExecutions)
|
|
258
|
+
.set({
|
|
259
|
+
state: "blocked",
|
|
260
|
+
summary: CANCELLED_BUILDER_EXECUTION_SUMMARY,
|
|
261
|
+
lastError: null,
|
|
262
|
+
updatedAt: now,
|
|
263
|
+
})
|
|
264
|
+
.where(
|
|
265
|
+
and(
|
|
266
|
+
eq(schema.contentDatabaseSourceExecutions.sourceId, source.id),
|
|
267
|
+
eq(
|
|
268
|
+
schema.contentDatabaseSourceExecutions.changeSetId,
|
|
269
|
+
changeSet.id,
|
|
270
|
+
),
|
|
271
|
+
),
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
const noteSuffix = args.note?.trim() ? ` Note: ${args.note.trim()}` : "";
|
|
275
|
+
await tx.insert(schema.contentDatabaseSourceChangeReviews).values({
|
|
276
|
+
id: crypto.randomUUID(),
|
|
277
|
+
ownerEmail: database.ownerEmail,
|
|
278
|
+
sourceId: source.id,
|
|
279
|
+
changeSetId: changeSet.id,
|
|
280
|
+
reviewerEmail: actor,
|
|
281
|
+
decision: "rejected",
|
|
282
|
+
stateFrom: changeSet.state,
|
|
283
|
+
stateTo: "rejected",
|
|
284
|
+
note: `${CANCELLED_BUILDER_REVIEW_NOTE_PREFIX} by ${actor} at ${now}.${noteSuffix}`,
|
|
285
|
+
createdAt: now,
|
|
286
|
+
});
|
|
287
|
+
await tx
|
|
288
|
+
.update(schema.contentDatabaseSourceChangeSets)
|
|
289
|
+
.set({ state: "rejected", updatedAt: now })
|
|
290
|
+
.where(eq(schema.contentDatabaseSourceChangeSets.id, changeSet.id));
|
|
291
|
+
await tx
|
|
292
|
+
.update(schema.contentDatabaseSources)
|
|
293
|
+
.set({ updatedAt: now })
|
|
294
|
+
.where(eq(schema.contentDatabaseSources.id, source.id));
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
return {
|
|
298
|
+
...(await getContentDatabaseResponse(database.id)),
|
|
299
|
+
cancellation: {
|
|
300
|
+
sourceId: args.sourceId,
|
|
301
|
+
changeSetId: args.changeSetId,
|
|
302
|
+
executionIds,
|
|
303
|
+
status,
|
|
304
|
+
cancelledAt: now,
|
|
305
|
+
cancelledBy: actor,
|
|
306
|
+
},
|
|
307
|
+
};
|
|
308
|
+
},
|
|
309
|
+
});
|