@pithy-sh/support 0.3.3 → 0.4.1

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 (44) hide show
  1. package/dist/ai/classify.d.ts.map +1 -1
  2. package/dist/ai/classify.js +3 -3
  3. package/dist/http/handlers.d.ts.map +1 -1
  4. package/dist/http/handlers.js +2 -3
  5. package/dist/http/routes.d.ts +4 -1
  6. package/dist/http/routes.d.ts.map +1 -1
  7. package/dist/http/routes.js +60 -12
  8. package/dist/inbound/handler.d.ts.map +1 -1
  9. package/dist/inbound/handler.js +0 -1
  10. package/dist/inbound/ingest.d.ts +0 -2
  11. package/dist/inbound/ingest.d.ts.map +1 -1
  12. package/dist/inbound/ingest.js +0 -14
  13. package/dist/provision/provisionSupport.d.ts +25 -4
  14. package/dist/provision/provisionSupport.d.ts.map +1 -1
  15. package/dist/provision/provisionSupport.js +18 -3
  16. package/dist/reply/send.d.ts +0 -2
  17. package/dist/reply/send.d.ts.map +1 -1
  18. package/dist/reply/send.js +2 -15
  19. package/dist/store/search.d.ts +16 -9
  20. package/dist/store/search.d.ts.map +1 -1
  21. package/dist/store/search.js +16 -9
  22. package/dist/store/searchIndex.d.ts +131 -5
  23. package/dist/store/searchIndex.d.ts.map +1 -1
  24. package/dist/store/searchIndex.js +151 -6
  25. package/dist/store/threads.d.ts.map +1 -1
  26. package/dist/store/threads.js +3 -2
  27. package/dist/submission/submit.d.ts +0 -2
  28. package/dist/submission/submit.d.ts.map +1 -1
  29. package/dist/submission/submit.js +0 -14
  30. package/dist/version.generated.d.ts +1 -1
  31. package/dist/version.generated.js +1 -1
  32. package/package.json +10 -10
  33. package/src/ai/classify.ts +7 -3
  34. package/src/http/handlers.ts +2 -3
  35. package/src/http/routes.ts +96 -19
  36. package/src/inbound/handler.ts +0 -1
  37. package/src/inbound/ingest.ts +0 -14
  38. package/src/provision/provisionSupport.ts +33 -7
  39. package/src/reply/send.ts +2 -13
  40. package/src/store/search.ts +16 -9
  41. package/src/store/searchIndex.ts +176 -5
  42. package/src/store/threads.ts +2 -1
  43. package/src/submission/submit.ts +0 -11
  44. package/src/version.generated.ts +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"classify.d.ts","sourceRoot":"","sources":["../../src/ai/classify.ts"],"names":[],"mappings":"AAKA,OAAO,EAAoC,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC9F,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAiB,MAAM,eAAe,CAAC;AAGjF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,0GAA0G;AAC1G,MAAM,WAAW,SAAS;IACxB,qGAAqG;IACrG,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACtE;AAED,wCAAwC;AACxC,MAAM,WAAW,cAAc;IAC7B,kGAAkG;IAClG,QAAQ,EAAE,MAAM,CAAC;IACjB,mCAAmC;IACnC,QAAQ,EAAE,eAAe,CAAC;IAC1B,6BAA6B;IAC7B,SAAS,EAAE,gBAAgB,CAAC;IAC5B,6DAA6D;IAC7D,UAAU,EAAE,MAAM,CAAC;IACnB,qCAAqC;IACrC,KAAK,EAAE,MAAM,CAAC;CACf;AA4BD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,iBAAiB,GAAG,MAAM,CAe1E;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAS3F;AAqBD;;;;;;;;;;;GAWG;AACH,wBAAsB,eAAe,CACnC,EAAE,EAAE,SAAS,EACb,OAAO,EAAE;IACP,8BAA8B;IAC9B,UAAU,EAAE,iBAAiB,CAAC;IAC9B,2BAA2B;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,qCAAqC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,4BAA4B;IAC5B,WAAW,EAAE,MAAM,CAAC;CACrB,GACA,OAAO,CAAC,cAAc,CAAC,CAkEzB"}
