@pithy-sh/core 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (270) hide show
  1. package/dist/access/access.d.ts +172 -0
  2. package/dist/access/access.d.ts.map +1 -0
  3. package/dist/access/access.js +147 -0
  4. package/dist/access/acting.d.ts +49 -0
  5. package/dist/access/acting.d.ts.map +1 -0
  6. package/dist/access/acting.js +21 -0
  7. package/dist/access/policyCheck.d.ts +33 -0
  8. package/dist/access/policyCheck.d.ts.map +1 -0
  9. package/dist/access/policyCheck.js +48 -0
  10. package/dist/access/usage.d.ts +83 -0
  11. package/dist/access/usage.d.ts.map +1 -0
  12. package/dist/access/usage.js +104 -0
  13. package/dist/access/visibility.d.ts +66 -0
  14. package/dist/access/visibility.d.ts.map +1 -0
  15. package/dist/access/visibility.js +86 -0
  16. package/dist/audit/auditEvent.d.ts +5 -0
  17. package/dist/audit/auditEvent.d.ts.map +1 -1
  18. package/dist/audit/auditEvent.js +2 -0
  19. package/dist/audit/recorder.d.ts +10 -2
  20. package/dist/audit/recorder.d.ts.map +1 -1
  21. package/dist/capability/bindings.d.ts +47 -2
  22. package/dist/capability/bindings.d.ts.map +1 -1
  23. package/dist/capability/bindings.js +117 -7
  24. package/dist/capability/capability.d.ts +110 -28
  25. package/dist/capability/capability.d.ts.map +1 -1
  26. package/dist/capability/capability.js +2 -0
  27. package/dist/capability/manifest.d.ts +2 -1
  28. package/dist/capability/manifest.d.ts.map +1 -1
  29. package/dist/capability/manifest.js +1 -1
  30. package/dist/capability/purge.d.ts +80 -0
  31. package/dist/capability/purge.d.ts.map +1 -0
  32. package/dist/capability/purge.js +76 -0
  33. package/dist/capability/settings.d.ts +86 -2
  34. package/dist/capability/settings.d.ts.map +1 -1
  35. package/dist/capability/settings.js +80 -1
  36. package/dist/cli/commandCatalog.generated.d.ts +238 -24
  37. package/dist/cli/commandCatalog.generated.d.ts.map +1 -1
  38. package/dist/cli/commandCatalog.generated.js +251 -22
  39. package/dist/cli/commandFacts.d.ts +2 -1
  40. package/dist/cli/commandFacts.d.ts.map +1 -1
  41. package/dist/cli/commandFacts.js +2 -0
  42. package/dist/controlPlane/capability.d.ts.map +1 -1
  43. package/dist/controlPlane/capability.js +19 -2
  44. package/dist/controlPlane/context.d.ts +5 -0
  45. package/dist/controlPlane/context.d.ts.map +1 -1
  46. package/dist/controlPlane/context.js +3 -1
  47. package/dist/controlPlane/data/connection.d.ts +49 -12
  48. package/dist/controlPlane/data/connection.d.ts.map +1 -1
  49. package/dist/controlPlane/data/connection.js +56 -2
  50. package/dist/controlPlane/discovery/adminRoute.d.ts +29 -0
  51. package/dist/controlPlane/discovery/adminRoute.d.ts.map +1 -1
  52. package/dist/controlPlane/discovery/adminRoute.js +3 -0
  53. package/dist/controlPlane/error/errors.d.ts +4 -3
  54. package/dist/controlPlane/error/errors.d.ts.map +1 -1
  55. package/dist/controlPlane/http/cors.d.ts +4 -4
  56. package/dist/controlPlane/http/cors.js +4 -4
  57. package/dist/controlPlane/http/guard.d.ts +11 -0
  58. package/dist/controlPlane/http/guard.d.ts.map +1 -1
  59. package/dist/controlPlane/http/guard.js +17 -1
  60. package/dist/controlPlane/http/handlers.d.ts +139 -0
  61. package/dist/controlPlane/http/handlers.d.ts.map +1 -1
  62. package/dist/controlPlane/http/handlers.js +67 -4
  63. package/dist/controlPlane/http/readiness.d.ts +33 -0
  64. package/dist/controlPlane/http/readiness.d.ts.map +1 -0
  65. package/dist/controlPlane/http/readiness.js +39 -0
  66. package/dist/controlPlane/http/responses.d.ts +245 -0
  67. package/dist/controlPlane/http/responses.d.ts.map +1 -1
  68. package/dist/controlPlane/http/responses.js +101 -4
  69. package/dist/controlPlane/http/routes.d.ts +2 -0
  70. package/dist/controlPlane/http/routes.d.ts.map +1 -1
  71. package/dist/controlPlane/http/routes.js +31 -3
  72. package/dist/controlPlane/http/verify.d.ts.map +1 -1
  73. package/dist/controlPlane/http/verify.js +2 -1
  74. package/dist/controlPlane/migrationState/migrationState.d.ts +102 -0
  75. package/dist/controlPlane/migrationState/migrationState.d.ts.map +1 -0
  76. package/dist/controlPlane/migrationState/migrationState.js +142 -0
  77. package/dist/controlPlane/migrations/0003_connection_principal.d.ts +21 -0
  78. package/dist/controlPlane/migrations/0003_connection_principal.d.ts.map +1 -0
  79. package/dist/controlPlane/migrations/0003_connection_principal.js +33 -0
  80. package/dist/controlPlane/scope/scope.d.ts +23 -2
  81. package/dist/controlPlane/scope/scope.d.ts.map +1 -1
  82. package/dist/controlPlane/scope/scope.js +24 -3
  83. package/dist/createBackend.d.ts.map +1 -1
  84. package/dist/createBackend.js +9 -2
  85. package/dist/createEntrypoint.d.ts.map +1 -1
  86. package/dist/createEntrypoint.js +5 -3
  87. package/dist/crypto/envelope.d.ts +86 -0
  88. package/dist/crypto/envelope.d.ts.map +1 -0
  89. package/dist/crypto/envelope.js +129 -0
  90. package/dist/crypto/fieldKeys.d.ts +41 -0
  91. package/dist/crypto/fieldKeys.d.ts.map +1 -0
  92. package/dist/crypto/fieldKeys.js +56 -0
  93. package/dist/data/codecs.d.ts +23 -10
  94. package/dist/data/codecs.d.ts.map +1 -1
  95. package/dist/data/codecs.js +50 -36
  96. package/dist/entitlement/allowance.d.ts +25 -0
  97. package/dist/entitlement/allowance.d.ts.map +1 -0
  98. package/dist/entitlement/allowance.js +80 -0
  99. package/dist/entitlement/entitlement.d.ts +41 -0
  100. package/dist/entitlement/entitlement.d.ts.map +1 -1
  101. package/dist/entitlement/meter.d.ts +57 -0
  102. package/dist/entitlement/meter.d.ts.map +1 -0
  103. package/dist/entitlement/meter.js +13 -0
  104. package/dist/error/extend.d.ts +6 -5
  105. package/dist/error/extend.d.ts.map +1 -1
  106. package/dist/error/http.d.ts +168 -0
  107. package/dist/error/http.d.ts.map +1 -1
  108. package/dist/error/payload.d.ts +336 -0
  109. package/dist/error/payload.d.ts.map +1 -1
  110. package/dist/error/payload.js +116 -4
  111. package/dist/error/pithyError.d.ts +18 -7
  112. package/dist/error/pithyError.d.ts.map +1 -1
  113. package/dist/error/pithyError.js +6 -0
  114. package/dist/http/authContext.d.ts +7 -0
  115. package/dist/http/authContext.d.ts.map +1 -1
  116. package/dist/http/authContext.js +8 -1
  117. package/dist/http/scopeGates.d.ts +20 -0
  118. package/dist/http/scopeGates.d.ts.map +1 -0
  119. package/dist/http/scopeGates.js +36 -0
  120. package/dist/index.d.ts +1 -1
  121. package/dist/index.d.ts.map +1 -1
  122. package/dist/index.js +2 -2
  123. package/dist/logger/logger.d.ts +12 -5
  124. package/dist/logger/logger.d.ts.map +1 -1
  125. package/dist/logger/record.d.ts +98 -0
  126. package/dist/logger/record.d.ts.map +1 -1
  127. package/dist/migrations/capabilitySets.d.ts +23 -0
  128. package/dist/migrations/capabilitySets.d.ts.map +1 -0
  129. package/dist/migrations/capabilitySets.js +74 -0
  130. package/dist/migrations/registry.d.ts +26 -0
  131. package/dist/migrations/registry.d.ts.map +1 -1
  132. package/dist/migrations/registry.js +38 -7
  133. package/dist/migrations/state.d.ts +60 -0
  134. package/dist/migrations/state.d.ts.map +1 -0
  135. package/dist/migrations/state.js +46 -0
  136. package/dist/naming/feature.d.ts +27 -5
  137. package/dist/naming/feature.d.ts.map +1 -1
  138. package/dist/naming/feature.js +36 -6
  139. package/dist/naming/limits.d.ts +47 -3
  140. package/dist/naming/limits.d.ts.map +1 -1
  141. package/dist/naming/limits.js +47 -3
  142. package/dist/naming/provisionScope.d.ts +6 -0
  143. package/dist/naming/provisionScope.d.ts.map +1 -1
  144. package/dist/naming/provisionScope.js +8 -3
  145. package/dist/naming/resourceNames.d.ts +21 -2
  146. package/dist/naming/resourceNames.d.ts.map +1 -1
  147. package/dist/naming/resourceNames.js +21 -3
  148. package/dist/privacy/blindIndex.d.ts +27 -0
  149. package/dist/privacy/blindIndex.d.ts.map +1 -0
  150. package/dist/privacy/blindIndex.js +57 -0
  151. package/dist/privacy/classes.d.ts +142 -0
  152. package/dist/privacy/classes.d.ts.map +1 -0
  153. package/dist/privacy/classes.js +209 -0
  154. package/dist/privacy/classify.d.ts +336 -0
  155. package/dist/privacy/classify.d.ts.map +1 -0
  156. package/dist/privacy/classify.js +1024 -0
  157. package/dist/privacy/encrypted.d.ts +210 -0
  158. package/dist/privacy/encrypted.d.ts.map +1 -0
  159. package/dist/privacy/encrypted.js +350 -0
  160. package/dist/privacy/encryptedColumns.d.ts +64 -0
  161. package/dist/privacy/encryptedColumns.d.ts.map +1 -0
  162. package/dist/privacy/encryptedColumns.js +134 -0
  163. package/dist/privacy/fieldRotation.d.ts +34 -0
  164. package/dist/privacy/fieldRotation.d.ts.map +1 -0
  165. package/dist/privacy/fieldRotation.js +186 -0
  166. package/dist/privacy/inventory.d.ts +12 -0
  167. package/dist/privacy/inventory.d.ts.map +1 -0
  168. package/dist/privacy/inventory.js +154 -0
  169. package/dist/privacy/posture.d.ts +114 -0
  170. package/dist/privacy/posture.d.ts.map +1 -0
  171. package/dist/privacy/posture.js +136 -0
  172. package/dist/privacy/redact.d.ts +7 -0
  173. package/dist/privacy/redact.d.ts.map +1 -0
  174. package/dist/privacy/redact.js +404 -0
  175. package/dist/privacy/report.d.ts +58 -0
  176. package/dist/privacy/report.d.ts.map +1 -0
  177. package/dist/privacy/report.js +15 -0
  178. package/dist/privacy/retirement.d.ts +68 -0
  179. package/dist/privacy/retirement.d.ts.map +1 -0
  180. package/dist/privacy/retirement.js +72 -0
  181. package/dist/privacy/sealed.d.ts +87 -0
  182. package/dist/privacy/sealed.d.ts.map +1 -0
  183. package/dist/privacy/sealed.js +221 -0
  184. package/dist/privacy/types.d.ts +559 -0
  185. package/dist/privacy/types.d.ts.map +1 -0
  186. package/dist/privacy/types.js +2 -0
  187. package/dist/seed/writeD1.d.ts +7 -1
  188. package/dist/seed/writeD1.d.ts.map +1 -1
  189. package/dist/seed/writeD1.js +9 -3
  190. package/dist/seed/writeKv.d.ts +2 -0
  191. package/dist/seed/writeKv.d.ts.map +1 -1
  192. package/dist/seed/writeKv.js +6 -2
  193. package/dist/version.generated.d.ts +1 -1
  194. package/dist/version.generated.js +1 -1
  195. package/dist/worker/serviceLoopback.d.ts +36 -0
  196. package/dist/worker/serviceLoopback.d.ts.map +1 -0
  197. package/dist/worker/serviceLoopback.js +74 -0
  198. package/dist/workflow/faults.d.ts +12 -1
  199. package/dist/workflow/faults.d.ts.map +1 -1
  200. package/dist/workflow/faults.js +22 -15
  201. package/package.json +2 -2
  202. package/src/access/access.ts +298 -0
  203. package/src/access/acting.ts +59 -0
  204. package/src/access/policyCheck.ts +77 -0
  205. package/src/access/usage.ts +141 -0
  206. package/src/access/visibility.ts +109 -0
  207. package/src/audit/auditEvent.ts +4 -0
  208. package/src/audit/recorder.ts +10 -2
  209. package/src/capability/bindings.ts +159 -13
  210. package/src/capability/capability.ts +114 -29
  211. package/src/capability/purge.ts +124 -0
  212. package/src/capability/settings.ts +146 -2
  213. package/src/cli/commandCatalog.generated.ts +271 -22
  214. package/src/cli/commandFacts.ts +2 -0
  215. package/src/controlPlane/capability.ts +36 -1
  216. package/src/controlPlane/context.ts +4 -0
  217. package/src/controlPlane/data/connection.ts +90 -0
  218. package/src/controlPlane/discovery/adminRoute.ts +11 -0
  219. package/src/controlPlane/error/errors.ts +4 -3
  220. package/src/controlPlane/http/cors.ts +4 -4
  221. package/src/controlPlane/http/guard.ts +21 -0
  222. package/src/controlPlane/http/handlers.ts +98 -4
  223. package/src/controlPlane/http/readiness.ts +50 -0
  224. package/src/controlPlane/http/responses.ts +240 -2
  225. package/src/controlPlane/http/routes.ts +55 -1
  226. package/src/controlPlane/http/verify.ts +2 -0
  227. package/src/controlPlane/migrationState/migrationState.ts +200 -0
  228. package/src/controlPlane/migrations/0003_connection_principal.ts +37 -0
  229. package/src/controlPlane/scope/scope.ts +25 -2
  230. package/src/controlPlane/token/base64url.ts +1 -1
  231. package/src/createBackend.ts +28 -6
  232. package/src/createEntrypoint.ts +9 -3
  233. package/src/crypto/envelope.ts +196 -0
  234. package/src/crypto/fieldKeys.ts +76 -0
  235. package/src/data/codecs.ts +69 -37
  236. package/src/entitlement/allowance.ts +85 -0
  237. package/src/entitlement/entitlement.ts +38 -0
  238. package/src/entitlement/meter.ts +69 -0
  239. package/src/error/extend.ts +6 -5
  240. package/src/error/payload.ts +253 -4
  241. package/src/error/pithyError.ts +21 -7
  242. package/src/http/authContext.ts +13 -1
  243. package/src/http/scopeGates.ts +34 -0
  244. package/src/index.ts +2 -0
  245. package/src/logger/logger.ts +12 -5
  246. package/src/migrations/capabilitySets.ts +87 -0
  247. package/src/migrations/registry.ts +46 -2
  248. package/src/migrations/state.ts +94 -0
  249. package/src/naming/feature.ts +46 -7
  250. package/src/naming/limits.ts +35 -3
  251. package/src/naming/provisionScope.ts +19 -1
  252. package/src/naming/resourceNames.ts +52 -5
  253. package/src/privacy/blindIndex.ts +81 -0
  254. package/src/privacy/classes.ts +277 -0
  255. package/src/privacy/classify.ts +1447 -0
  256. package/src/privacy/encrypted.ts +513 -0
  257. package/src/privacy/encryptedColumns.ts +185 -0
  258. package/src/privacy/fieldRotation.ts +234 -0
  259. package/src/privacy/inventory.ts +147 -0
  260. package/src/privacy/posture.ts +205 -0
  261. package/src/privacy/redact.ts +423 -0
  262. package/src/privacy/report.ts +64 -0
  263. package/src/privacy/retirement.ts +102 -0
  264. package/src/privacy/sealed.ts +323 -0
  265. package/src/privacy/types.ts +804 -0
  266. package/src/seed/writeD1.ts +13 -4
  267. package/src/seed/writeKv.ts +6 -2
  268. package/src/version.generated.ts +1 -1
  269. package/src/worker/serviceLoopback.ts +105 -0
  270. package/src/workflow/faults.ts +51 -24
