@highstate/backend 0.9.16 → 0.9.18

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 (125) hide show
  1. package/dist/chunk-NAAIDR4U.js +8499 -0
  2. package/dist/chunk-NAAIDR4U.js.map +1 -0
  3. package/dist/chunk-OU5OQBLB.js +74 -0
  4. package/dist/chunk-OU5OQBLB.js.map +1 -0
  5. package/dist/{chunk-WHALQHEZ.js → chunk-Y7DXREVO.js} +502 -774
  6. package/dist/chunk-Y7DXREVO.js.map +1 -0
  7. package/dist/highstate.manifest.json +4 -4
  8. package/dist/index.js +2979 -2233
  9. package/dist/index.js.map +1 -1
  10. package/dist/library/package-resolution-worker.js +7 -5
  11. package/dist/library/package-resolution-worker.js.map +1 -1
  12. package/dist/library/worker/main.js +40 -41
  13. package/dist/library/worker/main.js.map +1 -1
  14. package/dist/magic-string.es-5ABAC4JN.js +1292 -0
  15. package/dist/magic-string.es-5ABAC4JN.js.map +1 -0
  16. package/dist/shared/index.js +3 -216
  17. package/dist/shared/index.js.map +1 -1
  18. package/package.json +9 -6
  19. package/src/artifact/encryption.ts +47 -7
  20. package/src/artifact/factory.ts +2 -2
  21. package/src/artifact/local.ts +2 -6
  22. package/src/business/__traces__/secret/update-instance-secrets/create-and-delete-secrets-simultaneously.md +356 -0
  23. package/src/business/__traces__/secret/update-instance-secrets/create-new-secrets-for-instance.md +274 -0
  24. package/src/business/__traces__/secret/update-instance-secrets/delete-existing-secrets.md +223 -0
  25. package/src/business/__traces__/secret/update-instance-secrets/no-op-when-no-changes.md +147 -0
  26. package/src/business/__traces__/secret/update-instance-secrets/update-existing-secrets.md +280 -0
  27. package/src/business/__traces__/worker/update-unit-registrations/add-new-unit-registration-when-other-exists.md +360 -0
  28. package/src/business/__traces__/worker/update-unit-registrations/add-new-unit-registration.md +215 -0
  29. package/src/business/__traces__/worker/update-unit-registrations/create-multiple-workers-with-different-identities.md +427 -0
  30. package/src/business/__traces__/worker/update-unit-registrations/handle-nonexistent-registration-id-gracefully.md +217 -0
  31. package/src/business/__traces__/worker/update-unit-registrations/no-op-when-no-changes.md +132 -0
  32. package/src/business/__traces__/worker/update-unit-registrations/recreate-worker-when-image-changes.md +454 -0
  33. package/src/business/__traces__/worker/update-unit-registrations/recreate-worker-when-image-version-changes.md +426 -0
  34. package/src/business/__traces__/worker/update-unit-registrations/recreate-worker-with-same-identity-reuses-service-account.md +372 -0
  35. package/src/business/__traces__/worker/update-unit-registrations/remove-one-of-multiple-unit-registrations.md +383 -0
  36. package/src/business/__traces__/worker/update-unit-registrations/remove-unit-registration.md +245 -0
  37. package/src/business/__traces__/worker/update-unit-registrations/update-existing-unit-registration-when-params-change.md +174 -0
  38. package/src/business/__traces__/worker/update-unit-registrations/update-params-and-image-simultaneously.md +432 -0
  39. package/src/business/__traces__/worker/update-unit-registrations/worker-with-multiple-registrations-not-deleted-when-one-removed.md +220 -0
  40. package/src/business/artifact.ts +2 -1
  41. package/src/business/index.ts +1 -0
  42. package/src/business/instance-lock.ts +3 -2
  43. package/src/business/instance-state.ts +202 -60
  44. package/src/business/project-unlock.ts +41 -23
  45. package/src/business/project.ts +299 -0
  46. package/src/business/secret.test.ts +178 -0
  47. package/src/business/secret.ts +139 -45
  48. package/src/business/worker.test.ts +614 -0
  49. package/src/business/worker.ts +289 -52
  50. package/src/common/clock.ts +18 -0
  51. package/src/common/index.ts +3 -0
  52. package/src/common/random.ts +68 -0
  53. package/src/common/test/index.ts +2 -0
  54. package/src/common/test/render.ts +98 -0
  55. package/src/common/test/tracer.ts +359 -0
  56. package/src/config.ts +5 -1
  57. package/src/hotstate/manager.ts +8 -8
  58. package/src/hotstate/validation.ts +0 -1
  59. package/src/library/abstractions.ts +20 -11
  60. package/src/library/local.ts +6 -13
  61. package/src/library/worker/evaluator.ts +30 -34
  62. package/src/library/worker/loader.lite.ts +13 -0
  63. package/src/library/worker/main.ts +8 -8
  64. package/src/library/worker/protocol.ts +0 -11
  65. package/src/lock/index.ts +1 -0
  66. package/src/lock/manager.ts +17 -2
  67. package/src/lock/test.ts +108 -0
  68. package/src/orchestrator/manager.ts +17 -36
  69. package/src/orchestrator/operation-workset.ts +34 -37
  70. package/src/orchestrator/operation.ts +129 -74
  71. package/src/project/abstractions.ts +27 -51
  72. package/src/project/evaluation.ts +248 -0
  73. package/src/project/index.ts +1 -1
  74. package/src/project/local.ts +75 -127
  75. package/src/pubsub/manager.ts +21 -13
  76. package/src/runner/abstractions.ts +29 -9
  77. package/src/runner/artifact-env.ts +3 -3
  78. package/src/runner/local.ts +29 -19
  79. package/src/runner/pulumi.ts +4 -1
  80. package/src/services.ts +77 -24
  81. package/src/shared/models/backend/library.ts +4 -4
  82. package/src/shared/models/backend/project.ts +25 -6
  83. package/src/shared/models/backend/unlock-method.ts +1 -1
  84. package/src/shared/models/base.ts +1 -84
  85. package/src/shared/models/project/api-key.ts +5 -2
  86. package/src/shared/models/project/artifact.ts +3 -33
  87. package/src/shared/models/project/index.ts +1 -2
  88. package/src/shared/models/project/lock.ts +3 -3
  89. package/src/shared/models/project/model.ts +14 -0
  90. package/src/shared/models/project/operation.ts +3 -3
  91. package/src/shared/models/project/page.ts +3 -3
  92. package/src/shared/models/project/secret.ts +4 -18
  93. package/src/shared/models/project/service-account.ts +2 -2
  94. package/src/shared/models/project/state.ts +32 -15
  95. package/src/shared/models/project/terminal.ts +4 -5
  96. package/src/shared/models/project/trigger.ts +1 -1
  97. package/src/shared/models/project/unlock-method.ts +9 -2
  98. package/src/shared/models/project/worker.ts +9 -7
  99. package/src/shared/resolvers/graph-resolver.ts +41 -26
  100. package/src/shared/resolvers/input.ts +47 -5
  101. package/src/shared/resolvers/validation.ts +23 -7
  102. package/src/shared/utils/args.ts +25 -0
  103. package/src/shared/utils/index.ts +1 -0
  104. package/src/state/abstractions.ts +98 -259
  105. package/src/state/encryption.ts +39 -0
  106. package/src/state/index.ts +1 -0
  107. package/src/state/local/backend.ts +29 -222
  108. package/src/state/local/collection.ts +105 -86
  109. package/src/state/manager.ts +358 -287
  110. package/src/state/memory/backend.ts +70 -0
  111. package/src/state/memory/collection.ts +270 -0
  112. package/src/state/memory/index.ts +2 -0
  113. package/src/state/repository/repository.index.ts +1 -1
  114. package/src/state/repository/repository.ts +71 -22
  115. package/src/state/test.ts +457 -0
  116. package/src/unlock/abstractions.ts +49 -0
  117. package/src/unlock/index.ts +2 -0
  118. package/src/unlock/memory.ts +32 -0
  119. package/src/worker/manager.ts +28 -0
  120. package/dist/chunk-RCB4AFGD.js +0 -159
  121. package/dist/chunk-RCB4AFGD.js.map +0 -1
  122. package/dist/chunk-WHALQHEZ.js.map +0 -1
  123. package/src/project/manager.ts +0 -574
  124. package/src/shared/models/project/component.ts +0 -45
  125. package/src/shared/models/project/instance.ts +0 -74
