@cosmicdrift/kumiko-framework 0.269.2 → 0.270.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 (84) 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-open-to-all/manual-only.expected.ts +16 -0
  6. package/src/__tests__/fixtures/migrate-open-to-all/manual-only.input.ts +16 -0
  7. package/src/__tests__/fixtures/migrate-open-to-all/test-nested-shorthand.expected.ts +12 -0
  8. package/src/__tests__/fixtures/migrate-open-to-all/test-nested-shorthand.input.ts +12 -0
  9. package/src/__tests__/fixtures/migrate-open-to-all/test-simple.expected.ts +7 -0
  10. package/src/__tests__/fixtures/migrate-open-to-all/test-simple.input.ts +7 -0
  11. package/src/__tests__/full-stack.integration.test.ts +2 -2
  12. package/src/__tests__/migrate-open-to-all-codemod.test.ts +84 -0
  13. package/src/__tests__/transition-guard.integration.test.ts +7 -7
  14. package/src/api/__tests__/api.test.ts +2 -2
  15. package/src/api/__tests__/body-limit.test.ts +1 -1
  16. package/src/api/__tests__/pii-leak-guard.integration.test.ts +2 -2
  17. package/src/api/__tests__/request-locale.integration.test.ts +1 -1
  18. package/src/api/__tests__/server-error-logging.test.ts +1 -1
  19. package/src/changes.json +8 -0
  20. package/src/db/__tests__/list-filter-field-access.integration.test.ts +1 -1
  21. package/src/derivatives/__tests__/derivatives-context.integration.test.ts +2 -2
  22. package/src/engine/__tests__/boot-validator-dashboard.test.ts +5 -5
  23. package/src/engine/__tests__/boot-validator-pii-retention.test.ts +1 -1
  24. package/src/engine/__tests__/boot-validator-projection-list.test.ts +17 -17
  25. package/src/engine/__tests__/boot-validator-query-output-schema.test.ts +18 -18
  26. package/src/engine/__tests__/boot-validator-query-refs.test.ts +3 -3
  27. package/src/engine/__tests__/boot-validator-s0-integration.test.ts +2 -2
  28. package/src/engine/__tests__/boot-validator.test.ts +18 -18
  29. package/src/engine/__tests__/build-app-schema.test.ts +10 -6
  30. package/src/engine/__tests__/build-config-feature-schema-secrets.test.ts +48 -0
  31. package/src/engine/__tests__/build-config-feature-schema.test.ts +34 -5
  32. package/src/engine/__tests__/engine.test.ts +39 -25
  33. package/src/engine/__tests__/entity-handlers.test.ts +19 -8
  34. package/src/engine/__tests__/entity-presave-wiring.integration.test.ts +2 -2
  35. package/src/engine/__tests__/extends-registrar.test.ts +3 -3
  36. package/src/engine/__tests__/feature-crud-shorthand.test.ts +9 -3
  37. package/src/engine/__tests__/feature-crud-verb-access.integration.test.ts +1 -1
  38. package/src/engine/__tests__/hook-phases.test.ts +5 -5
  39. package/src/engine/__tests__/lifecycle-hooks.test.ts +9 -3
  40. package/src/engine/__tests__/multiselect-filter.integration.test.ts +1 -1
  41. package/src/engine/__tests__/post-query-hook.test.ts +6 -2
  42. package/src/engine/__tests__/projection-detail-actions.test.ts +2 -2
  43. package/src/engine/__tests__/projection-detail-singleton.test.ts +3 -3
  44. package/src/engine/__tests__/projection-detail-tabs.test.ts +3 -3
  45. package/src/engine/__tests__/projection-list-filter.integration.test.ts +1 -1
  46. package/src/engine/__tests__/registry.test.ts +19 -13
  47. package/src/engine/__tests__/screen.test.ts +26 -20
  48. package/src/engine/boot-validator/__tests__/access-declarations.test.ts +5 -4
  49. package/src/engine/boot-validator/__tests__/parent-ref.test.ts +18 -6
  50. package/src/engine/boot-validator/access-declarations.ts +3 -5
  51. package/src/engine/boot-validator/nav.ts +4 -3
  52. package/src/engine/build-config-feature-schema.ts +9 -2
  53. package/src/engine/entity-handlers.ts +1 -1
  54. package/src/engine/feature-ast/__tests__/canonical-form.test.ts +3 -1
  55. package/src/engine/feature-ast/__tests__/parse-happy-path.test.ts +2 -2
  56. package/src/engine/feature-ast/__tests__/parse.test.ts +106 -4
  57. package/src/engine/feature-ast/__tests__/patcher.test.ts +3 -3
  58. package/src/engine/feature-ast/__tests__/read-optional-access-rule.test.ts +4 -4
  59. package/src/engine/feature-ast/__tests__/render-roundtrip.test.ts +5 -1
  60. package/src/engine/feature-ast/extractors/handlers.ts +16 -2
  61. package/src/engine/feature-ast/extractors/hooks.ts +25 -4
  62. package/src/engine/feature-ast/patcher.ts +15 -3
  63. package/src/engine/feature-ast/patterns.ts +4 -0
  64. package/src/engine/feature-ast/render.ts +3 -0
  65. package/src/engine/pattern-library/shared-fields.ts +7 -3
  66. package/src/entrypoint/__tests__/entrypoint-attach-dispatcher.integration.test.ts +1 -1
  67. package/src/entrypoint/__tests__/entrypoint-job-wiring.integration.test.ts +3 -3
  68. package/src/entrypoint/__tests__/split-deploy.integration.test.ts +1 -1
  69. package/src/es-ops/__tests__/runner.integration.test.ts +2 -2
  70. package/src/jobs/__tests__/job-event-trigger.integration.test.ts +2 -2
  71. package/src/jobs/__tests__/job-multi-trigger.integration.test.ts +2 -2
  72. package/src/jobs/__tests__/jobs.integration.test.ts +1 -1
  73. package/src/observability/__tests__/observability.integration.test.ts +7 -7
  74. package/src/pipeline/__tests__/archive-stream.integration.test.ts +2 -2
  75. package/src/pipeline/__tests__/dispatcher.test.ts +15 -15
  76. package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +10 -10
  77. package/src/pipeline/__tests__/load-aggregate-query.integration.test.ts +1 -1
  78. package/src/pipeline/__tests__/post-query-hook.integration.test.ts +3 -3
  79. package/src/pipeline/__tests__/query-projection.integration.test.ts +3 -3
  80. package/src/scripts/codemod/migrate-open-to-all.ts +248 -0
  81. package/src/secrets/__tests__/contains-secret.test.ts +5 -5
  82. package/src/stack/__tests__/request-helper.integration.test.ts +3 -3
  83. package/src/stack/__tests__/setup-test-stack-jobs.integration.test.ts +1 -1
  84. package/src/testing/__tests__/e2e-generator.test.ts +19 -5