@@ -181,6 +181,38 @@ const AuthSessionNotFreshPublic = z
181
181
  })
182
182
  .describe("The session is authentic but the holder authenticated too long ago to attach a provider (403).");
183
183
 
184
+ const AuthAccountSuspendedPublic = z
185
+ .object({
186
+ code: z
187
+ .literal("auth/account_suspended")
188
+ .describe(
189
+ "The account this credential belongs to is suspended, so the request is refused whatever it carries. An operator suspended it on purpose, with a reason, and the reason is the `message`. When the suspension has an end, `params.suspendedUntil` names it as ISO-8601; a suspension past its end is no longer one, and nothing has to clear it. A separate code from `auth/forbidden` so a client can render the screen that says what happened rather than a generic refusal.",
190
+ ),
191
+ status: z
192
+ .literal(403)
193
+ .describe(
194
+ "Forbidden — the credential is genuine and the account behind it may not act. Not 401: signing in again cannot help, and a client that read it as an expired token would loop.",
195
+ ),
196
+ ...publicFields,
197
+ })
198
+ .describe("The account is suspended (403).");
199
+
200
+ const AuthAccountDeletedPublic = z
201
+ .object({
202
+ code: z
203
+ .literal("auth/account_deleted")
204
+ .describe(
205
+ "The account this credential belongs to is deleted and inside its purge window: it cannot sign in or act, and it can still be restored until the window closes. After the purge there is no account, and the credential is simply invalid.",
206
+ ),
207
+ status: z
208
+ .literal(403)
209
+ .describe(
210
+ "Forbidden — the account exists and may not act. Not 404, because until the purge runs it can come back, and not 401, because signing in again cannot help.",
211
+ ),
212
+ ...publicFields,
213
+ })
214
+ .describe("The account is deleted and awaiting its purge (403).");
215
+
184
216
  const NotFoundPublic = z
