@cosmicdrift/kumiko-framework 0.336.1 → 0.337.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/dist/api/auth-middleware.js +43 -19
  2. package/dist/api/auth-routes.d.ts +1 -0
  3. package/dist/api/auth-routes.js +20 -0
  4. package/dist/api/redis-sse-broker.d.ts +1 -0
  5. package/dist/api/redis-sse-broker.js +4 -5
  6. package/dist/api/server.js +3 -3
  7. package/dist/api/sse-route.js +14 -1
  8. package/dist/bun-db/query.js +6 -4
  9. package/dist/crypto/kek-source.js +1 -0
  10. package/dist/db/blind-index-cleanup.js +1 -1
  11. package/dist/db/event-store-executor-read.js +15 -4
  12. package/dist/db/queries/event-consumer.d.ts +2 -2
  13. package/dist/db/queries/event-consumer.js +5 -4
  14. package/dist/db/queries/event-store.js +6 -6
  15. package/dist/db/queries/projection-rebuild.js +4 -4
  16. package/dist/db/queries/seed-context.js +6 -6
  17. package/dist/derivatives/derivatives-context.js +5 -2
  18. package/dist/engine/boot-validator/projection-list-screens.d.ts +1 -1
  19. package/dist/engine/boot-validator/projection-list-screens.js +13 -0
  20. package/dist/engine/boot-validator/screens.js +12 -0
  21. package/dist/engine/build-config-feature-schema.js +4 -0
  22. package/dist/engine/define-feature.js +1 -0
  23. package/dist/engine/feature-ast/extractors/index.d.ts +1 -1
  24. package/dist/engine/feature-ast/extractors/index.js +1 -1
  25. package/dist/engine/feature-ast/extractors/jobs-routes.js +6 -2
  26. package/dist/engine/feature-ast/extractors/round3.d.ts +2 -1
  27. package/dist/engine/feature-ast/extractors/round3.js +11 -0
  28. package/dist/engine/feature-ast/extractors/shared.js +1 -3
  29. package/dist/engine/feature-ast/index.d.ts +1 -1
  30. package/dist/engine/feature-ast/parse.js +3 -1
  31. package/dist/engine/feature-ast/patch.d.ts +3 -0
  32. package/dist/engine/feature-ast/patch.js +8 -11
  33. package/dist/engine/feature-ast/patcher.d.ts +6 -1
  34. package/dist/engine/feature-ast/patcher.js +3 -0
  35. package/dist/engine/feature-ast/pattern-change-schema.d.ts +590 -1
  36. package/dist/engine/feature-ast/pattern-change-schema.js +17 -2
  37. package/dist/engine/feature-ast/patterns.d.ts +8 -2
  38. package/dist/engine/feature-ast/patterns.js +1 -0
  39. package/dist/engine/feature-ast/render.js +9 -0
  40. package/dist/engine/feature-builder-state.d.ts +2 -1
  41. package/dist/engine/feature-builder-state.js +1 -0
  42. package/dist/engine/feature-config-events-jobs.d.ts +4 -1
  43. package/dist/engine/feature-config-events-jobs.js +20 -0
  44. package/dist/engine/feature-ui-extensions.js +15 -21
  45. package/dist/engine/index.d.ts +1 -1
  46. package/dist/engine/pattern-library/library.js +2 -1
  47. package/dist/engine/pattern-library/static-schemas.d.ts +1 -0
  48. package/dist/engine/pattern-library/static-schemas.js +47 -4
  49. package/dist/engine/registry-facade.js +7 -0
  50. package/dist/engine/registry-ingest.js +7 -0
  51. package/dist/engine/registry-state.d.ts +2 -1
  52. package/dist/engine/registry-state.js +1 -0
  53. package/dist/engine/registry-validate.d.ts +1 -1
  54. package/dist/engine/registry-validate.js +39 -17
  55. package/dist/engine/registry.js +2 -2
  56. package/dist/engine/types/index.d.ts +2 -2
  57. package/dist/engine/types/index.js +1 -1
  58. package/dist/engine/types/screen.d.ts +1 -1
  59. package/dist/engine/types/screen.js +1 -1
  60. package/dist/errors/classes.d.ts +5 -0
  61. package/dist/errors/classes.js +15 -0
  62. package/dist/errors/index.d.ts +1 -1
  63. package/dist/errors/index.js +1 -1
  64. package/dist/i18n/required-surface-keys.js +3 -5
  65. package/dist/migrations/pending-rebuilds.d.ts +2 -0
  66. package/dist/migrations/pending-rebuilds.js +3 -0
  67. package/dist/pipeline/dispatch-query.js +3 -5
  68. package/dist/pipeline/dispatch-stream.js +2 -4
  69. package/dist/pipeline/dispatch-write.js +2 -4
  70. package/dist/pipeline/event-dispatcher-delivery.d.ts +0 -1
  71. package/dist/pipeline/event-dispatcher-delivery.js +1 -8
  72. package/dist/pipeline/event-dispatcher.js +16 -25
  73. package/dist/pipeline/handler-access-error.d.ts +3 -0
  74. package/dist/pipeline/handler-access-error.js +17 -0
  75. package/dist/pipeline/irreversible-operation-gate.js +9 -3
  76. package/dist/pipeline/member-read-only-transaction.js +8 -3
  77. package/dist/pipeline/system-hooks.js +7 -1
  78. package/dist/pipeline/system-identity-switch.js +1 -1
  79. package/dist/search/meilisearch-adapter.js +0 -2
  80. package/dist/stack/push-entity-projection-tables.js +5 -3
  81. package/dist/stack/redis.d.ts +1 -0
  82. package/dist/stack/redis.js +11 -2
  83. package/dist/stack/table-helpers.d.ts +3 -1
  84. package/dist/stack/table-helpers.js +9 -7
  85. package/dist/stack/test-stack.js +1 -7
  86. package/dist/testing/handler-context.js +3 -6
  87. package/dist/ui-types/index.d.ts +1 -1
  88. package/dist/ui-types/index.js +1 -1
  89. package/dist/upgrade-cli.js +29 -2
  90. package/package.json +4 -4
  91. package/src/changes.json +92 -0
  92. package/src/scripts/codemod/collect-source-files.ts +48 -0
  93. package/src/scripts/codemod/migrate-cross-tenant.ts +3 -23
  94. package/src/scripts/codemod/migrate-db-raw.ts +6 -23
  95. package/src/scripts/codemod/migrate-open-to-all.ts +4 -24
  96. package/src/scripts/codemod/unprocessable-error-details-reason.ts +47 -3
@@ -413,6 +413,14 @@ const secretSchema = z
413
413
  options: passthroughSchema(),
414
414
  })
415
415
  .strict();
416
+ const secretNamespaceSchema = z
417
+ .object({
418
+ kind: z.literal("secretNamespace"),
419
+ source: sourceFieldSchema,
420
+ shortName: z.string(),
421
+ options: passthroughSchema(),
422
+ })
423
+ .strict();
416
424
  const claimKeySchema = z
