@pithy-sh/core 0.1.2 → 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,356 @@
1
+ import { BindingSpec } from "./bindings.js";
2
+ import { ValidationError } from "../error/pithyError.js";
3
+ import { manifestRecord } from "./vanishingKey.js";
4
+ import { DevSecret } from "./devSecret.js";
5
+ import { NAMESPACE_PATTERN } from "../migrations/registry.js";
6
+ import { DeclaredSecret } from "./secretOrigin.js";
7
+ import { z } from "zod";
8
+ //#region src/capability/manifest.ts
9
+ /**
10
+ * Whether a string prints as a double-quoted literal Biome leaves exactly as written.
11
+ *
12
+ * Two sequences are out, and only two. A `"` inside the value makes `JSON.stringify` escape it, and
13
+ * Biome's formatter then reprints the whole literal in single quotes to avoid the escape —
14
+ * `"he said \"hi\""` becomes `'he said "hi"'`, a formatting diff on a file the adopter never opened.
15
+ * And `${` trips `lint/suspicious/noTemplateCurlyInString`, which is noise in the same place.
16
+ *
17
+ * An apostrophe stays legal: with no `"` to escape, Biome keeps the double quotes. So do a backslash, a
18
+ * tab, a newline, and any non-ASCII character — every one of these checked by running Biome over the
19
+ * rendered output, because the whole defect class this closes is guessing what Biome would print.
20
+ */
21
+ function isPrintableString(value) {
22
+ return !value.includes("\"") && !value.includes("${");
23
+ }
24
+ /** A number whose `String()` form is already the literal Biome prints: a plain decimal, no exponent. */
25
+ const PLAIN_DECIMAL = /^-?\d+(?:\.\d+)?$/;
26
+ /**
27
+ * Whether a number prints as a numeric literal Biome leaves exactly as written.
28
+ *
29
+ * `String(1e21)` is `"1e+21"`, which Biome normalizes to `1e21`. Rather than carry a copy of its numeric
30
+ * normalizer, a default is held to a plain decimal — which is every number a worked example has any
31
+ * business carrying. `NaN` and `Infinity` fall out of the same rule, and JSON cannot state either.
32
+ */
33
+ function isPrintableNumber(value) {
34
+ return PLAIN_DECIMAL.test(String(value));
35
+ }
36
+ /** A string a manifest may state, and an object key it may use: one {@link renderConfigValue} can print. */
37
+ const PrintableString = z.string().refine(isPrintableString, { error: "A rendered value may not contain a double quote or \"${\" — Biome reprints the first and lints the second." });
38
+ /** A number a manifest may state: one whose `String()` form is the literal Biome prints. */
39
+ const PrintableNumber = z.number().refine(isPrintableNumber, { error: "A rendered number must be a plain decimal — Biome prints 1e21 where JavaScript prints 1e+21." });
40
+ const ConfigOptionValue = z.lazy(() => z.union([
41
+ PrintableString,
42
+ PrintableNumber,
43
+ z.boolean(),
44
+ z.array(ConfigOptionValue),
45
+ manifestRecord(z.record(PrintableString, ConfigOptionValue))
46
+ ])).describe("An option's rendered value: a JSON scalar, or a minimal worked example the adopter replaces.");
47
+ /**
48
+ * An object key that can be written bare in a TypeScript object literal.
49
+ *
50
+ * ASCII only, which is narrower than JavaScript: `café` is a legal bare identifier and Biome keeps it,
51
+ * checked by running Biome over it. It stays out because this is also the shape a *config option's* key
52
+ * is held to, and an option name an English keyboard cannot type is not one a capability should ship.
53
+ * Reserved words are in — `{ default: 1 }` and `{ class: 1 }` are both legal property names, checked the
54
+ * same way — so nothing here needs a keyword list to keep in step with the language.
55
+ */
56
+ const BARE_KEY = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
57
+ /**
58
+ * The characters that end a `//` comment. All four are line terminators to a JavaScript parser, so a
59
+ * `describe` carrying one puts everything after it into `pithy.config.ts` as bare code — Biome's first
60
+ * report on the generated file is a parse error, not a lint (#174). U+2028 and U+2029 are here because
61
+ * they really do terminate the comment; that was measured with Biome, not assumed.
62
+ */
63
+ const LINE_TERMINATOR = /[\n\r\u2028\u2029]/;
64
+ /**
65
+ * Whether a string prints as a `//` comment Biome leaves exactly as written.
66
+ *
67
+ * Two things break it, and only two. A line terminator ends the comment, so the rest of the `describe`
68
+ * lands as code. And trailing whitespace parses fine and then fails `biome format`, which is a diff on a
69
+ * file the adopter never opened — the same failure mode #171 closed for values.
70
+ *
71
+ * Everything else stays legal, each one checked by running Biome over the rendered comment: leading
72
+ * whitespace, an interior tab, a block-comment terminator, `${x}`, non-ASCII, and a line of any length.
73
+ * A `//` comment ends at the line and nowhere else, and Biome never reflows one, so there is no width
74
+ * rule here either.
75
+ */
76
+ function isPrintableComment(value) {
77
+ return !LINE_TERMINATOR.test(value) && value === value.trimEnd();
78
+ }
79
+ /** Print one string as source, refusing the two sequences {@link isPrintableString} keeps out. */
80
+ function renderString(value) {
81
+ if (!isPrintableString(value)) throw new ValidationError({
82
+ message: "A config value may not contain a double quote or \"${\".",
83
+ action: "Pick a value carrying neither, or write the option by hand in pithy.config.ts.",
84
+ detail: `Rendered as ${JSON.stringify(value)}, Biome would reprint or lint the line, so the generated pithy.config.ts would fail the scaffold's own biome check.`
85
+ });
86
+ return JSON.stringify(value);
87
+ }
88
+ /** Print one number as source, refusing anything {@link isPrintableNumber} keeps out. */
89
+ function renderNumber(value) {
90
+ if (!isPrintableNumber(value)) throw new ValidationError({
91
+ message: "A config number must be a plain decimal.",
92
+ action: "Write it without an exponent.",
93
+ detail: `${String(value)} is not the numeric literal Biome prints for it, so the generated pithy.config.ts would fail the scaffold's own biome check.`
94
+ });
95
+ return String(value);
96
+ }
97
+ /**
98
+ * Render a manifest default as TypeScript source, on one line.
99
+ *
100
+ * `pithy add` writes this straight into the adopter's `pithy.config.ts`, which the scaffold's own
101
+ * `biome check` then reads — so *valid* TypeScript is not the bar. It has to be the TypeScript Biome
102
+ * would have printed. `JSON.stringify` is not: it quotes every key, and Biome's `quoteProperties`
103
+ * default rewrites `{"code":"chips"}` to `{ code: "chips" }`, failing the lint gate on a project the
104
+ * adopter has not touched. This prints Biome's shape directly — bare keys wherever they are legal,
105
+ * spaces inside braces, `, ` between entries, `{}` and `[]` for the empty literals #161 relies on.
106
+ *
107
+ * **Total over what {@link ConfigOptionValue} *parses*, which is narrower than what it types.** The two
108
+ * shapes the schema refuses — a string carrying `"` or `${`, a number no plain decimal spells — are
109
+ * refused here too, with a `ValidationError`. A manifest cannot reach that path, since every manifest is
110
+ * parsed before it is rendered; `pithy add --set` can, because a flag's string goes straight through. A
111
+ * refusal at the command beats a config file that fails the adopter's first `bun run lint`.
112
+ *
113
+ * One line, always. Biome breaks any literal past {@link CONFIG_LINE_WIDTH}, and a broken literal is a
114
+ * `biome check` failure on a project the adopter has not touched — #161 and #168 both. That rule used to
115
+ * be this sentence and nothing else, with multiplayer's seed sitting at 98 columns of 120; it is now a
116
+ * test over every shipped manifest default, rendered by {@link renderConfigOptionLine} at the indent the
117
+ * writers really use (#171).
118
+ */
119
+ function renderConfigValue(value) {
120
+ if (typeof value === "string") return renderString(value);
121
+ if (typeof value === "number") return renderNumber(value);
122
+ if (typeof value === "boolean") return String(value);
123
+ if (Array.isArray(value)) return `[${value.map(renderConfigValue).join(", ")}]`;
124
+ const entries = Object.entries(value).map(([key, nested]) => `${BARE_KEY.test(key) ? key : renderString(key)}: ${renderConfigValue(nested)}`);
125
+ return entries.length === 0 ? "{}" : `{ ${entries.join(", ")} }`;
126
+ }
127
+ /**
128
+ * The column Biome wraps at — `formatter.lineWidth` in the `biome.jsonc` that `pithy init` scaffolds.
129
+ *
130
+ * A line of exactly this width survives untouched; one character more and Biome explodes the literal
131
+ * across a dozen lines, so `biome check` fails on a file the adopter never opened. Measured by running
132
+ * Biome over generated lines of 118 through 122 columns, not read off a docs page.
133
+ */
134
+ const CONFIG_LINE_WIDTH = 120;
135
+ /**
136
+ * The indent both writers render an option line at in a scaffolded project: the managed-region marker
137
+ * sits four columns in, and an option sits two further.
138
+ *
139
+ * Each writer takes its real indent from the file it is editing, so an adopter who nested `capabilities`
140
+ * deeper gets a longer line than this. This is the scaffold's indent, and the scaffold is what the width
141
+ * rule is measured against — a default that does not fit here fits nowhere.
142
+ */
143
+ const CONFIG_OPTION_INDENT = " ";
144
+ /** Whether a rendered value is a reference to one of the scaffold's constants rather than a literal. */
145
+ function isConstantRef(value) {
146
+ return typeof value === "object" && value !== null && !Array.isArray(value) && "constant" in value;
147
+ }
148
+ function renderConfigOptionLine(key, value, indent) {
149
+ if (!BARE_KEY.test(key)) throw new ValidationError({
150
+ message: `A config option key must be a bare identifier, and "${key}" is not.`,
151
+ action: "Rename the option, or write it by hand in pithy.config.ts.",
152
+ detail: `Rendered as ${JSON.stringify(`${key}: …`)}, the generated pithy.config.ts would not parse as TypeScript — a key is written bare, so it cannot carry a hyphen, a space, a quote, or a leading digit.`
153
+ });
154
+ return `${indent}${key}: ${isConstantRef(value) ? CONFIG_CONSTANTS[value.constant] : renderConfigValue(value)},`;
155
+ }
156
+ /**
157
+ * The comment line both writers put above an option: its `describe`, verbatim, as a `//` comment.
158
+ *
159
+ * The rationale is the whole reason the generated config documents itself, and it comes out of a manifest
160
+ * read from `node_modules` — third-party text reaching generated source. Both writers used to build this
161
+ * line themselves, which is the arrangement that let `pithy add` and `pithy upgrade` disagree about the
162
+ * line *below* it (#171); they now share this, so a rule stated here holds for both.
163
+ *
164
+ * Total over what {@link ConfigOption} parses, exactly as {@link renderConfigOptionLine} is: a manifest
165
+ * cannot reach the throw, because every manifest is parsed before it is rendered.
166
+ */
167
+ function renderConfigOptionComment(describe, indent) {
168
+ if (!isPrintableComment(describe)) throw new ValidationError({
169
+ message: "A config option's rationale must be one line, with no trailing whitespace.",
170
+ action: "Put it on one line, or write the option by hand in pithy.config.ts.",
171
+ detail: `Rendered as ${JSON.stringify(`// ${describe}`)}, the generated pithy.config.ts would either not parse — a line break ends the comment and the rest becomes code — or fail biome format on the trailing whitespace.`
172
+ });
173
+ return `${indent}// ${describe}`;
174
+ }
175
+ /**
176
+ * A capability's own name, as generated source carries it.
177
+ *
178
+ * The same shape a config option's key is held to, and for the same reason: it is written **bare**, and
179
+ * twice — as the binding of the import statement (`import { auth } from …`) and as the registration call
180
+ * (`auth(),`). Neither position quotes anything, so a name that is not an identifier is not a syntax
181
+ * error in a string, it is a hole. `audit }) ; evil(` closed the capabilities array and opened a call
182
+ * (#183).
183
+ *
184
+ * It is also the leaf of the fork directory `pithy add --eject` writes (`./capabilities/<name>`), so the
185
+ * same rule keeps a name from carrying a path separator or a `..`.
186
+ */
187
+ const CAPABILITY_NAME = BARE_KEY;
188
+ /**
189
+ * The npm package name a capability ships under, as the import specifier carries it.
190
+ *
191
+ * Rendered inside a double-quoted specifier — `"@pithy-sh/auth/src/index"` — where the required shape is
192
+ * an npm package name and not a bare identifier. The registry's own grammar is narrower than "no quote
193
+ * in it", so that is what is stated: an optional `@scope/`, then a name starting alphanumeric and made of
194
+ * letters, digits, `.`, `_` and `-`. Uppercase is admitted because npm still serves the legacy names that
195
+ * carry it; everything npm forbids outright — a leading `.` or `_`, a space, a second `/`, a quote — is
196
+ * out, and with it the escape `@pithy-sh/audit"; evil(); //` used to close the specifier and append a
197
+ * statement (#183).
198
+ *
199
+ * 214 is npm's own length cap, checked separately so the refusal can say which rule was broken.
200
+ */
201
+ const PACKAGE_NAME = /^(?:@[A-Za-z0-9][A-Za-z0-9._-]*\/)?[A-Za-z0-9][A-Za-z0-9._-]*$/;
202
+ /** npm's own limit on a package name, counted across the scope and the separator. */
203
+ const PACKAGE_NAME_MAX = 214;
204
+ /**
205
+ * Whether a module specifier prints inside a double-quoted literal Biome leaves exactly as written.
206
+ *
207
+ * The same two sequences {@link isPrintableString} keeps out of a value, plus the line terminators that
208
+ * would end the statement early. A specifier is not a manifest field — it is composed from one
209
+ * ({@link CapabilityManifest.package}) or from the fork path — so it is checked here rather than at the
210
+ * schema, and the check is what makes {@link renderCapabilityImport} total over both callers.
211
+ */
212
+ function isPrintableSpecifier(value) {
213
+ return value.length > 0 && isPrintableString(value) && !LINE_TERMINATOR.test(value);
214
+ }
215
+ /** Refuse a capability name generated source cannot carry. Total over what {@link CapabilityManifest} parses. */
216
+ function requireCapabilityName(name) {
217
+ if (CAPABILITY_NAME.test(name)) return;
218
+ throw new ValidationError({
219
+ message: `A capability name must be a bare identifier, and ${JSON.stringify(name)} is not.`,
220
+ action: "Rename the capability, or wire it by hand in pithy.config.ts.",
221
+ detail: `It is written bare into the generated pithy.config.ts twice — as the import binding and as the registration call — so ${JSON.stringify(name)} would produce a file that does not parse as TypeScript.`
222
+ });
223
+ }
224
+ /**
225
+ * The one import line a capability's wiring is written as.
226
+ *
227
+ * Both halves are interpolated raw, and both used to be `z.string().min(1)`: `pithy add` wrote
228
+ * `import { audit }) ; evil( } from "@pithy-sh/audit/src/index";` from a manifest that parsed, and
229
+ * reported `Done.` (#183). The line has one producer now, for the same reason
230
+ * {@link renderConfigOptionLine} does — a rule stated at one call site is a rule the next call site does
231
+ * not have.
232
+ *
233
+ * Total over what {@link CapabilityManifest} parses. A parsed manifest cannot reach either throw; the
234
+ * fork path `pithy add --eject` composes can, which is why the specifier is checked rather than assumed.
235
+ */
236
+ function renderCapabilityImport(name, specifier) {
237
+ requireCapabilityName(name);
238
+ if (!isPrintableSpecifier(specifier)) throw new ValidationError({
239
+ message: `A capability's import specifier may not be empty, span lines, or contain a double quote or "\${".`,
240
+ action: "Correct the capability's package name, or write the import by hand in pithy.config.ts.",
241
+ detail: `Rendered as ${JSON.stringify(`from "${specifier}"`)}, the generated pithy.config.ts would either not parse or fail the scaffold's own biome check.`
242
+ });
243
+ return `import { ${name} } from "${specifier}";`;
244
+ }
245
+ /**
246
+ * The registration both writers put in `pithy.config.ts` for one capability — `auth(),` with no options,
247
+ * the block form with them.
248
+ *
249
+ * The name is the only thing here that comes from a manifest, and it is the thing that reached generated
250
+ * source unchecked (#183). One producer, so the rule holds for `pithy add`'s whole-entry write and for
251
+ * `pithy upgrade`'s conversion of a one-liner into a block alike.
252
+ */
253
+ function renderCapabilityRegistration({ name, indent, optionLines = [], trailingComma = true }) {
254
+ requireCapabilityName(name);
255
+ const end = trailingComma ? "," : "";
256
+ if (optionLines.length === 0) return `${indent}${name}()${end}`;
257
+ return [
258
+ `${indent}${name}({`,
259
+ ...optionLines,
260
+ `${indent}})${end}`
261
+ ].join("\n");
262
+ }
263
+ /**
264
+ * One configurable option a capability exposes. `pithy add` renders each as
265
+ * `cap({ key: default })` in `pithy.config.ts`, with `describe` as the comment
266
+ * above it — the self-documenting config surface (docs/CLI.md §Config). The
267
+ * mount-point model rides on options like this (e.g. `basePath`), so handlers
268
+ * stay in the package and the user owns the wiring.
269
+ *
270
+ * **Every option the capability's config type requires belongs here.** The manifest is the only thing
271
+ * `pithy add` reads, so an option it omits is an option the generated config omits — see
272
+ * {@link ConfigOptionValue} for the one that got away.
273
+ *
274
+ * **All three fields are narrowed to what the renderer can print, not just the value.** #171 held
275
+ * `default` to shapes Biome prints unchanged and left `key` and `describe` as `z.string().min(1)`, so a
276
+ * manifest could still state `content-type` — rendered `content-type: "x",`, which is not TypeScript at
277
+ * all — or a `describe` with a newline in it, whose second line landed in `pithy.config.ts` as bare code.
278
+ * A manifest is third-party data read from `node_modules`, and an option key is that data interpolated
279
+ * into generated source; `}) ; evil(` is the shape that makes the point. Narrowing here is what lets
280
+ * {@link renderConfigOptionLine} and {@link renderConfigOptionComment} guarantee the whole line rather
281
+ * than its right-hand side (#174).
282
+ */
283
+ /**
284
+ * The constants a scaffolded `pithy.config.ts` defines, that a manifest option may name instead of
285
+ * stating a literal — identifier by key, so the manifest carries a **name from a closed set** and never
286
+ * an expression.
287
+ *
288
+ * That is the whole security argument, and it is the same one #183 settled for a capability's name: a
289
+ * manifest is third-party data read out of `node_modules` and written into the adopter's TypeScript, so
290
+ * anything it states unquoted must come from a list this package controls. An `expression` field would
291
+ * put arbitrary code in the adopter's config on the strength of a package they installed.
292
+ *
293
+ * One entry today. `PUBLIC_ORIGIN` is the Worker's address for the environment it composes in, derived
294
+ * by `originFor(compositionEnvironment(), DOMAINS)` in the scaffolded config — the line that exists so no
295
+ * capability ever asks an adopter to write an origin down again.
296
+ */
297
+ const CONFIG_CONSTANTS = { publicOrigin: "PUBLIC_ORIGIN" };
298
+ /** Which scaffolded constant an option's value is, by key. */
299
+ const ConfigConstant = z.enum(Object.keys(CONFIG_CONSTANTS)).describe("A constant the scaffolded pithy.config.ts defines, named from a closed set rather than spelled out.");
300
+ const ConfigOption = z.object({
301
+ key: z.string().regex(BARE_KEY, { error: (issue) => `A config option key must be a bare identifier, and ${JSON.stringify(issue.input)} is not — it is written bare into pithy.config.ts.` }).describe("Option name passed to the capability factory (e.g. \"basePath\"); a bare identifier."),
302
+ default: ConfigOptionValue.optional().describe("The value rendered into pithy.config.ts when no override is given — and **its absence is what makes an option required**. There is no flag beside it saying so: an option that states a default has an answer the kit is willing to pick, and an option that states none has one only the adopter can. A required option is settled before a byte is written — a prompt asks it with nothing to accept by pressing enter, `--set key=value` answers it non-interactively, and a run carrying neither is refused naming the flag. Nothing is guessed, because a decision worth refusing over is one that is expensive to reverse: payments' `billingSubject` decides whether an entitlement is held by a person or a company, and it lands in a column and a UNIQUE index."),
303
+ choices: z.array(PrintableString).min(1).optional().describe("The values this option accepts, when they are a closed set. Three things follow from stating them, and none of them can be had without: the prompt is a select rather than free text, `--set` refuses a value outside them instead of writing it into the config, and a refusal can name what is legal — which is the whole of what a caller needs in order to answer a required option. Held to the same printable-string rule as a default, because a choice becomes the value written into the adopter's TypeScript."),
304
+ choicesNeedingCode: manifestRecord(z.record(PrintableString, z.string().min(1))).optional().describe("Choices `pithy add` must refuse, each mapped to the sentence telling the adopter what to do instead — a value whose composition needs a seam only they can write. `pithy add` renders JSON into `pithy.config.ts`; it cannot render a function, so a choice whose validity depends on one is a choice it can write and the kit will then refuse to load. `payments`' `billingSubject: \"organization\"` is the one: it requires a `resolveSubject` saying which organization a caller acts for, deliberately has no default because a capability that guessed would key a company's plan to whoever signed in first, and so a config carrying the choice without the seam is refused at assembly — bricking every later command in the project, since they all begin by loading the config (#483). Refusing the choice up front costs the adopter one hand-edit; writing it costs them the project."),
305
+ constant: ConfigConstant.optional().describe("A constant the scaffolded pithy.config.ts defines, rendered *unquoted* in place of `default` when the target config declares it. The one that exists is `publicOrigin` — `PUBLIC_ORIGIN`, this Worker's address for the environment it composes in. An option whose value is an origin must name it rather than state a URL, because a URL written down is production's URL written into staging: it is what mailed staging's testers magic links into production and unsubscribed them there (#256). `default` is still required *for such an option* and is what a config with no such constant gets, so an older project is never handed an identifier it does not define — the one place the absent-default rule does not reach, and it is checked rather than trusted."),
306
+ describe: z.string().min(1).refine(isPrintableComment, { error: (issue) => `A config option's rationale must be one line with no trailing whitespace, and ${JSON.stringify(issue.input)} is not — it is written into pithy.config.ts as a // comment.` }).describe("Rationale rendered as the comment above this option in pithy.config.ts; one line.")
307
+ }).check((ctx) => {
308
+ const option = ctx.value;
309
+ if (option.choices && option.default !== void 0 && !option.choices.includes(option.default)) ctx.issues.push({
310
+ code: "custom",
311
+ input: option.default,
312
+ path: ["default"],
313
+ message: `A config option's default must be one of its choices, and ${JSON.stringify(option.default)} is not — ${option.key} offers ${JSON.stringify(option.choices)}.`
314
+ });
315
+ for (const choice of Object.keys(option.choicesNeedingCode ?? {})) if (!option.choices?.includes(choice)) ctx.issues.push({
316
+ code: "custom",
317
+ input: choice,
318
+ path: ["choicesNeedingCode", choice],
319
+ message: `A choice needing code must be one this option offers, and ${JSON.stringify(choice)} is not — ${option.key} offers ${JSON.stringify(option.choices ?? [])}.`
320
+ });
321
+ if (option.constant !== void 0 && option.default === void 0) ctx.issues.push({
322
+ code: "custom",
323
+ input: option.constant,
324
+ path: ["default"],
325
+ message: `A config option naming a constant must still carry a default, and ${option.key} names ${JSON.stringify(option.constant)} with none — a project whose pithy.config.ts predates the constant gets the default instead, so an option with no default would hand it an identifier nothing declares.`
326
+ });
327
+ }).describe("A configurable option a capability exposes (key, default, choices, rationale).");
328
+ /**
329
+ * Declarative, CLI-facing description of a capability. Lives at `pithy.manifest.json` in
330
+ * each capability package; read by `pithy add`/`upgrade` to wire bindings into
331
+ * `wrangler.jsonc` and scaffold config — without executing the package. Plain data, so
332
+ * it's a validated Zod object. `requiredBindings` reuse the `BindingSpec` contract, so a
333
+ * manifest's bindings are normalized (and rejected) exactly like a capability's own.
334
+ *
335
+ * **The invariant, stated here rather than at the writers: every string a manifest states that reaches
336
+ * generated source is constrained to what the renderer can print.** Three rounds got there one field at a
337
+ * time — #171 narrowed an option's `default`, #174 its `key` and `describe`, #183 the capability's own
338
+ * `name` and `package` — because each time the rule lived at the call site that had just been fixed. It
339
+ * lives here now, and `manifest.test.ts` walks this schema and fails the build on a string field that is
340
+ * neither constrained nor declared prose the CLI only ever prints.
341
+ */
342
+ const CapabilityManifest = z.object({
343
+ name: z.string().regex(CAPABILITY_NAME, { error: (issue) => `A capability name must be a bare identifier, and ${JSON.stringify(issue.input)} is not — it is written bare into pithy.config.ts as both the import binding and the registration call.` }).describe("Capability name, e.g. \"auth\"; a bare identifier."),
344
+ package: z.string().max(PACKAGE_NAME_MAX).regex(PACKAGE_NAME, { error: (issue) => `A capability's package must be an npm package name, and ${JSON.stringify(issue.input)} is not — it is written into pithy.config.ts as the import specifier.` }).describe("npm package providing this capability, e.g. \"@pithy-sh/auth\"."),
345
+ requiredBindings: z.array(BindingSpec).describe("Bindings the CLI must wire into wrangler.jsonc (normalized via BindingSpec)."),
346
+ peerCapabilities: z.array(z.string().regex(CAPABILITY_NAME, { error: (issue) => `A peer capability is named by its capability name, and ${JSON.stringify(issue.input)} is not a bare identifier.` })).default([]).describe("Capabilities that must also be present (e.g. auth ⇒ email)."),
347
+ optionalCapabilities: z.array(z.string().regex(CAPABILITY_NAME, { error: (issue) => `An optional capability is named by its capability name, and ${JSON.stringify(issue.input)} is not a bare identifier.` })).default([]).describe("Capabilities that, if present, get wired together (e.g. turnstile onto auth)."),
348
+ migrationNamespace: z.string().regex(NAMESPACE_PATTERN).optional().describe("Namespace prefix for this capability's migrations — must match the registry's format (e.g. \"auth\")."),
349
+ devSecrets: z.array(DevSecret).default([]).describe("Secrets from this capability's registry whose dev value `pithy add` mints into `.dev.vars`."),
350
+ secrets: z.array(DeclaredSecret).default([]).describe("Every secret from this capability's registry that declares how it comes to exist and how it is replaced. The route `devSecrets` proved: a client reads it without executing the package, so `pithy doctor` says *not set — run this* or *not set — get it here*, and no client keeps its own table of names. A secret declaring neither axis is absent, which reads as *nothing is known*, not as *nothing can help*."),
351
+ scaffold: z.array(z.string()).default([]).describe("Human-readable scaffold steps the CLI performs or explains."),
352
+ configOptions: z.array(ConfigOption).default([]).describe("Config options the capability exposes; `pithy add` renders each as `cap({ key: default })`."),
353
+ whenToEnable: z.string().optional().describe("Self-documenting rationale the CLI surfaces when offering this capability.")
354
+ }).describe("Declarative, CLI-facing description of a capability (pithy.manifest.json).");
355
+ //#endregion
356
+ export { CONFIG_CONSTANTS, CONFIG_LINE_WIDTH, CONFIG_OPTION_INDENT, CapabilityManifest, ConfigConstant, ConfigOption, ConfigOptionValue, renderCapabilityImport, renderCapabilityRegistration, renderConfigOptionComment, renderConfigOptionLine, renderConfigValue };
@@ -0,0 +1,121 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * How a secret's value comes to exist, and how it is replaced.
4
+ *
5
+ * A secret the kit cannot mint leaves an adopter on their own. The registry has always known which
6
+ * those are — no `devValue` — and nothing about how a human is supposed to get one, so every client
7
+ * that wanted to help had to carry its own table of names, and a capability added tomorrow silently
8
+ * fell off it.
9
+ *
10
+ * It lives in core, and in its own module, for the reason `devSecret.ts` does: both ends need it and
11
+ * neither can import the other. The owning capability declares it on its `@pithy-sh/secrets` registry
12
+ * entry; a client — `pithy doctor`, `pithy secrets ls`, a management dashboard — reads the same
13
+ * declaration off `pithy.manifest.json`, which it must, since `pithy add` wires a capability without
14
+ * ever executing it.
15
+ *
16
+ * **A function cannot cross into the manifest. A tag can.** So everything here is data, and the code
17
+ * that knows what `random` means stays in the CLI — exactly as it already does for `devValue`.
18
+ */
19
+ export declare const SecretIssuer: z.ZodEnum<{
20
+ apple: "apple";
21
+ atlassian: "atlassian";
22
+ cloudflare: "cloudflare";
23
+ facebook: "facebook";
24
+ github: "github";
25
+ gitlab: "gitlab";
26
+ google: "google";
27
+ lemonSqueezy: "lemonSqueezy";
28
+ microsoft: "microsoft";
29
+ other: "other";
30
+ paddle: "paddle";
31
+ project: "project";
32
+ stripe: "stripe";
33
+ }>;
34
+ export type SecretIssuer = z.output<typeof SecretIssuer>;
35
+ export declare const SecretRecipe: z.ZodDiscriminatedUnion<[z.ZodObject<{
36
+ kind: z.ZodLiteral<"random">;
37
+ bytes: z.ZodInt;
38
+ encoding: z.ZodEnum<{
39
+ base64: "base64";
40
+ base64url: "base64url";
41
+ hex: "hex";
42
+ }>;
43
+ }, z.core.$strip>, z.ZodObject<{
44
+ kind: z.ZodLiteral<"encryptionConfig">;
45
+ }, z.core.$strip>], "kind">;
46
+ export type SecretRecipe = z.output<typeof SecretRecipe>;
47
+ export declare const SecretOrigin: z.ZodDiscriminatedUnion<[z.ZodObject<{
48
+ kind: z.ZodLiteral<"minted">;
49
+ recipe: z.ZodDiscriminatedUnion<[z.ZodObject<{
50
+ kind: z.ZodLiteral<"random">;
51
+ bytes: z.ZodInt;
52
+ encoding: z.ZodEnum<{
53
+ base64: "base64";
54
+ base64url: "base64url";
55
+ hex: "hex";
56
+ }>;
57
+ }, z.core.$strip>, z.ZodObject<{
58
+ kind: z.ZodLiteral<"encryptionConfig">;
59
+ }, z.core.$strip>], "kind">;
60
+ }, z.core.$strip>, z.ZodObject<{
61
+ kind: z.ZodLiteral<"helped">;
62
+ issuer: z.ZodType<"apple" | "atlassian" | "cloudflare" | "facebook" | "github" | "gitlab" | "google" | "lemonSqueezy" | "microsoft" | "other" | "paddle" | "project" | "stripe", unknown, z.core.$ZodTypeInternals<"apple" | "atlassian" | "cloudflare" | "facebook" | "github" | "gitlab" | "google" | "lemonSqueezy" | "microsoft" | "other" | "paddle" | "project" | "stripe", unknown>>;
63
+ needs: z.ZodPipe<z.ZodCustom<Partial<Record<string, string[]>>, Partial<Record<string, string[]>>>, z.ZodType<Partial<Record<string, string[]>>, Partial<Record<string, string[]>>, z.core.$ZodTypeInternals<Partial<Record<string, string[]>>, Partial<Record<string, string[]>>>>>;
64
+ documentation: z.ZodOptional<z.ZodURL>;
65
+ }, z.core.$strip>, z.ZodObject<{
66
+ kind: z.ZodLiteral<"obtained">;
67
+ issuer: z.ZodType<"apple" | "atlassian" | "cloudflare" | "facebook" | "github" | "gitlab" | "google" | "lemonSqueezy" | "microsoft" | "other" | "paddle" | "project" | "stripe", unknown, z.core.$ZodTypeInternals<"apple" | "atlassian" | "cloudflare" | "facebook" | "github" | "gitlab" | "google" | "lemonSqueezy" | "microsoft" | "other" | "paddle" | "project" | "stripe", unknown>>;
68
+ documentation: z.ZodURL;
69
+ }, z.core.$strip>], "kind">;
70
+ export type SecretOrigin = z.output<typeof SecretOrigin>;
71
+ export declare const SecretRotation: z.ZodDiscriminatedUnion<[z.ZodObject<{
72
+ kind: z.ZodLiteral<"local">;
73
+ }, z.core.$strip>, z.ZodObject<{
74
+ kind: z.ZodLiteral<"provider">;
75
+ issuer: z.ZodType<"apple" | "atlassian" | "cloudflare" | "facebook" | "github" | "gitlab" | "google" | "lemonSqueezy" | "microsoft" | "other" | "paddle" | "project" | "stripe", unknown, z.core.$ZodTypeInternals<"apple" | "atlassian" | "cloudflare" | "facebook" | "github" | "gitlab" | "google" | "lemonSqueezy" | "microsoft" | "other" | "paddle" | "project" | "stripe", unknown>>;
76
+ documentation: z.ZodOptional<z.ZodURL>;
77
+ }, z.core.$strip>, z.ZodObject<{
78
+ kind: z.ZodLiteral<"manual">;
79
+ issuer: z.ZodType<"apple" | "atlassian" | "cloudflare" | "facebook" | "github" | "gitlab" | "google" | "lemonSqueezy" | "microsoft" | "other" | "paddle" | "project" | "stripe", unknown, z.core.$ZodTypeInternals<"apple" | "atlassian" | "cloudflare" | "facebook" | "github" | "gitlab" | "google" | "lemonSqueezy" | "microsoft" | "other" | "paddle" | "project" | "stripe", unknown>>;
80
+ documentation: z.ZodURL;
81
+ }, z.core.$strip>], "kind">;
82
+ export type SecretRotation = z.output<typeof SecretRotation>;
83
+ export declare const DeclaredSecret: z.ZodObject<{
84
+ name: z.ZodString;
85
+ origin: z.ZodDiscriminatedUnion<[z.ZodObject<{
86
+ kind: z.ZodLiteral<"minted">;
87
+ recipe: z.ZodDiscriminatedUnion<[z.ZodObject<{
88
+ kind: z.ZodLiteral<"random">;
89
+ bytes: z.ZodInt;
90
+ encoding: z.ZodEnum<{
91
+ base64: "base64";
92
+ base64url: "base64url";
93
+ hex: "hex";
94
+ }>;
95
+ }, z.core.$strip>, z.ZodObject<{
96
+ kind: z.ZodLiteral<"encryptionConfig">;
97
+ }, z.core.$strip>], "kind">;
98
+ }, z.core.$strip>, z.ZodObject<{
99
+ kind: z.ZodLiteral<"helped">;
100
+ issuer: z.ZodType<"apple" | "atlassian" | "cloudflare" | "facebook" | "github" | "gitlab" | "google" | "lemonSqueezy" | "microsoft" | "other" | "paddle" | "project" | "stripe", unknown, z.core.$ZodTypeInternals<"apple" | "atlassian" | "cloudflare" | "facebook" | "github" | "gitlab" | "google" | "lemonSqueezy" | "microsoft" | "other" | "paddle" | "project" | "stripe", unknown>>;
101
+ needs: z.ZodPipe<z.ZodCustom<Partial<Record<string, string[]>>, Partial<Record<string, string[]>>>, z.ZodType<Partial<Record<string, string[]>>, Partial<Record<string, string[]>>, z.core.$ZodTypeInternals<Partial<Record<string, string[]>>, Partial<Record<string, string[]>>>>>;
102
+ documentation: z.ZodOptional<z.ZodURL>;
103
+ }, z.core.$strip>, z.ZodObject<{
104
+ kind: z.ZodLiteral<"obtained">;
105
+ issuer: z.ZodType<"apple" | "atlassian" | "cloudflare" | "facebook" | "github" | "gitlab" | "google" | "lemonSqueezy" | "microsoft" | "other" | "paddle" | "project" | "stripe", unknown, z.core.$ZodTypeInternals<"apple" | "atlassian" | "cloudflare" | "facebook" | "github" | "gitlab" | "google" | "lemonSqueezy" | "microsoft" | "other" | "paddle" | "project" | "stripe", unknown>>;
106
+ documentation: z.ZodURL;
107
+ }, z.core.$strip>], "kind">;
108
+ rotation: z.ZodDiscriminatedUnion<[z.ZodObject<{
109
+ kind: z.ZodLiteral<"local">;
110
+ }, z.core.$strip>, z.ZodObject<{
111
+ kind: z.ZodLiteral<"provider">;
112
+ issuer: z.ZodType<"apple" | "atlassian" | "cloudflare" | "facebook" | "github" | "gitlab" | "google" | "lemonSqueezy" | "microsoft" | "other" | "paddle" | "project" | "stripe", unknown, z.core.$ZodTypeInternals<"apple" | "atlassian" | "cloudflare" | "facebook" | "github" | "gitlab" | "google" | "lemonSqueezy" | "microsoft" | "other" | "paddle" | "project" | "stripe", unknown>>;
113
+ documentation: z.ZodOptional<z.ZodURL>;
114
+ }, z.core.$strip>, z.ZodObject<{
115
+ kind: z.ZodLiteral<"manual">;
116
+ issuer: z.ZodType<"apple" | "atlassian" | "cloudflare" | "facebook" | "github" | "gitlab" | "google" | "lemonSqueezy" | "microsoft" | "other" | "paddle" | "project" | "stripe", unknown, z.core.$ZodTypeInternals<"apple" | "atlassian" | "cloudflare" | "facebook" | "github" | "gitlab" | "google" | "lemonSqueezy" | "microsoft" | "other" | "paddle" | "project" | "stripe", unknown>>;
117
+ documentation: z.ZodURL;
118
+ }, z.core.$strip>], "kind">;
119
+ }, z.core.$strip>;
120
+ export type DeclaredSecret = z.output<typeof DeclaredSecret>;
121
+ //# sourceMappingURL=secretOrigin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secretOrigin.d.ts","sourceRoot":"","sources":["../../src/capability/secretOrigin.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;;;;;;;;;;GAgBG;AAEH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;EAkBtB,CAAC;AACJ,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,YAAY,CAAC,CAAC;AAsEzD,eAAO,MAAM,YAAY;;;;;;;;;;2BAoCtB,CAAC;AACJ,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,YAAY,CAAC,CAAC;AAEzD,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;2BA0CtB,CAAC;AACJ,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,YAAY,CAAC,CAAC;AAEzD,eAAO,MAAM,cAAc;;;;;;;;;;2BAsCxB,CAAC;AACJ,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,cAAc,CAAC,CAAC;AAE7D,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAaxB,CAAC;AACJ,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,cAAc,CAAC,CAAC"}
@@ -0,0 +1,134 @@
1
+ import { manifestRecord } from "./vanishingKey.js";
2
+ import { z } from "zod";
3
+ //#region src/capability/secretOrigin.ts
4
+ /**
5
+ * How a secret's value comes to exist, and how it is replaced.
6
+ *
7
+ * A secret the kit cannot mint leaves an adopter on their own. The registry has always known which
8
+ * those are — no `devValue` — and nothing about how a human is supposed to get one, so every client
9
+ * that wanted to help had to carry its own table of names, and a capability added tomorrow silently
10
+ * fell off it.
11
+ *
12
+ * It lives in core, and in its own module, for the reason `devSecret.ts` does: both ends need it and
13
+ * neither can import the other. The owning capability declares it on its `@pithy-sh/secrets` registry
14
+ * entry; a client — `pithy doctor`, `pithy secrets ls`, a management dashboard — reads the same
15
+ * declaration off `pithy.manifest.json`, which it must, since `pithy add` wires a capability without
16
+ * ever executing it.
17
+ *
18
+ * **A function cannot cross into the manifest. A tag can.** So everything here is data, and the code
19
+ * that knows what `random` means stays in the CLI — exactly as it already does for `devValue`.
20
+ */
21
+ const SecretIssuer = z.enum([
22
+ "project",
23
+ "cloudflare",
24
+ "apple",
25
+ "google",
26
+ "github",
27
+ "facebook",
28
+ "microsoft",
29
+ "stripe",
30
+ "lemonSqueezy",
31
+ "paddle",
32
+ "gitlab",
33
+ "atlassian",
34
+ "other"
35
+ ]).describe("Who issues this value. `project` means it is arbitrary and ours to make. Every other member is a third party whose console or API is the only source: payments rails, auth's social providers, `cloudflare` for R2, Turnstile and API tokens, and the repository hosts an adopter may rotate CI credentials against. `other` is the floor an unrecognized issuer lands on, so a capability added tomorrow is renderable by a client built today.");
36
+ /**
37
+ * The issuer **as a field**, with its one degradation rule attached.
38
+ *
39
+ * **An unrecognized issuer degrades to `other` rather than failing.** A manifest is read from
40
+ * `node_modules`, so it can be newer than the client reading it — and a client that threw on an issuer
41
+ * it had not been built against would blank a pane over a name it did not need to understand. `other`
42
+ * is the honest answer: *somebody issues this, and I cannot help you with them.*
43
+ *
44
+ * A field holds one value, so rewriting it destroys nothing. {@link IssuerKey} is the same name in the
45
+ * one position where that is false.
46
+ */
47
+ const IssuerName = SecretIssuer.catch("other");
48
+ /** The issuer as a field, with the sentence that site needs. The rule comes from {@link IssuerName}. */
49
+ function issuedBy(description) {
50
+ return IssuerName.describe(description);
51
+ }
52
+ /**
53
+ * The issuer **as a record key**, where the field's degradation rule would destroy data.
54
+ *
55
+ * **A key cannot degrade the way a field does, and the attempt to share one rule between them was the
56
+ * defect.** `SecretIssuer.catch("other")` rewrites what it parses. On a field that is a rename; on a key
57
+ * it is a *merge*, and the merge is silent. Keyed by the field's rule, `{ vercel: ["a"], netlify: ["b"] }`
58
+ * parsed to `{ other: ["b"] }` — vercel's requirement gone, the parse successful, nothing anywhere
59
+ * reporting it. Requirements lost without a word are worse than a manifest that refuses to parse, because
60
+ * a refusal is at least visible.
61
+ *
62
+ * So a key keeps the name it was written with. What a client loses by that is nothing it had: a reader
63
+ * that branches on the closed set runs `SecretIssuer.safeParse(key)` at the point of use and falls back to
64
+ * `other` for rendering, which is the same answer the schema used to force on it — except that the scopes
65
+ * are still there beside it, and a second unknown issuer is still a second entry.
66
+ *
67
+ * A preserved key reaches a rendered command and a rendered heading unchanged, so it is held to the shape
68
+ * of a name. That constraint is not a degradation path: an issuer a client has never heard of is still
69
+ * spelled like an issuer, and anything that is not is hostile data rather than a newer manifest.
70
+ */
71
+ const IssuerKey = z.string().regex(/^[A-Za-z][A-Za-z0-9_.-]*$/).describe("An issuer, as the key of a record. Preserved exactly as written — unlike the field, which degrades to `other` — because two unrecognized issuers rewritten to one key overwrite each other's requirements with no error. A client that only knows the closed set narrows this with `SecretIssuer.safeParse` where it renders.");
72
+ /** The characters a secret name may hold. Excludes `/`, which separates a keyspace from a member key. */
73
+ const SECRET_NAME = /^[A-Za-z0-9][A-Za-z0-9_.-]*$/;
74
+ /** What a helper must supply, per item. Permission groups and scope names, and nothing that reads as code. */
75
+ const HELPER_NEED = /^[A-Za-z0-9][A-Za-z0-9_.:@/-]*$/;
76
+ /**
77
+ * A documentation link, held to `https:` and nothing else.
78
+ *
79
+ * `z.url()` on its own is not enough here: it accepts `javascript:alert(1)`, and it accepts `http:`. A
80
+ * manifest is third-party data read out of `node_modules`, and this is the one field in it whose whole
81
+ * purpose is to be turned into an anchor an operator clicks. So the scheme is constrained at the schema,
82
+ * where a client cannot forget to.
83
+ */
84
+ const DOCUMENTATION_URL = /^https:\/\//;
85
+ /** The documentation field, wherever it appears: a real URL, and an `https:` one. */
86
+ function documentedAt(description) {
87
+ return z.url().regex(DOCUMENTATION_URL).describe(description);
88
+ }
89
+ const SecretRecipe = z.discriminatedUnion("kind", [z.object({
90
+ kind: z.literal("random").describe("Random bytes rendered as a string. Any value works, because nothing outside the project reads it."),
91
+ bytes: z.int().positive().describe("Entropy before encoding. Stated because 32 and 16 are not interchangeable — a value sized for AES-256 that arrives half that long fails at decrypt, not at write, and by then it is stored."),
92
+ encoding: z.enum([
93
+ "base64url",
94
+ "base64",
95
+ "hex"
96
+ ]).describe("How the bytes are rendered. The kit mints `base64url`, unpadded, so a value survives a `.dev.vars` line, a shell export and a URL without quoting.")
97
+ }).describe("A minted value the kit makes from entropy: how many bytes, and how they are rendered."), z.object({ kind: z.literal("encryptionConfig").describe("An `EncryptionConfig` — `currentVersion`, a `versions` map, `lastRotatedAt` — minted by `initialMasterKeyConfig`. `SECRETS_ENCRYPTION_KEYS` is the one, and the reason this is a union rather than an enum: it is minted, and a random string is not one of these. It states no `bytes` or `encoding` because the structure states its own.") }).describe("A minted structure rather than a string. The tag is the whole recipe; nothing about it is configurable.")]).describe("What produces a minted value. A tag the CLI resolves to code, never the code itself, because this crosses into the manifest.");
98
+ const SecretOrigin = z.discriminatedUnion("kind", [
99
+ z.object({
100
+ kind: z.literal("minted").describe("The kit produces this value. Nothing outside the project has to agree with it."),
101
+ recipe: SecretRecipe.describe("What produces it.")
102
+ }).describe("Minted by the kit. The recipe says how, and no third party has to agree with the result."),
103
+ z.object({
104
+ kind: z.literal("helped").describe("The kit cannot produce it, but knows enough to compose the command that does — a Cloudflare API token, whose permission groups an operator should not have to look up."),
105
+ issuer: issuedBy("Whose console or API issues it."),
106
+ needs: manifestRecord(z.partialRecord(IssuerKey, z.array(z.string().regex(HELPER_NEED)))).describe("What a helper must supply, keyed by issuer. Cloudflare's key is its permission groups. Keyed rather than flat so a second issuer does not widen a shape every consumer must handle. An unrecognized key is kept verbatim rather than degraded — see `IssuerKey`: rewriting two of them onto `other` loses one issuer's requirements silently. Wrapped in `manifestRecord` so the key rule is given every key the manifest wrote, including the one a parse would otherwise drop before the rule ran."),
107
+ documentation: documentedAt("Where the command's arguments come from, for an operator who would rather check them by hand.").optional()
108
+ }).describe("Issued elsewhere, but the kit knows enough to compose the command that asks for it."),
109
+ z.object({
110
+ kind: z.literal("obtained").describe("A human gets it from a third party and there is no command. An OAuth client secret is the case, and always will be."),
111
+ issuer: issuedBy("Whose console issues it."),
112
+ documentation: documentedAt("Where a human goes. The specific settings page, not a product homepage — the point is to end a search, not to start one.")
113
+ }).describe("Fetched by a human from a third party. There is no command, and the link is the whole help.")
114
+ ]).describe("How this secret's value first comes to exist. Closed, so a consumer branches rather than parses, and so an unhandled member is a type error rather than a blank screen.");
115
+ const SecretRotation = z.discriminatedUnion("kind", [
116
+ z.object({ kind: z.literal("local").describe("Replaced by producing a new value the same way it was minted. Every `origin.kind: \"minted\"` secret, and nothing else — if the kit can make one it can make another.") }).describe("Rotated by the kit, on its own. The tag carries everything; there is nobody to call."),
117
+ z.object({
118
+ kind: z.literal("provider").describe("Replaced by calling the issuer, which returns the new value. The kit cannot invent this call; the capability or the adopter supplies it, and it changes state in somebody else's system."),
119
+ issuer: issuedBy("Who is called."),
120
+ documentation: documentedAt("What the call does, for an operator deciding whether to trust it with a live credential.").optional()
121
+ }).describe("Rotated by calling the issuer. The new value comes back from somebody else's system."),
122
+ z.object({
123
+ kind: z.literal("manual").describe("A human, in a console. No API returns the new value — a GitHub or Google OAuth client secret. This is a real answer, not a missing one."),
124
+ issuer: issuedBy("Whose console."),
125
+ documentation: documentedAt("The page where it is done.")
126
+ }).describe("Rotated by a human, in a console. A real answer, and the page is the whole help.")
127
+ ]).describe("How this secret is replaced, declared separately from `origin` because neither follows from the other: a GitLab token is `obtained` and rotates by `provider`, while an OAuth client secret is `obtained` and rotates only by `manual`. Declared per secret, never per issuer — some Cloudflare secrets roll and some do not.");
128
+ const DeclaredSecret = z.object({
129
+ name: z.string().regex(SECRET_NAME).describe("The registry name the secret is declared under. Constrained, because a manifest is third-party data read from `node_modules` and this name reaches a rendered command."),
130
+ origin: SecretOrigin.describe("How its value first comes to exist. Must match the registry entry's `origin`."),
131
+ rotation: SecretRotation.describe("How it is replaced. Must match the registry entry's `rotation`.")
132
+ }).describe("One declared secret, as a manifest carries it: its registry name and both axes. Both, always — an origin without a rotation answers half of what an operator holding an overdue secret needs to know. The manifest's projection of the registry entry, and each capability's own tests assert the two agree.");
133
+ //#endregion
134
+ export { DeclaredSecret, SecretIssuer, SecretOrigin, SecretRecipe, SecretRotation };