185
217
  .object({
186
218
  code: z.literal("core/not_found").describe("The requested resource does not exist."),
@@ -501,6 +533,28 @@ const TurnstileConfigPublic = z
501
533
  })
502
534
  .describe("The Turnstile middleware is misconfigured (500).");
503
535
 
536
+ const TurnstileAnalyticsWindowPublic = z
537
+ .object({
538
+ code: z
539
+ .literal("turnstile/analytics_window")
540
+ .describe(
541
+ "The requested challenge window reaches past what Cloudflare keeps, or is wider than one read may ask for.",
542
+ ),
543
+ status: z.literal(400).describe("Bad Request — narrow the window or move it later."),
544
+ ...publicFields,
545
+ })
546
+ .describe("A Turnstile-analytics window outside Cloudflare's retention or range (400).");
547
+
548
+ const TurnstileAnalyticsUnavailablePublic = z
549
+ .object({
550
+ code: z
551
+ .literal("turnstile/analytics_unavailable")
552
+ .describe("Cloudflare does not serve Turnstile analytics to this account, so no outcome can be read."),
553
+ status: z.literal(409).describe("Conflict — the account's configuration rules the read out."),
554
+ ...publicFields,
555
+ })
556
+ .describe("Turnstile analytics are not served to this account (409).");
557
+
504
558
  // --- @pithy-sh/audit: audit-trail recorder codes ---
