@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
@@ -1,14 +1,39 @@
1
1
  import type {
2
+ ActionFormScreenDefinition,
3
+ ConfigEditScreenDefinition,
2
4
  EditExtensionSection,
3
5
  EditFieldSpec,
4
6
  EditFieldsSection,
5
7
  EditSectionSpec,
6
8
  EditWriteFormSection,
9
+ EntityEditScreenDefinition,
7
10
  FieldCondition,
8
11
  FormatSpec,
9
12
  ListColumnSpec,
13
+ ProjectionDetailScreenDefinition,
14
+ ScreenDefinition,
15
+ SecretMintScreenDefinition,
10
16
  } from "./types/screen";
11
17
 
18
+ export type EditLayoutScreen =
19
+ | ProjectionDetailScreenDefinition
20
+ | EntityEditScreenDefinition
21
+ | ActionFormScreenDefinition
22
+ | ConfigEditScreenDefinition
23
+ | SecretMintScreenDefinition;
24
+
25
+ // Shared by boot-validators that walk the EditLayout shape (entityEdit's
26
+ // layout, reused verbatim by projectionDetail/actionForm/configEdit/secretMint).
27
+ export function isEditLayoutScreen(screen: ScreenDefinition): screen is EditLayoutScreen {
28
+ return (
29
+ screen.type === "projectionDetail" ||
30
+ screen.type === "entityEdit" ||
31
+ screen.type === "actionForm" ||
32
+ screen.type === "configEdit" ||
33
+ screen.type === "secretMint"
34
+ );
35
+ }
36
+
12
37
  export function isExtensionEditSection(section: EditSectionSpec): section is EditExtensionSection {
13
38
  return section.kind === "extension";
14
39
  }
