@agent-native/core 0.80.5 → 0.80.7
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 +13 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/cli/create.ts +1 -0
- package/corpus/core/src/client/extensions/EmbeddedExtension.tsx +45 -0
- package/corpus/core/src/sharing/registry.ts +17 -0
- package/corpus/core/src/templates/default/pnpm-workspace.yaml +1 -0
- package/corpus/core/src/templates/workspace-root/pnpm-workspace.yaml +1 -0
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +42 -4
- package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +2 -1
- package/corpus/templates/analytics/actions/update-dashboard.ts +23 -9
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +72 -1
- package/corpus/templates/analytics/app/i18n/zh-TW.ts +2 -0
- package/corpus/templates/analytics/app/i18n-data.ts +34 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +88 -2
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +7 -1
- package/corpus/templates/analytics/changelog/2026-06-29-dashboards-can-now-include-extension-panels-that-embed-a-san.md +6 -0
- package/corpus/templates/content/AGENTS.md +45 -44
- package/corpus/templates/content/README.md +16 -0
- package/corpus/templates/content/actions/_builder-cms-read-client.ts +125 -0
- package/corpus/templates/content/actions/_builder-cms-source-adapter.ts +28 -6
- package/corpus/templates/content/actions/_builder-cms-write-adapter.ts +238 -69
- package/corpus/templates/content/actions/_builder-cms-write-settings.ts +125 -43
- package/corpus/templates/content/actions/_database-source-utils.ts +746 -97
- package/corpus/templates/content/actions/_database-utils.ts +17 -14
- package/corpus/templates/content/actions/_federation-join.ts +14 -2
- package/corpus/templates/content/actions/_join-suggestion.ts +89 -14
- package/corpus/templates/content/actions/_local-file-documents.ts +2 -1
- package/corpus/templates/content/actions/attach-content-database-source.ts +124 -5
- package/corpus/templates/content/actions/bind-content-database-source-field.ts +256 -0
- package/corpus/templates/content/actions/change-content-database-source-role.ts +420 -0
- package/corpus/templates/content/actions/create-document.ts +8 -0
- package/corpus/templates/content/actions/execute-builder-source-batch.ts +282 -0
- package/corpus/templates/content/actions/execute-builder-source-execution.ts +220 -8
- package/corpus/templates/content/actions/get-content-database.ts +10 -2
- package/corpus/templates/content/actions/prepare-builder-source-execution.ts +19 -2
- package/corpus/templates/content/actions/prepare-builder-source-review.ts +47 -15
- package/corpus/templates/content/actions/refresh-content-database-source.ts +11 -4
- package/corpus/templates/content/actions/review-content-database-source-change-set.ts +6 -2
- package/corpus/templates/content/actions/set-content-database-source-write-mode.ts +32 -12
- package/corpus/templates/content/actions/stage-builder-revision.ts +14 -10
- package/corpus/templates/content/actions/validate-builder-source-execution.ts +25 -3
- package/corpus/templates/content/app/components/editor/DocumentDatabase.tsx +1520 -553
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +150 -1
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +647 -66
- package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +543 -204
- package/corpus/templates/content/app/hooks/use-content-database.ts +62 -0
- package/corpus/templates/content/app/i18n/zh-TW.ts +64 -0
- package/corpus/templates/content/app/i18n-data.ts +784 -0
- package/corpus/templates/content/changelog/2026-06-29-builder-review-checks-now-preserve-publish-and-unpublish-cho.md +6 -0
- package/corpus/templates/content/changelog/2026-06-29-database-sources-can-now-be-added-as-more-rows-or-matched.md +6 -0
- package/corpus/templates/content/changelog/2026-06-29-fixed-loading-the-final-rows-in-larger-databases-without-bre.md +6 -0
- package/corpus/templates/content/shared/api.ts +91 -1
- package/corpus/templates/content/vite.config.ts +14 -7
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +1 -0
- package/dist/cli/create.js.map +1 -1
- package/dist/client/extensions/EmbeddedExtension.d.ts +10 -1
- package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
- package/dist/client/extensions/EmbeddedExtension.js +34 -3
- package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
- package/dist/collab/routes.d.ts +2 -2
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +7 -7
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/sharing/registry.d.ts.map +1 -1
- package/dist/sharing/registry.js +11 -0
- package/dist/sharing/registry.js.map +1 -1
- package/dist/templates/default/pnpm-workspace.yaml +1 -0
- package/dist/templates/workspace-root/pnpm-workspace.yaml +1 -0
- package/package.json +1 -1
- package/src/templates/default/pnpm-workspace.yaml +1 -0
- package/src/templates/workspace-root/pnpm-workspace.yaml +1 -0
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
BuilderCmsPublicationTransitionIntent,
|
|
3
|
+
BuilderCmsWriteEffect,
|
|
2
4
|
ContentDatabaseSource,
|
|
3
5
|
ContentDatabaseSourceChangeSet,
|
|
4
6
|
ContentDatabaseSourceExecutionState,
|
|
5
7
|
ContentDatabaseSourcePushMode,
|
|
8
|
+
ContentDatabaseSourceWriteMode,
|
|
6
9
|
} from "../shared/api.js";
|
|
7
10
|
import { BUILDER_CMS_SAFE_WRITE_MODEL as SAFE_WRITE_MODEL } from "../shared/api.js";
|
|
8
11
|
import { builderCmsSourceRowIdentityState } from "./_builder-cms-source-adapter.js";
|
|
12
|
+
import { builderCmsPushModeForTier } from "./_builder-cms-write-settings.js";
|
|
9
13
|
|
|
10
|
-
export type
|
|
11
|
-
| "autosave_revision"
|
|
12
|
-
| "save_draft"
|
|
13
|
-
| "publish";
|
|
14
|
+
export type { BuilderCmsWriteEffect };
|
|
14
15
|
|
|
15
16
|
export interface BuilderCmsExecutionOperation {
|
|
16
17
|
sourceFieldKey: string;
|
|
@@ -24,7 +25,7 @@ export interface BuilderCmsExecutionPayload {
|
|
|
24
25
|
sourceTable: string;
|
|
25
26
|
changeSetId: string;
|
|
26
27
|
pushMode: ContentDatabaseSourcePushMode;
|
|
27
|
-
|
|
28
|
+
effect: BuilderCmsWriteEffect;
|
|
28
29
|
target: {
|
|
29
30
|
model: string;
|
|
30
31
|
entryId: string | null;
|
|
@@ -71,12 +72,107 @@ export function builderCmsExecutionIdempotencyKey(args: {
|
|
|
71
72
|
return `builder-cms:${args.sourceId}:${args.changeSetId}:${args.pushMode}`;
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
function
|
|
75
|
-
pushMode: ContentDatabaseSourcePushMode
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
75
|
+
function builderEffectForWrite(args: {
|
|
76
|
+
pushMode: ContentDatabaseSourcePushMode;
|
|
77
|
+
writeMode?: ContentDatabaseSourceWriteMode | null;
|
|
78
|
+
entryId: string | null;
|
|
79
|
+
publicationTransition?: BuilderCmsPublicationTransitionIntent | null;
|
|
80
|
+
}): BuilderCmsWriteEffect {
|
|
81
|
+
if (!args.entryId) return "create_draft";
|
|
82
|
+
if (args.publicationTransition === "publish") return "publish";
|
|
83
|
+
if (args.publicationTransition === "unpublish") return "unpublish";
|
|
84
|
+
if (args.writeMode === "stage_only") return "autosave";
|
|
85
|
+
if (args.writeMode === "publish_updates") return "update_in_place";
|
|
86
|
+
if (args.pushMode === "autosave") return "autosave";
|
|
87
|
+
return "update_in_place";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function normalizeSourceWriteMode(
|
|
91
|
+
value: unknown,
|
|
92
|
+
): ContentDatabaseSourceWriteMode | null {
|
|
93
|
+
return value === "read_only" ||
|
|
94
|
+
value === "stage_only" ||
|
|
95
|
+
value === "publish_updates"
|
|
96
|
+
? value
|
|
97
|
+
: null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Single source of truth for the push mode that gates an execution. Prepare and
|
|
102
|
+
* execute MUST resolve this identically, or their idempotency keys diverge and
|
|
103
|
+
* the gate lookup fails ("Prepare the Builder execution gate before executing
|
|
104
|
+
* it"). The write tier wins when set, so a change-set's own `pushMode` (e.g. a
|
|
105
|
+
* local create hardcoded to "autosave") cannot drift from the tier.
|
|
106
|
+
*/
|
|
107
|
+
export function resolveBuilderCmsExecutionPushMode(args: {
|
|
108
|
+
source: ContentDatabaseSource;
|
|
109
|
+
changeSet: ContentDatabaseSourceChangeSet;
|
|
110
|
+
}): ContentDatabaseSourcePushMode {
|
|
111
|
+
const sourceWriteMode = normalizeSourceWriteMode(
|
|
112
|
+
args.source.metadata.writeMode,
|
|
113
|
+
);
|
|
114
|
+
if (sourceWriteMode) {
|
|
115
|
+
return builderCmsPushModeForTier(sourceWriteMode);
|
|
116
|
+
}
|
|
117
|
+
return args.changeSet.pushMode ?? args.source.metadata.pushMode ?? "autosave";
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Resolve the Builder entry this change-set targets. A synthetic-fixture row
|
|
122
|
+
* (sourceRowId `builder-<documentId>`, never matched to a real entry) resolves
|
|
123
|
+
* to a null entry id, which is what makes the effect a create.
|
|
124
|
+
*/
|
|
125
|
+
export function resolveBuilderCmsWriteTarget(args: {
|
|
126
|
+
source: ContentDatabaseSource;
|
|
127
|
+
changeSet: ContentDatabaseSourceChangeSet;
|
|
128
|
+
}) {
|
|
129
|
+
const targetRow =
|
|
130
|
+
args.source.rows.find(
|
|
131
|
+
(row) =>
|
|
132
|
+
row.documentId === args.changeSet.documentId ||
|
|
133
|
+
row.databaseItemId === args.changeSet.databaseItemId,
|
|
134
|
+
) ?? null;
|
|
135
|
+
const target = targetRow
|
|
136
|
+
? builderCmsSourceRowIdentityState({ row: targetRow })
|
|
137
|
+
: null;
|
|
138
|
+
const entryId = target?.isSyntheticFixture
|
|
139
|
+
? null
|
|
140
|
+
: (target?.sourceRowId ?? null);
|
|
141
|
+
const sourceQualifiedId = target?.isSyntheticFixture
|
|
142
|
+
? null
|
|
143
|
+
: (target?.sourceQualifiedId ?? null);
|
|
144
|
+
return { targetRow, target, entryId, sourceQualifiedId };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* The resolved write effect (create_draft / update_in_place / autosave /
|
|
149
|
+
* publish / unpublish) for a change-set. Unlike buildBuilderCmsExecutionPlan
|
|
150
|
+
* this does not require the change-set to be approved, so it is safe to call
|
|
151
|
+
* while building review payloads for plain-language labels.
|
|
152
|
+
*/
|
|
153
|
+
export function resolveBuilderCmsWriteEffect(args: {
|
|
154
|
+
source: ContentDatabaseSource;
|
|
155
|
+
changeSet: ContentDatabaseSourceChangeSet;
|
|
156
|
+
publicationTransition?: BuilderCmsPublicationTransitionIntent | null;
|
|
157
|
+
}): BuilderCmsWriteEffect {
|
|
158
|
+
const sourceWriteMode = normalizeSourceWriteMode(
|
|
159
|
+
args.source.metadata.writeMode,
|
|
160
|
+
);
|
|
161
|
+
const pushMode = resolveBuilderCmsExecutionPushMode({
|
|
162
|
+
source: args.source,
|
|
163
|
+
changeSet: args.changeSet,
|
|
164
|
+
});
|
|
165
|
+
const effectivePushMode = pushMode === "none" ? "autosave" : pushMode;
|
|
166
|
+
const { entryId } = resolveBuilderCmsWriteTarget({
|
|
167
|
+
source: args.source,
|
|
168
|
+
changeSet: args.changeSet,
|
|
169
|
+
});
|
|
170
|
+
return builderEffectForWrite({
|
|
171
|
+
pushMode: effectivePushMode,
|
|
172
|
+
writeMode: sourceWriteMode,
|
|
173
|
+
entryId,
|
|
174
|
+
publicationTransition: args.publicationTransition,
|
|
175
|
+
});
|
|
80
176
|
}
|
|
81
177
|
|
|
82
178
|
function nestedBuilderPatch(
|
|
@@ -98,15 +194,15 @@ function nestedBuilderPatch(
|
|
|
98
194
|
return body;
|
|
99
195
|
}
|
|
100
196
|
|
|
101
|
-
function
|
|
102
|
-
|
|
197
|
+
function builderRequestForEffect(args: {
|
|
198
|
+
effect: BuilderCmsWriteEffect;
|
|
103
199
|
model: string;
|
|
104
200
|
entryId: string | null;
|
|
105
201
|
bodyPatch: Record<string, unknown>;
|
|
106
202
|
}): BuilderCmsExecutionPayload["request"] {
|
|
107
203
|
const entryPath = args.entryId ? `/${encodeURIComponent(args.entryId)}` : "";
|
|
108
204
|
const basePath = `/api/v1/write/${encodeURIComponent(args.model)}${entryPath}`;
|
|
109
|
-
if (args.
|
|
205
|
+
if (args.effect === "autosave") {
|
|
110
206
|
return {
|
|
111
207
|
method: "PATCH",
|
|
112
208
|
path: basePath,
|
|
@@ -117,12 +213,22 @@ function builderRequestForIntent(args: {
|
|
|
117
213
|
body: args.bodyPatch,
|
|
118
214
|
};
|
|
119
215
|
}
|
|
120
|
-
if (args.
|
|
216
|
+
if (args.effect === "update_in_place") {
|
|
217
|
+
return {
|
|
218
|
+
method: "PATCH",
|
|
219
|
+
path: basePath,
|
|
220
|
+
query: {
|
|
221
|
+
triggerWebhooks: "true",
|
|
222
|
+
},
|
|
223
|
+
body: args.bodyPatch,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
if (args.effect === "publish") {
|
|
121
227
|
return {
|
|
122
228
|
method: args.entryId ? "PATCH" : "POST",
|
|
123
229
|
path: basePath,
|
|
124
230
|
query: {
|
|
125
|
-
triggerWebhooks: "
|
|
231
|
+
triggerWebhooks: "true",
|
|
126
232
|
},
|
|
127
233
|
body: {
|
|
128
234
|
...args.bodyPatch,
|
|
@@ -130,8 +236,21 @@ function builderRequestForIntent(args: {
|
|
|
130
236
|
},
|
|
131
237
|
};
|
|
132
238
|
}
|
|
239
|
+
if (args.effect === "unpublish") {
|
|
240
|
+
return {
|
|
241
|
+
method: "PATCH",
|
|
242
|
+
path: basePath,
|
|
243
|
+
query: {
|
|
244
|
+
triggerWebhooks: "true",
|
|
245
|
+
},
|
|
246
|
+
body: {
|
|
247
|
+
...args.bodyPatch,
|
|
248
|
+
published: "draft",
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
}
|
|
133
252
|
return {
|
|
134
|
-
method:
|
|
253
|
+
method: "POST",
|
|
135
254
|
path: basePath,
|
|
136
255
|
query: {
|
|
137
256
|
triggerWebhooks: "false",
|
|
@@ -147,7 +266,9 @@ function builderSafetyChecks(args: {
|
|
|
147
266
|
source: ContentDatabaseSource;
|
|
148
267
|
changeSet: ContentDatabaseSourceChangeSet;
|
|
149
268
|
pushMode: ContentDatabaseSourcePushMode;
|
|
150
|
-
|
|
269
|
+
effect: BuilderCmsWriteEffect;
|
|
270
|
+
publicationTransition?: BuilderCmsPublicationTransitionIntent | null;
|
|
271
|
+
confirmUnpublish?: boolean;
|
|
151
272
|
entryId: string | null;
|
|
152
273
|
syntheticFixtureTarget: boolean;
|
|
153
274
|
operations: BuilderCmsExecutionOperation[];
|
|
@@ -155,7 +276,6 @@ function builderSafetyChecks(args: {
|
|
|
155
276
|
const checks = [
|
|
156
277
|
"Requires explicit approval before execution.",
|
|
157
278
|
"Uses the stored execution idempotency key.",
|
|
158
|
-
"Does not run while live Builder writes are disabled.",
|
|
159
279
|
];
|
|
160
280
|
const blockers: string[] = [];
|
|
161
281
|
|
|
@@ -165,28 +285,45 @@ function builderSafetyChecks(args: {
|
|
|
165
285
|
if (args.changeSet.bodyChange) {
|
|
166
286
|
blockers.push("Builder body diffs are not executable in this slice.");
|
|
167
287
|
}
|
|
168
|
-
if (args.
|
|
169
|
-
|
|
288
|
+
if (args.effect === "autosave" || args.effect === "update_in_place") {
|
|
289
|
+
const label = args.effect === "autosave" ? "Autosave" : "Update in place";
|
|
290
|
+
checks.push(
|
|
291
|
+
`${label} preserves publication state — no published field is sent.`,
|
|
292
|
+
);
|
|
170
293
|
if (args.syntheticFixtureTarget) {
|
|
171
294
|
blockers.push(
|
|
172
295
|
"This row is not matched to a Builder entry yet. Refresh or match a Builder row before pushing.",
|
|
173
296
|
);
|
|
174
297
|
} else if (!args.entryId) {
|
|
175
|
-
blockers.push(
|
|
298
|
+
blockers.push(`${label} requires an existing Builder entry ID.`);
|
|
176
299
|
}
|
|
177
300
|
}
|
|
178
|
-
if (args.
|
|
179
|
-
checks.push(
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
301
|
+
if (args.effect === "create_draft") {
|
|
302
|
+
checks.push(
|
|
303
|
+
"Create draft writes a new Builder entry with published state set to draft.",
|
|
304
|
+
);
|
|
305
|
+
// A create_draft target has no Builder entry by definition — that is the
|
|
306
|
+
// whole point of a create. The unmatched-row blocker only applies to
|
|
307
|
+
// effects that write to an existing entry (autosave / update_in_place).
|
|
308
|
+
}
|
|
309
|
+
if (args.effect === "publish") {
|
|
310
|
+
checks.push(
|
|
311
|
+
"Publish transition sets Builder published state to published.",
|
|
312
|
+
);
|
|
313
|
+
if (args.publicationTransition !== "publish") {
|
|
314
|
+
blockers.push("Publish requires an explicit publication transition.");
|
|
315
|
+
}
|
|
316
|
+
if (args.source.metadata.allowPublicationTransitions !== true) {
|
|
317
|
+
blockers.push("Publication transitions are not enabled for this source.");
|
|
184
318
|
}
|
|
185
319
|
}
|
|
186
|
-
if (args.
|
|
187
|
-
checks.push("
|
|
188
|
-
if (args.source.metadata.
|
|
189
|
-
blockers.push("
|
|
320
|
+
if (args.effect === "unpublish") {
|
|
321
|
+
checks.push("Unpublish transition sets Builder published state to draft.");
|
|
322
|
+
if (args.source.metadata.allowPublicationTransitions !== true) {
|
|
323
|
+
blockers.push("Publication transitions are not enabled for this source.");
|
|
324
|
+
}
|
|
325
|
+
if (args.confirmUnpublish !== true) {
|
|
326
|
+
blockers.push("Unpublish requires explicit confirmation.");
|
|
190
327
|
}
|
|
191
328
|
}
|
|
192
329
|
|
|
@@ -202,6 +339,18 @@ function builderSafetyChecks(args: {
|
|
|
202
339
|
`Live Builder writes are only allowed for ${SAFE_WRITE_MODEL}.`,
|
|
203
340
|
);
|
|
204
341
|
}
|
|
342
|
+
if (args.source.capabilities.liveWritesEnabled !== true) {
|
|
343
|
+
checks.push("Does not run while live Builder writes are disabled.");
|
|
344
|
+
if (
|
|
345
|
+
args.effect === "update_in_place" ||
|
|
346
|
+
args.effect === "publish" ||
|
|
347
|
+
args.effect === "unpublish"
|
|
348
|
+
) {
|
|
349
|
+
blockers.push(
|
|
350
|
+
`${args.effect} requires live Builder writes to be enabled.`,
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
205
354
|
|
|
206
355
|
return { checks, blockers };
|
|
207
356
|
}
|
|
@@ -210,6 +359,8 @@ export function buildBuilderCmsExecutionPlan(args: {
|
|
|
210
359
|
source: ContentDatabaseSource;
|
|
211
360
|
changeSet: ContentDatabaseSourceChangeSet;
|
|
212
361
|
pushModeConfirmation?: ContentDatabaseSourcePushMode | null;
|
|
362
|
+
publicationTransition?: BuilderCmsPublicationTransitionIntent | null;
|
|
363
|
+
confirmUnpublish?: boolean;
|
|
213
364
|
}): BuilderCmsExecutionPlan {
|
|
214
365
|
if (args.source.sourceType !== "builder-cms") {
|
|
215
366
|
throw new Error("Builder execution plans require a Builder CMS source.");
|
|
@@ -223,45 +374,56 @@ export function buildBuilderCmsExecutionPlan(args: {
|
|
|
223
374
|
);
|
|
224
375
|
}
|
|
225
376
|
|
|
226
|
-
const
|
|
227
|
-
args.
|
|
377
|
+
const sourceWriteMode = normalizeSourceWriteMode(
|
|
378
|
+
args.source.metadata.writeMode,
|
|
379
|
+
);
|
|
380
|
+
const pushMode = resolveBuilderCmsExecutionPushMode({
|
|
381
|
+
source: args.source,
|
|
382
|
+
changeSet: args.changeSet,
|
|
383
|
+
});
|
|
384
|
+
const effectivePushMode = pushMode === "none" ? "autosave" : pushMode;
|
|
228
385
|
if (pushMode === "none") {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
386
|
+
if (args.source.capabilities.liveWritesEnabled === true) {
|
|
387
|
+
throw new Error(
|
|
388
|
+
"Builder execution requires Autosave, Draft, or Publish push mode.",
|
|
389
|
+
);
|
|
390
|
+
}
|
|
232
391
|
}
|
|
233
|
-
if (
|
|
392
|
+
if (
|
|
393
|
+
pushMode !== "none" &&
|
|
394
|
+
args.pushModeConfirmation &&
|
|
395
|
+
args.pushModeConfirmation !== pushMode
|
|
396
|
+
) {
|
|
234
397
|
throw new Error(
|
|
235
398
|
`Push mode confirmation did not match approved change set: ${pushMode}.`,
|
|
236
399
|
);
|
|
237
400
|
}
|
|
238
401
|
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
:
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
: (target?.sourceRowId ?? null);
|
|
254
|
-
const targetSourceQualifiedId = target?.isSyntheticFixture
|
|
255
|
-
? null
|
|
256
|
-
: (target?.sourceQualifiedId ?? null);
|
|
402
|
+
const {
|
|
403
|
+
target,
|
|
404
|
+
entryId: targetEntryId,
|
|
405
|
+
sourceQualifiedId: targetSourceQualifiedId,
|
|
406
|
+
} = resolveBuilderCmsWriteTarget({
|
|
407
|
+
source: args.source,
|
|
408
|
+
changeSet: args.changeSet,
|
|
409
|
+
});
|
|
410
|
+
const effect = builderEffectForWrite({
|
|
411
|
+
pushMode: effectivePushMode,
|
|
412
|
+
writeMode: sourceWriteMode,
|
|
413
|
+
entryId: targetEntryId,
|
|
414
|
+
publicationTransition: args.publicationTransition,
|
|
415
|
+
});
|
|
257
416
|
const operations = args.changeSet.fieldChanges.map((field) => ({
|
|
258
417
|
sourceFieldKey: field.sourceFieldKey,
|
|
259
418
|
localFieldKey: field.localFieldKey,
|
|
260
419
|
value: field.proposedValue,
|
|
261
420
|
}));
|
|
262
421
|
const bodyPatch = nestedBuilderPatch(operations);
|
|
263
|
-
|
|
264
|
-
|
|
422
|
+
// State-preserving effects must not include `published` in the body. Builder
|
|
423
|
+
// PATCH preserves omitted publication state, so only transition/create effects
|
|
424
|
+
// are allowed to set it.
|
|
425
|
+
const request = builderRequestForEffect({
|
|
426
|
+
effect,
|
|
265
427
|
model: args.source.sourceTable,
|
|
266
428
|
entryId: targetEntryId,
|
|
267
429
|
bodyPatch,
|
|
@@ -269,8 +431,10 @@ export function buildBuilderCmsExecutionPlan(args: {
|
|
|
269
431
|
const safety = builderSafetyChecks({
|
|
270
432
|
source: args.source,
|
|
271
433
|
changeSet: args.changeSet,
|
|
272
|
-
pushMode,
|
|
273
|
-
|
|
434
|
+
pushMode: effectivePushMode,
|
|
435
|
+
effect,
|
|
436
|
+
publicationTransition: args.publicationTransition,
|
|
437
|
+
confirmUnpublish: args.confirmUnpublish,
|
|
274
438
|
entryId: targetEntryId,
|
|
275
439
|
syntheticFixtureTarget:
|
|
276
440
|
args.source.capabilities.liveWritesEnabled === true &&
|
|
@@ -284,17 +448,22 @@ export function buildBuilderCmsExecutionPlan(args: {
|
|
|
284
448
|
: args.source.capabilities.liveWritesEnabled === true
|
|
285
449
|
? "ready"
|
|
286
450
|
: "write_disabled";
|
|
451
|
+
// Key on the RAW resolved push mode (which may be "none" for a read-only
|
|
452
|
+
// tier), not the effective one. Collapsing "none" → "autosave" would let a
|
|
453
|
+
// read-only gate share a key with a stage-only gate for the same change-set,
|
|
454
|
+
// so enabling live writes could reuse a gate prepared under read-only.
|
|
287
455
|
const idempotencyKey = builderCmsExecutionIdempotencyKey({
|
|
288
456
|
sourceId: args.source.id,
|
|
289
457
|
changeSetId: args.changeSet.id,
|
|
290
458
|
pushMode,
|
|
291
459
|
});
|
|
460
|
+
const summaryMode = pushMode === "none" ? "read-only" : pushMode;
|
|
292
461
|
const summary =
|
|
293
462
|
state === "ready"
|
|
294
|
-
? `Prepared Builder ${
|
|
463
|
+
? `Prepared Builder ${summaryMode} execution. Ready to send to Builder.`
|
|
295
464
|
: state === "blocked"
|
|
296
|
-
? `Prepared Builder ${
|
|
297
|
-
: `Prepared Builder ${
|
|
465
|
+
? `Prepared Builder ${summaryMode} execution, but it is blocked: ${safety.blockers.join(" ")}`
|
|
466
|
+
: `Prepared Builder ${summaryMode} execution, but live writes are disabled.`;
|
|
298
467
|
const lastError =
|
|
299
468
|
state === "ready"
|
|
300
469
|
? null
|
|
@@ -304,7 +473,7 @@ export function buildBuilderCmsExecutionPlan(args: {
|
|
|
304
473
|
|
|
305
474
|
return {
|
|
306
475
|
adapter: "builder-cms",
|
|
307
|
-
pushMode,
|
|
476
|
+
pushMode: effectivePushMode,
|
|
308
477
|
state,
|
|
309
478
|
idempotencyKey,
|
|
310
479
|
summary,
|
|
@@ -313,7 +482,7 @@ export function buildBuilderCmsExecutionPlan(args: {
|
|
|
313
482
|
databaseId: args.source.databaseId,
|
|
314
483
|
sourceTable: args.source.sourceTable,
|
|
315
484
|
changeSetId: args.changeSet.id,
|
|
316
|
-
|
|
485
|
+
effect,
|
|
317
486
|
target: {
|
|
318
487
|
model: args.source.sourceTable,
|
|
319
488
|
entryId: targetEntryId,
|
|
@@ -321,7 +490,7 @@ export function buildBuilderCmsExecutionPlan(args: {
|
|
|
321
490
|
documentId: args.changeSet.documentId,
|
|
322
491
|
databaseItemId: args.changeSet.databaseItemId,
|
|
323
492
|
},
|
|
324
|
-
pushMode,
|
|
493
|
+
pushMode: effectivePushMode,
|
|
325
494
|
request,
|
|
326
495
|
operations,
|
|
327
496
|
safety: {
|
|
@@ -384,9 +553,9 @@ export function validateBuilderCmsExecutionDryRun(args: {
|
|
|
384
553
|
"Stored Builder operations no longer match the approved change.",
|
|
385
554
|
);
|
|
386
555
|
}
|
|
387
|
-
if (storedComparable.
|
|
556
|
+
if (storedComparable.effect !== planComparable.effect) {
|
|
388
557
|
mismatches.push(
|
|
389
|
-
"Stored Builder
|
|
558
|
+
"Stored Builder effect no longer matches the approved write mode.",
|
|
390
559
|
);
|
|
391
560
|
}
|
|
392
561
|
if (
|
|
@@ -414,7 +583,7 @@ export function validateBuilderCmsExecutionDryRun(args: {
|
|
|
414
583
|
validatedAt: args.now,
|
|
415
584
|
checks: [
|
|
416
585
|
"Rebuilt execution plan from current source state.",
|
|
417
|
-
"Compared request, operations,
|
|
586
|
+
"Compared request, operations, effect, and target against stored gate.",
|
|
418
587
|
"No Builder API call was made.",
|
|
419
588
|
],
|
|
420
589
|
mismatches,
|