@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.
Files changed (120) hide show
  1. package/dist/{chunk-V2NILDHS.js → chunk-52MY2TCE.js} +347 -19
  2. package/dist/chunk-52MY2TCE.js.map +1 -0
  3. package/dist/{chunk-I7BWSAN6.js → chunk-UAWBPTDW.js} +3 -3
  4. package/dist/{chunk-I7BWSAN6.js.map → chunk-UAWBPTDW.js.map} +1 -1
  5. package/dist/highstate.manifest.json +4 -4
  6. package/dist/index.js +4159 -785
  7. package/dist/index.js.map +1 -1
  8. package/dist/library/worker/main.js +5 -2
  9. package/dist/library/worker/main.js.map +1 -1
  10. package/dist/shared/index.js +2 -2
  11. package/package.json +7 -7
  12. package/prisma/backend/_schema/object.prisma +12 -0
  13. package/prisma/backend/sqlite/migrations/20260222113554_add_object_tracking/migration.sql +7 -0
  14. package/prisma/project/artifact.prisma +3 -0
  15. package/prisma/project/entity.prisma +125 -0
  16. package/prisma/project/instance.prisma +6 -0
  17. package/prisma/project/migrations/20260301210131_add_entity_tracking/migration.sql +70 -0
  18. package/prisma/project/migrations/20260302212734_add_resource_hooks_flag/migration.sql +1 -0
  19. package/prisma/project/operation.prisma +3 -0
  20. package/src/business/artifact.test.ts +22 -2
  21. package/src/business/artifact.ts +7 -1
  22. package/src/business/entity-snapshot.test.ts +684 -0
  23. package/src/business/entity-snapshot.ts +904 -0
  24. package/src/business/evaluation.test.ts +56 -0
  25. package/src/business/evaluation.ts +102 -22
  26. package/src/business/global-search.test.ts +344 -0
  27. package/src/business/global-search.ts +902 -0
  28. package/src/business/index.ts +4 -0
  29. package/src/business/instance-lock.ts +58 -74
  30. package/src/business/instance-state.test.ts +15 -1
  31. package/src/business/instance-state.ts +37 -14
  32. package/src/business/object-ref-index.test.ts +140 -0
  33. package/src/business/object-ref-index.ts +193 -0
  34. package/src/business/operation.test.ts +15 -1
  35. package/src/business/operation.ts +4 -0
  36. package/src/business/project-model.ts +154 -13
  37. package/src/business/project-unlock.ts +25 -2
  38. package/src/business/project.ts +9 -0
  39. package/src/business/secret.test.ts +35 -2
  40. package/src/business/secret.ts +32 -9
  41. package/src/business/settings.ts +761 -0
  42. package/src/business/unit-output.test.ts +477 -0
  43. package/src/business/unit-output.ts +461 -0
  44. package/src/business/worker.ts +55 -4
  45. package/src/database/_generated/backend/postgresql/browser.ts +6 -0
  46. package/src/database/_generated/backend/postgresql/client.ts +6 -0
  47. package/src/database/_generated/backend/postgresql/internal/class.ts +23 -5
  48. package/src/database/_generated/backend/postgresql/internal/prismaNamespace.ts +89 -5
  49. package/src/database/_generated/backend/postgresql/internal/prismaNamespaceBrowser.ts +9 -0
  50. package/src/database/_generated/backend/postgresql/models/Object.ts +1076 -0
  51. package/src/database/_generated/backend/postgresql/models.ts +1 -0
  52. package/src/database/_generated/backend/sqlite/browser.ts +6 -0
  53. package/src/database/_generated/backend/sqlite/client.ts +6 -0
  54. package/src/database/_generated/backend/sqlite/internal/class.ts +23 -5
  55. package/src/database/_generated/backend/sqlite/internal/prismaNamespace.ts +89 -5
  56. package/src/database/_generated/backend/sqlite/internal/prismaNamespaceBrowser.ts +9 -0
  57. package/src/database/_generated/backend/sqlite/models/Object.ts +1074 -0
  58. package/src/database/_generated/backend/sqlite/models.ts +1 -0
  59. package/src/database/_generated/project/browser.ts +23 -0
  60. package/src/database/_generated/project/client.ts +23 -0
  61. package/src/database/_generated/project/commonInputTypes.ts +87 -53
  62. package/src/database/_generated/project/enums.ts +8 -0
  63. package/src/database/_generated/project/internal/class.ts +53 -5
  64. package/src/database/_generated/project/internal/prismaNamespace.ts +367 -13
  65. package/src/database/_generated/project/internal/prismaNamespaceBrowser.ts +48 -1
  66. package/src/database/_generated/project/models/Artifact.ts +199 -11
  67. package/src/database/_generated/project/models/Entity.ts +1274 -0
  68. package/src/database/_generated/project/models/EntitySnapshot.ts +2389 -0
  69. package/src/database/_generated/project/models/EntitySnapshotContent.ts +1260 -0
  70. package/src/database/_generated/project/models/EntitySnapshotReference.ts +1449 -0
  71. package/src/database/_generated/project/models/InstanceState.ts +361 -1
  72. package/src/database/_generated/project/models/Operation.ts +148 -3
  73. package/src/database/_generated/project/models/OperationLog.ts +0 -4
  74. package/src/database/_generated/project/models.ts +4 -0
  75. package/src/database/migration.ts +3 -0
  76. package/src/library/worker/evaluator.ts +7 -1
  77. package/src/orchestrator/manager.ts +7 -0
  78. package/src/orchestrator/operation-context.captured-outputs.test.ts +118 -0
  79. package/src/orchestrator/operation-context.ts +154 -16
  80. package/src/orchestrator/operation-plan.destroy.test.md +33 -12
  81. package/src/orchestrator/operation-plan.destroy.test.ts +140 -2
  82. package/src/orchestrator/operation-plan.fixtures.ts +2 -0
  83. package/src/orchestrator/operation-plan.md +4 -1
  84. package/src/orchestrator/operation-plan.ts +286 -92
  85. package/src/orchestrator/operation-plan.update.test.md +286 -11
  86. package/src/orchestrator/operation-plan.update.test.ts +656 -5
  87. package/src/orchestrator/operation-workset.ts +72 -22
  88. package/src/orchestrator/operation.cancel.test.ts +4 -0
  89. package/src/orchestrator/operation.composite.test.ts +341 -0
  90. package/src/orchestrator/operation.destroy.test.ts +4 -0
  91. package/src/orchestrator/operation.output-validation.failure.test.ts +124 -0
  92. package/src/orchestrator/operation.preview.test.ts +4 -0
  93. package/src/orchestrator/operation.refresh.test.ts +4 -0
  94. package/src/orchestrator/operation.test-utils.ts +52 -13
  95. package/src/orchestrator/operation.ts +228 -68
  96. package/src/orchestrator/operation.update.failure.test.ts +4 -0
  97. package/src/orchestrator/operation.update.skip.test.ts +110 -0
  98. package/src/orchestrator/operation.update.test.ts +4 -0
  99. package/src/orchestrator/plan-test-builder.ts +1 -0
  100. package/src/orchestrator/unit-input-values.test.ts +450 -0
  101. package/src/orchestrator/unit-input-values.ts +281 -0
  102. package/src/pubsub/manager.ts +3 -0
  103. package/src/runner/abstractions.ts +23 -54
  104. package/src/runner/local.ts +109 -85
  105. package/src/services.ts +52 -1
  106. package/src/shared/models/prisma.ts +1 -0
  107. package/src/shared/models/project/entity.ts +121 -0
  108. package/src/shared/models/project/index.ts +1 -0
  109. package/src/shared/models/project/operation.ts +61 -3
  110. package/src/shared/models/project/state.ts +10 -0
  111. package/src/shared/models/project/worker.ts +7 -0
  112. package/src/shared/resolvers/effective-output-type.test.ts +494 -0
  113. package/src/shared/resolvers/effective-output-type.ts +162 -0
  114. package/src/shared/resolvers/index.ts +1 -0
  115. package/src/shared/resolvers/input.ts +59 -9
  116. package/src/shared/utils/index.ts +1 -0
  117. package/src/shared/utils/stable-json.ts +41 -0
  118. package/src/terminal/manager.ts +6 -0
  119. package/src/worker/manager.ts +97 -1
  120. package/dist/chunk-V2NILDHS.js.map +0 -1