1
+ {"version":3,"file":"classify.d.ts","sourceRoot":"","sources":["../../src/ai/classify.ts"],"names":[],"mappings":"AAKA,OAAO,EAAoC,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC9F,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAiB,MAAM,eAAe,CAAC;AAGjF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,0GAA0G;AAC1G,MAAM,WAAW,SAAS;IACxB,qGAAqG;IACrG,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACtE;AAED,wCAAwC;AACxC,MAAM,WAAW,cAAc;IAC7B,kGAAkG;IAClG,QAAQ,EAAE,MAAM,CAAC;IACjB,mCAAmC;IACnC,QAAQ,EAAE,eAAe,CAAC;IAC1B,6BAA6B;IAC7B,SAAS,EAAE,gBAAgB,CAAC;IAC5B,6DAA6D;IAC7D,UAAU,EAAE,MAAM,CAAC;IACnB,qCAAqC;IACrC,KAAK,EAAE,MAAM,CAAC;CACf;AA4BD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,iBAAiB,GAAG,MAAM,CAe1E;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAS3F;AAqBD;;;;;;;;;;;GAWG;AACH,wBAAsB,eAAe,CACnC,EAAE,EAAE,SAAS,EACb,OAAO,EAAE;IACP,8BAA8B;IAC9B,UAAU,EAAE,iBAAiB,CAAC;IAC9B,2BAA2B;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,qCAAqC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,4BAA4B;IAC5B,WAAW,EAAE,MAAM,CAAC;CACrB,GACA,OAAO,CAAC,cAAc,CAAC,CAsEzB"}
@@ -108,11 +108,11 @@ function unsure(model) {
108
108
  * The code is the whole of how the step can tell the two apart.
109
109
  */
110
110
  async function classifyMessage(ai, options) {
111
- const run = ai?.run;
112
- if (typeof run !== "function") throw new SupportClassificationError({ detail: "the AI binding is absent from this worker's env; classification cannot run" });
111
+ const binding = ai;
112
+ if (typeof binding?.run !== "function") throw new SupportClassificationError({ detail: "the AI binding is absent from this worker's env; classification cannot run" });
113
113
  let raw;
114
114
  try {
115
- raw = await run.call(ai, options.model, {
115
+ raw = await binding.run(options.model, {
116
116
  messages: [{
117
117
  role: "system",
118
118
  content: classificationPrompt(options.categories)
@@ -1 +1 @@
1
- {"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/http/handlers.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/http/handlers.ts"],"names":[],"mappings":"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"}
@@ -12,6 +12,7 @@ import { submitFeedback } from "../submission/submit.js";
12
12
  import { latestInboundMessageId } from "../workflows/classify.js";
13
13
  import { listedThreadView, messageView, myMessageView, myThreadView, senderView, threadView } from "./views.js";
14
14
  import { ValidationError } from "@pithy-sh/core/src/error/pithyError";
15
+ import { pithyCommandOf } from "@pithy-sh/core/src/cli/command";
15
16
  //#region src/http/handlers.ts
16
17
  /** List the inbox. */
17
18
  async function listInbox(deps, query, viewer) {
@@ -98,7 +99,6 @@ async function replyToConversation(deps, threadId, input, viewer) {
98
99
  db: deps.db,
99
100
  config: deps.config,
100
101
  enqueue: deps.enqueue,
101
- fts: deps.fts,
102
102
  emit: deps.emit,
103
103
  log: deps.log,
104
104
  newId: deps.newId,
@@ -119,7 +119,7 @@ async function reclassifyConversation(deps, threadId, viewer) {
119
119
  });
120
120
  if (!await deps.dispatchClassify(messageId)) throw new SupportClassificationError({
121
121
  message: "Classification is not available on this deployment.",
122
- action: "Run `pithy support provision` to deploy the classification worker, then retry.",
122
+ action: `Run \`${pithyCommandOf("support provision", {}, { runtime: true })}\` to deploy the classification worker, then retry.`,
123
123
  detail: `the SUPPORT_CLASSIFY workflow binding is absent, so no instance was started`
124
124
  });
125
125
  await deps.emit({
@@ -184,7 +184,6 @@ async function submitFeedbackRequest(deps, input, userId) {
184
184
  config: deps.config,
185
185
  categories: deps.categories,
186
186
  bucket: deps.bucket,
187
- fts: deps.fts,
188
187
  resolveAccount: (id) => resolveSubmitterAccount(deps.d1, id, deps.peers),
189
188
  dispatchClassify: deps.dispatchClassify,
190
189
  emit: deps.emit,
@@ -76,7 +76,10 @@ import { type HandlerDeps } from "./handlers";
76
76
  * `createBackend` mounts **every** capability's middleware before **any** capability's routes, and the
77
77
  * adopter's `app` capability composes last — so their middleware runs after auth has resolved the
78
78
  * session and before this file's `requireAuth()`. `createBackend.workers.test.ts` pins that ordering,
79
- * because a paragraph asserting it is not the same as a test failing when it changes.
79
+ * because a paragraph asserting it is not the same as a test failing when it changes. Readiness guards
80
+ * (`Capability.readiness`) mount between the two, after every middleware and before every route, and
81
+ * skip the admin surface; `createBackend.readiness.workers.test.ts` pins that. A check like this one
82
+ * refuses a caller, not a broken Worker, so it stays in `middleware`.
80
83
  *
81
84
  * Two consequences worth stating rather than discovering. Their middleware sees `c.var.auth` as **null**
82
85
  * on an unauthenticated request, since it runs ahead of the route's own gate — so it should pass those
@@ -1 +1 @@
1
- {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/http/routes.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0CAA0C,CAAC;AAK7E,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE1C,OAAO,EAEL,KAAK,WAAW,EAUjB,MAAM,YAAY,CAAC;AAoBpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuFG;AAEH,2CAA2C;AAC3C,MAAM,WAAW,oBAAoB;IACnC,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,qDAAqD;IACrD,WAAW,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;CACjE;AAuCD,mFAAmF;AACnF,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,oBAAoB,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,YAAY,CAAC,KAAK,IAAI,CAqFtG"}
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/http/routes.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0CAA0C,CAAC;AAM7E,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE1C,OAAO,EAEL,KAAK,WAAW,EAUjB,MAAM,YAAY,CAAC;AA+BpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0FG;AAEH,2CAA2C;AAC3C,MAAM,WAAW,oBAAoB;IACnC,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,qDAAqD;IACrD,WAAW,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;CACjE;AAuCD,mFAAmF;AACnF,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,oBAAoB,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,YAAY,CAAC,KAAK,IAAI,CAoJtG"}
@@ -2,13 +2,14 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  import { requireAuth } from "./guards.js";
4
4
  import { archiveConversation, listInbox, listMyThreads, listReplies, readConversation, readMyThread, reclassifyConversation, replyToConversation, submitFeedbackRequest, updateFlags } from "./handlers.js";
5
+ import { SupportArchiveResponse, SupportFlagsResponse, SupportMyThreadResponse, SupportMyThreadsResponse, SupportReclassifiedResponse, SupportRepliesResponse, SupportReplySentResponse, SupportSubmissionResponse, SupportThreadResponse, SupportThreadsResponse } from "./responses.js";
5
6
  import { ArchiveThreadInput, FlagsInput, ListThreadsQuery, MyThreadsQuery, RepliesQuery, ReplyInput, SubmitFeedbackInput, ThreadIdParam } from "./schemas.js";
6
7
  import { SUPPORT_THREADS_ARCHIVE_SCOPE, SUPPORT_THREADS_FLAG_SCOPE, SUPPORT_THREADS_READ_SCOPE, SUPPORT_THREADS_RECLASSIFY_SCOPE, SUPPORT_THREADS_REPLY_SCOPE } from "./scopes.js";
7
8
  import { InternalError } from "@pithy-sh/core/src/error/pithyError";
8
- import { zValidator } from "@hono/zod-validator";
9
9
  import { requireControlPlane } from "@pithy-sh/core/src/controlPlane/http/guard";
10
+ import { describeRoute } from "@pithy-sh/core/src/http/routeSpec";
10
11
  import { requireSameOrigin } from "@pithy-sh/core/src/http/sameOrigin";
11
- import { validationHook } from "@pithy-sh/core/src/http/validation";
12
+ import { validate } from "@pithy-sh/core/src/http/validation";
12
13
  //#region src/http/routes.ts
13
14
  /**
14
15
  * The verified management client behind a control-plane call.
@@ -47,17 +48,64 @@ function registerSupportRoutes(options) {
47
48
  const resolve = options.resolveDeps;
48
49
  const submission = options.submission ?? true;
49
50
  return (app) => {
50
- app.get(`${base}/threads`, requireControlPlane(SUPPORT_THREADS_READ_SCOPE), zValidator("query", ListThreadsQuery, validationHook), async (c) => c.json(await listInbox(await resolve(c), c.req.valid("query"), viewer(c))));
51
- app.get(`${base}/replies`, requireControlPlane(SUPPORT_THREADS_READ_SCOPE), zValidator("query", RepliesQuery, validationHook), async (c) => c.json({ replies: listReplies(await resolve(c), c.req.valid("query")) }));
52
- app.get(`${base}/threads/:id`, requireControlPlane(SUPPORT_THREADS_READ_SCOPE), zValidator("param", ThreadIdParam, validationHook), async (c) => c.json(await readConversation(await resolve(c), c.req.valid("param").id)));
53
- app.post(`${base}/threads/:id/archive`, requireControlPlane(SUPPORT_THREADS_ARCHIVE_SCOPE), zValidator("param", ThreadIdParam, validationHook), zValidator("json", ArchiveThreadInput, validationHook), async (c) => c.json(await archiveConversation(await resolve(c), c.req.valid("param").id, c.req.valid("json"), viewer(c))));
54
- app.post(`${base}/threads/:id/reply`, requireControlPlane(SUPPORT_THREADS_REPLY_SCOPE), zValidator("param", ThreadIdParam, validationHook), zValidator("json", ReplyInput, validationHook), async (c) => c.json(await replyToConversation(await resolve(c), c.req.valid("param").id, c.req.valid("json"), viewer(c)), 201));
55
- app.post(`${base}/threads/:id/reclassify`, requireControlPlane(SUPPORT_THREADS_RECLASSIFY_SCOPE), zValidator("param", ThreadIdParam, validationHook), async (c) => c.json(await reclassifyConversation(await resolve(c), c.req.valid("param").id, viewer(c))));
56
- app.post(`${base}/threads/:id/flags`, requireControlPlane(SUPPORT_THREADS_FLAG_SCOPE), zValidator("param", ThreadIdParam, validationHook), zValidator("json", FlagsInput, validationHook), async (c) => c.json(await updateFlags(await resolve(c), c.req.valid("param").id, c.req.valid("json"), viewer(c))));
51
+ app.get(`${base}/threads`, describeRoute({
52
+ summary: "List the support inbox.",
53
+ verification: "control-plane",
54
+ scopes: [SUPPORT_THREADS_READ_SCOPE],
55
+ responses: { 200: SupportThreadsResponse }
56
+ }), requireControlPlane(SUPPORT_THREADS_READ_SCOPE), validate("query", ListThreadsQuery), async (c) => c.json(await listInbox(await resolve(c), c.req.valid("query"), viewer(c))));
57
+ app.get(`${base}/replies`, describeRoute({
58
+ summary: "List the canned replies.",
59
+ verification: "control-plane",
60
+ scopes: [SUPPORT_THREADS_READ_SCOPE],
61
+ responses: { 200: SupportRepliesResponse }
62
+ }), requireControlPlane(SUPPORT_THREADS_READ_SCOPE), validate("query", RepliesQuery), async (c) => c.json({ replies: listReplies(await resolve(c), c.req.valid("query")) }));
63
+ app.get(`${base}/threads/:id`, describeRoute({
64
+ summary: "Read one conversation and its messages.",
65
+ verification: "control-plane",
66
+ scopes: [SUPPORT_THREADS_READ_SCOPE],
67
+ responses: { 200: SupportThreadResponse }
68
+ }), requireControlPlane(SUPPORT_THREADS_READ_SCOPE), validate("param", ThreadIdParam), async (c) => c.json(await readConversation(await resolve(c), c.req.valid("param").id)));
69
+ app.post(`${base}/threads/:id/archive`, describeRoute({
70
+ summary: "Mark a conversation done, or reopen it.",
71
+ verification: "control-plane",
72
+ scopes: [SUPPORT_THREADS_ARCHIVE_SCOPE],
73
+ responses: { 200: SupportArchiveResponse }
74
+ }), requireControlPlane(SUPPORT_THREADS_ARCHIVE_SCOPE), validate("param", ThreadIdParam), validate("json", ArchiveThreadInput), async (c) => c.json(await archiveConversation(await resolve(c), c.req.valid("param").id, c.req.valid("json"), viewer(c))));
75
+ app.post(`${base}/threads/:id/reply`, describeRoute({
76
+ summary: "Answer the customer.",
77
+ verification: "control-plane",
78
+ scopes: [SUPPORT_THREADS_REPLY_SCOPE],
79
+ responses: { 201: SupportReplySentResponse }
80
+ }), requireControlPlane(SUPPORT_THREADS_REPLY_SCOPE), validate("param", ThreadIdParam), validate("json", ReplyInput), async (c) => c.json(await replyToConversation(await resolve(c), c.req.valid("param").id, c.req.valid("json"), viewer(c)), 201));
81
+ app.post(`${base}/threads/:id/reclassify`, describeRoute({
82
+ summary: "Run the classifier again on the latest inbound message.",
83
+ verification: "control-plane",
84
+ scopes: [SUPPORT_THREADS_RECLASSIFY_SCOPE],
85
+ responses: { 200: SupportReclassifiedResponse }
86
+ }), requireControlPlane(SUPPORT_THREADS_RECLASSIFY_SCOPE), validate("param", ThreadIdParam), async (c) => c.json(await reclassifyConversation(await resolve(c), c.req.valid("param").id, viewer(c))));
87
+ app.post(`${base}/threads/:id/flags`, describeRoute({
88
+ summary: "Set the caller's private read and snooze flags.",
89
+ verification: "control-plane",
90
+ scopes: [SUPPORT_THREADS_FLAG_SCOPE],
91
+ responses: { 200: SupportFlagsResponse }
92
+ }), requireControlPlane(SUPPORT_THREADS_FLAG_SCOPE), validate("param", ThreadIdParam), validate("json", FlagsInput), async (c) => c.json(await updateFlags(await resolve(c), c.req.valid("param").id, c.req.valid("json"), viewer(c))));
57
93
  if (!submission) return;
58
- app.post(`${base}/feedback`, requireAuth(), requireSameOrigin(), zValidator("json", SubmitFeedbackInput, validationHook), async (c) => c.json(await submitFeedbackRequest(await resolve(c), c.req.valid("json"), submitter(c)), 201));
59
- app.get(`${base}/feedback`, requireAuth(), zValidator("query", MyThreadsQuery, validationHook), async (c) => c.json(await listMyThreads(await resolve(c), c.req.valid("query"), submitter(c))));
60
- app.get(`${base}/feedback/:id`, requireAuth(), zValidator("param", ThreadIdParam, validationHook), async (c) => c.json(await readMyThread(await resolve(c), c.req.valid("param").id, submitter(c))));
94
+ app.post(`${base}/feedback`, describeRoute({
95
+ summary: "Write in to support from the app.",
96
+ verification: ["bearer", "session"],
97
+ responses: { 201: SupportSubmissionResponse }
98
+ }), requireAuth(), requireSameOrigin(), validate("json", SubmitFeedbackInput), async (c) => c.json(await submitFeedbackRequest(await resolve(c), c.req.valid("json"), submitter(c)), 201));
99
+ app.get(`${base}/feedback`, describeRoute({
100
+ summary: "List the caller's own support requests.",
101
+ verification: ["bearer", "session"],
102
+ responses: { 200: SupportMyThreadsResponse }
103
+ }), requireAuth(), validate("query", MyThreadsQuery), async (c) => c.json(await listMyThreads(await resolve(c), c.req.valid("query"), submitter(c))));
104
+ app.get(`${base}/feedback/:id`, describeRoute({
105
+ summary: "Read one of the caller's own support requests.",
106
+ verification: ["bearer", "session"],
107
+ responses: { 200: SupportMyThreadResponse }
108
+ }), requireAuth(), validate("param", ThreadIdParam), async (c) => c.json(await readMyThread(await resolve(c), c.req.valid("param").id, submitter(c))));
61
109
  };
62
110
  }
63
111
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/inbound/handler.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,0CAA0C,CAAC;AAEvF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAMnD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,aAAa,GAAG,sBAAsB,CAkEvF"}
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/inbound/handler.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,0CAA0C,CAAC;AAEvF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAMnD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,aAAa,GAAG,sBAAsB,CAiEvF"}
@@ -57,7 +57,6 @@ function createSupportEmailHandler(wiring) {
57
57
  db: supportDatabase(d1),
58
58
  config: wiring.config,
59
59
  bucket: bindings.SUPPORT_BUCKET,
60
- fts: wiring.config.search.fts,
61
60
  dispatchClassify: makeClassifyDispatcher(env, log),
62
61
  linkSender: (address) => resolveSenderUserId(d1, address, wiring.peers),
63
62
  emit: wiring.emit,
@@ -72,8 +72,6 @@ export interface IngestDeps {
72
72
  config: SupportConfig;
73
73
  /** The R2 bucket raw messages and attachments are written to. Absent means neither is kept. */
74
74
  bucket?: R2Bucket;
75
- /** Whether the FTS5 index is composed. False means search runs as a `LIKE` scan and nothing is indexed. */
76
- fts: boolean;
77
75
  /**
78
76
  * Start the classification Workflow for a stored message. Allowed to fail and allowed to decline —
79
77
  * an unprovisioned project has no binding, and a thread that stays `uncategorized` is a legitimate
@@ -1 +1 @@
1
- {"version":3,"file":"ingest.d.ts","sourceRoot":"","sources":["../../src/inbound/ingest.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAC;AAEnE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kCAAkC,CAAC;AAG/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAGtD,OAAO,EAIL,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AAWxB;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,uBAAuB;AACvB,MAAM,MAAM,aAAa;AACvB,oFAAoF;AAClF;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,eAAe,CAAA;CAAE;AAC7C,wDAAwD;GACtD;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,UAAU,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE;AAC3D,yEAAyE;GACvE;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE;AACzE,cAAc;GACZ;IACE,OAAO,EAAE,IAAI,CAAC;IACd,SAAS,EAAE,KAAK,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,SAAS,EAAE,OAAO,CAAC;IACnB,8EAA8E;IAC9E,WAAW,EAAE,MAAM,CAAC;IACpB,iGAAiG;IACjG,WAAW,EAAE,OAAO,CAAC;CACtB,CAAC;AAEN,+CAA+C;AAC/C,MAAM,WAAW,UAAU;IACzB,0BAA0B;IAC1B,EAAE,EAAE,eAAe,CAAC;IACpB,2BAA2B;IAC3B,MAAM,EAAE,aAAa,CAAC;IACtB,+FAA+F;IAC/F,MAAM,CAAC,EAAE,QAAQ,CAAC;IAClB,2GAA2G;IAC3G,GAAG,EAAE,OAAO,CAAC;IACb;;;;;OAKG;IACH,gBAAgB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1D,oFAAoF;IACpF,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACxD,sBAAsB;IACtB,IAAI,EAAE,SAAS,CAAC;IAChB,qCAAqC;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,yBAAyB;IACzB,KAAK,EAAE,MAAM,MAAM,CAAC;IACpB,WAAW;IACX,GAAG,EAAE,MAAM,IAAI,CAAC;CACjB;AAED,iFAAiF;AACjF,MAAM,WAAW,WAAW;IAC1B,0BAA0B;IAC1B,GAAG,EAAE,WAAW,CAAC;IACjB,wGAAwG;IACxG,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8FAA8F;IAC9F,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AA0ID,kCAAkC;AAClC,wBAAsB,aAAa,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,CAAC,CAsShG"}
1
+ {"version":3,"file":"ingest.d.ts","sourceRoot":"","sources":["../../src/inbound/ingest.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAC;AAEnE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kCAAkC,CAAC;AAG/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAGtD,OAAO,EAIL,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AAUxB;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,uBAAuB;AACvB,MAAM,MAAM,aAAa;AACvB,oFAAoF;AAClF;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,eAAe,CAAA;CAAE;AAC7C,wDAAwD;GACtD;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,UAAU,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE;AAC3D,yEAAyE;GACvE;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE;AACzE,cAAc;GACZ;IACE,OAAO,EAAE,IAAI,CAAC;IACd,SAAS,EAAE,KAAK,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,SAAS,EAAE,OAAO,CAAC;IACnB,8EAA8E;IAC9E,WAAW,EAAE,MAAM,CAAC;IACpB,iGAAiG;IACjG,WAAW,EAAE,OAAO,CAAC;CACtB,CAAC;AAEN,+CAA+C;AAC/C,MAAM,WAAW,UAAU;IACzB,0BAA0B;IAC1B,EAAE,EAAE,eAAe,CAAC;IACpB,2BAA2B;IAC3B,MAAM,EAAE,aAAa,CAAC;IACtB,+FAA+F;IAC/F,MAAM,CAAC,EAAE,QAAQ,CAAC;IAClB;;;;;OAKG;IACH,gBAAgB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1D,oFAAoF;IACpF,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACxD,sBAAsB;IACtB,IAAI,EAAE,SAAS,CAAC;IAChB,qCAAqC;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,yBAAyB;IACzB,KAAK,EAAE,MAAM,MAAM,CAAC;IACpB,WAAW;IACX,GAAG,EAAE,MAAM,IAAI,CAAC;CACjB;AAED,iFAAiF;AACjF,MAAM,WAAW,WAAW;IAC1B,0BAA0B;IAC1B,GAAG,EAAE,WAAW,CAAC;IACjB,wGAAwG;IACxG,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8FAA8F;IAC9F,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AA0ID,kCAAkC;AAClC,wBAAsB,aAAa,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,CAAC,CA2RhG"}
@@ -7,7 +7,6 @@ import { SUPPORT_ATTACHMENTS_TABLE, SUPPORT_MESSAGES_TABLE, SUPPORT_THREADS_TABL
7
7
  import { attachmentKey, putAttachment, rawMessageKey, sha256Hex } from "../attachment/store.js";
8
8
  import { SupportAuditActions } from "../audit/actions.js";
9
9
  import { parentCandidates } from "../mime/threading.js";
10
- import { indexMessage } from "../store/search.js";
11
10
  import { truncateToBytes } from "../mime/truncate.js";
12
11
  import { MAX_TEXT_BODY, parseInbound } from "../mime/parse.js";
13
12
  import { checkRates, checkSize } from "./guard.js";
@@ -283,19 +282,6 @@ async function ingestInbound(deps, input) {
283
282
  });
284
283
  throw error;
285
284
  }
286
- if (deps.fts) try {
287
- await indexMessage(deps.db, {
288
- threadId,
289
- messageId,
290
- subject,
291
- body: text
292
- });
293
- } catch (error) {
294
- deps.log.warn("support message not indexed", {
295
- messageId,
296
- error
297
- });
298
- }
299
285
  let attachments = 0;
300
286
  try {
301
287
  attachments = await storeAttachments(deps, parsed, threadId, messageId, now);
@@ -48,6 +48,13 @@ export declare function supportRoutingRuleName(project: string): string;
48
48
  * audits and deletes under cannot drift from the name it deploys under.
49
49
  */
50
50
  export declare function supportWorkerName(project: string, env: ManagedEnvironment): string;
51
+ /**
52
+ * The Workflows the classification worker is deployed with in one environment —
53
+ * `<project>-<env>-support-classify`. Derived by the call `resolveSupportConfig` deploys with, so teardown
54
+ * deletes the names provision created and no others (#789). Deleting a Worker does not delete the
55
+ * Workflows registered against it.
56
+ */
57
+ export declare function supportWorkflowNames(project: string, env: ManagedEnvironment): string[];
51
58
  /** The live Cloudflare/wrangler seam. Each step must be idempotent. */
52
59
  export interface SupportProvisioner {
53
60
  /**
@@ -69,17 +76,25 @@ export interface SupportProvisioner {
69
76
  /** Deploy the prebuilt classification worker for this environment. */
70
77
  deployWorker(env: ManagedEnvironment): Promise<void>;
71
78
  /**
72
- * Create or drop the full-text index in this environment's app database, to match `search.fts`.
79
+ * Create or drop the full-text index **and its triggers** in this environment's app database, to
80
+ * match `search.fts`.
73
81
  *
74
82
  * A provisioning step rather than a migration, because the index is **derived** — every row in it
75
83
  * comes from `pithy_support_messages` and `reindexThread` rebuilds it on demand. That is the line a
76
84
  * migration is for: schema whose loss loses data. Keeping it here is also what makes the flag safe
77
85
  * to toggle at all, since a config-conditional migration removed from the set is corruption to
78
- * Kysely and blocks `pithy migrate` for every capability sharing the database.
86
+ * Kysely and blocks `pithy migrate` for every capability sharing the database. The triggers inherit
87
+ * that constraint exactly: they exist only where the table does, so they are provisioning too.
88
+ *
89
+ * **Three outcomes, not two.** `repaired` is a database whose table was already there and whose
90
+ * triggers were not — an adopter who took the release that moved the index onto triggers and deployed
91
+ * without re-provisioning. Nothing in the kit maintains that index, so the run creates the triggers
92
+ * and backfills the gap, and says which of the three it did.
79
93
  */
80
94
  ensureSearchIndex(env: ManagedEnvironment): Promise<{
81
95
  created: boolean;
82
96
  dropped: boolean;
97
+ repaired: boolean;
83
98
  }>;
84
99
  /**
85
100
  * Ensure the inbound Email Routing rule that delivers the support address to the app worker.
@@ -100,11 +115,12 @@ export interface SupportProvisionResult {
100
115
  };
101
116
  /** The environments a classification worker was deployed for. */
102
117
  environments: ManagedEnvironment[];
103
- /** What the full-text index did, per environment — created, dropped, or already correct. */
118
+ /** What the full-text index did, per environment — created, dropped, repaired, or already correct. */
104
119
  search: Array<{
105
120
  env: ManagedEnvironment;
106
121
  created: boolean;
107
122
  dropped: boolean;
123
+ repaired: boolean;
108
124
  }>;
109
125
  /** Whether the inbound routing rule was created, already present, or skipped. */
110
126
  routing: {
@@ -136,6 +152,11 @@ export interface SupportDeprovisioner {
136
152
  * environment before it removes the bucket or the rule they share (#591).
137
153
  */
138
154
  hasWorker(env: ManagedEnvironment): Promise<boolean>;
155
+ /**
156
+ * Delete the env's classification Workflows, by {@link supportWorkflowNames}. Idempotent (a Workflow already
157
+ * gone is a no-op). Deleting the worker leaves them registered, so they go first (#789).
158
+ */
159
+ deleteWorkflows(env: ManagedEnvironment): Promise<void>;
139
160
  /** Delete the env's classification worker. Idempotent. */
140
161
  deleteWorker(env: ManagedEnvironment): Promise<void>;
141
162
  /**
@@ -178,7 +199,7 @@ export interface SupportDeprovisionResult {
178
199
  * is every environment's support history — so they go with the last environment, never a staging teardown.
179
200
  * 3. Remove the routing rule, when asked, **first**: stop new mail arriving before removing the worker that would
180
201
  * have handled it.
181
- * 4. Delete the environment's worker, then — only when asked — the bucket and everything in it.
202
+ * 4. Delete the environment's Workflows, then its worker, then — only when asked — the bucket and everything in it.
182
203
  */
183
204
  export declare function deprovisionSupport(deprovisioner: SupportDeprovisioner, target: DeprovisionTarget, options?: SupportDeprovisionOptions): Promise<SupportDeprovisionResult>;
184
205
  //# sourceMappingURL=provisionSupport.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"provisionSupport.d.ts","sourceRoot":"","sources":["../../src/provision/provisionSupport.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"provisionSupport.d.ts","sourceRoot":"","sources":["../../src/provision/provisionSupport.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAC;AAKlF,OAAO,EAEL,KAAK,iBAAiB,EAEtB,KAAK,kBAAkB,EAExB,MAAM,6BAA6B,CAAC;AAGrC;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,kBAAkB,GAAG,MAAM,CAElF;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,kBAAkB,GAAG,MAAM,EAAE,CAIvF;AAED,uEAAuE;AACvE,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B;;;;;OAKG;IACH,YAAY,IAAI,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAChF,sEAAsE;IACtE,YAAY,CAAC,GAAG,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD;;;;;;;;;;;;;;;OAeG;IACH,iBAAiB,CAAC,GAAG,EAAE,kBAAkB,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC/G;;;OAGG;IACH,iBAAiB,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CACtE;AAED,kCAAkC;AAClC,MAAM,WAAW,sBAAsB;IACrC,uDAAuD;IACvD,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IAC/D,iEAAiE;IACjE,YAAY,EAAE,kBAAkB,EAAE,CAAC;IACnC,sGAAsG;IACtG,MAAM,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,kBAAkB,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAClG,iFAAiF;IACjF,OAAO,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;CACjD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,gBAAgB,CACpC,WAAW,EAAE,kBAAkB,EAC/B,YAAY,EAAE,oBAAoB,GAAG,SAAS,MAAM,EAAE,GACrD,OAAO,CAAC,sBAAsB,CAAC,CAiBjC;AAED,4FAA4F;AAC5F,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,SAAS,CAAC,GAAG,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACrD;;;OAGG;IACH,eAAe,CAAC,GAAG,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,0DAA0D;IAC1D,YAAY,CAAC,GAAG,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD;;;OAGG;IACH,iBAAiB,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACnD;;;OAGG;IACH,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B;AAED,+FAA+F;AAC/F,MAAM,WAAW,yBAAyB;IACxC,sGAAsG;IACtG,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,gGAAgG;IAChG,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,mCAAmC;AACnC,MAAM,WAAW,wBAAwB;IACvC,qCAAqC;IACrC,GAAG,EAAE,kBAAkB,CAAC;IACxB,yDAAyD;IACzD,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,kBAAkB,CACtC,aAAa,EAAE,oBAAoB,EACnC,MAAM,EAAE,iBAAiB,EACzB,OAAO,GAAE,yBAA8B,GACtC,OAAO,CAAC,wBAAwB,CAAC,CAWnC"}
@@ -1,9 +1,10 @@
1
1
  // SPDX-FileCopyrightText: 2026 Pithy
2
2
  // SPDX-License-Identifier: MIT
3
- import { SUPPORT_CAPABILITY } from "../workflows/specs.js";
3
+ import { SUPPORT_CAPABILITY, supportWorkflowRegistry } from "../workflows/specs.js";
4
4
  import { GLOBAL_SCOPE } from "@pithy-sh/core/src/naming/environment";
5
5
  import { resourceName } from "@pithy-sh/core/src/naming/resource";
6
6
  import { resourceNames } from "@pithy-sh/core/src/naming/resourceNames";
7
+ import { hostWorkflowsFor } from "@pithy-sh/core/src/workflow/host";
7
8
  import { assertSharedLeavesLast, deprovisionTarget, managedEnvironments } from "@pithy-sh/secrets/src/scope";
8
9
  //#region src/provision/provisionSupport.ts
9
10
  /**
@@ -61,6 +62,19 @@ function supportWorkerName(project, env) {
61
62
  return resourceNames(project).env(env).worker(SUPPORT_CAPABILITY);
62
63
  }
63
64
  /**
65
+ * The Workflows the classification worker is deployed with in one environment —
66
+ * `<project>-<env>-support-classify`. Derived by the call `resolveSupportConfig` deploys with, so teardown
67
+ * deletes the names provision created and no others (#789). Deleting a Worker does not delete the
68
+ * Workflows registered against it.
69
+ */
70
+ function supportWorkflowNames(project, env) {
71
+ return hostWorkflowsFor(supportWorkflowRegistry, {
72
+ project,
73
+ capability: SUPPORT_CAPABILITY,
74
+ env
75
+ }).workflows.map((workflow) => workflow.name);
76
+ }
77
+ /**
64
78
  * Provision the support infrastructure.
65
79
  *
66
80
  * The order matters and is the inverse of how it fails: the bucket exists before a worker that could
@@ -111,7 +125,7 @@ async function provisionSupport(provisioner, environments) {
111
125
  * is every environment's support history — so they go with the last environment, never a staging teardown.
112
126
  * 3. Remove the routing rule, when asked, **first**: stop new mail arriving before removing the worker that would
113
127
  * have handled it.
114
- * 4. Delete the environment's worker, then — only when asked — the bucket and everything in it.
128
+ * 4. Delete the environment's Workflows, then its worker, then — only when asked — the bucket and everything in it.
115
129
  */
116
130
  async function deprovisionSupport(deprovisioner, target, options = {}) {
117
131
  const env = deprovisionTarget(target);
@@ -123,6 +137,7 @@ async function deprovisionSupport(deprovisioner, target, options = {}) {
123
137
  flag: "--routing-zone"
124
138
  }] : []]);
125
139
  const routingRuleRemoved = options.removeRouting ? (await deprovisioner.removeRoutingRule()).removed : false;
140
+ await deprovisioner.deleteWorkflows(env);
126
141
  await deprovisioner.deleteWorker(env);
127
142
  if (options.deleteStorage) await deprovisioner.deleteBucket();
128
143
  return {
@@ -131,4 +146,4 @@ async function deprovisionSupport(deprovisioner, target, options = {}) {
131
146
  };
132
147
  }
133
148
  //#endregion
134
- export { deprovisionSupport, provisionSupport, supportRoutingRuleName, supportWorkerName };
149
+ export { deprovisionSupport, provisionSupport, supportRoutingRuleName, supportWorkerName, supportWorkflowNames };
@@ -78,8 +78,6 @@ export interface ReplyDeps {
78
78
  }) => Promise<{
79
79
  jobId: string;
80
80
  }>;
81
- /** Whether the full-text index is in use. */
82
- fts: boolean;
83
81
  /** The audit seam. */
84
82
  emit: AuditEmit;
85
83
  /** The request logger. */
@@ -1 +1 @@
1
- {"version":3,"file":"send.d.ts","sourceRoot":"","sources":["../../src/reply/send.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAC;AACnE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kCAAkC,CAAC;AAE/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,OAAO,EAAiD,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAKrG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAEH,0CAA0C;AAC1C,MAAM,WAAW,SAAS;IACxB,0BAA0B;IAC1B,EAAE,EAAE,eAAe,CAAC;IACpB,2BAA2B;IAC3B,MAAM,EAAE,aAAa,CAAC;IACtB;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE;QAChB,EAAE,EAAE,MAAM,CAAC;QACX,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,+FAA+F;QAC/F,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,KAAK,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjC,6CAA6C;IAC7C,GAAG,EAAE,OAAO,CAAC;IACb,sBAAsB;IACtB,IAAI,EAAE,SAAS,CAAC;IAChB,0BAA0B;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,yBAAyB;IACzB,KAAK,EAAE,MAAM,MAAM,CAAC;IACpB,WAAW;IACX,GAAG,EAAE,MAAM,IAAI,CAAC;CACjB;AAED,oCAAoC;AACpC,MAAM,WAAW,UAAU;IACzB,iCAAiC;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GACnB;IACE,0DAA0D;IAC1D,OAAO,EAAE,OAAO,CAAC;IACjB,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAC;CACf,GACD;IACE,0FAA0F;IAC1F,OAAO,EAAE,KAAK,CAAC;IACf,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEN,gCAAgC;AAChC,wBAAsB,SAAS,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CA2LxF"}
1
+ {"version":3,"file":"send.d.ts","sourceRoot":"","sources":["../../src/reply/send.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAC;AAEnE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kCAAkC,CAAC;AAE/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,OAAO,EAAiD,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAIrG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAEH,0CAA0C;AAC1C,MAAM,WAAW,SAAS;IACxB,0BAA0B;IAC1B,EAAE,EAAE,eAAe,CAAC;IACpB,2BAA2B;IAC3B,MAAM,EAAE,aAAa,CAAC;IACtB;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE;QAChB,EAAE,EAAE,MAAM,CAAC;QACX,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,+FAA+F;QAC/F,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,KAAK,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjC,sBAAsB;IACtB,IAAI,EAAE,SAAS,CAAC;IAChB,0BAA0B;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,yBAAyB;IACzB,KAAK,EAAE,MAAM,MAAM,CAAC;IACpB,WAAW;IACX,GAAG,EAAE,MAAM,IAAI,CAAC;CACjB;AAED,oCAAoC;AACpC,MAAM,WAAW,UAAU;IACzB,iCAAiC;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GACnB;IACE,0DAA0D;IAC1D,OAAO,EAAE,OAAO,CAAC;IACjB,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAC;CACf,GACD;IACE,0FAA0F;IAC1F,OAAO,EAAE,KAAK,CAAC;IACf,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEN,gCAAgC;AAChC,wBAAsB,SAAS,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAkLxF"}
@@ -5,7 +5,7 @@ import { SupportMessage } from "../data/message.js";
5
5
  import { SUPPORT_MESSAGES_TABLE, SUPPORT_THREADS_TABLE } from "../data/tables.js";
6
6
  import { SupportAuditActions } from "../audit/actions.js";
7
7
  import { buildReferencesHeader, replySubject } from "../mime/threading.js";
8
- import { indexMessage } from "../store/search.js";
8
+ import { commandName } from "@pithy-sh/core/src/cli/command";
9
9
  //#region src/reply/send.ts
10
10
  /** Send a reply on a thread. */
11
11
  async function sendReply(deps, input) {
@@ -60,7 +60,7 @@ async function sendReply(deps, input) {
60
60
  if (!inApp) {
61
61
  if (!enqueue) throw new SupportReplyFailedError({
62
62
  message: "This deployment cannot send mail.",
63
- action: "Add the email capability (`pithy add email`) and provision it, then retry.",
63
+ action: `Compose the email capability into this Worker with \`${commandName("add")}\` and provision it, then retry.`,
64
64
  detail: `thread ${input.threadId} needs a mailed reply and no email capability is composed`
65
65
  });
66
66
  if (!replyTo) throw new SupportReplyFailedError({
@@ -110,19 +110,6 @@ async function sendReply(deps, input) {
110
110
  createdAt: now
111
111
  };
112
112
  await deps.db.insertInto(SUPPORT_MESSAGES_TABLE).values(SupportMessage.encode(message)).execute();
113
- if (deps.fts) try {
114
- await indexMessage(deps.db, {
115
- threadId: input.threadId,
116
- messageId,
117
- subject,
118
- body: input.body
119
- });
120
- } catch (error) {
121
- deps.log.warn("support reply not indexed", {
122
- messageId,
123
- error
124
- });
125
- }
126
113
  await deps.db.updateTable(SUPPORT_THREADS_TABLE).set((eb) => ({
127
114
  messageCount: eb("messageCount", "+", 1),
128
115
  lastMessageAt: now.getTime(),
@@ -26,16 +26,23 @@ export declare function searchPredicate(term: string, options: {
26
26
  /**
27
27
  * Add a message to the full-text index, replacing any row already there for it.
28
28
  *
29
- * **Remove-then-insert, not a bare insert** — the same shape the CMS `syncFts` uses, and for the
30
- * reason application-managed indexes always need it: an FTS5 table has no primary key and no unique
31
- * constraint to lean on, so a second call for the same message would silently add a second copy and
32
- * the thread would start appearing twice in its own search results. Making the write idempotent means
33
- * a retry, a reindex, and a first index are all the same operation, which is the property every other
34
- * write path in this repo is built on.
29
+ * **No write path calls this any more.** The index is maintained by the triggers `createSearchIndex`
30
+ * provisions (`store/searchIndex.ts`), so a message is indexed because it was written rather than
31
+ * because the code that wrote it remembered. This is the **repair** primitive now: {@link reindexThread}
32
+ * and {@link reindexAll} are its only callers in the kit, and it stays exported because
33
+ * `package.json` exports `./src/*`, so an adopter may be reaching it.
35
34
  *
36
- * Best-effort by contract: a failed index write must never lose a customer's message, so the caller
37
- * logs and carries on — the row is still there, still readable, still repairable by
38
- * {@link reindexThread}, and only missing from one search box.
35
+ * **Remove-then-insert, not a bare insert** — the same shape the trigger bodies use, and for the
36
+ * reason an FTS5 table always needs it: it has no primary key and no unique constraint to lean on, so
37
+ * a second call for the same message would silently add a second copy and the thread would start
38
+ * appearing twice in its own search results. Making the write idempotent means a retry, a reindex, and
39
+ * a first index are all the same operation.
40
+ *
41
+ * **It no longer carries a swallow contract, because it is no longer on a message write.** A failure
42
+ * here is a failed repair, and the caller is a command or a route that can say so. The durability
43
+ * question moved with the index itself: a trigger runs inside the message's own statement, so a
44
+ * failure in its body takes the message write down with it — which is what `searchIndex.ts` documents
45
+ * and what the triggers' own tests hold.
39
46
  */
40
47
  export declare function indexMessage(db: SupportDatabase, entry: {
41
48
  threadId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/store/search.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,OAAO,EAAO,MAAM,QAAQ,CAAC;AAC5D,OAAO,EAA0B,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AA+B9E;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAS7C;AAED,oEAAoE;AACpE,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,CAEhE;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,GAAG,EAAE,OAAO,CAAA;CAAE,GAAG,UAAU,CAAC,OAAO,CAAC,CAkB5F;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,YAAY,CAChC,EAAE,EAAE,eAAe,EACnB,KAAK,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC5E,OAAO,CAAC,IAAI,CAAC,CAMf;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,EAAE,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAkB1F;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,UAAU,CAC9B,EAAE,EAAE,eAAe,EACnB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CAAO,GAC1E,OAAO,CAAC,MAAM,CAAC,CA4CjB"}
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/store/search.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,OAAO,EAAO,MAAM,QAAQ,CAAC;AAC5D,OAAO,EAA0B,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AA+B9E;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAS7C;AAED,oEAAoE;AACpE,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,CAEhE;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,GAAG,EAAE,OAAO,CAAA;CAAE,GAAG,UAAU,CAAC,OAAO,CAAC,CAkB5F;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,YAAY,CAChC,EAAE,EAAE,eAAe,EACnB,KAAK,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC5E,OAAO,CAAC,IAAI,CAAC,CAMf;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,EAAE,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAkB1F;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,UAAU,CAC9B,EAAE,EAAE,eAAe,EACnB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CAAO,GAC1E,OAAO,CAAC,MAAM,CAAC,CA4CjB"}
@@ -67,16 +67,23 @@ function searchPredicate(term, options) {
67
67
  /**
68
68
  * Add a message to the full-text index, replacing any row already there for it.
69
69
  *
70
- * **Remove-then-insert, not a bare insert** — the same shape the CMS `syncFts` uses, and for the
71
- * reason application-managed indexes always need it: an FTS5 table has no primary key and no unique
72
- * constraint to lean on, so a second call for the same message would silently add a second copy and
73
- * the thread would start appearing twice in its own search results. Making the write idempotent means
74
- * a retry, a reindex, and a first index are all the same operation, which is the property every other
75
- * write path in this repo is built on.
70
+ * **No write path calls this any more.** The index is maintained by the triggers `createSearchIndex`
71
+ * provisions (`store/searchIndex.ts`), so a message is indexed because it was written rather than
72
+ * because the code that wrote it remembered. This is the **repair** primitive now: {@link reindexThread}
73
+ * and {@link reindexAll} are its only callers in the kit, and it stays exported because
74
+ * `package.json` exports `./src/*`, so an adopter may be reaching it.
76
75
  *
77
- * Best-effort by contract: a failed index write must never lose a customer's message, so the caller
78
- * logs and carries on — the row is still there, still readable, still repairable by
79
- * {@link reindexThread}, and only missing from one search box.
76
+ * **Remove-then-insert, not a bare insert** — the same shape the trigger bodies use, and for the
77
+ * reason an FTS5 table always needs it: it has no primary key and no unique constraint to lean on, so
78
+ * a second call for the same message would silently add a second copy and the thread would start
79
+ * appearing twice in its own search results. Making the write idempotent means a retry, a reindex, and
80
+ * a first index are all the same operation.
81
+ *
82
+ * **It no longer carries a swallow contract, because it is no longer on a message write.** A failure
83
+ * here is a failed repair, and the caller is a command or a route that can say so. The durability
84
+ * question moved with the index itself: a trigger runs inside the message's own statement, so a
85
+ * failure in its body takes the message write down with it — which is what `searchIndex.ts` documents
86
+ * and what the triggers' own tests hold.
80
87
  */
81
88
  async function indexMessage(db, entry) {
82
89
  await sql`delete from pithy_support_search where message_id = ${entry.messageId}`.execute(db);