@cosmicdrift/kumiko-framework 0.363.0 → 0.365.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 (281) hide show
  1. package/AGENTS.md +338 -0
  2. package/agents/examples/access-control.md +355 -0
  3. package/agents/examples/anonymous-access-multitenant.md +446 -0
  4. package/agents/examples/anonymous-access.md +288 -0
  5. package/agents/examples/apex-landing.md +528 -0
  6. package/agents/examples/apex-surface-auth.md +355 -0
  7. package/agents/examples/api-worker-split.md +434 -0
  8. package/agents/examples/auth-claims.md +351 -0
  9. package/agents/examples/auth-foundation-providers.md +155 -0
  10. package/agents/examples/auth-mfa.md +319 -0
  11. package/agents/examples/basic-entity.md +420 -0
  12. package/agents/examples/boot-check.md +122 -0
  13. package/agents/examples/chat-channels.md +254 -0
  14. package/agents/examples/compliance-profiles-demo.md +307 -0
  15. package/agents/examples/cross-feature-events.md +317 -0
  16. package/agents/examples/crypto-shredding-hr.md +314 -0
  17. package/agents/examples/currencies-global.md +609 -0
  18. package/agents/examples/currencies-per-tenant.md +453 -0
  19. package/agents/examples/custom-fields-basic.md +315 -0
  20. package/agents/examples/custom-handlers.md +444 -0
  21. package/agents/examples/dashboard-stats.md +229 -0
  22. package/agents/examples/delivery-notifications.md +432 -0
  23. package/agents/examples/designer-demo.md +86 -0
  24. package/agents/examples/e2e-generator.md +211 -0
  25. package/agents/examples/embedded-entity-form.md +598 -0
  26. package/agents/examples/embedded.md +649 -0
  27. package/agents/examples/encrypted-tenant-config.md +414 -0
  28. package/agents/examples/error-contract.md +535 -0
  29. package/agents/examples/event-sourcing.md +1287 -0
  30. package/agents/examples/feature-toggles.md +274 -0
  31. package/agents/examples/field-access.md +271 -0
  32. package/agents/examples/files-post-processing.md +433 -0
  33. package/agents/examples/i18n.md +166 -0
  34. package/agents/examples/idempotency.md +179 -0
  35. package/agents/examples/inbound-mail-triage.md +315 -0
  36. package/agents/examples/lane-routing.md +246 -0
  37. package/agents/examples/ledger-banking.md +215 -0
  38. package/agents/examples/ledger-invoicing.md +236 -0
  39. package/agents/examples/legal-pages.md +393 -0
  40. package/agents/examples/lifecycle-hooks.md +368 -0
  41. package/agents/examples/managed-config.md +428 -0
  42. package/agents/examples/managed-pages.md +353 -0
  43. package/agents/examples/notes-history-basic.md +346 -0
  44. package/agents/examples/ownership.md +404 -0
  45. package/agents/examples/pdf-audit-export.md +306 -0
  46. package/agents/examples/pipeline-basics.md +792 -0
  47. package/agents/examples/public-share-token.md +351 -0
  48. package/agents/examples/rate-limiting.md +211 -0
  49. package/agents/examples/realtime-sse.md +187 -0
  50. package/agents/examples/record-detail-layout.md +297 -0
  51. package/agents/examples/reference-data.md +151 -0
  52. package/agents/examples/relations.md +280 -0
  53. package/agents/examples/screens-nav.md +353 -0
  54. package/agents/examples/search.md +233 -0
  55. package/agents/examples/secrets-demo.md +417 -0
  56. package/agents/examples/security-baseline.md +187 -0
  57. package/agents/examples/seed-migration.md +99 -0
  58. package/agents/examples/session-revocation.md +250 -0
  59. package/agents/examples/state-machine.md +920 -0
  60. package/agents/examples/tags-basic.md +401 -0
  61. package/agents/examples/tenant-isolation.md +173 -0
  62. package/agents/examples/tenant-settings.md +275 -0
  63. package/agents/examples/tier-admin.md +347 -0
  64. package/agents/examples/timezones.md +366 -0
  65. package/agents/examples/user-data-rights.md +418 -0
  66. package/agents/examples/user-profile.md +226 -0
  67. package/agents/examples/webhook-step.md +597 -0
  68. package/agents/examples/wizard-form.md +379 -0
  69. package/agents/examples/workflow-engine.md +1731 -0
  70. package/agents/examples/writeform-section.md +300 -0
  71. package/agents/examples.md +75 -0
  72. package/agents/features.md +768 -0
  73. package/agents/widgets.md +181 -0
  74. package/dist/api/auth-middleware.d.ts +2 -0
  75. package/dist/api/auth-middleware.js +7 -0
  76. package/dist/api/auth-routes.d.ts +0 -1
  77. package/dist/api/auth-routes.js +49 -46
  78. package/dist/api/extra-route.d.ts +3 -0
  79. package/dist/api/index.d.ts +2 -2
  80. package/dist/api/index.js +1 -1
  81. package/dist/api/routes.js +46 -17
  82. package/dist/api/server.d.ts +4 -3
  83. package/dist/api/server.js +39 -10
  84. package/dist/api/session-cookies.d.ts +1 -0
  85. package/dist/api/session-cookies.js +20 -5
  86. package/dist/api/sse-route.js +16 -1
  87. package/dist/bun-db/connection.js +3 -16
  88. package/dist/bun-db/index.d.ts +1 -1
  89. package/dist/bun-db/index.js +1 -1
  90. package/dist/bun-db/query.d.ts +15 -3
  91. package/dist/bun-db/query.js +76 -24
  92. package/dist/crypto/owner-transition-rekey.d.ts +1 -0
  93. package/dist/crypto/owner-transition-rekey.js +7 -3
  94. package/dist/crypto/pii-field-encryption.d.ts +1 -0
  95. package/dist/crypto/pii-field-encryption.js +9 -2
  96. package/dist/crypto/subject-resolver.d.ts +1 -0
  97. package/dist/crypto/subject-resolver.js +7 -0
  98. package/dist/db/api.d.ts +4 -3
  99. package/dist/db/apply-entity-event.js +26 -2
  100. package/dist/db/blind-index-cleanup.js +2 -1
  101. package/dist/db/bun-provider.js +9 -18
  102. package/dist/db/collect-table-metas.js +11 -7
  103. package/dist/db/dialect.d.ts +1 -1
  104. package/dist/db/dialect.js +2 -0
  105. package/dist/db/eagerload.js +4 -3
  106. package/dist/db/event-store-executor-context.d.ts +6 -6
  107. package/dist/db/event-store-executor-context.js +25 -22
  108. package/dist/db/event-store-executor-read.js +31 -16
  109. package/dist/db/event-store-executor-write.d.ts +1 -1
  110. package/dist/db/event-store-executor-write.js +84 -63
  111. package/dist/db/index.d.ts +2 -1
  112. package/dist/db/index.js +2 -1
  113. package/dist/db/lock-guard.d.ts +4 -0
  114. package/dist/db/lock-guard.js +14 -0
  115. package/dist/db/migrate-generator.js +1 -1
  116. package/dist/db/migrate-runner.d.ts +1 -0
  117. package/dist/db/migrate-runner.js +64 -38
  118. package/dist/db/queries/backfill-pii.js +63 -25
  119. package/dist/db/queries/event-consumer.d.ts +6 -6
  120. package/dist/db/queries/event-consumer.js +2 -1
  121. package/dist/db/queries/event-store-transfer.js +15 -4
  122. package/dist/db/queries/event-store.d.ts +1 -1
  123. package/dist/db/queries/event-store.js +11 -10
  124. package/dist/db/queries/pii-backfill-state.js +3 -2
  125. package/dist/db/queries/postgres-array-literal.d.ts +2 -0
  126. package/dist/db/queries/postgres-array-literal.js +6 -0
  127. package/dist/db/queries/projection-rebuild.d.ts +1 -1
  128. package/dist/db/queries/projection-rebuild.js +3 -2
  129. package/dist/db/queries/shadow-swap.d.ts +1 -1
  130. package/dist/db/queries/shadow-swap.js +12 -8
  131. package/dist/db/query.d.ts +1 -1
  132. package/dist/db/query.js +1 -1
  133. package/dist/db/retired-framework-tables.js +22 -4
  134. package/dist/db/table-builder.js +6 -2
  135. package/dist/db/tenant-db-runner.d.ts +4 -0
  136. package/dist/db/tenant-db-runner.js +17 -0
  137. package/dist/db/tenant-db.d.ts +4 -0
  138. package/dist/db/tenant-db.js +50 -5
  139. package/dist/derivatives/delete-stored-file.d.ts +4 -0
  140. package/dist/derivatives/delete-stored-file.js +9 -0
  141. package/dist/derivatives/derivatives-context.js +1 -1
  142. package/dist/derivatives/index.d.ts +1 -1
  143. package/dist/derivatives/index.js +1 -1
  144. package/dist/engine/boot-validator/access-declarations.js +2 -2
  145. package/dist/engine/boot-validator/api-ext.js +3 -3
  146. package/dist/engine/boot-validator/boot-check.js +1 -1
  147. package/dist/engine/boot-validator/config-deps.js +13 -13
  148. package/dist/engine/boot-validator/detail-screens.js +1 -1
  149. package/dist/engine/boot-validator/entity-handler.d.ts +1 -0
  150. package/dist/engine/boot-validator/entity-handler.js +87 -35
  151. package/dist/engine/boot-validator/entity-list-screens.js +2 -2
  152. package/dist/engine/boot-validator/handler-field-references.js +19 -9
  153. package/dist/engine/boot-validator/i18n-keys.js +2 -2
  154. package/dist/engine/boot-validator/nav.js +1 -1
  155. package/dist/engine/boot-validator/pii-retention.js +8 -8
  156. package/dist/engine/boot-validator/projection-list-screens.js +5 -5
  157. package/dist/engine/boot-validator/query-output-columns.js +7 -7
  158. package/dist/engine/boot-validator/query-refs.js +14 -2
  159. package/dist/engine/boot-validator/screens.d.ts +2 -1
  160. package/dist/engine/boot-validator/screens.js +153 -95
  161. package/dist/engine/boot-validator/url-prefill-fields.js +54 -35
  162. package/dist/engine/dedupe-features.js +31 -4
  163. package/dist/engine/define-handler.d.ts +2 -2
  164. package/dist/engine/define-handler.js +8 -1
  165. package/dist/engine/entity-handlers.js +14 -7
  166. package/dist/engine/feature-ast/extractors/handlers.js +2 -0
  167. package/dist/engine/feature-ast/pattern-change-schema.d.ts +2 -0
  168. package/dist/engine/feature-ast/pattern-change-schema.js +1 -0
  169. package/dist/engine/feature-ast/render.js +10 -2
  170. package/dist/engine/feature-entity-handlers.d.ts +2 -2
  171. package/dist/engine/feature-entity-handlers.js +12 -3
  172. package/dist/engine/handler-field-references.d.ts +21 -2
  173. package/dist/engine/handler-field-references.js +55 -17
  174. package/dist/engine/index.d.ts +3 -3
  175. package/dist/engine/index.js +1 -1
  176. package/dist/engine/schema-builder.js +2 -1
  177. package/dist/engine/screen-helpers.d.ts +1 -0
  178. package/dist/engine/screen-helpers.js +2 -2
  179. package/dist/engine/soft-delete-cleanup.js +10 -3
  180. package/dist/engine/steps/_read-source.js +5 -2
  181. package/dist/engine/steps/retry.js +2 -1
  182. package/dist/engine/steps/unsafe-projection-delete.js +2 -2
  183. package/dist/engine/steps/unsafe-projection-upsert.js +2 -2
  184. package/dist/engine/template-variables.d.ts +3 -0
  185. package/dist/engine/template-variables.js +3 -0
  186. package/dist/engine/tier-resolver-extension.d.ts +1 -0
  187. package/dist/engine/tier-resolver-extension.js +6 -0
  188. package/dist/engine/types/index.d.ts +1 -1
  189. package/dist/entrypoint/index.d.ts +2 -2
  190. package/dist/env/_zod-introspect.d.ts +1 -0
  191. package/dist/errors/classes.js +1 -0
  192. package/dist/errors/docs-url.js +6 -6
  193. package/dist/errors/index.d.ts +1 -1
  194. package/dist/errors/index.js +1 -1
  195. package/dist/errors/kumiko-error.d.ts +2 -0
  196. package/dist/errors/kumiko-error.js +2 -0
  197. package/dist/errors/subject-erased.d.ts +1 -0
  198. package/dist/errors/subject-erased.js +6 -0
  199. package/dist/errors/write-error-info.js +2 -0
  200. package/dist/event-store/admin-api.js +3 -2
  201. package/dist/event-store/errors.d.ts +9 -0
  202. package/dist/event-store/errors.js +20 -0
  203. package/dist/event-store/event-store.d.ts +6 -0
  204. package/dist/event-store/event-store.js +28 -6
  205. package/dist/event-store/index.d.ts +1 -1
  206. package/dist/event-store/index.js +1 -1
  207. package/dist/event-store/tenant-db-append.js +2 -2
  208. package/dist/files/assert-file-refs-belong-to-tenant.d.ts +4 -5
  209. package/dist/files/assert-file-refs-belong-to-tenant.js +7 -15
  210. package/dist/files/bind-pending-file-refs.d.ts +2 -0
  211. package/dist/files/bind-pending-file-refs.js +3 -3
  212. package/dist/files/bind-referenced-files.write.d.ts +1 -1
  213. package/dist/files/bind-referenced-files.write.js +22 -13
  214. package/dist/files/create-file-ref-from-bytes.d.ts +6 -3
  215. package/dist/files/create-file-ref-from-bytes.js +67 -4
  216. package/dist/files/create-file-ref.d.ts +24 -1
  217. package/dist/files/create-file-ref.js +67 -25
  218. package/dist/files/dispose-removed-file-refs.js +21 -6
  219. package/dist/files/feature.d.ts +1 -0
  220. package/dist/files/feature.js +2 -1
  221. package/dist/files/file-handle.d.ts +1 -1
  222. package/dist/files/file-handle.js +12 -0
  223. package/dist/files/file-routes.js +10 -6
  224. package/dist/files/index.d.ts +3 -3
  225. package/dist/files/index.js +2 -2
  226. package/dist/files/load-save-file-refs.d.ts +10 -0
  227. package/dist/files/load-save-file-refs.js +47 -0
  228. package/dist/files/purge-expired-file-ref-bytes.d.ts +4 -1
  229. package/dist/files/purge-expired-file-ref-bytes.js +46 -21
  230. package/dist/files/resolve-content-type.js +40 -4
  231. package/dist/files/storage-tracking.js +5 -4
  232. package/dist/files/types.js +5 -8
  233. package/dist/files/zip-entry-names.d.ts +1 -0
  234. package/dist/files/zip-entry-names.js +46 -0
  235. package/dist/jobs/index.d.ts +1 -1
  236. package/dist/jobs/job-runner.d.ts +5 -1
  237. package/dist/jobs/job-runner.js +124 -37
  238. package/dist/observability/escape-hatch-report.d.ts +2 -1
  239. package/dist/observability/escape-hatch-report.js +48 -7
  240. package/dist/observability/index.d.ts +1 -1
  241. package/dist/observability/index.js +1 -1
  242. package/dist/observability/metrics-handle.d.ts +1 -0
  243. package/dist/observability/metrics-handle.js +7 -0
  244. package/dist/observability/standard-metrics.js +4 -4
  245. package/dist/pipeline/cascade-handler.js +5 -2
  246. package/dist/pipeline/dispatch-batch.js +79 -31
  247. package/dist/pipeline/dispatch-query.d.ts +2 -1
  248. package/dist/pipeline/dispatch-query.js +6 -6
  249. package/dist/pipeline/dispatch-shared.d.ts +7 -2
  250. package/dist/pipeline/dispatch-shared.js +136 -48
  251. package/dist/pipeline/dispatch-stream.js +10 -4
  252. package/dist/pipeline/dispatch-write.js +34 -22
  253. package/dist/pipeline/dispatcher.js +3 -1
  254. package/dist/pipeline/event-dispatcher.d.ts +2 -2
  255. package/dist/pipeline/event-dispatcher.js +11 -9
  256. package/dist/pipeline/event-retention.d.ts +1 -0
  257. package/dist/pipeline/event-retention.js +7 -1
  258. package/dist/pipeline/index.d.ts +1 -0
  259. package/dist/pipeline/index.js +1 -0
  260. package/dist/pipeline/notify-scope.js +3 -1
  261. package/dist/pipeline/pre-transaction-reservations.d.ts +3 -1
  262. package/dist/pipeline/pre-transaction-reservations.js +29 -9
  263. package/dist/rate-limit/bucket.d.ts +3 -3
  264. package/dist/rate-limit/bucket.js +14 -4
  265. package/dist/redis/cache-sync-bus.js +9 -3
  266. package/dist/schema-cli.js +12 -1
  267. package/dist/search/meilisearch-adapter.js +36 -9
  268. package/dist/stack/db.d.ts +2 -2
  269. package/dist/stack/test-stack.js +2 -1
  270. package/dist/testing/index.d.ts +1 -0
  271. package/dist/testing/index.js +1 -0
  272. package/dist/testing/multipart-helper.d.ts +6 -0
  273. package/dist/testing/multipart-helper.js +1 -1
  274. package/dist/testing/zip-fixture.d.ts +1 -0
  275. package/dist/testing/zip-fixture.js +33 -0
  276. package/dist/ui-types/card-column-roles.js +4 -2
  277. package/dist/ui-types/index.d.ts +1 -1
  278. package/dist/ui-types/index.js +1 -1
  279. package/dist/upgrade-cli.js +46 -13
  280. package/package.json +7 -5
  281. package/src/changes.json +512 -8
