@pithy-sh/support 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/dist/ai/classify.d.ts +90 -0
  2. package/dist/ai/classify.d.ts.map +1 -0
  3. package/dist/ai/classify.js +151 -0
  4. package/dist/attachment/store.d.ts +53 -0
  5. package/dist/attachment/store.d.ts.map +1 -0
  6. package/dist/attachment/store.js +67 -0
  7. package/dist/audit/actions.d.ts +68 -0
  8. package/dist/audit/actions.d.ts.map +1 -0
  9. package/dist/audit/actions.js +68 -0
  10. package/dist/capability.d.ts +93 -0
  11. package/dist/capability.d.ts.map +1 -0
  12. package/dist/capability.js +187 -0
  13. package/dist/client/projection.d.ts +56 -0
  14. package/dist/client/projection.d.ts.map +1 -0
  15. package/dist/client/projection.js +0 -0
  16. package/dist/config/config.d.ts +215 -0
  17. package/dist/config/config.d.ts.map +1 -0
  18. package/dist/config/config.js +169 -0
  19. package/dist/data/attachment.d.ts +30 -0
  20. package/dist/data/attachment.d.ts.map +1 -0
  21. package/dist/data/attachment.js +32 -0
  22. package/dist/data/billingScope.d.ts +30 -0
  23. package/dist/data/billingScope.d.ts.map +1 -0
  24. package/dist/data/billingScope.js +32 -0
  25. package/dist/data/categories.d.ts +54 -0
  26. package/dist/data/categories.d.ts.map +1 -0
  27. package/dist/data/categories.js +97 -0
  28. package/dist/data/classification.d.ts +34 -0
  29. package/dist/data/classification.d.ts.map +1 -0
  30. package/dist/data/classification.js +28 -0
  31. package/dist/data/enums.d.ts +82 -0
  32. package/dist/data/enums.d.ts.map +1 -0
  33. package/dist/data/enums.js +70 -0
  34. package/dist/data/flag.d.ts +25 -0
  35. package/dist/data/flag.d.ts.map +1 -0
  36. package/dist/data/flag.js +27 -0
  37. package/dist/data/message.d.ts +81 -0
  38. package/dist/data/message.d.ts.map +1 -0
  39. package/dist/data/message.js +84 -0
  40. package/dist/data/tables.d.ts +160 -0
  41. package/dist/data/tables.d.ts.map +1 -0
  42. package/dist/data/tables.js +44 -0
  43. package/dist/data/thread.d.ts +77 -0
  44. package/dist/data/thread.d.ts.map +1 -0
  45. package/dist/data/thread.js +65 -0
  46. package/dist/error/errors.d.ts +64 -0
  47. package/dist/error/errors.d.ts.map +1 -0
  48. package/dist/error/errors.js +82 -0
  49. package/dist/http/guards.d.ts +44 -0
  50. package/dist/http/guards.d.ts.map +1 -0
  51. package/dist/http/guards.js +53 -0
  52. package/dist/http/handlers.d.ts +89 -0
  53. package/dist/http/handlers.d.ts.map +1 -0
  54. package/dist/http/handlers.js +248 -0
  55. package/dist/http/resolve.d.ts +44 -0
  56. package/dist/http/resolve.d.ts.map +1 -0
  57. package/dist/http/resolve.js +72 -0
  58. package/dist/http/responses.d.ts +829 -0
  59. package/dist/http/responses.d.ts.map +1 -0
  60. package/dist/http/responses.js +303 -0
  61. package/dist/http/routes.d.ts +109 -0
  62. package/dist/http/routes.d.ts.map +1 -0
  63. package/dist/http/routes.js +62 -0
  64. package/dist/http/schemas.d.ts +121 -0
  65. package/dist/http/schemas.d.ts.map +1 -0
  66. package/dist/http/schemas.js +89 -0
  67. package/dist/http/scopes.d.ts +56 -0
  68. package/dist/http/scopes.d.ts.map +1 -0
  69. package/dist/http/scopes.js +107 -0
  70. package/dist/http/views.d.ts +36 -0
  71. package/dist/http/views.d.ts.map +1 -0
  72. package/dist/http/views.js +144 -0
  73. package/dist/inbound/authenticity.d.ts +81 -0
  74. package/dist/inbound/authenticity.d.ts.map +1 -0
  75. package/dist/inbound/authenticity.js +61 -0
  76. package/dist/inbound/guard.d.ts +74 -0
  77. package/dist/inbound/guard.d.ts.map +1 -0
  78. package/dist/inbound/guard.js +78 -0
  79. package/dist/inbound/handler.d.ts +28 -0
  80. package/dist/inbound/handler.d.ts.map +1 -0
  81. package/dist/inbound/handler.js +82 -0
  82. package/dist/inbound/ingest.d.ts +104 -0
  83. package/dist/inbound/ingest.d.ts.map +1 -0
  84. package/dist/inbound/ingest.js +326 -0
  85. package/dist/inbound/recipient.d.ts +16 -0
  86. package/dist/inbound/recipient.d.ts.map +1 -0
  87. package/dist/inbound/recipient.js +46 -0
  88. package/dist/index.d.ts +20 -0
  89. package/dist/index.d.ts.map +1 -0
  90. package/dist/index.js +14 -0
  91. package/dist/link/sender.d.ts +160 -0
  92. package/dist/link/sender.d.ts.map +1 -0
  93. package/dist/link/sender.js +200 -0
  94. package/dist/migrations/0001_threads.d.ts +26 -0
  95. package/dist/migrations/0001_threads.d.ts.map +1 -0
  96. package/dist/migrations/0001_threads.js +103 -0
  97. package/dist/mime/address.d.ts +22 -0
  98. package/dist/mime/address.d.ts.map +1 -0
  99. package/dist/mime/address.js +28 -0
  100. package/dist/mime/parse.d.ts +96 -0
  101. package/dist/mime/parse.d.ts.map +1 -0
  102. package/dist/mime/parse.js +146 -0
  103. package/dist/mime/sanitize.d.ts +25 -0
  104. package/dist/mime/sanitize.d.ts.map +1 -0
  105. package/dist/mime/sanitize.js +212 -0
  106. package/dist/mime/threading.d.ts +79 -0
  107. package/dist/mime/threading.d.ts.map +1 -0
  108. package/dist/mime/threading.js +111 -0
  109. package/dist/mime/truncate.d.ts +17 -0
  110. package/dist/mime/truncate.d.ts.map +1 -0
  111. package/dist/mime/truncate.js +47 -0
  112. package/dist/provision/provisionSupport.d.ts +158 -0
  113. package/dist/provision/provisionSupport.d.ts.map +1 -0
  114. package/dist/provision/provisionSupport.js +110 -0
  115. package/dist/provision/resolveSupportConfig.d.ts +45 -0
  116. package/dist/provision/resolveSupportConfig.d.ts.map +1 -0
  117. package/dist/provision/resolveSupportConfig.js +22 -0
  118. package/dist/reply/send.d.ts +123 -0
  119. package/dist/reply/send.d.ts.map +1 -0
  120. package/dist/reply/send.js +153 -0
  121. package/dist/reply/snippets.d.ts +79 -0
  122. package/dist/reply/snippets.d.ts.map +1 -0
  123. package/dist/reply/snippets.js +135 -0
  124. package/dist/secret/registry.d.ts +18 -0
  125. package/dist/secret/registry.d.ts.map +1 -0
  126. package/dist/secret/registry.js +21 -0
  127. package/dist/seeds/example.d.ts +3 -0
  128. package/dist/seeds/example.d.ts.map +1 -0
  129. package/dist/seeds/example.js +350 -0
  130. package/dist/store/paging.d.ts +18 -0
  131. package/dist/store/paging.d.ts.map +1 -0
  132. package/dist/store/paging.js +20 -0
  133. package/dist/store/search.d.ts +67 -0
  134. package/dist/store/search.d.ts.map +1 -0
  135. package/dist/store/search.js +160 -0
  136. package/dist/store/searchIndex.d.ts +53 -0
  137. package/dist/store/searchIndex.d.ts.map +1 -0
  138. package/dist/store/searchIndex.js +66 -0
  139. package/dist/store/threads.d.ts +147 -0
  140. package/dist/store/threads.d.ts.map +1 -0
  141. package/dist/store/threads.js +197 -0
  142. package/dist/submission/encoding.d.ts +21 -0
  143. package/dist/submission/encoding.d.ts.map +1 -0
  144. package/dist/submission/encoding.js +55 -0
  145. package/dist/submission/guard.d.ts +71 -0
  146. package/dist/submission/guard.d.ts.map +1 -0
  147. package/dist/submission/guard.js +46 -0
  148. package/dist/submission/submit.d.ts +152 -0
  149. package/dist/submission/submit.d.ts.map +1 -0
  150. package/dist/submission/submit.js +319 -0
  151. package/dist/version.generated.d.ts +5 -0
  152. package/dist/version.generated.d.ts.map +1 -0
  153. package/dist/version.generated.js +7 -0
  154. package/dist/workflows/classify.d.ts +63 -0
  155. package/dist/workflows/classify.d.ts.map +1 -0
  156. package/dist/workflows/classify.js +68 -0
  157. package/dist/workflows/retryPolicy.d.ts +39 -0
  158. package/dist/workflows/retryPolicy.d.ts.map +1 -0
  159. package/dist/workflows/retryPolicy.js +43 -0
  160. package/dist/workflows/specs.d.ts +43 -0
  161. package/dist/workflows/specs.d.ts.map +1 -0
  162. package/dist/workflows/specs.js +46 -0
  163. package/dist/workflows/worker.d.ts +48 -0
  164. package/dist/workflows/worker.d.ts.map +1 -0
  165. package/dist/workflows/worker.js +37 -0
  166. package/package.json +24 -15
  167. package/src/version.generated.ts +1 -1
