@monster-orange/den 0.0.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (255) hide show
  1. package/CHANGELOG.md +204 -0
  2. package/LICENSE +21 -0
  3. package/README.md +165 -1
  4. package/bin/den +68 -0
  5. package/docs/distributions.md +499 -0
  6. package/make/demo.mk +82 -0
  7. package/make/den.mk +80 -0
  8. package/make/journeys.mk +57 -0
  9. package/make/local_dev.mk +154 -0
  10. package/make/security.mk +59 -0
  11. package/make/test.mk +405 -0
  12. package/package.json +101 -4
  13. package/scripts/auth-configure.ts +415 -0
  14. package/scripts/heal-config.ts +92 -0
  15. package/scripts/jsonl-playwright-reporter.js +70 -0
  16. package/scripts/manifest-read.cjs +91 -0
  17. package/scripts/setup-1password.ts +570 -0
  18. package/scripts/setup-secrets.ts +149 -0
  19. package/scripts/view-results.js +156 -0
  20. package/service/install.sh +112 -0
  21. package/src/pillars/api/fixtures/index.ts +128 -0
  22. package/src/pillars/api/fixtures/openapi-validator.ts +111 -0
  23. package/src/pillars/api/global-setup.ts +103 -0
  24. package/src/pillars/api/playwright.config.ts +105 -0
  25. package/src/pillars/api/schemas/example-api.json +110 -0
  26. package/src/pillars/api/tests/health/health.spec.ts +19 -0
  27. package/src/pillars/api/tests/health/manifest.yml +6 -0
  28. package/src/pillars/browser/fixtures/index.ts +232 -0
  29. package/src/pillars/browser/fixtures/package.json +10 -0
  30. package/src/pillars/browser/global-setup.ts +140 -0
  31. package/src/pillars/browser/global-teardown.ts +48 -0
  32. package/src/pillars/browser/playwright.config.ts +212 -0
  33. package/src/pillars/browser/setup.ts +219 -0
  34. package/src/pillars/journeys/app-config.ts +237 -0
  35. package/src/pillars/journeys/discover.ts +160 -0
  36. package/src/pillars/journeys/hooks/deny-shell.cjs +44 -0
  37. package/src/pillars/journeys/observability/events.ts +288 -0
  38. package/src/pillars/journeys/observability/sdk-mapper.ts +306 -0
  39. package/src/pillars/journeys/report/friction.ts +304 -0
  40. package/src/pillars/journeys/report/junit.ts +105 -0
  41. package/src/pillars/journeys/report/render.ts +273 -0
  42. package/src/pillars/journeys/report/results.ts +323 -0
  43. package/src/pillars/journeys/run.ts +673 -0
  44. package/src/pillars/journeys/runner.ts +1025 -0
  45. package/src/pillars/journeys/sandbox.ts +403 -0
  46. package/src/pillars/journeys/spec/loader.ts +152 -0
  47. package/src/pillars/journeys/spec/schema.ts +208 -0
  48. package/src/pillars/journeys/teardown.ts +98 -0
  49. package/src/pillars/journeys/tools/browser-session.ts +219 -0
  50. package/src/pillars/journeys/tools/browser-tool.ts +914 -0
  51. package/src/pillars/journeys/tools/http-tool.ts +564 -0
  52. package/src/pillars/journeys/tools/ledger.ts +278 -0
  53. package/src/pillars/journeys/tools/snapshot-store.ts +117 -0
  54. package/src/pillars/journeys/verify/assertions.ts +502 -0
  55. package/src/pillars/journeys/verify/verdict.ts +71 -0
  56. package/src/pillars/perf/build-report.ts +36 -0
  57. package/src/pillars/perf/k6.config.js +112 -0
  58. package/src/pillars/perf/run.ts +345 -0
  59. package/src/pillars/perf/scenarios/load-profile.js +97 -0
  60. package/src/pillars/perf/staging.ts +73 -0
  61. package/src/pillars/security/discover.ts +124 -0
  62. package/src/pillars/security/findings.ts +151 -0
  63. package/src/pillars/security/plan.ts +355 -0
  64. package/src/pillars/security/run.ts +757 -0
  65. package/src/pillars/smoke/admin-probe.ts +68 -0
  66. package/src/pillars/smoke/base-url.ts +56 -0
  67. package/src/pillars/smoke/cleanup.ts +8 -0
  68. package/src/pillars/smoke/discover.ts +237 -0
  69. package/src/pillars/smoke/ensure-smoke-teardown.ts +40 -0
  70. package/src/pillars/smoke/fixture-manager.ts +217 -0
  71. package/src/pillars/smoke/fixtures.ts +468 -0
  72. package/src/pillars/smoke/global-setup-all.ts +262 -0
  73. package/src/pillars/smoke/global-setup-global.ts +262 -0
  74. package/src/pillars/smoke/global-setup.ts +273 -0
  75. package/src/pillars/smoke/global-teardown-all.ts +8 -0
  76. package/src/pillars/smoke/global-teardown-global.ts +8 -0
  77. package/src/pillars/smoke/global-teardown.ts +139 -0
  78. package/src/pillars/smoke/playwright-all.config.ts +147 -0
  79. package/src/pillars/smoke/playwright-global.config.ts +141 -0
  80. package/src/pillars/smoke/playwright.config.ts +162 -0
  81. package/src/pillars/smoke/setup-env.ts +132 -0
  82. package/src/pillars/smoke/setup-global.ts +126 -0
  83. package/src/pillars/smoke/setup.ts +115 -0
  84. package/src/pillars/smoke/smoke-repo.ts +16 -0
  85. package/src/pillars/smoke/staging.ts +49 -0
  86. package/src/pillars/suites/run.ts +147 -0
  87. package/src/scripts/configure-aws-profiles.ts +114 -0
  88. package/src/scripts/register-repos.ts +307 -0
  89. package/src/scripts/setup-default-workspace.ts +203 -0
  90. package/src/shared/agent-actor.ts +77 -0
  91. package/src/shared/applications.ts +138 -0
  92. package/src/shared/auth/api-token.ts +416 -0
  93. package/src/shared/auth/auth0-helper.ts +284 -0
  94. package/src/shared/auth/headless-login.ts +265 -0
  95. package/src/shared/auth/jwt-helper.ts +186 -0
  96. package/src/shared/auth/sudo-vault.ts +69 -0
  97. package/src/shared/auth/test-accounts.ts +98 -0
  98. package/src/shared/auth/token-injection.ts +184 -0
  99. package/src/shared/auth/token-providers/auth0.ts +80 -0
  100. package/src/shared/auth/token-providers/index.ts +182 -0
  101. package/src/shared/auth/token-refresh.ts +273 -0
  102. package/src/shared/banner.ts +15 -0
  103. package/src/shared/brand.ts +79 -0
  104. package/src/shared/cli-tabs.ts +131 -0
  105. package/src/shared/constants.ts +128 -0
  106. package/src/shared/den-config.ts +688 -0
  107. package/src/shared/dev-metrics/emit.ts +354 -0
  108. package/src/shared/edge/config.ts +229 -0
  109. package/src/shared/edge/render.ts +221 -0
  110. package/src/shared/edge/runtime.ts +268 -0
  111. package/src/shared/edge/status.ts +74 -0
  112. package/src/shared/edge/types.ts +62 -0
  113. package/src/shared/engine.ts +67 -0
  114. package/src/shared/environment/aws-profiles.ts +49 -0
  115. package/src/shared/environment/env-selector.ts +161 -0
  116. package/src/shared/environment/playwright-env.ts +82 -0
  117. package/src/shared/environment-manager/catalog.ts +77 -0
  118. package/src/shared/environment-manager/clone-guard.ts +163 -0
  119. package/src/shared/environment-manager/dag.ts +286 -0
  120. package/src/shared/environment-manager/default-workspace.ts +182 -0
  121. package/src/shared/environment-manager/derivation/alias.ts +90 -0
  122. package/src/shared/environment-manager/derivation/cache.ts +97 -0
  123. package/src/shared/environment-manager/derivation/derive-graph.ts +421 -0
  124. package/src/shared/environment-manager/derivation/derived-up.ts +354 -0
  125. package/src/shared/environment-manager/derivation/load-alias.ts +11 -0
  126. package/src/shared/environment-manager/derivation/rules-compose.ts +42 -0
  127. package/src/shared/environment-manager/derivation/rules-events.ts +50 -0
  128. package/src/shared/environment-manager/derivation/rules-http.ts +247 -0
  129. package/src/shared/environment-manager/derivation/rules-infra.ts +64 -0
  130. package/src/shared/environment-manager/derivation/rules-readiness.ts +96 -0
  131. package/src/shared/environment-manager/derivation/scan.ts +160 -0
  132. package/src/shared/environment-manager/derivation/text.ts +40 -0
  133. package/src/shared/environment-manager/derivation/types.ts +69 -0
  134. package/src/shared/environment-manager/health.ts +635 -0
  135. package/src/shared/environment-manager/index.ts +6 -0
  136. package/src/shared/environment-manager/load-applications.ts +108 -0
  137. package/src/shared/environment-manager/log-tail.ts +213 -0
  138. package/src/shared/environment-manager/make-targets.ts +78 -0
  139. package/src/shared/environment-manager/opctl.ts +335 -0
  140. package/src/shared/environment-manager/readiness.ts +93 -0
  141. package/src/shared/environment-manager/register-local.ts +72 -0
  142. package/src/shared/environment-manager/secrets-check.ts +260 -0
  143. package/src/shared/environment-manager/service-container.ts +47 -0
  144. package/src/shared/environment-manager/service-env.ts +110 -0
  145. package/src/shared/environment-manager/service-runner.ts +1603 -0
  146. package/src/shared/environment-manager/sync-targets.ts +66 -0
  147. package/src/shared/environment-manager/up-plan.ts +326 -0
  148. package/src/shared/environment-manager/workspace-manager.ts +905 -0
  149. package/src/shared/environment-manager/workspaces.ts +860 -0
  150. package/src/shared/manifest/deps.ts +27 -0
  151. package/src/shared/manifest/merge-override.ts +75 -0
  152. package/src/shared/manifest/migrate-manifest.ts +101 -0
  153. package/src/shared/manifest/placement.ts +321 -0
  154. package/src/shared/manifest/read-manifest.ts +278 -0
  155. package/src/shared/manifest/schema.ts +313 -0
  156. package/src/shared/manifest/service-links.ts +93 -0
  157. package/src/shared/manifest/validate-layers.ts +229 -0
  158. package/src/shared/manifest/validate-manifest.ts +200 -0
  159. package/src/shared/manifest/workspace-manifests.ts +92 -0
  160. package/src/shared/open-url.ts +122 -0
  161. package/src/shared/org.ts +44 -0
  162. package/src/shared/paths.ts +190 -0
  163. package/src/shared/perf/optional-fields.ts +105 -0
  164. package/src/shared/perf/report-normalizer.ts +404 -0
  165. package/src/shared/perf/report-paths.ts +19 -0
  166. package/src/shared/perf/report-store.ts +110 -0
  167. package/src/shared/perf/report-types.ts +70 -0
  168. package/src/shared/perf/test-paths.ts +25 -0
  169. package/src/shared/process/log-rotation.ts +147 -0
  170. package/src/shared/process/spawn-streaming.ts +229 -0
  171. package/src/shared/product.ts +92 -0
  172. package/src/shared/profile.ts +141 -0
  173. package/src/shared/redact.ts +107 -0
  174. package/src/shared/repo-kind.ts +32 -0
  175. package/src/shared/reporting/jsonl-reporter.ts +131 -0
  176. package/src/shared/reporting/slack-reporter.ts +340 -0
  177. package/src/shared/results/result-reader.ts +266 -0
  178. package/src/shared/results/result-writer.ts +168 -0
  179. package/src/shared/secrets/doppler.ts +126 -0
  180. package/src/shared/secrets/env.ts +48 -0
  181. package/src/shared/secrets/index.ts +197 -0
  182. package/src/shared/secrets/keys.ts +33 -0
  183. package/src/shared/secrets/onepassword.ts +75 -0
  184. package/src/shared/secrets/types.ts +52 -0
  185. package/src/shared/service-url.ts +119 -0
  186. package/src/shared/smoke/branch-resolver.ts +141 -0
  187. package/src/shared/smoke/git-remote.ts +70 -0
  188. package/src/shared/smoke/repo-cloner.ts +151 -0
  189. package/src/shared/stacks/config.ts +137 -0
  190. package/src/shared/stacks/manifest-commands.ts +118 -0
  191. package/src/shared/stacks/render.ts +253 -0
  192. package/src/shared/stacks/runtime.ts +334 -0
  193. package/src/shared/stacks/status.ts +99 -0
  194. package/src/shared/stacks/types.ts +53 -0
  195. package/src/shared/terminal/tabs.ts +212 -0
  196. package/src/shared/test-discovery/module-scanner.ts +569 -0
  197. package/src/shared/types/index.ts +701 -0
  198. package/src/shared/ui-marker.ts +19 -0
  199. package/src/ui/cli/applications-command.ts +148 -0
  200. package/src/ui/cli/config-command.ts +68 -0
  201. package/src/ui/cli/create-main.ts +9 -0
  202. package/src/ui/cli/create.ts +360 -0
  203. package/src/ui/cli/edge-command.ts +81 -0
  204. package/src/ui/cli/flag-parser.ts +246 -0
  205. package/src/ui/cli/graph-command.ts +570 -0
  206. package/src/ui/cli/health-display.ts +63 -0
  207. package/src/ui/cli/index.ts +3742 -0
  208. package/src/ui/cli/init.ts +855 -0
  209. package/src/ui/cli/invocation-target.ts +175 -0
  210. package/src/ui/cli/logs-command.ts +104 -0
  211. package/src/ui/cli/mcp-main.ts +13 -0
  212. package/src/ui/cli/mcp-stdout-guard.ts +10 -0
  213. package/src/ui/cli/module-selection.ts +31 -0
  214. package/src/ui/cli/prompts.ts +767 -0
  215. package/src/ui/cli/results-viewer.ts +436 -0
  216. package/src/ui/cli/secrets-preflight.ts +91 -0
  217. package/src/ui/cli/stack-command.ts +117 -0
  218. package/src/ui/cli/stalled-setup-recovery.ts +129 -0
  219. package/src/ui/cli/test-runner.ts +1319 -0
  220. package/src/ui/cli/web-command.ts +1073 -0
  221. package/src/ui/mcp/api.ts +284 -0
  222. package/src/ui/mcp/guards.ts +141 -0
  223. package/src/ui/mcp/server.ts +147 -0
  224. package/src/ui/mcp/tools.ts +565 -0
  225. package/src/ui/server/audit.ts +71 -0
  226. package/src/ui/server/changelog-parser.ts +105 -0
  227. package/src/ui/server/daemon-reaper.ts +129 -0
  228. package/src/ui/server/editor-manager.ts +301 -0
  229. package/src/ui/server/github-service.ts +223 -0
  230. package/src/ui/server/graph-prefs.ts +89 -0
  231. package/src/ui/server/health-row.ts +15 -0
  232. package/src/ui/server/http-helpers.ts +98 -0
  233. package/src/ui/server/index.ts +3350 -0
  234. package/src/ui/server/log-history.ts +453 -0
  235. package/src/ui/server/perf-report.ts +42 -0
  236. package/src/ui/server/public/environments.html +496 -0
  237. package/src/ui/server/public/favicon.ico +0 -0
  238. package/src/ui/server/public/graph-model.js +416 -0
  239. package/src/ui/server/public/graph.css +976 -0
  240. package/src/ui/server/public/graph.js +986 -0
  241. package/src/ui/server/public/header.js +1373 -0
  242. package/src/ui/server/public/index.html +2595 -0
  243. package/src/ui/server/public/local-dev.html +5344 -0
  244. package/src/ui/server/public/logs.html +1496 -0
  245. package/src/ui/server/public/panel.js +787 -0
  246. package/src/ui/server/public/perf-report.js +359 -0
  247. package/src/ui/server/public/release.html +2111 -0
  248. package/src/ui/server/public/services.html +1103 -0
  249. package/src/ui/server/public/workspace-picker.js +69 -0
  250. package/src/ui/server/readonly-routes.ts +200 -0
  251. package/src/ui/server/release-deploy-session.ts +246 -0
  252. package/src/ui/server/release-service.ts +1125 -0
  253. package/src/ui/server/sudo-store.ts +111 -0
  254. package/src/ui/server/ui-assets.ts +73 -0
  255. package/src/ui/server/workspace-routes.ts +2185 -0