@@ -1,18 +1,18 @@
1
- import type { WorkerData, WorkerResponse } from "./protocol"
1
+ import type { WorkerData } from "./protocol"
2
2
  import { parentPort, workerData } from "node:worker_threads"
3
3
  import { pino } from "pino"
4
4
  import { errorToString } from "../../common"
5
- import { evaluateInstances } from "./evaluator"
5
+ import { evaluateProject } from "./evaluator"
6
6
  import { loadComponents } from "./loader.lite"
7
7
 
8
8
  const data = workerData as WorkerData
9
9
 
10
- const logger = pino({ name: "library-worker", level: data.logLevel })
10
+ const logger = pino({ name: "library-worker" })
11
11
 
12
12
  try {
13
13
  const library = await loadComponents(logger, data.libraryModulePaths)
14
14
 
15
- const results = evaluateInstances(
15
+ const result = evaluateProject(
16
16
  logger,
17
17
  library,
18
18
  data.allInstances,
@@ -20,12 +20,12 @@ try {
20
20
  data.instanceIds,
21
21
  )
22
22
 
23
- parentPort!.postMessage({ type: "results", results } satisfies WorkerResponse)
23
+ parentPort!.postMessage(result)
24
24
  } catch (error) {
25
- logger.error({ error }, "failed to evaluate")
25
+ logger.error({ error }, "failed to evaluate project")
26
26
 
27
27
  parentPort!.postMessage({
28
- type: "error",
28
+ success: false,
29
29
  error: errorToString(error),
30
- } satisfies WorkerResponse)
30
+ })
31
31
  }