@@ -0,0 +1,187 @@
1
+ import { resolveReplies } from "./reply/snippets.js";
2
+ import { SupportConfig, supportNeedsBucket } from "./config/config.js";
3
+ import { resolveCategories } from "./data/categories.js";
4
+ import { supportTables } from "./data/tables.js";
5
+ import { supportSecretsRegistry } from "./secret/registry.js";
6
+ import { supportWorkflows } from "./workflows/specs.js";
7
+ import { makeResolveDeps } from "./http/resolve.js";
8
+ import { supportAdminRoutes } from "./http/scopes.js";
9
+ import { registerSupportRoutes } from "./http/routes.js";
10
+ import { createSupportEmailHandler } from "./inbound/handler.js";
11
+ import { support_0001_threads } from "./migrations/0001_threads.js";
12
+ import { supportExampleSeed } from "./seeds/example.js";
13
+ import { PACKAGE_VERSION } from "./version.generated.js";
14
+ import { noopEmit } from "@pithy-sh/core/src/audit/recorder";
15
+ import { defineCapability } from "@pithy-sh/core/src/capability/capability";
16
+ import { workflowBindings } from "@pithy-sh/core/src/workflow/bindings";
17
+ import { isEmailCapability } from "@pithy-sh/email/src/capability";
18
+ //#region src/capability.ts
19
+ /**
20
+ * Sort order of the support migrations within the app database, relative to other capabilities.
21
+ * Unique per database; the registry composes keys like `1200_support_0001_threads`.
22
+ *
23
+ * Taken from `NEXT_FREE_ORDER` in `packages/cli/src/migrations/orders.test.ts` at implementation
24
+ * time, as the procedure in that file requires — never picked by grepping, which is how two pairs of
25
+ * capabilities once collided in a range that was 99% empty. Stable forever: renumbering would rename
26
+ * the composed keys, and Kysely would then read applied migrations as unapplied and re-run them.
27
+ */
28
+ const SUPPORT_MIGRATION_ORDER = 1200;
29
+ /**
30
+ * What a browser may know about this project's support — the `virtual:pithy/support` module.
31
+ *
32
+ * **One route on this capability is called by a browser**, and that is the whole reason this exists:
33
+ * `POST {basePath}/feedback`, with the two reads beside it. Everything else answers to a control-plane
34
+ * credential a management client holds, and a management client reads the manifest. So the list is
35
+ * `basePath` and the bounds the compose form must hold somebody to before it lets them press Send —
36
+ * a form that stops at 200 characters because the handler refuses at 200 characters is a better form
37
+ * than one that finds out afterwards.
38
+ *
39
+ * **`{ enabled: false }` when the submission channel is off**, and that is not the same statement as
40
+ * "support is not composed" only in a sentence nobody writes. `registerSupportRoutes` does not mount
41
+ * the feedback routes when `submission.enabled` is false — they answer 404, not 403 — so a browser
42
+ * then has nothing on this capability to call and no use for a path. A screen branches on `enabled`
43
+ * rather than guarding, exactly as it does for a payments catalog with nothing in it.
44
+ *
45
+ * **What deliberately does not cross, and why each one was considered.**
46
+ * - **The taxonomy.** A category's value is the instruction a model reads and it lands in the prompt
47
+ * verbatim — prompt input written for a classifier, not copy for a chooser. An adopter's UI wants
48
+ * its own words either way, and the descriptions are the closest thing here to authored internals.
49
+ * - **`inboundAddresses` and `reply.replyToAddress`.** The inbox's own mail addresses. A browser never
50
+ * writes to them, and publishing an address into every bundle is a favor to nobody but a spammer.
51
+ * - **`reply.snippets`.** Canned copy an operator picks and edits in a dashboard. Staff-side, and the
52
+ * dashboard reads it through the control-plane route that already serves it.
53
+ * - **`submission.maxPerAccountPerHour`.** A rate a client cannot pre-enforce honestly — the count
54
+ * lives in D1 and the server's refusal is the only truth about it — so projecting it would publish
55
+ * an abuse budget in exchange for a number no form can act on.
56
+ * - **`ai`, `guard`, `attachments`, `search`.** The model and its prompt budget, the mail path's size
57
+ * and rate bounds, what happens to mail attachments, and how search is indexed. Each describes the
58
+ * deployment or a surface a browser never touches. The mail path's `attachments` block in
59
+ * particular is *not* the submission's: `config.ts` says at length why the two are stated
60
+ * separately, and inheriting one for the other here would undo that in the one place it reaches a
61
+ * client.
62
+ *
63
+ * There is no secret in reach of this closure to begin with — the R2 credential bundle lives in the
64
+ * secrets store behind `supportSecretsRegistry` and is never in config — so what discipline covers is
65
+ * the list above. `capability.test.ts` locks the key set by hand and sweeps the serialized result.
66
+ *
67
+ * `null` for attachments that are off, never `undefined`: the projection is inlined into a bundle with
68
+ * `JSON.stringify`, which drops an undefined value and leaves the screen reading a key that is simply
69
+ * absent. Null is also what makes "render no file picker" one check rather than three absences.
70
+ *
71
+ * The return type is {@link SupportClientProjection} — **declared, not inferred**. `ClientProjection` is
72
+ * `{ enabled: boolean }` plus a JSON catchall, which accepts anything this function could return. The
73
+ * declared type is what makes a dropped field a compile error here rather than a browser's problem, and
74
+ * what stops the list above widening by accident when `SupportConfig` grows.
75
+ */
76
+ function clientProjection(config, basePath) {
77
+ const submission = config.submission;
78
+ if (!submission.enabled) return { enabled: false };
79
+ const attachments = submission.attachments;
80
+ return {
81
+ enabled: true,
82
+ basePath,
83
+ submission: {
84
+ maxSubjectChars: submission.maxSubjectChars,
85
+ maxBodyChars: submission.maxBodyChars,
86
+ attachments: attachments.enabled ? {
87
+ maxCount: attachments.maxCount,
88
+ maxBytes: attachments.maxBytes,
89
+ allowedContentTypes: [...attachments.allowedContentTypes]
90
+ } : null
91
+ }
92
+ };
93
+ }
94
+ /**
95
+ * The support capability — an inbound support inbox in the adopter's own D1, classified on their own
96
+ * Workers AI binding.
97
+ *
98
+ * It contributes five tables (and optionally a full-text index) to the app `DB`, an `email()` handler
99
+ * that claims the configured addresses out of the Worker's single inbound entry, the classification
100
+ * Workflow that handler dispatches to, and a control-plane admin surface for a dashboard to read and
101
+ * act on.
102
+ *
103
+ * **Nothing here is gated by tier.** Every route is reachable with a control-plane credential the
104
+ * adopter issued, whatever anyone pays; a hosted dashboard gates its own UI and nothing else.
105
+ * Crippling MIT code would be both wrong and futile.
106
+ *
107
+ * Attachments presign through `@pithy-sh/storage`'s `ObjectStore` against `SUPPORT_BUCKET` under a
108
+ * credential name support declares, so `secrets` must be composed. The admin routes need the
109
+ * `control-plane` seam; without it every one denies, which is the correct failure for an inbox
110
+ * holding other people's correspondence. `@pithy-sh/auth` and `@pithy-sh/payments` are reached by
111
+ * guarded dynamic import for the sender link, so both stay genuinely optional.
112
+ */
113
+ function support(options = {}) {
114
+ const { basePath, ...configInput } = options;
115
+ const resolved = SupportConfig.parse(configInput);
116
+ const categories = resolveCategories(resolved.categories);
117
+ const snippets = resolveReplies(resolved.reply.snippets);
118
+ const mountPath = basePath ?? "/support";
119
+ const wiring = {
120
+ config: resolved,
121
+ categories,
122
+ snippets,
123
+ enqueueEmail: void 0,
124
+ emit: noopEmit
125
+ };
126
+ const migrations = { "0001_threads": support_0001_threads };
127
+ const requiredBindings = [
128
+ {
129
+ type: "d1",
130
+ name: "DB"
131
+ },
132
+ ...supportNeedsBucket(resolved) ? [{
133
+ type: "r2",
134
+ name: "SUPPORT_BUCKET",
135
+ optional: true
136
+ }] : [],
137
+ ...workflowBindings(supportWorkflows)
138
+ ];
139
+ const capability = defineCapability({
140
+ name: "support",
141
+ version: PACKAGE_VERSION,
142
+ dependsOn: ["secrets"],
143
+ secretRegistry: supportSecretsRegistry,
144
+ workflows: supportWorkflows,
145
+ requiredBindings,
146
+ ciPermissions: ["email:routing"],
147
+ databases: { app: {
148
+ binding: "DB",
149
+ tables: supportTables,
150
+ migrationOrder: SUPPORT_MIGRATION_ORDER,
151
+ migrations
152
+ } },
153
+ /**
154
+ * Bind the optional email seam once, at startup.
155
+ *
156
+ * Deliberately not a hard `dependsOn`. Auth *requires* email — a magic link that cannot be sent
157
+ * is a broken sign-in — but a support inbox that receives, threads, and classifies without being
158
+ * able to answer is a coherent and useful thing, so a missing email capability degrades one
159
+ * route instead of refusing to boot.
160
+ */
161
+ compose: ({ capabilities }) => {
162
+ const email = capabilities.find(isEmailCapability);
163
+ wiring.enqueueEmail = email?.enqueue;
164
+ },
165
+ client: () => clientProjection(resolved, mountPath),
166
+ adminRoutes: supportAdminRoutes(mountPath),
167
+ routes: registerSupportRoutes({
168
+ basePath: mountPath,
169
+ submission: resolved.submission.enabled,
170
+ resolveDeps: makeResolveDeps(wiring)
171
+ }),
172
+ email: createSupportEmailHandler(wiring),
173
+ seeds: [supportExampleSeed]
174
+ });
175
+ return Object.assign(capability, {
176
+ supportConfig: resolved,
177
+ categories,
178
+ snippets,
179
+ basePath: mountPath
180
+ });
181
+ }
182
+ /** Whether a capability is the support capability — carries its resolved config and effective taxonomy. */
183
+ function isSupportCapability(capability) {
184
+ return capability.name === "support" && "supportConfig" in capability;
185
+ }
186
+ //#endregion
187
+ export { SUPPORT_MIGRATION_ORDER, isSupportCapability, support };
@@ -0,0 +1,56 @@
1
+ /**
2
+ * What a browser may know about this project's support inbox — the shape of `virtual:pithy/support`.
3
+ *
4
+ * **This declaration is the contract, and the projection is checked against it.** It is written here
5
+ * rather than inferred from the closure that builds it, and that is the whole point: an inferred type
6
+ * follows whatever the producer last happened to say, so a projection that dropped `maxBodyChars`, or
7
+ * that started passing the mail path's attachment bounds through because `SupportConfig` moved a field,
8
+ * would take the type with it and nothing would go red. Declared, the arrow is the thing that has to
9
+ * change — and every widening of what a browser sees is a decision made here, on purpose.
10
+ *
11
+ * The list is short on purpose and `clientProjection`'s own comment says at length what stays behind:
12
+ * the taxonomy, the inbound addresses, the reply snippets, the per-account rate, and the whole `ai`,
13
+ * `guard`, mail-`attachments` and `search` blocks.
14
+ *
15
+ * **This is the only statement of the shape.** `@pithy-sh/ui-react`'s `templates/client-env.d.ts` — the
16
+ * ambient declaration `pithy ui add react` copies into an adopter's Worker — is generated from this type
17
+ * by `@pithy-sh/vite`'s `clientEnvDeclaration.ts` (#398). The unions and the per-field doc comments below
18
+ * are emitted verbatim, so what is written here is what a screen author reads.
19
+ */
20
+ export type SupportClientProjection = {
21
+ /**
22
+ * Support is not composed, or is not serving the in-app submission routes — the only ones a
23
+ * browser calls. A screen branches rather than rendering a compose form nothing will accept.
24
+ */
25
+ enabled: false;
26
+ } | {
27
+ /** Support is composed AND serving the in-app submission routes. */
28
+ enabled: true;
29
+ /** Where the support routes mount, e.g. `/support`. `POST {basePath}/feedback` writes in. */
30
+ basePath: string;
31
+ /**
32
+ * What one submission may carry. Hold a compose form to these so the handler does not have to
33
+ * refuse after somebody pressed Send. The taxonomy is not here: a category's text is the
34
+ * instruction a classifier reads, not copy for a chooser.
35
+ */
36
+ submission: {
37
+ /** The longest subject accepted. It becomes the thread's name in the inbox. */
38
+ maxSubjectChars: number;
39
+ /** The longest report body accepted. */
40
+ maxBodyChars: number;
41
+ /**
42
+ * What an upload control may offer, or null when attachments are off and it renders none.
43
+ * Null rather than absent: the projection is inlined with `JSON.stringify`, which drops an
44
+ * undefined value and leaves a screen reading a key that is simply gone.
45
+ */
46
+ attachments: {
47
+ /** How many files one submission may carry. */
48
+ maxCount: number;
49
+ /** The largest single file, measured on the decoded bytes. */
50
+ maxBytes: number;
51
+ /** The exact MIME types accepted — an allowlist, and the `accept` a file input wants. */
52
+ allowedContentTypes: string[];
53
+ } | null;
54
+ };
55
+ };
56
+ //# sourceMappingURL=projection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projection.d.ts","sourceRoot":"","sources":["../../src/client/projection.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,MAAM,uBAAuB,GAC/B;IACE;;;OAGG;IACH,OAAO,EAAE,KAAK,CAAC;CAChB,GACD;IACE,oEAAoE;IACpE,OAAO,EAAE,IAAI,CAAC;IACd,6FAA6F;IAC7F,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,UAAU,EAAE;QACV,+EAA+E;QAC/E,eAAe,EAAE,MAAM,CAAC;QACxB,wCAAwC;QACxC,YAAY,EAAE,MAAM,CAAC;QACrB;;;;WAIG;QACH,WAAW,EAAE;YACX,+CAA+C;YAC/C,QAAQ,EAAE,MAAM,CAAC;YACjB,8DAA8D;YAC9D,QAAQ,EAAE,MAAM,CAAC;YACjB,yFAAyF;YACzF,mBAAmB,EAAE,MAAM,EAAE,CAAC;SAC/B,GAAG,IAAI,CAAC;KACV,CAAC;CACH,CAAC"}
File without changes
@@ -0,0 +1,215 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * The support capability's configuration — the thin, user-owned surface in `pithy.config.ts`.
4
+ *
5
+ * The one field with no sensible default is `inboundAddresses`, and the reason is worth stating
6
+ * where an adopter will read it: **Cloudflare Email Routing takes over a zone's MX.** An address on
7
+ * the apex would move the adopter's real mail off their existing provider, so the address is theirs
8
+ * to choose deliberately — almost always on a subdomain (`support@help.theirdomain.com`) — and this
9
+ * capability will not guess one. Until it is set the inbox is inert: every message is ignored, and
10
+ * the handler says so once in the log rather than quietly storing mail from an address nobody
11
+ * configured.
12
+ */
13
+ /**
14
+ * The default Workers AI model for classification.
15
+ *
16
+ * An instruct model rather than a classifier: the taxonomy is federated, so the valid label set is
17
+ * not known until an adopter composes the capability, and a fine-tuned classifier cannot be given new
18
+ * classes in a config file. Small on purpose — this runs once per inbound message on the adopter's
19
+ * own bill, and the judgment is "which of these eight sentences fits", not a reasoning task.
20
+ */
21
+ export declare const DEFAULT_CLASSIFY_MODEL = "@cf/meta/llama-3.1-8b-instruct";
22
+ /** How the inbound message is classified, and by what. */
23
+ export declare const SupportAiConfig: z.ZodObject<{
24
+ enabled: z.ZodDefault<z.ZodBoolean>;
25
+ model: z.ZodDefault<z.ZodString>;
26
+ maxChars: z.ZodDefault<z.ZodNumber>;
27
+ temperature: z.ZodDefault<z.ZodNumber>;
28
+ }, z.core.$strip>;
29
+ export type SupportAiConfig = z.output<typeof SupportAiConfig>;
30
+ /** What happens to files that arrive attached to a message. */
31
+ export declare const SupportAttachmentsConfig: z.ZodObject<{
32
+ enabled: z.ZodDefault<z.ZodBoolean>;
33
+ maxBytes: z.ZodDefault<z.ZodNumber>;
34
+ maxCount: z.ZodDefault<z.ZodNumber>;
35
+ retainRaw: z.ZodDefault<z.ZodBoolean>;
36
+ }, z.core.$strip>;
37
+ export type SupportAttachmentsConfig = z.output<typeof SupportAttachmentsConfig>;
38
+ /**
39
+ * The spam and volume guard.
40
+ *
41
+ * A public address is a public write endpoint into the adopter's D1, and that is the honest way to
42
+ * think about it. These bounds are what stop a mail flood from being a storage bill.
43
+ */
44
+ export declare const SupportGuardConfig: z.ZodObject<{
45
+ maxRawBytes: z.ZodDefault<z.ZodNumber>;
46
+ maxPerSenderPerHour: z.ZodDefault<z.ZodNumber>;
47
+ maxPerHour: z.ZodDefault<z.ZodNumber>;
48
+ trustAuthenticationResults: z.ZodDefault<z.ZodBoolean>;
49
+ authservId: z.ZodOptional<z.ZodString>;
50
+ archiveSpam: z.ZodDefault<z.ZodBoolean>;
51
+ }, z.core.$strip>;
52
+ export type SupportGuardConfig = z.output<typeof SupportGuardConfig>;
53
+ /**
54
+ * What a signed-in user may attach to an in-app submission.
55
+ *
56
+ * **Stated here rather than inherited from `attachments`, and that is the point.** The mail path's
57
+ * bounds were written for parts of a MIME message somebody sent to a public address: they cap size and
58
+ * count, and they say nothing at all about type, because there is no useful type restriction on mail —
59
+ * refusing a `.docx` a customer attached to a bug report would lose the report. A direct upload from a
60
+ * browser is a different surface with a different answer: the client is authenticated but untrusted,
61
+ * the useful payload is a screenshot, and an allowlist is both possible and worth having. Inheriting
62
+ * the email numbers would have meant a 10 MB any-type upload endpoint arriving as a side effect of a
63
+ * setting an adopter tuned for their inbox.
64
+ *
65
+ * Bytes are stored exactly as the mail path stores them — server-derived opaque key,
66
+ * `application/octet-stream` on the object whatever was declared — so nothing here re-opens the
67
+ * stored-XSS question `attachment/store.ts` already closed.
68
+ */
69
+ /**
70
+ * The hard ceiling on how many attachments one submission may declare, whatever
71
+ * `submission.attachments.maxCount` is set to.
72
+ *
73
+ * On the schema as well as in the handler because the two refuse at different moments and the earlier
74
+ * one is free: the validator rejects a thousand-element array before a handler runs, without reading
75
+ * the resolved config. The configured bound is what an adopter tunes; this is what is true regardless.
76
+ */
77
+ export declare const MAX_SUBMISSION_ATTACHMENTS = 10;
78
+ export declare const SupportSubmissionAttachmentsConfig: z.ZodObject<{
79
+ enabled: z.ZodDefault<z.ZodBoolean>;
80
+ maxBytes: z.ZodDefault<z.ZodNumber>;
81
+ maxCount: z.ZodDefault<z.ZodNumber>;
82
+ allowedContentTypes: z.ZodDefault<z.ZodArray<z.ZodString>>;
83
+ }, z.core.$strip>;
84
+ export type SupportSubmissionAttachmentsConfig = z.output<typeof SupportSubmissionAttachmentsConfig>;
85
+ /**
86
+ * The ceilings a configured submission bound may not exceed — the route's own schema is written to
87
+ * these, so a payload beyond one is refused by the validator before a handler runs.
88
+ *
89
+ * Two layers, and both earn their place. The **config** bound is the adopter's number and produces the
90
+ * message their user reads; the **schema** ceiling is what stops a megabyte of text reaching a handler
91
+ * at all, and it cannot be config-derived because a request schema is built once at module load while
92
+ * config is resolved per project. Bounding the config field by the same constant is what keeps them
93
+ * from disagreeing: a setting above the ceiling would be a limit the route silently refused to honor.
94
+ */
95
+ export declare const MAX_SUBMISSION_SUBJECT_CHARS = 500;
96
+ /** The hard ceiling on a submission body, whatever `submission.maxBodyChars` is set to. */
97
+ export declare const MAX_SUBMISSION_BODY_CHARS = 100000;
98
+ /**
99
+ * The in-app submission channel: a signed-in user of the adopter's own app opening a support thread
100
+ * without leaving it.
101
+ *
102
+ * **The hardest problem in the mail path does not exist here.** `inbound/authenticity.ts` spends two
103
+ * hundred lines earning a customer link from a `From:` header that anybody can write; a submission
104
+ * arrives on a request `requireAuth()` already proved, so the account is the identity rather than a
105
+ * guess about it. Everything below is therefore a bound on *volume and shape*, never on identity.
106
+ *
107
+ * Bounded separately from `guard` for the same reason it is counted separately: that block exists
108
+ * because a public address is a public write endpoint, and this surface is neither public nor
109
+ * anonymous. Abuse here is attributable to an account and revocable with it, which is a materially
110
+ * different threat model and deserves its own numbers rather than a share of somebody else's.
111
+ */
112
+ export declare const SupportSubmissionConfig: z.ZodObject<{
113
+ enabled: z.ZodDefault<z.ZodBoolean>;
114
+ maxSubjectChars: z.ZodDefault<z.ZodNumber>;
115
+ maxBodyChars: z.ZodDefault<z.ZodNumber>;
116
+ maxPerAccountPerHour: z.ZodDefault<z.ZodNumber>;
117
+ attachments: z.ZodPrefault<z.ZodObject<{
118
+ enabled: z.ZodDefault<z.ZodBoolean>;
119
+ maxBytes: z.ZodDefault<z.ZodNumber>;
120
+ maxCount: z.ZodDefault<z.ZodNumber>;
121
+ allowedContentTypes: z.ZodDefault<z.ZodArray<z.ZodString>>;
122
+ }, z.core.$strip>>;
123
+ }, z.core.$strip>;
124
+ export type SupportSubmissionConfig = z.output<typeof SupportSubmissionConfig>;
125
+ /** How a reply leaves the building. */
126
+ export declare const SupportReplyConfig: z.ZodObject<{
127
+ enabled: z.ZodDefault<z.ZodBoolean>;
128
+ replyToAddress: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
129
+ deliverInApp: z.ZodDefault<z.ZodBoolean>;
130
+ snippets: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
131
+ label: z.ZodString;
132
+ category: z.ZodOptional<z.ZodString>;
133
+ body: z.ZodString;
134
+ }, z.core.$strip>>>;
135
+ }, z.core.$strip>;
136
+ export type SupportReplyConfig = z.output<typeof SupportReplyConfig>;
137
+ /**
138
+ * How text search is answered.
139
+ *
140
+ * The one setting here has a cost an adopter must opt into knowingly, which is why it is a setting
141
+ * at all rather than a decision this package made for them.
142
+ */
143
+ export declare const SupportSearchConfig: z.ZodObject<{
144
+ fts: z.ZodDefault<z.ZodBoolean>;
145
+ }, z.core.$strip>;
146
+ export type SupportSearchConfig = z.output<typeof SupportSearchConfig>;
147
+ /** The full support configuration. */
148
+ export declare const SupportConfig: z.ZodObject<{
149
+ inboundAddresses: z.ZodDefault<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>>;
150
+ categories: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
151
+ ai: z.ZodPrefault<z.ZodObject<{
152
+ enabled: z.ZodDefault<z.ZodBoolean>;
153
+ model: z.ZodDefault<z.ZodString>;
154
+ maxChars: z.ZodDefault<z.ZodNumber>;
155
+ temperature: z.ZodDefault<z.ZodNumber>;
156
+ }, z.core.$strip>>;
157
+ attachments: z.ZodPrefault<z.ZodObject<{
158
+ enabled: z.ZodDefault<z.ZodBoolean>;
159
+ maxBytes: z.ZodDefault<z.ZodNumber>;
160
+ maxCount: z.ZodDefault<z.ZodNumber>;
161
+ retainRaw: z.ZodDefault<z.ZodBoolean>;
162
+ }, z.core.$strip>>;
163
+ guard: z.ZodPrefault<z.ZodObject<{
164
+ maxRawBytes: z.ZodDefault<z.ZodNumber>;
165
+ maxPerSenderPerHour: z.ZodDefault<z.ZodNumber>;
166
+ maxPerHour: z.ZodDefault<z.ZodNumber>;
167
+ trustAuthenticationResults: z.ZodDefault<z.ZodBoolean>;
168
+ authservId: z.ZodOptional<z.ZodString>;
169
+ archiveSpam: z.ZodDefault<z.ZodBoolean>;
170
+ }, z.core.$strip>>;
171
+ submission: z.ZodPrefault<z.ZodObject<{
172
+ enabled: z.ZodDefault<z.ZodBoolean>;
173
+ maxSubjectChars: z.ZodDefault<z.ZodNumber>;
174
+ maxBodyChars: z.ZodDefault<z.ZodNumber>;
175
+ maxPerAccountPerHour: z.ZodDefault<z.ZodNumber>;
176
+ attachments: z.ZodPrefault<z.ZodObject<{
177
+ enabled: z.ZodDefault<z.ZodBoolean>;
178
+ maxBytes: z.ZodDefault<z.ZodNumber>;
179
+ maxCount: z.ZodDefault<z.ZodNumber>;
180
+ allowedContentTypes: z.ZodDefault<z.ZodArray<z.ZodString>>;
181
+ }, z.core.$strip>>;
182
+ }, z.core.$strip>>;
183
+ reply: z.ZodPrefault<z.ZodObject<{
184
+ enabled: z.ZodDefault<z.ZodBoolean>;
185
+ replyToAddress: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string | undefined, string | undefined>>;
186
+ deliverInApp: z.ZodDefault<z.ZodBoolean>;
187
+ snippets: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
188
+ label: z.ZodString;
189
+ category: z.ZodOptional<z.ZodString>;
190
+ body: z.ZodString;
191
+ }, z.core.$strip>>>;
192
+ }, z.core.$strip>>;
193
+ search: z.ZodPrefault<z.ZodObject<{
194
+ fts: z.ZodDefault<z.ZodBoolean>;
195
+ }, z.core.$strip>>;
196
+ }, z.core.$strip>;
197
+ export type SupportConfig = z.output<typeof SupportConfig>;
198
+ export type SupportConfigInput = z.input<typeof SupportConfig>;
199
+ /**
200
+ * Would this configuration ever put a byte in `SUPPORT_BUCKET`?
201
+ *
202
+ * **The one place the question is answered, because three independent settings answer it and each has
203
+ * its own writer.** `attachments.enabled` gates stored mail attachments (`inbound/ingest.ts:200`),
204
+ * `attachments.retainRaw` gates the raw MIME copy (`inbound/ingest.ts:346`), and
205
+ * `submission.attachments.enabled` gates an in-app submission's files (`submission/submit.ts:268`).
206
+ * All three default `true`, so the ordinary project is unaffected by this predicate existing.
207
+ *
208
+ * Two callers, and they must agree: `capability.ts` declares the binding only when this is true, and
209
+ * `pithy support provision` creates the bucket only when this is true. They disagreed before #440 —
210
+ * the provisioner asked about `enabled` and `retainRaw` and never about submissions, so a project that
211
+ * wanted uploads but no mail attachments got a binding pointing at a bucket nothing had created, and
212
+ * every submitted file was dropped with a warning. One predicate is what makes that unrepresentable.
213
+ */
214
+ export declare function supportNeedsBucket(config: SupportConfig): boolean;
215
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config/config.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;;;;GAUG;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,mCAAmC,CAAC;AAEvE,0DAA0D;AAC1D,eAAO,MAAM,eAAe;;;;;iBA+BkF,CAAC;AAC/G,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,eAAe,CAAC,CAAC;AAE/D,+DAA+D;AAC/D,eAAO,MAAM,wBAAwB;;;;;iBA6BkF,CAAC;AACxH,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAEjF;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB;;;;;;;iBA8C8D,CAAC;AAC9F,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAErE;;;;;;;;;;;;;;;GAeG;AACH;;;;;;;GAOG;AACH,eAAO,MAAM,0BAA0B,KAAK,CAAC;AAE7C,eAAO,MAAM,kCAAkC;;;;;iBAkC5C,CAAC;AACJ,MAAM,MAAM,kCAAkC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,kCAAkC,CAAC,CAAC;AAErG;;;;;;;;;GASG;AACH,eAAO,MAAM,4BAA4B,MAAM,CAAC;AAChD,2FAA2F;AAC3F,eAAO,MAAM,yBAAyB,SAAU,CAAC;AAEjD;;;;;;;;;;;;;GAaG;AAEH,eAAO,MAAM,uBAAuB;;;;;;;;;;;iBAwCjC,CAAC;AACJ,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAE/E,uCAAuC;AACvC,eAAO,MAAM,kBAAkB;;;;;;;;;iBAmC5B,CAAC;AACJ,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAErE;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB;;iBAS8D,CAAC;AAC/F,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEvE,sCAAsC;AACtC,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiCvB,CAAC;AACJ,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,aAAa,CAAC,CAAC;AAC3D,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAE/D;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAEjE"}