@cosmicdrift/kumiko-framework 0.269.2 → 0.273.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/README.md +5 -1
  2. package/package.json +3 -3
  3. package/src/__tests__/anonymous-access.integration.test.ts +1 -1
  4. package/src/__tests__/error-contract.integration.test.ts +1 -1
  5. package/src/__tests__/fixtures/migrate-cross-tenant/manual-only.expected.ts +44 -0
  6. package/src/__tests__/fixtures/migrate-cross-tenant/manual-only.input.ts +44 -0
  7. package/src/__tests__/fixtures/migrate-cross-tenant/rewrite.expected.ts +41 -0
  8. package/src/__tests__/fixtures/migrate-cross-tenant/rewrite.input.ts +41 -0
  9. package/src/__tests__/fixtures/migrate-open-to-all/manual-only.expected.ts +16 -0
  10. package/src/__tests__/fixtures/migrate-open-to-all/manual-only.input.ts +16 -0
  11. package/src/__tests__/fixtures/migrate-open-to-all/test-nested-shorthand.expected.ts +12 -0
  12. package/src/__tests__/fixtures/migrate-open-to-all/test-nested-shorthand.input.ts +12 -0
  13. package/src/__tests__/fixtures/migrate-open-to-all/test-simple.expected.ts +7 -0
  14. package/src/__tests__/fixtures/migrate-open-to-all/test-simple.input.ts +7 -0
  15. package/src/__tests__/full-stack.integration.test.ts +2 -2
  16. package/src/__tests__/migrate-cross-tenant-codemod.test.ts +106 -0
  17. package/src/__tests__/migrate-open-to-all-codemod.test.ts +84 -0
  18. package/src/__tests__/related-list-parent-filter.integration.test.ts +103 -0
  19. package/src/__tests__/transition-guard.integration.test.ts +7 -7
  20. package/src/api/__tests__/api.test.ts +2 -2
  21. package/src/api/__tests__/body-limit.test.ts +1 -1
  22. package/src/api/__tests__/nested-write-ownership.integration.test.ts +268 -0
  23. package/src/api/__tests__/pii-leak-guard.integration.test.ts +2 -2
  24. package/src/api/__tests__/request-locale.integration.test.ts +1 -1
  25. package/src/api/__tests__/server-error-logging.test.ts +1 -1
  26. package/src/api/server.ts +7 -20
  27. package/src/bun-db/__tests__/query-guards.test.ts +60 -3
  28. package/src/bun-db/query.ts +24 -21
  29. package/src/changes.json +99 -0
  30. package/src/db/__tests__/event-store-executor-write-verbs.integration.test.ts +77 -0
  31. package/src/db/__tests__/event-store-executor.integration.test.ts +1 -1
  32. package/src/db/__tests__/list-filter-field-access.integration.test.ts +1 -1
  33. package/src/db/__tests__/replay-migration-sql.test.ts +17 -0
  34. package/src/db/__tests__/tenant-db-convention-cross-tenant.test.ts +72 -0
  35. package/src/db/__tests__/tenant-db-declared-unsafe-raw.test.ts +104 -0
  36. package/src/db/__tests__/tenant-db-global.test.ts +92 -0
  37. package/src/db/__tests__/tenant-db-no-raw.test.ts +75 -0
  38. package/src/db/__tests__/tenant-db.integration.test.ts +3 -1
  39. package/src/db/__tests__/unchecked-system-db.test.ts +76 -0
  40. package/src/db/apply-entity-event.ts +1 -1
  41. package/src/db/assert-exists-in.ts +1 -1
  42. package/src/db/event-store-executor-context.ts +5 -2
  43. package/src/db/event-store-executor-read.ts +6 -4
  44. package/src/db/event-store-executor-write.ts +24 -18
  45. package/src/db/queries/shadow-swap.ts +36 -0
  46. package/src/db/replay-migration-sql.ts +5 -5
  47. package/src/db/tenant-db-runner.ts +23 -0
  48. package/src/db/tenant-db.ts +97 -24
  49. package/src/derivatives/__tests__/derivatives-context.integration.test.ts +2 -2
  50. package/src/derivatives/__tests__/derivatives-context.test.ts +0 -1
  51. package/src/engine/__tests__/boot-validator-dashboard.test.ts +5 -5
  52. package/src/engine/__tests__/boot-validator-nav-area.test.ts +116 -0
  53. package/src/engine/__tests__/boot-validator-pii-retention.test.ts +1 -1
  54. package/src/engine/__tests__/boot-validator-projection-list.test.ts +108 -17
  55. package/src/engine/__tests__/boot-validator-query-output-schema.test.ts +161 -18
  56. package/src/engine/__tests__/boot-validator-query-refs.test.ts +3 -3
  57. package/src/engine/__tests__/boot-validator-s0-integration.test.ts +2 -2
  58. package/src/engine/__tests__/boot-validator.test.ts +69 -18
  59. package/src/engine/__tests__/build-app-schema.test.ts +10 -6
  60. package/src/engine/__tests__/build-config-feature-schema-secrets.test.ts +48 -0
  61. package/src/engine/__tests__/build-config-feature-schema.test.ts +34 -5
  62. package/src/engine/__tests__/engine.test.ts +39 -25
  63. package/src/engine/__tests__/entity-create-caller-id.integration.test.ts +87 -0
  64. package/src/engine/__tests__/entity-handlers-escape-hatch.test.ts +91 -0
  65. package/src/engine/__tests__/entity-handlers.test.ts +47 -8
  66. package/src/engine/__tests__/entity-presave-wiring.integration.test.ts +2 -2
  67. package/src/engine/__tests__/entity-restore-tenant-isolation.integration.test.ts +30 -5
  68. package/src/engine/__tests__/entity-write-crosstenant.integration.test.ts +206 -47
  69. package/src/engine/__tests__/extends-registrar.test.ts +3 -3
  70. package/src/engine/__tests__/feature-crud-shorthand.test.ts +9 -3
  71. package/src/engine/__tests__/feature-crud-verb-access.integration.test.ts +1 -1
  72. package/src/engine/__tests__/hook-phases.test.ts +5 -5
  73. package/src/engine/__tests__/lifecycle-hooks.test.ts +9 -3
  74. package/src/engine/__tests__/multiselect-filter.integration.test.ts +1 -1
  75. package/src/engine/__tests__/post-query-hook.test.ts +6 -2
  76. package/src/engine/__tests__/projection-detail-actions.test.ts +2 -2
  77. package/src/engine/__tests__/projection-detail-singleton.test.ts +3 -3
  78. package/src/engine/__tests__/projection-detail-tabs.test.ts +3 -3
  79. package/src/engine/__tests__/projection-list-filter.integration.test.ts +1 -1
  80. package/src/engine/__tests__/read-steps-tenant-filter.integration.test.ts +215 -0
  81. package/src/engine/__tests__/registry.test.ts +19 -13
  82. package/src/engine/__tests__/required-surface-keys.test.ts +16 -0
  83. package/src/engine/__tests__/screen.test.ts +176 -20
  84. package/src/engine/__tests__/soft-delete-cleanup.test.ts +0 -1
  85. package/src/engine/__tests__/steps-read.test.ts +204 -8
  86. package/src/engine/__tests__/steps-unsafe-projection-delete.test.ts +7 -4
  87. package/src/engine/__tests__/steps-unsafe-projection-upsert.test.ts +6 -3
  88. package/src/engine/boot-validator/__tests__/access-declarations.test.ts +5 -4
  89. package/src/engine/boot-validator/__tests__/global-tenancy.test.ts +16 -1
  90. package/src/engine/boot-validator/__tests__/parent-ref.test.ts +18 -6
  91. package/src/engine/boot-validator/__tests__/rate-limit-disabled-reason.test.ts +45 -0
  92. package/src/engine/boot-validator/access-declarations.ts +3 -5
  93. package/src/engine/boot-validator/entity-handler.ts +21 -0
  94. package/src/engine/boot-validator/global-tenancy.ts +13 -1
  95. package/src/engine/boot-validator/nav.ts +4 -3
  96. package/src/engine/boot-validator/parent-ref.ts +1 -1
  97. package/src/engine/boot-validator/projection-list-screens.ts +67 -2
  98. package/src/engine/boot-validator/query-output-columns.ts +33 -0
  99. package/src/engine/boot-validator/screens.ts +154 -31
  100. package/src/engine/build-config-feature-schema.ts +12 -2
  101. package/src/engine/entity-handlers.ts +72 -20
  102. package/src/engine/feature-ast/__tests__/canonical-form.test.ts +3 -1
  103. package/src/engine/feature-ast/__tests__/parse-happy-path.test.ts +2 -2
  104. package/src/engine/feature-ast/__tests__/parse.test.ts +106 -4
  105. package/src/engine/feature-ast/__tests__/patcher.test.ts +3 -3
  106. package/src/engine/feature-ast/__tests__/read-optional-access-rule.test.ts +4 -4
  107. package/src/engine/feature-ast/__tests__/render-roundtrip.test.ts +5 -1
  108. package/src/engine/feature-ast/extractors/handlers.ts +16 -2
  109. package/src/engine/feature-ast/extractors/hooks.ts +25 -4
  110. package/src/engine/feature-ast/patcher.ts +15 -3
  111. package/src/engine/feature-ast/patterns.ts +4 -0
  112. package/src/engine/feature-ast/render.ts +3 -0
  113. package/src/engine/feature-entity-handlers.ts +4 -4
  114. package/src/engine/index.ts +5 -0
  115. package/src/engine/pattern-library/shared-fields.ts +7 -3
  116. package/src/engine/registry-validate.ts +12 -4
  117. package/src/engine/schema-builder.ts +6 -0
  118. package/src/engine/screen-helpers.ts +52 -0
  119. package/src/engine/steps/_read-source.ts +12 -0
  120. package/src/engine/steps/read-find-many.ts +8 -2
  121. package/src/engine/steps/read-find-one.ts +11 -8
  122. package/src/engine/steps/unsafe-projection-delete.ts +2 -1
  123. package/src/engine/steps/unsafe-projection-upsert.ts +2 -1
  124. package/src/engine/types/index.ts +8 -1
  125. package/src/entrypoint/__tests__/entrypoint-attach-dispatcher.integration.test.ts +1 -1
  126. package/src/entrypoint/__tests__/entrypoint-job-wiring.integration.test.ts +3 -3
  127. package/src/entrypoint/__tests__/split-deploy.integration.test.ts +1 -1
  128. package/src/errors/i18n/de.yaml +14 -0
  129. package/src/errors/i18n/en.yaml +13 -0
  130. package/src/errors/reasons.ts +4 -0
  131. package/src/es-ops/__tests__/runner.integration.test.ts +2 -2
  132. package/src/i18n/required-surface-keys.ts +6 -2
  133. package/src/jobs/__tests__/job-event-trigger.integration.test.ts +2 -2
  134. package/src/jobs/__tests__/job-multi-trigger.integration.test.ts +2 -2
  135. package/src/jobs/__tests__/job-systemdb.integration.test.ts +5 -4
  136. package/src/jobs/__tests__/jobs.integration.test.ts +42 -1
  137. package/src/jobs/job-runner.ts +35 -5
  138. package/src/observability/__tests__/observability.integration.test.ts +7 -7
  139. package/src/pipeline/__tests__/archive-stream.integration.test.ts +2 -2
  140. package/src/pipeline/__tests__/dispatcher.test.ts +15 -15
  141. package/src/pipeline/__tests__/escape-hatch-audit.integration.test.ts +254 -0
  142. package/src/pipeline/__tests__/escape-hatch-report.test.ts +149 -0
  143. package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +10 -10
  144. package/src/pipeline/__tests__/load-aggregate-query.integration.test.ts +1 -1
  145. package/src/pipeline/__tests__/msp-rebuild.integration.test.ts +28 -0
  146. package/src/pipeline/__tests__/post-query-hook.integration.test.ts +3 -3
  147. package/src/pipeline/__tests__/projection-rebuild.integration.test.ts +94 -0
  148. package/src/pipeline/__tests__/projections-runner-systemscope.integration.test.ts +101 -0
  149. package/src/pipeline/__tests__/query-projection.integration.test.ts +172 -17
  150. package/src/pipeline/__tests__/rate-limit-default.integration.test.ts +118 -0
  151. package/src/pipeline/__tests__/rate-limit-default.test.ts +117 -0
  152. package/src/pipeline/__tests__/search-consumer-named-events.integration.test.ts +5 -2
  153. package/src/pipeline/__tests__/system-identity-switch.test.ts +240 -1
  154. package/src/pipeline/dispatch-query.ts +27 -8
  155. package/src/pipeline/dispatch-shared.ts +141 -57
  156. package/src/pipeline/dispatch-stream.ts +1 -3
  157. package/src/pipeline/dispatch-write.ts +62 -14
  158. package/src/pipeline/dispatcher.ts +20 -0
  159. package/src/pipeline/escape-hatch-report.ts +127 -0
  160. package/src/pipeline/index.ts +8 -0
  161. package/src/pipeline/member-read-only-transaction.ts +75 -0
  162. package/src/pipeline/msp-rebuild.ts +2 -0
  163. package/src/pipeline/projection-rebuild.ts +2 -0
  164. package/src/pipeline/projections-runner.ts +15 -13
  165. package/src/pipeline/system-identity-switch.ts +121 -9
  166. package/src/scripts/codemod/migrate-cross-tenant.ts +335 -0
  167. package/src/scripts/codemod/migrate-db-raw.ts +19 -6
  168. package/src/scripts/codemod/migrate-open-to-all.ts +249 -0
  169. package/src/secrets/__tests__/contains-secret.test.ts +5 -5
  170. package/src/stack/__tests__/request-helper.integration.test.ts +3 -3
  171. package/src/stack/__tests__/setup-test-stack-jobs.integration.test.ts +1 -1
  172. package/src/testing/__tests__/e2e-generator.test.ts +19 -5
  173. package/src/ui-types/index.ts +3 -0
  174. package/src/utils/__tests__/ids.test.ts +29 -1
  175. package/src/utils/ids.ts +10 -1
  176. package/src/utils/index.ts +1 -1