@@ -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(),
@@ -145,7 +145,7 @@ describe("validateBoot — query output schema column refs (fw#2493)", () => {
145
145
  test("projectionDetail header.title referencing an unknown field throws", () => {
146
146
  const feature = defineFeature("app", (r) => {
147
147
  r.queryHandler("tenant:detail", z.object({}), async () => ({ id: "1", name: "x" }), {
148
- access: { openToAll: true },
148
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
149
149
  outputSchema: z.object({ id: z.string(), name: z.string() }),
150
150
  });
151
151
  r.screen({
@@ -164,7 +164,7 @@ describe("validateBoot — query output schema column refs (fw#2493)", () => {
164
164
  test("projectionDetail header.status referencing an unknown field throws", () => {
165
165
  const feature = defineFeature("app", (r) => {
166
166
  r.queryHandler("tenant:detail", z.object({}), async () => ({ id: "1", name: "x" }), {
167
- access: { openToAll: true },
167
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
168
168
  outputSchema: z.object({ id: z.string(), name: z.string() }),
169
169
  });
170
170
  r.screen({
@@ -181,7 +181,7 @@ describe("validateBoot — query output schema column refs (fw#2493)", () => {
181
181
  test("projectionDetail metrics referencing an unknown field throws", () => {
182
182
  const feature = defineFeature("app", (r) => {
183
183
  r.queryHandler("tenant:detail", z.object({}), async () => ({ id: "1", balance: 0 }), {
184
- access: { openToAll: true },
184
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
185
185
  outputSchema: z.object({ id: z.string(), balance: z.number() }),
186
186
  });
187
187
  r.screen({
@@ -214,7 +214,7 @@ describe("validateBoot — query output schema column refs (fw#2493)", () => {
214
214
  z.object({}),
215
215
  async () => ({ id: "1", name: "x", balance: 0 }),
216
216
  {
217
- access: { openToAll: true },
217
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
218
218
  outputSchema: z.object({
219
219
  id: z.string(),
220
220
  name: z.string(),
@@ -241,7 +241,7 @@ describe("validateBoot — query output schema column refs (fw#2493)", () => {
241
241
  test("dashboard stat panel valueField referencing an unknown field throws", () => {
242
242
  const feature = defineFeature("demo", (r) => {
243
243
  r.queryHandler("open-count", z.object({}), async () => ({ value: "0" }), {
244
- access: { openToAll: true },
244
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
245
245
  outputSchema: z.object({ value: z.string() }),
246
246
  });
247
247
  r.screen({
@@ -269,7 +269,7 @@ describe("validateBoot — query output schema column refs (fw#2493)", () => {
269
269
  test("dashboard stat-group child deltaField referencing an unknown field throws", () => {
270
270
  const feature = defineFeature("demo", (r) => {
271
271
  r.queryHandler("net-worth", z.object({}), async () => ({ value: "0" }), {
272
- access: { openToAll: true },
272
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
273
273
  outputSchema: z.object({ value: z.string() }),
274
274
  });
275
275
  r.screen({
@@ -308,7 +308,7 @@ describe("validateBoot — query output schema column refs (fw#2493)", () => {
308
308
  test("dashboard list panel column not in the query's row shape throws", () => {
309
309
  const feature = defineFeature("demo", (r) => {
310
310
  r.queryHandler("next-events", z.object({}), async () => ({ rows: [], nextCursor: null }), {
311
- access: { openToAll: true },
311
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
312
312
  outputSchema: z.object({
313
313
  rows: z.array(z.object({ title: z.string() })),
314
314
  nextCursor: z.string().nullable(),
@@ -342,7 +342,7 @@ describe("validateBoot — query output schema column refs (fw#2493)", () => {
342
342
  defineQueryHandler({
343
343
  name: "items:detail",
344
344
  schema: z.object({ id: z.string() }),
345
- access: { openToAll: true },
345
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
346
346
  outputSchema: z.object({ id: z.string(), name: z.string() }),
347
347
  handler: async () => ({ id: "1", name: "x" }),
348
348
  }),
@@ -366,7 +366,7 @@ describe("validateBoot — query output schema column refs (fw#2493)", () => {
366
366
  definePagedQueryHandler({
367
367
  name: "items:list",
368
368
  schema: z.object({}),
369
- access: { openToAll: true },
369
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
370
370
  outputSchema: pagedSchema,
371
371
  handler: async () => ({ rows: [], nextCursor: null }),
372
372
  }),
@@ -390,7 +390,7 @@ describe("validateBoot — query output schema column refs (fw#2493)", () => {
390
390
  definePagedQueryHandler({
391
391
  name: "items:list",
392
392
  schema: z.object({}),
393
- access: { openToAll: true },
393
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
394
394
  // Row schema passed directly instead of wrapping it in
395
395
  // { rows: [...], nextCursor } — the mistake fw#2493's follow-up
396
396
  // check catches instead of silently skipping every column check.
@@ -417,7 +417,7 @@ describe("validateBoot — query output schema column refs (fw#2493)", () => {
417
417
  definePagedQueryHandler({
418
418
  name: "items:list",
419
419
  schema: z.object({}),
420
- access: { openToAll: true },
420
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
421
421
  outputSchema: pagedSchema,
422
422
  handler: async () => ({ rows: [], nextCursor: null }),
423
423
  }),
@@ -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", {});
@@ -42,7 +42,7 @@ function stubListHandler(
42
42
  name: `${entityName}:list`,
43
43
  schema: z.object({}),
44
44
  handler: async () => ({ rows: [], nextCursor: null }) as never,
45
- access: { openToAll: true },
45
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
46
46
  });
47
47
  }
48
48
 
@@ -534,7 +534,7 @@ describe("boot-validator", () => {
534
534
  isSuccess: true as const,
535
535
  data: { id: "1" },
536
536
  }),
537
- { access: { openToAll: true } },
537
+ { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
538
538
  );
539
539
  }),
540
540
  ];
@@ -566,7 +566,7 @@ describe("boot-validator", () => {
566
566
  isSuccess: true as const,
567
567
  data: {},
568
568
  }),
569
- { access: { openToAll: true } },
569
+ { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
570
570
  );
571
571
  r.useExtension("credit-cap", "credit");
572
572
  });
@@ -631,7 +631,7 @@ describe("boot-validator", () => {
631
631
  const features = [
632
632
  defineFeature("a", (r) => {
633
633
  r.queryHandler("list", z.object({}), async () => [], {
634
- access: { openToAll: true },
634
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
635
635
  });
636
636
  }),
637
637
  ];
@@ -711,7 +711,7 @@ describe("boot-validator", () => {
711
711
  const features = [
712
712
  defineFeature("a", (r) => {
713
713
  r.streamHandler("chat:complete", z.object({}), async function* () {}, {
714
- access: { openToAll: true },
714
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
715
715
  });
716
716
  }),
717
717
  ];
@@ -2430,7 +2430,7 @@ describe("boot-validator", () => {
2430
2430
  name: "invoice:approve",
2431
2431
  schema: { _type: "stub" } as never,
2432
2432
  handler: async () => ({ isSuccess: true, data: {} }) as never,
2433
- access: { openToAll: true },
2433
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
2434
2434
  });
2435
2435
  r.screen({
2436
2436
  id: "approve-invoice",
@@ -2713,14 +2713,14 @@ describe("boot-validator", () => {
2713
2713
  name: "token:mint",
2714
2714
  schema: { _type: "stub" } as never,
2715
2715
  handler: async () => ({ isSuccess: true, data: {} }) as never,
2716
- access: { openToAll: true },
2716
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
2717
2717
  });
2718
2718
  if (confirmOverride !== undefined) {
2719
2719
  r.writeHandler({
2720
2720
  name: "token:confirm",
2721
2721
  schema: { _type: "stub" } as never,
2722
2722
  handler: async () => ({ isSuccess: true, data: {} }) as never,
2723
- access: { openToAll: true },
2723
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
2724
2724
  });
2725
2725
  }
2726
2726
  r.screen({
@@ -4177,7 +4177,7 @@ describe("boot-validator", () => {
4177
4177
  }),
4178
4178
  );
4179
4179
  r.queryHandler("products", z.object({}), async () => ({ rows: [], nextCursor: null }), {
4180
- access: { openToAll: true },
4180
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
4181
4181
  });
4182
4182
  r.screen({
4183
4183
  id: "product-projection",
@@ -4207,7 +4207,7 @@ describe("boot-validator", () => {
4207
4207
  }),
4208
4208
  );
4209
4209
  r.queryHandler("products", z.object({}), async () => ({ rows: [], nextCursor: null }), {
4210
- access: { openToAll: true },
4210
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
4211
4211
  });
4212
4212
  r.screen({
4213
4213
  id: "product-projection",
@@ -4426,13 +4426,13 @@ describe("boot-validator", () => {
4426
4426
  test("projectionDetail relatedList defaultSort.field sortable → kein Throw", () => {
4427
4427
  const feature = defineFeature("shop", (r) => {
4428
4428
  r.queryHandler("order-detail", z.object({}), async () => ({ id: "o1" }), {
4429
- access: { openToAll: true },
4429
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
4430
4430
  });
4431
4431
  r.queryHandler(
4432
4432
  "order-positions",
4433
4433
  z.object({}),
4434
4434
  async () => ({ rows: [], nextCursor: null }),
4435
- { access: { openToAll: true } },
4435
+ { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
4436
4436
  );
4437
4437
  r.screen({
4438
4438
  id: "order-detail",
@@ -4993,7 +4993,7 @@ describe("boot-validator", () => {
4993
4993
  const targetId = opts.targetId ?? "restock-form";
4994
4994
  return defineFeature("shop", (r) => {
4995
4995
  r.queryHandler("products", z.object({}), async () => ({ rows: [], nextCursor: null }), {
4996
- access: { openToAll: true },
4996
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
4997
4997
  });
4998
4998
  r.writeHandler(
4999
4999
  "restock",
@@ -5223,7 +5223,7 @@ describe("boot-validator — config key backing × scope", () => {
5223
5223
  ],
5224
5224
  });
5225
5225
  r.queryHandler("count", z.object({}), async () => ({ total: 0 }), {
5226
- access: { openToAll: true },
5226
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
5227
5227
  });
5228
5228
  r.screen({
5229
5229
  id: "product-dashboard",
@@ -5268,7 +5268,7 @@ describe("boot-validator — config key backing × scope", () => {
5268
5268
  ],
5269
5269
  });
5270
5270
  r.queryHandler("count", z.object({}), async () => ({ total: 0 }), {
5271
- access: { openToAll: true },
5271
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
5272
5272
  });
5273
5273
  r.screen({
5274
5274
  id: "product-dashboard",
@@ -5317,7 +5317,7 @@ describe("boot-validator — config key backing × scope", () => {
5317
5317
  ],
5318
5318
  });
5319
5319
  r.queryHandler("count", z.object({}), async () => ({ total: 0 }), {
5320
- access: { openToAll: true },
5320
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
5321
5321
  });
5322
5322
  r.screen({
5323
5323
  id: "product-dashboard",
@@ -5542,7 +5542,7 @@ describe("boot-validator — config key backing × scope", () => {
5542
5542
  }),
5543
5543
  );
5544
5544
  r.queryHandler("products", z.object({}), async () => ({ rows: [], nextCursor: null }), {
5545
- access: { openToAll: true },
5545
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
5546
5546
  });
5547
5547
  r.screen({
5548
5548
  id: "product-projection",
@@ -5624,7 +5624,7 @@ describe("boot-validator — config key backing × scope", () => {
5624
5624
  "create",
5625
5625
  z.object({ name: z.string() }),
5626
5626
  async () => ({ isSuccess: true as const, data: { id: "1" } }),
5627
- { access: { openToAll: true } },
5627
+ { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
5628
5628
  );
5629
5629
  r.screen({
5630
5630
  id: "product-list",
@@ -209,7 +209,7 @@ describe("buildAppSchema", () => {
209
209
  r.workspace({
210
210
  id: "admin",
211
211
  label: "Admin",
212
- access: { openToAll: true },
212
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
213
213
  nav: ["orders:nav:list", "fleet:nav:vehicles"],
214
214
  default: true,
215
215
  });
@@ -832,7 +832,11 @@ describe("buildAppSchema", () => {
832
832
  const f = defineFeature("ent", (r) => {
833
833
  r.entity("thing", entity);
834
834
  r.nav({ id: "n", label: "N" });
835
- r.workspace({ id: "ws", label: "Ws", access: { openToAll: true } });
835
+ r.workspace({
836
+ id: "ws",
837
+ label: "Ws",
838
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
839
+ });
836
840
  });
837
841
  const app = buildAppSchema(createRegistry([f]));
838
842
  const json = JSON.stringify(app);
@@ -980,7 +984,7 @@ describe("buildAppSchema", () => {
980
984
  cursor: z.string().optional(),
981
985
  }),
982
986
  async () => ({ rows: [], nextCursor: null }),
983
- { access: { openToAll: true } },
987
+ { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
984
988
  );
985
989
  r.screen({
986
990
  id: "schedule-list",
@@ -1000,7 +1004,7 @@ describe("buildAppSchema", () => {
1000
1004
  test("projectionList: a query schema without search/sort/cursor derives no capability", () => {
1001
1005
  const f = defineFeature("ledger", (r) => {
1002
1006
  r.queryHandler("schedule:list", z.object({}), async () => ({ rows: [], nextCursor: null }), {
1003
- access: { openToAll: true },
1007
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
1004
1008
  });
1005
1009
  r.screen({
1006
1010
  id: "schedule-list",
@@ -1023,7 +1027,7 @@ describe("buildAppSchema", () => {
1023
1027
  "schedule:list",
1024
1028
  z.union([z.object({ a: z.string() }), z.object({ b: z.string() })]),
1025
1029
  async () => ({ rows: [], nextCursor: null }),
1026
- { access: { openToAll: true } },
1030
+ { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
1027
1031
  );
1028
1032
  r.screen({
1029
1033
  id: "schedule-list",
@@ -1052,7 +1056,7 @@ describe("buildAppSchema", () => {
1052
1056
  rows: [],
1053
1057
  nextCursor: null,
1054
1058
  }),
1055
- { access: { openToAll: true } },
1059
+ { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
1056
1060
  );
1057
1061
  r.screen({
1058
1062
  id: "schedule-list",
@@ -1,8 +1,10 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
  import { z } from "zod";
3
3
  import { buildConfigFeatureSchema } from "../build-config-feature-schema";
4
+ import { createTenantConfig } from "../config-helpers";
4
5
  import { defineFeature } from "../define-feature";
5
6
  import { createRegistry } from "../registry";
7
+ import type { AccessRule } from "../types/handlers";
6
8
  import type { NavDefinition } from "../types/nav";
7
9
  import type { ScreenDefinition, SecretsEditScreenDefinition } from "../types/screen";
8
10
 
@@ -114,6 +116,52 @@ describe("buildConfigFeatureSchema — secrets derivation", () => {
114
116
  expect(navById(schema, "audience-tenant")?.access).toEqual({ roles: customRoles });
115
117
  });
116
118
 
119
+ test("audience nav union keeps a granted openToAll's own reason, not the generic ALL_ROLE reason", () => {
120
+ const customReason = "billing key rotation is safe for any signed-in tenant member";
121
+ const secretsOpenToAll = defineFeature("secrets", (r) => {
122
+ r.writeHandler(
123
+ "set",
124
+ z.object({ key: z.string(), value: z.string() }),
125
+ async () => ({ isSuccess: true, data: null }),
126
+ { access: { openToAll: { reason: customReason } } },
127
+ );
128
+ });
129
+ const billing = defineFeature("billing", (r) => {
130
+ r.config({
131
+ keys: { apiKey: createTenantConfig("text", { mask: { title: "billing.api-key" } }) },
132
+ });
133
+ r.secret("apiKey", { label: { en: "Billing API Key" }, scope: "tenant" });
134
+ });
135
+ const schema = buildConfigFeatureSchema(createRegistry([secretsOpenToAll, billing]));
136
+
137
+ expect(navById(schema, "audience-tenant")?.access).toEqual({
138
+ openToAll: { reason: customReason },
139
+ });
140
+ });
141
+
142
+ test("audience nav union ignores a malformed openToAll — falls back to the role union", () => {
143
+ const secretsMalformedOpenToAll = defineFeature("secrets", (r) => {
144
+ r.writeHandler(
145
+ "set",
146
+ z.object({ key: z.string(), value: z.string() }),
147
+ async () => ({ isSuccess: true, data: null }),
148
+ // @cast-boundary test — simulates JSON/Designer input that doesn't match the static union
149
+ { access: { openToAll: {} } as unknown as AccessRule },
150
+ );
151
+ });
152
+ const billing = defineFeature("billing", (r) => {
153
+ r.config({
154
+ keys: { apiKey: createTenantConfig("text", { mask: { title: "billing.api-key" } }) },
155
+ });
156
+ r.secret("apiKey", { label: { en: "Billing API Key" }, scope: "tenant" });
157
+ });
158
+ const schema = buildConfigFeatureSchema(createRegistry([secretsMalformedOpenToAll, billing]));
159
+
160
+ expect(navById(schema, "audience-tenant")?.access).toEqual({
161
+ roles: ["TenantAdmin", "Admin", "SystemAdmin"],
162
+ });
163
+ });
164
+
117
165
  test("declared secrets without a mounted secrets feature yield no secretsEdit screen and no secrets nav", () => {
118
166
  const stripe = defineFeature("stripe", (r) => {
119
167
  r.secret("apiKey", { label: { en: "Stripe API Key" }, scope: "tenant" });
@@ -8,6 +8,7 @@ import {
8
8
  } from "../config-helpers";
9
9
  import { defineFeature } from "../define-feature";
10
10
  import { createRegistry } from "../registry";
11
+ import { isOpenToAllGranted } from "../types/handlers";
11
12
  import type { NavDefinition } from "../types/nav";
12
13
  import type { ConfigEditScreenDefinition, ScreenDefinition } from "../types/screen";
13
14
 
@@ -265,7 +266,15 @@ describe("buildConfigFeatureSchema — access + workspace", () => {
265
266
  roles: ["TenantAdmin", "Admin", "SystemAdmin"],
266
267
  });
267
268
  // digest is createUserConfig → write access.all (["all"]) → openToAll
268
- expect(configScreen("notify-user").access).toEqual({ openToAll: true });
269
+ const notifyUserAccess = configScreen("notify-user").access;
270
+ expect(notifyUserAccess).toEqual({
271
+ openToAll: {
272
+ reason:
273
+ 'config key declares the "all" role, so every signed-in user of the tenant may read/write it',
274
+ },
275
+ });
276
+ // The synthesized reason must actually grant access, not just look configured.
277
+ expect(notifyUserAccess !== undefined && isOpenToAllGranted(notifyUserAccess)).toBe(true);
269
278
  });
270
279
 
271
280
  test("a screen mixing an openToAll key with role-restricted keys collapses to openToAll", () => {
@@ -283,8 +292,18 @@ describe("buildConfigFeatureSchema — access + workspace", () => {
283
292
  const screen = out.screens.find((s) => s.id === "mixedwrite-tenant");
284
293
  if (screen?.type !== "configEdit")
285
294
  throw new Error('expected configEdit screen "mixedwrite-tenant"');
286
- expect(screen.access).toEqual({ openToAll: true });
287
- expect(out.navs.find((n) => n.id === "audience-tenant")?.access).toEqual({ openToAll: true });
295
+ expect(screen.access).toEqual({
296
+ openToAll: {
297
+ reason:
298
+ 'config key declares the "all" role, so every signed-in user of the tenant may read/write it',
299
+ },
300
+ });
301
+ expect(out.navs.find((n) => n.id === "audience-tenant")?.access).toEqual({
302
+ openToAll: {
303
+ reason:
304
+ 'config key declares the "all" role, so every signed-in user of the tenant may read/write it',
305
+ },
306
+ });
288
307
  });
289
308
 
290
309
  test("openToAll collapse holds regardless of key declaration order (516/2)", () => {
@@ -305,7 +324,12 @@ describe("buildConfigFeatureSchema — access + workspace", () => {
305
324
  const screen = out.screens.find((s) => s.id === "mixedwritereversed-tenant");
306
325
  if (screen?.type !== "configEdit")
307
326
  throw new Error('expected configEdit screen "mixedwritereversed-tenant"');
308
- expect(screen.access).toEqual({ openToAll: true });
327
+ expect(screen.access).toEqual({
328
+ openToAll: {
329
+ reason:
330
+ 'config key declares the "all" role, so every signed-in user of the tenant may read/write it',
331
+ },
332
+ });
309
333
  });
310
334
 
311
335
  test("returns empty (no workspace) when no key opts into the hub via mask", () => {
@@ -343,7 +367,12 @@ describe("buildConfigFeatureSchema — access + workspace", () => {
343
367
  test("workspace access is the union of write roles across all hub keys", () => {
344
368
  // billing/notify tenant keys → admin write; notify-user digest → write all.
345
369
  // Any `all`-writable key collapses the whole switcher entry to openToAll.
346
- expect(schema.workspace?.definition.access).toEqual({ openToAll: true });
370
+ expect(schema.workspace?.definition.access).toEqual({
371
+ openToAll: {
372
+ reason:
373
+ 'config key declares the "all" role, so every signed-in user of the tenant may read/write it',
374
+ },
375
+ });
347
376
  });
348
377
 
349
378
  test("workspace access stays role-gated when no hub key is world-writable", () => {