@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.
- package/README.md +5 -1
- package/package.json +3 -3
- package/src/__tests__/anonymous-access.integration.test.ts +1 -1
- package/src/__tests__/error-contract.integration.test.ts +1 -1
- package/src/__tests__/fixtures/migrate-cross-tenant/manual-only.expected.ts +44 -0
- package/src/__tests__/fixtures/migrate-cross-tenant/manual-only.input.ts +44 -0
- package/src/__tests__/fixtures/migrate-cross-tenant/rewrite.expected.ts +41 -0
- package/src/__tests__/fixtures/migrate-cross-tenant/rewrite.input.ts +41 -0
- package/src/__tests__/fixtures/migrate-open-to-all/manual-only.expected.ts +16 -0
- package/src/__tests__/fixtures/migrate-open-to-all/manual-only.input.ts +16 -0
- package/src/__tests__/fixtures/migrate-open-to-all/test-nested-shorthand.expected.ts +12 -0
- package/src/__tests__/fixtures/migrate-open-to-all/test-nested-shorthand.input.ts +12 -0
- package/src/__tests__/fixtures/migrate-open-to-all/test-simple.expected.ts +7 -0
- package/src/__tests__/fixtures/migrate-open-to-all/test-simple.input.ts +7 -0
- package/src/__tests__/full-stack.integration.test.ts +2 -2
- package/src/__tests__/migrate-cross-tenant-codemod.test.ts +106 -0
- package/src/__tests__/migrate-open-to-all-codemod.test.ts +84 -0
- package/src/__tests__/related-list-parent-filter.integration.test.ts +103 -0
- package/src/__tests__/transition-guard.integration.test.ts +7 -7
- package/src/api/__tests__/api.test.ts +2 -2
- package/src/api/__tests__/body-limit.test.ts +1 -1
- package/src/api/__tests__/nested-write-ownership.integration.test.ts +268 -0
- package/src/api/__tests__/pii-leak-guard.integration.test.ts +2 -2
- package/src/api/__tests__/request-locale.integration.test.ts +1 -1
- package/src/api/__tests__/server-error-logging.test.ts +1 -1
- package/src/api/server.ts +7 -20
- package/src/bun-db/__tests__/query-guards.test.ts +60 -3
- package/src/bun-db/query.ts +24 -21
- package/src/changes.json +99 -0
- package/src/db/__tests__/event-store-executor-write-verbs.integration.test.ts +77 -0
- package/src/db/__tests__/event-store-executor.integration.test.ts +1 -1
- package/src/db/__tests__/list-filter-field-access.integration.test.ts +1 -1
- package/src/db/__tests__/replay-migration-sql.test.ts +17 -0
- package/src/db/__tests__/tenant-db-convention-cross-tenant.test.ts +72 -0
- package/src/db/__tests__/tenant-db-declared-unsafe-raw.test.ts +104 -0
- package/src/db/__tests__/tenant-db-global.test.ts +92 -0
- package/src/db/__tests__/tenant-db-no-raw.test.ts +75 -0
- package/src/db/__tests__/tenant-db.integration.test.ts +3 -1
- package/src/db/__tests__/unchecked-system-db.test.ts +76 -0
- package/src/db/apply-entity-event.ts +1 -1
- package/src/db/assert-exists-in.ts +1 -1
- package/src/db/event-store-executor-context.ts +5 -2
- package/src/db/event-store-executor-read.ts +6 -4
- package/src/db/event-store-executor-write.ts +24 -18
- package/src/db/queries/shadow-swap.ts +36 -0
- package/src/db/replay-migration-sql.ts +5 -5
- package/src/db/tenant-db-runner.ts +23 -0
- package/src/db/tenant-db.ts +97 -24
- package/src/derivatives/__tests__/derivatives-context.integration.test.ts +2 -2
- package/src/derivatives/__tests__/derivatives-context.test.ts +0 -1
- package/src/engine/__tests__/boot-validator-dashboard.test.ts +5 -5
- package/src/engine/__tests__/boot-validator-nav-area.test.ts +116 -0
- package/src/engine/__tests__/boot-validator-pii-retention.test.ts +1 -1
- package/src/engine/__tests__/boot-validator-projection-list.test.ts +108 -17
- package/src/engine/__tests__/boot-validator-query-output-schema.test.ts +161 -18
- package/src/engine/__tests__/boot-validator-query-refs.test.ts +3 -3
- package/src/engine/__tests__/boot-validator-s0-integration.test.ts +2 -2
- package/src/engine/__tests__/boot-validator.test.ts +69 -18
- package/src/engine/__tests__/build-app-schema.test.ts +10 -6
- package/src/engine/__tests__/build-config-feature-schema-secrets.test.ts +48 -0
- package/src/engine/__tests__/build-config-feature-schema.test.ts +34 -5
- package/src/engine/__tests__/engine.test.ts +39 -25
- package/src/engine/__tests__/entity-create-caller-id.integration.test.ts +87 -0
- package/src/engine/__tests__/entity-handlers-escape-hatch.test.ts +91 -0
- package/src/engine/__tests__/entity-handlers.test.ts +47 -8
- package/src/engine/__tests__/entity-presave-wiring.integration.test.ts +2 -2
- package/src/engine/__tests__/entity-restore-tenant-isolation.integration.test.ts +30 -5
- package/src/engine/__tests__/entity-write-crosstenant.integration.test.ts +206 -47
- package/src/engine/__tests__/extends-registrar.test.ts +3 -3
- package/src/engine/__tests__/feature-crud-shorthand.test.ts +9 -3
- package/src/engine/__tests__/feature-crud-verb-access.integration.test.ts +1 -1
- package/src/engine/__tests__/hook-phases.test.ts +5 -5
- package/src/engine/__tests__/lifecycle-hooks.test.ts +9 -3
- package/src/engine/__tests__/multiselect-filter.integration.test.ts +1 -1
- package/src/engine/__tests__/post-query-hook.test.ts +6 -2
- package/src/engine/__tests__/projection-detail-actions.test.ts +2 -2
- package/src/engine/__tests__/projection-detail-singleton.test.ts +3 -3
- package/src/engine/__tests__/projection-detail-tabs.test.ts +3 -3
- package/src/engine/__tests__/projection-list-filter.integration.test.ts +1 -1
- package/src/engine/__tests__/read-steps-tenant-filter.integration.test.ts +215 -0
- package/src/engine/__tests__/registry.test.ts +19 -13
- package/src/engine/__tests__/required-surface-keys.test.ts +16 -0
- package/src/engine/__tests__/screen.test.ts +176 -20
- package/src/engine/__tests__/soft-delete-cleanup.test.ts +0 -1
- package/src/engine/__tests__/steps-read.test.ts +204 -8
- package/src/engine/__tests__/steps-unsafe-projection-delete.test.ts +7 -4
- package/src/engine/__tests__/steps-unsafe-projection-upsert.test.ts +6 -3
- package/src/engine/boot-validator/__tests__/access-declarations.test.ts +5 -4
- package/src/engine/boot-validator/__tests__/global-tenancy.test.ts +16 -1
- package/src/engine/boot-validator/__tests__/parent-ref.test.ts +18 -6
- package/src/engine/boot-validator/__tests__/rate-limit-disabled-reason.test.ts +45 -0
- package/src/engine/boot-validator/access-declarations.ts +3 -5
- package/src/engine/boot-validator/entity-handler.ts +21 -0
- package/src/engine/boot-validator/global-tenancy.ts +13 -1
- package/src/engine/boot-validator/nav.ts +4 -3
- package/src/engine/boot-validator/parent-ref.ts +1 -1
- package/src/engine/boot-validator/projection-list-screens.ts +67 -2
- package/src/engine/boot-validator/query-output-columns.ts +33 -0
- package/src/engine/boot-validator/screens.ts +154 -31
- package/src/engine/build-config-feature-schema.ts +12 -2
- package/src/engine/entity-handlers.ts +72 -20
- package/src/engine/feature-ast/__tests__/canonical-form.test.ts +3 -1
- package/src/engine/feature-ast/__tests__/parse-happy-path.test.ts +2 -2
- package/src/engine/feature-ast/__tests__/parse.test.ts +106 -4
- package/src/engine/feature-ast/__tests__/patcher.test.ts +3 -3
- package/src/engine/feature-ast/__tests__/read-optional-access-rule.test.ts +4 -4
- package/src/engine/feature-ast/__tests__/render-roundtrip.test.ts +5 -1
- package/src/engine/feature-ast/extractors/handlers.ts +16 -2
- package/src/engine/feature-ast/extractors/hooks.ts +25 -4
- package/src/engine/feature-ast/patcher.ts +15 -3
- package/src/engine/feature-ast/patterns.ts +4 -0
- package/src/engine/feature-ast/render.ts +3 -0
- package/src/engine/feature-entity-handlers.ts +4 -4
- package/src/engine/index.ts +5 -0
- package/src/engine/pattern-library/shared-fields.ts +7 -3
- package/src/engine/registry-validate.ts +12 -4
- package/src/engine/schema-builder.ts +6 -0
- package/src/engine/screen-helpers.ts +52 -0
- package/src/engine/steps/_read-source.ts +12 -0
- package/src/engine/steps/read-find-many.ts +8 -2
- package/src/engine/steps/read-find-one.ts +11 -8
- package/src/engine/steps/unsafe-projection-delete.ts +2 -1
- package/src/engine/steps/unsafe-projection-upsert.ts +2 -1
- package/src/engine/types/index.ts +8 -1
- package/src/entrypoint/__tests__/entrypoint-attach-dispatcher.integration.test.ts +1 -1
- package/src/entrypoint/__tests__/entrypoint-job-wiring.integration.test.ts +3 -3
- package/src/entrypoint/__tests__/split-deploy.integration.test.ts +1 -1
- package/src/errors/i18n/de.yaml +14 -0
- package/src/errors/i18n/en.yaml +13 -0
- package/src/errors/reasons.ts +4 -0
- package/src/es-ops/__tests__/runner.integration.test.ts +2 -2
- package/src/i18n/required-surface-keys.ts +6 -2
- package/src/jobs/__tests__/job-event-trigger.integration.test.ts +2 -2
- package/src/jobs/__tests__/job-multi-trigger.integration.test.ts +2 -2
- package/src/jobs/__tests__/job-systemdb.integration.test.ts +5 -4
- package/src/jobs/__tests__/jobs.integration.test.ts +42 -1
- package/src/jobs/job-runner.ts +35 -5
- package/src/observability/__tests__/observability.integration.test.ts +7 -7
- package/src/pipeline/__tests__/archive-stream.integration.test.ts +2 -2
- package/src/pipeline/__tests__/dispatcher.test.ts +15 -15
- package/src/pipeline/__tests__/escape-hatch-audit.integration.test.ts +254 -0
- package/src/pipeline/__tests__/escape-hatch-report.test.ts +149 -0
- package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +10 -10
- package/src/pipeline/__tests__/load-aggregate-query.integration.test.ts +1 -1
- package/src/pipeline/__tests__/msp-rebuild.integration.test.ts +28 -0
- package/src/pipeline/__tests__/post-query-hook.integration.test.ts +3 -3
- package/src/pipeline/__tests__/projection-rebuild.integration.test.ts +94 -0
- package/src/pipeline/__tests__/projections-runner-systemscope.integration.test.ts +101 -0
- package/src/pipeline/__tests__/query-projection.integration.test.ts +172 -17
- package/src/pipeline/__tests__/rate-limit-default.integration.test.ts +118 -0
- package/src/pipeline/__tests__/rate-limit-default.test.ts +117 -0
- package/src/pipeline/__tests__/search-consumer-named-events.integration.test.ts +5 -2
- package/src/pipeline/__tests__/system-identity-switch.test.ts +240 -1
- package/src/pipeline/dispatch-query.ts +27 -8
- package/src/pipeline/dispatch-shared.ts +141 -57
- package/src/pipeline/dispatch-stream.ts +1 -3
- package/src/pipeline/dispatch-write.ts +62 -14
- package/src/pipeline/dispatcher.ts +20 -0
- package/src/pipeline/escape-hatch-report.ts +127 -0
- package/src/pipeline/index.ts +8 -0
- package/src/pipeline/member-read-only-transaction.ts +75 -0
- package/src/pipeline/msp-rebuild.ts +2 -0
- package/src/pipeline/projection-rebuild.ts +2 -0
- package/src/pipeline/projections-runner.ts +15 -13
- package/src/pipeline/system-identity-switch.ts +121 -9
- package/src/scripts/codemod/migrate-cross-tenant.ts +335 -0
- package/src/scripts/codemod/migrate-db-raw.ts +19 -6
- package/src/scripts/codemod/migrate-open-to-all.ts +249 -0
- package/src/secrets/__tests__/contains-secret.test.ts +5 -5
- package/src/stack/__tests__/request-helper.integration.test.ts +3 -3
- package/src/stack/__tests__/setup-test-stack-jobs.integration.test.ts +1 -1
- package/src/testing/__tests__/e2e-generator.test.ts +19 -5
- package/src/ui-types/index.ts +3 -0
- package/src/utils/__tests__/ids.test.ts +29 -1
- package/src/utils/ids.ts +10 -1
- package/src/utils/index.ts +1 -1
|
@@ -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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
714
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
715
715
|
});
|
|
716
716
|
}),
|
|
717
717
|
];
|
|
@@ -2403,6 +2403,10 @@ describe("boot-validator", () => {
|
|
|
2403
2403
|
readonly sections?: ReadonlyArray<{
|
|
2404
2404
|
readonly title: string;
|
|
2405
2405
|
readonly fields: readonly string[];
|
|
2406
|
+
readonly groups?: ReadonlyArray<{
|
|
2407
|
+
readonly title: string;
|
|
2408
|
+
readonly fields: readonly string[];
|
|
2409
|
+
}>;
|
|
2406
2410
|
}>;
|
|
2407
2411
|
readonly redirect?: string | { readonly screen: string; readonly idFrom: string };
|
|
2408
2412
|
readonly cancelTarget?: string | false;
|
|
@@ -2430,7 +2434,7 @@ describe("boot-validator", () => {
|
|
|
2430
2434
|
name: "invoice:approve",
|
|
2431
2435
|
schema: { _type: "stub" } as never,
|
|
2432
2436
|
handler: async () => ({ isSuccess: true, data: {} }) as never,
|
|
2433
|
-
access: { openToAll:
|
|
2437
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
2434
2438
|
});
|
|
2435
2439
|
r.screen({
|
|
2436
2440
|
id: "approve-invoice",
|
|
@@ -2501,6 +2505,53 @@ describe("boot-validator", () => {
|
|
|
2501
2505
|
).toThrow(/layout references unknown field "ghost"/);
|
|
2502
2506
|
});
|
|
2503
2507
|
|
|
2508
|
+
test("section mit fields UND groups → Throw", () => {
|
|
2509
|
+
expect(() =>
|
|
2510
|
+
validateBoot([
|
|
2511
|
+
makeFeature({
|
|
2512
|
+
sections: [
|
|
2513
|
+
{
|
|
2514
|
+
title: "Approval",
|
|
2515
|
+
fields: ["note"],
|
|
2516
|
+
groups: [{ title: "Priority", fields: ["priority"] }],
|
|
2517
|
+
},
|
|
2518
|
+
],
|
|
2519
|
+
}),
|
|
2520
|
+
]),
|
|
2521
|
+
).toThrow(/declares both fields and groups/);
|
|
2522
|
+
});
|
|
2523
|
+
|
|
2524
|
+
test("group referenziert unknown field → Throw", () => {
|
|
2525
|
+
expect(() =>
|
|
2526
|
+
validateBoot([
|
|
2527
|
+
makeFeature({
|
|
2528
|
+
sections: [
|
|
2529
|
+
{ title: "Approval", fields: [], groups: [{ title: "Priority", fields: ["ghost"] }] },
|
|
2530
|
+
],
|
|
2531
|
+
}),
|
|
2532
|
+
]),
|
|
2533
|
+
).toThrow(/layout references unknown field "ghost"/);
|
|
2534
|
+
});
|
|
2535
|
+
|
|
2536
|
+
test("valide groups → kein Throw", () => {
|
|
2537
|
+
expect(() =>
|
|
2538
|
+
validateBoot([
|
|
2539
|
+
makeFeature({
|
|
2540
|
+
sections: [
|
|
2541
|
+
{
|
|
2542
|
+
title: "Approval",
|
|
2543
|
+
fields: [],
|
|
2544
|
+
groups: [
|
|
2545
|
+
{ title: "Basics", fields: ["note"] },
|
|
2546
|
+
{ title: "Priority", fields: ["priority"] },
|
|
2547
|
+
],
|
|
2548
|
+
},
|
|
2549
|
+
],
|
|
2550
|
+
}),
|
|
2551
|
+
]),
|
|
2552
|
+
).not.toThrow();
|
|
2553
|
+
});
|
|
2554
|
+
|
|
2504
2555
|
test("redirect → existing screen-id im selben feature → kein Throw", () => {
|
|
2505
2556
|
expect(() =>
|
|
2506
2557
|
validateBoot([makeFeature({ redirect: "after-form", extraScreens: ["after-form"] })]),
|
|
@@ -2713,14 +2764,14 @@ describe("boot-validator", () => {
|
|
|
2713
2764
|
name: "token:mint",
|
|
2714
2765
|
schema: { _type: "stub" } as never,
|
|
2715
2766
|
handler: async () => ({ isSuccess: true, data: {} }) as never,
|
|
2716
|
-
access: { openToAll:
|
|
2767
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
2717
2768
|
});
|
|
2718
2769
|
if (confirmOverride !== undefined) {
|
|
2719
2770
|
r.writeHandler({
|
|
2720
2771
|
name: "token:confirm",
|
|
2721
2772
|
schema: { _type: "stub" } as never,
|
|
2722
2773
|
handler: async () => ({ isSuccess: true, data: {} }) as never,
|
|
2723
|
-
access: { openToAll:
|
|
2774
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
2724
2775
|
});
|
|
2725
2776
|
}
|
|
2726
2777
|
r.screen({
|
|
@@ -4177,7 +4228,7 @@ describe("boot-validator", () => {
|
|
|
4177
4228
|
}),
|
|
4178
4229
|
);
|
|
4179
4230
|
r.queryHandler("products", z.object({}), async () => ({ rows: [], nextCursor: null }), {
|
|
4180
|
-
access: { openToAll:
|
|
4231
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
4181
4232
|
});
|
|
4182
4233
|
r.screen({
|
|
4183
4234
|
id: "product-projection",
|
|
@@ -4207,7 +4258,7 @@ describe("boot-validator", () => {
|
|
|
4207
4258
|
}),
|
|
4208
4259
|
);
|
|
4209
4260
|
r.queryHandler("products", z.object({}), async () => ({ rows: [], nextCursor: null }), {
|
|
4210
|
-
access: { openToAll:
|
|
4261
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
4211
4262
|
});
|
|
4212
4263
|
r.screen({
|
|
4213
4264
|
id: "product-projection",
|
|
@@ -4426,13 +4477,13 @@ describe("boot-validator", () => {
|
|
|
4426
4477
|
test("projectionDetail relatedList defaultSort.field sortable → kein Throw", () => {
|
|
4427
4478
|
const feature = defineFeature("shop", (r) => {
|
|
4428
4479
|
r.queryHandler("order-detail", z.object({}), async () => ({ id: "o1" }), {
|
|
4429
|
-
access: { openToAll:
|
|
4480
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
4430
4481
|
});
|
|
4431
4482
|
r.queryHandler(
|
|
4432
4483
|
"order-positions",
|
|
4433
4484
|
z.object({}),
|
|
4434
4485
|
async () => ({ rows: [], nextCursor: null }),
|
|
4435
|
-
{ access: { openToAll:
|
|
4486
|
+
{ access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
|
|
4436
4487
|
);
|
|
4437
4488
|
r.screen({
|
|
4438
4489
|
id: "order-detail",
|
|
@@ -4993,7 +5044,7 @@ describe("boot-validator", () => {
|
|
|
4993
5044
|
const targetId = opts.targetId ?? "restock-form";
|
|
4994
5045
|
return defineFeature("shop", (r) => {
|
|
4995
5046
|
r.queryHandler("products", z.object({}), async () => ({ rows: [], nextCursor: null }), {
|
|
4996
|
-
access: { openToAll:
|
|
5047
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
4997
5048
|
});
|
|
4998
5049
|
r.writeHandler(
|
|
4999
5050
|
"restock",
|
|
@@ -5223,7 +5274,7 @@ describe("boot-validator — config key backing × scope", () => {
|
|
|
5223
5274
|
],
|
|
5224
5275
|
});
|
|
5225
5276
|
r.queryHandler("count", z.object({}), async () => ({ total: 0 }), {
|
|
5226
|
-
access: { openToAll:
|
|
5277
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
5227
5278
|
});
|
|
5228
5279
|
r.screen({
|
|
5229
5280
|
id: "product-dashboard",
|
|
@@ -5268,7 +5319,7 @@ describe("boot-validator — config key backing × scope", () => {
|
|
|
5268
5319
|
],
|
|
5269
5320
|
});
|
|
5270
5321
|
r.queryHandler("count", z.object({}), async () => ({ total: 0 }), {
|
|
5271
|
-
access: { openToAll:
|
|
5322
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
5272
5323
|
});
|
|
5273
5324
|
r.screen({
|
|
5274
5325
|
id: "product-dashboard",
|
|
@@ -5317,7 +5368,7 @@ describe("boot-validator — config key backing × scope", () => {
|
|
|
5317
5368
|
],
|
|
5318
5369
|
});
|
|
5319
5370
|
r.queryHandler("count", z.object({}), async () => ({ total: 0 }), {
|
|
5320
|
-
access: { openToAll:
|
|
5371
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
5321
5372
|
});
|
|
5322
5373
|
r.screen({
|
|
5323
5374
|
id: "product-dashboard",
|
|
@@ -5542,7 +5593,7 @@ describe("boot-validator — config key backing × scope", () => {
|
|
|
5542
5593
|
}),
|
|
5543
5594
|
);
|
|
5544
5595
|
r.queryHandler("products", z.object({}), async () => ({ rows: [], nextCursor: null }), {
|
|
5545
|
-
access: { openToAll:
|
|
5596
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
5546
5597
|
});
|
|
5547
5598
|
r.screen({
|
|
5548
5599
|
id: "product-projection",
|
|
@@ -5624,7 +5675,7 @@ describe("boot-validator — config key backing × scope", () => {
|
|
|
5624
5675
|
"create",
|
|
5625
5676
|
z.object({ name: z.string() }),
|
|
5626
5677
|
async () => ({ isSuccess: true as const, data: { id: "1" } }),
|
|
5627
|
-
{ access: { openToAll:
|
|
5678
|
+
{ access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
|
|
5628
5679
|
);
|
|
5629
5680
|
r.screen({
|
|
5630
5681
|
id: "product-list",
|
|
@@ -209,7 +209,7 @@ describe("buildAppSchema", () => {
|
|
|
209
209
|
r.workspace({
|
|
210
210
|
id: "admin",
|
|
211
211
|
label: "Admin",
|
|
212
|
-
access: { openToAll:
|
|
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({
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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({
|
|
287
|
-
|
|
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({
|
|
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({
|
|
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", () => {
|
|
@@ -87,7 +87,7 @@ describe("defineFeature", () => {
|
|
|
87
87
|
const _email: string = event.payload.email;
|
|
88
88
|
return { isSuccess: true, data: { id: 1 } };
|
|
89
89
|
},
|
|
90
|
-
{ access: { openToAll:
|
|
90
|
+
{ access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
|
|
91
91
|
);
|
|
92
92
|
});
|
|
93
93
|
|
|
@@ -105,7 +105,7 @@ describe("defineFeature", () => {
|
|
|
105
105
|
isSuccess: true,
|
|
106
106
|
data: null,
|
|
107
107
|
}),
|
|
108
|
-
{ access: { openToAll:
|
|
108
|
+
{ access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
|
|
109
109
|
);
|
|
110
110
|
});
|
|
111
111
|
expect(ref?.name).toBe("order:create");
|
|
@@ -115,7 +115,7 @@ describe("defineFeature", () => {
|
|
|
115
115
|
let ref: { name: string } | undefined;
|
|
116
116
|
defineFeature("test", (r) => {
|
|
117
117
|
ref = r.queryHandler("order:list", z.object({}), async () => [], {
|
|
118
|
-
access: { openToAll:
|
|
118
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
119
119
|
});
|
|
120
120
|
});
|
|
121
121
|
expect(ref?.name).toBe("order:list");
|
|
@@ -130,13 +130,15 @@ describe("defineFeature", () => {
|
|
|
130
130
|
async function* (query) {
|
|
131
131
|
yield `echo:${query.payload.prompt}`;
|
|
132
132
|
},
|
|
133
|
-
{ access: { openToAll:
|
|
133
|
+
{ access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
|
|
134
134
|
);
|
|
135
135
|
});
|
|
136
136
|
expect(ref?.name).toBe("chat:complete");
|
|
137
137
|
|
|
138
138
|
const stored = feature.streamHandlers["chat:complete"];
|
|
139
|
-
expect(stored?.access).toEqual({
|
|
139
|
+
expect(stored?.access).toEqual({
|
|
140
|
+
openToAll: { reason: "test handler callable by any signed-in test user" },
|
|
141
|
+
});
|
|
140
142
|
|
|
141
143
|
const chunks: string[] = [];
|
|
142
144
|
for await (const chunk of stored!.handler(
|
|
@@ -225,7 +227,7 @@ describe("defineFeature", () => {
|
|
|
225
227
|
const _id: number = query.payload.userId;
|
|
226
228
|
return { id: _id, email: "test@test.de" };
|
|
227
229
|
},
|
|
228
|
-
{ access: { openToAll:
|
|
230
|
+
{ access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
|
|
229
231
|
);
|
|
230
232
|
});
|
|
231
233
|
|
|
@@ -239,7 +241,7 @@ describe("defineFeature", () => {
|
|
|
239
241
|
handler: async () => {
|
|
240
242
|
return [{ id: 1, email: "test@test.de" }];
|
|
241
243
|
},
|
|
242
|
-
access: { openToAll:
|
|
244
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
243
245
|
});
|
|
244
246
|
|
|
245
247
|
const feature = defineFeature("test", (r) => {
|
|
@@ -402,12 +404,12 @@ describe("createRegistry", () => {
|
|
|
402
404
|
test("looks up handlers across features", () => {
|
|
403
405
|
const f1 = defineFeature("admin", (r) => {
|
|
404
406
|
r.writeHandler("user:invite", z.object({}), async () => ({ isSuccess: true, data: null }), {
|
|
405
|
-
access: { openToAll:
|
|
407
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
406
408
|
});
|
|
407
409
|
});
|
|
408
410
|
const f2 = defineFeature("profile", (r) => {
|
|
409
411
|
r.queryHandler("profile:me", z.object({}), async () => ({ id: 1 }), {
|
|
410
|
-
access: { openToAll:
|
|
412
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
411
413
|
});
|
|
412
414
|
});
|
|
413
415
|
|
|
@@ -442,12 +444,12 @@ describe("createRegistry", () => {
|
|
|
442
444
|
test("different features can have same handler short name (prefixed differently)", () => {
|
|
443
445
|
const f1 = defineFeature("a", (r) => {
|
|
444
446
|
r.writeHandler("user:invite", z.object({}), async () => ({ isSuccess: true, data: null }), {
|
|
445
|
-
access: { openToAll:
|
|
447
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
446
448
|
});
|
|
447
449
|
});
|
|
448
450
|
const f2 = defineFeature("b", (r) => {
|
|
449
451
|
r.writeHandler("user:invite", z.object({}), async () => ({ isSuccess: true, data: null }), {
|
|
450
|
-
access: { openToAll:
|
|
452
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
451
453
|
});
|
|
452
454
|
});
|
|
453
455
|
|
|
@@ -513,7 +515,7 @@ describe("createRegistry", () => {
|
|
|
513
515
|
);
|
|
514
516
|
// Handler name "promote" has no entity prefix → can't be mapped
|
|
515
517
|
r.writeHandler("promote", z.object({}), async () => ({ isSuccess: true, data: null }), {
|
|
516
|
-
access: { openToAll:
|
|
518
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
517
519
|
});
|
|
518
520
|
});
|
|
519
521
|
|
|
@@ -531,7 +533,7 @@ describe("createRegistry", () => {
|
|
|
531
533
|
);
|
|
532
534
|
// No field-access rules on entity → "reset" without entity prefix is fine
|
|
533
535
|
r.writeHandler("reset", z.object({}), async () => ({ isSuccess: true, data: null }), {
|
|
534
|
-
access: { openToAll:
|
|
536
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
535
537
|
});
|
|
536
538
|
});
|
|
537
539
|
|
|
@@ -562,7 +564,7 @@ describe("createRegistry", () => {
|
|
|
562
564
|
isSuccess: true,
|
|
563
565
|
data: null,
|
|
564
566
|
}),
|
|
565
|
-
{ access: { openToAll:
|
|
567
|
+
{ access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
|
|
566
568
|
);
|
|
567
569
|
});
|
|
568
570
|
|
|
@@ -591,10 +593,12 @@ describe("createRegistry", () => {
|
|
|
591
593
|
isSuccess: true,
|
|
592
594
|
data: null,
|
|
593
595
|
}),
|
|
594
|
-
{ access: { openToAll:
|
|
596
|
+
{ access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
|
|
595
597
|
);
|
|
596
598
|
// Typo: "emp" instead of "employee"
|
|
597
|
-
r.queryHandler("emp:list", z.object({}), async () => [], {
|
|
599
|
+
r.queryHandler("emp:list", z.object({}), async () => [], {
|
|
600
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
601
|
+
});
|
|
598
602
|
});
|
|
599
603
|
|
|
600
604
|
expect(() => createRegistry([feature])).toThrow(/emp:list.*entity-bound.*no matching entity/i);
|
|
@@ -622,13 +626,15 @@ describe("createRegistry", () => {
|
|
|
622
626
|
isSuccess: true,
|
|
623
627
|
data: null,
|
|
624
628
|
}),
|
|
625
|
-
{ access: { openToAll:
|
|
629
|
+
{ access: { openToAll: { reason: "test handler callable by any signed-in test user" } } },
|
|
626
630
|
);
|
|
627
631
|
// Standalone queries — no dot, intentionally not entity-bound
|
|
628
632
|
r.queryHandler("dashboard", z.object({}), async () => ({ total: 42 }), {
|
|
629
|
-
access: { openToAll:
|
|
633
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
634
|
+
});
|
|
635
|
+
r.queryHandler("orgChart", z.object({}), async () => [], {
|
|
636
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
630
637
|
});
|
|
631
|
-
r.queryHandler("orgChart", z.object({}), async () => [], { access: { openToAll: true } });
|
|
632
638
|
});
|
|
633
639
|
|
|
634
640
|
expect(() => createRegistry([feature])).not.toThrow();
|
|
@@ -924,12 +930,20 @@ describe("hasAccess", () => {
|
|
|
924
930
|
|
|
925
931
|
test("openToAll grants access to any authenticated user", () => {
|
|
926
932
|
const user = createTestUser({ roles: ["Employee"] });
|
|
927
|
-
expect(
|
|
933
|
+
expect(
|
|
934
|
+
hasAccess(user, {
|
|
935
|
+
openToAll: { reason: "test handler callable by any signed-in test user" },
|
|
936
|
+
}),
|
|
937
|
+
).toBe(true);
|
|
928
938
|
});
|
|
929
939
|
|
|
930
940
|
test("openToAll grants access even to user with no roles", () => {
|
|
931
941
|
const user = createTestUser({ roles: [] });
|
|
932
|
-
expect(
|
|
942
|
+
expect(
|
|
943
|
+
hasAccess(user, {
|
|
944
|
+
openToAll: { reason: "test handler callable by any signed-in test user" },
|
|
945
|
+
}),
|
|
946
|
+
).toBe(true);
|
|
933
947
|
});
|
|
934
948
|
|
|
935
949
|
test.each([{ openToAll: false }, { openToAll: {} }])(
|
|
@@ -2105,10 +2119,10 @@ describe("registry boot validation", () => {
|
|
|
2105
2119
|
const feature = defineFeature("shop", (r) => {
|
|
2106
2120
|
r.entity("order", createEntity({ table: "Orders", fields: {} }));
|
|
2107
2121
|
r.writeHandler("order:create", z.object({}), async () => ({ isSuccess: true, data: null }), {
|
|
2108
|
-
access: { openToAll:
|
|
2122
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
2109
2123
|
});
|
|
2110
2124
|
r.writeHandler("order:cancel", z.object({}), async () => ({ isSuccess: true, data: null }), {
|
|
2111
|
-
access: { openToAll:
|
|
2125
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
2112
2126
|
});
|
|
2113
2127
|
// Ein Job-Body, zwei Trigger — DRY-Pattern für Fanout-Cases.
|
|
2114
2128
|
r.job(
|
|
@@ -2129,7 +2143,7 @@ describe("registry boot validation", () => {
|
|
|
2129
2143
|
const feature = defineFeature("shop", (r) => {
|
|
2130
2144
|
r.entity("order", createEntity({ table: "Orders", fields: {} }));
|
|
2131
2145
|
r.writeHandler("order:create", z.object({}), async () => ({ isSuccess: true, data: null }), {
|
|
2132
|
-
access: { openToAll:
|
|
2146
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
2133
2147
|
});
|
|
2134
2148
|
// create existiert, cancel nicht — zweiter Trigger ist Geist
|
|
2135
2149
|
r.job(
|
|
@@ -2153,7 +2167,7 @@ describe("registry boot validation", () => {
|
|
|
2153
2167
|
const feature = defineFeature("test", (r) => {
|
|
2154
2168
|
r.entity("item", createEntity({ table: "Items", fields: {} }));
|
|
2155
2169
|
r.writeHandler("item.create", z.object({}), async () => ({ isSuccess: true, data: null }), {
|
|
2156
|
-
access: { openToAll:
|
|
2170
|
+
access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
|
|
2157
2171
|
});
|
|
2158
2172
|
r.hook("postSave", "item.create", async () => {});
|
|
2159
2173
|
});
|