@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,203 @@
1
+ /**
2
+ * setup-default-workspace
3
+ *
4
+ * Builds the "default" workspace non-interactively: every standard system repo,
5
+ * all managed. No prompts, no cloning at setup time — den clones each
6
+ * managed repo on first use, and per-repo mode is a one-click change in the web
7
+ * UX afterwards for anything you actively develop.
8
+ *
9
+ * The repo set is the union of:
10
+ * 1. shop-web (the entry system)
11
+ * 2. the committed release fleet (config/den.yml `release.repos`) — the
12
+ * authoritative list of standard system repos. A dependency-graph walk
13
+ * alone misses islands: control-plane has no deps and nothing depends on
14
+ * it, so no walk from shop-web can ever reach it.
15
+ * 3. a best-effort dependency walk from shop-web — catches deps that aren't
16
+ * in the release fleet, when sibling checkouts exist to read manifests
17
+ * from. On a bare machine this contributes nothing, and that's fine.
18
+ * 4. infra-stack — the shared local-infra provider. It only appears as a
19
+ * dependency inside OTHER repos' manifests, so on a machine with no
20
+ * checkouts neither the fleet nor the walk can discover it.
21
+ *
22
+ * Usage:
23
+ * make setup (called from Step 4)
24
+ * npx tsx src/scripts/setup-default-workspace.ts
25
+ */
26
+
27
+ import * as fs from "fs";
28
+ import * as path from "path";
29
+ import { readManifest } from "../shared/manifest/read-manifest.js";
30
+ import { depService } from "../shared/manifest/deps.js";
31
+ import {
32
+ readWorkspacesFile,
33
+ writeWorkspacesFile,
34
+ } from "../shared/environment-manager/workspaces.js";
35
+ import { upsertCatalogEntry } from "../shared/environment-manager/catalog.js";
36
+ import { getGitHubCoords } from "../shared/smoke/git-remote.js";
37
+ import {
38
+ getReposRoot,
39
+ readDenConfig,
40
+ readReleaseConfig,
41
+ requireGithubOrg,
42
+ } from "../shared/den-config.js";
43
+ import type { WorkspaceServiceEntry } from "../shared/types/index.js";
44
+
45
+ import { product } from "../shared/product.js";
46
+ const WORKSPACE_KEY = "default";
47
+
48
+ /** The repo the dependency walk starts from: `defaults.workspace.entry`. */
49
+ function entryRepo(): string | undefined {
50
+ return readDenConfig().defaults?.workspace?.entry?.trim() || undefined;
51
+ }
52
+
53
+ /**
54
+ * Repos invisible to both the release fleet and a manifest walk on a fresh
55
+ * machine: the shared local-infra repo (`graph.infra.repo`), if any.
56
+ */
57
+ function extraStandardRepos(): string[] {
58
+ const infra = readDenConfig().graph?.infra?.repo?.trim();
59
+ return infra ? [infra] : [];
60
+ }
61
+
62
+ /** Extract unique dep repo names from a manifest (strips subsystem, e.g. "shop/api" → "shop"). */
63
+ function getDepsFromManifest(repoPath: string): string[] {
64
+ const manifest = readManifest(repoPath);
65
+ if (!manifest?.services) return [];
66
+ const deps = new Set<string>();
67
+ for (const svc of Object.values(manifest.services)) {
68
+ for (const rawDep of svc.depends_on ?? []) {
69
+ const dep = depService(rawDep);
70
+ deps.add(dep.includes("/") ? dep.split("/")[0] : dep);
71
+ }
72
+ }
73
+ return Array.from(deps);
74
+ }
75
+
76
+ /**
77
+ * BFS through the dependency graph, only following repos that exist locally.
78
+ * Returns all reachable dep repo names (excludes startRepoName itself),
79
+ * ordered by discovery depth. Best-effort by construction: a repo that isn't
80
+ * checked out truncates its branch of the walk, which is why the release
81
+ * fleet — not this walk — is the primary seed source.
82
+ */
83
+ function collectAllDeps(startRepoName: string, reposRoot: string): string[] {
84
+ const visited = new Set<string>([startRepoName]);
85
+ const ordered: string[] = [];
86
+ const queue: string[] = [path.join(reposRoot, startRepoName)];
87
+
88
+ while (queue.length > 0) {
89
+ const repoPath = queue.shift()!;
90
+ if (!fs.existsSync(repoPath)) continue;
91
+
92
+ for (const depName of getDepsFromManifest(repoPath)) {
93
+ if (!visited.has(depName)) {
94
+ visited.add(depName);
95
+ ordered.push(depName);
96
+ queue.push(path.join(reposRoot, depName));
97
+ }
98
+ }
99
+ }
100
+
101
+ return ordered;
102
+ }
103
+
104
+ /** Union of seed sources, deduped, the entry repo first. Pure given its inputs. */
105
+ export function computeSeedRepos(
106
+ fleetRepos: string[],
107
+ graphDeps: string[],
108
+ extras: string[] = [],
109
+ entry?: string,
110
+ ): string[] {
111
+ const seen = new Set<string>();
112
+ const ordered: string[] = [];
113
+ for (const name of [entry, ...fleetRepos, ...graphDeps, ...extras]) {
114
+ if (!name || seen.has(name)) continue;
115
+ seen.add(name);
116
+ ordered.push(name);
117
+ }
118
+ return ordered;
119
+ }
120
+
121
+ /** Resolve the full seed list from live config + a best-effort manifest walk. Read-only. */
122
+ export function deriveSeedRepos(): string[] {
123
+ const reposRoot = getReposRoot();
124
+ const fleet = (readReleaseConfig().repos ?? [])
125
+ .filter((r) => r.enabled !== false)
126
+ .map((r) => r.name);
127
+ const entry = entryRepo();
128
+ return computeSeedRepos(
129
+ fleet,
130
+ entry ? collectAllDeps(entry, reposRoot) : [],
131
+ extraStandardRepos(),
132
+ entry,
133
+ );
134
+ }
135
+
136
+ /** Derive the GitHub slug from a local repo path, falling back to org/repoName. */
137
+ function ghSlugFor(repoPath: string, repoName: string, org: string): string {
138
+ const coords = getGitHubCoords(repoPath);
139
+ return coords ? `${coords.owner}/${coords.repo}` : `${org}/${repoName}`;
140
+ }
141
+
142
+ async function main() {
143
+ console.log(`\n ${product().cli} · Default Workspace Setup\n`);
144
+ console.log(
145
+ ' This builds the "default" workspace — every standard system repo,\n' +
146
+ ` all managed: ${product().cli} clones and keeps each one up to date for\n` +
147
+ " you, no checkouts or decisions required. For anything you actively\n" +
148
+ " develop, flip that repo to dev-managed in the web UX (Local Dev)\n" +
149
+ " and point it at your own checkout.\n",
150
+ );
151
+
152
+ const reposRoot = getReposRoot();
153
+ const org = requireGithubOrg();
154
+ const seed = deriveSeedRepos();
155
+
156
+ const services: Record<string, WorkspaceServiceEntry> = {};
157
+ for (const repoName of seed) {
158
+ const localPath = path.join(reposRoot, repoName);
159
+ const exists = fs.existsSync(localPath);
160
+ const slug = exists
161
+ ? ghSlugFor(localPath, repoName, org)
162
+ : `${org}/${repoName}`;
163
+
164
+ services[repoName] = { mode: "managed", branch: "main" };
165
+ upsertCatalogEntry(repoName, { gh: slug });
166
+
167
+ // Every row gets a trailer — a bare row in a column of annotated rows
168
+ // reads as "skipped" even though the entry was written either way.
169
+ console.log(
170
+ ` ✓ ${repoName.padEnd(18)} → managed (main)${
171
+ exists
172
+ ? ` local checkout at ${localPath} left untouched`
173
+ : ` no local checkout — ${product().cli} clones it on first use`
174
+ }`,
175
+ );
176
+ }
177
+
178
+ const wsFile = readWorkspacesFile();
179
+ wsFile.workspaces[WORKSPACE_KEY] = { services };
180
+ writeWorkspacesFile(wsFile);
181
+
182
+ console.log(
183
+ `\n ✓ Default workspace configured with ${seed.length} service(s), all managed.`,
184
+ );
185
+ console.log(
186
+ " Change any repo to dev-managed in the web UX: make web → Local Dev.",
187
+ );
188
+ console.log(` Run 'make ${product().cli}-up' to start your services.\n`);
189
+ }
190
+
191
+ // Importable without side effects (tests import computeSeedRepos/deriveSeedRepos);
192
+ // main runs only when executed directly. argv-based rather than import.meta:
193
+ // jest's CJS transform rejects import.meta, and this file must be importable
194
+ // under test.
195
+ const isDirectRun = Boolean(
196
+ process.argv[1] && /setup-default-workspace\.(ts|js)$/.test(process.argv[1]),
197
+ );
198
+ if (isDirectRun) {
199
+ main().catch((err) => {
200
+ console.error(err);
201
+ process.exit(1);
202
+ });
203
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Who asked: the headers the MCP server (`<cli> mcp`) sends with every web
3
+ * API call, so the server can record an action as an agent's. The client
4
+ * name and version are what the MCP client reported about itself at
5
+ * initialize: a label for the audit trail, never used for authorization.
6
+ */
7
+ import type { IncomingHttpHeaders } from "http";
8
+
9
+ export const AUDIT_HEADERS = {
10
+ actor: "x-den-actor",
11
+ client: "x-den-mcp-client",
12
+ clientVersion: "x-den-mcp-client-version",
13
+ pid: "x-den-mcp-pid",
14
+ session: "x-den-mcp-session",
15
+ } as const;
16
+
17
+ export interface AgentInfo {
18
+ /** The MCP client's self-reported name (clientInfo.name). */
19
+ client: string;
20
+ /** The MCP client's self-reported version (clientInfo.version). */
21
+ clientVersion: string;
22
+ /** The MCP server process's pid. */
23
+ mcpPid: number | null;
24
+ /** The MCP server's session id (one per `<cli> mcp` process). */
25
+ session: string;
26
+ }
27
+
28
+ export interface AgentActor extends AgentInfo {
29
+ kind: "agent";
30
+ }
31
+
32
+ /** null: a person in the web UI (no agent headers). */
33
+ export type Actor = AgentActor | null;
34
+
35
+ /** Printable, single-line, bounded: these values land in log lines. */
36
+ function clean(v: unknown, max = 64): string {
37
+ const s = Array.isArray(v) ? v[0] : v;
38
+ return String(s ?? "")
39
+ .replace(/[^\x20-\x7e]/g, "")
40
+ .trim()
41
+ .slice(0, max);
42
+ }
43
+
44
+ /** The headers the MCP client sends (see AUDIT_HEADERS). */
45
+ export function agentAuditHeaders(info: AgentInfo): Record<string, string> {
46
+ return {
47
+ [AUDIT_HEADERS.actor]: "agent",
48
+ [AUDIT_HEADERS.client]: clean(info.client) || "unknown",
49
+ [AUDIT_HEADERS.clientVersion]: clean(info.clientVersion) || "unknown",
50
+ [AUDIT_HEADERS.pid]: info.mcpPid === null ? "" : String(info.mcpPid),
51
+ [AUDIT_HEADERS.session]: clean(info.session).replace(/[^A-Za-z0-9-]/g, ""),
52
+ };
53
+ }
54
+
55
+ /** The actor an incoming request names, or null for a person in the UI. */
56
+ export function actorFromHeaders(
57
+ headers: IncomingHttpHeaders | Record<string, string | undefined>,
58
+ ): Actor {
59
+ const h = headers as Record<string, string | string[] | undefined>;
60
+ if (clean(h[AUDIT_HEADERS.actor]) !== "agent") return null;
61
+ const pid = clean(h[AUDIT_HEADERS.pid], 10);
62
+ return {
63
+ kind: "agent",
64
+ client: clean(h[AUDIT_HEADERS.client]) || "unknown",
65
+ clientVersion: clean(h[AUDIT_HEADERS.clientVersion], 32) || "unknown",
66
+ mcpPid: /^\d+$/.test(pid) ? Number(pid) : null,
67
+ session: clean(h[AUDIT_HEADERS.session]).replace(/[^A-Za-z0-9-]/g, ""),
68
+ };
69
+ }
70
+
71
+ /** "agent <client> <version> (mcp pid <pid>, session <id>)". */
72
+ export function actorLabel(actor: AgentActor): string {
73
+ return (
74
+ `agent ${actor.client} ${actor.clientVersion} ` +
75
+ `(mcp pid ${actor.mcpPid ?? "?"}, session ${actor.session || "?"})`
76
+ );
77
+ }
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Applications: the boundary between an org and its repos.
3
+ *
4
+ * Org > Application > Repo > Services
5
+ *
6
+ * - declared: `applications:` in config/den.yml, plus any repo whose own
7
+ * `.den.yml` sets `application:` (the repo's own setting wins);
8
+ * - repo: every other repo is its own application, named after the repo
9
+ * (a mono-repo: Application == Repo).
10
+ *
11
+ * An application's `repos` are the ones with a runtime (services). Modules and
12
+ * libraries are placed the same way but listed as `contributors`: code that
13
+ * ships inside another repo's runtime, with no status of its own.
14
+ *
15
+ * Pure: callers pass the config, manifest applications and kinds.
16
+ */
17
+ import type { ApplicationConfig } from "./den-config.js";
18
+ import type { RepoKind } from "./types/index.js";
19
+
20
+ export interface ApplicationContributor {
21
+ repo: string;
22
+ kind: Exclude<RepoKind, "service">;
23
+ /** Repos whose runtime this code ships in. */
24
+ contributesTo: string[];
25
+ }
26
+
27
+ export interface Application {
28
+ name: string;
29
+ title: string;
30
+ description?: string;
31
+ /** Who builds it. */
32
+ owner?: string;
33
+ /** declared in config or a manifest, or a repo standing as its own application. */
34
+ source: "declared" | "repo";
35
+ /** Repos with a runtime: these get status, up and down. */
36
+ repos: string[];
37
+ /** Modules and libraries in the application: no runtime of their own. */
38
+ contributors: ApplicationContributor[];
39
+ }
40
+
41
+ export function resolveApplications(opts: {
42
+ /** Repos to place (normally the workspace's). */
43
+ repos: string[];
44
+ config?: Record<string, ApplicationConfig>;
45
+ /** repo → application named in that repo's manifest. */
46
+ manifestApplications?: Record<string, string>;
47
+ /** repo → kind; repos not listed are services. */
48
+ kinds?: Record<string, RepoKind>;
49
+ /** repo → repos it contributes to (for modules and libraries). */
50
+ contributesTo?: Record<string, string[]>;
51
+ }): Application[] {
52
+ const inScope = new Set(opts.repos);
53
+ const placed = new Set<string>();
54
+ const apps = new Map<string, Application>();
55
+ const declare = (name: string): Application => {
56
+ let a = apps.get(name);
57
+ if (!a) {
58
+ const c = opts.config?.[name];
59
+ a = {
60
+ name,
61
+ title: c?.title ?? name,
62
+ ...(c?.description ? { description: c.description } : {}),
63
+ ...(c?.owner ? { owner: c.owner } : {}),
64
+ source: "declared",
65
+ repos: [],
66
+ contributors: [],
67
+ };
68
+ apps.set(name, a);
69
+ }
70
+ return a;
71
+ };
72
+ const place = (a: Application, repo: string) => {
73
+ if (!inScope.has(repo) || placed.has(repo)) return;
74
+ placed.add(repo);
75
+ a.repos.push(repo);
76
+ };
77
+
78
+ // Config order first; declared applications exist even when none of their
79
+ // repos are in scope.
80
+ for (const name of Object.keys(opts.config ?? {})) declare(name);
81
+ // A repo's own manifest wins over the team list.
82
+ for (const [repo, name] of Object.entries(opts.manifestApplications ?? {}))
83
+ place(declare(name), repo);
84
+ for (const [name, c] of Object.entries(opts.config ?? {}))
85
+ for (const repo of c.repos ?? []) place(declare(name), repo);
86
+
87
+ // Every other repo is its own application. A declared application that
88
+ // already carries the repo's name takes it in instead.
89
+ const standalone: Application[] = [];
90
+ for (const repo of [...opts.repos].sort()) {
91
+ if (placed.has(repo)) continue;
92
+ const named = apps.get(repo);
93
+ if (named) {
94
+ place(named, repo);
95
+ continue;
96
+ }
97
+ placed.add(repo);
98
+ standalone.push({
99
+ name: repo,
100
+ title: repo,
101
+ source: "repo",
102
+ repos: [repo],
103
+ contributors: [],
104
+ });
105
+ }
106
+
107
+ return [...apps.values(), ...standalone].map((a) =>
108
+ splitByKind(a, opts.kinds, opts.contributesTo),
109
+ );
110
+ }
111
+
112
+ function splitByKind(
113
+ a: Application,
114
+ kinds: Record<string, RepoKind> = {},
115
+ contributesTo: Record<string, string[]> = {},
116
+ ): Application {
117
+ const repos: string[] = [];
118
+ const contributors: ApplicationContributor[] = [];
119
+ for (const repo of [...a.repos].sort()) {
120
+ const kind = kinds[repo] ?? "service";
121
+ if (kind === "service") repos.push(repo);
122
+ else
123
+ contributors.push({
124
+ repo,
125
+ kind,
126
+ contributesTo: contributesTo[repo] ?? [],
127
+ });
128
+ }
129
+ return { ...a, repos, contributors };
130
+ }
131
+
132
+ /** The application named `name`. */
133
+ export function findApplication(
134
+ apps: Application[],
135
+ name: string,
136
+ ): Application | undefined {
137
+ return apps.find((a) => a.name === name);
138
+ }