@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.
Files changed (157) hide show
  1. package/dist/api/auth-routes.js +19 -4
  2. package/dist/api/client-ip.js +3 -6
  3. package/dist/api/extra-route.js +3 -2
  4. package/dist/api/pii-leak-guard.js +7 -4
  5. package/dist/api/redis-sse-broker.d.ts +2 -0
  6. package/dist/api/redis-sse-broker.js +6 -7
  7. package/dist/api/routes.js +16 -17
  8. package/dist/api/server.js +14 -7
  9. package/dist/api/sse-broker.d.ts +3 -0
  10. package/dist/api/sse-broker.js +14 -0
  11. package/dist/api/sse-route.d.ts +1 -1
  12. package/dist/api/sse-route.js +39 -6
  13. package/dist/bun-db/connection.js +0 -2
  14. package/dist/bun-db/index.d.ts +1 -1
  15. package/dist/bun-db/index.js +1 -1
  16. package/dist/bun-db/query.d.ts +1 -0
  17. package/dist/bun-db/query.js +15 -4
  18. package/dist/crypto/kek-source.js +40 -36
  19. package/dist/crypto/kms-adapter.js +2 -2
  20. package/dist/db/apply-entity-event.js +8 -2
  21. package/dist/db/blind-index-cleanup.js +11 -1
  22. package/dist/db/entity-table-meta.js +5 -3
  23. package/dist/db/event-store-executor-context.js +13 -19
  24. package/dist/db/event-store-executor-read.js +39 -36
  25. package/dist/db/event-store-executor-write.js +15 -17
  26. package/dist/db/migrate-generator.js +27 -28
  27. package/dist/db/pg-error.js +9 -5
  28. package/dist/db/queries/backfill-pii.js +15 -5
  29. package/dist/db/queries/shadow-swap.js +5 -4
  30. package/dist/db/table-builder.d.ts +1 -0
  31. package/dist/db/table-builder.js +7 -15
  32. package/dist/db/tenant-db.js +18 -2
  33. package/dist/derivatives/variant-key.js +3 -1
  34. package/dist/engine/boot-validator/access-declarations.js +12 -3
  35. package/dist/engine/boot-validator/entity-handler.js +13 -1
  36. package/dist/engine/boot-validator/entity-list-screens.d.ts +1 -0
  37. package/dist/engine/boot-validator/entity-list-screens.js +2 -2
  38. package/dist/engine/boot-validator/index.js +6 -6
  39. package/dist/engine/boot-validator/nav.d.ts +2 -1
  40. package/dist/engine/boot-validator/nav.js +10 -20
  41. package/dist/engine/boot-validator/ownership.d.ts +1 -1
  42. package/dist/engine/boot-validator/ownership.js +2 -1
  43. package/dist/engine/boot-validator/pii-retention.d.ts +2 -0
  44. package/dist/engine/boot-validator/pii-retention.js +5 -2
  45. package/dist/engine/boot-validator/projection-list-screens.js +8 -0
  46. package/dist/engine/boot-validator/query-output-columns.js +41 -5
  47. package/dist/engine/boot-validator/screens.d.ts +2 -2
  48. package/dist/engine/boot-validator/screens.js +103 -33
  49. package/dist/engine/boot-validator/transfer-graph.js +9 -6
  50. package/dist/engine/boot-validator/url-prefill-fields.js +8 -6
  51. package/dist/engine/boot-validator/zod-shape.js +17 -4
  52. package/dist/engine/build-app-schema.js +62 -19
  53. package/dist/engine/build-config-feature-schema.js +8 -1
  54. package/dist/engine/changeset-changes.js +24 -10
  55. package/dist/engine/feature-ast/extractors/events.js +11 -8
  56. package/dist/engine/feature-ast/extractors/handlers.js +38 -16
  57. package/dist/engine/feature-ast/extractors/resolve-import.d.ts +7 -2
  58. package/dist/engine/feature-ast/extractors/resolve-import.js +42 -15
  59. package/dist/engine/feature-ast/parse.d.ts +1 -0
  60. package/dist/engine/feature-ast/parse.js +18 -3
  61. package/dist/engine/feature-ast/patch.js +80 -61
  62. package/dist/engine/feature-ast/patcher.d.ts +1 -0
  63. package/dist/engine/feature-ast/patcher.js +2 -1
  64. package/dist/engine/feature-ast/render.js +1 -1
  65. package/dist/engine/index.d.ts +4 -3
  66. package/dist/engine/index.js +3 -2
  67. package/dist/engine/ownership.d.ts +1 -0
  68. package/dist/engine/ownership.js +15 -2
  69. package/dist/engine/pattern-library/mixed-schemas.js +15 -29
  70. package/dist/engine/pattern-library/shared-fields.d.ts +2 -0
  71. package/dist/engine/pattern-library/shared-fields.js +17 -0
  72. package/dist/engine/registry-facade.js +3 -0
  73. package/dist/engine/role-assignment.d.ts +7 -1
  74. package/dist/engine/role-assignment.js +27 -7
  75. package/dist/engine/schema-builder.d.ts +1 -0
  76. package/dist/engine/schema-builder.js +9 -2
  77. package/dist/engine/screen-helpers.js +15 -4
  78. package/dist/engine/steps/_event-match.js +22 -3
  79. package/dist/engine/steps/_read-source.js +8 -0
  80. package/dist/engine/where-rule-lint.js +22 -4
  81. package/dist/env/index.js +21 -1
  82. package/dist/errors/reasons.d.ts +0 -1
  83. package/dist/errors/reasons.js +2 -5
  84. package/dist/event-store/event-store.js +6 -1
  85. package/dist/event-store/provenance-append.js +4 -1
  86. package/dist/files/file-routes.d.ts +0 -1
  87. package/dist/files/file-routes.js +22 -7
  88. package/dist/files/local-provider.js +13 -10
  89. package/dist/files/provider-resolver.js +20 -1
  90. package/dist/files/types.js +16 -1
  91. package/dist/i18n/request-locale.d.ts +3 -2
  92. package/dist/i18n/request-locale.js +16 -6
  93. package/dist/i18n/required-surface-keys.js +23 -35
  94. package/dist/i18n/settings-hub-keys.js +3 -0
  95. package/dist/jobs/index.d.ts +2 -2
  96. package/dist/jobs/index.js +1 -1
  97. package/dist/jobs/job-runner.d.ts +2 -0
  98. package/dist/jobs/job-runner.js +150 -59
  99. package/dist/migrations/index.d.ts +1 -1
  100. package/dist/migrations/index.js +1 -1
  101. package/dist/migrations/pending-rebuilds.d.ts +4 -0
  102. package/dist/migrations/pending-rebuilds.js +23 -1
  103. package/dist/observability/escape-hatch-report.d.ts +1 -0
  104. package/dist/observability/escape-hatch-report.js +3 -0
  105. package/dist/observability/index.d.ts +1 -0
  106. package/dist/observability/index.js +1 -0
  107. package/dist/pipeline/dispatch-query.d.ts +1 -1
  108. package/dist/pipeline/dispatch-query.js +2 -2
  109. package/dist/pipeline/dispatch-shared.d.ts +2 -3
  110. package/dist/pipeline/dispatch-shared.js +14 -9
  111. package/dist/pipeline/dispatch-stream.js +2 -5
  112. package/dist/pipeline/dispatch-write.js +22 -5
  113. package/dist/pipeline/dispatcher.js +1 -2
  114. package/dist/pipeline/event-dispatcher-admin.js +12 -2
  115. package/dist/pipeline/event-dispatcher.d.ts +1 -0
  116. package/dist/pipeline/event-dispatcher.js +26 -11
  117. package/dist/pipeline/index.d.ts +1 -1
  118. package/dist/pipeline/index.js +1 -1
  119. package/dist/pipeline/member-read-only-transaction.js +1 -14
  120. package/dist/pipeline/msp-rebuild.d.ts +1 -0
  121. package/dist/pipeline/msp-rebuild.js +1 -0
  122. package/dist/pipeline/system-hooks.d.ts +1 -1
  123. package/dist/pipeline/system-hooks.js +9 -7
  124. package/dist/pipeline/tenant-timezone-cache.d.ts +2 -0
  125. package/dist/pipeline/tenant-timezone-cache.js +20 -6
  126. package/dist/redis/pubsub-signal.d.ts +2 -0
  127. package/dist/redis/pubsub-signal.js +2 -2
  128. package/dist/schema-cli.js +4 -14
  129. package/dist/search/meilisearch-adapter.js +17 -6
  130. package/dist/search/purge-subject.js +8 -3
  131. package/dist/stack/drain-jobs.js +24 -12
  132. package/dist/stack/table-helpers.js +10 -8
  133. package/dist/testing/closed-connection-error.js +2 -3
  134. package/dist/testing/wait-for.js +1 -0
  135. package/dist/ui-types/index.d.ts +1 -1
  136. package/dist/ui-types/index.js +1 -1
  137. package/dist/upgrade-cli.d.ts +1 -0
  138. package/dist/upgrade-cli.js +36 -15
  139. package/package.json +4 -4
  140. package/src/changes.json +206 -0
  141. package/src/scripts/codemod/migrate-cross-tenant.ts +14 -2
  142. package/src/scripts/codemod/migrate-db-raw.ts +16 -8
  143. package/src/scripts/codemod/migrate-open-to-all.ts +7 -1
  144. package/src/scripts/codemod/pii-personal-migration.ts +56 -19
  145. package/src/scripts/codemod/unprocessable-error-details-reason.ts +83 -26
  146. package/dist/scripts/codemod/crypto-shredding-testing-move.d.ts +0 -2
  147. package/dist/scripts/codemod/crypto-shredding-testing-move.js +0 -109
  148. package/dist/scripts/codemod/migrate-cross-tenant.d.ts +0 -19
  149. package/dist/scripts/codemod/migrate-cross-tenant.js +0 -266
  150. package/dist/scripts/codemod/migrate-db-raw.d.ts +0 -23
  151. package/dist/scripts/codemod/migrate-db-raw.js +0 -345
  152. package/dist/scripts/codemod/migrate-open-to-all.d.ts +0 -22
  153. package/dist/scripts/codemod/migrate-open-to-all.js +0 -194
  154. package/dist/scripts/codemod/pii-personal-migration.d.ts +0 -11
  155. package/dist/scripts/codemod/pii-personal-migration.js +0 -615
  156. package/dist/scripts/codemod/unprocessable-error-details-reason.d.ts +0 -2
  157. package/dist/scripts/codemod/unprocessable-error-details-reason.js +0 -235