505
559
 
506
560
  const AuditInvalidEventPublic = z
@@ -535,6 +589,18 @@ const MediaNotFoundPublic = z
535
589
  })
536
590
  .describe("A requested media record does not exist (404).");
537
591
 
592
+ const MediaForbiddenPublic = z
593
+ .object({
594
+ code: z
595
+ .literal("media/forbidden")
596
+ .describe(
597
+ "The caller may read the media record but not change it: only its owner, acting for the record's organization, or a role holding `media:manage` there may edit, delete or re-scope it.",
598
+ ),
599
+ status: z.literal(403).describe("Forbidden."),
600
+ ...publicFields,
601
+ })
602
+ .describe("The caller may read the record but not change it (403).");
603
+
538
604
  const MediaUnsupportedPublic = z
539
605
  .object({
540
606
  code: z
@@ -565,6 +631,30 @@ const MediaEnrichmentFailedPublic = z
565
631
  })
566
632
  .describe("A media AI-enrichment step failed (500).");
567
633
 
634
+ const MediaAnalyticsWindowPublic = z
635
+ .object({
636
+ code: z
637
+ .literal("media/analytics_window")
638
+ .describe(
639
+ "The requested delivery window reaches past what Cloudflare keeps, or is wider than one read may ask for.",
640
+ ),
641
+ status: z.literal(400).describe("Bad Request — narrow the window or move it later."),
642
+ ...publicFields,
643
+ })
644
+ .describe("A delivery-analytics window outside Cloudflare's retention or range (400).");
645
+
646
+ const MediaAnalyticsUnavailablePublic = z
647
+ .object({
648
+ code: z
649
+ .literal("media/analytics_unavailable")
650
+ .describe(
651
+ "Cloudflare does not serve the delivery analytics this read needs to this account — a Customer Metadata Boundary outside the US carries none of them.",
652
+ ),
653
+ status: z.literal(409).describe("Conflict — the account's configuration rules the read out."),
654
+ ...publicFields,
655
+ })
656
+ .describe("Delivery analytics are not served to this account (409).");
657
+
568
658
  // --- @pithy-sh/leaderboard: optional ranking capability codes ---
569
659
 
570
660
  const LeaderboardBoardNotFoundPublic = z
@@ -931,7 +1021,7 @@ const StorageNotFoundPublic = z
931
1021
  .object({
932
1022
  code: z
933
1023
  .literal("storage/not_found")
934
- .describe("No object row matches, or the object exists but is not visible to this caller."),
1024
+ .describe("No object row matches, or the object exists but this caller may not read it."),
935
1025
  status: z.literal(404).describe("Not Found."),
936
1026
  ...publicFields,
937
1027
  })
@@ -939,18 +1029,22 @@ const StorageNotFoundPublic = z
939
1029
 
940
1030
  const StorageForbiddenPublic = z
941
1031
  .object({
942
- code: z.literal("storage/forbidden").describe("The caller does not own the object and the object is not public."),
1032
+ code: z
1033
+ .literal("storage/forbidden")
1034
+ .describe(
1035
+ "The caller may read the object but not change it: only its owner, acting for the object's organization, or a role holding `storage:manage` there may.",
1036
+ ),
943
1037
  status: z.literal(403).describe("Forbidden."),
944
1038
  ...publicFields,
945
1039
  })
946
- .describe("The caller is not the owner and the object is not public (403).");
1040
+ .describe("The caller may read the object but not change it (403).");
947
1041
 
948
1042
  const StorageQuotaExceededPublic = z
949
1043
  .object({
950
1044
  code: z
951
1045
  .literal("storage/quota_exceeded")
952
1046
  .describe(
953
- "The declared upload size would push the owner past their byte quota. Checked at init against pending and stored rows together, so concurrent inits cannot race past the limit.",
1047
+ "The declared upload size would push the owner past the configured `bytesPerOwner` quota. Checked at init against pending and stored rows together, so concurrent inits cannot race past the limit. Where the `storageBytes` meter is enforced, the same refusal is `payments/quota_exceeded` (402).",
954
1048
  ),
955
1049
  status: z.literal(413).describe("Content Too Large."),
956
1050
  ...publicFields,
@@ -1257,6 +1351,30 @@ const PaymentsSubscriptionChangeRefusedPublic = z
1257
1351
  })
1258
1352
  .describe("A subscription's state forbids the change asked of it (409).");
1259
1353
 
1354
+ const PaymentsOverrideStalePublic = z
1355
+ .object({
1356
+ code: z
1357
+ .literal("payments/override_stale")
1358
+ .describe(
1359
+ "A control-plane override write named a version behind the subject's override row. Two operators share one customer: both opened the same allowance, and the second would silently overwrite the first while the first believed they had succeeded. So the write is refused and nothing is written. 409, because the request is well-formed and the same request against the version it now holds would be accepted — the caller re-reads, and tries again.",
1360
+ ),
1361
+ status: z.literal(409).describe("Conflict — the override changed since the caller read it."),
1362
+ ...publicFields,
1363
+ })
1364
+ .describe("An override write named a stale version (409).");
1365
+
1366
+ const PaymentsQuotaExceededPublic = z
1367
+ .object({
1368
+ code: z
1369
+ .literal("payments/quota_exceeded")
1370
+ .describe(
1371
+ "An enforced kit meter — emails enqueued, bytes stored, media items held — would pass the subject's effective quota for the period. Nothing was written. 402 Payment Required, because it is the honest status: the plan does not allow more and the remedy is a purchase, a bigger tier, or a number pinned for this subject from the control plane. A meter refuses and never bills — the kit does not sell overage.",
1372
+ ),
1373
+ status: z.literal(402).describe("Payment Required — the plan's allowance for this period is used up."),
1374
+ ...publicFields,
1375
+ })
1376
+ .describe("An enforced meter is at its quota for this period (402).");
1377
+
1260
1378
  // --- @pithy-sh/core: the `control-plane` verification strategy ---