@@ -0,0 +1,323 @@
1
+ /**
2
+ * Journeys JSONL result rows — collect → finalize (plan: jrn-reporting
3
+ * packet; critique carry-over R3-1).
4
+ *
5
+ * The split is load-bearing for the entrypoint's ordering contract
6
+ * (runJourney → verify → report → teardown in finally):
7
+ * - `collectJourneyReport()` runs right after verification and normalizes
8
+ * everything known at that point (verdict, usage, snapshots, friction);
9
+ * - teardown runs;
10
+ * - `finalizeJourneyReport()` runs in the same finally, folding the
11
+ * TeardownReport (deletion failures + unmanaged-mutation warnings) into
12
+ * the JSONL row, the JUnit case, and the rendered markdown report — so
13
+ * teardown outcomes are part of every persisted output.
14
+ *
15
+ * Verdict discipline: the row's status comes EXCLUSIVELY from the
16
+ * deterministic verifier's Verdict. Friction points ride along as metadata
17
+ * and can never flip it (pinned by test).
18
+ */
19
+
20
+ import { mkdir, writeFile } from "fs/promises";
21
+ import * as os from "os";
22
+ import * as path from "path";
23
+ import { resultWriter } from "../../../shared/results/result-writer.js";
24
+ import type { TestResult } from "../../../shared/types/index.js";
25
+ import type { JourneyEvent } from "../observability/events.js";
26
+ import type { FailureKind, RunOutcome } from "../runner.js";
27
+ import type { JourneySpec } from "../spec/schema.js";
28
+ import type { TeardownReport } from "../teardown.js";
29
+ import type { SnapshotIndexEntry } from "../tools/snapshot-store.js";
30
+ import type { AssertionResult } from "../verify/assertions.js";
31
+ import type { Verdict } from "../verify/verdict.js";
32
+ import type { FrictionPoint } from "./friction.js";
33
+ import { renderJourneyReportMarkdown } from "./render.js";
34
+ import type { JourneysJUnitCase } from "./junit.js";
35
+
36
+ /** Spec fields the report consumes (full JourneySpec accepted). */
37
+ export type ReportableSpec = Pick<JourneySpec, "name" | "surface" | "budgets">;
38
+
39
+ export interface CollectJourneyReportInput {
40
+ spec: ReportableSpec;
41
+ env: string;
42
+ outcome: RunOutcome;
43
+ verdict: Verdict;
44
+ frictionPoints: readonly FrictionPoint[];
45
+ /** Defaults to the current OS user. */
46
+ user?: string;
47
+ /** Defaults to now (injectable for deterministic tests). */
48
+ timestamp?: string;
49
+ }
50
+
51
+ /** Budget consumption snapshot for the JSONL row. */
52
+ export interface BudgetConsumption {
53
+ tokensUsed?: number;
54
+ maxTotalTokens: number;
55
+ timeoutS: number;
56
+ maxTurns: number;
57
+ }
58
+
59
+ /** Everything known after verification, before teardown. */
60
+ export interface CollectedJourneyReport {
61
+ journeyName: string;
62
+ surface: JourneySpec["surface"];
63
+ environment: string;
64
+ timestamp: string;
65
+ user: string;
66
+ runId: string;
67
+ status: "passed" | "failed";
68
+ failureKind?: FailureKind;
69
+ durationMs: number;
70
+ usageTotalTokens?: number;
71
+ budgetConsumption: BudgetConsumption;
72
+ transcriptPath: string;
73
+ reportDir: string;
74
+ snapshotsDir: string;
75
+ snapshotCount: number;
76
+ snapshotIndex: readonly SnapshotIndexEntry[];
77
+ frictionPoints: readonly FrictionPoint[];
78
+ assertionResults: readonly AssertionResult[];
79
+ /** Structured harness timeline (tool calls, bindings, denials, error). */
80
+ events: readonly JourneyEvent[];
81
+ /** Basename of the events.jsonl sidecar (for the report header). */
82
+ eventsFile: string;
83
+ /** SDK terminal error detail on an agent_error run (previously dropped). */
84
+ agentError?: string;
85
+ }
86
+
87
+ /**
88
+ * Normalize one verified journey into report data. Pure — writes nothing.
89
+ * Called BEFORE teardown so the collected data is safe even if teardown
90
+ * itself fails catastrophically.
91
+ */
92
+ export function collectJourneyReport(
93
+ input: CollectJourneyReportInput,
94
+ ): CollectedJourneyReport {
95
+ const reportDir = path.dirname(input.outcome.transcriptPath);
96
+ const collected: CollectedJourneyReport = {
97
+ journeyName: input.spec.name,
98
+ surface: input.spec.surface,
99
+ environment: input.env,
100
+ timestamp: input.timestamp ?? new Date().toISOString(),
101
+ user: input.user ?? process.env.USER ?? os.userInfo().username,
102
+ runId: input.outcome.runId,
103
+ status: input.verdict.passed ? "passed" : "failed",
104
+ durationMs: input.outcome.durationMs,
105
+ budgetConsumption: {
106
+ tokensUsed: input.outcome.usage?.totalTokens,
107
+ maxTotalTokens: input.spec.budgets.max_total_tokens,
108
+ timeoutS: input.spec.budgets.timeout_s,
109
+ maxTurns: input.spec.budgets.max_turns,
110
+ },
111
+ transcriptPath: input.outcome.transcriptPath,
112
+ reportDir,
113
+ snapshotsDir: path.join(reportDir, "snapshots"),
114
+ snapshotCount: input.outcome.snapshotIndex.length,
115
+ snapshotIndex: input.outcome.snapshotIndex,
116
+ frictionPoints: input.frictionPoints,
117
+ assertionResults: input.verdict.results,
118
+ events: input.outcome.events,
119
+ eventsFile: path.basename(input.outcome.eventsPath),
120
+ };
121
+ if (input.outcome.usage !== undefined) {
122
+ collected.usageTotalTokens = input.outcome.usage.totalTokens;
123
+ }
124
+ if (input.verdict.failure_kind !== undefined) {
125
+ collected.failureKind = input.verdict.failure_kind;
126
+ }
127
+ if (input.outcome.error !== undefined) {
128
+ collected.agentError = input.outcome.error;
129
+ }
130
+ return collected;
131
+ }
132
+
133
+ /** Teardown block embedded in the JSONL row metadata. */
134
+ export interface TeardownRowMetadata {
135
+ attempted: number;
136
+ failed: number;
137
+ failures: Array<{
138
+ resourceType: string;
139
+ id: string;
140
+ cleanup: { method: string; path: string };
141
+ status?: number;
142
+ error?: string;
143
+ }>;
144
+ unmanaged_mutation_warnings: Array<{
145
+ method: string;
146
+ path: string;
147
+ status: number;
148
+ source: string;
149
+ }>;
150
+ /** Browser interception-processing failures (finding 7 — never silent). */
151
+ interception_errors: string[];
152
+ }
153
+
154
+ export interface FinalizedJourneyReport {
155
+ row: TestResult;
156
+ junitCase: JourneysJUnitCase;
157
+ reportPath: string;
158
+ }
159
+
160
+ /** Injectable output sinks (real writers by default). */
161
+ export interface FinalizeJourneyReportDeps {
162
+ writeResult: (row: TestResult) => Promise<void>;
163
+ writeFile: (filePath: string, contents: string) => Promise<void>;
164
+ }
165
+
166
+ async function defaultWriteFile(
167
+ filePath: string,
168
+ contents: string,
169
+ ): Promise<void> {
170
+ await mkdir(path.dirname(filePath), { recursive: true });
171
+ await writeFile(filePath, contents);
172
+ }
173
+
174
+ function toTeardownMetadata(
175
+ teardown: TeardownReport | undefined,
176
+ ): TeardownRowMetadata {
177
+ return {
178
+ attempted: teardown?.results.length ?? 0,
179
+ failed: teardown?.failures.length ?? 0,
180
+ failures: (teardown?.failures ?? []).map((failure) => ({
181
+ resourceType: failure.entry.resourceType,
182
+ id: failure.entry.id,
183
+ cleanup: failure.entry.cleanup,
184
+ ...(failure.status !== undefined ? { status: failure.status } : {}),
185
+ ...(failure.error !== undefined ? { error: failure.error } : {}),
186
+ })),
187
+ unmanaged_mutation_warnings: (teardown?.unmanagedWarnings ?? []).map(
188
+ (warning) => ({
189
+ method: warning.method,
190
+ path: warning.path,
191
+ status: warning.status,
192
+ source: warning.source,
193
+ }),
194
+ ),
195
+ interception_errors: [...(teardown?.interceptionErrors ?? [])],
196
+ };
197
+ }
198
+
199
+ function failedAssertionMessages(
200
+ results: readonly AssertionResult[],
201
+ ): string[] {
202
+ return results
203
+ .filter((result) => !result.passed)
204
+ .map(
205
+ (result) =>
206
+ result.message ??
207
+ `assertion ${result.assertion.kind} failed ` +
208
+ `(expected ${JSON.stringify(result.expected)}, ` +
209
+ `actual ${JSON.stringify(result.actual)})`,
210
+ );
211
+ }
212
+
213
+ /** Snapshot-capture failures (finding 9) — missing required evidence. */
214
+ function snapshotCaptureErrorStrings(
215
+ snapshotIndex: CollectedJourneyReport["snapshotIndex"],
216
+ ): string[] {
217
+ return snapshotIndex
218
+ .filter((entry) => entry.captureError !== undefined)
219
+ .map(
220
+ (entry) =>
221
+ `snapshot capture failed for ${entry.action} (seq ${entry.seq}): ` +
222
+ `${entry.captureError}`,
223
+ );
224
+ }
225
+
226
+ function teardownWarningStrings(metadata: TeardownRowMetadata): string[] {
227
+ return [
228
+ ...metadata.failures.map(
229
+ (failure) =>
230
+ `teardown failed for ${failure.resourceType} ${failure.id}: ` +
231
+ `${failure.cleanup.method} ${failure.cleanup.path}` +
232
+ (failure.status !== undefined ? ` (${failure.status})` : "") +
233
+ (failure.error !== undefined ? ` — ${failure.error}` : ""),
234
+ ),
235
+ ...metadata.unmanaged_mutation_warnings.map(
236
+ (warning) =>
237
+ `unmanaged mutation: ${warning.method} ${warning.path} ` +
238
+ `(${warning.status})`,
239
+ ),
240
+ ...metadata.interception_errors.map(
241
+ (message) => `browser interception error: ${message}`,
242
+ ),
243
+ ];
244
+ }
245
+
246
+ /**
247
+ * Finalize one journey's persisted outputs AFTER teardown: the JSONL row
248
+ * (via the shared resultWriter → results/journeys-<env>-<date>.jsonl), the
249
+ * JUnit case (aggregated across journeys by the entrypoint), and the
250
+ * rendered markdown report with the snapshot gallery. Teardown warnings —
251
+ * deletion failures and unmanaged mutations — are folded into all three.
252
+ */
253
+ export async function finalizeJourneyReport(
254
+ collected: CollectedJourneyReport,
255
+ teardown: TeardownReport | undefined,
256
+ deps: Partial<FinalizeJourneyReportDeps> = {},
257
+ ): Promise<FinalizedJourneyReport> {
258
+ const writeResult =
259
+ deps.writeResult ?? ((row: TestResult) => resultWriter.writeResult(row));
260
+ const writeReportFile = deps.writeFile ?? defaultWriteFile;
261
+
262
+ const teardownMetadata = toTeardownMetadata(teardown);
263
+ const failureMessages = failedAssertionMessages(collected.assertionResults);
264
+ const snapshotWarnings = snapshotCaptureErrorStrings(collected.snapshotIndex);
265
+
266
+ const errorParts: string[] = [];
267
+ if (collected.failureKind !== undefined) {
268
+ errorParts.push(`failure_kind=${collected.failureKind}`);
269
+ }
270
+ errorParts.push(...failureMessages);
271
+
272
+ const row: TestResult = {
273
+ timestamp: collected.timestamp,
274
+ pillar: "journeys",
275
+ environment: collected.environment,
276
+ test: collected.journeyName,
277
+ status: collected.status,
278
+ duration: collected.durationMs,
279
+ user: collected.user,
280
+ ...(collected.status === "failed" && errorParts.length > 0
281
+ ? { error: errorParts.join("; ") }
282
+ : {}),
283
+ metadata: {
284
+ surface: collected.surface,
285
+ runId: collected.runId,
286
+ ...(collected.failureKind !== undefined
287
+ ? { failure_kind: collected.failureKind }
288
+ : {}),
289
+ usage_total_tokens: collected.usageTotalTokens,
290
+ budget_consumption: collected.budgetConsumption,
291
+ transcript_path: collected.transcriptPath,
292
+ snapshot_count: collected.snapshotCount,
293
+ snapshots_dir: collected.snapshotsDir,
294
+ friction_points: collected.frictionPoints,
295
+ teardown: teardownMetadata,
296
+ snapshot_capture_errors: snapshotWarnings,
297
+ },
298
+ };
299
+
300
+ const junitCase: JourneysJUnitCase = {
301
+ name: collected.journeyName,
302
+ environment: collected.environment,
303
+ durationMs: collected.durationMs,
304
+ passed: collected.status === "passed",
305
+ ...(collected.failureKind !== undefined
306
+ ? { failureKind: collected.failureKind }
307
+ : {}),
308
+ failureDetails: failureMessages,
309
+ teardownWarnings: [
310
+ ...snapshotWarnings,
311
+ ...teardownWarningStrings(teardownMetadata),
312
+ ],
313
+ };
314
+
315
+ const reportPath = path.join(collected.reportDir, "report.md");
316
+ await writeResult(row);
317
+ await writeReportFile(
318
+ reportPath,
319
+ renderJourneyReportMarkdown(collected, teardown),
320
+ );
321
+
322
+ return { row, junitCase, reportPath };
323
+ }