@pithy-sh/core 0.10.0 → 0.11.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 (180) 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/capability/bindings.d.ts +47 -2
  20. package/dist/capability/bindings.d.ts.map +1 -1
  21. package/dist/capability/bindings.js +117 -7
  22. package/dist/capability/capability.d.ts +98 -28
  23. package/dist/capability/capability.d.ts.map +1 -1
  24. package/dist/capability/manifest.d.ts +2 -1
  25. package/dist/capability/manifest.d.ts.map +1 -1
  26. package/dist/capability/manifest.js +1 -1
  27. package/dist/capability/settings.d.ts +86 -2
  28. package/dist/capability/settings.d.ts.map +1 -1
  29. package/dist/capability/settings.js +80 -1
  30. package/dist/cli/commandCatalog.generated.d.ts +184 -22
  31. package/dist/cli/commandCatalog.generated.d.ts.map +1 -1
  32. package/dist/cli/commandCatalog.generated.js +196 -22
  33. package/dist/cli/commandFacts.d.ts +1 -1
  34. package/dist/cli/commandFacts.d.ts.map +1 -1
  35. package/dist/cli/commandFacts.js +1 -0
  36. package/dist/controlPlane/capability.d.ts.map +1 -1
  37. package/dist/controlPlane/capability.js +12 -2
  38. package/dist/controlPlane/context.d.ts +5 -0
  39. package/dist/controlPlane/context.d.ts.map +1 -1
  40. package/dist/controlPlane/context.js +3 -1
  41. package/dist/controlPlane/data/connection.d.ts +47 -0
  42. package/dist/controlPlane/data/connection.d.ts.map +1 -1
  43. package/dist/controlPlane/data/connection.js +55 -1
  44. package/dist/controlPlane/http/cors.d.ts +4 -4
  45. package/dist/controlPlane/http/cors.js +4 -4
  46. package/dist/controlPlane/http/guard.d.ts +11 -0
  47. package/dist/controlPlane/http/guard.d.ts.map +1 -1
  48. package/dist/controlPlane/http/guard.js +17 -1
  49. package/dist/controlPlane/http/handlers.d.ts +60 -0
  50. package/dist/controlPlane/http/handlers.d.ts.map +1 -1
  51. package/dist/controlPlane/http/handlers.js +31 -2
  52. package/dist/controlPlane/http/readiness.d.ts +33 -0
  53. package/dist/controlPlane/http/readiness.d.ts.map +1 -0
  54. package/dist/controlPlane/http/readiness.js +39 -0
  55. package/dist/controlPlane/http/responses.d.ts +147 -0
  56. package/dist/controlPlane/http/responses.d.ts.map +1 -1
  57. package/dist/controlPlane/http/responses.js +61 -4
  58. package/dist/controlPlane/http/routes.d.ts +1 -0
  59. package/dist/controlPlane/http/routes.d.ts.map +1 -1
  60. package/dist/controlPlane/http/routes.js +17 -3
  61. package/dist/controlPlane/http/verify.d.ts.map +1 -1
  62. package/dist/controlPlane/http/verify.js +2 -1
  63. package/dist/controlPlane/migrationState/migrationState.d.ts +102 -0
  64. package/dist/controlPlane/migrationState/migrationState.d.ts.map +1 -0
  65. package/dist/controlPlane/migrationState/migrationState.js +142 -0
  66. package/dist/controlPlane/migrations/0003_connection_principal.d.ts +21 -0
  67. package/dist/controlPlane/migrations/0003_connection_principal.d.ts.map +1 -0
  68. package/dist/controlPlane/migrations/0003_connection_principal.js +33 -0
  69. package/dist/controlPlane/scope/scope.d.ts +13 -2
  70. package/dist/controlPlane/scope/scope.d.ts.map +1 -1
  71. package/dist/controlPlane/scope/scope.js +14 -3
  72. package/dist/createBackend.d.ts.map +1 -1
  73. package/dist/createBackend.js +7 -2
  74. package/dist/createEntrypoint.d.ts.map +1 -1
  75. package/dist/createEntrypoint.js +5 -3
  76. package/dist/entitlement/allowance.d.ts +25 -0
  77. package/dist/entitlement/allowance.d.ts.map +1 -0
  78. package/dist/entitlement/allowance.js +80 -0
  79. package/dist/entitlement/entitlement.d.ts +41 -0
  80. package/dist/entitlement/entitlement.d.ts.map +1 -1
  81. package/dist/entitlement/meter.d.ts +57 -0
  82. package/dist/entitlement/meter.d.ts.map +1 -0
  83. package/dist/entitlement/meter.js +13 -0
  84. package/dist/error/http.d.ts +144 -0
  85. package/dist/error/http.d.ts.map +1 -1
  86. package/dist/error/payload.d.ts +288 -0
  87. package/dist/error/payload.d.ts.map +1 -1
  88. package/dist/error/payload.js +100 -4
  89. package/dist/http/authContext.d.ts +7 -0
  90. package/dist/http/authContext.d.ts.map +1 -1
  91. package/dist/http/authContext.js +8 -1
  92. package/dist/http/scopeGates.d.ts +20 -0
  93. package/dist/http/scopeGates.d.ts.map +1 -0
  94. package/dist/http/scopeGates.js +36 -0
  95. package/dist/index.d.ts +1 -1
  96. package/dist/index.d.ts.map +1 -1
  97. package/dist/index.js +2 -2
  98. package/dist/logger/record.d.ts +84 -0
  99. package/dist/logger/record.d.ts.map +1 -1
  100. package/dist/migrations/capabilitySets.d.ts +23 -0
  101. package/dist/migrations/capabilitySets.d.ts.map +1 -0
  102. package/dist/migrations/capabilitySets.js +74 -0
  103. package/dist/migrations/registry.d.ts +26 -0
  104. package/dist/migrations/registry.d.ts.map +1 -1
  105. package/dist/migrations/registry.js +38 -7
  106. package/dist/migrations/state.d.ts +60 -0
  107. package/dist/migrations/state.d.ts.map +1 -0
  108. package/dist/migrations/state.js +46 -0
  109. package/dist/naming/feature.d.ts +27 -5
  110. package/dist/naming/feature.d.ts.map +1 -1
  111. package/dist/naming/feature.js +36 -6
  112. package/dist/naming/limits.d.ts +47 -3
  113. package/dist/naming/limits.d.ts.map +1 -1
  114. package/dist/naming/limits.js +47 -3
  115. package/dist/naming/provisionScope.d.ts +6 -0
  116. package/dist/naming/provisionScope.d.ts.map +1 -1
  117. package/dist/naming/provisionScope.js +8 -3
  118. package/dist/naming/resourceNames.d.ts +21 -2
  119. package/dist/naming/resourceNames.d.ts.map +1 -1
  120. package/dist/naming/resourceNames.js +21 -3
  121. package/dist/seed/writeD1.d.ts +7 -1
  122. package/dist/seed/writeD1.d.ts.map +1 -1
  123. package/dist/seed/writeD1.js +9 -3
  124. package/dist/seed/writeKv.d.ts +2 -0
  125. package/dist/seed/writeKv.d.ts.map +1 -1
  126. package/dist/seed/writeKv.js +6 -2
  127. package/dist/version.generated.d.ts +1 -1
  128. package/dist/version.generated.js +1 -1
  129. package/dist/worker/serviceLoopback.d.ts +36 -0
  130. package/dist/worker/serviceLoopback.d.ts.map +1 -0
  131. package/dist/worker/serviceLoopback.js +74 -0
  132. package/dist/workflow/faults.d.ts +12 -1
  133. package/dist/workflow/faults.d.ts.map +1 -1
  134. package/dist/workflow/faults.js +22 -15
  135. package/package.json +2 -2
  136. package/src/access/access.ts +298 -0
  137. package/src/access/acting.ts +59 -0
  138. package/src/access/policyCheck.ts +77 -0
  139. package/src/access/usage.ts +141 -0
  140. package/src/access/visibility.ts +109 -0
  141. package/src/audit/auditEvent.ts +4 -0
  142. package/src/capability/bindings.ts +159 -13
  143. package/src/capability/capability.ts +99 -29
  144. package/src/capability/settings.ts +146 -2
  145. package/src/cli/commandCatalog.generated.ts +212 -22
  146. package/src/cli/commandFacts.ts +1 -0
  147. package/src/controlPlane/capability.ts +25 -1
  148. package/src/controlPlane/context.ts +4 -0
  149. package/src/controlPlane/data/connection.ts +90 -0
  150. package/src/controlPlane/http/cors.ts +4 -4
  151. package/src/controlPlane/http/guard.ts +21 -0
  152. package/src/controlPlane/http/handlers.ts +41 -0
  153. package/src/controlPlane/http/readiness.ts +50 -0
  154. package/src/controlPlane/http/responses.ts +140 -2
  155. package/src/controlPlane/http/routes.ts +25 -1
  156. package/src/controlPlane/http/verify.ts +2 -0
  157. package/src/controlPlane/migrationState/migrationState.ts +200 -0
  158. package/src/controlPlane/migrations/0003_connection_principal.ts +37 -0
  159. package/src/controlPlane/scope/scope.ts +14 -2
  160. package/src/createBackend.ts +24 -6
  161. package/src/createEntrypoint.ts +9 -3
  162. package/src/entitlement/allowance.ts +85 -0
  163. package/src/entitlement/entitlement.ts +38 -0
  164. package/src/entitlement/meter.ts +69 -0
  165. package/src/error/payload.ts +211 -4
  166. package/src/http/authContext.ts +13 -1
  167. package/src/http/scopeGates.ts +34 -0
  168. package/src/index.ts +1 -0
  169. package/src/migrations/capabilitySets.ts +87 -0
  170. package/src/migrations/registry.ts +46 -2
  171. package/src/migrations/state.ts +94 -0
  172. package/src/naming/feature.ts +46 -7
  173. package/src/naming/limits.ts +35 -3
  174. package/src/naming/provisionScope.ts +19 -1
  175. package/src/naming/resourceNames.ts +52 -5
  176. package/src/seed/writeD1.ts +13 -4
  177. package/src/seed/writeKv.ts +6 -2
  178. package/src/version.generated.ts +1 -1
  179. package/src/worker/serviceLoopback.ts +105 -0
  180. package/src/workflow/faults.ts +51 -24
