@pithy-sh/core 0.1.1 → 0.1.3

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 (393) hide show
  1. package/dist/address/address.d.ts +57 -0
  2. package/dist/address/address.d.ts.map +1 -0
  3. package/dist/address/address.js +71 -0
  4. package/dist/audit/auditEvent.d.ts +89 -0
  5. package/dist/audit/auditEvent.d.ts.map +1 -0
  6. package/dist/audit/auditEvent.js +69 -0
  7. package/dist/audit/recorder.d.ts +18 -0
  8. package/dist/audit/recorder.d.ts.map +1 -0
  9. package/dist/audit/recorder.js +8 -0
  10. package/dist/capability/bindings.d.ts +62 -0
  11. package/dist/capability/bindings.d.ts.map +1 -0
  12. package/dist/capability/bindings.js +97 -0
  13. package/dist/capability/capability.d.ts +501 -0
  14. package/dist/capability/capability.d.ts.map +1 -0
  15. package/dist/capability/capability.js +16 -0
  16. package/dist/capability/client.d.ts +70 -0
  17. package/dist/capability/client.d.ts.map +1 -0
  18. package/dist/capability/client.js +86 -0
  19. package/dist/capability/compose.d.ts +25 -0
  20. package/dist/capability/compose.d.ts.map +1 -0
  21. package/dist/capability/compose.js +54 -0
  22. package/dist/capability/composition.d.ts +26 -0
  23. package/dist/capability/composition.d.ts.map +1 -0
  24. package/dist/capability/composition.js +84 -0
  25. package/dist/capability/config.d.ts +7 -0
  26. package/dist/capability/config.d.ts.map +1 -0
  27. package/dist/capability/config.js +14 -0
  28. package/dist/capability/devSecret.d.ts +32 -0
  29. package/dist/capability/devSecret.d.ts.map +1 -0
  30. package/dist/capability/devSecret.js +28 -0
  31. package/dist/capability/manifest.d.ts +298 -0
  32. package/dist/capability/manifest.d.ts.map +1 -0
  33. package/dist/capability/manifest.js +356 -0
  34. package/dist/capability/secretOrigin.d.ts +121 -0
  35. package/dist/capability/secretOrigin.d.ts.map +1 -0
  36. package/dist/capability/secretOrigin.js +134 -0
  37. package/dist/capability/settings.d.ts +119 -0
  38. package/dist/capability/settings.d.ts.map +1 -0
  39. package/dist/capability/settings.js +66 -0
  40. package/dist/capability/validateBindings.d.ts +4 -0
  41. package/dist/capability/validateBindings.d.ts.map +1 -0
  42. package/dist/capability/validateBindings.js +34 -0
  43. package/dist/capability/vanishingKey.d.ts +31 -0
  44. package/dist/capability/vanishingKey.d.ts.map +1 -0
  45. package/dist/capability/vanishingKey.js +79 -0
  46. package/dist/controlPlane/audit/actions.d.ts +70 -0
  47. package/dist/controlPlane/audit/actions.d.ts.map +1 -0
  48. package/dist/controlPlane/audit/actions.js +76 -0
  49. package/dist/controlPlane/capability.d.ts +47 -0
  50. package/dist/controlPlane/capability.d.ts.map +1 -0
  51. package/dist/controlPlane/capability.js +138 -0
  52. package/dist/controlPlane/config/config.d.ts +19 -0
  53. package/dist/controlPlane/config/config.d.ts.map +1 -0
  54. package/dist/controlPlane/config/config.js +85 -0
  55. package/dist/controlPlane/context.d.ts +27 -0
  56. package/dist/controlPlane/context.d.ts.map +1 -0
  57. package/dist/controlPlane/context.js +29 -0
  58. package/dist/controlPlane/data/connection.d.ts +79 -0
  59. package/dist/controlPlane/data/connection.d.ts.map +1 -0
  60. package/dist/controlPlane/data/connection.js +55 -0
  61. package/dist/controlPlane/data/keyLifecycle.d.ts +62 -0
  62. package/dist/controlPlane/data/keyLifecycle.d.ts.map +1 -0
  63. package/dist/controlPlane/data/keyLifecycle.js +126 -0
  64. package/dist/controlPlane/data/replay.d.ts +18 -0
  65. package/dist/controlPlane/data/replay.d.ts.map +1 -0
  66. package/dist/controlPlane/data/replay.js +20 -0
  67. package/dist/controlPlane/data/tables.d.ts +34 -0
  68. package/dist/controlPlane/data/tables.d.ts.map +1 -0
  69. package/dist/controlPlane/data/tables.js +37 -0
  70. package/dist/controlPlane/discovery/adminRoute.d.ts +313 -0
  71. package/dist/controlPlane/discovery/adminRoute.d.ts.map +1 -0
  72. package/dist/controlPlane/discovery/adminRoute.js +136 -0
  73. package/dist/controlPlane/discovery/configuration.d.ts +108 -0
  74. package/dist/controlPlane/discovery/configuration.d.ts.map +1 -0
  75. package/dist/controlPlane/discovery/configuration.js +187 -0
  76. package/dist/controlPlane/discovery/drift.d.ts +64 -0
  77. package/dist/controlPlane/discovery/drift.d.ts.map +1 -0
  78. package/dist/controlPlane/discovery/drift.js +58 -0
  79. package/dist/controlPlane/discovery/health.d.ts +99 -0
  80. package/dist/controlPlane/discovery/health.d.ts.map +1 -0
  81. package/dist/controlPlane/discovery/health.js +151 -0
  82. package/dist/controlPlane/discovery/healthSummary.d.ts +236 -0
  83. package/dist/controlPlane/discovery/healthSummary.d.ts.map +1 -0
  84. package/dist/controlPlane/discovery/healthSummary.js +278 -0
  85. package/dist/controlPlane/error/errors.d.ts +67 -0
  86. package/dist/controlPlane/error/errors.d.ts.map +1 -0
  87. package/dist/controlPlane/error/errors.js +79 -0
  88. package/dist/controlPlane/http/cors.d.ts +112 -0
  89. package/dist/controlPlane/http/cors.d.ts.map +1 -0
  90. package/dist/controlPlane/http/cors.js +192 -0
  91. package/dist/controlPlane/http/guard.d.ts +77 -0
  92. package/dist/controlPlane/http/guard.d.ts.map +1 -0
  93. package/dist/controlPlane/http/guard.js +142 -0
  94. package/dist/controlPlane/http/handlers.d.ts +179 -0
  95. package/dist/controlPlane/http/handlers.d.ts.map +1 -0
  96. package/dist/controlPlane/http/handlers.js +245 -0
  97. package/dist/controlPlane/http/responses.d.ts +99 -0
  98. package/dist/controlPlane/http/responses.d.ts.map +1 -0
  99. package/dist/controlPlane/http/responses.js +63 -0
  100. package/dist/controlPlane/http/routes.d.ts +40 -0
  101. package/dist/controlPlane/http/routes.d.ts.map +1 -0
  102. package/dist/controlPlane/http/routes.js +83 -0
  103. package/dist/controlPlane/http/schemas.d.ts +39 -0
  104. package/dist/controlPlane/http/schemas.d.ts.map +1 -0
  105. package/dist/controlPlane/http/schemas.js +31 -0
  106. package/dist/controlPlane/http/verify.d.ts +84 -0
  107. package/dist/controlPlane/http/verify.d.ts.map +1 -0
  108. package/dist/controlPlane/http/verify.js +65 -0
  109. package/dist/controlPlane/migrations/0001_init.d.ts +51 -0
  110. package/dist/controlPlane/migrations/0001_init.d.ts.map +1 -0
  111. package/dist/controlPlane/migrations/0001_init.js +65 -0
  112. package/dist/controlPlane/replay/d1Guard.d.ts +29 -0
  113. package/dist/controlPlane/replay/d1Guard.d.ts.map +1 -0
  114. package/dist/controlPlane/replay/d1Guard.js +39 -0
  115. package/dist/controlPlane/replay/guard.d.ts +52 -0
  116. package/dist/controlPlane/replay/guard.d.ts.map +1 -0
  117. package/dist/controlPlane/replay/guard.js +0 -0
  118. package/dist/controlPlane/replay/kvGuard.d.ts +99 -0
  119. package/dist/controlPlane/replay/kvGuard.d.ts.map +1 -0
  120. package/dist/controlPlane/replay/kvGuard.js +101 -0
  121. package/dist/controlPlane/scope/scope.d.ts +68 -0
  122. package/dist/controlPlane/scope/scope.d.ts.map +1 -0
  123. package/dist/controlPlane/scope/scope.js +63 -0
  124. package/dist/controlPlane/token/base64url.d.ts +10 -0
  125. package/dist/controlPlane/token/base64url.d.ts.map +1 -0
  126. package/dist/controlPlane/token/base64url.js +48 -0
  127. package/dist/controlPlane/token/claims.d.ts +83 -0
  128. package/dist/controlPlane/token/claims.d.ts.map +1 -0
  129. package/dist/controlPlane/token/claims.js +86 -0
  130. package/dist/controlPlane/token/digest.d.ts +44 -0
  131. package/dist/controlPlane/token/digest.d.ts.map +1 -0
  132. package/dist/controlPlane/token/digest.js +56 -0
  133. package/dist/controlPlane/token/jws.d.ts +52 -0
  134. package/dist/controlPlane/token/jws.d.ts.map +1 -0
  135. package/dist/controlPlane/token/jws.js +65 -0
  136. package/dist/controlPlane/token/mint.d.ts +56 -0
  137. package/dist/controlPlane/token/mint.d.ts.map +1 -0
  138. package/dist/controlPlane/token/mint.js +55 -0
  139. package/dist/controlPlane/wire.d.ts +122 -0
  140. package/dist/controlPlane/wire.d.ts.map +1 -0
  141. package/dist/controlPlane/wire.js +123 -0
  142. package/dist/createBackend.d.ts +58 -0
  143. package/dist/createBackend.d.ts.map +1 -0
  144. package/dist/createBackend.js +170 -0
  145. package/dist/createEntrypoint.d.ts +53 -0
  146. package/dist/createEntrypoint.d.ts.map +1 -0
  147. package/dist/createEntrypoint.js +52 -0
  148. package/dist/data/boundParameters.d.ts +83 -0
  149. package/dist/data/boundParameters.d.ts.map +1 -0
  150. package/dist/data/boundParameters.js +162 -0
  151. package/dist/data/codecs.d.ts +34 -0
  152. package/dist/data/codecs.d.ts.map +1 -0
  153. package/dist/data/codecs.js +135 -0
  154. package/dist/data/cursor.d.ts +81 -0
  155. package/dist/data/cursor.d.ts.map +1 -0
  156. package/dist/data/cursor.js +104 -0
  157. package/dist/data/databases.d.ts +55 -0
  158. package/dist/data/databases.d.ts.map +1 -0
  159. package/dist/data/databases.js +32 -0
  160. package/dist/data/db.d.ts +42 -0
  161. package/dist/data/db.d.ts.map +1 -0
  162. package/dist/data/db.js +24 -0
  163. package/dist/data/withD1Retry.d.ts +91 -0
  164. package/dist/data/withD1Retry.d.ts.map +1 -0
  165. package/dist/data/withD1Retry.js +152 -0
  166. package/dist/entitlement/entitlement.d.ts +118 -0
  167. package/dist/entitlement/entitlement.d.ts.map +1 -0
  168. package/dist/entitlement/entitlement.js +59 -0
  169. package/dist/entitlement/gateScan.d.ts +26 -0
  170. package/dist/entitlement/gateScan.d.ts.map +1 -0
  171. package/dist/entitlement/gateScan.js +96 -0
  172. package/dist/entitlement/require.d.ts +12 -0
  173. package/dist/entitlement/require.d.ts.map +1 -0
  174. package/dist/entitlement/require.js +123 -0
  175. package/dist/env/ambient.d.ts +52 -0
  176. package/dist/env/ambient.d.ts.map +1 -0
  177. package/dist/env/ambient.js +35 -0
  178. package/dist/env/ci.d.ts +36 -0
  179. package/dist/env/ci.d.ts.map +1 -0
  180. package/dist/env/ci.js +40 -0
  181. package/dist/env/stem.d.ts +23 -0
  182. package/dist/env/stem.d.ts.map +1 -0
  183. package/dist/env/stem.js +29 -0
  184. package/dist/error/cause.d.ts +143 -0
  185. package/dist/error/cause.d.ts.map +1 -0
  186. package/dist/error/cause.js +192 -0
  187. package/dist/error/client.d.ts +35 -0
  188. package/dist/error/client.d.ts.map +1 -0
  189. package/dist/error/client.js +38 -0
  190. package/dist/error/extend.d.ts +101 -0
  191. package/dist/error/extend.d.ts.map +1 -0
  192. package/dist/error/extend.js +76 -0
  193. package/dist/error/http.d.ts +1516 -0
  194. package/dist/error/http.d.ts.map +1 -0
  195. package/dist/error/http.js +72 -0
  196. package/dist/error/payload.d.ts +3034 -0
  197. package/dist/error/payload.d.ts.map +1 -0
  198. package/dist/error/payload.js +1110 -0
  199. package/dist/error/pithyError.d.ts +154 -0
  200. package/dist/error/pithyError.d.ts.map +1 -0
  201. package/dist/error/pithyError.js +197 -0
  202. package/dist/error/terminal.d.ts +28 -0
  203. package/dist/error/terminal.d.ts.map +1 -0
  204. package/dist/error/terminal.js +33 -0
  205. package/dist/http/authContext.d.ts +21 -0
  206. package/dist/http/authContext.d.ts.map +1 -0
  207. package/dist/http/authContext.js +23 -0
  208. package/dist/http/routeContract.d.ts +39 -0
  209. package/dist/http/routeContract.d.ts.map +1 -0
  210. package/dist/http/routeContract.js +82 -0
  211. package/dist/http/sameOrigin.d.ts +47 -0
  212. package/dist/http/sameOrigin.d.ts.map +1 -0
  213. package/dist/http/sameOrigin.js +24 -0
  214. package/dist/http/signedWebhook.d.ts +225 -0
  215. package/dist/http/signedWebhook.d.ts.map +1 -0
  216. package/dist/http/signedWebhook.js +247 -0
  217. package/dist/http/validation.d.ts +37 -0
  218. package/dist/http/validation.d.ts.map +1 -0
  219. package/dist/http/validation.js +14 -0
  220. package/dist/http/verification.d.ts +10 -0
  221. package/dist/http/verification.d.ts.map +1 -0
  222. package/dist/http/verification.js +17 -0
  223. package/dist/i18n/acceptLanguage.d.ts +22 -0
  224. package/dist/i18n/acceptLanguage.d.ts.map +1 -0
  225. package/dist/i18n/acceptLanguage.js +55 -0
  226. package/dist/i18n/catalog.d.ts +39 -0
  227. package/dist/i18n/catalog.d.ts.map +1 -0
  228. package/dist/i18n/catalog.js +57 -0
  229. package/dist/i18n/locale.d.ts +74 -0
  230. package/dist/i18n/locale.d.ts.map +1 -0
  231. package/dist/i18n/locale.js +109 -0
  232. package/dist/i18n/localeMarker.d.ts +29 -0
  233. package/dist/i18n/localeMarker.d.ts.map +1 -0
  234. package/dist/i18n/localeMarker.js +112 -0
  235. package/dist/i18n/match.d.ts +26 -0
  236. package/dist/i18n/match.d.ts.map +1 -0
  237. package/dist/i18n/match.js +88 -0
  238. package/dist/i18n/registry.d.ts +25 -0
  239. package/dist/i18n/registry.d.ts.map +1 -0
  240. package/dist/i18n/registry.js +51 -0
  241. package/dist/i18n/translator.d.ts +107 -0
  242. package/dist/i18n/translator.d.ts.map +1 -0
  243. package/dist/i18n/translator.js +80 -0
  244. package/dist/index.d.ts +32 -0
  245. package/dist/index.d.ts.map +1 -0
  246. package/dist/index.js +17 -0
  247. package/dist/kv/kv.d.ts +166 -0
  248. package/dist/kv/kv.d.ts.map +1 -0
  249. package/dist/kv/kv.js +261 -0
  250. package/dist/kv/namespaces.d.ts +58 -0
  251. package/dist/kv/namespaces.d.ts.map +1 -0
  252. package/dist/kv/namespaces.js +40 -0
  253. package/dist/logger/local.d.ts +41 -0
  254. package/dist/logger/local.d.ts.map +1 -0
  255. package/dist/logger/local.js +45 -0
  256. package/dist/logger/logger.d.ts +56 -0
  257. package/dist/logger/logger.d.ts.map +1 -0
  258. package/dist/logger/logger.js +83 -0
  259. package/dist/logger/record.d.ts +919 -0
  260. package/dist/logger/record.d.ts.map +1 -0
  261. package/dist/logger/record.js +79 -0
  262. package/dist/logger/worker.d.ts +74 -0
  263. package/dist/logger/worker.d.ts.map +1 -0
  264. package/dist/logger/worker.js +54 -0
  265. package/dist/migrations/batch.d.ts +10 -0
  266. package/dist/migrations/batch.d.ts.map +1 -0
  267. package/dist/migrations/batch.js +193 -0
  268. package/dist/migrations/bookkeeping.d.ts +24 -0
  269. package/dist/migrations/bookkeeping.d.ts.map +1 -0
  270. package/dist/migrations/bookkeeping.js +57 -0
  271. package/dist/migrations/owner.d.ts +41 -0
  272. package/dist/migrations/owner.d.ts.map +1 -0
  273. package/dist/migrations/owner.js +105 -0
  274. package/dist/migrations/registry.d.ts +36 -0
  275. package/dist/migrations/registry.d.ts.map +1 -0
  276. package/dist/migrations/registry.js +70 -0
  277. package/dist/migrations/runner.d.ts +67 -0
  278. package/dist/migrations/runner.d.ts.map +1 -0
  279. package/dist/migrations/runner.js +218 -0
  280. package/dist/naming/domains.d.ts +108 -0
  281. package/dist/naming/domains.d.ts.map +1 -0
  282. package/dist/naming/domains.js +156 -0
  283. package/dist/naming/environment.d.ts +118 -0
  284. package/dist/naming/environment.d.ts.map +1 -0
  285. package/dist/naming/environment.js +190 -0
  286. package/dist/naming/feature.d.ts +68 -0
  287. package/dist/naming/feature.d.ts.map +1 -0
  288. package/dist/naming/feature.js +106 -0
  289. package/dist/naming/limits.d.ts +229 -0
  290. package/dist/naming/limits.d.ts.map +1 -0
  291. package/dist/naming/limits.js +187 -0
  292. package/dist/naming/provisionScope.d.ts +91 -0
  293. package/dist/naming/provisionScope.d.ts.map +1 -0
  294. package/dist/naming/provisionScope.js +65 -0
  295. package/dist/naming/resource.d.ts +169 -0
  296. package/dist/naming/resource.d.ts.map +1 -0
  297. package/dist/naming/resource.js +220 -0
  298. package/dist/naming/resourceNames.d.ts +83 -0
  299. package/dist/naming/resourceNames.d.ts.map +1 -0
  300. package/dist/naming/resourceNames.js +102 -0
  301. package/dist/naming/segment.d.ts +29 -0
  302. package/dist/naming/segment.d.ts.map +1 -0
  303. package/dist/naming/segment.js +31 -0
  304. package/dist/projection/asRead.d.ts +72 -0
  305. package/dist/projection/asRead.d.ts.map +1 -0
  306. package/dist/projection/asRead.js +169 -0
  307. package/dist/projection/published.d.ts +95 -0
  308. package/dist/projection/published.d.ts.map +1 -0
  309. package/dist/projection/published.js +114 -0
  310. package/dist/schema/describedness.d.ts +55 -0
  311. package/dist/schema/describedness.d.ts.map +1 -0
  312. package/dist/schema/describedness.js +180 -0
  313. package/dist/seed/compose.d.ts +41 -0
  314. package/dist/seed/compose.d.ts.map +1 -0
  315. package/dist/seed/compose.js +51 -0
  316. package/dist/seed/devLogin.d.ts +50 -0
  317. package/dist/seed/devLogin.d.ts.map +1 -0
  318. package/dist/seed/devLogin.js +52 -0
  319. package/dist/seed/exampleIdentities.d.ts +58 -0
  320. package/dist/seed/exampleIdentities.d.ts.map +1 -0
  321. package/dist/seed/exampleIdentities.js +26 -0
  322. package/dist/seed/metadata.d.ts +20 -0
  323. package/dist/seed/metadata.d.ts.map +1 -0
  324. package/dist/seed/metadata.js +27 -0
  325. package/dist/seed/seed.d.ts +264 -0
  326. package/dist/seed/seed.d.ts.map +1 -0
  327. package/dist/seed/seed.js +37 -0
  328. package/dist/seed/seededRows.d.ts +26 -0
  329. package/dist/seed/seededRows.d.ts.map +1 -0
  330. package/dist/seed/seededRows.js +20 -0
  331. package/dist/seed/writeD1.d.ts +43 -0
  332. package/dist/seed/writeD1.d.ts.map +1 -0
  333. package/dist/seed/writeD1.js +73 -0
  334. package/dist/seed/writeKv.d.ts +29 -0
  335. package/dist/seed/writeKv.d.ts.map +1 -0
  336. package/dist/seed/writeKv.js +63 -0
  337. package/dist/semver/semver.d.ts +62 -0
  338. package/dist/semver/semver.d.ts.map +1 -0
  339. package/dist/semver/semver.js +137 -0
  340. package/dist/text/comments.d.ts +62 -0
  341. package/dist/text/comments.d.ts.map +1 -0
  342. package/dist/text/comments.js +161 -0
  343. package/dist/version.generated.d.ts +5 -0
  344. package/dist/version.generated.d.ts.map +1 -0
  345. package/dist/version.generated.js +7 -0
  346. package/dist/worker/health.d.ts +39 -0
  347. package/dist/worker/health.d.ts.map +1 -0
  348. package/dist/worker/health.js +41 -0
  349. package/dist/worker/identity.d.ts +191 -0
  350. package/dist/worker/identity.d.ts.map +1 -0
  351. package/dist/worker/identity.js +162 -0
  352. package/dist/workflow/bindings.d.ts +41 -0
  353. package/dist/workflow/bindings.d.ts.map +1 -0
  354. package/dist/workflow/bindings.js +53 -0
  355. package/dist/workflow/dispatch.d.ts +98 -0
  356. package/dist/workflow/dispatch.d.ts.map +1 -0
  357. package/dist/workflow/dispatch.js +148 -0
  358. package/dist/workflow/dispatchRoute.d.ts +80 -0
  359. package/dist/workflow/dispatchRoute.d.ts.map +1 -0
  360. package/dist/workflow/dispatchRoute.js +134 -0
  361. package/dist/workflow/faults.d.ts +97 -0
  362. package/dist/workflow/faults.d.ts.map +1 -0
  363. package/dist/workflow/faults.js +111 -0
  364. package/dist/workflow/host.d.ts +176 -0
  365. package/dist/workflow/host.d.ts.map +1 -0
  366. package/dist/workflow/host.js +116 -0
  367. package/dist/workflow/hostEntry.d.ts +50 -0
  368. package/dist/workflow/hostEntry.d.ts.map +1 -0
  369. package/dist/workflow/hostEntry.js +24 -0
  370. package/dist/workflow/hostEnv.d.ts +143 -0
  371. package/dist/workflow/hostEnv.d.ts.map +1 -0
  372. package/dist/workflow/hostEnv.js +170 -0
  373. package/dist/workflow/loopback.d.ts +52 -0
  374. package/dist/workflow/loopback.d.ts.map +1 -0
  375. package/dist/workflow/loopback.js +76 -0
  376. package/dist/workflow/naming.d.ts +79 -0
  377. package/dist/workflow/naming.d.ts.map +1 -0
  378. package/dist/workflow/naming.js +138 -0
  379. package/dist/workflow/register.d.ts +19 -0
  380. package/dist/workflow/register.d.ts.map +1 -0
  381. package/dist/workflow/register.js +40 -0
  382. package/dist/workflow/schemas.d.ts +44 -0
  383. package/dist/workflow/schemas.d.ts.map +1 -0
  384. package/dist/workflow/schemas.js +43 -0
  385. package/dist/workflow/spec.d.ts +81 -0
  386. package/dist/workflow/spec.d.ts.map +1 -0
  387. package/dist/workflow/spec.js +0 -0
  388. package/dist/workflow/stepMessage.d.ts +122 -0
  389. package/dist/workflow/stepMessage.d.ts.map +1 -0
  390. package/dist/workflow/stepMessage.js +152 -0
  391. package/package.json +20 -9
  392. package/src/capability/manifest.ts +21 -0
  393. package/src/version.generated.ts +1 -1
