@cat-factory/orchestration 0.188.2 → 0.189.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/container/engine-dependent-modules.d.ts.map +1 -1
- package/dist/container/engine-dependent-modules.js +12 -0
- package/dist/container/engine-dependent-modules.js.map +1 -1
- package/dist/container/modules.d.ts.map +1 -1
- package/dist/container/modules.js +1 -0
- package/dist/container/modules.js.map +1 -1
- package/dist/modules/execution/ExecutionService.d.ts +16 -0
- package/dist/modules/execution/ExecutionService.d.ts.map +1 -1
- package/dist/modules/execution/ExecutionService.js +46 -19
- package/dist/modules/execution/ExecutionService.js.map +1 -1
- package/dist/modules/execution/ExecutionServiceDependencies.d.ts +8 -1
- package/dist/modules/execution/ExecutionServiceDependencies.d.ts.map +1 -1
- package/dist/modules/execution/PrVerificationReportController.d.ts +35 -8
- package/dist/modules/execution/PrVerificationReportController.d.ts.map +1 -1
- package/dist/modules/execution/PrVerificationReportController.js +79 -9
- package/dist/modules/execution/PrVerificationReportController.js.map +1 -1
- package/dist/modules/execution/prReport.environments.d.ts +49 -0
- package/dist/modules/execution/prReport.environments.d.ts.map +1 -0
- package/dist/modules/execution/prReport.environments.js +408 -0
- package/dist/modules/execution/prReport.environments.js.map +1 -0
- package/dist/modules/execution/prReport.logic.d.ts +7 -0
- package/dist/modules/execution/prReport.logic.d.ts.map +1 -1
- package/dist/modules/execution/prReport.logic.js +9 -75
- package/dist/modules/execution/prReport.logic.js.map +1 -1
- package/dist/modules/execution/prReport.steps.d.ts +13 -0
- package/dist/modules/execution/prReport.steps.d.ts.map +1 -0
- package/dist/modules/execution/prReport.steps.js +27 -0
- package/dist/modules/execution/prReport.steps.js.map +1 -0
- package/package.json +11 -11
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
import { hostMarkdown, redactSecrets } from '@cat-factory/kernel';
|
|
2
|
+
import { DEPLOYER_AGENT_KIND } from '@cat-factory/integrations';
|
|
3
|
+
import { isTesterKind } from './ci.logic.js';
|
|
4
|
+
import { findStep } from './prReport.steps.js';
|
|
5
|
+
/** Scrub credentials out of an optional free-text value, preserving `null`/`undefined`. */
|
|
6
|
+
function scrub(value) {
|
|
7
|
+
return value == null ? null : (redactSecrets(value) ?? null);
|
|
8
|
+
}
|
|
9
|
+
/** The lifecycle states that mean an environment is no longer standing. */
|
|
10
|
+
const GONE_STATUSES = new Set(['torn_down', 'expired', 'failed']);
|
|
11
|
+
/** The human-readable rendering of each way the timeline can come back empty. */
|
|
12
|
+
const TIMELINE_GAP_NOTES = {
|
|
13
|
+
unwired: 'This deployment retains no provisioning event log, so the environment lifecycle could not be dated.',
|
|
14
|
+
unreadable: 'The provisioning event log could not be read for this run, so the environment lifecycle could not be dated. This is a transient read failure, not a statement that nothing happened.',
|
|
15
|
+
truncated: 'This run has more provisioning events than one report read may take, so the history is incomplete and the environment lifecycle is not dated from a partial one.',
|
|
16
|
+
not_provisioned: 'This run has no deployer step, so it stood no environment up and there is no lifecycle to date.',
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Index the log rows by environment identity. Rows carrying no `targetId` name no single
|
|
20
|
+
* environment (a provision that failed before a record existed, a stack recipe's per-step rows),
|
|
21
|
+
* so they inform the failure COUNT and never the identity sets: reading a recipe step's success
|
|
22
|
+
* as an environment coming up would invent an environment that then has to be reclaimed.
|
|
23
|
+
*/
|
|
24
|
+
function indexEnvironments(events) {
|
|
25
|
+
const provisioned = new Set();
|
|
26
|
+
const latestTeardown = new Map();
|
|
27
|
+
for (const event of events) {
|
|
28
|
+
if (!event.targetId)
|
|
29
|
+
continue;
|
|
30
|
+
if (event.operation === 'provision' && event.outcome === 'success') {
|
|
31
|
+
provisioned.add(event.targetId);
|
|
32
|
+
}
|
|
33
|
+
else if (event.operation === 'teardown') {
|
|
34
|
+
const seen = latestTeardown.get(event.targetId);
|
|
35
|
+
if (!seen || event.createdAt >= seen.createdAt)
|
|
36
|
+
latestTeardown.set(event.targetId, event);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const reclaimed = new Set();
|
|
40
|
+
const stuck = new Set();
|
|
41
|
+
for (const [id, event] of latestTeardown) {
|
|
42
|
+
// A teardown of an environment this run has no bring-up row for is still evidence that it is
|
|
43
|
+
// gone; a FAILED one is only this run's problem to report if this run stood it up.
|
|
44
|
+
if (event.outcome === 'success')
|
|
45
|
+
reclaimed.add(id);
|
|
46
|
+
else if (provisioned.has(id))
|
|
47
|
+
stuck.add(id);
|
|
48
|
+
}
|
|
49
|
+
return { provisioned, reclaimed, stuck };
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Fold the run's environment rows in the provisioning log. A gap status means there is nothing
|
|
53
|
+
* to fold, which is reported as the reason it is empty rather than as an empty history (see the
|
|
54
|
+
* header).
|
|
55
|
+
*/
|
|
56
|
+
function foldLifecycle(read) {
|
|
57
|
+
if (read.status !== 'read') {
|
|
58
|
+
return {
|
|
59
|
+
logged: null,
|
|
60
|
+
timeline: {
|
|
61
|
+
gap: read.status,
|
|
62
|
+
note: TIMELINE_GAP_NOTES[read.status],
|
|
63
|
+
provisionedAt: null,
|
|
64
|
+
tornDownAt: null,
|
|
65
|
+
provisionFailures: 0,
|
|
66
|
+
teardownFailures: 0,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const logged = indexEnvironments(read.events);
|
|
71
|
+
let provisionedAt = null;
|
|
72
|
+
let tornDownAt = null;
|
|
73
|
+
let provisionFailures = 0;
|
|
74
|
+
for (const event of read.events) {
|
|
75
|
+
if (event.operation === 'provision') {
|
|
76
|
+
if (event.outcome === 'failure')
|
|
77
|
+
provisionFailures++;
|
|
78
|
+
// Only a row naming an environment dates a bring-up: a stack recipe's steps succeed
|
|
79
|
+
// several times on the way to one environment, and the first of those is not when it
|
|
80
|
+
// came up.
|
|
81
|
+
else if (event.targetId && (provisionedAt == null || event.createdAt < provisionedAt)) {
|
|
82
|
+
provisionedAt = event.createdAt;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else if (event.operation === 'teardown' && event.outcome === 'success') {
|
|
86
|
+
if (tornDownAt == null || event.createdAt > tornDownAt)
|
|
87
|
+
tornDownAt = event.createdAt;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
logged,
|
|
92
|
+
timeline: {
|
|
93
|
+
gap: null,
|
|
94
|
+
provisionedAt,
|
|
95
|
+
tornDownAt,
|
|
96
|
+
provisionFailures,
|
|
97
|
+
teardownFailures: logged.stuck.size,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Whether the run's own step projections POSITIVELY show every environment gone. This is the
|
|
103
|
+
* WEAKER of the two teardown signals and is only consulted when there is no log to read: the
|
|
104
|
+
* projection is written by the run's own polls and is never refreshed once the run settles, so
|
|
105
|
+
* an environment reclaimed by the TTL sweep afterwards keeps a stale `ready` projection forever.
|
|
106
|
+
* The log is what turns "we stopped watching" into "it was torn down at a time".
|
|
107
|
+
*
|
|
108
|
+
* Phrased POSITIVELY (`every`, over a non-empty set) rather than as "nothing looks live",
|
|
109
|
+
* because a run that projected no environment at all would satisfy the negative form and
|
|
110
|
+
* confirm a teardown nobody observed.
|
|
111
|
+
*/
|
|
112
|
+
function projectionsAllGone(instance) {
|
|
113
|
+
const projections = instance.steps.filter((s) => s.environment != null);
|
|
114
|
+
return (projections.length > 0 && projections.every((s) => GONE_STATUSES.has(s.environment.status)));
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Whether the environments a run stood up are gone again.
|
|
118
|
+
*
|
|
119
|
+
* The RECORDED teardowns win over the projection (see {@link projectionsAllGone}), and a
|
|
120
|
+
* recorded FAILURE is its own answer rather than being flattened into `pending`: an environment
|
|
121
|
+
* still standing because nobody asked and one still standing because the provider refused need
|
|
122
|
+
* different people to do different things.
|
|
123
|
+
*
|
|
124
|
+
* Decided by IDENTITY: `confirmed` means every environment id the log records this run standing
|
|
125
|
+
* up was reclaimed, never that a count of teardowns reached a count of ready frames. The tally
|
|
126
|
+
* form reads as correct until a run replaces an environment mid-flight, at which point the
|
|
127
|
+
* superseded one's teardown balances the books while its replacement is still running.
|
|
128
|
+
*
|
|
129
|
+
* `confirmed` requires POSITIVE evidence from one source or the other. With a readable log that
|
|
130
|
+
* records the bring-up and no teardown, the answer is `pending`: the log not mentioning a
|
|
131
|
+
* teardown IS the observation that none happened, and no projection may override it.
|
|
132
|
+
*/
|
|
133
|
+
function teardownState(instance, entries, logged) {
|
|
134
|
+
if (!entries.some((e) => e.status === 'ready'))
|
|
135
|
+
return 'not_applicable';
|
|
136
|
+
// No log to read: fall back to the run's own step projections, the weaker signal.
|
|
137
|
+
if (!logged)
|
|
138
|
+
return projectionsAllGone(instance) ? 'confirmed' : 'pending';
|
|
139
|
+
if (logged.stuck.size > 0)
|
|
140
|
+
return 'failed';
|
|
141
|
+
// A log that records no bring-up at all cannot speak to the teardown either way, so the
|
|
142
|
+
// projection is consulted rather than concluding from the log's silence.
|
|
143
|
+
if (logged.provisioned.size === 0)
|
|
144
|
+
return projectionsAllGone(instance) ? 'confirmed' : 'pending';
|
|
145
|
+
const outstanding = [...logged.provisioned].filter((id) => !logged.reclaimed.has(id));
|
|
146
|
+
return outstanding.length === 0 ? 'confirmed' : 'pending';
|
|
147
|
+
}
|
|
148
|
+
/** The tester step whose report the evidence leg reads. */
|
|
149
|
+
function testerStep(instance) {
|
|
150
|
+
return findStep(instance, (s) => isTesterKind(s.agentKind), (s) => s.test?.lastReport != null);
|
|
151
|
+
}
|
|
152
|
+
/** The deployer step whose per-frame outcomes the "up" leg reads. */
|
|
153
|
+
function deployerStep(instance) {
|
|
154
|
+
return findStep(instance, (s) => s.agentKind === DEPLOYER_AGENT_KIND, (s) => Object.keys(s.deployEnvs ?? {}).length > 0);
|
|
155
|
+
}
|
|
156
|
+
/** The empty evidence leg, carrying the reason it is empty. */
|
|
157
|
+
function noEvidence(note) {
|
|
158
|
+
return {
|
|
159
|
+
status: 'absent',
|
|
160
|
+
note,
|
|
161
|
+
ranAgainst: null,
|
|
162
|
+
capturedAt: null,
|
|
163
|
+
outcomes: 0,
|
|
164
|
+
requirementVerdicts: 0,
|
|
165
|
+
screenshots: [],
|
|
166
|
+
url: null,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* What the tester observed, and where. The artifacts are reported whatever the attribution (they
|
|
171
|
+
* exist and a reviewer should be able to reach them), while `status` governs whether they
|
|
172
|
+
* may be read as evidence ABOUT this environment.
|
|
173
|
+
*/
|
|
174
|
+
function composeEvidence(instance, inputs, cap) {
|
|
175
|
+
const step = testerStep(instance);
|
|
176
|
+
if (!step) {
|
|
177
|
+
return noEvidence('No tester step in this pipeline, so nothing was exercised against an environment by the platform.');
|
|
178
|
+
}
|
|
179
|
+
const report = step.test?.lastReport;
|
|
180
|
+
if (!report) {
|
|
181
|
+
return noEvidence('The tester step produced no report, so nothing was observed anywhere.');
|
|
182
|
+
}
|
|
183
|
+
const ranAgainst = report.environment ?? null;
|
|
184
|
+
const screenshots = cap(report.screenshots ?? [], 'environments.evidence.screenshots').map((shot) => ({
|
|
185
|
+
view: redactSecrets(shot.view) ?? '',
|
|
186
|
+
artifactId: shot.artifactId,
|
|
187
|
+
hasReference: !!shot.referenceArtifactId,
|
|
188
|
+
}));
|
|
189
|
+
const common = {
|
|
190
|
+
ranAgainst,
|
|
191
|
+
capturedAt: step.finishedAt ?? null,
|
|
192
|
+
outcomes: report.outcomes.length,
|
|
193
|
+
requirementVerdicts: report.requirementVerdicts?.length ?? 0,
|
|
194
|
+
screenshots,
|
|
195
|
+
url: inputs.evidenceUrl,
|
|
196
|
+
};
|
|
197
|
+
if (ranAgainst === 'ephemeral')
|
|
198
|
+
return { status: 'captured', ...common };
|
|
199
|
+
if (ranAgainst === 'local') {
|
|
200
|
+
return {
|
|
201
|
+
status: 'local',
|
|
202
|
+
note: 'The tester stood its dependencies up locally, so its observations are not evidence about the ephemeral environment below.',
|
|
203
|
+
...common,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
status: 'undeclared',
|
|
208
|
+
note: 'The tester report does not say where it ran, so its observations cannot be attributed to the ephemeral environment below.',
|
|
209
|
+
...common,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Compose the verdict over the three legs. Every failing condition appends its own line, so
|
|
214
|
+
* `proof: 'incomplete'` is never a bare label, and `complete` is exactly "nothing was found
|
|
215
|
+
* to say".
|
|
216
|
+
*/
|
|
217
|
+
function composeProof(entries, teardown, timeline, evidence) {
|
|
218
|
+
const ready = entries.filter((e) => e.status === 'ready').length;
|
|
219
|
+
const failed = entries.filter((e) => e.status === 'failed').length;
|
|
220
|
+
// Every frame skipped (or nothing recorded at all) means no environment was ever meant to
|
|
221
|
+
// stand up, so there is no proof to be incomplete about.
|
|
222
|
+
if (ready === 0 && failed === 0)
|
|
223
|
+
return { proof: 'not_applicable', gaps: [] };
|
|
224
|
+
const gaps = [];
|
|
225
|
+
if (failed > 0) {
|
|
226
|
+
gaps.push(`${failed} of ${entries.length} service frames failed to provision, so that part of the system was never stood up.`);
|
|
227
|
+
}
|
|
228
|
+
if (ready === 0) {
|
|
229
|
+
gaps.push('No environment reached a ready state, so nothing could be exercised against one.');
|
|
230
|
+
return { proof: 'incomplete', gaps };
|
|
231
|
+
}
|
|
232
|
+
if (timeline.gap) {
|
|
233
|
+
gaps.push(timeline.note ?? TIMELINE_GAP_NOTES[timeline.gap]);
|
|
234
|
+
}
|
|
235
|
+
else if (timeline.provisionedAt == null) {
|
|
236
|
+
gaps.push('The provisioning log holds no successful bring-up for this run, so when the environment came up is unknown.');
|
|
237
|
+
}
|
|
238
|
+
if (timeline.provisionFailures > 0) {
|
|
239
|
+
gaps.push(`${timeline.provisionFailures} provisioning attempt${timeline.provisionFailures === 1 ? '' : 's'} failed for this run.`);
|
|
240
|
+
}
|
|
241
|
+
if (evidence.status !== 'captured') {
|
|
242
|
+
gaps.push(evidence.note ?? 'Nothing was observed against the environment.');
|
|
243
|
+
}
|
|
244
|
+
if (teardown === 'pending') {
|
|
245
|
+
gaps.push('The environment has not been confirmed torn down; it may still be running.');
|
|
246
|
+
}
|
|
247
|
+
if (teardown === 'failed') {
|
|
248
|
+
gaps.push(timeline.teardownFailures === 1
|
|
249
|
+
? 'An environment could not be torn down, so it is still standing and needs reclaiming by hand.'
|
|
250
|
+
: `${timeline.teardownFailures} environments could not be torn down, so they are still standing and need reclaiming by hand.`);
|
|
251
|
+
}
|
|
252
|
+
// The ORDERING check: the two ways captured evidence can be real and still not be about the
|
|
253
|
+
// environment that was standing. Only computable when both ends are dated, which is why it is
|
|
254
|
+
// stated here rather than folded into the evidence status.
|
|
255
|
+
const at = evidence.capturedAt;
|
|
256
|
+
if (at != null && timeline.provisionedAt != null && at < timeline.provisionedAt) {
|
|
257
|
+
gaps.push('The tester settled BEFORE the environment came up, so it cannot have used it.');
|
|
258
|
+
}
|
|
259
|
+
// Only once the whole set is reclaimed does `tornDownAt` mark the end of the lifecycle. While
|
|
260
|
+
// anything is still standing it is the last teardown RECORDED, which on a run that replaced an
|
|
261
|
+
// environment mid-flight is the superseded one going away, and testing against its replacement
|
|
262
|
+
// afterwards is exactly what should have happened.
|
|
263
|
+
if (teardown === 'confirmed' && at != null && timeline.tornDownAt != null) {
|
|
264
|
+
if (at > timeline.tornDownAt) {
|
|
265
|
+
gaps.push('The tester settled AFTER the environment was torn down, so it cannot have used it.');
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return { proof: gaps.length === 0 ? 'complete' : 'incomplete', gaps };
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Compose the test-environment lifecycle section. Reads the deployer step's per-frame outcomes,
|
|
272
|
+
* the run's provisioning-log rows and the tester's report, all already resolved by the caller,
|
|
273
|
+
* so nothing here re-probes a provider.
|
|
274
|
+
*/
|
|
275
|
+
export function composeEnvironments(instance, inputs, cap) {
|
|
276
|
+
const { timeline, logged } = foldLifecycle(inputs.provisioning);
|
|
277
|
+
const evidence = composeEvidence(instance, inputs, cap);
|
|
278
|
+
const step = deployerStep(instance);
|
|
279
|
+
const absent = (note) => ({
|
|
280
|
+
status: 'absent',
|
|
281
|
+
note,
|
|
282
|
+
entries: [],
|
|
283
|
+
teardown: 'not_applicable',
|
|
284
|
+
timeline,
|
|
285
|
+
evidence,
|
|
286
|
+
proof: 'not_applicable',
|
|
287
|
+
gaps: [],
|
|
288
|
+
});
|
|
289
|
+
if (!step) {
|
|
290
|
+
return absent('No deployer step in this pipeline, so no ephemeral environment was provisioned.');
|
|
291
|
+
}
|
|
292
|
+
const entries = cap(Object.entries(step.deployEnvs ?? {}), 'environments.entries').map(([frameId, state]) => ({
|
|
293
|
+
frameId,
|
|
294
|
+
status: state.status,
|
|
295
|
+
url: state.url ?? null,
|
|
296
|
+
error: scrub(state.error),
|
|
297
|
+
}));
|
|
298
|
+
if (entries.length === 0) {
|
|
299
|
+
return absent('The deployer step recorded no environment outcomes (it did not run to completion).');
|
|
300
|
+
}
|
|
301
|
+
const teardown = teardownState(instance, entries, logged);
|
|
302
|
+
return {
|
|
303
|
+
status: 'reported',
|
|
304
|
+
entries,
|
|
305
|
+
teardown,
|
|
306
|
+
timeline,
|
|
307
|
+
evidence,
|
|
308
|
+
...composeProof(entries, teardown, timeline, evidence),
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
// ---------------------------------------------------------------------------
|
|
312
|
+
// Rendering. Every untrusted hole goes through kernel's `hostMarkdown` boundary: a frame id, a
|
|
313
|
+
// provider's stderr and a tester's view name all land in a host-parsed, often public PR body.
|
|
314
|
+
// ---------------------------------------------------------------------------
|
|
315
|
+
/** An epoch-ms instant as a UTC timestamp a reviewer can line up against CI and the log. */
|
|
316
|
+
function at(epochMs) {
|
|
317
|
+
return new Date(epochMs)
|
|
318
|
+
.toISOString()
|
|
319
|
+
.replace('T', ' ')
|
|
320
|
+
.replace(/\.\d+Z$/, 'Z');
|
|
321
|
+
}
|
|
322
|
+
/** The proof headline plus, when it is not clean, the lines saying what is missing. */
|
|
323
|
+
function renderProof(envs) {
|
|
324
|
+
if (envs.proof === 'not_applicable')
|
|
325
|
+
return [];
|
|
326
|
+
const headline = envs.proof === 'complete'
|
|
327
|
+
? '**Proof:** ✅ environment up → evidence captured against it → teardown confirmed'
|
|
328
|
+
: '**Proof:** ⚠️ incomplete';
|
|
329
|
+
const out = [headline];
|
|
330
|
+
if (envs.gaps.length) {
|
|
331
|
+
out.push('', ...envs.gaps.map((gap) => `- ${hostMarkdown.cell(gap)}`));
|
|
332
|
+
}
|
|
333
|
+
return [...out, ''];
|
|
334
|
+
}
|
|
335
|
+
function renderTimeline(timeline) {
|
|
336
|
+
// The note is one of this module's own constants, so it needs no host-markdown escaping.
|
|
337
|
+
if (timeline.gap) {
|
|
338
|
+
return [`**Timeline:** not evidenced. ${timeline.note ?? TIMELINE_GAP_NOTES[timeline.gap]}`];
|
|
339
|
+
}
|
|
340
|
+
const parts = [];
|
|
341
|
+
parts.push(timeline.provisionedAt != null ? `up ${at(timeline.provisionedAt)}` : 'no bring-up on record');
|
|
342
|
+
if (timeline.tornDownAt != null)
|
|
343
|
+
parts.push(`torn down ${at(timeline.tornDownAt)}`);
|
|
344
|
+
if (timeline.provisionFailures > 0) {
|
|
345
|
+
parts.push(`${timeline.provisionFailures} failed provisioning attempts`);
|
|
346
|
+
}
|
|
347
|
+
if (timeline.teardownFailures > 0) {
|
|
348
|
+
parts.push(`${timeline.teardownFailures} could not be torn down`);
|
|
349
|
+
}
|
|
350
|
+
return [`**Timeline:** ${parts.join(' · ')}`];
|
|
351
|
+
}
|
|
352
|
+
function renderEvidence(evidence) {
|
|
353
|
+
const label = evidence.status === 'captured'
|
|
354
|
+
? '✅ captured from the live environment'
|
|
355
|
+
: evidence.status === 'local'
|
|
356
|
+
? '➖ the tester ran against local dependencies'
|
|
357
|
+
: evidence.status === 'undeclared'
|
|
358
|
+
? '❓ the tester did not say where it ran'
|
|
359
|
+
: '➖ none';
|
|
360
|
+
const out = [`**Evidence:** ${label}`];
|
|
361
|
+
if (evidence.status !== 'captured' && evidence.note)
|
|
362
|
+
out.push(`_${evidence.note}_`);
|
|
363
|
+
if (evidence.status === 'absent')
|
|
364
|
+
return [...out, ''];
|
|
365
|
+
const counts = [
|
|
366
|
+
`${evidence.outcomes} area${evidence.outcomes === 1 ? '' : 's'} exercised`,
|
|
367
|
+
`${evidence.requirementVerdicts} requirement verdict${evidence.requirementVerdicts === 1 ? '' : 's'}`,
|
|
368
|
+
`${evidence.screenshots.length} screenshot${evidence.screenshots.length === 1 ? '' : 's'}`,
|
|
369
|
+
];
|
|
370
|
+
if (evidence.capturedAt != null)
|
|
371
|
+
counts.push(`observed ${at(evidence.capturedAt)}`);
|
|
372
|
+
out.push(counts.join(' · '));
|
|
373
|
+
if (evidence.url)
|
|
374
|
+
out.push(`[Open the captured evidence](${evidence.url})`);
|
|
375
|
+
if (evidence.screenshots.length) {
|
|
376
|
+
out.push('', '| View | Artifact | Reference |', '| --- | --- | --- |');
|
|
377
|
+
for (const shot of evidence.screenshots) {
|
|
378
|
+
out.push(`| ${hostMarkdown.cell(shot.view)} | \`${hostMarkdown.cell(shot.artifactId)}\` | ${shot.hasReference ? 'paired' : '—'} |`);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return [...out, ''];
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Render the section: the computed proof first (it is what a reviewer acts on), then the
|
|
385
|
+
* per-frame outcomes, the dated timeline, the evidence and the teardown verdict.
|
|
386
|
+
*/
|
|
387
|
+
export function renderEnvironments(envs) {
|
|
388
|
+
const out = ['### Test environment lifecycle', ''];
|
|
389
|
+
if (envs.status === 'absent') {
|
|
390
|
+
return [...out, `_${envs.note}_`, '', ...renderEvidence(envs.evidence)];
|
|
391
|
+
}
|
|
392
|
+
out.push(...renderProof(envs));
|
|
393
|
+
out.push('| Service frame | State | URL | Error |', '| --- | --- | --- | --- |');
|
|
394
|
+
for (const entry of envs.entries) {
|
|
395
|
+
out.push(`| \`${hostMarkdown.cell(entry.frameId)}\` | ${entry.status} | ${hostMarkdown.cell(entry.url ?? '')} | ${hostMarkdown.cell(entry.error ?? '')} |`);
|
|
396
|
+
}
|
|
397
|
+
out.push('', ...renderTimeline(envs.timeline));
|
|
398
|
+
const teardown = envs.teardown === 'confirmed'
|
|
399
|
+
? '✅ torn down'
|
|
400
|
+
: envs.teardown === 'pending'
|
|
401
|
+
? '⏳ still live'
|
|
402
|
+
: envs.teardown === 'failed'
|
|
403
|
+
? '❌ teardown failed'
|
|
404
|
+
: 'nothing to tear down';
|
|
405
|
+
out.push(`**Teardown:** ${teardown}`, '');
|
|
406
|
+
return [...out, ...renderEvidence(envs.evidence)];
|
|
407
|
+
}
|
|
408
|
+
//# sourceMappingURL=prReport.environments.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prReport.environments.js","sourceRoot":"","sources":["../../../src/modules/execution/prReport.environments.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Block, ExecutionInstance, PrReportIssue, PrVerificationReport, SpecDoc } from '@cat-factory/kernel';
|
|
2
|
+
import { type PrReportEnvironmentInputs } from './prReport.environments.js';
|
|
2
3
|
/** The non-run inputs the composer needs beyond the instance itself. */
|
|
3
4
|
export interface PrReportInputs {
|
|
4
5
|
block: Block;
|
|
@@ -17,6 +18,12 @@ export interface PrReportInputs {
|
|
|
17
18
|
* a spec that IS readable but records no requirements.
|
|
18
19
|
*/
|
|
19
20
|
spec?: SpecDoc | null;
|
|
21
|
+
/**
|
|
22
|
+
* The environment-lifecycle section's own resolved inputs: the run's rows in the provisioning
|
|
23
|
+
* event log (the DATED half of the up → observed → torn-down proof) and the deep link into
|
|
24
|
+
* its captured evidence. See `prReport.environments.ts`.
|
|
25
|
+
*/
|
|
26
|
+
environments: PrReportEnvironmentInputs;
|
|
20
27
|
/** Epoch ms stamped as the report's `generatedAt`. */
|
|
21
28
|
now: number;
|
|
22
29
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prReport.logic.d.ts","sourceRoot":"","sources":["../../../src/modules/execution/prReport.logic.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,KAAK,EACL,iBAAiB,
|
|
1
|
+
{"version":3,"file":"prReport.logic.d.ts","sourceRoot":"","sources":["../../../src/modules/execution/prReport.logic.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,KAAK,EACL,iBAAiB,EAKjB,aAAa,EAEb,oBAAoB,EAEpB,OAAO,EAER,MAAM,qBAAqB,CAAA;AAI5B,OAAO,EACL,KAAK,yBAAyB,EAG/B,MAAM,4BAA4B,CAAA;AAgFnC,wEAAwE;AACxE,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,KAAK,CAAA;IACZ,2FAA2F;IAC3F,MAAM,EAAE,aAAa,EAAE,CAAA;IACvB,gEAAgE;IAChE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,uFAAuF;IACvF,QAAQ,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,IAAI,CAAA;IACrC,+FAA+F;IAC/F,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;IACrB;;;;OAIG;IACH,YAAY,EAAE,yBAAyB,CAAA;IACvC,sDAAsD;IACtD,GAAG,EAAE,MAAM,CAAA;CACZ;AA8WD,oFAAoF;AACpF,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,iBAAiB,EAC3B,MAAM,EAAE,cAAc,GACrB,oBAAoB,CAqCtB;AA0OD;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,oBAAoB,GAAG,MAAM,CAwC/E"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { hostMarkdown, redactSecrets } from '@cat-factory/kernel';
|
|
2
2
|
import { PR_VERIFICATION_REPORT_VERSION } from '@cat-factory/contracts';
|
|
3
|
-
import { DEPLOYER_AGENT_KIND } from '@cat-factory/integrations';
|
|
4
3
|
import { CI_AGENT_KIND, MERGER_AGENT_KIND, isTesterKind } from './ci.logic.js';
|
|
4
|
+
import { composeEnvironments, renderEnvironments, } from './prReport.environments.js';
|
|
5
|
+
import { findStep } from './prReport.steps.js';
|
|
5
6
|
// ---------------------------------------------------------------------------
|
|
6
7
|
// The PR verification report's PURE half: compose it from a run's already-loaded state, and
|
|
7
8
|
// render it as markdown + a fenced JSON block.
|
|
@@ -13,6 +14,12 @@ import { CI_AGENT_KIND, MERGER_AGENT_KIND, isTesterKind } from './ci.logic.js';
|
|
|
13
14
|
// disagree with the verdict the gate actually acted on, which would make the report a
|
|
14
15
|
// worse record than the run it describes.
|
|
15
16
|
//
|
|
17
|
+
// The TEST ENVIRONMENT LIFECYCLE section lives next door in `prReport.environments.ts`: it is
|
|
18
|
+
// the one section composed from a source outside the in-memory run (the provisioning event log,
|
|
19
|
+
// which is what dates the bring-up and the teardown), and it joins three producers into one
|
|
20
|
+
// computed proof. The caller resolves its inputs and passes them in, so this file stays the
|
|
21
|
+
// report's spine.
|
|
22
|
+
//
|
|
16
23
|
// Sections whose producing step did not run are emitted with `status: 'absent'` and a `note`
|
|
17
24
|
// that SAYS so. A silently missing section is indistinguishable from a clean one, which is
|
|
18
25
|
// exactly the false reassurance this feature exists to remove.
|
|
@@ -70,24 +77,6 @@ function verdictDetail(value) {
|
|
|
70
77
|
return text;
|
|
71
78
|
return `${text.slice(0, hostMarkdown.MAX_CELL_CHARS - 1)}…`;
|
|
72
79
|
}
|
|
73
|
-
/**
|
|
74
|
-
* The step a section should report on: the LAST matching step that carries evidence, else the
|
|
75
|
-
* first matching step (so a pipeline that has the step but hasn't reached it still gets the
|
|
76
|
-
* "not run yet" note rather than nothing).
|
|
77
|
-
*
|
|
78
|
-
* Last-with-evidence, not first-match: a pipeline may legitimately carry the same kind twice —
|
|
79
|
-
* a `ci` gate after the coder and another after the tester, say — and the later run is the one
|
|
80
|
-
* that describes the PR head as it stands now. Reporting the first would pin the section to a
|
|
81
|
-
* verdict two steps of work out of date.
|
|
82
|
-
*/
|
|
83
|
-
function findStep(instance, matches, hasEvidence) {
|
|
84
|
-
const matching = instance.steps.filter(matches);
|
|
85
|
-
for (let i = matching.length - 1; i >= 0; i--) {
|
|
86
|
-
if (hasEvidence(matching[i]))
|
|
87
|
-
return matching[i];
|
|
88
|
-
}
|
|
89
|
-
return matching[0];
|
|
90
|
-
}
|
|
91
80
|
/** A step is "settled" once it finished — a pending step has no evidence to report yet. */
|
|
92
81
|
function settled(step) {
|
|
93
82
|
return !!step && (step.state === 'done' || step.progress >= 1);
|
|
@@ -168,46 +157,6 @@ function composeTests(instance, truncations) {
|
|
|
168
157
|
maxFixerAttempts: step.test?.maxAttempts ?? null,
|
|
169
158
|
};
|
|
170
159
|
}
|
|
171
|
-
/**
|
|
172
|
-
* Whether the ephemeral environments a run stood up are gone again. Read off the live
|
|
173
|
-
* per-step environment projections rather than the terminal `deployEnvs` outcomes, because
|
|
174
|
-
* only the projection carries the CURRENT lifecycle state (`torn_down` / `expired`).
|
|
175
|
-
*/
|
|
176
|
-
function teardownState(instance, entries) {
|
|
177
|
-
if (!entries.some((e) => e.status === 'ready'))
|
|
178
|
-
return 'not_applicable';
|
|
179
|
-
const live = instance.steps.some((s) => s.environment != null &&
|
|
180
|
-
s.environment.status !== 'torn_down' &&
|
|
181
|
-
s.environment.status !== 'expired' &&
|
|
182
|
-
s.environment.status !== 'failed');
|
|
183
|
-
return live ? 'pending' : 'confirmed';
|
|
184
|
-
}
|
|
185
|
-
function composeEnvironments(instance, truncations) {
|
|
186
|
-
const step = findStep(instance, (s) => s.agentKind === DEPLOYER_AGENT_KIND, (s) => Object.keys(s.deployEnvs ?? {}).length > 0);
|
|
187
|
-
if (!step) {
|
|
188
|
-
return {
|
|
189
|
-
status: 'absent',
|
|
190
|
-
note: 'No deployer step in this pipeline — no ephemeral environment was provisioned.',
|
|
191
|
-
entries: [],
|
|
192
|
-
teardown: 'not_applicable',
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
const entries = cap(Object.entries(step.deployEnvs ?? {}), 'environments.entries', truncations).map(([frameId, state]) => ({
|
|
196
|
-
frameId,
|
|
197
|
-
status: state.status,
|
|
198
|
-
url: state.url ?? null,
|
|
199
|
-
error: scrub(state.error),
|
|
200
|
-
}));
|
|
201
|
-
if (entries.length === 0) {
|
|
202
|
-
return {
|
|
203
|
-
status: 'absent',
|
|
204
|
-
note: 'The deployer step recorded no environment outcomes (it did not run to completion).',
|
|
205
|
-
entries: [],
|
|
206
|
-
teardown: 'not_applicable',
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
return { status: 'reported', entries, teardown: teardownState(instance, entries) };
|
|
210
|
-
}
|
|
211
160
|
/**
|
|
212
161
|
* The merger's structured verdict. `step.custom` carries the engine's {@link MergeDecision}
|
|
213
162
|
* once the merge resolver has run, and the agent's raw {@link MergeAssessment} in the window
|
|
@@ -476,7 +425,7 @@ export function composePrVerificationReport(instance, inputs) {
|
|
|
476
425
|
ci: composeCi(instance, truncations),
|
|
477
426
|
tests: composeTests(instance, truncations),
|
|
478
427
|
requirements: composeRequirements(instance, inputs.spec, truncations),
|
|
479
|
-
environments: composeEnvironments(instance, truncations),
|
|
428
|
+
environments: composeEnvironments(instance, inputs.environments, (items, label) => cap(items, label, truncations)),
|
|
480
429
|
merge: composeMerge(instance),
|
|
481
430
|
judges: composeJudges(instance, truncations),
|
|
482
431
|
observability: { runUrl: inputs.runUrl },
|
|
@@ -596,21 +545,6 @@ function renderRequirements(reqs) {
|
|
|
596
545
|
out.push('', '_`established` = observed to hold on some run; `aspirational` = agreed but not yet built,', 'so `not checked` against one is expected and a failure against one is unfinished work, not', 'a break. A failure against an `established` requirement is a 🔴 regression. The acceptance', 'criteria themselves live in `spec/` in this repository._', '');
|
|
597
546
|
return out;
|
|
598
547
|
}
|
|
599
|
-
function renderEnvironments(envs) {
|
|
600
|
-
const out = ['### Ephemeral environment', ''];
|
|
601
|
-
if (envs.status === 'absent')
|
|
602
|
-
return [...out, `_${envs.note}_`, ''];
|
|
603
|
-
out.push('| Service frame | State | URL | Error |', '| --- | --- | --- | --- |');
|
|
604
|
-
for (const entry of envs.entries) {
|
|
605
|
-
out.push(`| \`${hostMarkdown.cell(entry.frameId)}\` | ${entry.status} | ${hostMarkdown.cell(entry.url ?? '')} | ${hostMarkdown.cell(entry.error ?? '')} |`);
|
|
606
|
-
}
|
|
607
|
-
const teardown = envs.teardown === 'confirmed'
|
|
608
|
-
? '✅ torn down'
|
|
609
|
-
: envs.teardown === 'pending'
|
|
610
|
-
? '⏳ still live'
|
|
611
|
-
: 'nothing to tear down';
|
|
612
|
-
return [...out, '', `**Teardown:** ${teardown}`, ''];
|
|
613
|
-
}
|
|
614
548
|
function renderMerge(merge) {
|
|
615
549
|
const out = ['### Merge assessment', ''];
|
|
616
550
|
if (merge.status === 'absent')
|