@agent-native/core-corpus 0.216.0 → 0.217.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.
Files changed (149) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +61 -1
  3. package/corpus/templates/analytics/AGENTS.md +1 -1
  4. package/corpus/templates/analytics/CHANGELOG.md +1 -0
  5. package/corpus/templates/analytics/actions/get-session-replay-summary.ts +9 -65
  6. package/corpus/templates/analytics/actions/list-event-catalog.ts +1 -1
  7. package/corpus/templates/analytics/actions/list-session-outputs.ts +40 -0
  8. package/corpus/templates/analytics/actions/list-session-recordings.ts +17 -4
  9. package/corpus/templates/analytics/actions/view-screen.ts +29 -3
  10. package/corpus/templates/analytics/app/i18n/en-US.ts +27 -0
  11. package/corpus/templates/analytics/app/pages/sessions/EventCatalogPage.tsx +18 -1
  12. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +3 -1
  13. package/corpus/templates/analytics/app/pages/sessions/SessionOutputs.tsx +197 -0
  14. package/corpus/templates/analytics/app/pages/sessions/SessionsTriagePage.tsx +114 -4
  15. package/corpus/templates/analytics/changelog/2026-10-15-sessions-can-find-people-who-made-a-slides-deck-but-didn-t-p.md +6 -0
  16. package/corpus/templates/analytics/changelog/2026-10-16-the-activation-funnel-counts-visitors-who-decline-analytics-.md +6 -0
  17. package/corpus/templates/analytics/server/db/schema.ts +67 -0
  18. package/corpus/templates/analytics/server/lib/canonical-first-party-dashboard-repair.ts +9 -0
  19. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +28 -22
  20. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +36 -1
  21. package/corpus/templates/analytics/server/lib/output-outcomes.ts +712 -0
  22. package/corpus/templates/analytics/server/lib/replay-origins.ts +33 -0
  23. package/corpus/templates/analytics/server/lib/session-event-index.ts +20 -0
  24. package/corpus/templates/analytics/server/lib/session-performance.ts +6 -40
  25. package/corpus/templates/analytics/server/lib/session-recording-associations.ts +30 -1
  26. package/corpus/templates/analytics/server/lib/session-replay-triage.ts +99 -0
  27. package/corpus/templates/analytics/server/lib/session-replay.ts +45 -31
  28. package/corpus/templates/analytics/server/lib/sessions-triage-lab.ts +8 -2
  29. package/corpus/templates/analytics/server/plugins/db.ts +46 -0
  30. package/corpus/templates/analytics/shared/output-use.ts +205 -0
  31. package/corpus/templates/analytics/shared/session-events.ts +6 -0
  32. package/corpus/templates/calendar/CHANGELOG.md +6 -0
  33. package/corpus/templates/calendar/actions/create-event.ts +2 -4
  34. package/corpus/templates/calendar/actions/update-event.ts +35 -6
  35. package/corpus/templates/calendar/changelog/2026-10-21-solo-events-avoid-notification-approval.md +6 -0
  36. package/corpus/templates/community/qr/package.json +1 -1
  37. package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +6 -3
  38. package/corpus/templates/content/CHANGELOG.md +8 -0
  39. package/corpus/templates/content/actions/_browser-document-save-attempt.ts +23 -1
  40. package/corpus/templates/content/actions/_builder-cms-blob-custody.ts +467 -0
  41. package/corpus/templates/content/actions/_builder-cms-execution-lease.ts +17 -0
  42. package/corpus/templates/content/actions/_builder-cms-intent-lookup.ts +5 -0
  43. package/corpus/templates/content/actions/_builder-cms-read-client.ts +881 -21
  44. package/corpus/templates/content/actions/_builder-cms-write-adapter.ts +28 -2
  45. package/corpus/templates/content/actions/_builder-cms-write-client.ts +109 -11
  46. package/corpus/templates/content/actions/_builder-docs-client.ts +1 -0
  47. package/corpus/templates/content/actions/_builder-private-blob.test-fixture.ts +90 -0
  48. package/corpus/templates/content/actions/_builder-source-execution-guard.ts +48 -0
  49. package/corpus/templates/content/actions/_content-save-outcomes.ts +5 -0
  50. package/corpus/templates/content/actions/_database-source-utils.ts +290 -8
  51. package/corpus/templates/content/actions/_database-utils.ts +93 -7
  52. package/corpus/templates/content/actions/_database-view-setup.ts +21 -2
  53. package/corpus/templates/content/actions/_delete-content-space.ts +8 -1
  54. package/corpus/templates/content/actions/_github-source-freshness.ts +30 -4
  55. package/corpus/templates/content/actions/add-content-database-source-field-property.ts +23 -6
  56. package/corpus/templates/content/actions/cancel-prepared-builder-source-update.ts +110 -26
  57. package/corpus/templates/content/actions/change-content-database-source-role.ts +51 -7
  58. package/corpus/templates/content/actions/delete-document.ts +41 -3
  59. package/corpus/templates/content/actions/disconnect-content-database-source.ts +68 -2
  60. package/corpus/templates/content/actions/disconnect-local-folder-source.ts +33 -2
  61. package/corpus/templates/content/actions/execute-builder-source-execution.ts +900 -140
  62. package/corpus/templates/content/actions/get-document.ts +2 -0
  63. package/corpus/templates/content/actions/permanently-delete-document.ts +6 -0
  64. package/corpus/templates/content/actions/prepare-builder-source-execution.ts +155 -13
  65. package/corpus/templates/content/actions/prepare-builder-source-review.ts +299 -73
  66. package/corpus/templates/content/actions/set-live-body-switch.ts +45 -0
  67. package/corpus/templates/content/actions/update-document.ts +139 -20
  68. package/corpus/templates/content/actions/validate-builder-source-execution.ts +135 -26
  69. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +23 -0
  70. package/corpus/templates/content/app/components/editor/database/GitHubSourceStatusLine.tsx +27 -2
  71. package/corpus/templates/content/app/components/editor/database/ListItemLayout.tsx +32 -8
  72. package/corpus/templates/content/app/components/editor/database/ListItemRow.tsx +27 -2
  73. package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +1 -0
  74. package/corpus/templates/content/app/hooks/use-content-database.ts +40 -2
  75. package/corpus/templates/content/app/hooks/use-documents.ts +14 -0
  76. package/corpus/templates/content/app/i18n-data.ts +1 -0
  77. package/corpus/templates/content/changelog/2026-10-17-builder-content-avoids-retrying-a-write-when-builder-accepte.md +6 -0
  78. package/corpus/templates/content/changelog/2026-10-18-link-a-new-collection-to-a-github-folder-from-its-sources.md +1 -1
  79. package/corpus/templates/content/changelog/2026-10-20-builder-content-sync-keeps-read-and-write-status-accurate-wh.md +6 -0
  80. package/corpus/templates/content/changelog/2026-10-21-list-rows-can-show-each-pages-first-lines.md +6 -0
  81. package/corpus/templates/content/changelog/2026-10-22-a-collection-linked-to-a-big-github-folder-fills-faster-and-p.md +6 -0
  82. package/corpus/templates/content/docs/product/capabilities/content.source.adapters.md +29 -0
  83. package/corpus/templates/content/parity/matrix.md +42 -42
  84. package/corpus/templates/content/parity/matrix.ts +3 -1
  85. package/corpus/templates/content/server/db/schema.ts +20 -0
  86. package/corpus/templates/content/server/lib/content-trash-purge.ts +15 -1
  87. package/corpus/templates/content/server/lib/live-body-writer.ts +138 -26
  88. package/corpus/templates/content/server/live-copy/binding.ts +31 -0
  89. package/corpus/templates/content/server/live-copy/bounded-task.d.ts +6 -0
  90. package/corpus/templates/content/server/live-copy/exec-db.ts +1 -0
  91. package/corpus/templates/content/server/live-copy/holds.ts +59 -0
  92. package/corpus/templates/content/server/live-copy/persist.ts +656 -0
  93. package/corpus/templates/content/server/live-copy/switch.ts +136 -0
  94. package/corpus/templates/content/server/live-copy/task-names.ts +4 -0
  95. package/corpus/templates/content/server/live-copy/tasks.ts +183 -0
  96. package/corpus/templates/content/server/live-copy/write-cause.ts +85 -0
  97. package/corpus/templates/content/server/live-copy/write.ts +85 -0
  98. package/corpus/templates/content/server/plugins/builder-private-payload-cleanup.ts +22 -0
  99. package/corpus/templates/content/server/plugins/collab.ts +3 -0
  100. package/corpus/templates/content/server/plugins/db.ts +28 -0
  101. package/corpus/templates/content/shared/api.ts +30 -1
  102. package/corpus/templates/content/shared/database-item-layout.ts +54 -22
  103. package/corpus/templates/content/shared/document-intent-merge.ts +16 -9
  104. package/corpus/templates/content/shared/og-description.ts +258 -36
  105. package/corpus/templates/design/.agents/skills/design-templates/SKILL.md +5 -1
  106. package/corpus/templates/design/.generated/bridge/shader-runtime.generated.ts +25 -4
  107. package/corpus/templates/design/CHANGELOG.md +2 -0
  108. package/corpus/templates/design/actions/get-getting-started.ts +53 -0
  109. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +10 -0
  110. package/corpus/templates/design/app/components/design/bridge/native-shader-runtime.bridge.ts +132 -79
  111. package/corpus/templates/design/app/components/design/bridge/native-source-provider.ts +268 -130
  112. package/corpus/templates/design/app/components/design/bridge/native-worker-scene-host.ts +122 -42
  113. package/corpus/templates/design/app/components/design/bridge/shader-runtime.bridge.ts +41 -4
  114. package/corpus/templates/design/app/components/design/design-canvas/text-content-change-result.ts +46 -12
  115. package/corpus/templates/design/app/components/home/DesignHomeLibrary.tsx +15 -8
  116. package/corpus/templates/design/app/components/layout/GettingStarted.tsx +50 -0
  117. package/corpus/templates/design/app/components/layout/Sidebar.tsx +3 -0
  118. package/corpus/templates/design/app/components/templates/DesignTemplateGrid.tsx +163 -0
  119. package/corpus/templates/design/app/components/templates/DesignTemplateLibrary.tsx +65 -78
  120. package/corpus/templates/design/app/components/templates/TemplatePreview.tsx +6 -2
  121. package/corpus/templates/design/app/global.css +6 -0
  122. package/corpus/templates/design/app/i18n/en-US.ts +17 -0
  123. package/corpus/templates/design/app/lib/canvas-load-outcome.ts +147 -0
  124. package/corpus/templates/design/app/lib/canvas-outcome-tracking.ts +531 -0
  125. package/corpus/templates/design/app/lib/canvas-style-kind.ts +50 -0
  126. package/corpus/templates/design/app/pages/Index.tsx +10 -3
  127. package/corpus/templates/design/app/pages/design-editor/commands/commit-visual-styles.ts +86 -29
  128. package/corpus/templates/design/app/pages/design-editor/commands/save-file-content.ts +38 -3
  129. package/corpus/templates/design/app/pages/design-editor/domains/use-editor-core.ts +9 -0
  130. package/corpus/templates/design/app/pages/design-editor/domains/use-editor-files-and-saving.ts +2 -0
  131. package/corpus/templates/design/changelog/2026-10-21-a-getting-started-checklist-at-the-bottom-of-the-sidebar-tra.md +6 -0
  132. package/corpus/templates/design/changelog/2026-10-21-templates-now-open-a-read-only-preview-from-the-card-and-the.md +6 -0
  133. package/corpus/templates/design/changelog/2026-10-22-collapsed-shader-layers-no-longer-interrupt-other-shaders-on.md +6 -0
  134. package/corpus/templates/design/changelog/2026-10-22-shader-fills-keep-rendering-when-other-screen-content-cannot.md +6 -0
  135. package/corpus/templates/design/scripts/design-export-validation/export-baseline.json +24 -0
  136. package/corpus/templates/design/server/lib/getting-started.ts +267 -0
  137. package/corpus/templates/design/server/plugins/getting-started.ts +5 -0
  138. package/corpus/templates/design/shared/design-template-presets/analytics-dashboard.ts +127 -0
  139. package/corpus/templates/design/shared/design-template-presets/design-studio.ts +98 -0
  140. package/corpus/templates/design/shared/design-template-presets/messaging-app.ts +101 -0
  141. package/corpus/templates/design/shared/design-template-presets/project-tasks.ts +120 -0
  142. package/corpus/templates/design/shared/design-template-presets.ts +8 -0
  143. package/corpus/templates/forms/server/plugins/agent-chat.ts +1 -1
  144. package/corpus/templates/slides/CHANGELOG.md +6 -0
  145. package/corpus/templates/slides/actions/delete-deck.ts +7 -0
  146. package/corpus/templates/slides/changelog/2026-10-21-connected-deck-deletion-requires-approval.md +6 -0
  147. package/corpus/templates/slides/scripts/edit-fidelity/client-action.ts +61 -0
  148. package/corpus/templates/slides/scripts/edit-fidelity/run.ts +10 -0
  149. package/package.json +1 -1
