@mvriu5/payload-ai 1.1.1 → 1.2.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.
@@ -1,8 +1,21 @@
1
1
  import { verifyActionProposal } from "../ai/proposalSigning.js";
2
2
  import { containsSensitiveData, redactSensitiveData } from "../ai/sensitiveData.js";
3
- import { getCollectionFields, getLocalizedRequiredFallbackData, isAuthCollection, normalizeAuthData, normalizeDataForFields } from "../payload/normalizeData.js";
3
+ import { getSchemaFields } from "../payload/normalizeData.js";
4
+ import { applyLocalizedRequiredFallbackToPreparedData, prepareProposalWriteData } from "../payload/proposalData.js";
4
5
  import { isCollectionActionAllowed } from "../payload/collectionPermissions.js";
5
- import { getDefaultLocale, getJSONLineKey, getOptionalNumber, hasLocalizedData, isActionProposal, isKnownGlobal, mergeData } from "../payload/shared.js";
6
+ import { getDefaultLocale, getJSONLineKey, getOptionalNumber, hasLocalizedData, isActionProposal, isKnownGlobal, isRecord, mergeData } from "../payload/shared.js";
7
+ import { getLogPreview, logHandlerEvent } from "../payload/logging.js";
8
+ const getProposalLogSummary = (proposal)=>({
9
+ action: proposal.action,
10
+ collection: "collection" in proposal ? proposal.collection : undefined,
11
+ hasData: "data" in proposal && Boolean(proposal.data),
12
+ hasLocalizedData: "localizedData" in proposal && Boolean(proposal.localizedData),
13
+ id: "id" in proposal ? proposal.id : undefined,
14
+ label: proposal.label,
15
+ locale: proposal.locale,
16
+ locales: "localizedData" in proposal && proposal.localizedData ? Object.keys(proposal.localizedData) : undefined,
17
+ slug: "slug" in proposal ? proposal.slug : undefined
18
+ });
6
19
  const getProposalMeta = (proposal)=>{
7
20
  if (!proposal) return {};
8
21
  return {
@@ -12,6 +25,12 @@ const getProposalMeta = (proposal)=>{
12
25
  slug: "slug" in proposal ? proposal.slug : undefined
13
26
  };
14
27
  };
28
+ const createApplyDebugPayload = ({ details, phase, proposal, reason })=>({
29
+ ...getProposalMeta(proposal),
30
+ details,
31
+ phase,
32
+ reason
33
+ });
15
34
  const getAppliedDocReference = (doc)=>{
16
35
  return doc?.id === undefined ? undefined : {
17
36
  id: doc.id
@@ -25,13 +44,6 @@ const isAllowedCollection = (req, collection, collections, action = "read")=>{
25
44
  slug: collection
26
45
  });
27
46
  };
28
- const applyLocalizedRequiredFallback = ({ data, fallbackSource, fields })=>{
29
- return mergeData(getLocalizedRequiredFallbackData({
30
- fields,
31
- source: fallbackSource,
32
- target: data
33
- }), data);
34
- };
35
47
  const countDiffChanges = ({ after, before })=>{
36
48
  const beforeLines = JSON.stringify(before, null, 2).split("\n");
37
49
  const afterLines = JSON.stringify(after, null, 2).split("\n");
@@ -175,39 +187,90 @@ const logAIChange = async ({ changeLogCollection, context, target, proposal, req
175
187
  }
176
188
  };
177
189
  export const createApplyActionHandler = (options = {})=>async (req)=>{
178
- if (!req.user) return Response.json({
179
- error: "Unauthorized"
180
- }, {
181
- status: 401
182
- });
190
+ const failApply = ({ debug, error, logMessage, proposal, status = 400 })=>{
191
+ logHandlerEvent(req, "warn", {
192
+ debug,
193
+ msg: logMessage,
194
+ proposal: getProposalMeta(proposal)
195
+ });
196
+ return Response.json({
197
+ debug,
198
+ error
199
+ }, {
200
+ status
201
+ });
202
+ };
203
+ if (!req.user) {
204
+ return failApply({
205
+ debug: createApplyDebugPayload({
206
+ phase: "authorization",
207
+ reason: "unauthorized"
208
+ }),
209
+ error: "Unauthorized",
210
+ logMessage: "AI apply blocked: unauthorized request",
211
+ status: 401
212
+ });
213
+ }
183
214
  const body = req.json ? await req.json().catch(()=>null) : null;
184
215
  const proposal = body?.proposal;
185
- if (!proposal) return Response.json({
186
- error: "Proposal is required"
187
- }, {
188
- status: 400
189
- });
190
- if (!verifyActionProposal(proposal)) return Response.json({
191
- error: "Proposal signature is invalid or expired."
192
- }, {
193
- status: 400
194
- });
195
- if (!isActionProposal(proposal)) return Response.json({
196
- error: "Proposal is invalid."
197
- }, {
198
- status: 400
199
- });
200
- if ("data" in proposal && proposal.data && containsSensitiveData(proposal.data)) return Response.json({
201
- error: "Proposal contains sensitive fields and cannot be applied."
202
- }, {
203
- status: 400
204
- });
205
- if (hasLocalizedData(proposal) && Object.values(proposal.localizedData).some((value)=>containsSensitiveData(value))) return Response.json({
206
- error: "Proposal contains sensitive fields and cannot be applied."
207
- }, {
208
- status: 400
209
- });
210
- let normalized;
216
+ if (!proposal) {
217
+ return failApply({
218
+ debug: createApplyDebugPayload({
219
+ phase: "apply_validation",
220
+ reason: "missing_proposal"
221
+ }),
222
+ error: "Proposal is required",
223
+ logMessage: "AI apply blocked: missing proposal payload"
224
+ });
225
+ }
226
+ if (!verifyActionProposal(proposal)) {
227
+ return failApply({
228
+ debug: createApplyDebugPayload({
229
+ phase: "apply_validation",
230
+ proposal,
231
+ reason: "invalid_signature"
232
+ }),
233
+ error: "Proposal signature is invalid or expired.",
234
+ logMessage: "AI apply blocked: invalid proposal signature",
235
+ proposal
236
+ });
237
+ }
238
+ if (!isActionProposal(proposal)) {
239
+ return failApply({
240
+ debug: createApplyDebugPayload({
241
+ phase: "apply_validation",
242
+ proposal,
243
+ reason: "invalid_proposal_shape"
244
+ }),
245
+ error: "Proposal is invalid.",
246
+ logMessage: "AI apply blocked: invalid proposal shape",
247
+ proposal
248
+ });
249
+ }
250
+ if ("data" in proposal && proposal.data && containsSensitiveData(proposal.data)) {
251
+ return failApply({
252
+ debug: createApplyDebugPayload({
253
+ phase: "apply_validation",
254
+ proposal,
255
+ reason: "sensitive_data_in_data"
256
+ }),
257
+ error: "Proposal contains sensitive fields and cannot be applied.",
258
+ logMessage: "AI apply blocked: sensitive data detected in proposal data",
259
+ proposal
260
+ });
261
+ }
262
+ if (hasLocalizedData(proposal) && Object.values(proposal.localizedData).some((value)=>containsSensitiveData(value))) {
263
+ return failApply({
264
+ debug: createApplyDebugPayload({
265
+ phase: "apply_validation",
266
+ proposal,
267
+ reason: "sensitive_data_in_localized_data"
268
+ }),
269
+ error: "Proposal contains sensitive fields and cannot be applied.",
270
+ logMessage: "AI apply blocked: sensitive data detected in localized proposal data",
271
+ proposal
272
+ });
273
+ }
211
274
  const logContext = {
212
275
  aiResponse: typeof body?.aiResponse === "string" && body.aiResponse.trim() ? body.aiResponse.trim() : undefined,
213
276
  prompt: typeof body?.prompt === "string" && body.prompt.trim() ? body.prompt.trim() : undefined,
@@ -217,19 +280,61 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
217
280
  totalTokens: getOptionalNumber(body.tokenUsage.totalTokens)
218
281
  } : undefined
219
282
  };
283
+ logHandlerEvent(req, "info", {
284
+ msg: "AI apply started",
285
+ promptPreview: getLogPreview(logContext.prompt),
286
+ proposal: getProposalLogSummary(proposal),
287
+ tokenUsage: logContext.tokenUsage
288
+ });
220
289
  try {
290
+ const inferenceText = logContext.prompt;
221
291
  const defaultLocale = getDefaultLocale(req);
222
292
  if (proposal.action === "updateGlobal") {
223
- if (!isKnownGlobal(req, proposal.slug)) return Response.json({
224
- error: "Unknown global"
225
- }, {
226
- status: 400
227
- });
293
+ if (!isKnownGlobal(req, proposal.slug)) {
294
+ return failApply({
295
+ debug: createApplyDebugPayload({
296
+ phase: "apply_validation",
297
+ proposal,
298
+ reason: "unknown_global"
299
+ }),
300
+ error: "Unknown global",
301
+ logMessage: "AI apply blocked: unknown global",
302
+ proposal
303
+ });
304
+ }
228
305
  const globalConfig = req.payload.config.globals?.find((global)=>global.slug === proposal.slug);
229
306
  if (hasLocalizedData(proposal)) {
230
307
  const beforeByLocale = {};
231
308
  const afterByLocale = {};
232
- const globalFields = globalConfig?.fields || [];
309
+ const globalFields = getSchemaFields({
310
+ fields: globalConfig?.fields || [],
311
+ slug: proposal.slug
312
+ });
313
+ const preparedData = prepareProposalWriteData({
314
+ collectionConfig: {
315
+ fields: globalConfig?.fields || [],
316
+ slug: proposal.slug
317
+ },
318
+ inferenceText,
319
+ label: proposal.label,
320
+ localizedData: proposal.localizedData,
321
+ mode: "update"
322
+ });
323
+ if (preparedData.issues.length > 0 || !preparedData.localizedData) {
324
+ return failApply({
325
+ debug: createApplyDebugPayload({
326
+ details: {
327
+ issues: preparedData.issues
328
+ },
329
+ phase: "apply_validation",
330
+ proposal,
331
+ reason: "invalid_global_write_shape"
332
+ }),
333
+ error: "Proposal is invalid.",
334
+ logMessage: "AI apply blocked: invalid global proposal data",
335
+ proposal
336
+ });
337
+ }
233
338
  const defaultLocaleDoc = defaultLocale ? await req.payload.findGlobal({
234
339
  depth: 2,
235
340
  fallbackLocale: false,
@@ -237,8 +342,7 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
237
342
  req,
238
343
  slug: proposal.slug
239
344
  }) : null;
240
- for (const [locale, localeData] of Object.entries(proposal.localizedData)){
241
- const localeNormalized = normalizeDataForFields(globalFields, localeData);
345
+ for (const [locale, localeData] of Object.entries(preparedData.localizedData)){
242
346
  const before = await req.payload.findGlobal({
243
347
  depth: 2,
244
348
  fallbackLocale: false,
@@ -246,10 +350,10 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
246
350
  req,
247
351
  slug: proposal.slug
248
352
  });
249
- const completedData = applyLocalizedRequiredFallback({
250
- data: localeNormalized.data,
353
+ const completedData = applyLocalizedRequiredFallbackToPreparedData({
251
354
  fallbackSource: locale === defaultLocale ? before : defaultLocaleDoc || before,
252
- fields: globalFields
355
+ fields: globalFields,
356
+ preparedData: localeData
253
357
  });
254
358
  await req.payload.updateGlobal({
255
359
  data: completedData,
@@ -271,13 +375,43 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
271
375
  before: beforeByLocale
272
376
  }
273
377
  });
378
+ logHandlerEvent(req, "info", {
379
+ changeLogged: Boolean(change),
380
+ locales: Object.keys(proposal.localizedData),
381
+ msg: "AI apply succeeded",
382
+ proposal: getProposalLogSummary(proposal)
383
+ });
274
384
  return Response.json({
275
385
  change,
276
386
  doc: undefined,
277
387
  status: "applied"
278
388
  });
279
389
  }
280
- normalized = normalizeDataForFields(globalConfig?.fields || [], proposal.data);
390
+ const preparedData = prepareProposalWriteData({
391
+ collectionConfig: {
392
+ fields: globalConfig?.fields || [],
393
+ slug: proposal.slug
394
+ },
395
+ data: proposal.data,
396
+ inferenceText,
397
+ label: proposal.label,
398
+ mode: "update"
399
+ });
400
+ if (preparedData.issues.length > 0 || !preparedData.data) {
401
+ return failApply({
402
+ debug: createApplyDebugPayload({
403
+ details: {
404
+ issues: preparedData.issues
405
+ },
406
+ phase: "apply_validation",
407
+ proposal,
408
+ reason: "invalid_global_write_shape"
409
+ }),
410
+ error: "Proposal is invalid.",
411
+ logMessage: "AI apply blocked: invalid global proposal data",
412
+ proposal
413
+ });
414
+ }
281
415
  const before = await req.payload.findGlobal({
282
416
  depth: 2,
283
417
  ...proposal.locale ? {
@@ -287,7 +421,7 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
287
421
  slug: proposal.slug
288
422
  });
289
423
  const doc = await req.payload.updateGlobal({
290
- data: normalized.data,
424
+ data: preparedData.data,
291
425
  ...proposal.locale ? {
292
426
  locale: proposal.locale
293
427
  } : {},
@@ -301,21 +435,34 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
301
435
  proposal,
302
436
  req,
303
437
  target: {
304
- after: mergeData(before, normalized.data),
438
+ after: mergeData(before, preparedData.data),
305
439
  before
306
440
  }
307
441
  });
442
+ logHandlerEvent(req, "info", {
443
+ changeLogged: Boolean(change),
444
+ locale: proposal.locale,
445
+ msg: "AI apply succeeded",
446
+ proposal: getProposalLogSummary(proposal)
447
+ });
308
448
  return Response.json({
309
449
  change,
310
450
  doc: getAppliedDocReference(doc),
311
451
  status: "applied"
312
452
  });
313
453
  }
314
- if (!isAllowedCollection(req, proposal.collection, options.collections, proposal.action)) return Response.json({
315
- error: "Unknown collection"
316
- }, {
317
- status: 400
318
- });
454
+ if (!isAllowedCollection(req, proposal.collection, options.collections, proposal.action)) {
455
+ return failApply({
456
+ debug: createApplyDebugPayload({
457
+ phase: "authorization",
458
+ proposal,
459
+ reason: "unknown_or_disallowed_collection"
460
+ }),
461
+ error: "Unknown collection",
462
+ logMessage: "AI apply blocked: unknown or disallowed collection",
463
+ proposal
464
+ });
465
+ }
319
466
  if (proposal.action === "delete") {
320
467
  const doc = await req.payload.delete({
321
468
  collection: proposal.collection,
@@ -334,6 +481,12 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
334
481
  documentID: proposal.id
335
482
  }
336
483
  });
484
+ logHandlerEvent(req, "info", {
485
+ changeLogged: Boolean(change),
486
+ documentID: proposal.id,
487
+ msg: "AI apply succeeded",
488
+ proposal: getProposalLogSummary(proposal)
489
+ });
337
490
  return Response.json({
338
491
  change,
339
492
  doc: getAppliedDocReference(doc),
@@ -341,35 +494,48 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
341
494
  });
342
495
  }
343
496
  const collectionConfig = req.payload.config.collections.find((collection)=>collection.slug === proposal.collection);
344
- const collectionFields = getCollectionFields(collectionConfig);
345
- const normalizeCollectionData = (data)=>normalizeAuthData(collectionConfig, normalizeDataForFields(collectionFields, data));
497
+ const collectionFields = getSchemaFields(collectionConfig);
346
498
  if (hasLocalizedData(proposal)) {
499
+ const preparedData = prepareProposalWriteData({
500
+ collectionConfig,
501
+ inferenceText,
502
+ label: proposal.label,
503
+ localizedData: proposal.localizedData,
504
+ mode: proposal.action
505
+ });
506
+ if (preparedData.issues.length > 0 || !preparedData.localizedData) {
507
+ return failApply({
508
+ debug: createApplyDebugPayload({
509
+ details: {
510
+ issues: preparedData.issues
511
+ },
512
+ phase: "apply_validation",
513
+ proposal,
514
+ reason: "invalid_collection_write_shape"
515
+ }),
516
+ error: "Proposal is invalid.",
517
+ logMessage: "AI apply blocked: invalid collection proposal data",
518
+ proposal
519
+ });
520
+ }
347
521
  if (proposal.action === "create") {
348
- const localeEntries = Object.entries(proposal.localizedData);
522
+ const localeEntries = Object.entries(preparedData.localizedData);
349
523
  const [firstLocale, firstLocaleData] = localeEntries[0] || [];
350
- if (!firstLocale) return Response.json({
351
- error: "Proposal is invalid."
352
- }, {
353
- status: 400
354
- });
355
- const firstNormalized = normalizeCollectionData(firstLocaleData);
356
- if (isAuthCollection(collectionConfig) && !firstNormalized.data.password) {
357
- return Response.json({
358
- error: "Password is required when creating a user."
359
- }, {
360
- status: 400
361
- });
362
- }
363
- if (isAuthCollection(collectionConfig) && !firstNormalized.data.email) {
364
- return Response.json({
365
- error: "Email is required when creating a user."
366
- }, {
367
- status: 400
524
+ if (!firstLocale) {
525
+ return failApply({
526
+ debug: createApplyDebugPayload({
527
+ phase: "apply_validation",
528
+ proposal,
529
+ reason: "localized_create_without_locales"
530
+ }),
531
+ error: "Proposal is invalid.",
532
+ logMessage: "AI apply blocked: localized create proposal has no locales",
533
+ proposal
368
534
  });
369
535
  }
370
536
  const doc = await req.payload.create({
371
537
  collection: proposal.collection,
372
- data: firstNormalized.data,
538
+ data: firstLocaleData,
373
539
  locale: firstLocale,
374
540
  overrideAccess: false,
375
541
  req
@@ -382,11 +548,10 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
382
548
  };
383
549
  let fallbackSource = doc;
384
550
  for (const [locale, localeData] of localeEntries.slice(1)){
385
- const localeNormalized = normalizeCollectionData(localeData);
386
- const completedData = applyLocalizedRequiredFallback({
387
- data: localeNormalized.data,
551
+ const completedData = applyLocalizedRequiredFallbackToPreparedData({
388
552
  fallbackSource,
389
- fields: collectionFields
553
+ fields: collectionFields,
554
+ preparedData: localeData
390
555
  });
391
556
  await req.payload.update({
392
557
  collection: proposal.collection,
@@ -410,6 +575,13 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
410
575
  documentID: doc.id
411
576
  }
412
577
  });
578
+ logHandlerEvent(req, "info", {
579
+ changeLogged: Boolean(change),
580
+ documentID: doc.id,
581
+ locales: localeEntries.map(([locale])=>locale),
582
+ msg: "AI apply succeeded",
583
+ proposal: getProposalLogSummary(proposal)
584
+ });
413
585
  return Response.json({
414
586
  change,
415
587
  doc: getAppliedDocReference(doc),
@@ -426,8 +598,7 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
426
598
  locale: defaultLocale,
427
599
  req
428
600
  }) : null;
429
- for (const [locale, localeData] of Object.entries(proposal.localizedData)){
430
- const localeNormalized = normalizeCollectionData(localeData);
601
+ for (const [locale, localeData] of Object.entries(preparedData.localizedData)){
431
602
  const before = await req.payload.findByID({
432
603
  collection: proposal.collection,
433
604
  depth: 2,
@@ -436,10 +607,10 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
436
607
  locale,
437
608
  req
438
609
  });
439
- const completedData = applyLocalizedRequiredFallback({
440
- data: localeNormalized.data,
610
+ const completedData = applyLocalizedRequiredFallbackToPreparedData({
441
611
  fallbackSource: locale === defaultLocale ? before : defaultLocaleDoc || before,
442
- fields: collectionFields
612
+ fields: collectionFields,
613
+ preparedData: localeData
443
614
  });
444
615
  await req.payload.update({
445
616
  collection: proposal.collection,
@@ -463,6 +634,13 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
463
634
  documentID: proposal.id
464
635
  }
465
636
  });
637
+ logHandlerEvent(req, "info", {
638
+ changeLogged: Boolean(change),
639
+ documentID: proposal.id,
640
+ locales: Object.keys(proposal.localizedData),
641
+ msg: "AI apply succeeded",
642
+ proposal: getProposalLogSummary(proposal)
643
+ });
466
644
  return Response.json({
467
645
  change,
468
646
  doc: {
@@ -471,25 +649,32 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
471
649
  status: "applied"
472
650
  });
473
651
  }
474
- normalized = normalizeCollectionData(proposal.data);
475
- if (proposal.action === "create" && isAuthCollection(collectionConfig) && !normalized.data.password) {
476
- return Response.json({
477
- error: "Password is required when creating a user."
478
- }, {
479
- status: 400
480
- });
481
- }
482
- if (proposal.action === "create" && isAuthCollection(collectionConfig) && !normalized.data.email) {
483
- return Response.json({
484
- error: "Email is required when creating a user."
485
- }, {
486
- status: 400
652
+ const preparedData = prepareProposalWriteData({
653
+ collectionConfig,
654
+ data: proposal.data,
655
+ inferenceText,
656
+ label: proposal.label,
657
+ mode: proposal.action
658
+ });
659
+ if (preparedData.issues.length > 0 || !preparedData.data) {
660
+ return failApply({
661
+ debug: createApplyDebugPayload({
662
+ details: {
663
+ issues: preparedData.issues
664
+ },
665
+ phase: "apply_validation",
666
+ proposal,
667
+ reason: "invalid_collection_write_shape"
668
+ }),
669
+ error: "Proposal is invalid.",
670
+ logMessage: "AI apply blocked: invalid collection proposal data",
671
+ proposal
487
672
  });
488
673
  }
489
674
  if (proposal.action === "create") {
490
675
  const doc = await req.payload.create({
491
676
  collection: proposal.collection,
492
- data: normalized.data,
677
+ data: preparedData.data,
493
678
  ...proposal.locale ? {
494
679
  locale: proposal.locale
495
680
  } : {},
@@ -507,6 +692,12 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
507
692
  documentID: doc.id
508
693
  }
509
694
  });
695
+ logHandlerEvent(req, "info", {
696
+ changeLogged: Boolean(change),
697
+ documentID: doc.id,
698
+ msg: "AI apply succeeded",
699
+ proposal: getProposalLogSummary(proposal)
700
+ });
510
701
  return Response.json({
511
702
  change,
512
703
  doc: getAppliedDocReference(doc),
@@ -524,7 +715,7 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
524
715
  });
525
716
  const doc = await req.payload.update({
526
717
  collection: proposal.collection,
527
- data: normalized.data,
718
+ data: preparedData.data,
528
719
  id: proposal.id,
529
720
  ...proposal.locale ? {
530
721
  locale: proposal.locale
@@ -538,23 +729,41 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
538
729
  proposal,
539
730
  req,
540
731
  target: {
541
- after: mergeData(before, normalized.data),
732
+ after: mergeData(before, preparedData.data),
542
733
  before,
543
734
  documentID: proposal.id
544
735
  }
545
736
  });
737
+ logHandlerEvent(req, "info", {
738
+ changeLogged: Boolean(change),
739
+ documentID: proposal.id,
740
+ msg: "AI apply succeeded",
741
+ proposal: getProposalLogSummary(proposal)
742
+ });
546
743
  return Response.json({
547
744
  change,
548
745
  doc: getAppliedDocReference(doc),
549
746
  status: "applied"
550
747
  });
551
748
  } catch (err) {
749
+ const debug = createApplyDebugPayload({
750
+ details: err && typeof err === "object" && "data" in err && isRecord(err.data) ? {
751
+ payloadError: err.data
752
+ } : undefined,
753
+ phase: "payload_operation",
754
+ proposal,
755
+ reason: "payload_operation_failed"
756
+ });
552
757
  req.payload.logger.error({
758
+ debug,
553
759
  err,
554
760
  msg: "AI apply action failed",
555
- proposal: getProposalMeta(proposal)
761
+ promptPreview: getLogPreview(logContext.prompt),
762
+ proposal: getProposalMeta(proposal),
763
+ tokenUsage: logContext.tokenUsage
556
764
  });
557
765
  return Response.json({
766
+ debug,
558
767
  error: "Could not apply proposal."
559
768
  }, {
560
769
  status: 400