1261
1379
  //
1262
1380
  // NOT Cloudflare's control plane. Everywhere else in this repo "control plane" means the Cloudflare
@@ -1407,6 +1525,68 @@ const OrganizationNominationInvalidPublic = z
1407
1525
  })
1408
1526
  .describe("No standing ownership offer this caller may accept (400).");
1409
1527
 
1528
+ // --- @pithy-sh/artifacts: Git repositories, ownership, grants and short-lived tokens ---
1529
+
1530
+ const ArtifactsNotFoundPublic = z
1531
+ .object({
1532
+ code: z
1533
+ .literal("artifacts/not_found")
1534
+ .describe(
1535
+ "No such repository, **or** one the caller holds no grant on and does not own. One code for both, byte for byte: a repository's name is in its clone URL, and a refusal that told the two apart would hand it over. Also what the credential route answers for a repository with remote access off. Which it was survives in `detail`, which the HTTP codec strips.",
1536
+ ),
1537
+ status: z.literal(404).describe("Not Found."),
1538
+ ...publicFields,
1539
+ })
1540
+ .describe("No such repository, or none this caller may see — the same answer for both (404).");
1541
+
1542
+ const ArtifactsForbiddenPublic = z
1543
+ .object({
1544
+ code: z
1545
+ .literal("artifacts/forbidden")
1546
+ .describe(
1547
+ "The caller can see the repository and may not do this to it: a grant holder asking for what only an owner may do, or a member of an organization whose role lacks the configured `managePower`. A 403 rather than the 404 above, because by then the caller already knows the repository exists.",
1548
+ ),
1549
+ status: z.literal(403).describe("Forbidden."),
1550
+ ...publicFields,
1551
+ })
1552
+ .describe("The caller may see this repository and may not do this (403).");
1553
+
1554
+ const ArtifactsInvalidNamePublic = z
1555
+ .object({
1556
+ code: z
1557
+ .literal("artifacts/invalid_name")
1558
+ .describe(
1559
+ "A supplied repository name is outside Cloudflare's charset or the 2-to-63 cap. Refused and never shortened: a name somebody chose is in every clone URL, and a silently different one is a repository nobody can find.",
1560
+ ),
1561
+ status: z.literal(400).describe("Bad Request."),
1562
+ ...publicFields,
1563
+ })
1564
+ .describe("A repository name breaks the naming rule (400).");
1565
+
1566
+ const ArtifactsNameTakenPublic = z
1567
+ .object({
1568
+ code: z
1569
+ .literal("artifacts/name_taken")
1570
+ .describe(
1571
+ "The owner already holds a repository with that display name, or a supplied physical name is already in the namespace. The second happens only where the adopter supplies names, so uniqueness is theirs.",
1572
+ ),
1573
+ status: z.literal(409).describe("Conflict."),
1574
+ ...publicFields,
1575
+ })
1576
+ .describe("That repository name is already in use (409).");
1577
+
1578
+ const ArtifactsInvalidConfigPublic = z
1579
+ .object({
1580
+ code: z
1581
+ .literal("artifacts/invalid_config")
1582
+ .describe(
1583
+ "The capability's configuration cannot be honored: a `tokenTtlSeconds` above the ceiling, or `organization` composed with no `managePower` named. Raised at boot, so a mistake fails on deploy rather than at the first mint.",
1584
+ ),
1585
+ status: z.literal(400).describe("Bad Request — the configuration is malformed."),
1586
+ ...publicFields,
1587
+ })
1588
+ .describe("The artifacts configuration is invalid (400).");
1589
+
1410
1590
  // --- @pithy-sh/support: inbound inbox, taxonomy, and classification codes ---
1411
1591
 
1412
1592
  const SupportNotFoundPublic = z
@@ -1610,6 +1790,8 @@ export const KitPublicErrorPayload = z
1610
1790
  ForbiddenPublic,
1611
1791
  AuthProviderUnavailablePublic,
1612
1792
  AuthSessionNotFreshPublic,
1793
+ AuthAccountSuspendedPublic,
1794
+ AuthAccountDeletedPublic,
1613
1795
  NotFoundPublic,
1614
1796
  ConflictPublic,
1615
1797
  RateLimitPublic,
@@ -1639,12 +1821,17 @@ export const KitPublicErrorPayload = z
1639
1821
  TurnstileMissingTokenPublic,
1640
1822
  TurnstileFailedPublic,
1641
1823
  TurnstileConfigPublic,
1824
+ TurnstileAnalyticsWindowPublic,
1825
+ TurnstileAnalyticsUnavailablePublic,
1642
1826
  AuditInvalidEventPublic,
1643
1827
  AuditWriteFailedPublic,
1644
1828
  MediaNotFoundPublic,
1829
+ MediaForbiddenPublic,
1645
1830
  MediaUnsupportedPublic,
1646
1831
  MediaStorageFailedPublic,
1647
1832
  MediaEnrichmentFailedPublic,
1833
+ MediaAnalyticsWindowPublic,
1834
+ MediaAnalyticsUnavailablePublic,
1648
1835
  LeaderboardBoardNotFoundPublic,
1649
1836
  LeaderboardEntryNotFoundPublic,
1650
1837
  LeaderboardScoreRejectedPublic,
@@ -1707,6 +1894,8 @@ export const KitPublicErrorPayload = z
1707
1894
  PaymentsEntitlementNotInCatalogPublic,
1708
1895
  PaymentsSubjectUnresolvedPublic,
1709
1896
  PaymentsSubscriptionChangeRefusedPublic,
1897
+ PaymentsOverrideStalePublic,
1898
+ PaymentsQuotaExceededPublic,
1710
1899
  ControlPlaneNotConnectedPublic,
1711
1900
  ControlPlaneInvalidCredentialPublic,
1712
1901
  ControlPlaneInsufficientScopePublic,
@@ -1719,6 +1908,11 @@ export const KitPublicErrorPayload = z
1719
1908
  OrganizationLastAdministratorPublic,
1720
1909
  OrganizationSlugTakenPublic,
1721
1910
  OrganizationNominationInvalidPublic,