@@ -242,6 +242,16 @@ const TurnstileConfigPublic = z.object({
242
242
  status: z.literal(500).describe("Internal Server Error — a configuration fault, not a client request."),
243
243
  ...publicFields
244
244
  }).describe("The Turnstile middleware is misconfigured (500).");
245
+ const TurnstileAnalyticsWindowPublic = z.object({
246
+ code: z.literal("turnstile/analytics_window").describe("The requested challenge window reaches past what Cloudflare keeps, or is wider than one read may ask for."),
247
+ status: z.literal(400).describe("Bad Request — narrow the window or move it later."),
248
+ ...publicFields
249
+ }).describe("A Turnstile-analytics window outside Cloudflare's retention or range (400).");
250
+ const TurnstileAnalyticsUnavailablePublic = z.object({
251
+ code: z.literal("turnstile/analytics_unavailable").describe("Cloudflare does not serve Turnstile analytics to this account, so no outcome can be read."),
252
+ status: z.literal(409).describe("Conflict — the account's configuration rules the read out."),
253
+ ...publicFields
254
+ }).describe("Turnstile analytics are not served to this account (409).");
245
255
  const AuditInvalidEventPublic = z.object({
246
256
  code: z.literal("audit/invalid_event").describe("An audit event failed validation against the AuditEvent schema (bad action, outcome, or actor)."),
247
257
  status: z.literal(400).describe("Bad Request — the event did not match its schema."),
@@ -257,6 +267,11 @@ const MediaNotFoundPublic = z.object({
257
267
  status: z.literal(404).describe("Not Found."),
258
268
  ...publicFields
259
269
  }).describe("A requested media record does not exist (404).");
270
+ const MediaForbiddenPublic = z.object({
271
+ code: z.literal("media/forbidden").describe("The caller may read the media record but not change it: only its owner, acting for the record's organization, or a role holding `media:manage` there may edit, delete or re-scope it."),
272
+ status: z.literal(403).describe("Forbidden."),
273
+ ...publicFields
274
+ }).describe("The caller may read the record but not change it (403).");
260
275
  const MediaUnsupportedPublic = z.object({
261
276
  code: z.literal("media/unsupported").describe("The media type, extension, or backend is not supported for the requested operation."),
262
277
  status: z.literal(400).describe("Bad Request — the operation does not apply to this media."),
@@ -272,6 +287,16 @@ const MediaEnrichmentFailedPublic = z.object({
272
287
  status: z.literal(500).describe("Internal Server Error — an enrichment step could not complete."),
273
288
  ...publicFields
274
289
  }).describe("A media AI-enrichment step failed (500).");
290
+ const MediaAnalyticsWindowPublic = z.object({
291
+ code: z.literal("media/analytics_window").describe("The requested delivery window reaches past what Cloudflare keeps, or is wider than one read may ask for."),
292
+ status: z.literal(400).describe("Bad Request — narrow the window or move it later."),
293
+ ...publicFields
294
+ }).describe("A delivery-analytics window outside Cloudflare's retention or range (400).");
295
+ const MediaAnalyticsUnavailablePublic = z.object({
296
+ code: z.literal("media/analytics_unavailable").describe("Cloudflare does not serve the delivery analytics this read needs to this account — a Customer Metadata Boundary outside the US carries none of them."),
297
+ status: z.literal(409).describe("Conflict — the account's configuration rules the read out."),
298
+ ...publicFields
299
+ }).describe("Delivery analytics are not served to this account (409).");
275
300
  const LeaderboardBoardNotFoundPublic = z.object({
276
301
  code: z.literal("leaderboard/board_not_found").describe("No board with that key is configured. Board keys come from `pithy.config.ts`, not from the database."),
277
302
  status: z.literal(404).describe("Not Found."),
@@ -438,17 +463,17 @@ const MatchmakingNotQueuedPublic = z.object({
438
463
  ...publicFields
439
464
  }).describe("The player is not in the queue (404).");
440
465
  const StorageNotFoundPublic = z.object({
441
- code: z.literal("storage/not_found").describe("No object row matches, or the object exists but is not visible to this caller."),
466
+ code: z.literal("storage/not_found").describe("No object row matches, or the object exists but this caller may not read it."),
442
467
  status: z.literal(404).describe("Not Found."),
443
468
  ...publicFields
444
469
  }).describe("The object does not exist, or is not visible to the caller (404).");
445
470
  const StorageForbiddenPublic = z.object({
446
- code: z.literal("storage/forbidden").describe("The caller does not own the object and the object is not public."),
471
+ code: z.literal("storage/forbidden").describe("The caller may read the object but not change it: only its owner, acting for the object's organization, or a role holding `storage:manage` there may."),
447
472
  status: z.literal(403).describe("Forbidden."),
448
473
  ...publicFields
449
- }).describe("The caller is not the owner and the object is not public (403).");
474
+ }).describe("The caller may read the object but not change it (403).");
450
475
  const StorageQuotaExceededPublic = z.object({
451
- code: z.literal("storage/quota_exceeded").describe("The declared upload size would push the owner past their byte quota. Checked at init against pending and stored rows together, so concurrent inits cannot race past the limit."),
476
+ code: z.literal("storage/quota_exceeded").describe("The declared upload size would push the owner past the configured `bytesPerOwner` quota. Checked at init against pending and stored rows together, so concurrent inits cannot race past the limit. Where the `storageBytes` meter is enforced, the same refusal is `payments/quota_exceeded` (402)."),
452
477
  status: z.literal(413).describe("Content Too Large."),
453
478
  ...publicFields
454
479
  }).describe("The upload would exceed the owner's byte quota (413).");
@@ -582,6 +607,16 @@ const PaymentsSubscriptionChangeRefusedPublic = z.object({
582
607
  status: z.literal(409).describe("Conflict — the subscription's present state forbids the change asked of it."),
583
608
  ...publicFields
584
609
  }).describe("A subscription's state forbids the change asked of it (409).");
610
+ const PaymentsOverrideStalePublic = z.object({
611
+ code: z.literal("payments/override_stale").describe("A control-plane override write named a version behind the subject's override row. Two operators share one customer: both opened the same allowance, and the second would silently overwrite the first while the first believed they had succeeded. So the write is refused and nothing is written. 409, because the request is well-formed and the same request against the version it now holds would be accepted — the caller re-reads, and tries again."),
612
+ status: z.literal(409).describe("Conflict — the override changed since the caller read it."),
613
+ ...publicFields
614
+ }).describe("An override write named a stale version (409).");
615
+ const PaymentsQuotaExceededPublic = z.object({
616
+ code: z.literal("payments/quota_exceeded").describe("An enforced kit meter — emails enqueued, bytes stored, media items held — would pass the subject's effective quota for the period. Nothing was written. 402 Payment Required, because it is the honest status: the plan does not allow more and the remedy is a purchase, a bigger tier, or a number pinned for this subject from the control plane. A meter refuses and never bills — the kit does not sell overage."),
617
+ status: z.literal(402).describe("Payment Required — the plan's allowance for this period is used up."),
618
+ ...publicFields
619
+ }).describe("An enforced meter is at its quota for this period (402).");
585
620
  const ControlPlaneNotConnectedPublic = z.object({
586
621
  code: z.literal("controlplane/not_connected").describe("No management-client connection is registered for this environment. The shipped default: the seam is present and denies everything until the adopter provisions a credential. There is no backdoor."),
587
622
  status: z.literal(403).describe("Forbidden."),
@@ -642,6 +677,31 @@ const OrganizationNominationInvalidPublic = z.object({
642
677
  status: z.literal(400).describe("Bad Request — no offer this caller may accept."),
643
678
  ...publicFields
644
679
  }).describe("No standing ownership offer this caller may accept (400).");
680
+ const ArtifactsNotFoundPublic = z.object({
681
+ code: z.literal("artifacts/not_found").describe("No such repository, **or** one the caller holds no grant on and does not own. One code for both, byte for byte: a repository's name is in its clone URL, and a refusal that told the two apart would hand it over. Also what the credential route answers for a repository with remote access off. Which it was survives in `detail`, which the HTTP codec strips."),
682
+ status: z.literal(404).describe("Not Found."),
683
+ ...publicFields
684
+ }).describe("No such repository, or none this caller may see — the same answer for both (404).");
685
+ const ArtifactsForbiddenPublic = z.object({
686
+ code: z.literal("artifacts/forbidden").describe("The caller can see the repository and may not do this to it: a grant holder asking for what only an owner may do, or a member of an organization whose role lacks the configured `managePower`. A 403 rather than the 404 above, because by then the caller already knows the repository exists."),
687
+ status: z.literal(403).describe("Forbidden."),
688
+ ...publicFields
689
+ }).describe("The caller may see this repository and may not do this (403).");
690
+ const ArtifactsInvalidNamePublic = z.object({
691
+ code: z.literal("artifacts/invalid_name").describe("A supplied repository name is outside Cloudflare's charset or the 2-to-63 cap. Refused and never shortened: a name somebody chose is in every clone URL, and a silently different one is a repository nobody can find."),
692
+ status: z.literal(400).describe("Bad Request."),
693
+ ...publicFields
694
+ }).describe("A repository name breaks the naming rule (400).");
695
+ const ArtifactsNameTakenPublic = z.object({
696
+ code: z.literal("artifacts/name_taken").describe("The owner already holds a repository with that display name, or a supplied physical name is already in the namespace. The second happens only where the adopter supplies names, so uniqueness is theirs."),
697
+ status: z.literal(409).describe("Conflict."),
698
+ ...publicFields
699
+ }).describe("That repository name is already in use (409).");
700
+ const ArtifactsInvalidConfigPublic = z.object({
701
+ code: z.literal("artifacts/invalid_config").describe("The capability's configuration cannot be honored: a `tokenTtlSeconds` above the ceiling, or `organization` composed with no `managePower` named. Raised at boot, so a mistake fails on deploy rather than at the first mint."),
702
+ status: z.literal(400).describe("Bad Request — the configuration is malformed."),
703
+ ...publicFields
704
+ }).describe("The artifacts configuration is invalid (400).");
645
705
  const SupportNotFoundPublic = z.object({
646
706
  code: z.literal("support/not_found").describe("A requested support thread, message, or attachment does not exist."),
647
707
  status: z.literal(404).describe("Not Found."),
@@ -766,12 +826,17 @@ const KitPublicErrorPayload = z.discriminatedUnion("code", [
766
826
  TurnstileMissingTokenPublic,
767
827
  TurnstileFailedPublic,
768
828
  TurnstileConfigPublic,
829
+ TurnstileAnalyticsWindowPublic,
830
+ TurnstileAnalyticsUnavailablePublic,
769
831
  AuditInvalidEventPublic,
770
832
  AuditWriteFailedPublic,
771
833
  MediaNotFoundPublic,
834
+ MediaForbiddenPublic,
772
835
  MediaUnsupportedPublic,
773
836
  MediaStorageFailedPublic,
774
837
  MediaEnrichmentFailedPublic,
838
+ MediaAnalyticsWindowPublic,
839
+ MediaAnalyticsUnavailablePublic,
775
840
  LeaderboardBoardNotFoundPublic,
776
841
  LeaderboardEntryNotFoundPublic,
777
842
  LeaderboardScoreRejectedPublic,
@@ -834,6 +899,8 @@ const KitPublicErrorPayload = z.discriminatedUnion("code", [
834
899
  PaymentsEntitlementNotInCatalogPublic,
835
900
  PaymentsSubjectUnresolvedPublic,
836
901
  PaymentsSubscriptionChangeRefusedPublic,
902
+ PaymentsOverrideStalePublic,
903
+ PaymentsQuotaExceededPublic,
837
904
  ControlPlaneNotConnectedPublic,
838
905
  ControlPlaneInvalidCredentialPublic,
839
906
  ControlPlaneInsufficientScopePublic,
@@ -846,6 +913,11 @@ const KitPublicErrorPayload = z.discriminatedUnion("code", [
846
913
  OrganizationLastAdministratorPublic,
847
914
  OrganizationSlugTakenPublic,
848
915
  OrganizationNominationInvalidPublic,
916
+ ArtifactsNotFoundPublic,
917
+ ArtifactsForbiddenPublic,
918
+ ArtifactsInvalidNamePublic,
919
+ ArtifactsNameTakenPublic,
920
+ ArtifactsInvalidConfigPublic,
849
921
  SupportNotFoundPublic,
850
922
  SupportInvalidCategoryPublic,
851
923
  SupportUnparseableMessagePublic,
@@ -900,12 +972,17 @@ const EmailSendFailed = EmailSendFailedPublic.extend(internalFields).describe(Em
900
972
  const TurnstileMissingToken = TurnstileMissingTokenPublic.extend(internalFields).describe(TurnstileMissingTokenPublic.description ?? "");
901
973
  const TurnstileFailed = TurnstileFailedPublic.extend(internalFields).describe(TurnstileFailedPublic.description ?? "");
902
974
  const TurnstileConfig = TurnstileConfigPublic.extend(internalFields).describe(TurnstileConfigPublic.description ?? "");
975
+ const TurnstileAnalyticsWindow = TurnstileAnalyticsWindowPublic.extend(internalFields).describe(TurnstileAnalyticsWindowPublic.description ?? "");
976
+ const TurnstileAnalyticsUnavailable = TurnstileAnalyticsUnavailablePublic.extend(internalFields).describe(TurnstileAnalyticsUnavailablePublic.description ?? "");
903
977
  const AuditInvalidEvent = AuditInvalidEventPublic.extend(internalFields).describe(AuditInvalidEventPublic.description ?? "");
904
978
  const AuditWriteFailed = AuditWriteFailedPublic.extend(internalFields).describe(AuditWriteFailedPublic.description ?? "");
905
979
  const MediaNotFound = MediaNotFoundPublic.extend(internalFields).describe(MediaNotFoundPublic.description ?? "");
980
+ const MediaForbidden = MediaForbiddenPublic.extend(internalFields).describe(MediaForbiddenPublic.description ?? "");
906
981
  const MediaUnsupported = MediaUnsupportedPublic.extend(internalFields).describe(MediaUnsupportedPublic.description ?? "");
907
982
  const MediaStorageFailed = MediaStorageFailedPublic.extend(internalFields).describe(MediaStorageFailedPublic.description ?? "");
908
983
  const MediaEnrichmentFailed = MediaEnrichmentFailedPublic.extend(internalFields).describe(MediaEnrichmentFailedPublic.description ?? "");
984
+ const MediaAnalyticsWindow = MediaAnalyticsWindowPublic.extend(internalFields).describe(MediaAnalyticsWindowPublic.description ?? "");
985
+ const MediaAnalyticsUnavailable = MediaAnalyticsUnavailablePublic.extend(internalFields).describe(MediaAnalyticsUnavailablePublic.description ?? "");
909
986
  const LeaderboardBoardNotFound = LeaderboardBoardNotFoundPublic.extend(internalFields).describe(LeaderboardBoardNotFoundPublic.description ?? "");
910
987
  const LeaderboardEntryNotFound = LeaderboardEntryNotFoundPublic.extend(internalFields).describe(LeaderboardEntryNotFoundPublic.description ?? "");
911
988
  const LeaderboardScoreRejected = LeaderboardScoreRejectedPublic.extend(internalFields).describe(LeaderboardScoreRejectedPublic.description ?? "");
@@ -968,6 +1045,8 @@ const PaymentsDiscountInvalid = PaymentsDiscountInvalidPublic.extend(internalFie
968
1045
  const PaymentsEntitlementNotInCatalog = PaymentsEntitlementNotInCatalogPublic.extend(internalFields).describe(PaymentsEntitlementNotInCatalogPublic.description ?? "");
969
1046
  const PaymentsSubjectUnresolved = PaymentsSubjectUnresolvedPublic.extend(internalFields).describe(PaymentsSubjectUnresolvedPublic.description ?? "");
970
1047
  const PaymentsSubscriptionChangeRefused = PaymentsSubscriptionChangeRefusedPublic.extend(internalFields).describe(PaymentsSubscriptionChangeRefusedPublic.description ?? "");
1048
+ const PaymentsOverrideStale = PaymentsOverrideStalePublic.extend(internalFields).describe(PaymentsOverrideStalePublic.description ?? "");
1049
+ const PaymentsQuotaExceeded = PaymentsQuotaExceededPublic.extend(internalFields).describe(PaymentsQuotaExceededPublic.description ?? "");
971
1050
  const ControlPlaneNotConnected = ControlPlaneNotConnectedPublic.extend(internalFields).describe(ControlPlaneNotConnectedPublic.description ?? "");
972
1051
  const ControlPlaneInvalidCredential = ControlPlaneInvalidCredentialPublic.extend(internalFields).describe(ControlPlaneInvalidCredentialPublic.description ?? "");
973
1052
  const ControlPlaneInsufficientScope = ControlPlaneInsufficientScopePublic.extend(internalFields).describe(ControlPlaneInsufficientScopePublic.description ?? "");
@@ -990,6 +1069,11 @@ const OrganizationInvitationInvalid = OrganizationInvitationInvalidPublic.extend
990
1069
  const OrganizationLastAdministrator = OrganizationLastAdministratorPublic.extend(internalFields).describe(OrganizationLastAdministratorPublic.description ?? "");
991
1070
  const OrganizationSlugTaken = OrganizationSlugTakenPublic.extend(internalFields).describe(OrganizationSlugTakenPublic.description ?? "");
992
1071
  const OrganizationNominationInvalid = OrganizationNominationInvalidPublic.extend(internalFields).describe(OrganizationNominationInvalidPublic.description ?? "");
1072
+ const ArtifactsNotFound = ArtifactsNotFoundPublic.extend(internalFields).describe(ArtifactsNotFoundPublic.description ?? "");
1073
+ const ArtifactsForbidden = ArtifactsForbiddenPublic.extend(internalFields).describe(ArtifactsForbiddenPublic.description ?? "");
1074
+ const ArtifactsInvalidName = ArtifactsInvalidNamePublic.extend(internalFields).describe(ArtifactsInvalidNamePublic.description ?? "");
1075
+ const ArtifactsNameTaken = ArtifactsNameTakenPublic.extend(internalFields).describe(ArtifactsNameTakenPublic.description ?? "");
1076
+ const ArtifactsInvalidConfig = ArtifactsInvalidConfigPublic.extend(internalFields).describe(ArtifactsInvalidConfigPublic.description ?? "");
993
1077
  const SupportNotFound = SupportNotFoundPublic.extend(internalFields).describe(SupportNotFoundPublic.description ?? "");
994
1078
  const SupportInvalidCategory = SupportInvalidCategoryPublic.extend(internalFields).describe(SupportInvalidCategoryPublic.description ?? "");
995
1079
  const SupportUnparseableMessage = SupportUnparseableMessagePublic.extend(internalFields).describe(SupportUnparseableMessagePublic.description ?? "");
@@ -1039,12 +1123,17 @@ const KitErrorPayload = z.discriminatedUnion("code", [
1039
1123
  TurnstileMissingToken,
1040
1124
  TurnstileFailed,
1041
1125
  TurnstileConfig,
1126
+ TurnstileAnalyticsWindow,
1127
+ TurnstileAnalyticsUnavailable,
1042
1128
  AuditInvalidEvent,
1043
1129
  AuditWriteFailed,
1044
1130
  MediaNotFound,
1131
+ MediaForbidden,
1045
1132
  MediaUnsupported,
1046
1133
  MediaStorageFailed,
1047
1134
  MediaEnrichmentFailed,
1135
+ MediaAnalyticsWindow,
1136
+ MediaAnalyticsUnavailable,
1048
1137
  LeaderboardBoardNotFound,
1049
1138
  LeaderboardEntryNotFound,
1050
1139
  LeaderboardScoreRejected,
@@ -1107,6 +1196,8 @@ const KitErrorPayload = z.discriminatedUnion("code", [
1107
1196
  PaymentsEntitlementNotInCatalog,
1108
1197
  PaymentsSubjectUnresolved,
1109
1198
  PaymentsSubscriptionChangeRefused,
1199
+ PaymentsOverrideStale,
1200
+ PaymentsQuotaExceeded,
1110
1201
  ControlPlaneNotConnected,
1111
1202
  ControlPlaneInvalidCredential,
1112
1203
  ControlPlaneInsufficientScope,
@@ -1119,6 +1210,11 @@ const KitErrorPayload = z.discriminatedUnion("code", [
1119
1210
  OrganizationLastAdministrator,
1120
1211
  OrganizationSlugTaken,
1121
1212
  OrganizationNominationInvalid,
1213
+ ArtifactsNotFound,
1214
+ ArtifactsForbidden,
1215
+ ArtifactsInvalidName,
1216
+ ArtifactsNameTaken,
1217
+ ArtifactsInvalidConfig,
1122
1218
  SupportNotFound,
1123
1219
  SupportInvalidCategory,
1124
1220
  SupportUnparseableMessage,
@@ -12,6 +12,13 @@ import { z } from "zod";
12
12
  * stored choice outrank their device's `Accept-Language`. That link read `c.var.locale` at first,
13
13
  * which nothing ever wrote, so it silently contributed nothing and the chain degraded to
14
14
  * `param → cookie → header → default` while the docs said otherwise.
15
+ *
16
+ * **`scopes` answers one question: did this request come from the adopter's own trusted backend, or from a
17
+ * device?** The name reads like a grant to the user, and it is not one. Tenancy roles went to `c.var.acting`
18
+ * with `@pithy-sh/organization`; management access is the control plane's, with its own credential and its
19
+ * own scope type. What is left is an attestation about the caller, which `@pithy-sh/auth`'s `resolveScopes`
20
+ * makes on every request from something the request proves — a service binding, a verified client
21
+ * certificate, a short-lived token bound to this origin — and never from anything it merely says (#560).
15
22
  */
16
23
  export declare const AuthContext: z.ZodObject<{
17
24
  userId: z.ZodString;
@@ -1 +1 @@
1
- {"version":3,"file":"authContext.d.ts","sourceRoot":"","sources":["../../src/http/authContext.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,WAAW;;;;;;iBAeiE,CAAC;AAC1F,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC"}
1
+ {"version":3,"file":"authContext.d.ts","sourceRoot":"","sources":["../../src/http/authContext.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,WAAW;;;;;;iBAoBiE,CAAC;AAC1F,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC"}
@@ -14,11 +14,18 @@ import { z } from "zod";
14
14
  * stored choice outrank their device's `Accept-Language`. That link read `c.var.locale` at first,
15
15
  * which nothing ever wrote, so it silently contributed nothing and the chain degraded to
16
16
  * `param → cookie → header → default` while the docs said otherwise.
17
+ *
18
+ * **`scopes` answers one question: did this request come from the adopter's own trusted backend, or from a
19
+ * device?** The name reads like a grant to the user, and it is not one. Tenancy roles went to `c.var.acting`
20
+ * with `@pithy-sh/organization`; management access is the control plane's, with its own credential and its
21
+ * own scope type. What is left is an attestation about the caller, which `@pithy-sh/auth`'s `resolveScopes`
22
+ * makes on every request from something the request proves — a service binding, a verified client
23
+ * certificate, a short-lived token bound to this origin — and never from anything it merely says (#560).
17
24
  */
18
25
  const AuthContext = z.object({
19
26
  userId: z.string().describe("ID of the authenticated user (populated by @pithy-sh/auth)."),
20
27
  sessionId: z.string().describe("ID of the active session this request belongs to."),
21
- scopes: z.array(z.string()).default([]).describe("Permission scopes granted to this session."),
28
+ scopes: z.array(z.string()).default([]).describe("Attestations about the caller, not permissions granted to the user: what this request's caller was established to be — the adopter's own trusted backend, say — by the `resolveScopes` seam on `@pithy-sh/auth`, resolved per request and never cached. Empty when no resolver is configured. Tenancy roles are not here; they are `c.var.acting`."),
22
29
  locale: Locale.nullish().describe("The reader's own stored language, from `pithy_auth_users.locale`; null or absent when they have never chosen."),
23
30
  authenticatedAt: z.date().nullish().describe("When the holder of this session last actually authenticated — a sign-in, never a rotation, which carries the value forward unchanged. It rides here for the same reason `locale` does: the session lookup has already loaded the row, so a gate that needs it should not pay for a second read. Distinct from the session's own `createdAt`, which `/token/rotate` stamps fresh and is therefore resettable by anyone holding a stolen refresh token. Null or absent for a session written before the column existed, and for a Worker whose auth capability predates it.")
24
31
  }).describe("Per-request authenticated identity; the seam other capabilities depend on.");
@@ -0,0 +1,20 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { Hono } from "hono";
4
+ import type { PithyHonoEnv, ScopeGate } from "../capability/capability";
5
+ /**
6
+ * **A capability's scope gates, read off its own routes rather than listed beside them (#560).**
7
+ *
8
+ * `Capability.scopeGates` is what `pithy doctor` names when nothing composed fills `AuthContext.scopes`.
9
+ * A list written out by hand next to the routes is a second copy of something the routes already say: every
10
+ * gated route carries `describeRoute({ scopes })`, because that is how the OpenAPI contract learns it. So the
11
+ * registrar is run against a throwaway app and the specs are read back — the same walk `openApiDocument`
12
+ * makes over a composed app, and a route whose scope changes changes what this returns with it.
13
+ *
14
+ * **Only `bearer` and `session` routes.** Those are the strategies `requireAuth()` takes, so a scope on one
15
+ * is an `AuthContext` scope. A `control-plane` route also names scopes, and they are a different credential
16
+ * entirely — a signed management token with its own type, never `c.var.auth` — so naming one here would tell
17
+ * an adopter to configure a resolver that could never open it.
18
+ */
19
+ export declare function scopeGatesOf(register: (app: Hono<PithyHonoEnv>) => void): ScopeGate[];
20
+ //# sourceMappingURL=scopeGates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scopeGates.d.ts","sourceRoot":"","sources":["../../src/http/scopeGates.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAGxE;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,IAAI,CAAC,YAAY,CAAC,KAAK,IAAI,GAAG,SAAS,EAAE,CAYrF"}
@@ -0,0 +1,36 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { routeSpecOf } from "./routeSpec.js";
4
+ import { Hono } from "hono";
5
+ //#region src/http/scopeGates.ts
6
+ /**
7
+ * **A capability's scope gates, read off its own routes rather than listed beside them (#560).**
8
+ *
9
+ * `Capability.scopeGates` is what `pithy doctor` names when nothing composed fills `AuthContext.scopes`.
10
+ * A list written out by hand next to the routes is a second copy of something the routes already say: every
11
+ * gated route carries `describeRoute({ scopes })`, because that is how the OpenAPI contract learns it. So the
12
+ * registrar is run against a throwaway app and the specs are read back — the same walk `openApiDocument`
13
+ * makes over a composed app, and a route whose scope changes changes what this returns with it.
14
+ *
15
+ * **Only `bearer` and `session` routes.** Those are the strategies `requireAuth()` takes, so a scope on one
16
+ * is an `AuthContext` scope. A `control-plane` route also names scopes, and they are a different credential
17
+ * entirely — a signed management token with its own type, never `c.var.auth` — so naming one here would tell
18
+ * an adopter to configure a resolver that could never open it.
19
+ */
20
+ function scopeGatesOf(register) {
21
+ const app = new Hono();
22
+ register(app);
23
+ const gates = [];
24
+ for (const route of app.routes) {
25
+ const spec = routeSpecOf(route.handler);
26
+ if (!spec?.scopes || spec.scopes.length === 0) continue;
27
+ if (![spec.verification].flat().some((strategy) => strategy === "bearer" || strategy === "session")) continue;
28
+ for (const scope of spec.scopes) gates.push({
29
+ scope,
30
+ route: `${route.method} ${route.path}`
31
+ });
32
+ }
33
+ return gates;
34
+ }
35
+ //#endregion
36
+ export { scopeGatesOf };
package/dist/index.d.ts CHANGED
@@ -28,7 +28,7 @@ export { type AdminRouteDrift, missingAdminRoutes } from "./controlPlane/discove
28
28
  export { type CapabilityHealth, type CapabilityHealthInput, type CapabilityHealthSource, capabilityHealthSources, defineCapabilityHealth, readCapabilityHealth, } from "./controlPlane/discovery/health";
29
29
  export { CapabilityHealthReport, type CapabilityHealthWire, HealthCountNominal, HealthNominal, type HealthStanding, HealthSummary, HealthSummaryKey, HealthSummaryValue, HealthValueCost, HealthValueKind, healthAttention, healthReport, healthWire, type NamedHealthValue, namedHealthValues, standingOf, } from "./controlPlane/discovery/healthSummary";
30
30
  export { requireControlPlane } from "./controlPlane/http/guard";
31
- export { ANY_VERIFIED_CALLER, type ControlPlaneRequirement, ControlPlaneScope, KEYS_ROTATE_SCOPE, MANIFEST_READ_SCOPE, SEAM_SCOPES, } from "./controlPlane/scope/scope";
31
+ export { ANY_VERIFIED_CALLER, type ControlPlaneRequirement, ControlPlaneScope, KEYS_ROTATE_SCOPE, MANIFEST_READ_SCOPE, MIGRATIONS_READ_SCOPE, SEAM_SCOPES, } from "./controlPlane/scope/scope";
32
32
  export { exportPublicJwk, type MintControlPlaneToken, mintControlPlaneToken } from "./controlPlane/token/mint";
33
33
  export { CONTROL_PLANE_HEADER, CONTROL_PLANE_VERSION_CREATED_HEADER, CONTROL_PLANE_VERSION_HEADER, type WorkerBuild, workerBuildChanged, } from "./controlPlane/wire";
34
34
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,KAAK,uBAAuB,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AACtG,OAAO,EACL,wBAAwB,EACxB,4BAA4B,EAC5B,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,YAAY,EACZ,wBAAwB,GACzB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,kBAAkB,EAAE,KAAK,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAChG,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AACzG,OAAO,EACL,+BAA+B,EAC/B,KAAK,oBAAoB,EACzB,oBAAoB,GACrB,MAAM,4BAA4B,CAAC;AAIpC,OAAO,EACL,UAAU,EACV,KAAK,gBAAgB,EACrB,qBAAqB,EACrB,oBAAoB,EACpB,oBAAoB,EACpB,KAAK,wBAAwB,GAC9B,MAAM,qCAAqC,CAAC;AAK7C,OAAO,EACL,KAAK,wBAAwB,EAC7B,KAAK,6BAA6B,EAClC,oBAAoB,EACpB,iBAAiB,EACjB,mBAAmB,EACnB,oBAAoB,EACpB,KAAK,gBAAgB,EACrB,iBAAiB,GAClB,MAAM,wCAAwC,CAAC;AAChD,OAAO,EAAE,KAAK,eAAe,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAQ1F,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,uBAAuB,EACvB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,sBAAsB,EACtB,KAAK,oBAAoB,EACzB,kBAAkB,EAClB,aAAa,EACb,KAAK,cAAc,EACnB,aAAa,EACb,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EACf,eAAe,EACf,eAAe,EACf,YAAY,EACZ,UAAU,EACV,KAAK,gBAAgB,EACrB,iBAAiB,EACjB,UAAU,GACX,MAAM,wCAAwC,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EACL,mBAAmB,EACnB,KAAK,uBAAuB,EAC5B,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,EACnB,WAAW,GACZ,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,eAAe,EAAE,KAAK,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAI/G,OAAO,EACL,oBAAoB,EACpB,oCAAoC,EACpC,4BAA4B,EAC5B,KAAK,WAAW,EAChB,kBAAkB,GACnB,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,KAAK,uBAAuB,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AACtG,OAAO,EACL,wBAAwB,EACxB,4BAA4B,EAC5B,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,YAAY,EACZ,wBAAwB,GACzB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,kBAAkB,EAAE,KAAK,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAChG,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AACzG,OAAO,EACL,+BAA+B,EAC/B,KAAK,oBAAoB,EACzB,oBAAoB,GACrB,MAAM,4BAA4B,CAAC;AAIpC,OAAO,EACL,UAAU,EACV,KAAK,gBAAgB,EACrB,qBAAqB,EACrB,oBAAoB,EACpB,oBAAoB,EACpB,KAAK,wBAAwB,GAC9B,MAAM,qCAAqC,CAAC;AAK7C,OAAO,EACL,KAAK,wBAAwB,EAC7B,KAAK,6BAA6B,EAClC,oBAAoB,EACpB,iBAAiB,EACjB,mBAAmB,EACnB,oBAAoB,EACpB,KAAK,gBAAgB,EACrB,iBAAiB,GAClB,MAAM,wCAAwC,CAAC;AAChD,OAAO,EAAE,KAAK,eAAe,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAQ1F,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,uBAAuB,EACvB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,sBAAsB,EACtB,KAAK,oBAAoB,EACzB,kBAAkB,EAClB,aAAa,EACb,KAAK,cAAc,EACnB,aAAa,EACb,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EACf,eAAe,EACf,eAAe,EACf,YAAY,EACZ,UAAU,EACV,KAAK,gBAAgB,EACrB,iBAAiB,EACjB,UAAU,GACX,MAAM,wCAAwC,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EACL,mBAAmB,EACnB,KAAK,uBAAuB,EAC5B,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,EACnB,qBAAqB,EACrB,WAAW,GACZ,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,eAAe,EAAE,KAAK,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAI/G,OAAO,EACL,oBAAoB,EACpB,oCAAoC,EACpC,4BAA4B,EAC5B,KAAK,WAAW,EAChB,kBAAkB,GACnB,MAAM,qBAAqB,CAAC"}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // SPDX-FileCopyrightText: 2026 Pithy
2
2
  // SPDX-License-Identifier: MIT
3
- import { ANY_VERIFIED_CALLER, ControlPlaneScope, KEYS_ROTATE_SCOPE, MANIFEST_READ_SCOPE, SEAM_SCOPES } from "./controlPlane/scope/scope.js";
3
+ import { ANY_VERIFIED_CALLER, ControlPlaneScope, KEYS_ROTATE_SCOPE, MANIFEST_READ_SCOPE, MIGRATIONS_READ_SCOPE, SEAM_SCOPES } from "./controlPlane/scope/scope.js";
4
4
  import { ControlPlaneConfig } from "./controlPlane/config/config.js";
5
5
  import { ControlPlaneConnection, Ed25519PublicJwk, RegisteredKey } from "./controlPlane/data/connection.js";
6
6
  import { CONTROL_PLANE_CONNECTIONS_TABLE, controlPlaneDatabase } from "./controlPlane/data/tables.js";
@@ -16,4 +16,4 @@ import { CONTROL_PLANE_KV_BINDING } from "./controlPlane/replay/kvGuard.js";
16
16
  import { CONTROLPLANE_MIGRATION_ORDER, controlplane, isControlPlaneCapability } from "./controlPlane/capability.js";
17
17
  import { missingAdminRoutes } from "./controlPlane/discovery/drift.js";
18
18
  import { exportPublicJwk, mintControlPlaneToken } from "./controlPlane/token/mint.js";
19
- export { ANY_VERIFIED_CALLER, AdminRoute, CONTROLPLANE_MIGRATION_ORDER, CONTROL_PLANE_CONNECTIONS_TABLE, CONTROL_PLANE_HEADER, CONTROL_PLANE_KV_BINDING, CONTROL_PLANE_VERSION_CREATED_HEADER, CONTROL_PLANE_VERSION_HEADER, CapabilityDeclaration, CapabilityDescriptor, CapabilityHealthReport, ControlPlaneAuditActions, ControlPlaneConfig, ControlPlaneConnection, ControlPlaneContext, ControlPlaneManifest, ControlPlaneScope, Ed25519PublicJwk, HealthCountNominal, HealthNominal, HealthSummary, HealthSummaryKey, HealthSummaryValue, HealthValueCost, HealthValueKind, KEYS_ROTATE_SCOPE, MANIFEST_READ_SCOPE, ManifestConfigKey, ManifestConfigValue, ManifestConfigValues, RegisteredKey, SEAM_SCOPES, capabilityHealthSources, controlPlaneDatabase, controlplane, defineCapabilityHealth, defineManifestConfig, exportPublicJwk, healthAttention, healthReport, healthWire, isControlPlaneCapability, mintControlPlaneToken, missingAdminRoutes, namedConfigValues, namedHealthValues, readCapabilityHealth, requireControlPlane, standingOf, workerBuildChanged };
19
+ export { ANY_VERIFIED_CALLER, AdminRoute, CONTROLPLANE_MIGRATION_ORDER, CONTROL_PLANE_CONNECTIONS_TABLE, CONTROL_PLANE_HEADER, CONTROL_PLANE_KV_BINDING, CONTROL_PLANE_VERSION_CREATED_HEADER, CONTROL_PLANE_VERSION_HEADER, CapabilityDeclaration, CapabilityDescriptor, CapabilityHealthReport, ControlPlaneAuditActions, ControlPlaneConfig, ControlPlaneConnection, ControlPlaneContext, ControlPlaneManifest, ControlPlaneScope, Ed25519PublicJwk, HealthCountNominal, HealthNominal, HealthSummary, HealthSummaryKey, HealthSummaryValue, HealthValueCost, HealthValueKind, KEYS_ROTATE_SCOPE, MANIFEST_READ_SCOPE, MIGRATIONS_READ_SCOPE, ManifestConfigKey, ManifestConfigValue, ManifestConfigValues, RegisteredKey, SEAM_SCOPES, capabilityHealthSources, controlPlaneDatabase, controlplane, defineCapabilityHealth, defineManifestConfig, exportPublicJwk, healthAttention, healthReport, healthWire, isControlPlaneCapability, mintControlPlaneToken, missingAdminRoutes, namedConfigValues, namedHealthValues, readCapabilityHealth, requireControlPlane, standingOf, workerBuildChanged };
@@ -310,6 +310,20 @@ export declare const LogRecord: z.ZodObject<{
310
310
  detail: z.ZodOptional<z.ZodString>;
311
311
  code: z.ZodLiteral<"turnstile/config">;
312
312
  status: z.ZodLiteral<500>;
313
+ }, z.core.$strip>, z.ZodObject<{
314
+ message: z.ZodString;
315
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
316
+ action: z.ZodOptional<z.ZodString>;
317
+ detail: z.ZodOptional<z.ZodString>;
318
+ code: z.ZodLiteral<"turnstile/analytics_window">;
319
+ status: z.ZodLiteral<400>;
320
+ }, z.core.$strip>, z.ZodObject<{
321
+ message: z.ZodString;
322
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
323
+ action: z.ZodOptional<z.ZodString>;
324
+ detail: z.ZodOptional<z.ZodString>;
325
+ code: z.ZodLiteral<"turnstile/analytics_unavailable">;
326
+ status: z.ZodLiteral<409>;
313
327
  }, z.core.$strip>, z.ZodObject<{
314
328
  message: z.ZodString;
315
329
  params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
@@ -331,6 +345,13 @@ export declare const LogRecord: z.ZodObject<{
331
345
  detail: z.ZodOptional<z.ZodString>;
332
346
  code: z.ZodLiteral<"media/not_found">;
333
347
  status: z.ZodLiteral<404>;
348
+ }, z.core.$strip>, z.ZodObject<{
349
+ message: z.ZodString;
350
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
351
+ action: z.ZodOptional<z.ZodString>;
352
+ detail: z.ZodOptional<z.ZodString>;
353
+ code: z.ZodLiteral<"media/forbidden">;
354
+ status: z.ZodLiteral<403>;
334
355
  }, z.core.$strip>, z.ZodObject<{
335
356
  message: z.ZodString;
336
357
  params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
@@ -352,6 +373,20 @@ export declare const LogRecord: z.ZodObject<{
352
373
  detail: z.ZodOptional<z.ZodString>;
353
374
  code: z.ZodLiteral<"media/enrichment_failed">;
354
375
  status: z.ZodLiteral<500>;
376
+ }, z.core.$strip>, z.ZodObject<{
377
+ message: z.ZodString;
378
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
379
+ action: z.ZodOptional<z.ZodString>;
380
+ detail: z.ZodOptional<z.ZodString>;
381
+ code: z.ZodLiteral<"media/analytics_window">;
382
+ status: z.ZodLiteral<400>;
383
+ }, z.core.$strip>, z.ZodObject<{
384
+ message: z.ZodString;
385
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
386
+ action: z.ZodOptional<z.ZodString>;
387
+ detail: z.ZodOptional<z.ZodString>;
388
+ code: z.ZodLiteral<"media/analytics_unavailable">;
389
+ status: z.ZodLiteral<409>;
355
390
  }, z.core.$strip>, z.ZodObject<{
356
391
  message: z.ZodString;
357
392
  params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
@@ -786,6 +821,20 @@ export declare const LogRecord: z.ZodObject<{
786
821
  detail: z.ZodOptional<z.ZodString>;
787
822
  code: z.ZodLiteral<"payments/subscription_change_refused">;
788
823
  status: z.ZodLiteral<409>;
824
+ }, z.core.$strip>, z.ZodObject<{
825
+ message: z.ZodString;
826
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
827
+ action: z.ZodOptional<z.ZodString>;
828
+ detail: z.ZodOptional<z.ZodString>;
829
+ code: z.ZodLiteral<"payments/override_stale">;
830
+ status: z.ZodLiteral<409>;
831
+ }, z.core.$strip>, z.ZodObject<{
832
+ message: z.ZodString;
833
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
834
+ action: z.ZodOptional<z.ZodString>;
835
+ detail: z.ZodOptional<z.ZodString>;
836
+ code: z.ZodLiteral<"payments/quota_exceeded">;
837
+ status: z.ZodLiteral<402>;
789
838
  }, z.core.$strip>, z.ZodObject<{
790
839
  message: z.ZodString;
791
840
  params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
@@ -870,6 +919,41 @@ export declare const LogRecord: z.ZodObject<{
870
919
  detail: z.ZodOptional<z.ZodString>;
871
920
  code: z.ZodLiteral<"organization/nomination_invalid">;
872
921
  status: z.ZodLiteral<400>;
922
+ }, z.core.$strip>, z.ZodObject<{
923
+ message: z.ZodString;
924
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
925
+ action: z.ZodOptional<z.ZodString>;
926
+ detail: z.ZodOptional<z.ZodString>;
927
+ code: z.ZodLiteral<"artifacts/not_found">;
928
+ status: z.ZodLiteral<404>;
929
+ }, z.core.$strip>, z.ZodObject<{
930
+ message: z.ZodString;
931
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
932
+ action: z.ZodOptional<z.ZodString>;
933
+ detail: z.ZodOptional<z.ZodString>;
934
+ code: z.ZodLiteral<"artifacts/forbidden">;
935
+ status: z.ZodLiteral<403>;
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/invalid_name">;
942
+ status: z.ZodLiteral<400>;
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/name_taken">;
949
+ status: z.ZodLiteral<409>;
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_config">;
956
+ status: z.ZodLiteral<400>;
873
957
  }, z.core.$strip>, z.ZodObject<{
874
958
  message: z.ZodString;
875
959
  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 };