@@ -23,8 +23,10 @@ export function createDeferringNotify(deps) {
23
23
  assertNotificationVariables(registry, notificationType, options.data);
24
24
  if (options.immediate === true)
25
25
  return notifyNow(notificationType, options);
26
+ // The hook runs after the handler returned; a caller reusing `data` meanwhile must not change what is sent.
27
+ const optionsAtCallTime = structuredClone(options);
26
28
  scheduleAfterCommit(async () => {
27
- await notifyAfterCommit(notificationType, options);
29
+ await notifyAfterCommit(notificationType, optionsAtCallTime);
28
30
  });
29
31
  return { kind: "deferred" };
30
32
  }
@@ -1,12 +1,14 @@
1
1
  import type { ReservationHandle } from "@cosmicdrift/kumiko-types/handlers";
2
- import type { SessionUser } from "../engine/types/index.js";
2
+ import type { SessionUser, WriteHandlerDef } from "../engine/types/index.js";
3
3
  import { type WriteErrorInfo } from "../errors/index.js";
4
4
  import type { BatchCommand, DispatchContext } from "./dispatch-shared.js";
5
5
  import type { WriteOrigin } from "./write-origin.js";
6
6
  type ReservationConfirm = ReservationHandle["confirmInTransaction"];
7
7
  export type ConsumedReservation = {
8
8
  readonly confirmInTransaction: ReservationConfirm | undefined;
9
+ readonly prepared: unknown;
9
10
  };
