@ted-galago/wave-cli 0.1.7 → 0.1.9
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 +18 -18
- package/dist/index.cjs +10 -5
- package/dist/index.js +10 -5
- package/package.json +1 -1
- package/scripts/verify-dev-api.mjs +470 -5
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
|
|
109
|
-
wave
|
|
110
|
-
wave
|
|
111
|
-
wave
|
|
112
|
-
wave
|
|
113
|
-
wave
|
|
114
|
-
wave
|
|
115
|
-
wave
|
|
116
|
-
wave
|
|
117
|
-
wave
|
|
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
|
-
##
|
|
293
|
+
## Content Command Contract
|
|
294
294
|
|
|
295
|
-
The `
|
|
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
|
-
- `
|
|
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
|
-
- `
|
|
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
|
-
- `
|
|
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
|
-
- `
|
|
321
|
+
- `content create-project`
|
|
322
322
|
- `contentable_type: "Project"`
|
|
323
323
|
- `contentable_id: PROJECT_ID`
|
|
324
324
|
- `member_id: ACTOR_MEMBER_ID`
|
|
325
|
-
- `
|
|
325
|
+
- `content create-customer`
|
|
326
326
|
- `contentable_type: "Customer"`
|
|
327
327
|
- `contentable_id: CUSTOMER_ID`
|
|
328
328
|
- `member_id: ACTOR_MEMBER_ID`
|
|
329
|
-
- `
|
|
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
|
|
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
|
|
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: "
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
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: "
|
|
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:
|
|
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:
|
|
2632
|
+
selectionSet: projectShowFullSelectionSet,
|
|
2628
2633
|
transformData: normalizeProjectRowForDisplay
|
|
2629
2634
|
});
|
|
2630
2635
|
});
|
package/package.json
CHANGED
|
@@ -41,9 +41,27 @@ function runWave(args, env) {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
function firstId(parsed, field) {
|
|
44
|
-
const
|
|
45
|
-
if (
|
|
46
|
-
|
|
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:
|
|
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) +
|
|
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);
|