@agent-native/core-corpus 0.216.0 → 0.218.0
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/templates/analytics/.agents/skills/session-replay/SKILL.md +61 -1
- package/corpus/templates/analytics/AGENTS.md +1 -1
- package/corpus/templates/analytics/CHANGELOG.md +3 -0
- package/corpus/templates/analytics/actions/get-session-replay-summary.ts +9 -65
- package/corpus/templates/analytics/actions/list-event-catalog.ts +1 -1
- package/corpus/templates/analytics/actions/list-session-outputs.ts +40 -0
- package/corpus/templates/analytics/actions/list-session-recordings.ts +17 -4
- package/corpus/templates/analytics/actions/view-screen.ts +29 -3
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +38 -167
- package/corpus/templates/analytics/app/i18n/en-US.ts +27 -0
- package/corpus/templates/analytics/app/pages/sessions/EventCatalogPage.tsx +18 -1
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +3 -1
- package/corpus/templates/analytics/app/pages/sessions/SessionOutputs.tsx +197 -0
- package/corpus/templates/analytics/app/pages/sessions/SessionsTriagePage.tsx +114 -4
- package/corpus/templates/analytics/changelog/2026-10-15-sessions-can-find-people-who-made-a-slides-deck-but-didn-t-p.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-16-the-activation-funnel-counts-visitors-who-decline-analytics-.md +6 -0
- package/corpus/templates/analytics/server/db/schema.ts +67 -0
- package/corpus/templates/analytics/server/lib/canonical-first-party-dashboard-repair.ts +9 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +28 -22
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +36 -1
- package/corpus/templates/analytics/server/lib/output-outcomes.ts +712 -0
- package/corpus/templates/analytics/server/lib/replay-origins.ts +33 -0
- package/corpus/templates/analytics/server/lib/session-event-index.ts +20 -0
- package/corpus/templates/analytics/server/lib/session-performance.ts +6 -40
- package/corpus/templates/analytics/server/lib/session-recording-associations.ts +30 -1
- package/corpus/templates/analytics/server/lib/session-replay-triage.ts +99 -0
- package/corpus/templates/analytics/server/lib/session-replay.ts +45 -31
- package/corpus/templates/analytics/server/lib/sessions-triage-lab.ts +8 -2
- package/corpus/templates/analytics/server/plugins/db.ts +46 -0
- package/corpus/templates/analytics/shared/output-use.ts +205 -0
- package/corpus/templates/analytics/shared/session-events.ts +6 -0
- package/corpus/templates/assets/CHANGELOG.md +6 -0
- package/corpus/templates/assets/app/components/layout/Sidebar.tsx +51 -117
- package/corpus/templates/assets/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
- package/corpus/templates/brain/app/components/layout/Sidebar.tsx +34 -101
- package/corpus/templates/brain/app/routes/sources.tsx +1 -1
- package/corpus/templates/brain/vitest.config.ts +7 -0
- package/corpus/templates/calendar/CHANGELOG.md +12 -0
- package/corpus/templates/calendar/actions/create-event.ts +2 -4
- package/corpus/templates/calendar/actions/update-event.ts +35 -6
- package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +1 -1
- package/corpus/templates/calendar/changelog/2026-10-16-booking-links-show-localized-visibility-badges.md +6 -0
- package/corpus/templates/calendar/changelog/2026-10-21-solo-events-avoid-notification-approval.md +6 -0
- package/corpus/templates/chat/app/components/layout/Sidebar.tsx +62 -101
- package/corpus/templates/clips/CHANGELOG.md +7 -20
- package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +20 -77
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +63 -90
- package/corpus/templates/clips/app/components/sharing/share-test-support.tsx +180 -0
- package/corpus/templates/clips/app/components/sharing/share-ui.tsx +5 -584
- package/corpus/templates/clips/changelog/2026-10-16-share-dialogs-make-it-easier-to-see-who-has-access-and-manag.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-21-share-panels-describe-general-access-in-a-sentence.md +6 -0
- package/corpus/templates/community/qr/package.json +1 -1
- package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +6 -3
- package/corpus/templates/content/CHANGELOG.md +10 -0
- package/corpus/templates/content/actions/_browser-document-save-attempt.ts +23 -1
- package/corpus/templates/content/actions/_builder-cms-blob-custody.ts +467 -0
- package/corpus/templates/content/actions/_builder-cms-execution-lease.ts +17 -0
- package/corpus/templates/content/actions/_builder-cms-intent-lookup.ts +5 -0
- package/corpus/templates/content/actions/_builder-cms-read-client.ts +881 -21
- package/corpus/templates/content/actions/_builder-cms-write-adapter.ts +28 -2
- package/corpus/templates/content/actions/_builder-cms-write-client.ts +109 -11
- package/corpus/templates/content/actions/_builder-docs-client.ts +1 -0
- package/corpus/templates/content/actions/_builder-private-blob.test-fixture.ts +90 -0
- package/corpus/templates/content/actions/_builder-source-execution-guard.ts +48 -0
- package/corpus/templates/content/actions/_content-save-outcomes.ts +5 -0
- package/corpus/templates/content/actions/_database-source-utils.ts +290 -8
- package/corpus/templates/content/actions/_database-utils.ts +93 -7
- package/corpus/templates/content/actions/_database-view-setup.ts +21 -2
- package/corpus/templates/content/actions/_delete-content-space.ts +8 -1
- package/corpus/templates/content/actions/_github-source-freshness.ts +30 -4
- package/corpus/templates/content/actions/add-content-database-source-field-property.ts +23 -6
- package/corpus/templates/content/actions/cancel-prepared-builder-source-update.ts +110 -26
- package/corpus/templates/content/actions/change-content-database-source-role.ts +51 -7
- package/corpus/templates/content/actions/delete-document.ts +41 -3
- package/corpus/templates/content/actions/disconnect-content-database-source.ts +68 -2
- package/corpus/templates/content/actions/disconnect-local-folder-source.ts +33 -2
- package/corpus/templates/content/actions/execute-builder-source-execution.ts +900 -140
- package/corpus/templates/content/actions/get-document.ts +2 -0
- package/corpus/templates/content/actions/permanently-delete-document.ts +6 -0
- package/corpus/templates/content/actions/prepare-builder-source-execution.ts +155 -13
- package/corpus/templates/content/actions/prepare-builder-source-review.ts +299 -73
- package/corpus/templates/content/actions/set-live-body-switch.ts +45 -0
- package/corpus/templates/content/actions/update-document.ts +139 -20
- package/corpus/templates/content/actions/validate-builder-source-execution.ts +135 -26
- package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +119 -30
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +56 -13
- package/corpus/templates/content/app/components/editor/SuggestionText.tsx +18 -3
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +89 -22
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +23 -0
- package/corpus/templates/content/app/components/editor/database/GitHubSourceStatusLine.tsx +27 -2
- package/corpus/templates/content/app/components/editor/database/ListItemLayout.tsx +32 -8
- package/corpus/templates/content/app/components/editor/database/ListItemRow.tsx +27 -2
- package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +1 -0
- package/corpus/templates/content/app/components/editor/extensions/SuggestionHighlight.ts +383 -34
- package/corpus/templates/content/app/components/editor/suggestions/draft-session.ts +31 -0
- package/corpus/templates/content/app/components/editor/suggestions/presentation-rebase.ts +82 -4
- package/corpus/templates/content/app/global.css +31 -0
- package/corpus/templates/content/app/hooks/use-content-database.ts +40 -2
- package/corpus/templates/content/app/hooks/use-documents.ts +14 -0
- package/corpus/templates/content/app/i18n-data.ts +1 -0
- package/corpus/templates/content/changelog/2026-10-16-accepting-a-suggestion-on-a-page-that-changed-since-it-was-s.md +6 -0
- package/corpus/templates/content/changelog/2026-10-17-builder-content-avoids-retrying-a-write-when-builder-accepte.md +6 -0
- package/corpus/templates/content/changelog/2026-10-18-link-a-new-collection-to-a-github-folder-from-its-sources.md +1 -1
- package/corpus/templates/content/changelog/2026-10-20-builder-content-sync-keeps-read-and-write-status-accurate-wh.md +6 -0
- package/corpus/templates/content/changelog/2026-10-20-suggested-edits-that-add-remove-split-or-merge-paragraphs-li.md +6 -0
- package/corpus/templates/content/changelog/2026-10-21-list-rows-can-show-each-pages-first-lines.md +6 -0
- package/corpus/templates/content/changelog/2026-10-22-a-collection-linked-to-a-big-github-folder-fills-faster-and-p.md +6 -0
- package/corpus/templates/content/docs/product/capabilities/content.source.adapters.md +29 -0
- package/corpus/templates/content/parity/matrix.md +42 -42
- package/corpus/templates/content/parity/matrix.ts +3 -1
- package/corpus/templates/content/server/db/schema.ts +20 -0
- package/corpus/templates/content/server/lib/content-trash-purge.ts +15 -1
- package/corpus/templates/content/server/lib/live-body-writer.ts +138 -26
- package/corpus/templates/content/server/live-copy/binding.ts +31 -0
- package/corpus/templates/content/server/live-copy/bounded-task.d.ts +6 -0
- package/corpus/templates/content/server/live-copy/exec-db.ts +1 -0
- package/corpus/templates/content/server/live-copy/holds.ts +59 -0
- package/corpus/templates/content/server/live-copy/persist.ts +656 -0
- package/corpus/templates/content/server/live-copy/switch.ts +136 -0
- package/corpus/templates/content/server/live-copy/task-names.ts +4 -0
- package/corpus/templates/content/server/live-copy/tasks.ts +183 -0
- package/corpus/templates/content/server/live-copy/write-cause.ts +85 -0
- package/corpus/templates/content/server/live-copy/write.ts +85 -0
- package/corpus/templates/content/server/plugins/builder-private-payload-cleanup.ts +22 -0
- package/corpus/templates/content/server/plugins/collab.ts +3 -0
- package/corpus/templates/content/server/plugins/db.ts +28 -0
- package/corpus/templates/content/shared/api.ts +30 -1
- package/corpus/templates/content/shared/database-item-layout.ts +54 -22
- package/corpus/templates/content/shared/document-intent-merge.ts +16 -9
- package/corpus/templates/content/shared/og-description.ts +258 -36
- package/corpus/templates/content/shared/suggestion-diff.ts +23 -6
- package/corpus/templates/content/shared/suggestion-formatting.ts +44 -14
- package/corpus/templates/content/shared/suggestion-text.ts +20 -5
- package/corpus/templates/design/.agents/skills/design-templates/SKILL.md +5 -1
- package/corpus/templates/design/.generated/bridge/shader-runtime.generated.ts +25 -4
- package/corpus/templates/design/CHANGELOG.md +5 -0
- package/corpus/templates/design/actions/get-getting-started.ts +53 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +10 -0
- package/corpus/templates/design/app/components/design/bridge/native-shader-runtime.bridge.ts +132 -79
- package/corpus/templates/design/app/components/design/bridge/native-source-provider.ts +268 -130
- package/corpus/templates/design/app/components/design/bridge/native-worker-scene-host.ts +122 -42
- package/corpus/templates/design/app/components/design/bridge/shader-runtime.bridge.ts +41 -4
- package/corpus/templates/design/app/components/design/design-canvas/text-content-change-result.ts +46 -12
- package/corpus/templates/design/app/components/home/DesignHomeLibrary.tsx +15 -8
- package/corpus/templates/design/app/components/layout/GettingStarted.tsx +50 -0
- package/corpus/templates/design/app/components/layout/Sidebar.tsx +3 -0
- package/corpus/templates/design/app/components/templates/DesignTemplateGrid.tsx +163 -0
- package/corpus/templates/design/app/components/templates/DesignTemplateLibrary.tsx +65 -78
- package/corpus/templates/design/app/components/templates/TemplatePreview.tsx +6 -2
- package/corpus/templates/design/app/global.css +6 -0
- package/corpus/templates/design/app/i18n/en-US.ts +17 -0
- package/corpus/templates/design/app/lib/canvas-load-outcome.ts +147 -0
- package/corpus/templates/design/app/lib/canvas-outcome-tracking.ts +531 -0
- package/corpus/templates/design/app/lib/canvas-style-kind.ts +50 -0
- package/corpus/templates/design/app/pages/DesignSystems.tsx +1 -1
- package/corpus/templates/design/app/pages/Index.tsx +28 -67
- package/corpus/templates/design/app/pages/design-editor/commands/commit-visual-styles.ts +86 -29
- package/corpus/templates/design/app/pages/design-editor/commands/save-file-content.ts +38 -3
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-core.ts +9 -0
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-files-and-saving.ts +2 -0
- package/corpus/templates/design/changelog/2026-10-16-design-pages-use-localized-visibility-badges-and-a-shared-ow.md +6 -0
- package/corpus/templates/design/changelog/2026-10-21-a-getting-started-checklist-at-the-bottom-of-the-sidebar-tra.md +6 -0
- package/corpus/templates/design/changelog/2026-10-21-templates-now-open-a-read-only-preview-from-the-card-and-the.md +6 -0
- package/corpus/templates/design/changelog/2026-10-22-collapsed-shader-layers-no-longer-interrupt-other-shaders-on.md +6 -0
- package/corpus/templates/design/changelog/2026-10-22-shader-fills-keep-rendering-when-other-screen-content-cannot.md +6 -0
- package/corpus/templates/design/scripts/design-export-validation/export-baseline.json +24 -0
- package/corpus/templates/design/server/lib/getting-started.ts +267 -0
- package/corpus/templates/design/server/plugins/getting-started.ts +5 -0
- package/corpus/templates/design/shared/design-template-presets/analytics-dashboard.ts +127 -0
- package/corpus/templates/design/shared/design-template-presets/design-studio.ts +98 -0
- package/corpus/templates/design/shared/design-template-presets/messaging-app.ts +101 -0
- package/corpus/templates/design/shared/design-template-presets/project-tasks.ts +120 -0
- package/corpus/templates/design/shared/design-template-presets.ts +8 -0
- package/corpus/templates/dispatch/CHANGELOG.md +4 -0
- package/corpus/templates/dispatch/app/i18n-data.ts +75 -0
- package/corpus/templates/dispatch/changelog/2026-10-21-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
- package/corpus/templates/factory/app/components/layout/Sidebar.tsx +34 -100
- package/corpus/templates/forms/app/pages/FormsListPage.tsx +1 -1
- package/corpus/templates/forms/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/plan/CHANGELOG.md +6 -0
- package/corpus/templates/plan/app/components/layout/Sidebar.tsx +34 -109
- package/corpus/templates/plan/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
- package/corpus/templates/slides/CHANGELOG.md +7 -0
- package/corpus/templates/slides/actions/delete-deck.ts +7 -0
- package/corpus/templates/slides/app/components/deck/DeckCard.tsx +1 -1
- package/corpus/templates/slides/app/components/design-system/DesignSystemCard.tsx +1 -1
- package/corpus/templates/slides/app/pages/Index.tsx +7 -2
- package/corpus/templates/slides/changelog/2026-10-16-deck-lists-use-the-shared-ownership-filter-and-visibility-ba.md +6 -0
- package/corpus/templates/slides/changelog/2026-10-21-connected-deck-deletion-requires-approval.md +6 -0
- package/corpus/templates/slides/scripts/edit-fidelity/client-action.ts +61 -0
- package/corpus/templates/slides/scripts/edit-fidelity/run.ts +10 -0
- package/package.json +1 -1
- package/corpus/templates/chat/app/lib/sidebar-thread-state.ts +0 -29
- package/corpus/templates/slides/app/components/deck/DeckFilterMenu.tsx +0 -83
|
@@ -15,12 +15,14 @@ import {
|
|
|
15
15
|
import {
|
|
16
16
|
CONTENT_DATABASE_ITEM_LAYOUT_PRESET_IDS,
|
|
17
17
|
CONTENT_DATABASE_ITEM_LEADING_KINDS,
|
|
18
|
+
CONTENT_DATABASE_ITEM_LINE2_SOURCES,
|
|
18
19
|
CONTENT_DATABASE_ITEM_TITLE_WEIGHTS,
|
|
19
20
|
MAX_ITEM_LAYOUT_META_PROPERTIES,
|
|
20
21
|
contentDatabaseItemLayoutPresets,
|
|
21
22
|
contentDatabaseItemLayoutReferenceErrors,
|
|
22
23
|
dropDeletedContentDatabaseItemLayoutReferences,
|
|
23
24
|
itemLayoutLeadingCandidates,
|
|
25
|
+
itemLayoutLine2Candidates,
|
|
24
26
|
normalizeContentDatabaseItemLayout,
|
|
25
27
|
pruneContentDatabaseItemLayout,
|
|
26
28
|
savableContentDatabaseItemLayout,
|
|
@@ -118,6 +120,7 @@ export const contentDatabaseItemLayoutSchema = z
|
|
|
118
120
|
titleWeight: z.enum(CONTENT_DATABASE_ITEM_TITLE_WEIGHTS),
|
|
119
121
|
line2: z
|
|
120
122
|
.object({
|
|
123
|
+
source: z.enum(CONTENT_DATABASE_ITEM_LINE2_SOURCES).optional(),
|
|
121
124
|
propertyId: itemLayoutPropertyIdSchema.nullable(),
|
|
122
125
|
lines: itemLayoutLinesSchema,
|
|
123
126
|
})
|
|
@@ -139,6 +142,7 @@ const itemLayoutPatchSchema = z
|
|
|
139
142
|
titleWeight: z.enum(CONTENT_DATABASE_ITEM_TITLE_WEIGHTS).optional(),
|
|
140
143
|
line2: z
|
|
141
144
|
.object({
|
|
145
|
+
source: z.enum(CONTENT_DATABASE_ITEM_LINE2_SOURCES).optional(),
|
|
142
146
|
propertyId: itemLayoutPropertyIdSchema.nullable().optional(),
|
|
143
147
|
lines: itemLayoutLinesSchema.optional(),
|
|
144
148
|
})
|
|
@@ -149,7 +153,7 @@ const itemLayoutPatchSchema = z
|
|
|
149
153
|
})
|
|
150
154
|
.strict()
|
|
151
155
|
.describe(
|
|
152
|
-
"List views only. Slots to change in each row; omitted slots keep their saved value. leading.kind checkbox or status uses the first property of that type unless propertyId names one. line2 shows a text, URL, or Blocks property; line3 and trailing list property IDs in order. Trailing values move under the title on narrow screens. Pass null to restore the default rows.",
|
|
156
|
+
"List views only. Slots to change in each row; omitted slots keep their saved value. leading.kind checkbox or status uses the first property of that type unless propertyId names one. line2 shows a text, URL, or Blocks property, or with source page the first lines of each row's page; line3 and trailing list property IDs in order. Trailing values move under the title on narrow screens. Pass null to restore the default rows.",
|
|
153
157
|
);
|
|
154
158
|
|
|
155
159
|
const listPatchFields = {
|
|
@@ -467,6 +471,17 @@ function nextListItemLayout(
|
|
|
467
471
|
const ids = itemLayout[slot];
|
|
468
472
|
if (ids) uniqueReferences(ids, slot);
|
|
469
473
|
}
|
|
474
|
+
if (itemLayout.line2?.source === "page" && itemLayout.line2.propertyId) {
|
|
475
|
+
setupError(
|
|
476
|
+
"INVALID_VIEW_REFERENCE",
|
|
477
|
+
"line2.propertyId must be null or left out when line2.source is page.",
|
|
478
|
+
400,
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
// Naming a property switches line 2 back from page text.
|
|
482
|
+
const line2Source =
|
|
483
|
+
itemLayout.line2?.source ??
|
|
484
|
+
(itemLayout.line2?.propertyId !== undefined ? "property" : undefined);
|
|
470
485
|
const leading = itemLayout.leading && {
|
|
471
486
|
kind: itemLayout.leading.kind,
|
|
472
487
|
propertyId:
|
|
@@ -480,10 +495,14 @@ function nextListItemLayout(
|
|
|
480
495
|
leading: leading ?? from.leading,
|
|
481
496
|
titleWeight: itemLayout.titleWeight ?? from.titleWeight,
|
|
482
497
|
line2: {
|
|
498
|
+
source: line2Source ?? from.line2.source,
|
|
483
499
|
propertyId:
|
|
484
500
|
itemLayout.line2?.propertyId !== undefined
|
|
485
501
|
? itemLayout.line2.propertyId
|
|
486
|
-
: from.line2.
|
|
502
|
+
: line2Source === "property" && from.line2.source === "page"
|
|
503
|
+
? (itemLayoutLine2Candidates(properties)[0]?.definition.id ??
|
|
504
|
+
null)
|
|
505
|
+
: from.line2.propertyId,
|
|
487
506
|
lines: itemLayout.line2?.lines ?? from.line2.lines,
|
|
488
507
|
},
|
|
489
508
|
line3: itemLayout.line3 ?? from.line3,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { and, eq } from "drizzle-orm";
|
|
2
2
|
|
|
3
3
|
import { getDb, schema } from "../server/db/index.js";
|
|
4
|
+
import { cleanupBuilderPrivatePayload } from "./_builder-cms-blob-custody.js";
|
|
4
5
|
import { resolveContentSpaceAccess } from "./_content-space-access.js";
|
|
5
6
|
import {
|
|
6
7
|
deleteDocumentRootsRecursive,
|
|
@@ -11,7 +12,8 @@ type Db = ReturnType<typeof getDb>;
|
|
|
11
12
|
const MAX_DELETE_SCOPE_ATTEMPTS = 3;
|
|
12
13
|
|
|
13
14
|
async function deleteUserContentSpaceOnce(db: Db, spaceId: string) {
|
|
14
|
-
|
|
15
|
+
const deletedBlobReferences = new Set<string>();
|
|
16
|
+
const result = await db.transaction(async (tx) => {
|
|
15
17
|
const scopedDb = tx as unknown as Db;
|
|
16
18
|
const access = await resolveContentSpaceAccess(spaceId, "editor", {
|
|
17
19
|
db: scopedDb,
|
|
@@ -53,6 +55,7 @@ async function deleteUserContentSpaceOnce(db: Db, spaceId: string) {
|
|
|
53
55
|
scopedDb,
|
|
54
56
|
[mapping.documentId, filesDatabase.documentId],
|
|
55
57
|
access.space.ownerEmail,
|
|
58
|
+
deletedBlobReferences,
|
|
56
59
|
);
|
|
57
60
|
|
|
58
61
|
const remainingDocuments = await scopedDb
|
|
@@ -79,6 +82,10 @@ async function deleteUserContentSpaceOnce(db: Db, spaceId: string) {
|
|
|
79
82
|
deletedDocuments: deletedDocuments.length,
|
|
80
83
|
};
|
|
81
84
|
});
|
|
85
|
+
for (const reference of deletedBlobReferences) {
|
|
86
|
+
await cleanupBuilderPrivatePayload(reference, "deleted workspace source");
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
82
89
|
}
|
|
83
90
|
|
|
84
91
|
export async function deleteUserContentSpace(db: Db, spaceId: string) {
|
|
@@ -26,6 +26,12 @@ export const GITHUB_SOURCE_STALE_MS = 60_000;
|
|
|
26
26
|
const READ_SYNC_BUDGET_MS = 3_000;
|
|
27
27
|
/** One link's share of a background sync, which has 90 seconds in all. */
|
|
28
28
|
const SWEEP_SYNC_BUDGET_MS = 20_000;
|
|
29
|
+
/**
|
|
30
|
+
* The share of a link partway through a push, like a big folder's first fill.
|
|
31
|
+
* It stops before the next background sync starts a minute later, so that one
|
|
32
|
+
* finds the link free and carries the push on.
|
|
33
|
+
*/
|
|
34
|
+
const SWEEP_PUSH_BUDGET_MS = 50_000;
|
|
29
35
|
/** Links the background sync checks at once. */
|
|
30
36
|
const SWEEP_CONCURRENCY = 4;
|
|
31
37
|
/** The background sync skips links a read or another sync checked this recently. */
|
|
@@ -274,10 +280,25 @@ export async function runGitHubSourceSweep(context: RecurringSweepContext) {
|
|
|
274
280
|
asc(sql`coalesce(${schema.contentDatabaseSources.lastRefreshedAt}, '')`),
|
|
275
281
|
)
|
|
276
282
|
.limit(SWEEP_MAX_LINKS);
|
|
283
|
+
// A link partway through a push goes after the rest, so a big folder's
|
|
284
|
+
// first fill doesn't hold up a push to a small one. Those links take turns
|
|
285
|
+
// by when a run last moved their push, oldest first. The query's order
|
|
286
|
+
// can't do that: a run partway through isn't a check, so it leaves
|
|
287
|
+
// lastRefreshedAt as it was, and the same link would go first every time.
|
|
288
|
+
const carrying = new Set(
|
|
289
|
+
due
|
|
290
|
+
.map((link) => ({ link, state: linkState(link.metadataJson) }))
|
|
291
|
+
.filter(({ state }) => state?.pendingCommitSha)
|
|
292
|
+
.sort((a, b) =>
|
|
293
|
+
(a.state?.checkedAt ?? "").localeCompare(b.state?.checkedAt ?? ""),
|
|
294
|
+
)
|
|
295
|
+
.map(({ link }) => link),
|
|
296
|
+
);
|
|
297
|
+
const order = [...due.filter((link) => !carrying.has(link)), ...carrying];
|
|
277
298
|
let next = 0;
|
|
278
299
|
const worker = async () => {
|
|
279
|
-
while (next <
|
|
280
|
-
const link =
|
|
300
|
+
while (next < order.length) {
|
|
301
|
+
const link = order[next++]!;
|
|
281
302
|
const remaining = context.deadlineAt - Date.now();
|
|
282
303
|
if (context.signal?.aborted || remaining < 2_000) return;
|
|
283
304
|
const owner = linkedBy(link);
|
|
@@ -288,7 +309,12 @@ export async function runGitHubSourceSweep(context: RecurringSweepContext) {
|
|
|
288
309
|
() =>
|
|
289
310
|
syncGitHubSource({
|
|
290
311
|
sourceId: link.sourceId,
|
|
291
|
-
budgetMs: Math.min(
|
|
312
|
+
budgetMs: Math.min(
|
|
313
|
+
carrying.has(link)
|
|
314
|
+
? SWEEP_PUSH_BUDGET_MS
|
|
315
|
+
: SWEEP_SYNC_BUDGET_MS,
|
|
316
|
+
remaining - 1_000,
|
|
317
|
+
),
|
|
292
318
|
signal: context.signal,
|
|
293
319
|
}),
|
|
294
320
|
);
|
|
@@ -303,6 +329,6 @@ export async function runGitHubSourceSweep(context: RecurringSweepContext) {
|
|
|
303
329
|
}
|
|
304
330
|
};
|
|
305
331
|
await Promise.all(
|
|
306
|
-
Array.from({ length: Math.min(SWEEP_CONCURRENCY,
|
|
332
|
+
Array.from({ length: Math.min(SWEEP_CONCURRENCY, order.length) }, worker),
|
|
307
333
|
);
|
|
308
334
|
}
|
|
@@ -393,15 +393,23 @@ export default defineAction({
|
|
|
393
393
|
throw new Error("The title source field is already mapped to Name.");
|
|
394
394
|
}
|
|
395
395
|
|
|
396
|
-
let
|
|
396
|
+
let sourceMetadata: {
|
|
397
|
+
builderSpacePublicKey?: unknown;
|
|
398
|
+
connectionId?: unknown;
|
|
399
|
+
federation?: { role?: unknown };
|
|
400
|
+
} = {};
|
|
397
401
|
try {
|
|
398
|
-
const parsed = JSON.parse(source.metadataJson ?? "{}") as
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
+
const parsed = JSON.parse(source.metadataJson ?? "{}") as unknown;
|
|
403
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
404
|
+
sourceMetadata = parsed as typeof sourceMetadata;
|
|
405
|
+
}
|
|
402
406
|
} catch {
|
|
403
|
-
|
|
407
|
+
sourceMetadata = {};
|
|
404
408
|
}
|
|
409
|
+
const federationRole =
|
|
410
|
+
typeof sourceMetadata.federation?.role === "string"
|
|
411
|
+
? sourceMetadata.federation.role
|
|
412
|
+
: null;
|
|
405
413
|
const isSecondary = federationRole === "secondary";
|
|
406
414
|
|
|
407
415
|
const materializeProperty = (
|
|
@@ -676,9 +684,18 @@ export default defineAction({
|
|
|
676
684
|
|
|
677
685
|
const builderRead = await readBuilderCmsContentEntries({
|
|
678
686
|
model: source.sourceTable,
|
|
687
|
+
expectedSourceSpace:
|
|
688
|
+
typeof sourceMetadata.builderSpacePublicKey === "string"
|
|
689
|
+
? sourceMetadata.builderSpacePublicKey
|
|
690
|
+
: undefined,
|
|
691
|
+
expectedSourceConnectionId:
|
|
692
|
+
typeof sourceMetadata.connectionId === "string"
|
|
693
|
+
? sourceMetadata.connectionId
|
|
694
|
+
: undefined,
|
|
679
695
|
fieldPaths: [field.sourceFieldKey],
|
|
680
696
|
limit: Math.max(BUILDER_FIELD_REFRESH_MINIMUM_LIMIT, error.rowCount),
|
|
681
697
|
offset: 0,
|
|
698
|
+
requireBoundOAuth: true,
|
|
682
699
|
});
|
|
683
700
|
if (builderRead.state !== "live") {
|
|
684
701
|
throw new Error(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { defineAction } from "@agent-native/core/action";
|
|
1
|
+
import { defineAction, fail } from "@agent-native/core/action";
|
|
2
2
|
import { getRequestUserEmail } from "@agent-native/core/server/request-context";
|
|
3
3
|
import { assertAccess } from "@agent-native/core/sharing";
|
|
4
|
-
import { and, eq, inArray } from "drizzle-orm";
|
|
4
|
+
import { and, eq, inArray, isNull } from "drizzle-orm";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
|
|
7
7
|
import { getDb, schema } from "../server/db/index.js";
|
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
CancelPreparedBuilderSourceUpdateRequest,
|
|
10
10
|
CancelPreparedBuilderSourceUpdateResponse,
|
|
11
11
|
} from "../shared/api.js";
|
|
12
|
+
import { readBuilderExecutionPayload } from "./_builder-cms-blob-custody.js";
|
|
12
13
|
import {
|
|
13
14
|
CANCELLED_BUILDER_EXECUTION_SUMMARY,
|
|
14
15
|
CANCELLED_BUILDER_REVIEW_NOTE_PREFIX,
|
|
@@ -45,28 +46,63 @@ function isLocalBlockedDryRun(payload: Record<string, unknown>) {
|
|
|
45
46
|
);
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
function isLivePreflightBlocked(payload: Record<string, unknown>) {
|
|
50
|
+
const preflight = payload.livePreflight;
|
|
51
|
+
if (!preflight || typeof preflight !== "object" || Array.isArray(preflight)) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
const record = preflight as Record<string, unknown>;
|
|
55
|
+
return (
|
|
56
|
+
typeof record.checkedAt === "string" &&
|
|
57
|
+
typeof record.exists === "boolean" &&
|
|
58
|
+
(record.lastUpdated === null ||
|
|
59
|
+
typeof record.lastUpdated === "number" ||
|
|
60
|
+
typeof record.lastUpdated === "string") &&
|
|
61
|
+
(record.blocksHash === null || typeof record.blocksHash === "string")
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function isDefinitivelyRejectedWrite(payload: Record<string, unknown>) {
|
|
66
|
+
const response = payload.response;
|
|
67
|
+
if (!response || typeof response !== "object" || Array.isArray(response)) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
const record = response as Record<string, unknown>;
|
|
71
|
+
return (
|
|
72
|
+
record.ok === false &&
|
|
73
|
+
typeof record.status === "number" &&
|
|
74
|
+
Number.isInteger(record.status) &&
|
|
75
|
+
record.status >= 400 &&
|
|
76
|
+
record.status < 500
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
48
80
|
function assertProvablyPreDispatch(execution: {
|
|
49
81
|
state: string;
|
|
50
82
|
summary: string;
|
|
51
83
|
payloadJson: string;
|
|
52
84
|
attemptToken: string | null;
|
|
53
85
|
}) {
|
|
54
|
-
|
|
86
|
+
const payload = parseExecutionPayload(execution.payloadJson);
|
|
87
|
+
const definitiveRejectedWrite =
|
|
88
|
+
(execution.state === "failed" || execution.state === "blocked") &&
|
|
89
|
+
isDefinitivelyRejectedWrite(payload);
|
|
90
|
+
if (!PRE_DISPATCH_STATES.has(execution.state) && !definitiveRejectedWrite) {
|
|
55
91
|
throw new Error(
|
|
56
92
|
`Cannot cancel a Builder execution in state ${execution.state}. Its outcome must be preserved.`,
|
|
57
93
|
);
|
|
58
94
|
}
|
|
59
|
-
if (execution.attemptToken) {
|
|
95
|
+
if (execution.attemptToken && !definitiveRejectedWrite) {
|
|
60
96
|
throw new Error(
|
|
61
97
|
"Cannot cancel this Builder update because a dispatch attempt was recorded.",
|
|
62
98
|
);
|
|
63
99
|
}
|
|
64
100
|
|
|
65
|
-
const payload = parseExecutionPayload(execution.payloadJson);
|
|
66
101
|
if (
|
|
67
102
|
Object.prototype.hasOwnProperty.call(payload, "response") ||
|
|
68
103
|
Object.prototype.hasOwnProperty.call(payload, "dispatch")
|
|
69
104
|
) {
|
|
105
|
+
if (definitiveRejectedWrite) return;
|
|
70
106
|
throw new Error(
|
|
71
107
|
"Cannot cancel this Builder update because dispatch or response evidence was recorded.",
|
|
72
108
|
);
|
|
@@ -74,7 +110,8 @@ function assertProvablyPreDispatch(execution: {
|
|
|
74
110
|
if (
|
|
75
111
|
execution.state === "blocked" &&
|
|
76
112
|
execution.summary !== CANCELLED_BUILDER_EXECUTION_SUMMARY &&
|
|
77
|
-
!isLocalBlockedDryRun(payload)
|
|
113
|
+
!isLocalBlockedDryRun(payload) &&
|
|
114
|
+
!isLivePreflightBlocked(payload)
|
|
78
115
|
) {
|
|
79
116
|
throw new Error(
|
|
80
117
|
"Cannot cancel this blocked Builder update because it is not provably a local pre-dispatch block.",
|
|
@@ -111,7 +148,7 @@ function assertCanonicalClaims(
|
|
|
111
148
|
|
|
112
149
|
export default defineAction({
|
|
113
150
|
description:
|
|
114
|
-
"Cancel one exact prepared Builder update only when every execution
|
|
151
|
+
"Cancel one exact prepared Builder update only when every execution never dispatched or Builder definitively rejected it. This never calls Builder or deletes audit history.",
|
|
115
152
|
schema: z
|
|
116
153
|
.object({
|
|
117
154
|
databaseId: z.string().optional().describe("Collection ID"),
|
|
@@ -167,7 +204,7 @@ export default defineAction({
|
|
|
167
204
|
.limit(1);
|
|
168
205
|
if (!changeSet) throw new Error("Source change-set not found.");
|
|
169
206
|
|
|
170
|
-
const
|
|
207
|
+
const storedExecutions = await tx
|
|
171
208
|
.select()
|
|
172
209
|
.from(schema.contentDatabaseSourceExecutions)
|
|
173
210
|
.where(
|
|
@@ -179,6 +216,24 @@ export default defineAction({
|
|
|
179
216
|
),
|
|
180
217
|
),
|
|
181
218
|
);
|
|
219
|
+
const executions = await Promise.all(
|
|
220
|
+
storedExecutions.map(async (execution) => ({
|
|
221
|
+
...execution,
|
|
222
|
+
storedPayloadJson: execution.payloadJson,
|
|
223
|
+
payloadJson: JSON.stringify(
|
|
224
|
+
await readBuilderExecutionPayload({
|
|
225
|
+
payloadJson: execution.payloadJson,
|
|
226
|
+
binding: {
|
|
227
|
+
ownerEmail: execution.ownerEmail,
|
|
228
|
+
sourceId: execution.sourceId,
|
|
229
|
+
changeSetId: execution.changeSetId,
|
|
230
|
+
executionId: execution.id,
|
|
231
|
+
idempotencyKey: execution.idempotencyKey,
|
|
232
|
+
},
|
|
233
|
+
}),
|
|
234
|
+
),
|
|
235
|
+
})),
|
|
236
|
+
);
|
|
182
237
|
executionIds = executions.map((execution) => execution.id);
|
|
183
238
|
|
|
184
239
|
if (executions.length === 0) {
|
|
@@ -253,23 +308,40 @@ export default defineAction({
|
|
|
253
308
|
);
|
|
254
309
|
}
|
|
255
310
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
schema.contentDatabaseSourceExecutions.
|
|
269
|
-
|
|
311
|
+
for (const execution of executions) {
|
|
312
|
+
const [updated] = await tx
|
|
313
|
+
.update(schema.contentDatabaseSourceExecutions)
|
|
314
|
+
.set({
|
|
315
|
+
state: "blocked",
|
|
316
|
+
summary: CANCELLED_BUILDER_EXECUTION_SUMMARY,
|
|
317
|
+
attemptToken: null,
|
|
318
|
+
lastError: null,
|
|
319
|
+
updatedAt: now,
|
|
320
|
+
})
|
|
321
|
+
.where(
|
|
322
|
+
and(
|
|
323
|
+
eq(schema.contentDatabaseSourceExecutions.id, execution.id),
|
|
324
|
+
eq(schema.contentDatabaseSourceExecutions.state, execution.state),
|
|
325
|
+
eq(
|
|
326
|
+
schema.contentDatabaseSourceExecutions.payloadJson,
|
|
327
|
+
execution.storedPayloadJson,
|
|
328
|
+
),
|
|
329
|
+
execution.attemptToken
|
|
330
|
+
? eq(
|
|
331
|
+
schema.contentDatabaseSourceExecutions.attemptToken,
|
|
332
|
+
execution.attemptToken,
|
|
333
|
+
)
|
|
334
|
+
: isNull(schema.contentDatabaseSourceExecutions.attemptToken),
|
|
270
335
|
),
|
|
271
|
-
)
|
|
272
|
-
|
|
336
|
+
)
|
|
337
|
+
.returning({ id: schema.contentDatabaseSourceExecutions.id });
|
|
338
|
+
if (!updated) {
|
|
339
|
+
fail(
|
|
340
|
+
"Cannot cancel this Builder update because an execution changed before cancellation committed.",
|
|
341
|
+
{ errorCode: "builder_update_changed", statusCode: 409 },
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
273
345
|
|
|
274
346
|
const noteSuffix = args.note?.trim() ? ` Note: ${args.note.trim()}` : "";
|
|
275
347
|
await tx.insert(schema.contentDatabaseSourceChangeReviews).values({
|
|
@@ -284,10 +356,22 @@ export default defineAction({
|
|
|
284
356
|
note: `${CANCELLED_BUILDER_REVIEW_NOTE_PREFIX} by ${actor} at ${now}.${noteSuffix}`,
|
|
285
357
|
createdAt: now,
|
|
286
358
|
});
|
|
287
|
-
await tx
|
|
359
|
+
const [rejectedChangeSet] = await tx
|
|
288
360
|
.update(schema.contentDatabaseSourceChangeSets)
|
|
289
361
|
.set({ state: "rejected", updatedAt: now })
|
|
290
|
-
.where(
|
|
362
|
+
.where(
|
|
363
|
+
and(
|
|
364
|
+
eq(schema.contentDatabaseSourceChangeSets.id, changeSet.id),
|
|
365
|
+
eq(schema.contentDatabaseSourceChangeSets.state, changeSet.state),
|
|
366
|
+
),
|
|
367
|
+
)
|
|
368
|
+
.returning({ id: schema.contentDatabaseSourceChangeSets.id });
|
|
369
|
+
if (!rejectedChangeSet) {
|
|
370
|
+
fail(
|
|
371
|
+
"Cannot cancel this Builder update because its review state changed before cancellation committed.",
|
|
372
|
+
{ errorCode: "builder_update_changed", statusCode: 409 },
|
|
373
|
+
);
|
|
374
|
+
}
|
|
291
375
|
await tx
|
|
292
376
|
.update(schema.contentDatabaseSources)
|
|
293
377
|
.set({ updatedAt: now })
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineAction } from "@agent-native/core/action";
|
|
1
|
+
import { defineAction, fail } from "@agent-native/core/action";
|
|
2
2
|
import { assertAccess } from "@agent-native/core/sharing";
|
|
3
3
|
import { and, asc, eq, inArray, isNull, ne, sql } from "drizzle-orm";
|
|
4
4
|
import { z } from "zod";
|
|
@@ -135,6 +135,33 @@ function sourceRole(source: SourceRecord): "primary" | "secondary" | null {
|
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
+
function builderSourceReadBinding(source: SourceRecord) {
|
|
139
|
+
let parsed: unknown;
|
|
140
|
+
try {
|
|
141
|
+
parsed = JSON.parse(source.metadataJson ?? "{}");
|
|
142
|
+
} catch {
|
|
143
|
+
fail("Builder source connection metadata is unreadable.", {
|
|
144
|
+
errorCode: "builder_source_binding_invalid",
|
|
145
|
+
statusCode: 412,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
149
|
+
fail("Builder source connection metadata is unreadable.", {
|
|
150
|
+
errorCode: "builder_source_binding_invalid",
|
|
151
|
+
statusCode: 412,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
const metadata = parsed as Record<string, unknown>;
|
|
155
|
+
return {
|
|
156
|
+
expectedSourceSpace:
|
|
157
|
+
typeof metadata.builderSpacePublicKey === "string"
|
|
158
|
+
? metadata.builderSpacePublicKey
|
|
159
|
+
: null,
|
|
160
|
+
expectedSourceConnectionId:
|
|
161
|
+
typeof metadata.connectionId === "string" ? metadata.connectionId : null,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
138
165
|
async function clearSourceFederation(sourceId: string, now: string) {
|
|
139
166
|
await mutateContentDatabaseSourceMetadata({
|
|
140
167
|
sourceId,
|
|
@@ -235,6 +262,8 @@ export async function readBuilderCmsEntriesForRoleChange(
|
|
|
235
262
|
args: {
|
|
236
263
|
model: string;
|
|
237
264
|
existingFieldPaths?: readonly string[];
|
|
265
|
+
expectedSourceSpace?: string | null;
|
|
266
|
+
expectedSourceConnectionId?: string | null;
|
|
238
267
|
},
|
|
239
268
|
dependencies: {
|
|
240
269
|
readModelFields?: typeof readBuilderCmsModelFields;
|
|
@@ -247,7 +276,11 @@ export async function readBuilderCmsEntriesForRoleChange(
|
|
|
247
276
|
let modelFields: BuilderCmsModelFieldSummary[] = [];
|
|
248
277
|
let modelFieldsError: unknown = null;
|
|
249
278
|
try {
|
|
250
|
-
modelFields = await readModelFields({
|
|
279
|
+
modelFields = await readModelFields({
|
|
280
|
+
model: args.model,
|
|
281
|
+
expectedSourceSpace: args.expectedSourceSpace,
|
|
282
|
+
expectedSourceConnectionId: args.expectedSourceConnectionId,
|
|
283
|
+
});
|
|
251
284
|
} catch (error) {
|
|
252
285
|
modelFieldsError = error;
|
|
253
286
|
}
|
|
@@ -257,14 +290,23 @@ export async function readBuilderCmsEntriesForRoleChange(
|
|
|
257
290
|
...(args.existingFieldPaths ?? []),
|
|
258
291
|
...modelFields.map((field) => `data.${field.name}`),
|
|
259
292
|
],
|
|
293
|
+
expectedSourceSpace: args.expectedSourceSpace,
|
|
294
|
+
expectedSourceConnectionId: args.expectedSourceConnectionId,
|
|
295
|
+
requireBoundOAuth: true,
|
|
260
296
|
});
|
|
261
297
|
if (modelFieldsError) throw modelFieldsError;
|
|
298
|
+
if (read.state !== "live") {
|
|
299
|
+
throw new Error(
|
|
300
|
+
read.message ??
|
|
301
|
+
"Builder source could not be read for this role change; no source changes were made.",
|
|
302
|
+
);
|
|
303
|
+
}
|
|
262
304
|
return { read, modelFields };
|
|
263
305
|
}
|
|
264
306
|
|
|
265
307
|
async function readSourceEntries(args: {
|
|
266
308
|
sourceType: ContentDatabaseSourceType;
|
|
267
|
-
|
|
309
|
+
source: SourceRecord;
|
|
268
310
|
limit: number;
|
|
269
311
|
offset: number;
|
|
270
312
|
}): Promise<{
|
|
@@ -276,7 +318,8 @@ async function readSourceEntries(args: {
|
|
|
276
318
|
}> {
|
|
277
319
|
if (args.sourceType === "builder-cms") {
|
|
278
320
|
const { read, modelFields } = await readBuilderCmsEntriesForRoleChange({
|
|
279
|
-
model: args.sourceTable,
|
|
321
|
+
model: args.source.sourceTable,
|
|
322
|
+
...builderSourceReadBinding(args.source),
|
|
280
323
|
});
|
|
281
324
|
return {
|
|
282
325
|
entries: read.state === "live" ? read.entries : [],
|
|
@@ -288,7 +331,7 @@ async function readSourceEntries(args: {
|
|
|
288
331
|
}
|
|
289
332
|
if (args.sourceType === "local-table") {
|
|
290
333
|
const { entries, modelFields } = await readLocalTableEntries(
|
|
291
|
-
args.sourceTable,
|
|
334
|
+
args.source.sourceTable,
|
|
292
335
|
{ limit: args.limit, offset: args.offset },
|
|
293
336
|
);
|
|
294
337
|
return {
|
|
@@ -302,7 +345,7 @@ async function readSourceEntries(args: {
|
|
|
302
345
|
if (args.sourceType === "notion-database") {
|
|
303
346
|
const read = await getContentDatabaseSourceAdapter("notion-database")!.read(
|
|
304
347
|
{
|
|
305
|
-
sourceTable: args.sourceTable,
|
|
348
|
+
sourceTable: args.source.sourceTable,
|
|
306
349
|
limit: args.limit,
|
|
307
350
|
offset: args.offset,
|
|
308
351
|
},
|
|
@@ -372,7 +415,7 @@ export default defineAction({
|
|
|
372
415
|
|
|
373
416
|
const { entries, modelFields } = await readSourceEntries({
|
|
374
417
|
sourceType: normalizedType,
|
|
375
|
-
|
|
418
|
+
source,
|
|
376
419
|
limit: args.limit ?? 100,
|
|
377
420
|
offset: args.offset ?? 0,
|
|
378
421
|
});
|
|
@@ -433,6 +476,7 @@ export default defineAction({
|
|
|
433
476
|
existingFieldPaths: existingSourceFields.map(
|
|
434
477
|
(field) => field.sourceFieldKey,
|
|
435
478
|
),
|
|
479
|
+
...builderSourceReadBinding(source),
|
|
436
480
|
});
|
|
437
481
|
const entries = read.state === "live" ? read.entries : [];
|
|
438
482
|
await db
|
|
@@ -15,6 +15,13 @@ import { documentChangeResource } from "../server/lib/document-change-resource.j
|
|
|
15
15
|
import { deleteDocumentImports } from "../server/lib/document-imports.js";
|
|
16
16
|
import { chunks } from "./_batch-utils.js";
|
|
17
17
|
import { deleteBlocksFieldIdentity } from "./_blocks-field-identity.js";
|
|
18
|
+
import {
|
|
19
|
+
builderExecutionPayloadReference,
|
|
20
|
+
cleanupBuilderPrivatePayload,
|
|
21
|
+
enqueueBuilderPrivatePayloadCleanup,
|
|
22
|
+
isBuilderPrivatePayloadBoundToSource,
|
|
23
|
+
} from "./_builder-cms-blob-custody.js";
|
|
24
|
+
import { assertNoActiveBuilderSourceExecutions } from "./_builder-source-execution-guard.js";
|
|
18
25
|
import {
|
|
19
26
|
lockContentDatabaseMutation,
|
|
20
27
|
touchContentDatabase,
|
|
@@ -972,19 +979,26 @@ export async function deleteDocumentRecursive(
|
|
|
972
979
|
id: string,
|
|
973
980
|
ownerEmail: string,
|
|
974
981
|
): Promise<string[]> {
|
|
975
|
-
|
|
982
|
+
const deletedBlobReferences = new Set<string>();
|
|
983
|
+
const deleted = await db.transaction((tx) =>
|
|
976
984
|
deleteDocumentRootsRecursive(
|
|
977
985
|
tx as unknown as ReturnType<typeof getDb>,
|
|
978
986
|
[id],
|
|
979
987
|
ownerEmail,
|
|
988
|
+
deletedBlobReferences,
|
|
980
989
|
),
|
|
981
990
|
);
|
|
991
|
+
for (const reference of deletedBlobReferences) {
|
|
992
|
+
await cleanupBuilderPrivatePayload(reference, "deleted document source");
|
|
993
|
+
}
|
|
994
|
+
return deleted;
|
|
982
995
|
}
|
|
983
996
|
|
|
984
997
|
export async function deleteDocumentRootsRecursive(
|
|
985
998
|
db: ReturnType<typeof getDb>,
|
|
986
999
|
rootIds: string[],
|
|
987
1000
|
ownerEmail: string,
|
|
1001
|
+
deletedBlobReferences: Set<string>,
|
|
988
1002
|
): Promise<string[]> {
|
|
989
1003
|
const collectScope = () =>
|
|
990
1004
|
collectDocumentSubtreeForDelete(db, rootIds, ownerEmail);
|
|
@@ -998,6 +1012,7 @@ export async function deleteDocumentRootsRecursive(
|
|
|
998
1012
|
documentIds,
|
|
999
1013
|
ownedDatabaseIds,
|
|
1000
1014
|
ownerEmail,
|
|
1015
|
+
deletedBlobReferences,
|
|
1001
1016
|
);
|
|
1002
1017
|
}
|
|
1003
1018
|
|
|
@@ -1006,6 +1021,7 @@ async function deleteCollectedDocuments(
|
|
|
1006
1021
|
documentIds: string[],
|
|
1007
1022
|
ownedDatabaseIds: string[],
|
|
1008
1023
|
ownerEmail: string,
|
|
1024
|
+
deletedBlobReferences: Set<string>,
|
|
1009
1025
|
): Promise<string[]> {
|
|
1010
1026
|
await assertNotWorkspaceCatalogDocuments(db, documentIds, "deleted");
|
|
1011
1027
|
|
|
@@ -1041,6 +1057,7 @@ async function deleteCollectedDocuments(
|
|
|
1041
1057
|
});
|
|
1042
1058
|
|
|
1043
1059
|
await deleteWhereIn(sourceIds, async (sourceIdBatch) => {
|
|
1060
|
+
await assertNoActiveBuilderSourceExecutions(db, sourceIdBatch);
|
|
1044
1061
|
await db
|
|
1045
1062
|
.delete(schema.contentDatabaseBodyHydrationQueue)
|
|
1046
1063
|
.where(
|
|
@@ -1049,11 +1066,30 @@ async function deleteCollectedDocuments(
|
|
|
1049
1066
|
sourceIdBatch,
|
|
1050
1067
|
),
|
|
1051
1068
|
);
|
|
1052
|
-
await db
|
|
1069
|
+
const deletedExecutions = await db
|
|
1053
1070
|
.delete(schema.contentDatabaseSourceExecutions)
|
|
1054
1071
|
.where(
|
|
1055
1072
|
inArray(schema.contentDatabaseSourceExecutions.sourceId, sourceIdBatch),
|
|
1056
|
-
)
|
|
1073
|
+
)
|
|
1074
|
+
.returning({
|
|
1075
|
+
ownerEmail: schema.contentDatabaseSourceExecutions.ownerEmail,
|
|
1076
|
+
sourceId: schema.contentDatabaseSourceExecutions.sourceId,
|
|
1077
|
+
payloadJson: schema.contentDatabaseSourceExecutions.payloadJson,
|
|
1078
|
+
});
|
|
1079
|
+
for (const row of deletedExecutions) {
|
|
1080
|
+
const reference = builderExecutionPayloadReference(row.payloadJson);
|
|
1081
|
+
if (
|
|
1082
|
+
reference &&
|
|
1083
|
+
isBuilderPrivatePayloadBoundToSource(
|
|
1084
|
+
reference,
|
|
1085
|
+
row.ownerEmail,
|
|
1086
|
+
row.sourceId,
|
|
1087
|
+
)
|
|
1088
|
+
) {
|
|
1089
|
+
deletedBlobReferences.add(reference);
|
|
1090
|
+
await enqueueBuilderPrivatePayloadCleanup(db, reference);
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1057
1093
|
await db
|
|
1058
1094
|
.delete(schema.contentDatabaseSourceChangeReviews)
|
|
1059
1095
|
.where(
|
|
@@ -1225,6 +1261,7 @@ export async function deleteTrashedDocumentSubtree(
|
|
|
1225
1261
|
db: ReturnType<typeof getDb>,
|
|
1226
1262
|
id: string,
|
|
1227
1263
|
ownerEmail: string,
|
|
1264
|
+
deletedBlobReferences: Set<string>,
|
|
1228
1265
|
frozen?: ReadonlyArray<{
|
|
1229
1266
|
documentId: string;
|
|
1230
1267
|
expectedTrashedAt: string;
|
|
@@ -1374,6 +1411,7 @@ export async function deleteTrashedDocumentSubtree(
|
|
|
1374
1411
|
documentIds,
|
|
1375
1412
|
ownedDatabaseIds,
|
|
1376
1413
|
ownerEmail,
|
|
1414
|
+
deletedBlobReferences,
|
|
1377
1415
|
);
|
|
1378
1416
|
}
|
|
1379
1417
|
|