@cosmicdrift/kumiko-framework 0.257.0 → 0.258.1

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 (138) hide show
  1. package/package.json +3 -3
  2. package/src/__tests__/anonymous-access.integration.test.ts +3 -3
  3. package/src/__tests__/derived-fields.integration.test.ts +1 -1
  4. package/src/__tests__/entity-list-limits.integration.test.ts +1 -1
  5. package/src/__tests__/error-contract.integration.test.ts +1 -1
  6. package/src/__tests__/ownership-where-write-path.integration.test.ts +1 -1
  7. package/src/__tests__/ownership.integration.test.ts +7 -3
  8. package/src/__tests__/schema-cli-rebuild.integration.test.ts +2 -2
  9. package/src/__tests__/transition-guard.integration.test.ts +3 -3
  10. package/src/api/__tests__/api.test.ts +21 -3
  11. package/src/api/__tests__/batch.integration.test.ts +3 -3
  12. package/src/api/__tests__/body-limit.test.ts +7 -1
  13. package/src/api/__tests__/dispatcher-live.integration.test.ts +1 -1
  14. package/src/api/__tests__/http-route-rate-limit.integration.test.ts +7 -1
  15. package/src/api/__tests__/metrics-endpoint.test.ts +7 -1
  16. package/src/api/__tests__/nested-write.integration.test.ts +3 -3
  17. package/src/api/__tests__/server-boot-guards.test.ts +11 -4
  18. package/src/bun-db/__tests__/write-brand.test.ts +4 -1
  19. package/src/db/__tests__/apply-entity-event-tenant.integration.test.ts +1 -1
  20. package/src/db/__tests__/assert-exists-in.integration.test.ts +1 -1
  21. package/src/db/__tests__/assert-no-unreachable-live-rows.integration.test.ts +2 -2
  22. package/src/db/__tests__/collect-table-metas.test.ts +9 -5
  23. package/src/db/__tests__/compound-types.test.ts +1 -1
  24. package/src/db/__tests__/config-seed.integration.test.ts +3 -3
  25. package/src/db/__tests__/eagerload.integration.test.ts +7 -7
  26. package/src/db/__tests__/entity-table-from-registry.test.ts +8 -2
  27. package/src/db/__tests__/entity-table-meta-source.test.ts +7 -3
  28. package/src/db/__tests__/entity-table-types.test.ts +4 -4
  29. package/src/db/__tests__/event-store-executor-list.integration.test.ts +9 -2
  30. package/src/db/__tests__/event-store-executor-reference-search.integration.test.ts +4 -4
  31. package/src/db/__tests__/event-store-executor-reference-sort.integration.test.ts +3 -3
  32. package/src/db/__tests__/event-store-executor-write-verbs.integration.test.ts +4 -4
  33. package/src/db/__tests__/event-store-executor.integration.test.ts +11 -6
  34. package/src/db/__tests__/event-store-system-stream.integration.test.ts +2 -2
  35. package/src/db/__tests__/implicit-projection-equivalence.integration.test.ts +6 -4
  36. package/src/db/__tests__/located-timestamp.test.ts +1 -1
  37. package/src/db/__tests__/money.test.ts +1 -1
  38. package/src/db/__tests__/multi-row-insert.integration.test.ts +3 -3
  39. package/src/db/__tests__/parent-ref-clause.test.ts +213 -0
  40. package/src/db/__tests__/schema-migration.integration.test.ts +30 -14
  41. package/src/db/__tests__/source-shadow-create.integration.test.ts +1 -1
  42. package/src/db/__tests__/table-builder-indexes.test.ts +14 -12
  43. package/src/db/__tests__/table-builder-required.test.ts +2 -2
  44. package/src/db/__tests__/tenant-db-where-merge.integration.test.ts +1 -1
  45. package/src/db/__tests__/tenant-db-where-merge.test.ts +5 -2
  46. package/src/db/__tests__/tenant-db.integration.test.ts +2 -2
  47. package/src/db/__tests__/unique-violation-mapping.integration.test.ts +2 -2
  48. package/src/db/event-store-executor-read.ts +97 -19
  49. package/src/db/parent-ref-clause.ts +182 -0
  50. package/src/db/table-builder.ts +7 -0
  51. package/src/engine/__tests__/boot-validator-action-wiring.test.ts +50 -8
  52. package/src/engine/__tests__/boot-validator-detail-for.test.ts +28 -4
  53. package/src/engine/__tests__/boot-validator-entity-list.test.ts +20 -4
  54. package/src/engine/__tests__/boot-validator-i18n-keys.test.ts +9 -3
  55. package/src/engine/__tests__/boot-validator.test.ts +716 -91
  56. package/src/engine/__tests__/define-feature-entity-mapping.test.ts +2 -2
  57. package/src/engine/__tests__/engine.test.ts +128 -37
  58. package/src/engine/__tests__/entity-handlers.test.ts +3 -3
  59. package/src/engine/__tests__/entity-presave-wiring.integration.test.ts +4 -4
  60. package/src/engine/__tests__/entity-restore-tenant-isolation.integration.test.ts +1 -1
  61. package/src/engine/__tests__/entity-write-crosstenant.integration.test.ts +1 -1
  62. package/src/engine/__tests__/extend-entity-projection.test.ts +1 -1
  63. package/src/engine/__tests__/extends-registrar.test.ts +13 -3
  64. package/src/engine/__tests__/factories-long-text.test.ts +10 -6
  65. package/src/engine/__tests__/feature-crud-shorthand.test.ts +1 -1
  66. package/src/engine/__tests__/feature-crud-verb-access.integration.test.ts +1 -1
  67. package/src/engine/__tests__/multiselect-filter.integration.test.ts +1 -1
  68. package/src/engine/__tests__/nav.test.ts +1 -1
  69. package/src/engine/__tests__/pipeline-handler.integration.test.ts +1 -1
  70. package/src/engine/__tests__/pipeline-observability.integration.test.ts +1 -1
  71. package/src/engine/__tests__/pipeline-performance.integration.test.ts +1 -1
  72. package/src/engine/__tests__/projection-detail-actions.test.ts +6 -1
  73. package/src/engine/__tests__/projection-detail-singleton.test.ts +6 -1
  74. package/src/engine/__tests__/projection-detail-tabs.test.ts +6 -1
  75. package/src/engine/__tests__/projection-list-filter.integration.test.ts +7 -2
  76. package/src/engine/__tests__/projection.test.ts +1 -1
  77. package/src/engine/__tests__/registry.test.ts +13 -3
  78. package/src/engine/__tests__/schema-builder.test.ts +39 -11
  79. package/src/engine/__tests__/screen.test.ts +21 -15
  80. package/src/engine/__tests__/search-payload-extension.test.ts +15 -3
  81. package/src/engine/__tests__/store-table.test.ts +6 -3
  82. package/src/engine/__tests__/validate-projection-allowlist.test.ts +12 -4
  83. package/src/engine/__tests__/validation-hooks.test.ts +7 -1
  84. package/src/engine/boot-validator/__tests__/ownership.test.ts +2 -2
  85. package/src/engine/boot-validator/__tests__/parent-ref.test.ts +149 -0
  86. package/src/engine/boot-validator/action-wiring.ts +7 -7
  87. package/src/engine/boot-validator/index.ts +2 -0
  88. package/src/engine/boot-validator/parent-ref.ts +96 -0
  89. package/src/engine/boot-validator/projection-list-screens.ts +36 -23
  90. package/src/engine/boot-validator/query-output-columns.ts +2 -1
  91. package/src/engine/boot-validator/query-refs.ts +2 -1
  92. package/src/engine/boot-validator/screens.ts +322 -131
  93. package/src/engine/build-config-feature-schema.ts +2 -2
  94. package/src/engine/define-handler.ts +6 -0
  95. package/src/engine/entity-handlers.ts +6 -1
  96. package/src/engine/factories.ts +2 -0
  97. package/src/engine/feature-entity-handlers.ts +5 -0
  98. package/src/engine/index.ts +6 -0
  99. package/src/engine/ownership.ts +15 -0
  100. package/src/engine/pattern-library/mixed-schemas.ts +1 -0
  101. package/src/engine/screen-helpers.ts +25 -0
  102. package/src/engine/types/index.ts +5 -0
  103. package/src/es-ops/__tests__/runner.integration.test.ts +1 -1
  104. package/src/event-store/__tests__/upcaster.integration.test.ts +1 -1
  105. package/src/files/__tests__/file-field-pipeline.integration.test.ts +1 -1
  106. package/src/files/__tests__/files.integration.test.ts +1 -1
  107. package/src/files/file-ref-entity.ts +9 -9
  108. package/src/i18n/__tests__/i18n.test.ts +7 -1
  109. package/src/i18n/required-surface-keys.ts +48 -0
  110. package/src/migrations/__tests__/kumiko-drift.integration.test.ts +3 -1
  111. package/src/migrations/__tests__/pending-rebuilds.integration.test.ts +2 -2
  112. package/src/pipeline/__tests__/archive-stream.integration.test.ts +1 -1
  113. package/src/pipeline/__tests__/cascade-handler.integration.test.ts +23 -8
  114. package/src/pipeline/__tests__/cascade-handler.test.ts +18 -3
  115. package/src/pipeline/__tests__/causation-chain.integration.test.ts +1 -1
  116. package/src/pipeline/__tests__/ctx-bridge.integration.test.ts +5 -3
  117. package/src/pipeline/__tests__/dispatcher.test.ts +42 -6
  118. package/src/pipeline/__tests__/domain-events-projections.integration.test.ts +2 -2
  119. package/src/pipeline/__tests__/fetch-for-writing.integration.test.ts +1 -1
  120. package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +42 -6
  121. package/src/pipeline/__tests__/load-aggregate-query.integration.test.ts +2 -2
  122. package/src/pipeline/__tests__/msp-multi-hop.integration.test.ts +1 -1
  123. package/src/pipeline/__tests__/msp-rebuild.integration.test.ts +6 -2
  124. package/src/pipeline/__tests__/multi-stream-projection.integration.test.ts +6 -2
  125. package/src/pipeline/__tests__/perf-rebuild.integration.test.ts +1 -1
  126. package/src/pipeline/__tests__/post-query-hook.integration.test.ts +3 -3
  127. package/src/pipeline/__tests__/projection-rebuild.integration.test.ts +3 -3
  128. package/src/pipeline/__tests__/query-projection.integration.test.ts +1 -1
  129. package/src/pipeline/__tests__/rebuild-poison-quarantine.integration.test.ts +2 -2
  130. package/src/pipeline/__tests__/search-consumer-named-events.integration.test.ts +13 -2
  131. package/src/pipeline/__tests__/try-append-event.integration.test.ts +1 -1
  132. package/src/search/__tests__/reindex-entity.integration.test.ts +6 -1
  133. package/src/search/__tests__/search-pii-derived-index.integration.test.ts +7 -1
  134. package/src/stack/__tests__/ownership-boot-guard.integration.test.ts +1 -1
  135. package/src/testing/__tests__/e2e-generator.test.ts +15 -5
  136. package/src/testing/e2e-generator.ts +2 -0
  137. package/src/testing/shared-entities.ts +4 -2
  138. package/src/ui-types/index.ts +4 -0