@@ -0,0 +1,138 @@
1
+ import { defineCapability } from "../capability/capability.js";
2
+ import { PACKAGE_VERSION } from "../version.generated.js";
3
+ import "../worker/identity.js";
4
+ import { ControlPlaneConfig } from "./config/config.js";
5
+ import { ControlPlaneConnection } from "./data/connection.js";
6
+ import { CONTROL_PLANE_CONNECTIONS_TABLE, controlPlaneTables } from "./data/tables.js";
7
+ import { capabilityHealthSources } from "./discovery/health.js";
8
+ import { createControlPlaneVerifier } from "./http/guard.js";
9
+ import { controlPlaneRouteDescriptors, registerControlPlaneRoutes } from "./http/routes.js";
10
+ import { controlplane_0001_init } from "./migrations/0001_init.js";
11
+ import { d1ReplayGuard } from "./replay/d1Guard.js";
12
+ import { CONTROL_PLANE_KV_BINDING, controlPlaneKvNamespaces, kvReplayGuard } from "./replay/kvGuard.js";
13
+ //#region src/controlPlane/capability.ts
14
+ /**
15
+ * Where the control-plane registration sorts in the app database. Composed key:
16
+ * `1100_controlplane_0001_init`.
17
+ *
18
+ * Taken from `NEXT_FREE_ORDER` in `packages/cli/src/migrations/orders.test.ts`, which is the one place
19
+ * an order may be allocated, and registered in that file's `DECLARED` table. Core used to be skipped by
20
+ * that scanner on the reasoning that it owns the ceiling rather than an order; that stopped being true
21
+ * here, and the skip was removed with this change. Stable forever — renumbering would rename the
22
+ * composed key and make Kysely re-run an applied migration.
23
+ */
24
+ const CONTROLPLANE_MIGRATION_ORDER = 1100;
25
+ /** Narrow a composed capability to this one — the CLI and tests use it to find the seam's config. */
26
+ function isControlPlaneCapability(capability) {
27
+ return capability.name === "controlplane";
28
+ }
29
+ /**
30
+ * The `control-plane` seam: inbound, adopter-authenticated M2M admin access to this Worker.
31
+ *
32
+ * **Not Cloudflare's control plane.** That is the outbound REST API `@pithy-sh/cloudflare` calls to
33
+ * provision D1, KV, and Workers, and it carries `cloudflare/*` codes. This is the inverse — a
34
+ * management client calling *in*, authenticated by a key the adopter registered. See
35
+ * `docs/CONTROL-PLANE.md`, whose first section exists entirely to keep the two apart.
36
+ *
37
+ * **Composing it grants nobody anything.** The capability ships present-and-denying: with no connection
38
+ * registered, every route it guards answers `controlplane/not_connected`, and there is no flag that
39
+ * changes that. `pithy dashboard connect` is the deliberate second step, and revoking is a row the
40
+ * adopter deletes without asking anyone.
41
+ *
42
+ * It lives in `@pithy-sh/core` rather than its own package because every capability contributing admin
43
+ * routes imports `requireControlPlane`, and a capability may depend on a core seam but never on a
44
+ * sibling package (principle 4). MIT with the rest of core, and never gated by tier: this is the code
45
+ * that runs in the adopter's own Worker, so restricting it would make "build your own client against
46
+ * your own Worker" untrue.
47
+ */
48
+ function controlplane(options = {}) {
49
+ const config = ControlPlaneConfig.parse(options);
50
+ const migrations = { "0001_init": controlplane_0001_init };
51
+ const requiredBindings = [{
52
+ type: "d1",
53
+ name: "DB"
54
+ }];
55
+ const usesKvReplay = config.replayBackend === "kv";
56
+ if (usesKvReplay) requiredBindings.push({
57
+ type: "kv",
58
+ name: CONTROL_PLANE_KV_BINDING
59
+ });
60
+ /**
61
+ * The app database for this request, off the derived registry. `c.var.db` is `unknown` on the base
62
+ * seam by design — its precise type depends on which capabilities a project composed — so each
63
+ * capability narrows it to its own slice, the same cast every other capability makes.
64
+ */
65
+ const database = (c) => c.var.db.app;
66
+ const now = () => /* @__PURE__ */ new Date();
67
+ /**
68
+ * The replay guard for this request, chosen once by config.
69
+ *
70
+ * `d1` is the default and claims on a primary key, so one token is spendable exactly once wherever the
71
+ * requests land. `kv` remains selectable and is best-effort — see `./replay/guard.ts`. The interface is
72
+ * what made moving the default a one-line change here rather than an edit at every call site.
73
+ */
74
+ const replay = (c) => config.replayBackend === "kv" ? kvReplayGuard(c.var.kv.controlplane.jtis) : d1ReplayGuard(database(c), {
75
+ now,
76
+ ttlSeconds: config.jtiTtlSeconds
77
+ });
78
+ let composed = [];
79
+ let health = /* @__PURE__ */ new Map();
80
+ return {
81
+ ...defineCapability({
82
+ name: "controlplane",
83
+ version: PACKAGE_VERSION,
84
+ requiredBindings,
85
+ config: ControlPlaneConfig,
86
+ databases: { app: {
87
+ binding: "DB",
88
+ tables: controlPlaneTables(),
89
+ migrationOrder: CONTROLPLANE_MIGRATION_ORDER,
90
+ migrations
91
+ } },
92
+ kvNamespaces: usesKvReplay ? controlPlaneKvNamespaces(config.jtiTtlSeconds) : {},
93
+ adminRoutes: controlPlaneRouteDescriptors(config.basePath),
94
+ compose: ({ capabilities }) => {
95
+ composed = capabilities.map((cap) => ({
96
+ name: cap.name,
97
+ version: cap.version ?? null,
98
+ adminRoutes: [...cap.adminRoutes ?? []],
99
+ healthKeys: [...cap.health?.keys ?? []],
100
+ configKeys: [...cap.manifestConfig?.keys ?? []],
101
+ config: { ...cap.manifestConfig?.values ?? {} }
102
+ }));
103
+ health = capabilityHealthSources(capabilities);
104
+ },
105
+ middleware: [(app) => {
106
+ app.use("*", async (c, next) => {
107
+ const db = database(c);
108
+ c.set("controlPlaneVerifier", createControlPlaneVerifier({
109
+ loadConnection: async (connectionId) => {
110
+ const row = await db.selectFrom(CONTROL_PLANE_CONNECTIONS_TABLE).selectAll().where("id", "=", connectionId).executeTakeFirst();
111
+ return row ? ControlPlaneConnection.parse(row) : null;
112
+ },
113
+ countConnections: async () => {
114
+ return (await db.selectFrom(CONTROL_PLANE_CONNECTIONS_TABLE).select("id").limit(1).execute()).length;
115
+ },
116
+ replay: replay(c),
117
+ environment: String(c.env["ENVIRONMENT"] ?? ""),
118
+ config,
119
+ now
120
+ }));
121
+ await next();
122
+ });
123
+ }],
124
+ routes: (app) => {
125
+ registerControlPlaneRoutes(app, config.basePath, {
126
+ config,
127
+ database,
128
+ composedCapabilities: () => composed,
129
+ composedHealth: () => health,
130
+ now
131
+ });
132
+ }
133
+ }),
134
+ controlPlaneConfig: config
135
+ };
136
+ }
137
+ //#endregion
138
+ export { CONTROLPLANE_MIGRATION_ORDER, CONTROL_PLANE_KV_BINDING, controlplane, isControlPlaneCapability };
@@ -0,0 +1,19 @@
1
+ import { z } from "zod";
2
+ export declare const ControlPlaneConfig: z.ZodObject<{
3
+ basePath: z.ZodDefault<z.ZodString>;
4
+ issuer: z.ZodDefault<z.ZodURL>;
5
+ allowedOrigins: z.ZodDefault<z.ZodArray<z.ZodURL>>;
6
+ corsMaxAgeSeconds: z.ZodDefault<z.ZodNumber>;
7
+ clockSkewSeconds: z.ZodDefault<z.ZodNumber>;
8
+ maxTokenLifetimeSeconds: z.ZodDefault<z.ZodNumber>;
9
+ jtiTtlSeconds: z.ZodDefault<z.ZodNumber>;
10
+ replayBackend: z.ZodDefault<z.ZodEnum<{
11
+ d1: "d1";
12
+ kv: "kv";
13
+ }>>;
14
+ keyRetentionDays: z.ZodDefault<z.ZodNumber>;
15
+ maxKeys: z.ZodDefault<z.ZodNumber>;
16
+ }, z.core.$strip>;
17
+ export type ControlPlaneConfig = z.output<typeof ControlPlaneConfig>;
18
+ export type ControlPlaneConfigInput = z.input<typeof ControlPlaneConfig>;
19
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/controlPlane/config/config.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AA0ExB,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;iBAmH3B,CAAC;AACL,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,kBAAkB,CAAC,CAAC;AACrE,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC"}
@@ -0,0 +1,85 @@
1
+ import "../token/claims.js";
2
+ import { z } from "zod";
3
+ //#region src/controlPlane/config/config.ts
4
+ /**
5
+ * The `control-plane` seam's config — the thin, user-owned surface in a Worker's `pithy.config.ts`.
6
+ * Every field is `.describe()`d: the descriptions feed the self-documenting CLI (CLAUDE.md §Config).
7
+ *
8
+ * Parsed once at assembly, so a bad value fails on deploy rather than on the first management call.
9
+ * That matters more here than anywhere else in the tree: the failure mode of a loose setting is not a
10
+ * broken feature, it is an admin surface that accepts something it should have refused, and nothing
11
+ * observable goes wrong until someone uses it.
12
+ *
13
+ * The defaults are deliberately tight. A management token lives one minute, is accepted one minute
14
+ * either side of the clock, and its `jti` is remembered for three — comfortably longer than the widest
15
+ * window any such token can be accepted in, which is the whole point of the cross-field rule below.
16
+ *
17
+ * None of this concerns Cloudflare's control plane. That is the outbound provisioning API behind
18
+ * `@pithy-sh/cloudflare`; this is an inbound seam the adopter's own Worker exposes.
19
+ */
20
+ /** A rooted path with no trailing slash — Hono mounts on exactly this string. */
21
+ const BASE_PATH_PATTERN = /^\/[a-zA-Z0-9\-._~]+(?:\/[a-zA-Z0-9\-._~]+)*$/;
22
+ /** Five minutes. The ceiling on skew and on a token's life: past it, "short-lived" stops being true. */
23
+ const MAX_WINDOW_SECONDS = 300;
24
+ /** An hour. The ceiling on replay memory — beyond it the set costs more than the risk it retires. */
25
+ const MAX_JTI_TTL_SECONDS = 3600;
26
+ /** Two hours. Chrome's own ceiling on a cached preflight; asking for more is asking for nothing. */
27
+ const MAX_CORS_MAX_AGE_SECONDS = 7200;
28
+ /**
29
+ * Exactly an origin — what a browser puts in `Origin`, and nothing else.
30
+ *
31
+ * `z.url()` is not this check: it accepts `https://ops.example.com/`, a path, a wildcard, and `ftp://`.
32
+ * Every one of those would sit in the config file looking correct and then match no browser `Origin`,
33
+ * which fails as a bare refusal with nothing to read. The trailing-slash form is the one to expect,
34
+ * because it is what an address bar shows the person copying the value.
35
+ */
36
+ /** A DNS hostname, or a bracketed IP literal — `[::1]`, the address a Vite dev server binds by default. */
37
+ const ORIGIN_HOSTNAME = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/;
38
+ const IP_LITERAL = /^\[[0-9a-fA-F:.]+\]$/;
39
+ /**
40
+ * Exactly an origin: a scheme a browser sends, a real host, and nothing after it.
41
+ *
42
+ * **It refuses a misunderstanding and forgives a spelling.** A path, a query, a fragment or a
43
+ * credential means the writer thinks this value scopes something it does not — `https://ops.example.com/admin`
44
+ * does not restrict anything to `/admin` — so those are refused at deploy, where the message can say so.
45
+ * A trailing slash and an explicit `:443` are the same origin written a different way, and they are what
46
+ * an address bar hands the person copying the value, so they are accepted and normalized by
47
+ * `allowedOriginSet` rather than turned into a puzzle.
48
+ *
49
+ * `z.url()` is not this check: it accepts `https://*.example.com` — a wildcard is a legal hostname to
50
+ * the URL parser and means nothing to a browser — and `ftp://x.com`, which has an origin too.
51
+ */
52
+ const ORIGIN_ONLY = (value) => {
53
+ let url;
54
+ try {
55
+ url = new URL(value);
56
+ } catch {
57
+ return false;
58
+ }
59
+ if (url.protocol !== "https:" && url.protocol !== "http:") return false;
60
+ if (url.username !== "" || url.password !== "") return false;
61
+ if (url.pathname !== "/" || url.search !== "" || url.hash !== "") return false;
62
+ return ORIGIN_HOSTNAME.test(url.hostname) || IP_LITERAL.test(url.hostname);
63
+ };
64
+ const ControlPlaneConfig = z.object({
65
+ basePath: z.string().regex(BASE_PATH_PATTERN, "A base path must start with `/`, carry no trailing slash, and contain no spaces (e.g. `/control-plane`).").default("/control-plane").describe("Where the seam's own routes mount — ping, manifest, and key management. Move it to sit under an existing admin prefix; it is not a security control, since every route behind it is default-denied anyway."),
66
+ issuer: z.url().default("https://app.pithy.sh").describe("The management-client origin a NEW connection is registered against — trust-critical, and effectively permanent. Every registered Worker verifies the `iss` on every call, so changing this is a migration across every connection, not a config edit. Verification reads the issuer stored on the connection; this only supplies the default written at connect time. It also seeds the browser origins this Worker answers a CORS preflight for (see `allowedOrigins`), which is the one thing about this value the Worker itself reads at runtime."),
67
+ allowedOrigins: z.array(z.url().refine(ORIGIN_ONLY, "An allowed origin is a scheme and a host — `https://ops.example.com`, `http://localhost:5173`, `http://[::1]:5173`. A path, a query or a credential is refused, because it looks like it scopes the entry and does not.").describe("One exact browser origin, spelled the way a browser sends it in `Origin`.")).default([]).describe("Browser origins allowed to call this Worker's control-plane surface cross-origin, **in addition to** `issuer`. Additive by construction: an entry here never removes `issuer`, so adding your own console cannot lock out the dashboard that was already working. Read from this config alone and never from a connection row — a preflight carries no credential, so answering one must cost no database read and must reveal nothing about which origins are registered. A management client you host yourself belongs here, or in `issuer` if it is the only one."),
68
+ corsMaxAgeSeconds: z.number().int().min(0).max(MAX_CORS_MAX_AGE_SECONDS).default(600).describe("How long a browser may cache a preflight for this Worker's admin surface, in seconds. The allow-list it caches is a compile-time constant, so ten minutes costs nothing and saves the dashboard a second round trip on every call. **Set it to 0 while you are working out an allow-list**: a browser that cached a refusal keeps refusing for the full window after you have fixed `allowedOrigins`, which reads exactly like a change that did not take. Browsers cap this themselves, so a larger number here is a request, not a guarantee."),
69
+ clockSkewSeconds: z.number().int().min(0).max(MAX_WINDOW_SECONDS).default(60).describe("How far a token's `iat`/`exp` may sit outside this Worker's clock and still be accepted. It absorbs real drift between two machines; it also widens the window a captured token stays live in, which is why it is capped rather than left to taste."),
70
+ maxTokenLifetimeSeconds: z.number().int().positive().max(MAX_WINDOW_SECONDS).default(60).describe("The longest `exp - iat` this Worker will honor, whatever the token asks for. The adopter's ceiling, not the management client's choice: a client that mints hour-long tokens still gets one minute of them here."),
71
+ jtiTtlSeconds: z.number().int().positive().max(MAX_JTI_TTL_SECONDS).default(300).describe("How long a spent `jti` is remembered, so the same token cannot be presented twice. It must outlive the widest window a token is accepted in (lifetime plus skew) — a shorter memory forgets a token that is still valid, which is precisely the replay this exists to stop."),
72
+ replayBackend: z.enum(["d1", "kv"]).default("d1").describe("Where spent token ids are recorded. `d1` claims with `INSERT … ON CONFLICT DO NOTHING`, so the primary key decides the race and one token is spendable exactly once, wherever the requests land — it costs one write on a path an administrator paces. `kv` skips that write and is best-effort: Workers KV has no compare-and-set and is eventually consistent across colocations, so one token presented twice in two places inside the propagation window can pass twice. Choose `kv` only where every management operation is idempotent."),
73
+ keyRetentionDays: z.number().int().positive().max(365).default(30).describe("How long an expired or revoked key stays on the connection row before pruning. Long enough to read a rotation back; short enough that verification never parses a growing blob. The audit trail is the history of record — the row is working state, not an archive."),
74
+ maxKeys: z.number().int().min(2).max(32).default(8).describe("The most keys one connection may hold, live and superseded together. At least two, because a rotation overlap needs the new key registered while the old one still works — a ceiling of one would make safe rotation impossible.")
75
+ }).describe("Configuration for the inbound `control-plane` seam: where its routes mount, which management-client origin new connections trust, and the token and key-lifecycle bounds this Worker enforces.").check((ctx) => {
76
+ const widestWindow = ctx.value.maxTokenLifetimeSeconds + 2 * ctx.value.clockSkewSeconds;
77
+ if (ctx.value.jtiTtlSeconds <= widestWindow) ctx.issues.push({
78
+ code: "custom",
79
+ input: ctx.value,
80
+ path: ["jtiTtlSeconds"],
81
+ message: `jtiTtlSeconds (${ctx.value.jtiTtlSeconds}) must exceed maxTokenLifetimeSeconds + 2 × clockSkewSeconds (${widestWindow}). A token is accepted a clock skew either side of its window, so a shorter memory lets it outlive the record of it and replay.`
82
+ });
83
+ });
84
+ //#endregion
85
+ export { ControlPlaneConfig };
@@ -0,0 +1,27 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * The verified control-plane caller, on `c.var.controlPlane`.
4
+ *
5
+ * **This is deliberately not `AuthContext`, and the two must never be conflated.** A management client
6
+ * is not a user of the adopter's app: it holds no session, owns no user row, and signed up for
7
+ * nothing. If a control-plane call populated `c.var.auth`, every `requireAuth()` in every capability
8
+ * would pass for it — a scope escalation across the whole tree, from one seemingly convenient
9
+ * assignment. So the seam gets its own request variable, and `requireControlPlane` is the only gate
10
+ * that reads it.
11
+ *
12
+ * Statelessness is the feature. Nothing here is persisted: the context is derived from one token,
13
+ * lives for one request, and is discarded. There is no session to hijack and no row to leak. Exactly
14
+ * two things outlive the call — the `jti` in the replay set, and the audit event.
15
+ */
16
+ export declare const ControlPlaneContext: z.ZodObject<{
17
+ connectionId: z.ZodString;
18
+ environment: z.ZodString;
19
+ issuer: z.ZodString;
20
+ subject: z.ZodString;
21
+ scope: z.ZodString;
22
+ grantedScopes: z.ZodArray<z.ZodString>;
23
+ keyId: z.ZodString;
24
+ tokenId: z.ZodString;
25
+ }, z.core.$strip>;
26
+ export type ControlPlaneContext = z.output<typeof ControlPlaneContext>;
27
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/controlPlane/context.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;iBAyC7B,CAAC;AACJ,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,mBAAmB,CAAC,CAAC"}
@@ -0,0 +1,29 @@
1
+ import { ControlPlaneScope } from "./scope/scope.js";
2
+ import { z } from "zod";
3
+ //#region src/controlPlane/context.ts
4
+ /**
5
+ * The verified control-plane caller, on `c.var.controlPlane`.
6
+ *
7
+ * **This is deliberately not `AuthContext`, and the two must never be conflated.** A management client
8
+ * is not a user of the adopter's app: it holds no session, owns no user row, and signed up for
9
+ * nothing. If a control-plane call populated `c.var.auth`, every `requireAuth()` in every capability
10
+ * would pass for it — a scope escalation across the whole tree, from one seemingly convenient
11
+ * assignment. So the seam gets its own request variable, and `requireControlPlane` is the only gate
12
+ * that reads it.
13
+ *
14
+ * Statelessness is the feature. Nothing here is persisted: the context is derived from one token,
15
+ * lives for one request, and is discarded. There is no session to hijack and no row to leak. Exactly
16
+ * two things outlive the call — the `jti` in the replay set, and the audit event.
17
+ */
18
+ const ControlPlaneContext = z.object({
19
+ connectionId: z.string().describe("The connection this call authenticated against — the token's `aud`, verified against the loaded registration. Identifies the management client, per adopter, per project, per environment."),
20
+ environment: z.string().describe("The environment the connection is bound to, already checked against this Worker's own. A staging credential never reaches production, so by the time this is set the two agree."),
21
+ issuer: z.string().describe("The `iss` this connection trusts and this token carried. Verified, not merely recorded — it is the origin the adopter agreed to accept calls from."),
22
+ subject: z.string().describe("The management client's own user id from the token's `sub` — who, on their side, is acting. Recorded as the audit `actorId` so the trail answers 'which person at the dashboard did this', not just 'the dashboard'."),
23
+ scope: ControlPlaneScope.describe("The single scope this token was minted for. One call, one operation — a token carrying the whole grant would make every call as dangerous as the most dangerous one."),
24
+ grantedScopes: z.array(ControlPlaneScope).describe("Every scope the adopter granted this connection, read from their own row. The authority: a scope absent here is denied however the token is written."),
25
+ keyId: z.string().describe("Which registered key verified this call. Recorded so a rotation can be traced through the trail, and so a key still in use is visible before it is expired."),
26
+ tokenId: z.string().describe("The token's `jti`, already claimed in the replay set. Recorded to tie one audit event to one call.")
27
+ }).describe("The verified control-plane caller for one request. Never an AuthContext: a management client is not a user of the adopter's app.");
28
+ //#endregion
29
+ export { ControlPlaneContext };
@@ -0,0 +1,79 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * The registration record: who may call this Worker's control-plane routes, from where, with what
4
+ * keys, for which operations.
5
+ *
6
+ * **This lives in D1, not the Secrets Store.** A public key is not confidential — but it *is*
7
+ * integrity-critical, because whoever can change it owns the seam. D1 gives it a queryable lifecycle,
8
+ * an audit trail, and room for two valid keys during a rotation overlap. Writes come only from the CLI
9
+ * provisioning path or from a route authenticated by the key being replaced; no runtime route with a
10
+ * lesser credential can touch it.
11
+ */
12
+ /** An Ed25519 public key in JWK form — the only key type this seam accepts. */
13
+ export declare const Ed25519PublicJwk: z.ZodObject<{
14
+ kty: z.ZodLiteral<"OKP">;
15
+ crv: z.ZodLiteral<"Ed25519">;
16
+ x: z.ZodString;
17
+ }, z.core.$strip>;
18
+ export type Ed25519PublicJwk = z.output<typeof Ed25519PublicJwk>;
19
+ /**
20
+ * One registered public key and its validity window — an element of the connection's versioned array.
21
+ *
22
+ * Several keys are valid at once during a rotation overlap. That is a normal state, not an exception:
23
+ * the new key is appended and proven *before* the old one is given an end date, so there is always a
24
+ * live key to authenticate the next step with.
25
+ */
26
+ export declare const RegisteredKey: z.ZodObject<{
27
+ keyId: z.ZodString;
28
+ publicKey: z.ZodObject<{
29
+ kty: z.ZodLiteral<"OKP">;
30
+ crv: z.ZodLiteral<"Ed25519">;
31
+ x: z.ZodString;
32
+ }, z.core.$strip>;
33
+ validFrom: z.ZodCodec<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodDate]>, z.ZodDate>;
34
+ validUntil: z.ZodNullable<z.ZodCodec<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodDate]>, z.ZodDate>>;
35
+ revokedAt: z.ZodNullable<z.ZodCodec<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodDate]>, z.ZodDate>>;
36
+ }, z.core.$strip>;
37
+ export type RegisteredKey = z.output<typeof RegisteredKey>;
38
+ /**
39
+ * One management-client connection — the row in `pithy_controlplane_connections`.
40
+ *
41
+ * Keys are a versioned JSON column rather than a child table for two reasons. **Rotation becomes one
42
+ * atomic `UPDATE`**: appending the new key and expiring the old cannot half-apply, where separate rows
43
+ * would need a batch. And **verification stays a single read** with no join on a Worker hot path —
44
+ * scanning two or three keys for a matching `kid` is free.
45
+ */
46
+ export declare const ControlPlaneConnection: z.ZodObject<{
47
+ id: z.ZodUUID;
48
+ environment: z.ZodString;
49
+ issuer: z.ZodURL;
50
+ workerUrl: z.ZodURL;
51
+ basePath: z.ZodDefault<z.ZodString>;
52
+ scopes: z.ZodCodec<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodArray<z.ZodString>>;
53
+ keys: z.ZodCodec<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodObject<{
54
+ keyId: z.ZodString;
55
+ publicKey: z.ZodObject<{
56
+ kty: z.ZodLiteral<"OKP">;
57
+ crv: z.ZodLiteral<"Ed25519">;
58
+ x: z.ZodString;
59
+ }, z.core.$strip>;
60
+ validFrom: z.ZodCodec<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodDate]>, z.ZodDate>;
61
+ validUntil: z.ZodNullable<z.ZodCodec<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodDate]>, z.ZodDate>>;
62
+ revokedAt: z.ZodNullable<z.ZodCodec<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodDate]>, z.ZodDate>>;
63
+ }, z.core.$strip>>]>, z.ZodArray<z.ZodObject<{
64
+ keyId: z.ZodString;
65
+ publicKey: z.ZodObject<{
66
+ kty: z.ZodLiteral<"OKP">;
67
+ crv: z.ZodLiteral<"Ed25519">;
68
+ x: z.ZodString;
69
+ }, z.core.$strip>;
70
+ validFrom: z.ZodCodec<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodDate]>, z.ZodDate>;
71
+ validUntil: z.ZodNullable<z.ZodCodec<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodDate]>, z.ZodDate>>;
72
+ revokedAt: z.ZodNullable<z.ZodCodec<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodDate]>, z.ZodDate>>;
73
+ }, z.core.$strip>>>;
74
+ createdAt: z.ZodCodec<z.ZodUnion<readonly [z.ZodNumber, z.ZodString, z.ZodDate]>, z.ZodDate>;
75
+ updatedAt: z.ZodCodec<z.ZodUnion<readonly [z.ZodNumber, z.ZodString, z.ZodDate]>, z.ZodDate>;
76
+ }, z.core.$strip>;
77
+ export type ControlPlaneConnection = z.output<typeof ControlPlaneConnection>;
78
+ export type ControlPlaneConnectionRow = z.input<typeof ControlPlaneConnection>;
79
+ //# sourceMappingURL=connection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../../src/controlPlane/data/connection.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB;;;;;;;;;GASG;AAEH,+EAA+E;AAC/E,eAAO,MAAM,gBAAgB;;;;iBAgBoF,CAAC;AAClH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEjE;;;;;;GAMG;AACH,eAAO,MAAM,aAAa;;;;;;;;;;iBAoBoF,CAAC;AAC/G,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,aAAa,CAAC,CAAC;AAE3D;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA4ChC,CAAC;AACJ,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAC7E,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC"}
@@ -0,0 +1,55 @@
1
+ import { ControlPlaneScope } from "../scope/scope.js";
2
+ import { JsonDate, SQLiteDate, sqliteJson } from "../../data/codecs.js";
3
+ import { z } from "zod";
4
+ //#region src/controlPlane/data/connection.ts
5
+ /**
6
+ * The registration record: who may call this Worker's control-plane routes, from where, with what
7
+ * keys, for which operations.
8
+ *
9
+ * **This lives in D1, not the Secrets Store.** A public key is not confidential — but it *is*
10
+ * integrity-critical, because whoever can change it owns the seam. D1 gives it a queryable lifecycle,
11
+ * an audit trail, and room for two valid keys during a rotation overlap. Writes come only from the CLI
12
+ * provisioning path or from a route authenticated by the key being replaced; no runtime route with a
13
+ * lesser credential can touch it.
14
+ */
15
+ /** An Ed25519 public key in JWK form — the only key type this seam accepts. */
16
+ const Ed25519PublicJwk = z.object({
17
+ kty: z.literal("OKP").describe("Key type. Ed25519 is an octet key pair, so this is always `OKP` (RFC 8037)."),
18
+ crv: z.literal("Ed25519").describe("The curve, pinned to Ed25519. Pinned rather than accepted from the key so a weaker curve cannot be smuggled in at registration and honored at verification."),
19
+ x: z.string().min(1).max(128).describe("The base64url-encoded public key point. Public by definition — safe to store, and safe to return from `GET /control-plane/keys`.")
20
+ }).describe("An Ed25519 public key as a JWK: what a management client registers, and what verification imports.");
21
+ /**
22
+ * One registered public key and its validity window — an element of the connection's versioned array.
23
+ *
24
+ * Several keys are valid at once during a rotation overlap. That is a normal state, not an exception:
25
+ * the new key is appended and proven *before* the old one is given an end date, so there is always a
26
+ * live key to authenticate the next step with.
27
+ */
28
+ const RegisteredKey = z.object({
29
+ keyId: z.string().min(1).max(64).describe("The key's id, matched against a token's `kid` header. Opaque, and minted by the management client."),
30
+ publicKey: Ed25519PublicJwk.describe("The public key this id names. Imported per request to verify a signature; nothing secret is stored here."),
31
+ validFrom: JsonDate.describe("When this key became valid. A call signed before it is rejected, so a key registered ahead of time cannot be used early."),
32
+ validUntil: JsonDate.nullable().describe("When this key stops being accepted, or null while it is open-ended. Set only by the expire route, never at registration — the open-ended overlap is what keeps a failed rotation from locking anyone out."),
33
+ revokedAt: JsonDate.nullable().describe("When this key was revoked outright, or null. Revocation ignores `validUntil` and takes effect immediately — it is the adopter's unilateral control, and it must not wait for a window to close.")
34
+ }).describe("One registered public key with its validity window. Two live at once during a rotation overlap.");
35
+ /**
36
+ * One management-client connection — the row in `pithy_controlplane_connections`.
37
+ *
38
+ * Keys are a versioned JSON column rather than a child table for two reasons. **Rotation becomes one
39
+ * atomic `UPDATE`**: appending the new key and expiring the old cannot half-apply, where separate rows
40
+ * would need a batch. And **verification stays a single read** with no join on a Worker hot path —
41
+ * scanning two or three keys for a matching `kid` is free.
42
+ */
43
+ const ControlPlaneConnection = z.object({
44
+ id: z.uuid().describe("The connection id — a UUID because it is the token `aud` and therefore externally exposed. Text rather than an autoincrementing integer, so connections cannot be enumerated."),
45
+ environment: z.string().min(1).max(32).describe("The environment this connection is valid in (`dev`, `staging`, `production`). Checked against the Worker's own on every call, so a staging credential cannot reach production."),
46
+ issuer: z.url().describe("The exact `iss` this connection accepts. Verified on every call, and effectively permanent — changing the management client's origin is a migration across every registered connection, not a DNS change."),
47
+ workerUrl: z.url().describe("This environment's deployed Worker URL, captured at connect. The address the management client calls; re-pointed by `pithy dashboard connect --update` when a custom domain or a rename moves it."),
48
+ basePath: z.string().min(1).default("/control-plane").describe("Where this Worker mounts the control-plane seam, captured at connect from its resolved config. Stored beside `workerUrl` because the two together fully determine the manifest address — and this is the one part of it a client cannot discover, since it *is* the manifest's own address. Defaulted rather than required so a connection registered before the column existed still parses, reading as the default it was necessarily using."),
49
+ scopes: sqliteJson(z.array(ControlPlaneScope)).describe("The operations this connection was granted, stored and enforced on the adopter's side. Enforced only by the caller, a scope would not be a limit at all."),
50
+ keys: sqliteJson(z.array(RegisteredKey)).describe("Every key this connection may sign with, current and superseded. One JSON column rather than a child table, so a rotation is one atomic UPDATE and verification is one read."),
51
+ createdAt: SQLiteDate.describe("When the connection was registered. Ms-epoch in SQLite, a Date in TypeScript."),
52
+ updatedAt: SQLiteDate.describe("When the row last changed — a key registered or expired, or the Worker URL re-pointed.")
53
+ }).describe("One management-client connection: its identity, the environment and URL it is bound to, the operations it was granted, and its versioned public keys.");
54
+ //#endregion
55
+ export { ControlPlaneConnection, Ed25519PublicJwk, RegisteredKey };
@@ -0,0 +1,62 @@
1
+ import type { RegisteredKey } from "./connection";
2
+ /**
3
+ * The registered-key lifecycle: pure array logic over a connection's `keys` column.
4
+ *
5
+ * **Registering a key and expiring one are two operations, and folding them into a single `rotate()`
6
+ * is the one design mistake this module exists to prevent.** One call would make append-and-expire
7
+ * atomic, and atomic is precisely wrong here. The whole safety property is that the old key outlives
8
+ * the new one's first successful call: a mis-copied JWK, a private key the management client cannot
9
+ * actually sign with, or a Worker that never answers leaves the adopter holding a credential that
10
+ * still works. Fuse the two writes together and that same mistake locks them out of the seam with
11
+ * nothing left to authenticate the fix with. Kept apart, a broken rotation costs a stale key.
12
+ *
13
+ * So the safe ordering is enforced here rather than documented: {@link appendKey} may not touch an
14
+ * existing key's window, and {@link expireKey} refuses unless another key is already live.
15
+ *
16
+ * Nothing in this module reads a clock, a binding, or D1. `now` is injected on every call, which is
17
+ * what lets a test assert what a connection looks like a year into a failed rotation.
18
+ */
19
+ /** Every key that may verify a call at `now`: not revoked, started, and not yet ended. */
20
+ export declare function activeKeys(keys: readonly RegisteredKey[], now: Date): RegisteredKey[];
21
+ /**
22
+ * The key that may verify this call, or null. Matching on `kid` alone would be the vulnerability —
23
+ * the window and the revocation are checked here so no caller can forget them.
24
+ */
25
+ export declare function findVerifyingKey(keys: readonly RegisteredKey[], keyId: string, now: Date): RegisteredKey | null;
26
+ /**
27
+ * Register a key. Appends, and appends only — no existing key's `validUntil` moves, so a connection
28
+ * mid-rotation always has the key it arrived with.
29
+ *
30
+ * A key that is already retired at `now` is refused rather than stored: it could never be proven by a
31
+ * ping, so registering it would only offer a second step that can never safely be taken.
32
+ */
33
+ export declare function appendKey(keys: readonly RegisteredKey[], key: RegisteredKey, now: Date): RegisteredKey[];
34
+ /**
35
+ * Close a key's window at `now`, once a replacement has proven itself.
36
+ *
37
+ * `provenKeyId` is the key that verified the request asking for this expiry — proof by use, and the
38
+ * reason the seam's `ping` scope is held implicitly by every connection. Two refusals guard the
39
+ * ordering: a prover that is not itself live has proven nothing, and an expiry that would leave no
40
+ * live key is the lockout, whatever the caller believes about their replacement.
41
+ *
42
+ * Re-expiring a key already closed is a no-op on its end date. The window a call was rejected under
43
+ * must not move retroactively.
44
+ */
45
+ export declare function expireKey(keys: readonly RegisteredKey[], keyId: string, options: {
46
+ provenKeyId: string;
47
+ now: Date;
48
+ }): RegisteredKey[];
49
+ /**
50
+ * Drop keys retired longer ago than `retentionMs`, then cap the array at `maxKeys`.
51
+ *
52
+ * The column is versioned, not a log — a superseded key is kept only long enough for the audit trail
53
+ * and a `GET /control-plane/keys` to explain a rotation. Two keys are never dropped: a live one, and
54
+ * one registered ahead of its `validFrom`, which is unretired rather than dead. So the cap is a
55
+ * target, not a guarantee; a connection with more live keys than `maxKeys` keeps all of them.
56
+ */
57
+ export declare function pruneKeys(keys: readonly RegisteredKey[], options: {
58
+ now: Date;
59
+ retentionMs: number;
60
+ maxKeys: number;
61
+ }): RegisteredKey[];
62
+ //# sourceMappingURL=keyLifecycle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keyLifecycle.d.ts","sourceRoot":"","sources":["../../../src/controlPlane/data/keyLifecycle.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAElD;;;;;;;;;;;;;;;;GAgBG;AAEH,0FAA0F;AAC1F,wBAAgB,UAAU,CAAC,IAAI,EAAE,SAAS,aAAa,EAAE,EAAE,GAAG,EAAE,IAAI,GAAG,aAAa,EAAE,CAErF;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,aAAa,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,GAAG,aAAa,GAAG,IAAI,CAE/G;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,aAAa,EAAE,EAAE,GAAG,EAAE,aAAa,EAAE,GAAG,EAAE,IAAI,GAAG,aAAa,EAAE,CAgBxG;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,SAAS,aAAa,EAAE,EAC9B,KAAK,EAAE,MAAM,EACb,OAAO,EAAE;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,IAAI,CAAA;CAAE,GAC1C,aAAa,EAAE,CAwBjB;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,SAAS,aAAa,EAAE,EAC9B,OAAO,EAAE;IAAE,GAAG,EAAE,IAAI,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC3D,aAAa,EAAE,CAyBjB"}