@@ -1,5 +1,4 @@
1
1
  import type { InstanceModel } from "@highstate/contract"
2
- import type { InstanceEvaluationResult } from "../abstractions"
3
2
  import type { ResolvedInstanceInput } from "../../shared"
4
3
 
5
4
  export type WorkerData = {
@@ -10,13 +9,3 @@ export type WorkerData = {
10
9
  resolvedInputs: Record<string, Record<string, ResolvedInstanceInput[]>>
11
10
  instanceIds: string[]
12
11
  }
13
-
14
- export type WorkerResponse =
15
- | {
16
- type: "results"
17
- results: InstanceEvaluationResult[]
18
- }
19
- | {
20
- type: "error"
21
- error: string
22
- }
package/src/lock/index.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from "./abstractions"
2
2
  export * from "./factory"
3
3
  export * from "./manager"
4
+ export * from "./test"
@@ -3,9 +3,24 @@ import { join } from "remeda"
3
3
 
4
4
  export type LockKeyMap = {
5
5
  /**
6
- * Lock for a specific project.
6
+ * Lock for the project names to ensure uniqueness.
7
7
  */
8
- project: [projectId: string]
8
+ "project-name": [name: string]
9
+
10
+ /**
11
+ * Lock for instances and hubs of the project.
12
+ */
13
+ "project-nodes": [projectId: string]
14
+
15
+ /**
16
+ * Locks for the evaluation of the project.
17
+ */
18
+ "project-evaluation": [projectId: string]
19
+
20
+ /**
21
+ * Lock for all instance states of the project.
22
+ */
23
+ "project-instance-states": [projectId: string]
9
24
 
10
25
  /**
11
26
  * Lock for a specific instance within a project.
@@ -0,0 +1,108 @@
1
+ import type { Fixtures } from "@vitest/runner"
2
+ import type { LockKey } from "./manager"
3
+ import { linkTraceEntry, renderTraceEntry, type TestTracer, type TraceEntry } from "../common"
4
+ import { LockManager } from "./manager"
5
+ import { MemoryLockBackend } from "./memory"
6
+
7
+ class LockAcquireEntry implements TraceEntry {
8
+ releaseEntry?: TraceEntry
9
+
10
+ constructor(
11
+ readonly id: number,
12
+ private readonly keys: string[],
13
+ ) {}
14
+
15
+ render(): string {
16
+ return renderTraceEntry({
17
+ icon: "🔒",
18
+ title: this.releaseEntry
19
+ ? `locked, released at ${linkTraceEntry(this.releaseEntry)}`
20
+ : "locked",
21
+ fields: {
22
+ keys: {
23
+ value: this.keys,
24
+ },
25
+ },
26
+ })
27
+ }
28
+ }
29
+
30
+ class LockReleaseEntry implements TraceEntry {
31
+ constructor(
32
+ readonly id: number,
33
+ private readonly keys: string[],
34
+ private readonly acquireEntry: TraceEntry,
35
+ ) {}
36
+
37
+ render(): string {
38
+ return renderTraceEntry({
39
+ icon: "🔓",
40
+ title: `released, locked at ${linkTraceEntry(this.acquireEntry)}`,
41
+ fields: {
42
+ keys: {
43
+ value: this.keys,
44
+ },
45
+ },
46
+ })
47
+ }
48
+ }
49
+
50
+ class TestLockManager {
51
+ constructor(
52
+ private readonly lockManager: LockManager,
53
+ private readonly tracer: TestTracer,
54
+ ) {}
55
+
56
+ async acquire<T>(keys: LockKey | LockKey[], fn: () => Promise<T> | T): Promise<T> {
57
+ const keyStrings = this.formatKeys(keys)
58
+
59
+ // add acquire trace entry
60
+ const acquireEntry = new LockAcquireEntry(this.tracer.nextEntryId(), keyStrings)
61
+ this.tracer.addEntry(acquireEntry)
62
+
63
+ try {
64
+ const result = await this.lockManager.acquire(keys, fn)
65
+
66
+ // add release trace entry
67
+ const releaseEntry = new LockReleaseEntry(this.tracer.nextEntryId(), keyStrings, acquireEntry)
68
+ this.tracer.addEntry(releaseEntry)
69
+
70
+ // link acquire entry to release entry
71
+ acquireEntry.releaseEntry = releaseEntry
72
+
73
+ return result
74
+ } catch (error) {
75
+ // add release trace entry even on error
76
+ const releaseEntry = new LockReleaseEntry(this.tracer.nextEntryId(), keyStrings, acquireEntry)
77
+ this.tracer.addEntry(releaseEntry)
78
+
79
+ // link acquire entry to release entry
80
+ acquireEntry.releaseEntry = releaseEntry
81
+
82
+ throw error
83
+ }
84
+ }
85
+
86
+ private formatKeys(keys: LockKey | LockKey[]): string[] {
87
+ if (typeof keys[0] === "string") {
88
+ return [keys.join(":")]
89
+ }
90
+
91
+ return keys.map(key => key.join(":"))
92
+ }
93
+ }
94
+
95
+ export const lockFixtures: Fixtures<
96
+ {
97
+ lockManager: LockManager
98
+ },
99
+ { tracer: TestTracer }
100
+ > = {
101
+ lockManager: async ({ tracer }, use) => {
102
+ const backend = MemoryLockBackend.create()
103
+ const baseLockManager = new LockManager(backend)
104
+ const testLockManager = new TestLockManager(baseLockManager, tracer) as unknown as LockManager
105
+
106
+ await use(testLockManager)
107
+ },
108
+ }
@@ -10,10 +10,16 @@ import type {
10
10
  SecretService,
11
11
  ProjectUnlockService,
12
12
  InstanceStateService,
13
+ WorkerService,
13
14
  } from "../business"
14
15
  import type { PubSubManager } from "../pubsub"
15
16
  import { v7 as uuidv7 } from "uuid"
16
- import { isFinalOperationStatus, type Operation, type OperationRequest } from "../shared"
17
+ import {
18
+ isFinalOperationStatus,
19
+ type Operation,
20
+ type OperationRequest,
21
+ type Project,
22
+ } from "../shared"
17
23
  import { RuntimeOperation } from "./operation"
18
24
 
19
25
  export class OperationManager {
@@ -29,6 +35,7 @@ export class OperationManager {
29
35
  private readonly secretService: SecretService,
30
36
  private readonly instanceStateService: InstanceStateService,
31
37
  private readonly pubsubManager: PubSubManager,
38
+ private readonly workerService: WorkerService,
32
39
  private readonly logger: Logger,
33
40
  ) {
34
41
  this.stateUnlockService.registerUnlockTask(
@@ -74,7 +81,12 @@ export class OperationManager {
74
81
 
75
82
  await this.operationService.updateOperation(request.projectId, operation)
76
83
 
77
- this.startOperation(request.projectId, operation)
84
+ const project = await this.stateManager.getProjectRepository().get(request.projectId)
85
+ if (!project) {
86
+ throw new Error(`Project with ID "${request.projectId}" not found`)
87
+ }
88
+
89
+ this.startOperation(project, operation)
78
90
 
79
91
  return operation
80
92
  }
@@ -97,9 +109,9 @@ export class OperationManager {
97
109
  }
98
110
  }
99
111
 
100
- private startOperation(projectId: string, operation: Operation): void {
112
+ private startOperation(project: Project, operation: Operation): void {
101
113
  const runtimeOperation = new RuntimeOperation(
102
- projectId,
114
+ project,
103
115
  operation,
104
116
  this.runnerBackend,
105
117
  this.libraryBackend,
@@ -111,6 +123,7 @@ export class OperationManager {
111
123
  this.secretService,
112
124
  this.instanceStateService,
113
125
  this.pubsubManager,
126
+ this.workerService,
114
127
  this.logger.child({ operationId: operation.id }),
115
128
  )
116
129
 
@@ -182,36 +195,4 @@ export class OperationManager {
182
195
  projectId,
183
196
  )
184
197
  }
185
-
186
- static create(
187
- runnerBackend: RunnerBackend,
188
- libraryBackend: LibraryBackend,
189
- projectBackend: ProjectBackend,
190
- artifactManager: ArtifactService,
191
- stateManager: StateManager,
192
- instanceLockService: InstanceLockService,
193
- stateUnlockService: ProjectUnlockService,
194
- operationService: OperationService,
195
- secretService: SecretService,
196
- instanceService: InstanceStateService,
197
- pubsubManager: PubSubManager,
198
- logger: Logger,
199
- ): OperationManager {
200
- const operator = new OperationManager(
201
- runnerBackend,
202
- libraryBackend,
203
- projectBackend,
204
- artifactManager,
205
- stateManager,
206
- instanceLockService,
207
- stateUnlockService,
208
- operationService,
209
- secretService,
210
- instanceService,
211
- pubsubManager,
212
- logger.child({ service: "OperationManager" }),
213
- )
214
-
215
- return operator
216
- }
217
198
  }
@@ -23,6 +23,7 @@ import {
23
23
  type InstanceStatePatch,
24
24
  type LibraryModel,
25
25
  type Operation,
26
+ type Project,
26
27
  type ResolvedInstanceInput,
27
28
  } from "../shared"
28
29
 
@@ -62,10 +63,9 @@ export class OperationWorkset {
62
63
  private readonly lockEventEmitter = new EventEmitter()
63
64
 
64
65
  private constructor(
65
- public readonly projectId: string,
66
+ public readonly project: Project,
66
67
  public readonly operation: Operation,
67
68
  public readonly library: LibraryModel,
68
- public readonly libraryId: string,
69
69
  private readonly instanceLockService: InstanceLockService,
70
70
  private readonly instanceStateService: InstanceStateService,
71
71
  private readonly logger: Logger,
@@ -116,7 +116,7 @@ export class OperationWorkset {
116
116
 
117
117
  public async patchState(patch: InstanceStatePatch): Promise<InstanceState> {
118
118
  const state = await this.instanceStateService.patchOperationInstanceState(
119
- this.projectId,
119
+ this.project.id,
120
120
  this.operation,
121
121
  patch,
122
122
  )
@@ -168,7 +168,12 @@ export class OperationWorkset {
168
168
  instanceId,
169
169
  )
170
170
 
171
- await this.instanceStateService.updateInstanceStates(this.projectId, [initialState], true)
171
+ await this.instanceStateService.updateInstanceStates(
172
+ this.project.id,
173
+ [initialState],
174
+ true,
175
+ false,
176
+ )
172
177
 
173
178
  if (initialState.parentId) {
174
179
  await this.recalculateCompositeInstanceState(initialState.parentId)
@@ -185,7 +190,12 @@ export class OperationWorkset {
185
190
  }
186
191
  }
187
192
 
188
- await this.instanceStateService.updateInstanceStates(this.projectId, instanceStates)
193
+ await this.instanceStateService.updateInstanceStates(
194
+ this.project.id,
195
+ instanceStates,
196
+ false,
197
+ false,
198
+ )
189
199
  }
190
200
 
191
201
  public getAffectedCompositeChildren(instanceId: string): InstanceModel[] {
@@ -532,7 +542,7 @@ export class OperationWorkset {
532
542
  */
533
543
  public async tryLock(instancesToLock?: string[]): Promise<void> {
534
544
  const [, lockedInstanceIds] = await this.instanceLockService.tryLockInstances(
535
- this.projectId,
545
+ this.project.id,
536
546
  instancesToLock ?? Array.from(this.instanceIdsToLockIds),
537
547
  {
538
548
  title: "Operation Lock",
@@ -552,7 +562,7 @@ export class OperationWorkset {
552
562
  }
553
563
 
554
564
  public static async load(
555
- projectId: string,
565
+ project: Project,
556
566
  operation: Operation,
557
567
  projectBackend: ProjectBackend,
558
568
  libraryBackend: LibraryBackend,
@@ -562,59 +572,46 @@ export class OperationWorkset {
562
572
  logger: Logger,
563
573
  signal: AbortSignal,
564
574
  ): Promise<OperationWorkset> {
565
- // First get project info to obtain libraryId
566
- const projectInfo = await projectBackend.getProjectInfo(projectId, signal)
567
-
568
- // Use StateManager for instance states, StateBackend directly for composite instances
569
- // TODO: use hotstate for composite instances as well
570
- const compositeInstanceRepo = stateManager.getCompositeInstanceRepository(projectId)
571
-
572
- const [library, project, compositeInstances, states] = await Promise.all([
573
- libraryBackend.loadLibrary(projectInfo.libraryId, signal),
574
- projectBackend.getProject(projectId, signal),
575
- compositeInstanceRepo.getAllItems(),
576
- instanceStateService.getInstanceStates(projectId),
575
+ // TODO: use hotstate for virtual instances
576
+ const [library, { instances, hubs }, virtualInstances, states] = await Promise.all([
577
+ libraryBackend.loadLibrary(project.libraryId, signal),
578
+ projectBackend.getProjectModel(project, signal),
579
+ stateManager.getVirtualInstanceRepository(project.id).getAllItems(),
580
+ instanceStateService.getInstanceStates(project.id),
577
581
  ])
578
582
 
579
583
  const workset = new OperationWorkset(
580
- projectId,
584
+ project,
581
585
  operation,
582
586
  library,
583
- projectInfo.libraryId,
584
587
  instanceLockService,
585
588
  instanceStateService,
586
589
  logger.child({ operationId: operation.id, service: "OperationWorkset" }),
587
590
  )
588
591
 
589
592
  // prepare instances
590
- for (const instance of project.instances) {
593
+ for (const instance of instances) {
591
594
  workset.addInstance(instance)
592
595
  }
593
596
 
594
- for (const instance of compositeInstances) {
595
- const worksetInstance = workset.instanceMap.get(instance.instance.id)
597
+ for (const instance of virtualInstances) {
598
+ const worksetInstance = workset.instanceMap.get(instance.id)
596
599
 
597
600
  if (worksetInstance) {
598
- worksetInstance.outputs = instance.instance.outputs
599
- worksetInstance.resolvedOutputs = instance.instance.resolvedOutputs
600
- } else if (instance.instance.parentId) {
601
- workset.addInstance(instance.instance)
601
+ worksetInstance.outputs = instance.outputs
602
+ worksetInstance.resolvedOutputs = instance.resolvedOutputs
603
+ } else if (instance.parentId) {
604
+ workset.addInstance(instance)
602
605
  } else {
603
606
  workset.logger.warn(
604
- `ignoring instance "${instance.instance.id}" from composite instance state because it is not in the project or is not a part of known composite instance`,
607
+ `ignoring virtual instance "${instance.id}" because it is not in the project or is not a part of known composite instance`,
605
608
  )
606
609
  continue
607
610
  }
608
-
609
- for (const child of instance.children) {
610
- if (!workset.instanceMap.has(child.id)) {
611
- workset.addInstance(child)
612
- }
613
- }
614
611
  }
615
612
 
616
613
  const unitSources = await libraryBackend.getResolvedUnitSources(
617
- projectInfo.libraryId,
614
+ project.libraryId,
618
615
  unique(Array.from(workset.instanceMap.values()).map(i => i.type)),
619
616
  )
620
617
 
@@ -642,7 +639,7 @@ export class OperationWorkset {
642
639
  })
643
640
  }
644
641
 
645
- for (const hub of project.hubs) {
642
+ for (const hub of hubs) {
646
643
  workset.inputResolverNodes.set(`hub:${hub.id}`, { kind: "hub", hub })
647
644
  }
648
645