@pithy-sh/support 0.5.1 → 0.6.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 (173) hide show
  1. package/README.md +8 -0
  2. package/dist/audit/actions.d.ts +9 -0
  3. package/dist/audit/actions.d.ts.map +1 -1
  4. package/dist/audit/actions.js +9 -0
  5. package/dist/capability.d.ts +2 -1
  6. package/dist/capability.d.ts.map +1 -1
  7. package/dist/capability.js +30 -6
  8. package/dist/config/config.d.ts +63 -2
  9. package/dist/config/config.d.ts.map +1 -1
  10. package/dist/config/config.js +63 -5
  11. package/dist/data/assignment.d.ts +43 -0
  12. package/dist/data/assignment.d.ts.map +1 -0
  13. package/dist/data/assignment.js +38 -0
  14. package/dist/data/embedding.d.ts +21 -0
  15. package/dist/data/embedding.d.ts.map +1 -0
  16. package/dist/data/embedding.js +23 -0
  17. package/dist/data/tables.d.ts +28 -0
  18. package/dist/data/tables.d.ts.map +1 -1
  19. package/dist/data/tables.js +10 -2
  20. package/dist/data/thread.d.ts +9 -4
  21. package/dist/data/thread.d.ts.map +1 -1
  22. package/dist/data/thread.js +9 -4
  23. package/dist/http/guards.d.ts +32 -14
  24. package/dist/http/guards.d.ts.map +1 -1
  25. package/dist/http/guards.js +44 -16
  26. package/dist/http/handlers.d.ts +35 -2
  27. package/dist/http/handlers.d.ts.map +1 -1
  28. package/dist/http/handlers.js +81 -8
  29. package/dist/http/resolve.d.ts +8 -0
  30. package/dist/http/resolve.d.ts.map +1 -1
  31. package/dist/http/resolve.js +20 -1
  32. package/dist/http/responses.d.ts +355 -0
  33. package/dist/http/responses.d.ts.map +1 -1
  34. package/dist/http/responses.js +64 -2
  35. package/dist/http/routes.d.ts +25 -11
  36. package/dist/http/routes.d.ts.map +1 -1
  37. package/dist/http/routes.js +34 -5
  38. package/dist/http/schemas.d.ts +67 -3
  39. package/dist/http/schemas.d.ts.map +1 -1
  40. package/dist/http/schemas.js +44 -5
  41. package/dist/http/scopes.d.ts +10 -5
  42. package/dist/http/scopes.d.ts.map +1 -1
  43. package/dist/http/scopes.js +42 -6
  44. package/dist/http/views.d.ts +9 -1
  45. package/dist/http/views.d.ts.map +1 -1
  46. package/dist/http/views.js +40 -1
  47. package/dist/inbound/ingest.d.ts +1 -1
  48. package/dist/inbound/ingest.d.ts.map +1 -1
  49. package/dist/inbound/ingest.js +3 -1
  50. package/dist/index.d.ts +5 -3
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +6 -4
  53. package/dist/migrations/0002_assignment.d.ts +20 -0
  54. package/dist/migrations/0002_assignment.d.ts.map +1 -0
  55. package/dist/migrations/0002_assignment.js +45 -0
  56. package/dist/migrations/0003_embeddings.d.ts +15 -0
  57. package/dist/migrations/0003_embeddings.d.ts.map +1 -0
  58. package/dist/migrations/0003_embeddings.js +23 -0
  59. package/dist/migrations/set.d.ts +13 -0
  60. package/dist/migrations/set.d.ts.map +1 -0
  61. package/dist/migrations/set.js +21 -0
  62. package/dist/presence/room.d.ts +35 -0
  63. package/dist/presence/room.d.ts.map +1 -0
  64. package/dist/presence/room.js +66 -0
  65. package/dist/provision/provisionSupport.d.ts +32 -0
  66. package/dist/provision/provisionSupport.d.ts.map +1 -1
  67. package/dist/provision/provisionSupport.js +10 -2
  68. package/dist/provision/resolveSupportConfig.d.ts.map +1 -1
  69. package/dist/provision/resolveSupportConfig.js +13 -2
  70. package/dist/purge/contribution.d.ts +49 -0
  71. package/dist/purge/contribution.d.ts.map +1 -0
  72. package/dist/purge/contribution.js +164 -0
  73. package/dist/secret/registry.d.ts +26 -2
  74. package/dist/secret/registry.d.ts.map +1 -1
  75. package/dist/secret/registry.js +15 -3
  76. package/dist/semantic/documents.d.ts +21 -0
  77. package/dist/semantic/documents.d.ts.map +1 -0
  78. package/dist/semantic/documents.js +128 -0
  79. package/dist/semantic/embed.d.ts +59 -0
  80. package/dist/semantic/embed.d.ts.map +1 -0
  81. package/dist/semantic/embed.js +59 -0
  82. package/dist/semantic/messageIndex.d.ts +46 -0
  83. package/dist/semantic/messageIndex.d.ts.map +1 -0
  84. package/dist/semantic/messageIndex.js +67 -0
  85. package/dist/semantic/query.d.ts +37 -0
  86. package/dist/semantic/query.d.ts.map +1 -0
  87. package/dist/semantic/query.js +46 -0
  88. package/dist/staff/staff.d.ts +22 -0
  89. package/dist/staff/staff.d.ts.map +1 -0
  90. package/dist/staff/staff.js +64 -0
  91. package/dist/store/assignment.d.ts +63 -0
  92. package/dist/store/assignment.d.ts.map +1 -0
  93. package/dist/store/assignment.js +86 -0
  94. package/dist/store/hybrid.d.ts +134 -0
  95. package/dist/store/hybrid.d.ts.map +1 -0
  96. package/dist/store/hybrid.js +185 -0
  97. package/dist/store/paging.d.ts +10 -0
  98. package/dist/store/paging.d.ts.map +1 -1
  99. package/dist/store/paging.js +11 -1
  100. package/dist/store/provisionIndex.d.ts +21 -0
  101. package/dist/store/provisionIndex.d.ts.map +1 -0
  102. package/dist/store/provisionIndex.js +36 -0
  103. package/dist/store/search.d.ts +18 -9
  104. package/dist/store/search.d.ts.map +1 -1
  105. package/dist/store/search.js +36 -34
  106. package/dist/store/searchIndex.d.ts +4 -4
  107. package/dist/store/searchIndex.js +4 -4
  108. package/dist/store/threads.d.ts +174 -8
  109. package/dist/store/threads.d.ts.map +1 -1
  110. package/dist/store/threads.js +33 -40
  111. package/dist/submission/submit.d.ts +1 -1
  112. package/dist/submission/submit.d.ts.map +1 -1
  113. package/dist/submission/submit.js +2 -1
  114. package/dist/test-utils/seed.d.ts +33 -0
  115. package/dist/test-utils/seed.d.ts.map +1 -0
  116. package/dist/test-utils/seed.js +102 -0
  117. package/dist/test-utils/semantic.d.ts +21 -0
  118. package/dist/test-utils/semantic.d.ts.map +1 -0
  119. package/dist/test-utils/semantic.js +107 -0
  120. package/dist/version.generated.d.ts +1 -1
  121. package/dist/version.generated.js +1 -1
  122. package/dist/workflows/specs.d.ts +23 -0
  123. package/dist/workflows/specs.d.ts.map +1 -1
  124. package/dist/workflows/specs.js +27 -7
  125. package/dist/workflows/worker.d.ts +15 -2
  126. package/dist/workflows/worker.d.ts.map +1 -1
  127. package/dist/workflows/worker.js +48 -4
  128. package/package.json +9 -7
  129. package/pithy.manifest.json +21 -3
  130. package/src/audit/actions.ts +9 -0
  131. package/src/capability.ts +43 -4
  132. package/src/config/config.ts +104 -5
  133. package/src/data/assignment.ts +55 -0
  134. package/src/data/embedding.ts +27 -0
  135. package/src/data/tables.ts +8 -0
  136. package/src/data/thread.ts +26 -4
  137. package/src/http/guards.ts +52 -15
  138. package/src/http/handlers.ts +125 -4
  139. package/src/http/resolve.ts +19 -0
  140. package/src/http/responses.ts +154 -0
  141. package/src/http/routes.ts +98 -12
  142. package/src/http/schemas.ts +117 -28
  143. package/src/http/scopes.ts +43 -6
  144. package/src/http/views.ts +44 -0
  145. package/src/inbound/ingest.ts +5 -2
  146. package/src/index.ts +5 -0
  147. package/src/migrations/0002_assignment.ts +65 -0
  148. package/src/migrations/0003_embeddings.ts +29 -0
  149. package/src/migrations/set.ts +21 -0
  150. package/src/presence/room.ts +94 -0
  151. package/src/provision/provisionSupport.ts +33 -1
  152. package/src/provision/resolveSupportConfig.ts +24 -13
  153. package/src/purge/contribution.ts +294 -0
  154. package/src/secret/registry.ts +17 -2
  155. package/src/semantic/documents.ts +178 -0
  156. package/src/semantic/embed.ts +99 -0
  157. package/src/semantic/messageIndex.ts +74 -0
  158. package/src/semantic/query.ts +66 -0
  159. package/src/staff/staff.ts +128 -0
  160. package/src/store/assignment.ts +179 -0
  161. package/src/store/hybrid.ts +314 -0
  162. package/src/store/paging.ts +14 -0
  163. package/src/store/provisionIndex.ts +46 -0
  164. package/src/store/search.ts +58 -37
  165. package/src/store/searchIndex.ts +4 -4
  166. package/src/store/threads.ts +91 -91
  167. package/src/submission/submit.ts +3 -2
  168. package/src/test-utils/seed.ts +141 -0
  169. package/src/test-utils/semantic.ts +110 -0
  170. package/src/version.generated.ts +1 -1
  171. package/src/workflows/specs.ts +34 -0
  172. package/src/workflows/worker.ts +84 -8
  173. package/src/workflows/wrangler.jsonc +8 -3
