@synth-deploy/server 0.1.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 (317) hide show
  1. package/dist/agent/debrief-retention.d.ts +12 -0
  2. package/dist/agent/debrief-retention.d.ts.map +1 -0
  3. package/dist/agent/debrief-retention.js +27 -0
  4. package/dist/agent/debrief-retention.js.map +1 -0
  5. package/dist/agent/envoy-client.d.ts +216 -0
  6. package/dist/agent/envoy-client.d.ts.map +1 -0
  7. package/dist/agent/envoy-client.js +266 -0
  8. package/dist/agent/envoy-client.js.map +1 -0
  9. package/dist/agent/envoy-registry.d.ts +102 -0
  10. package/dist/agent/envoy-registry.d.ts.map +1 -0
  11. package/dist/agent/envoy-registry.js +319 -0
  12. package/dist/agent/envoy-registry.js.map +1 -0
  13. package/dist/agent/health-checker.d.ts +39 -0
  14. package/dist/agent/health-checker.d.ts.map +1 -0
  15. package/dist/agent/health-checker.js +49 -0
  16. package/dist/agent/health-checker.js.map +1 -0
  17. package/dist/agent/mcp-client-manager.d.ts +36 -0
  18. package/dist/agent/mcp-client-manager.d.ts.map +1 -0
  19. package/dist/agent/mcp-client-manager.js +106 -0
  20. package/dist/agent/mcp-client-manager.js.map +1 -0
  21. package/dist/agent/stale-deployment-detector.d.ts +15 -0
  22. package/dist/agent/stale-deployment-detector.d.ts.map +1 -0
  23. package/dist/agent/stale-deployment-detector.js +50 -0
  24. package/dist/agent/stale-deployment-detector.js.map +1 -0
  25. package/dist/agent/step-runner.d.ts +31 -0
  26. package/dist/agent/step-runner.d.ts.map +1 -0
  27. package/dist/agent/step-runner.js +80 -0
  28. package/dist/agent/step-runner.js.map +1 -0
  29. package/dist/agent/synth-agent.d.ts +168 -0
  30. package/dist/agent/synth-agent.d.ts.map +1 -0
  31. package/dist/agent/synth-agent.js +1195 -0
  32. package/dist/agent/synth-agent.js.map +1 -0
  33. package/dist/api/agent.d.ts +36 -0
  34. package/dist/api/agent.d.ts.map +1 -0
  35. package/dist/api/agent.js +867 -0
  36. package/dist/api/agent.js.map +1 -0
  37. package/dist/api/api-keys.d.ts +4 -0
  38. package/dist/api/api-keys.d.ts.map +1 -0
  39. package/dist/api/api-keys.js +118 -0
  40. package/dist/api/api-keys.js.map +1 -0
  41. package/dist/api/artifacts.d.ts +5 -0
  42. package/dist/api/artifacts.d.ts.map +1 -0
  43. package/dist/api/artifacts.js +142 -0
  44. package/dist/api/artifacts.js.map +1 -0
  45. package/dist/api/auth.d.ts +4 -0
  46. package/dist/api/auth.d.ts.map +1 -0
  47. package/dist/api/auth.js +280 -0
  48. package/dist/api/auth.js.map +1 -0
  49. package/dist/api/deployments.d.ts +11 -0
  50. package/dist/api/deployments.d.ts.map +1 -0
  51. package/dist/api/deployments.js +1098 -0
  52. package/dist/api/deployments.js.map +1 -0
  53. package/dist/api/environments.d.ts +5 -0
  54. package/dist/api/environments.d.ts.map +1 -0
  55. package/dist/api/environments.js +69 -0
  56. package/dist/api/environments.js.map +1 -0
  57. package/dist/api/envoy-reports.d.ts +17 -0
  58. package/dist/api/envoy-reports.d.ts.map +1 -0
  59. package/dist/api/envoy-reports.js +138 -0
  60. package/dist/api/envoy-reports.js.map +1 -0
  61. package/dist/api/envoys.d.ts +5 -0
  62. package/dist/api/envoys.d.ts.map +1 -0
  63. package/dist/api/envoys.js +192 -0
  64. package/dist/api/envoys.js.map +1 -0
  65. package/dist/api/fleet.d.ts +11 -0
  66. package/dist/api/fleet.d.ts.map +1 -0
  67. package/dist/api/fleet.js +394 -0
  68. package/dist/api/fleet.js.map +1 -0
  69. package/dist/api/graph.d.ts +8 -0
  70. package/dist/api/graph.d.ts.map +1 -0
  71. package/dist/api/graph.js +355 -0
  72. package/dist/api/graph.js.map +1 -0
  73. package/dist/api/health.d.ts +20 -0
  74. package/dist/api/health.d.ts.map +1 -0
  75. package/dist/api/health.js +248 -0
  76. package/dist/api/health.js.map +1 -0
  77. package/dist/api/idp-schemas.d.ts +41 -0
  78. package/dist/api/idp-schemas.d.ts.map +1 -0
  79. package/dist/api/idp-schemas.js +17 -0
  80. package/dist/api/idp-schemas.js.map +1 -0
  81. package/dist/api/idp.d.ts +6 -0
  82. package/dist/api/idp.d.ts.map +1 -0
  83. package/dist/api/idp.js +620 -0
  84. package/dist/api/idp.js.map +1 -0
  85. package/dist/api/intake.d.ts +10 -0
  86. package/dist/api/intake.d.ts.map +1 -0
  87. package/dist/api/intake.js +418 -0
  88. package/dist/api/intake.js.map +1 -0
  89. package/dist/api/partitions.d.ts +5 -0
  90. package/dist/api/partitions.d.ts.map +1 -0
  91. package/dist/api/partitions.js +113 -0
  92. package/dist/api/partitions.js.map +1 -0
  93. package/dist/api/progress-event-store.d.ts +62 -0
  94. package/dist/api/progress-event-store.d.ts.map +1 -0
  95. package/dist/api/progress-event-store.js +118 -0
  96. package/dist/api/progress-event-store.js.map +1 -0
  97. package/dist/api/schemas.d.ts +1000 -0
  98. package/dist/api/schemas.d.ts.map +1 -0
  99. package/dist/api/schemas.js +328 -0
  100. package/dist/api/schemas.js.map +1 -0
  101. package/dist/api/security-boundaries.d.ts +4 -0
  102. package/dist/api/security-boundaries.d.ts.map +1 -0
  103. package/dist/api/security-boundaries.js +32 -0
  104. package/dist/api/security-boundaries.js.map +1 -0
  105. package/dist/api/settings.d.ts +4 -0
  106. package/dist/api/settings.d.ts.map +1 -0
  107. package/dist/api/settings.js +99 -0
  108. package/dist/api/settings.js.map +1 -0
  109. package/dist/api/system.d.ts +75 -0
  110. package/dist/api/system.d.ts.map +1 -0
  111. package/dist/api/system.js +558 -0
  112. package/dist/api/system.js.map +1 -0
  113. package/dist/api/telemetry.d.ts +4 -0
  114. package/dist/api/telemetry.d.ts.map +1 -0
  115. package/dist/api/telemetry.js +24 -0
  116. package/dist/api/telemetry.js.map +1 -0
  117. package/dist/api/users.d.ts +4 -0
  118. package/dist/api/users.d.ts.map +1 -0
  119. package/dist/api/users.js +173 -0
  120. package/dist/api/users.js.map +1 -0
  121. package/dist/archive-unpacker.d.ts +24 -0
  122. package/dist/archive-unpacker.d.ts.map +1 -0
  123. package/dist/archive-unpacker.js +239 -0
  124. package/dist/archive-unpacker.js.map +1 -0
  125. package/dist/artifact-analyzer.d.ts +59 -0
  126. package/dist/artifact-analyzer.d.ts.map +1 -0
  127. package/dist/artifact-analyzer.js +334 -0
  128. package/dist/artifact-analyzer.js.map +1 -0
  129. package/dist/auth/idp/index.d.ts +9 -0
  130. package/dist/auth/idp/index.d.ts.map +1 -0
  131. package/dist/auth/idp/index.js +5 -0
  132. package/dist/auth/idp/index.js.map +1 -0
  133. package/dist/auth/idp/ldap.d.ts +56 -0
  134. package/dist/auth/idp/ldap.d.ts.map +1 -0
  135. package/dist/auth/idp/ldap.js +276 -0
  136. package/dist/auth/idp/ldap.js.map +1 -0
  137. package/dist/auth/idp/oidc.d.ts +27 -0
  138. package/dist/auth/idp/oidc.d.ts.map +1 -0
  139. package/dist/auth/idp/oidc.js +97 -0
  140. package/dist/auth/idp/oidc.js.map +1 -0
  141. package/dist/auth/idp/role-mapping.d.ts +9 -0
  142. package/dist/auth/idp/role-mapping.d.ts.map +1 -0
  143. package/dist/auth/idp/role-mapping.js +16 -0
  144. package/dist/auth/idp/role-mapping.js.map +1 -0
  145. package/dist/auth/idp/saml.d.ts +40 -0
  146. package/dist/auth/idp/saml.d.ts.map +1 -0
  147. package/dist/auth/idp/saml.js +117 -0
  148. package/dist/auth/idp/saml.js.map +1 -0
  149. package/dist/auth/idp/types.d.ts +23 -0
  150. package/dist/auth/idp/types.d.ts.map +1 -0
  151. package/dist/auth/idp/types.js +2 -0
  152. package/dist/auth/idp/types.js.map +1 -0
  153. package/dist/fleet/fleet-executor.d.ts +35 -0
  154. package/dist/fleet/fleet-executor.d.ts.map +1 -0
  155. package/dist/fleet/fleet-executor.js +228 -0
  156. package/dist/fleet/fleet-executor.js.map +1 -0
  157. package/dist/fleet/fleet-store.d.ts +13 -0
  158. package/dist/fleet/fleet-store.d.ts.map +1 -0
  159. package/dist/fleet/fleet-store.js +13 -0
  160. package/dist/fleet/fleet-store.js.map +1 -0
  161. package/dist/fleet/index.d.ts +5 -0
  162. package/dist/fleet/index.d.ts.map +1 -0
  163. package/dist/fleet/index.js +4 -0
  164. package/dist/fleet/index.js.map +1 -0
  165. package/dist/fleet/representative-selector.d.ts +15 -0
  166. package/dist/fleet/representative-selector.d.ts.map +1 -0
  167. package/dist/fleet/representative-selector.js +71 -0
  168. package/dist/fleet/representative-selector.js.map +1 -0
  169. package/dist/graph/graph-executor.d.ts +36 -0
  170. package/dist/graph/graph-executor.d.ts.map +1 -0
  171. package/dist/graph/graph-executor.js +348 -0
  172. package/dist/graph/graph-executor.js.map +1 -0
  173. package/dist/graph/graph-inference.d.ts +22 -0
  174. package/dist/graph/graph-inference.d.ts.map +1 -0
  175. package/dist/graph/graph-inference.js +149 -0
  176. package/dist/graph/graph-inference.js.map +1 -0
  177. package/dist/graph/graph-store.d.ts +12 -0
  178. package/dist/graph/graph-store.d.ts.map +1 -0
  179. package/dist/graph/graph-store.js +61 -0
  180. package/dist/graph/graph-store.js.map +1 -0
  181. package/dist/graph/index.d.ts +5 -0
  182. package/dist/graph/index.d.ts.map +1 -0
  183. package/dist/graph/index.js +4 -0
  184. package/dist/graph/index.js.map +1 -0
  185. package/dist/index.d.ts +2 -0
  186. package/dist/index.d.ts.map +1 -0
  187. package/dist/index.js +837 -0
  188. package/dist/index.js.map +1 -0
  189. package/dist/intake/index.d.ts +6 -0
  190. package/dist/intake/index.d.ts.map +1 -0
  191. package/dist/intake/index.js +5 -0
  192. package/dist/intake/index.js.map +1 -0
  193. package/dist/intake/intake-processor.d.ts +17 -0
  194. package/dist/intake/intake-processor.d.ts.map +1 -0
  195. package/dist/intake/intake-processor.js +99 -0
  196. package/dist/intake/intake-processor.js.map +1 -0
  197. package/dist/intake/intake-store.d.ts +7 -0
  198. package/dist/intake/intake-store.d.ts.map +1 -0
  199. package/dist/intake/intake-store.js +7 -0
  200. package/dist/intake/intake-store.js.map +1 -0
  201. package/dist/intake/registry-poller.d.ts +41 -0
  202. package/dist/intake/registry-poller.d.ts.map +1 -0
  203. package/dist/intake/registry-poller.js +202 -0
  204. package/dist/intake/registry-poller.js.map +1 -0
  205. package/dist/intake/webhook-handlers.d.ts +37 -0
  206. package/dist/intake/webhook-handlers.d.ts.map +1 -0
  207. package/dist/intake/webhook-handlers.js +268 -0
  208. package/dist/intake/webhook-handlers.js.map +1 -0
  209. package/dist/logger.d.ts +5 -0
  210. package/dist/logger.d.ts.map +1 -0
  211. package/dist/logger.js +15 -0
  212. package/dist/logger.js.map +1 -0
  213. package/dist/mcp/resources.d.ts +9 -0
  214. package/dist/mcp/resources.d.ts.map +1 -0
  215. package/dist/mcp/resources.js +72 -0
  216. package/dist/mcp/resources.js.map +1 -0
  217. package/dist/mcp/server.d.ts +15 -0
  218. package/dist/mcp/server.d.ts.map +1 -0
  219. package/dist/mcp/server.js +20 -0
  220. package/dist/mcp/server.js.map +1 -0
  221. package/dist/mcp/tools.d.ts +9 -0
  222. package/dist/mcp/tools.d.ts.map +1 -0
  223. package/dist/mcp/tools.js +88 -0
  224. package/dist/mcp/tools.js.map +1 -0
  225. package/dist/middleware/auth.d.ts +29 -0
  226. package/dist/middleware/auth.d.ts.map +1 -0
  227. package/dist/middleware/auth.js +76 -0
  228. package/dist/middleware/auth.js.map +1 -0
  229. package/dist/middleware/permissions.d.ts +13 -0
  230. package/dist/middleware/permissions.d.ts.map +1 -0
  231. package/dist/middleware/permissions.js +32 -0
  232. package/dist/middleware/permissions.js.map +1 -0
  233. package/dist/pattern-store.d.ts +104 -0
  234. package/dist/pattern-store.d.ts.map +1 -0
  235. package/dist/pattern-store.js +299 -0
  236. package/dist/pattern-store.js.map +1 -0
  237. package/package.json +54 -0
  238. package/src/agent/debrief-retention.ts +44 -0
  239. package/src/agent/envoy-client.ts +474 -0
  240. package/src/agent/envoy-registry.ts +384 -0
  241. package/src/agent/health-checker.ts +70 -0
  242. package/src/agent/mcp-client-manager.ts +131 -0
  243. package/src/agent/stale-deployment-detector.ts +79 -0
  244. package/src/agent/step-runner.ts +124 -0
  245. package/src/agent/synth-agent.ts +1567 -0
  246. package/src/api/agent.ts +1075 -0
  247. package/src/api/api-keys.ts +129 -0
  248. package/src/api/artifacts.ts +194 -0
  249. package/src/api/auth.ts +320 -0
  250. package/src/api/deployments.ts +1347 -0
  251. package/src/api/environments.ts +97 -0
  252. package/src/api/envoy-reports.ts +159 -0
  253. package/src/api/envoys.ts +237 -0
  254. package/src/api/fleet.ts +510 -0
  255. package/src/api/graph.ts +516 -0
  256. package/src/api/health.ts +311 -0
  257. package/src/api/idp-schemas.ts +19 -0
  258. package/src/api/idp.ts +735 -0
  259. package/src/api/intake.ts +537 -0
  260. package/src/api/partitions.ts +147 -0
  261. package/src/api/progress-event-store.ts +153 -0
  262. package/src/api/schemas.ts +376 -0
  263. package/src/api/security-boundaries.ts +54 -0
  264. package/src/api/settings.ts +118 -0
  265. package/src/api/system.ts +704 -0
  266. package/src/api/telemetry.ts +32 -0
  267. package/src/api/users.ts +210 -0
  268. package/src/archive-unpacker.ts +271 -0
  269. package/src/artifact-analyzer.ts +438 -0
  270. package/src/auth/idp/index.ts +8 -0
  271. package/src/auth/idp/ldap.ts +340 -0
  272. package/src/auth/idp/oidc.ts +117 -0
  273. package/src/auth/idp/role-mapping.ts +22 -0
  274. package/src/auth/idp/saml.ts +148 -0
  275. package/src/auth/idp/types.ts +22 -0
  276. package/src/fleet/fleet-executor.ts +309 -0
  277. package/src/fleet/fleet-store.ts +13 -0
  278. package/src/fleet/index.ts +4 -0
  279. package/src/fleet/representative-selector.ts +83 -0
  280. package/src/graph/graph-executor.ts +446 -0
  281. package/src/graph/graph-inference.ts +184 -0
  282. package/src/graph/graph-store.ts +75 -0
  283. package/src/graph/index.ts +4 -0
  284. package/src/index.ts +916 -0
  285. package/src/intake/index.ts +5 -0
  286. package/src/intake/intake-processor.ts +111 -0
  287. package/src/intake/intake-store.ts +7 -0
  288. package/src/intake/registry-poller.ts +230 -0
  289. package/src/intake/webhook-handlers.ts +328 -0
  290. package/src/logger.ts +19 -0
  291. package/src/mcp/resources.ts +98 -0
  292. package/src/mcp/server.ts +34 -0
  293. package/src/mcp/tools.ts +117 -0
  294. package/src/middleware/auth.ts +103 -0
  295. package/src/middleware/permissions.ts +35 -0
  296. package/src/pattern-store.ts +409 -0
  297. package/tests/agent-mode.test.ts +536 -0
  298. package/tests/api-handlers.test.ts +1245 -0
  299. package/tests/archive-unpacker.test.ts +179 -0
  300. package/tests/artifact-analyzer.test.ts +240 -0
  301. package/tests/auth-middleware.test.ts +189 -0
  302. package/tests/decision-diary.test.ts +957 -0
  303. package/tests/diary-reader.test.ts +782 -0
  304. package/tests/envoy-client.test.ts +342 -0
  305. package/tests/envoy-reports.test.ts +156 -0
  306. package/tests/mcp-tools.test.ts +213 -0
  307. package/tests/orchestration.test.ts +536 -0
  308. package/tests/partition-deletion.test.ts +143 -0
  309. package/tests/partition-isolation.test.ts +830 -0
  310. package/tests/pattern-store.test.ts +371 -0
  311. package/tests/rbac-enforcement.test.ts +409 -0
  312. package/tests/ssrf-validation.test.ts +56 -0
  313. package/tests/stale-deployment.test.ts +85 -0
  314. package/tests/step-runner.test.ts +308 -0
  315. package/tests/ui-journey.test.ts +330 -0
  316. package/tsconfig.json +11 -0
  317. package/vitest.config.ts +27 -0
