@ted-galago/wave-cli 0.1.6 → 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 +23 -18
- package/dist/index.cjs +829 -228
- package/dist/index.js +829 -228
- package/package.json +1 -1
- package/scripts/verify-dev-api.mjs +546 -6
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
|
|
|
@@ -61,7 +79,15 @@ function classify(result) {
|
|
|
61
79
|
if (!result.parsed) {
|
|
62
80
|
return { ok: false, reason: "non_json_output" };
|
|
63
81
|
}
|
|
82
|
+
const embeddedError = findFirstEmbeddedErrors(result.parsed);
|
|
64
83
|
if (result.parsed.ok === true) {
|
|
84
|
+
if (embeddedError) {
|
|
85
|
+
return {
|
|
86
|
+
ok: false,
|
|
87
|
+
reason: "embedded_errors",
|
|
88
|
+
details: { path: embeddedError.path, sample: embeddedError.sample }
|
|
89
|
+
};
|
|
90
|
+
}
|
|
65
91
|
return { ok: true, reason: "ok" };
|
|
66
92
|
}
|
|
67
93
|
|
|
@@ -91,12 +117,79 @@ function classify(result) {
|
|
|
91
117
|
if (code === "invalid_args" && message.includes("Missing required create fields")) {
|
|
92
118
|
return { ok: true, reason: "skipped_missing_parent_dependency" };
|
|
93
119
|
}
|
|
120
|
+
if (result.parsed?.status === 422 && code === "VALIDATION_ERROR") {
|
|
121
|
+
return { ok: true, reason: "domain_validation_error" };
|
|
122
|
+
}
|
|
94
123
|
if (code === "invalid_args") {
|
|
95
124
|
return { ok: false, reason: code };
|
|
96
125
|
}
|
|
97
126
|
return { ok: true, reason: `reachable_${code}` };
|
|
98
127
|
}
|
|
99
128
|
|
|
129
|
+
function isNonEmptyErrorsValue(value) {
|
|
130
|
+
if (value === null || value === undefined) return false;
|
|
131
|
+
if (Array.isArray(value)) {
|
|
132
|
+
if (value.length === 0) return false;
|
|
133
|
+
return value.some((entry) => {
|
|
134
|
+
if (entry === null || entry === undefined) return false;
|
|
135
|
+
if (typeof entry === "string") return entry.trim().length > 0;
|
|
136
|
+
if (typeof entry === "object") return Object.keys(entry).length > 0;
|
|
137
|
+
return true;
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
if (typeof value === "string") return value.trim().length > 0;
|
|
141
|
+
if (typeof value === "object") return Object.keys(value).length > 0;
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function summarizeValue(value) {
|
|
146
|
+
try {
|
|
147
|
+
if (typeof value === "string") return value.slice(0, 160);
|
|
148
|
+
const serialized = JSON.stringify(value);
|
|
149
|
+
if (typeof serialized !== "string") return String(value);
|
|
150
|
+
return serialized.slice(0, 300);
|
|
151
|
+
} catch {
|
|
152
|
+
return String(value);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function findFirstEmbeddedErrors(input) {
|
|
157
|
+
const queue = [{ value: input, path: "$", depth: 0 }];
|
|
158
|
+
const maxDepth = 12;
|
|
159
|
+
const maxNodes = 2000;
|
|
160
|
+
let visited = 0;
|
|
161
|
+
|
|
162
|
+
while (queue.length > 0) {
|
|
163
|
+
const current = queue.shift();
|
|
164
|
+
if (!current) break;
|
|
165
|
+
visited += 1;
|
|
166
|
+
if (visited > maxNodes) break;
|
|
167
|
+
|
|
168
|
+
const { value, path, depth } = current;
|
|
169
|
+
if (!value || typeof value !== "object") continue;
|
|
170
|
+
|
|
171
|
+
if (Array.isArray(value)) {
|
|
172
|
+
if (depth >= maxDepth) continue;
|
|
173
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
174
|
+
queue.push({ value: value[i], path: `${path}[${i}]`, depth: depth + 1 });
|
|
175
|
+
}
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
180
|
+
const nextPath = `${path}.${key}`;
|
|
181
|
+
if (key === "errors" && isNonEmptyErrorsValue(nested)) {
|
|
182
|
+
return { path: nextPath, sample: summarizeValue(nested) };
|
|
183
|
+
}
|
|
184
|
+
if (depth < maxDepth && nested && typeof nested === "object") {
|
|
185
|
+
queue.push({ value: nested, path: nextPath, depth: depth + 1 });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
|
|
100
193
|
function payload(root, attrs) {
|
|
101
194
|
return JSON.stringify({ [root]: attrs });
|
|
102
195
|
}
|
|
@@ -151,6 +244,83 @@ function verifySubissueDeleteWithPolling(env, issueId, subIssueId) {
|
|
|
151
244
|
return { ok: false, attempts, tries: 5 };
|
|
152
245
|
}
|
|
153
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
|
+
|
|
154
324
|
const root = process.cwd();
|
|
155
325
|
const envFile = resolve(root, ".env");
|
|
156
326
|
const dot = loadDotEnv(envFile);
|
|
@@ -412,6 +582,352 @@ for (const args of probes) {
|
|
|
412
582
|
results.push(runWave(args, env));
|
|
413
583
|
}
|
|
414
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
|
+
|
|
415
931
|
let deleteVerifyLine = "";
|
|
416
932
|
let deleteVerifyFailure = 0;
|
|
417
933
|
if (issueId) {
|
|
@@ -577,7 +1093,7 @@ verifyFieldWrite({
|
|
|
577
1093
|
verifyFieldWrite({
|
|
578
1094
|
label: "feedbacks.update verify quarter",
|
|
579
1095
|
showArgs: ["feedbacks", "show", "--id", feedbackId],
|
|
580
|
-
expectedValue:
|
|
1096
|
+
expectedValue: feedbackQuarterExpected,
|
|
581
1097
|
requiredSuccessPrefix: ["feedbacks", "update"],
|
|
582
1098
|
candidatePaths: [["data", "feedback", "attributes", "quarter"]]
|
|
583
1099
|
});
|
|
@@ -597,7 +1113,10 @@ for (const res of results) {
|
|
|
597
1113
|
const cmd = res.args.join(" ");
|
|
598
1114
|
const status = res.parsed?.status ?? "n/a";
|
|
599
1115
|
const errCode = res.parsed?.error?.code ?? "none";
|
|
600
|
-
|
|
1116
|
+
const reasonDetail = verdict.details ? ` (${JSON.stringify(verdict.details)})` : "";
|
|
1117
|
+
lines.push(
|
|
1118
|
+
`${verdict.ok ? "PASS" : "FAIL"} | status=${status} | err=${errCode} | reason=${verdict.reason}${reasonDetail} | ${cmd}`
|
|
1119
|
+
);
|
|
601
1120
|
if (!verdict.ok) failures += 1;
|
|
602
1121
|
}
|
|
603
1122
|
|
|
@@ -609,9 +1128,30 @@ if (postUpdateVerificationLines.length > 0) {
|
|
|
609
1128
|
lines.push(...postUpdateVerificationLines);
|
|
610
1129
|
}
|
|
611
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
|
+
}
|
|
612
1150
|
|
|
613
1151
|
console.log(lines.join("\n"));
|
|
614
1152
|
const extraChecksCount =
|
|
615
|
-
(deleteVerifyLine ? 1 : 0) +
|
|
1153
|
+
(deleteVerifyLine ? 1 : 0) +
|
|
1154
|
+
postUpdateVerificationLines.length +
|
|
1155
|
+
validationRemediationEntries.length;
|
|
616
1156
|
console.log(`\nSUMMARY total=${results.length + extraChecksCount} failures=${failures}`);
|
|
617
1157
|
if (failures > 0) process.exit(1);
|