package/README.md CHANGED
@@ -10,8 +10,16 @@ pithy add support
10
10
 
11
11
  **Documentation: [pithy.sh/docs/capabilities/support](https://pithy.sh/docs/capabilities/support).** Overview, adding it, using it, and the reference: the MX constraint, provisioning, classification, provenance.
12
12
 
13
+ **A shared inbox.** Assign a conversation to somebody, filter the inbox by who has it, and see who else has a thread open before you reply: `POST /support/threads/:id/assign` behind `support:threads:assign`, `assignee=<ref>` or `assignee=none` on the listing, every handover kept in the thread's history, and one `support.thread` presence room per thread, with `viewing` and `replying`. Your own signed-in staff reach both through `support({ staff: { readPower, assignPower } })`, on unassignable roles other than the one ownership transfer confers, so only an operator can confer them. The seam is [`docs/CONTROL-PLANE.md`](../../docs/CONTROL-PLANE.md).
14
+
15
+ **Search, by words and by meaning.** `GET /support/threads/search` takes `q`, a `mode` (`keyword`, `semantic` or `hybrid`), a `limit` (20, at most 50) and the inbox list's filters, and returns ranked threads, each named by its best-matching message with that message's raw `bm25` and similarity. Keyword is FTS5 with `search.fts` on and the `LIKE` scan otherwise; hybrid fuses it with semantic search by rank, so a paraphrase is found and an order number still comes first. The inbox list no longer takes `q`. **Semantic search costs a second inference per inbound message on your own Workers AI binding**, on top of classification — roughly double the per-message AI cost — so `search: { semantic: true }` is off until you turn it on. `pithy support provision` creates the Vectorize index and backfills it once, and `pithy vector reprocess --index support-messages` re-embeds after a model change, with no `vector()` composed. Vectors carry message ids only. See [`docs/commands/support.md`](../../docs/commands/support.md#search).
16
+
13
17
  _Everything else is on the site. `pithy.sh/docs` is canonical — new prose goes there, not here._
14
18
 
19
+ ## A person's data
20
+
21
+ **Erased.** When an account is purged, support deletes every thread linked to the account, or sent from its address and linked to no other account, with every message, attachment and raw mail in R2, classification, read state and assignment history, and each message's vector in the semantic index with its model stamp, and an export hands the threads, messages, attachments and assignment history back first. The export leaves out who made each assignment: that is the operator's, and the audit trail keeps it with its actor. A staff member's purge takes their id off every handover they made as a signed-in user on someone else's thread, and their export lists those handovers by thread and time, without the assignee or the note. Who held a thread is kept until the customer's thread goes: an assignee is an opaque reference and a name the assigner chose, and support cannot tell which reference is a person. See [`@pithy-sh/auth`'s account lifecycle](../auth/docs/account-lifecycle.md#what-each-capability-does-with-a-persons-rows).
22
+
15
23
  ## License
16
24
 
17
25
  MIT.
@@ -29,6 +29,15 @@ export declare const SupportAuditActions: {
29
29
  readonly threadArchived: "support/thread_archived";
30
30
  /** A thread was taken back out of the archive. Recorded separately, because undoing is its own decision. */
31
31
  readonly threadUnarchived: "support/thread_unarchived";
32
+ /**
33
+ * A thread was given to somebody, or handed from one person to another (#816). Audited because who has
34
+ * a customer's conversation decides who answers them, and the thread keeps only the current answer.
35
+ * Metadata: the previous assignee, the new one, and the assigner's note — staff words, never mail.
36
+ * Reassigning the current assignee changes nothing and emits nothing.
37
+ */
38
+ readonly threadAssigned: "support/thread_assigned";
39
+ /** A thread's assignee was cleared. Its own action, because giving a conversation up is its own decision. */
40
+ readonly threadUnassigned: "support/thread_unassigned";
32
41
  /**
33
42
  * A reply went out. Audited because it leaves under the adopter's domain and their DKIM: to the
34
43
  * recipient it is indistinguishable from the founder writing it, so who actually sent it is a
@@ -1 +1 @@
1
- {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../src/audit/actions.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,mBAAmB;IAC9B;;;;OAIG;aACH,cAAc,EAAE,yBAAyB;IACzC,4GAA4G;aAC5G,gBAAgB,EAAE,2BAA2B;IAC7C;;;;OAIG;aACH,SAAS,EAAE,oBAAoB;IAC/B;;;OAGG;aACH,kBAAkB,EAAE,6BAA6B;IACjD;;;;;;;OAOG;aACH,eAAe,EAAE,0BAA0B;IAC3C;;;;;;;;;;;;;OAaG;aACH,kBAAkB,EAAE,6BAA6B;CACzC,CAAC;AAEX,6CAA6C;AAC7C,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,OAAO,mBAAmB,CAAC,CAAC"}
1
+ {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../src/audit/actions.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,mBAAmB;IAC9B;;;;OAIG;aACH,cAAc,EAAE,yBAAyB;IACzC,4GAA4G;aAC5G,gBAAgB,EAAE,2BAA2B;IAC7C;;;;;OAKG;aACH,cAAc,EAAE,yBAAyB;IACzC,6GAA6G;aAC7G,gBAAgB,EAAE,2BAA2B;IAC7C;;;;OAIG;aACH,SAAS,EAAE,oBAAoB;IAC/B;;;OAGG;aACH,kBAAkB,EAAE,6BAA6B;IACjD;;;;;;;OAOG;aACH,eAAe,EAAE,0BAA0B;IAC3C;;;;;;;;;;;;;OAaG;aACH,kBAAkB,EAAE,6BAA6B;CACzC,CAAC;AAEX,6CAA6C;AAC7C,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,OAAO,mBAAmB,CAAC,CAAC"}
@@ -31,6 +31,15 @@ const SupportAuditActions = {
31
31
  /** A thread was taken back out of the archive. Recorded separately, because undoing is its own decision. */
32
32
  threadUnarchived: "support/thread_unarchived",
33
33
  /**
34
+ * A thread was given to somebody, or handed from one person to another (#816). Audited because who has
35
+ * a customer's conversation decides who answers them, and the thread keeps only the current answer.
36
+ * Metadata: the previous assignee, the new one, and the assigner's note — staff words, never mail.
37
+ * Reassigning the current assignee changes nothing and emits nothing.
38
+ */
39
+ threadAssigned: "support/thread_assigned",
40
+ /** A thread's assignee was cleared. Its own action, because giving a conversation up is its own decision. */
41
+ threadUnassigned: "support/thread_unassigned",
42
+ /**
34
43
  * A reply went out. Audited because it leaves under the adopter's domain and their DKIM: to the
35
44
  * recipient it is indistinguishable from the founder writing it, so who actually sent it is a
36
45
  * security-relevant fact with no other record.
@@ -80,7 +80,8 @@ export interface SupportCapability extends Capability<DatabaseSpecMap, KvNamespa
80
80
  * The support capability — an inbound support inbox in the adopter's own D1, classified on their own
81
81
  * Workers AI binding.
82
82
  *
83
- * It contributes five tables (and optionally a full-text index) to the app `DB`, an `email()` handler
83
+ * It contributes its tables (and optionally a full-text index, and a semantic message index in Vectorize) to the
84
+ * app `DB`, an `email()` handler
84
85
  * that claims the configured addresses out of the Worker's single inbound entry, the classification
85
86
  * Workflow that handler dispatches to, and a control-plane admin surface for a dashboard to read and
86
87
  * act on.
@@ -1 +1 @@
1
- {"version":3,"file":"capability.d.ts","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAC;AAGnE,OAAO,EAAE,KAAK,UAAU,EAAoB,MAAM,0CAA0C,CAAC;AAC7F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AAE3E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAItE,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,kBAAkB,EAGxB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAqB,KAAK,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAO9E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD,OAAO,EAAkB,KAAK,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAI7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAErD;;;;;;;;GAQG;AACH,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAE5C,+FAA+F;AAC/F,MAAM,MAAM,cAAc,GAAG,kBAAkB,GAAG;IAChD,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,2BAA2B;IAC3B,MAAM,EAAE,aAAa,CAAC;IACtB,8BAA8B;IAC9B,UAAU,EAAE,iBAAiB,CAAC;IAC9B,0CAA0C;IAC1C,QAAQ,EAAE,oBAAoB,CAAC;IAC/B;;;;;OAKG;IACH,YAAY,EAAE,eAAe,CAAC,SAAS,CAAC,GAAG,SAAS,CAAC;IACrD;;;OAGG;IACH,KAAK,EAAE,WAAW,CAAC;IACnB;;;;;;;;OAQG;IACH,IAAI,EAAE,SAAS,CAAC;CACjB;AA4ED;;;;;GAKG;AACH,MAAM,WAAW,iBACf,SAAQ,UAAU,CAAC,eAAe,EAAE,kBAAkB,EAAE,SAAS,EAAE,OAAO,gBAAgB,CAAC;IAC3F,mCAAmC;IACnC,aAAa,EAAE,aAAa,CAAC;IAC7B,wGAAwG;IACxG,UAAU,EAAE,iBAAiB,CAAC;IAC9B,0CAA0C;IAC1C,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,iFAAiF;IACjF,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,OAAO,CAAC,OAAO,GAAE,cAAmB,GAAG,iBAAiB,CA0GvE;AAED,2GAA2G;AAC3G,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,UAAU,GAAG,UAAU,IAAI,iBAAiB,CAE3F"}
1
+ {"version":3,"file":"capability.d.ts","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAC;AAGnE,OAAO,EAAE,KAAK,UAAU,EAAoB,MAAM,0CAA0C,CAAC;AAC7F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AAE3E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAItE,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,kBAAkB,EAGxB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAqB,KAAK,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAO9E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAIjD,OAAO,EAAkB,KAAK,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAW7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAErD;;;;;;;;GAQG;AACH,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAE5C,+FAA+F;AAC/F,MAAM,MAAM,cAAc,GAAG,kBAAkB,GAAG;IAChD,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,2BAA2B;IAC3B,MAAM,EAAE,aAAa,CAAC;IACtB,8BAA8B;IAC9B,UAAU,EAAE,iBAAiB,CAAC;IAC9B,0CAA0C;IAC1C,QAAQ,EAAE,oBAAoB,CAAC;IAC/B;;;;;OAKG;IACH,YAAY,EAAE,eAAe,CAAC,SAAS,CAAC,GAAG,SAAS,CAAC;IACrD;;;OAGG;IACH,KAAK,EAAE,WAAW,CAAC;IACnB;;;;;;;;OAQG;IACH,IAAI,EAAE,SAAS,CAAC;CACjB;AA4ED;;;;;GAKG;AACH,MAAM,WAAW,iBACf,SAAQ,UAAU,CAAC,eAAe,EAAE,kBAAkB,EAAE,SAAS,EAAE,OAAO,gBAAgB,CAAC;IAC3F,mCAAmC;IACnC,aAAa,EAAE,aAAa,CAAC;IAC7B,wGAAwG;IACxG,UAAU,EAAE,iBAAiB,CAAC;IAC9B,0CAA0C;IAC1C,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,iFAAiF;IACjF,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,OAAO,CAAC,OAAO,GAAE,cAAmB,GAAG,iBAAiB,CAuIvE;AAED,2GAA2G;AAC3G,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,UAAU,GAAG,UAAU,IAAI,iBAAiB,CAE3F"}
@@ -5,13 +5,17 @@ import { SupportConfig, supportNeedsBucket } from "./config/config.js";
5
5
  import { resolveCategories } from "./data/categories.js";
6
6
  import { supportTables } from "./data/tables.js";
7
7
  import { SUPPORT_BUCKET_BINDING, inapplicableAttachmentSecrets, supportSecretsRegistry } from "./secret/registry.js";
8
+ import { SUPPORT_MESSAGES_INDEX, SUPPORT_VECTORIZE_BINDING, supportMessageIndex } from "./semantic/messageIndex.js";
8
9
  import { supportWorkflows } from "./workflows/specs.js";
9
10
  import { makeResolveDeps } from "./http/resolve.js";
11
+ import { assertStaffComposition } from "./staff/staff.js";
10
12
  import { supportAdminRoutes } from "./http/scopes.js";
11
13
  import { registerSupportRoutes } from "./http/routes.js";
12
14
  import { createSupportEmailHandler } from "./inbound/handler.js";
13
15
  import { senderPeers } from "./link/peers.js";
14
- import { support_0001_threads } from "./migrations/0001_threads.js";
16
+ import { SUPPORT_MIGRATIONS } from "./migrations/set.js";
17
+ import { supportPresence } from "./presence/room.js";
18
+ import { supportUserPurge } from "./purge/contribution.js";
15
19
  import { supportExampleSeed } from "./seeds/example.js";
16
20
  import { PACKAGE_NAME, PACKAGE_VERSION } from "./version.generated.js";
17
21
  import { noopEmit } from "@pithy-sh/core/src/audit/recorder";
@@ -98,7 +102,8 @@ function clientProjection(config, basePath) {
98
102
  * The support capability — an inbound support inbox in the adopter's own D1, classified on their own
99
103
  * Workers AI binding.
100
104
  *
101
- * It contributes five tables (and optionally a full-text index) to the app `DB`, an `email()` handler
105
+ * It contributes its tables (and optionally a full-text index, and a semantic message index in Vectorize) to the
106
+ * app `DB`, an `email()` handler
102
107
  * that claims the configured addresses out of the Worker's single inbound entry, the classification
103
108
  * Workflow that handler dispatches to, and a control-plane admin surface for a dashboard to read and
104
109
  * act on.
@@ -127,7 +132,7 @@ function support(options = {}) {
127
132
  peers: {},
128
133
  emit: noopEmit
129
134
  };
130
- const migrations = { "0001_threads": support_0001_threads };
135
+ const migrations = SUPPORT_MIGRATIONS;
131
136
  const requiredBindings = [
132
137
  {
133
138
  type: "d1",
@@ -138,7 +143,17 @@ function support(options = {}) {
138
143
  name: SUPPORT_BUCKET_BINDING,
139
144
  optional: true
140
145
  }] : [],
141
- ...workflowBindings(supportWorkflows)
146
+ ...workflowBindings(supportWorkflows),
147
+ ...supportMessageIndex(resolved.search) ? [{
148
+ type: "vectorize",
149
+ name: SUPPORT_VECTORIZE_BINDING,
150
+ optional: true,
151
+ remote: true
152
+ }, {
153
+ type: "ai",
154
+ name: "AI",
155
+ remote: true
156
+ }] : []
142
157
  ];
143
158
  const capability = defineCapability({
144
159
  name: "support",
@@ -164,20 +179,29 @@ function support(options = {}) {
164
179
  * able to answer is a coherent and useful thing, so a missing email capability degrades one
165
180
  * route instead of refusing to boot.
166
181
  */
182
+ ...resolved.staff ? { powers: [resolved.staff.readPower, resolved.staff.assignPower] } : {},
167
183
  compose: ({ capabilities }) => {
184
+ assertStaffComposition(resolved.staff, capabilities);
168
185
  const email = capabilities.find(isEmailCapability);
169
186
  wiring.enqueueEmail = email?.enqueue;
170
187
  wiring.peers = senderPeers(capabilities, resolved);
171
188
  },
172
189
  client: () => clientProjection(resolved, mountPath),
173
190
  adminRoutes: supportAdminRoutes(mountPath),
191
+ ...supportMessageIndex(resolved.search) ? { vectorIndexes: [{
192
+ name: SUPPORT_MESSAGES_INDEX,
193
+ reprocessJob: "reprocess"
194
+ }] } : {},
195
+ presence: supportPresence(mountPath, wiring),
174
196
  routes: registerSupportRoutes({
175
197
  basePath: mountPath,
176
198
  submission: resolved.submission.enabled,
177
- resolveDeps: makeResolveDeps(wiring)
199
+ resolveDeps: makeResolveDeps(wiring),
200
+ staff: resolved.staff
178
201
  }),
179
202
  email: createSupportEmailHandler(wiring),
180
- seeds: [supportExampleSeed]
203
+ seeds: [supportExampleSeed],
204
+ purge: [supportUserPurge("DB", { semantic: supportMessageIndex(resolved.search) !== void 0 })]
181
205
  });
182
206
  return Object.assign(capability, {
183
207
  supportConfig: resolved,
@@ -136,17 +136,70 @@ export declare const SupportReplyConfig: z.ZodObject<{
136
136
  }, z.core.$strip>>>;
137
137
  }, z.core.$strip>;
138
138
  export type SupportReplyConfig = z.output<typeof SupportReplyConfig>;
139
+ /**
140
+ * The default embedding model for semantic search — the kit's own default (`DEFAULT_EMBEDDING_MODEL` in
141
+ * `@pithy-sh/cloudflare/src/ai/aiManager.ts`), restated rather than imported because a Worker cannot reach
142
+ * the REST client package and should not.
143
+ *
144
+ * **Multilingual, and that is why.** An inbox takes mail in whatever language the customer writes, and an
145
+ * English-only model would embed a Spanish complaint into the wrong neighborhood with no error.
146
+ */
147
+ export declare const DEFAULT_EMBEDDING_MODEL = "@cf/baai/bge-m3";
148
+ /**
149
+ * How many components `bge-m3` produces. It is not a Vectorize preset, so the index states its own shape;
150
+ * a model override that produces another width states its `dimensions` beside it.
151
+ */
152
+ export declare const DEFAULT_EMBEDDING_DIMENSIONS = 1024;
153
+ /** The `{ model }` form of `search.semantic`: semantic search on, with a model of your choosing. */
154
+ export declare const SupportSemanticConfig: z.ZodObject<{
155
+ model: z.ZodDefault<z.ZodString>;
156
+ dimensions: z.ZodDefault<z.ZodNumber>;
157
+ }, z.core.$strip>;
158
+ export type SupportSemanticConfig = z.output<typeof SupportSemanticConfig>;
139
159
  /**
140
160
  * How text search is answered.
141
161
  *
142
- * The one setting here has a cost an adopter must opt into knowingly, which is why it is a setting
143
- * at all rather than a decision this package made for them.
162
+ * FTS5 by default, with the `LIKE` scan kept for a project that wants no virtual table at all. Semantic
163
+ * search is off by default because it costs a second inference per inbound message.
144
164
  */
145
165
  export declare const SupportSearchConfig: z.ZodObject<{
166
+ semantic: z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
167
+ model: z.ZodDefault<z.ZodString>;
168
+ dimensions: z.ZodDefault<z.ZodNumber>;
169
+ }, z.core.$strip>]>>;
146
170
  fts: z.ZodDefault<z.ZodBoolean>;
147
171
  }, z.core.$strip>;
