@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,59 @@
1
+ import { z } from "zod";
2
+ const EntitlementKey = z.string().min(1).max(64).regex(/^[a-z][a-z0-9_]*$/, "An entitlement key is lowercase letters, digits, and underscores, starting with a letter.").describe("A logical entitlement key (`pro`, `ads_removed`) — what gating code names. Never a store SKU: many products across many rails grant one key.");
3
+ /**
4
+ * One entitlement the caller holds, as the seam exposes it. This is the resolved read model, not a table:
5
+ * `@pithy-sh/payments` materializes a row per `(holder, entitlement)` and decodes it into this shape.
6
+ *
7
+ * **Who the holder is stays out of this shape, deliberately.** A payments project bills either a person or
8
+ * an organization, and its rows are keyed on that pair — but the seam resolves for *one* caller acting for
9
+ * *one* holder, so the pair is constant across a single `list()` and every element of the result would
10
+ * repeat it. Carrying it here would put a comparison within reach of {@link requireEntitlement} that the
11
+ * gate has no correct way to make (see {@link EntitlementResolver}), and would hand every non-payments
12
+ * provider two fields it cannot fill.
13
+ *
14
+ * `active` and `expiresAt` are both present, and both matter. The flag is an optimization written by
15
+ * the projection; the timestamp is the truth. {@link entitlementGrantsAccess} applies both, so a
16
+ * subscription that lapsed with no notification arriving stops granting access on the read path
17
+ * without waiting for a write.
18
+ */
19
+ const Entitlement = z.object({
20
+ key: EntitlementKey.describe("The entitlement key this grant is for."),
21
+ active: z.boolean().describe("Whether the granting purchase was in an access-granting state when the projection last wrote this row. An optimization, not the truth — `expiresAt` is rechecked on every read."),
22
+ expiresAt: z.date().nullable().describe("When the grant lapses, or null for a grant that never does (a non-consumable purchase). Evaluated at read time, so a silent lapse revokes access without a write."),
23
+ source: z.string().nullable().describe("Provenance — an opaque reference to whatever currently grants this entitlement (a purchase id, or a support grant). Opaque to core; the provider decides what it means.")
24
+ }).describe("One entitlement the current caller holds, as the core seam exposes it — the resolved read model.");
25
+ /**
26
+ * The read-time rule, in one place: an entitlement grants access when the projection marked it active
27
+ * **and** it has not expired. Applied by {@link requireEntitlement} rather than left to each provider,
28
+ * so the recheck is a property of the seam instead of something a provider must remember.
29
+ *
30
+ * A read never writes. Repairing a stale row is the reconciliation Workflow's job, which keeps the hot
31
+ * path a pure read.
32
+ */
33
+ function entitlementGrantsAccess(entitlement, now) {
34
+ if (!entitlement.active) return false;
35
+ return entitlement.expiresAt === null || now.getTime() < entitlement.expiresAt.getTime();
36
+ }
37
+ /** Every key in `entitlements` that grants access at `now`, deduped. The set a gate is checked against. */
38
+ function grantedEntitlementKeys(entitlements, now) {
39
+ const granted = /* @__PURE__ */ new Set();
40
+ for (const entitlement of entitlements) if (entitlementGrantsAccess(entitlement, now)) granted.add(entitlement.key);
41
+ return granted;
42
+ }
43
+ /**
44
+ * The default resolver when no provider is composed: hold nothing. Every gate denies, which is the
45
+ * fail-closed default the seam exists to guarantee. `createBackend` seeds it on every request, and a
46
+ * provider's middleware replaces it.
47
+ */
48
+ const noEntitlementProvider = {
49
+ provider: null,
50
+ list: async () => []
51
+ };
52
+ /**
53
+ * The audit action a denied entitlement gate records, through the core `emit()` seam. Part of the
54
+ * federated `domain/reason` taxonomy — core owns this one because core owns the gate. A blocked
55
+ * attempt is a first-class audit record (`outcome: "denied"`), not only a 403.
56
+ */
57
+ const ENTITLEMENT_DENIED_ACTION = "entitlement/denied";
58
+ //#endregion
59
+ export { ENTITLEMENT_DENIED_ACTION, Entitlement, EntitlementKey, entitlementGrantsAccess, grantedEntitlementKeys, noEntitlementProvider };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The source with its comments blanked, so a `// TODO: requireEntitlement("pro")` is not read as a gate.
3
+ *
4
+ * **String-aware, and it has to be.** Doing this with two regexes over raw source looks equivalent and is
5
+ * not: a `/*` inside a string literal opens a comment the scanner never sees closed, and everything up to
6
+ * the next `*&#47;` in the file vanishes. A Worker with `app.get("/assets/*", …)` followed by any doc comment
7
+ * loses whatever sat between them — so a real gate goes unseen and the check reports clean. The same trap
8
+ * in reverse is `https://`, whose `//` is not a comment.
9
+ *
10
+ * So this walks the source once, tracking which of code / string / template / line comment / block comment
11
+ * it is in, and replaces comment characters with spaces rather than removing them — offsets stay put, which
12
+ * keeps anything reported against this text meaningful. A real parser would be the third option and is far
13
+ * more than a two-identifier search needs.
14
+ */
15
+ export declare function withoutComments(source: string): string;
16
+ /** Whether this source applies an entitlement gate, comments and string literals discounted. */
17
+ export declare function callsGate(source: string): boolean;
18
+ /**
19
+ * The files, of those given, that apply an entitlement gate — sorted, so a finding reads as a set.
20
+ *
21
+ * Takes the sources rather than a directory, which is what keeps this half free of `node:fs`. The CLI
22
+ * hands it a walked `src/`; a test in a Workers-typed program hands it `import.meta.glob(…, "?raw")`,
23
+ * which is the only way that program can read files at all.
24
+ */
25
+ export declare function gateCallSites(sources: Readonly<Record<string, string>>): string[];
26
+ //# sourceMappingURL=gateScan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateScan.d.ts","sourceRoot":"","sources":["../../src/entitlement/gateScan.ts"],"names":[],"mappings":"AAkCA;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAuCtD;AAED,gGAAgG;AAChG,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,MAAM,EAAE,CAKjF"}
@@ -0,0 +1,96 @@
1
+ //#region src/entitlement/gateScan.ts
2
+ /**
3
+ * Finding an entitlement gate in source text — the pure half of the composition check.
4
+ *
5
+ * `pithy doctor` scans a Worker's own source for calls to the gates in `./require`, because the runtime
6
+ * cannot tell the two failing cases apart: a legitimately unentitled user and a Worker that forgot to
7
+ * compose a provider both produce a 403. Walking a directory needs the filesystem. Deciding whether one
8
+ * string of source gates does not, and the halves are split here so the second can be used on its own.
9
+ *
10
+ * On its own, because an adopter has the same question about their own source with the answer inverted:
11
+ * not *where a gate must exist* but *where one may not*. No control-plane route in the Pithy dashboard may
12
+ * gate on a subscription — a customer's data must not become unreachable over a billing problem of ours —
13
+ * and a gate that is never reached at runtime is precisely the one a runtime test misses. That assertion
14
+ * is a few lines over `import.meta.glob(…, "?raw")`, provided it can import these rules rather than restate
15
+ * them. A restated copy drifts, and drifts quietly: both copies keep passing while they disagree.
16
+ *
17
+ * It lives beside the gates it names rather than in the CLI. A rename in `./require` is then one file's
18
+ * work, core is the package every adopter already depends on, and core's no-`node:` rule is asserted over
19
+ * every module here (`../worker-safety.test.ts`) — which is what makes this importable from a program
20
+ * typed for the Workers runtime at all. The CLI composes it; see `entitlementGap.ts` there.
21
+ *
22
+ * The scan is textual, deliberately. Loading a Worker's routes to inspect them would execute the code
23
+ * under audit, and the question is not subtle enough to need a parser: a call to one of two named helpers.
24
+ */
25
+ /**
26
+ * A call to either gate. `\b` before the name keeps `myRequireEntitlement(` out, and requiring the open
27
+ * paren keeps a bare import or a re-export out — importing a gate is not applying one, and reporting an
28
+ * unused import would make the check noise rather than signal.
29
+ */
30
+ const GATE_CALL = /\brequire(?:Any)?Entitlement\s*\(/;
31
+ /**
32
+ * The source with its comments blanked, so a `// TODO: requireEntitlement("pro")` is not read as a gate.
33
+ *
34
+ * **String-aware, and it has to be.** Doing this with two regexes over raw source looks equivalent and is
35
+ * not: a `/*` inside a string literal opens a comment the scanner never sees closed, and everything up to
36
+ * the next `*&#47;` in the file vanishes. A Worker with `app.get("/assets/*", …)` followed by any doc comment
37
+ * loses whatever sat between them — so a real gate goes unseen and the check reports clean. The same trap
38
+ * in reverse is `https://`, whose `//` is not a comment.
39
+ *
40
+ * So this walks the source once, tracking which of code / string / template / line comment / block comment
41
+ * it is in, and replaces comment characters with spaces rather than removing them — offsets stay put, which
42
+ * keeps anything reported against this text meaningful. A real parser would be the third option and is far
43
+ * more than a two-identifier search needs.
44
+ */
45
+ function withoutComments(source) {
46
+ const out = [];
47
+ let index = 0;
48
+ while (index < source.length) {
49
+ const char = source[index];
50
+ const next = source[index + 1];
51
+ if (char === "/" && next === "*") {
52
+ const close = source.indexOf("*/", index + 2);
53
+ const end = close === -1 ? source.length : close + 2;
54
+ out.push(source.slice(index, end).replace(/[^\n]/g, " "));
55
+ index = end;
56
+ continue;
57
+ }
58
+ if (char === "/" && next === "/") {
59
+ const newline = source.indexOf("\n", index);
60
+ const end = newline === -1 ? source.length : newline;
61
+ out.push(" ".repeat(end - index));
62
+ index = end;
63
+ continue;
64
+ }
65
+ if (char === "\"" || char === "'" || char === "`") {
66
+ let scan = index + 1;
67
+ while (scan < source.length && source[scan] !== char) {
68
+ if (source[scan] === "\n" && char !== "`") break;
69
+ scan += source[scan] === "\\" ? 2 : 1;
70
+ }
71
+ const end = Math.min(scan + 1, source.length);
72
+ out.push(source.slice(index, end));
73
+ index = end;
74
+ continue;
75
+ }
76
+ out.push(char);
77
+ index += 1;
78
+ }
79
+ return out.join("");
80
+ }
81
+ /** Whether this source applies an entitlement gate, comments and string literals discounted. */
82
+ function callsGate(source) {
83
+ return GATE_CALL.test(withoutComments(source));
84
+ }
85
+ /**
86
+ * The files, of those given, that apply an entitlement gate — sorted, so a finding reads as a set.
87
+ *
88
+ * Takes the sources rather than a directory, which is what keeps this half free of `node:fs`. The CLI
89
+ * hands it a walked `src/`; a test in a Workers-typed program hands it `import.meta.glob(…, "?raw")`,
90
+ * which is the only way that program can read files at all.
91
+ */
92
+ function gateCallSites(sources) {
93
+ return Object.entries(sources).filter(([, source]) => callsGate(source)).map(([file]) => file).sort((a, b) => a.localeCompare(b));
94
+ }
95
+ //#endregion
96
+ export { callsGate, gateCallSites, withoutComments };
@@ -0,0 +1,12 @@
1
+ import type { MiddlewareHandler } from "hono";
2
+ import type { PithyHonoEnv } from "../capability/capability";
3
+ /**
4
+ * Require any one of `keys`. Every key is parsed at construction, so a store SKU where an entitlement
5
+ * key belongs (`com.acme.pro.monthly`) fails on deploy rather than gating on something no projection
6
+ * ever writes. An empty list is refused for the same reason: it reads as "nothing required" but would
7
+ * deny every caller forever.
8
+ */
9
+ export declare function requireAnyEntitlement(keys: readonly string[]): MiddlewareHandler<PithyHonoEnv>;
10
+ /** Require one entitlement. The common case: one key gates one feature. */
11
+ export declare function requireEntitlement(key: string): MiddlewareHandler<PithyHonoEnv>;
12
+ //# sourceMappingURL=require.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"require.d.ts","sourceRoot":"","sources":["../../src/entitlement/require.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAoG7D;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,iBAAiB,CAAC,YAAY,CAAC,CAmF9F;AAED,2EAA2E;AAC3E,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB,CAAC,YAAY,CAAC,CAE/E"}
@@ -0,0 +1,123 @@
1
+ import { InternalError, PithyError, UnauthorizedError, messageOf } from "../error/pithyError.js";
2
+ import { ENTITLEMENT_DENIED_ACTION, EntitlementKey, grantedEntitlementKeys } from "./entitlement.js";
3
+ //#region src/entitlement/require.ts
4
+ /**
5
+ * The entitlement gate — the middleware half of the seam. `requireEntitlement("pro")` is the
6
+ * `verification`-strategy sibling every paid route carries: it says what the caller must *hold*, the
7
+ * way `requireAuth()` says who the caller must *be*, and it belongs on the route line beside it.
8
+ *
9
+ * app.get("/reports", requireAuth(), requireEntitlement("pro"), zValidator(…), handler)
10
+ *
11
+ * It lives in `@pithy-sh/core` rather than in `@pithy-sh/payments` for the same reason `requireAuth()`
12
+ * is copied into each capability instead of imported from `@pithy-sh/auth`: a gate that arrives with a
13
+ * package fails **open** when that package is absent. Depending on the core seam means an uncomposed
14
+ * provider leaves `noEntitlementProvider` on the request, and every gate denies.
15
+ *
16
+ * Two names, not one variadic, because "any of these" and "all of these" are both plausible readings
17
+ * of `requireEntitlement("pro", "team")` and the wrong reading is a security bug rather than a typo.
18
+ */
19
+ /**
20
+ * Why a gate denied — recorded in `detail` and in the audit event, never sent to the client.
21
+ *
22
+ * **Three readings, and keeping them apart is the whole job.** Nothing composed is one. A holder that has
23
+ * bought nothing is the second. A caller acting for **no** holder at all is the third — the ordinary state
24
+ * of somebody signed in with no organization selected, and before #412 it was spelled `payments resolved
25
+ * []`, identically to the second. They want opposite fixes: one is a sale, the other is a subject resolver
26
+ * returning nothing, and an operator reading one sentence had no way to tell which they were looking at.
27
+ *
28
+ * The holder is named only when the resolver reports one, and it is named as {@link EntitlementHolder}'s
29
+ * `label` — display text, never a value this gate compares. A resolver that cannot say omits it, and the
30
+ * sentence is exactly what it was.
31
+ */
32
+ function denialDetail(keys, provider, held, reading) {
33
+ const required = keys.join(" | ");
34
+ if (provider === null) return `no entitlement provider is composed, so \`${required}\` cannot be held. Add a payments capability to this Worker's pithy.config.ts.`;
35
+ switch (reading.kind) {
36
+ case "nobody": return `${provider} resolved no holder for this caller, so nothing can be held; the route requires \`${required}\`. Under organization billing this is a caller acting for no organization, not an unentitled one.`;
37
+ case "failed": return `${provider} could not resolve a holder for this caller — the read raised, see the preceding log line; the route requires \`${required}\`.`;
38
+ case "known": return `${provider} resolved [${[...held].sort().join(", ")}] for ${reading.holder.label}; the route requires \`${required}\`.`;
39
+ default: return `${provider} resolved [${[...held].sort().join(", ")}]; the route requires \`${required}\`.`;
40
+ }
41
+ }
42
+ /** The gate's denial: one code, one status, whatever the reason. The reason rides in `detail`. */
43
+ function denied(keys, provider, held, reading) {
44
+ return new PithyError({
45
+ code: "payments/entitlement_required",
46
+ status: 403,
47
+ message: "This feature requires an active subscription or purchase.",
48
+ action: "Purchase or restore the product that grants access, then retry.",
49
+ detail: denialDetail(keys, provider, held, reading)
50
+ });
51
+ }
52
+ /**
53
+ * Require any one of `keys`. Every key is parsed at construction, so a store SKU where an entitlement
54
+ * key belongs (`com.acme.pro.monthly`) fails on deploy rather than gating on something no projection
55
+ * ever writes. An empty list is refused for the same reason: it reads as "nothing required" but would
56
+ * deny every caller forever.
57
+ */
58
+ function requireAnyEntitlement(keys) {
59
+ if (keys.length === 0) throw new InternalError({
60
+ message: "requireAnyEntitlement() needs at least one entitlement key.",
61
+ action: "Name the entitlements that grant access, or drop the gate if the route is not paid."
62
+ });
63
+ const required = keys.map((key) => EntitlementKey.parse(key));
64
+ return async (c, next) => {
65
+ const auth = c.var.auth;
66
+ if (!auth) throw new UnauthorizedError({
67
+ message: "Authentication required.",
68
+ action: "Sign in and retry with a valid session or bearer token."
69
+ });
70
+ const resolver = c.var.entitlements;
71
+ let held;
72
+ try {
73
+ held = grantedEntitlementKeys(await resolver.list(), /* @__PURE__ */ new Date());
74
+ } catch (error) {
75
+ c.var.log.error("entitlement resolution failed", {
76
+ provider: resolver.provider,
77
+ cause: messageOf(error)
78
+ });
79
+ held = /* @__PURE__ */ new Set();
80
+ }
81
+ if (required.some((key) => held.has(key))) {
82
+ await next();
83
+ return;
84
+ }
85
+ let reading = { kind: "unavailable" };
86
+ if (resolver.holder) try {
87
+ const answered = await resolver.holder();
88
+ reading = answered === void 0 ? { kind: "nobody" } : {
89
+ kind: "known",
90
+ holder: answered
91
+ };
92
+ } catch (error) {
93
+ reading = { kind: "failed" };
94
+ c.var.log.error("entitlement holder resolution failed", {
95
+ provider: resolver.provider,
96
+ cause: messageOf(error)
97
+ });
98
+ }
99
+ await c.var.emit({
100
+ action: ENTITLEMENT_DENIED_ACTION,
101
+ outcome: "denied",
102
+ severity: "info",
103
+ actorType: "user",
104
+ actorId: auth.userId,
105
+ sessionId: auth.sessionId,
106
+ resourceType: "entitlement",
107
+ resourceId: required.join("|"),
108
+ tenant: reading.kind === "known" ? reading.holder.tenant : null,
109
+ metadata: {
110
+ provider: resolver.provider,
111
+ required,
112
+ held: [...held].sort()
113
+ }
114
+ });
115
+ throw denied(required, resolver.provider, held, reading);
116
+ };
117
+ }
118
+ /** Require one entitlement. The common case: one key gates one feature. */
119
+ function requireEntitlement(key) {
120
+ return requireAnyEntitlement([key]);
121
+ }
122
+ //#endregion
123
+ export { requireAnyEntitlement, requireEntitlement };
@@ -0,0 +1,52 @@
1
+ /**
2
+ * What the *ambient* environment says about the composition being assembled — the two questions a
3
+ * capability may ask before it registers anything, and the one reader for both.
4
+ *
5
+ * ## Why a capability can ask this at all
6
+ *
7
+ * A Worker is handed its bindings per request, so `c.env` does not exist when `createBackend` calls a
8
+ * capability's `routes()`. `process.env` does: with `nodejs_compat` and a compatibility date past
9
+ * 2025-04-01 — every Worker `pithy init` scaffolds — workerd populates `process.env` from the script's
10
+ * own `vars` and secrets, and it is populated at module scope, before the first request. Verified
11
+ * against a real `wrangler dev`: at module scope `Object.keys(process.env)` is exactly the declared
12
+ * vars.
13
+ *
14
+ * That is the whole reach of this module, and the limit is the interesting half:
15
+ *
16
+ * - **`ENVIRONMENT` is here**, because `pithy init` stamps it into every environment stanza of every
17
+ * Worker's `wrangler.jsonc` ({@link ENVIRONMENT_VAR}). A composition can therefore know it is `dev`
18
+ * without being told.
19
+ * - **The host's environment is not here.** `CI=true` in the shell that ran `wrangler dev` does *not*
20
+ * appear inside the Worker — workerd replaces `process.env` with the bindings rather than merging.
21
+ * {@link ./ci.ts} carries that consequence and what is done about it.
22
+ *
23
+ * Read at call time, never captured at import: a module-scope snapshot is unstubbable in a test and
24
+ * would freeze the answer for the life of an isolate that outlives a `.dev.vars` edit.
25
+ */
26
+ /** A process environment as this module reads one — the shape of `process.env`, and nothing assumed of it. */
27
+ export type AmbientEnv = Readonly<Record<string, string | undefined>>;
28
+ /**
29
+ * The ambient environment, or an empty one where there is no `process` at all.
30
+ *
31
+ * Guarded rather than assumed: this module is bundled into Workers, and a runtime without
32
+ * `nodejs_compat` has no `process`. An empty environment answers "nothing was stamped", which is the
33
+ * refusing answer everywhere it is used — a missing `process` must never read as `dev`.
34
+ */
35
+ export declare function ambientEnv(): AmbientEnv;
36
+ /**
37
+ * Whether an environment variable counts as **set**: any non-blank value, blank is no override.
38
+ *
39
+ * One rule, stated once. It is `PITHY_OFFLINE`'s (#218), and the reason it is not "`=== "true"`" is
40
+ * that runners disagree — `true`, `1`, and an operator's own word all mean the same thing, and a
41
+ * variable someone deliberately blanked (`CI=`) is the one case that means "no".
42
+ */
43
+ export declare function ambientFlag(env: AmbientEnv, name: string): boolean;
44
+ /**
45
+ * The environment this composition was stamped for — `dev`, `staging`, `prod`, or an adopter's own —
46
+ * and `undefined` where nothing stamped one.
47
+ *
48
+ * **`undefined`, never a defaulted `dev`.** The callers are gates, and a gate that assumes `dev` when
49
+ * it was told nothing opens itself in exactly the deployment whose `wrangler.jsonc` lost the var.
50
+ */
51
+ export declare function compositionEnvironment(env?: AmbientEnv): string | undefined;
52
+ //# sourceMappingURL=ambient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ambient.d.ts","sourceRoot":"","sources":["../../src/env/ambient.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,8GAA8G;AAC9G,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;AAEtE;;;;;;GAMG;AACH,wBAAgB,UAAU,IAAI,UAAU,CAEvC;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAElE;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,GAAE,UAAyB,GAAG,MAAM,GAAG,SAAS,CAGzF"}
@@ -0,0 +1,35 @@
1
+ import "../worker/identity.js";
2
+ //#region src/env/ambient.ts
3
+ /**
4
+ * The ambient environment, or an empty one where there is no `process` at all.
5
+ *
6
+ * Guarded rather than assumed: this module is bundled into Workers, and a runtime without
7
+ * `nodejs_compat` has no `process`. An empty environment answers "nothing was stamped", which is the
8
+ * refusing answer everywhere it is used — a missing `process` must never read as `dev`.
9
+ */
10
+ function ambientEnv() {
11
+ return globalThis.process?.env ?? {};
12
+ }
13
+ /**
14
+ * Whether an environment variable counts as **set**: any non-blank value, blank is no override.
15
+ *
16
+ * One rule, stated once. It is `PITHY_OFFLINE`'s (#218), and the reason it is not "`=== "true"`" is
17
+ * that runners disagree — `true`, `1`, and an operator's own word all mean the same thing, and a
18
+ * variable someone deliberately blanked (`CI=`) is the one case that means "no".
19
+ */
20
+ function ambientFlag(env, name) {
21
+ return (env[name] ?? "").trim().length > 0;
22
+ }
23
+ /**
24
+ * The environment this composition was stamped for — `dev`, `staging`, `prod`, or an adopter's own —
25
+ * and `undefined` where nothing stamped one.
26
+ *
27
+ * **`undefined`, never a defaulted `dev`.** The callers are gates, and a gate that assumes `dev` when
28
+ * it was told nothing opens itself in exactly the deployment whose `wrangler.jsonc` lost the var.
29
+ */
30
+ function compositionEnvironment(env = ambientEnv()) {
31
+ const value = (env["ENVIRONMENT"] ?? "").trim();
32
+ return value.length > 0 ? value : void 0;
33
+ }
34
+ //#endregion
35
+ export { ambientEnv, ambientFlag, compositionEnvironment };
@@ -0,0 +1,36 @@
1
+ import { type AmbientEnv } from "./ambient";
2
+ /**
3
+ * Is this continuous integration? One read, one rule, one place — the convention starts here.
4
+ *
5
+ * Nothing in this repository read `CI` before #243, so this is the declaration rather than a fourth
6
+ * copy of one. It exists because a gate that inlines `!!process.env.CI` at the point it guards is a
7
+ * gate whose meaning is re-decided by whoever writes the next one.
8
+ *
9
+ * ## What counts as set
10
+ *
11
+ * **Any non-blank value.** `CI=true`, `CI=1`, and `CI=woodpecker` are all CI; `CI=` (set but empty,
12
+ * which some runners do) and an absent `CI` are not. That is `PITHY_OFFLINE`'s rule (#218) applied
13
+ * unchanged, rather than a second rule for a second variable — see {@link ambientFlag}.
14
+ *
15
+ * ## Why anything reads it
16
+ *
17
+ * CI is where an unsupervised capability is most reachable and least watched. A `dev` composition is
18
+ * exactly what CI boots — integration suites, packaging checks, `pithy dev` itself — so "not `dev`"
19
+ * does not cover CI, and CI is not production, so neither signal implies the other. Anything gated on
20
+ * both asks twice, in two statements: an `||` folded into one expression is one edit from an `&&`, and
21
+ * the failure mode is a session-minting endpoint that answers.
22
+ *
23
+ * ## Where it can be read, and where it cannot
24
+ *
25
+ * In the CLI, and in anything else running under Node, this is the host's own `CI`. **Inside a Worker
26
+ * it is not**: workerd populates `process.env` from the script's bindings and nothing else, so the
27
+ * shell variable does not cross that boundary (see {@link ./ambient.ts}). A Worker-side gate is
28
+ * therefore only as truthful as what started the Worker — which is why `pithy dev` forwards `CI` into
29
+ * every Worker it launches as a var, and why a gate that depends on this must also carry a second,
30
+ * independent refusal that needs no forwarding.
31
+ */
32
+ /** The variable every CI runner sets without configuration. The name is not ours to choose. */
33
+ export declare const CI_ENV = "CI";
34
+ /** Whether this process is running under continuous integration. Any non-blank `CI`; blank is not CI. */
35
+ export declare function isContinuousIntegration(env?: AmbientEnv): boolean;
36
+ //# sourceMappingURL=ci.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ci.d.ts","sourceRoot":"","sources":["../../src/env/ci.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,UAAU,EAA2B,MAAM,WAAW,CAAC;AAErE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,+FAA+F;AAC/F,eAAO,MAAM,MAAM,OAAO,CAAC;AAE3B,yGAAyG;AACzG,wBAAgB,uBAAuB,CAAC,GAAG,GAAE,UAAyB,GAAG,OAAO,CAE/E"}
package/dist/env/ci.js ADDED
@@ -0,0 +1,40 @@
1
+ import { ambientEnv, ambientFlag } from "./ambient.js";
2
+ //#region src/env/ci.ts
3
+ /**
4
+ * Is this continuous integration? One read, one rule, one place — the convention starts here.
5
+ *
6
+ * Nothing in this repository read `CI` before #243, so this is the declaration rather than a fourth
7
+ * copy of one. It exists because a gate that inlines `!!process.env.CI` at the point it guards is a
8
+ * gate whose meaning is re-decided by whoever writes the next one.
9
+ *
10
+ * ## What counts as set
11
+ *
12
+ * **Any non-blank value.** `CI=true`, `CI=1`, and `CI=woodpecker` are all CI; `CI=` (set but empty,
13
+ * which some runners do) and an absent `CI` are not. That is `PITHY_OFFLINE`'s rule (#218) applied
14
+ * unchanged, rather than a second rule for a second variable — see {@link ambientFlag}.
15
+ *
16
+ * ## Why anything reads it
17
+ *
18
+ * CI is where an unsupervised capability is most reachable and least watched. A `dev` composition is
19
+ * exactly what CI boots — integration suites, packaging checks, `pithy dev` itself — so "not `dev`"
20
+ * does not cover CI, and CI is not production, so neither signal implies the other. Anything gated on
21
+ * both asks twice, in two statements: an `||` folded into one expression is one edit from an `&&`, and
22
+ * the failure mode is a session-minting endpoint that answers.
23
+ *
24
+ * ## Where it can be read, and where it cannot
25
+ *
26
+ * In the CLI, and in anything else running under Node, this is the host's own `CI`. **Inside a Worker
27
+ * it is not**: workerd populates `process.env` from the script's bindings and nothing else, so the
28
+ * shell variable does not cross that boundary (see {@link ./ambient.ts}). A Worker-side gate is
29
+ * therefore only as truthful as what started the Worker — which is why `pithy dev` forwards `CI` into
30
+ * every Worker it launches as a var, and why a gate that depends on this must also carry a second,
31
+ * independent refusal that needs no forwarding.
32
+ */
33
+ /** The variable every CI runner sets without configuration. The name is not ours to choose. */
34
+ const CI_ENV = "CI";
35
+ /** Whether this process is running under continuous integration. Any non-blank `CI`; blank is not CI. */
36
+ function isContinuousIntegration(env = ambientEnv()) {
37
+ return ambientFlag(env, "CI");
38
+ }
39
+ //#endregion
40
+ export { CI_ENV, isContinuousIntegration };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * How a member of the dev set publishes its address, and how a sibling looks it up.
3
+ *
4
+ * `pithy dev` pins one port per worker at feature-create and exports `<STEM>_PORT` / `<STEM>_ORIGIN`
5
+ * for every one of them, so a worker reaches its peers at a known address rather than through
6
+ * wrangler's cross-`wrangler dev` service registry (CLAUDE.md §CLI). The stem is the join: the CLI
7
+ * writes the var, and code running *inside* the Worker reads it.
8
+ *
9
+ * **It lives in core because both ends are now real.** It was the CLI's private helper while only the
10
+ * CLI used it; the loopback Workflow dispatcher ({@link ../workflow/loopback.ts}) made the runtime the
11
+ * other end of the same wire, and two copies of a name-derivation rule are two answers the first day
12
+ * one of them learns about a character the other does not.
13
+ */
14
+ /**
15
+ * The env-var stem for a worker or capability: uppercased, every non-alphanumeric run collapsed to a
16
+ * single `_`, leading and trailing separators dropped. `media-cli` → `MEDIA_CLI`.
17
+ *
18
+ * Deterministic, so the name a worker publishes is the name every sibling looks up.
19
+ */
20
+ export declare function envStem(name: string): string;
21
+ /** The var carrying one member's loopback address — `email` → `EMAIL_ORIGIN`. */
22
+ export declare function originVarName(name: string): string;
23
+ //# sourceMappingURL=stem.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stem.d.ts","sourceRoot":"","sources":["../../src/env/stem.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;GAYG;AAEH;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAK5C;AAED,iFAAiF;AACjF,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAElD"}
@@ -0,0 +1,29 @@
1
+ //#region src/env/stem.ts
2
+ /**
3
+ * How a member of the dev set publishes its address, and how a sibling looks it up.
4
+ *
5
+ * `pithy dev` pins one port per worker at feature-create and exports `<STEM>_PORT` / `<STEM>_ORIGIN`
6
+ * for every one of them, so a worker reaches its peers at a known address rather than through
7
+ * wrangler's cross-`wrangler dev` service registry (CLAUDE.md §CLI). The stem is the join: the CLI
8
+ * writes the var, and code running *inside* the Worker reads it.
9
+ *
10
+ * **It lives in core because both ends are now real.** It was the CLI's private helper while only the
11
+ * CLI used it; the loopback Workflow dispatcher ({@link ../workflow/loopback.ts}) made the runtime the
12
+ * other end of the same wire, and two copies of a name-derivation rule are two answers the first day
13
+ * one of them learns about a character the other does not.
14
+ */
15
+ /**
16
+ * The env-var stem for a worker or capability: uppercased, every non-alphanumeric run collapsed to a
17
+ * single `_`, leading and trailing separators dropped. `media-cli` → `MEDIA_CLI`.
18
+ *
19
+ * Deterministic, so the name a worker publishes is the name every sibling looks up.
20
+ */
21
+ function envStem(name) {
22
+ return name.toUpperCase().replace(/[^A-Z0-9]+/g, "_").replace(/^_+|_+$/g, "");
23
+ }
24
+ /** The var carrying one member's loopback address — `email` → `EMAIL_ORIGIN`. */
25
+ function originVarName(name) {
26
+ return `${envStem(name)}_ORIGIN`;
27
+ }
28
+ //#endregion
29
+ export { envStem, originVarName };