1911
+ ArtifactsNotFoundPublic,
1912
+ ArtifactsForbiddenPublic,
1913
+ ArtifactsInvalidNamePublic,
1914
+ ArtifactsNameTakenPublic,
1915
+ ArtifactsInvalidConfigPublic,
1722
1916
  SupportNotFoundPublic,
1723
1917
  SupportInvalidCategoryPublic,
1724
1918
  SupportUnparseableMessagePublic,
@@ -1754,6 +1948,12 @@ const AuthProviderUnavailable = AuthProviderUnavailablePublic.extend(internalFie
1754
1948
  const AuthSessionNotFresh = AuthSessionNotFreshPublic.extend(internalFields).describe(
1755
1949
  AuthSessionNotFreshPublic.description ?? "",
1756
1950
  );
1951
+ const AuthAccountSuspended = AuthAccountSuspendedPublic.extend(internalFields).describe(
1952
+ AuthAccountSuspendedPublic.description ?? "",
1953
+ );
1954
+ const AuthAccountDeleted = AuthAccountDeletedPublic.extend(internalFields).describe(
1955
+ AuthAccountDeletedPublic.description ?? "",
1956
+ );
1757
1957
  const NotFound = NotFoundPublic.extend(internalFields).describe(NotFoundPublic.description ?? "");
1758
1958
  const Conflict = ConflictPublic.extend(internalFields).describe(ConflictPublic.description ?? "");
1759
1959
  const RateLimit = RateLimitPublic.extend(internalFields).describe(RateLimitPublic.description ?? "");
@@ -1815,6 +2015,12 @@ const TurnstileMissingToken = TurnstileMissingTokenPublic.extend(internalFields)
1815
2015
  );
1816
2016
  const TurnstileFailed = TurnstileFailedPublic.extend(internalFields).describe(TurnstileFailedPublic.description ?? "");
1817
2017
  const TurnstileConfig = TurnstileConfigPublic.extend(internalFields).describe(TurnstileConfigPublic.description ?? "");
2018
+ const TurnstileAnalyticsWindow = TurnstileAnalyticsWindowPublic.extend(internalFields).describe(
2019
+ TurnstileAnalyticsWindowPublic.description ?? "",
2020
+ );
2021
+ const TurnstileAnalyticsUnavailable = TurnstileAnalyticsUnavailablePublic.extend(internalFields).describe(
2022
+ TurnstileAnalyticsUnavailablePublic.description ?? "",
2023
+ );
1818
2024
  const AuditInvalidEvent = AuditInvalidEventPublic.extend(internalFields).describe(
1819
2025
  AuditInvalidEventPublic.description ?? "",
1820
2026
  );
@@ -1822,6 +2028,7 @@ const AuditWriteFailed = AuditWriteFailedPublic.extend(internalFields).describe(
1822
2028
  AuditWriteFailedPublic.description ?? "",
1823
2029
  );
1824
2030
  const MediaNotFound = MediaNotFoundPublic.extend(internalFields).describe(MediaNotFoundPublic.description ?? "");
2031
+ const MediaForbidden = MediaForbiddenPublic.extend(internalFields).describe(MediaForbiddenPublic.description ?? "");
1825
2032
  const MediaUnsupported = MediaUnsupportedPublic.extend(internalFields).describe(
1826
2033
  MediaUnsupportedPublic.description ?? "",
1827
2034
  );
@@ -1831,6 +2038,12 @@ const MediaStorageFailed = MediaStorageFailedPublic.extend(internalFields).descr
1831
2038
  const MediaEnrichmentFailed = MediaEnrichmentFailedPublic.extend(internalFields).describe(
1832
2039
  MediaEnrichmentFailedPublic.description ?? "",
1833
2040
  );
2041
+ const MediaAnalyticsWindow = MediaAnalyticsWindowPublic.extend(internalFields).describe(
2042
+ MediaAnalyticsWindowPublic.description ?? "",
2043
+ );
2044
+ const MediaAnalyticsUnavailable = MediaAnalyticsUnavailablePublic.extend(internalFields).describe(
2045
+ MediaAnalyticsUnavailablePublic.description ?? "",
2046
+ );
1834
2047
  const LeaderboardBoardNotFound = LeaderboardBoardNotFoundPublic.extend(internalFields).describe(
1835
2048
  LeaderboardBoardNotFoundPublic.description ?? "",
1836
2049
  );
@@ -2016,6 +2229,12 @@ const PaymentsSubjectUnresolved = PaymentsSubjectUnresolvedPublic.extend(interna
2016
2229
  const PaymentsSubscriptionChangeRefused = PaymentsSubscriptionChangeRefusedPublic.extend(internalFields).describe(
2017
2230
  PaymentsSubscriptionChangeRefusedPublic.description ?? "",
2018
2231
  );
2232
+ const PaymentsOverrideStale = PaymentsOverrideStalePublic.extend(internalFields).describe(
2233
+ PaymentsOverrideStalePublic.description ?? "",
2234
+ );
2235
+ const PaymentsQuotaExceeded = PaymentsQuotaExceededPublic.extend(internalFields).describe(
2236
+ PaymentsQuotaExceededPublic.description ?? "",
2237
+ );
2019
2238
  const ControlPlaneNotConnected = ControlPlaneNotConnectedPublic.extend(internalFields).describe(
2020
2239
  ControlPlaneNotConnectedPublic.description ?? "",
2021
2240
  );
@@ -2086,6 +2305,22 @@ const OrganizationNominationInvalid = OrganizationNominationInvalidPublic.extend
2086
2305
  OrganizationNominationInvalidPublic.description ?? "",
2087
2306
  );
2088
2307
 
2308
+ const ArtifactsNotFound = ArtifactsNotFoundPublic.extend(internalFields).describe(
2309
+ ArtifactsNotFoundPublic.description ?? "",
2310
+ );
2311
+ const ArtifactsForbidden = ArtifactsForbiddenPublic.extend(internalFields).describe(
2312
+ ArtifactsForbiddenPublic.description ?? "",
2313
+ );
2314
+ const ArtifactsInvalidName = ArtifactsInvalidNamePublic.extend(internalFields).describe(
2315
+ ArtifactsInvalidNamePublic.description ?? "",
2316
+ );
2317
+ const ArtifactsNameTaken = ArtifactsNameTakenPublic.extend(internalFields).describe(
2318
+ ArtifactsNameTakenPublic.description ?? "",
2319
+ );
2320
+ const ArtifactsInvalidConfig = ArtifactsInvalidConfigPublic.extend(internalFields).describe(
2321
+ ArtifactsInvalidConfigPublic.description ?? "",
2322
+ );
2323
+
2089
2324
  const SupportNotFound = SupportNotFoundPublic.extend(internalFields).describe(SupportNotFoundPublic.description ?? "");
