@pithy-sh/core 0.10.0 → 0.12.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 (270) hide show
  1. package/dist/access/access.d.ts +172 -0
  2. package/dist/access/access.d.ts.map +1 -0
  3. package/dist/access/access.js +147 -0
  4. package/dist/access/acting.d.ts +49 -0
  5. package/dist/access/acting.d.ts.map +1 -0
  6. package/dist/access/acting.js +21 -0
  7. package/dist/access/policyCheck.d.ts +33 -0
  8. package/dist/access/policyCheck.d.ts.map +1 -0
  9. package/dist/access/policyCheck.js +48 -0
  10. package/dist/access/usage.d.ts +83 -0
  11. package/dist/access/usage.d.ts.map +1 -0
  12. package/dist/access/usage.js +104 -0
  13. package/dist/access/visibility.d.ts +66 -0
  14. package/dist/access/visibility.d.ts.map +1 -0
  15. package/dist/access/visibility.js +86 -0
  16. package/dist/audit/auditEvent.d.ts +5 -0
  17. package/dist/audit/auditEvent.d.ts.map +1 -1
  18. package/dist/audit/auditEvent.js +2 -0
  19. package/dist/audit/recorder.d.ts +10 -2
  20. package/dist/audit/recorder.d.ts.map +1 -1
  21. package/dist/capability/bindings.d.ts +47 -2
  22. package/dist/capability/bindings.d.ts.map +1 -1
  23. package/dist/capability/bindings.js +117 -7
  24. package/dist/capability/capability.d.ts +110 -28
  25. package/dist/capability/capability.d.ts.map +1 -1
  26. package/dist/capability/capability.js +2 -0
  27. package/dist/capability/manifest.d.ts +2 -1
  28. package/dist/capability/manifest.d.ts.map +1 -1
  29. package/dist/capability/manifest.js +1 -1
  30. package/dist/capability/purge.d.ts +80 -0
  31. package/dist/capability/purge.d.ts.map +1 -0
  32. package/dist/capability/purge.js +76 -0
  33. package/dist/capability/settings.d.ts +86 -2
  34. package/dist/capability/settings.d.ts.map +1 -1
  35. package/dist/capability/settings.js +80 -1
  36. package/dist/cli/commandCatalog.generated.d.ts +238 -24
  37. package/dist/cli/commandCatalog.generated.d.ts.map +1 -1
  38. package/dist/cli/commandCatalog.generated.js +251 -22
  39. package/dist/cli/commandFacts.d.ts +2 -1
  40. package/dist/cli/commandFacts.d.ts.map +1 -1
  41. package/dist/cli/commandFacts.js +2 -0
  42. package/dist/controlPlane/capability.d.ts.map +1 -1
  43. package/dist/controlPlane/capability.js +19 -2
  44. package/dist/controlPlane/context.d.ts +5 -0
  45. package/dist/controlPlane/context.d.ts.map +1 -1
  46. package/dist/controlPlane/context.js +3 -1
  47. package/dist/controlPlane/data/connection.d.ts +49 -12
  48. package/dist/controlPlane/data/connection.d.ts.map +1 -1
  49. package/dist/controlPlane/data/connection.js +56 -2
  50. package/dist/controlPlane/discovery/adminRoute.d.ts +29 -0
  51. package/dist/controlPlane/discovery/adminRoute.d.ts.map +1 -1
  52. package/dist/controlPlane/discovery/adminRoute.js +3 -0
  53. package/dist/controlPlane/error/errors.d.ts +4 -3
  54. package/dist/controlPlane/error/errors.d.ts.map +1 -1
  55. package/dist/controlPlane/http/cors.d.ts +4 -4
  56. package/dist/controlPlane/http/cors.js +4 -4
  57. package/dist/controlPlane/http/guard.d.ts +11 -0
  58. package/dist/controlPlane/http/guard.d.ts.map +1 -1
  59. package/dist/controlPlane/http/guard.js +17 -1
  60. package/dist/controlPlane/http/handlers.d.ts +139 -0
  61. package/dist/controlPlane/http/handlers.d.ts.map +1 -1
  62. package/dist/controlPlane/http/handlers.js +67 -4
  63. package/dist/controlPlane/http/readiness.d.ts +33 -0
  64. package/dist/controlPlane/http/readiness.d.ts.map +1 -0
  65. package/dist/controlPlane/http/readiness.js +39 -0
  66. package/dist/controlPlane/http/responses.d.ts +245 -0
  67. package/dist/controlPlane/http/responses.d.ts.map +1 -1
  68. package/dist/controlPlane/http/responses.js +101 -4
  69. package/dist/controlPlane/http/routes.d.ts +2 -0
  70. package/dist/controlPlane/http/routes.d.ts.map +1 -1
  71. package/dist/controlPlane/http/routes.js +31 -3
  72. package/dist/controlPlane/http/verify.d.ts.map +1 -1
  73. package/dist/controlPlane/http/verify.js +2 -1
  74. package/dist/controlPlane/migrationState/migrationState.d.ts +102 -0
  75. package/dist/controlPlane/migrationState/migrationState.d.ts.map +1 -0
  76. package/dist/controlPlane/migrationState/migrationState.js +142 -0
  77. package/dist/controlPlane/migrations/0003_connection_principal.d.ts +21 -0
  78. package/dist/controlPlane/migrations/0003_connection_principal.d.ts.map +1 -0
  79. package/dist/controlPlane/migrations/0003_connection_principal.js +33 -0
  80. package/dist/controlPlane/scope/scope.d.ts +23 -2
  81. package/dist/controlPlane/scope/scope.d.ts.map +1 -1
  82. package/dist/controlPlane/scope/scope.js +24 -3
  83. package/dist/createBackend.d.ts.map +1 -1
  84. package/dist/createBackend.js +9 -2
  85. package/dist/createEntrypoint.d.ts.map +1 -1
  86. package/dist/createEntrypoint.js +5 -3
  87. package/dist/crypto/envelope.d.ts +86 -0
  88. package/dist/crypto/envelope.d.ts.map +1 -0
  89. package/dist/crypto/envelope.js +129 -0
  90. package/dist/crypto/fieldKeys.d.ts +41 -0
  91. package/dist/crypto/fieldKeys.d.ts.map +1 -0
  92. package/dist/crypto/fieldKeys.js +56 -0
  93. package/dist/data/codecs.d.ts +23 -10
  94. package/dist/data/codecs.d.ts.map +1 -1
  95. package/dist/data/codecs.js +50 -36
  96. package/dist/entitlement/allowance.d.ts +25 -0
  97. package/dist/entitlement/allowance.d.ts.map +1 -0
  98. package/dist/entitlement/allowance.js +80 -0
  99. package/dist/entitlement/entitlement.d.ts +41 -0
  100. package/dist/entitlement/entitlement.d.ts.map +1 -1
  101. package/dist/entitlement/meter.d.ts +57 -0
  102. package/dist/entitlement/meter.d.ts.map +1 -0
  103. package/dist/entitlement/meter.js +13 -0
  104. package/dist/error/extend.d.ts +6 -5
  105. package/dist/error/extend.d.ts.map +1 -1
  106. package/dist/error/http.d.ts +168 -0
  107. package/dist/error/http.d.ts.map +1 -1
  108. package/dist/error/payload.d.ts +336 -0
  109. package/dist/error/payload.d.ts.map +1 -1
  110. package/dist/error/payload.js +116 -4
  111. package/dist/error/pithyError.d.ts +18 -7
  112. package/dist/error/pithyError.d.ts.map +1 -1
  113. package/dist/error/pithyError.js +6 -0
  114. package/dist/http/authContext.d.ts +7 -0
  115. package/dist/http/authContext.d.ts.map +1 -1
  116. package/dist/http/authContext.js +8 -1
  117. package/dist/http/scopeGates.d.ts +20 -0
  118. package/dist/http/scopeGates.d.ts.map +1 -0
  119. package/dist/http/scopeGates.js +36 -0
  120. package/dist/index.d.ts +1 -1
  121. package/dist/index.d.ts.map +1 -1
  122. package/dist/index.js +2 -2
  123. package/dist/logger/logger.d.ts +12 -5
  124. package/dist/logger/logger.d.ts.map +1 -1
  125. package/dist/logger/record.d.ts +98 -0
  126. package/dist/logger/record.d.ts.map +1 -1
  127. package/dist/migrations/capabilitySets.d.ts +23 -0
  128. package/dist/migrations/capabilitySets.d.ts.map +1 -0
  129. package/dist/migrations/capabilitySets.js +74 -0
  130. package/dist/migrations/registry.d.ts +26 -0
  131. package/dist/migrations/registry.d.ts.map +1 -1
  132. package/dist/migrations/registry.js +38 -7
  133. package/dist/migrations/state.d.ts +60 -0
  134. package/dist/migrations/state.d.ts.map +1 -0
  135. package/dist/migrations/state.js +46 -0
  136. package/dist/naming/feature.d.ts +27 -5
  137. package/dist/naming/feature.d.ts.map +1 -1
  138. package/dist/naming/feature.js +36 -6
  139. package/dist/naming/limits.d.ts +47 -3
  140. package/dist/naming/limits.d.ts.map +1 -1
  141. package/dist/naming/limits.js +47 -3
  142. package/dist/naming/provisionScope.d.ts +6 -0
  143. package/dist/naming/provisionScope.d.ts.map +1 -1
  144. package/dist/naming/provisionScope.js +8 -3
  145. package/dist/naming/resourceNames.d.ts +21 -2
  146. package/dist/naming/resourceNames.d.ts.map +1 -1
  147. package/dist/naming/resourceNames.js +21 -3
  148. package/dist/privacy/blindIndex.d.ts +27 -0
  149. package/dist/privacy/blindIndex.d.ts.map +1 -0
  150. package/dist/privacy/blindIndex.js +57 -0
  151. package/dist/privacy/classes.d.ts +142 -0
  152. package/dist/privacy/classes.d.ts.map +1 -0
  153. package/dist/privacy/classes.js +209 -0
  154. package/dist/privacy/classify.d.ts +336 -0
  155. package/dist/privacy/classify.d.ts.map +1 -0
  156. package/dist/privacy/classify.js +1024 -0
  157. package/dist/privacy/encrypted.d.ts +210 -0
  158. package/dist/privacy/encrypted.d.ts.map +1 -0
  159. package/dist/privacy/encrypted.js +350 -0
  160. package/dist/privacy/encryptedColumns.d.ts +64 -0
  161. package/dist/privacy/encryptedColumns.d.ts.map +1 -0
  162. package/dist/privacy/encryptedColumns.js +134 -0
  163. package/dist/privacy/fieldRotation.d.ts +34 -0
  164. package/dist/privacy/fieldRotation.d.ts.map +1 -0
  165. package/dist/privacy/fieldRotation.js +186 -0
  166. package/dist/privacy/inventory.d.ts +12 -0
  167. package/dist/privacy/inventory.d.ts.map +1 -0
  168. package/dist/privacy/inventory.js +154 -0
  169. package/dist/privacy/posture.d.ts +114 -0
  170. package/dist/privacy/posture.d.ts.map +1 -0
  171. package/dist/privacy/posture.js +136 -0
  172. package/dist/privacy/redact.d.ts +7 -0
  173. package/dist/privacy/redact.d.ts.map +1 -0
  174. package/dist/privacy/redact.js +404 -0
  175. package/dist/privacy/report.d.ts +58 -0
  176. package/dist/privacy/report.d.ts.map +1 -0
  177. package/dist/privacy/report.js +15 -0
  178. package/dist/privacy/retirement.d.ts +68 -0
  179. package/dist/privacy/retirement.d.ts.map +1 -0
  180. package/dist/privacy/retirement.js +72 -0
  181. package/dist/privacy/sealed.d.ts +87 -0
  182. package/dist/privacy/sealed.d.ts.map +1 -0
  183. package/dist/privacy/sealed.js +221 -0
  184. package/dist/privacy/types.d.ts +559 -0
  185. package/dist/privacy/types.d.ts.map +1 -0
  186. package/dist/privacy/types.js +2 -0
  187. package/dist/seed/writeD1.d.ts +7 -1
  188. package/dist/seed/writeD1.d.ts.map +1 -1
  189. package/dist/seed/writeD1.js +9 -3
  190. package/dist/seed/writeKv.d.ts +2 -0
  191. package/dist/seed/writeKv.d.ts.map +1 -1
  192. package/dist/seed/writeKv.js +6 -2
  193. package/dist/version.generated.d.ts +1 -1
  194. package/dist/version.generated.js +1 -1
  195. package/dist/worker/serviceLoopback.d.ts +36 -0
  196. package/dist/worker/serviceLoopback.d.ts.map +1 -0
  197. package/dist/worker/serviceLoopback.js +74 -0
  198. package/dist/workflow/faults.d.ts +12 -1
  199. package/dist/workflow/faults.d.ts.map +1 -1
  200. package/dist/workflow/faults.js +22 -15
  201. package/package.json +2 -2
  202. package/src/access/access.ts +298 -0
  203. package/src/access/acting.ts +59 -0
  204. package/src/access/policyCheck.ts +77 -0
  205. package/src/access/usage.ts +141 -0
  206. package/src/access/visibility.ts +109 -0
  207. package/src/audit/auditEvent.ts +4 -0
  208. package/src/audit/recorder.ts +10 -2
  209. package/src/capability/bindings.ts +159 -13
  210. package/src/capability/capability.ts +114 -29
  211. package/src/capability/purge.ts +124 -0
  212. package/src/capability/settings.ts +146 -2
  213. package/src/cli/commandCatalog.generated.ts +271 -22
  214. package/src/cli/commandFacts.ts +2 -0
  215. package/src/controlPlane/capability.ts +36 -1
  216. package/src/controlPlane/context.ts +4 -0
  217. package/src/controlPlane/data/connection.ts +90 -0
  218. package/src/controlPlane/discovery/adminRoute.ts +11 -0
  219. package/src/controlPlane/error/errors.ts +4 -3
  220. package/src/controlPlane/http/cors.ts +4 -4
  221. package/src/controlPlane/http/guard.ts +21 -0
  222. package/src/controlPlane/http/handlers.ts +98 -4
  223. package/src/controlPlane/http/readiness.ts +50 -0
  224. package/src/controlPlane/http/responses.ts +240 -2
  225. package/src/controlPlane/http/routes.ts +55 -1
  226. package/src/controlPlane/http/verify.ts +2 -0
  227. package/src/controlPlane/migrationState/migrationState.ts +200 -0
  228. package/src/controlPlane/migrations/0003_connection_principal.ts +37 -0
  229. package/src/controlPlane/scope/scope.ts +25 -2
  230. package/src/controlPlane/token/base64url.ts +1 -1
  231. package/src/createBackend.ts +28 -6
  232. package/src/createEntrypoint.ts +9 -3
  233. package/src/crypto/envelope.ts +196 -0
  234. package/src/crypto/fieldKeys.ts +76 -0
  235. package/src/data/codecs.ts +69 -37
  236. package/src/entitlement/allowance.ts +85 -0
  237. package/src/entitlement/entitlement.ts +38 -0
  238. package/src/entitlement/meter.ts +69 -0
  239. package/src/error/extend.ts +6 -5
  240. package/src/error/payload.ts +253 -4
  241. package/src/error/pithyError.ts +21 -7
  242. package/src/http/authContext.ts +13 -1
  243. package/src/http/scopeGates.ts +34 -0
  244. package/src/index.ts +2 -0
  245. package/src/logger/logger.ts +12 -5
  246. package/src/migrations/capabilitySets.ts +87 -0
  247. package/src/migrations/registry.ts +46 -2
  248. package/src/migrations/state.ts +94 -0
  249. package/src/naming/feature.ts +46 -7
  250. package/src/naming/limits.ts +35 -3
  251. package/src/naming/provisionScope.ts +19 -1
  252. package/src/naming/resourceNames.ts +52 -5
  253. package/src/privacy/blindIndex.ts +81 -0
  254. package/src/privacy/classes.ts +277 -0
  255. package/src/privacy/classify.ts +1447 -0
  256. package/src/privacy/encrypted.ts +513 -0
  257. package/src/privacy/encryptedColumns.ts +185 -0
  258. package/src/privacy/fieldRotation.ts +234 -0
  259. package/src/privacy/inventory.ts +147 -0
  260. package/src/privacy/posture.ts +205 -0
  261. package/src/privacy/redact.ts +423 -0
  262. package/src/privacy/report.ts +64 -0
  263. package/src/privacy/retirement.ts +102 -0
  264. package/src/privacy/sealed.ts +323 -0
  265. package/src/privacy/types.ts +804 -0
  266. package/src/seed/writeD1.ts +13 -4
  267. package/src/seed/writeKv.ts +6 -2
  268. package/src/version.generated.ts +1 -1
  269. package/src/worker/serviceLoopback.ts +105 -0
  270. package/src/workflow/faults.ts +51 -24