148
172
  export type SupportSearchConfig = z.output<typeof SupportSearchConfig>;
173
+ /**
174
+ * The semantic model and width this configuration asks for, or `undefined` when semantic search is off.
175
+ *
176
+ * The one reading of the three-shaped flag — `false`, `true`, `{ model }` — so the capability's bindings,
177
+ * the search route, the classification Workflow and provisioning cannot each decide what `true` means.
178
+ */
179
+ export declare function supportSemantic(search: SupportSearchConfig): SupportSemanticConfig | undefined;
149
180
  /** The full support configuration. */
181
+ /**
182
+ * **Who of the app's own signed-in users works the inbox (#816).** Off until named.
183
+ *
184
+ * The inbox holds every customer's correspondence, so a signed-in user reaches it only through a power the
185
+ * adopter names in their own role catalog, held in the organization the request acts for — the same seam
186
+ * `media:manage` and `storage:manage` ask, and the same rule `artifacts({ managePower })` follows: a
187
+ * capability never reserves a power name in a catalog that is the adopter's.
188
+ *
189
+ * **Both powers must sit only on roles no organization can hand itself** — listed in
190
+ * `defineRoles({ unassignable })`, and not the role `organization({ ownership })` confers — and `support()`
191
+ * refuses the composition otherwise. A role is the catalog's, not one organization's, and an organization's
192
+ * own administrator may give any assignable role to their own members — so a support power on an assignable
193
+ * role would let any customer's administrator make themselves staff and read every other customer's mail.
194
+ * The ownership role is unassignable and still self-served: anybody may found an organization, nominate
195
+ * themselves its owner, and accept. On any other unassignable role it is conferred by an operator alone,
196
+ * through the control plane.
197
+ */
198
+ export declare const SupportStaffConfig: z.ZodObject<{
199
+ readPower: z.ZodString;
200
+ assignPower: z.ZodString;
201
+ }, z.core.$strip>;
202
+ export type SupportStaffConfig = z.output<typeof SupportStaffConfig>;
150
203
  export declare const SupportConfig: z.ZodObject<{
151
204
  inboundAddresses: z.ZodDefault<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>>;
152
205
  categories: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
@@ -193,8 +246,16 @@ export declare const SupportConfig: z.ZodObject<{
193
246
  }, z.core.$strip>>>;
194
247
  }, z.core.$strip>>;