@@ -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";
@@ -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;
@@ -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, tableColumnSqlNames(table), `ownership where-rule on "${tableNameOf(table)}"`);
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
- path: "description",
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
- path: "description",
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
- export type AssignableFromRole = "User" | "Member" | "Editor" | "Admin" | "TenantAdmin" | "SystemAdmin";
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
- // App roles are ordinary, non-privileged tiers: whoever may manage members
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 (requiredAssignRank(role, assignableAppRoles) <= actorRank)
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
- shape["id"] = z.uuid().optional();
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 ? screens.find((s) => getId(s) === 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") && a.screen === detailScreenId);
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" && s.createScreen === detailScreenId)
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
- return expr.nodes.every((node) => evaluateExpr(node, payload));
73
- case "or":
74
- return expr.nodes.some((node) => evaluateExpr(node, payload));
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 (fail-open on
21
- // the lint itself would be worse than skipping it).
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).safeParse(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;
@@ -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];
@@ -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
- // Both the docs site (`errors/` pages) and the enterprise repo read this same
17
- // source, so the prefix stays a naming convention here, not a new registry.
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
- if (!event.type.includes(":")) {
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
  });
@@ -14,7 +14,6 @@ export type FileRef = {
14
14
  entityId: string | null;
15
15
  fieldName: string | null;
16
16
  insertedById: string | null;
17
- isDeleted: boolean;
18
17
  };
19
18
  export type FileAccessGuard = (args: {
20
19
  readonly fileRef: FileRef;