@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.
- package/dist/chunk-5WVU2AK4.js +1535 -0
- package/dist/chunk-5WVU2AK4.js.map +1 -0
- package/dist/chunk-I7BWSAN6.js +49 -0
- package/dist/chunk-I7BWSAN6.js.map +1 -0
- package/dist/{chunk-RCB4AFGD.js → chunk-VB4YL327.js} +51 -71
- package/dist/chunk-VB4YL327.js.map +1 -0
- package/dist/database/local/prisma.config.js +26 -0
- package/dist/database/local/prisma.config.js.map +1 -0
- package/dist/highstate.manifest.json +5 -4
- package/dist/index.js +7676 -6634
- package/dist/index.js.map +1 -1
- package/dist/library/package-resolution-worker.js +8 -6
- package/dist/library/package-resolution-worker.js.map +1 -1
- package/dist/library/worker/main.js +63 -58
- package/dist/library/worker/main.js.map +1 -1
- package/dist/shared/index.js +3 -216
- package/dist/shared/index.js.map +1 -1
- package/package.json +23 -11
- package/prisma/backend/_schema/layout.prisma +7 -0
- package/prisma/backend/_schema/library.prisma +17 -0
- package/prisma/backend/_schema/project.prisma +101 -0
- package/prisma/backend/_schema/pulumi.prisma +17 -0
- package/prisma/backend/postgresql/main.prisma +17 -0
- package/prisma/backend/sqlite/main.prisma +17 -0
- package/prisma/backend/sqlite/migrations/20250817070609_initiial/migration.sql +34 -0
- package/prisma/backend/sqlite/migrations/20250817104948_add_fields/migration.sql +59 -0
- package/prisma/backend/sqlite/migrations/20250818082732_add_models/migration.sql +41 -0
- package/prisma/backend/sqlite/migrations/20250818083106_a/migration.sql +19 -0
- package/prisma/backend/sqlite/migrations/20250818101945_hi/migration.sql +1 -0
- package/prisma/backend/sqlite/migrations/20250819082315_a/migration.sql +5 -0
- package/prisma/backend/sqlite/migrations/migration_lock.toml +3 -0
- package/prisma/project/api-key.prisma +27 -0
- package/prisma/project/artifact.prisma +52 -0
- package/prisma/project/custom-status.prisma +46 -0
- package/prisma/project/evaluation.prisma +35 -0
- package/prisma/project/instance.prisma +160 -0
- package/prisma/project/layout.prisma +23 -0
- package/prisma/project/lock.prisma +18 -0
- package/prisma/project/main.prisma +17 -0
- package/prisma/project/migrations/20250816081310_initial/migration.sql +300 -0
- package/prisma/project/migrations/20250816082523_test/migration.sql +72 -0
- package/prisma/project/migrations/20250818065643_update/migration.sql +42 -0
- package/prisma/project/migrations/20250818070758_a/migration.sql +8 -0
- package/prisma/project/migrations/20250818070913_a/migration.sql +8 -0
- package/prisma/project/migrations/20250818082720_add_motels/migration.sql +11 -0
- package/prisma/project/migrations/20250818112523_hello/migration.sql +35 -0
- package/prisma/project/migrations/20250819082305_a/migration.sql +14 -0
- package/prisma/project/migrations/20250819165004_add_missing_fields/migration.sql +216 -0
- package/prisma/project/migrations/20250819171309_a/migration.sql +22 -0
- package/prisma/project/migrations/20250820113949_a/migration.sql +66 -0
- package/prisma/project/migrations/20250820144256_b/migration.sql +31 -0
- package/prisma/project/migrations/20250820145547_a/migration.sql +24 -0
- package/prisma/project/migrations/20250820182517_b/migration.sql +2 -0
- package/prisma/project/migrations/20250821172324_a/migration.sql +2 -0
- package/prisma/project/migrations/20250822081339_a/migration.sql +219 -0
- package/prisma/project/migrations/20250822083742_b/migration.sql +1 -0
- package/prisma/project/migrations/20250822105134_boom/migration.sql +1 -0
- package/prisma/project/migrations/20250822141028_b/migration.sql +1 -0
- package/prisma/project/migrations/20250822142342_b/migration.sql +16 -0
- package/prisma/project/migrations/20250824072720_a/migration.sql +1 -0
- package/prisma/project/migrations/20250824093656_b/migration.sql +21 -0
- package/prisma/project/migrations/20250825082518_a/migration.sql +1 -0
- package/prisma/project/migrations/20250825085343_b/migration.sql +1 -0
- package/prisma/project/migrations/20250825091312_a/migration.sql +1 -0
- package/prisma/project/migrations/20250903095431_hi/migration.sql +44 -0
- package/prisma/project/migrations/20250903174255_a/migration.sql +24 -0
- package/prisma/project/migrations/20250908095205_hi/migration.sql +18 -0
- package/prisma/project/migrations/20250909155857_hi/migration.sql +15 -0
- package/prisma/project/migrations/migration_lock.toml +3 -0
- package/prisma/project/model.prisma +37 -0
- package/prisma/project/operation.prisma +148 -0
- package/prisma/project/page.prisma +41 -0
- package/prisma/project/secret.prisma +42 -0
- package/prisma/project/service-account.prisma +36 -0
- package/prisma/project/terminal.prisma +90 -0
- package/prisma/project/trigger.prisma +31 -0
- package/prisma/project/unlock-method.prisma +32 -0
- package/prisma/project/worker.prisma +138 -0
- package/src/artifact/abstractions.ts +13 -13
- package/src/artifact/encryption.ts +31 -15
- package/src/artifact/factory.ts +7 -10
- package/src/artifact/local.ts +33 -50
- package/src/business/api-key.ts +24 -36
- package/src/business/artifact.test.ts +978 -0
- package/src/business/artifact.ts +136 -215
- package/src/business/evaluation.ts +328 -0
- package/src/business/index.ts +5 -1
- package/src/business/instance-lock.test.ts +1060 -0
- package/src/business/instance-lock.ts +387 -77
- package/src/business/instance-state.test.ts +735 -0
- package/src/business/instance-state.ts +604 -217
- package/src/business/operation.test.ts +439 -0
- package/src/business/operation.ts +174 -208
- package/src/business/project-model.ts +258 -0
- package/src/business/project-unlock.ts +172 -112
- package/src/business/project.ts +407 -0
- package/src/business/secret.test.ts +513 -0
- package/src/business/secret.ts +194 -131
- package/src/business/settings.test.ts +695 -0
- package/src/business/settings.ts +855 -0
- package/src/business/terminal-session.ts +90 -0
- package/src/business/unit-extra.test.ts +539 -0
- package/src/business/unit-extra.ts +160 -0
- package/src/business/worker.test.ts +391 -0
- package/src/business/worker.ts +250 -114
- package/src/common/codebase.ts +65 -0
- package/src/common/index.ts +3 -2
- package/src/common/logger.ts +5 -0
- package/src/common/utils.ts +4 -3
- package/src/config.ts +15 -12
- package/src/database/_generated/backend/postgresql/client.ts +72 -0
- package/src/database/_generated/backend/postgresql/commonInputTypes.ts +350 -0
- package/src/database/_generated/backend/postgresql/enums.ts +13 -0
- package/src/database/_generated/backend/postgresql/internal/class.ts +320 -0
- package/src/database/_generated/backend/postgresql/internal/prismaNamespace.ts +1238 -0
- package/src/database/_generated/backend/postgresql/models/Library.ts +1263 -0
- package/src/database/_generated/backend/postgresql/models/Project.ts +2175 -0
- package/src/database/_generated/backend/postgresql/models/ProjectModelStorage.ts +1263 -0
- package/src/database/_generated/backend/postgresql/models/ProjectSpace.ts +1602 -0
- package/src/database/_generated/backend/postgresql/models/PulumiBackend.ts +1263 -0
- package/src/database/_generated/backend/postgresql/models/UserWorkspaseLayout.ts +1065 -0
- package/src/database/_generated/backend/postgresql/models.ts +16 -0
- package/src/database/_generated/backend/postgresql/pjtg.ts +182 -0
- package/src/database/_generated/backend/sqlite/client.ts +72 -0
- package/src/database/_generated/backend/sqlite/commonInputTypes.ts +331 -0
- package/src/database/_generated/backend/sqlite/enums.ts +13 -0
- package/src/database/_generated/backend/sqlite/internal/class.ts +318 -0
- package/src/database/_generated/backend/sqlite/internal/prismaNamespace.ts +1207 -0
- package/src/database/_generated/backend/sqlite/models/Library.ts +1261 -0
- package/src/database/_generated/backend/sqlite/models/Project.ts +2169 -0
- package/src/database/_generated/backend/sqlite/models/ProjectModelStorage.ts +1261 -0
- package/src/database/_generated/backend/sqlite/models/ProjectSpace.ts +1599 -0
- package/src/database/_generated/backend/sqlite/models/PulumiBackend.ts +1261 -0
- package/src/database/_generated/backend/sqlite/models/UserWorkspaseLayout.ts +1063 -0
- package/src/database/_generated/backend/sqlite/models.ts +16 -0
- package/src/database/_generated/backend/sqlite/pjtg.ts +182 -0
- package/src/database/_generated/project/client.ts +204 -0
- package/src/database/_generated/project/commonInputTypes.ts +827 -0
- package/src/database/_generated/project/enums.ts +104 -0
- package/src/database/_generated/project/internal/class.ts +479 -0
- package/src/database/_generated/project/internal/prismaNamespace.ts +2974 -0
- package/src/database/_generated/project/models/ApiKey.ts +1506 -0
- package/src/database/_generated/project/models/Artifact.ts +2051 -0
- package/src/database/_generated/project/models/HubModel.ts +1125 -0
- package/src/database/_generated/project/models/InstanceCustomStatus.ts +1713 -0
- package/src/database/_generated/project/models/InstanceEvaluationState.ts +1312 -0
- package/src/database/_generated/project/models/InstanceLock.ts +1268 -0
- package/src/database/_generated/project/models/InstanceModel.ts +1125 -0
- package/src/database/_generated/project/models/InstanceOperationState.ts +1707 -0
- package/src/database/_generated/project/models/InstanceState.ts +4613 -0
- package/src/database/_generated/project/models/Operation.ts +1647 -0
- package/src/database/_generated/project/models/OperationLog.ts +1455 -0
- package/src/database/_generated/project/models/Page.ts +1838 -0
- package/src/database/_generated/project/models/Secret.ts +1692 -0
- package/src/database/_generated/project/models/ServiceAccount.ts +2165 -0
- package/src/database/_generated/project/models/Terminal.ts +2038 -0
- package/src/database/_generated/project/models/TerminalSession.ts +1454 -0
- package/src/database/_generated/project/models/TerminalSessionLog.ts +1280 -0
- package/src/database/_generated/project/models/Trigger.ts +1430 -0
- package/src/database/_generated/project/models/UnlockMethod.ts +1220 -0
- package/src/database/_generated/project/models/UserCompositeViewport.ts +1280 -0
- package/src/database/_generated/project/models/UserProjectViewport.ts +1059 -0
- package/src/database/_generated/project/models/Worker.ts +1459 -0
- package/src/database/_generated/project/models/WorkerUnitRegistration.ts +1524 -0
- package/src/database/_generated/project/models/WorkerVersion.ts +1974 -0
- package/src/database/_generated/project/models/WorkerVersionLog.ts +1318 -0
- package/src/database/_generated/project/models.ts +35 -0
- package/src/database/_generated/project/pjtg.ts +182 -0
- package/src/database/abstractions.ts +19 -0
- package/src/database/factory.ts +37 -0
- package/src/database/index.ts +6 -0
- package/src/database/local/backend.ts +134 -0
- package/src/database/local/index.ts +3 -0
- package/src/database/local/meta.ts +46 -0
- package/src/database/local/prisma.config.ts +25 -0
- package/src/database/local/project.ts +39 -0
- package/src/database/manager.ts +181 -0
- package/src/database/migrate.ts +35 -0
- package/src/database/prisma.ts +56 -0
- package/src/database/well-known.ts +38 -0
- package/src/index.ts +4 -4
- package/src/library/abstractions.ts +21 -14
- package/src/library/factory.ts +1 -1
- package/src/library/local.ts +86 -38
- package/src/library/package-resolution-worker.ts +1 -1
- package/src/library/worker/evaluator.ts +61 -48
- package/src/library/worker/loader.lite.ts +14 -1
- package/src/library/worker/main.ts +9 -16
- package/src/library/worker/protocol.ts +0 -12
- package/src/lock/manager.ts +12 -7
- package/src/orchestrator/manager.ts +198 -131
- package/src/orchestrator/operation-context.ts +357 -0
- package/src/orchestrator/operation-plan.destroy.test.md +357 -0
- package/src/orchestrator/operation-plan.destroy.test.ts +775 -0
- package/src/orchestrator/operation-plan.fixtures.ts +213 -0
- package/src/orchestrator/operation-plan.md +198 -0
- package/src/orchestrator/operation-plan.refresh.test.md +199 -0
- package/src/orchestrator/operation-plan.refresh.test.ts +367 -0
- package/src/orchestrator/operation-plan.ts +709 -0
- package/src/orchestrator/operation-plan.update.test.md +485 -0
- package/src/orchestrator/operation-plan.update.test.ts +1066 -0
- package/src/orchestrator/operation-workset.ts +235 -583
- package/src/orchestrator/operation.ts +446 -904
- package/src/orchestrator/plan-test-builder.ts +267 -0
- package/src/project-model/abstractions.ts +118 -0
- package/src/project-model/backends/codebase.ts +365 -0
- package/src/project-model/backends/database.ts +440 -0
- package/src/project-model/errors.ts +81 -0
- package/src/project-model/factory.ts +24 -0
- package/src/project-model/index.ts +4 -0
- package/src/project-model/utils.test.ts +544 -0
- package/src/project-model/utils.ts +242 -0
- package/src/pubsub/abstractions.ts +10 -1
- package/src/pubsub/factory.ts +4 -4
- package/src/pubsub/index.ts +1 -0
- package/src/pubsub/manager.ts +49 -25
- package/src/pubsub/memory.ts +31 -0
- package/src/runner/abstractions.ts +38 -26
- package/src/runner/artifact-env.ts +17 -6
- package/src/runner/factory.ts +6 -6
- package/src/runner/force-abort.ts +3 -6
- package/src/runner/local.ts +79 -72
- package/src/runner/pulumi.ts +26 -63
- package/src/services.ts +214 -103
- package/src/shared/models/backend/index.ts +3 -1
- package/src/shared/models/backend/library.ts +12 -4
- package/src/shared/models/backend/project.ts +43 -23
- package/src/shared/models/backend/pulumi.ts +14 -0
- package/src/shared/models/backend/unlock-method.ts +1 -1
- package/src/shared/models/backend/well-known.ts +58 -0
- package/src/shared/models/base.ts +40 -109
- package/src/shared/models/errors.ts +82 -1
- package/src/shared/models/index.ts +3 -2
- package/src/shared/models/prisma.ts +36 -0
- package/src/shared/models/project/api-key.ts +37 -56
- package/src/shared/models/project/artifact.ts +15 -105
- package/src/shared/models/project/custom-status.ts +12 -0
- package/src/shared/models/project/index.ts +9 -9
- package/src/shared/models/project/lock.ts +10 -78
- package/src/shared/models/project/model.ts +32 -0
- package/src/shared/models/project/operation.ts +222 -99
- package/src/shared/models/project/page.ts +37 -48
- package/src/shared/models/project/secret.ts +29 -103
- package/src/shared/models/project/service-account.ts +12 -17
- package/src/shared/models/project/state.ts +100 -390
- package/src/shared/models/project/terminal.ts +75 -89
- package/src/shared/models/project/trigger.ts +13 -49
- package/src/shared/models/project/unlock-method.ts +21 -20
- package/src/shared/models/project/worker.ts +89 -88
- package/src/shared/resolvers/graph-resolver.ts +62 -26
- package/src/shared/resolvers/index.ts +1 -1
- package/src/shared/resolvers/input-hash.ts +24 -14
- package/src/shared/resolvers/input.ts +48 -6
- package/src/shared/resolvers/registry.ts +5 -4
- package/src/shared/resolvers/state.ts +12 -1
- package/src/shared/resolvers/validation.ts +29 -9
- package/src/shared/utils/index.ts +1 -1
- package/src/shared/utils/promise-tracker.ts +30 -3
- package/src/terminal/abstractions.ts +1 -1
- package/src/terminal/docker.ts +3 -3
- package/src/terminal/manager.ts +102 -118
- package/src/test-utils/database.ts +119 -0
- package/src/test-utils/index.ts +2 -0
- package/src/test-utils/services.ts +134 -0
- package/src/unlock/abstractions.ts +31 -0
- package/src/unlock/index.ts +2 -0
- package/src/unlock/memory.ts +27 -0
- package/src/worker/abstractions.ts +7 -4
- package/src/worker/docker.ts +14 -19
- package/src/worker/manager.ts +376 -79
- package/dist/chunk-RCB4AFGD.js.map +0 -1
- package/dist/chunk-WHALQHEZ.js +0 -2017
- package/dist/chunk-WHALQHEZ.js.map +0 -1
- package/src/business/backend-unlock.ts +0 -10
- package/src/common/performance.ts +0 -44
- package/src/hotstate/abstractions.ts +0 -48
- package/src/hotstate/factory.ts +0 -17
- package/src/hotstate/index.ts +0 -3
- package/src/hotstate/manager.ts +0 -192
- package/src/hotstate/memory.ts +0 -100
- package/src/hotstate/validation.ts +0 -101
- package/src/project/abstractions.ts +0 -102
- package/src/project/factory.ts +0 -11
- package/src/project/index.ts +0 -3
- package/src/project/local.ts +0 -469
- package/src/project/manager.ts +0 -574
- package/src/pubsub/local.ts +0 -36
- package/src/pubsub/validation.ts +0 -33
- package/src/shared/models/project/component.ts +0 -45
- package/src/shared/models/project/instance.ts +0 -74
- package/src/state/abstractions.ts +0 -450
- package/src/state/encryption.ts +0 -59
- package/src/state/factory.ts +0 -20
- package/src/state/index.ts +0 -6
- package/src/state/local/backend.ts +0 -299
- package/src/state/local/collection.ts +0 -342
- package/src/state/local/index.ts +0 -2
- package/src/state/manager.ts +0 -819
- package/src/state/repository/index.ts +0 -2
- package/src/state/repository/repository.index.ts +0 -193
- package/src/state/repository/repository.ts +0 -458
- /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`
|