@cosmicdrift/kumiko-framework 0.337.1 → 0.339.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/api/api-constants.d.ts +1 -0
  2. package/dist/api/api-constants.js +9 -0
  3. package/dist/api/auth-middleware.d.ts +1 -0
  4. package/dist/api/auth-middleware.js +56 -12
  5. package/dist/api/routes.d.ts +2 -1
  6. package/dist/api/routes.js +29 -6
  7. package/dist/api/server.js +20 -2
  8. package/dist/bun-db/query.js +8 -2
  9. package/dist/compliance/sub-processors.js +10 -1
  10. package/dist/crypto/event-pii.js +13 -4
  11. package/dist/crypto/index.d.ts +1 -1
  12. package/dist/crypto/index.js +1 -1
  13. package/dist/crypto/pii-ciphertext-format.d.ts +4 -0
  14. package/dist/crypto/pii-ciphertext-format.js +11 -0
  15. package/dist/crypto/pii-field-encryption.d.ts +4 -2
  16. package/dist/crypto/pii-field-encryption.js +50 -21
  17. package/dist/crypto/system-event-pii.js +2 -2
  18. package/dist/db/blind-index-cleanup.d.ts +1 -0
  19. package/dist/db/blind-index-cleanup.js +11 -0
  20. package/dist/db/event-store-executor-read.js +18 -3
  21. package/dist/db/index.d.ts +1 -1
  22. package/dist/db/index.js +1 -1
  23. package/dist/db/queries/backfill-pii.js +4 -0
  24. package/dist/db/tenant-db.js +25 -13
  25. package/dist/engine/boot-validator/access-declarations.js +13 -7
  26. package/dist/engine/boot-validator/entity-handler.d.ts +2 -0
  27. package/dist/engine/boot-validator/entity-handler.js +45 -0
  28. package/dist/engine/boot-validator/index.js +2 -1
  29. package/dist/engine/boot-validator/pii-retention.js +41 -1
  30. package/dist/engine/config-helpers.d.ts +2 -1
  31. package/dist/engine/config-helpers.js +1 -0
  32. package/dist/engine/define-handler.js +1 -0
  33. package/dist/engine/extensions/escape-hatch-usage.js +3 -1
  34. package/dist/engine/extensions/user-data.d.ts +1 -1
  35. package/dist/engine/feature-ast/extractors/handlers.js +1 -0
  36. package/dist/engine/feature-ast/extractors/hooks.js +7 -1
  37. package/dist/engine/feature-ast/pattern-change-schema.d.ts +40 -0
  38. package/dist/engine/feature-ast/pattern-change-schema.js +2 -0
  39. package/dist/engine/feature-config-events-jobs.js +5 -1
  40. package/dist/engine/feature-entity-handlers.js +1 -0
  41. package/dist/engine/feature-manifest.d.ts +8 -0
  42. package/dist/engine/feature-manifest.js +9 -0
  43. package/dist/engine/feature-ui-extensions.js +9 -0
  44. package/dist/engine/index.d.ts +3 -2
  45. package/dist/engine/index.js +1 -0
  46. package/dist/engine/registry-ingest.d.ts +1 -0
  47. package/dist/engine/registry-ingest.js +9 -0
  48. package/dist/engine/registry.js +2 -1
  49. package/dist/engine/steps/retry.js +0 -1
  50. package/dist/engine/steps/wait-for-event.js +0 -1
  51. package/dist/engine/steps/wait.js +0 -4
  52. package/dist/engine/steps/workflow-system-events.d.ts +2 -0
  53. package/dist/engine/steps/workflow-system-events.js +89 -0
  54. package/dist/engine/types/index.d.ts +1 -1
  55. package/dist/jobs/job-runner.js +2 -1
  56. package/dist/pipeline/append-event-core.d.ts +3 -1
  57. package/dist/pipeline/append-event-core.js +8 -4
  58. package/dist/pipeline/dispatch-shared.js +37 -7
  59. package/dist/pipeline/system-identity-switch.js +10 -7
  60. package/package.json +4 -4
  61. package/src/changes.json +102 -0