417
425
  .object({
418
426
  kind: z.literal("claimKey"),
@@ -707,6 +715,7 @@ export const PATTERN_SCHEMAS_BY_KIND = {
707
715
  uiHints: uiHintsSchema,
708
716
  metric: metricSchema,
709
717
  secret: secretSchema,
718
+ secretNamespace: secretNamespaceSchema,
710
719
  claimKey: claimKeySchema,
711
720
  referenceData: referenceDataSchema,
712
721
  readsConfig: readsConfigSchema,
@@ -733,7 +742,7 @@ export const PATTERN_SCHEMAS_BY_KIND = {
733
742
  "ai.classify": aiClassifySchema,
734
743
  unknown: unknownPatternSchema,
735
744
  };
736
- const patternSchema = z.discriminatedUnion("kind", [
745
+ export const patternSchema = z.discriminatedUnion("kind", [
737
746
  entitySchema,
738
747
  relationSchema,
739
748
  navSchema,
@@ -748,6 +757,7 @@ const patternSchema = z.discriminatedUnion("kind", [
748
757
  uiHintsSchema,
749
758
  metricSchema,
750
759
  secretSchema,
760
+ secretNamespaceSchema,
751
761
  claimKeySchema,
752
762
  referenceDataSchema,
753
763
  readsConfigSchema,
@@ -810,6 +820,9 @@ const patternIdMetricSchema = z
810
820
  const patternIdSecretSchema = z
811
821
  .object({ kind: z.literal("secret"), shortName: z.string() })
812
822
  .strict();
823
+ const patternIdSecretNamespaceSchema = z
824
+ .object({ kind: z.literal("secretNamespace"), shortName: z.string() })
825
+ .strict();
813
826
  const patternIdClaimKeySchema = z
814
827
  .object({ kind: z.literal("claimKey"), shortName: z.string() })
815
828
  .strict();
@@ -870,6 +883,7 @@ export const PATTERN_ID_SCHEMAS_BY_KIND = {
870
883
  hook: patternIdHookSchema,
871
884
  metric: patternIdMetricSchema,
872
885
  secret: patternIdSecretSchema,
886
+ secretNamespace: patternIdSecretNamespaceSchema,
873
887
  claimKey: patternIdClaimKeySchema,
874
888
  referenceData: patternIdReferenceDataSchema,
875
889
  useExtension: patternIdUseExtensionSchema,
@@ -895,7 +909,7 @@ export const PATTERN_ID_SCHEMAS_BY_KIND = {
895
909
  authClaims: patternIdAuthClaimsSchema,
896
910
  treeActions: patternIdTreeActionsSchema,
897
911
  };
898
- const patternIdSchema = z.discriminatedUnion("kind", [
912
+ export const patternIdSchema = z.discriminatedUnion("kind", [
899
913
  patternIdEntitySchema,
900
914
  patternIdRelationSchema,
901
915
  patternIdNavSchema,
@@ -907,6 +921,7 @@ const patternIdSchema = z.discriminatedUnion("kind", [
907
921
  patternIdHookSchema,
908
922
  patternIdMetricSchema,
909
923
  patternIdSecretSchema,
924
+ patternIdSecretNamespaceSchema,
910
925
  patternIdClaimKeySchema,
911
926
  patternIdReferenceDataSchema,
912
927
  patternIdUseExtensionSchema,
@@ -1,6 +1,6 @@
1
1
  import type { LifecycleHookType } from "../constants.js";
2
2
  import type { ConfigKeyDefinition, ConfigKeyType, JobDefinition, ReferenceDataDef, RunIn, TranslationKeys } from "../types/config.js";
3
- import type { MetricOptions, SecretOptions } from "../types/feature.js";
3
+ import type { MetricOptions, SecretNamespaceOptions, SecretOptions } from "../types/feature.js";
4
4
  import type { EntityDefinition } from "../types/fields.js";
5
5
  import type { AccessRule, AgentHandlerHints, ClaimKeyType, EscapeHatchDeclaration, RateLimitDeclaration } from "../types/handlers.js";
6
6
  import type { HookPhase } from "../types/hooks.js";
@@ -86,6 +86,12 @@ export type SecretPattern = {
86
86
  readonly shortName: string;
87
87
  readonly options: SecretOptions;
88
88
  };
89
+ export type SecretNamespacePattern = {
90
+ readonly kind: "secretNamespace";
91
+ readonly source: SourceLocation;
92
+ readonly shortName: string;
93
+ readonly options: SecretNamespaceOptions;
94
+ };
89
95
  export type ClaimKeyPattern = {
90
96
  readonly kind: "claimKey";
91
97
  readonly source: SourceLocation;
@@ -295,7 +301,7 @@ export type UnknownPattern = {
295
301
  readonly source: SourceLocation;
296
302
  readonly methodName: string;
297
303
  };
298
- export type FeaturePattern = EntityPattern | RelationPattern | NavPattern | WorkspacePattern | ConfigPattern | TranslationsPattern | RequiresPattern | OptionalRequiresPattern | SystemScopePattern | ToggleablePattern | DescribePattern | UiHintsPattern | MetricPattern | SecretPattern | ClaimKeyPattern | ReferenceDataPattern | ReadsConfigPattern | UseExtensionPattern | UsesApiPattern | ExposesApiPattern | TreeActionsPattern | ScreenPattern | WriteHandlerPattern | QueryHandlerPattern | StreamHandlerPattern | HookPattern | JobPattern | NotificationPattern | AuthClaimsPattern | HttpRoutePattern | ProjectionPattern | MultiStreamProjectionPattern | DefineEventPattern | ExtendsRegistrarPattern | EnvSchemaPattern | AiGeneratePattern | AiExtractPattern | AiClassifyPattern | UnknownPattern;
304
+ export type FeaturePattern = EntityPattern | RelationPattern | NavPattern | WorkspacePattern | ConfigPattern | TranslationsPattern | RequiresPattern | OptionalRequiresPattern | SystemScopePattern | ToggleablePattern | DescribePattern | UiHintsPattern | MetricPattern | SecretPattern | SecretNamespacePattern | ClaimKeyPattern | ReferenceDataPattern | ReadsConfigPattern | UseExtensionPattern | UsesApiPattern | ExposesApiPattern | TreeActionsPattern | ScreenPattern | WriteHandlerPattern | QueryHandlerPattern | StreamHandlerPattern | HookPattern | JobPattern | NotificationPattern | AuthClaimsPattern | HttpRoutePattern | ProjectionPattern | MultiStreamProjectionPattern | DefineEventPattern | ExtendsRegistrarPattern | EnvSchemaPattern | AiGeneratePattern | AiExtractPattern | AiClassifyPattern | UnknownPattern;
299
305
  export type FeaturePatternKind = FeaturePattern["kind"];
300
306
  export type Editability = "static" | "mixed" | "opaque";
301
307
  export declare function getEditability(pattern: FeaturePattern): Editability;
@@ -74,6 +74,7 @@ export function getEditability(pattern) {
74
74
  case "describe":
75
75
  case "metric":
76
76
  case "secret":
77
+ case "secretNamespace":
77
78
  case "claimKey":
78
79
  case "referenceData":
79
80
  case "readsConfig":
@@ -61,6 +61,8 @@ export function renderPattern(pattern) {
61
61
  return renderMetric(pattern);
62
62
  case "secret":
63
63
  return renderSecret(pattern);
64
+ case "secretNamespace":
65
+ return renderSecretNamespace(pattern);
64
66
  case "claimKey":
65
67
  return renderClaimKey(pattern);
66
68
  case "referenceData":
@@ -245,6 +247,13 @@ function renderSecret(p) {
245
247
  const merged = { name: p.shortName, ...p.options };
246
248
  return `r.secret(${renderValue(merged)});`;
247
249
  }
250
+ function renderSecretNamespace(p) {
251
+ if (isRawRefSentinel(p.options)) {
252
+ return `r.secretNamespace(${JSON.stringify(p.shortName)}, ${p.options.__raw});`;
253
+ }
254
+ const merged = { name: p.shortName, ...p.options };
255
+ return `r.secretNamespace(${renderValue(merged)});`;
256
+ }
248
257
  function renderClaimKey(p) {
249
258
  return `r.claimKey(${renderValue({ name: p.shortName, type: p.claimType })});`;
250
259
  }
@@ -2,7 +2,7 @@ import type { WebSocketRouteDefinition } from "@cosmicdrift/kumiko-types/websock
2
2
  import type * as z from "zod";
3
3
  import type { ZodType } from "zod";
4
4
  import type { HttpRouteDefinition } from "./types/http-route.js";
5
- import type { AuthClaimsFn, BootCheckFn, ClaimKeyDefinition, ConfigKeyDefinition, ConfigSeedDef, EntityDefinition, EntityProjectionExtension, EventMigrationDef, ExtensionSelectorDef, FeatureMetricDef, JobDefinition, LifecycleHookFn, MultiStreamProjectionDefinition, NotificationDefinition, OwnedFn, PhasedHook, PostDeleteHookFn, PostQueryHookFn, PostSaveHookFn, PreDeleteHookFn, ProjectionDefinition, QueryHandlerDef, ReferenceDataDef, RegistrarExtensionDef, RegistrarExtensionRegistration, RelationDefinition, SearchPayloadContributorFn, SecretKeyDefinition, StoreTableEntry, StreamHandlerDef, TranslationKeys, TreeActionDef, UiHints, ValidationHookFn, WriteHandlerDef } from "./types/index.js";
5
+ import type { AuthClaimsFn, BootCheckFn, ClaimKeyDefinition, ConfigKeyDefinition, ConfigSeedDef, EntityDefinition, EntityProjectionExtension, EventMigrationDef, ExtensionSelectorDef, FeatureMetricDef, JobDefinition, LifecycleHookFn, MultiStreamProjectionDefinition, NotificationDefinition, OwnedFn, PhasedHook, PostDeleteHookFn, PostQueryHookFn, PostSaveHookFn, PreDeleteHookFn, ProjectionDefinition, QueryHandlerDef, ReferenceDataDef, RegistrarExtensionDef, RegistrarExtensionRegistration, RelationDefinition, SearchPayloadContributorFn, SecretKeyDefinition, SecretNamespaceDefinition, StoreTableEntry, StreamHandlerDef, TranslationKeys, TreeActionDef, UiHints, ValidationHookFn, WriteHandlerDef } from "./types/index.js";
6
6
  import type { ContentCollectionDefinition, NavDefinition } from "./types/nav.js";
7
7
  import type { ScreenDefinition } from "./types/screen.js";
8
8
  import type { WorkspaceDefinition } from "./types/workspace.js";
@@ -46,6 +46,7 @@ export type FeatureBuilderState = {
46
46
  handlerEntityMappings: Record<string, string>;
47
47
  metrics: Record<string, FeatureMetricDef>;
48
48
  secretKeys: Record<string, SecretKeyDefinition>;
49
+ secretNamespaces: Record<string, SecretNamespaceDefinition>;
49
50
  projections: Record<string, ProjectionDefinition>;
50
51
  multiStreamProjections: Record<string, MultiStreamProjectionDefinition>;
51
52
  entityProjectionExtensions: Record<string, EntityProjectionExtension[]>;
@@ -41,6 +41,7 @@ export function createInitialFeatureBuilderState() {
41
41
  handlerEntityMappings: {},
42
42
  metrics: {},
43
43
  secretKeys: {},
44
+ secretNamespaces: {},
44
45
  projections: {},
45
46
  multiStreamProjections: {},
46
47
  entityProjectionExtensions: {},
@@ -1,7 +1,7 @@
1
1
  import type * as z from "zod";
2
2
  import { type ZodType } from "zod";
3
3
  import type { FeatureBuilderState } from "./feature-builder-state.js";
4
- import type { AuthClaimsFn, ClaimKeyHandle, ClaimKeyType, ConfigKeyDefinition, ConfigKeyHandle, ConfigKeyType, ConfigSeedDef, DeclarativeEventMigration, EventDef, EventPiiStance, EventUpcastFn, JobDefinition, JobHandlerFn, MetricOptions, NameOrRef, NotificationDataFn, NotificationRecipientFn, NotificationTemplateFn, QualifiedEventName, SecretKeyHandle, SecretOptions, TranslationsDef } from "./types/index.js";
4
+ import type { AuthClaimsFn, ClaimKeyHandle, ClaimKeyType, ConfigKeyDefinition, ConfigKeyHandle, ConfigKeyType, ConfigSeedDef, DeclarativeEventMigration, EventDef, EventPiiStance, EventUpcastFn, JobDefinition, JobHandlerFn, MetricOptions, NameOrRef, NotificationDataFn, NotificationRecipientFn, NotificationTemplateFn, QualifiedEventName, SecretKeyHandle, SecretNamespaceHandle, SecretNamespaceOptions, SecretOptions, TranslationsDef } from "./types/index.js";
5
5
  export declare function buildConfigEventsJobsMethods<TName extends string>(state: FeatureBuilderState, name: TName): {
6
6
  config: {
7
7
  <T extends ConfigKeyType>(keyName: string, def: ConfigKeyDefinition<T>): ConfigKeyHandle<T>;
@@ -48,6 +48,9 @@ export declare function buildConfigEventsJobsMethods<TName extends string>(state
48
48
  secret(shortNameOrDefinition: string | ({
49
49
  readonly name: string;
50
50
  } & SecretOptions), options?: SecretOptions): SecretKeyHandle;
51
+ secretNamespace(shortNameOrDefinition: string | ({
52
+ readonly name: string;
53
+ } & SecretNamespaceOptions), options?: SecretNamespaceOptions): SecretNamespaceHandle;
51
54
  claimKey<T extends ClaimKeyType>(shortNameOrDefinition: string | {
52
55
  readonly name: string;
53
56
  readonly type: T;
@@ -235,6 +235,10 @@ export function buildConfigEventsJobsMethods(state, name) {
235
235
  throw new Error(`[Feature ${name}] Secret "${shortName}" already registered. ` +
236
236
  `Secret key names must be unique per feature.`);
237
237
  }
238
+ if (secretOptions.writeRoles !== undefined && secretOptions.writeRoles.length === 0) {
239
+ throw new Error(`[Feature ${name}] Secret "${shortName}" declares an empty writeRoles list, ` +
240
+ `which nobody could ever satisfy. Omit writeRoles to fall back to the secrets handler access.`);
241
+ }
238
242
  // Qualified name follows the framework's "<feature>:<type>:<name>"
239
243
  // QN convention — same pattern config / jobs / events use. toKebab
240
244
  // handles the common input shapes ("stripe.apiKey" → "stripe-api-key")
@@ -248,6 +252,22 @@ export function buildConfigEventsJobsMethods(state, name) {
248
252
  };
249
253
  return { name: qualifiedName };
250
254
  },
255
+ secretNamespace(shortNameOrDefinition, options) {
256
+ const [shortName, namespaceOptions] = typeof shortNameOrDefinition === "string"
257
+ ? [shortNameOrDefinition, options]
258
+ : splitNamedDefinition(shortNameOrDefinition);
259
+ if (state.secretNamespaces[shortName]) {
260
+ throw new Error(`[Feature ${name}] Secret namespace "${shortName}" already registered. ` +
261
+ `Secret namespace names must be unique per feature.`);
262
+ }
263
+ if (namespaceOptions.writeRoles !== undefined && namespaceOptions.writeRoles.length === 0) {
264
+ throw new Error(`[Feature ${name}] Secret namespace "${shortName}" declares an empty writeRoles list, ` +
265
+ `which nobody could ever satisfy. Omit writeRoles to fall back to the secrets handler access.`);
266
+ }
267
+ const qualifiedPrefix = `${toKebab(name)}:${toKebab(shortName)}.`;
268
+ state.secretNamespaces[shortName] = { shortName, qualifiedPrefix, ...namespaceOptions };
269
+ return { prefix: qualifiedPrefix, keyFor: (suffix) => `${qualifiedPrefix}${suffix}` };
270
+ },
251
271
  claimKey(shortNameOrDefinition, options) {
252
272
  const shortName = typeof shortNameOrDefinition === "string"
253
273
  ? shortNameOrDefinition
@@ -99,7 +99,10 @@ export function buildUiExtensionsMethods(state, name) {
99
99
  `Nav ids must be unique per feature — remove the standalone ` +
100
100
  `r.nav("${navDefinition.id}", ...) call or the screen's inline nav.`);
101
101
  }
102
- state.navs[navDefinition.id] = navDefinition;
102
+ // Explicit `undefined` values (e.g. `icon: cond ? "x" : undefined`) would trip
103
+ // buildAppSchema's JSON-safety check, so strip them for every nav path.
104
+ // @cast-boundary engine-bridge — filtering undefined keeps the NavDefinition shape.
105
+ state.navs[navDefinition.id] = Object.fromEntries(Object.entries(navDefinition).filter(([, value]) => value !== undefined));
103
106
  }
104
107
  return {
105
108
  hook(type, target, fn, options) {
@@ -337,15 +340,12 @@ export function buildUiExtensionsMethods(state, name) {
337
340
  // Sugar for the common "one nav entry pointing at this screen"
338
341
  // case — synthesizes id/screen from the screen's own id. Beyond
339
342
  // label/icon/parent/order, declare a standalone r.nav() instead.
340
- // Optional fields stay absent rather than explicitly undefined —
341
- // buildAppSchema's JSON-safety check flags undefined values. Same
342
- // pattern as r.contentCollection() below.
343
343
  registerNav({
344
344
  id: definition.id,
345
345
  label: definition.nav.label,
346
- ...(definition.nav.icon !== undefined && { icon: definition.nav.icon }),
347
- ...(definition.nav.parent !== undefined && { parent: definition.nav.parent }),
348
- ...(definition.nav.order !== undefined && { order: definition.nav.order }),
346
+ icon: definition.nav.icon,
347
+ parent: definition.nav.parent,
348
+ order: definition.nav.order,
349
349
  screen: `${name}:screen:${definition.id}`,
350
350
  });
351
351
  }
@@ -359,26 +359,20 @@ export function buildUiExtensionsMethods(state, name) {
359
359
  `Collection ids must be unique per feature.`);
360
360
  }
361
361
  // registerNav owns the kebab + collision checks, including collisions
362
- // with a plain r.nav() of the same id. Optional fields stay absent
363
- // rather than explicitly undefined — buildAppSchema's JSON-safety check
364
- // flags undefined values.
362
+ // with a plain r.nav() of the same id.
365
363
  registerNav({
366
364
  id: definition.id,
367
365
  label: definition.nav.label,
368
- ...(definition.nav.icon !== undefined && { icon: definition.nav.icon }),
369
- ...(definition.nav.parent !== undefined && { parent: definition.nav.parent }),
370
- ...(definition.nav.order !== undefined && { order: definition.nav.order }),
366
+ icon: definition.nav.icon,
367
+ parent: definition.nav.parent,
368
+ order: definition.nav.order,
371
369
  // Nav visibility follows the collection's access unless the caller
372
370
  // overrode it — a node the handler would refuse has no business in
373
371
  // the sidebar.
374
- ...((definition.nav.access ?? definition.access) !== undefined && {
375
- access: definition.nav.access ?? definition.access,
376
- }),
377
- ...(definition.nav.workspaces !== undefined && { workspaces: definition.nav.workspaces }),
378
- ...(definition.nav.createAction !== undefined && {
379
- createAction: definition.nav.createAction,
380
- }),
381
- ...(definition.nav.actions !== undefined && { actions: definition.nav.actions }),
372
+ access: definition.nav.access ?? definition.access,
373
+ workspaces: definition.nav.workspaces,
374
+ createAction: definition.nav.createAction,
375
+ actions: definition.nav.actions,
382
376
  // The tree children come from a runtime provider keyed on this QN —
383
377
  // a collection without it would render as an empty leaf.
384
378
  provider: true,
@@ -73,7 +73,7 @@ export { describeWorkflowStepError } from "./steps/describe-workflow-step-error.
73
73
  export { ANONYMOUS_ROLE, ANONYMOUS_USER_ID, createAnonymousUser, createSystemUser, SYSTEM_ROLE, SYSTEM_USER_ID, } from "./system-user.js";
74
74
  export { type EffectiveFeaturesResolver, findTierResolverUsage, isTierResolverPlugin, TENANT_TIER_RESOLVER_EXT, type TierResolverPlugin, type TrialGate, } from "./tier-resolver-extension.js";
75
75
  export { isSystemTenant, isUuid, parseTenantId, SYSTEM_TENANT_ID } from "./types/identifiers.js";
76
- export type { AccessRule, ActionFormScreenDefinition, ActiveMembership, ActiveMembershipRejection, ActiveMembershipResult, AgentExposure, AgentHandlerHints, AgentRisk, AppContext, AppendEventArgs, AppendEventFn, AuthClaimsContext, AuthClaimsFn, AuthClaimsHookDef, BelongsToRelation, BigIntFieldDef, BooleanFieldDef, CamelToKebab, ClaimKeyDefinition, ClaimKeyHandle, ClaimKeyJsType, ClaimKeyType, ConcurrencyMode, ConfigAccessor, ConfigAccessorFactory, ConfigBacking, ConfigCascade, ConfigCascadeLevel, ConfigDefinition, ConfigEditScreenDefinition, ConfigKeyAccess, ConfigKeyDefinition, ConfigKeyHandle, ConfigKeyType, ConfigMask, ConfigResolver, ConfigScope, ConfigSecretsReader, ConfigSeedDef, ConfigStoredRow, ConfigStoredRowWithSource, ConfigValue, ConfigValueSource, ConfigValueWithSource, ContentCollectionDefinition, CreateSeedOptions, CreateTenantSeedOptions, CreateUserSeedOptions, CustomScreenDefinition, CustomScreenRoute, DashboardChartPanel, DashboardCustomPanel, DashboardFeedPanel, DashboardFilterDefinition, DashboardListPanel, DashboardPanelDefinition, DashboardPanelVisibility, DashboardProgressListPanel, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DateFieldDef, DecimalFieldDef, DeleteContext, DeriveContext, DerivedFieldDef, DerivedFieldsMap, DerivedValueType, EditExtensionSection, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EntityDefinition, EntityEditScreenDefinition, EntityId, EntityListExpandableRow, EntityListScreenDefinition, EntityRef, EntityRelations, EscapeHatchAuditSink, EscapeHatchDeclaration, EscapeHatchKind, EscapeHatchReporter, EscapeHatchTarget, EscapeHatchUseEvent, EventDef, ExtensionOptionsArgs, ExtensionOptionsFor, FeatureDefinition, FeatureRegistrar, FieldAccess, FieldCondition, FieldDefinition, FieldFormatRegistry, FieldIconKey, FieldRenderer, FileFieldDef, FilesFieldDef, Findability, FormatSpec, FormWidth, HandlerContext, HasManyRelation, HookMap, ImageFieldDef, ImagesFieldDef, JobBackoff, JobBackoffStrategy, JobContext, JobDefinition, JobHandlerFn, JobTrigger, JobTriggerEvent, JsonbFieldDef, KumikoEntityTypeMap, KumikoEventTypeMap, KumikoExtensionOptionsMap, KumikoHandlerPayloadMap, KumikoHandlerResultMap, LifecycleHookType, ListColumnSpec, ListFacetSpec, ListPaginationMode, ListSortDir, ListSortSpec, LongTextFindability, ManyToManyRelation, MemberReader, MetricNavigate, MetricSpec, MspErrorMode, MspErrorPolicy, MultiSelectFieldDef, MultiStreamApplyFn, MultiStreamProjectionDefinition, NameOrRef, NavDefinition, NavIconKey, NotificationDataFn, NotificationDefinition, NotificationRecipientFn, NotificationTemplateFn, NotifyFactory, NotifyFn, NotifyOptions, NotifyPriority, NumberFieldDef, OnDeleteStrategy, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, ParentRefDef, PersonalAnnotations, PersonalAnnotationsLongText, PersonalAnnotationsNoFind, PersonalSubject, PlatformComponent, PluralForms, PostDeleteHookFn, PostSaveHookFn, PreDeleteHookFn, PreQueryHookFn, PreSaveHookFn, ProjectionDefinition, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, ProjectionTable, QualifiedEventName, QueryEvent, QueryHandlerDef, QueryHandlerFn, RateLimitPer, RecordHeaderSpec, RecordHeaderSubtitlePart, ReferenceDataDef, Registry, RelationDefinition, ResolvedPiiFlags, RetentionDef, RoleAccessPersonalData, RoleAccessRule, RowAction, RowActionNavigate, RowActionNavigateBase, RowActionWriteHandler, RowFieldExtractor, SaveContext, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenNavSugar, ScreenSlots, SecretKeyHandle, SecretMintConfirmStep, SecretMintScreenDefinition, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, SelectFieldDef, SessionUser, SessionUserOrigin, StreamHandlerDef, StreamHandlerFn, Subscribe, TargetRef, TenantId, TextFieldDef, ToolbarAction, TranslationEntry, TranslationKeys, TranslationsDef, TranslationValue, TreeAction, TreeActionDef, TreeActionsHandle, TreeChildrenSubscribe, TreeNode, TreeNodeState, UnitKey, UnsafeAppendEventFn, ValidationError, ValidationHookFn, WorkspaceDefinition, WriteEvent, WriteHandlerDef, WriteHandlerFn, WriteResult, } from "./types/index.js";
76
+ export type { AccessRule, ActionFormScreenDefinition, ActiveMembership, ActiveMembershipRejection, ActiveMembershipResult, AgentExposure, AgentHandlerHints, AgentRisk, AppContext, AppendEventArgs, AppendEventFn, AuthClaimsContext, AuthClaimsFn, AuthClaimsHookDef, BelongsToRelation, BigIntFieldDef, BooleanFieldDef, CamelToKebab, ClaimKeyDefinition, ClaimKeyHandle, ClaimKeyJsType, ClaimKeyType, ConcurrencyMode, ConfigAccessor, ConfigAccessorFactory, ConfigBacking, ConfigCascade, ConfigCascadeLevel, ConfigDefinition, ConfigEditScreenDefinition, ConfigKeyAccess, ConfigKeyDefinition, ConfigKeyHandle, ConfigKeyType, ConfigMask, ConfigResolver, ConfigScope, ConfigSecretsReader, ConfigSeedDef, ConfigStoredRow, ConfigStoredRowWithSource, ConfigValue, ConfigValueSource, ConfigValueWithSource, ContentCollectionDefinition, CreateSeedOptions, CreateTenantSeedOptions, CreateUserSeedOptions, CustomScreenDefinition, CustomScreenRoute, DashboardChartPanel, DashboardCustomPanel, DashboardFeedPanel, DashboardFilterDefinition, DashboardListPanel, DashboardPanelDefinition, DashboardPanelVisibility, DashboardProgressListPanel, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DateFieldDef, DecimalFieldDef, DeleteContext, DeriveContext, DerivedFieldDef, DerivedFieldsMap, DerivedValueType, EditExtensionSection, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EntityDefinition, EntityEditScreenDefinition, EntityId, EntityListExpandableRow, EntityListScreenDefinition, EntityRef, EntityRelations, EscapeHatchAuditSink, EscapeHatchDeclaration, EscapeHatchKind, EscapeHatchReporter, EscapeHatchTarget, EscapeHatchUseEvent, EventDef, ExtensionOptionsArgs, ExtensionOptionsFor, FeatureDefinition, FeatureRegistrar, FieldAccess, FieldCondition, FieldDefinition, FieldFormatRegistry, FieldIconKey, FieldRenderer, FileFieldDef, FilesFieldDef, Findability, FormatSpec, FormWidth, HandlerContext, HasManyRelation, HookMap, ImageFieldDef, ImagesFieldDef, JobBackoff, JobBackoffStrategy, JobContext, JobDefinition, JobHandlerFn, JobTrigger, JobTriggerEvent, JsonbFieldDef, KumikoEntityTypeMap, KumikoEventTypeMap, KumikoExtensionOptionsMap, KumikoHandlerPayloadMap, KumikoHandlerResultMap, LifecycleHookType, ListColumnSpec, ListFacetSpec, ListPaginationMode, ListSortDir, ListSortSpec, LongTextFindability, ManyToManyRelation, MemberReader, MetricNavigate, MetricSpec, MspErrorMode, MspErrorPolicy, MultiSelectFieldDef, MultiStreamApplyFn, MultiStreamProjectionDefinition, NameOrRef, NavDefinition, NavIconKey, NotificationDataFn, NotificationDefinition, NotificationRecipientFn, NotificationTemplateFn, NotifyFactory, NotifyFn, NotifyOptions, NotifyPriority, NumberFieldDef, OnDeleteStrategy, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, ParentRefDef, PersonalAnnotations, PersonalAnnotationsLongText, PersonalAnnotationsNoFind, PersonalSubject, PlatformComponent, PluralForms, PostDeleteHookFn, PostSaveHookFn, PreDeleteHookFn, PreQueryHookFn, PreSaveHookFn, ProjectionDefinition, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, ProjectionTable, QualifiedEventName, QueryEvent, QueryHandlerDef, QueryHandlerFn, RateLimitPer, RecordHeaderSpec, RecordHeaderSubtitlePart, ReferenceDataDef, Registry, RelationDefinition, ResolvedPiiFlags, RetentionDef, RoleAccessPersonalData, RoleAccessRule, RowAction, RowActionNavigate, RowActionNavigateBase, RowActionWriteHandler, RowFieldExtractor, SaveContext, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenNavSugar, ScreenSlots, SecretKeyDefinition, SecretKeyHandle, SecretMintConfirmStep, SecretMintScreenDefinition, SecretNamespaceDefinition, SecretNamespaceHandle, SecretNamespaceOptions, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, SelectFieldDef, SessionUser, SessionUserOrigin, StreamHandlerDef, StreamHandlerFn, Subscribe, TargetRef, TenantId, TextFieldDef, ToolbarAction, TranslationEntry, TranslationKeys, TranslationsDef, TranslationValue, TreeAction, TreeActionDef, TreeActionsHandle, TreeChildrenSubscribe, TreeNode, TreeNodeState, UnitKey, UnsafeAppendEventFn, ValidationError, ValidationHookFn, WorkspaceDefinition, WriteEvent, WriteHandlerDef, WriteHandlerFn, WriteResult, } from "./types/index.js";
77
77
  export { DEFAULT_CURRENCIES, DEFAULT_LOCALES, HookPhases, isAgentVisibleScreen, isOpenToAllGranted, resolveAgentExposure, } from "./types/index.js";
78
78
  export type { AwaitedEventType, PipelineBuildCtx, PipelineCtx, PipelineDef, StepBuilder, StepDef, StepFailureStrategy, StepInstance, StepKind, StepNamespace, StepResolver, } from "./types/step.js";
79
79
  export { runValidation } from "./validation.js";
@@ -14,7 +14,7 @@
14
14
  // the Designer/LLM relies on.
15
15
  import { aiClassifySchema, aiExtractSchema, aiGenerateSchema, authClaimsSchema, defineEventSchema, hookSchema, httpRouteSchema, jobSchema, multiStreamProjectionSchema, notificationSchema, projectionSchema, queryHandlerSchema, screenSchema, streamHandlerSchema, writeHandlerSchema, } from "./mixed-schemas.js";
16
16
  import { envSchemaSchema, exposesApiSchema, extendsRegistrarSchema, treeActionsSchema, unknownSchema, usesApiSchema, } from "./opaque-schemas.js";
17
- import { claimKeySchema, configSchema, describeSchema, entitySchema, metricSchema, navSchema, optionalRequiresSchema, readsConfigSchema, referenceDataSchema, relationSchema, requiresSchema, secretSchema, systemScopeSchema, toggleableSchema, translationsSchema, uiHintsSchema, useExtensionSchema, workspaceSchema, } from "./static-schemas.js";
17
+ import { claimKeySchema, configSchema, describeSchema, entitySchema, metricSchema, navSchema, optionalRequiresSchema, readsConfigSchema, referenceDataSchema, relationSchema, requiresSchema, secretNamespaceSchema, secretSchema, systemScopeSchema, toggleableSchema, translationsSchema, uiHintsSchema, useExtensionSchema, workspaceSchema, } from "./static-schemas.js";
18
18
  export const PATTERN_LIBRARY = {
19
19
  requires: requiresSchema,
20
20
  optionalRequires: optionalRequiresSchema,
@@ -31,6 +31,7 @@ export const PATTERN_LIBRARY = {
31
31
  translations: translationsSchema,
32
32
  metric: metricSchema,
33
33
  secret: secretSchema,
34
+ secretNamespace: secretNamespaceSchema,
34
35
  claimKey: claimKeySchema,
35
36
  referenceData: referenceDataSchema,
36
37
  useExtension: useExtensionSchema,
@@ -14,6 +14,7 @@ export declare const configSchema: PatternFormSchema;
14
14
  export declare const translationsSchema: PatternFormSchema;
15
15
  export declare const metricSchema: PatternFormSchema;
16
16
  export declare const secretSchema: PatternFormSchema;
17
+ export declare const secretNamespaceSchema: PatternFormSchema;
17
18
  export declare const claimKeySchema: PatternFormSchema;
18
19
  export declare const referenceDataSchema: PatternFormSchema;
19
20
  export declare const useExtensionSchema: PatternFormSchema;
@@ -351,10 +351,53 @@ export const secretSchema = {
351
351
  path: "options.scope",
352
352
  label: { en: "Scope", de: "Geltungsbereich" },
353
353
  input: "select",
354
- options: [
355
- { value: "tenant", label: { en: "Tenant" } },
356
- { value: "system", label: { en: "System" } },
357
- ],
354
+ options: [{ value: "tenant", label: { en: "Tenant" } }],
355
+ },
356
+ {
357
+ path: "options.writeRoles",
358
+ label: { en: "Write roles", de: "Schreibrollen" },
359
+ hint: {
360
+ en: "Roles allowed to set or delete. Leave empty to rely on the secrets handler access alone.",
361
+ de: "Rollen, die setzen oder löschen dürfen. Leer lassen, dann entscheidet allein der Handler-Zugriff.",
362
+ },
363
+ input: "string-list",
364
+ },
365
+ ],
366
+ };
367
+ export const secretNamespaceSchema = {
368
+ kind: "secretNamespace",
369
+ label: { en: "Secret namespace", de: "Secret-Namespace" },
370
+ summary: { en: "Family of tenant-scoped secrets with a runtime-chosen name." },
371
+ category: "meta",
372
+ editability: "static",
373
+ fields: [
374
+ {
375
+ path: "shortName",
376
+ label: { en: "Short name", de: "Kurzname" },
377
+ input: "text",
378
+ required: true,
379
+ },
380
+ {
381
+ path: "options.label",
382
+ label: { en: "UI label (i18n)", de: "UI-Label (i18n)" },
383
+ hint: { en: "{ en: 'Webhook token', de: 'Webhook-Token' }" },
384
+ input: "json-readonly",
385
+ required: true,
386
+ },
387
+ {
388
+ path: "options.scope",
389
+ label: { en: "Scope", de: "Geltungsbereich" },
390
+ input: "select",
391
+ options: [{ value: "tenant", label: { en: "Tenant" } }],
392
+ },
393
+ {
394
+ path: "options.writeRoles",
395
+ label: { en: "Write roles", de: "Schreibrollen" },
396
+ hint: {
397
+ en: "Roles allowed to set or delete. Leave empty to rely on the secrets handler access alone.",
398
+ de: "Rollen, die setzen oder löschen dürfen. Leer lassen, dann entscheidet allein der Handler-Zugriff.",
399
+ },
400
+ input: "string-list",
358
401
  },
359
402
  ],
360
403
  };
@@ -125,6 +125,13 @@ export function buildRegistryFacade(state) {
125
125
  getSecretKey(qualifiedName) {
126
126
  return state.secretKeyMap.get(qualifiedName);
127
127
  },
128
+ findSecretNamespace(key) {
129
+ for (const [prefix, def] of state.secretNamespaceMap) {
130
+ if (key.startsWith(prefix))
131
+ return def;
132
+ }
133
+ return undefined;
134
+ },
128
135
  getConfigKey(qualifiedKey) {
129
136
  return state.configKeyMap.get(qualifiedKey);
130
137
  },
@@ -221,6 +221,13 @@ export function populateMetricsAndSecrets(state, feature) {
221
221
  }
222
222
  state.secretKeyMap.set(def.qualifiedName, def);
223
223
  }
224
+ for (const def of Object.values(feature.secretNamespaces ?? {})) {
225
+ if (state.secretNamespaceMap.has(def.qualifiedPrefix)) {
226
+ throw new Error(`[Kumiko Secrets] Secret namespace "${def.qualifiedPrefix}" registered multiple times. ` +
227
+ "Namespace prefixes must be globally unique across features.");
228
+ }
229
+ state.secretNamespaceMap.set(def.qualifiedPrefix, def);
230
+ }
224
231
  }
225
232
  // Explicit + multi-stream projections (source-entity indexed) + store tables +
226
233
  // unmanaged tables (both cross-feature-uniqueness-by-physical-name guarded).
@@ -1,5 +1,5 @@
1
1
  import { type QnType, qualifyEntityName } from "./qualified-name.js";
2
- import type { AuthClaimsHookDef, ClaimKeyDefinition, ConfigKeyDefinition, ConfigSeedDef, EntityDefinition, EntityProjectionExtension, EventDef, EventUpcastFn, ExtensionSelectorPanel, FeatureDefinition, FeatureMetricDef, HookPhase, JobDefinition, MultiStreamProjectionDefinition, NavDefinition, NotificationDefinition, OwnedFn, PhasedHook, PostDeleteHookFn, PostQueryHookFn, PostSaveHookFn, PreDeleteHookFn, PreQueryHookFn, PreSaveHookFn, ProjectionDefinition, QueryHandlerDef, ReferenceDataDef, RegistrarExtensionDef, RegistrarExtensionRegistration, RelationDefinition, ScreenDefinition, SearchPayloadContributorFn, SecretKeyDefinition, StoreTableDef, StreamHandlerDef, TranslationValue, TreeActionDef, WorkspaceDefinition, WriteHandlerDef } from "./types/index.js";
2
+ import type { AuthClaimsHookDef, ClaimKeyDefinition, ConfigKeyDefinition, ConfigSeedDef, EntityDefinition, EntityProjectionExtension, EventDef, EventUpcastFn, ExtensionSelectorPanel, FeatureDefinition, FeatureMetricDef, HookPhase, JobDefinition, MultiStreamProjectionDefinition, NavDefinition, NotificationDefinition, OwnedFn, PhasedHook, PostDeleteHookFn, PostQueryHookFn, PostSaveHookFn, PreDeleteHookFn, PreQueryHookFn, PreSaveHookFn, ProjectionDefinition, QueryHandlerDef, ReferenceDataDef, RegistrarExtensionDef, RegistrarExtensionRegistration, RelationDefinition, ScreenDefinition, SearchPayloadContributorFn, SecretKeyDefinition, SecretNamespaceDefinition, StoreTableDef, StreamHandlerDef, TranslationValue, TreeActionDef, WorkspaceDefinition, WriteHandlerDef } from "./types/index.js";
3
3
  export type IncomingRelation = {
4
4
  sourceEntity: string;
5
5
  relationName: string;
@@ -54,6 +54,7 @@ export type RegistryState = {
54
54
  readonly featureName: string;
55
55
  }>;
56
56
  secretKeyMap: Map<string, SecretKeyDefinition>;
57
+ secretNamespaceMap: Map<string, SecretNamespaceDefinition>;
57
58
  projectionMap: Map<string, ProjectionDefinition>;
58
59
  projectionsBySource: Map<string, ProjectionDefinition[]>;
59
60
  multiStreamProjectionMap: Map<string, MultiStreamProjectionDefinition>;
@@ -122,6 +122,7 @@ export function createInitialState() {
122
122
  mergedTranslations: {},
123
123
  metricMap: new Map(),
124
124
  secretKeyMap: new Map(),
125
+ secretNamespaceMap: new Map(),
125
126
  projectionMap: new Map(),
126
127
  projectionsBySource: new Map(),
127
128
  multiStreamProjectionMap: new Map(),
@@ -16,7 +16,7 @@ export declare function validateRequiredFeatures(state: RegistryState, features:
16
16
  export declare function resolveNotificationTriggersAndRegisterHooks(state: RegistryState): void;
17
17
  export declare function validateLifecycleHookTargets(state: RegistryState): void;
18
18
  export declare function validateEntityHookTargets(state: RegistryState, features: readonly FeatureDefinition[]): void;
19
- export declare function validateJobTriggers(state: RegistryState): void;
19
+ export declare function resolveJobTriggers(state: RegistryState): void;
20
20
  export declare function validateLiveEntities(state: RegistryState): void;
21
21
  export declare function validateBootGates(state: RegistryState): void;
22
22
  export declare function validateJobBackoff(state: RegistryState): void;
@@ -544,29 +544,51 @@ export function validateEntityHookTargets(state, features) {
544
544
  }
545
545
  }
546
546
  }
547
- export function validateJobTriggers(state) {
548
- // Validate: job event triggers must reference an existing write/query
549
- // handler OR an existing r.defineEvent registration. The latter is
550
- // delivered async via the job-trigger event-consumer (server.ts), not
551
- // the synchronous write-handler dispatch path — see
552
- // createJobTriggerEventConsumer in pipeline/system-hooks.ts.
553
- // Multi-Trigger-Form: jeden Eintrag im Array gegen allHandlers prüfen,
554
- // auch wenn nur einer fehlt fail-fast.
547
+ export function resolveJobTriggers(state) {
548
+ // Job event triggers must reference an existing write/query handler OR an
549
+ // existing r.defineEvent registration. The latter is delivered async via the
550
+ // job-trigger event-consumer (server.ts), not the synchronous write-handler
551
+ // dispatch path — see createJobTriggerEventConsumer in
552
+ // pipeline/system-hooks.ts. Short names are qualified against the job's own
553
+ // feature and written back, because the job runner matches by exact QN.
555
554
  const allHandlers = allHandlerQns(state);
556
555
  for (const [jobName, jobDef] of state.jobMap) {
557
556
  if (!("on" in jobDef.trigger))
558
557
  continue;
558
+ const featureName = state.jobFeatureMap.get(jobName);
559
559
  const triggerOn = jobDef.trigger.on;
560
- const triggers = Array.isArray(triggerOn) ? triggerOn : [triggerOn];
561
- for (const t of triggers) {
562
- const rawName = resolveName(t);
563
- if (allHandlers.has(rawName))
564
- continue;
565
- if (state.eventMap.has(rawName))
566
- continue;
567
- throw new Error(`Job "${jobName}" triggers on "${rawName}" but no handler or event with that name exists`);
568
- }
560
+ const resolveTrigger = (trigger) => resolveJobTriggerName(state, allHandlers, jobName, featureName, resolveName(trigger));
561
+ const resolvedOn = isNameOrRefList(triggerOn)
562
+ ? triggerOn.map(resolveTrigger)
563
+ : resolveTrigger(triggerOn);
564
+ state.jobMap.set(jobName, { ...jobDef, trigger: { ...jobDef.trigger, on: resolvedOn } });
565
+ }
566
+ }
567
+ // Array.isArray does not narrow a readonly array out of a union.
568
+ function isNameOrRefList(on) {
569
+ return Array.isArray(on);
570
+ }
571
+ // An already-qualified name (e.g. a misspelled own-feature QN) is not a
572
+ // short name; qualify() rejects it, and the "no handler" error below names it.
573
+ function qualifyShortTriggerName(featureName, type, trigger) {
574
+ try {
575
+ return [qualify(featureName, type, trigger)];
569
576
  }
577
+ catch {
578
+ return [];
579
+ }
580
+ }
581
+ function resolveJobTriggerName(state, allHandlers, jobName, featureName, trigger) {
582
+ if (allHandlers.has(trigger) || state.eventMap.has(trigger))
583
+ return trigger;
584
+ const candidates = featureName === undefined
585
+ ? []
586
+ : ["write", "query", "event"].flatMap((type) => qualifyShortTriggerName(featureName, type, trigger));
587
+ const match = candidates.find((qn) => allHandlers.has(qn) || state.eventMap.has(qn));
588
+ if (match !== undefined)
589
+ return match;
590
+ throw new Error(`Job "${jobName}" triggers on "${trigger}" but no handler or event with that name exists. ` +
591
+ `Tried: ${[trigger, ...candidates].map((name) => `"${name}"`).join(", ")}`);
570
592
  }
571
593
  export function validateLiveEntities(state) {
572
594
  for (const [handlerQn, handler] of state.queryHandlerMap) {
@@ -3,7 +3,7 @@ import { dedupeFeatures } from "./dedupe-features.js";
3
3
  import { buildRegistryFacade } from "./registry-facade.js";
4
4
  import { populateClaimsAndAuth, populateConfigKeys, populateEvents, populateExtensionsAndSeeds, populateFeatureCore, populateHandlers, populateHooks, populateJobsAndNotifications, populateMetricsAndSecrets, populateProjectionsAndTables, populateScreensNavWorkspaces, populateTranslations, } from "./registry-ingest.js";
5
5
  import { createInitialState } from "./registry-state.js";
6
- import { applyExtensionUsages, autoWireSoftDeleteJobs, buildEventUpcasterChains, buildImplicitProjections, buildIncomingRelations, buildSearchableSortableCaches, computeHasRateLimitedHandler, finalizeWorkspaceNavMembership, populateHandlerEntityMappings, publishEventPiiCatalog, resolveNotificationTriggersAndRegisterHooks, validateBootGates, validateEntityHookTargets, validateEventMigrationVersions, validateExtensionSelectors, validateExtensionUsageTargets, validateFieldAccessHandlersAreEntityMapped, validateJobBackoff, validateJobTriggers, validateLifecycleHookTargets, validateLiveEntities, validateProjectionApplyKeys, validateRelationTargetsExist, validateRequiredFeatures, } from "./registry-validate.js";
6
+ import { applyExtensionUsages, autoWireSoftDeleteJobs, buildEventUpcasterChains, buildImplicitProjections, buildIncomingRelations, buildSearchableSortableCaches, computeHasRateLimitedHandler, finalizeWorkspaceNavMembership, populateHandlerEntityMappings, publishEventPiiCatalog, resolveJobTriggers, resolveNotificationTriggersAndRegisterHooks, validateBootGates, validateEntityHookTargets, validateEventMigrationVersions, validateExtensionSelectors, validateExtensionUsageTargets, validateFieldAccessHandlersAreEntityMapped, validateJobBackoff, validateLifecycleHookTargets, validateLiveEntities, validateProjectionApplyKeys, validateRelationTargetsExist, validateRequiredFeatures, } from "./registry-validate.js";
7
7
  // This is where the magic happens. By "magic" I mean: precomputed maps.
8
8
  // I build everything once at boot (hooks, relations, searchable fields, ...)
9
9
  // so nothing has to iterate over objects at runtime. O(1) instead of O(n*m).
@@ -41,7 +41,7 @@ export function createRegistry(rawFeatures) {
41
41
  resolveNotificationTriggersAndRegisterHooks(state);
42
42
  validateLifecycleHookTargets(state);
43
43
  validateEntityHookTargets(state, features);
44
- validateJobTriggers(state);
44
+ resolveJobTriggers(state);
45
45
  validateBootGates(state);
46
46
  validateLiveEntities(state);
47
47
  validateJobBackoff(state);
@@ -4,7 +4,7 @@ export type { QueryHandlerDefinition, StreamHandlerDefinition, WriteHandlerDefin
4
4
  export type { EntityCrudRegistrar, EntityCrudVerb, EntityHandlerOptions, EntityQueryHandlerOptions, EntityWriteHandlerOptions, RegisterEntityCrudOptions, } from "@cosmicdrift/kumiko-types/entity-handlers";
5
5
  export type { KumikoEntityTypeMap, KumikoEventTypeMap, KumikoHandlerPayloadMap, KumikoHandlerResultMap, } from "@cosmicdrift/kumiko-types/event-type-map";
6
6
  export type { ExtensionOptionsArgs, ExtensionOptionsFor, KumikoExtensionOptionsMap, } from "@cosmicdrift/kumiko-types/extension-options-map";
7
- export type { BootCheckContext, BootCheckFn, FeatureDefinition, FeatureMetricDef, FeatureMetricType, FeatureRegistrar, MetricOptions, Registry, SecretKeyDefinition, SecretKeyHandle, SecretOptions, StoreTableDef, StoreTableEntry, StoreTableOptions, UiHintOption, UiHints, } from "@cosmicdrift/kumiko-types/feature";
7
+ export type { BootCheckContext, BootCheckFn, FeatureDefinition, FeatureMetricDef, FeatureMetricType, FeatureRegistrar, MetricOptions, Registry, SecretKeyDefinition, SecretKeyHandle, SecretNamespaceDefinition, SecretNamespaceHandle, SecretNamespaceOptions, SecretOptions, StoreTableDef, StoreTableEntry, StoreTableOptions, UiHintOption, UiHints, } from "@cosmicdrift/kumiko-types/feature";
8
8
  export type { FieldIconKey } from "@cosmicdrift/kumiko-types/field-icon";
9
9
  export type { AnyFileFieldDef, BigIntFieldDef, BooleanFieldDef, ClientDerivedFieldDef, DateFieldDef, DecimalFieldDef, DefaultCurrency, DeriveContext, DerivedFieldDef, DerivedFieldsMap, DerivedValueType, EmbeddedDerivedCellDef, EmbeddedFieldDef, EmbeddedSubFieldDef, EntityDefinition, EntityIndexDef, FieldAccess, FieldDefinition, FieldsMap, FileFieldDef, FilesFieldDef, Findability, ImageFieldDef, ImagesFieldDef, JsonbFieldDef, LocatedTimestampFieldDef, LongTextFieldDef, LongTextFindability, MoneyCurrencySource, MoneyFieldDef, MultiSelectFieldDef, NumberFieldDef, OptionsQueryPayload, OptionsQueryPayloadValue, ParentRefDef, PersonalAnnotations, PersonalAnnotationsLongText, PersonalAnnotationsNoFind, PersonalSubject, ReferenceFieldDef, ResolvedPiiFlags, RetentionDef, SelectFieldDef, SelectOptionTone, TextFieldDef, TimestampFieldDef, TransitionMap, TzFieldDef, } from "@cosmicdrift/kumiko-types/fields";
10
10
  export type { AccessRule, ActiveMembership, ActiveMembershipRejection, ActiveMembershipResult, AgentExposure, AgentHandlerHints, AgentRisk, AggregateStreamHandle, AppContext, AppendEventArgs, AppendEventFn, AuthClaimsContext, AuthClaimsFn, AuthClaimsHookDef, CamelToKebab, ClaimKeyDefinition, ClaimKeyHandle, ClaimKeyJsType, ClaimKeyType, DeclarativeEventMigration, DispatchWriteRef, EntityRef, EscapeHatchAuditSink, EscapeHatchDeclaration, EscapeHatchKind, EscapeHatchReporter, EscapeHatchTarget, EscapeHatchUseEvent, EventDef, EventMigrationDef, EventPiiFields, EventPiiStance, EventUpcastCtx, EventUpcastFn, FetchForWritingArgs, HandlerContext, HandlerRef, JobContext, JobRunnerRef, JobTriggerEvent, MemberReader, NameOrRef, NotifyFactory, NotifyFn, NotifyOptions, NotifyPriority, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, QualifiedEventName, QueryEvent, QueryHandlerDef, QueryHandlerFn, RateLimitDeclaration, RateLimitDisabled, RateLimitOption, RateLimitPer, RoleAccessPersonalData, RoleAccessRule, SessionUser, SessionUserOrigin, StreamHandlerDef, StreamHandlerFn, TryAppendEventFn, TryAppendEventResult, UnsafeAppendEventFn, WriteEvent, WriteHandlerDef, WriteHandlerFn, WriteResult, } from "@cosmicdrift/kumiko-types/handlers";
@@ -18,7 +18,7 @@ export type { FromRule, FromRuleKind, OwnershipClause, OwnershipMap, OwnershipRe
18
18
  export type { EntityProjectionExtension, MspErrorMode, MspErrorPolicy, MultiStreamApplyFn, MultiStreamProjectionDefinition, ProjectionDefinition, ProjectionTable, SingleStreamApplyFn, } from "@cosmicdrift/kumiko-types/projection";
19
19
  export type { BelongsToRelation, EntityRelations, HasManyRelation, ManyToManyRelation, RelationDefinition, } from "@cosmicdrift/kumiko-types/relations";
20
20
  export type { ActionFormScreenDefinition, ConfigEditScreenDefinition, CustomScreenDefinition, CustomScreenRoute, DashboardChartPanel, DashboardCustomPanel, DashboardFeedPanel, DashboardFilterDefinition, DashboardListPanel, DashboardPanelDefinition, DashboardPanelVisibility, DashboardProgressListPanel, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, EditExtensionSection, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EditWriteFormSection, EntityEditScreenDefinition, EntityListExpandableRow, EntityListScreenDefinition, FieldCondition, FieldFormatRegistry, FieldRenderer, FormatSpec, FormWidth, ListColumnSpec, ListFacetSpec, ListPaginationMode, ListSortDir, ListSortSpec, MetricNavigate, MetricSpec, PlatformComponent, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, RecordHeaderSpec, RecordHeaderSubtitlePart, RowAction, RowActionNavigate, RowActionNavigateBase, RowActionWriteHandler, RowFieldExtractor, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenNavSugar, ScreenSlots, SecretMintConfirmStep, SecretMintScreenDefinition, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, ToolbarAction, UnitKey, } from "@cosmicdrift/kumiko-types/screen";
21
- export { metricField } from "@cosmicdrift/kumiko-types/screen";
21
+ export { metricField, relatedListGroupHeaderLabel, relatedListGroupKey, } from "@cosmicdrift/kumiko-types/screen";
22
22
  export type { TargetRef } from "@cosmicdrift/kumiko-types/target-ref";
23
23
  export type { Subscribe, TreeAction, TreeActionDef, TreeActionsHandle, TreeChildrenSubscribe, TreeNode, TreeNodeState, } from "@cosmicdrift/kumiko-types/tree-node";
24
24
  export type { WebSocketConnection, WebSocketMessageData, WebSocketRouteConnectDeps, WebSocketRouteDefinition, WebSocketSessionHandlers, } from "@cosmicdrift/kumiko-types/websocket-route";
@@ -3,7 +3,7 @@
3
3
  export { isAgentVisibleScreen, resolveAgentExposure, } from "@cosmicdrift/kumiko-types/agent-exposure";
4
4
  export { isOpenToAllGranted, isRateLimitDisabled, isUiAccessGranted, } from "@cosmicdrift/kumiko-types/handlers";
5
5
  export { isSystemTenant, isUuid, parseTenantId, SYSTEM_TENANT_ID, } from "@cosmicdrift/kumiko-types/identifiers";
6
- export { metricField } from "@cosmicdrift/kumiko-types/screen";
6
+ export { metricField, relatedListGroupHeaderLabel, relatedListGroupKey, } from "@cosmicdrift/kumiko-types/screen";
7
7
  export { DEFAULT_CURRENCIES, DEFAULT_LOCALES, isFileField } from "../field-helpers.js";
8
8
  export { resolveName, withResponseData } from "../handler-helpers.js";
9
9
  export { HookPhases } from "../hook-helpers.js";
@@ -1,2 +1,2 @@
1
1
  export type * from "@cosmicdrift/kumiko-types/screen";
2
- export { metricField } from "@cosmicdrift/kumiko-types/screen";
2
+ export { metricField, relatedListGroupHeaderLabel, relatedListGroupKey, } from "@cosmicdrift/kumiko-types/screen";
@@ -1 +1 @@
1
- export { metricField } from "@cosmicdrift/kumiko-types/screen";
1
+ export { metricField, relatedListGroupHeaderLabel, relatedListGroupKey, } from "@cosmicdrift/kumiko-types/screen";
@@ -24,6 +24,11 @@ export declare class AccessDeniedError extends KumikoError {
24
24
  readonly httpStatus = 403;
25
25
  constructor(opts?: Pick<ErrorOpts, "message" | "i18nKey" | "i18nParams" | "details" | "cause">);
26
26
  }
27
+ export declare class UnauthenticatedError extends KumikoError {
28
+ readonly code = "unauthenticated";
29
+ readonly httpStatus = 401;
30
+ constructor(opts?: Pick<ErrorOpts, "message" | "details" | "cause">);
31
+ }
27
32
  export type NotFoundDetails = {
28
33
  readonly entity: string;
29
34
  readonly id?: string;