@cosmicdrift/kumiko-framework 0.269.1 → 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 (99) 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-db-raw/global-fetch.expected.ts +9 -0
  6. package/src/__tests__/fixtures/migrate-db-raw/global-fetch.input.ts +10 -0
  7. package/src/__tests__/fixtures/migrate-db-raw/global-select.expected.ts +6 -0
  8. package/src/__tests__/fixtures/migrate-db-raw/global-select.input.ts +12 -0
  9. package/src/__tests__/fixtures/migrate-db-raw/import-pruning.expected.ts +12 -0
  10. package/src/__tests__/fixtures/migrate-db-raw/import-pruning.input.ts +12 -0
  11. package/src/__tests__/fixtures/migrate-db-raw/manual-only.expected.ts +26 -0
  12. package/src/__tests__/fixtures/migrate-db-raw/manual-only.input.ts +26 -0
  13. package/src/__tests__/fixtures/migrate-db-raw/own-tenant-const-shorthand.expected.ts +10 -0
  14. package/src/__tests__/fixtures/migrate-db-raw/own-tenant-const-shorthand.input.ts +11 -0
  15. package/src/__tests__/fixtures/migrate-db-raw/own-tenant-event.expected.ts +9 -0
  16. package/src/__tests__/fixtures/migrate-db-raw/own-tenant-event.input.ts +15 -0
  17. package/src/__tests__/fixtures/migrate-open-to-all/manual-only.expected.ts +16 -0
  18. package/src/__tests__/fixtures/migrate-open-to-all/manual-only.input.ts +16 -0
  19. package/src/__tests__/fixtures/migrate-open-to-all/test-nested-shorthand.expected.ts +12 -0
  20. package/src/__tests__/fixtures/migrate-open-to-all/test-nested-shorthand.input.ts +12 -0
  21. package/src/__tests__/fixtures/migrate-open-to-all/test-simple.expected.ts +7 -0
  22. package/src/__tests__/fixtures/migrate-open-to-all/test-simple.input.ts +7 -0
  23. package/src/__tests__/full-stack.integration.test.ts +2 -2
  24. package/src/__tests__/migrate-db-raw-codemod.test.ts +132 -0
  25. package/src/__tests__/migrate-open-to-all-codemod.test.ts +84 -0
  26. package/src/__tests__/transition-guard.integration.test.ts +7 -7
  27. package/src/__tests__/upgrade-cli.test.ts +54 -0
  28. package/src/api/__tests__/api.test.ts +2 -2
  29. package/src/api/__tests__/body-limit.test.ts +1 -1
  30. package/src/api/__tests__/pii-leak-guard.integration.test.ts +2 -2
  31. package/src/api/__tests__/request-locale.integration.test.ts +1 -1
  32. package/src/api/__tests__/server-error-logging.test.ts +1 -1
  33. package/src/changes.json +10 -2
  34. package/src/db/__tests__/list-filter-field-access.integration.test.ts +1 -1
  35. package/src/derivatives/__tests__/derivatives-context.integration.test.ts +2 -2
  36. package/src/engine/__tests__/boot-validator-dashboard.test.ts +5 -5
  37. package/src/engine/__tests__/boot-validator-pii-retention.test.ts +1 -1
  38. package/src/engine/__tests__/boot-validator-projection-list.test.ts +17 -17
  39. package/src/engine/__tests__/boot-validator-query-output-schema.test.ts +18 -18
  40. package/src/engine/__tests__/boot-validator-query-refs.test.ts +3 -3
  41. package/src/engine/__tests__/boot-validator-s0-integration.test.ts +2 -2
  42. package/src/engine/__tests__/boot-validator.test.ts +18 -18
  43. package/src/engine/__tests__/build-app-schema.test.ts +10 -6
  44. package/src/engine/__tests__/build-config-feature-schema-secrets.test.ts +48 -0
  45. package/src/engine/__tests__/build-config-feature-schema.test.ts +34 -5
  46. package/src/engine/__tests__/engine.test.ts +39 -25
  47. package/src/engine/__tests__/entity-handlers.test.ts +19 -8
  48. package/src/engine/__tests__/entity-presave-wiring.integration.test.ts +2 -2
  49. package/src/engine/__tests__/extends-registrar.test.ts +3 -3
  50. package/src/engine/__tests__/feature-crud-shorthand.test.ts +9 -3
  51. package/src/engine/__tests__/feature-crud-verb-access.integration.test.ts +1 -1
  52. package/src/engine/__tests__/hook-phases.test.ts +5 -5
  53. package/src/engine/__tests__/lifecycle-hooks.test.ts +9 -3
  54. package/src/engine/__tests__/multiselect-filter.integration.test.ts +1 -1
  55. package/src/engine/__tests__/post-query-hook.test.ts +6 -2
  56. package/src/engine/__tests__/projection-detail-actions.test.ts +2 -2
  57. package/src/engine/__tests__/projection-detail-singleton.test.ts +3 -3
  58. package/src/engine/__tests__/projection-detail-tabs.test.ts +3 -3
  59. package/src/engine/__tests__/projection-list-filter.integration.test.ts +1 -1
  60. package/src/engine/__tests__/registry.test.ts +19 -13
  61. package/src/engine/__tests__/screen.test.ts +26 -20
  62. package/src/engine/boot-validator/__tests__/access-declarations.test.ts +5 -4
  63. package/src/engine/boot-validator/__tests__/parent-ref.test.ts +18 -6
  64. package/src/engine/boot-validator/access-declarations.ts +3 -5
  65. package/src/engine/boot-validator/nav.ts +4 -3
  66. package/src/engine/build-config-feature-schema.ts +9 -2
  67. package/src/engine/entity-handlers.ts +1 -1
  68. package/src/engine/feature-ast/__tests__/canonical-form.test.ts +3 -1
  69. package/src/engine/feature-ast/__tests__/parse-happy-path.test.ts +2 -2
  70. package/src/engine/feature-ast/__tests__/parse.test.ts +106 -4
  71. package/src/engine/feature-ast/__tests__/patcher.test.ts +3 -3
  72. package/src/engine/feature-ast/__tests__/read-optional-access-rule.test.ts +4 -4
  73. package/src/engine/feature-ast/__tests__/render-roundtrip.test.ts +5 -1
  74. package/src/engine/feature-ast/extractors/handlers.ts +16 -2
  75. package/src/engine/feature-ast/extractors/hooks.ts +25 -4
  76. package/src/engine/feature-ast/patcher.ts +15 -3
  77. package/src/engine/feature-ast/patterns.ts +4 -0
  78. package/src/engine/feature-ast/render.ts +3 -0
  79. package/src/engine/pattern-library/shared-fields.ts +7 -3
  80. package/src/entrypoint/__tests__/entrypoint-attach-dispatcher.integration.test.ts +1 -1
  81. package/src/entrypoint/__tests__/entrypoint-job-wiring.integration.test.ts +3 -3
  82. package/src/entrypoint/__tests__/split-deploy.integration.test.ts +1 -1
  83. package/src/es-ops/__tests__/runner.integration.test.ts +2 -2
  84. package/src/jobs/__tests__/job-event-trigger.integration.test.ts +2 -2
  85. package/src/jobs/__tests__/job-multi-trigger.integration.test.ts +2 -2
  86. package/src/jobs/__tests__/jobs.integration.test.ts +1 -1
  87. package/src/observability/__tests__/observability.integration.test.ts +7 -7
  88. package/src/pipeline/__tests__/archive-stream.integration.test.ts +2 -2
  89. package/src/pipeline/__tests__/dispatcher.test.ts +15 -15
  90. package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +10 -10
  91. package/src/pipeline/__tests__/load-aggregate-query.integration.test.ts +1 -1
  92. package/src/pipeline/__tests__/post-query-hook.integration.test.ts +3 -3
  93. package/src/pipeline/__tests__/query-projection.integration.test.ts +3 -3
  94. package/src/scripts/codemod/migrate-db-raw.ts +397 -0
  95. package/src/scripts/codemod/migrate-open-to-all.ts +248 -0
  96. package/src/secrets/__tests__/contains-secret.test.ts +5 -5
  97. package/src/stack/__tests__/request-helper.integration.test.ts +3 -3
  98. package/src/stack/__tests__/setup-test-stack-jobs.integration.test.ts +1 -1
  99. package/src/testing/__tests__/e2e-generator.test.ts +19 -5