@@ -50,7 +50,13 @@ describe("boot-validator", () => {
50
50
  test("passes for valid features with no issues", () => {
51
51
  const features = [
52
52
  defineFeature("a", (r) => {
53
- r.entity("user", createEntity({ table: "Users", fields: { name: createTextField() } }));
53
+ r.entity(
54
+ "user",
55
+ createEntity({
56
+ table: "Users",
57
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
58
+ }),
59
+ );
54
60
  }),
55
61
  ];
56
62
  expect(() => validateBoot(features)).not.toThrow();
@@ -404,7 +410,7 @@ describe("boot-validator", () => {
404
410
  "item",
405
411
  createEntity({
406
412
  table: "Items",
407
- fields: { name: createTextField() },
413
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
408
414
  }),
409
415
  );
410
416
  r.extendsRegistrar("custom", {
@@ -426,7 +432,7 @@ describe("boot-validator", () => {
426
432
  "item",
427
433
  createEntity({
428
434
  table: "Items",
429
- fields: { status: createTextField() },
435
+ fields: { status: createTextField({ personal: false, reason: "test_fixture" }) },
430
436
  derivedFields: {
431
437
  status: createDerivedField({ valueType: "text", derive: () => "computed" }),
432
438
  },
@@ -446,7 +452,7 @@ describe("boot-validator", () => {
446
452
  "item",
447
453
  createEntity({
448
454
  table: "Items",
449
- fields: { status: createTextField() },
455
+ fields: { status: createTextField({ personal: false, reason: "test_fixture" }) },
450
456
  derivedFields: {
451
457
  statusLabel: createDerivedField({ valueType: "text", derive: () => "computed" }),
452
458
  },
@@ -498,7 +504,7 @@ describe("boot-validator", () => {
498
504
  "item",
499
505
  createEntity({
500
506
  table: "Items",
501
- fields: { name: createTextField() },
507
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
502
508
  }),
503
509
  );
504
510
  r.extendsRegistrar("custom", {
@@ -514,7 +520,13 @@ describe("boot-validator", () => {
514
520
  test("accepts bare CRUD write handler when feature name matches entity", () => {
515
521
  const features = [
516
522
  defineFeature("credit", (r) => {
517
- r.entity("credit", createEntity({ table: "Credits", fields: { name: createTextField() } }));
523
+ r.entity(
524
+ "credit",
525
+ createEntity({
526
+ table: "Credits",
527
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
528
+ }),
529
+ );
518
530
  r.writeHandler(
519
531
  "create",
520
532
  z.object({ name: z.string() }),
@@ -540,7 +552,13 @@ describe("boot-validator", () => {
540
552
  });
541
553
  const consumer = defineFeature("money-horse", (r) => {
542
554
  r.requires("cap-ext");
543
- r.entity("credit", createEntity({ table: "Credits", fields: { name: createTextField() } }));
555
+ r.entity(
556
+ "credit",
557
+ createEntity({
558
+ table: "Credits",
559
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
560
+ }),
561
+ );
544
562
  r.writeHandler(
545
563
  "doSomething",
546
564
  z.object({}),
@@ -583,7 +601,9 @@ describe("boot-validator", () => {
583
601
  test("registerEntityCrud without access options fails boot (no implicit openToAll)", () => {
584
602
  const widgetEntity = createEntity({
585
603
  table: "phase0_widgets",
586
- fields: { name: createTextField({ required: true }) },
604
+ fields: {
605
+ name: createTextField({ required: true, personal: false, reason: "test_fixture" }),
606
+ },
587
607
  });
588
608
  const features = [
589
609
  defineFeature("phase0", (r) => {
@@ -948,7 +968,13 @@ describe("boot-validator", () => {
948
968
  "order",
949
969
  createEntity({
950
970
  table: "orders",
951
- fields: { teamId: createTextField({ required: true }) },
971
+ fields: {
972
+ teamId: createTextField({
973
+ required: true,
974
+ personal: false,
975
+ reason: "test_fixture",
976
+ }),
977
+ },
952
978
  access: {
953
979
  read: { Admin: "all", TeamMember: from("claim:teams:teamId") },
954
980
  },
@@ -966,7 +992,13 @@ describe("boot-validator", () => {
966
992
  "order",
967
993
  createEntity({
968
994
  table: "orders",
969
- fields: { teamId: createTextField({ required: true }) },
995
+ fields: {
996
+ teamId: createTextField({
997
+ required: true,
998
+ personal: false,
999
+ reason: "test_fixture",
1000
+ }),
1001
+ },
970
1002
  access: {
971
1003
  // No "teams" feature registered — claim doesn't exist.
972
1004
  read: { TeamMember: from("claim:teams:teamId") },
@@ -990,7 +1022,13 @@ describe("boot-validator", () => {
990
1022
  "order",
991
1023
  createEntity({
992
1024
  table: "orders",
993
- fields: { teamId: createTextField({ required: true }) },
1025
+ fields: {
1026
+ teamId: createTextField({
1027
+ required: true,
1028
+ personal: false,
1029
+ reason: "test_fixture",
1030
+ }),
1031
+ },
994
1032
  access: {
995
1033
  read: {
996
1034
  // column "nonExistentColumn" not on entity
@@ -1017,12 +1055,18 @@ describe("boot-validator", () => {
1017
1055
  createEntity({
1018
1056
  table: "contracts",
1019
1057
  fields: {
1020
- teamId: createTextField({ required: true }),
1058
+ teamId: createTextField({
1059
+ required: true,
1060
+ personal: false,
1061
+ reason: "test_fixture",
1062
+ }),
1021
1063
  propC: createTextField({
1022
1064
  access: {
1023
1065
  read: { Admin: "all", TeamMember: from("claim:teams:teamId") },
1024
1066
  write: { Admin: "all", TeamMember: from("claim:teams:teamId") },
1025
1067
  },
1068
+ personal: false,
1069
+ reason: "test_fixture",
1026
1070
  }),
1027
1071
  },
1028
1072
  }),
@@ -1040,12 +1084,18 @@ describe("boot-validator", () => {
1040
1084
  createEntity({
1041
1085
  table: "contracts",
1042
1086
  fields: {
1043
- teamId: createTextField({ required: true }),
1087
+ teamId: createTextField({
1088
+ required: true,
1089
+ personal: false,
1090
+ reason: "test_fixture",
1091
+ }),
1044
1092
  propC: createTextField({
1045
1093
  access: {
1046
1094
  // claim not declared anywhere
1047
1095
  read: { TeamMember: from("claim:nowhere:teamId") },
1048
1096
  },
1097
+ personal: false,
1098
+ reason: "test_fixture",
1049
1099
  }),
1050
1100
  },
1051
1101
  }),
@@ -1064,7 +1114,7 @@ describe("boot-validator", () => {
1064
1114
  "order",
1065
1115
  createEntity({
1066
1116
  table: "orders",
1067
- fields: { assigneeId: createTextField() },
1117
+ fields: { assigneeId: createTextField({ personal: false, reason: "test_fixture" }) },
1068
1118
  access: {
1069
1119
  read: { Driver: from("user:id", "assigneeId") },
1070
1120
  },
@@ -1082,7 +1132,7 @@ describe("boot-validator", () => {
1082
1132
  "order",
1083
1133
  createEntity({
1084
1134
  table: "orders",
1085
- fields: { assigneeId: createTextField() },
1135
+ fields: { assigneeId: createTextField({ personal: false, reason: "test_fixture" }) },
1086
1136
  access: {
1087
1137
  read: {
1088
1138
  Admin: "all",
@@ -1106,7 +1156,13 @@ describe("boot-validator", () => {
1106
1156
  "order",
1107
1157
  createEntity({
1108
1158
  table: "orders",
1109
- fields: { entityId: createTextField({ required: true }) },
1159
+ fields: {
1160
+ entityId: createTextField({
1161
+ required: true,
1162
+ personal: false,
1163
+ reason: "test_fixture",
1164
+ }),
1165
+ },
1110
1166
  access: {
1111
1167
  read: {
1112
1168
  Auditor: {
@@ -1132,7 +1188,13 @@ describe("boot-validator", () => {
1132
1188
  "order",
1133
1189
  createEntity({
1134
1190
  table: "orders",
1135
- fields: { entityId: createTextField({ required: true }) },
1191
+ fields: {
1192
+ entityId: createTextField({
1193
+ required: true,
1194
+ personal: false,
1195
+ reason: "test_fixture",
1196
+ }),
1197
+ },
1136
1198
  access: {
1137
1199
  read: {
1138
1200
  Auditor: {
@@ -1196,7 +1258,13 @@ describe("boot-validator", () => {
1196
1258
  "order",
1197
1259
  createEntity({
1198
1260
  table: "orders",
1199
- fields: { teamId: createTextField({ required: true }) },
1261
+ fields: {
1262
+ teamId: createTextField({
1263
+ required: true,
1264
+ personal: false,
1265
+ reason: "test_fixture",
1266
+ }),
1267
+ },
1200
1268
  access: {
1201
1269
  read: { Admi: "all" },
1202
1270
  },
@@ -1225,7 +1293,11 @@ describe("boot-validator", () => {
1225
1293
  createEntity({
1226
1294
  table: "orders",
1227
1295
  fields: {
1228
- secret: createTextField({ access: { read: ["Admni"] } }),
1296
+ secret: createTextField({
1297
+ access: { read: ["Admni"] },
1298
+ personal: false,
1299
+ reason: "test_fixture",
1300
+ }),
1229
1301
  },
1230
1302
  }),
1231
1303
  );
@@ -1251,7 +1323,13 @@ describe("boot-validator", () => {
1251
1323
  "order",
1252
1324
  createEntity({
1253
1325
  table: "orders",
1254
- fields: { teamId: createTextField({ required: true }) },
1326
+ fields: {
1327
+ teamId: createTextField({
1328
+ required: true,
1329
+ personal: false,
1330
+ reason: "test_fixture",
1331
+ }),
1332
+ },
1255
1333
  access: {
1256
1334
  read: { Admin: "all", TeamMember: "all" },
1257
1335
  },
@@ -1273,7 +1351,13 @@ describe("boot-validator", () => {
1273
1351
  "order",
1274
1352
  createEntity({
1275
1353
  table: "orders",
1276
- fields: { teamId: createTextField({ required: true }) },
1354
+ fields: {
1355
+ teamId: createTextField({
1356
+ required: true,
1357
+ personal: false,
1358
+ reason: "test_fixture",
1359
+ }),
1360
+ },
1277
1361
  access: {
1278
1362
  read: { AnyRole: "all" },
1279
1363
  },
@@ -1807,7 +1891,12 @@ describe("boot-validator", () => {
1807
1891
  describe("entityList column renderer form", () => {
1808
1892
  function shopFeature(renderer: unknown) {
1809
1893
  return defineFeature("shop", (r) => {
1810
- r.entity("product", createEntity({ fields: { name: createTextField() } }));
1894
+ r.entity(
1895
+ "product",
1896
+ createEntity({
1897
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
1898
+ }),
1899
+ );
1811
1900
  r.screen({
1812
1901
  id: "product-list",
1813
1902
  type: "entityList",
@@ -1970,7 +2059,12 @@ describe("boot-validator", () => {
1970
2059
  describe("entityList virtual labeled column", () => {
1971
2060
  function noteFeature(column: unknown) {
1972
2061
  return defineFeature("notes", (r) => {
1973
- r.entity("note", createEntity({ fields: { title: createTextField() } }));
2062
+ r.entity(
2063
+ "note",
2064
+ createEntity({
2065
+ fields: { title: createTextField({ personal: false, reason: "test_fixture" }) },
2066
+ }),
2067
+ );
1974
2068
  r.screen({
1975
2069
  id: "note-list",
1976
2070
  type: "entityList",
@@ -2030,7 +2124,10 @@ describe("boot-validator", () => {
2030
2124
  return defineFeature("notes", (r) => {
2031
2125
  r.entity(
2032
2126
  "note",
2033
- createEntity({ fields: { title: createTextField() }, softDelete: options?.softDelete }),
2127
+ createEntity({
2128
+ fields: { title: createTextField({ personal: false, reason: "test_fixture" }) },
2129
+ softDelete: options?.softDelete,
2130
+ }),
2034
2131
  );
2035
2132
  r.screen({
2036
2133
  id: "note-list",
@@ -2089,7 +2186,12 @@ describe("boot-validator", () => {
2089
2186
  for (const typo of typoColumns) {
2090
2187
  test(`"${typo}" fails validateScreens (via validateBoot)`, () => {
2091
2188
  const feature = defineFeature("notes", (r) => {
2092
- r.entity("note", createEntity({ fields: { title: createTextField() } }));
2189
+ r.entity(
2190
+ "note",
2191
+ createEntity({
2192
+ fields: { title: createTextField({ personal: false, reason: "test_fixture" }) },
2193
+ }),
2194
+ );
2093
2195
  r.screen({
2094
2196
  id: "note-list",
2095
2197
  type: "entityList",
@@ -2104,7 +2206,12 @@ describe("boot-validator", () => {
2104
2206
 
2105
2207
  test(`"${typo}" fails validateEntityListScreens directly ("unknown column field")`, () => {
2106
2208
  const feature = defineFeature("notes", (r) => {
2107
- r.entity("note", createEntity({ fields: { title: createTextField() } }));
2209
+ r.entity(
2210
+ "note",
2211
+ createEntity({
2212
+ fields: { title: createTextField({ personal: false, reason: "test_fixture" }) },
2213
+ }),
2214
+ );
2108
2215
  r.screen({
2109
2216
  id: "note-list",
2110
2217
  type: "entityList",
@@ -2137,11 +2244,11 @@ describe("boot-validator", () => {
2137
2244
  "product",
2138
2245
  createEntity({
2139
2246
  fields: {
2140
- name: createTextField({ sortable: true }),
2247
+ name: createTextField({ sortable: true, personal: false, reason: "test_fixture" }),
2141
2248
  // Bewusst NICHT sortable: bestätigt dass Validator das
2142
2249
  // unterscheidet und nur sortable-Felder als defaultSort
2143
2250
  // akzeptiert.
2144
- description: createTextField(),
2251
+ description: createTextField({ personal: false, reason: "test_fixture" }),
2145
2252
  },
2146
2253
  }),
2147
2254
  );
@@ -2558,6 +2665,234 @@ describe("boot-validator", () => {
2558
2665
  });
2559
2666
  });
2560
2667
 
2668
+ // --- secretMint-Screen (fw#2548) ---
2669
+ // Mint-form → one-time reveal → confirm. Shares its six inline-form checks
2670
+ // with actionForm (validateInlineFormScreen), plus its own reveal.fields
2671
+ // checks: non-empty, unique `field`, non-empty `label`.
2672
+ describe("secretMint screen (fw#2548)", () => {
2673
+ type SecretMintOverride = {
2674
+ readonly handler?: string | undefined;
2675
+ readonly fields?: Record<string, unknown>;
2676
+ readonly sections?: ReadonlyArray<{
2677
+ readonly title: string;
2678
+ readonly fields: readonly string[];
2679
+ }>;
2680
+ readonly reveal?: {
2681
+ readonly fields: ReadonlyArray<{
2682
+ readonly field: string;
2683
+ readonly label: string;
2684
+ readonly display?: "code" | "list";
2685
+ }>;
2686
+ };
2687
+ readonly redirect?: string;
2688
+ readonly extraScreens?: readonly string[];
2689
+ readonly confirm?: {
2690
+ readonly handler?: string;
2691
+ readonly fields?: Record<string, unknown>;
2692
+ readonly sections?: ReadonlyArray<{
2693
+ readonly title: string;
2694
+ readonly fields: readonly string[];
2695
+ }>;
2696
+ readonly carry?: readonly string[];
2697
+ readonly mode?: "single" | "wizard" | "tabs";
2698
+ readonly draft?: boolean;
2699
+ };
2700
+ };
2701
+
2702
+ function makeFeature(override: SecretMintOverride = {}) {
2703
+ const handler = override.handler ?? "shop:write:token:mint";
2704
+ const fields = override.fields ?? { label: { type: "text" } };
2705
+ const sections = override.sections ?? [{ title: "Mint", fields: ["label"] }];
2706
+ const reveal = override.reveal ?? {
2707
+ fields: [{ field: "token", label: "shop:screen:mint-token.field:token" }],
2708
+ };
2709
+ const confirmOverride = override.confirm;
2710
+ return defineFeature("shop", (r) => {
2711
+ r.writeHandler({
2712
+ name: "token:mint",
2713
+ schema: { _type: "stub" } as never,
2714
+ handler: async () => ({ isSuccess: true, data: {} }) as never,
2715
+ access: { openToAll: true },
2716
+ });
2717
+ if (confirmOverride !== undefined) {
2718
+ r.writeHandler({
2719
+ name: "token:confirm",
2720
+ schema: { _type: "stub" } as never,
2721
+ handler: async () => ({ isSuccess: true, data: {} }) as never,
2722
+ access: { openToAll: true },
2723
+ });
2724
+ }
2725
+ r.screen({
2726
+ id: "mint-token",
2727
+ type: "secretMint",
2728
+ handler,
2729
+ fields: fields as never,
2730
+ layout: { sections: sections as never },
2731
+ reveal: reveal as never,
2732
+ ...(override.redirect !== undefined && { redirect: override.redirect }),
2733
+ ...(confirmOverride !== undefined && {
2734
+ confirm: {
2735
+ handler: confirmOverride.handler ?? "shop:write:token:confirm",
2736
+ fields: (confirmOverride.fields ?? { code: { type: "text" } }) as never,
2737
+ layout: {
2738
+ sections: (confirmOverride.sections ?? [
2739
+ { title: "Confirm", fields: ["code"] },
2740
+ ]) as never,
2741
+ ...(confirmOverride.mode !== undefined && { mode: confirmOverride.mode }),
2742
+ ...(confirmOverride.draft !== undefined && { draft: confirmOverride.draft }),
2743
+ },
2744
+ ...(confirmOverride.carry !== undefined && { carry: confirmOverride.carry }),
2745
+ },
2746
+ }),
2747
+ });
2748
+ for (const extra of override.extraScreens ?? []) {
2749
+ r.screen({
2750
+ id: extra,
2751
+ type: "custom",
2752
+ renderer: { react: "stub" },
2753
+ });
2754
+ }
2755
+ });
2756
+ }
2757
+
2758
+ test("happy path: handler + fields + layout + reveal konsistent → kein Throw", () => {
2759
+ expect(() => validateBoot([makeFeature()])).not.toThrow();
2760
+ });
2761
+
2762
+ test("handler nicht als write-handler registriert → Throw", () => {
2763
+ expect(() => validateBoot([makeFeature({ handler: "shop:query:token:list" })])).toThrow(
2764
+ /\(secretMint\) handler "shop:query:token:list" is not a registered write-handler/,
2765
+ );
2766
+ });
2767
+
2768
+ // secretMint allows an empty fields map (input-less mint, fw#2838) — but
2769
+ // the default `sections` still references "label", which no longer
2770
+ // exists once `fields` is empty, so this still throws (just via the
2771
+ // layout check instead of the fields-map check).
2772
+ test("fields empty-Map without also clearing sections → Throw", () => {
2773
+ expect(() => validateBoot([makeFeature({ fields: {} })])).toThrow(
2774
+ /\(secretMint\) layout references unknown field "label"/,
2775
+ );
2776
+ });
2777
+
2778
+ test("layout referenziert unknown field → Throw", () => {
2779
+ expect(() =>
2780
+ validateBoot([makeFeature({ sections: [{ title: "x", fields: ["ghost"] }] })]),
2781
+ ).toThrow(/\(secretMint\) layout references unknown field "ghost"/);
2782
+ });
2783
+
2784
+ test("redirect → existing screen-id → kein Throw", () => {
2785
+ expect(() =>
2786
+ validateBoot([makeFeature({ redirect: "after-mint", extraScreens: ["after-mint"] })]),
2787
+ ).not.toThrow();
2788
+ });
2789
+
2790
+ test("redirect → unknown screen-id → Throw", () => {
2791
+ expect(() => validateBoot([makeFeature({ redirect: "ghost-screen" })])).toThrow(
2792
+ /\(secretMint\) redirect "ghost-screen" does not resolve to a registered screen/,
2793
+ );
2794
+ });
2795
+
2796
+ test("reveal.fields leer → Throw", () => {
2797
+ expect(() => validateBoot([makeFeature({ reveal: { fields: [] } })])).toThrow(
2798
+ /has an empty reveal\.fields list/,
2799
+ );
2800
+ });
2801
+
2802
+ test("reveal.fields mit doppeltem field → Throw", () => {
2803
+ expect(() =>
2804
+ validateBoot([
2805
+ makeFeature({
2806
+ reveal: {
2807
+ fields: [
2808
+ { field: "token", label: "a" },
2809
+ { field: "token", label: "b" },
2810
+ ],
2811
+ },
2812
+ }),
2813
+ ]),
2814
+ ).toThrow(/reveal\.fields has a duplicate field "token"/);
2815
+ });
2816
+
2817
+ test("reveal.fields entry mit leerem label → Throw", () => {
2818
+ expect(() =>
2819
+ validateBoot([makeFeature({ reveal: { fields: [{ field: "token", label: "" }] } })]),
2820
+ ).toThrow(/reveal\.fields entry "token" has an empty or non-string "label"/);
2821
+ });
2822
+
2823
+ // --- input-less mint (fw#2838): fields: {} + layout.sections: [] is the
2824
+ // only valid empty shape — the secret is server-generated, the mint step
2825
+ // is just its submit button.
2826
+ test("empty fields + empty sections (input-less mint) → no throw", () => {
2827
+ expect(() => validateBoot([makeFeature({ fields: {}, sections: [] })])).not.toThrow();
2828
+ });
2829
+
2830
+ test("empty fields but non-empty sections list → Throw", () => {
2831
+ expect(() =>
2832
+ validateBoot([makeFeature({ fields: {}, sections: [{ title: "x", fields: ["ghost"] }] })]),
2833
+ ).toThrow(/\(secretMint\) layout references unknown field "ghost"/);
2834
+ });
2835
+
2836
+ test("non-empty fields but empty sections list → Throw", () => {
2837
+ expect(() => validateBoot([makeFeature({ sections: [] })])).toThrow(
2838
+ /\(secretMint\) has an empty sections list/,
2839
+ );
2840
+ });
2841
+
2842
+ // --- confirm step (fw#2838) ---
2843
+ describe("confirm step", () => {
2844
+ test("happy path: confirm handler + fields + layout consistent → no throw", () => {
2845
+ expect(() => validateBoot([makeFeature({ confirm: {} })])).not.toThrow();
2846
+ });
2847
+
2848
+ test("confirm.handler not a registered write-handler → Throw", () => {
2849
+ expect(() =>
2850
+ validateBoot([makeFeature({ confirm: { handler: "shop:query:token:list" } })]),
2851
+ ).toThrow(
2852
+ /\(secretMint confirm\) handler "shop:query:token:list" is not a registered write-handler/,
2853
+ );
2854
+ });
2855
+
2856
+ test("confirm.fields empty map → Throw", () => {
2857
+ expect(() => validateBoot([makeFeature({ confirm: { fields: {} } })])).toThrow(
2858
+ /\(secretMint confirm\) has empty fields map/,
2859
+ );
2860
+ });
2861
+
2862
+ test("confirm layout references unknown field → Throw", () => {
2863
+ expect(() =>
2864
+ validateBoot([
2865
+ makeFeature({ confirm: { sections: [{ title: "x", fields: ["ghost"] }] } }),
2866
+ ]),
2867
+ ).toThrow(/\(secretMint confirm\) layout references unknown field "ghost"/);
2868
+ });
2869
+
2870
+ test("confirm layout mode 'wizard' → Throw", () => {
2871
+ expect(() => validateBoot([makeFeature({ confirm: { mode: "wizard" } })])).toThrow(
2872
+ /\(secretMint confirm\) sets layout\.mode: "wizard"/,
2873
+ );
2874
+ });
2875
+
2876
+ test("confirm layout draft true → Throw", () => {
2877
+ expect(() => validateBoot([makeFeature({ confirm: { draft: true } })])).toThrow(
2878
+ /\(secretMint confirm\) sets layout\.draft: true/,
2879
+ );
2880
+ });
2881
+
2882
+ test("carry entry colliding with a confirm.fields name → Throw", () => {
2883
+ expect(() => validateBoot([makeFeature({ confirm: { carry: ["code"] } })])).toThrow(
2884
+ /\(secretMint confirm\) carry field "code" also names a confirm\.fields entry/,
2885
+ );
2886
+ });
2887
+
2888
+ test("carry entry that is not a confirm.fields name → no throw", () => {
2889
+ expect(() =>
2890
+ validateBoot([makeFeature({ confirm: { carry: ["setupToken"] } })]),
2891
+ ).not.toThrow();
2892
+ });
2893
+ });
2894
+ });
2895
+
2561
2896
  // --- configEdit-Screen ---
2562
2897
  // Form gegen das bundled config-feature. Boot-Validator prüft:
2563
2898
  // 1) fields non-empty + jeder mit type-Discriminator
@@ -2728,7 +3063,12 @@ describe("boot-validator", () => {
2728
3063
  describe("entityEdit extension section", () => {
2729
3064
  function makeFeature(component: unknown) {
2730
3065
  return defineFeature("shop", (r) => {
2731
- r.entity("product", createEntity({ fields: { name: createTextField() } }));
3066
+ r.entity(
3067
+ "product",
3068
+ createEntity({
3069
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
3070
+ }),
3071
+ );
2732
3072
  r.screen({
2733
3073
  id: "product-edit",
2734
3074
  type: "entityEdit",
@@ -2769,7 +3109,12 @@ describe("boot-validator", () => {
2769
3109
  return defineFeature("shop", (r) => {
2770
3110
  r.entity(
2771
3111
  "product",
2772
- createEntity({ fields: { name: createTextField(), sku: createTextField() } }),
3112
+ createEntity({
3113
+ fields: {
3114
+ name: createTextField({ personal: false, reason: "test_fixture" }),
3115
+ sku: createTextField({ personal: false, reason: "test_fixture" }),
3116
+ },
3117
+ }),
2773
3118
  );
2774
3119
  r.screen({
2775
3120
  id: "product-edit",
@@ -2826,7 +3171,12 @@ describe("boot-validator", () => {
2826
3171
  return defineFeature("shop", (r) => {
2827
3172
  r.entity(
2828
3173
  "product",
2829
- createEntity({ fields: { name: createTextField(), sku: createTextField() } }),
3174
+ createEntity({
3175
+ fields: {
3176
+ name: createTextField({ personal: false, reason: "test_fixture" }),
3177
+ sku: createTextField({ personal: false, reason: "test_fixture" }),
3178
+ },
3179
+ }),
2830
3180
  );
2831
3181
  r.screen({
2832
3182
  id: "product-edit",
@@ -2960,7 +3310,7 @@ describe("boot-validator", () => {
2960
3310
  "order",
2961
3311
  createEntity({
2962
3312
  fields: {
2963
- kind: createTextField(),
3313
+ kind: createTextField({ personal: false, reason: "test_fixture" }),
2964
3314
  lines: createEmbeddedField({ note: { type: "text" } }, { required: true }),
2965
3315
  },
2966
3316
  }),
@@ -3064,7 +3414,12 @@ describe("boot-validator", () => {
3064
3414
  test("reference auf bestehende Entity → kein Throw", () => {
3065
3415
  const features = [
3066
3416
  defineFeature("shop", (r) => {
3067
- r.entity("customer", createEntity({ fields: { name: createTextField() } }));
3417
+ r.entity(
3418
+ "customer",
3419
+ createEntity({
3420
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
3421
+ }),
3422
+ );
3068
3423
  stubListHandler(r, "customer");
3069
3424
  r.entity(
3070
3425
  "order",
@@ -3100,7 +3455,12 @@ describe("boot-validator", () => {
3100
3455
  test("reference labelField auf unknown Field → Throw", () => {
3101
3456
  const features = [
3102
3457
  defineFeature("shop", (r) => {
3103
- r.entity("customer", createEntity({ fields: { name: createTextField() } }));
3458
+ r.entity(
3459
+ "customer",
3460
+ createEntity({
3461
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
3462
+ }),
3463
+ );
3104
3464
  r.entity(
3105
3465
  "order",
3106
3466
  createEntity({
@@ -3119,7 +3479,12 @@ describe("boot-validator", () => {
3119
3479
  test("reference labelField=id ist immer ok (PK)", () => {
3120
3480
  const features = [
3121
3481
  defineFeature("shop", (r) => {
3122
- r.entity("customer", createEntity({ fields: { name: createTextField() } }));
3482
+ r.entity(
3483
+ "customer",
3484
+ createEntity({
3485
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
3486
+ }),
3487
+ );
3123
3488
  stubListHandler(r, "customer");
3124
3489
  r.entity(
3125
3490
  "order",
@@ -3141,7 +3506,7 @@ describe("boot-validator", () => {
3141
3506
  "category",
3142
3507
  createEntity({
3143
3508
  fields: {
3144
- name: createTextField(),
3509
+ name: createTextField({ personal: false, reason: "test_fixture" }),
3145
3510
  parentId: { type: "reference", entity: "category", labelField: "name" },
3146
3511
  },
3147
3512
  }),
@@ -3155,13 +3520,18 @@ describe("boot-validator", () => {
3155
3520
  test("reference mit multiple: true → kein Throw (Tier 2.7e-Multi)", () => {
3156
3521
  const features = [
3157
3522
  defineFeature("shop", (r) => {
3158
- r.entity("tag", createEntity({ fields: { name: createTextField() } }));
3523
+ r.entity(
3524
+ "tag",
3525
+ createEntity({
3526
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
3527
+ }),
3528
+ );
3159
3529
  stubListHandler(r, "tag");
3160
3530
  r.entity(
3161
3531
  "post",
3162
3532
  createEntity({
3163
3533
  fields: {
3164
- title: createTextField(),
3534
+ title: createTextField({ personal: false, reason: "test_fixture" }),
3165
3535
  tagIds: {
3166
3536
  type: "reference",
3167
3537
  entity: "tag",
@@ -3180,7 +3550,12 @@ describe("boot-validator", () => {
3180
3550
  test("cross-feature reference (feature:entity) → kein Throw", () => {
3181
3551
  const features = [
3182
3552
  defineFeature("users", (r) => {
3183
- r.entity("user", createEntity({ fields: { email: createTextField() } }));
3553
+ r.entity(
3554
+ "user",
3555
+ createEntity({
3556
+ fields: { email: createTextField({ personal: false, reason: "test_fixture" }) },
3557
+ }),
3558
+ );
3184
3559
  stubListHandler(r, "user");
3185
3560
  }),
3186
3561
  defineFeature("shop", (r) => {
@@ -3200,7 +3575,12 @@ describe("boot-validator", () => {
3200
3575
  test("Audit-Fix #2: cross-feature reference ohne list-handler → Throw", () => {
3201
3576
  const features = [
3202
3577
  defineFeature("users", (r) => {
3203
- r.entity("user", createEntity({ fields: { email: createTextField() } }));
3578
+ r.entity(
3579
+ "user",
3580
+ createEntity({
3581
+ fields: { email: createTextField({ personal: false, reason: "test_fixture" }) },
3582
+ }),
3583
+ );
3204
3584
  // KEINE stubListHandler — das ist der Punkt des Tests
3205
3585
  }),
3206
3586
  defineFeature("shop", (r) => {
@@ -3240,7 +3620,12 @@ describe("boot-validator", () => {
3240
3620
  test("cross-feature reference auf unknown entity → Throw mit feature-context", () => {
3241
3621
  const features = [
3242
3622
  defineFeature("users", (r) => {
3243
- r.entity("user", createEntity({ fields: { email: createTextField() } }));
3623
+ r.entity(
3624
+ "user",
3625
+ createEntity({
3626
+ fields: { email: createTextField({ personal: false, reason: "test_fixture" }) },
3627
+ }),
3628
+ );
3244
3629
  }),
3245
3630
  defineFeature("shop", (r) => {
3246
3631
  r.entity(
@@ -3261,7 +3646,12 @@ describe("boot-validator", () => {
3261
3646
  test("cross-feature labelField auf unknown Field → Throw", () => {
3262
3647
  const features = [
3263
3648
  defineFeature("users", (r) => {
3264
- r.entity("user", createEntity({ fields: { email: createTextField() } }));
3649
+ r.entity(
3650
+ "user",
3651
+ createEntity({
3652
+ fields: { email: createTextField({ personal: false, reason: "test_fixture" }) },
3653
+ }),
3654
+ );
3265
3655
  }),
3266
3656
  defineFeature("shop", (r) => {
3267
3657
  r.entity(
@@ -3312,7 +3702,12 @@ describe("boot-validator", () => {
3312
3702
  test("reference sub-field with a valid target + registered list-handler → kein Throw", () => {
3313
3703
  const features = [
3314
3704
  defineFeature("shop", (r) => {
3315
- r.entity("product", createEntity({ fields: { name: createTextField() } }));
3705
+ r.entity(
3706
+ "product",
3707
+ createEntity({
3708
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
3709
+ }),
3710
+ );
3316
3711
  stubListHandler(r, "product");
3317
3712
  r.entity(
3318
3713
  "invoice",
@@ -3332,7 +3727,12 @@ describe("boot-validator", () => {
3332
3727
  test("reference sub-field with a valid target but no registered list-handler → Throw", () => {
3333
3728
  const features = [
3334
3729
  defineFeature("shop", (r) => {
3335
- r.entity("product", createEntity({ fields: { name: createTextField() } }));
3730
+ r.entity(
3731
+ "product",
3732
+ createEntity({
3733
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
3734
+ }),
3735
+ );
3336
3736
  // KEIN stubListHandler — das ist der Punkt des Tests
3337
3737
  r.entity(
3338
3738
  "invoice",
@@ -3354,7 +3754,12 @@ describe("boot-validator", () => {
3354
3754
  test("reference sub-field labelField referencing an unknown field → Throw with the parent.child field path", () => {
3355
3755
  const features = [
3356
3756
  defineFeature("shop", (r) => {
3357
- r.entity("product", createEntity({ fields: { name: createTextField() } }));
3757
+ r.entity(
3758
+ "product",
3759
+ createEntity({
3760
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
3761
+ }),
3762
+ );
3358
3763
  stubListHandler(r, "product");
3359
3764
  r.entity(
3360
3765
  "invoice",
@@ -3382,7 +3787,12 @@ describe("boot-validator", () => {
3382
3787
  describe("entityList rowAction kind=navigate (Tier 2.7e-1)", () => {
3383
3788
  function makeFeature(targetScreen: string, withTarget: boolean) {
3384
3789
  return defineFeature("shop", (r) => {
3385
- r.entity("product", createEntity({ fields: { name: createTextField() } }));
3790
+ r.entity(
3791
+ "product",
3792
+ createEntity({
3793
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
3794
+ }),
3795
+ );
3386
3796
  r.screen({
3387
3797
  id: "product-list",
3388
3798
  type: "entityList",
@@ -3422,7 +3832,12 @@ describe("boot-validator", () => {
3422
3832
  // Custom-Editor der Consumer-App (anderes Feature). Runtime löst die bare
3423
3833
  // id app-weit auf → der Validator muss das erlauben.
3424
3834
  const list = defineFeature("shop", (r) => {
3425
- r.entity("product", createEntity({ fields: { name: createTextField() } }));
3835
+ r.entity(
3836
+ "product",
3837
+ createEntity({
3838
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
3839
+ }),
3840
+ );
3426
3841
  r.screen({
3427
3842
  id: "product-list",
3428
3843
  type: "entityList",
@@ -3445,7 +3860,12 @@ describe("boot-validator", () => {
3445
3860
  // open with no entity context at runtime. The validator must catch it
3446
3861
  // at boot instead.
3447
3862
  const list = defineFeature("shop", (r) => {
3448
- r.entity("product", createEntity({ fields: { name: createTextField() } }));
3863
+ r.entity(
3864
+ "product",
3865
+ createEntity({
3866
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
3867
+ }),
3868
+ );
3449
3869
  r.screen({
3450
3870
  id: "product-list",
3451
3871
  type: "entityList",
@@ -3457,7 +3877,12 @@ describe("boot-validator", () => {
3457
3877
  });
3458
3878
  });
3459
3879
  const consumer = defineFeature("billing", (r) => {
3460
- r.entity("invoice", createEntity({ fields: { name: createTextField() } }));
3880
+ r.entity(
3881
+ "invoice",
3882
+ createEntity({
3883
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
3884
+ }),
3885
+ );
3461
3886
  r.screen({
3462
3887
  id: "invoice-edit",
3463
3888
  type: "entityEdit",
@@ -3474,7 +3899,12 @@ describe("boot-validator", () => {
3474
3899
  const list = defineFeature("shop", (r) => {
3475
3900
  r.entity(
3476
3901
  "product",
3477
- createEntity({ fields: { name: createTextField(), invoiceId: createTextField() } }),
3902
+ createEntity({
3903
+ fields: {
3904
+ name: createTextField({ personal: false, reason: "test_fixture" }),
3905
+ invoiceId: createTextField({ personal: false, reason: "test_fixture" }),
3906
+ },
3907
+ }),
3478
3908
  );
3479
3909
  r.screen({
3480
3910
  id: "product-list",
@@ -3493,7 +3923,12 @@ describe("boot-validator", () => {
3493
3923
  });
3494
3924
  });
3495
3925
  const consumer = defineFeature("billing", (r) => {
3496
- r.entity("invoice", createEntity({ fields: { name: createTextField() } }));
3926
+ r.entity(
3927
+ "invoice",
3928
+ createEntity({
3929
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
3930
+ }),
3931
+ );
3497
3932
  r.screen({
3498
3933
  id: "invoice-edit",
3499
3934
  type: "entityEdit",
@@ -3506,7 +3941,12 @@ describe("boot-validator", () => {
3506
3941
 
3507
3942
  test("same-feature navigate to entityEdit WITHOUT entityId → kein Throw (same-feature fallback applies)", () => {
3508
3943
  const feature = defineFeature("shop", (r) => {
3509
- r.entity("product", createEntity({ fields: { name: createTextField() } }));
3944
+ r.entity(
3945
+ "product",
3946
+ createEntity({
3947
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
3948
+ }),
3949
+ );
3510
3950
  r.screen({
3511
3951
  id: "product-list",
3512
3952
  type: "entityList",
@@ -3531,7 +3971,12 @@ describe("boot-validator", () => {
3531
3971
  describe("entityList rowAction kind=navigate entity-target (fw#2228)", () => {
3532
3972
  test("entity-target resolving via detailFor → kein Throw", () => {
3533
3973
  const feature = defineFeature("shop", (r) => {
3534
- r.entity("product", createEntity({ fields: { name: createTextField() } }));
3974
+ r.entity(
3975
+ "product",
3976
+ createEntity({
3977
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
3978
+ }),
3979
+ );
3535
3980
  r.screen({
3536
3981
  id: "product-list",
3537
3982
  type: "entityList",
@@ -3551,7 +3996,12 @@ describe("boot-validator", () => {
3551
3996
 
3552
3997
  test("entity-target with no detailFor screen anywhere → Throw mit klarer Message", () => {
3553
3998
  const feature = defineFeature("shop", (r) => {
3554
- r.entity("product", createEntity({ fields: { name: createTextField() } }));
3999
+ r.entity(
4000
+ "product",
4001
+ createEntity({
4002
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
4003
+ }),
4004
+ );
3555
4005
  r.screen({
3556
4006
  id: "product-list",
3557
4007
  type: "entityList",
@@ -3567,7 +4017,12 @@ describe("boot-validator", () => {
3567
4017
 
3568
4018
  test("both screen and entity set → Throw", () => {
3569
4019
  const feature = defineFeature("shop", (r) => {
3570
- r.entity("product", createEntity({ fields: { name: createTextField() } }));
4020
+ r.entity(
4021
+ "product",
4022
+ createEntity({
4023
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
4024
+ }),
4025
+ );
3571
4026
  r.screen({
3572
4027
  id: "product-list",
3573
4028
  type: "entityList",
@@ -3597,7 +4052,12 @@ describe("boot-validator", () => {
3597
4052
 
3598
4053
  test("neither screen nor entity set → Throw", () => {
3599
4054
  const feature = defineFeature("shop", (r) => {
3600
- r.entity("product", createEntity({ fields: { name: createTextField() } }));
4055
+ r.entity(
4056
+ "product",
4057
+ createEntity({
4058
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
4059
+ }),
4060
+ );
3601
4061
  r.screen({
3602
4062
  id: "product-list",
3603
4063
  type: "entityList",
@@ -3616,7 +4076,12 @@ describe("boot-validator", () => {
3616
4076
  // detailFor resolution — the owning feature's list navigates to a
3617
4077
  // detail screen a consumer app registers for the entity.
3618
4078
  const list = defineFeature("shop", (r) => {
3619
- r.entity("product", createEntity({ fields: { name: createTextField() } }));
4079
+ r.entity(
4080
+ "product",
4081
+ createEntity({
4082
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
4083
+ }),
4084
+ );
3620
4085
  r.screen({
3621
4086
  id: "product-list",
3622
4087
  type: "entityList",
@@ -3643,7 +4108,12 @@ describe("boot-validator", () => {
3643
4108
  // row["id"]), so the same-feature-fallback gap that check guards
3644
4109
  // against doesn't exist here.
3645
4110
  const list = defineFeature("shop", (r) => {
3646
- r.entity("product", createEntity({ fields: { name: createTextField() } }));
4111
+ r.entity(
4112
+ "product",
4113
+ createEntity({
4114
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
4115
+ }),
4116
+ );
3647
4117
  r.screen({
3648
4118
  id: "product-list",
3649
4119
  type: "entityList",
@@ -3653,7 +4123,12 @@ describe("boot-validator", () => {
3653
4123
  });
3654
4124
  });
3655
4125
  const consumer = defineFeature("billing", (r) => {
3656
- r.entity("invoice", createEntity({ fields: { name: createTextField() } }));
4126
+ r.entity(
4127
+ "invoice",
4128
+ createEntity({
4129
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
4130
+ }),
4131
+ );
3657
4132
  r.screen({
3658
4133
  id: "invoice-edit",
3659
4134
  type: "entityEdit",
@@ -3670,7 +4145,12 @@ describe("boot-validator", () => {
3670
4145
  // guaranteed "id" field, unlike entityList rows — an entity-target
3671
4146
  // there must name the field to read the id from.
3672
4147
  const feature = defineFeature("shop", (r) => {
3673
- r.entity("product", createEntity({ fields: { name: createTextField() } }));
4148
+ r.entity(
4149
+ "product",
4150
+ createEntity({
4151
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
4152
+ }),
4153
+ );
3674
4154
  r.queryHandler("products", z.object({}), async () => ({ rows: [], nextCursor: null }), {
3675
4155
  access: { openToAll: true },
3676
4156
  });
@@ -3695,7 +4175,12 @@ describe("boot-validator", () => {
3695
4175
 
3696
4176
  test("projectionList rowAction entity-target with explicit entityId → kein Throw", () => {
3697
4177
  const feature = defineFeature("shop", (r) => {
3698
- r.entity("product", createEntity({ fields: { name: createTextField() } }));
4178
+ r.entity(
4179
+ "product",
4180
+ createEntity({
4181
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
4182
+ }),
4183
+ );
3699
4184
  r.queryHandler("products", z.object({}), async () => ({ rows: [], nextCursor: null }), {
3700
4185
  access: { openToAll: true },
3701
4186
  });
@@ -3726,7 +4211,12 @@ describe("boot-validator", () => {
3726
4211
 
3727
4212
  test("projectionDetail action entity-target without explicit entityId → Throw", () => {
3728
4213
  const feature = defineFeature("shop", (r) => {
3729
- r.entity("product", createEntity({ fields: { name: createTextField() } }));
4214
+ r.entity(
4215
+ "product",
4216
+ createEntity({
4217
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
4218
+ }),
4219
+ );
3730
4220
  r.screen({
3731
4221
  id: "order-detail",
3732
4222
  type: "projectionDetail",
@@ -3776,7 +4266,12 @@ describe("boot-validator", () => {
3776
4266
 
3777
4267
  test('entityEdit.actions drawer-target unknown → throw with actionLabel "action"', () => {
3778
4268
  const feature = defineFeature("shop", (r) => {
3779
- r.entity("product", createEntity({ fields: { name: createTextField() } }));
4269
+ r.entity(
4270
+ "product",
4271
+ createEntity({
4272
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
4273
+ }),
4274
+ );
3780
4275
  r.screen({
3781
4276
  id: "product-edit",
3782
4277
  type: "entityEdit",
@@ -3794,7 +4289,12 @@ describe("boot-validator", () => {
3794
4289
 
3795
4290
  test("entityEdit.actions drawer-target → registered actionForm → no throw", () => {
3796
4291
  const feature = defineFeature("shop", (r) => {
3797
- r.entity("product", createEntity({ fields: { name: createTextField() } }));
4292
+ r.entity(
4293
+ "product",
4294
+ createEntity({
4295
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
4296
+ }),
4297
+ );
3798
4298
  r.writeHandler(
3799
4299
  "add-note",
3800
4300
  z.object({ note: z.string() }),
@@ -3959,7 +4459,12 @@ describe("boot-validator", () => {
3959
4459
  describe("entityList rowAction rowClick", () => {
3960
4460
  function makeFeature(rowClickCount: number) {
3961
4461
  return defineFeature("shop", (r) => {
3962
- r.entity("product", createEntity({ fields: { name: createTextField() } }));
4462
+ r.entity(
4463
+ "product",
4464
+ createEntity({
4465
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
4466
+ }),
4467
+ );
3963
4468
  r.screen({ id: "product-editor", type: "custom", renderer: { react: "stub" } });
3964
4469
  r.screen({
3965
4470
  id: "product-list",
@@ -3992,7 +4497,12 @@ describe("boot-validator", () => {
3992
4497
  describe("entityList rowAction kind=writeHandler handler-QN", () => {
3993
4498
  function makeFeature(handlerQn: string, register: boolean) {
3994
4499
  return defineFeature("shop", (r) => {
3995
- r.entity("product", createEntity({ fields: { name: createTextField() } }));
4500
+ r.entity(
4501
+ "product",
4502
+ createEntity({
4503
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
4504
+ }),
4505
+ );
3996
4506
  r.screen({
3997
4507
  id: "product-list",
3998
4508
  type: "entityList",
@@ -4033,7 +4543,12 @@ describe("boot-validator", () => {
4033
4543
  describe("entityList rowAction payload pick (Tier 2.7e-3)", () => {
4034
4544
  function makeFeature(pick: readonly string[]) {
4035
4545
  return defineFeature("shop", (r) => {
4036
- r.entity("product", createEntity({ fields: { name: createTextField() } }));
4546
+ r.entity(
4547
+ "product",
4548
+ createEntity({
4549
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
4550
+ }),
4551
+ );
4037
4552
  r.screen({
4038
4553
  id: "product-list",
4039
4554
  type: "entityList",
@@ -4090,7 +4605,7 @@ describe("boot-validator", () => {
4090
4605
  r.entity(
4091
4606
  "product",
4092
4607
  createEntity({
4093
- fields: { name: createTextField() },
4608
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
4094
4609
  softDelete: opts.softDelete ?? false,
4095
4610
  }),
4096
4611
  );
@@ -4166,7 +4681,12 @@ describe("boot-validator", () => {
4166
4681
  describe("entityList toolbarAction navigate (Tier 2.7e-2)", () => {
4167
4682
  function makeFeature(targetScreen: string, withTarget: boolean) {
4168
4683
  return defineFeature("shop", (r) => {
4169
- r.entity("product", createEntity({ fields: { name: createTextField() } }));
4684
+ r.entity(
4685
+ "product",
4686
+ createEntity({
4687
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
4688
+ }),
4689
+ );
4170
4690
  r.screen({
4171
4691
  id: "product-list",
4172
4692
  type: "entityList",
@@ -4194,7 +4714,12 @@ describe("boot-validator", () => {
4194
4714
 
4195
4715
  test("navigate-target → screen in ANOTHER feature → kein Throw (cross-feature)", () => {
4196
4716
  const list = defineFeature("shop", (r) => {
4197
- r.entity("product", createEntity({ fields: { name: createTextField() } }));
4717
+ r.entity(
4718
+ "product",
4719
+ createEntity({
4720
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
4721
+ }),
4722
+ );
4198
4723
  r.screen({
4199
4724
  id: "product-list",
4200
4725
  type: "entityList",
@@ -4215,7 +4740,12 @@ describe("boot-validator", () => {
4215
4740
  describe("entityList toolbarAction writeHandler handler-QN (Tier 2.7e-2)", () => {
4216
4741
  function makeFeature(handlerQn: string, register: boolean) {
4217
4742
  return defineFeature("shop", (r) => {
4218
- r.entity("product", createEntity({ fields: { name: createTextField() } }));
4743
+ r.entity(
4744
+ "product",
4745
+ createEntity({
4746
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
4747
+ }),
4748
+ );
4219
4749
  r.screen({
4220
4750
  id: "product-list",
4221
4751
  type: "entityList",
@@ -4257,7 +4787,12 @@ describe("boot-validator", () => {
4257
4787
  }) {
4258
4788
  const targetId = opts.targetId ?? "restock-form";
4259
4789
  return defineFeature("shop", (r) => {
4260
- r.entity("product", createEntity({ fields: { name: createTextField() } }));
4790
+ r.entity(
4791
+ "product",
4792
+ createEntity({
4793
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
4794
+ }),
4795
+ );
4261
4796
  r.screen({
4262
4797
  id: "product-list",
4263
4798
  type: "entityList",
@@ -4314,7 +4849,12 @@ describe("boot-validator", () => {
4314
4849
 
4315
4850
  test("drawer-target → actionForm in ANOTHER feature → throw (drawer resolves same-feature only)", () => {
4316
4851
  const list = defineFeature("shop", (r) => {
4317
- r.entity("product", createEntity({ fields: { name: createTextField() } }));
4852
+ r.entity(
4853
+ "product",
4854
+ createEntity({
4855
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
4856
+ }),
4857
+ );
4318
4858
  r.screen({
4319
4859
  id: "product-list",
4320
4860
  type: "entityList",
@@ -4354,7 +4894,12 @@ describe("boot-validator", () => {
4354
4894
  }) {
4355
4895
  const targetId = opts.targetId ?? "restock-form";
4356
4896
  return defineFeature("shop", (r) => {
4357
- r.entity("product", createEntity({ fields: { name: createTextField() } }));
4897
+ r.entity(
4898
+ "product",
4899
+ createEntity({
4900
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
4901
+ }),
4902
+ );
4358
4903
  r.screen({
4359
4904
  id: "product-list",
4360
4905
  type: "entityList",
@@ -4576,7 +5121,12 @@ describe("boot-validator", () => {
4576
5121
  test('screen.id mit "." → defineFeature throws (kebab-case)', () => {
4577
5122
  expect(() =>
4578
5123
  defineFeature("shop", (r) => {
4579
- r.entity("product", createEntity({ fields: { name: createTextField() } }));
5124
+ r.entity(
5125
+ "product",
5126
+ createEntity({
5127
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
5128
+ }),
5129
+ );
4580
5130
  r.screen({
4581
5131
  id: "product.list",
4582
5132
  type: "entityList",
@@ -4589,7 +5139,12 @@ describe("boot-validator", () => {
4589
5139
 
4590
5140
  test("screen.id im kebab-case → kein Throw", () => {
4591
5141
  const feature = defineFeature("shop", (r) => {
4592
- r.entity("product", createEntity({ fields: { name: createTextField() } }));
5142
+ r.entity(
5143
+ "product",
5144
+ createEntity({
5145
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
5146
+ }),
5147
+ );
4593
5148
  r.screen({ id: "product-list", type: "entityList", entity: "product", columns: ["name"] });
4594
5149
  });
4595
5150
  expect(() => validateBoot([feature])).not.toThrow();
@@ -4621,7 +5176,12 @@ describe("boot-validator — config key backing × scope", () => {
4621
5176
 
4622
5177
  test("navigate with params targeting a dashboard screen without a filter → Throw (params would be a no-op)", () => {
4623
5178
  const feature = defineFeature("shop", (r) => {
4624
- r.entity("product", createEntity({ fields: { name: createTextField() } }));
5179
+ r.entity(
5180
+ "product",
5181
+ createEntity({
5182
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
5183
+ }),
5184
+ );
4625
5185
  r.screen({
4626
5186
  id: "product-list",
4627
5187
  type: "entityList",
@@ -4661,7 +5221,12 @@ describe("boot-validator — config key backing × scope", () => {
4661
5221
 
4662
5222
  test("navigate with params targeting a dashboard screen with a matching filter → no throw (useFilterParams reads it, fw#1708 follow-up)", () => {
4663
5223
  const feature = defineFeature("shop", (r) => {
4664
- r.entity("product", createEntity({ fields: { name: createTextField() } }));
5224
+ r.entity(
5225
+ "product",
5226
+ createEntity({
5227
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
5228
+ }),
5229
+ );
4665
5230
  r.screen({
4666
5231
  id: "product-list",
4667
5232
  type: "entityList",
@@ -4705,7 +5270,12 @@ describe("boot-validator — config key backing × scope", () => {
4705
5270
 
4706
5271
  test("navigate with params targeting a dashboard screen whose filter id doesn't match any extracted key → Throw", () => {
4707
5272
  const feature = defineFeature("shop", (r) => {
4708
- r.entity("product", createEntity({ fields: { name: createTextField() } }));
5273
+ r.entity(
5274
+ "product",
5275
+ createEntity({
5276
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
5277
+ }),
5278
+ );
4709
5279
  r.screen({
4710
5280
  id: "product-list",
4711
5281
  type: "entityList",
@@ -4751,8 +5321,18 @@ describe("boot-validator — config key backing × scope", () => {
4751
5321
 
4752
5322
  test("navigate with params targeting a cross-entity entityList screen → no throw (list screens read URL search params for filter-prefill, fw#1708)", () => {
4753
5323
  const feature = defineFeature("housing", (r) => {
4754
- r.entity("unit", createEntity({ fields: { id: createTextField() } }));
4755
- r.entity("contract", createEntity({ fields: { unitId: createTextField() } }));
5324
+ r.entity(
5325
+ "unit",
5326
+ createEntity({
5327
+ fields: { id: createTextField({ personal: false, reason: "test_fixture" }) },
5328
+ }),
5329
+ );
5330
+ r.entity(
5331
+ "contract",
5332
+ createEntity({
5333
+ fields: { unitId: createTextField({ personal: false, reason: "test_fixture" }) },
5334
+ }),
5335
+ );
4756
5336
  r.screen({
4757
5337
  id: "unit-list",
4758
5338
  type: "entityList",
@@ -4780,7 +5360,12 @@ describe("boot-validator — config key backing × scope", () => {
4780
5360
 
4781
5361
  test("navigate with params targeting a custom screen → no throw (author owns the component, may read searchParams itself)", () => {
4782
5362
  const feature = defineFeature("shop", (r) => {
4783
- r.entity("product", createEntity({ fields: { name: createTextField() } }));
5363
+ r.entity(
5364
+ "product",
5365
+ createEntity({
5366
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
5367
+ }),
5368
+ );
4784
5369
  r.screen({
4785
5370
  id: "product-list",
4786
5371
  type: "entityList",
@@ -4810,7 +5395,12 @@ describe("boot-validator — config key backing × scope", () => {
4810
5395
  // runNavigate() auto-fills row["id"], landing in EntityEditUpdateBody,
4811
5396
  // which never reads searchParams. Params would silently no-op.
4812
5397
  const feature = defineFeature("shop", (r) => {
4813
- r.entity("product", createEntity({ fields: { name: createTextField() } }));
5398
+ r.entity(
5399
+ "product",
5400
+ createEntity({
5401
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
5402
+ }),
5403
+ );
4814
5404
  r.screen({
4815
5405
  id: "product-list",
4816
5406
  type: "entityList",
@@ -4840,8 +5430,18 @@ describe("boot-validator — config key backing × scope", () => {
4840
5430
 
4841
5431
  test("navigate with params + explicit entityId targeting an entityEdit screen → Throw (forced update mode)", () => {
4842
5432
  const feature = defineFeature("housing", (r) => {
4843
- r.entity("unit", createEntity({ fields: { name: createTextField() } }));
4844
- r.entity("contract", createEntity({ fields: { unitId: createTextField() } }));
5433
+ r.entity(
5434
+ "unit",
5435
+ createEntity({
5436
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
5437
+ }),
5438
+ );
5439
+ r.entity(
5440
+ "contract",
5441
+ createEntity({
5442
+ fields: { unitId: createTextField({ personal: false, reason: "test_fixture" }) },
5443
+ }),
5444
+ );
4845
5445
  r.screen({
4846
5446
  id: "unit-list",
4847
5447
  type: "entityList",
@@ -4874,7 +5474,12 @@ describe("boot-validator — config key backing × scope", () => {
4874
5474
  // extended to projectionList rowActions (#1680 covered entityList only).
4875
5475
  test("projectionList rowAction: navigate with params to an entityEdit target + explicit entityId → Throw", () => {
4876
5476
  const feature = defineFeature("shop", (r) => {
4877
- r.entity("product", createEntity({ fields: { name: createTextField() } }));
5477
+ r.entity(
5478
+ "product",
5479
+ createEntity({
5480
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
5481
+ }),
5482
+ );
4878
5483
  r.screen({
4879
5484
  id: "product-projection",
4880
5485
  type: "projectionList",
@@ -4905,7 +5510,12 @@ describe("boot-validator — config key backing × scope", () => {
4905
5510
 
4906
5511
  test("projectionList rowAction: navigate with params to an entityEdit-create target (no entityId) → no throw", () => {
4907
5512
  const feature = defineFeature("shop", (r) => {
4908
- r.entity("product", createEntity({ fields: { name: createTextField() } }));
5513
+ r.entity(
5514
+ "product",
5515
+ createEntity({
5516
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
5517
+ }),
5518
+ );
4909
5519
  r.queryHandler("products", z.object({}), async () => ({ rows: [], nextCursor: null }), {
4910
5520
  access: { openToAll: true },
4911
5521
  });
@@ -4940,8 +5550,18 @@ describe("boot-validator — config key backing × scope", () => {
4940
5550
  // renderer's same-entity row["id"] fallback does NOT fire, so this stays
4941
5551
  // in create mode and EntityEditCreateBody reads the params.
4942
5552
  const feature = defineFeature("housing", (r) => {
4943
- r.entity("unit", createEntity({ fields: { name: createTextField() } }));
4944
- r.entity("contract", createEntity({ fields: { unitId: createTextField() } }));
5553
+ r.entity(
5554
+ "unit",
5555
+ createEntity({
5556
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
5557
+ }),
5558
+ );
5559
+ r.entity(
5560
+ "contract",
5561
+ createEntity({
5562
+ fields: { unitId: createTextField({ personal: false, reason: "test_fixture" }) },
5563
+ }),
5564
+ );
4945
5565
  r.screen({
4946
5566
  id: "unit-list",
4947
5567
  type: "entityList",
@@ -4969,7 +5589,12 @@ describe("boot-validator — config key backing × scope", () => {
4969
5589
 
4970
5590
  test("navigate with params targeting an actionForm screen → no throw", () => {
4971
5591
  const feature = defineFeature("shop", (r) => {
4972
- r.entity("product", createEntity({ fields: { name: createTextField() } }));
5592
+ r.entity(
5593
+ "product",
5594
+ createEntity({
5595
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
5596
+ }),
5597
+ );
4973
5598
  r.writeHandler(
4974
5599
  "create",
4975
5600
  z.object({ name: z.string() }),
@@ -4995,7 +5620,7 @@ describe("boot-validator — config key backing × scope", () => {
4995
5620
  id: "product-form",
4996
5621
  type: "actionForm",
4997
5622
  handler: "shop:write:create",
4998
- fields: { name: createTextField() },
5623
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
4999
5624
  layout: { sections: [{ columns: 1, fields: ["name"] }] },
5000
5625
  });
5001
5626
  });