@@ -122,6 +122,7 @@ export type {
122
122
  MoneyFieldDef,
123
123
  MultiSelectFieldDef,
124
124
  NumberFieldDef,
125
+ ParentRefDef,
125
126
  PersonalAnnotations,
126
127
  PersonalAnnotationsLongText,
127
128
  PersonalAnnotationsNoFind,
@@ -308,6 +309,10 @@ export type {
308
309
  ScreenFilterOp,
309
310
  ScreenNavSugar,
310
311
  ScreenSlots,
312
+ SecretMintConfirmStep,
313
+ SecretMintScreenDefinition,
314
+ SecretReveal,
315
+ SecretRevealField,
311
316
  SecretsEditScreenDefinition,
312
317
  SecretsEditSection,
313
318
  ToolbarAction,
@@ -43,7 +43,7 @@ import { runPendingSeedMigrations } from "../runner";
43
43
  // is exercised against the production write-pipeline, not a stubbed return.
44
44
  const probeEntity = createEntity({
45
45
  table: "read_esops_probes",
46
- fields: { label: createTextField({ required: true }) },
46
+ fields: { label: createTextField({ personal: false, reason: "test_fixture", required: true }) },
47
47
  });
48
48
  const probeTable = buildEntityTable("esops-probe", probeEntity);
49
49
  const probeExecutor = createEventStoreExecutor(probeTable, probeEntity, {
@@ -33,7 +33,7 @@ import { upcastStoredEvent } from "../upcaster";
33
33
  const orderEntity = createEntity({
34
34
  table: "read_upcast_orders",
35
35
  fields: {
36
- customer: createTextField({ required: true }),
36
+ customer: createTextField({ personal: false, reason: "test_fixture", required: true }),
37
37
  },
38
38
  });
39
39
  const orderTable = buildEntityTable("upcast-order", orderEntity);
@@ -47,7 +47,7 @@ import { createLocalProvider } from "../local-provider";
47
47
  const documentEntity = createEntity({
48
48
  table: "pipeline_documents",
49
49
  fields: {
50
- title: createTextField({ required: true }),
50
+ title: createTextField({ personal: false, reason: "test_fixture", required: true }),
51
51
  attachment: createFileField(),
52
52
  cover: createImageField(),
53
53
  photos: createImagesField(),
@@ -57,7 +57,7 @@ const JWT_SECRET = "files-test-secret-at-least-32-characters!!";
57
57
  const testTenantEntity = createEntity({
58
58
  table: "test_tenants",
59
59
  fields: {
60
- name: createTextField({ required: true }),
60
+ name: createTextField({ personal: false, reason: "test_fixture", required: true }),
61
61
  logo: createImageField({ maxSize: "2mb", accept: ["png", "jpg"] }),
62
62
  },
63
63
  });
@@ -14,20 +14,20 @@ import { createBigIntField, createEntity, createTextField } from "../engine";
14
14
  // ohne `.default(now()).notNull()` macht inserted_at still nullable.
15
15
  //
16
16
  // PII-Annotations:
17
- // - fileName → personal: "self" (Originalname enthält oft Personen-Bezug:
18
- // "Marc-Lebenslauf.pdf", "Krankheitsattest-Mai.pdf"). Andere Felder
19
- // (storageKey, mimeType, size, entityType, entityId, fieldName) treffen
20
- // die PII-Heuristik nicht.
17
+ // - fileName → personal: "self" (original filename often carries a personal
18
+ // reference: "Marc-Lebenslauf.pdf", "Krankheitsattest-Mai.pdf"). The other
19
+ // fields (storageKey, mimeType, entityType, entityId, fieldName) are
20
+ // technical references or system metadata, not subject content.
21
21
  export const fileRefEntity = createEntity({
22
22
  table: "file_refs",
23
23
  softDelete: true,
24
24
  fields: {
25
- storageKey: createTextField({ required: true }),
25
+ storageKey: createTextField({ required: true, personal: false, reason: "technical_reference" }),
26
26
  fileName: createTextField({ required: true, personal: "self", find: "none" }),
27
- mimeType: createTextField({ required: true }),
27
+ mimeType: createTextField({ required: true, personal: false, reason: "system_metadata" }),
28
28
  size: createBigIntField({ required: true }),
29
- entityType: createTextField(),
30
- entityId: createTextField(),
31
- fieldName: createTextField(),
29
+ entityType: createTextField({ personal: false, reason: "technical_reference" }),
30
+ entityId: createTextField({ personal: false, reason: "technical_reference" }),
31
+ fieldName: createTextField({ personal: false, reason: "technical_reference" }),
32
32
  },
33
33
  });
@@ -4,7 +4,13 @@ import { createI18n } from "../index";
4
4
 
5
5
  describe("createI18n", () => {
6
6
  const adminFeature = defineFeature("adminUsers", (r) => {
7
- r.entity("user", createEntity({ table: "Users", fields: { email: createTextField() } }));
7
+ r.entity(
8
+ "user",
9
+ createEntity({
10
+ table: "Users",
11
+ fields: { email: createTextField({ personal: false, reason: "test_fixture" }) },
12
+ }),
13
+ );
8
14
  r.translations({
9
15
  keys: {
10
16
  "nav.title": { de: "Benutzer", en: "Users" },
@@ -18,6 +18,7 @@ import type {
18
18
  ProjectionListScreenDefinition,
19
19
  RowAction,
20
20
  ScreenDefinition,
21
+ SecretMintScreenDefinition,
21
22
  SecretsEditScreenDefinition,
22
23
  ToolbarAction,
23
24
  WorkspaceDefinition,
@@ -211,6 +212,53 @@ export function requiredKeysFromScreen(
211
212
  }
212
213
  break;
213
214
  }
215
+ case "secretMint": {
216
+ // Mirrors the actionForm case above (submitLabel, fields/section titles
217
+ // via ACTION_FORM_ENTITY) plus the reveal-phase's own i18n surface.
218
+ const mint = screen as SecretMintScreenDefinition;
219
+ pushKey(out, mint.submitLabel);
220
+ for (const fieldName of Object.keys(mint.fields)) {
221
+ out.add(fieldLabelKey(featureName, ACTION_FORM_ENTITY, fieldName));
222
+ }
223
+ for (const section of mint.layout.sections) {
224
+ if (isExtensionEditSection(section)) {
225
+ pushKey(out, section.title);
226
+ continue;
227
+ }
228
+ if (section.kind === "relatedList") continue; // rejected at boot, unreachable here
229
+ pushKey(out, section.title);
230
+ for (const f of section.fields) {
231
+ const fieldName = editFieldName(f);
232
+ out.add(fieldLabelKey(featureName, ACTION_FORM_ENTITY, fieldName));
233
+ }
234
+ }
235
+ pushKey(out, mint.reveal.title);
236
+ pushKey(out, mint.reveal.warning);
237
+ pushKey(out, mint.reveal.confirmLabel);
238
+ for (const revealField of mint.reveal.fields) {
239
+ pushKey(out, revealField.label);
240
+ }
241
+ if (mint.confirm !== undefined) {
242
+ pushKey(out, mint.confirm.submitLabel);
243
+ pushKey(out, mint.confirm.doneMessage);
244
+ for (const fieldName of Object.keys(mint.confirm.fields)) {
245
+ out.add(fieldLabelKey(featureName, ACTION_FORM_ENTITY, fieldName));
246
+ }
247
+ for (const section of mint.confirm.layout.sections) {
248
+ if (isExtensionEditSection(section)) {
249
+ pushKey(out, section.title);
250
+ continue;
251
+ }
252
+ if (section.kind === "relatedList") continue; // rejected at boot, unreachable here
253
+ pushKey(out, section.title);
254
+ for (const f of section.fields) {
255
+ const fieldName = editFieldName(f);
256
+ out.add(fieldLabelKey(featureName, ACTION_FORM_ENTITY, fieldName));
257
+ }
258
+ }
259
+ }
260
+ break;
261
+ }
214
262
  case "configEdit": {
215
263
  const config = screen as ConfigEditScreenDefinition;
216
264
  pushKey(out, config.submitLabel);
@@ -198,7 +198,9 @@ describe("kumiko-drift end-to-end (generate → apply → gate)", () => {
198
198
  test("generate from entity metas → apply → gate ok (the local-verify proof)", async () => {
199
199
  const entity = createEntity({
200
200
  table: "kdrift_gen",
201
- fields: { name: createTextField({ required: true }) },
201
+ fields: {
202
+ name: createTextField({ personal: false, reason: "test_fixture", required: true }),
203
+ },
202
204
  });
203
205
  const meta = deriveEntityTableMeta("kdriftGen", entity);
204
206
  const result = generateMigration({
@@ -43,8 +43,8 @@ import {
43
43
  const itemEntity = createEntity({
44
44
  table: "read_pending_items",
45
45
  fields: {
46
- groupId: createTextField({ required: true }),
47
- name: createTextField({ required: true }),
46
+ groupId: createTextField({ personal: false, reason: "test_fixture", required: true }),
47
+ name: createTextField({ personal: false, reason: "test_fixture", required: true }),
48
48
  },
49
49
  });
50
50
  const itemTable = buildEntityTable("pending-item", itemEntity);
@@ -25,7 +25,7 @@ import {
25
25
 
26
26
  const itemEntity = createEntity({
27
27
  table: "read_arch_items",
28
- fields: { label: createTextField({ required: true }) },
28
+ fields: { label: createTextField({ personal: false, reason: "test_fixture", required: true }) },
29
29
  });
30
30
  const itemTable = buildEntityTable("arch-item", itemEntity);
31
31
 
@@ -43,39 +43,54 @@ const admin = TestUsers.admin;
43
43
 
44
44
  const departmentEntity = createEntity({
45
45
  table: "cascade_departments",
46
- fields: { name: createTextField() },
46
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
47
47
  });
48
48
  const userEntity = createEntity({
49
49
  table: "cascade_users",
50
- fields: { name: createTextField(), departmentId: createTextField() },
50
+ fields: {
51
+ name: createTextField({ personal: false, reason: "test_fixture" }),
52
+ departmentId: createTextField({ personal: false, reason: "test_fixture" }),
53
+ },
51
54
  });
52
55
  const sessionEntity = createEntity({
53
56
  table: "cascade_sessions",
54
- fields: { userId: createTextField(), token: createTextField() },
57
+ fields: {
58
+ userId: createTextField({ personal: false, reason: "test_fixture" }),
59
+ token: createTextField({ personal: false, reason: "test_fixture" }),
60
+ },
55
61
  });
56
62
  const groupEntity = createEntity({
57
63
  table: "cascade_groups",
58
- fields: { name: createTextField() },
64
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
59
65
  });
60
66
  // Two separate junction tables so one Registry can host both onDelete
61
67
  // strategies (restrict + cascade) on the same user→group pair.
62
68
  const userGroupRestrictEntity = createEntity({
63
69
  table: "cascade_user_group_restrict",
64
- fields: { userId: createTextField(), groupId: createTextField() },
70
+ fields: {
71
+ userId: createTextField({ personal: false, reason: "test_fixture" }),
72
+ groupId: createTextField({ personal: false, reason: "test_fixture" }),
73
+ },
65
74
  });
66
75
  const userGroupCascadeEntity = createEntity({
67
76
  table: "cascade_user_group_cascade",
68
- fields: { userId: createTextField(), groupId: createTextField() },
77
+ fields: {
78
+ userId: createTextField({ personal: false, reason: "test_fixture" }),
79
+ groupId: createTextField({ personal: false, reason: "test_fixture" }),
80
+ },
69
81
  });
70
82
  // setNull pair: team→member with onDelete "setNull" — members survive
71
83
  // the team deletion with teamId nulled out.
72
84
  const teamEntity = createEntity({
73
85
  table: "cascade_teams",
74
- fields: { name: createTextField() },
86
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
75
87
  });
76
88
  const memberEntity = createEntity({
77
89
  table: "cascade_members",
78
- fields: { name: createTextField(), teamId: createTextField() },
90
+ fields: {
91
+ name: createTextField({ personal: false, reason: "test_fixture" }),
92
+ teamId: createTextField({ personal: false, reason: "test_fixture" }),
93
+ },
79
94
  });
80
95
 
81
96
  beforeAll(async () => {
@@ -5,10 +5,25 @@ describe("getIncomingRelations", () => {
5
5
  const feature = defineFeature("core", (r) => {
6
6
  r.entity(
7
7
  "department",
8
- createEntity({ table: "Departments", fields: { name: createTextField() } }),
8
+ createEntity({
9
+ table: "Departments",
10
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
11
+ }),
12
+ );
13
+ r.entity(
14
+ "user",
15
+ createEntity({
16
+ table: "Users",
17
+ fields: { departmentId: createTextField({ personal: false, reason: "test_fixture" }) },
18
+ }),
19
+ );
20
+ r.entity(
21
+ "session",
22
+ createEntity({
23
+ table: "Sessions",
24
+ fields: { userId: createTextField({ personal: false, reason: "test_fixture" }) },
25
+ }),
9
26
  );
10
- r.entity("user", createEntity({ table: "Users", fields: { departmentId: createTextField() } }));
11
- r.entity("session", createEntity({ table: "Sessions", fields: { userId: createTextField() } }));
12
27
 
13
28
  r.relation("department", "users", {
14
29
  type: "hasMany",
@@ -36,7 +36,7 @@ import {
36
36
  const orderEntity = createEntity({
37
37
  table: "read_causation_orders",
38
38
  fields: {
39
- item: createTextField({ required: true }),
39
+ item: createTextField({ personal: false, reason: "test_fixture", required: true }),
40
40
  },
41
41
  });
42
42
 
@@ -26,7 +26,7 @@ import { setupTestStack, type TestStack, TestUsers, unsafeCreateEntityTable } fr
26
26
  const bagEntity = createEntity({
27
27
  table: "ctx_bags",
28
28
  fields: {
29
- label: createTextField({ required: true }),
29
+ label: createTextField({ personal: false, reason: "test_fixture", required: true }),
30
30
  counter: createNumberField({ default: 0 }),
31
31
  },
32
32
  });
@@ -37,8 +37,10 @@ const bagTable = buildEntityTable("bag", bagEntity);
37
37
  const secretEntity = createEntity({
38
38
  table: "ctx_secrets",
39
39
  fields: {
40
- owner: createTextField({ required: true }),
40
+ owner: createTextField({ personal: false, reason: "test_fixture", required: true }),
41
41
  token: createTextField({
42
+ personal: false,
43
+ reason: "test_fixture",
42
44
  required: true,
43
45
  access: { read: access.privileged, write: access.privileged },
44
46
  }),
@@ -53,7 +55,7 @@ const secretTable = buildEntityTable("secret", secretEntity);
53
55
  const echoEntity = createEntity({
54
56
  table: "ctx_echoes",
55
57
  fields: {
56
- bagId: createTextField({ required: true }),
58
+ bagId: createTextField({ personal: false, reason: "test_fixture", required: true }),
57
59
  },
58
60
  });
59
61
  const echoTable = buildEntityTable("echo", echoEntity);
@@ -11,7 +11,13 @@ import { createDispatcher } from "../dispatcher";
11
11
  const streamCleanupState = { closed: false };
12
12
 
13
13
  const echoFeature = defineFeature("echo", (r) => {
14
- r.entity("item", createEntity({ table: "Items", fields: { name: createTextField() } }));
14
+ r.entity(
15
+ "item",
16
+ createEntity({
17
+ table: "Items",
18
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
19
+ }),
20
+ );
15
21
 
16
22
  r.writeHandler(
17
23
  "item:create",
@@ -119,7 +125,13 @@ describe("dispatcher.write", () => {
119
125
  // it must SKIP the rate-limit — not throw "no RateLimitResolver is
120
126
  // configured". (The HTTP path still has the resolver for real anon writes.)
121
127
  const rlFeature = defineFeature("rl", (r) => {
122
- r.entity("item", createEntity({ table: "Items", fields: { name: createTextField() } }));
128
+ r.entity(
129
+ "item",
130
+ createEntity({
131
+ table: "Items",
132
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
133
+ }),
134
+ );
123
135
  r.writeHandler(
124
136
  "item:create",
125
137
  z.object({ name: z.string() }),
@@ -144,7 +156,13 @@ describe("dispatcher.write", () => {
144
156
  // rate-limiting feature. User buckets cannot skip (unlike ip+no-IP), so we
145
157
  // fail loud at first write instead of silently running uncapped.
146
158
  const rlFeature = defineFeature("rl-user", (r) => {
147
- r.entity("item", createEntity({ table: "Items", fields: { name: createTextField() } }));
159
+ r.entity(
160
+ "item",
161
+ createEntity({
162
+ table: "Items",
163
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
164
+ }),
165
+ );
148
166
  r.writeHandler(
149
167
  "item:create",
150
168
  z.object({ name: z.string() }),
@@ -174,7 +192,13 @@ describe("dispatcher.write", () => {
174
192
  // gegen denselben SessionUser — sonst ist's nicht der gleiche.
175
193
  const captured: { fromEvent?: unknown; fromCtx?: unknown } = {};
176
194
  const aliasFeature = defineFeature("alias", (r) => {
177
- r.entity("item", createEntity({ table: "Items", fields: { name: createTextField() } }));
195
+ r.entity(
196
+ "item",
197
+ createEntity({
198
+ table: "Items",
199
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
200
+ }),
201
+ );
178
202
  r.writeHandler(
179
203
  "item:create",
180
204
  z.object({ name: z.string() }),
@@ -676,7 +700,13 @@ describe("dispatcher feature-gate", () => {
676
700
  function toggled() {
677
701
  return defineFeature("toggled", (r) => {
678
702
  r.toggleable({ default: true });
679
- r.entity("widget", createEntity({ table: "Widgets", fields: { name: createTextField() } }));
703
+ r.entity(
704
+ "widget",
705
+ createEntity({
706
+ table: "Widgets",
707
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
708
+ }),
709
+ );
680
710
  r.queryHandler("widget:list", z.object({}).passthrough(), async () => ({ items: [] }), {
681
711
  access: { openToAll: true },
682
712
  });
@@ -941,7 +971,13 @@ describe("write-handler shape guard", () => {
941
971
 
942
972
  function brokenFeature() {
943
973
  return defineFeature("broken", (r) => {
944
- r.entity("item", createEntity({ table: "Items", fields: { name: createTextField() } }));
974
+ r.entity(
975
+ "item",
976
+ createEntity({
977
+ table: "Items",
978
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
979
+ }),
980
+ );
945
981
  r.writeHandler(
946
982
  "item:create",
947
983
  z.object({ name: z.string() }),
@@ -35,8 +35,8 @@ import {
35
35
  const shipmentEntity = createEntity({
36
36
  table: "read_domain_shipments",
37
37
  fields: {
38
- cargo: createTextField({ required: true }),
39
- status: createTextField({ required: true }),
38
+ cargo: createTextField({ personal: false, reason: "test_fixture", required: true }),
39
+ status: createTextField({ personal: false, reason: "test_fixture", required: true }),
40
40
  },
41
41
  });
42
42
 
@@ -22,7 +22,7 @@ import { setupTestStack, type TestStack, TestUsers, unsafeCreateEntityTable } fr
22
22
  const cartEntity = createEntity({
23
23
  table: "read_f4w_carts",
24
24
  fields: {
25
- customer: createTextField({ required: true }),
25
+ customer: createTextField({ personal: false, reason: "test_fixture", required: true }),
26
26
  },
27
27
  });
28
28
 
@@ -15,7 +15,13 @@ import { buildEventId, createLifecycleHooks, type SystemHooks } from "../lifecyc
15
15
 
16
16
  function makeRegistry(hooks?: { preSave?: PreSaveHookFn[]; postSave?: PostSaveHookFn[] }) {
17
17
  const feature = defineFeature("test", (r) => {
18
- r.entity("user", createEntity({ table: "Users", fields: { email: createTextField() } }));
18
+ r.entity(
19
+ "user",
20
+ createEntity({
21
+ table: "Users",
22
+ fields: { email: createTextField({ personal: false, reason: "test_fixture" }) },
23
+ }),
24
+ );
19
25
  // Dummy handler so hook targets resolve (boot validation requires it)
20
26
  r.writeHandler("user", z.object({}), async () => ({ isSuccess: true as const, data: null }), {
21
27
  access: { openToAll: true },
@@ -422,7 +428,13 @@ describe("Sprint 8a: per-tenant entity-hook filter", () => {
422
428
  const calls: Array<{ tenant: string }> = [];
423
429
 
424
430
  const featureA = defineFeature("feat-a", (r) => {
425
- r.entity("widget", createEntity({ table: "Widgets", fields: { name: createTextField() } }));
431
+ r.entity(
432
+ "widget",
433
+ createEntity({
434
+ table: "Widgets",
435
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
436
+ }),
437
+ );
426
438
  r.writeHandler(
427
439
  "widget:create",
428
440
  z.object({ name: z.string() }),
@@ -550,7 +562,13 @@ describe("runPreDelete", () => {
550
562
  test("runs feature + entity + system hooks, all inTransaction (throw on error)", async () => {
551
563
  const calls: string[] = [];
552
564
  const feature = defineFeature("test", (r) => {
553
- r.entity("user", createEntity({ table: "Users", fields: { email: createTextField() } }));
565
+ r.entity(
566
+ "user",
567
+ createEntity({
568
+ table: "Users",
569
+ fields: { email: createTextField({ personal: false, reason: "test_fixture" }) },
570
+ }),
571
+ );
554
572
  r.writeHandler("user", z.object({}), async () => ({ isSuccess: true as const, data: null }), {
555
573
  access: { openToAll: true },
556
574
  });
@@ -580,7 +598,13 @@ describe("runPreDelete", () => {
580
598
 
581
599
  test("a hook throwing aborts the delete (rejects, not swallowed)", async () => {
582
600
  const feature = defineFeature("test", (r) => {
583
- r.entity("user", createEntity({ table: "Users", fields: { email: createTextField() } }));
601
+ r.entity(
602
+ "user",
603
+ createEntity({
604
+ table: "Users",
605
+ fields: { email: createTextField({ personal: false, reason: "test_fixture" }) },
606
+ }),
607
+ );
584
608
  r.writeHandler("user", z.object({}), async () => ({ isSuccess: true as const, data: null }), {
585
609
  access: { openToAll: true },
586
610
  });
@@ -620,7 +644,13 @@ describe("runPostDelete", () => {
620
644
  test("runs feature then system hooks (best-effort by default phase)", async () => {
621
645
  const calls: string[] = [];
622
646
  const feature = defineFeature("test", (r) => {
623
- r.entity("user", createEntity({ table: "Users", fields: { email: createTextField() } }));
647
+ r.entity(
648
+ "user",
649
+ createEntity({
650
+ table: "Users",
651
+ fields: { email: createTextField({ personal: false, reason: "test_fixture" }) },
652
+ }),
653
+ );
624
654
  r.writeHandler("user", z.object({}), async () => ({ isSuccess: true as const, data: null }), {
625
655
  access: { openToAll: true },
626
656
  });
@@ -647,7 +677,13 @@ describe("runPostDelete", () => {
647
677
 
648
678
  test("inTransaction phase: hook errors throw", async () => {
649
679
  const feature = defineFeature("test", (r) => {
650
- r.entity("user", createEntity({ table: "Users", fields: { email: createTextField() } }));
680
+ r.entity(
681
+ "user",
682
+ createEntity({
683
+ table: "Users",
684
+ fields: { email: createTextField({ personal: false, reason: "test_fixture" }) },
685
+ }),
686
+ );
651
687
  r.writeHandler("user", z.object({}), async () => ({ isSuccess: true as const, data: null }), {
652
688
  access: { openToAll: true },
653
689
  });
@@ -29,8 +29,8 @@ import {
29
29
  const invoiceEntity = createEntity({
30
30
  table: "read_asof_invoices",
31
31
  fields: {
32
- customer: createTextField({ required: true }),
33
- status: createTextField({ required: true }),
32
+ customer: createTextField({ personal: false, reason: "test_fixture", required: true }),
33
+ status: createTextField({ personal: false, reason: "test_fixture", required: true }),
34
34
  },
35
35
  });
36
36
  const invoiceTable = buildEntityTable("asof-invoice", invoiceEntity);
@@ -30,7 +30,7 @@ import {
30
30
  const orderEntity = createEntity({
31
31
  table: "read_mmh_orders",
32
32
  fields: {
33
- item: createTextField({ required: true }),
33
+ item: createTextField({ personal: false, reason: "test_fixture", required: true }),
34
34
  },
35
35
  });
36
36
 
@@ -40,13 +40,17 @@ import {
40
40
 
41
41
  const invoiceEntity = createEntity({
42
42
  table: "read_mspreb_invoices",
43
- fields: { customer: createTextField({ required: true }) },
43
+ fields: {
44
+ customer: createTextField({ personal: false, reason: "test_fixture", required: true }),
45
+ },
44
46
  });
45
47
  const invoiceTable = buildEntityTable("msp-reb-invoice", invoiceEntity);
46
48
 
47
49
  const paymentEntity = createEntity({
48
50
  table: "read_mspreb_payments",
49
- fields: { customer: createTextField({ required: true }) },
51
+ fields: {
52
+ customer: createTextField({ personal: false, reason: "test_fixture", required: true }),
53
+ },
50
54
  });
51
55
  const paymentTable = buildEntityTable("msp-reb-payment", paymentEntity);
52
56
 
@@ -26,13 +26,17 @@ import {
26
26
 
27
27
  const shipmentEntity = createEntity({
28
28
  table: "read_msp_shipments",
29
- fields: { customer: createTextField({ required: true }) },
29
+ fields: {
30
+ customer: createTextField({ personal: false, reason: "test_fixture", required: true }),
31
+ },
30
32
  });
31
33
  const shipmentTable = buildEntityTable("msp-shipment", shipmentEntity);
32
34
 
33
35
  const refundEntity = createEntity({
34
36
  table: "read_msp_refunds",
35
- fields: { customer: createTextField({ required: true }) },
37
+ fields: {
38
+ customer: createTextField({ personal: false, reason: "test_fixture", required: true }),
39
+ },
36
40
  });
37
41
  const refundTable = buildEntityTable("msp-refund", refundEntity);
38
42
 
@@ -52,7 +52,7 @@ const taskCountProjection: ProjectionDefinition = {
52
52
 
53
53
  const taskEntity = createEntity({
54
54
  table: "perf_rebuild_tasks",
55
- fields: { title: createTextField({ required: true }) },
55
+ fields: { title: createTextField({ personal: false, reason: "test_fixture", required: true }) },
56
56
  });
57
57
 
58
58
  const feature = defineFeature("perfrebuild", (r) => {