@@ -7,6 +7,7 @@ export type ManifestConfigKey = {
7
7
  readonly default: string | number | boolean | null;
8
8
  readonly encrypted: boolean;
9
9
  readonly computed: boolean;
10
+ readonly validated: boolean;
10
11
  readonly options: readonly string[] | null;
11
12
  readonly bounds: {
12
13
  readonly min?: number;
@@ -29,6 +30,12 @@ export type ManifestExtension = {
29
30
  readonly extensionName: string;
30
31
  readonly entityName: string;
31
32
  };
33
+ /** Framework-owned registered event (`kumiko:system:*`), e.g. the workflow
34
+ * run-stream events a projection can apply. Not owned by any feature. */
35
+ export type ManifestSystemEvent = {
36
+ readonly name: string;
37
+ readonly version: number;
38
+ };
32
39
  export type ManifestFeature = {
33
40
  readonly name: string;
34
41
  readonly description: string | null;
@@ -57,6 +64,7 @@ export type FeatureManifest = {
57
64
  readonly source: string;
58
65
  readonly featureCount: number;
59
66
  readonly features: readonly ManifestFeature[];
67
+ readonly systemEvents?: readonly ManifestSystemEvent[];
60
68
  readonly tier?: string;
61
69
  };
62
70
  export type BuildManifestOptions = {
@@ -5,6 +5,7 @@
5
5
  // die Kopie still. Quelle der Wahrheit ist die GEBOOTETE Registry — der
6
6
  // AST-Parser kann die imperativen Factory-Helper der bundled features nicht
7
7
  // lesen.
8
+ import { SYSTEM_EVENT_PREFIX } from "../crypto/system-event-pii.js";
8
9
  import { compareByCodepoint } from "../utils/index.js";
9
10
  import { isEncryptedAtRest } from "./config-helpers.js";
10
11
  import { qualifyEntityName } from "./qualified-name.js";
@@ -29,6 +30,7 @@ export function buildManifestFromRegistry(registry, options) {
29
30
  default: def.default ?? null,
30
31
  encrypted: isEncryptedAtRest(def),
31
32
  computed: def.computed !== undefined,
33
+ validated: def.validate !== undefined,
32
34
  options: def.options ?? null,
33
35
  bounds: def.bounds ?? null,
34
36
  pattern: def.pattern ?? null,
@@ -75,10 +77,17 @@ export function buildManifestFromRegistry(registry, options) {
75
77
  });
76
78
  }
77
79
  manifestFeatures.sort((a, b) => compareByCodepoint(a.name, b.name));
80
+ const systemEvents = [];
81
+ for (const [name, info] of registry.getEventUpcasters()) {
82
+ if (name.startsWith(SYSTEM_EVENT_PREFIX))
83
+ systemEvents.push({ name, version: info.currentVersion });
84
+ }
85
+ systemEvents.sort((a, b) => compareByCodepoint(a.name, b.name));
78
86
  return {
79
87
  source: options.source,
80
88
  featureCount: manifestFeatures.length,
81
89
  features: manifestFeatures,
90
+ systemEvents,
82
91
  ...(options.tier !== undefined && { tier: options.tier }),
83
92
  };
84
93
  }
@@ -1,3 +1,4 @@
1
+ import { escapeHatchGrantsProblem } from "@cosmicdrift/kumiko-types/handlers";
1
2
  import { WEBSOCKET_MAX_CONNECTIONS_PER_USER_LIMIT, WEBSOCKET_MAX_PAYLOAD_BYTES, WEBSOCKET_ROUTE_PATH_PREFIX, } from "@cosmicdrift/kumiko-types/websocket-route";
2
3
  import { bindHookEscapeHatchGrant } from "../pipeline/system-identity-switch.js";
3
4
  import { LifecycleHookTypes } from "./constants.js";
@@ -117,6 +118,10 @@ export function buildUiExtensionsMethods(state, name) {
117
118
  throw new Error(`[Feature ${name}] r.hook("${type}", ...) declares { escapeHatch: { reason: "" } } — ` +
118
119
  "the reason must be a non-empty string explaining why this hook switches identity to SYSTEM.");
119
120
  }
121
+ const hookGrantsProblem = options?.escapeHatch && escapeHatchGrantsProblem(options.escapeHatch);
122
+ if (hookGrantsProblem) {
123
+ throw new Error(`[Feature ${name}] r.hook("${type}", ...) declares an invalid escapeHatch — ${hookGrantsProblem}`);
124
+ }
120
125
  // Wrapped once for both branches below; validation hooks stay unwrapped (no context).
121
126
  const hookLabel = `${type} hook of feature "${name}"`;
122
127
  const wrapped = type === "validation"
@@ -211,6 +216,10 @@ export function buildUiExtensionsMethods(state, name) {
211
216
  throw new Error(`[Feature ${name}] r.useExtension("${extensionName}", "${resolvedEntityName}", ...) declares an invalid { escapeHatch } — ` +
212
217
  `must be { reason: "<non-empty string>" } explaining why this usage needs unfiltered db access.`);
213
218
  }
219
+ const extGrantsProblem = escapeHatchGrantsProblem(escapeHatch);
220
+ if (extGrantsProblem) {
221
+ throw new Error(`[Feature ${name}] r.useExtension("${extensionName}", "${resolvedEntityName}", ...) declares an invalid { escapeHatch } — ${extGrantsProblem}`);
222
+ }
214
223
  }
215
224
  state.extensionUsages.push({
216
225
  extensionName,
@@ -40,7 +40,7 @@ export { createBigIntField, createBooleanField, createDateField, createDecimalFi
40
40
  export type { AddEntityArgs, AddHookArgs, AddRelationArgs, AddWriteHandlerArgs, FeaturePatcher, FeaturePattern, FeaturePatternKind, FormFieldLabel, FormFieldSpec, FormInputType, HandlerHeaderUpdate, ParseError, ParseResult, PatternCategory, PatternChange, PatternChangeIssue, PatternChangesParseResult, PatternFormSchema, PatternId, RenderFeatureFileInput, SourceLocation, } from "./feature-ast/index.js";
41
41
  export { addPattern, applyChanges, createFeaturePatcher, getPatternSchema, groupByCategory, PATTERN_LIBRARY, parseFeatureFile, parsePatternChanges, parseSourceFile, removePattern, renderFeatureFile, renderPattern, replacePattern, updatePattern, VERSION_HEADER, } from "./feature-ast/index.js";
42
42
  export { type ChangelogEntry, type ChangelogType, compareVersions, type FeatureChangelog, filterEntriesAfter, parseFeatureChangelog, sortEntries, validateChangelog, } from "./feature-changelog.js";
43
- export { type BuildManifestOptions, buildManifestFromRegistry, type FeatureManifest, type ManifestConfigKey, type ManifestExtension, type ManifestFeature, type ManifestSecret, serializeManifest, } from "./feature-manifest.js";
43
+ export { type BuildManifestOptions, buildManifestFromRegistry, type FeatureManifest, type ManifestConfigKey, type ManifestExtension, type ManifestFeature, type ManifestSecret, type ManifestSystemEvent, serializeManifest, } from "./feature-manifest.js";
44
44
  export { checkWriteFieldOwnership, checkWriteFieldRoles, filterReadFields, maskWriteOnlyFields, } from "./field-access.js";
45
45
  export { resolveName, withResponseData } from "./handler-helpers.js";
46
46
  export { i18nKey } from "./i18n-key.js";
@@ -70,10 +70,11 @@ export { defineTransitions, guardTransition } from "./state-machine.js";
70
70
  export { evaluateEventMatch } from "./steps/_event-match.js";
71
71
  export { STEP_DISPATCH_AGGREGATE_TYPE, STEP_DISPATCH_FAILED_TYPE, STEP_DISPATCH_REQUESTED_TYPE, STEP_DISPATCHED_TYPE, SUSPEND_SENTINEL, WORKFLOW_AGGREGATE_TYPE, WORKFLOW_RESUMED_TYPE, WORKFLOW_RETRY_SCHEDULED_TYPE, WORKFLOW_RUN_COMPLETED_TYPE, WORKFLOW_RUN_FAILED_TYPE, WORKFLOW_RUN_STARTED_TYPE, WORKFLOW_WAITING_FOR_EVENT_TYPE, WORKFLOW_WAITING_TYPE, } from "./steps/_step-dispatch-constants.js";
72
72
  export { describeWorkflowStepError } from "./steps/describe-workflow-step-error.js";
73
+ export { WORKFLOW_SYSTEM_EVENT_DEFS } from "./steps/workflow-system-events.js";
73
74
  export { ANONYMOUS_ROLE, ANONYMOUS_USER_ID, createAnonymousUser, createSystemUser, SYSTEM_ROLE, SYSTEM_USER_ID, } from "./system-user.js";
74
75
  export { type EffectiveFeaturesResolver, findTierResolverUsage, isTierResolverPlugin, TENANT_TIER_RESOLVER_EXT, type TierResolverPlugin, type TrialGate, } from "./tier-resolver-extension.js";
75
76
  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, 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
+ 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, ConfigValidateContext, ConfigValidateFn, 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
78
  export { DEFAULT_CURRENCIES, DEFAULT_LOCALES, HookPhases, isAgentVisibleScreen, isOpenToAllGranted, resolveAgentExposure, } from "./types/index.js";
78
79
  export type { AwaitedEventType, PipelineBuildCtx, PipelineCtx, PipelineDef, StepBuilder, StepDef, StepFailureStrategy, StepInstance, StepKind, StepNamespace, StepResolver, } from "./types/step.js";
79
80
  export { runValidation } from "./validation.js";
@@ -64,6 +64,7 @@ export { defineTransitions, guardTransition } from "./state-machine.js";
64
64
  export { evaluateEventMatch } from "./steps/_event-match.js";
65
65
  export { STEP_DISPATCH_AGGREGATE_TYPE, STEP_DISPATCH_FAILED_TYPE, STEP_DISPATCH_REQUESTED_TYPE, STEP_DISPATCHED_TYPE, SUSPEND_SENTINEL, WORKFLOW_AGGREGATE_TYPE, WORKFLOW_RESUMED_TYPE, WORKFLOW_RETRY_SCHEDULED_TYPE, WORKFLOW_RUN_COMPLETED_TYPE, WORKFLOW_RUN_FAILED_TYPE, WORKFLOW_RUN_STARTED_TYPE, WORKFLOW_WAITING_FOR_EVENT_TYPE, WORKFLOW_WAITING_TYPE, } from "./steps/_step-dispatch-constants.js";
66
66
  export { describeWorkflowStepError } from "./steps/describe-workflow-step-error.js";
67
+ export { WORKFLOW_SYSTEM_EVENT_DEFS } from "./steps/workflow-system-events.js";
67
68
  export { ANONYMOUS_ROLE, ANONYMOUS_USER_ID, createAnonymousUser, createSystemUser, SYSTEM_ROLE, SYSTEM_USER_ID, } from "./system-user.js";
68
69
  export { findTierResolverUsage, isTierResolverPlugin, TENANT_TIER_RESOLVER_EXT, } from "./tier-resolver-extension.js";
69
70
  export { isSystemTenant, isUuid, parseTenantId, SYSTEM_TENANT_ID } from "./types/identifiers.js";
@@ -5,6 +5,7 @@ export declare function populateHandlers(state: RegistryState, feature: FeatureD
5
5
  export declare function populateConfigKeys(state: RegistryState, feature: FeatureDefinition): void;
6
6
  export declare function populateJobsAndNotifications(state: RegistryState, feature: FeatureDefinition): void;
7
7
  export declare function populateEvents(state: RegistryState, feature: FeatureDefinition): void;
8
+ export declare function populateSystemEvents(state: RegistryState): void;
8
9
  export declare function populateTranslations(state: RegistryState, feature: FeatureDefinition): void;
9
10
  export declare function populateHooks(state: RegistryState, feature: FeatureDefinition): void;
10
11
  export declare function populateExtensionsAndSeeds(state: RegistryState, feature: FeatureDefinition): void;
@@ -1,6 +1,7 @@
1
1
  import { resolveTableName } from "../db/entity-table-meta.js";
2
2
  import { buildMetricName, validateMetricName } from "../observability/index.js";
3
3
  import { mergeHookList, mergeHookListQualified, qualify } from "./registry-state.js";
4
+ import { WORKFLOW_SYSTEM_EVENT_DEFS } from "./steps/workflow-system-events.js";
4
5
  // Feature registration + entities (globally-unique, physical-table-checked) + relations
5
6
  // (additive per entity, duplicate-per-name guarded).
6
7
  export function populateFeatureCore(state, feature) {
@@ -132,6 +133,14 @@ export function populateEvents(state, feature) {
132
133
  state.eventMap.set(qualified, { ...eventDef, name: qualified });
133
134
  }
134
135
  }
136
+ // Framework-owned events outside any feature namespace (kumiko:system:workflow.*).
137
+ // Seeded into the same eventMap as r.defineEvent so apply-key validation, the
138
+ // PII catalog and the upcaster chain see them through one path.
139
+ export function populateSystemEvents(state) {
140
+ for (const [name, eventDef] of WORKFLOW_SYSTEM_EVENT_DEFS) {
141
+ state.eventMap.set(name, eventDef);
142
+ }
143
+ }
135
144
  export function populateTranslations(state, feature) {
136
145
  // Translations prefixed with featureName: (i18next namespace convention).
137
146
  // Keys that already carry the feature's own namespace prefix (e.g. a nav
@@ -1,7 +1,7 @@
1
1
  import { validateExtensionPreSaveWiring } from "./boot-validator/entity-handler.js";
2
2
  import { dedupeFeatures } from "./dedupe-features.js";
3
3
  import { buildRegistryFacade } from "./registry-facade.js";
4
- import { populateClaimsAndAuth, populateConfigKeys, populateEvents, populateExtensionsAndSeeds, populateFeatureCore, populateHandlers, populateHooks, populateJobsAndNotifications, populateMetricsAndSecrets, populateProjectionsAndTables, populateScreensNavWorkspaces, populateTranslations, } from "./registry-ingest.js";
4
+ import { populateClaimsAndAuth, populateConfigKeys, populateEvents, populateExtensionsAndSeeds, populateFeatureCore, populateHandlers, populateHooks, populateJobsAndNotifications, populateMetricsAndSecrets, populateProjectionsAndTables, populateScreensNavWorkspaces, populateSystemEvents, populateTranslations, } from "./registry-ingest.js";
5
5
  import { createInitialState } from "./registry-state.js";
6
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.
@@ -24,6 +24,7 @@ export function createRegistry(rawFeatures) {
24
24
  populateClaimsAndAuth(state, feature);
25
25
  populateScreensNavWorkspaces(state, feature);
26
26
  }
27
+ populateSystemEvents(state);
27
28
  finalizeWorkspaceNavMembership(state);
28
29
  populateHandlerEntityMappings(state, features);
29
30
  validateExtensionSelectors(state);
@@ -54,7 +54,6 @@ defineStep({
54
54
  workflowName: ctx.workflow.workflowName,
55
55
  error: describeWorkflowStepError(error),
56
56
  triggerEventType: ctx.event.type,
57
- triggerPayload: ctx.event.payload,
58
57
  ...(ctx.workflow.definitionFingerprint && {
59
58
  definitionFingerprint: ctx.workflow.definitionFingerprint,
60
59
  }),
@@ -49,7 +49,6 @@ defineStep({
49
49
  stepIndex: ctx.workflow.stepIndex,
50
50
  workflowName: ctx.workflow.workflowName,
51
51
  triggerEventType: ctx.event.type,
52
- triggerPayload: ctx.event.payload,
53
52
  ...(ctx.workflow.definitionFingerprint && {
54
53
  definitionFingerprint: ctx.workflow.definitionFingerprint,
55
54
  }),
@@ -32,11 +32,7 @@ defineStep({
32
32
  wakeAt,
33
33
  stepIndex: ctx.workflow.stepIndex,
34
34
  workflowName: ctx.workflow.workflowName,
35
- // Trigger snapshot: pinned so the resume-loop re-feeds the
36
- // pipeline with what the original run saw. event-sourcing across
37
- // suspensions hinges on this being stable.
38
35
  triggerEventType: ctx.event.type,
39
- triggerPayload: ctx.event.payload,
40
36
  ...(ctx.workflow.definitionFingerprint && {
41
37
  definitionFingerprint: ctx.workflow.definitionFingerprint,
42
38
  }),
@@ -0,0 +1,2 @@
1
+ import type { EventDef } from "@cosmicdrift/kumiko-types/handlers";
2
+ export declare const WORKFLOW_SYSTEM_EVENT_DEFS: ReadonlyMap<string, EventDef>;
@@ -0,0 +1,89 @@
1
+ // Registered-event declarations for the `kumiko:system:workflow.*` run-stream
2
+ // events. The names stay the literal system types stored in kumiko_events;
3
+ // registering them makes projections (r.extendEntityProjection / r.projection
4
+ // with source `workflow-run`) valid apply-key targets and lists them in the
5
+ // registry's event catalog.
6
+ //
7
+ // Appends of these types keep bypassing schema validation (append-event-core):
8
+ // the schemas here document the payloads the writers append and tolerate the
9
+ // legacy shapes still stored in old streams.
10
+ import * as z from "zod";
11
+ import { SYSTEM_EVENT_PII_STANCES } from "../../crypto/system-event-pii.js";
12
+ import { WORKFLOW_RESUMED_TYPE, WORKFLOW_RETRY_SCHEDULED_TYPE, WORKFLOW_RUN_COMPLETED_TYPE, WORKFLOW_RUN_FAILED_TYPE, WORKFLOW_RUN_STARTED_TYPE, WORKFLOW_WAITING_FOR_EVENT_TYPE, WORKFLOW_WAITING_TYPE, } from "./_step-dispatch-constants.js";
13
+ const triggerEventRefSchema = z.object({
14
+ eventId: z.string(),
15
+ aggregateId: z.string(),
16
+ version: z.number().int(),
17
+ });
18
+ // Optional on every step event: runs started before the Q7 fingerprint existed
19
+ // have none.
20
+ const stepContext = {
21
+ workflowName: z.string(),
22
+ stepIndex: z.number().int(),
23
+ triggerEventType: z.string(),
24
+ definitionFingerprint: z.string().optional(),
25
+ };
26
+ const WORKFLOW_SYSTEM_EVENT_SCHEMAS = [
27
+ [
28
+ WORKFLOW_RUN_STARTED_TYPE,
29
+ z.object({
30
+ workflowName: z.string(),
31
+ triggerEventType: z.string(),
32
+ // Current streams reference the trigger event; streams written before
33
+ // #3474 carry a copy of its payload instead.
34
+ triggerEventRef: triggerEventRefSchema.optional(),
35
+ triggerPayload: z.unknown().optional(),
36
+ definitionFingerprint: z.string().optional(),
37
+ idempotencyKey: z.string().optional(),
38
+ }),
39
+ ],
40
+ [
41
+ WORKFLOW_RUN_COMPLETED_TYPE,
42
+ z.object({ workflowName: z.string(), stepIndex: z.number().int() }),
43
+ ],
44
+ [
45
+ WORKFLOW_RUN_FAILED_TYPE,
46
+ z.object({
47
+ workflowName: z.string(),
48
+ stepIndex: z.number().int(),
49
+ error: z.string(),
50
+ reason: z.string().optional(),
51
+ }),
52
+ ],
53
+ [WORKFLOW_WAITING_TYPE, z.object({ ...stepContext, wakeAt: z.string() })],
54
+ [
55
+ WORKFLOW_WAITING_FOR_EVENT_TYPE,
56
+ z.object({
57
+ ...stepContext,
58
+ eventType: z.string(),
59
+ match: z.record(z.string(), z.unknown()).optional(),
60
+ timeoutAt: z.string(),
61
+ }),
62
+ ],
63
+ [
64
+ WORKFLOW_RESUMED_TYPE,
65
+ z.object({ stepIndex: z.number().int(), retryAttempt: z.number().int().optional() }),
66
+ ],
67
+ [
68
+ WORKFLOW_RETRY_SCHEDULED_TYPE,
69
+ z.object({
70
+ ...stepContext,
71
+ attempt: z.number().int(),
72
+ maxAttempts: z.number().int(),
73
+ wakeAt: z.string(),
74
+ error: z.string(),
75
+ }),
76
+ ],
77
+ ];
78
+ function buildWorkflowSystemEventDefs() {
79
+ const defs = new Map();
80
+ for (const [name, schema] of WORKFLOW_SYSTEM_EVENT_SCHEMAS) {
81
+ const piiFields = SYSTEM_EVENT_PII_STANCES.get(name);
82
+ if (piiFields === undefined) {
83
+ throw new Error(`Workflow system event "${name}" has no entry in SYSTEM_EVENT_PII_STANCES`);
84
+ }
85
+ defs.set(name, { name, schema, version: 1, piiFields });
86
+ }
87
+ return defs;
88
+ }
89
+ export const WORKFLOW_SYSTEM_EVENT_DEFS = buildWorkflowSystemEventDefs();
@@ -1,5 +1,5 @@
1
1
  export { isAgentVisibleScreen, resolveAgentExposure, } from "@cosmicdrift/kumiko-types/agent-exposure";
2
- export type { ConfigAccessor, ConfigAccessorFactory, ConfigBacking, ConfigBounds, ConfigCascade, ConfigCascadeLevel, ConfigComputedContext, ConfigComputedFn, ConfigDefinition, ConfigKeyAccess, ConfigKeyDefinition, ConfigKeyHandle, ConfigKeyType, ConfigMask, ConfigResolver, ConfigSecretsReader, ConfigSeedDef, ConfigStoredRow, ConfigStoredRowWithSource, ConfigValue, ConfigValueSource, ConfigValueWithSource, CreateSeedOptions, CreateTenantSeedOptions, CreateUserSeedOptions, ExtensionSelectorDef, ExtensionSelectorPanel, JobBackoff, JobBackoffStrategy, JobDefinition, JobHandlerFn, JobRunIn, JobTrigger, NotificationDataFn, NotificationDefinition, NotificationRecipientFn, NotificationTemplateFn, PluralForms, ReferenceDataDef, RegistrarExtensionDef, RegistrarExtensionHooks, RegistrarExtensionRegistration, RunIn, TranslationEntry, TranslationKeys, TranslationsDef, TranslationValue, UiExtensionDef, } from "@cosmicdrift/kumiko-types/config";
2
+ export type { ConfigAccessor, ConfigAccessorFactory, ConfigBacking, ConfigBounds, ConfigCascade, ConfigCascadeLevel, ConfigComputedContext, ConfigComputedFn, ConfigDefinition, ConfigKeyAccess, ConfigKeyDefinition, ConfigKeyHandle, ConfigKeyType, ConfigMask, ConfigResolver, ConfigSecretsReader, ConfigSeedDef, ConfigStoredRow, ConfigStoredRowWithSource, ConfigValidateContext, ConfigValidateFn, ConfigValue, ConfigValueSource, ConfigValueWithSource, CreateSeedOptions, CreateTenantSeedOptions, CreateUserSeedOptions, ExtensionSelectorDef, ExtensionSelectorPanel, JobBackoff, JobBackoffStrategy, JobDefinition, JobHandlerFn, JobRunIn, JobTrigger, NotificationDataFn, NotificationDefinition, NotificationRecipientFn, NotificationTemplateFn, PluralForms, ReferenceDataDef, RegistrarExtensionDef, RegistrarExtensionHooks, RegistrarExtensionRegistration, RunIn, TranslationEntry, TranslationKeys, TranslationsDef, TranslationValue, UiExtensionDef, } from "@cosmicdrift/kumiko-types/config";
3
3
  export type { QueryHandlerDefinition, StreamHandlerDefinition, WriteHandlerDefinition, WriteHandlerInput, } from "@cosmicdrift/kumiko-types/define-handler";
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";
@@ -1,3 +1,4 @@
1
+ import { escapeHatchFor } from "@cosmicdrift/kumiko-types/handlers";
1
2
  import { Queue, UnrecoverableError, Worker } from "bullmq";
2
3
  import { Redis } from "ioredis";
3
4
  import { requestContext } from "../api/request-context.js";
@@ -703,7 +704,7 @@ export function createJobRunner(options) {
703
704
  : undefined;
704
705
  const jobDb = configDb
705
706
  ? createTenantDb(configDb, tenantId, "tenant", context.tracer, context.meter, undefined, {
706
- unsafeRaw: jobDef.escapeHatch,
707
+ unsafeRaw: escapeHatchFor(jobDef.escapeHatch, "unsafeRaw"),
707
708
  report: reportEscapeHatch,
708
709
  ...(jobPersonalDataGate && { personalDataGate: jobPersonalDataGate }),
709
710
  })
@@ -9,4 +9,6 @@ export type AppendDomainEventCoreDeps = {
9
9
  readonly callSiteLabel: string;
10
10
  readonly callerFeature?: string;
11
11
  };
12
- export declare function appendDomainEventCore(deps: AppendDomainEventCoreDeps, args: AppendEventArgs): Promise<StoredEvent>;
12
+ export declare function appendDomainEventCore(deps: AppendDomainEventCoreDeps, args: AppendEventArgs & {
13
+ readonly expectedVersion?: number;
14
+ }): Promise<StoredEvent>;
@@ -33,10 +33,12 @@ export async function appendDomainEventCore(deps, args) {
33
33
  });
34
34
  }
35
35
  }
36
- // System events skip schema validation — payload shape is owned by the
37
- // framework step that emits + the bundled MSP that consumes.
36
+ // System events skip schema validation even when registered (workflow
37
+ // events are, so projections can apply them): payload shape is owned by the
38
+ // framework step that emits + the bundled MSP that consumes, and stored
39
+ // streams keep legacy shapes. Parsing would also strip unknown keys.
38
40
  let validatedPayload;
39
- if (eventDef) {
41
+ if (eventDef && !isSystemEvent) {
40
42
  const parsed = eventDef.schema.safeParse(args.payload ?? {});
41
43
  if (!parsed.success)
42
44
  throw validationErrorFromZod(parsed.error);
@@ -53,7 +55,9 @@ export async function appendDomainEventCore(deps, args) {
53
55
  }
54
56
  // Stream-version authoritative. See Block 0 / getStreamVersion doc for
55
57
  // why row.version isn't sufficient once ctx.appendEvent enters the picture.
56
- const expectedVersion = await getStreamVersion(deps.db, args.aggregateId, deps.tenantId);
58
+ // A caller-pinned version (fetchForWriting handles) must reach append() so a
59
+ // write landing between fetch and append conflicts instead of being re-read.
60
+ const expectedVersion = args.expectedVersion ?? (await getStreamVersion(deps.db, args.aggregateId, deps.tenantId));
57
61
  const reqCtx = requestContext.get();
58
62
  // metadata.requestId is a plain trace marker — no uniqueness constraint,
59
63
  // every event of the request carries it. HTTP-level idempotency runs in
@@ -1,9 +1,11 @@
1
+ import { escapeHatchFor } from "@cosmicdrift/kumiko-types/handlers";
1
2
  import { requestContext, runWithOrigin } from "../api/request-context.js";
2
3
  import { runInSavepoint, selectMany } from "../db/query.js";
3
4
  import { createTenantDb, createUncheckedSystemDb, hasTenantColumn, } from "../db/tenant-db.js";
4
5
  import { createDerivativesContext } from "../derivatives/derivatives-context.js";
6
+ import { createSystemUser } from "../engine/system-user.js";
5
7
  import { isRateLimitDisabled, resolveAgentExposure } from "../engine/types/index.js";
6
- import { FeatureDisabledError, InternalError, memberResolutionReadOnlyDenied, VersionConflictError, } from "../errors/index.js";
8
+ import { AccessDeniedError, FeatureDisabledError, InternalError, memberResolutionReadOnlyDenied, VersionConflictError, } from "../errors/index.js";
7
9
  import { archiveStream as archiveStreamHelper, isStreamArchived, restoreStream as restoreStreamHelper, } from "../event-store/archive.js";
8
10
  import { IdempotentAppendConflictError as EventStoreIdempotentAppendConflictError, VersionConflictError as EventStoreVersionConflictError, } from "../event-store/errors.js";
9
11
  import { getStreamVersion, loadAggregate, loadAggregateAsOf, } from "../event-store/event-store.js";
@@ -148,7 +150,8 @@ export async function buildHandlerContext(ctx, type, user, origin, tx, afterComm
148
150
  const handlerEscapeHatch = writeEscapeHatch ??
149
151
  registry.getQueryHandler(type)?.escapeHatch ??
150
152
  registry.getStreamHandler(type)?.escapeHatch;
151
- const hasIdentitySwitchGrant = isSystem || handlerEscapeHatch !== undefined;
153
+ const identitySwitchEscapeHatch = escapeHatchFor(handlerEscapeHatch, "systemIdentity");
154
+ const hasIdentitySwitchGrant = isSystem || identitySwitchEscapeHatch !== undefined;
152
155
  const featureName = registry.getHandlerFeature(type);
153
156
  const reportEscapeHatch = createEscapeHatchReporter({
154
157
  handler: type,
@@ -158,11 +161,11 @@ export async function buildHandlerContext(ctx, type, user, origin, tx, afterComm
158
161
  log: context.log,
159
162
  window: ctx.escapeHatchReportWindow,
160
163
  });
161
- const identitySwitchGrantReason = handlerEscapeHatch?.reason ??
164
+ const identitySwitchGrantReason = identitySwitchEscapeHatch?.reason ??
162
165
  (isSystem ? `r.systemScope() feature "${featureName ?? "unknown"}"` : undefined);
163
166
  const buildTenantScopedDb = (source, signal) => createTenantDb(source, user.tenantId, isSystem ? "system" : "tenant", context.tracer, context.meter, signal, {
164
- globalWrites: writeEscapeHatch,
165
- unsafeRaw: handlerEscapeHatch,
167
+ globalWrites: escapeHatchFor(writeEscapeHatch, "globalWrites"),
168
+ unsafeRaw: escapeHatchFor(handlerEscapeHatch, "unsafeRaw"),
166
169
  report: reportEscapeHatch,
167
170
  memberReadOnly: isMemberResolutionPrincipal(user),
168
171
  personalDataGate: buildPersonalDataGate(registry, origin),
@@ -212,6 +215,29 @@ export async function buildHandlerContext(ctx, type, user, origin, tx, afterComm
212
215
  secrets: context.secrets,
213
216
  })
214
217
  : undefined;
218
+ // Cross-tenant accessor for SystemAdmin/system callers. The
219
+ // identity is the target tenant's system user so the caller's own user-scope
220
+ // values never resolve into another tenant's config; the db is tenant-mode
221
+ // scoped to the target.
222
+ const configFactory = context._configAccessorFactory;
223
+ const configFor = configFactory && dbSource
224
+ ? (targetTenantId) => {
225
+ if (targetTenantId === user.tenantId && config)
226
+ return config;
227
+ if (!isSystemIdentity(user) && !user.roles.includes("SystemAdmin")) {
228
+ throw new AccessDeniedError({
229
+ message: "ctx.configFor(otherTenant) requires the system role or SystemAdmin",
230
+ details: { reason: "config_for_requires_system_admin" },
231
+ });
232
+ }
233
+ const targetSystemUser = createSystemUser(targetTenantId);
234
+ return configFactory({
235
+ user: { id: targetSystemUser.id, tenantId: targetTenantId },
236
+ db: createTenantDb(dbSource, targetTenantId, "tenant", context.tracer, context.meter),
237
+ secrets: context.secrets,
238
+ });
239
+ }
240
+ : undefined;
215
241
  // ctx.files resolved per-tenant through file-foundation (lazy — the
216
242
  // provider is only resolved when a handle actually does I/O). Boot wires
217
243
  // _fileProviderResolver when a file-provider plugin is mounted; falls back
@@ -373,8 +399,10 @@ export async function buildHandlerContext(ctx, type, user, origin, tx, afterComm
373
399
  currentVersion: fetchedVersion,
374
400
  });
375
401
  }
376
- // Handle's internal version bumps on every appendOne so multiple
377
- // appends in a row stay in order without re-reading the DB.
402
+ // Pinned into every append: a write landing between fetch and append
403
+ // fails with VersionConflictError instead of stacking on a state the
404
+ // caller's business rules never saw. Bumps per appendOne so consecutive
405
+ // appends stay in order without re-reading the DB.
378
406
  let handleVersion = fetchedVersion;
379
407
  const appendOne = async (appendArgs) => {
380
408
  await appendDomainEvent(ctx, {
@@ -382,6 +410,7 @@ export async function buildHandlerContext(ctx, type, user, origin, tx, afterComm
382
410
  aggregateType: args.aggregateType,
383
411
  type: appendArgs.type,
384
412
  payload: appendArgs.payload,
413
+ expectedVersion: handleVersion,
385
414
  }, user, tx, registry.getHandlerFeature(type));
386
415
  handleVersion += 1;
387
416
  };
@@ -584,6 +613,7 @@ export async function buildHandlerContext(ctx, type, user, origin, tx, afterComm
584
613
  log,
585
614
  notify,
586
615
  ...(config && { config }),
616
+ ...(configFor && { configFor }),
587
617
  ...(files && { files }),
588
618
  ...(derivatives && { derivatives }),
589
619
  // preSave hooks need `changes`/`previous`/`isNew`, which only exist once
@@ -1,3 +1,4 @@
1
+ import { escapeHatchFor } from "@cosmicdrift/kumiko-types/handlers";
1
2
  import { withSystemDbUnsafeRawGrant, withUnsafeRawGrant, } from "../db/tenant-db.js";
2
3
  import { SYSTEM_ROLE, SYSTEM_USER_ID } from "../engine/system-user.js";
3
4
  import { AccessDeniedError, FrameworkReasons, InternalError } from "../errors/index.js";
@@ -212,6 +213,8 @@ export function withHookEscapeHatchGrant(context, callerLabel, escapeHatch) {
212
213
  const ctxDb = readDbLikeValue(context, "db");
213
214
  const ctxDbOutsideTransaction = readDbLikeValue(context, "dbOutsideTransaction");
214
215
  const ctxSystemDb = readDbLikeValue(context, "systemDb");
216
+ const identityHatch = escapeHatchFor(escapeHatch, "systemIdentity");
217
+ const rawHatch = escapeHatchFor(escapeHatch, "unsafeRaw");
215
218
  if (!ctxQueryAs &&
216
219
  !ctxWriteAs &&
217
220
  !ctxResolveActiveMembership &&
@@ -224,21 +227,21 @@ export function withHookEscapeHatchGrant(context, callerLabel, escapeHatch) {
224
227
  }
225
228
  return {
226
229
  ...context,
227
- ...gatedIdentitySwitchFields(callerLabel, escapeHatch, ctxQueryAs, ctxWriteAs),
230
+ ...gatedIdentitySwitchFields(callerLabel, identityHatch, ctxQueryAs, ctxWriteAs),
228
231
  ...(ctxResolveActiveMembership &&
229
- escapeHatch === undefined && {
232
+ identityHatch === undefined && {
230
233
  resolveActiveMembership: deniedResolveActiveMembership(callerLabel),
231
234
  }),
232
- ...gatedMemberReaderField(callerLabel, escapeHatch, ctxQueryAsMember),
233
- ...gatedProjectionReaderField(callerLabel, escapeHatch, ctxQueryProjection),
235
+ ...gatedMemberReaderField(callerLabel, identityHatch, ctxQueryAsMember),
236
+ ...gatedProjectionReaderField(callerLabel, identityHatch, ctxQueryProjection),
234
237
  // @cast-boundary engine-bridge — withUnsafeRawGrant passes non-TenantDb values (e.g. a guard Proxy) through unchanged.
235
- ...(ctxDb && { db: withUnsafeRawGrant(ctxDb, escapeHatch) }),
238
+ ...(ctxDb && { db: withUnsafeRawGrant(ctxDb, rawHatch) }),
236
239
  ...(ctxDbOutsideTransaction && {
237
- dbOutsideTransaction: withUnsafeRawGrant(ctxDbOutsideTransaction, escapeHatch),
240
+ dbOutsideTransaction: withUnsafeRawGrant(ctxDbOutsideTransaction, rawHatch),
238
241
  }),
239
242
  // @cast-boundary engine-bridge — withSystemDbUnsafeRawGrant passes non-UncheckedSystemDb values through unchanged.
240
243
  ...(ctxSystemDb && {
241
- systemDb: withSystemDbUnsafeRawGrant(ctxSystemDb, escapeHatch, callerLabel),
244
+ systemDb: withSystemDbUnsafeRawGrant(ctxSystemDb, rawHatch, callerLabel),
242
245
  }),
243
246
  };
244
247
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-framework",
3
- "version": "0.337.1",
3
+ "version": "0.339.0",
4
4
  "description": "Framework core — engine, pipeline, API, DB, and every other bit that makes Kumiko go.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -206,8 +206,8 @@
206
206
  "./package.json": "./package.json"
207
207
  },
208
208
  "dependencies": {
209
- "@cosmicdrift/kumiko-http": "0.337.1",
210
- "@cosmicdrift/kumiko-types": "0.337.1",
209
+ "@cosmicdrift/kumiko-http": "0.339.0",
210
+ "@cosmicdrift/kumiko-types": "0.339.0",
211
211
  "bullmq": "^5.76.7",
212
212
  "bun-types": "^1.3.13",
213
213
  "hono": "^4.13.1",
@@ -223,7 +223,7 @@
223
223
  "zod": "^4.4.3"
224
224
  },
225
225
  "devDependencies": {
226
- "@cosmicdrift/kumiko-dispatcher-live": "0.337.1",
226
+ "@cosmicdrift/kumiko-dispatcher-live": "0.339.0",
227
227
  "bun-types": "^1.3.13",
228
228
  "pino-pretty": "^13.1.3"
229
229
  },