@cosmicdrift/kumiko-framework 0.2.2 → 0.3.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 (191) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/package.json +124 -38
  3. package/src/__tests__/full-stack.integration.ts +2 -2
  4. package/src/api/auth-routes.ts +5 -5
  5. package/src/api/jwt.ts +2 -2
  6. package/src/api/route-registrars.ts +1 -1
  7. package/src/api/routes.ts +3 -3
  8. package/src/api/server.ts +6 -7
  9. package/src/auth/__tests__/roles.test.ts +24 -0
  10. package/src/auth/index.ts +7 -0
  11. package/src/auth/roles.ts +42 -0
  12. package/src/compliance/__tests__/duration-spec.test.ts +72 -0
  13. package/src/compliance/__tests__/profiles.test.ts +308 -0
  14. package/src/compliance/__tests__/sub-processors.test.ts +139 -0
  15. package/src/compliance/duration-spec.ts +44 -0
  16. package/src/compliance/index.ts +31 -0
  17. package/src/compliance/override-schema.ts +136 -0
  18. package/src/compliance/profiles.ts +427 -0
  19. package/src/compliance/sub-processors.ts +152 -0
  20. package/src/db/__tests__/big-int-field.test.ts +131 -0
  21. package/src/db/assert-exists-in.ts +2 -2
  22. package/src/db/cursor.ts +3 -3
  23. package/src/db/event-store-executor.ts +19 -13
  24. package/src/db/located-timestamp.ts +1 -1
  25. package/src/db/money.ts +12 -2
  26. package/src/db/pg-error.ts +1 -1
  27. package/src/db/row-helpers.ts +1 -1
  28. package/src/db/table-builder.ts +20 -5
  29. package/src/db/tenant-db.ts +9 -9
  30. package/src/engine/__tests__/_pipeline-test-utils.ts +23 -0
  31. package/src/engine/__tests__/boot-validator-api-exposure.test.ts +142 -0
  32. package/src/engine/__tests__/boot-validator-pii-retention.test.ts +570 -0
  33. package/src/engine/__tests__/boot-validator-s0-integration.test.ts +160 -0
  34. package/src/engine/__tests__/build-target.test.ts +135 -0
  35. package/src/engine/__tests__/codemod-pipeline.test.ts +551 -0
  36. package/src/engine/__tests__/entity-handlers.test.ts +3 -3
  37. package/src/engine/__tests__/event-helpers.test.ts +4 -4
  38. package/src/engine/__tests__/pipeline-engine.test.ts +215 -0
  39. package/src/engine/__tests__/pipeline-handler.integration.ts +894 -0
  40. package/src/engine/__tests__/pipeline-observability.integration.ts +142 -0
  41. package/src/engine/__tests__/pipeline-performance.integration.ts +152 -0
  42. package/src/engine/__tests__/pipeline-sub-pipelines.test.ts +288 -0
  43. package/src/engine/__tests__/raw-table.test.ts +2 -2
  44. package/src/engine/__tests__/steps-aggregate-append-event.test.ts +115 -0
  45. package/src/engine/__tests__/steps-aggregate-create.test.ts +92 -0
  46. package/src/engine/__tests__/steps-aggregate-update.test.ts +127 -0
  47. package/src/engine/__tests__/steps-call-feature.test.ts +123 -0
  48. package/src/engine/__tests__/steps-mail-send.test.ts +136 -0
  49. package/src/engine/__tests__/steps-read.test.ts +142 -0
  50. package/src/engine/__tests__/steps-resolver-utils.test.ts +50 -0
  51. package/src/engine/__tests__/steps-unsafe-projection-delete.test.ts +69 -0
  52. package/src/engine/__tests__/steps-unsafe-projection-upsert.test.ts +117 -0
  53. package/src/engine/__tests__/steps-webhook-send.test.ts +135 -0
  54. package/src/engine/__tests__/steps-workflow.test.ts +198 -0
  55. package/src/engine/__tests__/validate-projection-allowlist.test.ts +491 -0
  56. package/src/engine/__tests__/visual-tree-patterns.test.ts +251 -0
  57. package/src/engine/boot-validator/api-ext.ts +77 -0
  58. package/src/engine/boot-validator/config-deps.ts +163 -0
  59. package/src/engine/boot-validator/entity-handler.ts +466 -0
  60. package/src/engine/boot-validator/index.ts +159 -0
  61. package/src/engine/boot-validator/ownership.ts +198 -0
  62. package/src/engine/boot-validator/pii-retention.ts +155 -0
  63. package/src/engine/boot-validator/screens-nav.ts +624 -0
  64. package/src/engine/boot-validator.ts +1 -1528
  65. package/src/engine/build-app-schema.ts +1 -1
  66. package/src/engine/build-target.ts +99 -0
  67. package/src/engine/codemod/index.ts +15 -0
  68. package/src/engine/codemod/pipeline-codemod.ts +641 -0
  69. package/src/engine/config-helpers.ts +9 -19
  70. package/src/engine/constants.ts +1 -1
  71. package/src/engine/define-feature.ts +127 -9
  72. package/src/engine/define-handler.ts +89 -3
  73. package/src/engine/define-roles.ts +2 -2
  74. package/src/engine/define-step.ts +28 -0
  75. package/src/engine/define-workflow.ts +110 -0
  76. package/src/engine/entity-handlers.ts +10 -9
  77. package/src/engine/event-helpers.ts +4 -4
  78. package/src/engine/extension-names.ts +105 -0
  79. package/src/engine/extensions/user-data.ts +106 -0
  80. package/src/engine/factories.ts +26 -16
  81. package/src/engine/feature-ast/__tests__/visual-tree-parse.test.ts +184 -0
  82. package/src/engine/feature-ast/extractors/index.ts +74 -0
  83. package/src/engine/feature-ast/extractors/round1.ts +110 -0
  84. package/src/engine/feature-ast/extractors/round2.ts +253 -0
  85. package/src/engine/feature-ast/extractors/round3.ts +471 -0
  86. package/src/engine/feature-ast/extractors/round4.ts +1365 -0
  87. package/src/engine/feature-ast/extractors/round5.ts +72 -0
  88. package/src/engine/feature-ast/extractors/round6.ts +66 -0
  89. package/src/engine/feature-ast/extractors/shared.ts +177 -0
  90. package/src/engine/feature-ast/parse.ts +13 -0
  91. package/src/engine/feature-ast/patch.ts +9 -1
  92. package/src/engine/feature-ast/patcher.ts +10 -3
  93. package/src/engine/feature-ast/patterns.ts +71 -1
  94. package/src/engine/feature-ast/render.ts +31 -1
  95. package/src/engine/index.ts +66 -2
  96. package/src/engine/pattern-library/__tests__/library.test.ts +11 -0
  97. package/src/engine/pattern-library/library.ts +78 -2
  98. package/src/engine/pipeline.ts +88 -0
  99. package/src/engine/projection-helpers.ts +1 -1
  100. package/src/engine/read-claim.ts +1 -1
  101. package/src/engine/registry.ts +30 -2
  102. package/src/engine/resolve-config-or-param.ts +4 -0
  103. package/src/engine/run-pipeline.ts +162 -0
  104. package/src/engine/schema-builder.ts +10 -4
  105. package/src/engine/state-machine.ts +1 -1
  106. package/src/engine/steps/_drizzle-boundary.ts +19 -0
  107. package/src/engine/steps/_duration-utils.ts +33 -0
  108. package/src/engine/steps/_no-return-guard.ts +21 -0
  109. package/src/engine/steps/_resolver-utils.ts +42 -0
  110. package/src/engine/steps/_step-dispatch-constants.ts +38 -0
  111. package/src/engine/steps/aggregate-append-event.ts +56 -0
  112. package/src/engine/steps/aggregate-create.ts +56 -0
  113. package/src/engine/steps/aggregate-update.ts +68 -0
  114. package/src/engine/steps/branch.ts +84 -0
  115. package/src/engine/steps/call-feature.ts +49 -0
  116. package/src/engine/steps/compute.ts +41 -0
  117. package/src/engine/steps/for-each.ts +111 -0
  118. package/src/engine/steps/mail-send.ts +44 -0
  119. package/src/engine/steps/read-find-many.ts +51 -0
  120. package/src/engine/steps/read-find-one.ts +58 -0
  121. package/src/engine/steps/retry.ts +87 -0
  122. package/src/engine/steps/return.ts +34 -0
  123. package/src/engine/steps/unsafe-projection-delete.ts +46 -0
  124. package/src/engine/steps/unsafe-projection-upsert.ts +69 -0
  125. package/src/engine/steps/wait-for-event.ts +71 -0
  126. package/src/engine/steps/wait.ts +69 -0
  127. package/src/engine/steps/webhook-send.ts +71 -0
  128. package/src/engine/system-user.ts +1 -1
  129. package/src/engine/types/feature.ts +143 -1
  130. package/src/engine/types/fields.ts +134 -10
  131. package/src/engine/types/handlers.ts +18 -10
  132. package/src/engine/types/identifiers.ts +1 -0
  133. package/src/engine/types/index.ts +15 -1
  134. package/src/engine/types/step.ts +334 -0
  135. package/src/engine/types/target-ref.ts +21 -0
  136. package/src/engine/types/tree-node.ts +130 -0
  137. package/src/engine/types/workspace.ts +7 -0
  138. package/src/engine/validate-projection-allowlist.ts +161 -0
  139. package/src/event-store/snapshot.ts +1 -1
  140. package/src/event-store/upcaster-dead-letter.ts +1 -1
  141. package/src/event-store/upcaster.ts +1 -1
  142. package/src/files/__tests__/read-stream.test.ts +105 -0
  143. package/src/files/__tests__/write-stream.test.ts +233 -0
  144. package/src/files/__tests__/zip-stream.test.ts +357 -0
  145. package/src/files/file-routes.ts +1 -1
  146. package/src/files/in-memory-provider.ts +38 -0
  147. package/src/files/index.ts +3 -0
  148. package/src/files/local-provider.ts +58 -1
  149. package/src/files/types.ts +36 -8
  150. package/src/files/zip-stream.ts +251 -0
  151. package/src/jobs/job-runner.ts +10 -10
  152. package/src/lifecycle/lifecycle.ts +0 -3
  153. package/src/logging/index.ts +1 -0
  154. package/src/logging/pino-logger.ts +11 -7
  155. package/src/logging/utils.ts +24 -0
  156. package/src/observability/prometheus-meter.ts +7 -5
  157. package/src/pipeline/__tests__/archive-stream.integration.ts +1 -1
  158. package/src/pipeline/__tests__/causation-chain.integration.ts +1 -1
  159. package/src/pipeline/__tests__/domain-events-projections.integration.ts +3 -3
  160. package/src/pipeline/__tests__/event-define-event-strict.integration.ts +4 -4
  161. package/src/pipeline/__tests__/load-aggregate-query.integration.ts +1 -1
  162. package/src/pipeline/__tests__/msp-multi-hop.integration.ts +3 -3
  163. package/src/pipeline/__tests__/msp-rebuild.integration.ts +3 -3
  164. package/src/pipeline/__tests__/multi-stream-projection.integration.ts +2 -2
  165. package/src/pipeline/__tests__/query-projection.integration.ts +5 -5
  166. package/src/pipeline/append-event-core.ts +22 -6
  167. package/src/pipeline/dispatcher-utils.ts +188 -0
  168. package/src/pipeline/dispatcher.ts +63 -283
  169. package/src/pipeline/distributed-lock.ts +1 -1
  170. package/src/pipeline/entity-cache.ts +2 -2
  171. package/src/pipeline/event-consumer-state.ts +0 -13
  172. package/src/pipeline/event-dispatcher.ts +4 -4
  173. package/src/pipeline/index.ts +0 -2
  174. package/src/pipeline/lifecycle-pipeline.ts +6 -12
  175. package/src/pipeline/msp-rebuild.ts +5 -5
  176. package/src/pipeline/multi-stream-apply-context.ts +6 -7
  177. package/src/pipeline/projection-rebuild.ts +2 -2
  178. package/src/pipeline/projection-state.ts +0 -12
  179. package/src/rate-limit/__tests__/resolver.integration.ts +8 -4
  180. package/src/rate-limit/resolver.ts +1 -1
  181. package/src/search/in-memory-adapter.ts +1 -1
  182. package/src/search/meilisearch-adapter.ts +3 -3
  183. package/src/search/types.ts +1 -1
  184. package/src/secrets/leak-guard.ts +2 -2
  185. package/src/stack/request-helper.ts +9 -5
  186. package/src/stack/test-stack.ts +1 -1
  187. package/src/testing/handler-context.ts +4 -4
  188. package/src/testing/http-cookies.ts +1 -1
  189. package/src/time/tz-context.ts +1 -2
  190. package/src/ui-types/index.ts +4 -0
  191. package/src/engine/feature-ast/extractors.ts +0 -2562