@@ -0,0 +1,830 @@
1
+ import { describe, it, expect, beforeEach } from "vitest";
2
+ import {
3
+ DecisionDebrief,
4
+ PartitionManager,
5
+ PartitionStore,
6
+ EnvironmentStore,
7
+ ArtifactStore,
8
+ } from "@synth-deploy/core";
9
+ import type { Environment, DebriefEntry } from "@synth-deploy/core";
10
+ import {
11
+ SynthAgent,
12
+ InMemoryDeploymentStore,
13
+ } from "../src/agent/synth-agent.js";
14
+ import type {
15
+ ServiceHealthChecker,
16
+ HealthCheckResult,
17
+ } from "../src/agent/health-checker.js";
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Test helpers
21
+ // ---------------------------------------------------------------------------
22
+
23
+ class MockHealthChecker implements ServiceHealthChecker {
24
+ private responses: HealthCheckResult[] = [];
25
+ async check(): Promise<HealthCheckResult> {
26
+ const next = this.responses.shift();
27
+ if (next) return next;
28
+ return { reachable: true, responseTimeMs: 1, error: null };
29
+ }
30
+ willReturn(...results: HealthCheckResult[]): void {
31
+ this.responses.push(...results);
32
+ }
33
+ }
34
+
35
+ const HEALTHY: HealthCheckResult = {
36
+ reachable: true,
37
+ responseTimeMs: 1,
38
+ error: null,
39
+ };
40
+
41
+ const CONN_REFUSED: HealthCheckResult = {
42
+ reachable: false,
43
+ responseTimeMs: null,
44
+ error: "ECONNREFUSED: Connection refused",
45
+ };
46
+
47
+ function makeEnvironment(overrides: Partial<Environment> = {}): Environment {
48
+ return {
49
+ id: "env-prod",
50
+ name: "production",
51
+ variables: {},
52
+ ...overrides,
53
+ };
54
+ }
55
+
56
+ function findDecisions(entries: DebriefEntry[], substr: string): DebriefEntry[] {
57
+ return entries.filter((e) =>
58
+ e.decision.toLowerCase().includes(substr.toLowerCase()),
59
+ );
60
+ }
61
+
62
+ // Shared stores for SynthAgent
63
+ let artifactStore: ArtifactStore;
64
+ let envStore: EnvironmentStore;
65
+ let partStore: PartitionStore;
66
+
67
+ /** Insert a partition into the store with a specific ID (bypassing UUID generation). */
68
+ function forceInsertPartition(id: string, name: string, variables: Record<string, string>) {
69
+ if (partStore.get(id)) return;
70
+ (partStore as any).partitions.set(id, { id, name, variables, createdAt: new Date() });
71
+ }
72
+
73
+ /** Insert an environment into the store with a specific ID. */
74
+ function forceInsertEnvironment(id: string, name: string, variables: Record<string, string>) {
75
+ if (envStore.get(id)) return;
76
+ (envStore as any).environments.set(id, { id, name, variables });
77
+ }
78
+
79
+ /** Seed a minimal artifact and return it. */
80
+ function getOrCreateArtifact() {
81
+ const existing = artifactStore.list();
82
+ if (existing.length > 0) return existing[0];
83
+ return artifactStore.create({
84
+ name: "web-app",
85
+ type: "nodejs",
86
+ analysis: {
87
+ summary: "Test artifact",
88
+ dependencies: [],
89
+ configurationExpectations: {},
90
+ deploymentIntent: "rolling",
91
+ confidence: 0.9,
92
+ },
93
+ annotations: [],
94
+ learningHistory: [],
95
+ });
96
+ }
97
+
98
+ /**
99
+ * Deploy via SynthAgent using a PartitionContainer (from PartitionManager).
100
+ * Ensures all entities are registered in the agent's stores before triggering.
101
+ */
102
+ async function testDeployWithPartition(
103
+ agent: SynthAgent,
104
+ partitionLike: { id: string; toPartition: () => { id: string; name: string; variables: Record<string, string> } },
105
+ env: Environment,
106
+ version = "1.0.0",
107
+ variables?: Record<string, string>,
108
+ ) {
109
+ const partition = partitionLike.toPartition();
110
+
111
+ // Sync PartitionManager's partition into the agent's PartitionStore
112
+ forceInsertPartition(partition.id, partition.name, partition.variables);
113
+ if (partStore.get(partition.id)) {
114
+ partStore.setVariables(partition.id, partition.variables);
115
+ }
116
+
117
+ // Sync environment into the agent's EnvironmentStore
118
+ forceInsertEnvironment(env.id, env.name, env.variables);
119
+
120
+ const artifact = getOrCreateArtifact();
121
+
122
+ const trigger = {
123
+ artifactId: artifact.id,
124
+ artifactVersionId: version,
125
+ partitionId: partition.id,
126
+ environmentId: env.id,
127
+ triggeredBy: "user" as const,
128
+ ...(variables ? { variables } : {}),
129
+ };
130
+ return agent.triggerDeployment(trigger);
131
+ }
132
+
133
+ // ---------------------------------------------------------------------------
134
+ // Tests
135
+ // ---------------------------------------------------------------------------
136
+
137
+ describe("Partition Isolation", () => {
138
+ let diary: DecisionDebrief;
139
+ let deployments: InMemoryDeploymentStore;
140
+ let healthChecker: MockHealthChecker;
141
+ let agent: SynthAgent;
142
+ let manager: PartitionManager;
143
+
144
+ beforeEach(() => {
145
+ diary = new DecisionDebrief();
146
+ deployments = new InMemoryDeploymentStore();
147
+ healthChecker = new MockHealthChecker();
148
+ artifactStore = new ArtifactStore();
149
+ envStore = new EnvironmentStore();
150
+ partStore = new PartitionStore();
151
+ agent = new SynthAgent(
152
+ diary, deployments, artifactStore, envStore, partStore,
153
+ healthChecker, { healthCheckBackoffMs: 1, executionDelayMs: 1 },
154
+ );
155
+ manager = new PartitionManager(deployments, diary);
156
+ });
157
+
158
+ // -----------------------------------------------------------------------
159
+ // 1. Variable isolation — setting vars on A does not touch B
160
+ // -----------------------------------------------------------------------
161
+
162
+ describe("variable isolation", () => {
163
+ it("setting variables on Partition A has zero effect on Partition B", () => {
164
+ const partitionA = manager.createPartition("Acme Corp", {
165
+ DB_HOST: "acme-db",
166
+ LOG_LEVEL: "warn",
167
+ });
168
+ const partitionB = manager.createPartition("Beta Inc", {
169
+ DB_HOST: "beta-db",
170
+ LOG_LEVEL: "info",
171
+ });
172
+
173
+ // Mutate A's variables
174
+ partitionA.setVariables({ DB_HOST: "acme-db-v2", NEW_VAR: "only-acme" });
175
+
176
+ // B is completely unaffected
177
+ expect(partitionB.getVariables()).toEqual({
178
+ DB_HOST: "beta-db",
179
+ LOG_LEVEL: "info",
180
+ });
181
+
182
+ // A has the updated values
183
+ expect(partitionA.getVariables()).toEqual({
184
+ DB_HOST: "acme-db-v2",
185
+ LOG_LEVEL: "warn",
186
+ NEW_VAR: "only-acme",
187
+ });
188
+ });
189
+
190
+ it("getVariables returns a copy — external mutation cannot corrupt internal state", () => {
191
+ const partition = manager.createPartition("Acme Corp", { DB_HOST: "acme-db" });
192
+
193
+ const vars = partition.getVariables();
194
+ vars.DB_HOST = "CORRUPTED";
195
+ vars.INJECTED = "malicious";
196
+
197
+ // Internal state is untouched
198
+ expect(partition.getVariables()).toEqual({ DB_HOST: "acme-db" });
199
+ });
200
+ });
201
+
202
+ // -----------------------------------------------------------------------
203
+ // 2. Deployment visibility — A's deployments invisible to B
204
+ // -----------------------------------------------------------------------
205
+
206
+ describe("deployment visibility isolation", () => {
207
+ it("Partition A deployments are invisible to Partition B", async () => {
208
+ const partitionA = manager.createPartition("Acme Corp");
209
+ const partitionB = manager.createPartition("Beta Inc");
210
+ const env = makeEnvironment();
211
+
212
+ // Deploy to Partition A
213
+ const resultA = await testDeployWithPartition(agent, partitionA, env);
214
+ expect(resultA.status).toBe("succeeded");
215
+
216
+ // Partition A sees its deployment
217
+ expect(partitionA.getDeployments()).toHaveLength(1);
218
+ expect(partitionA.getDeployments()[0].id).toBe(resultA.id);
219
+
220
+ // Partition B sees nothing
221
+ expect(partitionB.getDeployments()).toHaveLength(0);
222
+ });
223
+
224
+ it("Partition B cannot access Partition A deployment by ID", async () => {
225
+ const partitionA = manager.createPartition("Acme Corp");
226
+ const partitionB = manager.createPartition("Beta Inc");
227
+ const env = makeEnvironment();
228
+
229
+ const resultA = await testDeployWithPartition(agent, partitionA, env);
230
+
231
+ // Partition A can access by ID
232
+ expect(partitionA.getDeployment(resultA.id)).toBeDefined();
233
+ expect(partitionA.getDeployment(resultA.id)!.id).toBe(resultA.id);
234
+
235
+ // Partition B cannot access A's deployment — returns undefined
236
+ expect(partitionB.getDeployment(resultA.id)).toBeUndefined();
237
+ });
238
+
239
+ it("multiple deployments across partitions stay fully partitioned", async () => {
240
+ const partitionA = manager.createPartition("Acme Corp");
241
+ const partitionB = manager.createPartition("Beta Inc");
242
+ const env = makeEnvironment();
243
+
244
+ // Deploy 3 times to A, 2 times to B
245
+ for (let i = 0; i < 3; i++) {
246
+ await testDeployWithPartition(agent, partitionA, env, `a-${i}`);
247
+ }
248
+ for (let i = 0; i < 2; i++) {
249
+ await testDeployWithPartition(agent, partitionB, env, `b-${i}`);
250
+ }
251
+
252
+ expect(partitionA.getDeployments()).toHaveLength(3);
253
+ expect(partitionB.getDeployments()).toHaveLength(2);
254
+
255
+ // Every deployment in A belongs to A
256
+ for (const d of partitionA.getDeployments()) {
257
+ expect(d.partitionId).toBe(partitionA.id);
258
+ }
259
+ // Every deployment in B belongs to B
260
+ for (const d of partitionB.getDeployments()) {
261
+ expect(d.partitionId).toBe(partitionB.id);
262
+ }
263
+ });
264
+ });
265
+
266
+ // -----------------------------------------------------------------------
267
+ // 3. Diary isolation — A's diary entries invisible to B
268
+ // -----------------------------------------------------------------------
269
+
270
+ describe("diary entry isolation", () => {
271
+ it("Partition A diary entries are invisible to Partition B", async () => {
272
+ const partitionA = manager.createPartition("Acme Corp");
273
+ const partitionB = manager.createPartition("Beta Inc");
274
+ const env = makeEnvironment();
275
+
276
+ await testDeployWithPartition(agent, partitionA, env);
277
+
278
+ // A has diary entries
279
+ const entriesA = partitionA.getDebriefEntries();
280
+ expect(entriesA.length).toBeGreaterThan(0);
281
+
282
+ // B has none
283
+ expect(partitionB.getDebriefEntries()).toHaveLength(0);
284
+
285
+ // Every entry in A is tagged with A's partitionId
286
+ for (const entry of entriesA) {
287
+ expect(entry.partitionId).toBe(partitionA.id);
288
+ }
289
+ });
290
+ });
291
+
292
+ // -----------------------------------------------------------------------
293
+ // 4. Error containment — failure in A doesn't affect B
294
+ // -----------------------------------------------------------------------
295
+
296
+ describe("error containment", () => {
297
+ it("deployment failure on Partition A does not prevent Partition B deployment", async () => {
298
+ const partitionA = manager.createPartition("Acme Corp");
299
+ const partitionB = manager.createPartition("Beta Inc");
300
+ const env = makeEnvironment();
301
+
302
+ // Partition A: deployment fails (health check fails)
303
+ healthChecker.willReturn(CONN_REFUSED, CONN_REFUSED);
304
+ const resultA = await testDeployWithPartition(agent, partitionA, env);
305
+ expect(resultA.status).toBe("failed");
306
+
307
+ // Partition B: deployment succeeds — A's failure had no effect
308
+ const resultB = await testDeployWithPartition(agent, partitionB, env);
309
+ expect(resultB.status).toBe("succeeded");
310
+
311
+ // Each partition sees only their own result
312
+ expect(partitionA.getDeployments()).toHaveLength(1);
313
+ expect(partitionA.getDeployments()[0].status).toBe("failed");
314
+
315
+ expect(partitionB.getDeployments()).toHaveLength(1);
316
+ expect(partitionB.getDeployments()[0].status).toBe("succeeded");
317
+ });
318
+
319
+ it("A's failure diary entries don't leak into B's diary", async () => {
320
+ const partitionA = manager.createPartition("Acme Corp");
321
+ const partitionB = manager.createPartition("Beta Inc");
322
+ const env = makeEnvironment();
323
+
324
+ // A fails
325
+ healthChecker.willReturn(CONN_REFUSED, CONN_REFUSED);
326
+ await testDeployWithPartition(agent, partitionA, env);
327
+
328
+ // B succeeds
329
+ await testDeployWithPartition(agent, partitionB, env);
330
+
331
+ // A has failure entries
332
+ const failEntries = findDecisions(
333
+ partitionA.getDebriefEntries(),
334
+ "failed",
335
+ );
336
+ expect(failEntries.length).toBeGreaterThan(0);
337
+
338
+ // B has zero failure entries
339
+ const bFailEntries = findDecisions(
340
+ partitionB.getDebriefEntries(),
341
+ "failed",
342
+ );
343
+ expect(bFailEntries).toHaveLength(0);
344
+
345
+ // B only has success-path entries
346
+ const bSuccess = findDecisions(
347
+ partitionB.getDebriefEntries(),
348
+ "Marking deployment of",
349
+ );
350
+ expect(bSuccess).toHaveLength(1);
351
+ });
352
+ });
353
+
354
+ // -----------------------------------------------------------------------
355
+ // 5. PartitionManager access control
356
+ // -----------------------------------------------------------------------
357
+
358
+ describe("manager access control", () => {
359
+ it("getPartition returns undefined for non-existent partition", () => {
360
+ expect(manager.getPartition("does-not-exist")).toBeUndefined();
361
+ });
362
+
363
+ it("listPartitions exposes metadata only — not data access paths", () => {
364
+ manager.createPartition("Acme Corp", { SECRET: "s3cret" });
365
+ manager.createPartition("Beta Inc");
366
+
367
+ const list = manager.listPartitions();
368
+ expect(list).toHaveLength(2);
369
+
370
+ // List contains id and name only
371
+ for (const item of list) {
372
+ expect(Object.keys(item)).toEqual(["id", "name"]);
373
+ }
374
+ });
375
+ });
376
+ });
377
+
378
+ // ---------------------------------------------------------------------------
379
+ // Variable precedence resolution
380
+ // ---------------------------------------------------------------------------
381
+
382
+ describe("Variable Precedence Resolution", () => {
383
+ let diary: DecisionDebrief;
384
+ let deployments: InMemoryDeploymentStore;
385
+ let manager: PartitionManager;
386
+
387
+ beforeEach(() => {
388
+ diary = new DecisionDebrief();
389
+ deployments = new InMemoryDeploymentStore();
390
+ manager = new PartitionManager(deployments, diary);
391
+ });
392
+
393
+ it("partition-level values override environment defaults", () => {
394
+ const partition = manager.createPartition("Acme Corp", {
395
+ LOG_LEVEL: "error",
396
+ DB_HOST: "acme-db",
397
+ });
398
+ const env = makeEnvironment({
399
+ variables: { LOG_LEVEL: "warn", APP_ENV: "production" },
400
+ });
401
+
402
+ const { resolved, precedenceLog } = partition.resolveVariables(env);
403
+
404
+ expect(resolved.LOG_LEVEL).toBe("error"); // partition wins
405
+ expect(resolved.APP_ENV).toBe("production"); // env only
406
+ expect(resolved.DB_HOST).toBe("acme-db"); // partition only
407
+
408
+ // Precedence log records the override
409
+ const logOverride = precedenceLog.find((e) => e.variable === "LOG_LEVEL");
410
+ expect(logOverride).toBeDefined();
411
+ expect(logOverride!.source).toBe("partition");
412
+ expect(logOverride!.resolvedValue).toBe("error");
413
+ expect(logOverride!.overrode).toEqual({
414
+ value: "warn",
415
+ source: "environment",
416
+ });
417
+ expect(logOverride!.reason).toContain("overrides");
418
+ expect(logOverride!.reason).toContain("environment");
419
+ });
420
+
421
+ it("trigger overrides both partition and environment", () => {
422
+ const partition = manager.createPartition("Acme Corp", {
423
+ LOG_LEVEL: "error",
424
+ });
425
+ const env = makeEnvironment({
426
+ variables: { LOG_LEVEL: "warn", APP_ENV: "production" },
427
+ });
428
+
429
+ const { resolved, precedenceLog } = partition.resolveVariables(env, {
430
+ LOG_LEVEL: "debug",
431
+ APP_ENV: "staging",
432
+ });
433
+
434
+ expect(resolved.LOG_LEVEL).toBe("debug"); // trigger > partition
435
+ expect(resolved.APP_ENV).toBe("staging"); // trigger > environment
436
+
437
+ // LOG_LEVEL trigger overrode partition
438
+ const logLevel = precedenceLog.find((e) => e.variable === "LOG_LEVEL");
439
+ expect(logLevel!.source).toBe("trigger");
440
+ expect(logLevel!.overrode).toEqual({ value: "error", source: "partition" });
441
+
442
+ // APP_ENV trigger overrode environment
443
+ const appEnv = precedenceLog.find((e) => e.variable === "APP_ENV");
444
+ expect(appEnv!.source).toBe("trigger");
445
+ expect(appEnv!.overrode).toEqual({
446
+ value: "production",
447
+ source: "environment",
448
+ });
449
+ });
450
+
451
+ it("full three-layer resolution: trigger > partition > environment", () => {
452
+ const partition = manager.createPartition("Acme Corp", {
453
+ DB_HOST: "acme-db",
454
+ LOG_LEVEL: "error",
455
+ });
456
+ const env = makeEnvironment({
457
+ variables: {
458
+ APP_ENV: "production",
459
+ LOG_LEVEL: "warn",
460
+ REGION: "us-east-1",
461
+ },
462
+ });
463
+
464
+ const { resolved, precedenceLog } = partition.resolveVariables(env, {
465
+ LOG_LEVEL: "debug",
466
+ });
467
+
468
+ // Full merged result
469
+ expect(resolved).toEqual({
470
+ APP_ENV: "production", // env only
471
+ LOG_LEVEL: "debug", // trigger > partition > env
472
+ REGION: "us-east-1", // env only
473
+ DB_HOST: "acme-db", // partition only
474
+ });
475
+
476
+ // Every variable has a log entry
477
+ expect(precedenceLog).toHaveLength(4);
478
+
479
+ // LOG_LEVEL: trigger wins over partition
480
+ const logLevel = precedenceLog.find((e) => e.variable === "LOG_LEVEL")!;
481
+ expect(logLevel.source).toBe("trigger");
482
+ expect(logLevel.overrode!.source).toBe("partition");
483
+ expect(logLevel.overrode!.value).toBe("error");
484
+
485
+ // REGION: environment default, no override
486
+ const region = precedenceLog.find((e) => e.variable === "REGION")!;
487
+ expect(region.source).toBe("environment");
488
+ expect(region.overrode).toBeNull();
489
+ expect(region.reason).toContain("no higher-level override");
490
+
491
+ // DB_HOST: partition-only
492
+ const dbHost = precedenceLog.find((e) => e.variable === "DB_HOST")!;
493
+ expect(dbHost.source).toBe("partition");
494
+ expect(dbHost.overrode).toBeNull();
495
+ expect(dbHost.reason).toContain("not defined at environment level");
496
+ });
497
+
498
+ it("non-conflicting variables from all levels merge correctly", () => {
499
+ const partition = manager.createPartition("Acme Corp", {
500
+ PARTITION_ONLY: "t-val",
501
+ });
502
+ const env = makeEnvironment({
503
+ variables: { ENV_ONLY: "e-val" },
504
+ });
505
+
506
+ const { resolved, precedenceLog } = partition.resolveVariables(env, {
507
+ TRIGGER_ONLY: "tr-val",
508
+ });
509
+
510
+ expect(resolved).toEqual({
511
+ ENV_ONLY: "e-val",
512
+ PARTITION_ONLY: "t-val",
513
+ TRIGGER_ONLY: "tr-val",
514
+ });
515
+
516
+ // No overrides — all variables come from distinct levels
517
+ for (const entry of precedenceLog) {
518
+ expect(entry.overrode).toBeNull();
519
+ }
520
+ });
521
+
522
+ it("same value at multiple levels is not reported as an override", () => {
523
+ const partition = manager.createPartition("Acme Corp", {
524
+ APP_ENV: "production",
525
+ });
526
+ const env = makeEnvironment({
527
+ variables: { APP_ENV: "production" },
528
+ });
529
+
530
+ const { resolved, precedenceLog } = partition.resolveVariables(env);
531
+
532
+ expect(resolved.APP_ENV).toBe("production");
533
+
534
+ // Partition "wins" by precedence but value is the same — no override reported
535
+ const appEnv = precedenceLog.find((e) => e.variable === "APP_ENV")!;
536
+ expect(appEnv.source).toBe("partition");
537
+ expect(appEnv.overrode).toBeNull();
538
+ });
539
+
540
+ it("precedence log entries have plain-language reason for every conflict", () => {
541
+ const partition = manager.createPartition("Acme Corp", {
542
+ DB_HOST: "acme-db",
543
+ LOG_LEVEL: "error",
544
+ });
545
+ const env = makeEnvironment({
546
+ variables: { DB_HOST: "default-db", LOG_LEVEL: "warn", REGION: "us-east-1" },
547
+ });
548
+
549
+ const { precedenceLog } = partition.resolveVariables(env, {
550
+ LOG_LEVEL: "debug",
551
+ });
552
+
553
+ // Every entry has a non-empty reason
554
+ for (const entry of precedenceLog) {
555
+ expect(entry.reason.length).toBeGreaterThan(0);
556
+ }
557
+
558
+ // Overrides explain what was overridden
559
+ const dbHost = precedenceLog.find((e) => e.variable === "DB_HOST")!;
560
+ expect(dbHost.reason).toContain("overrides");
561
+ expect(dbHost.reason).toContain("environment");
562
+
563
+ const logLevel = precedenceLog.find((e) => e.variable === "LOG_LEVEL")!;
564
+ expect(logLevel.reason).toContain("takes precedence");
565
+ expect(logLevel.reason).toContain("partition");
566
+ });
567
+ });
568
+
569
+ // ---------------------------------------------------------------------------
570
+ // Variable precedence integrated with SynthAgent + Decision Diary
571
+ // ---------------------------------------------------------------------------
572
+
573
+ describe("Precedence Recording in Decision Diary", () => {
574
+ let diary: DecisionDebrief;
575
+ let deployments: InMemoryDeploymentStore;
576
+ let healthChecker: MockHealthChecker;
577
+ let agent: SynthAgent;
578
+ let manager: PartitionManager;
579
+
580
+ beforeEach(() => {
581
+ diary = new DecisionDebrief();
582
+ deployments = new InMemoryDeploymentStore();
583
+ healthChecker = new MockHealthChecker();
584
+ artifactStore = new ArtifactStore();
585
+ envStore = new EnvironmentStore();
586
+ partStore = new PartitionStore();
587
+ agent = new SynthAgent(
588
+ diary, deployments, artifactStore, envStore, partStore,
589
+ healthChecker, { healthCheckBackoffMs: 1, executionDelayMs: 1 },
590
+ );
591
+ manager = new PartitionManager(deployments, diary);
592
+ });
593
+
594
+ it("SynthAgent records variable conflicts to the diary with full reasoning", async () => {
595
+ const partition = manager.createPartition("Acme Corp", {
596
+ LOG_LEVEL: "error",
597
+ });
598
+ const env = makeEnvironment({
599
+ variables: { LOG_LEVEL: "warn", APP_ENV: "production" },
600
+ });
601
+
602
+ const result = await testDeployWithPartition(
603
+ agent,
604
+ partition,
605
+ env,
606
+ "1.0.0",
607
+ { LOG_LEVEL: "debug" },
608
+ );
609
+
610
+ expect(result.status).toBe("succeeded");
611
+
612
+ // The agent's diary entries record the conflict resolution
613
+ const entries = partition.getDebriefEntries();
614
+ const configEntries = findDecisions(entries, "Accepted configuration");
615
+ expect(configEntries).toHaveLength(1);
616
+ expect(configEntries[0].reasoning).toContain("precedence");
617
+ expect(configEntries[0].reasoning).toContain("conflict");
618
+
619
+ // The standard override was recorded
620
+ const overrideEntries = findDecisions(entries, "precedence rules");
621
+ expect(overrideEntries.length).toBeGreaterThan(0);
622
+ });
623
+ });
624
+
625
+ // ---------------------------------------------------------------------------
626
+ // Scale: 50 partitions
627
+ // ---------------------------------------------------------------------------
628
+
629
+ describe("Scale: 50 Partitions", () => {
630
+ let diary: DecisionDebrief;
631
+ let deployments: InMemoryDeploymentStore;
632
+ let healthChecker: MockHealthChecker;
633
+ let agent: SynthAgent;
634
+ let manager: PartitionManager;
635
+
636
+ beforeEach(() => {
637
+ diary = new DecisionDebrief();
638
+ deployments = new InMemoryDeploymentStore();
639
+ healthChecker = new MockHealthChecker();
640
+ artifactStore = new ArtifactStore();
641
+ envStore = new EnvironmentStore();
642
+ partStore = new PartitionStore();
643
+ agent = new SynthAgent(
644
+ diary, deployments, artifactStore, envStore, partStore,
645
+ healthChecker, { healthCheckBackoffMs: 1, executionDelayMs: 1 },
646
+ );
647
+ manager = new PartitionManager(deployments, diary);
648
+ });
649
+
650
+ it("creates 50 partitions without performance degradation", () => {
651
+ const start = performance.now();
652
+
653
+ const partitions = Array.from({ length: 50 }, (_, i) =>
654
+ manager.createPartition(`Partition-${i}`, {
655
+ DB_HOST: `db-${i}.internal`,
656
+ APP_ENV: "production",
657
+ PARTITION_ID: `t-${i}`,
658
+ }),
659
+ );
660
+
661
+ const elapsed = performance.now() - start;
662
+
663
+ expect(manager.size).toBe(50);
664
+ expect(partitions).toHaveLength(50);
665
+
666
+ // Creation of 50 partitions should complete in well under 1 second
667
+ expect(elapsed).toBeLessThan(1000);
668
+
669
+ // Each partition has unique id
670
+ const ids = new Set(partitions.map((t) => t.id));
671
+ expect(ids.size).toBe(50);
672
+ });
673
+
674
+ it("50 partitions maintain full isolation across deployments", async () => {
675
+ const env = makeEnvironment({
676
+ variables: { APP_ENV: "production", LOG_LEVEL: "warn" },
677
+ });
678
+
679
+ // Create 50 partitions with distinct variables
680
+ const partitions = Array.from({ length: 50 }, (_, i) =>
681
+ manager.createPartition(`Partition-${i}`, {
682
+ DB_HOST: `db-${i}.internal`,
683
+ PARTITION_MARKER: `marker-${i}`,
684
+ }),
685
+ );
686
+
687
+ // Deploy to all 50 partitions
688
+ const start = performance.now();
689
+ const results = await Promise.all(
690
+ partitions.map((partition) =>
691
+ testDeployWithPartition(agent, partition, env),
692
+ ),
693
+ );
694
+ const elapsed = performance.now() - start;
695
+
696
+ // All 50 succeed
697
+ for (const result of results) {
698
+ expect(result.status).toBe("succeeded");
699
+ }
700
+
701
+ // 50 deployments in under 5 seconds (generous bound)
702
+ expect(elapsed).toBeLessThan(5000);
703
+
704
+ // Isolation: each partition sees exactly 1 deployment
705
+ for (let i = 0; i < 50; i++) {
706
+ const partitionDeployments = partitions[i].getDeployments();
707
+ expect(partitionDeployments).toHaveLength(1);
708
+ expect(partitionDeployments[0].partitionId).toBe(partitions[i].id);
709
+
710
+ // Variables resolved with this partition's specific values
711
+ expect(partitionDeployments[0].variables.DB_HOST).toBe(
712
+ `db-${i}.internal`,
713
+ );
714
+ expect(partitionDeployments[0].variables.PARTITION_MARKER).toBe(
715
+ `marker-${i}`,
716
+ );
717
+ expect(partitionDeployments[0].variables.APP_ENV).toBe("production");
718
+ }
719
+ });
720
+
721
+ it("partition lookup is O(1) — constant time regardless of count", () => {
722
+ // Create 50 partitions
723
+ const partitions = Array.from({ length: 50 }, (_, i) =>
724
+ manager.createPartition(`Partition-${i}`),
725
+ );
726
+
727
+ // Lookup the 1st, 25th, and 50th partition — all should be fast
728
+ const ids = [partitions[0].id, partitions[24].id, partitions[49].id];
729
+ const times: number[] = [];
730
+
731
+ for (const id of ids) {
732
+ const start = performance.now();
733
+ for (let i = 0; i < 1000; i++) {
734
+ manager.getPartition(id);
735
+ }
736
+ times.push(performance.now() - start);
737
+ }
738
+
739
+ // All 1000 lookups per partition should be sub-millisecond territory
740
+ // (generous bound: 50ms for 1000 lookups)
741
+ for (const t of times) {
742
+ expect(t).toBeLessThan(50);
743
+ }
744
+
745
+ // No dramatic variance — first and last should be within 10x of each other
746
+ const ratio = Math.max(...times) / Math.min(...times);
747
+ expect(ratio).toBeLessThan(10);
748
+ });
749
+
750
+ it("diary entries stay partitioned across all 50 partitions", async () => {
751
+ const env = makeEnvironment();
752
+
753
+ const partitions = Array.from({ length: 50 }, (_, i) =>
754
+ manager.createPartition(`Partition-${i}`),
755
+ );
756
+
757
+ // Deploy to all 50
758
+ await Promise.all(
759
+ partitions.map((partition) =>
760
+ testDeployWithPartition(agent, partition, env),
761
+ ),
762
+ );
763
+
764
+ // The full diary has entries for all 50
765
+ const allEntries = diary.getRecent(10000);
766
+ expect(allEntries.length).toBeGreaterThan(0);
767
+
768
+ // But each container only sees its own
769
+ for (let i = 0; i < 50; i++) {
770
+ const entries = partitions[i].getDebriefEntries();
771
+ expect(entries.length).toBeGreaterThan(0);
772
+ for (const entry of entries) {
773
+ expect(entry.partitionId).toBe(partitions[i].id);
774
+ }
775
+ }
776
+
777
+ // No partition sees another partition's entries
778
+ for (let i = 0; i < 50; i++) {
779
+ const myEntries = partitions[i].getDebriefEntries();
780
+ for (const entry of myEntries) {
781
+ // This entry should NOT appear in any other partition's view
782
+ for (let j = 0; j < 50; j++) {
783
+ if (j === i) continue;
784
+ const otherEntries = partitions[j].getDebriefEntries();
785
+ const leaked = otherEntries.find((e) => e.id === entry.id);
786
+ expect(leaked).toBeUndefined();
787
+ }
788
+ }
789
+ }
790
+ });
791
+
792
+ it("variable resolution at scale — 50 partitions with different overrides", () => {
793
+ const env = makeEnvironment({
794
+ variables: {
795
+ APP_ENV: "production",
796
+ LOG_LEVEL: "warn",
797
+ REGION: "us-east-1",
798
+ },
799
+ });
800
+
801
+ const partitions = Array.from({ length: 50 }, (_, i) =>
802
+ manager.createPartition(`Partition-${i}`, {
803
+ LOG_LEVEL: i % 2 === 0 ? "error" : "info",
804
+ DB_HOST: `db-${i}.internal`,
805
+ }),
806
+ );
807
+
808
+ const start = performance.now();
809
+
810
+ for (let i = 0; i < 50; i++) {
811
+ const { resolved, precedenceLog } = partitions[i].resolveVariables(env);
812
+
813
+ // Correct precedence applied
814
+ expect(resolved.APP_ENV).toBe("production"); // env
815
+ expect(resolved.REGION).toBe("us-east-1"); // env
816
+ expect(resolved.DB_HOST).toBe(`db-${i}.internal`); // partition
817
+ expect(resolved.LOG_LEVEL).toBe(i % 2 === 0 ? "error" : "info"); // partition overrides env
818
+
819
+ // LOG_LEVEL override recorded
820
+ const logEntry = precedenceLog.find((e) => e.variable === "LOG_LEVEL")!;
821
+ expect(logEntry.source).toBe("partition");
822
+ expect(logEntry.overrode).toEqual({ value: "warn", source: "environment" });
823
+ expect(logEntry.reason).toContain("overrides");
824
+ }
825
+
826
+ const elapsed = performance.now() - start;
827
+ // 50 resolutions in under 500ms
828
+ expect(elapsed).toBeLessThan(500);
829
+ });
830
+ });