@highstate/backend 0.9.16 → 0.9.19

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 (302) hide show
  1. package/dist/chunk-5WVU2AK4.js +1535 -0
  2. package/dist/chunk-5WVU2AK4.js.map +1 -0
  3. package/dist/chunk-I7BWSAN6.js +49 -0
  4. package/dist/chunk-I7BWSAN6.js.map +1 -0
  5. package/dist/{chunk-RCB4AFGD.js → chunk-VB4YL327.js} +51 -71
  6. package/dist/chunk-VB4YL327.js.map +1 -0
  7. package/dist/database/local/prisma.config.js +26 -0
  8. package/dist/database/local/prisma.config.js.map +1 -0
  9. package/dist/highstate.manifest.json +5 -4
  10. package/dist/index.js +7676 -6634
  11. package/dist/index.js.map +1 -1
  12. package/dist/library/package-resolution-worker.js +8 -6
  13. package/dist/library/package-resolution-worker.js.map +1 -1
  14. package/dist/library/worker/main.js +63 -58
  15. package/dist/library/worker/main.js.map +1 -1
  16. package/dist/shared/index.js +3 -216
  17. package/dist/shared/index.js.map +1 -1
  18. package/package.json +23 -11
  19. package/prisma/backend/_schema/layout.prisma +7 -0
  20. package/prisma/backend/_schema/library.prisma +17 -0
  21. package/prisma/backend/_schema/project.prisma +101 -0
  22. package/prisma/backend/_schema/pulumi.prisma +17 -0
  23. package/prisma/backend/postgresql/main.prisma +17 -0
  24. package/prisma/backend/sqlite/main.prisma +17 -0
  25. package/prisma/backend/sqlite/migrations/20250817070609_initiial/migration.sql +34 -0
  26. package/prisma/backend/sqlite/migrations/20250817104948_add_fields/migration.sql +59 -0
  27. package/prisma/backend/sqlite/migrations/20250818082732_add_models/migration.sql +41 -0
  28. package/prisma/backend/sqlite/migrations/20250818083106_a/migration.sql +19 -0
  29. package/prisma/backend/sqlite/migrations/20250818101945_hi/migration.sql +1 -0
  30. package/prisma/backend/sqlite/migrations/20250819082315_a/migration.sql +5 -0
  31. package/prisma/backend/sqlite/migrations/migration_lock.toml +3 -0
  32. package/prisma/project/api-key.prisma +27 -0
  33. package/prisma/project/artifact.prisma +52 -0
  34. package/prisma/project/custom-status.prisma +46 -0
  35. package/prisma/project/evaluation.prisma +35 -0
  36. package/prisma/project/instance.prisma +160 -0
  37. package/prisma/project/layout.prisma +23 -0
  38. package/prisma/project/lock.prisma +18 -0
  39. package/prisma/project/main.prisma +17 -0
  40. package/prisma/project/migrations/20250816081310_initial/migration.sql +300 -0
  41. package/prisma/project/migrations/20250816082523_test/migration.sql +72 -0
  42. package/prisma/project/migrations/20250818065643_update/migration.sql +42 -0
  43. package/prisma/project/migrations/20250818070758_a/migration.sql +8 -0
  44. package/prisma/project/migrations/20250818070913_a/migration.sql +8 -0
  45. package/prisma/project/migrations/20250818082720_add_motels/migration.sql +11 -0
  46. package/prisma/project/migrations/20250818112523_hello/migration.sql +35 -0
  47. package/prisma/project/migrations/20250819082305_a/migration.sql +14 -0
  48. package/prisma/project/migrations/20250819165004_add_missing_fields/migration.sql +216 -0
  49. package/prisma/project/migrations/20250819171309_a/migration.sql +22 -0
  50. package/prisma/project/migrations/20250820113949_a/migration.sql +66 -0
  51. package/prisma/project/migrations/20250820144256_b/migration.sql +31 -0
  52. package/prisma/project/migrations/20250820145547_a/migration.sql +24 -0
  53. package/prisma/project/migrations/20250820182517_b/migration.sql +2 -0
  54. package/prisma/project/migrations/20250821172324_a/migration.sql +2 -0
  55. package/prisma/project/migrations/20250822081339_a/migration.sql +219 -0
  56. package/prisma/project/migrations/20250822083742_b/migration.sql +1 -0
  57. package/prisma/project/migrations/20250822105134_boom/migration.sql +1 -0
  58. package/prisma/project/migrations/20250822141028_b/migration.sql +1 -0
  59. package/prisma/project/migrations/20250822142342_b/migration.sql +16 -0
  60. package/prisma/project/migrations/20250824072720_a/migration.sql +1 -0
  61. package/prisma/project/migrations/20250824093656_b/migration.sql +21 -0
  62. package/prisma/project/migrations/20250825082518_a/migration.sql +1 -0
  63. package/prisma/project/migrations/20250825085343_b/migration.sql +1 -0
  64. package/prisma/project/migrations/20250825091312_a/migration.sql +1 -0
  65. package/prisma/project/migrations/20250903095431_hi/migration.sql +44 -0
  66. package/prisma/project/migrations/20250903174255_a/migration.sql +24 -0
  67. package/prisma/project/migrations/20250908095205_hi/migration.sql +18 -0
  68. package/prisma/project/migrations/20250909155857_hi/migration.sql +15 -0
  69. package/prisma/project/migrations/migration_lock.toml +3 -0
  70. package/prisma/project/model.prisma +37 -0
  71. package/prisma/project/operation.prisma +148 -0
  72. package/prisma/project/page.prisma +41 -0
  73. package/prisma/project/secret.prisma +42 -0
  74. package/prisma/project/service-account.prisma +36 -0
  75. package/prisma/project/terminal.prisma +90 -0
  76. package/prisma/project/trigger.prisma +31 -0
  77. package/prisma/project/unlock-method.prisma +32 -0
  78. package/prisma/project/worker.prisma +138 -0
  79. package/src/artifact/abstractions.ts +13 -13
  80. package/src/artifact/encryption.ts +31 -15
  81. package/src/artifact/factory.ts +7 -10
  82. package/src/artifact/local.ts +33 -50
  83. package/src/business/api-key.ts +24 -36
  84. package/src/business/artifact.test.ts +978 -0
  85. package/src/business/artifact.ts +136 -215
  86. package/src/business/evaluation.ts +328 -0
  87. package/src/business/index.ts +5 -1
  88. package/src/business/instance-lock.test.ts +1060 -0
  89. package/src/business/instance-lock.ts +387 -77
  90. package/src/business/instance-state.test.ts +735 -0
  91. package/src/business/instance-state.ts +604 -217
  92. package/src/business/operation.test.ts +439 -0
  93. package/src/business/operation.ts +174 -208
  94. package/src/business/project-model.ts +258 -0
  95. package/src/business/project-unlock.ts +172 -112
  96. package/src/business/project.ts +407 -0
  97. package/src/business/secret.test.ts +513 -0
  98. package/src/business/secret.ts +194 -131
  99. package/src/business/settings.test.ts +695 -0
  100. package/src/business/settings.ts +855 -0
  101. package/src/business/terminal-session.ts +90 -0
  102. package/src/business/unit-extra.test.ts +539 -0
  103. package/src/business/unit-extra.ts +160 -0
  104. package/src/business/worker.test.ts +391 -0
  105. package/src/business/worker.ts +250 -114
  106. package/src/common/codebase.ts +65 -0
  107. package/src/common/index.ts +3 -2
  108. package/src/common/logger.ts +5 -0
  109. package/src/common/utils.ts +4 -3
  110. package/src/config.ts +15 -12
  111. package/src/database/_generated/backend/postgresql/client.ts +72 -0
  112. package/src/database/_generated/backend/postgresql/commonInputTypes.ts +350 -0
  113. package/src/database/_generated/backend/postgresql/enums.ts +13 -0
  114. package/src/database/_generated/backend/postgresql/internal/class.ts +320 -0
  115. package/src/database/_generated/backend/postgresql/internal/prismaNamespace.ts +1238 -0
  116. package/src/database/_generated/backend/postgresql/models/Library.ts +1263 -0
  117. package/src/database/_generated/backend/postgresql/models/Project.ts +2175 -0
  118. package/src/database/_generated/backend/postgresql/models/ProjectModelStorage.ts +1263 -0
  119. package/src/database/_generated/backend/postgresql/models/ProjectSpace.ts +1602 -0
  120. package/src/database/_generated/backend/postgresql/models/PulumiBackend.ts +1263 -0
  121. package/src/database/_generated/backend/postgresql/models/UserWorkspaseLayout.ts +1065 -0
  122. package/src/database/_generated/backend/postgresql/models.ts +16 -0
  123. package/src/database/_generated/backend/postgresql/pjtg.ts +182 -0
  124. package/src/database/_generated/backend/sqlite/client.ts +72 -0
  125. package/src/database/_generated/backend/sqlite/commonInputTypes.ts +331 -0
  126. package/src/database/_generated/backend/sqlite/enums.ts +13 -0
  127. package/src/database/_generated/backend/sqlite/internal/class.ts +318 -0
  128. package/src/database/_generated/backend/sqlite/internal/prismaNamespace.ts +1207 -0
  129. package/src/database/_generated/backend/sqlite/models/Library.ts +1261 -0
  130. package/src/database/_generated/backend/sqlite/models/Project.ts +2169 -0
  131. package/src/database/_generated/backend/sqlite/models/ProjectModelStorage.ts +1261 -0
  132. package/src/database/_generated/backend/sqlite/models/ProjectSpace.ts +1599 -0
  133. package/src/database/_generated/backend/sqlite/models/PulumiBackend.ts +1261 -0
  134. package/src/database/_generated/backend/sqlite/models/UserWorkspaseLayout.ts +1063 -0
  135. package/src/database/_generated/backend/sqlite/models.ts +16 -0
  136. package/src/database/_generated/backend/sqlite/pjtg.ts +182 -0
  137. package/src/database/_generated/project/client.ts +204 -0
  138. package/src/database/_generated/project/commonInputTypes.ts +827 -0
  139. package/src/database/_generated/project/enums.ts +104 -0
  140. package/src/database/_generated/project/internal/class.ts +479 -0
  141. package/src/database/_generated/project/internal/prismaNamespace.ts +2974 -0
  142. package/src/database/_generated/project/models/ApiKey.ts +1506 -0
  143. package/src/database/_generated/project/models/Artifact.ts +2051 -0
  144. package/src/database/_generated/project/models/HubModel.ts +1125 -0
  145. package/src/database/_generated/project/models/InstanceCustomStatus.ts +1713 -0
  146. package/src/database/_generated/project/models/InstanceEvaluationState.ts +1312 -0
  147. package/src/database/_generated/project/models/InstanceLock.ts +1268 -0
  148. package/src/database/_generated/project/models/InstanceModel.ts +1125 -0
  149. package/src/database/_generated/project/models/InstanceOperationState.ts +1707 -0
  150. package/src/database/_generated/project/models/InstanceState.ts +4613 -0
  151. package/src/database/_generated/project/models/Operation.ts +1647 -0
  152. package/src/database/_generated/project/models/OperationLog.ts +1455 -0
  153. package/src/database/_generated/project/models/Page.ts +1838 -0
  154. package/src/database/_generated/project/models/Secret.ts +1692 -0
  155. package/src/database/_generated/project/models/ServiceAccount.ts +2165 -0
  156. package/src/database/_generated/project/models/Terminal.ts +2038 -0
  157. package/src/database/_generated/project/models/TerminalSession.ts +1454 -0
  158. package/src/database/_generated/project/models/TerminalSessionLog.ts +1280 -0
  159. package/src/database/_generated/project/models/Trigger.ts +1430 -0
  160. package/src/database/_generated/project/models/UnlockMethod.ts +1220 -0
  161. package/src/database/_generated/project/models/UserCompositeViewport.ts +1280 -0
  162. package/src/database/_generated/project/models/UserProjectViewport.ts +1059 -0
  163. package/src/database/_generated/project/models/Worker.ts +1459 -0
  164. package/src/database/_generated/project/models/WorkerUnitRegistration.ts +1524 -0
  165. package/src/database/_generated/project/models/WorkerVersion.ts +1974 -0
  166. package/src/database/_generated/project/models/WorkerVersionLog.ts +1318 -0
  167. package/src/database/_generated/project/models.ts +35 -0
  168. package/src/database/_generated/project/pjtg.ts +182 -0
  169. package/src/database/abstractions.ts +19 -0
  170. package/src/database/factory.ts +37 -0
  171. package/src/database/index.ts +6 -0
  172. package/src/database/local/backend.ts +134 -0
  173. package/src/database/local/index.ts +3 -0
  174. package/src/database/local/meta.ts +46 -0
  175. package/src/database/local/prisma.config.ts +25 -0
  176. package/src/database/local/project.ts +39 -0
  177. package/src/database/manager.ts +181 -0
  178. package/src/database/migrate.ts +35 -0
  179. package/src/database/prisma.ts +56 -0
  180. package/src/database/well-known.ts +38 -0
  181. package/src/index.ts +4 -4
  182. package/src/library/abstractions.ts +21 -14
  183. package/src/library/factory.ts +1 -1
  184. package/src/library/local.ts +86 -38
  185. package/src/library/package-resolution-worker.ts +1 -1
  186. package/src/library/worker/evaluator.ts +61 -48
  187. package/src/library/worker/loader.lite.ts +14 -1
  188. package/src/library/worker/main.ts +9 -16
  189. package/src/library/worker/protocol.ts +0 -12
  190. package/src/lock/manager.ts +12 -7
  191. package/src/orchestrator/manager.ts +198 -131
  192. package/src/orchestrator/operation-context.ts +357 -0
  193. package/src/orchestrator/operation-plan.destroy.test.md +357 -0
  194. package/src/orchestrator/operation-plan.destroy.test.ts +775 -0
  195. package/src/orchestrator/operation-plan.fixtures.ts +213 -0
  196. package/src/orchestrator/operation-plan.md +198 -0
  197. package/src/orchestrator/operation-plan.refresh.test.md +199 -0
  198. package/src/orchestrator/operation-plan.refresh.test.ts +367 -0
  199. package/src/orchestrator/operation-plan.ts +709 -0
  200. package/src/orchestrator/operation-plan.update.test.md +485 -0
  201. package/src/orchestrator/operation-plan.update.test.ts +1066 -0
  202. package/src/orchestrator/operation-workset.ts +235 -583
  203. package/src/orchestrator/operation.ts +446 -904
  204. package/src/orchestrator/plan-test-builder.ts +267 -0
  205. package/src/project-model/abstractions.ts +118 -0
  206. package/src/project-model/backends/codebase.ts +365 -0
  207. package/src/project-model/backends/database.ts +440 -0
  208. package/src/project-model/errors.ts +81 -0
  209. package/src/project-model/factory.ts +24 -0
  210. package/src/project-model/index.ts +4 -0
  211. package/src/project-model/utils.test.ts +544 -0
  212. package/src/project-model/utils.ts +242 -0
  213. package/src/pubsub/abstractions.ts +10 -1
  214. package/src/pubsub/factory.ts +4 -4
  215. package/src/pubsub/index.ts +1 -0
  216. package/src/pubsub/manager.ts +49 -25
  217. package/src/pubsub/memory.ts +31 -0
  218. package/src/runner/abstractions.ts +38 -26
  219. package/src/runner/artifact-env.ts +17 -6
  220. package/src/runner/factory.ts +6 -6
  221. package/src/runner/force-abort.ts +3 -6
  222. package/src/runner/local.ts +79 -72
  223. package/src/runner/pulumi.ts +26 -63
  224. package/src/services.ts +214 -103
  225. package/src/shared/models/backend/index.ts +3 -1
  226. package/src/shared/models/backend/library.ts +12 -4
  227. package/src/shared/models/backend/project.ts +43 -23
  228. package/src/shared/models/backend/pulumi.ts +14 -0
  229. package/src/shared/models/backend/unlock-method.ts +1 -1
  230. package/src/shared/models/backend/well-known.ts +58 -0
  231. package/src/shared/models/base.ts +40 -109
  232. package/src/shared/models/errors.ts +82 -1
  233. package/src/shared/models/index.ts +3 -2
  234. package/src/shared/models/prisma.ts +36 -0
  235. package/src/shared/models/project/api-key.ts +37 -56
  236. package/src/shared/models/project/artifact.ts +15 -105
  237. package/src/shared/models/project/custom-status.ts +12 -0
  238. package/src/shared/models/project/index.ts +9 -9
  239. package/src/shared/models/project/lock.ts +10 -78
  240. package/src/shared/models/project/model.ts +32 -0
  241. package/src/shared/models/project/operation.ts +222 -99
  242. package/src/shared/models/project/page.ts +37 -48
  243. package/src/shared/models/project/secret.ts +29 -103
  244. package/src/shared/models/project/service-account.ts +12 -17
  245. package/src/shared/models/project/state.ts +100 -390
  246. package/src/shared/models/project/terminal.ts +75 -89
  247. package/src/shared/models/project/trigger.ts +13 -49
  248. package/src/shared/models/project/unlock-method.ts +21 -20
  249. package/src/shared/models/project/worker.ts +89 -88
  250. package/src/shared/resolvers/graph-resolver.ts +62 -26
  251. package/src/shared/resolvers/index.ts +1 -1
  252. package/src/shared/resolvers/input-hash.ts +24 -14
  253. package/src/shared/resolvers/input.ts +48 -6
  254. package/src/shared/resolvers/registry.ts +5 -4
  255. package/src/shared/resolvers/state.ts +12 -1
  256. package/src/shared/resolvers/validation.ts +29 -9
  257. package/src/shared/utils/index.ts +1 -1
  258. package/src/shared/utils/promise-tracker.ts +30 -3
  259. package/src/terminal/abstractions.ts +1 -1
  260. package/src/terminal/docker.ts +3 -3
  261. package/src/terminal/manager.ts +102 -118
  262. package/src/test-utils/database.ts +119 -0
  263. package/src/test-utils/index.ts +2 -0
  264. package/src/test-utils/services.ts +134 -0
  265. package/src/unlock/abstractions.ts +31 -0
  266. package/src/unlock/index.ts +2 -0
  267. package/src/unlock/memory.ts +27 -0
  268. package/src/worker/abstractions.ts +7 -4
  269. package/src/worker/docker.ts +14 -19
  270. package/src/worker/manager.ts +376 -79
  271. package/dist/chunk-RCB4AFGD.js.map +0 -1
  272. package/dist/chunk-WHALQHEZ.js +0 -2017
  273. package/dist/chunk-WHALQHEZ.js.map +0 -1
  274. package/src/business/backend-unlock.ts +0 -10
  275. package/src/common/performance.ts +0 -44
  276. package/src/hotstate/abstractions.ts +0 -48
  277. package/src/hotstate/factory.ts +0 -17
  278. package/src/hotstate/index.ts +0 -3
  279. package/src/hotstate/manager.ts +0 -192
  280. package/src/hotstate/memory.ts +0 -100
  281. package/src/hotstate/validation.ts +0 -101
  282. package/src/project/abstractions.ts +0 -102
  283. package/src/project/factory.ts +0 -11
  284. package/src/project/index.ts +0 -3
  285. package/src/project/local.ts +0 -469
  286. package/src/project/manager.ts +0 -574
  287. package/src/pubsub/local.ts +0 -36
  288. package/src/pubsub/validation.ts +0 -33
  289. package/src/shared/models/project/component.ts +0 -45
  290. package/src/shared/models/project/instance.ts +0 -74
  291. package/src/state/abstractions.ts +0 -450
  292. package/src/state/encryption.ts +0 -59
  293. package/src/state/factory.ts +0 -20
  294. package/src/state/index.ts +0 -6
  295. package/src/state/local/backend.ts +0 -299
  296. package/src/state/local/collection.ts +0 -342
  297. package/src/state/local/index.ts +0 -2
  298. package/src/state/manager.ts +0 -819
  299. package/src/state/repository/index.ts +0 -2
  300. package/src/state/repository/repository.index.ts +0 -193
  301. package/src/state/repository/repository.ts +0 -458
  302. /package/src/{state → database/local}/keyring.ts +0 -0