@@ -3,8 +3,10 @@ import {
3
3
  existsSync,
4
4
  mkdirSync,
5
5
  mkdtempSync,
6
+ readdirSync,
6
7
  readFileSync,
7
8
  rmSync,
9
+ statSync,
8
10
  symlinkSync,
9
11
  writeFileSync,
10
12
  } from "node:fs";
@@ -12,6 +14,8 @@ import { tmpdir } from "node:os";
12
14
  import { join } from "node:path";
13
15
  import {
14
16
  findCodemodScriptsRoot,
17
+ findCoreChangelogFile,
18
+ findFeaturesDirs,
15
19
  resolveCodemodScript,
16
20
  runUpgradeCli,
17
21
  type UpgradeCliOut,
@@ -592,3 +596,53 @@ describe("upgrade command — filter baseline is the marker, not the installed v
592
596
  expect(result.pending.map((e) => e.title)).toEqual(["explicit-from-test-entry"]);
593
597
  });
594
598
  });
599
+
600
+ describe("changes.json codemod fields resolve to real published scripts", () => {
601
+ function findChangesJsonFiles(dir: string): string[] {
602
+ const found: string[] = [];
603
+ for (const name of readdirSync(dir)) {
604
+ if (name === "node_modules") continue;
605
+ const full = join(dir, name);
606
+ if (statSync(full).isDirectory()) {
607
+ found.push(...findChangesJsonFiles(full));
608
+ } else if (name === "changes.json") {
609
+ found.push(full);
610
+ }
611
+ }
612
+ return found;
613
+ }
614
+
615
+ test("every codemod field is a scripts/codemod/*.ts path that resolves to an existing published script", () => {
616
+ const coreChangelog = findCoreChangelogFile(REAL_REPO_ROOT);
617
+ const changesJsonFiles = [
618
+ ...(coreChangelog ? [coreChangelog] : []),
619
+ ...findFeaturesDirs(REAL_REPO_ROOT).flatMap((dir) => findChangesJsonFiles(dir)),
620
+ ];
621
+ expect(changesJsonFiles.length).toBeGreaterThan(0);
622
+
623
+ const offenders: string[] = [];
624
+ for (const file of changesJsonFiles) {
625
+ const entries = JSON.parse(readFileSync(file, "utf-8")) as ReadonlyArray<{
626
+ readonly version: string;
627
+ readonly title: string;
628
+ readonly codemod?: string;
629
+ }>;
630
+ for (const entry of entries) {
631
+ if (typeof entry.codemod !== "string") continue;
632
+ if (/\s/.test(entry.codemod)) {
633
+ offenders.push(
634
+ `${file} · ${entry.version} "${entry.title}": codemod is a shell command, not a path: "${entry.codemod}"`,
635
+ );
636
+ continue;
637
+ }
638
+ if (resolveCodemodScript(REAL_REPO_ROOT, entry.codemod) === null) {
639
+ offenders.push(
640
+ `${file} · ${entry.version} "${entry.title}": codemod path does not resolve: "${entry.codemod}"`,
641
+ );
642
+ }
643
+ }
644
+ }
645
+
646
+ expect(offenders).toEqual([]);
647
+ });
648
+ });
@@ -37,7 +37,7 @@ const testFeature = defineFeature("test", (r) => {
37
37
  "item:list",
38
38
  z.object({ search: z.string().optional() }),
39
39
  async () => [{ id: 1, name: "Test" }],
40
- { access: { openToAll: true } },
40
+ { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
41
41
  );
42
42
 
43
43
  r.streamHandler(
@@ -750,7 +750,7 @@ describe("feature-declared HTTP routes (r.httpRoute)", () => {
750
750
  }),
751
751
  );
752
752
  r.queryHandler("item:list", z.object({}), async () => [{ id: 7 }], {
753
- access: { openToAll: true },
753
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
754
754
  });
755
755
  r.httpRoute({
756
756
  method: "GET",
@@ -18,7 +18,7 @@ const testFeature = defineFeature("blob", (r) => {
18
18
  "note:create",
19
19
  z.object({ body: z.string() }),
20
20
  async (event) => ({ isSuccess: true, data: { body: event.payload.body } }),
21
- { access: { openToAll: true } },
21
+ { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
22
22
  );
23
23
  });
24
24
 
@@ -18,7 +18,7 @@ const leakyFeature = defineFeature("leaky", (r) => {
18
18
  defineQueryHandler({
19
19
  name: "raw",
20
20
  schema: z.object({}),
21
- access: { openToAll: true },
21
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
22
22
  handler: async () => ({ email: CIPHERTEXT, note: "plain" }),
23
23
  }),
24
24
  );
@@ -26,7 +26,7 @@ const leakyFeature = defineFeature("leaky", (r) => {
26
26
  defineQueryHandler({
27
27
  name: "clean",
28
28
  schema: z.object({}),
29
- access: { openToAll: true },
29
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
30
30
  handler: async () => ({ email: "marc@example.com" }),
31
31
  }),
32
32
  );
@@ -18,7 +18,7 @@ const localeProbe = defineFeature("locale-probe", (r) => {
18
18
  "read-locale",
19
19
  z.object({}),
20
20
  async (_event, ctx) => ({ isSuccess: true, data: { locale: ctx.locale } }),
21
- { access: { openToAll: true } },
21
+ { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
22
22
  );
23
23
  });
24
24
 
@@ -20,7 +20,7 @@ const boomFeature = defineFeature("boom", (r) => {
20
20
  async () => {
21
21
  throw new Error("disk on fire");
22
22
  },
23
- { access: { openToAll: true } },
23
+ { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
24
24
  );
25
25
  });
26
26
 
package/src/changes.json CHANGED
@@ -1,4 +1,12 @@
1
1
  [
2
+ {
3
+ "version": "0.269.0",
4
+ "type": "breaking",
5
+ "title": "`openToAll: true` is removed — every openToAll grant now requires { reason } (fw#2858).",
6
+ "detail": "`OpenToAllAccessRule` (`@cosmicdrift/kumiko-types/handlers`) no longer accepts `true`; only `OpenToAllDeclaration` (`{ reason: string; personalData?: \"tenant-members\" }`) satisfies it, so `{ openToAll: true }` no longer type-checks. `isOpenToAllGranted` denies `true` from untyped sources (pattern JSON, Designer) the same way it already denied a malformed object — a bare `true` reaching it now returns `false` instead of granting access. The boot validator (`engine/boot-validator/access-declarations.ts`) rejects an untyped `openToAll: true` access declaration with an error pointing at `{ reason }` instead of registering the handler. The pattern-library Designer access field (`shared-fields.ts` `accessRuleField`) is now a required text input on `access.openToAll.reason` instead of a boolean toggle, so the Designer can only ever produce the object form. `build-config-feature-schema.ts` synthesizes `{ openToAll: { reason: \"config key declares the \\\"all\\\" role, so every signed-in user of the tenant may read/write it\" } }` for a config key whose roles include `\"all\"`, instead of `{ openToAll: true }`. `boot-validator/nav.ts` and `renderer-web/app/create-app.tsx` switched from `\"openToAll\" in access` to `isOpenToAllGranted(access)`, so a malformed or `true` rule is no longer treated as a visible screen/workspace. The feature-AST extractor now also extracts `escapeHatch: { reason }` on write/query handlers (`extractors/handlers.ts`) and on `r.hook` options (`extractors/hooks.ts`), round-tripped through render/patch the same way `access`/`rateLimit` already were.",
7
+ "migration": "Replace every `openToAll: true` with `openToAll: { reason: \"<who may call it and why that is safe without a role>\" }`, adding `personalData: \"tenant-members\"` where the boot validator requires it for a write handler accepting unbound personal data. Pattern-library or Designer-authored JSON that still has `openToAll: true` must be re-saved with a reason before it type-checks or passes boot. `kumiko-upgrade --apply` runs the migrate-open-to-all codemod automatically, rewriting test files to the default reason and listing every non-test site for manual review; run `bun packages/framework/src/scripts/codemod/migrate-open-to-all.ts --test-reason \"<text>\" <paths>` directly to override the default test reason.",
8
+ "codemod": "scripts/codemod/migrate-open-to-all.ts"
9
+ },
2
10
  {
3
11
  "version": "0.269.0",
4
12
  "type": "breaking",
@@ -17,8 +25,8 @@
17
25
  "type": "breaking",
18
26
  "title": "ctx.db.unsafeRaw(reason) gated by escapeHatch, tenancy: \"global\" invariants, bundled features migrated off ctx.db.raw (fw#2858).",
19
27
  "detail": "`TenantDb.unsafeRaw(reason)` returns the unfiltered `DbRunner` only when the calling write or query handler declares `escapeHatch: { reason }` (lifecycle hooks: `r.hook(..., { escapeHatch })`; a hook no longer inherits the handler's grant, same as the #2859 SYSTEM identity-switch gate). Without a declaration it throws `AccessDeniedError`; an empty reason throws `Error`. `r.systemScope()` features keep using `ctx.systemDb.unsafeRaw(reason)`. `TenantDb.raw` stays deprecated until fw#2860. `createTenantDb`'s 7th parameter changed from an `EscapeHatchDeclaration` to `TenantDbGrants` `{ globalWrites?, unsafeRaw? }`; `withUnsafeRawGrant(tenantDb, grant)` rebinds the unsafeRaw grant. `bindHookIdentitySwitchGrant`/`withHookIdentitySwitchGrant` are renamed to `bindHookEscapeHatchGrant`/`withHookEscapeHatchGrant`. Global tenancy: a `tenancy: \"global\"` entity must declare `systemStream: true` (boot validator + executor) and its create/update payloads may not carry a non-SYSTEM `tenantId`; tenant-mode `insertOne` into a global table with a `tenant_id` column and `db.global(table)` writes with a non-SYSTEM `tenantId` are rejected; `defineUnmanagedTable({ tenancy: \"global\" })` rejects a `tenant_id` column; new `declareGlobalTenancy(table)` declares a plain `table()` store without `tenant_id` as global. Bundled features: `userEntity` is `tenancy: \"global\"`, `store_global_feature_state` is declared global, and every bundled `ctx.db.raw` call site now uses `ctx.db.global(table)`, a filtered `ctx.db` method, or `ctx.db.unsafeRaw(reason)` with an `escapeHatch` on the handler. Handlers that call the auth-mfa `mfaStatusChecker`/MFA code verifier callbacks, handlers wrapped by cap-counter `withStockCap` (declares it automatically) and callers of the custom-fields quota/field-access query helpers now need that declaration.",
20
- "migration": "Replace each `ctx.db.raw` use: reads of a `tenancy: \"global\"` table → `ctx.db.global(table).selectMany/fetchOne(...)`; reads/inserts whose where/values only use the caller's own tenantId → `ctx.db.selectMany/fetchOne/insertOne(table, ...)` (method form); everything else → `ctx.db.unsafeRaw(\"<what is read/written and why the tenant filter cannot apply>\")` plus `escapeHatch: { reason }` on the write/query handler definition or in the `r.hook` options. Generic reasons (todo, legacy, migration) fail infra#788. `bun scripts/migrate-db-raw.ts --dry-run <paths>` rewrites the global-table and own-tenant cases and lists the remaining sites; drop `--dry-run` to apply. Custom handlers calling `mfaStatusChecker`/the MFA code verifier or the custom-fields query helpers must declare `escapeHatch`. Direct `createTenantDb(..., escapeHatch)` calls pass `{ globalWrites: escapeHatch }` (and `unsafeRaw` if needed). Rename imports of `bindHookIdentitySwitchGrant`/`withHookIdentitySwitchGrant`. `defineUnmanagedTable({ tenancy: \"global\" })` definitions must drop their `tenant_id` column; `tenancy: \"global\"` entities must add `systemStream: true`.",
21
- "codemod": "bun scripts/migrate-db-raw.ts --dry-run <paths>"
28
+ "migration": "Replace each `ctx.db.raw` use: reads of a `tenancy: \"global\"` table → `ctx.db.global(table).selectMany/fetchOne(...)`; reads/inserts whose where/values only use the caller's own tenantId → `ctx.db.selectMany/fetchOne/insertOne(table, ...)` (method form); everything else → `ctx.db.unsafeRaw(\"<what is read/written and why the tenant filter cannot apply>\")` plus `escapeHatch: { reason }` on the write/query handler definition or in the `r.hook` options. Generic reasons (todo, legacy, migration) fail infra#788. `kumiko-upgrade --apply` runs the migrate-db-raw codemod automatically, rewriting the global-table and own-tenant cases and listing the remaining sites for manual review. Custom handlers calling `mfaStatusChecker`/the MFA code verifier or the custom-fields query helpers must declare `escapeHatch`. Direct `createTenantDb(..., escapeHatch)` calls pass `{ globalWrites: escapeHatch }` (and `unsafeRaw` if needed). Rename imports of `bindHookIdentitySwitchGrant`/`withHookIdentitySwitchGrant`. `defineUnmanagedTable({ tenancy: \"global\" })` definitions must drop their `tenant_id` column; `tenancy: \"global\"` entities must add `systemStream: true`.",
29
+ "codemod": "scripts/codemod/migrate-db-raw.ts"
22
30
  },
23
31
  {
24
32
  "version": "0.267.0",
@@ -36,7 +36,7 @@ const LIST_QN = "filternotes:query:note:list";
36
36
  const filterNotesFeature = defineFeature("filternotes", (r) => {
37
37
  r.crud("note", filterNoteEntity, {
38
38
  write: { access: { roles: ["Admin"] } },
39
- read: { access: { openToAll: true } },
39
+ read: { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
40
40
  });
41
41
  });
42
42
 
@@ -38,7 +38,7 @@ const derivativesTestFeature = defineFeature("derivativestest", (r) => {
38
38
  );
39
39
  return { isSuccess: true as const, data: result };
40
40
  },
41
- { access: { openToAll: true } },
41
+ { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
42
42
  );
43
43
 
44
44
  // Catches the error INSIDE the handler (not via HTTP serialization) so the
@@ -69,7 +69,7 @@ const derivativesTestFeature = defineFeature("derivativestest", (r) => {
69
69
  };
70
70
  }
71
71
  },
72
- { access: { openToAll: true } },
72
+ { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
73
73
  );
74
74
 
75
75
  r.job("record", { trigger: { manual: true }, runIn: "worker" }, async (payload, ctx) => {
@@ -19,10 +19,10 @@ function dashboardFeature(
19
19
  ) {
20
20
  return defineFeature("demo", (r) => {
21
21
  r.queryHandler("incident:open-count", z.object({}), async () => ({ count: 3 }), {
22
- access: { openToAll: true },
22
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
23
23
  });
24
24
  r.queryHandler("incident:latest", z.object({}), async () => ({ rows: [], nextCursor: null }), {
25
- access: { openToAll: true },
25
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
26
26
  });
27
27
  r.screen({
28
28
  id: "overview",
@@ -245,10 +245,10 @@ describe("validateBoot — dashboard screens", () => {
245
245
 
246
246
  const catalogFeature = defineFeature("catalog", (r) => {
247
247
  r.queryHandler("items:list", z.object({}), async () => ({ rows: [], nextCursor: null }), {
248
- access: { openToAll: true },
248
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
249
249
  });
250
250
  r.queryHandler("items:status", z.object({}), async () => ({ enabled: true }), {
251
- access: { openToAll: true },
251
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
252
252
  });
253
253
  r.screen({
254
254
  id: "items",
@@ -289,7 +289,7 @@ describe("validateBoot — dashboard screen panels (fw#2841)", () => {
289
289
  test("accepts a same-feature short id", () => {
290
290
  const feature = defineFeature("demo", (r) => {
291
291
  r.queryHandler("items:list", z.object({}), async () => ({ rows: [], nextCursor: null }), {
292
- access: { openToAll: true },
292
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
293
293
  });
294
294
  r.screen({
295
295
  id: "items",
@@ -50,7 +50,7 @@ function stubListHandler(r: any, entityName: string): void {
50
50
  name: `${entityName}:list`,
51
51
  schema: z.object({}),
52
52
  handler: async () => ({ rows: [], nextCursor: null }) as never,
53
- access: { openToAll: true },
53
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
54
54
  });
55
55
  }
56
56
 
@@ -7,7 +7,7 @@ describe("validateBoot — projectionList screens", () => {
7
7
  test("rejects hand-written searchable:true when the query schema has no search param (3a)", () => {
8
8
  const feature = defineFeature("ledger", (r) => {
9
9
  r.queryHandler("schedule:list", z.object({}), async () => ({ rows: [], nextCursor: null }), {
10
- access: { openToAll: true },
10
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
11
11
  });
12
12
  r.screen({
13
13
  id: "schedule-list",
@@ -29,7 +29,7 @@ describe("validateBoot — projectionList screens", () => {
29
29
  "schedule:list",
30
30
  z.object({ search: z.string().optional() }),
31
31
  async () => ({ rows: [], nextCursor: null }),
32
- { access: { openToAll: true } },
32
+ { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
33
33
  );
34
34
  r.screen({
35
35
  id: "schedule-list",
@@ -50,7 +50,7 @@ describe("validateBoot — projectionList screens", () => {
50
50
  "schedule:list",
51
51
  z.object({ sort: z.string().optional() }),
52
52
  async () => ({ rows: [], nextCursor: null }),
53
- { access: { openToAll: true } },
53
+ { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
54
54
  );
55
55
  r.screen({
56
56
  id: "schedule-list",
@@ -71,7 +71,7 @@ describe("validateBoot — projectionList screens", () => {
71
71
  "schedule:list",
72
72
  z.object({ search: z.string().optional(), sort: z.string().optional() }),
73
73
  async () => ({ rows: [], nextCursor: null }),
74
- { access: { openToAll: true } },
74
+ { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
75
75
  );
76
76
  r.screen({
77
77
  id: "schedule-list",
@@ -91,7 +91,7 @@ describe("validateBoot — projectionList screens", () => {
91
91
  test("passes when the schema offers neither search nor sort and no defaultSort is set", () => {
92
92
  const feature = defineFeature("ledger", (r) => {
93
93
  r.queryHandler("schedule:list", z.object({}), async () => ({ rows: [], nextCursor: null }), {
94
- access: { openToAll: true },
94
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
95
95
  });
96
96
  r.screen({
97
97
  id: "schedule-list",
@@ -112,7 +112,7 @@ describe("validateBoot — projectionList screens", () => {
112
112
  "schedule:list",
113
113
  z.object({ search: z.string().optional() }),
114
114
  async () => ({ rows: [], nextCursor: null }),
115
- { access: { openToAll: true } },
115
+ { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
116
116
  );
117
117
  r.screen({
118
118
  id: "schedule-list",
@@ -135,7 +135,7 @@ describe("validateBoot — projectionList screens", () => {
135
135
  "schedule:list",
136
136
  z.object({ search: z.string().optional() }),
137
137
  async () => ({ rows: [], nextCursor: null }),
138
- { access: { openToAll: true } },
138
+ { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
139
139
  );
140
140
  r.screen({
141
141
  id: "download-attempt-list",
@@ -154,7 +154,7 @@ describe("validateBoot — projectionList screens", () => {
154
154
  test("rejects hand-authored sortable on a projectionList screen (fw#2165 review)", () => {
155
155
  const feature = defineFeature("ledger", (r) => {
156
156
  r.queryHandler("schedule:list", z.object({}), async () => ({ rows: [], nextCursor: null }), {
157
- access: { openToAll: true },
157
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
158
158
  });
159
159
  r.screen({
160
160
  id: "schedule-list",
@@ -173,7 +173,7 @@ describe("validateBoot — projectionList screens", () => {
173
173
  test("rejects hand-authored paginated on a projectionList screen (fw#2165 review)", () => {
174
174
  const feature = defineFeature("ledger", (r) => {
175
175
  r.queryHandler("schedule:list", z.object({}), async () => ({ rows: [], nextCursor: null }), {
176
- access: { openToAll: true },
176
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
177
177
  });
178
178
  r.screen({
179
179
  id: "schedule-list",
@@ -202,7 +202,7 @@ describe("validateBoot — projectionList screens", () => {
202
202
  z.object({}),
203
203
  async () => ({ rows: [], nextCursor: null }),
204
204
  {
205
- access: { openToAll: true },
205
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
206
206
  },
207
207
  );
208
208
  r.screen({ id: "schedule-detail", type: "custom", renderer: { react: "stub" } });
@@ -251,7 +251,7 @@ describe("validateBoot — projectionList screens", () => {
251
251
  "schedule:list",
252
252
  z.object({ filter: z.unknown().optional() }),
253
253
  async () => ({ rows: [], nextCursor: null }),
254
- { access: { openToAll: true } },
254
+ { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
255
255
  );
256
256
  r.screen({
257
257
  id: "schedule-list",
@@ -274,7 +274,7 @@ describe("validateBoot — projectionList screens", () => {
274
274
  z.object({}),
275
275
  async () => ({ rows: [], nextCursor: null }),
276
276
  {
277
- access: { openToAll: true },
277
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
278
278
  },
279
279
  );
280
280
  r.screen({
@@ -297,7 +297,7 @@ describe("validateBoot — projectionList screens", () => {
297
297
  "schedule:list",
298
298
  z.object({ filters: z.unknown().optional() }),
299
299
  async () => ({ rows: [], nextCursor: null }),
300
- { access: { openToAll: true } },
300
+ { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
301
301
  );
302
302
  r.screen({
303
303
  id: "schedule-list",
@@ -326,7 +326,7 @@ describe("validateBoot — projectionList screens", () => {
326
326
  "schedule:list",
327
327
  z.object({ filters: z.unknown().optional() }),
328
328
  async () => ({ rows: [], nextCursor: null }),
329
- { access: { openToAll: true } },
329
+ { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
330
330
  );
331
331
  r.screen({
332
332
  id: "schedule-list",
@@ -363,7 +363,7 @@ describe("validateBoot — projectionList screens", () => {
363
363
  "schedule:list",
364
364
  z.object({ filters: z.unknown().optional() }),
365
365
  async () => ({ rows: [], nextCursor: null }),
366
- { access: { openToAll: true } },
366
+ { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
367
367
  );
368
368
  r.screen({
369
369
  id: "schedule-list",
@@ -386,7 +386,7 @@ describe("validateBoot — projectionList screens", () => {
386
386
  z.object({}),
387
387
  async () => ({ rows: [], nextCursor: null }),
388
388
  {
389
- access: { openToAll: true },
389
+ access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
390
390
  },
391
391
  );
392
392
  r.screen({
@@ -417,7 +417,7 @@ describe("validateBoot — projectionList screens", () => {
417
417
  "schedule:list",
418
418
  z.object({ filter: z.unknown().optional(), filters: z.unknown().optional() }),
419
419
  async () => ({ rows: [], nextCursor: null }),
420
- { access: { openToAll: true } },
420
+ { access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
421
421
  );
422
422
  r.screen({
423
423
  id: "schedule-list",
@@ -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", {});