@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,141 @@
1
+ /**
2
+ * The product profile: who this distribution is.
3
+ *
4
+ * Den is one engine; each distribution runs it under its own name. Everything product-named comes from here: the CLI, the env-var
5
+ * prefix, the config and manifest file names, and the state, services and
6
+ * workspaces directories. The profile lives in `den.yml` at the
7
+ * distribution root:
8
+ *
9
+ * product:
10
+ * name: acme # everything below derives from it by default
11
+ * # cli: acme
12
+ * # env_prefix: ACME
13
+ * # config: config/acme.yml
14
+ * # manifest: .acme.yml (.json is also read)
15
+ * # state_dir: ~/.acme
16
+ * # services_dir: .acme-services
17
+ * # workspaces_dir: acme-workspaces
18
+ *
19
+ * `DEN_PROFILE` may point at another profile file (tests use this). With no
20
+ * profile at all, Den runs under its own name.
21
+ */
22
+
23
+ import * as fs from "node:fs";
24
+ import * as os from "node:os";
25
+ import * as path from "node:path";
26
+ import { parse as parseYaml } from "yaml";
27
+
28
+ export const PROFILE_FILE = "den.yml";
29
+
30
+ export interface ProductProfile {
31
+ /** Display and slug name ("acme"). */
32
+ name: string;
33
+ /** The command a person types. */
34
+ cli: string;
35
+ /** Upper-case env-var prefix, without the trailing "_" ("ACME"). */
36
+ envPrefix: string;
37
+ /** Org config, relative to the distribution root. */
38
+ configFile: string;
39
+ /** Manifest file names probed in a repo, first match wins. */
40
+ manifestFiles: string[];
41
+ /** Per-user state directory (absolute, `~` expanded). */
42
+ stateDir: string;
43
+ /** Managed clones, relative to the distribution root. */
44
+ servicesDir: string;
45
+ /** Workspace checkouts, relative to the distribution root. */
46
+ workspacesDir: string;
47
+ }
48
+
49
+ interface RawProduct {
50
+ name?: unknown;
51
+ cli?: unknown;
52
+ env_prefix?: unknown;
53
+ config?: unknown;
54
+ manifest?: unknown;
55
+ state_dir?: unknown;
56
+ services_dir?: unknown;
57
+ workspaces_dir?: unknown;
58
+ }
59
+
60
+ const SLUG = /^[a-z][a-z0-9-]*$/;
61
+ const PREFIX = /^[A-Z][A-Z0-9_]*$/;
62
+
63
+ const str = (v: unknown): string | undefined =>
64
+ typeof v === "string" && v.trim() ? v.trim() : undefined;
65
+
66
+ const expandHome = (p: string): string =>
67
+ p === "~"
68
+ ? os.homedir()
69
+ : p.startsWith("~/")
70
+ ? path.join(os.homedir(), p.slice(2))
71
+ : p;
72
+
73
+ /** A profile from a `product:` block; unset fields derive from the name. */
74
+ export function profileFrom(raw: RawProduct = {}): ProductProfile {
75
+ const name = str(raw.name) ?? "den";
76
+ if (!SLUG.test(name))
77
+ throw new Error(
78
+ `den.yml: product.name "${name}" must be lower-case letters, digits and "-"`,
79
+ );
80
+ const envPrefix =
81
+ str(raw.env_prefix) ?? name.toUpperCase().replace(/-/g, "_");
82
+ if (!PREFIX.test(envPrefix))
83
+ throw new Error(
84
+ `den.yml: product.env_prefix "${envPrefix}" must be upper-case letters, digits and "_"`,
85
+ );
86
+ const manifest = str(raw.manifest) ?? `.${name}.yml`;
87
+ const json = manifest.replace(/\.ya?ml$/, ".json");
88
+ return {
89
+ name,
90
+ cli: str(raw.cli) ?? name,
91
+ envPrefix,
92
+ configFile: str(raw.config) ?? `config/${name}.yml`,
93
+ manifestFiles: json === manifest ? [manifest] : [manifest, json],
94
+ stateDir: path.resolve(expandHome(str(raw.state_dir) ?? `~/.${name}`)),
95
+ servicesDir: str(raw.services_dir) ?? `.${name}-services`,
96
+ workspacesDir: str(raw.workspaces_dir) ?? `${name}-workspaces`,
97
+ };
98
+ }
99
+
100
+ /** Read a profile file; a missing file is Den's own profile. */
101
+ export function readProfileFile(file: string): ProductProfile {
102
+ let raw: { product?: RawProduct } | null;
103
+ try {
104
+ raw = parseYaml(fs.readFileSync(file, "utf-8")) as typeof raw;
105
+ } catch (e) {
106
+ if ((e as NodeJS.ErrnoException).code === "ENOENT") return profileFrom();
107
+ throw new Error(`${file}: ${(e as Error).message}`);
108
+ }
109
+ return profileFrom(raw?.product ?? {});
110
+ }
111
+
112
+ let cached: { key: string; profile: ProductProfile } | null = null;
113
+
114
+ /**
115
+ * The profile for a distribution root: `DEN_PROFILE` if set, else
116
+ * `<root>/den.yml`, else Den's own. Cached per file.
117
+ */
118
+ export function loadProfile(root: string): ProductProfile {
119
+ const file = process.env.DEN_PROFILE?.trim() || path.join(root, PROFILE_FILE);
120
+ if (cached?.key !== file)
121
+ cached = { key: file, profile: readProfileFile(file) };
122
+ return cached.profile;
123
+ }
124
+
125
+ export function resetProfileCache(): void {
126
+ cached = null;
127
+ }
128
+
129
+ /** `${PREFIX}_${key}`: the env-var name this distribution uses for `key`. */
130
+ export function envName(profile: ProductProfile, key: string): string {
131
+ return `${profile.envPrefix}_${key}`;
132
+ }
133
+
134
+ /** The value of `${PREFIX}_${key}`, or undefined. */
135
+ export function readEnv(
136
+ profile: ProductProfile,
137
+ key: string,
138
+ env: NodeJS.ProcessEnv = process.env,
139
+ ): string | undefined {
140
+ return env[envName(profile, key)];
141
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Secret redaction, in two forms:
3
+ *
4
+ * - Known values (`redactValues`, `containsValue`): a run holds the live
5
+ * secret it minted (a bearer token, an agent key) and strips or refuses
6
+ * that exact string. The journeys and security pillars use these.
7
+ * - Secret-shaped values (`redactSecrets`): text from somewhere we don't
8
+ * control (service logs, command output) is scrubbed by pattern before it
9
+ * leaves Den, e.g. through the MCP server. Patterns err on the side of
10
+ * redacting: a hidden harmless value costs less than a leaked key.
11
+ */
12
+
13
+ export const REDACTED = "[REDACTED]";
14
+
15
+ /** Replace every occurrence of each non-empty value in `text`. */
16
+ export function redactValues(
17
+ text: string,
18
+ values: readonly (string | undefined)[],
19
+ ): string {
20
+ let out = text;
21
+ for (const v of values) if (v) out = out.split(v).join(REDACTED);
22
+ return out;
23
+ }
24
+
25
+ /** Whether `text` contains any non-empty value. */
26
+ export function containsValue(
27
+ text: string,
28
+ values: readonly (string | undefined)[],
29
+ ): boolean {
30
+ return values.some((v) => !!v && text.includes(v));
31
+ }
32
+
33
+ /** Key names whose values are secrets (matched anywhere in the key). */
34
+ const SECRET_KEY =
35
+ "[\\w.-]*(?:password|passwd|pwd|secret|token|api[_-]?key|apikey|access[_-]?key|private[_-]?key|credentials?|cookie)[\\w.-]*";
36
+
37
+ interface Pattern {
38
+ name: string;
39
+ re: RegExp;
40
+ replace: string | ((...m: string[]) => string);
41
+ }
42
+
43
+ /**
44
+ * Order matters: whole blocks first, then structured forms (URLs, auth
45
+ * headers), then bare token shapes, then generic key=value.
46
+ */
47
+ export const SECRET_PATTERNS: readonly Pattern[] = [
48
+ {
49
+ name: "private-key block",
50
+ re: /-----BEGIN ([A-Z0-9 ]*)PRIVATE KEY-----[\s\S]*?-----END \1PRIVATE KEY-----/g,
51
+ replace: (_m, kind) =>
52
+ `-----BEGIN ${kind}PRIVATE KEY-----${REDACTED}-----END ${kind}PRIVATE KEY-----`,
53
+ },
54
+ {
55
+ // A block cut off by a tail window: redact to the end of the text.
56
+ name: "private-key block (unterminated)",
57
+ re: /-----BEGIN ([A-Z0-9 ]*)PRIVATE KEY-----(?![\s\S]*-----END)(?!\[REDACTED\])[\s\S]*$/g,
58
+ replace: (_m, kind) => `-----BEGIN ${kind}PRIVATE KEY-----${REDACTED}`,
59
+ },
60
+ {
61
+ name: "URL credentials",
62
+ re: /\b([a-z][a-z0-9+.-]*:\/\/)([^\s:@/]+):([^\s@/]+)@/gi,
63
+ replace: (_m, scheme, user, pass) =>
64
+ pass === REDACTED ? _m : `${scheme}${user}:${REDACTED}@`,
65
+ },
66
+ {
67
+ name: "bearer token",
68
+ re: /\b(Bearer|Basic|Token)\s+[A-Za-z0-9._~+/=-]{8,}/gi,
69
+ replace: (_m, scheme) => `${scheme} ${REDACTED}`,
70
+ },
71
+ {
72
+ name: "JWT",
73
+ re: /\beyJ[A-Za-z0-9_-]{5,}\.[A-Za-z0-9_-]{5,}\.[A-Za-z0-9_-]{5,}/g,
74
+ replace: REDACTED,
75
+ },
76
+ {
77
+ name: "AWS access key id",
78
+ re: /\b(?:AKIA|ASIA|AGPA|AIDA|AROA|ANPA|ANVA|AIPA)[A-Z0-9]{16}\b/g,
79
+ replace: REDACTED,
80
+ },
81
+ {
82
+ name: "well-known token prefix",
83
+ re: /\b(?:gh[pousr]_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,}|xox[abprs]-[A-Za-z0-9-]{10,}|[sr]k_(?:live|test)_[A-Za-z0-9]{10,}|sk-[A-Za-z0-9_-]{20,})/g,
84
+ replace: REDACTED,
85
+ },
86
+ {
87
+ name: "key=value secret",
88
+ re: new RegExp(
89
+ `\\b(${SECRET_KEY})(["']?\\s*[:=]\\s*)(\\[REDACTED\\]|"[^"]*"|'[^']*'|[^\\s,;&'"}\\]]+)`,
90
+ "gi",
91
+ ),
92
+ replace: (_m, key, sep, value) => {
93
+ const q = value[0] === '"' || value[0] === "'" ? value[0] : "";
94
+ return `${key}${sep}${q}${REDACTED}${q}`;
95
+ },
96
+ },
97
+ ];
98
+
99
+ /** Redact every secret-shaped value in `text` (see SECRET_PATTERNS). */
100
+ export function redactSecrets(text: string): string {
101
+ let out = text;
102
+ for (const p of SECRET_PATTERNS) {
103
+ const r = p.replace;
104
+ out = typeof r === "string" ? out.replace(p.re, r) : out.replace(p.re, r);
105
+ }
106
+ return out;
107
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Repo kinds: only a `service` has a runtime (up/down/status). A `module`
3
+ * (bolt-on a host invokes) or `library` (imported code) contributes to other
4
+ * repos instead.
5
+ */
6
+ import { readManifest } from "./manifest/read-manifest.js";
7
+ import type { DerivedGraph } from "./environment-manager/derivation/types.js";
8
+ import type { RepoKind } from "./types/index.js";
9
+
10
+ export function repoKind(repoPath: string): RepoKind {
11
+ return readManifest(repoPath)?.kind ?? "service";
12
+ }
13
+
14
+ export function hasRuntime(kind: RepoKind): boolean {
15
+ return kind === "service";
16
+ }
17
+
18
+ /**
19
+ * What a non-service repo contributes to: its declared `contributes_to`, plus
20
+ * every repo that imports it (an SDK-import edge pointing at it).
21
+ */
22
+ export function contributesTo(
23
+ repo: string,
24
+ repoPath: string,
25
+ graph?: DerivedGraph,
26
+ ): string[] {
27
+ const declared = readManifest(repoPath)?.contributes_to ?? [];
28
+ const importers = (graph?.edges ?? [])
29
+ .filter((e) => e.to === repo && e.signal === "http:sdk-import")
30
+ .map((e) => e.from);
31
+ return [...new Set([...declared, ...importers])].filter((r) => r !== repo);
32
+ }
@@ -0,0 +1,131 @@
1
+ /**
2
+ * JSONL Reporter for Playwright
3
+ * Writes test results to JSONL format for historical tracking
4
+ */
5
+
6
+ import type {
7
+ Reporter,
8
+ TestCase,
9
+ TestResult as PlaywrightTestResult,
10
+ FullConfig,
11
+ Suite,
12
+ } from "@playwright/test/reporter";
13
+ import { resultWriter } from "../results/result-writer.js";
14
+ import type { Pillar, Environment } from "../types/index.js";
15
+ import * as os from "os";
16
+ import * as path from "path";
17
+
18
+ export default class JSONLReporter implements Reporter {
19
+ onBegin(_config: FullConfig, _suite: Suite) {
20
+ console.log("[JSONL Reporter] Initialized");
21
+ }
22
+
23
+ async onTestEnd(test: TestCase, result: PlaywrightTestResult) {
24
+ console.log("[JSONL Reporter] onTestEnd called");
25
+ try {
26
+ // Detect pillar from test file path
27
+ const pillar = this.detectPillar(test.location.file);
28
+ console.log(`[JSONL Reporter] Pillar detected: ${pillar}`);
29
+
30
+ // Get environment from env var or default to local
31
+ const environment = (process.env.TEST_ENV || "local") as Environment;
32
+
33
+ // Get test title path (e.g., "Smoke Tests - App Health › web app is accessible")
34
+ const testTitle = test.titlePath().slice(1).join(" › ");
35
+
36
+ // Get relative test file path for better readability
37
+ const testFile = this.getRelativeTestPath(test.location.file, pillar);
38
+
39
+ // Determine status
40
+ let status: "passed" | "failed" | "skipped";
41
+ if (result.status === "passed") {
42
+ status = "passed";
43
+ } else if (result.status === "skipped") {
44
+ status = "skipped";
45
+ } else {
46
+ status = "failed";
47
+ }
48
+
49
+ // Get screenshot and trace paths if they exist
50
+ const screenshot = result.attachments.find(
51
+ (a) => a.name === "screenshot" || a.contentType?.startsWith("image/"),
52
+ )?.path;
53
+
54
+ const trace = result.attachments.find(
55
+ (a) => a.name === "trace" || a.contentType?.includes("zip"),
56
+ )?.path;
57
+
58
+ // Write result to JSONL
59
+ await resultWriter.writeResult({
60
+ timestamp: new Date().toISOString(),
61
+ pillar,
62
+ environment,
63
+ test: `${testFile} › ${testTitle}`,
64
+ status,
65
+ duration: result.duration,
66
+ user: process.env.USER || os.userInfo().username || "unknown",
67
+ error: result.error?.message,
68
+ screenshot,
69
+ trace,
70
+ metadata: {
71
+ browser: test.parent.project()?.name,
72
+ retries: result.retry,
73
+ workerIndex: result.workerIndex,
74
+ },
75
+ });
76
+ } catch (error) {
77
+ // Don't let reporter errors break tests
78
+ console.warn(
79
+ `[JSONL Reporter] Failed to write result: ${(error as Error).message}`,
80
+ );
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Detect pillar from test file path
86
+ */
87
+ private detectPillar(filePath: string): Pillar {
88
+ if (
89
+ filePath.includes("/.synth-run/") ||
90
+ filePath.includes("/pillars/browser/")
91
+ ) {
92
+ return "synthetic";
93
+ } else if (
94
+ filePath.includes("/pillars/api/") ||
95
+ filePath.includes("/integration/")
96
+ ) {
97
+ return "integration";
98
+ } else if (
99
+ filePath.includes("/pillars/perf/") ||
100
+ filePath.includes("/performance/")
101
+ ) {
102
+ return "performance";
103
+ } else if (
104
+ filePath.includes("/pillars/smoke/") ||
105
+ filePath.includes("/.smoke-run/")
106
+ ) {
107
+ return "smoke";
108
+ } else if (filePath.includes("/pillars/journeys/")) {
109
+ // Journeys aren't Playwright-run (they have their own JSONL writer in
110
+ // src/pillars/journeys/report/); branch kept for union completeness.
111
+ return "journeys";
112
+ }
113
+
114
+ // Default to synthetic
115
+ return "synthetic";
116
+ }
117
+
118
+ /**
119
+ * Get relative test path for display
120
+ */
121
+ private getRelativeTestPath(filePath: string, pillar: Pillar): string {
122
+ // Extract path relative to pillar directory
123
+ const parts = filePath.split(`/${pillar}/tests/`);
124
+ if (parts.length > 1) {
125
+ return parts[1].replace(/\.spec\.ts$/, "");
126
+ }
127
+
128
+ // Fallback to just filename
129
+ return path.basename(filePath, ".spec.ts");
130
+ }
131
+ }