2090
2325
  const SupportInvalidCategory = SupportInvalidCategoryPublic.extend(internalFields).describe(
2091
2326
  SupportInvalidCategoryPublic.description ?? "",
@@ -2116,6 +2351,8 @@ export const KitErrorPayload = z
2116
2351
  Forbidden,
2117
2352
  AuthProviderUnavailable,
2118
2353
  AuthSessionNotFresh,
2354
+ AuthAccountSuspended,
2355
+ AuthAccountDeleted,
2119
2356
  NotFound,
2120
2357
  Conflict,
2121
2358
  RateLimit,
@@ -2145,12 +2382,17 @@ export const KitErrorPayload = z
2145
2382
  TurnstileMissingToken,
2146
2383
  TurnstileFailed,
2147
2384
  TurnstileConfig,
2385
+ TurnstileAnalyticsWindow,
2386
+ TurnstileAnalyticsUnavailable,
2148
2387
  AuditInvalidEvent,
2149
2388
  AuditWriteFailed,
2150
2389
  MediaNotFound,
2390
+ MediaForbidden,
2151
2391
  MediaUnsupported,
2152
2392
  MediaStorageFailed,
2153
2393
  MediaEnrichmentFailed,
2394
+ MediaAnalyticsWindow,
2395
+ MediaAnalyticsUnavailable,
2154
2396
  LeaderboardBoardNotFound,
2155
2397
  LeaderboardEntryNotFound,
2156
2398
  LeaderboardScoreRejected,
@@ -2213,6 +2455,8 @@ export const KitErrorPayload = z
2213
2455
  PaymentsEntitlementNotInCatalog,
2214
2456
  PaymentsSubjectUnresolved,
2215
2457
  PaymentsSubscriptionChangeRefused,
2458
+ PaymentsOverrideStale,
2459
+ PaymentsQuotaExceeded,
2216
2460
  ControlPlaneNotConnected,
2217
2461
  ControlPlaneInvalidCredential,
2218
2462
  ControlPlaneInsufficientScope,
@@ -2225,6 +2469,11 @@ export const KitErrorPayload = z
2225
2469
  OrganizationLastAdministrator,
2226
2470
  OrganizationSlugTaken,
2227
2471
  OrganizationNominationInvalid,
2472
+ ArtifactsNotFound,
2473
+ ArtifactsForbidden,
2474
+ ArtifactsInvalidName,
2475
+ ArtifactsNameTaken,
2476
+ ArtifactsInvalidConfig,
2228
2477
  SupportNotFound,
2229
2478
  SupportInvalidCategory,
2230
2479
  SupportUnparseableMessage,
@@ -3,6 +3,7 @@
3
3
 
4
4
  import type { z } from "zod";
5
5
  import type { MessageParams } from "../i18n/catalog";
6
+ import type { UnmarkedString } from "../privacy/types";
6
7
  import type { ValidationIssue } from "./payload";
7
8
  import { ErrorPayload } from "./payload";
8
9
 
@@ -18,7 +19,20 @@ import { ErrorPayload } from "./payload";
18
19
  export class PithyError extends Error {
19
20
  readonly payload: ErrorPayload;
20
21
 
21
- constructor(payload: ErrorPayload, options?: { cause?: unknown }) {
22
+ /**
23
+ * `message`, `action` and `detail` are each an {@link UnmarkedString} here and on every subclass's arguments
24
+ * (#132): a value marked with a privacy class is refused at compile time, because all three ride into every log
25
+ * line and audit row that carries this error. A template literal is a plain `string`, so interpolation is not caught — see
26
+ * `privacy/types.ts` for exactly what is.
27
+ */
28
+ constructor(
29
+ payload: ErrorPayload & {
30
+ message: UnmarkedString;
31
+ action?: UnmarkedString | undefined;
32
+ detail?: UnmarkedString | undefined;
33
+ },
34
+ options?: { cause?: unknown },
35
+ ) {
22
36
  const parsed = ErrorPayload.parse(payload);
23
37
  super(parsed.message, options);
24
38
  this.payload = parsed;
@@ -28,12 +42,12 @@ export class PithyError extends Error {
28
42
 
29
43
  /** Variable parts every subclass accepts; the `code`/`status` are fixed by the subclass. */
30
44
  interface ErrorArgs {
31
- /** Override the public, safe-to-expose message. Defaults to a per-code summary. */
32
- message?: string;
33
- /** A remediation hint (CLI action line). */
34
- action?: string;
35
- /** Internal context for logs + audit. Never serialized to clients. */
36
- detail?: string;
45
+ /** Override the public, safe-to-expose message. Defaults to a per-code summary. Never a privacy-marked value. */
46
+ message?: UnmarkedString;
47
+ /** A remediation hint (CLI action line). Never a privacy-marked value. */
48
+ action?: UnmarkedString;
49
+ /** Internal context for logs + audit. Never serialized to clients, and never a privacy-marked value (#132). */
50
+ detail?: UnmarkedString;
37
51
  /**
38
52
  * Values a translating client interpolates into its own wording for this code. Client-facing, so —
39
53
  * unlike `action` and `detail` — these cross the boundary with `message`. The `code` is the catalog
@@ -15,12 +15,24 @@ import { Locale } from "../i18n/locale";
15
15
  * stored choice outrank their device's `Accept-Language`. That link read `c.var.locale` at first,
16
16
  * which nothing ever wrote, so it silently contributed nothing and the chain degraded to
17
17
  * `param → cookie → header → default` while the docs said otherwise.
18
+ *
19
+ * **`scopes` answers one question: did this request come from the adopter's own trusted backend, or from a
20
+ * device?** The name reads like a grant to the user, and it is not one. Tenancy roles went to `c.var.acting`
21
+ * with `@pithy-sh/organization`; management access is the control plane's, with its own credential and its
22
+ * own scope type. What is left is an attestation about the caller, which `@pithy-sh/auth`'s `resolveScopes`
23
+ * makes on every request from something the request proves — a service binding, a verified client
24
+ * certificate, a short-lived token bound to this origin — and never from anything it merely says (#560).
18
25
  */
19
26
  export const AuthContext = z
20
27
  .object({
21
28
  userId: z.string().describe("ID of the authenticated user (populated by @pithy-sh/auth)."),
22
29
  sessionId: z.string().describe("ID of the active session this request belongs to."),
23
- scopes: z.array(z.string()).default([]).describe("Permission scopes granted to this session."),
30
+ scopes: z
31
+ .array(z.string())
32
+ .default([])
33
+ .describe(
34
+ "Attestations about the caller, not permissions granted to the user: what this request's caller was established to be — the adopter's own trusted backend, say — by the `resolveScopes` seam on `@pithy-sh/auth`, resolved per request and never cached. Empty when no resolver is configured. Tenancy roles are not here; they are `c.var.acting`.",
35
+ ),
24
36
  locale: Locale.nullish().describe(
25
37
  "The reader's own stored language, from `pithy_auth_users.locale`; null or absent when they have never chosen.",
26
38
  ),
@@ -0,0 +1,34 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import { Hono } from "hono";
5
+ import type { PithyHonoEnv, ScopeGate } from "../capability/capability";
6
+ import { routeSpecOf } from "./routeSpec";
7
+
8
+ /**
9
+ * **A capability's scope gates, read off its own routes rather than listed beside them (#560).**
10
+ *
11
+ * `Capability.scopeGates` is what `pithy doctor` names when nothing composed fills `AuthContext.scopes`.
12
+ * A list written out by hand next to the routes is a second copy of something the routes already say: every
13
+ * gated route carries `describeRoute({ scopes })`, because that is how the OpenAPI contract learns it. So the
14
+ * registrar is run against a throwaway app and the specs are read back — the same walk `openApiDocument`
15
+ * makes over a composed app, and a route whose scope changes changes what this returns with it.
16
+ *
17
+ * **Only `bearer` and `session` routes.** Those are the strategies `requireAuth()` takes, so a scope on one
18
+ * is an `AuthContext` scope. A `control-plane` route also names scopes, and they are a different credential
19
+ * entirely — a signed management token with its own type, never `c.var.auth` — so naming one here would tell
20
+ * an adopter to configure a resolver that could never open it.
21
+ */
22
+ export function scopeGatesOf(register: (app: Hono<PithyHonoEnv>) => void): ScopeGate[] {
23
+ const app = new Hono<PithyHonoEnv>();
24
+ register(app);
25
+ const gates: ScopeGate[] = [];
26
+ for (const route of app.routes) {
27
+ const spec = routeSpecOf(route.handler);
28
+ if (!spec?.scopes || spec.scopes.length === 0) continue;
29
+ const verification = [spec.verification].flat();
30
+ if (!verification.some((strategy) => strategy === "bearer" || strategy === "session")) continue;
31
+ for (const scope of spec.scopes) gates.push({ scope, route: `${route.method} ${route.path}` });
32
+ }
33
+ return gates;
34
+ }
package/src/index.ts CHANGED
@@ -101,6 +101,8 @@ export {
101
101
  ControlPlaneScope,
102
102
  KEYS_ROTATE_SCOPE,
103
103
  MANIFEST_READ_SCOPE,
104
+ MIGRATIONS_READ_SCOPE,
105
+ PRIVACY_READ_SCOPE,
104
106
  SEAM_SCOPES,
105
107
  } from "./controlPlane/scope/scope";
106
108
  export { exportPublicJwk, type MintControlPlaneToken, mintControlPlaneToken } from "./controlPlane/token/mint";
@@ -3,6 +3,7 @@
3
3
 
4
4
  import type { ErrorPayload } from "../error/payload";
5
5
  import { PithyError } from "../error/pithyError";
6
+ import type { Loggable, UnmarkedString } from "../privacy/types";
6
7
  import { LOG_LEVEL_ORDER, type LogFields, type LogLevel, type LogRecord } from "./record";
7
8
 
8
9
  /**
@@ -15,22 +16,28 @@ import { LOG_LEVEL_ORDER, type LogFields, type LogLevel, type LogRecord } from "
15
16
  * The reserved `error` field on any call may carry a `PithyError` or `ErrorPayload` — the engine lifts
16
17
  * it into the record's `error`, `detail` and all. A log is internal; this is the inverse of the HTTP
17
18
  * codec, which strips `detail`. The logger must never be wired to a client-facing surface.
19
+ *
20
+ * **A value marked with a privacy class does not compile here** (#132). Every method takes `F & Loggable<F>`,
21
+ * so a field holding a `pii()`/`phi()`/`secret()`/`pci()` value — the value itself, or one inside an object or
22
+ * an array, to the depth `privacy/types.ts` states — is refused at that key, and `redact(Schema, value)` is the
23
+ * way through. The message and a child's name take `UnmarkedString`, so `log.info(user.email)` is refused too. `LogFields` the schema stays permissive: a record that reaches a sink at runtime still parses,
24
+ * because a brand is a type and a parsed log line has none.
18
25
  */
19
26
  export interface Logger {
20
27
  /** Dev-time diagnostics. Dropped unless the logger's level is `debug`. */
21
- debug(msg: string, fields?: LogFields): void;
28
+ debug<F extends LogFields = LogFields>(msg: UnmarkedString, fields?: F & Loggable<F>): void;
22
29
  /** Normal operational events. */
23
- info(msg: string, fields?: LogFields): void;
30
+ info<F extends LogFields = LogFields>(msg: UnmarkedString, fields?: F & Loggable<F>): void;
24
31
  /** A recoverable problem worth attention. */
25
- warn(msg: string, fields?: LogFields): void;
32
+ warn<F extends LogFields = LogFields>(msg: UnmarkedString, fields?: F & Loggable<F>): void;
26
33
  /** A failure that was observed. Pass `{ error }` (a PithyError) to carry its full payload. */
27
- error(msg: string, fields?: LogFields): void;
34
+ error<F extends LogFields = LogFields>(msg: UnmarkedString, fields?: F & Loggable<F>): void;
28
35
  /**
29
36
  * A namespaced sub-logger. `name` composes as `parent:child`; bound `fields` merge into every record
30
37
  * the child emits. Omit `name` (pass `undefined`) to bind fields only — how request-correlation
31
38
  * attaches `request`/`method`/`path` without renaming the logger.
32
39
  */
33
- child(name?: string, fields?: LogFields): Logger;
40
+ child<F extends LogFields = LogFields>(name?: UnmarkedString, fields?: F & Loggable<F>): Logger;
34
41
  }
35
42
 
36
43
  /** A sink receives a finished {@link LogRecord}. Adapters supply one; the engine builds records and calls it. */