@@ -0,0 +1,467 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+
3
+ import { ActionContractError } from "@agent-native/core";
4
+ import {
5
+ deletePrivateBlob,
6
+ putPrivateBlob,
7
+ readPrivateBlob,
8
+ type PrivateBlobHandle,
9
+ } from "@agent-native/core/private-blob";
10
+ import { asc, eq, lte } from "drizzle-orm";
11
+
12
+ import { getDb, schema } from "../server/db/index.js";
13
+
14
+ const textEncoder = new TextEncoder();
15
+ const textDecoder = new TextDecoder();
16
+ const BUILDER_PRIVATE_PAYLOAD_CLEANUP_BATCH_SIZE = 50;
17
+ const BUILDER_PRIVATE_PAYLOAD_CLEANUP_RETRY_BASE_MS = 30_000;
18
+ const BUILDER_PRIVATE_PAYLOAD_CLEANUP_RETRY_MAX_MS = 6 * 60 * 60 * 1000;
19
+
20
+ export const BUILDER_EXECUTION_PAYLOAD_BLOB_KEY =
21
+ "__builder.execution.privatePayload";
22
+
23
+ type BuilderBlobBinding = Record<string, string>;
24
+
25
+ interface BuilderBlobReference {
26
+ kind: "agent-native.builder-private-payload";
27
+ version: 1;
28
+ binding: BuilderBlobBinding;
29
+ sha256: string;
30
+ handle: PrivateBlobHandle;
31
+ }
32
+
33
+ interface BuilderBlobEnvelope<T> {
34
+ kind: "agent-native.builder-private-payload";
35
+ version: 1;
36
+ binding: BuilderBlobBinding;
37
+ payload: T;
38
+ }
39
+
40
+ function stableJson(value: unknown): string {
41
+ if (Array.isArray(value)) {
42
+ return `[${value.map(stableJson).join(",")}]`;
43
+ }
44
+ if (value && typeof value === "object") {
45
+ const record = value as Record<string, unknown>;
46
+ return `{${Object.keys(record)
47
+ .sort()
48
+ .map((key) => `${JSON.stringify(key)}:${stableJson(record[key])}`)
49
+ .join(",")}}`;
50
+ }
51
+ return JSON.stringify(value) ?? "null";
52
+ }
53
+
54
+ function digest(bytes: Uint8Array) {
55
+ return createHash("sha256").update(bytes).digest("hex");
56
+ }
57
+
58
+ export function builderPrivatePayloadUnavailable(
59
+ message: string,
60
+ _cause?: unknown,
61
+ ): never {
62
+ throw new ActionContractError(message, {
63
+ errorCode: "BUILDER_PRIVATE_PAYLOAD_UNAVAILABLE",
64
+ statusCode: 409,
65
+ });
66
+ }
67
+
68
+ function logBuilderPrivatePayloadCleanupFailure(
69
+ label: string,
70
+ handle: PrivateBlobHandle,
71
+ reason: string,
72
+ ) {
73
+ console.error(`[builder-private-payload] ${label} cleanup failed`, {
74
+ provider: handle.provider,
75
+ handleId: handle.id,
76
+ reason,
77
+ });
78
+ }
79
+
80
+ function parseReference(value: unknown): BuilderBlobReference | null {
81
+ if (typeof value !== "string" || !value.trim()) return null;
82
+ try {
83
+ const parsed = JSON.parse(value) as BuilderBlobReference;
84
+ if (
85
+ parsed?.kind !== "agent-native.builder-private-payload" ||
86
+ parsed.version !== 1 ||
87
+ !parsed.binding ||
88
+ typeof parsed.binding !== "object" ||
89
+ typeof parsed.sha256 !== "string" ||
90
+ !parsed.handle ||
91
+ typeof parsed.handle.id !== "string" ||
92
+ typeof parsed.handle.provider !== "string" ||
93
+ parsed.handle.opaque !== true ||
94
+ typeof parsed.handle.encrypted !== "boolean"
95
+ ) {
96
+ return null;
97
+ }
98
+ return parsed;
99
+ // coercion-ok: null is the explicit invalid-reference marker; reads reject it and cleanup never authorizes deletion from it.
100
+ } catch {
101
+ return null;
102
+ }
103
+ }
104
+
105
+ function sameBinding(actual: BuilderBlobBinding, expected: BuilderBlobBinding) {
106
+ return stableJson(actual) === stableJson(expected);
107
+ }
108
+
109
+ export async function readBuilderPrivatePayload<T>(args: {
110
+ reference: unknown;
111
+ binding: BuilderBlobBinding;
112
+ label: string;
113
+ }): Promise<T> {
114
+ const reference = parseReference(args.reference);
115
+ if (!reference)
116
+ builderPrivatePayloadUnavailable(
117
+ `${args.label} private blob reference is missing or malformed.`,
118
+ );
119
+ if (!sameBinding(reference.binding, args.binding)) {
120
+ builderPrivatePayloadUnavailable(
121
+ `${args.label} private blob reference belongs to a different Builder operation.`,
122
+ );
123
+ }
124
+ let result;
125
+ try {
126
+ result = await readPrivateBlob(reference.handle);
127
+ } catch (error) {
128
+ builderPrivatePayloadUnavailable(
129
+ `${args.label} private blob could not be read.`,
130
+ error,
131
+ );
132
+ }
133
+ if (digest(result.data) !== reference.sha256) {
134
+ builderPrivatePayloadUnavailable(
135
+ `${args.label} private blob failed integrity verification.`,
136
+ );
137
+ }
138
+ try {
139
+ const envelope = JSON.parse(
140
+ textDecoder.decode(result.data),
141
+ ) as BuilderBlobEnvelope<T>;
142
+ if (
143
+ envelope?.kind !== "agent-native.builder-private-payload" ||
144
+ envelope.version !== 1 ||
145
+ !sameBinding(envelope.binding, args.binding)
146
+ ) {
147
+ builderPrivatePayloadUnavailable(
148
+ `${args.label} private blob identity is invalid.`,
149
+ );
150
+ }
151
+ return envelope.payload;
152
+ } catch (error) {
153
+ if (error instanceof ActionContractError) throw error;
154
+ builderPrivatePayloadUnavailable(
155
+ `${args.label} private blob payload is malformed.`,
156
+ error,
157
+ );
158
+ }
159
+ }
160
+
161
+ export async function putBuilderPrivatePayload<T>(args: {
162
+ payload: T;
163
+ binding: BuilderBlobBinding;
164
+ label: string;
165
+ ownerEmail?: string;
166
+ }): Promise<string> {
167
+ const bytes = textEncoder.encode(
168
+ JSON.stringify({
169
+ kind: "agent-native.builder-private-payload",
170
+ version: 1,
171
+ binding: args.binding,
172
+ payload: args.payload,
173
+ } satisfies BuilderBlobEnvelope<T>),
174
+ );
175
+ let handle: PrivateBlobHandle | null;
176
+ try {
177
+ handle = await putPrivateBlob({
178
+ data: bytes,
179
+ key: `content/builder/${createHash("sha256").update(stableJson(args.binding)).digest("hex")}/${randomUUID()}.json`,
180
+ filename: "builder-private-payload.json",
181
+ mimeType: "application/json",
182
+ ownerEmail: args.ownerEmail,
183
+ metadata: { kind: "builder-private-payload", version: 1 },
184
+ });
185
+ } catch (error) {
186
+ builderPrivatePayloadUnavailable(
187
+ `${args.label} could not be stored in private blob custody.`,
188
+ error,
189
+ );
190
+ }
191
+ if (!handle)
192
+ builderPrivatePayloadUnavailable(
193
+ `${args.label} requires configured private blob custody.`,
194
+ );
195
+ const reference = JSON.stringify({
196
+ kind: "agent-native.builder-private-payload",
197
+ version: 1,
198
+ binding: args.binding,
199
+ sha256: digest(bytes),
200
+ handle,
201
+ } satisfies BuilderBlobReference);
202
+ try {
203
+ await readBuilderPrivatePayload({
204
+ reference,
205
+ binding: args.binding,
206
+ label: args.label,
207
+ });
208
+ } catch (error) {
209
+ let cleanupReason: string | null = null;
210
+ try {
211
+ const cleanup = await deletePrivateBlob(handle);
212
+ if (!cleanup.deleted) {
213
+ cleanupReason = cleanup.reason ?? "Deletion was declined";
214
+ }
215
+ } catch (cleanupError) {
216
+ cleanupReason =
217
+ cleanupError instanceof Error ? cleanupError.name : "Unknown error";
218
+ }
219
+ if (cleanupReason) {
220
+ try {
221
+ await enqueueBuilderPrivatePayloadCleanup(getDb(), reference);
222
+ } catch {
223
+ // Keep the provider failure visible if durable tracking is unavailable.
224
+ }
225
+ logBuilderPrivatePayloadCleanupFailure(args.label, handle, cleanupReason);
226
+ }
227
+ throw error;
228
+ }
229
+ return reference;
230
+ }
231
+
232
+ export async function deleteBuilderPrivatePayload(reference: unknown) {
233
+ const parsed = parseReference(reference);
234
+ if (!parsed) return false;
235
+ const result = await deletePrivateBlob(parsed.handle);
236
+ return result.deleted;
237
+ }
238
+
239
+ export async function enqueueBuilderPrivatePayloadCleanup(
240
+ db: ReturnType<typeof getDb>,
241
+ reference: unknown,
242
+ ) {
243
+ if (!isBuilderPrivatePayloadReference(reference)) return;
244
+ await db
245
+ .insert(schema.contentDatabaseBuilderBlobCleanup)
246
+ .values({ blobReference: reference as string })
247
+ .onConflictDoNothing();
248
+ }
249
+
250
+ async function drainBuilderPrivatePayloadCleanupQueue(args: {
251
+ db: ReturnType<typeof getDb>;
252
+ label: string;
253
+ deadlineAt?: number;
254
+ signal?: AbortSignal;
255
+ }) {
256
+ const cleanupQueue = schema.contentDatabaseBuilderBlobCleanup;
257
+
258
+ let pending: Array<{ blobReference: string; attemptCount: number }>;
259
+ try {
260
+ pending = await args.db
261
+ .select({
262
+ blobReference: cleanupQueue.blobReference,
263
+ attemptCount: cleanupQueue.attemptCount,
264
+ })
265
+ .from(cleanupQueue)
266
+ .where(lte(cleanupQueue.nextAttemptAt, new Date().toISOString()))
267
+ .orderBy(asc(cleanupQueue.nextAttemptAt), asc(cleanupQueue.createdAt))
268
+ .limit(BUILDER_PRIVATE_PAYLOAD_CLEANUP_BATCH_SIZE);
269
+ } catch (error) {
270
+ console.error(
271
+ `[builder-private-payload] ${args.label} cleanup queue could not be read`,
272
+ error,
273
+ );
274
+ return;
275
+ }
276
+
277
+ for (const queued of pending) {
278
+ if (
279
+ args.signal?.aborted ||
280
+ (args.deadlineAt !== undefined && Date.now() >= args.deadlineAt)
281
+ ) {
282
+ return;
283
+ }
284
+ try {
285
+ const deleted = await deleteBuilderPrivatePayload(queued.blobReference);
286
+ if (!deleted) {
287
+ console.error(
288
+ `[builder-private-payload] ${args.label} cleanup was declined; retry retained`,
289
+ );
290
+ await scheduleBuilderPrivatePayloadCleanupRetry(args, queued);
291
+ continue;
292
+ }
293
+ await args.db
294
+ .delete(cleanupQueue)
295
+ .where(eq(cleanupQueue.blobReference, queued.blobReference));
296
+ } catch (error) {
297
+ console.error(
298
+ `[builder-private-payload] ${args.label} cleanup remains queued`,
299
+ error,
300
+ );
301
+ await scheduleBuilderPrivatePayloadCleanupRetry(args, queued);
302
+ }
303
+ }
304
+ }
305
+
306
+ async function scheduleBuilderPrivatePayloadCleanupRetry(
307
+ args: {
308
+ db: ReturnType<typeof getDb>;
309
+ label: string;
310
+ },
311
+ queued: { blobReference: string; attemptCount: number },
312
+ ) {
313
+ const attemptCount = queued.attemptCount + 1;
314
+ const delayMs = Math.min(
315
+ BUILDER_PRIVATE_PAYLOAD_CLEANUP_RETRY_BASE_MS *
316
+ 2 ** Math.min(attemptCount - 1, 10),
317
+ BUILDER_PRIVATE_PAYLOAD_CLEANUP_RETRY_MAX_MS,
318
+ );
319
+ try {
320
+ await args.db
321
+ .update(schema.contentDatabaseBuilderBlobCleanup)
322
+ .set({
323
+ attemptCount,
324
+ nextAttemptAt: new Date(Date.now() + delayMs).toISOString(),
325
+ })
326
+ .where(
327
+ eq(
328
+ schema.contentDatabaseBuilderBlobCleanup.blobReference,
329
+ queued.blobReference,
330
+ ),
331
+ );
332
+ } catch (error) {
333
+ console.error(
334
+ `[builder-private-payload] ${args.label} cleanup retry could not be scheduled`,
335
+ error,
336
+ );
337
+ }
338
+ }
339
+
340
+ export async function drainBuilderPrivatePayloadCleanup(
341
+ args: {
342
+ deadlineAt?: number;
343
+ signal?: AbortSignal;
344
+ } = {},
345
+ ) {
346
+ await drainBuilderPrivatePayloadCleanupQueue({
347
+ db: getDb(),
348
+ label: "scheduled",
349
+ deadlineAt: args.deadlineAt,
350
+ signal: args.signal,
351
+ });
352
+ }
353
+
354
+ export async function cleanupBuilderPrivatePayload(
355
+ reference: unknown,
356
+ _label: string,
357
+ ) {
358
+ if (!isBuilderPrivatePayloadReference(reference)) return;
359
+ const blobReference = reference as string;
360
+ await enqueueBuilderPrivatePayloadCleanup(getDb(), blobReference);
361
+ }
362
+
363
+ export function isBuilderPrivatePayloadReference(value: unknown) {
364
+ return parseReference(value) !== null;
365
+ }
366
+
367
+ export function isBuilderPrivatePayloadBoundToSource(
368
+ value: unknown,
369
+ ownerEmail: string,
370
+ sourceId: string,
371
+ ) {
372
+ const reference = parseReference(value);
373
+ return (
374
+ reference?.binding.ownerEmail === ownerEmail &&
375
+ reference.binding.sourceId === sourceId
376
+ );
377
+ }
378
+
379
+ export function builderExecutionPayloadReference(
380
+ payloadJson: string,
381
+ ): string | null {
382
+ try {
383
+ const payload = JSON.parse(payloadJson) as Record<string, unknown>;
384
+ const reference = payload?.[BUILDER_EXECUTION_PAYLOAD_BLOB_KEY];
385
+ return isBuilderPrivatePayloadReference(reference)
386
+ ? (reference as string)
387
+ : null;
388
+ // coercion-ok: malformed compact SQL cannot authorize execution-blob deletion.
389
+ } catch {
390
+ return null;
391
+ }
392
+ }
393
+
394
+ export interface BuilderExecutionPayloadBinding {
395
+ ownerEmail: string;
396
+ sourceId: string;
397
+ changeSetId: string;
398
+ executionId: string;
399
+ idempotencyKey: string;
400
+ }
401
+
402
+ function executionBinding(
403
+ value: BuilderExecutionPayloadBinding,
404
+ ): BuilderBlobBinding {
405
+ return { ...value };
406
+ }
407
+
408
+ export async function storeBuilderExecutionPayload(args: {
409
+ payload: unknown;
410
+ binding: BuilderExecutionPayloadBinding;
411
+ }) {
412
+ if (
413
+ !args.payload ||
414
+ typeof args.payload !== "object" ||
415
+ Array.isArray(args.payload)
416
+ ) {
417
+ builderPrivatePayloadUnavailable("Builder execution payload is malformed.");
418
+ }
419
+ const payload = args.payload as Record<string, unknown>;
420
+ const reference = await putBuilderPrivatePayload({
421
+ payload,
422
+ binding: executionBinding(args.binding),
423
+ label: "Builder prepared request and receipt",
424
+ ownerEmail: args.binding.ownerEmail,
425
+ });
426
+ const response =
427
+ payload.response && typeof payload.response === "object"
428
+ ? { stored: true }
429
+ : undefined;
430
+ return JSON.stringify({
431
+ [BUILDER_EXECUTION_PAYLOAD_BLOB_KEY]: reference,
432
+ ...(response ? { response } : {}),
433
+ });
434
+ }
435
+
436
+ export async function readBuilderExecutionPayload(args: {
437
+ payloadJson: string;
438
+ binding: BuilderExecutionPayloadBinding;
439
+ }): Promise<Record<string, unknown>> {
440
+ let stored: Record<string, unknown>;
441
+ try {
442
+ const parsed = JSON.parse(args.payloadJson) as unknown;
443
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
444
+ builderPrivatePayloadUnavailable(
445
+ "Builder execution payload JSON is malformed.",
446
+ );
447
+ }
448
+ stored = parsed as Record<string, unknown>;
449
+ } catch {
450
+ builderPrivatePayloadUnavailable(
451
+ "Builder execution payload JSON is malformed.",
452
+ );
453
+ }
454
+ const reference = stored[BUILDER_EXECUTION_PAYLOAD_BLOB_KEY];
455
+ if (reference === undefined) return stored;
456
+ const payload = await readBuilderPrivatePayload<unknown>({
457
+ reference,
458
+ binding: executionBinding(args.binding),
459
+ label: "Builder prepared request and receipt",
460
+ });
461
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
462
+ builderPrivatePayloadUnavailable(
463
+ "Builder execution private blob payload is malformed.",
464
+ );
465
+ }
466
+ return payload as Record<string, unknown>;
467
+ }
@@ -0,0 +1,17 @@
1
+ export const BUILDER_EXECUTION_CLAIM_TIMEOUT_MS = 10 * 60 * 1000;
2
+
3
+ export function builderExecutionStaleBefore(now: string) {
4
+ const timestamp = Date.parse(now);
5
+ return new Date(
6
+ (Number.isFinite(timestamp) ? timestamp : Date.now()) -
7
+ BUILDER_EXECUTION_CLAIM_TIMEOUT_MS,
8
+ ).toISOString();
9
+ }
10
+
11
+ export function isBuilderExecutionClaimStale(updatedAt: string, now: string) {
12
+ const updatedTimestamp = Date.parse(updatedAt);
13
+ return (
14
+ Number.isFinite(updatedTimestamp) &&
15
+ updatedTimestamp < Date.parse(builderExecutionStaleBefore(now))
16
+ );
17
+ }
@@ -67,6 +67,8 @@ export async function lookupBuilderCmsSafeModelIntent(args: {
67
67
  marker?: string;
68
68
  exactTitle?: string;
69
69
  intendedFields?: Record<string, unknown>;
70
+ expectedSourceSpace?: string | null;
71
+ expectedSourceConnectionId?: string | null;
70
72
  }) {
71
73
  if (!args.marker && !args.exactTitle) {
72
74
  throw new Error("Provide an exact execution marker or exact title.");
@@ -75,7 +77,10 @@ export async function lookupBuilderCmsSafeModelIntent(args: {
75
77
  model: BUILDER_CMS_SAFE_WRITE_MODEL,
76
78
  rawData: true,
77
79
  requirePrivateKey: true,
80
+ requireBoundOAuth: true,
78
81
  limit: 10_000,
82
+ expectedSourceSpace: args.expectedSourceSpace,
83
+ expectedSourceConnectionId: args.expectedSourceConnectionId,
79
84
  });
80
85
  if (result.state !== "live" || result.progress.partial) {
81
86
  throw new Error(