@@ -0,0 +1,160 @@
1
+ // Integration-Test: alle Sprint-0-Surfaces in einem Mini-Feature-Set.
2
+ //
3
+ // Beweist dass die einzelnen S0-Komponenten (PII-Annotations, retention,
4
+ // extension-names, exposesApi/usesApi, ROLES) zusammen funktionieren —
5
+ // keine still-konkurrierenden Validierungen, keine Race-Conditions
6
+ // zwischen Sub-Validatoren.
7
+ //
8
+ // Mini-Feature-Set:
9
+ // compliance-profiles exposesApi("compliance.forTenant")
10
+ // user-data-rights usesApi + extendsRegistrar(EXT_USER_DATA)
11
+ // tenant useExtension(EXT_USER_DATA, "user", ...)
12
+ // + entity mit pii / userOwned / tenantOwned-Fields
13
+ // + retention.blockDelete + anonymize-Funktion
14
+ // + handler-access mit ROLES.TenantAdmin
15
+
16
+ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
17
+ import { z } from "zod";
18
+ import { ROLES } from "../../auth";
19
+ import { validateBoot } from "../boot-validator";
20
+ import { defineFeature } from "../define-feature";
21
+ import {
22
+ createEntity,
23
+ createLongTextField,
24
+ createTextField,
25
+ createTimestampField,
26
+ EXT_USER_DATA,
27
+ } from "../index";
28
+
29
+ describe("S0 Integration — full surface stack", () => {
30
+ let warnSpy: ReturnType<typeof vi.spyOn>;
31
+
32
+ beforeEach(() => {
33
+ warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
34
+ });
35
+
36
+ afterEach(() => {
37
+ warnSpy.mockRestore();
38
+ });
39
+
40
+ test("alle S0-Surfaces zusammen passen Boot-Validation", () => {
41
+ const complianceProfiles = defineFeature("compliance-profiles", (r) => {
42
+ r.exposesApi("compliance.forTenant");
43
+ r.queryHandler({
44
+ name: "compliance:query:for-tenant",
45
+ schema: z.object({}),
46
+ handler: async () => ({ profile: "eu-dsgvo" }) as never,
47
+ access: { openToAll: true },
48
+ });
49
+ });
50
+
51
+ const userDataRights = defineFeature("user-data-rights", (r) => {
52
+ r.requires("compliance-profiles");
53
+ r.usesApi("compliance.forTenant");
54
+ r.extendsRegistrar(EXT_USER_DATA, {
55
+ hooks: {},
56
+ });
57
+ });
58
+
59
+ const tenantFeature = defineFeature("tenant-app", (r) => {
60
+ r.requires("user-data-rights", "compliance-profiles");
61
+
62
+ r.entity(
63
+ "user",
64
+ createEntity({
65
+ fields: {
66
+ email: createTextField({ pii: true }),
67
+ displayName: createTextField({ pii: true }),
68
+ lastLoginAt: createTimestampField({ pii: true }),
69
+ },
70
+ retention: {
71
+ keepFor: "10y",
72
+ strategy: "blockDelete",
73
+ reference: "createdAt",
74
+ },
75
+ }),
76
+ );
77
+
78
+ r.entity(
79
+ "comment",
80
+ createEntity({
81
+ fields: {
82
+ body: createLongTextField({
83
+ userOwned: { ownerField: "authorId" },
84
+ anonymize: () => "[ANONYMIZED]",
85
+ }),
86
+ authorId: { type: "reference", entity: "user" },
87
+ },
88
+ }),
89
+ );
90
+
91
+ r.queryHandler({
92
+ name: "user:list",
93
+ schema: z.object({}),
94
+ handler: async () => ({ rows: [], nextCursor: null }) as never,
95
+ access: { openToAll: true },
96
+ });
97
+
98
+ r.useExtension(EXT_USER_DATA, "user", {});
99
+ r.useExtension(EXT_USER_DATA, "comment", {});
100
+
101
+ r.writeHandler({
102
+ name: "user:rename",
103
+ schema: z.object({ id: z.string(), displayName: z.string() }),
104
+ handler: async () => undefined as never,
105
+ access: { roles: [ROLES.TenantAdmin] },
106
+ });
107
+ });
108
+
109
+ expect(() => validateBoot([complianceProfiles, userDataRights, tenantFeature])).not.toThrow();
110
+ });
111
+
112
+ test("missing requires() on usesApi-target throws even when other surfaces are clean", () => {
113
+ const complianceProfiles = defineFeature("compliance-profiles", (r) => {
114
+ r.exposesApi("compliance.forTenant");
115
+ });
116
+
117
+ const userDataRights = defineFeature("user-data-rights", (r) => {
118
+ // VERGESSEN: r.requires("compliance-profiles")
119
+ r.usesApi("compliance.forTenant");
120
+ });
121
+
122
+ expect(() => validateBoot([complianceProfiles, userDataRights])).toThrow(
123
+ /not in requires\/optionalRequires\. Add r\.requires\("compliance-profiles"\)/,
124
+ );
125
+ });
126
+
127
+ test("retention.reference pointing to non-existent field throws even with valid PII annotations", () => {
128
+ const feature = defineFeature("test", (r) => {
129
+ r.entity(
130
+ "user",
131
+ createEntity({
132
+ fields: {
133
+ email: createTextField({ pii: true }),
134
+ },
135
+ retention: {
136
+ keepFor: "30d",
137
+ strategy: "hardDelete",
138
+ reference: "notARealField",
139
+ },
140
+ }),
141
+ );
142
+ });
143
+ expect(() => validateBoot([feature])).toThrow(
144
+ /retention\.reference "notARealField" does not exist/,
145
+ );
146
+ });
147
+
148
+ test("ROLES.TenantAdmin works as handler-access role string (no Admin/TenantAdmin drift)", () => {
149
+ const feature = defineFeature("test", (r) => {
150
+ r.entity("thing", createEntity({ fields: { ts: createTimestampField() } }));
151
+ r.writeHandler({
152
+ name: "thing:create",
153
+ schema: z.object({}),
154
+ handler: async () => undefined as never,
155
+ access: { roles: [ROLES.TenantAdmin] },
156
+ });
157
+ });
158
+ expect(() => validateBoot([feature])).not.toThrow();
159
+ });
160
+ });
@@ -0,0 +1,135 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { buildTarget, type TreeActionDef } from "../index";
3
+
4
+ // createTreeActionsStub — Test-Helper für Phase-0-Stub-Features. Das
5
+ // `const`-Generic-Modifier forciert Literal-Inference, sodass die
6
+ // Action-Namen als string-literal-Union ankommen (statt zu `string`
7
+ // widening). In V.1.1 wird der Helper überflüssig: echte defineFeature-
8
+ // Outputs haben dieselbe Shape und Tests konsumieren die direkt.
9
+ function createTreeActionsStub<const TActions extends Record<string, TreeActionDef>>(spec: {
10
+ readonly id: string;
11
+ readonly treeActions: TActions;
12
+ }): { readonly id: string; readonly treeActions: TActions } {
13
+ return Object.freeze({ id: spec.id, treeActions: spec.treeActions });
14
+ }
15
+
16
+ const textContentStub = createTreeActionsStub({
17
+ id: "text-content",
18
+ treeActions: {
19
+ edit: { args: { slug: "" as string } },
20
+ create: { args: { folder: "" as string } },
21
+ list: {},
22
+ },
23
+ });
24
+
25
+ describe("buildTarget — NoArgs-Action", () => {
26
+ test("erzeugt TargetRef ohne args-Feld", () => {
27
+ const ref = buildTarget({ target: textContentStub, action: "list" });
28
+ expect(ref).toEqual({ featureId: "text-content", action: "list" });
29
+ expect("args" in ref).toBe(false);
30
+ });
31
+
32
+ test("output ist frozen (immutable)", () => {
33
+ const ref = buildTarget({ target: textContentStub, action: "list" });
34
+ expect(Object.isFrozen(ref)).toBe(true);
35
+ });
36
+ });
37
+
38
+ describe("buildTarget — WithArgs-Action", () => {
39
+ test("erzeugt TargetRef mit args", () => {
40
+ const ref = buildTarget({
41
+ target: textContentStub,
42
+ action: "edit",
43
+ args: { slug: "imprint" },
44
+ });
45
+ expect(ref).toEqual({
46
+ featureId: "text-content",
47
+ action: "edit",
48
+ args: { slug: "imprint" },
49
+ });
50
+ });
51
+
52
+ test("args sind frozen — Mutation am Input-Objekt schlägt nicht durch", () => {
53
+ const inputArgs = { slug: "imprint" };
54
+ const ref = buildTarget({
55
+ target: textContentStub,
56
+ action: "edit",
57
+ args: inputArgs,
58
+ });
59
+ inputArgs.slug = "mutated";
60
+ expect(ref.args).toEqual({ slug: "imprint" });
61
+ expect(Object.isFrozen(ref.args)).toBe(true);
62
+ });
63
+
64
+ test("verschiedene Actions haben unterschiedliche Arg-Shapes", () => {
65
+ const editRef = buildTarget({
66
+ target: textContentStub,
67
+ action: "edit",
68
+ args: { slug: "imprint" },
69
+ });
70
+ const createRef = buildTarget({
71
+ target: textContentStub,
72
+ action: "create",
73
+ args: { folder: "/marketing" },
74
+ });
75
+ expect(editRef.args).toEqual({ slug: "imprint" });
76
+ expect(createRef.args).toEqual({ folder: "/marketing" });
77
+ });
78
+ });
79
+
80
+ describe("buildTarget — Compile-Time-Safety (verified via @ts-expect-error)", () => {
81
+ // Jeder Test paart ein @ts-expect-error-Block (compile-time-validation
82
+ // via TypeScript) mit einem runtime-expect über den korrespondierenden
83
+ // Happy-Path. Doppelt-Coverage: Compiler prüft Rejection, vitest prüft
84
+ // dass die korrekte Form bei valid input das richtige Ergebnis liefert.
85
+ // Memory `[Keine Fake-Tests]` — Tests müssen runtime-Verhalten prüfen,
86
+ // nicht nur Compile-Time (Fake-Test-Guard).
87
+
88
+ test("unbekannte action wird vom Compiler abgelehnt", () => {
89
+ // @ts-expect-error — "delet" ist keine Action von textContentStub
90
+ buildTarget({ target: textContentStub, action: "delet" });
91
+ // Runtime: bekannte Action liefert valid TargetRef
92
+ const ref = buildTarget({ target: textContentStub, action: "list" });
93
+ expect(ref.action).toBe("list");
94
+ });
95
+
96
+ test("falsche args-shape wird vom Compiler abgelehnt", () => {
97
+ // @ts-expect-error — slug muss string sein, nicht number
98
+ buildTarget({
99
+ target: textContentStub,
100
+ action: "edit",
101
+ args: { slug: 42 },
102
+ });
103
+ // Runtime: korrekt-typed args liefern valid TargetRef
104
+ const ref = buildTarget({
105
+ target: textContentStub,
106
+ action: "edit",
107
+ args: { slug: "imprint" },
108
+ });
109
+ expect(ref.args).toEqual({ slug: "imprint" });
110
+ });
111
+
112
+ test("args bei NoArgs-Action wird vom Compiler abgelehnt", () => {
113
+ buildTarget({
114
+ target: textContentStub,
115
+ action: "list",
116
+ // @ts-expect-error — list hat keine args, args-Feld nicht erlaubt
117
+ args: { x: 1 },
118
+ });
119
+ // Runtime: NoArgs-Action ohne args-Feld liefert valid TargetRef
120
+ const ref = buildTarget({ target: textContentStub, action: "list" });
121
+ expect("args" in ref).toBe(false);
122
+ });
123
+
124
+ test("fehlende args bei WithArgs-Action wird vom Compiler abgelehnt", () => {
125
+ // @ts-expect-error — edit braucht args, fehlt
126
+ buildTarget({ target: textContentStub, action: "edit" });
127
+ // Runtime: WithArgs-Action mit args liefert valid TargetRef
128
+ const ref = buildTarget({
129
+ target: textContentStub,
130
+ action: "edit",
131
+ args: { slug: "imprint" },
132
+ });
133
+ expect(ref.args).toBeDefined();
134
+ });
135
+ });