@ted-galago/wave-cli 0.1.7 → 0.1.8

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/README.md CHANGED
@@ -105,16 +105,16 @@ wave lists create --data-json '{"name":"Weekly List"}'
105
105
  wave list-items create --data-json '{"list_id":"123","summary":"Follow up"}'
106
106
  wave todos create --data-json '{"todo_group_id":"55","name":"Send update","status":"open"}'
107
107
  wave knowledge create --data-json '{"content":{"name":"Runbook","content_type":"process","status":"draft","member_id":"67"}}'
108
- wave notes create-member --target-member-id 67 --name "1:1 Note" --body "Strong progress"
109
- wave notes create-manager --actor-member-id 67 --target-member-id 68 --name "Manager Note" --body "Coaching plan"
110
- wave notes create-team --actor-member-id 67 --team-id 9 --name "Team Note" --body "Team context"
111
- wave notes create-project --actor-member-id 67 --project-id 80 --name "Project Note" --body "Project context"
112
- wave notes create-customer --actor-member-id 67 --customer-id 30 --name "Customer Note" --body "Customer context"
113
- wave notes create-contact --actor-member-id 67 --contact-id 31 --name "Contact Note" --body "Contact context"
114
- wave notes list --contentable-type Member --contentable-id 67 --member-id 67 --page 1 --per 20
115
- wave notes show --id 1001
116
- wave notes update --id 1001 --name "Updated Note" --body "Updated body" --status published
117
- wave notes destroy --id 1001
108
+ wave content create-member --target-member-id 67 --name "1:1 Note" --body "Strong progress"
109
+ wave content create-manager --actor-member-id 67 --target-member-id 68 --name "Manager Note" --body "Coaching plan"
110
+ wave content create-team --actor-member-id 67 --team-id 9 --name "Team Note" --body "Team context"
111
+ wave content create-project --actor-member-id 67 --project-id 80 --name "Project Note" --body "Project context"
112
+ wave content create-customer --actor-member-id 67 --customer-id 30 --name "Customer Note" --body "Customer context"
113
+ wave content create-contact --actor-member-id 67 --contact-id 31 --name "Contact Note" --body "Contact context"
114
+ wave content list --contentable-type Member --contentable-id 67 --member-id 67 --page 1 --per 20
115
+ wave content show --id 1001
116
+ wave content update --id 1001 --name "Updated Note" --body "Updated body" --status published
117
+ wave content destroy --id 1001
118
118
  wave markdown-tree root --tree-view
119
119
  wave markdown-tree resolve --tool-key projects --node-key tasks --parent-id 123
120
120
  wave markdown-tree children --tool-key directory --node-key members --record-id 67
@@ -290,9 +290,9 @@ Ambiguity contract:
290
290
  - no silent bad guesses
291
291
  - returns `error.code = "ambiguous_match"` with ranked candidates in `error.details.candidates`
292
292
 
293
- ## Notes Contract
293
+ ## Content Command Contract
294
294
 
295
- The `notes` command uses exact GraphQL operation names:
295
+ The `content` command uses exact GraphQL operation names:
296
296
 
297
297
  - `CreateContent`
298
298
  - `UpdateContent`
@@ -302,31 +302,31 @@ The `notes` command uses exact GraphQL operation names:
302
302
 
303
303
  Placement rules enforced by command shape:
304
304
 
305
- - `notes create-member`
305
+ - `content create-member`
306
306
  - `contentable_type: "Member"`
307
307
  - `contentable_id: TARGET_MEMBER_ID`
308
308
  - `member_id: TARGET_MEMBER_ID`
309
309
  - `focus_member_id: null`
310
310
  - `focus_team_id: null`
311
- - `notes create-manager`
311
+ - `content create-manager`
312
312
  - `contentable_type: "Member"`
313
313
  - `contentable_id: ACTOR_MEMBER_ID`
314
314
  - `member_id: ACTOR_MEMBER_ID`
315
315
  - `focus_member_id: TARGET_MEMBER_ID`
316
- - `notes create-team`
316
+ - `content create-team`
317
317
  - `contentable_type: "Team"`
318
318
  - `contentable_id: TEAM_ID`
319
319
  - `member_id: ACTOR_MEMBER_ID`
320
320
  - `focus_team_id: TEAM_ID`
321
- - `notes create-project`
321
+ - `content create-project`
322
322
  - `contentable_type: "Project"`
323
323
  - `contentable_id: PROJECT_ID`
324
324
  - `member_id: ACTOR_MEMBER_ID`
325
- - `notes create-customer`
325
+ - `content create-customer`
326
326
  - `contentable_type: "Customer"`
327
327
  - `contentable_id: CUSTOMER_ID`
328
328
  - `member_id: ACTOR_MEMBER_ID`
329
- - `notes create-contact`
329
+ - `content create-contact`
330
330
  - `contentable_type: "Contact"`
331
331
  - `contentable_id: CONTACT_ID`
332
332
  - `member_id: ACTOR_MEMBER_ID`
