@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,208 @@
1
+ /**
2
+ * Zod schema for *.journey.yml journey specs (journeys pillar).
3
+ *
4
+ * Design rules (plan: agent-e2e-journeys, packet jrn-spec-schema):
5
+ * - STRICT everywhere: unknown keys are rejected at every level, so typos
6
+ * and unsupported fields fail loudly at load time.
7
+ * - Budgets are fail-closed: max_turns, timeout_s, and max_total_tokens are
8
+ * all REQUIRED with sanity bounds only — no default values are ever
9
+ * injected; spec authors set every budget explicitly.
10
+ * - `model` is an optional spec field — the journey's declared default
11
+ * agent model. The JOURNEYS_MODEL env var, when set, overrides every
12
+ * spec's model (operator control for cost pinning / CI). A journey with
13
+ * neither source configured is refused at run time (fail-closed).
14
+ * - persona.role is limited to admin|user|beta: browser session minting via
15
+ * headlessLogin only supports those roles
16
+ * (src/shared/auth/headless-login.ts). root_admin is excluded until a
17
+ * browser login path exists for it.
18
+ * - success.assertions is the sole pass/fail authority: a discriminated
19
+ * union on `kind`, each carrying a deterministic API probe request
20
+ * ({method, path, body?} — POST probes with bodies are first-class).
21
+ */
22
+
23
+ import { z } from "zod";
24
+
25
+ // ── Budgets ──────────────────────────────────────────────────────────────────
26
+
27
+ // HLD catalog rule: an individual journey must complete within 10 minutes.
28
+ export const MAX_TIMEOUT_S = 600;
29
+
30
+ const budgetsSchema = z
31
+ .strictObject({
32
+ /** Hard cap on agent turns; exceeded → run.cancel(). */
33
+ max_turns: z.int().positive(),
34
+ /** Wall-clock cap in seconds; bounded by the HLD's 10-minute catalog rule. */
35
+ timeout_s: z.int().positive().max(MAX_TIMEOUT_S),
36
+ /** Cumulative TokenUsage.totalTokens cap; exceeded → run.cancel(). */
37
+ max_total_tokens: z.int().positive(),
38
+ })
39
+ .describe(
40
+ "All budgets are required and fail-closed; no defaults are injected.",
41
+ );
42
+
43
+ // ── Persona / surface / environments ────────────────────────────────────────
44
+
45
+ // Roles supported by headlessLogin (browser session minting); root_admin
46
+ // intentionally excluded — no headless browser login path exists for it.
47
+ const personaRoleSchema = z.enum(["admin", "user", "beta"]);
48
+
49
+ const personaSchema = z.strictObject({
50
+ role: personaRoleSchema,
51
+ /** Natural-language brief the synthetic user acts on. */
52
+ brief: z.string().min(1),
53
+ });
54
+
55
+ const surfaceSchema = z.enum(["browser", "api", "mixed"]);
56
+
57
+ // ── HTTP capabilities (review finding R2-1) ─────────────────────────────────
58
+
59
+ // Only POST/PUT/PATCH are grantable: GET stays origin-wide on the scoped
60
+ // HTTP tool (reads are its purpose) and DELETE is harness-only (teardown).
61
+ const httpCapabilityMethodSchema = z.enum(["POST", "PUT", "PATCH"]);
62
+
63
+ /**
64
+ * One journey-scoped mutating-capability grant for the agent's HTTP tool.
65
+ * `path_pattern` matches the full request path; `*` matches exactly one
66
+ * path segment (e.g. `/api/v1/items/{*}/notes`, written without the
67
+ * braces in real patterns).
68
+ */
69
+ const httpCapabilitySchema = z.strictObject({
70
+ methods: z.array(httpCapabilityMethodSchema).min(1),
71
+ path_pattern: z.string().min(1),
72
+ });
73
+
74
+ // Must match config/environments/*.json (env-selector's known set).
75
+ // The runner additionally hard-refuses production and prod regardless of spec content.
76
+ const environmentNameSchema = z.enum([
77
+ "local",
78
+ "dev",
79
+ "sandbox",
80
+ "staging",
81
+ "beta",
82
+ "prod",
83
+ "production",
84
+ ]);
85
+
86
+ // ── Assertion probe request ──────────────────────────────────────────────────
87
+
88
+ const probeMethodSchema = z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]);
89
+
90
+ /**
91
+ * Deterministic API probe executed by the verifier (harness-side,
92
+ * allowlist + JWT client). `{placeholders}` in `path` bind to
93
+ * ledger-extracted resource ids at verification time.
94
+ */
95
+ const probeRequestSchema = z.strictObject({
96
+ method: probeMethodSchema,
97
+ path: z.string().min(1),
98
+ /** Optional JSON request body (e.g. search POST probes). */
99
+ body: z.record(z.string(), z.unknown()).optional(),
100
+ });
101
+
102
+ // JSON literal usable in equality / containment / match predicates.
103
+ const jsonLiteralSchema = z.union([
104
+ z.string(),
105
+ z.number(),
106
+ z.boolean(),
107
+ z.null(),
108
+ ]);
109
+
110
+ // ── Assertion discriminated union ────────────────────────────────────────────
111
+
112
+ const httpStatusAssertion = z.strictObject({
113
+ kind: z.literal("http_status"),
114
+ request: probeRequestSchema,
115
+ /** Expected HTTP status code. */
116
+ equals: z.int().min(100).max(599),
117
+ });
118
+
119
+ const jsonPathExistsAssertion = z.strictObject({
120
+ kind: z.literal("json_path_exists"),
121
+ request: probeRequestSchema,
122
+ /** JSONPath that must resolve to a value. */
123
+ path: z.string().min(1),
124
+ });
125
+
126
+ const jsonPathEqualsAssertion = z.strictObject({
127
+ kind: z.literal("json_path_equals"),
128
+ request: probeRequestSchema,
129
+ path: z.string().min(1),
130
+ /** Literal the JSONPath value must equal. */
131
+ equals: jsonLiteralSchema,
132
+ });
133
+
134
+ const jsonPathContainsAssertion = z.strictObject({
135
+ kind: z.literal("json_path_contains"),
136
+ request: probeRequestSchema,
137
+ path: z.string().min(1),
138
+ /** String/array containment target at the JSONPath. */
139
+ contains: jsonLiteralSchema,
140
+ });
141
+
142
+ const arrayMinLengthAssertion = z.strictObject({
143
+ kind: z.literal("array_min_length"),
144
+ request: probeRequestSchema,
145
+ path: z.string().min(1),
146
+ /** Minimum number of items the array at the JSONPath must contain. */
147
+ min: z.int().positive(),
148
+ });
149
+
150
+ const arrayContainsMatchAssertion = z.strictObject({
151
+ kind: z.literal("array_contains_match"),
152
+ request: probeRequestSchema,
153
+ path: z.string().min(1),
154
+ /** Field predicates at least one array item must match. */
155
+ match: z
156
+ .record(z.string(), jsonLiteralSchema)
157
+ .refine((m) => Object.keys(m).length > 0, {
158
+ message: "match must declare at least one field predicate",
159
+ }),
160
+ });
161
+
162
+ export const journeyAssertionSchema = z.discriminatedUnion("kind", [
163
+ httpStatusAssertion,
164
+ jsonPathExistsAssertion,
165
+ jsonPathEqualsAssertion,
166
+ jsonPathContainsAssertion,
167
+ arrayMinLengthAssertion,
168
+ arrayContainsMatchAssertion,
169
+ ]);
170
+
171
+ // ── Journey spec ─────────────────────────────────────────────────────────────
172
+
173
+ const successSchema = z.strictObject({
174
+ /** Deterministic assertions — the sole pass/fail authority. Min 1. */
175
+ assertions: z.array(journeyAssertionSchema).min(1),
176
+ /** Optional advisory rubric lines for the (advisory-only) LLM judge. */
177
+ rubric: z.array(z.string()).optional(),
178
+ });
179
+
180
+ export const journeySpecSchema = z.strictObject({
181
+ name: z.string().min(1),
182
+ persona: personaSchema,
183
+ surface: surfaceSchema,
184
+ /**
185
+ * Default agent model for this journey (e.g. "composer"). The
186
+ * JOURNEYS_MODEL env var overrides it when set; with neither configured
187
+ * the runner refuses the journey (fail-closed, no invented default).
188
+ */
189
+ model: z.string().min(1).optional(),
190
+ /** Environments this journey may run against. The runner targets exactly one per invocation. */
191
+ environments: z.array(environmentNameSchema).min(1),
192
+ budgets: budgetsSchema,
193
+ /**
194
+ * Mutating grants for the scoped HTTP tool (api/mixed surfaces).
195
+ * OMITTED → the tool is read-only for mutations (safe default): every
196
+ * POST/PUT/PATCH is rejected before fetching. GET needs no grant.
197
+ */
198
+ http_capabilities: z.array(httpCapabilitySchema).optional(),
199
+ success: successSchema,
200
+ });
201
+
202
+ export type JourneySpec = z.infer<typeof journeySpecSchema>;
203
+ export type JourneyHttpCapability = z.infer<typeof httpCapabilitySchema>;
204
+ export type JourneyAssertion = z.infer<typeof journeyAssertionSchema>;
205
+ export type JourneyProbeRequest = z.infer<typeof probeRequestSchema>;
206
+ export type JourneyBudgets = z.infer<typeof budgetsSchema>;
207
+ export type JourneyPersonaRole = z.infer<typeof personaRoleSchema>;
208
+ export type JourneySurface = z.infer<typeof surfaceSchema>;
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Journeys teardown executor — pure library over the run ledger (plan:
3
+ * jrn-runner packet, Approach §Isolation and teardown for Phase-1 pilots).
4
+ *
5
+ * INVOKED ONLY BY THE CALLER (the pilot entrypoint, jrn-pilot-journeys),
6
+ * strictly after runJourney → verify → report, in a finally block. Never
7
+ * called from inside runJourney.
8
+ *
9
+ * Deletes recognized ledger resources — from BOTH feeds (http-tool and
10
+ * browser network interception) — in REVERSE creation order, executing
11
+ * each entry's explicit cleanup descriptor through the caller-supplied
12
+ * allowlisted+authenticated HTTP client. Deletion failures and
13
+ * unmanaged-mutation warnings surface in the TeardownReport; nothing is
14
+ * swallowed, and unmanaged mutations are reported, never guessed-deleted.
15
+ *
16
+ * Run identity note: the runner injects the runId (`jrn-<journey>-<ts>-
17
+ * <rand4>`) into the persona brief as a required display-name prefix —
18
+ * best-effort labeling for humans; the ledger stays the teardown source
19
+ * of truth.
20
+ */
21
+
22
+ import type {
23
+ CleanupRequest,
24
+ LedgerEntry,
25
+ RunLedger,
26
+ UnmanagedMutationWarning,
27
+ } from "./tools/ledger.js";
28
+
29
+ /**
30
+ * Caller-supplied HTTP client executing one cleanup request. The pilot
31
+ * entrypoint wires this to the allowlisted+JWT client so teardown carries
32
+ * the same auth and reach constraints as the journey itself.
33
+ */
34
+ export type TeardownHttpClient = (
35
+ request: CleanupRequest,
36
+ ) => Promise<{ status: number }>;
37
+
38
+ /** Outcome of one attempted deletion. */
39
+ export interface TeardownResultItem {
40
+ entry: LedgerEntry;
41
+ ok: boolean;
42
+ /** HTTP status when the request completed. */
43
+ status?: number;
44
+ /** Error message when the client threw (transport failure etc.). */
45
+ error?: string;
46
+ }
47
+
48
+ export interface TeardownReport {
49
+ /** Every attempted deletion, in execution (reverse-creation) order. */
50
+ results: TeardownResultItem[];
51
+ /** Subset of results that did not succeed — never swallowed. */
52
+ failures: TeardownResultItem[];
53
+ /** Unmanaged mutations observed during the run (reported, not deleted). */
54
+ unmanagedWarnings: readonly UnmanagedMutationWarning[];
55
+ /**
56
+ * Browser interception-processing failures — a creation MAY have gone
57
+ * unrecorded, so these surface as report warnings (finding 7).
58
+ */
59
+ interceptionErrors: readonly string[];
60
+ }
61
+
62
+ /**
63
+ * Delete every recognized ledger resource in reverse creation order via
64
+ * its explicit cleanup descriptor. A failed deletion (non-2xx or thrown
65
+ * client error) is recorded and execution continues with the remaining
66
+ * entries — partial teardown is better than none, and the report carries
67
+ * the full picture.
68
+ */
69
+ export async function teardownLedger(
70
+ ledger: RunLedger,
71
+ httpClient: TeardownHttpClient,
72
+ ): Promise<TeardownReport> {
73
+ const results: TeardownResultItem[] = [];
74
+
75
+ for (const entry of [...ledger.entries].reverse()) {
76
+ try {
77
+ const response = await httpClient(entry.cleanup);
78
+ results.push({
79
+ entry,
80
+ ok: response.status >= 200 && response.status < 300,
81
+ status: response.status,
82
+ });
83
+ } catch (error) {
84
+ results.push({
85
+ entry,
86
+ ok: false,
87
+ error: error instanceof Error ? error.message : String(error),
88
+ });
89
+ }
90
+ }
91
+
92
+ return {
93
+ results,
94
+ failures: results.filter((result) => !result.ok),
95
+ unmanagedWarnings: ledger.warnings,
96
+ interceptionErrors: ledger.interceptionErrors,
97
+ };
98
+ }
@@ -0,0 +1,219 @@
1
+ /**
2
+ * Journeys browser-session provisioning — mints a FRESH Playwright
3
+ * storageState at every run start via the existing den machinery
4
+ * (getCredentials → headlessLogin), exactly mirroring
5
+ * src/pillars/browser/global-setup.ts, which re-mints every run. There is
6
+ * deliberately NO staleness heuristic.
7
+ *
8
+ * Ordering note for the runner (jrn-runner packet): getCredentials reads
9
+ * E2E_* env vars in CI, so this MUST run BEFORE sanitizeEnvironment().
10
+ * The return value is ONLY the auth-file path — credential values never
11
+ * leave this function, and downstream code (browser toolset) consumes the
12
+ * path alone.
13
+ */
14
+
15
+ import * as fs from "fs";
16
+ import * as path from "path";
17
+ import { getDistRoot } from "../../../shared/paths.js";
18
+ import {
19
+ getCredentials,
20
+ headlessLogin,
21
+ } from "../../../shared/auth/headless-login.js";
22
+ import { envSelector } from "../../../shared/environment/env-selector.js";
23
+ import {
24
+ resolveAuthPath,
25
+ type Role,
26
+ } from "../../../shared/environment/playwright-env.js";
27
+ import type { Environment } from "../../../shared/types/index.js";
28
+ import type { JourneysSessionTokenConfig } from "../app-config.js";
29
+
30
+ import { product } from "../../../shared/product.js";
31
+ /**
32
+ * Mint a fresh authenticated browser session for `role` against `env`'s
33
+ * web surface and return the storageState auth-file path (and nothing
34
+ * else). Throws a loud, actionable error when credentials cannot be
35
+ * sourced — naming both the 1Password path (local) and the E2E_* env-var
36
+ * path (CI), mirroring browser/global-setup.ts.
37
+ */
38
+ export async function provisionBrowserSession(
39
+ role: Role,
40
+ env: Environment,
41
+ ): Promise<string> {
42
+ const envConfig = await envSelector.loadEnvironment(env);
43
+ const webBaseUrl = envConfig.baseUrls.web;
44
+ const authFile = resolveAuthPath(role, env, getDistRoot());
45
+
46
+ const credentials = await getCredentials(role, env);
47
+ if (!credentials) {
48
+ const localHint =
49
+ role === "admin"
50
+ ? ` Local: make auth-configure env=${env} (handles 1Password sign-in automatically)\n`
51
+ : ` Local: sign in to the secrets backend (make ${product().cli}-setup-secrets); the ${role} account comes from test_accounts in the org config\n`;
52
+ throw new Error(
53
+ `No credentials for ${role}.\n` +
54
+ localHint +
55
+ ` CI: set E2E_${role.toUpperCase()}_EMAIL / E2E_${role.toUpperCase()}_PASSWORD`,
56
+ );
57
+ }
58
+
59
+ fs.mkdirSync(path.dirname(authFile), { recursive: true });
60
+
61
+ try {
62
+ await headlessLogin(
63
+ role,
64
+ webBaseUrl,
65
+ credentials.email,
66
+ credentials.password,
67
+ authFile,
68
+ envConfig.auth?.browser,
69
+ );
70
+ } catch (error) {
71
+ // Re-throw with a credential-free message: headlessLogin failures can
72
+ // carry page/URL context but must never leak email or password values
73
+ // into transcripts or reports.
74
+ const raw = error instanceof Error ? error.message : String(error);
75
+ const scrubbed = raw
76
+ .split(credentials.password)
77
+ .join("[REDACTED]")
78
+ .split(credentials.email)
79
+ .join("[REDACTED]");
80
+ throw new Error(
81
+ `Browser session mint failed for ${role} against ${webBaseUrl}: ${scrubbed}`,
82
+ );
83
+ }
84
+
85
+ return authFile;
86
+ }
87
+
88
+ /** Bearer + extra headers for API-assertion probes, from a browser session. */
89
+ export interface SessionApiToken {
90
+ /** Bearer token for the app's API. */
91
+ apiToken: string;
92
+ /**
93
+ * Extra headers every API call carries (`session_token.headers`: header
94
+ * name -> response field), e.g. the org or workspace the session resolves
95
+ * to. Not secrets; empty when none are declared.
96
+ */
97
+ headers: Record<string, string>;
98
+ }
99
+
100
+ /** Injectable HTTP layer for {@link deriveApiTokenFromSession} (unit tests). */
101
+ export interface DeriveApiTokenDeps {
102
+ /**
103
+ * GET `tokenPath` on the web origin with the saved session cookies. The
104
+ * default drives a short-lived Playwright request context seeded from the
105
+ * storageState auth file, so HttpOnly session cookies are replayed exactly
106
+ * as a browser would.
107
+ */
108
+ fetchAccessToken?: (
109
+ webBaseUrl: string,
110
+ authFilePath: string,
111
+ tokenPath: string,
112
+ ) => Promise<{ status: number; body: unknown }>;
113
+ }
114
+
115
+ /* istanbul ignore next - Playwright request context tested via E2E */
116
+ async function defaultFetchAccessToken(
117
+ webBaseUrl: string,
118
+ authFilePath: string,
119
+ tokenPath: string,
120
+ ): Promise<{ status: number; body: unknown }> {
121
+ const { request } = await import("playwright");
122
+ const context = await request.newContext({
123
+ baseURL: webBaseUrl,
124
+ storageState: authFilePath,
125
+ });
126
+ try {
127
+ const response = await context.get(tokenPath, {
128
+ timeout: 15000,
129
+ });
130
+ let body: unknown;
131
+ try {
132
+ body = await response.json();
133
+ } catch {
134
+ body = await response.text();
135
+ }
136
+ return { status: response.status(), body };
137
+ } finally {
138
+ await context.dispose();
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Derive an API Bearer token (and any declared scoping headers) from a
144
+ * minted browser session — no password grant. Calls the app's own
145
+ * session-token route (`journeys.app.session_token.path` in the manifest)
146
+ * on the web origin with the session cookies from `authFilePath`: the token
147
+ * comes from the same real-user session the agent navigates with.
148
+ *
149
+ * Fails loudly (never returns a partial/absent token) so a missing token can
150
+ * never silently degrade a deterministic API assertion into a false pass.
151
+ */
152
+ export async function deriveApiTokenFromSession(
153
+ authFilePath: string,
154
+ env: Environment,
155
+ sessionToken: JourneysSessionTokenConfig | undefined,
156
+ deps: DeriveApiTokenDeps = {},
157
+ ): Promise<SessionApiToken> {
158
+ if (sessionToken === undefined) {
159
+ throw new Error(
160
+ "No journeys.app.session_token in the spec-hosting repo's manifest: " +
161
+ "the journeys pillar needs the app's route that returns an API " +
162
+ "token for a browser session (path, token_field, headers) to run " +
163
+ "deterministic API assertions.",
164
+ );
165
+ }
166
+ const fetchAccessToken = deps.fetchAccessToken ?? defaultFetchAccessToken;
167
+ const envConfig = await envSelector.loadEnvironment(env);
168
+ const webBaseUrl = envConfig.baseUrls.web;
169
+ const tokenPath = sessionToken.path;
170
+ const tokenField = sessionToken.token_field ?? "access_token";
171
+
172
+ const { status, body } = await fetchAccessToken(
173
+ webBaseUrl,
174
+ authFilePath,
175
+ tokenPath,
176
+ );
177
+
178
+ if (status !== 200) {
179
+ const detail =
180
+ status === 401
181
+ ? "session not authenticated (storageState has no valid session cookie)"
182
+ : status === 403
183
+ ? "session is authenticated but has no API access"
184
+ : `unexpected status ${status}`;
185
+ throw new Error(
186
+ `Failed to derive an API token from the browser session against ` +
187
+ `${webBaseUrl}${tokenPath}: ${detail}. Deterministic API ` +
188
+ `assertions require a real-user token minted from the session.`,
189
+ );
190
+ }
191
+
192
+ const parsed =
193
+ typeof body === "object" && body !== null
194
+ ? (body as Record<string, unknown>)
195
+ : {};
196
+ const field = (name: string): string => {
197
+ const v = parsed[name];
198
+ return typeof v === "string" ? v.trim() : "";
199
+ };
200
+
201
+ const apiToken = field(tokenField);
202
+ const headers: Record<string, string> = {};
203
+ const missing: string[] = apiToken.length === 0 ? [tokenField] : [];
204
+ for (const [header, name] of Object.entries(sessionToken.headers ?? {})) {
205
+ const value = field(name);
206
+ if (value.length === 0) missing.push(name);
207
+ else headers[header] = value;
208
+ }
209
+
210
+ if (missing.length > 0) {
211
+ throw new Error(
212
+ `The browser session's ${tokenPath} response was missing ` +
213
+ `${missing.join(", ")}. Cannot run deterministic API assertions ` +
214
+ `without a real-user token.`,
215
+ );
216
+ }
217
+
218
+ return { apiToken, headers };
219
+ }