@cosmicdrift/kumiko-framework 0.388.0 → 0.389.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 (194) hide show
  1. package/AGENTS.md +3 -0
  2. package/agents/examples/golden-path.md +13 -1
  3. package/agents/examples/lifecycle-hooks.md +55 -2
  4. package/agents/examples/list-table-options.md +466 -0
  5. package/agents/examples/pdf-audit-export.md +4 -2
  6. package/agents/examples/state-machine.md +3 -2
  7. package/agents/examples/tenant-isolation.md +73 -1
  8. package/agents/examples.md +1 -0
  9. package/agents/widgets.md +1 -0
  10. package/dist/api/api-constants.d.ts +1 -0
  11. package/dist/api/api-constants.js +2 -0
  12. package/dist/api/auth-routes-shared.d.ts +2 -0
  13. package/dist/api/auth-routes-shared.js +5 -0
  14. package/dist/api/client-error-route.js +2 -3
  15. package/dist/api/entity-read-access.d.ts +2 -1
  16. package/dist/api/entity-read-access.js +14 -0
  17. package/dist/api/export/export-cells-reference.d.ts +9 -0
  18. package/dist/api/export/export-cells-reference.js +21 -0
  19. package/dist/api/export/export-cells.d.ts +11 -0
  20. package/dist/api/export/export-cells.js +136 -0
  21. package/dist/api/export/export-ciphertext-guard.d.ts +2 -0
  22. package/dist/api/export/export-ciphertext-guard.js +27 -0
  23. package/dist/api/export/export-columns.d.ts +29 -0
  24. package/dist/api/export/export-columns.js +85 -0
  25. package/dist/api/export/export-errors.d.ts +4 -0
  26. package/dist/api/export/export-errors.js +14 -0
  27. package/dist/api/export/export-route.d.ts +4 -0
  28. package/dist/api/export/export-route.js +74 -0
  29. package/dist/api/export/export-rows.d.ts +13 -0
  30. package/dist/api/export/export-rows.js +62 -0
  31. package/dist/api/export/export-screen.d.ts +12 -0
  32. package/dist/api/export/export-screen.js +47 -0
  33. package/dist/api/export/export-xlsx-writer.d.ts +4 -0
  34. package/dist/api/export/export-xlsx-writer.js +21 -0
  35. package/dist/api/extra-route.d.ts +4 -0
  36. package/dist/api/pii-leak-guard.d.ts +3 -0
  37. package/dist/api/pii-leak-guard.js +3 -2
  38. package/dist/api/routes.d.ts +16 -0
  39. package/dist/api/routes.js +2 -2
  40. package/dist/api/server-extra-routes.js +3 -0
  41. package/dist/api/server-routes.d.ts +1 -1
  42. package/dist/api/server-routes.js +8 -2
  43. package/dist/api/server-wiring.js +2 -0
  44. package/dist/api/sse-route.d.ts +2 -1
  45. package/dist/api/sse-route.js +11 -1
  46. package/dist/config/index.d.ts +3 -1
  47. package/dist/crypto/pg-kms-adapter.d.ts +1 -0
  48. package/dist/crypto/pg-kms-adapter.js +12 -18
  49. package/dist/db/aggregate-sql.d.ts +2 -0
  50. package/dist/db/aggregate-sql.js +10 -0
  51. package/dist/db/closed-connection.js +12 -19
  52. package/dist/db/connection.d.ts +4 -5
  53. package/dist/db/connection.js +29 -20
  54. package/dist/db/driver-row-coercion.js +4 -4
  55. package/dist/db/driver-value-preparation.js +6 -4
  56. package/dist/db/event-store-executor-read-aggregate-currencies.d.ts +17 -0
  57. package/dist/db/event-store-executor-read-aggregate-currencies.js +40 -0
  58. package/dist/db/event-store-executor-read-aggregate.js +36 -5
  59. package/dist/db/event-store-executor-read-filter.js +6 -3
  60. package/dist/db/event-store-executor-write-atomic.d.ts +4 -0
  61. package/dist/db/event-store-executor-write-atomic.js +41 -0
  62. package/dist/db/event-store-executor-write-steps.d.ts +0 -3
  63. package/dist/db/event-store-executor-write-steps.js +0 -5
  64. package/dist/db/event-store-executor-write.js +15 -15
  65. package/dist/db/event-store-executor.js +2 -1
  66. package/dist/db/executor-managed-tables.js +1 -1
  67. package/dist/db/index.d.ts +1 -1
  68. package/dist/db/index.js +1 -1
  69. package/dist/db/migrate-runner.js +3 -3
  70. package/dist/db/pg-error.js +26 -21
  71. package/dist/db/queries/backfill-pii.js +3 -6
  72. package/dist/db/queries/database-now.js +2 -1
  73. package/dist/db/queries/shadow-swap.js +2 -3
  74. package/dist/db/query.d.ts +14 -0
  75. package/dist/db/query.js +68 -43
  76. package/dist/db/reference-data.js +14 -2
  77. package/dist/db/statement-timeout.js +2 -1
  78. package/dist/db/tenant-db-upsert.d.ts +5 -0
  79. package/dist/db/tenant-db-upsert.js +91 -0
  80. package/dist/db/tenant-db.js +11 -1
  81. package/dist/engine/boot-validator/bundled-lists-table.d.ts +2 -0
  82. package/dist/engine/boot-validator/bundled-lists-table.js +36 -0
  83. package/dist/engine/boot-validator/entity-list-aggregate-options.d.ts +3 -0
  84. package/dist/engine/boot-validator/entity-list-aggregate-options.js +23 -0
  85. package/dist/engine/boot-validator/list-bulk-actions.d.ts +3 -0
  86. package/dist/engine/boot-validator/list-bulk-actions.js +49 -0
  87. package/dist/engine/boot-validator/list-column-layout.d.ts +8 -0
  88. package/dist/engine/boot-validator/list-column-layout.js +41 -0
  89. package/dist/engine/boot-validator/list-table-aggregate-options.d.ts +24 -0
  90. package/dist/engine/boot-validator/list-table-aggregate-options.js +145 -0
  91. package/dist/engine/boot-validator/list-table-aggregate-query.d.ts +10 -0
  92. package/dist/engine/boot-validator/list-table-aggregate-query.js +31 -0
  93. package/dist/engine/boot-validator/list-table-export-options.d.ts +1 -0
  94. package/dist/engine/boot-validator/list-table-export-options.js +27 -0
  95. package/dist/engine/boot-validator/list-table-options.d.ts +3 -0
  96. package/dist/engine/boot-validator/list-table-options.js +77 -0
  97. package/dist/engine/boot-validator/list-table-reject-unknown-keys.d.ts +1 -0
  98. package/dist/engine/boot-validator/list-table-reject-unknown-keys.js +7 -0
  99. package/dist/engine/boot-validator/projection-list-screens.d.ts +1 -1
  100. package/dist/engine/boot-validator/projection-list-screens.js +3 -13
  101. package/dist/engine/boot-validator/related-list-table-rules.d.ts +4 -0
  102. package/dist/engine/boot-validator/related-list-table-rules.js +42 -0
  103. package/dist/engine/boot-validator/screens-kind-entity-list.js +7 -0
  104. package/dist/engine/boot-validator/screens-kind-projection-list.js +4 -0
  105. package/dist/engine/boot-validator/screens-lists.js +12 -2
  106. package/dist/engine/build-app-schema.d.ts +2 -1
  107. package/dist/engine/build-app-schema.js +9 -1
  108. package/dist/engine/build-config-feature-schema.js +6 -1
  109. package/dist/engine/define-feature.js +4 -0
  110. package/dist/engine/feature-ast/extractors/index.d.ts +1 -1
  111. package/dist/engine/feature-ast/extractors/index.js +1 -1
  112. package/dist/engine/feature-ast/extractors/round1.d.ts +2 -1
  113. package/dist/engine/feature-ast/extractors/round1.js +6 -0
  114. package/dist/engine/feature-ast/index.d.ts +1 -1
  115. package/dist/engine/feature-ast/parse.js +3 -1
  116. package/dist/engine/feature-ast/patch.d.ts +2 -0
  117. package/dist/engine/feature-ast/patch.js +1 -0
  118. package/dist/engine/feature-ast/pattern-change-schema.d.ts +7 -245
  119. package/dist/engine/feature-ast/pattern-change-schema.js +9 -163
  120. package/dist/engine/feature-ast/pattern-id-matchers.js +1 -0
  121. package/dist/engine/feature-ast/pattern-id-schema.d.ts +251 -0
  122. package/dist/engine/feature-ast/pattern-id-schema.js +166 -0
  123. package/dist/engine/feature-ast/patterns.d.ts +5 -1
  124. package/dist/engine/feature-ast/patterns.js +1 -0
  125. package/dist/engine/feature-ast/render.js +4 -0
  126. package/dist/engine/feature-builder-state.d.ts +1 -0
  127. package/dist/engine/feature-builder-state.js +1 -0
  128. package/dist/engine/feature-manifest.d.ts +1 -0
  129. package/dist/engine/feature-manifest.js +1 -0
  130. package/dist/engine/field-access.d.ts +1 -0
  131. package/dist/engine/field-access.js +12 -0
  132. package/dist/engine/index.d.ts +2 -2
  133. package/dist/engine/index.js +1 -1
  134. package/dist/engine/pattern-library/library.js +2 -1
  135. package/dist/engine/pattern-library/static-schemas.d.ts +1 -0
  136. package/dist/engine/pattern-library/static-schemas.js +9 -0
  137. package/dist/engine/screen-filter-ops.js +2 -0
  138. package/dist/engine/strip-disabled-features-from-app-schema.js +7 -3
  139. package/dist/engine/types/feature.d.ts +1 -0
  140. package/dist/engine/types/index.d.ts +4 -3
  141. package/dist/engine/types/screen.d.ts +1 -1
  142. package/dist/engine/types/screen.js +1 -1
  143. package/dist/entrypoint/index.d.ts +2 -0
  144. package/dist/entrypoint/index.js +3 -0
  145. package/dist/event-store/event-store.d.ts +1 -0
  146. package/dist/event-store/event-store.js +7 -4
  147. package/dist/event-store/provenance-append.js +1 -1
  148. package/dist/files/create-file-ref-from-bytes.js +3 -0
  149. package/dist/files/create-file-ref.d.ts +3 -0
  150. package/dist/files/create-file-ref.js +11 -2
  151. package/dist/files/file-routes.d.ts +2 -0
  152. package/dist/files/file-routes.js +29 -2
  153. package/dist/files/provider-resolver.d.ts +1 -0
  154. package/dist/files/provider-resolver.js +1 -1
  155. package/dist/files/record-write-check.d.ts +6 -0
  156. package/dist/files/record-write-check.js +13 -0
  157. package/dist/files/upload-ledger.d.ts +2 -2
  158. package/dist/files/upload-ledger.js +4 -4
  159. package/dist/i18n/required-surface-keys.js +7 -2
  160. package/dist/internal/testing.js +1 -1
  161. package/dist/jobs/job-runner-scope.js +6 -6
  162. package/dist/jobs/job-runner.js +3 -0
  163. package/dist/observability/escape-hatch-report.js +9 -2
  164. package/dist/pipeline/after-rollback-sink.d.ts +6 -0
  165. package/dist/pipeline/after-rollback-sink.js +32 -0
  166. package/dist/pipeline/dispatch-batch.js +8 -1
  167. package/dist/pipeline/dispatch-shared.js +4 -0
  168. package/dist/pipeline/event-dispatcher-delivery.js +3 -1
  169. package/dist/pipeline/event-dispatcher.d.ts +1 -0
  170. package/dist/pipeline/event-dispatcher.js +6 -9
  171. package/dist/pipeline/handler-context-dispatch.d.ts +2 -0
  172. package/dist/pipeline/handler-context-dispatch.js +13 -1
  173. package/dist/pipeline/handler-context-events.d.ts +2 -4
  174. package/dist/pipeline/handler-context-events.js +6 -4
  175. package/dist/pipeline/system-hooks.d.ts +3 -1
  176. package/dist/pipeline/system-hooks.js +55 -6
  177. package/dist/pipeline/write-result-masking.js +13 -10
  178. package/dist/schema-cli/baseline-command.js +1 -1
  179. package/dist/schema-cli/status-command.js +1 -1
  180. package/dist/search/reindex-entity.js +15 -5
  181. package/dist/ui-types/index.d.ts +2 -2
  182. package/dist/ui-types/index.js +1 -1
  183. package/dist/upgrade-changelog.d.ts +2 -0
  184. package/dist/upgrade-changelog.js +14 -0
  185. package/dist/upgrade-cli.d.ts +3 -12
  186. package/dist/upgrade-cli.js +22 -76
  187. package/dist/upgrade-marker-parse.d.ts +32 -0
  188. package/dist/upgrade-marker-parse.js +73 -0
  189. package/package.json +9 -6
  190. package/src/changes.json +245 -0
  191. package/src/scripts/codemod/codemod-support.ts +1 -1
  192. package/src/scripts/codemod/db-factory.ts +1 -1
  193. package/dist/engine/provider-plugin-context.d.ts +0 -3
  194. package/dist/engine/provider-plugin-context.js +0 -28
