@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.
- package/dist/api/api-constants.d.ts +1 -0
- package/dist/api/api-constants.js +9 -0
- package/dist/api/auth-middleware.d.ts +1 -0
- package/dist/api/auth-middleware.js +56 -12
- package/dist/api/routes.d.ts +2 -1
- package/dist/api/routes.js +29 -6
- package/dist/api/server.js +20 -2
- package/dist/bun-db/query.js +8 -2
- package/dist/compliance/sub-processors.js +10 -1
- package/dist/crypto/event-pii.js +13 -4
- package/dist/crypto/index.d.ts +1 -1
- package/dist/crypto/index.js +1 -1
- package/dist/crypto/pii-ciphertext-format.d.ts +4 -0
- package/dist/crypto/pii-ciphertext-format.js +11 -0
- package/dist/crypto/pii-field-encryption.d.ts +4 -2
- package/dist/crypto/pii-field-encryption.js +50 -21
- package/dist/crypto/system-event-pii.js +2 -2
- package/dist/db/blind-index-cleanup.d.ts +1 -0
- package/dist/db/blind-index-cleanup.js +11 -0
- package/dist/db/event-store-executor-read.js +18 -3
- package/dist/db/index.d.ts +1 -1
- package/dist/db/index.js +1 -1
- package/dist/db/queries/backfill-pii.js +4 -0
- package/dist/db/tenant-db.js +25 -13
- package/dist/engine/boot-validator/access-declarations.js +13 -7
- package/dist/engine/boot-validator/entity-handler.d.ts +2 -0
- package/dist/engine/boot-validator/entity-handler.js +45 -0
- package/dist/engine/boot-validator/index.js +2 -1
- package/dist/engine/boot-validator/pii-retention.js +41 -1
- package/dist/engine/config-helpers.d.ts +2 -1
- package/dist/engine/config-helpers.js +1 -0
- package/dist/engine/define-handler.js +1 -0
- package/dist/engine/extensions/escape-hatch-usage.js +3 -1
- package/dist/engine/extensions/user-data.d.ts +1 -1
- package/dist/engine/feature-ast/extractors/handlers.js +1 -0
- package/dist/engine/feature-ast/extractors/hooks.js +7 -1
- package/dist/engine/feature-ast/pattern-change-schema.d.ts +40 -0
- package/dist/engine/feature-ast/pattern-change-schema.js +2 -0
- package/dist/engine/feature-config-events-jobs.js +5 -1
- package/dist/engine/feature-entity-handlers.js +1 -0
- package/dist/engine/feature-manifest.d.ts +8 -0
- package/dist/engine/feature-manifest.js +9 -0
- package/dist/engine/feature-ui-extensions.js +9 -0
- package/dist/engine/index.d.ts +3 -2
- package/dist/engine/index.js +1 -0
- package/dist/engine/registry-ingest.d.ts +1 -0
- package/dist/engine/registry-ingest.js +9 -0
- package/dist/engine/registry.js +2 -1
- package/dist/engine/steps/retry.js +0 -1
- package/dist/engine/steps/wait-for-event.js +0 -1
- package/dist/engine/steps/wait.js +0 -4
- package/dist/engine/steps/workflow-system-events.d.ts +2 -0
- package/dist/engine/steps/workflow-system-events.js +89 -0
- package/dist/engine/types/index.d.ts +1 -1
- package/dist/jobs/job-runner.js +2 -1
- package/dist/pipeline/append-event-core.d.ts +3 -1
- package/dist/pipeline/append-event-core.js +8 -4
- package/dist/pipeline/dispatch-shared.js +37 -7
- package/dist/pipeline/system-identity-switch.js +10 -7
- package/package.json +4 -4
- 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,
|
package/dist/engine/index.d.ts
CHANGED
|
@@ -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";
|
package/dist/engine/index.js
CHANGED
|
@@ -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
|
package/dist/engine/registry.js
CHANGED
|
@@ -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,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";
|
package/dist/jobs/job-runner.js
CHANGED
|
@@ -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
|
|
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
|
|
37
|
-
//
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
-
//
|
|
377
|
-
//
|
|
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,
|
|
230
|
+
...gatedIdentitySwitchFields(callerLabel, identityHatch, ctxQueryAs, ctxWriteAs),
|
|
228
231
|
...(ctxResolveActiveMembership &&
|
|
229
|
-
|
|
232
|
+
identityHatch === undefined && {
|
|
230
233
|
resolveActiveMembership: deniedResolveActiveMembership(callerLabel),
|
|
231
234
|
}),
|
|
232
|
-
...gatedMemberReaderField(callerLabel,
|
|
233
|
-
...gatedProjectionReaderField(callerLabel,
|
|
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,
|
|
238
|
+
...(ctxDb && { db: withUnsafeRawGrant(ctxDb, rawHatch) }),
|
|
236
239
|
...(ctxDbOutsideTransaction && {
|
|
237
|
-
dbOutsideTransaction: withUnsafeRawGrant(ctxDbOutsideTransaction,
|
|
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,
|
|
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.
|
|
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.
|
|
210
|
-
"@cosmicdrift/kumiko-types": "0.
|
|
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.
|
|
226
|
+
"@cosmicdrift/kumiko-dispatcher-live": "0.339.0",
|
|
227
227
|
"bun-types": "^1.3.13",
|
|
228
228
|
"pino-pretty": "^13.1.3"
|
|
229
229
|
},
|