11
+ export declare function hasPreTransactionPhase(handler: WriteHandlerDef): boolean;
10
12
  export declare function runAsCommand<T>(reserved: {
11
13
  readonly type: string;
12
14
  readonly reservation: ConsumedReservation;
@@ -1,10 +1,13 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
2
  import { toWriteErrorInfo } from "../errors/index.js";
3
3
  import { createFallbackLogger } from "../logging/utils.js";
4
- import { buildHandlerContext, isMemberResolutionPrincipal } from "./dispatch-shared.js";
4
+ import { applyBeforeTransactionReadOnly, buildHandlerContext, denyTransactionBoundSurfaces, isMemberResolutionPrincipal, } from "./dispatch-shared.js";
5
5
  import { wrapToKumiko } from "./dispatcher-utils.js";
6
6
  import { runPreHandlerGates } from "./pre-handler-gates.js";
7
7
  import { effectiveWriteOrigin, rootWriteOrigin } from "./write-origin.js";
8
+ export function hasPreTransactionPhase(handler) {
9
+ return handler.reserveBeforeTransaction !== undefined || handler.beforeTransaction !== undefined;
10
+ }
8
11
  // One-shot permission per top-level command: the handler that reserved before the transaction
9
12
  // consumes it on entry. A capped handler reached any other way (nested ctx.write, writeAs, a
10
13
  // second execution of the same type) finds it spent and is rejected instead of running unreserved.
@@ -36,13 +39,13 @@ async function releaseAll(ctx, releases) {
36
39
  await release();
37
40
  }
38
41
  catch (error) {
39
- log.error("releasing a pre-transaction reservation failed", { error });
42
+ log.error("releasing a pre-transaction phase failed", { error });
40
43
  }
41
44
  }
42
45
  }
