@agent-native/core 0.101.7 → 0.101.11
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/client.ts +23 -1
- package/corpus/core/src/a2a/handlers.ts +65 -1
- package/corpus/core/src/a2a/server.ts +124 -0
- package/corpus/core/src/a2a/task-store.ts +253 -0
- package/corpus/core/src/a2a/types.ts +26 -0
- package/corpus/core/src/agent/production-agent.ts +5 -2
- 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/mcp/builtin-tools.ts +25 -1
- package/corpus/core/src/scripts/call-agent.ts +22 -3
- package/corpus/core/src/server/agent-chat-plugin.ts +86 -1
- 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/core/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +25 -0
- 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/document-editing/SKILL.md +23 -0
- package/corpus/templates/content/AGENTS.md +3 -2
- 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 +1041 -154
- package/corpus/templates/content/actions/_database-utils.ts +89 -12
- package/corpus/templates/content/actions/_preview-document-draft.ts +31 -0
- package/corpus/templates/content/actions/cancel-prepared-builder-source-update.ts +309 -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/get-preview-document-draft.ts +26 -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/set-content-database-source-write-mode.ts +2 -2
- package/corpus/templates/content/actions/update-preview-document-draft.ts +153 -0
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +27 -15
- package/corpus/templates/content/app/components/editor/MathRenderer.tsx +43 -0
- package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +260 -4
- 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 +2353 -576
- 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/extensions/NotionExtensions.tsx +78 -0
- 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 +71 -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 +38 -0
- package/corpus/templates/content/app/i18n-data.ts +450 -0
- package/corpus/templates/content/app/root.tsx +2 -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-latex-equations-render-in-documents-public-pages-and-exports.md +6 -0
- package/corpus/templates/content/package.json +1 -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 +47 -0
- package/corpus/templates/content/server/plugins/auth.ts +1 -0
- package/corpus/templates/content/server/plugins/db.ts +53 -0
- package/corpus/templates/content/shared/api.ts +70 -1
- package/corpus/templates/content/shared/builder-mdx.ts +705 -15
- package/corpus/templates/content/shared/document-export.ts +296 -25
- package/corpus/templates/content/shared/inline-math.ts +128 -0
- package/corpus/templates/content/shared/math-rendering.ts +50 -0
- package/corpus/templates/content/shared/nfm.ts +20 -17
- package/corpus/templates/content/vite.config.ts +0 -1
- 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/mail/.agents/skills/email-drafts/SKILL.md +17 -0
- package/corpus/templates/mail/AGENTS.md +21 -19
- package/corpus/templates/mail/actions/create-attachment-upload.ts +49 -0
- package/corpus/templates/mail/changelog/2026-07-14-connected-agents-can-now-securely-upload-local-files-for-mai.md +6 -0
- package/corpus/templates/mail/server/handlers/media.ts +84 -61
- package/corpus/templates/mail/server/lib/attachment-upload-ticket.ts +234 -0
- package/corpus/templates/mail/server/lib/mail-connector-catalog.ts +8 -4
- package/corpus/templates/mail/server/lib/media-upload.ts +114 -0
- package/corpus/templates/mail/server/plugins/auth.ts +9 -1
- package/corpus/templates/mail/server/routes/api/media/attachment-upload/[uploadId].put.ts +1 -0
- package/corpus/templates/plan/server/plan-mdx.ts +53 -7
- package/dist/a2a/client.d.ts +5 -1
- package/dist/a2a/client.d.ts.map +1 -1
- package/dist/a2a/client.js +18 -1
- package/dist/a2a/client.js.map +1 -1
- package/dist/a2a/handlers.d.ts.map +1 -1
- package/dist/a2a/handlers.js +57 -8
- package/dist/a2a/handlers.js.map +1 -1
- package/dist/a2a/server.d.ts.map +1 -1
- package/dist/a2a/server.js +87 -1
- package/dist/a2a/server.js.map +1 -1
- package/dist/a2a/task-store.d.ts +27 -0
- package/dist/a2a/task-store.d.ts.map +1 -1
- package/dist/a2a/task-store.js +216 -0
- package/dist/a2a/task-store.js.map +1 -1
- package/dist/a2a/types.d.ts +24 -0
- package/dist/a2a/types.d.ts.map +1 -1
- package/dist/a2a/types.js.map +1 -1
- package/dist/agent/production-agent.d.ts +1 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +5 -2
- package/dist/agent/production-agent.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/collab/struct-routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/mcp/builtin-tools.d.ts.map +1 -1
- package/dist/mcp/builtin-tools.js +22 -3
- package/dist/mcp/builtin-tools.js.map +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/scripts/call-agent.d.ts +1 -1
- package/dist/scripts/call-agent.d.ts.map +1 -1
- package/dist/scripts/call-agent.js +19 -1
- package/dist/scripts/call-agent.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +71 -2
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +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/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +25 -0
- 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
- package/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +25 -0
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
1
3
|
import { defineAction } from "@agent-native/core";
|
|
2
4
|
import { getRequestUserEmail } from "@agent-native/core/server/request-context";
|
|
3
5
|
import { assertAccess } from "@agent-native/core/sharing";
|
|
4
|
-
import { and, eq } from "drizzle-orm";
|
|
6
|
+
import { and, eq, inArray, or } from "drizzle-orm";
|
|
5
7
|
import { z } from "zod";
|
|
6
8
|
|
|
7
9
|
import { getDb, schema } from "../server/db/index.js";
|
|
@@ -12,6 +14,7 @@ import type {
|
|
|
12
14
|
ContentDatabaseSourcePushMode,
|
|
13
15
|
ContentDatabaseSourceReviewPayload,
|
|
14
16
|
ContentDatabaseSourceRiskLevel,
|
|
17
|
+
ExecuteBuilderSourceBatchTransition,
|
|
15
18
|
PrepareBuilderSourceReviewRequest,
|
|
16
19
|
PrepareBuilderSourceReviewResponse,
|
|
17
20
|
} from "../shared/api.js";
|
|
@@ -20,16 +23,93 @@ import {
|
|
|
20
23
|
resolveBuilderCmsWriteEffect,
|
|
21
24
|
validateBuilderCmsExecutionDryRun,
|
|
22
25
|
} from "./_builder-cms-write-adapter.js";
|
|
26
|
+
import { claimBuilderSourceExecutionGate } from "./_builder-source-execution-claim.js";
|
|
27
|
+
import { shouldPreserveBuilderExecution } from "./_builder-source-execution-preservation.js";
|
|
28
|
+
import { createBuilderSourceTiming } from "./_builder-source-timings.js";
|
|
23
29
|
import {
|
|
30
|
+
canRefreshLocallyBlockedBuilderReview,
|
|
24
31
|
findOpenSourceChangeSet,
|
|
25
32
|
getContentDatabaseSourceSnapshotForWrite,
|
|
26
33
|
resolveDatabaseForSourceMutation,
|
|
34
|
+
serializeSourceRowRecord,
|
|
27
35
|
sourceChangeSetKey,
|
|
28
36
|
} from "./_database-source-utils.js";
|
|
29
37
|
import { getContentDatabaseResponse } from "./_database-utils.js";
|
|
30
38
|
|
|
31
39
|
export const BUILDER_SOURCE_REVIEW_PREPARE_LIMIT = 100;
|
|
32
40
|
|
|
41
|
+
export async function withAuthoritativeBuilderTargetRows(args: {
|
|
42
|
+
source: ContentDatabaseSource;
|
|
43
|
+
changeSets: ContentDatabaseSourceChangeSet[];
|
|
44
|
+
}) {
|
|
45
|
+
const representedDocumentIds = new Set(
|
|
46
|
+
args.source.rows.map((row) => row.documentId),
|
|
47
|
+
);
|
|
48
|
+
const representedItemIds = new Set(
|
|
49
|
+
args.source.rows.map((row) => row.databaseItemId),
|
|
50
|
+
);
|
|
51
|
+
const missingDocumentIds = args.changeSets.flatMap((changeSet) =>
|
|
52
|
+
changeSet.documentId && !representedDocumentIds.has(changeSet.documentId)
|
|
53
|
+
? [changeSet.documentId]
|
|
54
|
+
: [],
|
|
55
|
+
);
|
|
56
|
+
const missingItemIds = args.changeSets.flatMap((changeSet) =>
|
|
57
|
+
changeSet.databaseItemId &&
|
|
58
|
+
!representedItemIds.has(changeSet.databaseItemId)
|
|
59
|
+
? [changeSet.databaseItemId]
|
|
60
|
+
: [],
|
|
61
|
+
);
|
|
62
|
+
if (missingDocumentIds.length === 0 && missingItemIds.length === 0) {
|
|
63
|
+
return args.source;
|
|
64
|
+
}
|
|
65
|
+
const identityFilters = [
|
|
66
|
+
...(missingDocumentIds.length > 0
|
|
67
|
+
? [
|
|
68
|
+
inArray(
|
|
69
|
+
schema.contentDatabaseSourceRows.documentId,
|
|
70
|
+
missingDocumentIds,
|
|
71
|
+
),
|
|
72
|
+
]
|
|
73
|
+
: []),
|
|
74
|
+
...(missingItemIds.length > 0
|
|
75
|
+
? [
|
|
76
|
+
inArray(
|
|
77
|
+
schema.contentDatabaseSourceRows.databaseItemId,
|
|
78
|
+
missingItemIds,
|
|
79
|
+
),
|
|
80
|
+
]
|
|
81
|
+
: []),
|
|
82
|
+
];
|
|
83
|
+
const authoritativeRows = await getDb()
|
|
84
|
+
.select()
|
|
85
|
+
.from(schema.contentDatabaseSourceRows)
|
|
86
|
+
.where(
|
|
87
|
+
and(
|
|
88
|
+
eq(schema.contentDatabaseSourceRows.sourceId, args.source.id),
|
|
89
|
+
or(...identityFilters),
|
|
90
|
+
),
|
|
91
|
+
);
|
|
92
|
+
const existingIds = new Set(args.source.rows.map((row) => row.id));
|
|
93
|
+
return {
|
|
94
|
+
...args.source,
|
|
95
|
+
rows: [
|
|
96
|
+
...args.source.rows,
|
|
97
|
+
...authoritativeRows
|
|
98
|
+
.filter((row) => !existingIds.has(row.id))
|
|
99
|
+
.map((row) =>
|
|
100
|
+
serializeSourceRowRecord(row, {
|
|
101
|
+
includeHeavyBuilderBodyValues: true,
|
|
102
|
+
}),
|
|
103
|
+
),
|
|
104
|
+
],
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const publicationTransitionSchema = z.object({
|
|
109
|
+
publicationTransition: z.enum(["publish", "unpublish"]).optional(),
|
|
110
|
+
confirmUnpublish: z.boolean().optional(),
|
|
111
|
+
});
|
|
112
|
+
|
|
33
113
|
function riskRank(level: ContentDatabaseSourceRiskLevel) {
|
|
34
114
|
if (level === "high") return 3;
|
|
35
115
|
if (level === "medium") return 2;
|
|
@@ -43,7 +123,9 @@ function maxRisk(
|
|
|
43
123
|
return riskRank(next) > riskRank(current) ? next : current;
|
|
44
124
|
}
|
|
45
125
|
|
|
46
|
-
function reviewPreparePriority(
|
|
126
|
+
export function reviewPreparePriority(
|
|
127
|
+
changeSet: ContentDatabaseSourceChangeSet,
|
|
128
|
+
) {
|
|
47
129
|
if (changeSet.state === "pending_push") return 0;
|
|
48
130
|
if (changeSet.state === "staged_revision") return 1;
|
|
49
131
|
return 2;
|
|
@@ -60,6 +142,40 @@ function parsePayload(value: string) {
|
|
|
60
142
|
}
|
|
61
143
|
}
|
|
62
144
|
|
|
145
|
+
function stableReviewValue(value: unknown): string {
|
|
146
|
+
if (value === null || typeof value !== "object") {
|
|
147
|
+
return JSON.stringify(value) ?? "undefined";
|
|
148
|
+
}
|
|
149
|
+
if (Array.isArray(value)) {
|
|
150
|
+
return `[${value.map(stableReviewValue).join(",")}]`;
|
|
151
|
+
}
|
|
152
|
+
const record = value as Record<string, unknown>;
|
|
153
|
+
return `{${Object.keys(record)
|
|
154
|
+
.sort()
|
|
155
|
+
.map((key) => `${JSON.stringify(key)}:${stableReviewValue(record[key])}`)
|
|
156
|
+
.join(",")}}`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function reviewedRevisionChangeSetId(
|
|
160
|
+
changeSet: ContentDatabaseSourceChangeSet,
|
|
161
|
+
) {
|
|
162
|
+
const revision = createHash("sha256")
|
|
163
|
+
.update(
|
|
164
|
+
stableReviewValue({
|
|
165
|
+
databaseItemId: changeSet.databaseItemId,
|
|
166
|
+
documentId: changeSet.documentId,
|
|
167
|
+
kind: changeSet.kind,
|
|
168
|
+
direction: "outbound",
|
|
169
|
+
pushMode: changeSet.pushMode ?? "autosave",
|
|
170
|
+
fieldChanges: changeSet.fieldChanges,
|
|
171
|
+
bodyChange: changeSet.bodyChange,
|
|
172
|
+
}),
|
|
173
|
+
)
|
|
174
|
+
.digest("hex")
|
|
175
|
+
.slice(0, 16);
|
|
176
|
+
return `${changeSet.id}-revision-${revision}`;
|
|
177
|
+
}
|
|
178
|
+
|
|
63
179
|
function dryRunStatus(execution: ContentDatabaseSourceExecution | null) {
|
|
64
180
|
const dryRun =
|
|
65
181
|
execution?.payload.dryRun &&
|
|
@@ -97,6 +213,10 @@ export function buildBuilderSourceReviewPayload(args: {
|
|
|
97
213
|
const changedTitle =
|
|
98
214
|
changeSet.fieldChanges.find((field) => field.localFieldKey === "title")
|
|
99
215
|
?.proposedValue ?? null;
|
|
216
|
+
const effect = resolveBuilderCmsWriteEffect({
|
|
217
|
+
source: args.source,
|
|
218
|
+
changeSet,
|
|
219
|
+
});
|
|
100
220
|
|
|
101
221
|
return {
|
|
102
222
|
changeSetId: changeSet.id,
|
|
@@ -106,15 +226,14 @@ export function buildBuilderSourceReviewPayload(args: {
|
|
|
106
226
|
typeof changedTitle === "string" && changedTitle.trim()
|
|
107
227
|
? changedTitle
|
|
108
228
|
: row?.sourceDisplayKey || "Untitled",
|
|
229
|
+
targetEntryId:
|
|
230
|
+
effect === "create_draft" ? null : (row?.sourceRowId ?? null),
|
|
109
231
|
fieldChanges: changeSet.fieldChanges,
|
|
110
232
|
bodyChange: changeSet.bodyChange,
|
|
111
233
|
riskLevel: changeSet.riskLevel,
|
|
112
234
|
riskReasons: changeSet.riskReasons,
|
|
113
235
|
conflictState: changeSet.conflictState,
|
|
114
|
-
effect
|
|
115
|
-
source: args.source,
|
|
116
|
-
changeSet,
|
|
117
|
-
}),
|
|
236
|
+
effect,
|
|
118
237
|
execution: latestExecution,
|
|
119
238
|
};
|
|
120
239
|
});
|
|
@@ -131,19 +250,22 @@ export function buildBuilderSourceReviewPayload(args: {
|
|
|
131
250
|
executionStates.length > 0 &&
|
|
132
251
|
executionStates.every((state) => state === "succeeded")
|
|
133
252
|
? "succeeded"
|
|
134
|
-
: executionStates.includes("
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
253
|
+
: executionStates.includes("reconciliation_required") ||
|
|
254
|
+
executionStates.includes("response_received")
|
|
255
|
+
? "reconciliation_required"
|
|
256
|
+
: executionStates.includes("failed")
|
|
257
|
+
? "failed"
|
|
258
|
+
: executionStates.includes("running")
|
|
259
|
+
? "running"
|
|
260
|
+
: statuses.includes("stale")
|
|
261
|
+
? "stale"
|
|
262
|
+
: statuses.includes("blocked")
|
|
263
|
+
? "blocked"
|
|
264
|
+
: statuses.includes("validated")
|
|
145
265
|
? "validated"
|
|
146
|
-
:
|
|
266
|
+
: args.source.capabilities.liveWritesEnabled
|
|
267
|
+
? "validated"
|
|
268
|
+
: "write_disabled";
|
|
147
269
|
const pushMode = args.source.metadata.pushMode ?? "autosave";
|
|
148
270
|
const summary =
|
|
149
271
|
rows.length === 1
|
|
@@ -202,17 +324,80 @@ async function approveChangeSetForReview(args: {
|
|
|
202
324
|
fieldChanges: args.changeSet.fieldChanges,
|
|
203
325
|
bodyChange: args.changeSet.bodyChange,
|
|
204
326
|
});
|
|
205
|
-
const existing = await findOpenSourceChangeSet({
|
|
206
|
-
sourceId: args.sourceId,
|
|
207
|
-
key,
|
|
208
|
-
states: ["pending_push", "staged_revision", "approved"],
|
|
209
|
-
});
|
|
210
327
|
const db = getDb();
|
|
328
|
+
const [selectedExisting] = await db
|
|
329
|
+
.select()
|
|
330
|
+
.from(schema.contentDatabaseSourceChangeSets)
|
|
331
|
+
.where(
|
|
332
|
+
and(
|
|
333
|
+
eq(schema.contentDatabaseSourceChangeSets.sourceId, args.sourceId),
|
|
334
|
+
eq(schema.contentDatabaseSourceChangeSets.id, args.changeSet.id),
|
|
335
|
+
),
|
|
336
|
+
)
|
|
337
|
+
.limit(1);
|
|
338
|
+
const existing =
|
|
339
|
+
selectedExisting &&
|
|
340
|
+
(selectedExisting.state === "pending_push" ||
|
|
341
|
+
selectedExisting.state === "staged_revision" ||
|
|
342
|
+
selectedExisting.state === "approved")
|
|
343
|
+
? selectedExisting
|
|
344
|
+
: await findOpenSourceChangeSet({
|
|
345
|
+
sourceId: args.sourceId,
|
|
346
|
+
key,
|
|
347
|
+
states: ["pending_push", "staged_revision", "approved"],
|
|
348
|
+
});
|
|
211
349
|
const summary = args.changeSet.summary
|
|
212
350
|
.replace(/^Pending local Builder CMS/, "Reviewing local Builder CMS")
|
|
213
351
|
.replace(/^Staged local-only Builder CMS/, "Reviewing local Builder CMS");
|
|
352
|
+
const approvedChangeSet = (id: string): ContentDatabaseSourceChangeSet => ({
|
|
353
|
+
...args.changeSet,
|
|
354
|
+
id,
|
|
355
|
+
direction: "outbound",
|
|
356
|
+
state: "approved",
|
|
357
|
+
pushMode: args.changeSet.pushMode ?? "autosave",
|
|
358
|
+
localOnly: true,
|
|
359
|
+
summary,
|
|
360
|
+
updatedAt: args.now,
|
|
361
|
+
});
|
|
214
362
|
|
|
215
363
|
if (existing) {
|
|
364
|
+
const existingExecutions = await db
|
|
365
|
+
.select({
|
|
366
|
+
state: schema.contentDatabaseSourceExecutions.state,
|
|
367
|
+
payloadJson: schema.contentDatabaseSourceExecutions.payloadJson,
|
|
368
|
+
attemptToken: schema.contentDatabaseSourceExecutions.attemptToken,
|
|
369
|
+
})
|
|
370
|
+
.from(schema.contentDatabaseSourceExecutions)
|
|
371
|
+
.where(
|
|
372
|
+
and(
|
|
373
|
+
eq(schema.contentDatabaseSourceExecutions.sourceId, args.sourceId),
|
|
374
|
+
eq(schema.contentDatabaseSourceExecutions.changeSetId, existing.id),
|
|
375
|
+
),
|
|
376
|
+
);
|
|
377
|
+
const mayRefreshApprovedPayload =
|
|
378
|
+
canRefreshLocallyBlockedBuilderReview(existingExecutions);
|
|
379
|
+
|
|
380
|
+
// Once dispatch may have happened, the approved payload is evidence. Keep
|
|
381
|
+
// it byte-for-byte and let reconciliation decide what can happen next.
|
|
382
|
+
if (existing.state === "approved" && !mayRefreshApprovedPayload) {
|
|
383
|
+
return {
|
|
384
|
+
id: existing.id,
|
|
385
|
+
state: "approved" as const,
|
|
386
|
+
changeSet: {
|
|
387
|
+
...approvedChangeSet(existing.id),
|
|
388
|
+
summary: existing.summary,
|
|
389
|
+
fieldChanges: JSON.parse(
|
|
390
|
+
existing.fieldChangesJson,
|
|
391
|
+
) as ContentDatabaseSourceChangeSet["fieldChanges"],
|
|
392
|
+
bodyChange: existing.bodyChangeJson
|
|
393
|
+
? (JSON.parse(
|
|
394
|
+
existing.bodyChangeJson,
|
|
395
|
+
) as ContentDatabaseSourceChangeSet["bodyChange"])
|
|
396
|
+
: null,
|
|
397
|
+
updatedAt: existing.updatedAt,
|
|
398
|
+
},
|
|
399
|
+
};
|
|
400
|
+
}
|
|
216
401
|
await db
|
|
217
402
|
.update(schema.contentDatabaseSourceChangeSets)
|
|
218
403
|
.set({
|
|
@@ -242,10 +427,23 @@ async function approveChangeSetForReview(args: {
|
|
|
242
427
|
createdAt: args.now,
|
|
243
428
|
});
|
|
244
429
|
}
|
|
245
|
-
return
|
|
430
|
+
return {
|
|
431
|
+
id: existing.id,
|
|
432
|
+
state: "approved" as const,
|
|
433
|
+
changeSet: approvedChangeSet(existing.id),
|
|
434
|
+
};
|
|
246
435
|
}
|
|
247
436
|
|
|
248
|
-
|
|
437
|
+
// Local Builder diffs are materialized with deterministic IDs (for example,
|
|
438
|
+
// `local-pending-create-*`) before they have a persisted change-set row. Keep
|
|
439
|
+
// that exact identity on first approval so the prepared review matches the
|
|
440
|
+
// row the operator selected. A cancelled/rejected/applied audit row is
|
|
441
|
+
// immutable, though, and may already own that category-level synthetic ID.
|
|
442
|
+
// Bind a materially changed follow-up to a deterministic payload revision so
|
|
443
|
+
// its review, execution, and idempotency evidence cannot alias the old row.
|
|
444
|
+
const changeSetId = selectedExisting
|
|
445
|
+
? reviewedRevisionChangeSetId(args.changeSet)
|
|
446
|
+
: args.changeSet.id;
|
|
249
447
|
await db.insert(schema.contentDatabaseSourceChangeSets).values({
|
|
250
448
|
id: changeSetId,
|
|
251
449
|
ownerEmail: args.ownerEmail,
|
|
@@ -277,7 +475,11 @@ async function approveChangeSetForReview(args: {
|
|
|
277
475
|
note: "Approved by Builder update review.",
|
|
278
476
|
createdAt: args.now,
|
|
279
477
|
});
|
|
280
|
-
return
|
|
478
|
+
return {
|
|
479
|
+
id: changeSetId,
|
|
480
|
+
state: "approved" as const,
|
|
481
|
+
changeSet: approvedChangeSet(changeSetId),
|
|
482
|
+
};
|
|
281
483
|
}
|
|
282
484
|
|
|
283
485
|
async function upsertExecutionGate(args: {
|
|
@@ -297,20 +499,28 @@ async function upsertExecutionGate(args: {
|
|
|
297
499
|
confirmUnpublish: args.confirmUnpublish,
|
|
298
500
|
});
|
|
299
501
|
const db = getDb();
|
|
502
|
+
let executionId = await claimBuilderSourceExecutionGate({
|
|
503
|
+
ownerEmail: args.ownerEmail,
|
|
504
|
+
sourceId: args.source.id,
|
|
505
|
+
idempotencyKey: plan.idempotencyKey,
|
|
506
|
+
now: args.now,
|
|
507
|
+
});
|
|
300
508
|
const [existing] = await db
|
|
301
509
|
.select()
|
|
302
510
|
.from(schema.contentDatabaseSourceExecutions)
|
|
303
|
-
.where(
|
|
304
|
-
and(
|
|
305
|
-
eq(
|
|
306
|
-
schema.contentDatabaseSourceExecutions.idempotencyKey,
|
|
307
|
-
plan.idempotencyKey,
|
|
308
|
-
),
|
|
309
|
-
eq(schema.contentDatabaseSourceExecutions.sourceId, args.source.id),
|
|
310
|
-
),
|
|
311
|
-
);
|
|
511
|
+
.where(eq(schema.contentDatabaseSourceExecutions.id, executionId));
|
|
312
512
|
|
|
313
|
-
|
|
513
|
+
if (
|
|
514
|
+
existing &&
|
|
515
|
+
(shouldPreserveBuilderExecution({
|
|
516
|
+
state: existing.state,
|
|
517
|
+
payloadJson: existing.payloadJson,
|
|
518
|
+
}) ||
|
|
519
|
+
existing.state === "failed" ||
|
|
520
|
+
Boolean(existing.attemptToken))
|
|
521
|
+
) {
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
314
524
|
if (existing) {
|
|
315
525
|
await db
|
|
316
526
|
.update(schema.contentDatabaseSourceExecutions)
|
|
@@ -323,21 +533,41 @@ async function upsertExecutionGate(args: {
|
|
|
323
533
|
})
|
|
324
534
|
.where(eq(schema.contentDatabaseSourceExecutions.id, existing.id));
|
|
325
535
|
} else {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
536
|
+
try {
|
|
537
|
+
await db.insert(schema.contentDatabaseSourceExecutions).values({
|
|
538
|
+
id: executionId,
|
|
539
|
+
ownerEmail: args.ownerEmail,
|
|
540
|
+
sourceId: args.source.id,
|
|
541
|
+
changeSetId: args.changeSet.id,
|
|
542
|
+
adapter: plan.adapter,
|
|
543
|
+
pushMode: plan.pushMode,
|
|
544
|
+
state: plan.state,
|
|
545
|
+
idempotencyKey: plan.idempotencyKey,
|
|
546
|
+
summary: plan.summary,
|
|
547
|
+
payloadJson: JSON.stringify(plan.payload),
|
|
548
|
+
lastError: plan.lastError,
|
|
549
|
+
createdAt: args.now,
|
|
550
|
+
updatedAt: args.now,
|
|
551
|
+
});
|
|
552
|
+
} catch (error) {
|
|
553
|
+
const [winner] = await db
|
|
554
|
+
.select()
|
|
555
|
+
.from(schema.contentDatabaseSourceExecutions)
|
|
556
|
+
.where(eq(schema.contentDatabaseSourceExecutions.id, executionId))
|
|
557
|
+
.limit(1);
|
|
558
|
+
if (!winner) throw error;
|
|
559
|
+
if (
|
|
560
|
+
shouldPreserveBuilderExecution({
|
|
561
|
+
state: winner.state,
|
|
562
|
+
payloadJson: winner.payloadJson,
|
|
563
|
+
}) ||
|
|
564
|
+
winner.state === "failed" ||
|
|
565
|
+
Boolean(winner.attemptToken)
|
|
566
|
+
) {
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
executionId = winner.id;
|
|
570
|
+
}
|
|
341
571
|
}
|
|
342
572
|
|
|
343
573
|
const [execution] = await db
|
|
@@ -375,139 +605,202 @@ async function upsertExecutionGate(args: {
|
|
|
375
605
|
export default defineAction({
|
|
376
606
|
description:
|
|
377
607
|
"Prepare one local Builder CMS review payload from pending outbound changes. This approves, prepares, and validates a dry-run plan, but never calls Builder APIs.",
|
|
378
|
-
schema: z
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
608
|
+
schema: z
|
|
609
|
+
.object({
|
|
610
|
+
databaseId: z.string().optional().describe("Database ID"),
|
|
611
|
+
documentId: z.string().optional().describe("Database document/page ID"),
|
|
612
|
+
sourceId: z
|
|
613
|
+
.string()
|
|
614
|
+
.optional()
|
|
615
|
+
.describe("Target source ID (defaults to the primary source)"),
|
|
616
|
+
changeSetIds: z
|
|
617
|
+
.array(z.string())
|
|
618
|
+
.max(BUILDER_SOURCE_REVIEW_PREPARE_LIMIT)
|
|
619
|
+
.optional()
|
|
620
|
+
.describe("Optional bounded set of Builder change-set IDs to prepare"),
|
|
621
|
+
pushModeConfirmation: z
|
|
622
|
+
.enum(["autosave", "draft", "publish"])
|
|
623
|
+
.optional()
|
|
624
|
+
.describe("Explicit push mode confirmation for the planned write"),
|
|
625
|
+
publicationTransition: z
|
|
626
|
+
.enum(["publish", "unpublish"])
|
|
627
|
+
.optional()
|
|
628
|
+
.describe("Explicit publication transition to validate at write time"),
|
|
629
|
+
confirmUnpublish: z
|
|
630
|
+
.boolean()
|
|
631
|
+
.optional()
|
|
632
|
+
.describe("Required explicit confirmation for unpublish transitions"),
|
|
633
|
+
transitions: z
|
|
634
|
+
.record(z.string(), publicationTransitionSchema)
|
|
635
|
+
.optional()
|
|
636
|
+
.describe(
|
|
637
|
+
"Bounded publication transition intents keyed by selected Builder change-set ID",
|
|
638
|
+
),
|
|
639
|
+
})
|
|
640
|
+
.superRefine((value, ctx) => {
|
|
641
|
+
if (
|
|
642
|
+
Object.keys(value.transitions ?? {}).length >
|
|
643
|
+
BUILDER_SOURCE_REVIEW_PREPARE_LIMIT
|
|
644
|
+
) {
|
|
645
|
+
ctx.addIssue({
|
|
646
|
+
code: z.ZodIssueCode.custom,
|
|
647
|
+
path: ["transitions"],
|
|
648
|
+
message: `Too many publication transitions; maximum is ${BUILDER_SOURCE_REVIEW_PREPARE_LIMIT}.`,
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
}),
|
|
403
652
|
run: async (
|
|
404
653
|
args: PrepareBuilderSourceReviewRequest,
|
|
405
654
|
): Promise<PrepareBuilderSourceReviewResponse> => {
|
|
406
|
-
const
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
changeSet.direction === "outbound" &&
|
|
421
|
-
(changeSet.state === "pending_push" ||
|
|
422
|
-
changeSet.state === "staged_revision" ||
|
|
423
|
-
changeSet.state === "approved") &&
|
|
424
|
-
(requestedIds.size === 0 || requestedIds.has(changeSet.id)),
|
|
425
|
-
);
|
|
426
|
-
if (
|
|
427
|
-
requestedIds.size > 0 &&
|
|
428
|
-
allReviewableChanges.length !== requestedIds.size
|
|
429
|
-
) {
|
|
430
|
-
const foundIds = new Set(
|
|
431
|
-
allReviewableChanges.map((changeSet) => changeSet.id),
|
|
655
|
+
const timing = createBuilderSourceTiming("prepare_builder_source_review");
|
|
656
|
+
try {
|
|
657
|
+
const { database, snapshot } = await timing.measure(
|
|
658
|
+
"snapshot_read_and_diff_load",
|
|
659
|
+
async () => {
|
|
660
|
+
const database = await resolveDatabaseForSourceMutation(args);
|
|
661
|
+
if (!database) throw new Error("Database not found.");
|
|
662
|
+
await assertAccess("document", database.documentId, "editor");
|
|
663
|
+
const snapshot = await getContentDatabaseSourceSnapshotForWrite(
|
|
664
|
+
database,
|
|
665
|
+
args.sourceId,
|
|
666
|
+
);
|
|
667
|
+
return { database, snapshot };
|
|
668
|
+
},
|
|
432
669
|
);
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
670
|
+
if (!snapshot || snapshot.sourceType !== "builder-cms") {
|
|
671
|
+
throw new Error(
|
|
672
|
+
"Attach a Builder CMS source before reviewing updates.",
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
const requestedIds = new Set(args.changeSetIds ?? []);
|
|
676
|
+
const transitionEntries = Object.entries(args.transitions ?? {}) as Array<
|
|
677
|
+
[string, ExecuteBuilderSourceBatchTransition]
|
|
678
|
+
>;
|
|
679
|
+
const foreignTransitionIds = transitionEntries
|
|
680
|
+
.map(([changeSetId]) => changeSetId)
|
|
681
|
+
.filter((changeSetId) => !requestedIds.has(changeSetId));
|
|
682
|
+
if (foreignTransitionIds.length > 0) {
|
|
683
|
+
throw new Error(
|
|
684
|
+
`Publication transition does not belong to the requested Builder selection: ${foreignTransitionIds.join(", ")}.`,
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
const allReviewableChanges = snapshot.changeSets.filter(
|
|
688
|
+
(changeSet) =>
|
|
689
|
+
changeSet.direction === "outbound" &&
|
|
690
|
+
(changeSet.state === "pending_push" ||
|
|
691
|
+
changeSet.state === "staged_revision" ||
|
|
692
|
+
changeSet.state === "approved") &&
|
|
693
|
+
(requestedIds.size === 0 || requestedIds.has(changeSet.id)),
|
|
436
694
|
);
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
695
|
+
if (
|
|
696
|
+
requestedIds.size > 0 &&
|
|
697
|
+
allReviewableChanges.length !== requestedIds.size
|
|
698
|
+
) {
|
|
699
|
+
const foundIds = new Set(
|
|
700
|
+
allReviewableChanges.map((changeSet) => changeSet.id),
|
|
701
|
+
);
|
|
702
|
+
const missingIds = [...requestedIds].filter((id) => !foundIds.has(id));
|
|
703
|
+
throw new Error(
|
|
704
|
+
`Requested Builder change-set is not reviewable: ${missingIds.join(", ")}.`,
|
|
705
|
+
);
|
|
706
|
+
}
|
|
707
|
+
if (allReviewableChanges.length === 0) {
|
|
708
|
+
throw new Error("No pending local Builder changes to review.");
|
|
709
|
+
}
|
|
710
|
+
const reviewableChanges = [...allReviewableChanges]
|
|
711
|
+
.sort((a, b) => reviewPreparePriority(a) - reviewPreparePriority(b))
|
|
712
|
+
.slice(0, BUILDER_SOURCE_REVIEW_PREPARE_LIMIT);
|
|
713
|
+
const authoritativeSnapshot = await withAuthoritativeBuilderTargetRows({
|
|
714
|
+
source: snapshot,
|
|
715
|
+
changeSets: reviewableChanges,
|
|
716
|
+
});
|
|
444
717
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
718
|
+
const approvalStartedAt = timing.start();
|
|
719
|
+
const now = new Date().toISOString();
|
|
720
|
+
const reviewerEmail =
|
|
721
|
+
getRequestUserEmail() ?? "agent-runtime@agent-native.local";
|
|
722
|
+
const approvedIds: string[] = [];
|
|
723
|
+
const preparedChangeSetMappings: PrepareBuilderSourceReviewResponse["preparedChangeSetMappings"] =
|
|
724
|
+
[];
|
|
725
|
+
for (const changeSet of reviewableChanges) {
|
|
726
|
+
const approved = await approveChangeSetForReview({
|
|
452
727
|
sourceId: snapshot.id,
|
|
453
728
|
ownerEmail: database.ownerEmail,
|
|
454
729
|
changeSet,
|
|
455
730
|
reviewerEmail,
|
|
456
731
|
now,
|
|
457
|
-
})
|
|
732
|
+
});
|
|
733
|
+
approvedIds.push(approved.id);
|
|
734
|
+
preparedChangeSetMappings.push({
|
|
735
|
+
requestedChangeSetId: changeSet.id,
|
|
736
|
+
preparedChangeSetId: approved.id,
|
|
737
|
+
});
|
|
738
|
+
const transition =
|
|
739
|
+
args.transitions?.[changeSet.id] ?? args.transitions?.[approved.id];
|
|
740
|
+
await upsertExecutionGate({
|
|
741
|
+
source: authoritativeSnapshot,
|
|
742
|
+
changeSet: approved.changeSet,
|
|
743
|
+
pushModeConfirmation: args.pushModeConfirmation,
|
|
744
|
+
publicationTransition:
|
|
745
|
+
transition?.publicationTransition ?? args.publicationTransition,
|
|
746
|
+
confirmUnpublish:
|
|
747
|
+
transition?.confirmUnpublish ?? args.confirmUnpublish,
|
|
748
|
+
ownerEmail: database.ownerEmail,
|
|
749
|
+
now,
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
await getDb()
|
|
754
|
+
.update(schema.contentDatabaseSources)
|
|
755
|
+
.set({ updatedAt: now })
|
|
756
|
+
.where(eq(schema.contentDatabaseSources.id, snapshot.id));
|
|
757
|
+
timing.record(
|
|
758
|
+
"approval_gate_preparation_and_dry_run_validation",
|
|
759
|
+
approvalStartedAt,
|
|
458
760
|
);
|
|
459
|
-
}
|
|
460
761
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
762
|
+
const { reviewedSnapshot, response } = await timing.measure(
|
|
763
|
+
"reconciliation_and_response_load",
|
|
764
|
+
async () => ({
|
|
765
|
+
reviewedSnapshot: await getContentDatabaseSourceSnapshotForWrite(
|
|
766
|
+
database,
|
|
767
|
+
args.sourceId,
|
|
768
|
+
),
|
|
769
|
+
response: await getContentDatabaseResponse(database.id),
|
|
770
|
+
}),
|
|
771
|
+
);
|
|
772
|
+
if (!reviewedSnapshot) throw new Error("Builder source disappeared.");
|
|
773
|
+
// Build the review payload from the TARGET source snapshot, not
|
|
774
|
+
// response.source (which is always the primary). Re-read after the gate
|
|
775
|
+
// upsert so newly validated/blocked/stale execution rows are visible to
|
|
776
|
+
// the returned review payload.
|
|
777
|
+
const reviewedChangeSets = reviewedSnapshot.changeSets.filter(
|
|
778
|
+
(changeSet) => approvedIds.includes(changeSet.id),
|
|
779
|
+
);
|
|
780
|
+
const authoritativeReviewedSnapshot =
|
|
781
|
+
await withAuthoritativeBuilderTargetRows({
|
|
782
|
+
source: reviewedSnapshot,
|
|
783
|
+
changeSets: reviewedChangeSets,
|
|
784
|
+
});
|
|
785
|
+
const review = buildBuilderSourceReviewPayload({
|
|
786
|
+
source: authoritativeReviewedSnapshot,
|
|
787
|
+
changeSets: reviewedChangeSets,
|
|
479
788
|
});
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
await getDb()
|
|
483
|
-
.update(schema.contentDatabaseSources)
|
|
484
|
-
.set({ updatedAt: now })
|
|
485
|
-
.where(eq(schema.contentDatabaseSources.id, snapshot.id));
|
|
486
|
-
|
|
487
|
-
const reviewedSnapshot = await getContentDatabaseSourceSnapshotForWrite(
|
|
488
|
-
database,
|
|
489
|
-
args.sourceId,
|
|
490
|
-
);
|
|
491
|
-
if (!reviewedSnapshot) throw new Error("Builder source disappeared.");
|
|
492
|
-
// Build the review payload from the TARGET source snapshot, not
|
|
493
|
-
// response.source (which is always the primary). Re-read after the gate
|
|
494
|
-
// upsert so newly validated/blocked/stale execution rows are visible to
|
|
495
|
-
// the returned review payload.
|
|
496
|
-
const reviewedChangeSets = reviewedSnapshot.changeSets.filter((changeSet) =>
|
|
497
|
-
approvedIds.includes(changeSet.id),
|
|
498
|
-
);
|
|
499
|
-
const response = await getContentDatabaseResponse(database.id);
|
|
500
|
-
|
|
501
|
-
const review = buildBuilderSourceReviewPayload({
|
|
502
|
-
source: reviewedSnapshot,
|
|
503
|
-
changeSets: reviewedChangeSets,
|
|
504
|
-
});
|
|
505
|
-
review.totalRowCount = allReviewableChanges.length;
|
|
506
|
-
review.preparedRowLimit = BUILDER_SOURCE_REVIEW_PREPARE_LIMIT;
|
|
789
|
+
review.totalRowCount = allReviewableChanges.length;
|
|
790
|
+
review.preparedRowLimit = reviewableChanges.length;
|
|
507
791
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
792
|
+
const result = {
|
|
793
|
+
...response,
|
|
794
|
+
review,
|
|
795
|
+
preparedChangeSetMappings,
|
|
796
|
+
timings: timing.finish(),
|
|
797
|
+
};
|
|
798
|
+
timing.log("succeeded");
|
|
799
|
+
return result;
|
|
800
|
+
} catch (error) {
|
|
801
|
+
timing.ensure("approval_gate_preparation_and_dry_run_validation");
|
|
802
|
+
timing.log("failed");
|
|
803
|
+
throw error;
|
|
804
|
+
}
|
|
512
805
|
},
|
|
513
806
|
});
|