@@ -57,6 +57,7 @@ export function createInitialFeatureBuilderState() {
57
57
  translations: {},
58
58
  isSystemScoped: false,
59
59
  toggleableDefault: undefined,
60
+ adoptsBundledLists: false,
60
61
  description: undefined,
61
62
  uiHints: undefined,
62
63
  treeActions: undefined,
@@ -40,6 +40,7 @@ export type ManifestFeature = {
40
40
  readonly name: string;
41
41
  readonly description: string | null;
42
42
  readonly toggleableDefault: boolean | null;
43
+ readonly adoptsBundledLists?: true;
43
44
  readonly requires: readonly string[];
44
45
  readonly optionalRequires: readonly string[];
45
46
  readonly configReads: readonly string[];
@@ -59,6 +59,7 @@ export function buildManifestFromRegistry(registry, options) {
59
59
  name: feature.name,
60
60
  description: feature.description ?? null,
61
61
  toggleableDefault: feature.toggleableDefault ?? null,
62
+ ...(feature.adoptsBundledLists && { adoptsBundledLists: true }),
62
63
  requires: [...feature.requires],
63
64
  optionalRequires: [...feature.optionalRequires],
64
65
  configReads: [...feature.configReads],
@@ -1,6 +1,7 @@
1
1
  import type { EntityDefinition, SessionUser } from "./types/index.ts";
2
2
  export declare function maskWriteOnlyFields<T extends Readonly<Record<string, unknown>>>(entity: EntityDefinition, row: T): T;
3
3
  export declare function dropEmptyWriteOnlyValues<T extends Readonly<Record<string, unknown>>>(entity: EntityDefinition, values: T): T;
4
+ export declare function userMayReadFieldInBulk(entity: EntityDefinition, fieldName: string, user: Pick<SessionUser, "roles">): boolean;
4
5
  export declare function filterReadFields(entity: EntityDefinition, data: Readonly<Record<string, unknown>>, user: SessionUser): Record<string, unknown>;
5
6
  export declare function checkWriteFieldRoles(entity: EntityDefinition, changes: Readonly<Record<string, unknown>>, user: SessionUser): string | null;
6
7
  export declare function checkWriteFieldOwnership(entity: EntityDefinition, submittedChanges: Readonly<Record<string, unknown>>, user: SessionUser, oldRow?: Readonly<Record<string, unknown>>, rowContext?: Readonly<Record<string, unknown>>): string | null;
@@ -35,6 +35,18 @@ export function dropEmptyWriteOnlyValues(entity, values) {
35
35
  delete kept[name];
36
36
  return kept; // @cast-boundary engine-payload
37
37
  }
38
+ // Role-level gate for bulk exports: false for writeOnly fields and for fields whose read-access
39
+ // map grants none of the user's roles. Per-row ownership rules are not evaluated here; those
40
+ // cells come back empty from the list query itself.
41
+ export function userMayReadFieldInBulk(entity, fieldName, user) {
42
+ const field = entity.fields[fieldName];
43
+ if (!field)
44
+ return false;
45
+ if (field.type === "text" && field.writeOnly === true)
46
+ return false;
47
+ const accessMap = normalizeAccessEntry(field.access?.read);
48
+ return accessMap === undefined || user.roles.some((role) => accessMap[role] !== undefined);
49
+ }
38
50
  // Field-level read filtering. Returns a copy of `data` with fields stripped
39
51
  // if the user's roles don't grant read access OR the ownership-rule for the
40
52
  // matching role doesn't accept this concrete row. Fields without access
@@ -1,6 +1,7 @@
1
1
  export { handlerFieldLabelKey, handlerTitleKey } from "../i18n/required-surface-keys.ts";
2
2
  export { hasAccess } from "./access.ts";
3
3
  export { isPrincipalStatusPlugin, isTenantLifecycleStatusPlugin, type PrincipalProfile, type PrincipalStatus, type PrincipalStatusPlugin, TENANT_TEARDOWN_STATUSES, type TenantLifecycleStatusPlugin, } from "./active-membership.ts";
4
+ export { validateBundledListsOptions } from "./boot-validator/bundled-lists-table.ts";
4
5
  export { PII_DIRECT_NAME_HINTS, PII_USER_OWNED_NAME_HINTS, PII_USER_REFERENCE_NAME_HINTS, } from "./boot-validator/entity-handler.ts";
5
6
  export { entityHasAnonymizableSubjectField } from "./boot-validator/pii-retention.ts";
6
7
  export { type CardMetaOverflow, collectCardMetaOverflow, collectWriteHandlerQns, MAX_TRANSFER_DEPTH, SECURITY_BASELINE_FEATURE_NAMES, type ValidateBootOptions, validateAppCustomScreenWriteQns, validateBoot, } from "./boot-validator.ts";
@@ -59,7 +60,6 @@ export { parseRefTarget, parseRefTargetEntityName } from "./parse-ref-target.ts"
59
60
  export { buildPipelineSteps, stepsPipeline } from "./pipeline.ts";
60
61
  export { projectAppSchemaForRoles } from "./project-app-schema-for-roles.ts";
61
62
  export { defineApply, defineMspApply, setFields } from "./projection-helpers.ts";
62
- export { providerPluginContextFromHandler, providerPluginContextFromJob, } from "./provider-plugin-context.ts";
63
63
  export type { BuiltinQnType, ParsedQn, QnType } from "./qualified-name.ts";
64
64
  export { isValidQn, parseQn, QnTypes, qn, toKebab } from "./qualified-name.ts";
65
65
  export { readClaim } from "./read-claim.ts";
@@ -87,7 +87,7 @@ export { assertNotificationVariables, templateVariable, templateVariablesToZod,
87
87
  export { type EffectiveFeaturesResolver, findTierResolverUsage, isTierResolverPlugin, requireTierResolverPlugin, TENANT_TIER_RESOLVER_EXT, type TierResolverPlugin, type TrialGate, } from "./tier-resolver-extension.ts";
88
88
  export { findOverDeepTransferChain, resolveTransferAdjacency as resolveEntityTransferAdjacency, type TransferAdjacency, type TransferEdge, type TransferEdgeLink, } from "./transfer-adjacency.ts";
89
89
  export { isSystemTenant, isUuid, parseTenantId, SYSTEM_TENANT_ID } from "./types/identifiers.ts";
90
- export type { AccessRule, ActionFormScreenDefinition, ActiveMembership, ActiveMembershipRejection, ActiveMembershipResult, AgentExposure, AgentHandlerHints, AgentRisk, AppContext, AppendEventArgs, AppendEventFn, AuthClaimsContext, AuthClaimsFn, AuthClaimsHookDef, BeforeTransactionPhase, BeforeTransactionResult, BelongsToRelation, BigIntFieldDef, BooleanFieldDef, BootOptionName, CamelToKebab, ChatSendFailureCode, ClaimKeyDefinition, ClaimKeyHandle, ClaimKeyJsType, ClaimKeyType, ConcurrencyMode, ConfigAccessor, ConfigAccessorFactory, ConfigBacking, ConfigCascade, ConfigCascadeLevel, ConfigDefinition, ConfigEditScreenDefinition, ConfigKeyAccess, ConfigKeyDefinition, ConfigKeyHandle, ConfigKeyType, ConfigMask, ConfigResolver, ConfigScope, ConfigSecretsReader, ConfigSeedDef, ConfigStoredRow, ConfigStoredRowWithSource, ConfigValidateContext, ConfigValidateFn, ConfigValue, ConfigValueSource, ConfigValueWithSource, ContentCollectionDefinition, CreateSeedOptions, CreateTenantSeedOptions, CreateUserSeedOptions, CustomScreenDefinition, CustomScreenRoute, DashboardChartPanel, DashboardCurrencyFormat, DashboardCustomPanel, DashboardDateParam, DashboardFeedPanel, DashboardFilterDefinition, DashboardI18nText, DashboardListPanel, DashboardMoneyParam, DashboardPanelDefinition, DashboardPanelVisibility, DashboardProgressListPanel, DashboardProgressTone, DashboardRecordBinding, DashboardRefresh, DashboardRelativeTimeFormat, DashboardRelativeTimeParam, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DashboardText, DashboardTextBlock, DashboardTextPanel, DashboardTextParam, DashboardValueFormat, DateFieldDef, DecimalFieldDef, DeleteContext, DeliveryErrorCode, DeliveryFailureCode, DeliverySkipReason, DeriveContext, DerivedFieldDef, DerivedFieldsMap, DerivedValueType, EditExtensionSection, EditFieldPrefill, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EmbeddedListEditSpec, EntityDefinition, EntityEditAction, EntityEditActionPlacement, EntityEditScreenDefinition, EntityId, EntityListExpandableRow, EntityListScreenDefinition, EntityObjectDefinition, 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, HookDefinition, HookMap, ImageFieldDef, ImagesFieldDef, JobBackoff, JobBackoffStrategy, JobContext, JobDeferral, JobDefinition, JobHandlerFn, JobTrigger, JobTriggerEvent, JsonbFieldDef, KumikoEntityTypeMap, KumikoEventTypeMap, KumikoExtensionOptionsMap, KumikoHandlerPayloadMap, KumikoHandlerResultMap, LifecycleHookType, ListColumnSpec, ListFacetSpec, ListPaginationMode, ListSortDir, ListSortSpec, LongTextFindability, ManyToManyRelation, MaxPerTenantOptions, MemberReader, MetricNavigate, MetricSpec, MspErrorMode, MspErrorPolicy, MultiSelectFieldDef, MultiStreamApplyFn, MultiStreamProjectionDefinition, NameOrRef, NavDefinition, NavIconKey, NotificationDataFn, NotificationDefinition, NotificationLocalizedTemplate, NotificationRecipientFn, NotificationRecordMapping, NotificationTemplateBody, NotificationTemplateFn, NotificationTemplateMeta, NotifyDeferred, NotifyDelivery, NotifyDeliveryStatus, NotifyDispatched, NotifyFactory, NotifyFn, NotifyJobDispatcher, NotifyOptions, NotifyPriority, NotifyResult, NumberFieldDef, OnDeleteStrategy, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, ParentRefDef, PersonalAnnotations, PersonalAnnotationsLongText, PersonalAnnotationsNoFind, PersonalSubject, PlatformComponent, PluralForms, PostDeleteHookFn, PostSaveHookFn, PreDeleteHookFn, PreparedWriteEvent, PreQueryHookFn, PreSaveHookFn, ProjectionDefinition, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, ProjectionTable, ProviderPluginContext, QualifiedEventName, QueryEvent, QueryHandlerDef, QueryHandlerFn, RateLimitPer, RecordHeaderSpec, RecordHeaderSubtitlePart, ReferenceDataDef, Registry, RelationDefinition, ResolvedPiiFlags, RetentionDef, RoleAccessPersonalData, RoleAccessRule, RowAction, RowActionNavigate, RowActionNavigateBase, RowActionWriteHandler, RowFieldExtractor, SaveContext, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenNavSugar, ScreenSlots, SecretKeyDefinition, SecretKeyHandle, SecretMintConfirmStep, SecretMintScreenDefinition, SecretNamespaceDefinition, SecretNamespaceHandle, SecretNamespaceOptions, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, SelectFieldDef, SessionUser, SessionUserOrigin, StackBootRequirement, StreamHandlerDef, StreamHandlerFn, Subscribe, TargetRef, TemplateImagesVariableDef, TemplateListVariableDef, TemplateScalarVariableDef, TemplateVariableDef, TemplateVariables, TenantId, TenantWriteTarget, TextFieldDef, ToolbarAction, TranslationEntry, TranslationKeys, TranslationsDef, TranslationValue, TreeAction, TreeActionDef, TreeActionsHandle, TreeChildrenSubscribe, TreeNode, TreeNodeState, UnitKey, UnsafeAppendEventFn, ValidationError, ValidationHookFn, WorkspaceDefinition, WriteEvent, WriteHandlerDef, WriteHandlerFn, WriteResult, } from "./types/index.ts";
90
+ export type { AccessRule, ActionFormScreenDefinition, ActiveMembership, ActiveMembershipRejection, ActiveMembershipResult, AgentExposure, AgentHandlerHints, AgentRisk, AppContext, AppendEventArgs, AppendEventFn, AuthClaimsContext, AuthClaimsFn, AuthClaimsHookDef, BeforeTransactionPhase, BeforeTransactionResult, BelongsToRelation, BigIntFieldDef, BooleanFieldDef, BootOptionName, BundledListsOptions, CamelToKebab, ChatSendFailureCode, ClaimKeyDefinition, ClaimKeyHandle, ClaimKeyJsType, ClaimKeyType, ConcurrencyMode, ConfigAccessor, ConfigAccessorFactory, ConfigBacking, ConfigCascade, ConfigCascadeLevel, ConfigDefinition, ConfigEditScreenDefinition, ConfigKeyAccess, ConfigKeyDefinition, ConfigKeyHandle, ConfigKeyType, ConfigMask, ConfigResolver, ConfigScope, ConfigSecretsReader, ConfigSeedDef, ConfigStoredRow, ConfigStoredRowWithSource, ConfigValidateContext, ConfigValidateFn, ConfigValue, ConfigValueSource, ConfigValueWithSource, ContentCollectionDefinition, CreateSeedOptions, CreateTenantSeedOptions, CreateUserSeedOptions, CustomScreenDefinition, CustomScreenRoute, DashboardChartPanel, DashboardCurrencyFormat, DashboardCustomPanel, DashboardDateParam, DashboardFeedPanel, DashboardFilterDefinition, DashboardI18nText, DashboardListPanel, DashboardMoneyParam, DashboardPanelDefinition, DashboardPanelVisibility, DashboardProgressListPanel, DashboardProgressTone, DashboardRecordBinding, DashboardRefresh, DashboardRelativeTimeFormat, DashboardRelativeTimeParam, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DashboardText, DashboardTextBlock, DashboardTextPanel, DashboardTextParam, DashboardValueFormat, DateFieldDef, DecimalFieldDef, DeleteContext, DeliveryErrorCode, DeliveryFailureCode, DeliverySkipReason, DeriveContext, DerivedFieldDef, DerivedFieldsMap, DerivedValueType, EditExtensionSection, EditFieldPrefill, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EmbeddedListEditSpec, EntityDefinition, EntityEditAction, EntityEditActionPlacement, EntityEditScreenDefinition, EntityId, EntityListExpandableRow, EntityListScreenDefinition, EntityObjectDefinition, EntityRef, EntityRelations, EscapeHatchAuditSink, EscapeHatchDeclaration, EscapeHatchKind, EscapeHatchReporter, EscapeHatchTarget, EscapeHatchUseEvent, EventDef, ExpandableRowGrouping, ExtensionOptionsArgs, ExtensionOptionsFor, FeatureDefinition, FeatureRegistrar, FieldAccess, FieldCondition, FieldDefinition, FieldFormatRegistry, FieldIconKey, FieldRenderer, FileFieldDef, FilesFieldDef, Findability, FormatSpec, FormWidth, HandlerContext, HasManyRelation, HookDefinition, HookMap, ImageFieldDef, ImagesFieldDef, JobBackoff, JobBackoffStrategy, JobContext, JobDeferral, JobDefinition, JobHandlerFn, JobTrigger, JobTriggerEvent, JsonbFieldDef, KumikoEntityTypeMap, KumikoEventTypeMap, KumikoExtensionOptionsMap, KumikoHandlerPayloadMap, KumikoHandlerResultMap, LifecycleHookType, ListColumnSpec, ListFacetSpec, ListPaginationMode, ListSortDir, ListSortSpec, ListTableOptions, LongTextFindability, ManyToManyRelation, MaxPerTenantOptions, MemberReader, MetricNavigate, MetricSpec, MspErrorMode, MspErrorPolicy, MultiSelectFieldDef, MultiStreamApplyFn, MultiStreamProjectionDefinition, NameOrRef, NavDefinition, NavIconKey, NotificationDataFn, NotificationDefinition, NotificationLocalizedTemplate, NotificationRecipientFn, NotificationRecordMapping, NotificationTemplateBody, NotificationTemplateFn, NotificationTemplateMeta, NotifyDeferred, NotifyDelivery, NotifyDeliveryStatus, NotifyDispatched, NotifyFactory, NotifyFn, NotifyJobDispatcher, NotifyOptions, NotifyPriority, NotifyResult, NumberFieldDef, OnDeleteStrategy, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, ParentRefDef, PersonalAnnotations, PersonalAnnotationsLongText, PersonalAnnotationsNoFind, PersonalSubject, PlatformComponent, PluralForms, PostDeleteHookFn, PostSaveHookFn, PreDeleteHookFn, PreparedWriteEvent, PreQueryHookFn, PreSaveHookFn, ProjectionDefinition, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, ProjectionTable, QualifiedEventName, QueryEvent, QueryHandlerDef, QueryHandlerFn, RateLimitPer, RecordHeaderSpec, RecordHeaderSubtitlePart, ReferenceDataDef, Registry, RelationDefinition, ResolvedPiiFlags, RetentionDef, RoleAccessPersonalData, RoleAccessRule, RowAction, RowActionNavigate, RowActionNavigateBase, RowActionWriteHandler, RowFieldExtractor, SaveContext, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenNavSugar, ScreenSlots, SecretKeyDefinition, SecretKeyHandle, SecretMintConfirmStep, SecretMintScreenDefinition, SecretNamespaceDefinition, SecretNamespaceHandle, SecretNamespaceOptions, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, SelectFieldDef, SessionUser, SessionUserOrigin, StackBootRequirement, StreamHandlerDef, StreamHandlerFn, Subscribe, TargetRef, TemplateImagesVariableDef, TemplateListVariableDef, TemplateScalarVariableDef, TemplateVariableDef, TemplateVariables, TenantId, TenantWriteTarget, TextFieldDef, ToolbarAction, TranslationEntry, TranslationKeys, TranslationsDef, TranslationValue, TreeAction, TreeActionDef, TreeActionsHandle, TreeChildrenSubscribe, TreeNode, TreeNodeState, UnitKey, UnsafeAppendEventFn, ValidationError, ValidationHookFn, WorkspaceDefinition, WriteEvent, WriteHandlerDef, WriteHandlerFn, WriteResult, } from "./types/index.ts";
91
91
  export { DEFAULT_CURRENCIES, DEFAULT_LOCALES, DELIVERY_FAILURE_CODES, DELIVERY_SKIP_REASONS, HookPhases, HTTP_ERROR_CODE_PATTERN, isAgentVisibleScreen, isDeliveryErrorCode, isOpenToAllGranted, resolveAgentExposure, } from "./types/index.ts";
92
92
  export type { AwaitedEventType, PipelineBuildCtx, PipelineCtx, PipelineDef, StepBuilder, StepDef, StepFailureStrategy, StepInstance, StepKind, StepNamespace, StepResolver, } from "./types/step.ts";
93
93
  export { runValidation } from "./validation.ts";
@@ -2,6 +2,7 @@
2
2
  export { handlerFieldLabelKey, handlerTitleKey } from "../i18n/required-surface-keys.js";
3
3
  export { hasAccess } from "./access.js";
4
4
  export { isPrincipalStatusPlugin, isTenantLifecycleStatusPlugin, TENANT_TEARDOWN_STATUSES, } from "./active-membership.js";
5
+ export { validateBundledListsOptions } from "./boot-validator/bundled-lists-table.js";
5
6
  export { PII_DIRECT_NAME_HINTS, PII_USER_OWNED_NAME_HINTS, PII_USER_REFERENCE_NAME_HINTS, } from "./boot-validator/entity-handler.js";
6
7
  export { entityHasAnonymizableSubjectField } from "./boot-validator/pii-retention.js";
7
8
  export { collectCardMetaOverflow, collectWriteHandlerQns, MAX_TRANSFER_DEPTH, SECURITY_BASELINE_FEATURE_NAMES, validateAppCustomScreenWriteQns, validateBoot, } from "./boot-validator.js";
@@ -56,7 +57,6 @@ export { parseRefTarget, parseRefTargetEntityName } from "./parse-ref-target.js"
56
57
  export { buildPipelineSteps, stepsPipeline } from "./pipeline.js";
57
58
  export { projectAppSchemaForRoles } from "./project-app-schema-for-roles.js";
58
59
  export { defineApply, defineMspApply, setFields } from "./projection-helpers.js";
59
- export { providerPluginContextFromHandler, providerPluginContextFromJob, } from "./provider-plugin-context.js";
60
60
  export { isValidQn, parseQn, QnTypes, qn, toKebab } from "./qualified-name.js";
61
61
  export { readClaim } from "./read-claim.js";
62
62
  export { createRegistry } from "./registry.js";
@@ -14,12 +14,13 @@
14
14
  // the Designer/LLM relies on.
15
15
  import { aiClassifySchema, aiExtractSchema, aiGenerateSchema, authClaimsSchema, defineEventSchema, hookSchema, httpRouteSchema, jobSchema, multiStreamProjectionSchema, notificationSchema, projectionSchema, queryHandlerSchema, screenSchema, streamHandlerSchema, writeHandlerSchema, } from "./mixed-schemas.js";
16
16
  import { envSchemaSchema, exposesApiSchema, extendsRegistrarSchema, treeActionsSchema, unknownSchema, usesApiSchema, } from "./opaque-schemas.js";
17
- import { claimKeySchema, configSchema, describeSchema, entitySchema, metricSchema, navSchema, optionalRequiresSchema, readsConfigSchema, referenceDataSchema, relationSchema, requiresSchema, secretNamespaceSchema, secretSchema, systemScopeSchema, toggleableSchema, translationsSchema, uiHintsSchema, useExtensionSchema, workspaceSchema, } from "./static-schemas.js";
17
+ import { adoptsBundledListsSchema, claimKeySchema, configSchema, describeSchema, entitySchema, metricSchema, navSchema, optionalRequiresSchema, readsConfigSchema, referenceDataSchema, relationSchema, requiresSchema, secretNamespaceSchema, secretSchema, systemScopeSchema, toggleableSchema, translationsSchema, uiHintsSchema, useExtensionSchema, workspaceSchema, } from "./static-schemas.js";
18
18
  export const PATTERN_LIBRARY = {
19
19
  requires: requiresSchema,
20
20
  optionalRequires: optionalRequiresSchema,
21
21
  readsConfig: readsConfigSchema,
22
22
  systemScope: systemScopeSchema,
23
+ adoptsBundledLists: adoptsBundledListsSchema,
23
24
  toggleable: toggleableSchema,
24
25
  describe: describeSchema,
25
26
  uiHints: uiHintsSchema,
@@ -3,6 +3,7 @@ export declare const requiresSchema: PatternFormSchema;
3
3
  export declare const optionalRequiresSchema: PatternFormSchema;
4
4
  export declare const readsConfigSchema: PatternFormSchema;
5
5
  export declare const systemScopeSchema: PatternFormSchema;
6
+ export declare const adoptsBundledListsSchema: PatternFormSchema;
6
7
  export declare const toggleableSchema: PatternFormSchema;
7
8
  export declare const describeSchema: PatternFormSchema;
8
9
  export declare const uiHintsSchema: PatternFormSchema;
@@ -59,6 +59,15 @@ export const systemScopeSchema = {
59
59
  singleton: true,
60
60
  fields: [],
61
61
  };
62
+ export const adoptsBundledListsSchema = {
63
+ kind: "adoptsBundledLists",
64
+ label: { en: "Adopts bundled lists", de: "Übernimmt Bundled-Listen" },
65
+ summary: { en: "Feature packages only: takes the app-wide bundledLists table block." },
66
+ category: "meta",
67
+ editability: "opaque",
68
+ singleton: true,
69
+ fields: [],
70
+ };
62
71
  export const toggleableSchema = {
63
72
  kind: "toggleable",
64
73
  label: { en: "Toggleable", de: "Umschaltbar" },
@@ -28,6 +28,8 @@ export function getAllowedFilterOps(field) {
28
28
  case "select":
29
29
  case "multiSelect":
30
30
  return EQUALITY_ONLY;
31
+ case "reference":
32
+ return field.multiple === true ? EQUALITY_ONLY : [];
31
33
  case "boolean":
32
34
  return BOOL_OPS;
33
35
  case "number":
@@ -13,6 +13,7 @@
13
13
  // pointing at a missing nav; that is pruned here.
14
14
  import { createFallbackLogger, scrubLogText } from "../logging/index.js";
15
15
  import { resolveScreenTargetQn } from "./boot-validator/screens.js";
16
+ import { workspaceOwnerOf } from "./build-app-schema.js";
16
17
  import { isToggleableFeature } from "./effective-features.js";
17
18
  import { isPureGroupingNav } from "./project-app-schema-for-roles.js";
18
19
  import { isValidQn, qualifyEntityName } from "./qualified-name.js";
@@ -141,10 +142,13 @@ export function stripDisabledFeaturesFromAppSchema(schema, registry, disabledFea
141
142
  const { navs: _navs, ...rest } = feature;
142
143
  return navs.length > 0 ? { ...rest, navs } : rest;
143
144
  });
144
- // Workspace definitions carry the short id the author declared; the owning
145
- // feature is only known to the registry.
145
+ // Workspace ids are unique per feature only, so the owner decides. A definition the build did
146
+ // not register (schema assembled elsewhere) falls back to the short id.
146
147
  const disabledWorkspaceIds = new Set(disabledFeatureNames.flatMap((featureName) => Object.keys(registry.features.get(featureName)?.workspaces ?? {})));
147
- const workspaces = schema.workspaces?.filter((ws) => !disabledWorkspaceIds.has(ws.definition.id));
148
+ const workspaces = schema.workspaces?.filter((ws) => {
149
+ const owner = workspaceOwnerOf(ws.definition);
150
+ return owner === undefined ? !disabledWorkspaceIds.has(ws.definition.id) : !disabled.has(owner);
151
+ });
148
152
  const { workspaces: _original, ...restSchema } = schema;
149
153
  return {
150
154
  ...restSchema,
@@ -1 +1,2 @@
1
1
  export type * from "@cosmicdrift/kumiko-types/feature";
2
+ export type * from "@cosmicdrift/kumiko-types/feature-registrar";
@@ -4,10 +4,11 @@ export type { QueryHandlerDefinition, StreamHandlerDefinition, WriteHandlerDefin
4
4
  export type { EntityCrudRegistrar, EntityCrudVerb, EntityHandlerOptions, EntityQueryHandlerOptions, EntityWriteHandlerOptions, RegisterEntityCrudOptions, } from "@cosmicdrift/kumiko-types/entity-handlers";
5
5
  export type { KumikoEntityTypeMap, KumikoEventTypeMap, KumikoHandlerPayloadMap, KumikoHandlerResultMap, } from "@cosmicdrift/kumiko-types/event-type-map";
6
6
  export type { ExtensionOptionsArgs, ExtensionOptionsFor, KumikoExtensionOptionsMap, } from "@cosmicdrift/kumiko-types/extension-options-map";
7
- export type { BootCheckContext, BootCheckFn, BootOptionName, EntityObjectDefinition, FeatureDefinition, FeatureMetricDef, FeatureMetricType, FeatureRegistrar, HookDefinition, MetricOptions, Registry, SecretKeyDefinition, SecretKeyHandle, SecretNamespaceDefinition, SecretNamespaceHandle, SecretNamespaceOptions, SecretOptions, StackBootRequirement, StoreTableDef, StoreTableEntry, StoreTableOptions, UiHintOption, UiHints, } from "@cosmicdrift/kumiko-types/feature";
7
+ export type { BootCheckContext, BootCheckFn, BootOptionName, FeatureDefinition, FeatureMetricDef, FeatureMetricType, MetricOptions, Registry, SecretKeyDefinition, SecretKeyHandle, SecretNamespaceDefinition, SecretNamespaceHandle, SecretNamespaceOptions, SecretOptions, StackBootRequirement, StoreTableDef, StoreTableEntry, StoreTableOptions, UiHintOption, UiHints, } from "@cosmicdrift/kumiko-types/feature";
8
+ export type { EntityObjectDefinition, FeatureRegistrar, HookDefinition, } from "@cosmicdrift/kumiko-types/feature-registrar";
8
9
  export type { FieldIconKey } from "@cosmicdrift/kumiko-types/field-icon";
9
10
  export type { AnyFileFieldDef, BigIntFieldDef, BooleanFieldDef, ClientDerivedFieldDef, DateFieldDef, DecimalFieldDef, DefaultCurrency, DeriveContext, DerivedFieldDef, DerivedFieldsMap, DerivedValueType, EmbeddedDerivedCellDef, EmbeddedFieldDef, EmbeddedSubFieldDef, EntityDefinition, EntityEventMigration, EntityFieldDefinition, EntityIndexDef, EntityLongTextFieldDef, EntityTextFieldDef, FieldAccess, FieldDefinition, FieldsMap, FileFieldDef, FilesFieldDef, Findability, ImageFieldDef, ImagesFieldDef, JsonbFieldDef, LocatedTimestampFieldDef, LongTextFieldDef, LongTextFindability, MoneyCurrencySource, MoneyFieldDef, MultiSelectFieldDef, NumberFieldDef, OptionsQueryPayload, OptionsQueryPayloadValue, ParentRefDef, PersonalAnnotations, PersonalAnnotationsLongText, PersonalAnnotationsNoFind, PersonalSubject, ReferenceFieldDef, ResolvedPiiFlags, ResolvedPiiStance, RetentionDef, SelectFieldDef, SelectOptionTone, TextFieldDef, TimestampFieldDef, TransitionMap, TzFieldDef, } from "@cosmicdrift/kumiko-types/fields";
10
- export type { AccessRule, ActiveMembership, ActiveMembershipRejection, ActiveMembershipResult, AgentExposure, AgentHandlerHints, AgentRisk, AggregateStreamHandle, AppContext, AppendEventArgs, AppendEventFn, AuthClaimsContext, AuthClaimsFn, AuthClaimsHookDef, BeforeTransactionPhase, BeforeTransactionResult, CamelToKebab, ChatSendFailureCode, ClaimKeyDefinition, ClaimKeyHandle, ClaimKeyJsType, ClaimKeyType, DeclarativeEventMigration, DeliveryErrorCode, DeliveryFailureCode, DeliverySkipReason, DispatchWriteRef, EntityRef, EscapeHatchAuditSink, EscapeHatchDeclaration, EscapeHatchKind, EscapeHatchReporter, EscapeHatchTarget, EscapeHatchUseEvent, EventDef, EventMigrationDef, EventPiiFields, EventPiiStance, EventUpcastCtx, EventUpcastFn, FetchForWritingArgs, HandlerContext, HandlerRef, JobContext, JobDeferral, JobRunnerRef, JobTriggerEvent, MemberReader, NameOrRef, NotifyDeferred, NotifyDelivery, NotifyDeliveryStatus, NotifyDispatched, NotifyFactory, NotifyFn, NotifyJobDispatcher, NotifyOptions, NotifyPriority, NotifyResult, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, PayloadBucketNormalization, PayloadRateLimitOption, PreparedWriteEvent, ProviderPluginContext, QualifiedEventName, QueryEvent, QueryHandlerDef, QueryHandlerFn, RateLimitDeclaration, RateLimitDisabled, RateLimitOption, RateLimitOptions, RateLimitPer, RoleAccessPersonalData, RoleAccessRule, SessionUser, SessionUserOrigin, StreamHandlerDef, StreamHandlerFn, TenantWriteTarget, TryAppendEventFn, TryAppendEventResult, UnsafeAppendEventFn, WriteEvent, WriteHandlerDef, WriteHandlerFn, WriteResult, } from "@cosmicdrift/kumiko-types/handlers";
11
+ export type { AccessRule, ActiveMembership, ActiveMembershipRejection, ActiveMembershipResult, AgentExposure, AgentHandlerHints, AgentRisk, AggregateStreamHandle, AppContext, AppendEventArgs, AppendEventFn, AuthClaimsContext, AuthClaimsFn, AuthClaimsHookDef, BeforeTransactionPhase, BeforeTransactionResult, CamelToKebab, ChatSendFailureCode, ClaimKeyDefinition, ClaimKeyHandle, ClaimKeyJsType, ClaimKeyType, DeclarativeEventMigration, DeliveryErrorCode, DeliveryFailureCode, DeliverySkipReason, DispatchWriteRef, EntityRef, EscapeHatchAuditSink, EscapeHatchDeclaration, EscapeHatchKind, EscapeHatchReporter, EscapeHatchTarget, EscapeHatchUseEvent, EventDef, EventMigrationDef, EventPiiFields, EventPiiStance, EventUpcastCtx, EventUpcastFn, FetchForWritingArgs, HandlerContext, HandlerRef, JobContext, JobDeferral, JobRunnerRef, JobTriggerEvent, MemberReader, NameOrRef, NotifyDeferred, NotifyDelivery, NotifyDeliveryStatus, NotifyDispatched, NotifyFactory, NotifyFn, NotifyJobDispatcher, NotifyOptions, NotifyPriority, NotifyResult, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, PayloadBucketNormalization, PayloadRateLimitOption, PreparedWriteEvent, QualifiedEventName, QueryEvent, QueryHandlerDef, QueryHandlerFn, RateLimitDeclaration, RateLimitDisabled, RateLimitOption, RateLimitOptions, RateLimitPer, RoleAccessPersonalData, RoleAccessRule, SessionUser, SessionUserOrigin, StreamHandlerDef, StreamHandlerFn, TenantWriteTarget, TryAppendEventFn, TryAppendEventResult, UnsafeAppendEventFn, WriteEvent, WriteHandlerDef, WriteHandlerFn, WriteResult, } from "@cosmicdrift/kumiko-types/handlers";
11
12
  export { DELIVERY_FAILURE_CODES, DELIVERY_SKIP_REASONS, declaredRateLimitOptions, HTTP_ERROR_CODE_PATTERN, isDeliveryErrorCode, isOpenToAllGranted, isRateLimitDisabled, isRateLimitOption, isUiAccessGranted, } from "@cosmicdrift/kumiko-types/handlers";
12
13
  export type { DeleteContext, EntityHookMap, HookMap, HookOwner, HookPhase, LifecycleHookFn, LifecycleResult, OwnedFn, PhasedHook, PostDeleteBatchHookFn, PostDeleteHookFn, PostQueryHookFn, PostSaveBatchHookFn, PostSaveHookFn, PreDeleteHookFn, PreQueryHookFn, PreSaveHookFn, SaveContext, SearchPayloadContributorFn, ValidationError, ValidationHookFn, } from "@cosmicdrift/kumiko-types/hooks";
13
14
  export type { HttpRouteDefinition, HttpRouteHandler, HttpRouteHandlerDeps, HttpRouteMethod, } from "@cosmicdrift/kumiko-types/http-route";
@@ -17,7 +18,7 @@ export type { ContentCollectionDefinition, NavDefinition, NavIconKey, } from "@c
17
18
  export type { FromRule, FromRuleKind, OwnershipClause, OwnershipMap, OwnershipRef, OwnershipRule, SqlFragment, WhereRule, WhereRuleContext, } from "@cosmicdrift/kumiko-types/ownership";
18
19
  export type { EntityProjectionExtension, MspErrorMode, MspErrorPolicy, MultiStreamApplyFn, MultiStreamProjectionDefinition, ProjectionDefinition, ProjectionRowIdOf, ProjectionTable, SingleStreamApplyFn, } from "@cosmicdrift/kumiko-types/projection";
19
20
  export type { BelongsToRelation, EntityRelations, HasManyRelation, ManyToManyRelation, RelationDefinition, } from "@cosmicdrift/kumiko-types/relations";
20
- export type { ActionFormScreenDefinition, ConfigEditScreenDefinition, CustomScreenDefinition, CustomScreenRoute, DashboardChartMarkerKind, DashboardChartPanel, DashboardChartRangeOption, DashboardChartRanges, DashboardCurrencyFormat, DashboardCustomPanel, DashboardDateParam, DashboardFeedPanel, DashboardFilterDefinition, DashboardI18nText, DashboardListPanel, DashboardMoneyParam, DashboardPanelDefinition, DashboardPanelGate, DashboardPanelVisibility, DashboardProgressListPanel, DashboardProgressTone, DashboardRecordBinding, DashboardRefresh, DashboardRelativeTimeFormat, DashboardRelativeTimeParam, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DashboardText, DashboardTextBlock, DashboardTextPanel, DashboardTextParam, DashboardValueFormat, EditExtensionSection, EditFieldPrefill, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EditWriteFormSection, EmbeddedListEditSpec, EntityEditAction, EntityEditActionPlacement, EntityEditScreenDefinition, EntityListExpandableRow, EntityListScreenDefinition, FieldCondition, FieldFormatRegistry, FieldRenderer, FormatSpec, FormFieldDerivation, FormWidth, ListColumnSpec, ListEmptyStateSpec, ListFacetSpec, ListPaginationMode, ListSortDir, ListSortSpec, MetricNavigate, MetricSpec, PlatformComponent, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, RecordHeaderSpec, RecordHeaderSubtitlePart, RowAction, RowActionNavigate, RowActionNavigateBase, RowActionWriteHandler, RowFieldExtractor, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenNavSugar, ScreenSlots, SecretMintConfirmStep, SecretMintScreenDefinition, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, ToolbarAction, UnitKey, } from "@cosmicdrift/kumiko-types/screen";
21
+ export type { ActionFormScreenDefinition, BundledListsOptions, ConfigEditScreenDefinition, CustomScreenDefinition, CustomScreenRoute, DashboardChartMarkerKind, DashboardChartPanel, DashboardChartRangeOption, DashboardChartRanges, DashboardCurrencyFormat, DashboardCustomPanel, DashboardDateParam, DashboardFeedPanel, DashboardFilterDefinition, DashboardI18nText, DashboardListPanel, DashboardMoneyParam, DashboardPanelDefinition, DashboardPanelGate, DashboardPanelVisibility, DashboardProgressListPanel, DashboardProgressTone, DashboardRecordBinding, DashboardRefresh, DashboardRelativeTimeFormat, DashboardRelativeTimeParam, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DashboardText, DashboardTextBlock, DashboardTextPanel, DashboardTextParam, DashboardValueFormat, EditExtensionSection, EditFieldPrefill, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EditWriteFormSection, EmbeddedListEditSpec, EntityEditAction, EntityEditActionPlacement, EntityEditScreenDefinition, EntityListExpandableRow, EntityListScreenDefinition, ExpandableRowGrouping, FieldCondition, FieldFormatRegistry, FieldRenderer, FormatSpec, FormFieldDerivation, FormWidth, ListColumnSpec, ListEmptyStateSpec, ListFacetSpec, ListPaginationMode, ListSortDir, ListSortSpec, ListTableOptions, MetricNavigate, MetricSpec, PlatformComponent, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, RecordHeaderSpec, RecordHeaderSubtitlePart, RowAction, RowActionNavigate, RowActionNavigateBase, RowActionWriteHandler, RowFieldExtractor, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenNavSugar, ScreenSlots, SecretMintConfirmStep, SecretMintScreenDefinition, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, ToolbarAction, UnitKey, } from "@cosmicdrift/kumiko-types/screen";
21
22
  export { relatedListGroupHeaderLabel, relatedListGroupKey, } from "@cosmicdrift/kumiko-types/screen";
22
23
  export type { TargetRef } from "@cosmicdrift/kumiko-types/target-ref";
23
24
  export type { Subscribe, TreeAction, TreeActionDef, TreeActionsHandle, TreeChildrenSubscribe, TreeNode, TreeNodeState, } from "@cosmicdrift/kumiko-types/tree-node";
@@ -1,2 +1,2 @@
1
1
  export type * from "@cosmicdrift/kumiko-types/screen";
2
- export { relatedListGroupHeaderLabel, relatedListGroupKey, } from "@cosmicdrift/kumiko-types/screen";
2
+ export { DEFAULT_EXPORT_MAX_ROWS, LIST_BULK_ACTION_KEYS, LIST_COLUMN_PINS, LIST_TABLE_DENSITIES, LIST_TABLE_DENSITY_KEYS, LIST_TABLE_EXPORT_KEYS, LIST_TABLE_GROUPING_KEYS, LIST_TABLE_OPTION_KEYS, LIST_TABLE_TOTAL_KEYS, MAX_BULK_SELECTION, MAX_EXPORT_MAX_ROWS, relatedListGroupHeaderLabel, relatedListGroupKey, } from "@cosmicdrift/kumiko-types/screen";
@@ -1 +1 @@
1
- export { relatedListGroupHeaderLabel, relatedListGroupKey, } from "@cosmicdrift/kumiko-types/screen";
1
+ export { DEFAULT_EXPORT_MAX_ROWS, LIST_BULK_ACTION_KEYS, LIST_COLUMN_PINS, LIST_TABLE_DENSITIES, LIST_TABLE_DENSITY_KEYS, LIST_TABLE_EXPORT_KEYS, LIST_TABLE_GROUPING_KEYS, LIST_TABLE_OPTION_KEYS, LIST_TABLE_TOTAL_KEYS, MAX_BULK_SELECTION, MAX_EXPORT_MAX_ROWS, relatedListGroupHeaderLabel, relatedListGroupKey, } from "@cosmicdrift/kumiko-types/screen";
@@ -63,6 +63,7 @@ export type ApiEntrypoint = {
63
63
  readonly lifecycle: Lifecycle;
64
64
  readonly observability: ObservabilityProvider;
65
65
  readonly dispatcher: Dispatcher;
66
+ readonly context: AppContext;
66
67
  readonly lifecycleHooks: LifecycleHooks;
67
68
  readonly eventDispatcher?: EventDispatcher;
68
69
  readonly mode: "api";
@@ -74,6 +75,7 @@ export type WorkerEntrypoint = {
74
75
  readonly eventDispatcher: EventDispatcher;
75
76
  readonly jobRunner: JobRunner;
76
77
  readonly observability: ObservabilityProvider;
78
+ readonly context: AppContext;
77
79
  readonly dispatcher: Dispatcher;
78
80
  readonly mode: "worker";
79
81
  start(): Promise<void>;
@@ -244,6 +244,7 @@ export function createApiEntrypoint(options) {
244
244
  lifecycle,
245
245
  observability: server.observability,
246
246
  dispatcher: server.dispatcher,
247
+ context: server.context,
247
248
  lifecycleHooks: server.lifecycleHooks,
248
249
  ...(server.eventDispatcher && { eventDispatcher: server.eventDispatcher }),
249
250
  mode: "api",
@@ -279,6 +280,7 @@ export function createWorkerEntrypoint(options) {
279
280
  jobRunner,
280
281
  observability: server.observability,
281
282
  dispatcher: server.dispatcher,
283
+ context: server.context,
282
284
  mode: "worker",
283
285
  async start() {
284
286
  await eventDispatcher.start();
@@ -324,6 +326,7 @@ export function createAllInOneEntrypoint(options) {
324
326
  jobRunner: workerJobRunner,
325
327
  observability: server.observability,
326
328
  dispatcher: server.dispatcher,
329
+ context: server.context,
327
330
  lifecycleHooks: server.lifecycleHooks,
328
331
  mode: "all-in-one",
329
332
  async start() {
@@ -19,6 +19,7 @@ export declare function append(db: DbRunner, event: EventToAppend): Promise<Stor
19
19
  export declare function loadAggregate(db: DbRunner, aggregateId: string, tenantId: TenantId, options?: {
20
20
  readonly includeArchived?: boolean;
21
21
  readonly aggregateType?: string;
22
+ readonly atVersion?: number;
22
23
  }): Promise<readonly StoredEvent[]>;
23
24
  export declare function loadAggregateForWriting(db: DbRunner, aggregateId: string, tenantId: TenantId, options: {
24
25
  readonly aggregateType: string;
@@ -213,9 +213,12 @@ export async function loadAggregate(db, aggregateId, tenantId, options) {
213
213
  if (archived)
214
214
  return [];
215
215
  }
216
- const rows = await selectMany(db, eventsTable, options?.aggregateType !== undefined
217
- ? { aggregateId, tenantId, aggregateType: options.aggregateType }
218
- : { aggregateId, tenantId }, { orderBy: { col: "version", direction: "asc" } });
216
+ const rows = await selectMany(db, eventsTable, {
217
+ aggregateId,
218
+ tenantId,
219
+ ...(options?.aggregateType !== undefined && { aggregateType: options.aggregateType }),
220
+ ...(options?.atVersion !== undefined && { version: options.atVersion }),
221
+ }, { orderBy: { col: "version", direction: "asc" } });
219
222
  return rows.map(toStoredEvent);
220
223
  }
221
224
  // Events and the stream's latest version from ONE read, so both come from the same
@@ -311,7 +314,7 @@ export async function loadEventsAfterVersion(db, aggregateId, tenantId, afterVer
311
314
  // abort iteration. Checked both at batch boundaries (before the next
312
315
  // fetch) AND between yields (so abort lands within one event regardless
313
316
  // of batch size). Throws AbortError on the first check after abort;
314
- // in-flight queries are not actively cancelled (postgres-js connection-
317
+ // in-flight queries are not actively cancelled (connection
315
318
  // cancel is a separate, riskier concern handled per-query at the TenantDb
316
319
  // boundary).
317
320
  export async function* streamAllEventsByType(db, aggregateType, batchSize = 1000, signal) {
@@ -32,7 +32,7 @@ export async function appendProvenanceEvent(db, event) {
32
32
  message: `appendProvenanceEvent: event tenant "${event.tenantId}" does not match the TenantDb's tenant "${db.tenantId}".`,
33
33
  });
34
34
  }
35
- // postgres-js aborts the whole surrounding begin() on a statement
35
+ // Postgres aborts the whole surrounding transaction on a statement
36
36
  // error (25P02) even when the caller's catch swallows it — the savepoint
37
37
  // confines that to this scope. Pool connections have no ambient tx to poison.
38
38
  await runInSavepointIfSupported(runner, async (sp) => {
@@ -46,6 +46,9 @@ export async function createFileRefFromBytes(ctx, input) {
46
46
  return fileRejectedFailure(outcome.error);
47
47
  case "failed":
48
48
  return outcome.failure;
49
+ case "target-locked":
50
+ // Only the HTTP route supplies the lock re-check, so this path cannot produce it.
51
+ return writeFailure(new InternalError({ message: "createFileRefFromBytes: unexpected target-locked outcome" }));
49
52
  }
50
53
  }
51
54
  function fileRejectedFailure(error) {
@@ -50,6 +50,7 @@ export type StoreFileDeps = {
50
50
  readonly defaultMaxUploadSize: string;
51
51
  readonly writeBytes: (storageKey: string, data: Uint8Array, mimeType: string) => Promise<void>;
52
52
  readonly deleteBytes: (storageKey: string) => Promise<void>;
53
+ readonly isAttachTargetLocked?: (rowDb: TenantDb) => Promise<boolean>;
53
54
  };
54
55
  export type CreatedFileRef = {
55
56
  readonly id: string;
@@ -80,6 +81,8 @@ export type StoreFileOutcome = {
80
81
  } | {
81
82
  readonly kind: "rejected";
82
83
  readonly error: string;
84
+ } | {
85
+ readonly kind: "target-locked";
83
86
  } | {
84
87
  readonly kind: "failed";
85
88
  readonly failure: WriteFailure;
@@ -41,6 +41,8 @@ export function resolveAttachedField(registry, entityType, fieldName) {
41
41
  return { kind: "unresolvable" };
42
42
  return { kind: "resolved", fieldDef };
43
43
  }
44
+ class AttachTargetLockedError extends Error {
45
+ }
44
46
  function resolveUploadField(registry, input) {
45
47
  const { entityType, entityId, fieldName, actionForm } = input;
46
48
  if (actionForm === undefined)
@@ -196,14 +198,21 @@ export async function storeFileAsFileRef(deps, input) {
196
198
  // skip: the create failure is the error worth reporting; the leftover bytes stay in storage.
197
199
  }
198
200
  };
201
+ const createRow = async (rowDb) => {
202
+ if (await deps.isAttachTargetLocked?.(rowDb))
203
+ throw new AttachTargetLockedError();
204
+ return createFileRefRow({ user: deps.user, tenantDb: rowDb }, upload.uploaded);
205
+ };
199
206
  let row;
200
207
  try {
201
208
  row = deps.rowInOwnTransaction
202
- ? await runInOwnTransaction(deps.tenantDb, (txDb) => createFileRefRow({ user: deps.user, tenantDb: txDb }, upload.uploaded))
203
- : await createFileRefRow(deps, upload.uploaded);
209
+ ? await runInOwnTransaction(deps.tenantDb, createRow)
210
+ : await createRow(deps.tenantDb);
204
211
  }
205
212
  catch (e) {
206
213
  await deleteUploadedBytes();
214
+ if (e instanceof AttachTargetLockedError)
215
+ return { kind: "target-locked" };
207
216
  throw e;
208
217
  }
209
218
  if ("failure" in row) {
@@ -1,3 +1,4 @@
1
+ import type { RateLimitResolver } from "@cosmicdrift/kumiko-types/rate-limit-types";
1
2
  import type { WriteErrorInfo } from "@cosmicdrift/kumiko-types/write-error-info-types";
2
3
  import { Hono } from "hono";
3
4
  import type { DbConnection } from "../db/connection.ts";
@@ -30,6 +31,7 @@ export type FileAccessGuard = (args: {
30
31
  }) => FileAccessDecision | Promise<FileAccessDecision>;
31
32
  export type FileRoutesOptions = {
32
33
  readonly db: DbConnection;
34
+ readonly metaBatchRateLimitResolver?: RateLimitResolver;
33
35
  readonly resolveProvider: FileProviderResolver;
34
36
  readonly registry?: Registry;
35
37
  readonly maxUploadSize?: string;
@@ -9,13 +9,14 @@ import { createProjectingTenantDb } from "../db/tenant-db.js";
9
9
  import { createDerivativesContext, resolveFieldVariant, resolveRenderer, } from "../derivatives/index.js";
10
10
  import { isAnonymousSessionUser, SYSTEM_USER_ID } from "../engine/system-user.js";
11
11
  import { isFileField, isUuid, } from "../engine/types/index.js";
12
+ import { createFailOpenReporter, respondRateLimited } from "../rate-limit/middleware.js";
12
13
  import { listActionFormFileFields, mayUseActionForm } from "./action-form-upload.js";
13
14
  import { buildContentDispositionHeader } from "./content-disposition.js";
14
15
  import { DEFAULT_MAX_UPLOAD_SIZE, getFileRefExecutor, storeFileAsFileRef, } from "./create-file-ref.js";
15
16
  import { createFileContext, createTenantFileContext } from "./file-handle.js";
16
17
  import { fileRefEntity } from "./file-ref-entity.js";
17
18
  import { fileRefsTable } from "./file-ref-table.js";
18
- import { RecordWriteDecisions } from "./record-write-check.js";
19
+ import { isStoredRecordLocked, RecordWriteDecisions, } from "./record-write-check.js";
19
20
  import { neutralFileNameForMimeType } from "./resolve-content-type.js";
20
21
  import { parseMaxSize, resolveServedContentType } from "./types.js";
21
22
  import { withUploadLedger } from "./upload-ledger.js";
@@ -31,6 +32,8 @@ const DEFAULT_PRIVILEGED_ROLES = ["Admin", "SystemAdmin"];
31
32
  // credential (see "Signed-URL default expiry" in core-files.md).
32
33
  const SIGNED_URL_DEFAULT_EXPIRY_SECONDS = 15 * 60;
33
34
  const MAX_BATCH_META_IDS = 100;
35
+ const META_BATCH_IDS_PER_WINDOW = 1_000;
36
+ const META_BATCH_WINDOW_SECONDS = 60;
34
37
  // Each detail() read decrypts through the KMS pool; a bounded chunk keeps one batch from hogging it.
35
38
  const BATCH_META_CONCURRENCY = 10;
36
39
  // Default guard: on attached files, allow the uploader or a privileged role,
@@ -101,7 +104,8 @@ export function readFilesRouteOptions(registry) {
101
104
  return {};
102
105
  }
103
106
  export function createFileRoutes(options) {
104
- const { db } = options;
107
+ const { db, metaBatchRateLimitResolver } = options;
108
+ const reportMetaBatchLimiterFailOpen = createFailOpenReporter("files-meta-batch");
105
109
  // Plain resolvers (not built via withFileProviderResolver) get the ledger on the route's own root connection.
106
110
  const resolveProvider = options.resolveProvider.uploadLedgerFor
107
111
  ? options.resolveProvider
@@ -205,6 +209,10 @@ export function createFileRoutes(options) {
205
209
  }
206
210
  const data = new Uint8Array(await file.arrayBuffer());
207
211
  const files = createTenantFileContext(resolveProvider, user.tenantId);
212
+ const { registry } = options;
213
+ const isAttachTargetLocked = registry !== undefined && entityType !== undefined && entityId !== undefined
214
+ ? (rowDb) => isStoredRecordLocked({ registry, db: rowDb, entityType, entityId })
215
+ : undefined;
208
216
  const outcome = await storeFileAsFileRef({
209
217
  user,
210
218
  tenantDb: tenantDbFor(user.tenantId),
@@ -216,6 +224,7 @@ export function createFileRoutes(options) {
216
224
  await files.ref(key).delete();
217
225
  await files.forgetUploadBytes(key);
218
226
  },
227
+ ...(isAttachTargetLocked ? { isAttachTargetLocked } : {}),
219
228
  }, {
220
229
  fileName: file.name,
221
230
  declaredMimeType: file.type,
@@ -227,6 +236,9 @@ export function createFileRoutes(options) {
227
236
  });
228
237
  if (outcome.kind === "rejected")
229
238
  return c.json({ error: outcome.error }, 400);
239
+ if (outcome.kind === "target-locked") {
240
+ return c.json({ error: "record_locked: the record is locked" }, 409);
241
+ }
230
242
  if (outcome.kind === "failed")
231
243
  return c.json({ error: "upload_failed" }, 500);
232
244
  return c.json(outcome.fileRef, 201);
@@ -240,6 +252,21 @@ export function createFileRoutes(options) {
240
252
  const ids = [...new Set((c.req.query("ids") ?? "").split(",").filter((id) => isUuid(id)))];
241
253
  if (ids.length > MAX_BATCH_META_IDS)
242
254
  return c.json({ error: "too_many_ids" }, 400);
255
+ if (metaBatchRateLimitResolver && ids.length > 0) {
256
+ const bucket = `files-meta:${user.tenantId}:${user.id}`;
257
+ try {
258
+ const decision = await metaBatchRateLimitResolver.check(bucket, {
259
+ limit: META_BATCH_IDS_PER_WINDOW,
260
+ windowSeconds: META_BATCH_WINDOW_SECONDS,
261
+ cost: ids.length,
262
+ });
263
+ if (!decision.allowed)
264
+ return respondRateLimited(c, decision, bucket);
265
+ }
266
+ catch (e) {
267
+ reportMetaBatchLimiterFailOpen(e);
268
+ }
269
+ }
243
270
  const files = [];
244
271
  for (let start = 0; start < ids.length; start += BATCH_META_CONCURRENCY) {
245
272
  const chunk = ids.slice(start, start + BATCH_META_CONCURRENCY);
@@ -56,5 +56,6 @@ export type FileProviderResolverDeps = {
56
56
  readonly secrets?: SecretsContext;
57
57
  readonly db?: DbConnection | TenantDb;
58
58
  readonly cacheSync?: CacheSyncBus;
59
+ readonly now?: () => Temporal.Instant;
59
60
  };
60
61
  export declare function makeFileProviderResolver(deps: FileProviderResolverDeps): FileProviderResolver;
@@ -119,5 +119,5 @@ export function makeFileProviderResolver(deps) {
119
119
  cache.set(tenantId, built);
120
120
  return built;
121
121
  };
122
- return deps.db ? withUploadLedger(resolveProvider, deps.db) : resolveProvider;
122
+ return deps.db ? withUploadLedger(resolveProvider, deps.db, deps.now) : resolveProvider;
123
123
  }
@@ -20,3 +20,9 @@ export declare function createRecordWriteCheck(args: {
20
20
  readonly canReadRecord: RecordReadCheck;
21
21
  readonly tenantDbFor: (tenantId: TenantId) => TenantDb;
22
22
  }): RecordWriteCheck;
23
+ export declare function isStoredRecordLocked(args: {
24
+ readonly registry: Registry;
25
+ readonly db: TenantDb;
26
+ readonly entityType: string;
27
+ readonly entityId: string;
28
+ }): Promise<boolean>;
@@ -62,3 +62,16 @@ export function createRecordWriteCheck(args) {
62
62
  return RecordWriteDecisions.writable;
63
63
  };
64
64
  }
65
+ // The stored row, read through the given db so a transaction sees the lock state it commits against.
66
+ export async function isStoredRecordLocked(args) {
67
+ const { registry, db, entityType, entityId } = args;
68
+ const entity = registry.getEntity(entityType);
69
+ const table = findImplicitEntityTable(registry, entityType);
70
+ if (!entity || !table || entity.lockedWhen === undefined)
71
+ return false;
72
+ const row = await db.fetchOne(table, {
73
+ id: entityId,
74
+ ...(entity.softDelete === true && { isDeleted: false }),
75
+ });
76
+ return row !== undefined && isRecordLocked(entity.lockedWhen, row);
77
+ }
@@ -5,6 +5,6 @@ import type { TenantId } from "@cosmicdrift/kumiko-types/identifiers";
5
5
  import type { DbConnection } from "../db/connection.ts";
6
6
  import { type TenantDb } from "../db/tenant-db.ts";
7
7
  export declare const fileUploadLedgerTable: WritableTable;
8
- export declare function createFileUploadLedger(db: DbConnection | TenantDb, tenantId: TenantId): FileUploadLedger;
9
- export declare function withUploadLedger(resolver: (tenantId: TenantId) => Promise<FileStorageProvider>, db: DbConnection | TenantDb): FileProviderResolver;
8
+ export declare function createFileUploadLedger(db: DbConnection | TenantDb, tenantId: TenantId, now?: () => Temporal.Instant): FileUploadLedger;
9
+ export declare function withUploadLedger(resolver: (tenantId: TenantId) => Promise<FileStorageProvider>, db: DbConnection | TenantDb, now?: () => Temporal.Instant): FileProviderResolver;
10
10
  export declare function takeUploadLedgerEntry(txDb: TenantDb, storageKey: string): Promise<boolean>;
@@ -21,7 +21,7 @@ function isTenantDb(db) {
21
21
  }
22
22
  // The ledger writes on a connection of its own, never on a handler transaction: a rollback of the
23
23
  // handler must not take the row with it, or the bytes it uploaded would be unfindable.
24
- export function createFileUploadLedger(db, tenantId) {
24
+ export function createFileUploadLedger(db, tenantId, now = () => Temporal.Now.instant()) {
25
25
  const runner = isTenantDb(db) ? tenantDbRunner(db) : db;
26
26
  if (!runnerCanBeginTransaction(runner)) {
27
27
  throw new InternalError({
@@ -34,7 +34,7 @@ export function createFileUploadLedger(db, tenantId) {
34
34
  await ledgerDb.insertOne(fileUploadLedgerTable, {
35
35
  storageKey,
36
36
  tenantId,
37
- createdAt: Temporal.Now.instant(),
37
+ createdAt: now(),
38
38
  });
39
39
  },
40
40
  forget: async (storageKey) => {
@@ -42,9 +42,9 @@ export function createFileUploadLedger(db, tenantId) {
42
42
  },
43
43
  };
44
44
  }
45
- export function withUploadLedger(resolver, db) {
45
+ export function withUploadLedger(resolver, db, now) {
46
46
  return Object.assign(resolver, {
47
- uploadLedgerFor: (tenantId) => createFileUploadLedger(db, tenantId),
47
+ uploadLedgerFor: (tenantId) => createFileUploadLedger(db, tenantId, now),
48
48
  });
49
49
  }
50
50
  // Runs inside the transaction that creates the fileRef row. False means no ledger row was left to