43
46
  // Same gates as the in-transaction run, but before any transaction: a caller who is rate limited,
44
- // denied or sends an invalid payload never reserves anything, so a rejected request cannot consume
45
- // capacity or trigger the reservation's side effects.
47
+ // denied or sends an invalid payload never reserves or prepares anything, so a rejected request
48
+ // cannot consume capacity or trigger external calls.
46
49
  export async function reserveBeforeTransaction(ctx, commands, user, inheritedOrigin) {
47
50
  const releases = [];
48
51
  const reservedCommands = new Map();
@@ -51,7 +54,7 @@ export async function reserveBeforeTransaction(ctx, commands, user, inheritedOri
51
54
  for (const [index, command] of commands.entries()) {
52
55
  try {
53
56
  const handler = ctx.registry.getWriteHandler(command.type);
54
- if (!handler?.reserveBeforeTransaction)
57
+ if (!handler || !hasPreTransactionPhase(handler))
55
58
  continue;
56
59
  const gates = await runPreHandlerGates(ctx, handler, command.type, command.payload, user, "charge");
57
60
  if (!gates.isSuccess) {
@@ -60,10 +63,27 @@ export async function reserveBeforeTransaction(ctx, commands, user, inheritedOri
60
63
  }
61
64
  const origin = effectiveWriteOrigin(rootWriteOrigin(ctx.registry, command.type, user), inheritedOrigin);
62
65
  const handlerContext = await buildHandlerContext(ctx, command.type, user, origin);
63
- const handle = toReservationHandle(await handler.reserveBeforeTransaction({ type: command.type, payload: gates.payload, user }, handlerContext));
64
- if (handle)
65
- releases.push(handle.release);
66
- reservedCommands.set(index, { confirmInTransaction: handle?.confirmInTransaction });
66
+ const event = { type: command.type, payload: gates.payload, user };
67
+ let confirmInTransaction;
68
+ if (handler.reserveBeforeTransaction) {
69
+ const handle = toReservationHandle(await handler.reserveBeforeTransaction(event, denyTransactionBoundSurfaces(handlerContext)));
70
+ if (handle)
71
+ releases.push(handle.release);
72
+ confirmInTransaction = handle?.confirmInTransaction;
73
+ }
74
+ let prepared;
75
+ if (handler.beforeTransaction) {
76
+ const phaseContext = await buildHandlerContext(ctx, command.type, user, origin, undefined, undefined, undefined, undefined, undefined, true);
77
+ const phase = await handler.beforeTransaction(event, applyBeforeTransactionReadOnly(phaseContext));
78
+ if (!phase.isSuccess) {
79
+ await releaseAll(ctx, releases);
80
+ return { isSuccess: false, error: phase.error, failedIndex: index };
81
+ }
82
+ if (phase.release)
83
+ releases.push(phase.release);
84
+ prepared = phase.data;
85
+ }
86
+ reservedCommands.set(index, { confirmInTransaction, prepared });
67
87
  }
68
88
  catch (error) {
69
89
  await releaseAll(ctx, releases);
@@ -1,4 +1,4 @@
1
- import type { RateLimitOption, SessionUser } from "../engine/types/index.js";
1
+ import type { PayloadBucketNormalization, RateLimitOption, SessionUser } from "../engine/types/index.js";
2
2
  export type BucketContext = {
3
3
  readonly handlerName: string;
4
4
  readonly user: SessionUser;
@@ -12,7 +12,7 @@ export type BucketResult = {
12
12
  readonly reason: string;
13
13
  };
14
14
  export declare function buildBucketKey(option: RateLimitOption, ctx: BucketContext): BucketResult;
15
- export declare function normalizePayloadBucketValue(value: string): string;
16
- export declare function buildPayloadBucketKey(handlerName: string, field: string, digest: string): string;
15
+ export declare function normalizePayloadBucketValue(value: string, normalize?: PayloadBucketNormalization): string;
16
+ export declare function buildPayloadBucketKey(tenantId: string, handlerName: string, field: string, digest: string): string;
17
17
  export declare function toPublicBucketName(bucket: string): string;
18
18
  export declare function createPayloadDigest(masterSecret: string): (value: string) => string;
@@ -23,11 +23,21 @@ export function buildBucketKey(option, ctx) {
23
23
  // Payload-field buckets: the value (e.g. a recipient address) is normalized
24
24
  // and HMAC-hashed so Redis never holds it in plaintext.
25
25
  const PAYLOAD_BUCKET_PURPOSE = "kumiko:rate-limit:payload-bucket";
26
- export function normalizePayloadBucketValue(value) {
27
- return value.trim().toLowerCase();
26
+ export function normalizePayloadBucketValue(value, normalize) {
27
+ const base = value.normalize("NFKC").trim().toLowerCase();
28
+ return normalize === "email" ? stripEmailPlusTag(base) : base;
28
29
  }
29
- export function buildPayloadBucketKey(handlerName, field, digest) {
30
- return `payload+handler:${handlerName}:${field}:${digest}`;
30
+ // Only the `+tag` suffix is dropped; provider-specific dot rules are not applied.
31
+ function stripEmailPlusTag(address) {
32
+ const at = address.lastIndexOf("@");
33
+ if (at === -1)
34
+ return address;
35
+ const local = address.slice(0, at);
36
+ const plus = local.indexOf("+");
37
+ return plus <= 0 ? address : `${local.slice(0, plus)}${address.slice(at)}`;
38
+ }
39
+ export function buildPayloadBucketKey(tenantId, handlerName, field, digest) {
40
+ return `payload+handler:${tenantId}:${handlerName}:${field}:${digest}`;
31
41
  }
32
42
  // Everything after the scope tag identifies the caller (IP, user/tenant id,
33
43
  // auth target, payload digest). IPv6 addresses and handler names contain ":",
@@ -64,7 +64,12 @@ function createLocalFanout() {
64
64
  export function createLocalCacheSyncBus() {
65
65
  const fanout = createLocalFanout();
66
66
  return {
67
- publish: fanout.deliver,
67
+ publish(topic, message, options) {
68
+ // skip: a single-process bus has no remote subscribers to reach
69
+ if (options?.remoteOnly)
70
+ return;
71
+ fanout.deliver(topic, message);
72
+ },
68
73
  subscribe: fanout.subscribe,
69
74
  onResync: fanout.onResync,
70
75
  async close() { },
@@ -108,8 +113,9 @@ export function createCacheSyncBusOverSignal(signal, opts) {
108
113
  }
109
114
  });
110
115
  return {
111
- publish(topic, message) {
112
- fanout.deliver(topic, message);
116
+ publish(topic, message, options) {
117
+ if (!options?.remoteOnly)
118
+ fanout.deliver(topic, message);
113
119
  const envelope = { origin, topic, message };
114
120
  signal.publish(opts.channel, envelope);
115
121
  },
@@ -439,8 +439,19 @@ export async function runSchemaCli(argv, appCwd, out, options = {}) {
439
439
  await reportLeftoverDroppedTables(db, migrationsDir, out);
440
440
  return pending === 0 ? 0 : 1;
441
441
  }
442
+ catch (e) {
443
+ // Message only, never dbUrl: it can carry the password.
444
+ out.err(` ✗ schema status failed: ${e instanceof Error ? e.message : String(e)}`);
445
+ out.err(' Check that DATABASE_URL is reachable and the database exists (the scaffold\'s docker-compose creates "app").');
446
+ return 1;
447
+ }
442
448
  finally {
443
- await close();
449
+ try {
450
+ await close();
451
+ }
452
+ catch {
453
+ // The connection already failed and was reported above; a failing close must not throw again.
454
+ }
444
455
  }
445
456
  }
446
457
  default: {
@@ -23,6 +23,27 @@ const REMOVE_TOLERATED_ERROR_CODES = [ErrorStatusCode.INDEX_NOT_FOUND];
23
23
  const INDEX_LIST_PAGE_SIZE = 100;
24
24
  // What an unconfigured Meilisearch index reports as searchableAttributes.
25
25
  const MEILI_ALL_ATTRIBUTES = "*";
26
+ const DEFAULT_FILTERABLE_ATTRIBUTES = ["_type", "_weight"];
27
+ const DEFAULT_SORTABLE_ATTRIBUTES = ["_weight"];
28
+ function defaultSearchableAttributes(config) {
29
+ return config.rankingFields ?? config.searchableFields;
30
+ }
31
+ // Meilisearch may return granular filterable objects; those are never part of our default.
32
+ function isSubsetOf(stored, wanted) {
33
+ return stored.every((attribute) => typeof attribute === "string" && wanted.includes(attribute));
34
+ }
35
+ // A stored list that lies inside the current default and misses attributes is an
36
+ // older default that gained fields since; anything with a foreign attribute is
37
+ // an explicit configure() and stays untouched.
38
+ function isOutdatedDefault(stored, wanted) {
39
+ const pairs = [
40
+ [stored.searchable, wanted.searchable],
41
+ [stored.filterable, wanted.filterable],
42
+ [stored.sortable, wanted.sortable],
43
+ ];
44
+ return (pairs.every(([have, want]) => isSubsetOf(have, want)) &&
45
+ pairs.some(([have, want]) => want.some((attribute) => !have.includes(attribute))));
46
+ }
26
47
  function isIndexNotFound(err) {
27
48
  const cause = typeof err === "object" && err !== null && "cause" in err ? err.cause : null;
28
49
  return errorCodeOf(cause) === ErrorStatusCode.INDEX_NOT_FOUND;
@@ -54,10 +75,9 @@ export function createMeilisearchAdapter(options) {
54
75
  }
55
76
  async function applyConfig(tenantId, config) {
56
77
  const index = client.index(meilisearchTenantIndex(prefix, tenantId));
57
- const fields = config.rankingFields ?? config.searchableFields;
58
- await awaitSucceededTask(index.updateSearchableAttributes([...fields]));
59
- await awaitSucceededTask(index.updateFilterableAttributes(["_type", "_weight"]));
60
- await awaitSucceededTask(index.updateSortableAttributes(["_weight"]));
78
+ await awaitSucceededTask(index.updateSearchableAttributes([...defaultSearchableAttributes(config)]));
79
+ await awaitSucceededTask(index.updateFilterableAttributes([...DEFAULT_FILTERABLE_ATTRIBUTES]));
80
+ await awaitSucceededTask(index.updateSortableAttributes([...DEFAULT_SORTABLE_ATTRIBUTES]));
61
81
  }
62
82
  // Lazily configures a tenant's index off the default config on its first
63
83
  // access, so callers that never call `configure` still get a filterable/
@@ -67,15 +87,22 @@ export function createMeilisearchAdapter(options) {
67
87
  // Meili outage doesn't permanently poison the tenant for the process.
68
88
  // The configured-tenants map is process memory only: after a restart a stored explicit
69
89
  // configure() must not be overwritten by the registry default, so the default is applied only
70
- // to an index that is missing or still carries Meilisearch's factory settings.
71
- async function indexNeedsDefaultConfig(tenantId) {
90
+ // to an index that is missing, carries Meilisearch's factory settings, or holds an older
91
+ // default that is a strict subset of the current one (fields were added by a deploy).
92
+ async function indexNeedsDefaultConfig(tenantId, config) {
72
93
  try {
73
94
  const settings = await client.index(meilisearchTenantIndex(prefix, tenantId)).getSettings();
74
95
  const searchable = settings.searchableAttributes ?? [MEILI_ALL_ATTRIBUTES];
75
96
  const filterable = settings.filterableAttributes ?? [];
76
- return (searchable.length === 1 &&
97
+ const factorySettings = searchable.length === 1 &&
77
98
  searchable[0] === MEILI_ALL_ATTRIBUTES &&
78
- !filterable.includes("_type"));
99
+ !filterable.includes("_type");
100
+ return (factorySettings ||
101
+ isOutdatedDefault({ searchable, filterable, sortable: settings.sortableAttributes ?? [] }, {
102
+ searchable: defaultSearchableAttributes(config),
103
+ filterable: DEFAULT_FILTERABLE_ATTRIBUTES,
104
+ sortable: DEFAULT_SORTABLE_ATTRIBUTES,
105
+ }));
79
106
  }
80
107
  catch (err) {
81
108
  if (isIndexNotFound(err))
@@ -95,7 +122,7 @@ export function createMeilisearchAdapter(options) {
95
122
  }
96
123
  const config = defaultConfig;
97
124
  await trackConfigure(tenantId, async () => {
98
- if (await indexNeedsDefaultConfig(tenantId))
125
+ if (await indexNeedsDefaultConfig(tenantId, config))
99
126
  await applyConfig(tenantId, config);
100
127
  });
101
128
  }
@@ -1,9 +1,9 @@
1
- import type { DbConnection } from "@cosmicdrift/kumiko-types/db-connection";
1
+ import type { DbConnection, PgListenClient } from "@cosmicdrift/kumiko-types/db-connection";
2
2
  declare function requireEnv(name: string): string;
3
3
  export type TestDb = {
4
4
  db: DbConnection;
5
5
  outsideTransactionDb: DbConnection;
6
- client: unknown;
6
+ client: PgListenClient;
7
7
  dbName: string;
8
8
  cleanup: () => Promise<void>;
9
9
  };
@@ -275,7 +275,8 @@ export async function setupTestStack(options) {
275
275
  (appContext.redis === undefined || options.rateLimitNowMs !== undefined);
276
276
  const rateLimitFallback = needsOwnRateLimitResolver
277
277
  ? createRateLimitResolver({
278
- redis: testRedis.redis,
278
+ // An app-supplied redis keeps buckets and app redis on the same instance.
279
+ redis: appContext.redis ?? testRedis.redis,
279
280
  ...(options.rateLimitNowMs ? { nowMs: options.rateLimitNowMs } : {}),
280
281
  })
281
282
  : undefined;
@@ -22,3 +22,4 @@ export { sharedItemEntity, sharedItemTable, sharedUserEntity, sharedUserTable, s
22
22
  export { sleep } from "./utils.js";
23
23
  export { waitFor } from "./wait-for.js";
24
24
  export { withoutAmbientTemporal } from "./without-ambient-temporal.js";
25
+ export { zipWithEntries } from "./zip-fixture.js";
@@ -30,3 +30,4 @@ export { sharedItemEntity, sharedItemTable, sharedUserEntity, sharedUserTable, s
30
30
  export { sleep } from "./utils.js";
31
31
  export { waitFor } from "./wait-for.js";
32
32
  export { withoutAmbientTemporal } from "./without-ambient-temporal.js";
33
+ export { zipWithEntries } from "./zip-fixture.js";
@@ -1,3 +1,9 @@
1
+ /**
2
+ * Serializes a FormData instance to multipart/form-data bytes.
3
+ *
4
+ * Returns the encoded body and the Content-Type header value (including the
5
+ * generated boundary). Pass both directly to app.request or fetch.
6
+ */
1
7
  export declare function buildMultipartBody(fd: FormData): Promise<{
2
8
  body: BodyInit;
3
9
  contentType: string;
@@ -1,3 +1,4 @@
1
+ import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
1
2
  // Workarounds for Bun v1.3.x bun:test limitations with multipart/form-data.
2
3
  //
3
4
  // Two bugs combine to break file upload tests in bun:test:
@@ -19,7 +20,6 @@
19
20
  * Returns the encoded body and the Content-Type header value (including the
20
21
  * generated boundary). Pass both directly to app.request or fetch.
21
22
  */
22
- import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
23
23
  export async function buildMultipartBody(fd) {
24
24
  const boundary = `KumikoBnd${Date.now()}${Math.random().toString(36).slice(2, 8)}`;
25
25
  const enc = new TextEncoder();
@@ -0,0 +1 @@
1
+ export declare function zipWithEntries(...entryNames: readonly string[]): Uint8Array;
@@ -0,0 +1,33 @@
1
+ const LOCAL_HEADER_FIXED_LENGTH = 30;
2
+ const CENTRAL_HEADER_FIXED_LENGTH = 46;
3
+ const EOCD_LENGTH = 22;
4
+ // Minimal stored ZIP (no compression, zero CRC): enough structure for readers that
5
+ // walk the central directory. Not for extraction.
6
+ export function zipWithEntries(...entryNames) {
7
+ const encoder = new TextEncoder();
8
+ const names = entryNames.map((name) => encoder.encode(name));
9
+ const localSize = names.reduce((sum, name) => sum + LOCAL_HEADER_FIXED_LENGTH + name.length, 0);
10
+ const centralSize = names.reduce((sum, name) => sum + CENTRAL_HEADER_FIXED_LENGTH + name.length, 0);
11
+ const bytes = new Uint8Array(localSize + centralSize + EOCD_LENGTH);
12
+ const view = new DataView(bytes.buffer);
13
+ let localOffset = 0;
14
+ let centralOffset = localSize;
15
+ for (const name of names) {
16
+ view.setUint32(localOffset, 0x04034b50, true);
17
+ view.setUint16(localOffset + 26, name.length, true);
18
+ bytes.set(name, localOffset + LOCAL_HEADER_FIXED_LENGTH);
19
+ view.setUint32(centralOffset, 0x02014b50, true);
20
+ view.setUint16(centralOffset + 28, name.length, true);
21
+ view.setUint32(centralOffset + 42, localOffset, true);
22
+ bytes.set(name, centralOffset + CENTRAL_HEADER_FIXED_LENGTH);
23
+ localOffset += LOCAL_HEADER_FIXED_LENGTH + name.length;
24
+ centralOffset += CENTRAL_HEADER_FIXED_LENGTH + name.length;
25
+ }
26
+ const eocdOffset = localSize + centralSize;
27
+ view.setUint32(eocdOffset, 0x06054b50, true);
28
+ view.setUint16(eocdOffset + 8, names.length, true);
29
+ view.setUint16(eocdOffset + 10, names.length, true);
30
+ view.setUint32(eocdOffset + 12, centralSize, true);
31
+ view.setUint32(eocdOffset + 16, localSize, true);
32
+ return bytes;
33
+ }
@@ -18,8 +18,10 @@ export function cardFileDisplayKind(type, renderer) {
18
18
  // boot validator so the validator counts exactly what the card would draw.
19
19
  // The first image column is the card's leading preview, neither title nor meta.
20
20
  export function cardColumnRoles(columns) {
21
- const image = columns.find((col) => col.fileDisplay?.kind === "image");
22
- const textColumns = columns.filter((col) => col !== image);
21
+ const isImage = (col) => col.fileDisplay?.kind === "image";
22
+ const image = columns.find((col) => isImage(col) && col.hideOnNarrow !== true);
23
+ // A narrow-hidden image must not turn into the title just because it is not the preview.
24
+ const textColumns = columns.filter((col) => col !== image && !(isImage(col) && col.hideOnNarrow === true));
23
25
  const title = textColumns.find((col) => col.highlighted === true) ?? textColumns[0];
24
26
  const status = textColumns.find((col) => col !== title && col.type === "select" && col.renderer === undefined);
25
27
  const meta = textColumns.filter((col) => col !== title && col !== status && col.hideOnNarrow !== true);
@@ -4,7 +4,7 @@ export { computeDerivedCellValue, roundDerivedCellValue } from "../engine/embedd
4
4
  export type { ParsedRefTarget } from "../engine/parse-ref-target.js";
5
5
  export { parseRefTarget } from "../engine/parse-ref-target.js";
6
6
  export type { FieldsOrGroupsSection, UnavailableSelectOption } from "../engine/screen-helpers.js";
7
- export { availableSelectOptions, evalFieldCondition, explicitListScreenId, findUnavailableSelectOptions, isExtensionEditSection, isFieldsEditSection, isFormatSpec, isRecordLocked, isWriteFormEditSection, normalizeEditField, normalizeListColumn, resolveNavParentScreen, sectionFieldSpecs, } from "../engine/screen-helpers.js";
7
+ export { availableSelectOptions, evalFieldCondition, explicitListScreenId, findUnavailableSelectOptions, isExtensionEditSection, isFieldsEditSection, isFormatSpec, isRecordLocked, isUnchangedValue, isWriteFormEditSection, normalizeEditField, normalizeListColumn, resolveNavParentScreen, sectionFieldSpecs, } from "../engine/screen-helpers.js";
8
8
  export type { BooleanFieldDef, DateFieldDef, EmbeddedDerivedCellDef, EntityDefinition, FieldDefinition, FileFieldDef, FilesFieldDef, ImageFieldDef, ImagesFieldDef, NumberFieldDef, OptionsQueryPayload, OptionsQueryPayloadValue, SelectFieldDef, SelectOptionTone, TextFieldDef, } from "../engine/types/fields.js";
9
9
  export type { AccessRule, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, RoleAccessPersonalData, RoleAccessRule, } from "../engine/types/handlers.js";
10
10
  export { isOpenToAllGranted, isUiAccessGranted } from "../engine/types/handlers.js";
@@ -22,7 +22,7 @@
22
22
  export { NO_WIDGET_FIELD_TYPES } from "@cosmicdrift/kumiko-types/fields";
23
23
  export { computeDerivedCellValue, roundDerivedCellValue } from "../engine/embedded-derived.js";
24
24
  export { parseRefTarget } from "../engine/parse-ref-target.js";
25
- export { availableSelectOptions, evalFieldCondition, explicitListScreenId, findUnavailableSelectOptions, isExtensionEditSection, isFieldsEditSection, isFormatSpec, isRecordLocked, isWriteFormEditSection, normalizeEditField, normalizeListColumn, resolveNavParentScreen, sectionFieldSpecs, } from "../engine/screen-helpers.js";
25
+ export { availableSelectOptions, evalFieldCondition, explicitListScreenId, findUnavailableSelectOptions, isExtensionEditSection, isFieldsEditSection, isFormatSpec, isRecordLocked, isUnchangedValue, isWriteFormEditSection, normalizeEditField, normalizeListColumn, resolveNavParentScreen, sectionFieldSpecs, } from "../engine/screen-helpers.js";
26
26
  export { isOpenToAllGranted, isUiAccessGranted } from "../engine/types/handlers.js";
27
27
  export { metricField, relatedListGroupHeaderLabel, relatedListGroupKey, } from "../engine/types/screen.js";
28
28
  export { ACTION_FORM_ENTITY, handlerFieldLabelKey, handlerTitleKey, PROJECTION_DETAIL_ENTITY, WRITE_FORM_SECTION_ENTITY, } from "../i18n/required-surface-keys.js";
@@ -1,10 +1,11 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, statSync, writeFileSync, } from "node:fs";
3
- import { isAbsolute, join, relative, resolve } from "node:path";
3
+ import { dirname, isAbsolute, join, relative, resolve } from "node:path";
4
4
  import { getFlag, getStringFlag, parseArgs } from "./arg-parser.js";
5
5
  import { compareVersions, filterEntriesAfter, parseFeatureChangelog, sortEntries, } from "./engine/index.js";
6
6
  import { ensureTemporalPolyfill } from "./time/index.js";
7
7
  const SEMVER_RE = /^\d+\.\d+\.\d+$/;
8
+ const SEMVER_CORE_RE = /^(\d+\.\d+\.\d+)(?:-[0-9A-Za-z.-]+)?$/;
8
9
  const CODEMOD_SUBDIR = "scripts/codemod";
9
10
  function readPackageJsonVersion(packageJsonPath) {
10
11
  if (!existsSync(packageJsonPath))
@@ -21,20 +22,49 @@ function readPackageJsonVersion(packageJsonPath) {
21
22
  }
22
23
  // bun.lock pins transitive packages too, so a repo that only depends on
23
24
  // kumiko-cli/-guards still records the bundled-features version it resolved.
24
- function readLockfileVersion(root, pkgName) {
25
- const lockPath = join(root, "bun.lock");
25
+ // Prerelease and snapshot pins (0.364.0-next.1, 0.0.0-pr-123) are reduced to
26
+ // their x.y.z core, the only shape the rest of the CLI compares.
27
+ function readLockfileVersion(dir, pkgName) {
28
+ const lockPath = join(dir, "bun.lock");
26
29
  if (!existsSync(lockPath))
27
30
  return null;
28
- const pinned = new RegExp(`"@cosmicdrift/${pkgName}": \\["@cosmicdrift/${pkgName}@(\\d+\\.\\d+\\.\\d+)"`).exec(readFileSync(lockPath, "utf-8"));
29
- return pinned?.[1] ?? null;
31
+ const pinned = new RegExp(`"@cosmicdrift/${pkgName}": \\["@cosmicdrift/${pkgName}@([^"]+)"`).exec(readFileSync(lockPath, "utf-8"));
32
+ return pinned?.[1] === undefined ? null : (SEMVER_CORE_RE.exec(pinned[1])?.[1] ?? null);
30
33
  }
31
- // Only the repo's own install, package dir and lockfile count. Walking up
32
- // from a worktree nested in a parent workspace read that workspace's version,
33
- // and the isolated linker keeps transitive packages out of node_modules.
34
+ function declaresWorkspaces(packageJsonPath) {
35
+ if (!existsSync(packageJsonPath))
36
+ return false;
37
+ try {
38
+ const pkg = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
39
+ return typeof pkg === "object" && pkg !== null && "workspaces" in pkg;
40
+ }
41
+ catch {
42
+ return false;
43
+ }
44
+ }
45
+ function isRepoBoundary(dir) {
46
+ return existsSync(join(dir, ".git")) || declaresWorkspaces(join(dir, "package.json"));
47
+ }
48
+ function readPackageVersionAt(dir, pkgName, repoLocalPath) {
49
+ return (readPackageJsonVersion(join(dir, `node_modules/@cosmicdrift/${pkgName}/package.json`)) ??
50
+ readPackageJsonVersion(join(dir, repoLocalPath)) ??
51
+ readLockfileVersion(dir, pkgName));
52
+ }
53
+ // Walks up from a workspace member to the repo's own root (.git or a
54
+ // package.json with workspaces) but never past it: walking on from a worktree
55
+ // nested in a parent workspace read that workspace's version, and the
56
+ // isolated linker keeps transitive packages out of node_modules.
34
57
  function readPackageVersion(root, pkgName, repoLocalPath) {
35
- return (readPackageJsonVersion(join(root, `node_modules/@cosmicdrift/${pkgName}/package.json`)) ??
36
- readPackageJsonVersion(join(root, repoLocalPath)) ??
37
- readLockfileVersion(root, pkgName));
58
+ let dir = root;
59
+ for (;;) {
60
+ const version = readPackageVersionAt(dir, pkgName, repoLocalPath);
61
+ if (version !== null || isRepoBoundary(dir))
62
+ return version;
63
+ const parent = dirname(dir);
64
+ if (parent === dir)
65
+ return null;
66
+ dir = parent;
67
+ }
38
68
  }
39
69
  // Changelog entries come from @cosmicdrift/kumiko-bundled-features (see
40
70
  // findFeaturesDirs); comparing against the framework version instead
@@ -508,6 +538,8 @@ markerVersion, previous) {
508
538
  function parseResolveInput(args) {
509
539
  if (getFlag(args, "apply"))
510
540
  return { error: "--resolve cannot be combined with --apply." };
541
+ if (getFlag(args, "dry-run"))
542
+ return { error: "--resolve cannot be combined with --dry-run." };
511
543
  const refs = (getStringFlag(args, "resolve") ?? "")
512
544
  .split(",")
513
545
  .map((ref) => ref.trim())
@@ -565,8 +597,9 @@ function resolveManualMigrations(out, args, targetDir) {
565
597
  if (errors.length > 0)
566
598
  return fail(...errors, "Nothing was written.");
567
599
  const { reason } = input;
568
- const notApplicableFlag = args.flags.get("not-applicable");
569
- const resolution = notApplicableFlag !== undefined && notApplicableFlag !== false ? "not-applicable" : "migrated";
600
+ const resolution = getFlag(args, "not-applicable")
601
+ ? "not-applicable"
602
+ : "migrated";
570
603
  const resolvedAt = Temporal.Now.instant().toString();
571
604
  const newlyResolved = open
572
605
  .filter((entry) => resolvedIds.has(entry.id))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-framework",
3
- "version": "0.363.0",
3
+ "version": "0.365.0",
4
4
  "description": "Framework core — engine, pipeline, API, DB, and every other bit that makes Kumiko go.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -210,8 +210,8 @@
210
210
  "./package.json": "./package.json"
211
211
  },
212
212
  "dependencies": {
213
- "@cosmicdrift/kumiko-http": "0.363.0",
214
- "@cosmicdrift/kumiko-types": "0.363.0",
213
+ "@cosmicdrift/kumiko-http": "0.365.0",
214
+ "@cosmicdrift/kumiko-types": "0.365.0",
215
215
  "bullmq": "^5.76.7",
216
216
  "bun-types": "^1.3.13",
217
217
  "hono": "^4.13.1",
@@ -227,7 +227,7 @@
227
227
  "zod": "^4.4.3"
228
228
  },
229
229
  "devDependencies": {
230
- "@cosmicdrift/kumiko-dispatcher-live": "0.363.0",
230
+ "@cosmicdrift/kumiko-dispatcher-live": "0.365.0",
231
231
  "bun-types": "^1.3.13",
232
232
  "pino-pretty": "^13.1.3"
233
233
  },
@@ -419,6 +419,8 @@
419
419
  "src/changes.json",
420
420
  "src/scripts/codemod",
421
421
  "README.md",
422
- "LICENSE"
422
+ "LICENSE",
423
+ "AGENTS.md",
424
+ "agents"
423
425
  ]
424
426
  }