195
248
  search: z.ZodPrefault<z.ZodObject<{
249
+ semantic: z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
250
+ model: z.ZodDefault<z.ZodString>;
251
+ dimensions: z.ZodDefault<z.ZodNumber>;
252
+ }, z.core.$strip>]>>;
196
253
  fts: z.ZodDefault<z.ZodBoolean>;
197
254
  }, z.core.$strip>>;
255
+ staff: z.ZodOptional<z.ZodObject<{
256
+ readPower: z.ZodString;
257
+ assignPower: z.ZodString;
258
+ }, z.core.$strip>>;
198
259
  }, z.core.$strip>;
199
260
  export type SupportConfig = z.output<typeof SupportConfig>;
200
261
  export type SupportConfigInput = z.input<typeof SupportConfig>;
@@ -1 +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"}
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;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,oBAAoB,CAAC;AAEzD;;;GAGG;AACH,eAAO,MAAM,4BAA4B,OAAO,CAAC;AAEjD,oGAAoG;AACpG,eAAO,MAAM,qBAAqB;;;iBAmB2B,CAAC;AAC9D,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAE3E;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB;;;;;;iBAiB7B,CAAC;AACJ,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEvE;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,mBAAmB,GAAG,qBAAqB,GAAG,SAAS,CAI9F;AAED,sCAAsC;AACtC;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,kBAAkB;;;iBAiB5B,CAAC;AACJ,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAErE,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAoCvB,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"}
@@ -132,13 +132,70 @@ const SupportReplyConfig = z.object({
132
132
  snippets: z.record(z.string(), SupportReplySnippet).default({}).describe("Your own canned replies, merged over the ones Pithy ships and winning on a key collision. These are *starting points a human picks and edits* in the dashboard, not automatic replies — nothing here is ever sent without somebody pressing send. Author them with `defineSupportReplies` so a malformed one fails where it is written.")
133
133
  }).describe("Reply settings — whether replies are allowed, where the answer goes, and what to offer as a starting point.");