package/dist/index.cjs CHANGED
@@ -1244,8 +1244,11 @@ function defaultQuerySelectionSet(field, isList) {
1244
1244
  const canonicalField = RESOURCE_FIELD_ALIASES[field] ?? field;
1245
1245
  const explicitFields = RESOURCE_QUERY_FIELDS[canonicalField];
1246
1246
  if (explicitFields) {
1247
+ if (isList) {
1248
+ return "{ data { id type attributes } count currentPage totalPages }";
1249
+ }
1247
1250
  const fields = Array.from(/* @__PURE__ */ new Set(["id", "type", ...explicitFields, "attributes"])).join(" ");
1248
- return isList ? `{ data { ${fields} } count currentPage totalPages }` : `{ ${fields} }`;
1251
+ return `{ ${fields} }`;
1249
1252
  }
1250
1253
  if (isList) {
1251
1254
  return "{ data { id type attributes } count currentPage totalPages }";
@@ -2551,7 +2554,8 @@ var __testables = {
2551
2554
  var idSchema3 = import_zod4.z.string().min(1);
2552
2555
  var projectCreateDataJsonHelp = buildDataJsonHelp("project", "create") ?? 'JSON object for project or {"project": {...}}';
2553
2556
  var projectUpdateDataJsonHelp = buildDataJsonHelp("project", "update") ?? 'JSON object for project or {"project": {...}}';
2554
- var projectsIndexFullSelectionSet = "{ count currentPage totalPages data { id type attributes { name slug description status priority createdAt updatedAt startDate targetDate archivedAt memberId organizationId teamIds favorites { id memberId } resources { ...ResourceSummaryFull } } name slug description status priority createdAt updatedAt startDate targetDate archivedAt memberId organizationId teamIds emoji { id name memberId } backgroundImage { backgroundType gradientStart gradientEnd gradientDegrees color imageUrl image { url lowUrl } } favorites { id memberId } resources { ...ResourceSummaryFull } displayPreference { id type attributes viewType favorite groupingField groupToggleState memberId focusMemberId focusTeamId displayableId displayableType preferenceType agendaItemId } filterPreferences { id type attributes filterType filter memberId focusMemberId focusTeamId filterableId filterableType preferenceType agendaItemId } healthUpdates { id type attributes value memberId status createdAt updatedAt updatableType updatableId parentUpdateId emojis { id name memberId } resources { ...ResourceSummaryFull } replies { id type attributes value memberId createdAt updatedAt commentType commentableId commentableType parentCommentId emojis { id name memberId } resources { ...ResourceSummaryFull } replies { id type } } } } } fragment ResourceSummaryFull on ResourceSummary { id resourceType slug url skillId skill { id name slug emoji scope description } metadata { urlName imageUrl siteName providerName mediaType skillId skillName skillSlug skillScope skillEmoji skillDescription } attachment { id fileKey filename url } content { id name slug type firstChildSlug childCount } }";
2557
+ var projectsIndexMinimalSelectionSet = "{ count currentPage totalPages data { id type attributes { name slug status priority } } }";
2558
+ var projectShowFullSelectionSet = "{ id type name slug description status priority createdAt updatedAt startDate targetDate archivedAt memberId organizationId teamIds emoji { id name memberId } backgroundImage { backgroundType gradientStart gradientEnd gradientDegrees color imageUrl image { url lowUrl } } favorites { id memberId } resources { ...ResourceSummaryFull } displayPreference { id type attributes viewType favorite groupingField groupToggleState memberId focusMemberId focusTeamId displayableId displayableType preferenceType agendaItemId } filterPreferences { id type attributes filterType filter memberId focusMemberId focusTeamId filterableId filterableType preferenceType agendaItemId } healthUpdates { id type value memberId status createdAt updatedAt updatableType updatableId parentUpdateId emojis { id name memberId } resources { ...ResourceSummaryFull } replies { id type value memberId createdAt updatedAt commentType commentableId commentableType parentCommentId emojis { id name memberId } resources { ...ResourceSummaryFull } replies { id type } } } } fragment ResourceSummaryFull on ResourceSummary { id resourceType slug url skillId skill { id name slug emoji scope description } metadata { urlName imageUrl siteName providerName mediaType skillId skillName skillSlug skillScope skillEmoji skillDescription } attachment { id fileKey filename url } content { id name slug type firstChildSlug childCount } }";
2555
2559
  function asRecord2(value) {
2556
2560
  return value && typeof value === "object" ? value : null;
2557
2561
  }
@@ -2590,7 +2594,7 @@ function registerProjectCommands(program) {
2590
2594
  const organizationId = resolveOrganizationId(context.organizationId);
2591
2595
  await runGraphqlQueryCommand({
2592
2596
  command: "projects.list",
2593
- operationName: "ProjectsIndexFull",
2597
+ operationName: "ProjectsIndexSlim",
2594
2598
  runtimeOptions: context.runtimeOptions,
2595
2599
  field: "projects",
2596
2600
  variables: normalizeGraphqlVariables2({
@@ -2608,7 +2612,7 @@ function registerProjectCommands(program) {
2608
2612
  member_id: "ID"
2609
2613
  },
2610
2614
  isList: true,
2611
- selectionSet: projectsIndexFullSelectionSet,
2615
+ selectionSet: projectsIndexMinimalSelectionSet,
2612
2616
  transformData: normalizeProjectsListForDisplay
2613
2617
  });
2614
2618
  });
@@ -2618,6 +2622,7 @@ function registerProjectCommands(program) {
2618
2622
  const organizationId = resolveOrganizationId(context.organizationId);
2619
2623
  await runGraphqlQueryCommand({
2620
2624
  command: "projects.show",
2625
+ operationName: "ProjectShowDeepV2",
2621
2626
  runtimeOptions: context.runtimeOptions,
2622
2627
  field: "project",
2623
2628
  variables: {
@@ -2625,7 +2630,7 @@ function registerProjectCommands(program) {
2625
2630
  id
2626
2631
  },
2627
2632
  isShow: true,
2628
- selectionSet: "{ id type name }",
2633
+ selectionSet: projectShowFullSelectionSet,
2629
2634
  transformData: normalizeProjectRowForDisplay
2630
2635
  });
2631
2636
  });
package/dist/index.js CHANGED
@@ -1243,8 +1243,11 @@ function defaultQuerySelectionSet(field, isList) {
1243
1243
  const canonicalField = RESOURCE_FIELD_ALIASES[field] ?? field;
1244
1244
  const explicitFields = RESOURCE_QUERY_FIELDS[canonicalField];
1245
1245
  if (explicitFields) {
1246
+ if (isList) {
1247
+ return "{ data { id type attributes } count currentPage totalPages }";
1248
+ }
1246
1249
  const fields = Array.from(/* @__PURE__ */ new Set(["id", "type", ...explicitFields, "attributes"])).join(" ");
1247
- return isList ? `{ data { ${fields} } count currentPage totalPages }` : `{ ${fields} }`;
1250
+ return `{ ${fields} }`;
1248
1251
  }
1249
1252
  if (isList) {
1250
1253
  return "{ data { id type attributes } count currentPage totalPages }";
@@ -2550,7 +2553,8 @@ var __testables = {
2550
2553
  var idSchema3 = z4.string().min(1);
2551
2554
  var projectCreateDataJsonHelp = buildDataJsonHelp("project", "create") ?? 'JSON object for project or {"project": {...}}';
2552
2555
  var projectUpdateDataJsonHelp = buildDataJsonHelp("project", "update") ?? 'JSON object for project or {"project": {...}}';
2553
- var projectsIndexFullSelectionSet = "{ count currentPage totalPages data { id type attributes { name slug description status priority createdAt updatedAt startDate targetDate archivedAt memberId organizationId teamIds favorites { id memberId } resources { ...ResourceSummaryFull } } name slug description status priority createdAt updatedAt startDate targetDate archivedAt memberId organizationId teamIds emoji { id name memberId } backgroundImage { backgroundType gradientStart gradientEnd gradientDegrees color imageUrl image { url lowUrl } } favorites { id memberId } resources { ...ResourceSummaryFull } displayPreference { id type attributes viewType favorite groupingField groupToggleState memberId focusMemberId focusTeamId displayableId displayableType preferenceType agendaItemId } filterPreferences { id type attributes filterType filter memberId focusMemberId focusTeamId filterableId filterableType preferenceType agendaItemId } healthUpdates { id type attributes value memberId status createdAt updatedAt updatableType updatableId parentUpdateId emojis { id name memberId } resources { ...ResourceSummaryFull } replies { id type attributes value memberId createdAt updatedAt commentType commentableId commentableType parentCommentId emojis { id name memberId } resources { ...ResourceSummaryFull } replies { id type } } } } } fragment ResourceSummaryFull on ResourceSummary { id resourceType slug url skillId skill { id name slug emoji scope description } metadata { urlName imageUrl siteName providerName mediaType skillId skillName skillSlug skillScope skillEmoji skillDescription } attachment { id fileKey filename url } content { id name slug type firstChildSlug childCount } }";
2556
+ var projectsIndexMinimalSelectionSet = "{ count currentPage totalPages data { id type attributes { name slug status priority } } }";
2557
+ var projectShowFullSelectionSet = "{ id type name slug description status priority createdAt updatedAt startDate targetDate archivedAt memberId organizationId teamIds emoji { id name memberId } backgroundImage { backgroundType gradientStart gradientEnd gradientDegrees color imageUrl image { url lowUrl } } favorites { id memberId } resources { ...ResourceSummaryFull } displayPreference { id type attributes viewType favorite groupingField groupToggleState memberId focusMemberId focusTeamId displayableId displayableType preferenceType agendaItemId } filterPreferences { id type attributes filterType filter memberId focusMemberId focusTeamId filterableId filterableType preferenceType agendaItemId } healthUpdates { id type value memberId status createdAt updatedAt updatableType updatableId parentUpdateId emojis { id name memberId } resources { ...ResourceSummaryFull } replies { id type value memberId createdAt updatedAt commentType commentableId commentableType parentCommentId emojis { id name memberId } resources { ...ResourceSummaryFull } replies { id type } } } } fragment ResourceSummaryFull on ResourceSummary { id resourceType slug url skillId skill { id name slug emoji scope description } metadata { urlName imageUrl siteName providerName mediaType skillId skillName skillSlug skillScope skillEmoji skillDescription } attachment { id fileKey filename url } content { id name slug type firstChildSlug childCount } }";
2554
2558
  function asRecord2(value) {
2555
2559
  return value && typeof value === "object" ? value : null;
2556
2560
  }
@@ -2589,7 +2593,7 @@ function registerProjectCommands(program) {
2589
2593
  const organizationId = resolveOrganizationId(context.organizationId);
2590
2594
  await runGraphqlQueryCommand({
2591
2595
  command: "projects.list",
2592
- operationName: "ProjectsIndexFull",
2596
+ operationName: "ProjectsIndexSlim",
2593
2597
  runtimeOptions: context.runtimeOptions,
2594
2598
  field: "projects",
2595
2599
  variables: normalizeGraphqlVariables2({
@@ -2607,7 +2611,7 @@ function registerProjectCommands(program) {
2607
2611
  member_id: "ID"
2608
2612
  },
2609
2613
  isList: true,
2610
- selectionSet: projectsIndexFullSelectionSet,
2614
+ selectionSet: projectsIndexMinimalSelectionSet,
2611
2615
  transformData: normalizeProjectsListForDisplay
2612
2616
  });
2613
2617
  });
@@ -2617,6 +2621,7 @@ function registerProjectCommands(program) {
2617
2621
  const organizationId = resolveOrganizationId(context.organizationId);
2618
2622
  await runGraphqlQueryCommand({
2619
2623
  command: "projects.show",
2624
+ operationName: "ProjectShowDeepV2",
2620
2625
  runtimeOptions: context.runtimeOptions,
2621
2626
  field: "project",
2622
2627
  variables: {
@@ -2624,7 +2629,7 @@ function registerProjectCommands(program) {
2624
2629
  id
2625
2630
  },
2626
2631
  isShow: true,
2627
- selectionSet: "{ id type name }",
2632
+ selectionSet: projectShowFullSelectionSet,
2628
2633
  transformData: normalizeProjectRowForDisplay
2629
2634
  });
2630
2635
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ted-galago/wave-cli",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "wave": "dist/index.js"
@@ -41,9 +41,27 @@ function runWave(args, env) {
41
41
  }
42
42
 
43
43
  function firstId(parsed, field) {
44
- const data = parsed?.data?.[field];
45
- if (data?.data?.[0]?.id) return String(data.data[0].id);
46
- if (data?.id) return String(data.id);
44
+ const root = parsed?.data;
45
+ if (!root || typeof root !== "object") {
46
+ return undefined;
47
+ }
48
+
49
+ const lookup = root;
50
+ const candidates = [];
51
+ if (field) {
52
+ candidates.push(field);
53
+ }
54
+ for (const key of Object.keys(lookup)) {
55
+ if (!candidates.includes(key)) {
56
+ candidates.push(key);
57
+ }
58
+ }
59
+
60
+ for (const key of candidates) {
61
+ const data = lookup[key];
62
+ if (data?.data?.[0]?.id) return String(data.data[0].id);
63
+ if (data?.id) return String(data.id);
64
+ }
47
65
  return undefined;
48
66
  }
49
67
 
@@ -99,6 +117,9 @@ function classify(result) {
99
117
  if (code === "invalid_args" && message.includes("Missing required create fields")) {
100
118
  return { ok: true, reason: "skipped_missing_parent_dependency" };
101
119
  }
120
+ if (result.parsed?.status === 422 && code === "VALIDATION_ERROR") {
121
+ return { ok: true, reason: "domain_validation_error" };
122
+ }
102
123
  if (code === "invalid_args") {
103
124
  return { ok: false, reason: code };
104
125
  }
@@ -223,6 +244,83 @@ function verifySubissueDeleteWithPolling(env, issueId, subIssueId) {
223
244
  return { ok: false, attempts, tries: 5 };
224
245
  }
225
246
 
247
+ function isValidationErrorResult(result) {
248
+ return result?.parsed?.status === 422 && result?.parsed?.error?.code === "VALIDATION_ERROR";
249
+ }
250
+
251
+ function extractValidationMessages(result) {
252
+ const errors = result?.parsed?.error?.details?.errors?.data;
253
+ if (!Array.isArray(errors)) return [];
254
+ return errors
255
+ .map((entry) => (typeof entry === "string" ? entry.trim() : ""))
256
+ .filter((entry) => entry.length > 0);
257
+ }
258
+
259
+ function startsWithArgs(args, prefix) {
260
+ if (!Array.isArray(args)) return false;
261
+ if (args.length < prefix.length) return false;
262
+ for (let i = 0; i < prefix.length; i += 1) {
263
+ if (args[i] !== prefix[i]) return false;
264
+ }
265
+ return true;
266
+ }
267
+
268
+ function findFirstResultByPrefix(results, prefix) {
269
+ return results.find((entry) => startsWithArgs(entry.args, prefix));
270
+ }
271
+
272
+ function cloneArgs(args) {
273
+ return Array.isArray(args) ? [...args] : [];
274
+ }
275
+
276
+ function parseDataJsonFromArgs(args) {
277
+ const idx = args.indexOf("--data-json");
278
+ if (idx < 0 || idx + 1 >= args.length) return null;
279
+ try {
280
+ const parsed = JSON.parse(String(args[idx + 1]));
281
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
282
+ return parsed;
283
+ } catch {
284
+ return null;
285
+ }
286
+ }
287
+
288
+ function withUpdatedDataJson(args, nextPayload) {
289
+ const idx = args.indexOf("--data-json");
290
+ if (idx < 0 || idx + 1 >= args.length) return args;
291
+ const next = cloneArgs(args);
292
+ next[idx + 1] = JSON.stringify(nextPayload);
293
+ return next;
294
+ }
295
+
296
+ function getArgValue(args, flag) {
297
+ const idx = args.indexOf(flag);
298
+ if (idx < 0 || idx + 1 >= args.length) return undefined;
299
+ return args[idx + 1];
300
+ }
301
+
302
+ function isoDatePlusDays(isoDate, days) {
303
+ const date = new Date(`${isoDate}T00:00:00Z`);
304
+ if (Number.isNaN(date.getTime())) return null;
305
+ date.setUTCDate(date.getUTCDate() + days);
306
+ return date.toISOString().slice(0, 10);
307
+ }
308
+
309
+ function summarizeStatus(result) {
310
+ const status = result?.parsed?.status ?? "n/a";
311
+ const err = result?.parsed?.error?.code ?? "none";
312
+ return `status=${status},err=${err}`;
313
+ }
314
+
315
+ function resolveAnnualStrategicObjectiveId(result) {
316
+ return firstDefinedValue(result?.parsed, [
317
+ ["data", "annual_objective", "attributes", "strategicObjectiveId"],
318
+ ["data", "annual_objective", "attributes", "strategic_objective_id"],
319
+ ["data", "annual_objective", "strategicObjectiveId"],
320
+ ["data", "annual_objective", "strategic_objective_id"]
321
+ ]);
322
+ }
323
+
226
324
  const root = process.cwd();
227
325
  const envFile = resolve(root, ".env");
228
326
  const dot = loadDotEnv(envFile);
@@ -484,6 +582,352 @@ for (const args of probes) {
484
582
  results.push(runWave(args, env));
485
583
  }
486
584
 
585
+ const validationRemediationEntries = [];
586
+ let validationRemediationFailures = 0;
587
+ let feedbackQuarterExpected = feedbackQuarterVerify;
588
+
589
+ function recordValidationRemediation(entry) {
590
+ validationRemediationEntries.push(entry);
591
+ }
592
+
593
+ function remediateStandupsCreateDuplicateDate() {
594
+ const operation = "stand-ups.create";
595
+ const initial = findFirstResultByPrefix(results, ["stand-ups", "create"]);
596
+ if (!initial) {
597
+ recordValidationRemediation({
598
+ operation,
599
+ offendingFields: ["stand_up.completed_date"],
600
+ backendMessage: "initial probe missing",
601
+ initialResult: "missing",
602
+ remediation: "none",
603
+ finalResult: "missing"
604
+ });
605
+ validationRemediationFailures += 1;
606
+ return;
607
+ }
608
+
609
+ const messages = extractValidationMessages(initial);
610
+ const isDuplicateDateValidation =
611
+ isValidationErrorResult(initial) &&
612
+ messages.some((msg) => /completed date.*already been taken/i.test(msg));
613
+
614
+ if (!isDuplicateDateValidation) {
615
+ recordValidationRemediation({
616
+ operation,
617
+ offendingFields: ["stand_up.completed_date"],
618
+ backendMessage: messages[0] ?? "",
619
+ initialResult: summarizeStatus(initial),
620
+ remediation: "none_not_required",
621
+ finalResult: summarizeStatus(initial)
622
+ });
623
+ return;
624
+ }
625
+
626
+ const initialPayload = parseDataJsonFromArgs(initial.args);
627
+ const standUpBody =
628
+ initialPayload && typeof initialPayload.stand_up === "object" ? initialPayload.stand_up : null;
629
+ const baseDate =
630
+ standUpBody && typeof standUpBody.completed_date === "string"
631
+ ? standUpBody.completed_date
632
+ : "2026-04-06";
633
+
634
+ if (!standUpBody || typeof standUpBody.member_id !== "string") {
635
+ recordValidationRemediation({
636
+ operation,
637
+ offendingFields: ["stand_up.member_id", "stand_up.completed_date"],
638
+ backendMessage: messages[0] ?? "",
639
+ initialResult: summarizeStatus(initial),
640
+ remediation: "failed_missing_member_or_payload",
641
+ finalResult: summarizeStatus(initial)
642
+ });
643
+ validationRemediationFailures += 1;
644
+ return;
645
+ }
646
+
647
+ const attemptedDates = [];
648
+ let finalResult = initial;
649
+ let remediation = "retry_create_with_next_date";
650
+ for (let offset = 1; offset <= 7; offset += 1) {
651
+ const candidateDate = isoDatePlusDays(baseDate, offset);
652
+ if (!candidateDate) continue;
653
+ attemptedDates.push(candidateDate);
654
+ const nextPayload = {
655
+ stand_up: {
656
+ ...standUpBody,
657
+ completed_date: candidateDate
658
+ }
659
+ };
660
+ const retryArgs = withUpdatedDataJson(initial.args, nextPayload);
661
+ const retryResult = runWave(retryArgs, env);
662
+ results.push(retryResult);
663
+ finalResult = retryResult;
664
+ if (retryResult.parsed?.ok === true) {
665
+ remediation = `retry_create_with_next_date_success base=${baseDate} chosen=${candidateDate}`;
666
+ break;
667
+ }
668
+ const retryMessages = extractValidationMessages(retryResult);
669
+ const stillDuplicate = retryMessages.some((msg) =>
670
+ /completed date.*already been taken/i.test(msg)
671
+ );
672
+ if (!stillDuplicate) {
673
+ remediation = `retry_create_with_next_date_stopped_non_duplicate_error at=${candidateDate}`;
674
+ break;
675
+ }
676
+ remediation = `retry_create_with_next_date_exhausting base=${baseDate}`;
677
+ }
678
+
679
+ recordValidationRemediation({
680
+ operation,
681
+ offendingFields: ["stand_up.completed_date"],
682
+ backendMessage: messages[0] ?? "",
683
+ initialResult: summarizeStatus(initial),
684
+ remediation: `${remediation};attempted_dates=${attemptedDates.join(",")}`,
685
+ finalResult: summarizeStatus(finalResult)
686
+ });
687
+
688
+ if (finalResult.parsed?.ok !== true) {
689
+ validationRemediationFailures += 1;
690
+ }
691
+ }
692
+
693
+ function remediateFeedbacksUpdateDuplicateName() {
694
+ const operation = "feedbacks.update";
695
+ const initial = findFirstResultByPrefix(results, ["feedbacks", "update"]);
696
+ if (!initial) {
697
+ recordValidationRemediation({
698
+ operation,
699
+ offendingFields: ["feedback.name"],
700
+ backendMessage: "initial probe missing",
701
+ initialResult: "missing",
702
+ remediation: "none",
703
+ finalResult: "missing"
704
+ });
705
+ validationRemediationFailures += 1;
706
+ return;
707
+ }
708
+
709
+ const messages = extractValidationMessages(initial);
710
+ const isDuplicateNameValidation =
711
+ isValidationErrorResult(initial) &&
712
+ messages.some((msg) => /name has already been taken/i.test(msg));
713
+
714
+ if (!isDuplicateNameValidation) {
715
+ recordValidationRemediation({
716
+ operation,
717
+ offendingFields: ["feedback.name"],
718
+ backendMessage: messages[0] ?? "",
719
+ initialResult: summarizeStatus(initial),
720
+ remediation: "none_not_required",
721
+ finalResult: summarizeStatus(initial)
722
+ });
723
+ return;
724
+ }
725
+
726
+ const initialPayload = parseDataJsonFromArgs(initial.args);
727
+ const feedbackBody =
728
+ initialPayload && typeof initialPayload.feedback === "object" ? initialPayload.feedback : null;
729
+ const originalName =
730
+ feedbackBody && typeof feedbackBody.name === "string" ? feedbackBody.name : "feedback";
731
+
732
+ if (!feedbackBody) {
733
+ recordValidationRemediation({
734
+ operation,
735
+ offendingFields: ["feedback.name"],
736
+ backendMessage: messages[0] ?? "",
737
+ initialResult: summarizeStatus(initial),
738
+ remediation: "failed_missing_feedback_payload",
739
+ finalResult: summarizeStatus(initial)
740
+ });
741
+ validationRemediationFailures += 1;
742
+ return;
743
+ }
744
+
745
+ const feedbackId = getArgValue(initial.args, "--id");
746
+ if (!feedbackId) {
747
+ recordValidationRemediation({
748
+ operation,
749
+ offendingFields: ["feedback.name", "feedback.year"],
750
+ backendMessage: messages[0] ?? "",
751
+ initialResult: summarizeStatus(initial),
752
+ remediation: "failed_missing_feedback_id",
753
+ finalResult: summarizeStatus(initial)
754
+ });
755
+ validationRemediationFailures += 1;
756
+ return;
757
+ }
758
+
759
+ const showArgs = ["feedbacks", "show", "--id", String(feedbackId)];
760
+ const showResult = runWave(showArgs, env);
761
+ results.push(showResult);
762
+
763
+ const currentName = firstDefinedValue(showResult?.parsed, [
764
+ ["data", "feedback", "attributes", "name"],
765
+ ["data", "feedback", "name"]
766
+ ]);
767
+ const currentQuarter = firstDefinedValue(showResult?.parsed, [
768
+ ["data", "feedback", "attributes", "quarter"],
769
+ ["data", "feedback", "quarter"]
770
+ ]);
771
+ const currentYear = firstDefinedValue(showResult?.parsed, [
772
+ ["data", "feedback", "attributes", "year"],
773
+ ["data", "feedback", "year"]
774
+ ]);
775
+
776
+ if (!showResult.parsed?.ok || !currentName || !currentQuarter) {
777
+ recordValidationRemediation({
778
+ operation,
779
+ offendingFields: ["feedback.name", "feedback.year"],
780
+ backendMessage: messages[0] ?? "",
781
+ initialResult: summarizeStatus(initial),
782
+ remediation: `failed_feedback_show_lookup id=${feedbackId};lookup=${summarizeStatus(showResult)}`,
783
+ finalResult: summarizeStatus(initial)
784
+ });
785
+ validationRemediationFailures += 1;
786
+ return;
787
+ }
788
+
789
+ const parsedYear = Number.parseInt(String(currentYear ?? ""), 10);
790
+ const remediatedYear = Number.isFinite(parsedYear) ? parsedYear + 1 : 2030;
791
+ const nextPayload = {
792
+ feedback: {
793
+ ...feedbackBody,
794
+ name: String(currentName),
795
+ quarter: String(currentQuarter),
796
+ year: String(remediatedYear)
797
+ }
798
+ };
799
+ const retryArgs = withUpdatedDataJson(initial.args, nextPayload);
800
+ const finalResult = runWave(retryArgs, env);
801
+ results.push(finalResult);
802
+
803
+ recordValidationRemediation({
804
+ operation,
805
+ offendingFields: ["feedback.name", "feedback.year"],
806
+ backendMessage: messages[0] ?? "",
807
+ initialResult: summarizeStatus(initial),
808
+ remediation:
809
+ `fallback_non_unique_field original_name=${originalName}` +
810
+ ` remediated_name=${String(currentName)}` +
811
+ ` remediated_quarter=${String(currentQuarter)}` +
812
+ ` remediated_year=${String(remediatedYear)}`,
813
+ finalResult: summarizeStatus(finalResult)
814
+ });
815
+
816
+ if (finalResult.parsed?.ok !== true) {
817
+ validationRemediationFailures += 1;
818
+ } else {
819
+ feedbackQuarterExpected = String(currentQuarter);
820
+ }
821
+ }
822
+
823
+ function remediateQuarterlyObjectiveParentMismatch() {
824
+ const operation = "foundation.quarterly-objectives.create";
825
+ const initial = findFirstResultByPrefix(results, [
826
+ "foundation",
827
+ "quarterly-objectives",
828
+ "create"
829
+ ]);
830
+ if (!initial) {
831
+ recordValidationRemediation({
832
+ operation,
833
+ offendingFields: ["quarterly_objective.annual_objective_id", "quarterly_objective.strategic_objective_id"],
834
+ backendMessage: "initial probe missing",
835
+ initialResult: "missing",
836
+ remediation: "none",
837
+ finalResult: "missing"
838
+ });
839
+ validationRemediationFailures += 1;
840
+ return;
841
+ }
842
+
843
+ const messages = extractValidationMessages(initial);
844
+ const isParentMismatchValidation =
845
+ isValidationErrorResult(initial) &&
846
+ messages.some((msg) =>
847
+ /belongs to strategic_objective_id|matching strategic_objective_id/i.test(msg)
848
+ );
849
+
850
+ if (!isParentMismatchValidation) {
851
+ recordValidationRemediation({
852
+ operation,
853
+ offendingFields: ["quarterly_objective.annual_objective_id", "quarterly_objective.strategic_objective_id"],
854
+ backendMessage: messages[0] ?? "",
855
+ initialResult: summarizeStatus(initial),
856
+ remediation: "none_not_required",
857
+ finalResult: summarizeStatus(initial)
858
+ });
859
+ return;
860
+ }
861
+
862
+ const initialPayload = parseDataJsonFromArgs(initial.args);
863
+ const quarterlyBody =
864
+ initialPayload && typeof initialPayload.quarterly_objective === "object"
865
+ ? initialPayload.quarterly_objective
866
+ : null;
867
+ const annualId =
868
+ quarterlyBody && typeof quarterlyBody.annual_objective_id === "string"
869
+ ? quarterlyBody.annual_objective_id
870
+ : "";
871
+
872
+ if (!quarterlyBody || annualId.length === 0) {
873
+ recordValidationRemediation({
874
+ operation,
875
+ offendingFields: ["quarterly_objective.annual_objective_id", "quarterly_objective.strategic_objective_id"],
876
+ backendMessage: messages[0] ?? "",
877
+ initialResult: summarizeStatus(initial),
878
+ remediation: "failed_missing_annual_objective_id",
879
+ finalResult: summarizeStatus(initial)
880
+ });
881
+ validationRemediationFailures += 1;
882
+ return;
883
+ }
884
+
885
+ const annualShowArgs = ["foundation", "annual-objectives", "show", "--id", annualId];
886
+ const annualShowResult = runWave(annualShowArgs, env);
887
+ results.push(annualShowResult);
888
+ const resolvedStrategicObjectiveId = resolveAnnualStrategicObjectiveId(annualShowResult);
889
+ if (!annualShowResult.parsed?.ok || !resolvedStrategicObjectiveId) {
890
+ recordValidationRemediation({
891
+ operation,
892
+ offendingFields: ["quarterly_objective.annual_objective_id", "quarterly_objective.strategic_objective_id"],
893
+ backendMessage: messages[0] ?? "",
894
+ initialResult: summarizeStatus(initial),
895
+ remediation: `failed_parent_lookup annual_id=${annualId};lookup=${summarizeStatus(annualShowResult)}`,
896
+ finalResult: summarizeStatus(initial)
897
+ });
898
+ validationRemediationFailures += 1;
899
+ return;
900
+ }
901
+
902
+ const nextPayload = {
903
+ quarterly_objective: {
904
+ ...quarterlyBody,
905
+ strategic_objective_id: String(resolvedStrategicObjectiveId),
906
+ annual_objective_id: annualId
907
+ }
908
+ };
909
+ const retryArgs = withUpdatedDataJson(initial.args, nextPayload);
910
+ const retryResult = runWave(retryArgs, env);
911
+ results.push(retryResult);
912
+
913
+ recordValidationRemediation({
914
+ operation,
915
+ offendingFields: ["quarterly_objective.annual_objective_id", "quarterly_objective.strategic_objective_id"],
916
+ backendMessage: messages[0] ?? "",
917
+ initialResult: summarizeStatus(initial),
918
+ remediation: `parent_match annual_id=${annualId} strategic_objective_id=${String(resolvedStrategicObjectiveId)}`,
919
+ finalResult: summarizeStatus(retryResult)
920
+ });
921
+
922
+ if (retryResult.parsed?.ok !== true) {
923
+ validationRemediationFailures += 1;
924
+ }
925
+ }
926
+
927
+ remediateStandupsCreateDuplicateDate();
928
+ remediateFeedbacksUpdateDuplicateName();
929
+ remediateQuarterlyObjectiveParentMismatch();
930
+
487
931
  let deleteVerifyLine = "";
488
932
  let deleteVerifyFailure = 0;
489
933
  if (issueId) {
@@ -649,7 +1093,7 @@ verifyFieldWrite({
649
1093
  verifyFieldWrite({
650
1094
  label: "feedbacks.update verify quarter",
651
1095
  showArgs: ["feedbacks", "show", "--id", feedbackId],
652
- expectedValue: feedbackQuarterVerify,
1096
+ expectedValue: feedbackQuarterExpected,
653
1097
  requiredSuccessPrefix: ["feedbacks", "update"],
654
1098
  candidatePaths: [["data", "feedback", "attributes", "quarter"]]
655
1099
  });
@@ -684,9 +1128,30 @@ if (postUpdateVerificationLines.length > 0) {
684
1128
  lines.push(...postUpdateVerificationLines);
685
1129
  }
686
1130
  failures += postUpdateVerificationFailures;
1131
+ failures += validationRemediationFailures;
1132
+
1133
+ if (validationRemediationEntries.length > 0) {
1134
+ lines.push("VALIDATION_REMEDIATION_SUMMARY_START");
1135
+ for (const entry of validationRemediationEntries) {
1136
+ lines.push(
1137
+ [
1138
+ "VALIDATION_REMEDIATION",
1139
+ `operation=${entry.operation}`,
1140
+ `offending_fields=${entry.offendingFields.join(",") || "n/a"}`,
1141
+ `backend_message=${JSON.stringify(entry.backendMessage ?? "")}`,
1142
+ `initial_result=${entry.initialResult}`,
1143
+ `remediation=${entry.remediation}`,
1144
+ `final_result=${entry.finalResult}`
1145
+ ].join(" | ")
1146
+ );
1147
+ }
1148
+ lines.push("VALIDATION_REMEDIATION_SUMMARY_END");
1149
+ }
687
1150
 
688
1151
  console.log(lines.join("\n"));
689
1152
  const extraChecksCount =
690
- (deleteVerifyLine ? 1 : 0) + postUpdateVerificationLines.length;
1153
+ (deleteVerifyLine ? 1 : 0) +
1154
+ postUpdateVerificationLines.length +
1155
+ validationRemediationEntries.length;
691
1156
  console.log(`\nSUMMARY total=${results.length + extraChecksCount} failures=${failures}`);
692
1157
  if (failures > 0) process.exit(1);