@@ -0,0 +1,213 @@
1
+ import type { InstanceId, InstanceModel } from "@highstate/contract"
2
+ import type { InstanceLockService, InstanceStateService, ProjectModelService } from "../business"
3
+ import type { Operation } from "../database"
4
+ import type { LibraryBackend } from "../library"
5
+ import type { InstanceState, LibraryModel, OperationOptions } from "../shared"
6
+ import type { PlanTestBuilder } from "./plan-test-builder"
7
+ import { defineComponent, defineEntity, defineUnit, z } from "@highstate/contract"
8
+ import { createId } from "@paralleldrive/cuid2"
9
+ import { type MockedObject, vi } from "vitest"
10
+ import { test } from "../test-utils"
11
+ import { OperationContext } from "./operation-context"
12
+
13
+ export const operationPlanTest = test.extend<{
14
+ // mock services
15
+ libraryBackend: MockedObject<LibraryBackend>
16
+ instanceLockService: MockedObject<InstanceLockService>
17
+ instanceStateService: MockedObject<InstanceStateService>
18
+ projectModelService: MockedObject<ProjectModelService>
19
+
20
+ createTestOperation: (
21
+ type?: "update" | "destroy" | "recreate" | "preview" | "refresh",
22
+ instanceIds?: InstanceId[],
23
+ options?: Partial<OperationOptions>,
24
+ ) => Operation
25
+ createMockLibrary: (componentTypes?: string[]) => LibraryModel
26
+
27
+ // context management
28
+ createContext: (instances: InstanceModel[], states?: InstanceState[]) => Promise<OperationContext>
29
+
30
+ // plan test builder
31
+ testBuilder: () => PlanTestBuilder
32
+ }>({
33
+ // mock services setup
34
+ libraryBackend: async ({}, use) => {
35
+ const mock = vi.mocked({
36
+ loadLibrary: vi.fn(),
37
+ getResolvedUnitSources: vi.fn(),
38
+ } as unknown as LibraryBackend)
39
+
40
+ await use(mock)
41
+ },
42
+
43
+ instanceLockService: async ({}, use) => {
44
+ const mock = vi.mocked({
45
+ lockInstances: vi.fn(),
46
+ tryLockInstances: vi.fn(),
47
+ unlockInstancesUnconditionally: vi.fn(),
48
+ } as unknown as InstanceLockService)
49
+
50
+ await use(mock)
51
+ },
52
+
53
+ instanceStateService: async ({}, use) => {
54
+ const mock = vi.mocked({
55
+ getInstanceStates: vi.fn(),
56
+ updateOperationState: vi.fn(),
57
+ updateOperationProgress: vi.fn(),
58
+ forgetInstanceState: vi.fn(),
59
+ } as unknown as InstanceStateService)
60
+
61
+ await use(mock)
62
+ },
63
+
64
+ projectModelService: async ({}, use) => {
65
+ const mock = vi.mocked({
66
+ getProjectModel: vi.fn(),
67
+ } as unknown as ProjectModelService)
68
+
69
+ await use(mock)
70
+ },
71
+
72
+ createTestOperation: async ({}, use) => {
73
+ const createOperation = (
74
+ type: "update" | "destroy" | "recreate" | "preview" | "refresh" = "update",
75
+ instanceIds: InstanceId[] = [],
76
+ options: Partial<OperationOptions> = {},
77
+ ): Operation => ({
78
+ id: createId(),
79
+ meta: {
80
+ title: "Test Operation",
81
+ description: "Test operation for workset tests",
82
+ },
83
+ type,
84
+ status: "pending",
85
+ options: {
86
+ forceUpdateDependencies: false,
87
+ forceUpdateChildren: false,
88
+ destroyDependentInstances: true,
89
+ invokeDestroyTriggers: true,
90
+ deleteUnreachableResources: false,
91
+ forceDeleteState: false,
92
+ allowPartialCompositeInstanceUpdate: false,
93
+ allowPartialCompositeInstanceDestruction: false,
94
+ refresh: false,
95
+ ...options,
96
+ },
97
+ phases: [],
98
+ requestedInstanceIds: instanceIds,
99
+ startedAt: new Date(),
100
+ updatedAt: new Date(),
101
+ finishedAt: null,
102
+ })
103
+
104
+ await use(createOperation)
105
+ },
106
+
107
+ createMockLibrary: async ({}, use) => {
108
+ const createLibrary = (): LibraryModel => {
109
+ // create test entity for dependencies
110
+ const testEntity = defineEntity({
111
+ type: "test.entity.v1",
112
+ schema: z.object({
113
+ value: z.string(),
114
+ }),
115
+ })
116
+
117
+ // create test unit with dependency input
118
+ const testUnit = defineUnit({
119
+ type: "component.v1",
120
+ inputs: {
121
+ dependency: testEntity,
122
+ },
123
+ source: {
124
+ package: "@test/units",
125
+ path: "test-unit",
126
+ },
127
+ })
128
+
129
+ // create composite (not unit)
130
+ const testComposite = defineComponent({
131
+ type: "composite.v1",
132
+ create: () => {},
133
+ })
134
+
135
+ return {
136
+ components: {
137
+ "component.v1": testUnit.model,
138
+ "composite.v1": testComposite.model,
139
+ },
140
+ entities: {
141
+ "test.entity.v1": testEntity.model,
142
+ },
143
+ }
144
+ }
145
+
146
+ await use(createLibrary)
147
+ },
148
+
149
+ createContext: async (
150
+ {
151
+ project,
152
+ libraryBackend,
153
+ instanceStateService,
154
+ projectModelService,
155
+ logger,
156
+ createMockLibrary,
157
+ },
158
+ use,
159
+ ) => {
160
+ const createContext = async (
161
+ instances: InstanceModel[],
162
+ states: InstanceState[] = [],
163
+ ): Promise<OperationContext> => {
164
+ const library = createMockLibrary()
165
+
166
+ // setup mocks
167
+ projectModelService.getProjectModel.mockResolvedValue([
168
+ {
169
+ instances,
170
+ virtualInstances: [],
171
+ hubs: [],
172
+ ghostInstances: [],
173
+ },
174
+ project,
175
+ ])
176
+ libraryBackend.loadLibrary.mockResolvedValue(library)
177
+ libraryBackend.getResolvedUnitSources.mockResolvedValue([
178
+ {
179
+ unitType: "component.v1",
180
+ sourceHash: 12345,
181
+ projectPath: "test",
182
+ allowedDependencies: [],
183
+ },
184
+ {
185
+ unitType: "composite.v1",
186
+ sourceHash: 12345,
187
+ projectPath: "test",
188
+ allowedDependencies: [],
189
+ },
190
+ ])
191
+ instanceStateService.getInstanceStates.mockResolvedValue(states)
192
+
193
+ // create context
194
+ return await OperationContext.load(
195
+ project.id,
196
+ libraryBackend,
197
+ instanceStateService,
198
+ projectModelService,
199
+ logger,
200
+ )
201
+ }
202
+
203
+ await use(createContext)
204
+ },
205
+
206
+ // plan test builder
207
+ testBuilder: async ({ createContext, createTestOperation }, use) => {
208
+ const { PlanTestBuilder } = await import("./plan-test-builder")
209
+ const createPlanBuilder = () => new PlanTestBuilder(createContext, createTestOperation)
210
+
211
+ await use(createPlanBuilder)
212
+ },
213
+ })
@@ -0,0 +1,198 @@
1
+ # Operation Phase Calculation Model
2
+
3
+ ## Overview
4
+
5
+ This document describes the unified model for calculating execution phases in Highstate operations. It covers the distinction between **operations** (user requests) and **phases** (execution units), and how different operation types are assembled from combinations of phases.
6
+
7
+ ## Core Concepts
8
+
9
+ ### Operations vs Phases
10
+
11
+ **Operations** are high-level user requests that define what should happen:
12
+
13
+ - `update`: Deploy or update requested instances
14
+ - `destroy`: Remove requested instances
15
+ - `recreate`: Remove and then redeploy requested instances
16
+ - `preview`: Calculate what would change without executing
17
+ - `refresh`: Refresh state information
18
+
19
+ **Phases** are atomic execution units that perform specific actions:
20
+
21
+ - `update phase`: Deploys/updates a set of instances in dependency order (also used for preview mode)
22
+ - `refresh phase`: Refreshes state information for a set of instances in dependency order (similar to update phase but without actual deployment changes)
23
+ - `destroy phase`: Removes a set of instances in reverse dependency order
24
+
25
+ ### Operation Assembly
26
+
27
+ Different operation types are assembled from phases as follows:
28
+
29
+ **Update Operation:**
30
+
31
+ - Single `update phase` containing instances to be updated
32
+ - Optional `destroy phase` for ghost cleanup (if composites are being updated)
33
+
34
+ **Destroy Operation:**
35
+
36
+ - Single `destroy phase` containing instances to be destroyed
37
+
38
+ **Recreate Operation:**
39
+
40
+ - First a `destroy phase` containing instances to be destroyed
41
+ - Then an `update phase` containing the same instances to be recreated
42
+
43
+ **Preview Operation:**
44
+ - Single `update phase` calculated using update phase rules but not executed
45
+ - **Restriction**: Only allowed for "edge" instances (instances that depend on others but no instances depend on them)
46
+
47
+ **Refresh Operation:**
48
+ - Single `refresh phase` calculated using update phase rules for state refresh
49
+ - **Key difference**: No destroy phase is created, even if ghost cleanup would normally occur
50
+
51
+ ## Phase Calculation Inputs
52
+
53
+ - **Requested instances**: User explicitly asked to operate on these
54
+ - **Instance states**: Current deployment status, input hashes, error conditions, parent relationships
55
+ - **Dependency graph**: Which instances depend on which others (forward for updates, reverse for destroys)
56
+ - **Composite hierarchy**: Parent-child relationships between composites and their contents
57
+ - **Options**: Force flags, partial operation settings, and cascade controls
58
+
59
+ ## Phase Calculation Outputs
60
+
61
+ - **Update phase**: List of instances that will be updated, with reasons
62
+ - **Destroy phase**: List of instances that will be destroyed, with reasons
63
+
64
+ ## Shared Phase Concepts
65
+
66
+ ### Composite Classification
67
+
68
+ **Note**: These classifications are determined dynamically during operation planning, not fixed properties of the composite in the project model.
69
+
70
+ **A composite is substantive (for this operation) if:**
71
+
72
+ - It was explicitly requested by the user, OR
73
+ - It has at least one child included due to external dependencies (dependencies from outside the composite)
74
+
75
+ **A composite is compositional (for this operation) if:**
76
+
77
+ - It's only included because the user directly requested one of its children
78
+
79
+ ### Instance States
80
+
81
+ **Outdated instance**: A **unit instance** that needs updating due to:
82
+
83
+ - **Undeployed**: Doesn't exist yet
84
+ - **Failed**: Has error status
85
+ - **Changed**: Input hash mismatch (out-of-date)
86
+
87
+ **Important**: **Composite instances cannot be outdated**. Composites are containers that organize other instances but don't have deployable state themselves. They don't participate in change tracking via input hash comparisons.
88
+
89
+ **Deployed instance**: Instance that exists and can be operated on
90
+
91
+ **Ghost instances**: Virtual instances with status other than undeployed that no longer exist in the composite's evaluation
92
+
93
+ ### Common Options
94
+
95
+ 1. `allowPartialCompositeInstanceUpdate` / `allowPartialCompositeInstanceDestruction`:
96
+ When disabled (which is default), all **outdated unit** children of **substantive** composites are included. Otherwise, only children explicitly requested or required by dependencies are included.
97
+
98
+ 2. `forceUpdateDependencies`: Forces inclusion of all dependencies regardless of their state
99
+ 3. `destroyDependentInstances`: Forces inclusion of all dependents (instances that depend on destroyed instances)
100
+ 4. `forceUpdateChildren`: Forces all children of substantive composites regardless of state
101
+
102
+ ## Shared Phase Rules
103
+
104
+ ### Message Assignment
105
+
106
+ Since composite instances cannot be directly depended upon, message conflicts are rare. The first assigned message is kept and subsequent messages are ignored.
107
+
108
+ ### Propagation Rules
109
+
110
+ - **Substantive composite inclusions** trigger further dependency resolution and parent propagation
111
+ - **Compositional composite inclusions** do NOT trigger further propagation (boundary isolation)
112
+
113
+ ### Ghost Cleanup Rules
114
+
115
+ **Ghost instances are processed if:**
116
+
117
+ - They are children of **substantive composites** being operated on, OR
118
+ - They are explicitly requested for operation (which means deletion for ghosts)
119
+
120
+ ## Update Phase Rules
121
+
122
+ **Used by**: Update, Preview, and Refresh operations
123
+
124
+ **An instance is included in the update phase if ANY of these apply:**
125
+
126
+ 1. **Explicitly requested** by the user
127
+
128
+ 2. **Required dependency** of an included instance that:
129
+ - Is outdated (units only - composites are never outdated), OR
130
+ - Force dependencies flag is enabled
131
+
132
+ 3. **Child of substantive composite** that:
133
+ - Is outdated (units only) and partial update is disabled, OR
134
+ - Force children flag is enabled
135
+
136
+ 4. **Parent composite** of any included instance (automatically included with appropriate classification)
137
+
138
+ **Composite Instance Inclusion:** Composites are included purely for organizational purposes - to maintain the parent-child hierarchy during operations. They are included according to **substantive** or **compositional** classification rules, not because they themselves have changed or are outdated.
139
+
140
+ **Preview Operation Restriction:** For preview operations, only "edge" instances are allowed as requested instances. Edge instances are those that depend on others but have no instances depending on them.
141
+
142
+ **Note:** The final list must respect the dependency graph topology (all dependencies appear before their dependents)
143
+
144
+ ## Destroy Phase Rules
145
+
146
+ **An instance is included in the destroy phase if ANY of these apply:**
147
+
148
+ 1. **Explicitly requested** by the user
149
+
150
+ 2. **Dependent cascade** of an included instance that:
151
+ - Depends on the included instance, AND
152
+ - destroyDependentInstances flag is enabled
153
+
154
+ 3. **Child of substantive composite** that is being destroyed
155
+
156
+ 4. **Parent composite** of any included instance (automatically included with appropriate classification)
157
+
158
+ 5. **Ghost cleanup** during update operations:
159
+ - Virtual ghost instances that no longer exist in composite evaluations
160
+
161
+ **Note:** The final list must respect the reverse dependency graph topology (all dependents appear before their dependencies).
162
+
163
+ ## Refresh Phase Rules
164
+
165
+ **Used by**: Refresh operations
166
+
167
+ **An instance is included in the refresh phase if ANY of these apply:**
168
+
169
+ 1. **Explicitly requested** by the user
170
+
171
+ 2. **Required dependency** of an included instance when:
172
+ - Force dependencies flag is enabled
173
+
174
+ 3. **Child of substantive composite** that:
175
+ - Is outdated (units only) and partial update is disabled, OR
176
+ - Force children flag is enabled
177
+
178
+ 4. **Parent composite** of any included instance (automatically included with appropriate classification)
179
+
180
+ **Key Differences from Update Phase:**
181
+
182
+ - **No Automatic Dependency Inclusion**: Unlike update operations, refresh operations do NOT automatically include outdated dependencies unless `forceUpdateDependencies` is enabled
183
+ - **No Ghost Cleanup**: Unlike update operations, refresh operations do NOT create destroy phases for ghost cleanup, even when substantive composites have ghost children
184
+ - **Phase Type**: Creates a `refresh` phase type instead of `update` phase type
185
+ - **State-Only Operation**: Intended for refreshing state information without making actual deployment changes
186
+
187
+ **Note:** The final list must respect the dependency graph topology (all dependencies appear before their dependents), similar to update phase rules.
188
+
189
+ ## Execution Guarantees
190
+
191
+ Both update and destroy phases ensure:
192
+
193
+ - **Dependency Consistency**: No broken dependency relationships during execution
194
+ - **Composite Integrity**: Parent-child relationships remain valid during processing
195
+ - **Boundary Isolation**: Compositional composites don't trigger unintended propagation
196
+ - **User Intent**: Explicit requests are always honored while respecting safety constraints
197
+
198
+ **Note**: Circular dependencies are expected to be eliminated by the caller before phase calculation.
@@ -0,0 +1,199 @@
1
+ # Refresh Phase Examples
2
+
3
+ **Legend:**
4
+
5
+ - `✅` = Up-to-date instance
6
+ - `≠` = Out-of-date instance
7
+ - `∅` = Non-deployed instance
8
+ - `❌` = Error instance
9
+ - `🚀` = Explicitly requested instance
10
+ - `👻` = Ghost instance (virtual)
11
+
12
+ ### Example 1: Simple Refresh Request
13
+
14
+ **Test**: `should create refresh phase instead of update phase for refresh operations`
15
+
16
+ ```mermaid
17
+ graph RL
18
+ B["B 🚀≠"]
19
+ A["A ✅"]
20
+
21
+ B --> A
22
+ ```
23
+
24
+ **Decision Steps**:
25
+
26
+ 1. `B` explicitly requested for refresh;
27
+ 2. `B` depends on `A`, `A` is up-to-date → `A` excluded.
28
+
29
+ **Refresh Phase**: `B`
30
+
31
+ ### Example 2: Outdated Dependencies Ignored in Refresh
32
+
33
+ **Test**: `should not include outdated dependencies in refresh phase`
34
+
35
+ ```mermaid
36
+ graph RL
37
+ A["A ≠"]
38
+ B["B ✅"]
39
+ C["C 🚀"]
40
+
41
+ C --> B --> A
42
+ ```
43
+
44
+ **Decision Steps**:
45
+
46
+ 1. `C` explicitly requested for refresh;
47
+ 2. `C` depends on `B`, `B` is up-to-date → `B` excluded (no dependency propagation in refresh);
48
+ 3. `B` depends on `A`, `A` is outdated → `A` excluded (not forced).
49
+
50
+ **Refresh Phase**: `C`
51
+
52
+ **Key Difference**: Unlike update operations, refresh does not automatically include outdated dependencies.
53
+
54
+ ### Example 3: Ghost Children Ignored in Refresh
55
+
56
+ **Test**: `should not create destroy phase for refresh operations even with ghost children`
57
+
58
+ ```mermaid
59
+ graph RL
60
+ subgraph Parent["Parent 🚀"]
61
+ Child["Child 👻"]
62
+ end
63
+ ```
64
+
65
+ **Decision Steps**:
66
+
67
+ 1. `Parent` explicitly requested for refresh;
68
+ 2. `Child` is ghost child of `Parent` → `Child` ignored (no destroy phase in refresh);
69
+ 3. `Parent` has no non-ghost children needing update → no refresh phase created.
70
+
71
+ **Refresh Phase**: (none)
72
+
73
+ **Key Difference**: Unlike update operations, refresh operations do NOT create destroy phases for ghost cleanup.
74
+
75
+ ### Example 4: Mixed Ghost and Normal Children
76
+
77
+ **Test**: `should handle composite with mixed ghost and normal children in refresh`
78
+
79
+ ```mermaid
80
+ graph RL
81
+ subgraph Parent["Parent 🚀"]
82
+ GhostChild["GhostChild 👻"]
83
+ NormalChild["NormalChild ≠"]
84
+ end
85
+ ```
86
+
87
+ **Decision Steps**:
88
+
89
+ 1. `Parent` explicitly requested (substantive composite);
90
+ 2. `NormalChild` is child of substantive composite, outdated → `NormalChild` included;
91
+ 3. `GhostChild` is ghost child → ignored (no destroy phase in refresh).
92
+
93
+ **Refresh Phase**: `Parent`, `NormalChild`
94
+
95
+ ### Example 5: Force Dependencies with Refresh
96
+
97
+ **Test**: `should handle forceUpdateDependencies with refresh operation`
98
+
99
+ ```mermaid
100
+ graph RL
101
+ A["A ✅"]
102
+ B["B 🚀"]
103
+
104
+ B --> A
105
+ ```
106
+
107
+ **Options:**
108
+
109
+ - `forceUpdateDependencies`: `true`
110
+
111
+ **Decision Steps**:
112
+
113
+ 1. `B` explicitly requested for refresh;
114
+ 2. `B` depends on `A`, force flag enabled → `A` included.
115
+
116
+ **Refresh Phase**: `A`, `B`
117
+
118
+ ### Example 6: Force Children with Refresh
119
+
120
+ **Test**: `should handle forceUpdateChildren with refresh operation`
121
+
122
+ ```mermaid
123
+ graph RL
124
+ subgraph Parent["Parent 🚀"]
125
+ Child["Child ✅"]
126
+ end
127
+ ```
128
+
129
+ **Options:**
130
+
131
+ - `forceUpdateChildren`: `true`
132
+
133
+ **Decision Steps**:
134
+
135
+ 1. `Parent` explicitly requested (substantive composite);
136
+ 2. `Child` is child of substantive composite, force children enabled → `Child` included.
137
+
138
+ **Refresh Phase**: `Parent`, `Child`
139
+
140
+ ### Example 7: Undeployed Dependencies Ignored in Refresh
141
+
142
+ **Test**: `should not include undeployed dependencies in refresh operation`
143
+
144
+ ```mermaid
145
+ graph RL
146
+ A["A ∅"]
147
+ B["B 🚀"]
148
+
149
+ B --> A
150
+ ```
151
+
152
+ **Decision Steps**:
153
+
154
+ 1. `B` explicitly requested for refresh;
155
+ 2. `B` depends on `A`, `A` is undeployed (outdated) → `A` excluded (not forced).
156
+
157
+ **Refresh Phase**: `B`
158
+
159
+ ### Example 8: Failed Dependencies Ignored in Refresh
160
+
161
+ **Test**: `should not include failed dependencies in refresh operation`
162
+
163
+ ```mermaid
164
+ graph RL
165
+ A["A ❌"]
166
+ B["B 🚀"]
167
+
168
+ B --> A
169
+ ```
170
+
171
+ **Decision Steps**:
172
+
173
+ 1. `B` explicitly requested for refresh;
174
+ 2. `B` depends on `A`, `A` is failed (outdated) → `A` excluded (not forced).
175
+
176
+ **Refresh Phase**: `B`
177
+
178
+ ### Example 9: Forced Dependencies in Refresh
179
+
180
+ **Test**: `should include outdated dependencies when forced in refresh operation`
181
+
182
+ ```mermaid
183
+ graph RL
184
+ A["A ≠"]
185
+ B["B 🚀"]
186
+
187
+ B --> A
188
+ ```
189
+
190
+ **Options:**
191
+
192
+ - `forceUpdateDependencies`: `true`
193
+
194
+ **Decision Steps**:
195
+
196
+ 1. `B` explicitly requested for refresh;
197
+ 2. `B` depends on `A`, force dependencies enabled → `A` included.
198
+
199
+ **Refresh Phase**: `A`, `B`