134
134
  /**
135
+ * The default embedding model for semantic search — the kit's own default (`DEFAULT_EMBEDDING_MODEL` in
136
+ * `@pithy-sh/cloudflare/src/ai/aiManager.ts`), restated rather than imported because a Worker cannot reach
137
+ * the REST client package and should not.
138
+ *
139
+ * **Multilingual, and that is why.** An inbox takes mail in whatever language the customer writes, and an
140
+ * English-only model would embed a Spanish complaint into the wrong neighborhood with no error.
141
+ */
142
+ const DEFAULT_EMBEDDING_MODEL = "@cf/baai/bge-m3";
143
+ /**
144
+ * How many components `bge-m3` produces. It is not a Vectorize preset, so the index states its own shape;
145
+ * a model override that produces another width states its `dimensions` beside it.
146
+ */
147
+ const DEFAULT_EMBEDDING_DIMENSIONS = 1024;
148
+ /** The `{ model }` form of `search.semantic`: semantic search on, with a model of your choosing. */
149
+ const SupportSemanticConfig = z.object({
150
+ model: z.string().min(1).default(DEFAULT_EMBEDDING_MODEL).describe("The Workers AI embedding model the message index is built and queried with. Pinned: change it and `pithy vector reprocess --index support-messages` re-embeds what the old one wrote."),
151
+ dimensions: z.number().int().min(1).max(1536).default(DEFAULT_EMBEDDING_DIMENSIONS).describe("How many components the model produces — 1024 for `bge-m3`, 768 for `bge-base-en-v1.5`. Fixed when the index is created, so a model of another width needs the index dropped and recreated.")
152
+ }).describe("Semantic search with a chosen embedding model.");
153
+ /**
135
154
  * How text search is answered.
136
155
  *
137
- * The one setting here has a cost an adopter must opt into knowingly, which is why it is a setting
138
- * at all rather than a decision this package made for them.
156
+ * FTS5 by default, with the `LIKE` scan kept for a project that wants no virtual table at all. Semantic
157
+ * search is off by default because it costs a second inference per inbound message.
139
158
  */
140
- const SupportSearchConfig = z.object({ fts: z.boolean().default(false).describe("Build a SQLite FTS5 index over message subjects and bodies. **Off by default, and the reason is not performance.** `wrangler d1 export` refuses to dump any database containing an FTS5 virtual table — it fails outright rather than skipping the table — and the check runs server-side across the whole database before `--table` filtering, so turning this on takes your **entire app database's** export with it. A failed attempt has also been reported to leave the database inaccessible until it clears. If you do not use `wrangler d1 export`, none of that reaches you and FTS5 is the better search. Safe to toggle either way: the index is derived rather than migrated, so `pithy support provision` creates or drops it to match — and until you re-provision, search falls back to the `LIKE` scan rather than failing.") }).describe("Text search settings — the `LIKE` scan by default, FTS5 as a deliberate opt-in.");
159
+ const SupportSearchConfig = z.object({
160
+ semantic: z.union([z.boolean(), SupportSemanticConfig]).default(false).describe("Search by meaning as well as by words: `true` for the default model (`@cf/baai/bge-m3`), or `{ model }` for your own. **Off by default, and the reason is cost.** Every inbound message is embedded once, on your own Workers AI binding, in the classification Workflow — a second inference per message on top of classification, which roughly doubles this capability's per-message AI cost. The volume guard bounds messages, not spend. On, `pithy support provision` creates a Vectorize index and backfills it once; off, it drops the index. Vectors carry message ids and nothing else."),
161
+ fts: z.boolean().default(true).describe("Build a SQLite FTS5 index over message subjects and bodies — a ranked word search instead of an unindexed `LIKE` scan. **On by default.** A plain `wrangler d1 export` of a database holding an FTS5 table fails outright, so `pithy backup` leaves the index and its triggers out and `pithy restore` rebuilds them from `pithy_support_messages`; the bare `wrangler` route has to skip it by hand. The index is derived rather than migrated, so `pithy support provision` creates or drops it to match this flag, and until a project re-provisions, search falls back to the `LIKE` scan and `pithy doctor` names the environment.")
162
+ }).describe("Search settings — FTS5 by default, the `LIKE` scan when it is off or not yet provisioned, and semantic search an opt-in priced in resources you own.");
163
+ /**
164
+ * The semantic model and width this configuration asks for, or `undefined` when semantic search is off.
165
+ *
166
+ * The one reading of the three-shaped flag — `false`, `true`, `{ model }` — so the capability's bindings,
167
+ * the search route, the classification Workflow and provisioning cannot each decide what `true` means.
168
+ */
169
+ function supportSemantic(search) {
170
+ if (search.semantic === false) return void 0;
171
+ if (search.semantic === true) return {
172
+ model: DEFAULT_EMBEDDING_MODEL,
173
+ dimensions: DEFAULT_EMBEDDING_DIMENSIONS
174
+ };
175
+ return search.semantic;
176
+ }
141
177
  /** The full support configuration. */
