@highstate/backend 0.19.1 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-V2NILDHS.js → chunk-52MY2TCE.js} +347 -19
- package/dist/chunk-52MY2TCE.js.map +1 -0
- package/dist/{chunk-I7BWSAN6.js → chunk-UAWBPTDW.js} +3 -3
- package/dist/{chunk-I7BWSAN6.js.map → chunk-UAWBPTDW.js.map} +1 -1
- package/dist/highstate.manifest.json +4 -4
- package/dist/index.js +4159 -785
- package/dist/index.js.map +1 -1
- package/dist/library/worker/main.js +5 -2
- package/dist/library/worker/main.js.map +1 -1
- package/dist/shared/index.js +2 -2
- package/package.json +7 -7
- package/prisma/backend/_schema/object.prisma +12 -0
- package/prisma/backend/sqlite/migrations/20260222113554_add_object_tracking/migration.sql +7 -0
- package/prisma/project/artifact.prisma +3 -0
- package/prisma/project/entity.prisma +125 -0
- package/prisma/project/instance.prisma +6 -0
- package/prisma/project/migrations/20260301210131_add_entity_tracking/migration.sql +70 -0
- package/prisma/project/migrations/20260302212734_add_resource_hooks_flag/migration.sql +1 -0
- package/prisma/project/operation.prisma +3 -0
- package/src/business/artifact.test.ts +22 -2
- package/src/business/artifact.ts +7 -1
- package/src/business/entity-snapshot.test.ts +684 -0
- package/src/business/entity-snapshot.ts +904 -0
- package/src/business/evaluation.test.ts +56 -0
- package/src/business/evaluation.ts +102 -22
- package/src/business/global-search.test.ts +344 -0
- package/src/business/global-search.ts +902 -0
- package/src/business/index.ts +4 -0
- package/src/business/instance-lock.ts +58 -74
- package/src/business/instance-state.test.ts +15 -1
- package/src/business/instance-state.ts +37 -14
- package/src/business/object-ref-index.test.ts +140 -0
- package/src/business/object-ref-index.ts +193 -0
- package/src/business/operation.test.ts +15 -1
- package/src/business/operation.ts +4 -0
- package/src/business/project-model.ts +154 -13
- package/src/business/project-unlock.ts +25 -2
- package/src/business/project.ts +9 -0
- package/src/business/secret.test.ts +35 -2
- package/src/business/secret.ts +32 -9
- package/src/business/settings.ts +761 -0
- package/src/business/unit-output.test.ts +477 -0
- package/src/business/unit-output.ts +461 -0
- package/src/business/worker.ts +55 -4
- package/src/database/_generated/backend/postgresql/browser.ts +6 -0
- package/src/database/_generated/backend/postgresql/client.ts +6 -0
- package/src/database/_generated/backend/postgresql/internal/class.ts +23 -5
- package/src/database/_generated/backend/postgresql/internal/prismaNamespace.ts +89 -5
- package/src/database/_generated/backend/postgresql/internal/prismaNamespaceBrowser.ts +9 -0
- package/src/database/_generated/backend/postgresql/models/Object.ts +1076 -0
- package/src/database/_generated/backend/postgresql/models.ts +1 -0
- package/src/database/_generated/backend/sqlite/browser.ts +6 -0
- package/src/database/_generated/backend/sqlite/client.ts +6 -0
- package/src/database/_generated/backend/sqlite/internal/class.ts +23 -5
- package/src/database/_generated/backend/sqlite/internal/prismaNamespace.ts +89 -5
- package/src/database/_generated/backend/sqlite/internal/prismaNamespaceBrowser.ts +9 -0
- package/src/database/_generated/backend/sqlite/models/Object.ts +1074 -0
- package/src/database/_generated/backend/sqlite/models.ts +1 -0
- package/src/database/_generated/project/browser.ts +23 -0
- package/src/database/_generated/project/client.ts +23 -0
- package/src/database/_generated/project/commonInputTypes.ts +87 -53
- package/src/database/_generated/project/enums.ts +8 -0
- package/src/database/_generated/project/internal/class.ts +53 -5
- package/src/database/_generated/project/internal/prismaNamespace.ts +367 -13
- package/src/database/_generated/project/internal/prismaNamespaceBrowser.ts +48 -1
- package/src/database/_generated/project/models/Artifact.ts +199 -11
- package/src/database/_generated/project/models/Entity.ts +1274 -0
- package/src/database/_generated/project/models/EntitySnapshot.ts +2389 -0
- package/src/database/_generated/project/models/EntitySnapshotContent.ts +1260 -0
- package/src/database/_generated/project/models/EntitySnapshotReference.ts +1449 -0
- package/src/database/_generated/project/models/InstanceState.ts +361 -1
- package/src/database/_generated/project/models/Operation.ts +148 -3
- package/src/database/_generated/project/models/OperationLog.ts +0 -4
- package/src/database/_generated/project/models.ts +4 -0
- package/src/database/migration.ts +3 -0
- package/src/library/worker/evaluator.ts +7 -1
- package/src/orchestrator/manager.ts +7 -0
- package/src/orchestrator/operation-context.captured-outputs.test.ts +118 -0
- package/src/orchestrator/operation-context.ts +154 -16
- package/src/orchestrator/operation-plan.destroy.test.md +33 -12
- package/src/orchestrator/operation-plan.destroy.test.ts +140 -2
- package/src/orchestrator/operation-plan.fixtures.ts +2 -0
- package/src/orchestrator/operation-plan.md +4 -1
- package/src/orchestrator/operation-plan.ts +286 -92
- package/src/orchestrator/operation-plan.update.test.md +286 -11
- package/src/orchestrator/operation-plan.update.test.ts +656 -5
- package/src/orchestrator/operation-workset.ts +72 -22
- package/src/orchestrator/operation.cancel.test.ts +4 -0
- package/src/orchestrator/operation.composite.test.ts +341 -0
- package/src/orchestrator/operation.destroy.test.ts +4 -0
- package/src/orchestrator/operation.output-validation.failure.test.ts +124 -0
- package/src/orchestrator/operation.preview.test.ts +4 -0
- package/src/orchestrator/operation.refresh.test.ts +4 -0
- package/src/orchestrator/operation.test-utils.ts +52 -13
- package/src/orchestrator/operation.ts +228 -68
- package/src/orchestrator/operation.update.failure.test.ts +4 -0
- package/src/orchestrator/operation.update.skip.test.ts +110 -0
- package/src/orchestrator/operation.update.test.ts +4 -0
- package/src/orchestrator/plan-test-builder.ts +1 -0
- package/src/orchestrator/unit-input-values.test.ts +450 -0
- package/src/orchestrator/unit-input-values.ts +281 -0
- package/src/pubsub/manager.ts +3 -0
- package/src/runner/abstractions.ts +23 -54
- package/src/runner/local.ts +109 -85
- package/src/services.ts +52 -1
- package/src/shared/models/prisma.ts +1 -0
- package/src/shared/models/project/entity.ts +121 -0
- package/src/shared/models/project/index.ts +1 -0
- package/src/shared/models/project/operation.ts +61 -3
- package/src/shared/models/project/state.ts +10 -0
- package/src/shared/models/project/worker.ts +7 -0
- package/src/shared/resolvers/effective-output-type.test.ts +494 -0
- package/src/shared/resolvers/effective-output-type.ts +162 -0
- package/src/shared/resolvers/index.ts +1 -0
- package/src/shared/resolvers/input.ts +59 -9
- package/src/shared/utils/index.ts +1 -0
- package/src/shared/utils/stable-json.ts +41 -0
- package/src/terminal/manager.ts +6 -0
- package/src/worker/manager.ts +97 -1
- package/dist/chunk-V2NILDHS.js.map +0 -1
|
@@ -30,9 +30,84 @@ graph RL
|
|
|
30
30
|
|
|
31
31
|
**Update Phase**: `B`, `C`
|
|
32
32
|
|
|
33
|
-
### Example
|
|
33
|
+
### Example 1a: Ignore Changed Dependencies Only
|
|
34
34
|
|
|
35
|
-
**Test**: `should
|
|
35
|
+
**Test**: `should skip only changed dependencies when ignoreChangedDependencies enabled`
|
|
36
|
+
|
|
37
|
+
```mermaid
|
|
38
|
+
graph RL
|
|
39
|
+
A["A ✅"]
|
|
40
|
+
B["B ≠"]
|
|
41
|
+
C["C 🚀"]
|
|
42
|
+
|
|
43
|
+
C --> B --> A
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Options:**
|
|
47
|
+
|
|
48
|
+
- `ignoreChangedDependencies`: `true`
|
|
49
|
+
|
|
50
|
+
**Decision Steps**:
|
|
51
|
+
|
|
52
|
+
1. `C` explicitly requested;
|
|
53
|
+
2. `C` depends on `B`, `B` is only changed and changed dependencies are ignored → `B` excluded;
|
|
54
|
+
3. no failed/undeployed prerequisite is present in this chain.
|
|
55
|
+
|
|
56
|
+
**Update Phase**: `C`
|
|
57
|
+
|
|
58
|
+
### Example 1b: Ignore Changed But Keep Undeployed/Failed
|
|
59
|
+
|
|
60
|
+
**Test**: `should still include undeployed dependencies when ignoreChangedDependencies enabled`
|
|
61
|
+
|
|
62
|
+
```mermaid
|
|
63
|
+
graph RL
|
|
64
|
+
A["A ≠"]
|
|
65
|
+
B["B ∅"]
|
|
66
|
+
C["C 🚀"]
|
|
67
|
+
|
|
68
|
+
C --> B --> A
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Options:**
|
|
72
|
+
|
|
73
|
+
- `ignoreChangedDependencies`: `true`
|
|
74
|
+
|
|
75
|
+
**Decision Steps**:
|
|
76
|
+
|
|
77
|
+
1. `C` explicitly requested;
|
|
78
|
+
2. `C` depends on `B`, `B` is undeployed → `B` included for safety;
|
|
79
|
+
3. `B` depends on `A`, `A` is only changed and changed dependencies are ignored → `A` excluded.
|
|
80
|
+
|
|
81
|
+
**Update Phase**: `B`, `C`
|
|
82
|
+
|
|
83
|
+
### Example 1c: Manual Ignore All Dependencies
|
|
84
|
+
|
|
85
|
+
**Test**: `should ignore all dependencies when ignoreDependencies enabled`
|
|
86
|
+
|
|
87
|
+
```mermaid
|
|
88
|
+
graph RL
|
|
89
|
+
A["A ≠"]
|
|
90
|
+
B["B ∅"]
|
|
91
|
+
C["C 🚀"]
|
|
92
|
+
|
|
93
|
+
C --> B --> A
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Options:**
|
|
97
|
+
|
|
98
|
+
- `ignoreDependencies`: `true`
|
|
99
|
+
|
|
100
|
+
**Decision Steps**:
|
|
101
|
+
|
|
102
|
+
1. `C` explicitly requested;
|
|
103
|
+
2. `C` dependencies are fully ignored in manual mode;
|
|
104
|
+
3. both changed and undeployed prerequisites are skipped.
|
|
105
|
+
|
|
106
|
+
**Update Phase**: `C`
|
|
107
|
+
|
|
108
|
+
### Example 2: Full Ancestor Chain for Compositional Inclusion
|
|
109
|
+
|
|
110
|
+
**Test**: `should include full ancestor chain for compositional inclusion`
|
|
36
111
|
|
|
37
112
|
```mermaid
|
|
38
113
|
graph RL
|
|
@@ -52,10 +127,10 @@ graph RL
|
|
|
52
127
|
1. `B` explicitly requested;
|
|
53
128
|
2. `B` depends on `A`, `A` is outdated → `A` included;
|
|
54
129
|
3. `A` is child of `Parent` → `Parent` included (compositional);
|
|
55
|
-
4. `Parent` is child of `GrandParent` → `GrandParent`
|
|
56
|
-
5. `C` is sibling of `Parent`,
|
|
130
|
+
4. `Parent` is child of `GrandParent` → `GrandParent` included as ancestor;
|
|
131
|
+
5. `C` is sibling of `Parent`, but ancestor inclusion does not auto-include siblings → `C` NOT included.
|
|
57
132
|
|
|
58
|
-
**Update Phase**: `A`, `Parent`, `B`
|
|
133
|
+
**Update Phase**: `A`, `Parent`, `GrandParent`, `B`
|
|
59
134
|
|
|
60
135
|
### Example 3: Force Dependencies
|
|
61
136
|
|
|
@@ -124,6 +199,157 @@ graph RL
|
|
|
124
199
|
|
|
125
200
|
**Destroy Phase**: `GhostChild`, `Parent`
|
|
126
201
|
|
|
202
|
+
### Example 5a: Force Children With Ghost-Only Composite
|
|
203
|
+
|
|
204
|
+
**Test**: `should cleanup ghost children when forceUpdateChildren is enabled`
|
|
205
|
+
|
|
206
|
+
```mermaid
|
|
207
|
+
graph RL
|
|
208
|
+
subgraph Parent["Parent 🚀"]
|
|
209
|
+
GhostChild["GhostChild 👻"]
|
|
210
|
+
end
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**Options:**
|
|
214
|
+
|
|
215
|
+
- `forceUpdateChildren`: `true`
|
|
216
|
+
|
|
217
|
+
**Decision Steps**:
|
|
218
|
+
|
|
219
|
+
1. `Parent` explicitly requested;
|
|
220
|
+
2. `forceUpdateChildren` applies to real children only, so `GhostChild` is not included in update phase;
|
|
221
|
+
3. `Parent` has no non-ghost child that needs update, so update phase stays empty;
|
|
222
|
+
4. ghost cleanup pass includes `GhostChild` in destroy phase;
|
|
223
|
+
5. `Parent` is added to destroy phase as parent of destroyed ghost child.
|
|
224
|
+
|
|
225
|
+
**Destroy Phase**: `GhostChild`, `Parent`
|
|
226
|
+
|
|
227
|
+
### Example 5b: Nested Ghost Cleanup for Child Composites
|
|
228
|
+
|
|
229
|
+
**Test**: `should cleanup ghosts from nested child composites during parent composite update`
|
|
230
|
+
|
|
231
|
+
```mermaid
|
|
232
|
+
graph RL
|
|
233
|
+
subgraph Parent["Parent 🚀"]
|
|
234
|
+
subgraph ChildComposite["ChildComposite ✅"]
|
|
235
|
+
GhostLeaf["GhostLeaf 👻"]
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**Decision Steps**:
|
|
241
|
+
|
|
242
|
+
1. `Parent` explicitly requested;
|
|
243
|
+
2. no non-ghost outdated descendants exist, so update phase is empty;
|
|
244
|
+
3. ghost cleanup traverses the full composite subtree, not only direct children;
|
|
245
|
+
4. nested `GhostLeaf` is included in destroy phase;
|
|
246
|
+
5. intermediate composite chain needed for state recalculation is included, so `ChildComposite` and `Parent` are also added to destroy phase.
|
|
247
|
+
|
|
248
|
+
**Destroy Phase**: `GhostLeaf`, `ChildComposite`, `Parent`
|
|
249
|
+
|
|
250
|
+
### Example 5c: Only Destroy Ghosts
|
|
251
|
+
|
|
252
|
+
**Test**: `should run only ghost destroy phase when onlyDestroyGhosts is enabled`
|
|
253
|
+
|
|
254
|
+
```mermaid
|
|
255
|
+
graph RL
|
|
256
|
+
subgraph Parent["Parent 🚀"]
|
|
257
|
+
Child1["Child1 ≠"]
|
|
258
|
+
GhostChild["GhostChild 👻"]
|
|
259
|
+
end
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
**Options:**
|
|
263
|
+
|
|
264
|
+
- `onlyDestroyGhosts`: `true`
|
|
265
|
+
|
|
266
|
+
**Decision Steps**:
|
|
267
|
+
|
|
268
|
+
1. `Parent` explicitly requested;
|
|
269
|
+
2. `Child1` is outdated and would normally be included in update phase;
|
|
270
|
+
3. `onlyDestroyGhosts` disables update phase generation entirely;
|
|
271
|
+
4. ghost cleanup still runs and includes `GhostChild`;
|
|
272
|
+
5. `Parent` is included in destroy phase as parent of destroyed ghost child.
|
|
273
|
+
|
|
274
|
+
**Destroy Phase**: `GhostChild`, `Parent`
|
|
275
|
+
|
|
276
|
+
### Example 5d: Destroy Ghosts Before Update
|
|
277
|
+
|
|
278
|
+
**Test**: `should place ghost destroy phase before update when firstDestroyGhosts is enabled`
|
|
279
|
+
|
|
280
|
+
```mermaid
|
|
281
|
+
graph RL
|
|
282
|
+
subgraph Parent["Parent 🚀"]
|
|
283
|
+
Child1["Child1 ≠"]
|
|
284
|
+
GhostChild["GhostChild 👻"]
|
|
285
|
+
end
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
**Options:**
|
|
289
|
+
|
|
290
|
+
- `firstDestroyGhosts`: `true`
|
|
291
|
+
|
|
292
|
+
**Decision Steps**:
|
|
293
|
+
|
|
294
|
+
1. `Parent` explicitly requested;
|
|
295
|
+
2. `Child1` is outdated and included in update phase;
|
|
296
|
+
3. `GhostChild` is included in ghost cleanup destroy phase;
|
|
297
|
+
4. option reorders phases so destroy runs before update;
|
|
298
|
+
5. both phases are kept.
|
|
299
|
+
|
|
300
|
+
**Destroy Phase**: `GhostChild`, `Parent`
|
|
301
|
+
|
|
302
|
+
**Update Phase**: `Child1`, `Parent`
|
|
303
|
+
|
|
304
|
+
### Example 5e: Ignore Ghost Cleanup
|
|
305
|
+
|
|
306
|
+
**Test**: `should skip ghost destroy phase when ignoreGhosts is enabled`
|
|
307
|
+
|
|
308
|
+
```mermaid
|
|
309
|
+
graph RL
|
|
310
|
+
subgraph Parent["Parent 🚀"]
|
|
311
|
+
Child1["Child1 ≠"]
|
|
312
|
+
GhostChild["GhostChild 👻"]
|
|
313
|
+
end
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
**Options:**
|
|
317
|
+
|
|
318
|
+
- `ignoreGhosts`: `true`
|
|
319
|
+
|
|
320
|
+
**Decision Steps**:
|
|
321
|
+
|
|
322
|
+
1. `Parent` explicitly requested;
|
|
323
|
+
2. `Child1` is outdated and included in update phase;
|
|
324
|
+
3. ghost cleanup generation is disabled by `ignoreGhosts`;
|
|
325
|
+
4. no destroy phase is created for ghosts.
|
|
326
|
+
|
|
327
|
+
**Update Phase**: `Child1`, `Parent`
|
|
328
|
+
|
|
329
|
+
### Example 5f: Mutually Exclusive Ghost Strategy Options
|
|
330
|
+
|
|
331
|
+
**Test**: `should reject mutually exclusive ghost options`
|
|
332
|
+
|
|
333
|
+
```mermaid
|
|
334
|
+
graph RL
|
|
335
|
+
subgraph Parent["Parent 🚀"]
|
|
336
|
+
GhostChild["GhostChild 👻"]
|
|
337
|
+
end
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
**Options:**
|
|
341
|
+
|
|
342
|
+
- `onlyDestroyGhosts`: `true`
|
|
343
|
+
- `ignoreGhosts`: `true`
|
|
344
|
+
|
|
345
|
+
**Decision Steps**:
|
|
346
|
+
|
|
347
|
+
1. planner validates ghost strategy options before phase construction;
|
|
348
|
+
2. more than one of `onlyDestroyGhosts`, `firstDestroyGhosts`, `ignoreGhosts` is enabled;
|
|
349
|
+
3. operation plan creation is rejected as invalid.
|
|
350
|
+
|
|
351
|
+
**Result**: validation error
|
|
352
|
+
|
|
127
353
|
### Example 6: Nested Composites with Mixed Updates
|
|
128
354
|
|
|
129
355
|
**Test**: `should handle complex nested hierarchy correctly`
|
|
@@ -293,9 +519,9 @@ graph RL
|
|
|
293
519
|
|
|
294
520
|
**Update Phase**: `A`, `C`
|
|
295
521
|
|
|
296
|
-
### Example 13: Deep Nesting with
|
|
522
|
+
### Example 13: Deep Nesting with Ancestor Chain Inclusion
|
|
297
523
|
|
|
298
|
-
**Test**: `should
|
|
524
|
+
**Test**: `should include deep ancestor chain without ancestor siblings`
|
|
299
525
|
|
|
300
526
|
```mermaid
|
|
301
527
|
graph RL
|
|
@@ -314,10 +540,11 @@ graph RL
|
|
|
314
540
|
|
|
315
541
|
1. `Child` explicitly requested;
|
|
316
542
|
2. `Child` is child of `Parent` → `Parent` included (compositional);
|
|
317
|
-
3. `Parent` is child of `GrandParent` → `GrandParent`
|
|
318
|
-
4. `
|
|
543
|
+
3. `Parent` is child of `GrandParent` → `GrandParent` included as ancestor;
|
|
544
|
+
4. `GrandParent` is child of `GreatGrandParent` → `GreatGrandParent` included as ancestor;
|
|
545
|
+
5. `Uncle` and `GreatUncle` are ancestor siblings and are not auto-included.
|
|
319
546
|
|
|
320
|
-
**Update Phase**: `Child`, `Parent`
|
|
547
|
+
**Update Phase**: `Child`, `Parent`, `GrandParent`, `GreatGrandParent`
|
|
321
548
|
|
|
322
549
|
### Example 14: Mixed Force Flags
|
|
323
550
|
|
|
@@ -469,7 +696,7 @@ graph RL
|
|
|
469
696
|
Child3["Child3 ≠"]
|
|
470
697
|
Child4["Child4 ≠"]
|
|
471
698
|
end
|
|
472
|
-
|
|
699
|
+
|
|
473
700
|
Child2 --> Child1
|
|
474
701
|
Child3 --> Child4
|
|
475
702
|
```
|
|
@@ -483,3 +710,51 @@ graph RL
|
|
|
483
710
|
5. `Child4` is child of compositional composite, outdated → `Child4` excluded (rule 3 doesn't apply).
|
|
484
711
|
|
|
485
712
|
**Update Phase**: `Child1`, `Child2`, `Parent`
|
|
713
|
+
|
|
714
|
+
### Example 20: Explicit Empty Nested Composite
|
|
715
|
+
|
|
716
|
+
**Test**: `should skip explicitly requested empty nested composites`
|
|
717
|
+
|
|
718
|
+
```mermaid
|
|
719
|
+
graph RL
|
|
720
|
+
subgraph Root["Root 🚀"]
|
|
721
|
+
subgraph Level1["Level1 ✅"]
|
|
722
|
+
Level2["Level2 ✅"]
|
|
723
|
+
end
|
|
724
|
+
end
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
**Decision Steps**:
|
|
728
|
+
|
|
729
|
+
1. `Root` explicitly requested;
|
|
730
|
+
2. `Root` has no unit descendants (directly or recursively) → empty composite;
|
|
731
|
+
3. empty composites are not added to operation plan;
|
|
732
|
+
4. `Level1` and `Level2` are also empty composites and remain excluded.
|
|
733
|
+
|
|
734
|
+
**Update Phase**: _(empty)_
|
|
735
|
+
|
|
736
|
+
### Example 21: Mixed Nested Branches with Empty Composite Pruning
|
|
737
|
+
|
|
738
|
+
**Test**: `should keep non-empty branch while skipping empty nested composites`
|
|
739
|
+
|
|
740
|
+
```mermaid
|
|
741
|
+
graph RL
|
|
742
|
+
subgraph Root["Root 🚀"]
|
|
743
|
+
subgraph EmptyLevel1
|
|
744
|
+
EmptyLevel2["EmptyLevel2 ✅"]
|
|
745
|
+
end
|
|
746
|
+
subgraph NonEmptyLevel1
|
|
747
|
+
Leaf["Leaf ≠"]
|
|
748
|
+
end
|
|
749
|
+
end
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
**Decision Steps**:
|
|
753
|
+
|
|
754
|
+
1. `Root` explicitly requested;
|
|
755
|
+
2. `EmptyLevel1`/`EmptyLevel2` have no unit descendants → both excluded;
|
|
756
|
+
3. `Leaf` is outdated and is a unit descendant under `NonEmptyLevel1` → `Leaf` included;
|
|
757
|
+
4. `NonEmptyLevel1` is parent of included child → `NonEmptyLevel1` included;
|
|
758
|
+
5. `Root` is explicitly requested and non-empty due to `Leaf` branch → `Root` included.
|
|
759
|
+
|
|
760
|
+
**Update Phase**: `Root`, `Leaf`, `NonEmptyLevel1`
|