@@ -0,0 +1,477 @@
1
+ import type { LibraryBackend } from "../library"
2
+ import {
3
+ defineEntity,
4
+ defineUnit,
5
+ HighstateSignature,
6
+ unitArtifactId,
7
+ z,
8
+ } from "@highstate/contract"
9
+ import pino from "pino"
10
+ import { describe, expect, test, vi } from "vitest"
11
+ import { UnitOutputService } from "./unit-output"
12
+
13
+ describe("UnitOutputService", () => {
14
+ test("computes outputHash from non-$ outputs only", async () => {
15
+ const testEntity = defineEntity({
16
+ type: "test.entity.v1",
17
+ schema: z.object({ value: z.string() }),
18
+ })
19
+
20
+ const testUnit = defineUnit({
21
+ type: "component.v1",
22
+ outputs: {
23
+ value: testEntity,
24
+ },
25
+ source: {
26
+ package: "@test/units",
27
+ path: "unit",
28
+ },
29
+ })
30
+
31
+ const libraryBackend = vi.mockObject({
32
+ loadLibrary: vi.fn().mockResolvedValue({
33
+ components: {
34
+ "component.v1": testUnit.model,
35
+ },
36
+ entities: {
37
+ "test.entity.v1": testEntity.model,
38
+ },
39
+ }),
40
+ } as unknown as LibraryBackend)
41
+
42
+ const service = new UnitOutputService(libraryBackend, pino({ level: "silent" }))
43
+
44
+ const baseOutputs = {
45
+ value: {
46
+ value: {
47
+ $meta: { type: "test.entity.v1", identity: "id-1", title: "x" },
48
+ value: "hello",
49
+ },
50
+ },
51
+ $statusFields: {
52
+ value: [{ name: "status", meta: { title: "Status" }, value: "ok" }],
53
+ },
54
+ }
55
+
56
+ const a = await service.parseUnitOutputs({
57
+ libraryId: "lib",
58
+ instanceType: "component.v1",
59
+ outputs: baseOutputs,
60
+ })
61
+
62
+ const b = await service.parseUnitOutputs({
63
+ libraryId: "lib",
64
+ instanceType: "component.v1",
65
+ outputs: {
66
+ ...baseOutputs,
67
+ $statusFields: {
68
+ value: [{ name: "status", meta: { title: "Status" }, value: "changed" }],
69
+ },
70
+ },
71
+ })
72
+
73
+ expect(a.outputHash).not.toBeNull()
74
+ expect(b.outputHash).toBe(a.outputHash)
75
+
76
+ const c = await service.parseUnitOutputs({
77
+ libraryId: "lib",
78
+ instanceType: "component.v1",
79
+ outputs: {
80
+ ...baseOutputs,
81
+ value: {
82
+ value: {
83
+ $meta: { type: "test.entity.v1", identity: "id-1", title: "x" },
84
+ value: "world",
85
+ },
86
+ },
87
+ },
88
+ })
89
+
90
+ expect(c.outputHash).not.toBe(a.outputHash)
91
+ })
92
+
93
+ test("parses $statusFields/$terminals/$pages/$triggers/$workers/$secrets", async () => {
94
+ const service = new UnitOutputService(
95
+ vi.mockObject({
96
+ loadLibrary: vi.fn(),
97
+ } as unknown as LibraryBackend),
98
+ pino({ level: "silent" }),
99
+ )
100
+
101
+ const parsed = await service.parseUnitOutputs({
102
+ libraryId: "lib",
103
+ instanceType: "component.v1",
104
+ outputs: {
105
+ $statusFields: {
106
+ value: [{ name: "health", meta: { title: "Health" }, value: "healthy" }],
107
+ },
108
+ $terminals: {
109
+ value: [
110
+ {
111
+ name: "ssh",
112
+ meta: { title: "SSH" },
113
+ spec: { image: "alpine:3.18", command: ["sh"] },
114
+ },
115
+ ],
116
+ },
117
+ $pages: {
118
+ value: [
119
+ {
120
+ name: "readme",
121
+ meta: { title: "Readme" },
122
+ content: [{ type: "markdown", content: "hello" }],
123
+ },
124
+ ],
125
+ },
126
+ $triggers: {
127
+ value: [
128
+ {
129
+ name: "before-destroy",
130
+ meta: { title: "Before destroy" },
131
+ spec: { type: "before-destroy" },
132
+ },
133
+ ],
134
+ },
135
+ $workers: {
136
+ value: [{ name: "worker", image: "alpine:3.18", params: { a: 1 } }],
137
+ },
138
+ $secrets: {
139
+ value: { token: "secret" },
140
+ },
141
+ },
142
+ })
143
+
144
+ expect(parsed.outputHash).toBeNull()
145
+ expect(parsed.entitySnapshotPayload).toBeNull()
146
+ expect(parsed.statusFields?.[0]?.name).toBe("health")
147
+ expect(parsed.terminals?.[0]?.name).toBe("ssh")
148
+ expect(parsed.pages?.[0]?.name).toBe("readme")
149
+ expect(parsed.triggers?.[0]?.name).toBe("before-destroy")
150
+ expect(parsed.workers?.[0]?.name).toBe("worker")
151
+ expect(parsed.secrets).toEqual({ token: "secret" })
152
+ expect(parsed.hasResourceHooks).toBe(false)
153
+ })
154
+
155
+ test("parses $hasResourceHooks", async () => {
156
+ const service = new UnitOutputService(
157
+ vi.mockObject({
158
+ loadLibrary: vi.fn(),
159
+ } as unknown as LibraryBackend),
160
+ pino({ level: "silent" }),
161
+ )
162
+
163
+ const parsed = await service.parseUnitOutputs({
164
+ libraryId: "lib",
165
+ instanceType: "component.v1",
166
+ outputs: {
167
+ $hasResourceHooks: { value: true },
168
+ },
169
+ })
170
+
171
+ expect(parsed.hasResourceHooks).toBe(true)
172
+ })
173
+
174
+ test("extracts exportedArtifactIds from $artifacts", async () => {
175
+ const service = new UnitOutputService(
176
+ vi.mockObject({
177
+ loadLibrary: vi.fn(),
178
+ } as unknown as LibraryBackend),
179
+ pino({ level: "silent" }),
180
+ )
181
+
182
+ const parsed = await service.parseUnitOutputs({
183
+ libraryId: "lib",
184
+ instanceType: "component.v1",
185
+ outputs: {
186
+ $artifacts: {
187
+ value: {
188
+ output: [
189
+ {
190
+ [HighstateSignature.Artifact]: true,
191
+ [unitArtifactId]: "artifact-1",
192
+ hash: "hash-1",
193
+ },
194
+ ],
195
+ },
196
+ },
197
+ },
198
+ })
199
+
200
+ expect(parsed.exportedArtifactIds).toEqual({ output: ["artifact-1"] })
201
+ })
202
+
203
+ test("throws when $artifacts item misses unitArtifactId", async () => {
204
+ const service = new UnitOutputService(
205
+ vi.mockObject({
206
+ loadLibrary: vi.fn(),
207
+ } as unknown as LibraryBackend),
208
+ pino({ level: "silent" }),
209
+ )
210
+
211
+ await expect(
212
+ service.parseUnitOutputs({
213
+ libraryId: "lib",
214
+ instanceType: "component.v1",
215
+ outputs: {
216
+ $artifacts: {
217
+ value: {
218
+ output: [
219
+ {
220
+ [HighstateSignature.Artifact]: true,
221
+ hash: "hash-missing-id",
222
+ },
223
+ ],
224
+ },
225
+ },
226
+ },
227
+ }),
228
+ ).rejects.toThrow("Failed to determine artifact ID for artifact with hash hash-missing-id")
229
+ })
230
+
231
+ test("builds entitySnapshotPayload using static output specs and entity inclusions", async () => {
232
+ const childEntity = defineEntity({
233
+ type: "child.v1",
234
+ schema: z.object({ name: z.string() }),
235
+ })
236
+
237
+ const parentEntity = defineEntity({
238
+ type: "parent.v1",
239
+ schema: z.object({ name: z.string() }),
240
+ includes: {
241
+ childOne: childEntity,
242
+ childTwo: childEntity,
243
+ children: { entity: childEntity, multiple: true, required: false },
244
+ },
245
+ })
246
+
247
+ const testUnit = defineUnit({
248
+ type: "component.v1",
249
+ outputs: {
250
+ parent: parentEntity,
251
+ parents: { entity: parentEntity, multiple: true },
252
+ },
253
+ source: {
254
+ package: "@test/units",
255
+ path: "unit",
256
+ },
257
+ })
258
+
259
+ const library = {
260
+ components: {
261
+ "component.v1": testUnit.model,
262
+ },
263
+ entities: {
264
+ "child.v1": childEntity.model,
265
+ "parent.v1": parentEntity.model,
266
+ },
267
+ }
268
+
269
+ const libraryBackend = vi.mockObject({
270
+ loadLibrary: vi.fn().mockResolvedValue(library),
271
+ } as unknown as LibraryBackend)
272
+
273
+ const service = new UnitOutputService(libraryBackend, pino({ level: "silent" }))
274
+
275
+ const childOne = {
276
+ $meta: { type: "child.v1", identity: "child-1", title: "Child 1" },
277
+ name: "child-1",
278
+ }
279
+
280
+ const childTwo = {
281
+ $meta: { type: "child.v1", identity: "child-2", title: "Child 2" },
282
+ name: "child-2",
283
+ }
284
+
285
+ const parsed = await service.parseUnitOutputs({
286
+ libraryId: "lib",
287
+ instanceType: "component.v1",
288
+ outputs: {
289
+ parent: {
290
+ value: {
291
+ $meta: {
292
+ type: "parent.v1",
293
+ identity: "parent-1",
294
+ title: "Parent Title",
295
+ references: {
296
+ deps: ["external-ref"],
297
+ },
298
+ },
299
+ name: "p",
300
+ childOne: childOne,
301
+ childTwo: childTwo,
302
+ children: [childOne, childTwo],
303
+ },
304
+ },
305
+ parents: {
306
+ value: [
307
+ {
308
+ $meta: { type: "parent.v1", identity: "dup", title: "First" },
309
+ name: "x",
310
+ childOne: childOne,
311
+ childTwo: childTwo,
312
+ },
313
+ {
314
+ $meta: { type: "parent.v1", identity: "dup", title: "Second" },
315
+ name: "y",
316
+ childOne: childOne,
317
+ childTwo: childTwo,
318
+ },
319
+ ],
320
+ },
321
+ },
322
+ })
323
+
324
+ const payload = parsed.entitySnapshotPayload
325
+ expect(payload).not.toBeNull()
326
+
327
+ const nodes = payload!.nodes
328
+ const parentNodes = nodes.filter(n => n.entityType === "parent.v1")
329
+ const childNodes = nodes.filter(n => n.entityType === "child.v1")
330
+
331
+ expect(parentNodes).toHaveLength(2)
332
+ expect(childNodes).toHaveLength(2)
333
+
334
+ const parent = nodes.find(n => n.entityType === "parent.v1" && n.identity === "parent-1")
335
+ expect(parent?.entityType).toBe("parent.v1")
336
+ expect(parent?.identity).toBe("parent-1")
337
+ expect(payload?.explicitReferences).toEqual([
338
+ { fromEntityId: parent!.entityId, toEntityId: "external-ref", group: "deps" },
339
+ ])
340
+ expect(parent?.meta?.title).toBe("Parent Title")
341
+ expect(parent?.content).not.toHaveProperty("$meta")
342
+ expect(parent?.content).not.toHaveProperty("childOne")
343
+ expect(parent?.content).not.toHaveProperty("childTwo")
344
+ expect(parent?.content).not.toHaveProperty("children")
345
+ expect(parent?.exportedOutputs).toEqual(["parent"])
346
+
347
+ const child1 = nodes.find(n => n.entityType === "child.v1" && n.identity === "child-1")
348
+ const child2 = nodes.find(n => n.entityType === "child.v1" && n.identity === "child-2")
349
+ expect(child1?.referencedOutputs).toEqual(expect.arrayContaining(["parent", "parents"]))
350
+ expect(child1?.exportedOutputs).toEqual([])
351
+ expect(child2?.referencedOutputs).toEqual(expect.arrayContaining(["parent", "parents"]))
352
+ expect(child2?.exportedOutputs).toEqual([])
353
+
354
+ const duplicateParents = nodes.filter(n => n.entityType === "parent.v1" && n.identity === "dup")
355
+ expect(duplicateParents).toHaveLength(1)
356
+ expect(duplicateParents[0]?.exportedOutputs).toEqual(["parents"])
357
+
358
+ const implicitFromParent = payload!.implicitReferences.filter(
359
+ r => r.fromEntityId === parent!.entityId,
360
+ )
361
+ expect(implicitFromParent).toEqual(
362
+ expect.arrayContaining([
363
+ { fromEntityId: parent!.entityId, toEntityId: child1!.entityId, group: "childOne" },
364
+ { fromEntityId: parent!.entityId, toEntityId: child2!.entityId, group: "childTwo" },
365
+ { fromEntityId: parent!.entityId, toEntityId: child1!.entityId, group: "children" },
366
+ { fromEntityId: parent!.entityId, toEntityId: child2!.entityId, group: "children" },
367
+ ]),
368
+ )
369
+ })
370
+
371
+ test("keeps entity snapshot meta null when no UI meta fields are provided", async () => {
372
+ const entity = defineEntity({
373
+ type: "test.entity.v1",
374
+ schema: z.object({ value: z.string() }),
375
+ })
376
+
377
+ const unit = defineUnit({
378
+ type: "component.v1",
379
+ outputs: {
380
+ value: entity,
381
+ },
382
+ source: {
383
+ package: "@test/units",
384
+ path: "unit",
385
+ },
386
+ })
387
+
388
+ const libraryBackend = vi.mockObject({
389
+ loadLibrary: vi.fn().mockResolvedValue({
390
+ components: {
391
+ "component.v1": unit.model,
392
+ },
393
+ entities: {
394
+ "test.entity.v1": entity.model,
395
+ },
396
+ }),
397
+ } as unknown as LibraryBackend)
398
+
399
+ const service = new UnitOutputService(libraryBackend, pino({ level: "silent" }))
400
+
401
+ const parsed = await service.parseUnitOutputs({
402
+ libraryId: "lib",
403
+ instanceType: "component.v1",
404
+ outputs: {
405
+ value: {
406
+ value: {
407
+ $meta: { type: "test.entity.v1", identity: "id-1" },
408
+ value: "hello",
409
+ },
410
+ },
411
+ },
412
+ })
413
+
414
+ const payload = parsed.entitySnapshotPayload
415
+ expect(payload).not.toBeNull()
416
+ expect(payload!.nodes).toHaveLength(1)
417
+ expect(payload!.nodes[0]?.meta).toBeNull()
418
+ })
419
+
420
+ test("accepts inherited entity types in outputs", async () => {
421
+ const baseEntity = defineEntity({
422
+ type: "base.v1",
423
+ schema: z.object({ base: z.string() }),
424
+ })
425
+
426
+ const derivedEntity = defineEntity({
427
+ type: "derived.v1",
428
+ extends: {
429
+ base: baseEntity,
430
+ },
431
+ schema: z.object({ derived: z.string() }),
432
+ })
433
+
434
+ const unit = defineUnit({
435
+ type: "component.v1",
436
+ outputs: {
437
+ value: baseEntity,
438
+ },
439
+ source: {
440
+ package: "@test/units",
441
+ path: "unit",
442
+ },
443
+ })
444
+
445
+ const libraryBackend = vi.mockObject({
446
+ loadLibrary: vi.fn().mockResolvedValue({
447
+ components: {
448
+ "component.v1": unit.model,
449
+ },
450
+ entities: {
451
+ "base.v1": baseEntity.model,
452
+ "derived.v1": derivedEntity.model,
453
+ },
454
+ }),
455
+ } as unknown as LibraryBackend)
456
+
457
+ const service = new UnitOutputService(libraryBackend, pino({ level: "silent" }))
458
+
459
+ const parsed = await service.parseUnitOutputs({
460
+ libraryId: "lib",
461
+ instanceType: "component.v1",
462
+ outputs: {
463
+ value: {
464
+ value: {
465
+ $meta: { type: "derived.v1", identity: "id-1" },
466
+ base: "x",
467
+ derived: "y",
468
+ },
469
+ },
470
+ },
471
+ })
472
+
473
+ expect(parsed.entitySnapshotError).toBeNull()
474
+ expect(parsed.entitySnapshotPayload?.nodes).toHaveLength(1)
475
+ expect(parsed.entitySnapshotPayload?.nodes[0]?.entityType).toBe("derived.v1")
476
+ })
477
+ })