@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,468 @@
1
+ import type {
2
+ CommandDef,
3
+ Environment,
4
+ PerformanceServiceConfig,
5
+ RawManifest,
6
+ SmokeCleanupContextConfig,
7
+ SmokeCleanupFixtureConfig,
8
+ SmokeObjectStoreCleanupTargetConfig,
9
+ SmokeServiceConfig,
10
+ SmokeStorageFixtureConfig,
11
+ } from "../../shared/types/index.js";
12
+
13
+ export interface ResolvedSmokeStorageFixture {
14
+ endpoint: string;
15
+ apiEndpoint: string;
16
+ bucket: string;
17
+ accessKey: string;
18
+ secretKey: string;
19
+ cleanupContext?: ResolvedSmokeCleanupContext;
20
+ }
21
+
22
+ export interface ResolvedSmokeObjectStoreCleanupTarget {
23
+ endpoint: string;
24
+ bucket: string;
25
+ accessKey: string;
26
+ secretKey: string;
27
+ }
28
+
29
+ export interface ResolvedSmokeCleanupContext {
30
+ objectStores?: Record<string, ResolvedSmokeObjectStoreCleanupTarget>;
31
+ }
32
+
33
+ /** Resolved storage fixture with lifecycle metadata and env injection derived from the resolved payload. */
34
+ export interface ResolvedSmokeStorageFixtureLifecycle {
35
+ fixtureKey: string;
36
+ resolvedPayload: ResolvedSmokeStorageFixture;
37
+ envVars: Record<string, string>;
38
+ setup?: CommandDef;
39
+ teardown?: CommandDef;
40
+ /** When set, smoke/perf runners probe this path under the service base URL before tests. */
41
+ adminProbePath?: string;
42
+ /** Service-authored hint appended to the 403 error from the admin probe. */
43
+ adminProbe403Hint?: string;
44
+ }
45
+
46
+ /** Resolved teardown-only cleanup fixture (no storage payload). */
47
+ export interface ResolvedSmokeCleanupFixtureLifecycle {
48
+ fixtureKey: string;
49
+ resolvedPayload: Record<string, unknown>;
50
+ envVars: Record<string, string>;
51
+ setup?: CommandDef;
52
+ teardown: CommandDef;
53
+ adminProbePath?: string;
54
+ adminProbe403Hint?: string;
55
+ }
56
+
57
+ type FixtureCapableServiceConfig =
58
+ | SmokeServiceConfig
59
+ | PerformanceServiceConfig;
60
+
61
+ function resolveFixtureValue(
62
+ fixture: SmokeStorageFixtureConfig | SmokeObjectStoreCleanupTargetConfig,
63
+ key: "endpoint" | "apiEndpoint" | "bucket" | "accessKey" | "secretKey",
64
+ env: NodeJS.ProcessEnv,
65
+ missingEnvVars: string[],
66
+ ): string | undefined {
67
+ const literalKey = key as keyof typeof fixture;
68
+ const fromEnvKey = `${key}FromEnv` as keyof typeof fixture;
69
+
70
+ const literalValue = fixture[literalKey];
71
+ if (typeof literalValue === "string" && literalValue.length > 0) {
72
+ return literalValue;
73
+ }
74
+
75
+ const envVarName = fixture[fromEnvKey];
76
+ if (typeof envVarName === "string" && envVarName.length > 0) {
77
+ const envValue = env[envVarName];
78
+ if (typeof envValue === "string" && envValue.length > 0) {
79
+ return envValue;
80
+ }
81
+ missingEnvVars.push(envVarName);
82
+ }
83
+
84
+ return undefined;
85
+ }
86
+
87
+ function resolveCleanupContext(
88
+ cleanupContext: SmokeCleanupContextConfig | undefined,
89
+ env: NodeJS.ProcessEnv,
90
+ missingEnvVars: string[],
91
+ ): ResolvedSmokeCleanupContext | undefined {
92
+ if (!cleanupContext?.objectStores) {
93
+ return undefined;
94
+ }
95
+
96
+ const objectStores = Object.entries(cleanupContext.objectStores).reduce<
97
+ Record<string, ResolvedSmokeObjectStoreCleanupTarget>
98
+ >((acc, [targetName, target]) => {
99
+ const endpoint = resolveFixtureValue(
100
+ target,
101
+ "endpoint",
102
+ env,
103
+ missingEnvVars,
104
+ );
105
+ const bucket = resolveFixtureValue(target, "bucket", env, missingEnvVars);
106
+ const accessKey = resolveFixtureValue(
107
+ target,
108
+ "accessKey",
109
+ env,
110
+ missingEnvVars,
111
+ );
112
+ const secretKey = resolveFixtureValue(
113
+ target,
114
+ "secretKey",
115
+ env,
116
+ missingEnvVars,
117
+ );
118
+ const fieldIsConfigured = (
119
+ config: SmokeObjectStoreCleanupTargetConfig,
120
+ key: "endpoint" | "bucket" | "accessKey" | "secretKey",
121
+ ) =>
122
+ Boolean(
123
+ (typeof config[key] === "string" && config[key]!.length > 0) ||
124
+ (typeof config[`${key}FromEnv`] === "string" &&
125
+ config[`${key}FromEnv`]!.length > 0),
126
+ );
127
+ const missingFields = [
128
+ ["endpoint", endpoint, fieldIsConfigured(target, "endpoint")],
129
+ ["bucket", bucket, fieldIsConfigured(target, "bucket")],
130
+ ["accessKey", accessKey, fieldIsConfigured(target, "accessKey")],
131
+ ["secretKey", secretKey, fieldIsConfigured(target, "secretKey")],
132
+ ]
133
+ .filter(([, value, isConfigured]) => !value && !isConfigured)
134
+ .map(([field]) => field);
135
+
136
+ if (missingFields.length > 0) {
137
+ throw new Error(
138
+ `Storage fixture cleanupContext.objectStores.${targetName} is missing required fields: ${missingFields.join(", ")}`,
139
+ );
140
+ }
141
+
142
+ acc[targetName] = {
143
+ endpoint,
144
+ bucket,
145
+ accessKey,
146
+ secretKey,
147
+ } as ResolvedSmokeObjectStoreCleanupTarget;
148
+ return acc;
149
+ }, {});
150
+
151
+ return Object.keys(objectStores).length > 0 ? { objectStores } : undefined;
152
+ }
153
+
154
+ /**
155
+ * Deterministic JSON string for hashing or stable comparison: fixture key + resolved payload fields in fixed key order.
156
+ */
157
+ export function smokeStorageFixtureHashInput(
158
+ fixtureKey: string,
159
+ resolvedPayload: ResolvedSmokeStorageFixture,
160
+ ): string {
161
+ return JSON.stringify({
162
+ fixtureKey,
163
+ resolvedPayload: {
164
+ accessKey: resolvedPayload.accessKey,
165
+ apiEndpoint: resolvedPayload.apiEndpoint,
166
+ bucket: resolvedPayload.bucket,
167
+ ...(resolvedPayload.cleanupContext
168
+ ? { cleanupContext: resolvedPayload.cleanupContext }
169
+ : {}),
170
+ endpoint: resolvedPayload.endpoint,
171
+ secretKey: resolvedPayload.secretKey,
172
+ },
173
+ });
174
+ }
175
+
176
+ function validateAdminProbePath(
177
+ adminProbePath: string | undefined,
178
+ label: string,
179
+ ): void {
180
+ if (
181
+ adminProbePath !== undefined &&
182
+ adminProbePath.length > 0 &&
183
+ (!adminProbePath.startsWith("/") || adminProbePath.includes("://"))
184
+ ) {
185
+ throw new Error(
186
+ `${label} adminProbePath must be a bare path starting with "/" (got ${JSON.stringify(adminProbePath)})`,
187
+ );
188
+ }
189
+ }
190
+
191
+ function coerceTeardownEnv(
192
+ teardownEnv: Record<string, string | number | boolean> | undefined,
193
+ label: string,
194
+ ): Record<string, string> {
195
+ const teardownEnvStrings: Record<string, string> = {};
196
+ if (teardownEnv !== undefined) {
197
+ for (const [key, value] of Object.entries(teardownEnv)) {
198
+ if (value === undefined || value === null) {
199
+ throw new Error(
200
+ `${label} teardownEnv.${key} must not be null or undefined`,
201
+ );
202
+ }
203
+ if (
204
+ typeof value !== "string" &&
205
+ typeof value !== "number" &&
206
+ typeof value !== "boolean"
207
+ ) {
208
+ throw new Error(
209
+ `${label} teardownEnv.${key} must be a string, number, or boolean`,
210
+ );
211
+ }
212
+ teardownEnvStrings[key] = String(value);
213
+ }
214
+ }
215
+ return teardownEnvStrings;
216
+ }
217
+
218
+ /**
219
+ * Resolve the teardown-only cleanup fixture for `smoke.<service>.fixtures.cleanup.<env>`.
220
+ * Returns null when the service declares no cleanup fixture for the environment.
221
+ */
222
+ export function resolveSmokeCleanupFixture(
223
+ manifest: RawManifest,
224
+ serviceName: string,
225
+ environment: Environment,
226
+ ): ResolvedSmokeCleanupFixtureLifecycle | null {
227
+ const service = manifest.smoke?.[serviceName];
228
+ if (!service) {
229
+ throw new Error(`Smoke service "${serviceName}" not found in manifest`);
230
+ }
231
+
232
+ const fixture: SmokeCleanupFixtureConfig | undefined =
233
+ service.fixtures?.cleanup?.[environment];
234
+ if (!fixture) {
235
+ return null;
236
+ }
237
+
238
+ const label = `Cleanup fixture for smoke.${serviceName}.${environment}`;
239
+ if (fixture.teardown === undefined) {
240
+ throw new Error(`${label} must define a teardown command`);
241
+ }
242
+ validateAdminProbePath(fixture.adminProbePath, label);
243
+ const teardownEnvStrings = coerceTeardownEnv(fixture.teardownEnv, label);
244
+
245
+ return {
246
+ fixtureKey: `${serviceName}:cleanup:${environment}`,
247
+ resolvedPayload: { environment, teardownEnv: teardownEnvStrings },
248
+ envVars: teardownEnvStrings,
249
+ teardown: fixture.teardown,
250
+ ...(fixture.setup !== undefined ? { setup: fixture.setup } : {}),
251
+ ...(fixture.adminProbePath !== undefined &&
252
+ fixture.adminProbePath.length > 0
253
+ ? { adminProbePath: fixture.adminProbePath }
254
+ : {}),
255
+ ...(fixture.adminProbe403Hint !== undefined &&
256
+ fixture.adminProbe403Hint.length > 0
257
+ ? { adminProbe403Hint: fixture.adminProbe403Hint }
258
+ : {}),
259
+ };
260
+ }
261
+
262
+ export function resolveSmokeStorageFixture(
263
+ manifest: RawManifest,
264
+ serviceName: string,
265
+ environment: Environment,
266
+ env: NodeJS.ProcessEnv = process.env,
267
+ ): ResolvedSmokeStorageFixtureLifecycle {
268
+ const service = manifest.smoke?.[serviceName];
269
+ if (!service) {
270
+ throw new Error(`Smoke service "${serviceName}" not found in manifest`);
271
+ }
272
+
273
+ return resolveStorageFixtureLifecycle(
274
+ service,
275
+ "smoke",
276
+ serviceName,
277
+ environment,
278
+ env,
279
+ undefined,
280
+ );
281
+ }
282
+
283
+ export function resolvePerformanceStorageFixture(
284
+ manifest: RawManifest,
285
+ moduleName: string,
286
+ environment: Environment,
287
+ env: NodeJS.ProcessEnv = process.env,
288
+ ): ResolvedSmokeStorageFixtureLifecycle {
289
+ const moduleConfig = manifest.performance?.[moduleName];
290
+ if (!moduleConfig) {
291
+ throw new Error(`Performance module "${moduleName}" not found in manifest`);
292
+ }
293
+
294
+ return resolveStorageFixtureLifecycle(
295
+ moduleConfig,
296
+ "performance",
297
+ moduleConfig.service,
298
+ environment,
299
+ env,
300
+ moduleName,
301
+ );
302
+ }
303
+
304
+ function resolveStorageFixtureLifecycle(
305
+ service: FixtureCapableServiceConfig,
306
+ pillar: "smoke" | "performance",
307
+ serviceName: string,
308
+ environment: Environment,
309
+ env: NodeJS.ProcessEnv,
310
+ /** Manifest key for `performance.<name>` — scopes fixture identity per k6 module (distinct PERF_TEST_ID / teardown). */
311
+ performanceModuleKey?: string,
312
+ ): ResolvedSmokeStorageFixtureLifecycle {
313
+ const fixture = service.fixtures?.storage?.[environment];
314
+ if (!fixture) {
315
+ throw new Error(
316
+ `Storage fixture for ${pillar}.${serviceName} is not defined for environment "${environment}"`,
317
+ );
318
+ }
319
+
320
+ validateAdminProbePath(
321
+ fixture.adminProbePath,
322
+ `Storage fixture for ${pillar}.${serviceName}.${environment}:`,
323
+ );
324
+
325
+ const missingEnvVars: string[] = [];
326
+ const endpoint = resolveFixtureValue(
327
+ fixture,
328
+ "endpoint",
329
+ env,
330
+ missingEnvVars,
331
+ );
332
+ const apiEndpoint = resolveFixtureValue(
333
+ fixture,
334
+ "apiEndpoint",
335
+ env,
336
+ missingEnvVars,
337
+ );
338
+ const bucket = resolveFixtureValue(fixture, "bucket", env, missingEnvVars);
339
+ const accessKey = resolveFixtureValue(
340
+ fixture,
341
+ "accessKey",
342
+ env,
343
+ missingEnvVars,
344
+ );
345
+ const secretKey = resolveFixtureValue(
346
+ fixture,
347
+ "secretKey",
348
+ env,
349
+ missingEnvVars,
350
+ );
351
+ const cleanupContext = resolveCleanupContext(
352
+ fixture.cleanupContext,
353
+ env,
354
+ missingEnvVars,
355
+ );
356
+
357
+ if (missingEnvVars.length > 0) {
358
+ throw new Error(
359
+ `Storage fixture for ${pillar}.${serviceName}.${environment} is missing required env vars: ${missingEnvVars.join(", ")}`,
360
+ );
361
+ }
362
+
363
+ const missingFields = [
364
+ ["endpoint", endpoint],
365
+ ["apiEndpoint", apiEndpoint],
366
+ ["bucket", bucket],
367
+ ["accessKey", accessKey],
368
+ ["secretKey", secretKey],
369
+ ]
370
+ .filter(([, value]) => !value)
371
+ .map(([field]) => field);
372
+
373
+ if (missingFields.length > 0) {
374
+ throw new Error(
375
+ `Storage fixture for ${pillar}.${serviceName}.${environment} is missing required fields: ${missingFields.join(", ")}`,
376
+ );
377
+ }
378
+
379
+ const resolvedPayload = {
380
+ endpoint,
381
+ apiEndpoint,
382
+ bucket,
383
+ accessKey,
384
+ secretKey,
385
+ ...(cleanupContext ? { cleanupContext } : {}),
386
+ } as ResolvedSmokeStorageFixture;
387
+
388
+ const fixtureKey =
389
+ pillar === "performance" && performanceModuleKey !== undefined
390
+ ? `${serviceName}:perf:${performanceModuleKey}:storage:${environment}`
391
+ : `${serviceName}:storage:${environment}`;
392
+ const storageFixtureEnvPayload = {
393
+ endpoint,
394
+ apiEndpoint,
395
+ bucket,
396
+ accessKey,
397
+ secretKey,
398
+ };
399
+
400
+ const teardownEnvStrings: Record<string, string> = {};
401
+ if (fixture.teardownEnv !== undefined) {
402
+ for (const [key, value] of Object.entries(fixture.teardownEnv)) {
403
+ if (value === undefined || value === null) {
404
+ throw new Error(
405
+ `Storage fixture teardownEnv.${key} for ${pillar}.${serviceName}.${environment} must not be null or undefined`,
406
+ );
407
+ }
408
+ if (
409
+ typeof value !== "string" &&
410
+ typeof value !== "number" &&
411
+ typeof value !== "boolean"
412
+ ) {
413
+ throw new Error(
414
+ `Storage fixture teardownEnv.${key} for ${pillar}.${serviceName}.${environment} must be a string, number, or boolean`,
415
+ );
416
+ }
417
+ teardownEnvStrings[key] = String(value);
418
+ }
419
+ }
420
+
421
+ return {
422
+ fixtureKey,
423
+ resolvedPayload,
424
+ envVars: {
425
+ SMOKE_STORAGE_FIXTURE_JSON: JSON.stringify(storageFixtureEnvPayload),
426
+ ...(cleanupContext
427
+ ? {
428
+ SMOKE_CLEANUP_CONTEXT_JSON: JSON.stringify(cleanupContext),
429
+ }
430
+ : {}),
431
+ ...teardownEnvStrings,
432
+ },
433
+ ...(fixture.setup !== undefined ? { setup: fixture.setup } : {}),
434
+ ...(fixture.teardown !== undefined ? { teardown: fixture.teardown } : {}),
435
+ ...(fixture.adminProbePath !== undefined &&
436
+ fixture.adminProbePath.length > 0
437
+ ? { adminProbePath: fixture.adminProbePath }
438
+ : {}),
439
+ ...(fixture.adminProbe403Hint !== undefined &&
440
+ fixture.adminProbe403Hint.length > 0
441
+ ? { adminProbe403Hint: fixture.adminProbe403Hint }
442
+ : {}),
443
+ };
444
+ }
445
+
446
+ export function buildSmokeFixtureEnv(
447
+ manifest: RawManifest,
448
+ serviceName: string,
449
+ environment: Environment,
450
+ env: NodeJS.ProcessEnv = process.env,
451
+ ): Record<string, string> {
452
+ return resolveSmokeStorageFixture(manifest, serviceName, environment, env)
453
+ .envVars;
454
+ }
455
+
456
+ export function buildPerformanceFixtureEnv(
457
+ manifest: RawManifest,
458
+ moduleName: string,
459
+ environment: Environment,
460
+ env: NodeJS.ProcessEnv = process.env,
461
+ ): Record<string, string> {
462
+ return resolvePerformanceStorageFixture(
463
+ manifest,
464
+ moduleName,
465
+ environment,
466
+ env,
467
+ ).envVars;
468
+ }