@@ -107,6 +107,20 @@ export declare const LogRecord: z.ZodObject<{
107
107
  detail: z.ZodOptional<z.ZodString>;
108
108
  code: z.ZodLiteral<"auth/session_not_fresh">;
109
109
  status: z.ZodLiteral<403>;
110
+ }, z.core.$strip>, z.ZodObject<{
111
+ message: z.ZodString;
112
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
113
+ action: z.ZodOptional<z.ZodString>;
114
+ detail: z.ZodOptional<z.ZodString>;
115
+ code: z.ZodLiteral<"auth/account_suspended">;
116
+ status: z.ZodLiteral<403>;
117
+ }, z.core.$strip>, z.ZodObject<{
118
+ message: z.ZodString;
119
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
120
+ action: z.ZodOptional<z.ZodString>;
121
+ detail: z.ZodOptional<z.ZodString>;
122
+ code: z.ZodLiteral<"auth/account_deleted">;
123
+ status: z.ZodLiteral<403>;
110
124
  }, z.core.$strip>, z.ZodObject<{
111
125
  message: z.ZodString;
112
126
  params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
@@ -310,6 +324,20 @@ export declare const LogRecord: z.ZodObject<{
310
324
  detail: z.ZodOptional<z.ZodString>;
311
325
  code: z.ZodLiteral<"turnstile/config">;
312
326
  status: z.ZodLiteral<500>;
327
+ }, z.core.$strip>, z.ZodObject<{
328
+ message: z.ZodString;
329
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
330
+ action: z.ZodOptional<z.ZodString>;
331
+ detail: z.ZodOptional<z.ZodString>;
332
+ code: z.ZodLiteral<"turnstile/analytics_window">;
333
+ status: z.ZodLiteral<400>;
334
+ }, z.core.$strip>, z.ZodObject<{
335
+ message: z.ZodString;
336
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
337
+ action: z.ZodOptional<z.ZodString>;
338
+ detail: z.ZodOptional<z.ZodString>;
339
+ code: z.ZodLiteral<"turnstile/analytics_unavailable">;
340
+ status: z.ZodLiteral<409>;
313
341
  }, z.core.$strip>, z.ZodObject<{
314
342
  message: z.ZodString;
315
343
  params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
@@ -331,6 +359,13 @@ export declare const LogRecord: z.ZodObject<{
331
359
  detail: z.ZodOptional<z.ZodString>;
332
360
  code: z.ZodLiteral<"media/not_found">;
333
361
  status: z.ZodLiteral<404>;
362
+ }, z.core.$strip>, z.ZodObject<{
363
+ message: z.ZodString;
364
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
365
+ action: z.ZodOptional<z.ZodString>;
366
+ detail: z.ZodOptional<z.ZodString>;
367
+ code: z.ZodLiteral<"media/forbidden">;
368
+ status: z.ZodLiteral<403>;
334
369
  }, z.core.$strip>, z.ZodObject<{
335
370
  message: z.ZodString;
336
371
  params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
@@ -352,6 +387,20 @@ export declare const LogRecord: z.ZodObject<{
352
387
  detail: z.ZodOptional<z.ZodString>;
353
388
  code: z.ZodLiteral<"media/enrichment_failed">;
354
389
  status: z.ZodLiteral<500>;
390
+ }, z.core.$strip>, z.ZodObject<{
391
+ message: z.ZodString;
392
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
393
+ action: z.ZodOptional<z.ZodString>;
394
+ detail: z.ZodOptional<z.ZodString>;
395
+ code: z.ZodLiteral<"media/analytics_window">;
396
+ status: z.ZodLiteral<400>;
397
+ }, z.core.$strip>, z.ZodObject<{
398
+ message: z.ZodString;
399
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
400
+ action: z.ZodOptional<z.ZodString>;
401
+ detail: z.ZodOptional<z.ZodString>;
402
+ code: z.ZodLiteral<"media/analytics_unavailable">;
403
+ status: z.ZodLiteral<409>;
355
404
  }, z.core.$strip>, z.ZodObject<{
356
405
  message: z.ZodString;
357
406
  params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
@@ -786,6 +835,20 @@ export declare const LogRecord: z.ZodObject<{
786
835
  detail: z.ZodOptional<z.ZodString>;
787
836
  code: z.ZodLiteral<"payments/subscription_change_refused">;
788
837
  status: z.ZodLiteral<409>;
838
+ }, z.core.$strip>, z.ZodObject<{
839
+ message: z.ZodString;
840
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
841
+ action: z.ZodOptional<z.ZodString>;
842
+ detail: z.ZodOptional<z.ZodString>;
843
+ code: z.ZodLiteral<"payments/override_stale">;
844
+ status: z.ZodLiteral<409>;
845
+ }, z.core.$strip>, z.ZodObject<{
846
+ message: z.ZodString;
847
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
848
+ action: z.ZodOptional<z.ZodString>;
849
+ detail: z.ZodOptional<z.ZodString>;
850
+ code: z.ZodLiteral<"payments/quota_exceeded">;
851
+ status: z.ZodLiteral<402>;
789
852
  }, z.core.$strip>, z.ZodObject<{
790
853
  message: z.ZodString;
791
854
  params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
@@ -870,6 +933,41 @@ export declare const LogRecord: z.ZodObject<{
870
933
  detail: z.ZodOptional<z.ZodString>;
871
934
  code: z.ZodLiteral<"organization/nomination_invalid">;
872
935
  status: z.ZodLiteral<400>;
936
+ }, z.core.$strip>, z.ZodObject<{
937
+ message: z.ZodString;
938
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
939
+ action: z.ZodOptional<z.ZodString>;
940
+ detail: z.ZodOptional<z.ZodString>;
941
+ code: z.ZodLiteral<"artifacts/not_found">;
942
+ status: z.ZodLiteral<404>;
943
+ }, z.core.$strip>, z.ZodObject<{
944
+ message: z.ZodString;
945
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
946
+ action: z.ZodOptional<z.ZodString>;
947
+ detail: z.ZodOptional<z.ZodString>;
948
+ code: z.ZodLiteral<"artifacts/forbidden">;
949
+ status: z.ZodLiteral<403>;
950
+ }, z.core.$strip>, z.ZodObject<{
951
+ message: z.ZodString;
952
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
953
+ action: z.ZodOptional<z.ZodString>;
954
+ detail: z.ZodOptional<z.ZodString>;
955
+ code: z.ZodLiteral<"artifacts/invalid_name">;
956
+ status: z.ZodLiteral<400>;
957
+ }, z.core.$strip>, z.ZodObject<{
958
+ message: z.ZodString;
959
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
960
+ action: z.ZodOptional<z.ZodString>;
961
+ detail: z.ZodOptional<z.ZodString>;
962
+ code: z.ZodLiteral<"artifacts/name_taken">;
963
+ status: z.ZodLiteral<409>;
964
+ }, z.core.$strip>, z.ZodObject<{
965
+ message: z.ZodString;
966
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
967
+ action: z.ZodOptional<z.ZodString>;
968
+ detail: z.ZodOptional<z.ZodString>;
969
+ code: z.ZodLiteral<"artifacts/invalid_config">;
970
+ status: z.ZodLiteral<400>;
873
971
  }, z.core.$strip>, z.ZodObject<{
874
972
  message: z.ZodString;
875
973
  params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
@@ -1 +1 @@
1
- {"version":3,"file":"record.d.ts","sourceRoot":"","sources":["../../src/logger/record.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;GAKG;AAEH,4GAA4G;AAC5G,eAAO,MAAM,QAAQ;;;;;EAE4F,CAAC;AAClH,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,QAAQ,CAAC,CAAC;AAEjD;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAgD,CAAC;AAEtG;;;;GAIG;AACH,eAAO,MAAM,SAAS,wCAEsF,CAAC;AAC7G,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,SAAS,CAAC,CAAC;AAEnD;;;;;GAKG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAW+E,CAAC;AACtG,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,SAAS,CAAC,CAAC;AAmBnD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAMzD"}
1
+ {"version":3,"file":"record.d.ts","sourceRoot":"","sources":["../../src/logger/record.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;GAKG;AAEH,4GAA4G;AAC5G,eAAO,MAAM,QAAQ;;;;;EAE4F,CAAC;AAClH,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,QAAQ,CAAC,CAAC;AAEjD;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAgD,CAAC;AAEtG;;;;GAIG;AACH,eAAO,MAAM,SAAS,wCAEsF,CAAC;AAC7G,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,SAAS,CAAC,CAAC;AAEnD;;;;;GAKG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAW+E,CAAC;AACtG,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,SAAS,CAAC,CAAC;AAmBnD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAMzD"}
@@ -0,0 +1,23 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import type { MigrationProvider } from "kysely/migration";
4
+ import type { Capability } from "../capability/capability";
5
+ import { type NamespacedMigrations } from "./registry";
6
+ /**
7
+ * Every capability's per-database migration set (`DatabaseSpec.migrations`, namespaced by capability
8
+ * name), in composition order — the raw material {@link buildRegistryFromCapabilities}, the CLI's
9
+ * fan-out runner and the control-plane seam all build on. The runner needs the sets rather than a
10
+ * finished provider because two Workers can be bound to one physical D1: their sets merge into a single
11
+ * ordered provider so that database migrates once, with every Worker's capabilities in it. A Worker
12
+ * composes only its own, which is why the seam's `undeclared` is relative to that Worker. Migrations
13
+ * without a `migrationOrder` fail here, attributed to their capability.
14
+ */
15
+ export declare function collectMigrationSets(capabilities: Capability[]): NamespacedMigrations[];
16
+ /**
17
+ * Compose every capability's per-database migration sets (`DatabaseSpec.migrations`,
18
+ * namespaced by capability name) into one ordered provider per database. A database
19
+ * nobody migrates gets no provider; migrations without a `migrationOrder` fail here,
20
+ * attributed to their capability.
21
+ */
22
+ export declare function buildRegistryFromCapabilities(capabilities: Capability[]): Record<string, MigrationProvider>;
23
+ //# sourceMappingURL=capabilitySets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capabilitySets.d.ts","sourceRoot":"","sources":["../../src/migrations/capabilitySets.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAG3D,OAAO,EAA2B,KAAK,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAsChF;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,UAAU,EAAE,GAAG,oBAAoB,EAAE,CAsBvF;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,YAAY,EAAE,UAAU,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAE3G"}
@@ -0,0 +1,74 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { InternalError } from "../error/pithyError.js";
4
+ import { createMigrationRegistry } from "./registry.js";
5
+ //#region src/migrations/capabilitySets.ts
6
+ /**
7
+ * **Where a capability's migrations become a registry's sets — one function, composed twice (#729).**
8
+ *
9
+ * `pithy migrate` and `pithy doctor` compose these sets from the project on disk; the Worker composes them
10
+ * from the capabilities it was built with, to answer `GET {basePath}/migrations`. It lived in the CLI until
11
+ * the Worker needed it too. It reads nothing but `Capability.databases`, so it belongs in core — and if
12
+ * the two sides composed with two functions, `pithy doctor` and the route could disagree about one
13
+ * environment with nothing to say which was wrong.
14
+ */
15
+ /**
16
+ * A retained declaration must name a table this slice declares, beside the migrations that create it.
17
+ *
18
+ * A misspelled key would count a table that does not exist, find no rows, and let the `down` through —
19
+ * the refusal quietly off for the one table it was written for. And retention on a slice with no
20
+ * migrations has no `down` to be recorded on, so nothing would ever enforce it. Both are an author's mistake,
21
+ * caught where the declaration is read rather than discovered when a vault is empty (#588).
22
+ */
23
+ function assertRetainedDeclared(capability, database, spec) {
24
+ const retained = spec.retained ?? [];
25
+ if (retained.length === 0) return;
26
+ const unknown = retained.filter((table) => !(table in spec.tables));
27
+ if (unknown.length > 0) throw new InternalError({
28
+ message: `Capability "${capability}" retains ${unknown.map((table) => `"${table}"`).join(", ")} in database "${database}", which its tables do not declare.`,
29
+ action: "Name tables from that database spec's tables in retained."
30
+ });
31
+ if (!spec.migrations || Object.keys(spec.migrations).length === 0) throw new InternalError({
32
+ message: `Capability "${capability}" retains tables in database "${database}" but ships no migrations for it.`,
33
+ action: "Declare retained beside the migrations that create those tables."
34
+ });
35
+ }
36
+ /**
37
+ * Every capability's per-database migration set (`DatabaseSpec.migrations`, namespaced by capability
38
+ * name), in composition order — the raw material {@link buildRegistryFromCapabilities}, the CLI's
39
+ * fan-out runner and the control-plane seam all build on. The runner needs the sets rather than a
40
+ * finished provider because two Workers can be bound to one physical D1: their sets merge into a single
41
+ * ordered provider so that database migrates once, with every Worker's capabilities in it. A Worker
42
+ * composes only its own, which is why the seam's `undeclared` is relative to that Worker. Migrations
43
+ * without a `migrationOrder` fail here, attributed to their capability.
44
+ */
45
+ function collectMigrationSets(capabilities) {
46
+ const sets = [];
47
+ for (const capability of capabilities) for (const [database, spec] of Object.entries(capability.databases ?? {})) {
48
+ assertRetainedDeclared(capability.name, database, spec);
49
+ if (!spec.migrations) continue;
50
+ if (spec.migrationOrder === void 0) throw new InternalError({
51
+ message: `Capability "${capability.name}" has migrations for database "${database}" but no migrationOrder.`,
52
+ action: "Set migrationOrder on that database spec (core low, app high)."
53
+ });
54
+ sets.push({
55
+ database,
56
+ namespace: capability.name,
57
+ order: spec.migrationOrder,
58
+ migrations: spec.migrations,
59
+ ...spec.retained && spec.retained.length > 0 ? { retained: spec.retained } : {}
60
+ });
61
+ }
62
+ return sets;
63
+ }
64
+ /**
65
+ * Compose every capability's per-database migration sets (`DatabaseSpec.migrations`,
66
+ * namespaced by capability name) into one ordered provider per database. A database
67
+ * nobody migrates gets no provider; migrations without a `migrationOrder` fail here,
68
+ * attributed to their capability.
69
+ */
70
+ function buildRegistryFromCapabilities(capabilities) {
71
+ return createMigrationRegistry(collectMigrationSets(capabilities));
72
+ }
73
+ //#endregion
74
+ export { buildRegistryFromCapabilities, collectMigrationSets };
@@ -25,6 +25,32 @@ export interface NamespacedMigrations {
25
25
  */
26
26
  retained?: readonly string[];
27
27
  }
28
+ /**
29
+ * The composed key for one of a set's migrations: `NNNN_<namespace>_<localKey>`. The one place the shape is
30
+ * written, so the registry and anything naming a declared migration's namespace build the same string.
31
+ */
32
+ export declare function composedMigrationKey(set: Pick<NamespacedMigrations, "order" | "namespace">, localKey: string): string;
33
+ /** A composed key taken back apart: the three parts {@link createMigrationRegistry} joined. */
34
+ export interface ComposedMigrationKey {
35
+ /** The capability's `migrationOrder` — the zero-padded prefix, as a number. */
36
+ order: number;
37
+ /** The capability namespace — its `name`, which the manifest reports beside every capability. */
38
+ namespace: string;
39
+ /** The capability's own stable key, e.g. `0001_init`. */
40
+ localKey: string;
41
+ }
42
+ /**
43
+ * Take a composed key back apart — the inverse of the composition below.
44
+ *
45
+ * A declared migration's namespace is known from the set it came from. An **undeclared** one — applied in
46
+ * the database, carried by no provider this build composes — has nothing but its key, so this is the only
47
+ * way to say whose it was. **Null for a key no registry could have composed**, a migration written by hand
48
+ * or by another tool: guessing a namespace for it would be inventing one.
49
+ *
50
+ * The patterns are the same constants the composition enforces, so the two cannot disagree about what a
51
+ * key looks like.
52
+ */
53
+ export declare function decomposeMigrationKey(key: string): ComposedMigrationKey | null;
28
54
  /**
29
55
  * Merge namespaced migration sets into one ordered `MigrationProvider` **per database** — matching
30
56
  * multi-database D1, where each `c.var.db.<database>` is its own binding and schema. The result is
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/migrations/registry.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAOrE,4FAA4F;AAC5F,eAAO,MAAM,mBAAmB,QAAyB,CAAC;AAE1D,mGAAmG;AACnG,eAAO,MAAM,iBAAiB,QAAqB,CAAC;AAKpD,MAAM,WAAW,oBAAoB;IACnC;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB,uFAAuF;IACvF,SAAS,EAAE,MAAM,CAAC;IAClB,wGAAwG;IACxG,KAAK,EAAE,MAAM,CAAC;IACd,gFAAgF;IAChF,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACtC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC9B;AAgED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,oBAAoB,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAavG"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/migrations/registry.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAOrE,4FAA4F;AAC5F,eAAO,MAAM,mBAAmB,QAAyB,CAAC;AAE1D,mGAAmG;AACnG,eAAO,MAAM,iBAAiB,QAAqB,CAAC;AAKpD,MAAM,WAAW,oBAAoB;IACnC;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB,uFAAuF;IACvF,SAAS,EAAE,MAAM,CAAC;IAClB,wGAAwG;IACxG,KAAK,EAAE,MAAM,CAAC;IACd,gFAAgF;IAChF,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACtC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC9B;AA0BD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,IAAI,CAAC,oBAAoB,EAAE,OAAO,GAAG,WAAW,CAAC,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAErH;AAED,+FAA+F;AAC/F,MAAM,WAAW,oBAAoB;IACnC,+EAA+E;IAC/E,KAAK,EAAE,MAAM,CAAC;IACd,iGAAiG;IACjG,SAAS,EAAE,MAAM,CAAC;IAClB,yDAAyD;IACzD,QAAQ,EAAE,MAAM,CAAC;CAClB;AAUD;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,oBAAoB,GAAG,IAAI,CAM9E;AAuCD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,oBAAoB,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAavG"}
@@ -20,6 +20,40 @@ function assertValidOrder(order, namespace) {
20
20
  function assertValidLocalKey(localKey, namespace) {
21
21
  if (!LOCAL_KEY_PATTERN.test(localKey)) throw new InternalError({ message: `migration key "${localKey}" (namespace "${namespace}") must match ${LOCAL_KEY_PATTERN}` });
22
22
  }
23
+ /**
24
+ * The composed key for one of a set's migrations: `NNNN_<namespace>_<localKey>`. The one place the shape is
25
+ * written, so the registry and anything naming a declared migration's namespace build the same string.
26
+ */
27
+ function composedMigrationKey(set, localKey) {
28
+ return `${String(set.order).padStart(ORDER_DIGITS, "0")}_${set.namespace}_${localKey}`;
29
+ }
30
+ /**
31
+ * The composed key shape, anchored at both ends: the order prefix, a namespace (which admits no separator,
32
+ * so the first underscore after it is the boundary), then a local key, which may hold any number.
33
+ */
34
+ const COMPOSED_KEY_PATTERN = new RegExp(`^(\\d{${ORDER_DIGITS}})_(${NAMESPACE_PATTERN.source.slice(1, -1)})_(${LOCAL_KEY_PATTERN.source.slice(1, -1)})$`);
35
+ /**
36
+ * Take a composed key back apart — the inverse of the composition below.
37
+ *
38
+ * A declared migration's namespace is known from the set it came from. An **undeclared** one — applied in
39
+ * the database, carried by no provider this build composes — has nothing but its key, so this is the only
40
+ * way to say whose it was. **Null for a key no registry could have composed**, a migration written by hand
41
+ * or by another tool: guessing a namespace for it would be inventing one.
42
+ *
43
+ * The patterns are the same constants the composition enforces, so the two cannot disagree about what a
44
+ * key looks like.
45
+ */
46
+ function decomposeMigrationKey(key) {
47
+ const match = COMPOSED_KEY_PATTERN.exec(key);
48
+ if (!match) return null;
49
+ const [, order, namespace, localKey] = match;
50
+ if (order === void 0 || namespace === void 0 || localKey === void 0) return null;
51
+ return {
52
+ order: Number(order),
53
+ namespace,
54
+ localKey
55
+ };
56
+ }
23
57
  /** Merge one database's sets into a provider, enforcing the per-database invariants. */
24
58
  function composeDatabase(database, sets) {
25
59
  const seenOrders = /* @__PURE__ */ new Set();
@@ -34,12 +68,9 @@ function composeDatabase(database, sets) {
34
68
  seenNamespaces.add(set.namespace);
35
69
  }
36
70
  const composed = {};
37
- for (const set of [...sets].sort((a, b) => a.order - b.order)) {
38
- const prefix = String(set.order).padStart(ORDER_DIGITS, "0");
39
- for (const localKey of Object.keys(set.migrations).sort()) {
40
- const migration = set.migrations[localKey];
41
- if (migration) composed[`${prefix}_${set.namespace}_${localKey}`] = migration;
42
- }
71
+ for (const set of [...sets].sort((a, b) => a.order - b.order)) for (const localKey of Object.keys(set.migrations).sort()) {
72
+ const migration = set.migrations[localKey];
73
+ if (migration) composed[composedMigrationKey(set, localKey)] = migration;
43
74
  }
44
75
  for (const set of sets) declareRetained(set.migrations, set.retained ?? []);
45
76
  return { getMigrations: async () => composed };
@@ -71,4 +102,4 @@ function createMigrationRegistry(sets) {
71
102
  return registry;
72
103
  }
73
104
  //#endregion
74
- export { MAX_MIGRATION_ORDER, NAMESPACE_PATTERN, createMigrationRegistry };
105
+ export { MAX_MIGRATION_ORDER, NAMESPACE_PATTERN, composedMigrationKey, createMigrationRegistry, decomposeMigrationKey };
@@ -0,0 +1,60 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import type { D1Database } from "@cloudflare/workers-types";
4
+ import type { MigrationProvider } from "kysely/migration";
5
+ /**
6
+ * **Where one database stands, read in one place (#729).**
7
+ *
8
+ * The control-plane seam answers `GET {basePath}/migrations` from this: what is applied, what this build
9
+ * declares and the database lacks, what the database holds and this build no longer declares, and which
10
+ * run put it there. Each half already had a reader — {@link readMigrationLedger} for both directions,
11
+ * {@link readMigrationGroups} and {@link readGroupPosition} for the group (#694) — and this composes them
12
+ * rather than reading either table a second way.
13
+ *
14
+ * Read-only. It applies nothing, creates nothing, and reverses nothing: a screen somebody opened to *ask*
15
+ * must not be the thing that changes the schema.
16
+ */
17
+ /** Whether the last group can still be taken back by name — {@link readGroupPosition}, minus `absent`. */
18
+ export type MigrationGroupStanding = {
19
+ /** The group's migrations are the contiguous tip of this ledger. The CLI's rollback has refusals of its own. */
20
+ state: "top";
21
+ } | {
22
+ /** Something sits over the group or inside it, so reversing it by name is refused. */
23
+ state: "buried";
24
+ /** The groups in the way, newest first. */
25
+ above: string[];
26
+ /** The migrations in the way that no group claims, newest first. */
27
+ ungrouped: string[];
28
+ };
29
+ /** The newest group with anything still applied here, and whether it is still reversible. */
30
+ export interface MigrationLastGroup {
31
+ /** The group's value — what `--rollback --group` takes. */
32
+ group: string;
33
+ /** When it last applied something here. */
34
+ appliedAt: Date;
35
+ /** What it applied here and is still applied, in application order. */
36
+ migrations: string[];
37
+ /** Whether it is the tip of the chain, or what is in its way. */
38
+ position: MigrationGroupStanding;
39
+ }
40
+ /** One database's migration state. */
41
+ export interface MigrationState {
42
+ /** Every applied migration, in the order the ledger applied them. */
43
+ applied: string[];
44
+ /** Declared and not yet applied, in the order they would run. */
45
+ pending: string[];
46
+ /** Applied and not declared by this build, sorted. Kysely refuses the whole chain on any one of these. */
47
+ undeclared: string[];
48
+ /** The newest group with anything applied here, or null when nothing applied here was ever grouped. */
49
+ lastGroup: MigrationLastGroup | null;
50
+ }
51
+ /**
52
+ * Read one database's migration state against the provider this build composes for it.
53
+ *
54
+ * **The last group is the newest one with something still applied.** Group rows are forgotten as their
55
+ * migrations come down, so this is ordinarily the newest group recorded. A group whose rows outlived their
56
+ * ledger rows reads as `absent` and is passed over: naming it as where the environment stands would name a
57
+ * release that is no longer in the database.
58
+ */
59
+ export declare function readMigrationState(database: D1Database, provider: MigrationProvider): Promise<MigrationState>;
60
+ //# sourceMappingURL=state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/migrations/state.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAK1D;;;;;;;;;;;GAWG;AAEH,0GAA0G;AAC1G,MAAM,MAAM,sBAAsB,GAC9B;IACE,gHAAgH;IAChH,KAAK,EAAE,KAAK,CAAC;CACd,GACD;IACE,sFAAsF;IACtF,KAAK,EAAE,QAAQ,CAAC;IAChB,2CAA2C;IAC3C,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,oEAAoE;IACpE,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB,CAAC;AAEN,6FAA6F;AAC7F,MAAM,WAAW,kBAAkB;IACjC,2DAA2D;IAC3D,KAAK,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,SAAS,EAAE,IAAI,CAAC;IAChB,uEAAuE;IACvE,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,iEAAiE;IACjE,QAAQ,EAAE,sBAAsB,CAAC;CAClC;AAED,sCAAsC;AACtC,MAAM,WAAW,cAAc;IAC7B,qEAAqE;IACrE,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,iEAAiE;IACjE,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,0GAA0G;IAC1G,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,uGAAuG;IACvG,SAAS,EAAE,kBAAkB,GAAG,IAAI,CAAC;CACtC;AAED;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAAC,cAAc,CAAC,CAInH"}
@@ -0,0 +1,46 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { appliedMigrationChain, migrationKysely } from "./bookkeeping.js";
4
+ import { readGroupPosition, readMigrationGroups } from "./groups.js";
5
+ import { readMigrationLedger } from "./runner.js";
6
+ //#region src/migrations/state.ts
7
+ /**
8
+ * Read one database's migration state against the provider this build composes for it.
9
+ *
10
+ * **The last group is the newest one with something still applied.** Group rows are forgotten as their
11
+ * migrations come down, so this is ordinarily the newest group recorded. A group whose rows outlived their
12
+ * ledger rows reads as `absent` and is passed over: naming it as where the environment stands would name a
13
+ * release that is no longer in the database.
14
+ */
15
+ async function readMigrationState(database, provider) {
16
+ const { pending, undeclared } = await readMigrationLedger(database, provider);
17
+ const applied = await appliedMigrationChain(migrationKysely(database));
18
+ return {
19
+ applied,
20
+ pending,
21
+ undeclared,
22
+ lastGroup: await lastGroupOf(database, applied)
23
+ };
24
+ }
25
+ /** Walk the groups newest first and stop at the first one this database still holds any of. */
26
+ async function lastGroupOf(database, applied) {
27
+ const held = new Set(applied);
28
+ const groups = await readMigrationGroups(database);
29
+ for (const group of [...groups].reverse()) {
30
+ const position = await readGroupPosition(database, group.group);
31
+ if (position.state === "absent") continue;
32
+ return {
33
+ group: group.group,
34
+ appliedAt: group.appliedAt,
35
+ migrations: group.migrations.filter((name) => held.has(name)),
36
+ position: position.state === "top" ? { state: "top" } : {
37
+ state: "buried",
38
+ above: position.above,
39
+ ungrouped: position.ungrouped
40
+ }
41
+ };
42
+ }
43
+ return null;
44
+ }
45
+ //#endregion
46
+ export { readMigrationState };
@@ -27,10 +27,19 @@
27
27
  * It lives in `core`, beside the rest of the naming rule, so the CLI and the facade compose one
28
28
  * implementation rather than two that drift.
29
29
  */
30
- /** A Cloudflare resource kind a feature can provision. All two characters — `MAX_FEATURE_KIND` in `./limits`. */
31
- export type FeatureResourceKind = "d1" | "kv" | "r2";
30
+ /** A Cloudflare resource kind Pithy names and creates — in a feature, and in every declared environment. */
31
+ export type FeatureResourceKind = "d1" | "kv" | "r2" | "queue";
32
32
  /** Every feature resource kind, for tests and for callers that enumerate. */
33
33
  export declare const FEATURE_RESOURCE_KINDS: readonly FeatureResourceKind[];
34
+ /**
35
+ * **The segment each kind ends a feature resource's name with — never more than `MAX_FEATURE_KIND` (#662).**
36
+ *
37
+ * `d1`, `kv` and `r2` spell themselves. A queue is `q`, and that is load-bearing: `MAX_FEATURE_KIND` is a term in
38
+ * `FEATURE_DERIVED_PROJECT_NAME`, so spelling it `queue` would take the project budget from 26 to 23 — and a
39
+ * project name is stable forever once anything is provisioned, so a smaller budget orphans every resource of a
40
+ * project already named against the old one. `feature.test.ts` fails on a segment longer than the budget.
41
+ */
42
+ export declare const FEATURE_KIND_SEGMENT: Readonly<Record<FeatureResourceKind, string>>;
34
43
  /** The identity of a feature — everything a feature name is derived from. */
35
44
  export interface FeatureIdentity {
36
45
  /** The project name, from the root `pithy.config.ts` (e.g. `acme`). */
@@ -126,10 +135,11 @@ export declare function maxFeatureSlug(parts: FeatureHead, shapes: readonly Feat
126
135
  */
127
136
  export declare function assertFeatureSlugFits(identity: FeatureIdentity, shapes: readonly FeatureNameShape[]): void;
128
137
  /**
129
- * The full Cloudflare resource name for a feature's binding — `<project>-f<issue>-<slug>--<binding>-<kind>`.
138
+ * The full Cloudflare resource name for a feature's binding — `<project>-f<issue>-<slug>--<binding>-<kind>`, the
139
+ * kind spelled by {@link FEATURE_KIND_SEGMENT}.
130
140
  *
131
- * Held to **R2's 63**, the strictest of the three kinds a feature provisions, so one shape is legal for
132
- * all of them: lowercase, hyphenated, alphanumeric at both ends. A D1 or KV name could be longer, but a
141
+ * Held to **R2's 63**, the strictest of the kinds a feature provisions and a queue's number too, so one shape is
142
+ * legal for all of them: lowercase, hyphenated, alphanumeric at both ends. A D1 or KV name could be longer, but a
133
143
  * feature that provisions a bucket and a database wants them recognizably the same name.
134
144
  */
135
145
  export declare function featureResourceName(identity: FeatureIdentity, binding: string, kind: FeatureResourceKind): string;
@@ -168,6 +178,18 @@ export declare function featureSecretEntryName(identity: FeatureIdentity, secret
168
178
  * and picks.
169
179
  */
170
180
  export declare function featureWorkerName(identity: FeatureIdentity, app: string): string;
181
+ /** The `<thing>` a feature's Artifacts namespace ends with, after {@link FEATURE_TAIL_SEPARATOR} (#673). */
182
+ export declare const FEATURE_ARTIFACTS_THING = "artifacts";
183
+ /**
184
+ * A feature's **Artifacts namespace** — `<project>-f<issue>-<slug>--artifacts` (#673).
185
+ *
186
+ * A declared environment's namespace is `<project>-<env>` and has no `<thing>`. A feature's needs one, because the
187
+ * separator and what follows it are what make a name a feature name: {@link isFeatureOwnedName} reads this one back
188
+ * as this feature's, so `pithy feature destroy` reaps the repos of exactly this branch and of no sibling. Held to the
189
+ * namespace's 63, the same number the rest of a feature's names are derived against, and `artifacts` is shorter than
190
+ * the shortest binding tail, so it moves no slug budget.
191
+ */
192
+ export declare function featureArtifactsNamespace(identity: FeatureIdentity): string;
171
193
  /**
172
194
  * The deployed name of one of a feature's **Workflows** — `<project>-f<issue>-<slug>--<capability>-<job>` (#643).
173
195
  *
@@ -1 +1 @@
1
- {"version":3,"file":"feature.d.ts","sourceRoot":"","sources":["../../src/naming/feature.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"feature.d.ts","sourceRoot":"","sources":["../../src/naming/feature.ts"],"names":[],"mappings":"AAQA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,4GAA4G;AAC5G,MAAM,MAAM,mBAAmB,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,OAAO,CAAC;AAE/D,6EAA6E;AAC7E,eAAO,MAAM,sBAAsB,EAAE,SAAS,mBAAmB,EAAgC,CAAC;AAElG;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,EAAE,QAAQ,CAAC,MAAM,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAK9E,CAAC;AAEF,6EAA6E;AAC7E,MAAM,WAAW,eAAe;IAC9B,uEAAuE;IACvE,OAAO,EAAE,MAAM,CAAC;IAChB,6FAA6F;IAC7F,KAAK,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,IAAI,EAAE,MAAM,CAAC;CACd;AAmBD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGpD;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAEzE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAQ1D;AASD,iGAAiG;AACjG,MAAM,WAAW,iBAAiB;IAChC,qCAAqC;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC;IACb,wGAAwG;IACxG,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI,CAcvE;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAQnF;AAED,yFAAyF;AACzF,MAAM,MAAM,WAAW,GAAG,IAAI,CAAC,eAAe,EAAE,SAAS,GAAG,OAAO,CAAC,CAAC;AAErE;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAExF;AAsDD,+EAA+E;AAC/E,MAAM,WAAW,gBAAgB;IAC/B,gEAAgE;IAChE,KAAK,EAAE,MAAM,CAAC;IACd,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,qFAAqF;IACrF,KAAK,EAAE,MAAM,CAAC;CACf;AAED,sHAAsH;AACtH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,SAAS,gBAAgB,EAAE,GAClC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAA;CAAE,CAWjD;AAED,oGAAoG;AACpG,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,gBAAgB,EAAE,GAAG,MAAM,CAE9F;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,eAAe,EAAE,MAAM,EAAE,SAAS,gBAAgB,EAAE,GAAG,IAAI,CAkB1G;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,mBAAmB,GAAG,MAAM,CASjH;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CASxF;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAShF;AAED,4GAA4G;AAC5G,eAAO,MAAM,uBAAuB,cAAc,CAAC;AAEnD;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,CAS3E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAStG;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAS1F"}
@@ -9,9 +9,24 @@ import { assertValidProjectName, kebab } from "./resource.js";
9
9
  const FEATURE_RESOURCE_KINDS = [
10
10
  "d1",
11
11
  "kv",
12
- "r2"
12
+ "r2",
13
+ "queue"
13
14
  ];
14
15
  /**
16
+ * **The segment each kind ends a feature resource's name with — never more than `MAX_FEATURE_KIND` (#662).**
17
+ *
18
+ * `d1`, `kv` and `r2` spell themselves. A queue is `q`, and that is load-bearing: `MAX_FEATURE_KIND` is a term in
19
+ * `FEATURE_DERIVED_PROJECT_NAME`, so spelling it `queue` would take the project budget from 26 to 23 — and a
20
+ * project name is stable forever once anything is provisioned, so a smaller budget orphans every resource of a
21
+ * project already named against the old one. `feature.test.ts` fails on a segment longer than the budget.
22
+ */
23
+ const FEATURE_KIND_SEGMENT = {
24
+ d1: "d1",
25
+ kv: "kv",
26
+ r2: "r2",
27
+ queue: "q"
28
+ };
29
+ /**
15
30
  * Refuse an issue number the budgets were not derived against.
16
31
  *
17
32
  * `MAX_ISSUE_DIGITS` is a term in {@link FEATURE_DERIVED_PROJECT_NAME}, so an eight-digit issue would
@@ -193,14 +208,15 @@ function assertFeatureSlugFits(identity, shapes) {
193
208
  });
194
209
  }
195
210
  /**
196
- * The full Cloudflare resource name for a feature's binding — `<project>-f<issue>-<slug>--<binding>-<kind>`.
211
+ * The full Cloudflare resource name for a feature's binding — `<project>-f<issue>-<slug>--<binding>-<kind>`, the
212
+ * kind spelled by {@link FEATURE_KIND_SEGMENT}.
197
213
  *
198
- * Held to **R2's 63**, the strictest of the three kinds a feature provisions, so one shape is legal for
199
- * all of them: lowercase, hyphenated, alphanumeric at both ends. A D1 or KV name could be longer, but a
214
+ * Held to **R2's 63**, the strictest of the kinds a feature provisions and a queue's number too, so one shape is
215
+ * legal for all of them: lowercase, hyphenated, alphanumeric at both ends. A D1 or KV name could be longer, but a
200
216
  * feature that provisions a bucket and a database wants them recognizably the same name.
201
217
  */
202
218
  function featureResourceName(identity, binding, kind) {
203
- return composeFeatureName(identity, kebab(identity.slug), kebab(binding), `-${kind}`, NAMESPACE_LIMITS.r2.maxLength, NAMESPACE_LIMITS.r2.label);
219
+ return composeFeatureName(identity, kebab(identity.slug), kebab(binding), `-${FEATURE_KIND_SEGMENT[kind]}`, NAMESPACE_LIMITS.r2.maxLength, NAMESPACE_LIMITS.r2.label);
204
220
  }
205
221
  /**
206
222
  * The CF Secrets Store entry name holding one of a feature's **environment-scoped** secrets —
@@ -241,6 +257,20 @@ function featureSecretEntryName(identity, secret) {
241
257
  function featureWorkerName(identity, app) {
242
258
  return composeFeatureName(identity, kebab(identity.slug), kebab(app), "", NAMESPACE_LIMITS.worker.maxLength, NAMESPACE_LIMITS.worker.label);
243
259
  }
260
+ /** The `<thing>` a feature's Artifacts namespace ends with, after {@link FEATURE_TAIL_SEPARATOR} (#673). */
261
+ const FEATURE_ARTIFACTS_THING = "artifacts";
262
+ /**
263
+ * A feature's **Artifacts namespace** — `<project>-f<issue>-<slug>--artifacts` (#673).
264
+ *
265
+ * A declared environment's namespace is `<project>-<env>` and has no `<thing>`. A feature's needs one, because the
266
+ * separator and what follows it are what make a name a feature name: {@link isFeatureOwnedName} reads this one back
267
+ * as this feature's, so `pithy feature destroy` reaps the repos of exactly this branch and of no sibling. Held to the
268
+ * namespace's 63, the same number the rest of a feature's names are derived against, and `artifacts` is shorter than
269
+ * the shortest binding tail, so it moves no slug budget.
270
+ */
271
+ function featureArtifactsNamespace(identity) {
272
+ return composeFeatureName(identity, kebab(identity.slug), FEATURE_ARTIFACTS_THING, "", NAMESPACE_LIMITS.artifactsNamespace.maxLength, NAMESPACE_LIMITS.artifactsNamespace.label);
273
+ }
244
274
  /**
245
275
  * The deployed name of one of a feature's **Workflows** — `<project>-f<issue>-<slug>--<capability>-<job>` (#643).
246
276
  *
@@ -266,4 +296,4 @@ function featureVectorizeIndexName(identity, thing) {
266
296
  return composeFeatureName(identity, kebab(identity.slug), kebab(thing), "", NAMESPACE_LIMITS.vectorizeIndex.maxLength, NAMESPACE_LIMITS.vectorizeIndex.label);
267
297
  }
268
298
  //#endregion
269
- export { FEATURE_RESOURCE_KINDS, assertFeatureProject, assertFeatureSlugFits, canonicalIssue, featureMarkerInProjectName, featureResourceName, featureSecretEntryName, featureSlugRoom, featureVectorizeIndexName, featureWorkerName, featureWorkflowName, isFeatureOwnedName, maxFeatureSlug, parseFeatureName, tightestFeatureShape };
299
+ export { FEATURE_ARTIFACTS_THING, FEATURE_KIND_SEGMENT, FEATURE_RESOURCE_KINDS, assertFeatureProject, assertFeatureSlugFits, canonicalIssue, featureArtifactsNamespace, featureMarkerInProjectName, featureResourceName, featureSecretEntryName, featureSlugRoom, featureVectorizeIndexName, featureWorkerName, featureWorkflowName, isFeatureOwnedName, maxFeatureSlug, parseFeatureName, tightestFeatureShape };