178
+ /**
179
+ * **Who of the app's own signed-in users works the inbox (#816).** Off until named.
180
+ *
181
+ * The inbox holds every customer's correspondence, so a signed-in user reaches it only through a power the
182
+ * adopter names in their own role catalog, held in the organization the request acts for — the same seam
183
+ * `media:manage` and `storage:manage` ask, and the same rule `artifacts({ managePower })` follows: a
184
+ * capability never reserves a power name in a catalog that is the adopter's.
185
+ *
186
+ * **Both powers must sit only on roles no organization can hand itself** — listed in
187
+ * `defineRoles({ unassignable })`, and not the role `organization({ ownership })` confers — and `support()`
188
+ * refuses the composition otherwise. A role is the catalog's, not one organization's, and an organization's
189
+ * own administrator may give any assignable role to their own members — so a support power on an assignable
190
+ * role would let any customer's administrator make themselves staff and read every other customer's mail.
191
+ * The ownership role is unassignable and still self-served: anybody may found an organization, nominate
192
+ * themselves its owner, and accept. On any other unassignable role it is conferred by an operator alone,
193
+ * through the control plane.
194
+ */
195
+ const SupportStaffConfig = z.object({
196
+ readPower: z.string().min(1).describe("The power that lets a signed-in user see who else has a thread open — what the `support.thread` presence room's session ticket asks. Your own name, declared in your role catalog on unassignable roles only, and not on the role ownership transfer confers, e.g. `support:read`."),
197
+ assignPower: z.string().min(1).describe("The power that lets a signed-in user assign a thread, to themselves or to somebody else, at `POST {basePath}/staff/threads/:id/assign`. Your own name, declared in your role catalog on unassignable roles only, and not on the role ownership transfer confers, e.g. `support:assign`.")
198
+ }).describe("Who of your app's own signed-in users works the inbox, by the organization powers their role holds. Requires `organization`, and both powers on unassignable roles other than the one ownership transfer confers. Absent, no signed-in user can assign a thread or join a thread's presence room — management clients still can, through the control plane.");
142
199
  const SupportConfig = z.object({
143
200
  inboundAddresses: z.array(z.string().min(3).describe("One address this inbox accepts mail for, e.g. `support@help.example.com`.").transform((address) => parseAddress(address) ?? normalizeAddress(address))).default([]).describe("The addresses this capability claims. Every capability's `email()` handler sees every message the Worker receives, so this list is how support tells its mail apart from the bounce handler's — and it is a claim, not a route: the routing rule that delivers to this Worker is created by `pithy support provision`. **Use a subdomain, never your apex**: Email Routing takes over the zone's MX, and enabling it on the apex moves your real mail off your provider. Empty means the inbox is inert."),
144
201
  categories: z.record(z.string(), z.string()).default({}).describe("Your own categories, merged over the eight Pithy ships and winning on a key collision. A key is `snake_case`; the value is the instruction a model reads when deciding whether this is the one, and it lands in the prompt verbatim. Author it with `defineSupportCategories` so a typo fails where it is written."),
@@ -147,7 +204,8 @@ const SupportConfig = z.object({
147
204
  guard: SupportGuardConfig.prefault({}).describe("Inbound mail size and rate bounds."),
148
205
  submission: SupportSubmissionConfig.prefault({}).describe("The in-app submission channel."),
149
206
  reply: SupportReplyConfig.prefault({}).describe("Reply settings."),
150
- search: SupportSearchConfig.prefault({}).describe("How text search is answered.")
207
+ search: SupportSearchConfig.prefault({}).describe("How text search is answered."),
208
+ staff: SupportStaffConfig.optional().describe("Who of your app's signed-in users works the inbox, by organization power. Absent, the staff session routes refuse everyone.")
151
209
  }).describe("Configuration for the support capability — which addresses it claims, and how it classifies and bounds them.");
152
210
  /**
153
211
  * Would this configuration ever put a byte in `SUPPORT_BUCKET`?
@@ -168,4 +226,4 @@ function supportNeedsBucket(config) {
168
226
  return config.attachments.enabled || config.attachments.retainRaw || config.submission.attachments.enabled;
169
227
  }
170
228
  //#endregion
171
- export { DEFAULT_CLASSIFY_MODEL, MAX_SUBMISSION_ATTACHMENTS, MAX_SUBMISSION_BODY_CHARS, MAX_SUBMISSION_SUBJECT_CHARS, SupportAiConfig, SupportAttachmentsConfig, SupportConfig, SupportGuardConfig, SupportReplyConfig, SupportSearchConfig, SupportSubmissionAttachmentsConfig, SupportSubmissionConfig, supportNeedsBucket };
229
+ export { DEFAULT_CLASSIFY_MODEL, DEFAULT_EMBEDDING_DIMENSIONS, DEFAULT_EMBEDDING_MODEL, MAX_SUBMISSION_ATTACHMENTS, MAX_SUBMISSION_BODY_CHARS, MAX_SUBMISSION_SUBJECT_CHARS, SupportAiConfig, SupportAttachmentsConfig, SupportConfig, SupportGuardConfig, SupportReplyConfig, SupportSearchConfig, SupportSemanticConfig, SupportStaffConfig, SupportSubmissionAttachmentsConfig, SupportSubmissionConfig, supportNeedsBucket, supportSemantic };
@@ -0,0 +1,43 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { z } from "zod";
4
+ /**
5
+ * The `assignee` filter value meaning *nobody has it*. Reserved: an assignment to a ref spelled this way is
6
+ * refused at the boundary (`http/schemas.ts`), so the filter can never mean two things.
7
+ */
8
+ export declare const UNASSIGNED = "none";
9
+ /**
10
+ * Who assigned a thread: a management client through the control plane, or a signed-in user of the app
11
+ * through support's staff power. Recorded per change, because the two are different evidence about who
12
+ * acted, exactly as the audit trail's `actorType` is.
13
+ */
14
+ export declare const SupportAssignmentActorType: z.ZodEnum<{
15
+ "control-plane": "control-plane";
16
+ user: "user";
17
+ }>;
18
+ export type SupportAssignmentActorType = z.output<typeof SupportAssignmentActorType>;
19
+ /**
20
+ * One change of a thread's assignee in `pithy_support_assignments` — append-only, one row per change.
21
+ *
22
+ * The thread carries the **current** assignee because that is what the inbox filters on; this carries
23
+ * **every** change, the same split the classification table has with the thread. Nothing here is updated
24
+ * and nothing is deleted while the thread stands. A change that changed nothing — the current assignee
25
+ * assigned again — writes no row, so every row here is a real handover.
26
+ */
27
+ export declare const SupportAssignment: z.ZodObject<{
28
+ id: z.ZodString;
29
+ threadId: z.ZodString;
30
+ assigneeRef: z.ZodOptional<z.ZodNullable<z.ZodString>>;
31
+ assigneeName: z.ZodOptional<z.ZodNullable<z.ZodString>>;
32
+ previousRef: z.ZodOptional<z.ZodNullable<z.ZodString>>;
33
+ previousName: z.ZodOptional<z.ZodNullable<z.ZodString>>;
34
+ actorType: z.ZodEnum<{
35
+ "control-plane": "control-plane";
36
+ user: "user";
37
+ }>;
38
+ assignedBy: z.ZodString;
39
+ note: z.ZodOptional<z.ZodNullable<z.ZodString>>;
40
+ createdAt: z.ZodCodec<z.ZodUnion<readonly [z.ZodNumber, z.ZodString, z.ZodDate]>, z.ZodDate>;
41
+ }, z.core.$strip>;
42
+ export type SupportAssignment = z.output<typeof SupportAssignment>;
43
+ //# sourceMappingURL=assignment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assignment.d.ts","sourceRoot":"","sources":["../../src/data/assignment.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;GAGG;AACH,eAAO,MAAM,UAAU,SAAS,CAAC;AAEjC;;;;GAIG;AACH,eAAO,MAAM,0BAA0B;;;EAIpC,CAAC;AACJ,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAErF;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;iBAqBsE,CAAC;AACrG,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,iBAAiB,CAAC,CAAC"}
@@ -0,0 +1,38 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { z } from "zod";
4
+ import { SQLiteDate } from "@pithy-sh/core/src/data/codecs";
5
+ //#region src/data/assignment.ts
6
+ /**
7
+ * The `assignee` filter value meaning *nobody has it*. Reserved: an assignment to a ref spelled this way is
8
+ * refused at the boundary (`http/schemas.ts`), so the filter can never mean two things.
9
+ */
10
+ const UNASSIGNED = "none";
11
+ /**
12
+ * Who assigned a thread: a management client through the control plane, or a signed-in user of the app
13
+ * through support's staff power. Recorded per change, because the two are different evidence about who
14
+ * acted, exactly as the audit trail's `actorType` is.
15
+ */
16
+ const SupportAssignmentActorType = z.enum(["control-plane", "user"]).describe("How the assigner was verified: `control-plane` for a management client's scoped credential, `user` for a signed-in user of the app holding support's staff power.");
17
+ /**
18
+ * One change of a thread's assignee in `pithy_support_assignments` — append-only, one row per change.
19
+ *
20
+ * The thread carries the **current** assignee because that is what the inbox filters on; this carries
21
+ * **every** change, the same split the classification table has with the thread. Nothing here is updated
22
+ * and nothing is deleted while the thread stands. A change that changed nothing — the current assignee
23
+ * assigned again — writes no row, so every row here is a real handover.
24
+ */
25
+ const SupportAssignment = z.object({
26
+ id: z.string().describe("UUID primary key."),
27
+ threadId: z.string().describe("The `pithy_support_threads.id` whose assignee changed."),
28
+ assigneeRef: z.string().nullish().describe("Who has the thread after this change, as an opaque reference, or null when it was unassigned."),
29
+ assigneeName: z.string().nullish().describe("How that assignee was shown at the time, or null when unassigned."),
30
+ previousRef: z.string().nullish().describe("Who had the thread before this change, or null when nobody did."),
31
+ previousName: z.string().nullish().describe("How the previous assignee was shown, or null when nobody had it."),
32
+ actorType: SupportAssignmentActorType.describe("How the person or client making the change was verified."),
33
+ assignedBy: z.string().describe("Who made the change — the control-plane subject, or the signed-in user's id."),
34
+ note: z.string().nullish().describe("Why, in the assigner's own words, when they gave a reason. Staff-written, never customer text, and shown to anybody who can read the thread."),
35
+ createdAt: SQLiteDate.describe("When the change was made.")
36
+ }).describe("One append-only change of a support thread's assignee in `pithy_support_assignments`.");
37
+ //#endregion
38
+ export { SupportAssignment, SupportAssignmentActorType, UNASSIGNED };
@@ -0,0 +1,21 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { z } from "zod";
4
+ /**
5
+ * Which model embedded a message into the semantic index, in `pithy_support_embeddings`.
6
+ *
7
+ * **Its own table, not a column on the message**, for two reasons. A released migration is never edited, and
8
+ * a stamp belongs to a derived index rather than to the mail: drop the index and every stamp is a lie, so they
9
+ * go together, and nothing about a message changes. It is what `pithy vector reprocess --index
10
+ * support-messages` reads to re-embed exactly the rows whose model differs from the configured one — the same
11
+ * job `pithy_vector_documents.model` does for an index `vector()` owns.
12
+ *
13
+ * A message with no row was never embedded, which is the set a backfill and a repair both pick up.
14
+ */
15
+ export declare const SupportEmbedding: z.ZodObject<{
16
+ messageId: z.ZodString;
17
+ model: z.ZodString;
18
+ embeddedAt: z.ZodCodec<z.ZodUnion<readonly [z.ZodNumber, z.ZodString, z.ZodDate]>, z.ZodDate>;
19
+ }, z.core.$strip>;
20
+ export type SupportEmbedding = z.output<typeof SupportEmbedding>;
21
+ //# sourceMappingURL=embedding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embedding.d.ts","sourceRoot":"","sources":["../../src/data/embedding.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gBAAgB;;;;iBAQiD,CAAC;AAC/E,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,gBAAgB,CAAC,CAAC"}
@@ -0,0 +1,23 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { z } from "zod";
4
+ import { SQLiteDate } from "@pithy-sh/core/src/data/codecs";
5
+ //#region src/data/embedding.ts
6
+ /**
7
+ * Which model embedded a message into the semantic index, in `pithy_support_embeddings`.
8
+ *
9
+ * **Its own table, not a column on the message**, for two reasons. A released migration is never edited, and
10
+ * a stamp belongs to a derived index rather than to the mail: drop the index and every stamp is a lie, so they
11
+ * go together, and nothing about a message changes. It is what `pithy vector reprocess --index
12
+ * support-messages` reads to re-embed exactly the rows whose model differs from the configured one — the same
13
+ * job `pithy_vector_documents.model` does for an index `vector()` owns.
14
+ *
15
+ * A message with no row was never embedded, which is the set a backfill and a repair both pick up.
16
+ */
17
+ const SupportEmbedding = z.object({
18
+ messageId: z.string().describe("The `pithy_support_messages.id` this stamp is for — also the vector's id in the index."),
19
+ model: z.string().describe("The Workers AI embedding model that produced the vector now in the index."),
20
+ embeddedAt: SQLiteDate.describe("When the vector was written.")
21
+ }).describe("The model stamp for one message's vector in the semantic index.");
22
+ //#endregion
23
+ export { SupportEmbedding };
@@ -18,6 +18,10 @@ export declare const SUPPORT_ATTACHMENTS_TABLE = "pithySupportAttachments";
18
18
  export declare const SUPPORT_CLASSIFICATIONS_TABLE = "pithySupportClassifications";
19
19
  /** Per-viewer read/snooze state. `pithy_support_thread_flags`. */
20
20
  export declare const SUPPORT_FLAGS_TABLE = "pithySupportThreadFlags";
21
+ /** Append-only assignee history. `pithy_support_assignments`. */
22
+ export declare const SUPPORT_ASSIGNMENTS_TABLE = "pithySupportAssignments";
23
+ /** The semantic index's per-message model stamps. `pithy_support_embeddings`. */
24
+ export declare const SUPPORT_EMBEDDINGS_TABLE = "pithySupportEmbeddings";
21
25
  /**
22
26
  * The FTS5 full-text index. Not a Kysely table and deliberately not in {@link supportTables}: it is a
23
27
  * virtual table with no Zod schema and no rows of its own worth typing, reached through raw `sql`
@@ -64,6 +68,10 @@ export declare const supportTables: {
64
68
  archived: z.ZodCodec<z.ZodUnion<readonly [z.ZodLiteral<0>, z.ZodLiteral<1>, z.ZodBoolean, z.ZodString]>, z.ZodBoolean>;
65
69
  archivedAt: z.ZodOptional<z.ZodNullable<z.ZodCodec<z.ZodUnion<readonly [z.ZodNumber, z.ZodString, z.ZodDate]>, z.ZodDate>>>;
66
70
  archivedBy: z.ZodOptional<z.ZodNullable<z.ZodString>>;
71
+ assigneeRef: z.ZodOptional<z.ZodNullable<z.ZodString>>;
72
+ assigneeName: z.ZodOptional<z.ZodNullable<z.ZodString>>;
73
+ assignedAt: z.ZodOptional<z.ZodNullable<z.ZodCodec<z.ZodUnion<readonly [z.ZodNumber, z.ZodString, z.ZodDate]>, z.ZodDate>>>;
74
+ assignedBy: z.ZodOptional<z.ZodNullable<z.ZodString>>;
67
75
  messageCount: z.ZodNumber;
68
76
  firstMessageAt: z.ZodCodec<z.ZodUnion<readonly [z.ZodNumber, z.ZodString, z.ZodDate]>, z.ZodDate>;
69
77
  lastMessageAt: z.ZodCodec<z.ZodUnion<readonly [z.ZodNumber, z.ZodString, z.ZodDate]>, z.ZodDate>;
@@ -146,6 +154,26 @@ export declare const supportTables: {
146
154
  createdAt: z.ZodCodec<z.ZodUnion<readonly [z.ZodNumber, z.ZodString, z.ZodDate]>, z.ZodDate>;
147
155
  updatedAt: z.ZodCodec<z.ZodUnion<readonly [z.ZodNumber, z.ZodString, z.ZodDate]>, z.ZodDate>;
148
156
  }, z.core.$strip>;
157
+ pithySupportAssignments: z.ZodObject<{
158
+ id: z.ZodString;
159
+ threadId: z.ZodString;
160
+ assigneeRef: z.ZodOptional<z.ZodNullable<z.ZodString>>;
161
+ assigneeName: z.ZodOptional<z.ZodNullable<z.ZodString>>;
162
+ previousRef: z.ZodOptional<z.ZodNullable<z.ZodString>>;
163
+ previousName: z.ZodOptional<z.ZodNullable<z.ZodString>>;
164
+ actorType: z.ZodEnum<{
165
+ "control-plane": "control-plane";
166
+ user: "user";
167
+ }>;
168
+ assignedBy: z.ZodString;
169
+ note: z.ZodOptional<z.ZodNullable<z.ZodString>>;
170
+ createdAt: z.ZodCodec<z.ZodUnion<readonly [z.ZodNumber, z.ZodString, z.ZodDate]>, z.ZodDate>;
171
+ }, z.core.$strip>;
172
+ pithySupportEmbeddings: z.ZodObject<{
173
+ messageId: z.ZodString;
174
+ model: z.ZodString;
175
+ embeddedAt: z.ZodCodec<z.ZodUnion<readonly [z.ZodNumber, z.ZodString, z.ZodDate]>, z.ZodDate>;
176
+ }, z.core.$strip>;
149
177
  };
150
178
  /** The support tables, as a type. */
151
179
  export type SupportTables = typeof supportTables;
@@ -1 +1 @@
1
- {"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../src/data/tables.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAO7B;;;GAGG;AAEH,8CAA8C;AAC9C,eAAO,MAAM,qBAAqB,wBAAwB,CAAC;AAC3D,gEAAgE;AAChE,eAAO,MAAM,sBAAsB,yBAAyB,CAAC;AAC7D,wDAAwD;AACxD,eAAO,MAAM,yBAAyB,4BAA4B,CAAC;AACnE,2EAA2E;AAC3E,eAAO,MAAM,6BAA6B,gCAAgC,CAAC;AAC3E,kEAAkE;AAClE,eAAO,MAAM,mBAAmB,4BAA4B,CAAC;AAE7D;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,yBAAyB,CAAC;AAE3D,oEAAoE;AACpE,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAMa,CAAC;AAExC,qCAAqC;AACrC,MAAM,MAAM,aAAa,GAAG,OAAO,aAAa,CAAC;AAEjD,gFAAgF;AAChF,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC,CAAC;AAEpE,sDAAsD;AACtD,wBAAgB,eAAe,CAAC,EAAE,EAAE,UAAU,GAAG,eAAe,CAE/D"}
1
+ {"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../src/data/tables.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAS7B;;;GAGG;AAEH,8CAA8C;AAC9C,eAAO,MAAM,qBAAqB,wBAAwB,CAAC;AAC3D,gEAAgE;AAChE,eAAO,MAAM,sBAAsB,yBAAyB,CAAC;AAC7D,wDAAwD;AACxD,eAAO,MAAM,yBAAyB,4BAA4B,CAAC;AACnE,2EAA2E;AAC3E,eAAO,MAAM,6BAA6B,gCAAgC,CAAC;AAC3E,kEAAkE;AAClE,eAAO,MAAM,mBAAmB,4BAA4B,CAAC;AAC7D,iEAAiE;AACjE,eAAO,MAAM,yBAAyB,4BAA4B,CAAC;AACnE,iFAAiF;AACjF,eAAO,MAAM,wBAAwB,2BAA2B,CAAC;AAEjE;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,yBAAyB,CAAC;AAE3D,oEAAoE;AACpE,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAQa,CAAC;AAExC,qCAAqC;AACrC,MAAM,MAAM,aAAa,GAAG,OAAO,aAAa,CAAC;AAEjD,gFAAgF;AAChF,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC,CAAC;AAEpE,sDAAsD;AACtD,wBAAgB,eAAe,CAAC,EAAE,EAAE,UAAU,GAAG,eAAe,CAE/D"}
@@ -1,7 +1,9 @@
1
1
  // SPDX-FileCopyrightText: 2026 Pithy
2
2
  // SPDX-License-Identifier: MIT
3
+ import { SupportAssignment } from "./assignment.js";
3
4
  import { SupportAttachment } from "./attachment.js";
4
5
  import { SupportClassification } from "./classification.js";
6
+ import { SupportEmbedding } from "./embedding.js";
5
7
  import { SupportThreadFlag } from "./flag.js";
6
8
  import { SupportMessage } from "./message.js";
7
9
  import { SupportThread } from "./thread.js";
@@ -21,6 +23,10 @@ const SUPPORT_ATTACHMENTS_TABLE = "pithySupportAttachments";
21
23
  const SUPPORT_CLASSIFICATIONS_TABLE = "pithySupportClassifications";
22
24
  /** Per-viewer read/snooze state. `pithy_support_thread_flags`. */
23
25
  const SUPPORT_FLAGS_TABLE = "pithySupportThreadFlags";
26
+ /** Append-only assignee history. `pithy_support_assignments`. */
27
+ const SUPPORT_ASSIGNMENTS_TABLE = "pithySupportAssignments";
28
+ /** The semantic index's per-message model stamps. `pithy_support_embeddings`. */
29
+ const SUPPORT_EMBEDDINGS_TABLE = "pithySupportEmbeddings";
24
30
  /**
25
31
  * The FTS5 full-text index. Not a Kysely table and deliberately not in {@link supportTables}: it is a
26
32
  * virtual table with no Zod schema and no rows of its own worth typing, reached through raw `sql`
@@ -36,11 +42,13 @@ const supportTables = {
36
42
  [SUPPORT_MESSAGES_TABLE]: SupportMessage,
37
43
  [SUPPORT_ATTACHMENTS_TABLE]: SupportAttachment,
38
44
  [SUPPORT_CLASSIFICATIONS_TABLE]: SupportClassification,
39
- [SUPPORT_FLAGS_TABLE]: SupportThreadFlag
45
+ [SUPPORT_FLAGS_TABLE]: SupportThreadFlag,
46
+ [SUPPORT_ASSIGNMENTS_TABLE]: SupportAssignment,
47
+ [SUPPORT_EMBEDDINGS_TABLE]: SupportEmbedding
40
48
  };
41
49
  /** Build the support database from a `DB` binding. */
42
50
  function supportDatabase(d1) {
43
51
  return createDatabase(d1, supportTables);
44
52
  }
45
53
  //#endregion
46
- export { SUPPORT_ATTACHMENTS_TABLE, SUPPORT_CLASSIFICATIONS_TABLE, SUPPORT_FLAGS_TABLE, SUPPORT_MESSAGES_TABLE, SUPPORT_SEARCH_TABLE, SUPPORT_THREADS_TABLE, supportDatabase, supportTables };
54
+ export { SUPPORT_ASSIGNMENTS_TABLE, SUPPORT_ATTACHMENTS_TABLE, SUPPORT_CLASSIFICATIONS_TABLE, SUPPORT_EMBEDDINGS_TABLE, SUPPORT_FLAGS_TABLE, SUPPORT_MESSAGES_TABLE, SUPPORT_SEARCH_TABLE, SUPPORT_THREADS_TABLE, supportDatabase, supportTables };