@@ -2,12 +2,13 @@ import { describe, expect, test } from "bun:test";
2
2
  import { z } from "zod";
3
3
  import { validateBoot } from "../boot-validator";
4
4
  import { defineFeature } from "../define-feature";
5
+ import { createEntity, createTextField } from "../factories";
5
6
 
6
7
  describe("validateBoot — projectionList screens", () => {
7
8
  test("rejects hand-written searchable:true when the query schema has no search param (3a)", () => {
8
9
  const feature = defineFeature("ledger", (r) => {
9
10
  r.queryHandler("schedule:list", z.object({}), async () => ({ rows: [], nextCursor: null }), {
10
- access: { openToAll: true },
11
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
11
12
  });
12
13
  r.screen({
13
14
  id: "schedule-list",
@@ -29,7 +30,7 @@ describe("validateBoot — projectionList screens", () => {
29
30
  "schedule:list",
30
31
  z.object({ search: z.string().optional() }),
31
32
  async () => ({ rows: [], nextCursor: null }),
32
- { access: { openToAll: true } },
33
+ { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
33
34
  );
34
35
  r.screen({
35
36
  id: "schedule-list",
@@ -50,7 +51,7 @@ describe("validateBoot — projectionList screens", () => {
50
51
  "schedule:list",
51
52
  z.object({ sort: z.string().optional() }),
52
53
  async () => ({ rows: [], nextCursor: null }),
53
- { access: { openToAll: true } },
54
+ { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
54
55
  );
55
56
  r.screen({
56
57
  id: "schedule-list",
@@ -71,7 +72,7 @@ describe("validateBoot — projectionList screens", () => {
71
72
  "schedule:list",
72
73
  z.object({ search: z.string().optional(), sort: z.string().optional() }),
73
74
  async () => ({ rows: [], nextCursor: null }),
74
- { access: { openToAll: true } },
75
+ { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
75
76
  );
76
77
  r.screen({
77
78
  id: "schedule-list",
@@ -91,7 +92,7 @@ describe("validateBoot — projectionList screens", () => {
91
92
  test("passes when the schema offers neither search nor sort and no defaultSort is set", () => {
92
93
  const feature = defineFeature("ledger", (r) => {
93
94
  r.queryHandler("schedule:list", z.object({}), async () => ({ rows: [], nextCursor: null }), {
94
- access: { openToAll: true },
95
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
95
96
  });
96
97
  r.screen({
97
98
  id: "schedule-list",
@@ -112,7 +113,7 @@ describe("validateBoot — projectionList screens", () => {
112
113
  "schedule:list",
113
114
  z.object({ search: z.string().optional() }),
114
115
  async () => ({ rows: [], nextCursor: null }),
115
- { access: { openToAll: true } },
116
+ { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
116
117
  );
117
118
  r.screen({
118
119
  id: "schedule-list",
@@ -135,7 +136,7 @@ describe("validateBoot — projectionList screens", () => {
135
136
  "schedule:list",
136
137
  z.object({ search: z.string().optional() }),
137
138
  async () => ({ rows: [], nextCursor: null }),
138
- { access: { openToAll: true } },
139
+ { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
139
140
  );
140
141
  r.screen({
141
142
  id: "download-attempt-list",
@@ -154,7 +155,7 @@ describe("validateBoot — projectionList screens", () => {
154
155
  test("rejects hand-authored sortable on a projectionList screen (fw#2165 review)", () => {
155
156
  const feature = defineFeature("ledger", (r) => {
156
157
  r.queryHandler("schedule:list", z.object({}), async () => ({ rows: [], nextCursor: null }), {
157
- access: { openToAll: true },
158
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
158
159
  });
159
160
  r.screen({
160
161
  id: "schedule-list",
@@ -173,7 +174,7 @@ describe("validateBoot — projectionList screens", () => {
173
174
  test("rejects hand-authored paginated on a projectionList screen (fw#2165 review)", () => {
174
175
  const feature = defineFeature("ledger", (r) => {
175
176
  r.queryHandler("schedule:list", z.object({}), async () => ({ rows: [], nextCursor: null }), {
176
- access: { openToAll: true },
177
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
177
178
  });
178
179
  r.screen({
179
180
  id: "schedule-list",
@@ -202,7 +203,7 @@ describe("validateBoot — projectionList screens", () => {
202
203
  z.object({}),
203
204
  async () => ({ rows: [], nextCursor: null }),
204
205
  {
205
- access: { openToAll: true },
206
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
206
207
  },
207
208
  );
208
209
  r.screen({ id: "schedule-detail", type: "custom", renderer: { react: "stub" } });
@@ -251,7 +252,7 @@ describe("validateBoot — projectionList screens", () => {
251
252
  "schedule:list",
252
253
  z.object({ filter: z.unknown().optional() }),
253
254
  async () => ({ rows: [], nextCursor: null }),
254
- { access: { openToAll: true } },
255
+ { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
255
256
  );
256
257
  r.screen({
257
258
  id: "schedule-list",
@@ -274,7 +275,7 @@ describe("validateBoot — projectionList screens", () => {
274
275
  z.object({}),
275
276
  async () => ({ rows: [], nextCursor: null }),
276
277
  {
277
- access: { openToAll: true },
278
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
278
279
  },
279
280
  );
280
281
  r.screen({
@@ -297,7 +298,7 @@ describe("validateBoot — projectionList screens", () => {
297
298
  "schedule:list",
298
299
  z.object({ filters: z.unknown().optional() }),
299
300
  async () => ({ rows: [], nextCursor: null }),
300
- { access: { openToAll: true } },
301
+ { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
301
302
  );
302
303
  r.screen({
303
304
  id: "schedule-list",
@@ -326,7 +327,7 @@ describe("validateBoot — projectionList screens", () => {
326
327
  "schedule:list",
327
328
  z.object({ filters: z.unknown().optional() }),
328
329
  async () => ({ rows: [], nextCursor: null }),
329
- { access: { openToAll: true } },
330
+ { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
330
331
  );
331
332
  r.screen({
332
333
  id: "schedule-list",
@@ -363,7 +364,7 @@ describe("validateBoot — projectionList screens", () => {
363
364
  "schedule:list",
364
365
  z.object({ filters: z.unknown().optional() }),
365
366
  async () => ({ rows: [], nextCursor: null }),
366
- { access: { openToAll: true } },
367
+ { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
367
368
  );
368
369
  r.screen({
369
370
  id: "schedule-list",
@@ -386,7 +387,7 @@ describe("validateBoot — projectionList screens", () => {
386
387
  z.object({}),
387
388
  async () => ({ rows: [], nextCursor: null }),
388
389
  {
389
- access: { openToAll: true },
390
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
390
391
  },
391
392
  );
392
393
  r.screen({
@@ -417,7 +418,7 @@ describe("validateBoot — projectionList screens", () => {
417
418
  "schedule:list",
418
419
  z.object({ filter: z.unknown().optional(), filters: z.unknown().optional() }),
419
420
  async () => ({ rows: [], nextCursor: null }),
420
- { access: { openToAll: true } },
421
+ { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
421
422
  );
422
423
  r.screen({
423
424
  id: "schedule-list",
@@ -441,5 +442,95 @@ describe("validateBoot — projectionList screens", () => {
441
442
  });
442
443
  expect(() => validateBoot([feature])).not.toThrow();
443
444
  });
445
+
446
+ test("a reference facet targeting an unknown entity is rejected", () => {
447
+ const feature = defineFeature("ledger", (r) => {
448
+ r.queryHandler(
449
+ "schedule:list",
450
+ z.object({ filters: z.unknown().optional() }),
451
+ async () => ({ rows: [], nextCursor: null }),
452
+ { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
453
+ );
454
+ r.screen({
455
+ id: "schedule-list",
456
+ type: "projectionList",
457
+ query: "ledger:query:schedule:list",
458
+ columns: ["tenant"],
459
+ facets: [{ field: "tenant", type: "reference", label: "Tenant", entity: "tenant" }],
460
+ });
461
+ r.translations({
462
+ keys: { "screen:schedule-list.title": { de: "Liste", en: "List" } },
463
+ });
464
+ });
465
+ expect(() => validateBoot([feature])).toThrow(/does not resolve to a registered entity/);
466
+ });
467
+
468
+ test("a reference facet targeting a known entity passes boot", () => {
469
+ const feature = defineFeature("ledger", (r) => {
470
+ r.entity(
471
+ "tenant",
472
+ createEntity({
473
+ table: "Tenants",
474
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
475
+ }),
476
+ );
477
+ r.queryHandler(
478
+ "schedule:list",
479
+ z.object({ filters: z.unknown().optional() }),
480
+ async () => ({ rows: [], nextCursor: null }),
481
+ { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
482
+ );
483
+ r.screen({
484
+ id: "schedule-list",
485
+ type: "projectionList",
486
+ query: "ledger:query:schedule:list",
487
+ columns: ["tenant"],
488
+ facets: [{ field: "tenant", type: "reference", label: "Tenant", entity: "tenant" }],
489
+ });
490
+ r.translations({
491
+ keys: {
492
+ "screen:schedule-list.title": { de: "Liste", en: "List" },
493
+ "ledger:entity:tenant:field:name": { de: "Name", en: "Name" },
494
+ },
495
+ });
496
+ });
497
+ expect(() => validateBoot([feature])).not.toThrow();
498
+ });
499
+
500
+ test("a reference facet filtering by an id field with no same-named column boots (displays a different label column)", () => {
501
+ const feature = defineFeature("ledger", (r) => {
502
+ r.entity(
503
+ "property",
504
+ createEntity({
505
+ table: "Properties",
506
+ fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
507
+ }),
508
+ );
509
+ r.queryHandler(
510
+ "schedule:list",
511
+ z.object({ filters: z.unknown().optional() }),
512
+ async () => ({ rows: [], nextCursor: null }),
513
+ { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
514
+ );
515
+ r.screen({
516
+ id: "schedule-list",
517
+ type: "projectionList",
518
+ query: "ledger:query:schedule:list",
519
+ // Filters by propertyId, displays propertyLabel — no "propertyId"
520
+ // column exists, and none should be required for a reference facet.
521
+ columns: ["propertyLabel"],
522
+ facets: [
523
+ { field: "propertyId", type: "reference", label: "Property", entity: "property" },
524
+ ],
525
+ });
526
+ r.translations({
527
+ keys: {
528
+ "screen:schedule-list.title": { de: "Liste", en: "List" },
529
+ "ledger:entity:property:field:name": { de: "Name", en: "Name" },
530
+ },
531
+ });
532
+ });
533
+ expect(() => validateBoot([feature])).not.toThrow();
534
+ });
444
535
  });
445
536
  });
@@ -20,7 +20,7 @@ describe("validateBoot — query output schema column refs (fw#2493)", () => {
20
20
  test("projectionList column not in the paged query's row shape throws", () => {
21
21
  const feature = defineFeature("catalog", (r) => {
22
22
  r.queryHandler("items:list", z.object({}), async () => ({ rows: [], nextCursor: null }), {
23
- access: { openToAll: true },
23
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
24
24
  outputSchema: pagedSchema,
25
25
  });
26
26
  r.screen({
@@ -39,7 +39,7 @@ describe("validateBoot — query output schema column refs (fw#2493)", () => {
39
39
  test("projectionList column present in the row shape does not throw", () => {
40
40
  const feature = defineFeature("catalog", (r) => {
41
41
  r.queryHandler("items:list", z.object({}), async () => ({ rows: [], nextCursor: null }), {
42
- access: { openToAll: true },
42
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
43
43
  outputSchema: pagedSchema,
44
44
  });
45
45
  r.screen({
@@ -56,7 +56,7 @@ describe("validateBoot — query output schema column refs (fw#2493)", () => {
56
56
  test("projectionList column with a label is exempt as a virtual/computed column", () => {
57
57
  const feature = defineFeature("catalog", (r) => {
58
58
  r.queryHandler("items:list", z.object({}), async () => ({ rows: [], nextCursor: null }), {
59
- access: { openToAll: true },
59
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
60
60
  outputSchema: pagedSchema,
61
61
  });
62
62
  r.screen({
@@ -78,7 +78,7 @@ describe("validateBoot — query output schema column refs (fw#2493)", () => {
78
78
  test("projectionList without a declared outputSchema skips the column check entirely", () => {
79
79
  const feature = defineFeature("catalog", (r) => {
80
80
  r.queryHandler("items:list", z.object({}), async () => ({ rows: [], nextCursor: null }), {
81
- access: { openToAll: true },
81
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
82
82
  });
83
83
  r.screen({
84
84
  id: "items",
@@ -94,7 +94,7 @@ describe("validateBoot — query output schema column refs (fw#2493)", () => {
94
94
  test("projectionList outputSchema that isn't a ZodObject (e.g. a union) skips the column check", () => {
95
95
  const feature = defineFeature("catalog", (r) => {
96
96
  r.queryHandler("items:list", z.object({}), async () => ({ rows: [], nextCursor: null }), {
97
- access: { openToAll: true },
97
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
98
98
  outputSchema: z.union([pagedSchema, z.null()]),
99
99
  });
100
100
  r.screen({
@@ -111,11 +111,11 @@ describe("validateBoot — query output schema column refs (fw#2493)", () => {
111
111
  test("relatedList column not in the query's row shape throws", () => {
112
112
  const feature = defineFeature("app", (r) => {
113
113
  r.queryHandler("rent:detail", z.object({}), async () => ({ description: "x" }), {
114
- access: { openToAll: true },
114
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
115
115
  outputSchema: z.object({ description: z.string() }),
116
116
  });
117
117
  r.queryHandler("rent:payments", z.object({}), async () => ({ rows: [], nextCursor: null }), {
118
- access: { openToAll: true },
118
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
119
119
  outputSchema: z.object({
120
120
  rows: z.array(z.object({ amount: z.number() })),
121
121
  nextCursor: z.string().nullable(),
@@ -142,10 +142,153 @@ describe("validateBoot — query output schema column refs (fw#2493)", () => {
142
142
  );
143
143
  });
144
144
 
145
+ test("relatedList toolbarAction visible.field not in the projectionDetail's own outputSchema throws", () => {
146
+ const feature = defineFeature("app", (r) => {
147
+ r.queryHandler("lease:detail", z.object({}), async () => ({ id: "1", status: "active" }), {
148
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
149
+ outputSchema: z.object({ id: z.string(), status: z.string() }),
150
+ });
151
+ r.queryHandler(
152
+ "lease:positions",
153
+ z.object({}),
154
+ async () => ({ rows: [], nextCursor: null }),
155
+ {
156
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
157
+ outputSchema: z.object({
158
+ rows: z.array(z.object({ id: z.string() })),
159
+ nextCursor: z.string().nullable(),
160
+ }),
161
+ },
162
+ );
163
+ r.screen({
164
+ id: "lease-detail",
165
+ type: "projectionDetail",
166
+ query: "app:query:lease:detail",
167
+ layout: {
168
+ sections: [
169
+ {
170
+ kind: "relatedList",
171
+ title: "Positions",
172
+ query: "app:query:lease:positions",
173
+ columns: ["id"],
174
+ toolbarActions: [
175
+ {
176
+ kind: "navigate",
177
+ id: "create",
178
+ label: "Add position",
179
+ screen: "position-create",
180
+ visible: { field: "ghost-status", eq: "active" },
181
+ },
182
+ ],
183
+ },
184
+ ],
185
+ },
186
+ });
187
+ });
188
+ expect(() => validateBoot([feature])).toThrow(
189
+ /toolbarAction "create" visible\.field references unknown field "ghost-status"/,
190
+ );
191
+ });
192
+
193
+ test("relatedList toolbarAction navigate params source field not in the projectionDetail's own outputSchema throws", () => {
194
+ const feature = defineFeature("app", (r) => {
195
+ r.queryHandler("lease:detail", z.object({}), async () => ({ id: "1" }), {
196
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
197
+ outputSchema: z.object({ id: z.string() }),
198
+ });
199
+ r.queryHandler(
200
+ "lease:positions",
201
+ z.object({}),
202
+ async () => ({ rows: [], nextCursor: null }),
203
+ {
204
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
205
+ outputSchema: z.object({
206
+ rows: z.array(z.object({ id: z.string() })),
207
+ nextCursor: z.string().nullable(),
208
+ }),
209
+ },
210
+ );
211
+ r.screen({
212
+ id: "lease-detail",
213
+ type: "projectionDetail",
214
+ query: "app:query:lease:detail",
215
+ layout: {
216
+ sections: [
217
+ {
218
+ kind: "relatedList",
219
+ title: "Positions",
220
+ query: "app:query:lease:positions",
221
+ columns: ["id"],
222
+ toolbarActions: [
223
+ {
224
+ kind: "navigate",
225
+ id: "create",
226
+ label: "Add position",
227
+ screen: "position-create",
228
+ params: { map: { leaseId: "ghost-id" } },
229
+ },
230
+ ],
231
+ },
232
+ ],
233
+ },
234
+ });
235
+ });
236
+ expect(() => validateBoot([feature])).toThrow(
237
+ /toolbarAction "create" params references unknown field "ghost-id"/,
238
+ );
239
+ });
240
+
241
+ test("relatedList toolbarAction visible/params present in the projectionDetail's own outputSchema does not throw", () => {
242
+ const feature = defineFeature("app", (r) => {
243
+ r.queryHandler("lease:detail", z.object({}), async () => ({ id: "1", status: "active" }), {
244
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
245
+ outputSchema: z.object({ id: z.string(), status: z.string() }),
246
+ });
247
+ r.queryHandler(
248
+ "lease:positions",
249
+ z.object({}),
250
+ async () => ({ rows: [], nextCursor: null }),
251
+ {
252
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
253
+ outputSchema: z.object({
254
+ rows: z.array(z.object({ id: z.string() })),
255
+ nextCursor: z.string().nullable(),
256
+ }),
257
+ },
258
+ );
259
+ r.screen({
260
+ id: "lease-detail",
261
+ type: "projectionDetail",
262
+ query: "app:query:lease:detail",
263
+ layout: {
264
+ sections: [
265
+ {
266
+ kind: "relatedList",
267
+ title: "Positions",
268
+ query: "app:query:lease:positions",
269
+ columns: ["id"],
270
+ toolbarActions: [
271
+ {
272
+ kind: "navigate",
273
+ id: "create",
274
+ label: "Add position",
275
+ screen: "position-create",
276
+ visible: { field: "status", eq: "active" },
277
+ params: { map: { leaseId: "id" } },
278
+ },
279
+ ],
280
+ },
281
+ ],
282
+ },
283
+ });
284
+ });
285
+ expect(() => validateBoot([feature])).not.toThrow();
286
+ });
287
+
145
288
  test("projectionDetail header.title referencing an unknown field throws", () => {
146
289
  const feature = defineFeature("app", (r) => {
147
290
  r.queryHandler("tenant:detail", z.object({}), async () => ({ id: "1", name: "x" }), {
148
- access: { openToAll: true },
291
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
149
292
  outputSchema: z.object({ id: z.string(), name: z.string() }),
150
293
  });
151
294
  r.screen({
@@ -164,7 +307,7 @@ describe("validateBoot — query output schema column refs (fw#2493)", () => {
164
307
  test("projectionDetail header.status referencing an unknown field throws", () => {
165
308
  const feature = defineFeature("app", (r) => {
166
309
  r.queryHandler("tenant:detail", z.object({}), async () => ({ id: "1", name: "x" }), {
167
- access: { openToAll: true },
310
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
168
311
  outputSchema: z.object({ id: z.string(), name: z.string() }),
169
312
  });
170
313
  r.screen({
@@ -181,7 +324,7 @@ describe("validateBoot — query output schema column refs (fw#2493)", () => {
181
324
  test("projectionDetail metrics referencing an unknown field throws", () => {
182
325
  const feature = defineFeature("app", (r) => {
183
326
  r.queryHandler("tenant:detail", z.object({}), async () => ({ id: "1", balance: 0 }), {
184
- access: { openToAll: true },
327
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
185
328
  outputSchema: z.object({ id: z.string(), balance: z.number() }),
186
329
  });
187
330
  r.screen({
@@ -214,7 +357,7 @@ describe("validateBoot — query output schema column refs (fw#2493)", () => {
214
357
  z.object({}),
215
358
  async () => ({ id: "1", name: "x", balance: 0 }),
216
359
  {
217
- access: { openToAll: true },
360
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
218
361
  outputSchema: z.object({
219
362
  id: z.string(),
220
363
  name: z.string(),
@@ -241,7 +384,7 @@ describe("validateBoot — query output schema column refs (fw#2493)", () => {
241
384
  test("dashboard stat panel valueField referencing an unknown field throws", () => {
242
385
  const feature = defineFeature("demo", (r) => {
243
386
  r.queryHandler("open-count", z.object({}), async () => ({ value: "0" }), {
244
- access: { openToAll: true },
387
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
245
388
  outputSchema: z.object({ value: z.string() }),
246
389
  });
247
390
  r.screen({
@@ -269,7 +412,7 @@ describe("validateBoot — query output schema column refs (fw#2493)", () => {
269
412
  test("dashboard stat-group child deltaField referencing an unknown field throws", () => {
270
413
  const feature = defineFeature("demo", (r) => {
271
414
  r.queryHandler("net-worth", z.object({}), async () => ({ value: "0" }), {
272
- access: { openToAll: true },
415
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
273
416
  outputSchema: z.object({ value: z.string() }),
274
417
  });
275
418
  r.screen({
@@ -308,7 +451,7 @@ describe("validateBoot — query output schema column refs (fw#2493)", () => {
308
451
  test("dashboard list panel column not in the query's row shape throws", () => {
309
452
  const feature = defineFeature("demo", (r) => {
310
453
  r.queryHandler("next-events", z.object({}), async () => ({ rows: [], nextCursor: null }), {
311
- access: { openToAll: true },
454
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
312
455
  outputSchema: z.object({
313
456
  rows: z.array(z.object({ title: z.string() })),
314
457
  nextCursor: z.string().nullable(),
@@ -342,7 +485,7 @@ describe("validateBoot — query output schema column refs (fw#2493)", () => {
342
485
  defineQueryHandler({
343
486
  name: "items:detail",
344
487
  schema: z.object({ id: z.string() }),
345
- access: { openToAll: true },
488
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
346
489
  outputSchema: z.object({ id: z.string(), name: z.string() }),
347
490
  handler: async () => ({ id: "1", name: "x" }),
348
491
  }),
@@ -366,7 +509,7 @@ describe("validateBoot — query output schema column refs (fw#2493)", () => {
366
509
  definePagedQueryHandler({
367
510
  name: "items:list",
368
511
  schema: z.object({}),
369
- access: { openToAll: true },
512
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
370
513
  outputSchema: pagedSchema,
371
514
  handler: async () => ({ rows: [], nextCursor: null }),
372
515
  }),
@@ -390,7 +533,7 @@ describe("validateBoot — query output schema column refs (fw#2493)", () => {
390
533
  definePagedQueryHandler({
391
534
  name: "items:list",
392
535
  schema: z.object({}),
393
- access: { openToAll: true },
536
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
394
537
  // Row schema passed directly instead of wrapping it in
395
538
  // { rows: [...], nextCursor } — the mistake fw#2493's follow-up
396
539
  // check catches instead of silently skipping every column check.
@@ -417,7 +560,7 @@ describe("validateBoot — query output schema column refs (fw#2493)", () => {
417
560
  definePagedQueryHandler({
418
561
  name: "items:list",
419
562
  schema: z.object({}),
420
- access: { openToAll: true },
563
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
421
564
  outputSchema: pagedSchema,
422
565
  handler: async () => ({ rows: [], nextCursor: null }),
423
566
  }),
@@ -36,7 +36,7 @@ describe("validateBoot — query QN refs (fw#2178)", () => {
36
36
  test("projectionDetail relatedList section with a dead query QN throws (b)", () => {
37
37
  const feature = defineFeature("app", (r) => {
38
38
  r.queryHandler("rent:detail", z.object({}), async () => ({ description: "x" }), {
39
- access: { openToAll: true },
39
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
40
40
  });
41
41
  r.screen({
42
42
  id: "rent-detail",
@@ -96,7 +96,7 @@ describe("validateBoot — query QN refs (fw#2178)", () => {
96
96
  test("dashboard filter optionsQuery with a dead query QN throws", () => {
97
97
  const feature = defineFeature("demo", (r) => {
98
98
  r.queryHandler("open-count", z.object({}), async () => ({ value: "0" }), {
99
- access: { openToAll: true },
99
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
100
100
  });
101
101
  r.screen({
102
102
  id: "overview",
@@ -132,7 +132,7 @@ describe("validateBoot — query QN refs (fw#2178)", () => {
132
132
  test("a screen referencing a query registered by a different mounted feature does not throw (d)", () => {
133
133
  const provider = defineFeature("catalog", (r) => {
134
134
  r.queryHandler("items:list", z.object({}), async () => ({ rows: [], nextCursor: null }), {
135
- access: { openToAll: true },
135
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
136
136
  });
137
137
  });
138
138
  const consumer = defineFeature("storefront", (r) => {
@@ -44,7 +44,7 @@ describe("S0 Integration — full surface stack", () => {
44
44
  name: "compliance:query:for-tenant",
45
45
  schema: z.object({}),
46
46
  handler: async () => ({ profile: "eu-dsgvo" }) as never,
47
- access: { openToAll: true },
47
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
48
48
  });
49
49
  });
50
50
 
@@ -101,7 +101,7 @@ describe("S0 Integration — full surface stack", () => {
101
101
  name: "user:list",
102
102
  schema: z.object({}),
103
103
  handler: async () => ({ rows: [], nextCursor: null }) as never,
104
- access: { openToAll: true },
104
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
105
105
  });
106
106
 
107
107
  r.useExtension(EXT_USER_DATA, "user", {});