@cosmicdrift/kumiko-framework 0.334.0 → 0.335.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/auth-routes.js +19 -4
- package/dist/api/client-ip.js +3 -6
- package/dist/api/extra-route.js +3 -2
- package/dist/api/pii-leak-guard.js +7 -4
- package/dist/api/redis-sse-broker.d.ts +2 -0
- package/dist/api/redis-sse-broker.js +6 -7
- package/dist/api/routes.js +16 -17
- package/dist/api/server.js +14 -7
- package/dist/api/sse-broker.d.ts +3 -0
- package/dist/api/sse-broker.js +14 -0
- package/dist/api/sse-route.d.ts +1 -1
- package/dist/api/sse-route.js +39 -6
- package/dist/bun-db/connection.js +0 -2
- package/dist/bun-db/index.d.ts +1 -1
- package/dist/bun-db/index.js +1 -1
- package/dist/bun-db/query.d.ts +1 -0
- package/dist/bun-db/query.js +15 -4
- package/dist/crypto/kek-source.js +40 -36
- package/dist/crypto/kms-adapter.js +2 -2
- package/dist/db/apply-entity-event.js +8 -2
- package/dist/db/blind-index-cleanup.js +11 -1
- package/dist/db/entity-table-meta.js +5 -3
- package/dist/db/event-store-executor-context.js +13 -19
- package/dist/db/event-store-executor-read.js +39 -36
- package/dist/db/event-store-executor-write.js +15 -17
- package/dist/db/migrate-generator.js +27 -28
- package/dist/db/pg-error.js +9 -5
- package/dist/db/queries/backfill-pii.js +15 -5
- package/dist/db/queries/shadow-swap.js +5 -4
- package/dist/db/table-builder.d.ts +1 -0
- package/dist/db/table-builder.js +7 -15
- package/dist/db/tenant-db.js +18 -2
- package/dist/derivatives/variant-key.js +3 -1
- package/dist/engine/boot-validator/access-declarations.js +12 -3
- package/dist/engine/boot-validator/entity-handler.js +13 -1
- package/dist/engine/boot-validator/entity-list-screens.d.ts +1 -0
- package/dist/engine/boot-validator/entity-list-screens.js +2 -2
- package/dist/engine/boot-validator/index.js +6 -6
- package/dist/engine/boot-validator/nav.d.ts +2 -1
- package/dist/engine/boot-validator/nav.js +10 -20
- package/dist/engine/boot-validator/ownership.d.ts +1 -1
- package/dist/engine/boot-validator/ownership.js +2 -1
- package/dist/engine/boot-validator/pii-retention.d.ts +2 -0
- package/dist/engine/boot-validator/pii-retention.js +5 -2
- package/dist/engine/boot-validator/projection-list-screens.js +8 -0
- package/dist/engine/boot-validator/query-output-columns.js +41 -5
- package/dist/engine/boot-validator/screens.d.ts +2 -2
- package/dist/engine/boot-validator/screens.js +103 -33
- package/dist/engine/boot-validator/transfer-graph.js +9 -6
- package/dist/engine/boot-validator/url-prefill-fields.js +8 -6
- package/dist/engine/boot-validator/zod-shape.js +17 -4
- package/dist/engine/build-app-schema.js +62 -19
- package/dist/engine/build-config-feature-schema.js +8 -1
- package/dist/engine/changeset-changes.js +24 -10
- package/dist/engine/feature-ast/extractors/events.js +11 -8
- package/dist/engine/feature-ast/extractors/handlers.js +38 -16
- package/dist/engine/feature-ast/extractors/resolve-import.d.ts +7 -2
- package/dist/engine/feature-ast/extractors/resolve-import.js +42 -15
- package/dist/engine/feature-ast/parse.d.ts +1 -0
- package/dist/engine/feature-ast/parse.js +18 -3
- package/dist/engine/feature-ast/patch.js +80 -61
- package/dist/engine/feature-ast/patcher.d.ts +1 -0
- package/dist/engine/feature-ast/patcher.js +2 -1
- package/dist/engine/feature-ast/render.js +1 -1
- package/dist/engine/index.d.ts +4 -3
- package/dist/engine/index.js +3 -2
- package/dist/engine/ownership.d.ts +1 -0
- package/dist/engine/ownership.js +15 -2
- package/dist/engine/pattern-library/mixed-schemas.js +15 -29
- package/dist/engine/pattern-library/shared-fields.d.ts +2 -0
- package/dist/engine/pattern-library/shared-fields.js +17 -0
- package/dist/engine/registry-facade.js +3 -0
- package/dist/engine/role-assignment.d.ts +7 -1
- package/dist/engine/role-assignment.js +27 -7
- package/dist/engine/schema-builder.d.ts +1 -0
- package/dist/engine/schema-builder.js +9 -2
- package/dist/engine/screen-helpers.js +15 -4
- package/dist/engine/steps/_event-match.js +22 -3
- package/dist/engine/steps/_read-source.js +8 -0
- package/dist/engine/where-rule-lint.js +22 -4
- package/dist/env/index.js +21 -1
- package/dist/errors/reasons.d.ts +0 -1
- package/dist/errors/reasons.js +2 -5
- package/dist/event-store/event-store.js +6 -1
- package/dist/event-store/provenance-append.js +4 -1
- package/dist/files/file-routes.d.ts +0 -1
- package/dist/files/file-routes.js +22 -7
- package/dist/files/local-provider.js +13 -10
- package/dist/files/provider-resolver.js +20 -1
- package/dist/files/types.js +16 -1
- package/dist/i18n/request-locale.d.ts +3 -2
- package/dist/i18n/request-locale.js +16 -6
- package/dist/i18n/required-surface-keys.js +23 -35
- package/dist/i18n/settings-hub-keys.js +3 -0
- package/dist/jobs/index.d.ts +2 -2
- package/dist/jobs/index.js +1 -1
- package/dist/jobs/job-runner.d.ts +2 -0
- package/dist/jobs/job-runner.js +150 -59
- package/dist/migrations/index.d.ts +1 -1
- package/dist/migrations/index.js +1 -1
- package/dist/migrations/pending-rebuilds.d.ts +4 -0
- package/dist/migrations/pending-rebuilds.js +23 -1
- package/dist/observability/escape-hatch-report.d.ts +1 -0
- package/dist/observability/escape-hatch-report.js +3 -0
- package/dist/observability/index.d.ts +1 -0
- package/dist/observability/index.js +1 -0
- package/dist/pipeline/dispatch-query.d.ts +1 -1
- package/dist/pipeline/dispatch-query.js +2 -2
- package/dist/pipeline/dispatch-shared.d.ts +2 -3
- package/dist/pipeline/dispatch-shared.js +14 -9
- package/dist/pipeline/dispatch-stream.js +2 -5
- package/dist/pipeline/dispatch-write.js +22 -5
- package/dist/pipeline/dispatcher.js +1 -2
- package/dist/pipeline/event-dispatcher-admin.js +12 -2
- package/dist/pipeline/event-dispatcher.d.ts +1 -0
- package/dist/pipeline/event-dispatcher.js +26 -11
- package/dist/pipeline/index.d.ts +1 -1
- package/dist/pipeline/index.js +1 -1
- package/dist/pipeline/member-read-only-transaction.js +1 -14
- package/dist/pipeline/msp-rebuild.d.ts +1 -0
- package/dist/pipeline/msp-rebuild.js +1 -0
- package/dist/pipeline/system-hooks.d.ts +1 -1
- package/dist/pipeline/system-hooks.js +9 -7
- package/dist/pipeline/tenant-timezone-cache.d.ts +2 -0
- package/dist/pipeline/tenant-timezone-cache.js +20 -6
- package/dist/redis/pubsub-signal.d.ts +2 -0
- package/dist/redis/pubsub-signal.js +2 -2
- package/dist/schema-cli.js +4 -14
- package/dist/search/meilisearch-adapter.js +17 -6
- package/dist/search/purge-subject.js +8 -3
- package/dist/stack/drain-jobs.js +24 -12
- package/dist/stack/table-helpers.js +10 -8
- package/dist/testing/closed-connection-error.js +2 -3
- package/dist/testing/wait-for.js +1 -0
- package/dist/ui-types/index.d.ts +1 -1
- package/dist/ui-types/index.js +1 -1
- package/dist/upgrade-cli.d.ts +1 -0
- package/dist/upgrade-cli.js +36 -15
- package/package.json +4 -4
- package/src/changes.json +206 -0
- package/src/scripts/codemod/migrate-cross-tenant.ts +14 -2
- package/src/scripts/codemod/migrate-db-raw.ts +16 -8
- package/src/scripts/codemod/migrate-open-to-all.ts +7 -1
- package/src/scripts/codemod/pii-personal-migration.ts +56 -19
- package/src/scripts/codemod/unprocessable-error-details-reason.ts +83 -26
- package/dist/scripts/codemod/crypto-shredding-testing-move.d.ts +0 -2
- package/dist/scripts/codemod/crypto-shredding-testing-move.js +0 -109
- package/dist/scripts/codemod/migrate-cross-tenant.d.ts +0 -19
- package/dist/scripts/codemod/migrate-cross-tenant.js +0 -266
- package/dist/scripts/codemod/migrate-db-raw.d.ts +0 -23
- package/dist/scripts/codemod/migrate-db-raw.js +0 -345
- package/dist/scripts/codemod/migrate-open-to-all.d.ts +0 -22
- package/dist/scripts/codemod/migrate-open-to-all.js +0 -194
- package/dist/scripts/codemod/pii-personal-migration.d.ts +0 -11
- package/dist/scripts/codemod/pii-personal-migration.js +0 -615
- package/dist/scripts/codemod/unprocessable-error-details-reason.d.ts +0 -2
- package/dist/scripts/codemod/unprocessable-error-details-reason.js +0 -235
package/dist/engine/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { hasAccess } from "./access.js";
|
|
2
2
|
export { isPrincipalStatusPlugin, isTenantLifecycleStatusPlugin, type PrincipalProfile, type PrincipalStatus, type PrincipalStatusPlugin, TENANT_TEARDOWN_STATUSES, type TenantLifecycleStatusPlugin, } from "./active-membership.js";
|
|
3
3
|
export { PII_DIRECT_NAME_HINTS, PII_USER_OWNED_NAME_HINTS, PII_USER_REFERENCE_NAME_HINTS, } from "./boot-validator/entity-handler.js";
|
|
4
|
+
export { entityHasAnonymizableSubjectField } from "./boot-validator/pii-retention.js";
|
|
4
5
|
export { collectWriteHandlerQns, MAX_TRANSFER_DEPTH, SECURITY_BASELINE_FEATURE_NAMES, type ValidateBootOptions, validateAppCustomScreenWriteQns, validateBoot, } from "./boot-validator.js";
|
|
5
6
|
export { type BuildAppSchemaOptions, buildAppSchema } from "./build-app-schema.js";
|
|
6
7
|
export type { ConfigFeatureSchema } from "./build-config-feature-schema.js";
|
|
@@ -58,11 +59,11 @@ export { createRegistry } from "./registry.js";
|
|
|
58
59
|
export type { ClampInfo, ResolveOptions } from "./resolve-config-or-param.js";
|
|
59
60
|
export { resolveConfigOrParam } from "./resolve-config-or-param.js";
|
|
60
61
|
export type { AssignableAppRoles, AssignableFromRole } from "./role-assignment.js";
|
|
61
|
-
export { assignableAppRolesFromUsages, findForbiddenRoleAssignment, isAssignableByRole, } from "./role-assignment.js";
|
|
62
|
+
export { assignableAppRolesFromUsages, assignableAppRolesOf, canActorAssignRole, findForbiddenRoleAssignment, isAssignableByRole, mergeAssignedRoles, } from "./role-assignment.js";
|
|
62
63
|
export { runsInLane } from "./run-in.js";
|
|
63
64
|
export type { StepListOutcome } from "./run-pipeline.js";
|
|
64
65
|
export { runPipeline, runStepList } from "./run-pipeline.js";
|
|
65
|
-
export { buildInsertSchema, buildUpdateSchema, fieldToZod } from "./schema-builder.js";
|
|
66
|
+
export { buildInsertSchema, buildUpdateSchema, fieldToZod, SYSTEM_ONLY_JSON_SCHEMA_KEY, } from "./schema-builder.js";
|
|
66
67
|
export { isExtensionEditSection, isFieldsEditSection, isWriteFormEditSection, normalizeEditField, normalizeListColumn, sectionFieldSpecs, } from "./screen-helpers.js";
|
|
67
68
|
export type { TransitionGraph } from "./state-machine.js";
|
|
68
69
|
export { defineTransitions, guardTransition } from "./state-machine.js";
|
|
@@ -72,7 +73,7 @@ export { describeWorkflowStepError } from "./steps/describe-workflow-step-error.
|
|
|
72
73
|
export { ANONYMOUS_ROLE, ANONYMOUS_USER_ID, createAnonymousUser, createSystemUser, SYSTEM_ROLE, SYSTEM_USER_ID, } from "./system-user.js";
|
|
73
74
|
export { type EffectiveFeaturesResolver, findTierResolverUsage, isTierResolverPlugin, TENANT_TIER_RESOLVER_EXT, type TierResolverPlugin, type TrialGate, } from "./tier-resolver-extension.js";
|
|
74
75
|
export { isSystemTenant, isUuid, parseTenantId, SYSTEM_TENANT_ID } from "./types/identifiers.js";
|
|
75
|
-
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, RecordHeaderSpec, RecordHeaderSubtitlePart, ReferenceDataDef, Registry, RelationDefinition, ResolvedPiiFlags, RetentionDef, RoleAccessPersonalData, RoleAccessRule, RowAction, RowActionNavigate, RowActionNavigateBase, RowActionWriteHandler, RowFieldExtractor, SaveContext, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenNavSugar, ScreenSlots, SecretKeyHandle, SecretMintConfirmStep, SecretMintScreenDefinition, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, SelectFieldDef, SessionUser, SessionUserOrigin, StreamHandlerDef, StreamHandlerFn, Subscribe, TargetRef, TenantId, TextFieldDef, ToolbarAction, TranslationEntry, TranslationKeys, TranslationsDef, TranslationValue, TreeAction, TreeActionDef, TreeActionsHandle, TreeChildrenSubscribe, TreeNode, TreeNodeState, UnitKey, UnsafeAppendEventFn, ValidationError, ValidationHookFn, WorkspaceDefinition, WriteEvent, WriteHandlerDef, WriteHandlerFn, WriteResult, } from "./types/index.js";
|
|
76
|
+
export type { AccessRule, ActionFormScreenDefinition, ActiveMembership, ActiveMembershipRejection, ActiveMembershipResult, AgentExposure, AgentHandlerHints, AgentRisk, AppContext, AppendEventArgs, AppendEventFn, AuthClaimsContext, AuthClaimsFn, AuthClaimsHookDef, BelongsToRelation, BigIntFieldDef, BooleanFieldDef, CamelToKebab, ClaimKeyDefinition, ClaimKeyHandle, ClaimKeyJsType, ClaimKeyType, ConcurrencyMode, ConfigAccessor, ConfigAccessorFactory, ConfigBacking, ConfigCascade, ConfigCascadeLevel, ConfigDefinition, ConfigEditScreenDefinition, ConfigKeyAccess, ConfigKeyDefinition, ConfigKeyHandle, ConfigKeyType, ConfigMask, ConfigResolver, ConfigScope, ConfigSecretsReader, ConfigSeedDef, ConfigStoredRow, ConfigStoredRowWithSource, ConfigValue, ConfigValueSource, ConfigValueWithSource, ContentCollectionDefinition, CreateSeedOptions, CreateTenantSeedOptions, CreateUserSeedOptions, CustomScreenDefinition, CustomScreenRoute, DashboardChartPanel, DashboardCustomPanel, DashboardFeedPanel, DashboardFilterDefinition, DashboardListPanel, DashboardPanelDefinition, DashboardPanelVisibility, DashboardProgressListPanel, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DateFieldDef, DecimalFieldDef, DeleteContext, DeriveContext, DerivedFieldDef, DerivedFieldsMap, DerivedValueType, EditExtensionSection, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EntityDefinition, EntityEditScreenDefinition, EntityId, EntityListExpandableRow, EntityListScreenDefinition, EntityRef, EntityRelations, EscapeHatchAuditSink, EscapeHatchDeclaration, EscapeHatchKind, EscapeHatchReporter, EscapeHatchTarget, EscapeHatchUseEvent, EventDef, ExtensionOptionsArgs, ExtensionOptionsFor, FeatureDefinition, FeatureRegistrar, FieldAccess, FieldCondition, FieldDefinition, FieldFormatRegistry, FieldIconKey, FieldRenderer, FileFieldDef, FilesFieldDef, Findability, FormatSpec, FormWidth, HandlerContext, HasManyRelation, HookMap, ImageFieldDef, ImagesFieldDef, JobBackoff, JobBackoffStrategy, JobContext, JobDefinition, JobHandlerFn, JobTrigger, JobTriggerEvent, JsonbFieldDef, KumikoEntityTypeMap, KumikoEventTypeMap, KumikoExtensionOptionsMap, KumikoHandlerPayloadMap, KumikoHandlerResultMap, LifecycleHookType, ListColumnSpec, ListFacetSpec, ListPaginationMode, ListSortDir, ListSortSpec, LongTextFindability, ManyToManyRelation, MemberReader, MetricNavigate, MetricSpec, MspErrorMode, MspErrorPolicy, MultiSelectFieldDef, MultiStreamApplyFn, MultiStreamProjectionDefinition, NameOrRef, NavDefinition, NavIconKey, NotificationDataFn, NotificationDefinition, NotificationRecipientFn, NotificationTemplateFn, NotifyFactory, NotifyFn, NotifyOptions, NotifyPriority, NumberFieldDef, OnDeleteStrategy, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, ParentRefDef, PersonalAnnotations, PersonalAnnotationsLongText, PersonalAnnotationsNoFind, PersonalSubject, PlatformComponent, PluralForms, PostDeleteHookFn, PostSaveHookFn, PreDeleteHookFn, PreQueryHookFn, PreSaveHookFn, ProjectionDefinition, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, ProjectionTable, QualifiedEventName, QueryEvent, QueryHandlerDef, QueryHandlerFn, RateLimitPer, RecordHeaderSpec, RecordHeaderSubtitlePart, ReferenceDataDef, Registry, RelationDefinition, ResolvedPiiFlags, RetentionDef, RoleAccessPersonalData, RoleAccessRule, RowAction, RowActionNavigate, RowActionNavigateBase, RowActionWriteHandler, RowFieldExtractor, SaveContext, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenNavSugar, ScreenSlots, SecretKeyHandle, SecretMintConfirmStep, SecretMintScreenDefinition, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, SelectFieldDef, SessionUser, SessionUserOrigin, StreamHandlerDef, StreamHandlerFn, Subscribe, TargetRef, TenantId, TextFieldDef, ToolbarAction, TranslationEntry, TranslationKeys, TranslationsDef, TranslationValue, TreeAction, TreeActionDef, TreeActionsHandle, TreeChildrenSubscribe, TreeNode, TreeNodeState, UnitKey, UnsafeAppendEventFn, ValidationError, ValidationHookFn, WorkspaceDefinition, WriteEvent, WriteHandlerDef, WriteHandlerFn, WriteResult, } from "./types/index.js";
|
|
76
77
|
export { DEFAULT_CURRENCIES, DEFAULT_LOCALES, HookPhases, isAgentVisibleScreen, isOpenToAllGranted, resolveAgentExposure, } from "./types/index.js";
|
|
77
78
|
export type { AwaitedEventType, PipelineBuildCtx, PipelineCtx, PipelineDef, StepBuilder, StepDef, StepFailureStrategy, StepInstance, StepKind, StepNamespace, StepResolver, } from "./types/step.js";
|
|
78
79
|
export { runValidation } from "./validation.js";
|
package/dist/engine/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
export { hasAccess } from "./access.js";
|
|
3
3
|
export { isPrincipalStatusPlugin, isTenantLifecycleStatusPlugin, TENANT_TEARDOWN_STATUSES, } from "./active-membership.js";
|
|
4
4
|
export { PII_DIRECT_NAME_HINTS, PII_USER_OWNED_NAME_HINTS, PII_USER_REFERENCE_NAME_HINTS, } from "./boot-validator/entity-handler.js";
|
|
5
|
+
export { entityHasAnonymizableSubjectField } from "./boot-validator/pii-retention.js";
|
|
5
6
|
export { collectWriteHandlerQns, MAX_TRANSFER_DEPTH, SECURITY_BASELINE_FEATURE_NAMES, validateAppCustomScreenWriteQns, validateBoot, } from "./boot-validator.js";
|
|
6
7
|
export { buildAppSchema } from "./build-app-schema.js";
|
|
7
8
|
export { buildConfigFeatureSchema, SETTINGS_HUB_FEATURE, SETTINGS_HUB_WORKSPACE, } from "./build-config-feature-schema.js";
|
|
@@ -54,10 +55,10 @@ export { isValidQn, parseQn, QnTypes, qn, toKebab } from "./qualified-name.js";
|
|
|
54
55
|
export { readClaim } from "./read-claim.js";
|
|
55
56
|
export { createRegistry } from "./registry.js";
|
|
56
57
|
export { resolveConfigOrParam } from "./resolve-config-or-param.js";
|
|
57
|
-
export { assignableAppRolesFromUsages, findForbiddenRoleAssignment, isAssignableByRole, } from "./role-assignment.js";
|
|
58
|
+
export { assignableAppRolesFromUsages, assignableAppRolesOf, canActorAssignRole, findForbiddenRoleAssignment, isAssignableByRole, mergeAssignedRoles, } from "./role-assignment.js";
|
|
58
59
|
export { runsInLane } from "./run-in.js";
|
|
59
60
|
export { runPipeline, runStepList } from "./run-pipeline.js";
|
|
60
|
-
export { buildInsertSchema, buildUpdateSchema, fieldToZod } from "./schema-builder.js";
|
|
61
|
+
export { buildInsertSchema, buildUpdateSchema, fieldToZod, SYSTEM_ONLY_JSON_SCHEMA_KEY, } from "./schema-builder.js";
|
|
61
62
|
export { isExtensionEditSection, isFieldsEditSection, isWriteFormEditSection, normalizeEditField, normalizeListColumn, sectionFieldSpecs, } from "./screen-helpers.js";
|
|
62
63
|
export { defineTransitions, guardTransition } from "./state-machine.js";
|
|
63
64
|
export { evaluateEventMatch } from "./steps/_event-match.js";
|
|
@@ -7,6 +7,7 @@ export declare function userCanReadFieldRow(user: SessionUser, accessMap: Owners
|
|
|
7
7
|
export declare function userCanWriteFieldRow(user: SessionUser, accessMap: OwnershipMap | undefined, oldRow: Readonly<Record<string, unknown>>, newRow: Readonly<Record<string, unknown>>): boolean;
|
|
8
8
|
export declare function normalizeAccessEntry(entry: OwnershipMap | readonly string[] | undefined): OwnershipMap | undefined;
|
|
9
9
|
export declare function userCanCreateFieldRow(user: SessionUser, accessMap: OwnershipMap | undefined, newRow: Readonly<Record<string, unknown>>): boolean;
|
|
10
|
+
export declare function tableNameOf(table: unknown): string;
|
|
10
11
|
export declare function shiftParams(fragment: SqlFragment, shift: number): SqlFragment;
|
|
11
12
|
export declare function buildOwnershipClause(user: SessionUser, accessMap: OwnershipMap | undefined, table: unknown, paramStart?: number): OwnershipClause;
|
|
12
13
|
export declare function combineClauses(a: OwnershipClause, b: OwnershipClause): OwnershipClause;
|
package/dist/engine/ownership.js
CHANGED
|
@@ -185,7 +185,7 @@ export function userCanCreateFieldRow(user, accessMap, newRow) {
|
|
|
185
185
|
}
|
|
186
186
|
const PASS_CLAUSE = { kind: "pass" };
|
|
187
187
|
const EMPTY_CLAUSE = { kind: "empty" };
|
|
188
|
-
function tableNameOf(table) {
|
|
188
|
+
export function tableNameOf(table) {
|
|
189
189
|
if (table !== null && typeof table === "object") {
|
|
190
190
|
const sym = table[KUMIKO_NAME_SYMBOL];
|
|
191
191
|
if (typeof sym === "string")
|
|
@@ -298,6 +298,19 @@ export function combineClauses(a, b) {
|
|
|
298
298
|
params: [...a.params, ...shifted.params],
|
|
299
299
|
};
|
|
300
300
|
}
|
|
301
|
+
// The column set is constant per table object, but ruleToFragment runs on every
|
|
302
|
+
// read with a where-rule.
|
|
303
|
+
const columnSqlNamesByTable = new WeakMap();
|
|
304
|
+
function cachedTableColumnSqlNames(table) {
|
|
305
|
+
if (table === null || typeof table !== "object")
|
|
306
|
+
return tableColumnSqlNames(table);
|
|
307
|
+
let names = columnSqlNamesByTable.get(table);
|
|
308
|
+
if (!names) {
|
|
309
|
+
names = tableColumnSqlNames(table);
|
|
310
|
+
columnSqlNamesByTable.set(table, names);
|
|
311
|
+
}
|
|
312
|
+
return names;
|
|
313
|
+
}
|
|
301
314
|
function ruleToFragment(rule, user, table, paramStart) {
|
|
302
315
|
if (rule === "all") {
|
|
303
316
|
return { kind: "sql", sqlText: "TRUE", params: [] };
|
|
@@ -311,7 +324,7 @@ function ruleToFragment(rule, user, table, paramStart) {
|
|
|
311
324
|
// Runtime backstop for the boot-time probe (boot-validator/ownership.ts):
|
|
312
325
|
// an unqualified column in a correlated subquery silently turns into a
|
|
313
326
|
// fail-open tautology (fw#2639) — throw instead of shipping it.
|
|
314
|
-
assertQualifiedWhereFragment(frag.sqlText,
|
|
327
|
+
assertQualifiedWhereFragment(frag.sqlText, cachedTableColumnSqlNames(table), `ownership where-rule on "${tableNameOf(table)}"`);
|
|
315
328
|
return { kind: "sql", sqlText: frag.sqlText, params: frag.params };
|
|
316
329
|
}
|
|
317
330
|
// FromRule
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Mixed pattern schemas (header form + opaque body source).
|
|
2
|
-
import { accessRuleField, HOOK_TYPE_OPTIONS, HTTP_METHOD_OPTIONS } from "./shared-fields.js";
|
|
2
|
+
import { accessRuleField, agentDescriptionField, agentHintsField, HOOK_TYPE_OPTIONS, HTTP_METHOD_OPTIONS, } from "./shared-fields.js";
|
|
3
3
|
// --- Mixed patterns (header form + opaque body source) --------------------
|
|
4
4
|
export const screenSchema = {
|
|
5
5
|
kind: "screen",
|
|
@@ -71,20 +71,8 @@ export const writeHandlerSchema = {
|
|
|
71
71
|
readOnly: true,
|
|
72
72
|
},
|
|
73
73
|
accessRuleField,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
label: { en: "Description", de: "Beschreibung" },
|
|
77
|
-
hint: {
|
|
78
|
-
en: "Prose the AI agent sees as the tool description. Without it the handler stays invisible to the agent.",
|
|
79
|
-
},
|
|
80
|
-
input: "textarea",
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
path: "agent",
|
|
84
|
-
label: { en: "Agent hints", de: "Agent-Hinweise" },
|
|
85
|
-
hint: { en: "expose override + risk level for the AI-agent manifest." },
|
|
86
|
-
input: "json-readonly",
|
|
87
|
-
},
|
|
74
|
+
agentDescriptionField,
|
|
75
|
+
agentHintsField,
|
|
88
76
|
{
|
|
89
77
|
path: "rateLimit",
|
|
90
78
|
label: { en: "Rate limit", de: "Rate-Limit" },
|
|
@@ -126,20 +114,8 @@ export const queryHandlerSchema = {
|
|
|
126
114
|
readOnly: true,
|
|
127
115
|
},
|
|
128
116
|
accessRuleField,
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
label: { en: "Description", de: "Beschreibung" },
|
|
132
|
-
hint: {
|
|
133
|
-
en: "Prose the AI agent sees as the tool description. Without it the handler stays invisible to the agent.",
|
|
134
|
-
},
|
|
135
|
-
input: "textarea",
|
|
136
|
-
},
|
|
137
|
-
{
|
|
138
|
-
path: "agent",
|
|
139
|
-
label: { en: "Agent hints", de: "Agent-Hinweise" },
|
|
140
|
-
hint: { en: "expose override + risk level for the AI-agent manifest." },
|
|
141
|
-
input: "json-readonly",
|
|
142
|
-
},
|
|
117
|
+
agentDescriptionField,
|
|
118
|
+
agentHintsField,
|
|
143
119
|
{
|
|
144
120
|
path: "rateLimit",
|
|
145
121
|
label: { en: "Rate limit", de: "Rate-Limit" },
|
|
@@ -549,6 +525,16 @@ export const defineEventSchema = {
|
|
|
549
525
|
language: "zod",
|
|
550
526
|
readOnly: true,
|
|
551
527
|
},
|
|
528
|
+
{
|
|
529
|
+
path: "piiFields",
|
|
530
|
+
label: { en: "PII fields (source)", de: "PII-Felder (Source)" },
|
|
531
|
+
hint: {
|
|
532
|
+
en: 'Which payload fields are encrypted, or "none" when the event carries no PII.',
|
|
533
|
+
},
|
|
534
|
+
input: "code-block",
|
|
535
|
+
language: "typescript",
|
|
536
|
+
readOnly: true,
|
|
537
|
+
},
|
|
552
538
|
{
|
|
553
539
|
path: "version",
|
|
554
540
|
label: { en: "Version", de: "Version" },
|
|
@@ -117,4 +117,6 @@ export declare const ID_TYPE_OPTIONS: readonly [{
|
|
|
117
117
|
readonly de: "Serial Integer";
|
|
118
118
|
};
|
|
119
119
|
}];
|
|
120
|
+
export declare const agentDescriptionField: FormFieldSpec;
|
|
121
|
+
export declare const agentHintsField: FormFieldSpec;
|
|
120
122
|
export declare const accessRuleField: FormFieldSpec;
|
|
@@ -30,6 +30,23 @@ export const ID_TYPE_OPTIONS = [
|
|
|
30
30
|
{ value: "uuid", label: { en: "UUID (default)", de: "UUID (Standard)" } },
|
|
31
31
|
{ value: "serial", label: { en: "Serial integer", de: "Serial Integer" } },
|
|
32
32
|
];
|
|
33
|
+
export const agentDescriptionField = {
|
|
34
|
+
path: "description",
|
|
35
|
+
label: { en: "Description", de: "Beschreibung" },
|
|
36
|
+
hint: {
|
|
37
|
+
en: "Prose the AI agent sees as the tool description. Without it the handler stays invisible to the agent.",
|
|
38
|
+
},
|
|
39
|
+
input: "textarea",
|
|
40
|
+
};
|
|
41
|
+
export const agentHintsField = {
|
|
42
|
+
path: "agent",
|
|
43
|
+
label: { en: "Agent hints", de: "Agent-Hinweise" },
|
|
44
|
+
hint: {
|
|
45
|
+
en: "expose override + risk level for the AI-agent manifest.",
|
|
46
|
+
de: "Expose-Override und Risikostufe für das KI-Agent-Manifest.",
|
|
47
|
+
},
|
|
48
|
+
input: "json-readonly",
|
|
49
|
+
};
|
|
33
50
|
export const accessRuleField = {
|
|
34
51
|
path: "access",
|
|
35
52
|
label: { en: "Access", de: "Zugriff" },
|
|
@@ -113,6 +113,9 @@ export function buildRegistryFacade(state) {
|
|
|
113
113
|
getHandlerFeature(qualifiedHandler) {
|
|
114
114
|
return state.handlerFeatureMap.get(qualifiedHandler);
|
|
115
115
|
},
|
|
116
|
+
getJobFeature(qualifiedJobName) {
|
|
117
|
+
return state.jobFeatureMap.get(qualifiedJobName);
|
|
118
|
+
},
|
|
116
119
|
getAllMetrics() {
|
|
117
120
|
return state.metricMap;
|
|
118
121
|
},
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import type { Registry } from "./types/feature.js";
|
|
2
|
+
declare const ASSIGNABLE_FROM_ROLE_LIST: readonly ["User", "Member", "Editor", "Admin", "TenantAdmin", "SystemAdmin"];
|
|
3
|
+
export type AssignableFromRole = (typeof ASSIGNABLE_FROM_ROLE_LIST)[number];
|
|
2
4
|
/** App role → lowest built-in role allowed to assign it. */
|
|
3
5
|
export type AssignableAppRoles = ReadonlyMap<string, AssignableFromRole>;
|
|
4
6
|
export declare const DEFAULT_APP_ROLE_ASSIGNABLE_FROM: AssignableFromRole;
|
|
@@ -8,4 +10,8 @@ export declare function assignableAppRolesFromUsages(usages: readonly {
|
|
|
8
10
|
}[]): AssignableAppRoles;
|
|
9
11
|
/** True when `actorRole` ranks at least as high as the role an app role is assignable from. */
|
|
10
12
|
export declare function isAssignableByRole(assignableFrom: AssignableFromRole, actorRole: AssignableFromRole): boolean;
|
|
13
|
+
export declare function canActorAssignRole(actorRoles: readonly string[], role: string, assignableAppRoles?: AssignableAppRoles): boolean;
|
|
14
|
+
export declare function mergeAssignedRoles(actorRoles: readonly string[], assignedRoles: readonly string[], targetCurrentRoles: readonly string[], assignableAppRoles: AssignableAppRoles): readonly string[];
|
|
15
|
+
export declare function assignableAppRolesOf(registry: Pick<Registry, "getExtensionUsages">): AssignableAppRoles;
|
|
11
16
|
export declare function findForbiddenRoleAssignment(actorRoles: readonly string[], assignedRoles: readonly string[], targetCurrentRoles: readonly string[], assignableAppRoles?: AssignableAppRoles): string | undefined;
|
|
17
|
+
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ROLES } from "../auth/roles.js";
|
|
2
|
+
import { EXT_ASSIGNABLE_ROLE } from "./extension-names.js";
|
|
2
3
|
import { isForbiddenMembershipRole } from "./membership-roles.js";
|
|
3
4
|
const PLATFORM_ROLE_NAMES = new Set(Object.values(ROLES));
|
|
4
5
|
// Prototype-free rank table — Object literals leak Object.prototype
|
|
@@ -15,18 +16,19 @@ const ROLE_RANKS = new Map([
|
|
|
15
16
|
["SystemAdmin", 4],
|
|
16
17
|
["system", 5],
|
|
17
18
|
]);
|
|
18
|
-
|
|
19
|
-
// (access.admin) may grant them; an app raises the bar via assignableFrom.
|
|
20
|
-
export const DEFAULT_APP_ROLE_ASSIGNABLE_FROM = "Admin";
|
|
21
|
-
const NO_ASSIGNABLE_APP_ROLES = new Map();
|
|
22
|
-
const ASSIGNABLE_FROM_ROLES = new Set([
|
|
19
|
+
const ASSIGNABLE_FROM_ROLE_LIST = [
|
|
23
20
|
"User",
|
|
24
21
|
"Member",
|
|
25
22
|
"Editor",
|
|
26
23
|
"Admin",
|
|
27
24
|
"TenantAdmin",
|
|
28
25
|
"SystemAdmin",
|
|
29
|
-
]
|
|
26
|
+
];
|
|
27
|
+
// App roles are ordinary, non-privileged tiers: whoever may manage members
|
|
28
|
+
// (access.admin) may grant them; an app raises the bar via assignableFrom.
|
|
29
|
+
export const DEFAULT_APP_ROLE_ASSIGNABLE_FROM = "Admin";
|
|
30
|
+
const NO_ASSIGNABLE_APP_ROLES = new Map();
|
|
31
|
+
const ASSIGNABLE_FROM_ROLES = new Set(ASSIGNABLE_FROM_ROLE_LIST);
|
|
30
32
|
function isAssignableFromRole(value) {
|
|
31
33
|
return typeof value === "string" && ASSIGNABLE_FROM_ROLES.has(value);
|
|
32
34
|
}
|
|
@@ -88,6 +90,24 @@ function requiredAssignRank(role, assignableAppRoles) {
|
|
|
88
90
|
const assignableFrom = assignableAppRoles.get(role);
|
|
89
91
|
return assignableFrom === undefined ? Number.POSITIVE_INFINITY : getRoleRank(assignableFrom);
|
|
90
92
|
}
|
|
93
|
+
export function canActorAssignRole(actorRoles, role, assignableAppRoles = NO_ASSIGNABLE_APP_ROLES) {
|
|
94
|
+
return requiredAssignRank(role, assignableAppRoles) <= maxRoleRank(actorRoles);
|
|
95
|
+
}
|
|
96
|
+
// An actor only removes what it could also grant; clients (the member form)
|
|
97
|
+
// cannot render roles outside their option list, so those must survive a save.
|
|
98
|
+
export function mergeAssignedRoles(actorRoles, assignedRoles, targetCurrentRoles, assignableAppRoles) {
|
|
99
|
+
const preserved = targetCurrentRoles.filter((role) => !assignedRoles.includes(role) && !canActorAssignRole(actorRoles, role, assignableAppRoles));
|
|
100
|
+
return [...new Set([...assignedRoles, ...preserved])];
|
|
101
|
+
}
|
|
102
|
+
const assignableAppRolesByRegistry = new WeakMap();
|
|
103
|
+
export function assignableAppRolesOf(registry) {
|
|
104
|
+
const cached = assignableAppRolesByRegistry.get(registry);
|
|
105
|
+
if (cached !== undefined)
|
|
106
|
+
return cached;
|
|
107
|
+
const built = assignableAppRolesFromUsages(registry.getExtensionUsages(EXT_ASSIGNABLE_ROLE));
|
|
108
|
+
assignableAppRolesByRegistry.set(registry, built);
|
|
109
|
+
return built;
|
|
110
|
+
}
|
|
91
111
|
export function findForbiddenRoleAssignment(actorRoles, assignedRoles,
|
|
92
112
|
// Required so callers cannot accidentally disable the downgrade/
|
|
93
113
|
// takeover guard by omitting the third argument (new users: pass []).
|
|
@@ -96,7 +116,7 @@ targetCurrentRoles, assignableAppRoles = NO_ASSIGNABLE_APP_ROLES) {
|
|
|
96
116
|
// Assign path: fail-closed on unknown / above-actor roles — except unranked
|
|
97
117
|
// app roles the target already holds (round-trip restore after strip).
|
|
98
118
|
const forbiddenAssigned = assignedRoles.find((role) => {
|
|
99
|
-
if (
|
|
119
|
+
if (canActorAssignRole(actorRoles, role, assignableAppRoles))
|
|
100
120
|
return false;
|
|
101
121
|
if (knownRoleRank(role) === undefined && targetCurrentRoles.includes(role))
|
|
102
122
|
return false;
|
|
@@ -5,6 +5,7 @@ export declare function fieldToZod(field: FieldDefinition, currencies: readonly
|
|
|
5
5
|
readonly applyDefaults?: boolean;
|
|
6
6
|
readonly isUpdate?: boolean;
|
|
7
7
|
}): z.ZodTypeAny;
|
|
8
|
+
export declare const SYSTEM_ONLY_JSON_SCHEMA_KEY = "x-kumiko-system-only";
|
|
8
9
|
export declare function buildInsertSchema(entity: EntityDefinition, currencies?: readonly string[], excludedFields?: readonly string[], options?: {
|
|
9
10
|
readonly allowCallerId?: boolean;
|
|
10
11
|
}): z.ZodObject<Record<string, z.ZodTypeAny>>;
|
|
@@ -373,6 +373,7 @@ function applyTotalsMatchRefinements(entity, schema) {
|
|
|
373
373
|
function notWritableField() {
|
|
374
374
|
return z.never().optional();
|
|
375
375
|
}
|
|
376
|
+
export const SYSTEM_ONLY_JSON_SCHEMA_KEY = "x-kumiko-system-only";
|
|
376
377
|
export function buildInsertSchema(entity, currencies = [...DEFAULT_CURRENCIES], excludedFields = [], options = {}) {
|
|
377
378
|
const shape = {};
|
|
378
379
|
for (const [name, field] of Object.entries(entity.fields)) {
|
|
@@ -387,8 +388,14 @@ export function buildInsertSchema(entity, currencies = [...DEFAULT_CURRENCIES],
|
|
|
387
388
|
}
|
|
388
389
|
// Opt-in only for the generic create verb, which honors the id for system identities;
|
|
389
390
|
// other schema consumers must keep stripping an unknown `id`.
|
|
390
|
-
if (options.allowCallerId)
|
|
391
|
-
|
|
391
|
+
if (options.allowCallerId) {
|
|
392
|
+
// Marked so agent-facing exports can drop it: agent dispatch never runs as
|
|
393
|
+
// a system identity, so a caller-chosen id would be silently discarded.
|
|
394
|
+
shape["id"] = z
|
|
395
|
+
.uuid()
|
|
396
|
+
.optional()
|
|
397
|
+
.meta({ [SYSTEM_ONLY_JSON_SCHEMA_KEY]: true });
|
|
398
|
+
}
|
|
392
399
|
return applyTotalsMatchRefinements(entity, z.object(shape));
|
|
393
400
|
}
|
|
394
401
|
export function buildUpdateSchema(entity, currencies = [...DEFAULT_CURRENCIES], excludedFields = []) {
|
|
@@ -12,21 +12,32 @@ export function explicitListScreenId(screen) {
|
|
|
12
12
|
return undefined;
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
|
+
// Screen refs may be short ids or `<feature>:screen:<id>` QNs (boot-validator
|
|
16
|
+
// accepts both for navigate targets and listScreenId); compare on the short id.
|
|
17
|
+
function screenShortId(ref) {
|
|
18
|
+
return ref.slice(ref.lastIndexOf(":") + 1);
|
|
19
|
+
}
|
|
15
20
|
// Shared by the renderer breadcrumb and the boot-validator so both resolve
|
|
16
21
|
// parents the same way; `getId` lets each caller normalize `screen.id`.
|
|
17
22
|
export function resolveNavParentScreen(screens, detail, getId) {
|
|
18
|
-
const detailScreenId = getId(detail);
|
|
23
|
+
const detailScreenId = screenShortId(getId(detail));
|
|
19
24
|
const listFromExplicit = (() => {
|
|
20
25
|
const explicitId = explicitListScreenId(detail);
|
|
21
|
-
return explicitId !== undefined
|
|
26
|
+
return explicitId !== undefined
|
|
27
|
+
? screens.find((s) => screenShortId(getId(s)) === screenShortId(explicitId))
|
|
28
|
+
: undefined;
|
|
22
29
|
})();
|
|
23
30
|
// rowActions, toolbarActions, and drawer-kind actions (which mount inline
|
|
24
31
|
// and have no page of their own) all count as "reached from" this list.
|
|
25
|
-
const navigatesToDetail = (actions) => (actions ?? []).some((a) => (a.kind === "navigate" || a.kind === "drawer") &&
|
|
32
|
+
const navigatesToDetail = (actions) => (actions ?? []).some((a) => (a.kind === "navigate" || a.kind === "drawer") &&
|
|
33
|
+
a.screen !== undefined &&
|
|
34
|
+
screenShortId(a.screen) === detailScreenId);
|
|
26
35
|
// The create button of an entityList navigates to its createScreen, so the
|
|
27
36
|
// list is that screen's parent just like for a rowAction target.
|
|
28
37
|
const listFromRowAction = screens.find((s) => {
|
|
29
|
-
if (s.type === "entityList" &&
|
|
38
|
+
if (s.type === "entityList" &&
|
|
39
|
+
s.createScreen !== undefined &&
|
|
40
|
+
screenShortId(s.createScreen) === detailScreenId)
|
|
30
41
|
return true;
|
|
31
42
|
if (s.type !== "entityList" && s.type !== "projectionList")
|
|
32
43
|
return false;
|
|
@@ -48,6 +48,8 @@ function evaluateOp(op, actual) {
|
|
|
48
48
|
case "ne":
|
|
49
49
|
return actual !== op.value;
|
|
50
50
|
case "in":
|
|
51
|
+
if (!Array.isArray(op.values))
|
|
52
|
+
throw malformed(`"in" op has no "values" array`);
|
|
51
53
|
return op.values.some((value) => value === actual);
|
|
52
54
|
case "gt":
|
|
53
55
|
case "gte":
|
|
@@ -66,13 +68,28 @@ function evaluateOp(op, actual) {
|
|
|
66
68
|
}
|
|
67
69
|
}
|
|
68
70
|
}
|
|
71
|
+
// The AST round-trips through jsonb, so a truncated or hand-edited row must
|
|
72
|
+
// fail with a readable format error rather than a bare TypeError.
|
|
73
|
+
function malformed(what) {
|
|
74
|
+
return new Error(`Malformed EventMatch: ${what}`);
|
|
75
|
+
}
|
|
69
76
|
function evaluateExpr(expr, payload) {
|
|
77
|
+
if (!isRecord(expr))
|
|
78
|
+
throw malformed("expr is not an object");
|
|
70
79
|
switch (expr.kind) {
|
|
71
80
|
case "and":
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
81
|
+
case "or": {
|
|
82
|
+
if (!Array.isArray(expr.nodes))
|
|
83
|
+
throw malformed(`"${expr.kind}" expr has no "nodes" array`);
|
|
84
|
+
return expr.kind === "and"
|
|
85
|
+
? expr.nodes.every((node) => evaluateExpr(node, payload))
|
|
86
|
+
: expr.nodes.some((node) => evaluateExpr(node, payload));
|
|
87
|
+
}
|
|
75
88
|
case "atom":
|
|
89
|
+
if (!Array.isArray(expr.path))
|
|
90
|
+
throw malformed(`"atom" expr has no "path" array`);
|
|
91
|
+
if (!isRecord(expr.op))
|
|
92
|
+
throw malformed(`"atom" expr has no "op" object`);
|
|
76
93
|
return evaluateOp(expr.op, resolvePath(payload, expr.path));
|
|
77
94
|
default: {
|
|
78
95
|
const unrecognized = expr;
|
|
@@ -81,6 +98,8 @@ function evaluateExpr(expr, payload) {
|
|
|
81
98
|
}
|
|
82
99
|
}
|
|
83
100
|
export function evaluateEventMatch(match, payload) {
|
|
101
|
+
if (!isRecord(match))
|
|
102
|
+
throw malformed("match is not an object");
|
|
84
103
|
if (match.version !== 1) {
|
|
85
104
|
throw new Error(`Unsupported EventMatch version ${JSON.stringify(match.version)} — expected 1`);
|
|
86
105
|
}
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import { unsafeRawForDeclaredStep } from "../../db/tenant-db.js";
|
|
2
|
+
import { InternalError } from "../../errors/classes.js";
|
|
2
3
|
// unsafeRaw fails closed without the handler's escapeHatch (or systemScope) and reports the use.
|
|
3
4
|
export function readSourceFor(ctx, unsafeAllTenants) {
|
|
4
5
|
if (!unsafeAllTenants)
|
|
5
6
|
return ctx.db;
|
|
7
|
+
// Step args come from untyped call sites too; a boolean `true` (the shape other
|
|
8
|
+
// framework APIs use) must fail as a framework error, not a TypeError on `.reason`.
|
|
9
|
+
if (typeof unsafeAllTenants !== "object" || typeof unsafeAllTenants.reason !== "string") {
|
|
10
|
+
throw new InternalError({
|
|
11
|
+
message: "unsafeAllTenants on a read step must be { reason: string }",
|
|
12
|
+
});
|
|
13
|
+
}
|
|
6
14
|
return unsafeRawForDeclaredStep(ctx.systemDb ?? ctx.db, unsafeAllTenants.reason);
|
|
7
15
|
}
|
|
@@ -17,8 +17,9 @@ const SELF_COMPARISON_RE = new RegExp(`(${QUALIFIED_SRC})\\s*(?<![!<>=])=(?!=)\\
|
|
|
17
17
|
const QUALIFIED_PARSE_RE = new RegExp(`^(${IDENT_SRC})(?:\\.(${IDENT_SRC}))?$`);
|
|
18
18
|
// Resolve the SQL column names a table exposes, so the lint can tell a
|
|
19
19
|
// real column reference apart from a table alias / function name / SQL
|
|
20
|
-
// keyword. Returns an empty set when nothing is derivable
|
|
21
|
-
//
|
|
20
|
+
// keyword. Returns an empty set when nothing is derivable; the column-aware
|
|
21
|
+
// check then finds no bare column and is effectively skipped (only the
|
|
22
|
+
// self-comparison check still applies).
|
|
22
23
|
export function tableColumnSqlNames(table) {
|
|
23
24
|
if (table === null || typeof table !== "object")
|
|
24
25
|
return new Set();
|
|
@@ -131,16 +132,33 @@ function findSelfComparison(cleaned) {
|
|
|
131
132
|
// column on the OUTER table is the fail-open shape — Postgres resolves it
|
|
132
133
|
// against the innermost table first. A qualifier (`t.x`) or a table/alias
|
|
133
134
|
// name immediately followed by `.` is not a column reference and is
|
|
134
|
-
// ignored.
|
|
135
|
+
// ignored. Bare identifiers on the outer level bind to the outer table and
|
|
136
|
+
// are fine, so only tokens inside a `( SELECT ...` parenthesis block count.
|
|
137
|
+
const SUBQUERY_OPEN_RE = /^\s*SELECT\b/i;
|
|
135
138
|
function findUnqualifiedColumnReference(cleaned, columnSqlNames) {
|
|
139
|
+
const parenIsSubquery = [];
|
|
140
|
+
let subqueryDepth = 0;
|
|
141
|
+
let scanned = 0;
|
|
136
142
|
TOKEN_RE.lastIndex = 0;
|
|
137
143
|
let match = TOKEN_RE.exec(cleaned);
|
|
138
144
|
while (match !== null) {
|
|
139
145
|
const start = match.index;
|
|
140
146
|
const end = start + match[0].length;
|
|
147
|
+
for (; scanned < start; scanned++) {
|
|
148
|
+
const ch = cleaned[scanned];
|
|
149
|
+
if (ch === "(") {
|
|
150
|
+
const opensSubquery = SUBQUERY_OPEN_RE.test(cleaned.slice(scanned + 1));
|
|
151
|
+
parenIsSubquery.push(opensSubquery);
|
|
152
|
+
if (opensSubquery)
|
|
153
|
+
subqueryDepth++;
|
|
154
|
+
}
|
|
155
|
+
else if (ch === ")" && parenIsSubquery.pop() === true) {
|
|
156
|
+
subqueryDepth--;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
141
159
|
const precededByDot = start > 0 && cleaned[start - 1] === ".";
|
|
142
160
|
const followedByDot = cleaned[end] === ".";
|
|
143
|
-
if (!precededByDot && !followedByDot) {
|
|
161
|
+
if (subqueryDepth > 0 && !precededByDot && !followedByDot) {
|
|
144
162
|
const normalized = normalizeIdent(match[0]);
|
|
145
163
|
if (columnSqlNames.has(normalized))
|
|
146
164
|
return normalized;
|
package/dist/env/index.js
CHANGED
|
@@ -57,6 +57,9 @@ export function kmsSlotsOf(schema) {
|
|
|
57
57
|
// only exists after the boot-time decrypt, so requiring it here would reject
|
|
58
58
|
// exactly the deployment this meta enables. safeExtend, not extend: Zod 4 throws
|
|
59
59
|
// on extend for schemas carrying refinements (app-level superRefine).
|
|
60
|
+
// Caveat: refinements run against the relaxed object, so they see such a slot as
|
|
61
|
+
// `undefined` despite `z.infer<S>` promising a string. A refinement that reads the
|
|
62
|
+
// slot must tolerate that; a throw from it is reported by parseEnv as a boot error.
|
|
60
63
|
function relaxCiphertextOnlySlots(schema, env) {
|
|
61
64
|
const shape = zodShape(schema);
|
|
62
65
|
const relaxed = {};
|
|
@@ -227,6 +230,23 @@ export class KumikoBootError extends Error {
|
|
|
227
230
|
return formatBootError(this.errors);
|
|
228
231
|
}
|
|
229
232
|
}
|
|
233
|
+
// safeParse does not catch exceptions thrown inside refinements; a refinement
|
|
234
|
+
// reading a ciphertext-only slot (undefined here) would otherwise escape as a raw
|
|
235
|
+
// TypeError past the KumikoBootError mapping.
|
|
236
|
+
function safeParseReportingThrows(schema, input) {
|
|
237
|
+
try {
|
|
238
|
+
return schema.safeParse(input);
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
throw new KumikoBootError([
|
|
242
|
+
{
|
|
243
|
+
name: "<schema refinement>",
|
|
244
|
+
kind: "invalid",
|
|
245
|
+
message: `an env schema refinement threw: ${error instanceof Error ? error.message : String(error)}. If it reads a kms slot delivered only as <NAME>_CIPHERTEXT, the value is undefined at this point.`,
|
|
246
|
+
},
|
|
247
|
+
]);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
230
250
|
export function parseEnv(schema, env, options = {}) {
|
|
231
251
|
// Filter undefined values to keep Zod's required-vs-invalid signal clean.
|
|
232
252
|
// (process.env returns string|undefined; passing undefined would parse
|
|
@@ -236,7 +256,7 @@ export function parseEnv(schema, env, options = {}) {
|
|
|
236
256
|
if (v !== undefined)
|
|
237
257
|
cleaned[k] = v;
|
|
238
258
|
}
|
|
239
|
-
const result = relaxCiphertextOnlySlots(schema, cleaned)
|
|
259
|
+
const result = safeParseReportingThrows(relaxCiphertextOnlySlots(schema, cleaned), cleaned);
|
|
240
260
|
if (result.success) {
|
|
241
261
|
// @cast-boundary schema-walk — z.infer<S> erasure across safeParse result
|
|
242
262
|
return result.data;
|
package/dist/errors/reasons.d.ts
CHANGED
|
@@ -17,7 +17,6 @@ export type FrameworkReason = (typeof FrameworkReasons)[keyof typeof FrameworkRe
|
|
|
17
17
|
export declare const AgentReasons: {
|
|
18
18
|
readonly toolNotAllowed: "agent.tool_not_allowed";
|
|
19
19
|
readonly iterationLimit: "agent.iteration_limit";
|
|
20
|
-
readonly permissionDenied: "agent.permission_denied";
|
|
21
20
|
readonly highRiskNoAlways: "agent.high_risk_no_always";
|
|
22
21
|
};
|
|
23
22
|
export type AgentReason = (typeof AgentReasons)[keyof typeof AgentReasons];
|
package/dist/errors/reasons.js
CHANGED
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
// reasons surface from the AI-agent runtime (tool dispatch, permission and
|
|
14
14
|
// risk gates), not from a handler's own domain logic, and the `agent.`
|
|
15
15
|
// prefix keeps them visibly distinct from FrameworkReasons in error payloads.
|
|
16
|
-
//
|
|
17
|
-
//
|
|
16
|
+
// The docs site (`errors/` pages) reads this source; the enterprise repo keeps
|
|
17
|
+
// its own AGENT_REASONS, so only reasons it actually emits belong in both.
|
|
18
18
|
export const FrameworkReasons = {
|
|
19
19
|
// ConflictError: atomic UPDATE lost the race (another writer moved the row
|
|
20
20
|
// between our snapshot and our WHERE clause). Client SDK default: toast +
|
|
@@ -73,9 +73,6 @@ export const AgentReasons = {
|
|
|
73
73
|
// Not thrown: the run ends with a `clarify` turn (`payload.reason`) once
|
|
74
74
|
// MAX_AGENT_ROUNDS is reached.
|
|
75
75
|
iterationLimit: "agent.iteration_limit",
|
|
76
|
-
// AccessDeniedError: the tool exists, but the caller's roles don't cover
|
|
77
|
-
// the handler behind it.
|
|
78
|
-
permissionDenied: "agent.permission_denied",
|
|
79
76
|
// UnprocessableError: a permission rule with `always` was requested for an
|
|
80
77
|
// `agent: { risk: "high" }` handler; `details.handlerQn` names it.
|
|
81
78
|
highRiskNoAlways: "agent.high_risk_no_always",
|
|
@@ -175,12 +175,17 @@ function buildStoredEvent(event, newVersion, eventVersion, row) {
|
|
|
175
175
|
// style collision) must pass it to avoid folding a foreign type's events
|
|
176
176
|
// into the loaded stream.
|
|
177
177
|
export async function loadAggregate(db, aggregateId, tenantId, options) {
|
|
178
|
+
// "" must not fall through to the unfiltered load: a dynamically computed
|
|
179
|
+
// type that came out empty would silently fold in foreign aggregate types.
|
|
180
|
+
if (options?.aggregateType === "") {
|
|
181
|
+
throw new Error("loadAggregate: aggregateType must be a non-empty string when provided");
|
|
182
|
+
}
|
|
178
183
|
if (!options?.includeArchived) {
|
|
179
184
|
const archived = await isStreamArchived(db, tenantId, aggregateId);
|
|
180
185
|
if (archived)
|
|
181
186
|
return [];
|
|
182
187
|
}
|
|
183
|
-
const rows = await selectMany(db, eventsTable, options?.aggregateType
|
|
188
|
+
const rows = await selectMany(db, eventsTable, options?.aggregateType !== undefined
|
|
184
189
|
? { aggregateId, tenantId, aggregateType: options.aggregateType }
|
|
185
190
|
: { aggregateId, tenantId }, { orderBy: { col: "version", direction: "asc" } });
|
|
186
191
|
return rows.map(toStoredEvent);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SYSTEM_EVENT_PREFIX } from "../crypto/system-event-pii.js";
|
|
2
2
|
import { runInSavepointIfSupported } from "../db/query.js";
|
|
3
3
|
import { unsafeRawForDeclaredStep, withUnsafeRawGrant } from "../db/tenant-db.js";
|
|
4
|
+
import { qnScope } from "../engine/qualified-name.js";
|
|
4
5
|
import { AccessDeniedError, InternalError } from "../errors/index.js";
|
|
5
6
|
import { append, getStreamVersion } from "./event-store.js";
|
|
6
7
|
// Fixed by the framework, not the caller — the point of this entry point is
|
|
@@ -14,7 +15,9 @@ export async function appendProvenanceEvent(db, event) {
|
|
|
14
15
|
message: `appendProvenanceEvent("${event.type}") — the "${SYSTEM_EVENT_PREFIX}" namespace is framework-internal and is not reachable through this entry point.`,
|
|
15
16
|
});
|
|
16
17
|
}
|
|
17
|
-
|
|
18
|
+
// qnScope is the same owner definition appendDomainEventCore uses: a leading
|
|
19
|
+
// ":" (empty owner) counts as unowned there, so it must here too.
|
|
20
|
+
if (!qnScope(event.type) || event.type.endsWith(":")) {
|
|
18
21
|
throw new InternalError({
|
|
19
22
|
message: `appendProvenanceEvent("${event.type}") — event types must be owner-qualified ("<feature>:<name>"). Unowned types are not allowed here.`,
|
|
20
23
|
});
|