@maudecode/kanvil 0.4.4 → 0.5.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 (118) hide show
  1. package/apps/server/.env.example +5 -0
  2. package/apps/server/dist/{auth-B-Tz8SF4.mjs → auth-Bm6cIseE.mjs} +314 -127
  3. package/apps/server/dist/client.mjs +10 -9
  4. package/apps/server/dist/{config-CRADzIvL.mjs → config-CrJuk5mY.mjs} +15 -7
  5. package/apps/server/dist/index.mjs +98 -30
  6. package/apps/server/dist/migrate.mjs +2 -2
  7. package/apps/server/dist/recover.mjs +4 -4
  8. package/apps/server/dist/{src-BCtZ3wbK.mjs → src-D80LNZKP.mjs} +58 -20
  9. package/apps/web/dist/assets/{CompositeItem-DvEBmVYM.js → CompositeItem-Tueq2VKR.js} +1 -1
  10. package/apps/web/dist/assets/{Trans-NwgJNiLl.js → Trans-BgU0eWAX.js} +1 -1
  11. package/apps/web/dist/assets/admin-settings-Ckis3XJG.js +1 -0
  12. package/apps/web/dist/assets/{archive-restore-j20QLQCl.js → archive-restore-CSpoH6mS.js} +1 -1
  13. package/apps/web/dist/assets/az-AZ-bFxpMMZN.js +1 -0
  14. package/apps/web/dist/assets/{az-Dj0LhQ5O.js → az-DUcDY8Bp.js} +1 -1
  15. package/apps/web/dist/assets/{corner-left-up-BAGDaT_1.js → corner-left-up-C3OGlXIB.js} +1 -1
  16. package/apps/web/dist/assets/{de-BWVFKRmU.js → de-89C1V8Ut.js} +1 -1
  17. package/apps/web/dist/assets/de-DE-BeXI30Ll.js +1 -0
  18. package/apps/web/dist/assets/default-Dnep8Yat.js +56 -0
  19. package/apps/web/dist/assets/{delivery-settings-B26dRk_O.js → delivery-settings-cMldZ9K7.js} +1 -1
  20. package/apps/web/dist/assets/{el-IYSYQmsa.js → el-D6CICkhi.js} +1 -1
  21. package/apps/web/dist/assets/el-GR-SgRHlWhY.js +1 -0
  22. package/apps/web/dist/assets/{ellipsis-Cx1SIn8a.js → ellipsis-lJJU8zB7.js} +1 -1
  23. package/apps/web/dist/assets/{es-C3DJP0aS.js → es-B20Izib2.js} +1 -1
  24. package/apps/web/dist/assets/es-ES-BF1J5vap.js +1 -0
  25. package/apps/web/dist/assets/{forms-BwtRqvGj.js → forms--87R7VQW.js} +1 -1
  26. package/apps/web/dist/assets/{fr-BlVUPa15.js → fr-DVlGnGsu.js} +1 -1
  27. package/apps/web/dist/assets/fr-FR-D_sldwf6.js +1 -0
  28. package/apps/web/dist/assets/{hi-CINzKB6O.js → hi-D1F5Pi_I.js} +1 -1
  29. package/apps/web/dist/assets/hi-IN-CfOw4TWf.js +1 -0
  30. package/apps/web/dist/assets/{highlight-XHbOOx7A.js → highlight-ygDJPvRy.js} +1 -1
  31. package/apps/web/dist/assets/id-ID-CLKtHO7Q.js +1 -0
  32. package/apps/web/dist/assets/{id-CpyQwNyO.js → id-LESXKW7G.js} +1 -1
  33. package/apps/web/dist/assets/index-Bn-MADyj.css +2 -0
  34. package/apps/web/dist/assets/index-Ds3IHeIi.js +17 -0
  35. package/apps/web/dist/assets/{invitations-Ch02KEAB.js → invitations-C5WbdDUQ.js} +1 -1
  36. package/apps/web/dist/assets/{it-CxMkGyiF.js → it-CgRGAs6u.js} +1 -1
  37. package/apps/web/dist/assets/it-IT-eVZNW5qX.js +1 -0
  38. package/apps/web/dist/assets/{item-CRS9Ai9p.js → item-BD1Poxfs.js} +1 -1
  39. package/apps/web/dist/assets/{ja-D72d7qkv.js → ja-B7arhzgX.js} +1 -1
  40. package/apps/web/dist/assets/ja-JP-D96njn-P.js +1 -0
  41. package/apps/web/dist/assets/ko-KR-Bash_ieA.js +1 -0
  42. package/apps/web/dist/assets/{ko-BHuNjRQ3.js → ko-Ro_UonyA.js} +1 -1
  43. package/apps/web/dist/assets/{label-settings-tE57CalJ.js → label-settings-BaikCfal.js} +1 -1
  44. package/apps/web/dist/assets/{markdown-blocks-CN-hJdJS.js → markdown-blocks-toSPlHSc.js} +1 -1
  45. package/apps/web/dist/assets/{member-directory-CoLB-2Oc.js → member-directory-BjUy1KuK.js} +1 -1
  46. package/apps/web/dist/assets/{mk-D3TPH_Mz.js → mk-DR3kmrIp.js} +1 -1
  47. package/apps/web/dist/assets/mk-MK-BWQWGIRx.js +1 -0
  48. package/apps/web/dist/assets/{nl-BHJbk-Wr.js → nl-D3uRJJLT.js} +1 -1
  49. package/apps/web/dist/assets/nl-NL-D1tvo0Gd.js +1 -0
  50. package/apps/web/dist/assets/{pl-JW_P_Gb9.js → pl-CtzDOLdV.js} +1 -1
  51. package/apps/web/dist/assets/pl-PL-DNgGUEcQ.js +1 -0
  52. package/apps/web/dist/assets/{project-lifecycle-DuV33mlL.js → project-lifecycle-BBHcRi6p.js} +1 -1
  53. package/apps/web/dist/assets/project-settings-BaYuxYjg.js +1 -0
  54. package/apps/web/dist/assets/pt-BR-CZsUZQ6P.js +1 -0
  55. package/apps/web/dist/assets/{pt-BR-DWL_sEp3.js → pt-BR-FarXHHuf.js} +1 -1
  56. package/apps/web/dist/assets/{public-project-M93Zpx_e.js → public-project-DGf3nkg2.js} +1 -1
  57. package/apps/web/dist/assets/{rotate-ccw-DcHaluuA.js → rotate-ccw-Byx7O3RH.js} +1 -1
  58. package/apps/web/dist/assets/{ru-DQNby7hW.js → ru-DHMEVxvB.js} +1 -1
  59. package/apps/web/dist/assets/ru-RU-4ew92YE_.js +1 -0
  60. package/apps/web/dist/assets/{search-DQtwggUH.js → search-DwsPEC4E.js} +35 -35
  61. package/apps/web/dist/assets/{send-gpGpxsCa.js → send-DlGV2XQS.js} +1 -1
  62. package/apps/web/dist/assets/settings-C_sN8Y1h.js +1 -0
  63. package/apps/web/dist/assets/{sliders-horizontal-BxgR4pFt.js → sliders-horizontal-CEHGGft_.js} +1 -1
  64. package/apps/web/dist/assets/{switch-UPVGS5kv.js → switch-B_rsZYBf.js} +1 -1
  65. package/apps/web/dist/assets/{table-D8iAxhJH.js → table-2AEfwytk.js} +1 -1
  66. package/apps/web/dist/assets/ticket-detail-C2l8nwF-.js +2 -0
  67. package/apps/web/dist/assets/ticket-editor-B0UB98HS.js +2 -0
  68. package/apps/web/dist/assets/ticket-list-D_MHudj1.js +6 -0
  69. package/apps/web/dist/assets/{ticket-list-controls-ua3m0uRB.js → ticket-list-controls-jKg4i-NA.js} +1 -1
  70. package/apps/web/dist/assets/{tr-BNZEkwqd.js → tr-CjdSDu0z.js} +1 -1
  71. package/apps/web/dist/assets/tr-TR-CM381XCw.js +1 -0
  72. package/apps/web/dist/assets/{uk-Dmlqvk63.js → uk-C8od6u_H.js} +1 -1
  73. package/apps/web/dist/assets/uk-UA-AeOiWCVo.js +1 -0
  74. package/apps/web/dist/assets/{upload-Cu0J8ZFJ.js → upload-BeTAGU_W.js} +1 -1
  75. package/apps/web/dist/assets/{useQueries-BEzeS0hU.js → useQueries-BtRgTciJ.js} +1 -1
  76. package/apps/web/dist/assets/{vi-KALEc3bk.js → vi-BwdBtgfS.js} +1 -1
  77. package/apps/web/dist/assets/vi-VN-td08xXv9.js +1 -0
  78. package/apps/web/dist/assets/{visual-editor-DyhI5fa5.js → visual-editor-CzB_qwvB.js} +3 -3
  79. package/apps/web/dist/assets/{workspace-settings-DGr-Lwnz.js → workspace-settings-DOr9j8X1.js} +1 -1
  80. package/apps/web/dist/assets/zh-CN-D3dhEYLS.js +1 -0
  81. package/apps/web/dist/assets/{zh-CN-DfruhRl9.js → zh-CN-DM-UoFmL.js} +1 -1
  82. package/apps/web/dist/assets/zh-TW-CfhOEFo8.js +1 -0
  83. package/apps/web/dist/assets/{zh-TW-C4keUYMa.js → zh-TW-q2nrwFjt.js} +1 -1
  84. package/apps/web/dist/index.html +8 -8
  85. package/docs/api.md +19 -7
  86. package/docs/cli.md +1 -1
  87. package/docs/releasing.md +16 -2
  88. package/docs/self-hosting.md +8 -0
  89. package/package.json +1 -1
  90. package/apps/web/dist/assets/admin-settings-BVWOe67Z.js +0 -1
  91. package/apps/web/dist/assets/az-AZ-DlsPlUZi.js +0 -1
  92. package/apps/web/dist/assets/de-DE-DLCaqUUe.js +0 -1
  93. package/apps/web/dist/assets/default-D6I-Xfqm.js +0 -56
  94. package/apps/web/dist/assets/el-GR-P2XcwrSU.js +0 -1
  95. package/apps/web/dist/assets/es-ES-CJoX8f3w.js +0 -1
  96. package/apps/web/dist/assets/fr-FR-yLkQdy6a.js +0 -1
  97. package/apps/web/dist/assets/hi-IN-y8dqej7z.js +0 -1
  98. package/apps/web/dist/assets/id-ID-DSvh8mGH.js +0 -1
  99. package/apps/web/dist/assets/index-DyQNFS69.css +0 -2
  100. package/apps/web/dist/assets/index-MTE_naHG.js +0 -17
  101. package/apps/web/dist/assets/it-IT-BUcfUqNo.js +0 -1
  102. package/apps/web/dist/assets/ja-JP-C-jLP_Ay.js +0 -1
  103. package/apps/web/dist/assets/ko-KR-C5Ozdm2E.js +0 -1
  104. package/apps/web/dist/assets/mk-MK-BdM_SsE8.js +0 -1
  105. package/apps/web/dist/assets/nl-NL-CwjB5sva.js +0 -1
  106. package/apps/web/dist/assets/pl-PL-6-PMhTBd.js +0 -1
  107. package/apps/web/dist/assets/project-settings-9yJbfZRu.js +0 -1
  108. package/apps/web/dist/assets/pt-BR-TD_wt6Hr.js +0 -1
  109. package/apps/web/dist/assets/ru-RU-Bk1a6Oln.js +0 -1
  110. package/apps/web/dist/assets/settings-Co61IE04.js +0 -1
  111. package/apps/web/dist/assets/ticket-detail-BKilKtg_.js +0 -2
  112. package/apps/web/dist/assets/ticket-editor-B5eOWIj4.js +0 -2
  113. package/apps/web/dist/assets/ticket-list-C965rmmf.js +0 -5
  114. package/apps/web/dist/assets/tr-TR-CUfBb7K9.js +0 -1
  115. package/apps/web/dist/assets/uk-UA-BDaXBsDP.js +0 -1
  116. package/apps/web/dist/assets/vi-VN-Bgb-L6mE.js +0 -1
  117. package/apps/web/dist/assets/zh-CN-BypkvBe6.js +0 -1
  118. package/apps/web/dist/assets/zh-TW-CgMsT_XS.js +0 -1
@@ -1,5 +1,5 @@
1
- import { a as auth_schema_exports, c as oauthConsent, d as user, i as apikey, l as oauthRefreshToken, o as oauthAccessToken, r as account, s as oauthClient, u as session } from "./config-CRADzIvL.mjs";
2
- import { $ as permissionLabels, A as hasAdminRole, At as workspaceSchema, D as filterKey, Dt as ticketPredicates, E as eventSchema, Et as ticketImportSchema, F as isHexColor, J as operationPermission, Lt as s3SettingsSchema, Mt as authenticationPolicySchema, N as importSecretFields, Nt as brokerSettingsSchema, O as filterList, Ot as ticketSchema, P as invitationStatuses, Pt as instanceUpdateSchema, Q as parsePublicOutput, Rt as searchAuthentication, S as difficulties, St as states, T as eventLabels, Tt as ticketFilter, V as mentionScheme, X as parseCustomFieldFilter, Y as operations, Z as parseOutput, _ as deletedAccountName, _t as seededPermissions, at as publicOperations, b as destinationEventKeys, bt as stagedUploadLifetime, ct as repositoryProviderDefinitions, d as customFieldSchema, dt as resourceProvider, et as permissionScope, f as customFieldValue, ft as roleCategory, g as defaultWorkflow, gt as searchResultTypes, h as defaultOrder, ht as scopes, i as channelRouteAllows, it as projectSchema, k as formatEvent, kt as workflowOrder, l as credentialProjectsSchema, lt as repositoryProviders, m as defaultClaimMaxLeaseSeconds, mt as roles, n as attachmentLimit, nt as priorities, p as customFieldValueSchema, pt as roleLabels, r as channelKinds, rt as priorityLabels, s as closedStates, st as remoteKinds, t as ApiError, tt as permissions, u as customFieldFilterTokens, ut as repositorySecretFields, v as deliverySecretFields, vt as signInMethodLabels, w as eventCategories, wt as ticketExportFormat, x as destinationKinds, xt as stateLabels, y as deliverySettingsSchema, yt as snapshotSchema, z as isSeededRole, zt as searchSettingsSchema } from "./src-BCtZ3wbK.mjs";
1
+ import { a as auth_schema_exports, c as oauthConsent, d as user, i as apikey, l as oauthRefreshToken, o as oauthAccessToken, r as account, s as oauthClient, u as session } from "./config-CrJuk5mY.mjs";
2
+ import { $ as permissionLabels, A as hasAdminRole, At as workflowOrder, Bt as searchSettingsSchema, D as filterKey, Dt as ticketPredicates, E as eventSchema, Et as ticketImportSchema, F as isHexColor, Ft as instanceUpdateSchema, J as operationPermission, N as importSecretFields, Nt as authenticationPolicySchema, O as filterList, Ot as ticketSchema, P as invitationStatuses, Pt as brokerSettingsSchema, Q as parsePublicOutput, Rt as s3SettingsSchema, S as difficulties, St as states, T as eventLabels, Tt as ticketFilter, V as mentionScheme, X as parseCustomFieldFilter, Y as operations, Z as parseOutput, _ as deletedAccountName, _t as seededPermissions, at as publicOperations, b as destinationEventKeys, bt as stagedUploadLifetime, ct as repositoryProviderDefinitions, d as customFieldSchema, dt as resourceProvider, et as permissionScope, f as customFieldValue, ft as roleCategory, g as defaultWorkflow, gt as searchResultTypes, h as defaultOrder, ht as scopes, i as channelRouteAllows, it as projectSchema, jt as workspaceSchema, k as formatEvent, kt as ticketSummarySchema, l as credentialProjectsSchema, lt as repositoryProviders, m as defaultClaimMaxLeaseSeconds, mt as roles, n as attachmentLimit, nt as priorities, p as customFieldValueSchema, pt as roleLabels, r as channelKinds, rt as priorityLabels, s as closedStates, st as remoteKinds, t as ApiError, tt as permissions, u as customFieldFilterTokens, ut as repositorySecretFields, v as deliverySecretFields, vt as signInMethodLabels, w as eventCategories, wt as ticketExportFormat, x as destinationKinds, xt as stateLabels, y as deliverySettingsSchema, yt as snapshotSchema, z as isSeededRole, zt as searchAuthentication } from "./src-D80LNZKP.mjs";
3
3
  import { and, asc, count, desc, eq, getTableColumns, gt, ilike, inArray, isNotNull, isNull, lt, lte, ne, notInArray, or, sql } from "drizzle-orm";
4
4
  import { alias, bigint, boolean, check, customType, foreignKey, index, integer, jsonb, pgTable, primaryKey, text, timestamp, unionAll, unique, uniqueIndex, uuid } from "drizzle-orm/pg-core";
5
5
  import { z } from "zod";
@@ -15,6 +15,7 @@ import { request as request$1 } from "node:https";
15
15
  import { BlockList, isIP } from "node:net";
16
16
  import nodemailer from "nodemailer";
17
17
  import { APIError, createAuthMiddleware } from "better-auth/api";
18
+ import { AsyncLocalStorage } from "node:async_hooks";
18
19
  import { betterAuth } from "better-auth";
19
20
  import { admin, genericOAuth, jwt } from "better-auth/plugins";
20
21
  import { emailOTP } from "better-auth/plugins/email-otp";
@@ -769,7 +770,7 @@ function createBlobStorage(database, instance, factory = createObjectStore, sche
769
770
  skipLocked: true
770
771
  });
771
772
  for (const row of rows) {
772
- const backend = await instance.backend(requireRow(row.backendId ?? void 0)), key = requireRow(row.objectKey ?? void 0), bytes = requireRow(row.data ?? void 0);
773
+ const backend = await instance.backend(requireRow(row.backendId ?? void 0), tx), key = requireRow(row.objectKey ?? void 0), bytes = requireRow(row.data ?? void 0);
773
774
  await transfer(backend.spec, async (store) => {
774
775
  await store.put(key, bytes, row.contentType);
775
776
  const verified = await store.get(key, row.size);
@@ -818,7 +819,7 @@ function createBlobStorage(database, instance, factory = createObjectStore, sche
818
819
  });
819
820
  for (const { object } of rows) {
820
821
  if (object.backendId && object.objectKey) {
821
- const backend = await instance.backend(object.backendId), key = object.objectKey;
822
+ const backend = await instance.backend(object.backendId, tx), key = object.objectKey;
822
823
  await transfer(backend.spec, (store) => store.remove(key));
823
824
  }
824
825
  await tx.delete(blobObjects).where(eq(blobObjects.id, object.id));
@@ -872,6 +873,14 @@ function imageResponse(blob) {
872
873
  }
873
874
  //#endregion
874
875
  //#region src/adapters/elasticsearch.ts
876
+ const fields$1 = [
877
+ "title^3",
878
+ "key^3",
879
+ "description",
880
+ "acceptanceCriteria",
881
+ "labels",
882
+ "scopes"
883
+ ];
875
884
  function fail$1(_status, _code, message) {
876
885
  throw new Error(message);
877
886
  }
@@ -970,37 +979,44 @@ function createElasticsearchIndex(configuration) {
970
979
  let scrollId;
971
980
  try {
972
981
  const found = [];
982
+ let score;
973
983
  let response = await accepted(await call$1(settings, `${settings.index}/_search?scroll=1m`, {
974
984
  method: "POST",
975
985
  body: JSON.stringify({
976
986
  size: 1e3,
977
987
  _source: false,
978
- sort: ["_doc"],
979
988
  query: { bool: {
980
989
  filter: [{ terms: { projectId: [...projectIds] } }, { term: { instanceId: identity } }],
981
- must: [{ multi_match: {
982
- query,
983
- fields: [
984
- "title^3",
985
- "key^3",
986
- "description",
987
- "acceptanceCriteria",
988
- "labels",
989
- "scopes"
990
- ],
990
+ must: [...query.terms ? [{ multi_match: {
991
+ query: query.terms,
992
+ fields: fields$1,
991
993
  operator: "and",
992
994
  fuzziness: "AUTO"
993
- } }]
995
+ } }] : [], ...query.phrases.map((phrase) => ({ multi_match: {
996
+ query: phrase,
997
+ fields: fields$1,
998
+ type: "phrase"
999
+ } }))]
994
1000
  } }
995
1001
  })
996
1002
  }));
997
1003
  for (;;) {
998
1004
  const result = z.object({
999
1005
  _scroll_id: z.string().optional(),
1000
- hits: z.object({ hits: z.array(z.object({ _id: z.uuid() })) })
1006
+ hits: z.object({ hits: z.array(z.object({
1007
+ _id: z.uuid(),
1008
+ _score: z.number()
1009
+ })) })
1001
1010
  }).parse(await response.json());
1002
1011
  scrollId = result["_scroll_id"] ?? scrollId;
1003
- found.push(...result.hits.hits.map((hit) => hit["_id"]));
1012
+ for (const hit of result.hits.hits) {
1013
+ const rank = (found.at(-1)?.rank ?? 0) + (score === hit["_score"] ? 0 : 1);
1014
+ score = hit["_score"];
1015
+ found.push({
1016
+ id: hit["_id"],
1017
+ rank
1018
+ });
1019
+ }
1004
1020
  if (found.length > 1e4) throw new Error("External search unavailable");
1005
1021
  if (!result.hits.hits.length || !scrollId) break;
1006
1022
  response = await accepted(await call$1(settings, "_search/scroll", {
@@ -1034,12 +1050,32 @@ const createTicketIndex = (configuration) => {
1034
1050
  //#endregion
1035
1051
  //#region src/search.ts
1036
1052
  const likePattern = (query) => `%${query.replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_")}%`;
1053
+ function parseQuery(query) {
1054
+ const phrases = [];
1055
+ const terms = query.replace(/"([^"]*)"/gu, (_, phrase) => {
1056
+ const value = phrase.replace(/\s+/gu, " ").trim();
1057
+ if (value && phrases.length < 8 && !phrases.some((seen) => seen.toLowerCase() === value.toLowerCase())) phrases.push(value);
1058
+ return " ";
1059
+ }).replace(/\s+/gu, " ").trim();
1060
+ return phrases.length || terms ? {
1061
+ phrases,
1062
+ terms
1063
+ } : {
1064
+ phrases,
1065
+ terms: query
1066
+ };
1067
+ }
1068
+ const querySegments = ({ phrases, terms }) => terms ? [...phrases, terms] : phrases;
1037
1069
  function postgresSearch(query) {
1038
- const value = likePattern(query);
1039
- return or(ilike(tickets.title, value), ilike(tickets.description, value), ilike(tickets.key, value), ilike(tickets.acceptanceCriteria, value), sql`array_to_string(${tickets.labels}, ' ') ILIKE ${value}`, sql`array_to_string(${tickets.scopes}, ' ') ILIKE ${value}`);
1070
+ return and(...querySegments(parseQuery(query)).map((segment) => {
1071
+ const value = likePattern(segment);
1072
+ return or(ilike(tickets.title, value), ilike(tickets.description, value), ilike(tickets.key, value), ilike(tickets.acceptanceCriteria, value), sql`array_to_string(${tickets.labels}, ' ') ILIKE ${value}`, sql`array_to_string(${tickets.scopes}, ' ') ILIKE ${value}`);
1073
+ }));
1040
1074
  }
1075
+ const postgresRank = (query) => sql`CASE WHEN ${and(...querySegments(parseQuery(query)).map((segment) => ilike(tickets.title, likePattern(segment))))} THEN 0 ELSE 1 END`;
1041
1076
  const builtinSearch = { where: async (_db, _ids, query) => ({
1042
1077
  where: postgresSearch(query),
1078
+ rank: postgresRank(query),
1043
1079
  engine: "postgres",
1044
1080
  indexing: false
1045
1081
  }) };
@@ -1093,6 +1129,7 @@ function createTicketSearch(database, instance, factory = createTicketIndex, sch
1093
1129
  const settings = await instance.search();
1094
1130
  const fallback = () => ({
1095
1131
  where: postgresSearch(query),
1132
+ rank: postgresRank(query),
1096
1133
  engine: "postgres",
1097
1134
  indexing: !!settings
1098
1135
  });
@@ -1101,12 +1138,14 @@ function createTicketSearch(database, instance, factory = createTicketIndex, sch
1101
1138
  if (!state || !clock || state.error || state.configHash !== fingerprint(settings) || state.cursor !== clock.value) return fallback();
1102
1139
  const root = await instance.read();
1103
1140
  try {
1104
- const ids = await factory({
1141
+ const hits = await factory({
1105
1142
  provider: "elasticsearch",
1106
1143
  config: settings
1107
- }).find(root.identity, projectIds, query);
1144
+ }).find(root.identity, projectIds, parseQuery(query));
1145
+ const ids = hits.map((hit) => hit.id), ranks = hits.map((hit) => hit.rank);
1108
1146
  return {
1109
1147
  where: ids.length ? inArray(tickets.id, ids) : sql`false`,
1148
+ rank: sql`(${`{${ranks.join(",")}}`}::int[])[array_position(${`{${ids.join(",")}}`}::uuid[], ${tickets.id})]`,
1110
1149
  engine: "index",
1111
1150
  indexing: false
1112
1151
  };
@@ -2254,23 +2293,17 @@ async function validateReferences(ctx, ticketId, changes) {
2254
2293
  }
2255
2294
  async function assertNoCycle(ctx, sourceId, targetId) {
2256
2295
  if (sourceId === targetId) fail(409, "dependency_cycle", "A ticket cannot depend on itself");
2257
- const all = await ctx.db.select({
2258
- id: tickets.id,
2259
- parentId: tickets.parentId
2260
- }).from(tickets).where(eq(tickets.projectId, project(ctx).id));
2261
- const links = await ctx.db.select().from(relations$1).where(and(eq(relations$1.projectId, project(ctx).id), eq(relations$1.type, "blocks")));
2262
- const edges = /* @__PURE__ */ new Map();
2263
- const add = (from, to) => edges.set(from, [...edges.get(from) ?? [], to]);
2264
- for (const row of all) if (row.parentId) add(row.id, row.parentId);
2265
- for (const row of links) add(row.sourceId, row.targetId);
2266
- const pending = [targetId], visited = /* @__PURE__ */ new Set();
2267
- while (pending.length) {
2268
- const node = pending.pop();
2269
- if (!node || visited.has(node)) continue;
2270
- if (node === sourceId) fail(409, "dependency_cycle", "This relationship would create a dependency cycle");
2271
- visited.add(node);
2272
- pending.push(...edges.get(node) ?? []);
2273
- }
2296
+ const { rows } = await ctx.db.execute(sql`WITH RECURSIVE reach(id) AS (
2297
+ SELECT ${targetId}::uuid
2298
+ UNION
2299
+ SELECT edge.next FROM reach JOIN (
2300
+ SELECT source_id AS id, target_id AS next FROM ${relations$1} WHERE type = 'blocks'
2301
+ UNION ALL
2302
+ SELECT id, parent_id FROM ${tickets} WHERE parent_id IS NOT NULL
2303
+ ) edge ON edge.id = reach.id
2304
+ )
2305
+ SELECT 1 FROM reach WHERE id = ${sourceId}::uuid LIMIT 1`);
2306
+ if (rows.length) fail(409, "dependency_cycle", "This relationship would create a dependency cycle");
2274
2307
  }
2275
2308
  async function updateTicketRow(ctx, input) {
2276
2309
  const current = await getTicket(ctx, input.ticketId);
@@ -2417,7 +2450,7 @@ async function createTicketRow(ctx, input) {
2417
2450
  ...fields,
2418
2451
  ...state,
2419
2452
  ...dates,
2420
- customFields: applyCustomFieldValues(await projectCustomFields(ctx), null, customFields),
2453
+ customFields: intakeOnly(ctx.actor) ? {} : applyCustomFieldValues(await projectCustomFields(ctx), null, customFields),
2421
2454
  ...await allocateTicket(ctx),
2422
2455
  id,
2423
2456
  createdBy: ctx.actor.id
@@ -2464,6 +2497,20 @@ const hitRow = z.object({
2464
2497
  title: z.string().nullable(),
2465
2498
  status: z.enum(states).nullable()
2466
2499
  });
2500
+ const summarize = (ticket) => ticketSummarySchema.parse(json(ticket));
2501
+ const snippetLength = 240;
2502
+ function snippet(body, query) {
2503
+ const text = body?.replace(/\s+/gu, " ").trim() ?? "", lower = text.toLowerCase(), parsed = parseQuery(query);
2504
+ let at = -1, size = 0;
2505
+ for (const needle of [...querySegments(parsed), ...parsed.terms.split(" ")]) {
2506
+ const found = needle ? lower.indexOf(needle.toLowerCase()) : -1;
2507
+ if (found >= 0 && (at < 0 || found < at)) [at, size] = [found, needle.length];
2508
+ }
2509
+ if (at < 0) return "";
2510
+ if (text.length <= snippetLength) return text;
2511
+ const start = Math.max(0, Math.min(at + Math.floor((size - snippetLength) / 2), text.length - snippetLength)), end = start + snippetLength;
2512
+ return `${start > 0 ? "…" : ""}${text.slice(start > 0 ? start + 1 : start, end < text.length ? end - 1 : end)}${end < text.length ? "…" : ""}`;
2513
+ }
2467
2514
  function excerpt(body, query, radius = 80) {
2468
2515
  const text = body?.replace(/\s+/gu, " ").trim() ?? "", at = text.toLowerCase().indexOf(query.toLowerCase());
2469
2516
  if (at < 0) return "";
@@ -2481,25 +2528,29 @@ async function deleteTicketRows(ctx, selection) {
2481
2528
  const ids = [...selected.keys()];
2482
2529
  const [claimed] = await ctx.db.select({ ticketId: claims.ticketId }).from(claims).where(inArray(claims.ticketId, ids)).limit(1);
2483
2530
  if (claimed) fail(409, "claim_conflict", "Release ownership before deleting this ticket", { ticketId: claimed.ticketId });
2484
- await publishMany(ctx, [...(await ctx.db.update(tickets).set({
2485
- parentId: null,
2486
- version: sql`${tickets.version} + 1`,
2487
- updatedAt: /* @__PURE__ */ new Date()
2488
- }).where(and(inArray(tickets.parentId, ids), notInArray(tickets.id, ids))).returning()).map((child) => ({
2489
- type: "ticket.updated",
2490
- ticketId: child.id,
2491
- data: {
2492
- version: child.version,
2493
- changedFields: ["parentId"]
2494
- }
2495
- })), ...[...selected.values()].map((ticket) => ({
2496
- type: "ticket.deleted",
2497
- ticketId: ticket.id,
2498
- data: {
2499
- key: ticket.key,
2500
- version: ticket.version
2501
- }
2502
- }))]);
2531
+ await publishMany(ctx, [
2532
+ ...(await ctx.db.update(tickets).set({
2533
+ parentId: null,
2534
+ version: sql`${tickets.version} + 1`,
2535
+ updatedAt: /* @__PURE__ */ new Date()
2536
+ }).where(and(inArray(tickets.parentId, ids), notInArray(tickets.id, ids))).returning()).map((child) => ({
2537
+ type: "ticket.updated",
2538
+ ticketId: child.id,
2539
+ data: {
2540
+ version: child.version,
2541
+ changedFields: ["parentId"]
2542
+ }
2543
+ })),
2544
+ ...[...selected.values()].map((ticket) => ({
2545
+ type: "ticket.deleted",
2546
+ ticketId: ticket.id,
2547
+ data: {
2548
+ key: ticket.key,
2549
+ version: ticket.version
2550
+ }
2551
+ })),
2552
+ ...await severedRelationEvents(ctx, ids)
2553
+ ]);
2503
2554
  await ctx.db.delete(tickets).where(inArray(tickets.id, ids));
2504
2555
  }
2505
2556
  async function relocation(ctx, ticket, destinationProjectId) {
@@ -2568,6 +2619,16 @@ async function relocation(ctx, ticket, destinationProjectId) {
2568
2619
  fields
2569
2620
  };
2570
2621
  }
2622
+ async function readableTicket(ctx, identifier) {
2623
+ const allowed = await accessibleProjectRows(ctx.db, ctx.actor);
2624
+ const [ticket] = allowed.length ? await ctx.db.select().from(tickets).where(and(inArray(tickets.projectId, allowed.map((row) => row.id)), ticketMatch(identifier))).limit(1) : [];
2625
+ const owner = allowed.find((row) => row.id === ticket?.projectId);
2626
+ if (!ticket || !owner) fail(404, "not_found", "Ticket was not found");
2627
+ return {
2628
+ ticket,
2629
+ project: owner
2630
+ };
2631
+ }
2571
2632
  const ticketHandlers = {
2572
2633
  async list_ticket_labels(ctx, input) {
2573
2634
  const labels = labelUsage(ctx, input.archived).as("usage");
@@ -2578,7 +2639,11 @@ const ticketHandlers = {
2578
2639
  const related = input.relatedTo ? await getTicket(ctx, input.relatedTo) : void 0;
2579
2640
  const search = input.q ? await ctx.search.where(ctx.db, [project(ctx).id], input.q) : void 0;
2580
2641
  await assertCustomFieldFilters(ctx, [project(ctx).id], input);
2581
- return pageOf(ctx, and(eq(tickets.projectId, project(ctx).id), parent ? eq(tickets.parentId, parent.id) : void 0, related ? or(inArray(tickets.id, ctx.db.select({ id: relations$1.targetId }).from(relations$1).where(eq(relations$1.sourceId, related.id))), inArray(tickets.id, ctx.db.select({ id: relations$1.sourceId }).from(relations$1).where(eq(relations$1.targetId, related.id)))) : void 0, predicateWhere(input), input.archived === "include" ? void 0 : eq(tickets.archived, input.archived === "only"), dueWhere(input.due), input.stateId ? eq(tickets.stateId, input.stateId) : void 0, input.scheduledFrom ? sql`${scheduleEnd} >= ${input.scheduledFrom}::timestamptz` : void 0, input.scheduledTo ? sql`${scheduleStart} < ${input.scheduledTo}::timestamptz` : void 0, search?.where), ticketOrder(input), input);
2642
+ const page = await pageOf(ctx, and(eq(tickets.projectId, project(ctx).id), parent ? eq(tickets.parentId, parent.id) : void 0, related ? or(inArray(tickets.id, ctx.db.select({ id: relations$1.targetId }).from(relations$1).where(eq(relations$1.sourceId, related.id))), inArray(tickets.id, ctx.db.select({ id: relations$1.sourceId }).from(relations$1).where(eq(relations$1.targetId, related.id)))) : void 0, predicateWhere(input), input.archived === "include" ? void 0 : eq(tickets.archived, input.archived === "only"), dueWhere(input.due), input.stateId ? eq(tickets.stateId, input.stateId) : void 0, input.scheduledFrom ? sql`${scheduleEnd} >= ${input.scheduledFrom}::timestamptz` : void 0, input.scheduledTo ? sql`${scheduleStart} < ${input.scheduledTo}::timestamptz` : void 0, search?.where), ticketOrder(input), input);
2643
+ return input.view === "summary" ? {
2644
+ ...page,
2645
+ items: page.items.map(summarize)
2646
+ } : page;
2582
2647
  },
2583
2648
  async list_my_work(ctx, input) {
2584
2649
  const allowed = await accessibleProjectRows(ctx.db, ctx.actor, input.workspaceId, "exclude");
@@ -2607,6 +2672,21 @@ const ticketHandlers = {
2607
2672
  };
2608
2673
  },
2609
2674
  async create_ticket(ctx, input) {
2675
+ if (intakeOnly(ctx.actor)) input = {
2676
+ ...input,
2677
+ automationReady: false,
2678
+ holdReason: intakeHoldReason,
2679
+ status: "todo",
2680
+ stateId: void 0,
2681
+ assigneeId: null,
2682
+ parentId: null,
2683
+ archived: false,
2684
+ dueAt: null,
2685
+ startAt: null,
2686
+ customFields: {},
2687
+ uploads: [],
2688
+ labels: operations.create_ticket.input.shape.labels.parse([.../* @__PURE__ */ new Set([...input.labels, "intake"])])
2689
+ };
2610
2690
  const { row, event } = await createTicketRow(ctx, input);
2611
2691
  await publishMany(ctx, [event, ...(await claimStagedUploads(ctx, row.id, input.uploads)).map((item) => ({
2612
2692
  type: "attachment.created",
@@ -2652,7 +2732,7 @@ const ticketHandlers = {
2652
2732
  ...event.data,
2653
2733
  duplicateOf: source.id
2654
2734
  }
2655
- }, ...relationEvents("relation.added", relation)]);
2735
+ }, ...relationEvents("relation.added", relation, [row, source])]);
2656
2736
  return row;
2657
2737
  },
2658
2738
  update_ticket: updateTicket,
@@ -2742,10 +2822,7 @@ const ticketHandlers = {
2742
2822
  return row;
2743
2823
  },
2744
2824
  async resolve_ticket(ctx, input) {
2745
- const allowed = await accessibleProjectRows(ctx.db, ctx.actor);
2746
- const [ticket] = allowed.length ? await ctx.db.select().from(tickets).where(and(inArray(tickets.projectId, allowed.map((row) => row.id)), ticketMatch(input.ticketId))).limit(1) : [];
2747
- const owner = allowed.find((row) => row.id === ticket?.projectId);
2748
- if (!ticket || !owner) fail(404, "not_found", "Ticket was not found");
2825
+ const { ticket, project: owner } = await readableTicket(ctx, input.ticketId);
2749
2826
  return {
2750
2827
  ticket,
2751
2828
  project: {
@@ -2793,17 +2870,20 @@ const ticketHandlers = {
2793
2870
  async search_tickets(ctx, input) {
2794
2871
  const allowed = await accessibleProjectRows(ctx.db, ctx.actor, input.workspaceId, "exclude");
2795
2872
  if (!allowed.length) return { items: [] };
2796
- const ids = allowed.map((row) => row.id);
2797
- return { items: (await ctx.db.select().from(tickets).where(and(inArray(tickets.projectId, ids), eq(tickets.archived, false), or(ticketMatch(input.q), (await ctx.search.where(ctx.db, ids, input.q)).where))).orderBy(sql`CASE WHEN ${ticketMatch(input.q)} THEN 0 ELSE 1 END`, desc(tickets.updatedAt)).limit(input.limit)).flatMap((ticket) => {
2873
+ const ids = allowed.map((row) => row.id), match = await ctx.search.where(ctx.db, ids, input.q);
2874
+ return { items: (await ctx.db.select().from(tickets).where(and(inArray(tickets.projectId, ids), eq(tickets.archived, false), or(ticketMatch(input.q), match.where))).orderBy(sql`CASE WHEN ${ticketMatch(input.q)} THEN 0 ELSE 1 END`, sql`${match.rank} NULLS LAST`, desc(tickets.updatedAt)).limit(input.limit)).flatMap((ticket) => {
2798
2875
  const owner = allowed.find((row) => row.id === ticket.projectId);
2799
- return owner ? [{
2800
- ticket,
2876
+ if (!owner) return [];
2877
+ const summary = input.view === "summary", body = `${ticket.description} ${ticket.acceptanceCriteria}`;
2878
+ return [{
2879
+ ticket: summary ? summarize(ticket) : ticket,
2801
2880
  project: {
2802
2881
  id: owner.id,
2803
2882
  key: owner.key,
2804
2883
  name: owner.name
2805
- }
2806
- }] : [];
2884
+ },
2885
+ ...summary && { snippet: snippet(body, input.q) }
2886
+ }];
2807
2887
  }) };
2808
2888
  },
2809
2889
  async search_workspace(ctx, input) {
@@ -2828,23 +2908,26 @@ const ticketHandlers = {
2828
2908
  filtered ? void 0 : sql`SELECT 'project' AS type, projects.id::text AS id, projects.id AS project_id,
2829
2909
  NULL::uuid AS ticket_id, NULL::text AS actor_id, projects.description AS body,
2830
2910
  NULL::text AS event_type, NULL::jsonb AS data, projects.updated_at AS at,
2831
- CASE WHEN projects.key = ${key} THEN 0 ELSE 1 END AS rank, 0 AS family
2911
+ CASE WHEN projects.key = ${key} THEN 0 ELSE 1 END AS rank, 0 AS family,
2912
+ NULL::int AS score
2832
2913
  FROM projects WHERE ${inArray(projects.id, ids)}
2833
2914
  AND (projects.name ILIKE ${like} OR projects.key ILIKE ${like} OR projects.description ILIKE ${like})`,
2834
2915
  sql`SELECT 'ticket' AS type, tickets.id::text AS id, tickets.project_id,
2835
2916
  tickets.id AS ticket_id, NULL::text AS actor_id,
2836
2917
  concat_ws(' ', tickets.description, tickets.acceptance_criteria) AS body,
2837
2918
  NULL::text AS event_type, NULL::jsonb AS data, tickets.updated_at AS at,
2838
- CASE WHEN ${ticketMatch(input.q)} THEN 0 ELSE 1 END AS rank, 1 AS family
2919
+ CASE WHEN ${ticketMatch(input.q)} THEN 0 ELSE 1 END AS rank, 1 AS family,
2920
+ ${match.rank}::int AS score
2839
2921
  FROM tickets WHERE ${and(ticketScope, or(ticketMatch(input.q), match.where))}`,
2840
2922
  sql`SELECT 'comment' AS type, comments.id::text AS id, comments.project_id,
2841
2923
  comments.ticket_id, comments.actor_id, comments.body, NULL::text AS event_type,
2842
- NULL::jsonb AS data, comments.created_at AS at, 1 AS rank, 2 AS family
2924
+ NULL::jsonb AS data, comments.created_at AS at, 1 AS rank, 2 AS family,
2925
+ NULL::int AS score
2843
2926
  FROM comments JOIN tickets ON tickets.id = comments.ticket_id
2844
2927
  WHERE ${and(ticketScope, sql`comments.body ILIKE ${like}`)}`,
2845
2928
  sql`SELECT 'activity' AS type, events.id::text AS id, events.project_id,
2846
2929
  events.ticket_id, events.actor_id, NULL::text AS body, events.type AS event_type,
2847
- events.data, events.created_at AS at, 1 AS rank, 3 AS family
2930
+ events.data, events.created_at AS at, 1 AS rank, 3 AS family, NULL::int AS score
2848
2931
  FROM events
2849
2932
  LEFT JOIN tickets ON tickets.id = events.ticket_id AND tickets.project_id = events.project_id
2850
2933
  LEFT JOIN "user" ON "user".id = events.actor_id
@@ -2861,14 +2944,15 @@ const ticketHandlers = {
2861
2944
  FROM (${hits}) AS hits
2862
2945
  LEFT JOIN tickets ON tickets.id = hits.ticket_id AND tickets.project_id = hits.project_id
2863
2946
  WHERE ${input.type ? sql`hits.type = ${input.type}` : sql`true`}
2864
- ORDER BY hits.rank, hits.family, hits.at DESC, hits.id DESC
2947
+ ORDER BY hits.rank, hits.family, hits.score NULLS LAST, hits.at DESC, hits.id DESC
2865
2948
  LIMIT ${input.limit} OFFSET ${input.offset}`)).rows.map((row) => hitRow.parse(row));
2866
2949
  const projectsById = new Map(allowed.map((row) => [row.id, row]));
2950
+ const ticketIds = rows.flatMap((row) => row.ticket_id ? [row.ticket_id] : []), summaries = input.view === "summary" && ticketIds.length ? new Map((await ctx.db.select().from(tickets).where(inArray(tickets.id, ticketIds))).map((row) => [row.id, summarize(row)])) : void 0;
2867
2951
  return {
2868
2952
  items: rows.flatMap((row) => {
2869
2953
  const owner = projectsById.get(row.project_id);
2870
2954
  if (!owner) return [];
2871
- const ticket = row.ticket_id && row.key && row.title && row.status ? {
2955
+ const ticket = row.ticket_id && row.key && row.title && row.status ? summaries?.get(row.ticket_id) ?? {
2872
2956
  id: row.ticket_id,
2873
2957
  key: row.key,
2874
2958
  title: row.title,
@@ -2894,6 +2978,7 @@ const ticketHandlers = {
2894
2978
  createdAt: at
2895
2979
  }, true) : ticket?.title ?? "",
2896
2980
  excerpt: excerpt(row.body, input.q),
2981
+ ...input.view === "summary" && { snippet: snippet(row.body, input.q) },
2897
2982
  actorId: row.actor_id,
2898
2983
  at
2899
2984
  }];
@@ -2951,11 +3036,17 @@ const ticketHandlers = {
2951
3036
  },
2952
3037
  async recover_claim(ctx, input) {
2953
3038
  const ticket = await getTicket(ctx, input.ticketId);
2954
- const current = one(await ctx.db.select().from(claims).where(and(eq(claims.id, input.claimId), eq(claims.ticketId, ticket.id))));
2955
- if (input.cancelActive && !can(ctx, "project.manage")) fail(403, "forbidden", "Cancelling an active claim requires project management access and admin scope");
2956
- if (current.expiresAt > /* @__PURE__ */ new Date() && !input.cancelActive) fail(409, "claim_not_expired", "The current claim has not expired");
2957
- if (input.reschedule && input.expectedVersion !== void 0) {
2958
- assertVersion(ticket, input.expectedVersion, "ticket", "rescheduling recovered work");
3039
+ const current = input.claimId === void 0 ? void 0 : one(await ctx.db.select().from(claims).where(and(eq(claims.id, input.claimId), eq(claims.ticketId, ticket.id))));
3040
+ if (!current) {
3041
+ const held = (await ctx.db.select({ id: claims.id }).from(claims).where(eq(claims.ticketId, ticket.id)).limit(1))[0];
3042
+ if (held) fail(409, "claim_conflict", "This ticket has a claim; recover it by its claim ID", { claimId: held.id });
3043
+ if (!ticket.claimRequired) fail(409, "claim_not_required", "This ticket does not require ownership to recover");
3044
+ } else {
3045
+ if (input.cancelActive && !can(ctx, "project.manage")) fail(403, "forbidden", "Cancelling an active claim requires project management access and admin scope");
3046
+ if (current.expiresAt > /* @__PURE__ */ new Date() && !input.cancelActive) fail(409, "claim_not_expired", "The current claim has not expired");
3047
+ }
3048
+ if (input.expectedVersion !== void 0 && (input.reschedule || !current)) assertVersion(ticket, input.expectedVersion, "ticket", "recovering ownership");
3049
+ if (input.reschedule) {
2959
3050
  const row = one(await ctx.db.update(tickets).set({
2960
3051
  ...await resolveState(ctx, { status: "todo" }, ticket),
2961
3052
  version: ticket.version + 1,
@@ -2976,12 +3067,12 @@ const ticketHandlers = {
2976
3067
  changedFields: ["claimRequired"]
2977
3068
  });
2978
3069
  }
2979
- await ctx.db.delete(claims).where(eq(claims.id, current.id));
3070
+ if (current) await ctx.db.delete(claims).where(eq(claims.id, current.id));
2980
3071
  await publish(ctx, "claim.recovered", ticket.id, {
2981
- claimId: current.id,
3072
+ claimId: current?.id ?? null,
2982
3073
  reason: input.reason,
2983
3074
  confirmedStopped: true,
2984
- ...input.cancelActive && { cancelActive: true }
3075
+ ...current && input.cancelActive && { cancelActive: true }
2985
3076
  });
2986
3077
  return { ok: true };
2987
3078
  }
@@ -3259,6 +3350,13 @@ function mentionedUserIds(markdown) {
3259
3350
  //#region src/collaboration-operations.ts
3260
3351
  const sourceTicket = alias(tickets, "source_ticket");
3261
3352
  const targetTicket = alias(tickets, "target_ticket");
3353
+ const sourceProject = alias(projects, "source_project");
3354
+ const targetProject = alias(projects, "target_project");
3355
+ async function assertMayConstrain(ctx, relation, sides) {
3356
+ const constrained = relation.type === "blocks" ? relation.targetId : relation.type === "duplicate" ? relation.sourceId : null;
3357
+ const side = sides.find((item) => item.id === constrained);
3358
+ if (side && side.projectId !== project(ctx).id) await context(ctx.db, ctx.actor, "write", side.projectId);
3359
+ }
3262
3360
  async function editableComment(ctx, commentId, expectedVersion) {
3263
3361
  const row = one(await ctx.db.select().from(comments).where(and(eq(comments.projectId, project(ctx).id), eq(comments.id, commentId))));
3264
3362
  if (row.actorId !== ctx.actor.id && !can(ctx, "project.manage")) fail(403, "forbidden", "Only the author or a project administrator can edit this comment");
@@ -3289,18 +3387,45 @@ async function addComment(ctx, input) {
3289
3387
  const collaborationHandlers = {
3290
3388
  async list_relations(ctx, input) {
3291
3389
  const ticket = await getTicket(ctx, input.ticketId);
3292
- return ctx.db.select({
3390
+ const rows = await ctx.db.select({
3293
3391
  ...getTableColumns(relations$1),
3294
3392
  sourceKey: sourceTicket.key,
3295
3393
  sourceTitle: sourceTicket.title,
3394
+ sourceProjectId: sourceTicket.projectId,
3395
+ sourceProjectKey: sourceProject.key,
3296
3396
  targetKey: targetTicket.key,
3297
- targetTitle: targetTicket.title
3298
- }).from(relations$1).innerJoin(sourceTicket, eq(sourceTicket.id, relations$1.sourceId)).innerJoin(targetTicket, eq(targetTicket.id, relations$1.targetId)).where(and(eq(relations$1.projectId, project(ctx).id), or(eq(relations$1.sourceId, ticket.id), eq(relations$1.targetId, ticket.id))));
3397
+ targetTitle: targetTicket.title,
3398
+ targetProjectId: targetTicket.projectId,
3399
+ targetProjectKey: targetProject.key
3400
+ }).from(relations$1).innerJoin(sourceTicket, eq(sourceTicket.id, relations$1.sourceId)).innerJoin(targetTicket, eq(targetTicket.id, relations$1.targetId)).innerJoin(sourceProject, eq(sourceProject.id, sourceTicket.projectId)).innerJoin(targetProject, eq(targetProject.id, targetTicket.projectId)).where(or(eq(relations$1.sourceId, ticket.id), eq(relations$1.targetId, ticket.id)));
3401
+ const here = project(ctx).id, elsewhere = rows.some((row) => row.sourceProjectId !== here || row.targetProjectId !== here), readable = /* @__PURE__ */ new Set([here, ...(elsewhere ? await accessibleProjectRows(ctx.db, ctx.actor) : []).map((row) => row.id)]);
3402
+ return rows.map(({ sourceTitle, sourceProjectId, sourceProjectKey, targetTitle, targetProjectId, targetProjectKey, ...row }) => {
3403
+ const source = readable.has(sourceProjectId), target = readable.has(targetProjectId);
3404
+ return {
3405
+ ...row,
3406
+ ...source && {
3407
+ sourceTitle,
3408
+ sourceProjectId,
3409
+ sourceProjectKey
3410
+ },
3411
+ ...target && {
3412
+ targetTitle,
3413
+ targetProjectId,
3414
+ targetProjectKey
3415
+ },
3416
+ restricted: !source || !target
3417
+ };
3418
+ });
3299
3419
  },
3300
3420
  async add_relation(ctx, input) {
3301
- const current = await getTicket(ctx, input.ticketId), other = await getTicket(ctx, input.targetId);
3421
+ const current = await getTicket(ctx, input.ticketId), { ticket: other } = await readableTicket(ctx, input.targetId);
3302
3422
  const [source, target] = input.direction === "incoming" ? [other, current] : [current, other];
3303
3423
  if (source.id === target.id) fail(409, "invalid_relation", "Choose a different ticket");
3424
+ await assertMayConstrain(ctx, {
3425
+ type: input.type,
3426
+ sourceId: source.id,
3427
+ targetId: target.id
3428
+ }, [current, other]);
3304
3429
  if (input.type === "blocks") await assertNoCycle(ctx, source.id, target.id);
3305
3430
  const [sourceId, targetId] = input.type === "related" ? [source.id, target.id].toSorted() : [source.id, target.id];
3306
3431
  const row = one(await ctx.db.insert(relations$1).values({
@@ -3316,12 +3441,19 @@ const collaborationHandlers = {
3316
3441
  ],
3317
3442
  set: { type: input.type }
3318
3443
  }).returning());
3319
- await publishMany(ctx, relationEvents("relation.added", row));
3444
+ await publishMany(ctx, relationEvents("relation.added", row, [current, other]));
3320
3445
  return row;
3321
3446
  },
3322
3447
  async remove_relation(ctx, input) {
3323
- const row = one(await ctx.db.delete(relations$1).where(and(eq(relations$1.projectId, project(ctx).id), eq(relations$1.id, input.relationId))).returning());
3324
- await publishMany(ctx, relationEvents("relation.removed", row));
3448
+ const found = one(await ctx.db.select().from(relations$1).where(eq(relations$1.id, input.relationId)));
3449
+ const sides = await ctx.db.select({
3450
+ id: tickets.id,
3451
+ projectId: tickets.projectId
3452
+ }).from(tickets).where(inArray(tickets.id, [found.sourceId, found.targetId]));
3453
+ if (!sides.some((side) => side.projectId === project(ctx).id)) fail(404, "not_found", "The requested record was not found");
3454
+ await assertMayConstrain(ctx, found, sides);
3455
+ const row = one(await ctx.db.delete(relations$1).where(eq(relations$1.id, found.id)).returning());
3456
+ await publishMany(ctx, relationEvents("relation.removed", row, sides));
3325
3457
  return { ok: true };
3326
3458
  },
3327
3459
  async list_comments(ctx, input) {
@@ -3974,7 +4106,7 @@ function matches(rule, names, labels) {
3974
4106
  const has = (id) => carried.has(names.get(id)?.toLowerCase() ?? "\0");
3975
4107
  return rule.match === "all" ? rule.labels.every(has) : rule.labels.some(has);
3976
4108
  }
3977
- const qualifies = (rules, names, link, ticket) => link.origin === "outbound" ? matches(rules.outbound, names, ticket.labels) : matches(rules.inbound, names, link.remote?.labels ?? []);
4109
+ const qualifies = (rules, names, link, ticket) => !intakeQuarantined(ticket) && (link?.origin === "inbound" ? matches(rules.inbound, names, link.remote?.labels ?? []) : matches(rules.outbound, names, ticket.labels));
3978
4110
  const workspaceOf = async (db, projectId) => one(await db.select({ workspaceId: projects.workspaceId }).from(projects).where(eq(projects.id, projectId))).workspaceId;
3979
4111
  async function setLinks(db, projectId, links, changes) {
3980
4112
  if (!links.length) return [];
@@ -4036,7 +4168,7 @@ async function queueIssueSync(db, rows) {
4036
4168
  for (const ticket of found) for (const connection of connections) {
4037
4169
  const link = links.find((item) => item.connectionId === connection.id && item.ticketId === ticket.id), labelled = names.get(connection.id) ?? /* @__PURE__ */ new Map();
4038
4170
  if (!link) {
4039
- if (connection.projectId === ticket.projectId && !ticket.archived && !complete(ticket.status) && matches(connection.outbound, labelled, ticket.labels)) await queueSync(db, await db.insert(issueLinks).values({
4171
+ if (connection.projectId === ticket.projectId && !ticket.archived && !complete(ticket.status) && qualifies(connection, labelled, null, ticket)) await queueSync(db, await db.insert(issueLinks).values({
4040
4172
  connectionId: connection.id,
4041
4173
  ticketId: ticket.id,
4042
4174
  origin: "outbound",
@@ -4057,8 +4189,9 @@ async function queueIssueSync(db, rows) {
4057
4189
  state: "review",
4058
4190
  reason: "labels"
4059
4191
  });
4060
- else if (link.state === "active" && !connection.paused) {
4192
+ else if ((link.state === "active" || link.state === "creating") && qualified && !connection.paused) {
4061
4193
  await queueSync(db, [link]);
4194
+ if (link.state !== "active") continue;
4062
4195
  const fresh = commentEvents.filter((event) => event.ticketId === ticket.id && !copied.has(`${link.id}:${String(event.data.commentId)}`));
4063
4196
  if (fresh.length) await db.insert(deliveries).values(fresh.map((event) => ({
4064
4197
  connectionId: connection.id,
@@ -4311,6 +4444,10 @@ async function sendIssueSync(database, senders, secrets, drivers, job, event) {
4311
4444
  }).from(issueLinks).innerJoin(tickets, eq(tickets.id, issueLinks.ticketId)).innerJoin(repositoryConnections, eq(repositoryConnections.id, issueLinks.connectionId)).innerJoin(projects, eq(projects.id, repositoryConnections.projectId)).where(eq(issueLinks.id, job.issueLinkId ?? "")))[0];
4312
4445
  if (!loaded) return finish(database.db, job, "link_removed");
4313
4446
  const { link, ticket, connection } = loaded;
4447
+ if (intakeQuarantined(ticket)) return locked(database, async (db) => {
4448
+ const current = (await db.select({ holdReason: tickets.holdReason }).from(tickets).where(eq(tickets.id, ticket.id)))[0];
4449
+ if (!current || intakeQuarantined(current)) await finish(db, job, current ? "quarantined" : "link_removed");
4450
+ });
4314
4451
  const skip = connection.paused ? "paused" : !loaded.writer ? "access_revoked" : loaded.archivedAt ? "project_archived" : ticket.projectId !== connection.projectId ? "moved" : null;
4315
4452
  if (skip === "access_revoked" || skip === "project_archived") return locked(database, async (db) => {
4316
4453
  await finish(db, job, skip);
@@ -4525,7 +4662,7 @@ async function plan(ctx, connection, { inbound, outbound }) {
4525
4662
  if (link.state === "active") (qualified ? matching : pause).push(link);
4526
4663
  else if (link.state === "paused" && link.reason === "labels" && qualified) review.push(link);
4527
4664
  }
4528
- const exported = rules.outbound.enabled ? (await ctx.db.select().from(tickets).where(and(eq(tickets.projectId, connection.projectId), eq(tickets.archived, false), notInArray(tickets.status, [...closedStates]), notInArray(tickets.id, ctx.db.select({ id: issueLinks.ticketId }).from(issueLinks).where(eq(issueLinks.connectionId, connection.id)))))).filter((ticket) => matches(rules.outbound, names, ticket.labels)) : [];
4665
+ const exported = rules.outbound.enabled ? (await ctx.db.select().from(tickets).where(and(eq(tickets.projectId, connection.projectId), eq(tickets.archived, false), notInArray(tickets.status, [...closedStates]), notInArray(tickets.id, ctx.db.select({ id: issueLinks.ticketId }).from(issueLinks).where(eq(issueLinks.connectionId, connection.id)))))).filter((ticket) => qualifies(rules, names, null, ticket)) : [];
4529
4666
  return {
4530
4667
  matching,
4531
4668
  pause,
@@ -5769,22 +5906,26 @@ async function removeProject(ctx) {
5769
5906
  key: tickets.key,
5770
5907
  version: tickets.version
5771
5908
  }).from(tickets).where(eq(tickets.projectId, current.id)).orderBy(asc(tickets.number));
5772
- await recordAudience(ctx, requireRow((await publishMany(ctx, [...removed.map((ticket) => ({
5773
- type: "ticket.deleted",
5774
- ticketId: ticket.id,
5775
- data: {
5776
- key: ticket.key,
5777
- version: ticket.version
5778
- }
5779
- })), {
5780
- type: "project.deleted",
5781
- ticketId: null,
5782
- data: {
5783
- key: current.key,
5784
- name: current.name,
5785
- tickets: removed.length
5909
+ await recordAudience(ctx, requireRow((await publishMany(ctx, [
5910
+ ...removed.map((ticket) => ({
5911
+ type: "ticket.deleted",
5912
+ ticketId: ticket.id,
5913
+ data: {
5914
+ key: ticket.key,
5915
+ version: ticket.version
5916
+ }
5917
+ })),
5918
+ ...await severedRelationEvents(ctx),
5919
+ {
5920
+ type: "project.deleted",
5921
+ ticketId: null,
5922
+ data: {
5923
+ key: current.key,
5924
+ name: current.name,
5925
+ tickets: removed.length
5926
+ }
5786
5927
  }
5787
- }], false)).at(-1)), readers.map((person) => person.userId));
5928
+ ], false)).at(-1)), readers.map((person) => person.userId));
5788
5929
  await ctx.db.delete(tickets).where(eq(tickets.projectId, current.id));
5789
5930
  await ctx.db.delete(stagedUploads).where(eq(stagedUploads.projectId, current.id));
5790
5931
  await ctx.db.delete(projectLabels).where(eq(projectLabels.projectId, current.id));
@@ -6021,7 +6162,8 @@ const timeEntryHandlers = {
6021
6162
  //#region src/public-operations.ts
6022
6163
  const listTickets = async (ctx, input) => parseOutput("list_tickets", json(await ticketHandlers.list_tickets(ctx, operations.list_tickets.input.parse({
6023
6164
  ...input,
6024
- projectId: project(ctx).id
6165
+ projectId: project(ctx).id,
6166
+ view: "full"
6025
6167
  }))));
6026
6168
  async function published(ctx, ids) {
6027
6169
  if (!ids.length) return /* @__PURE__ */ new Set();
@@ -6824,7 +6966,13 @@ function json(value) {
6824
6966
  return JSON.parse(JSON.stringify(value, (_, item) => typeof item === "bigint" ? item.toString() : item));
6825
6967
  }
6826
6968
  const complete = (status) => closedStates.some((closed) => closed === status);
6827
- const hasScope = (actor, scope) => actor.scopes.includes("admin") || actor.scopes.includes(scope) || scope === "read" && actor.scopes.length > 0;
6969
+ const intakeHoldReason = "Untrusted intake: human triage required";
6970
+ const intakeQuarantined = (ticket) => ticket.holdReason === intakeHoldReason;
6971
+ const intakeOnly = (actor) => actor.scopes.length > 0 && actor.scopes.every((scope) => scope === "intake");
6972
+ const hasScope = (actor, scope, operation) => intakeOnly(actor) ? operation === "create_ticket" : actor.scopes.includes("admin") || actor.scopes.includes(scope) || scope === "read" && actor.scopes.some((value) => value !== "intake");
6973
+ function requireScope(actor, scope, operation) {
6974
+ if (!hasScope(actor, scope, operation)) fail(403, "insufficient_scope", `This credential requires ${scope} permission`);
6975
+ }
6828
6976
  const capabilities = (actor, granted) => granted.filter((permission) => hasScope(actor, permissionScope(permission)));
6829
6977
  const can = (ctx, permission) => capabilities(ctx.actor, ctx.permissions).includes(permission);
6830
6978
  function requirePermission(granted, permission, where) {
@@ -6875,8 +7023,8 @@ function ticketMatch(identifier) {
6875
7023
  const key = identifier.toUpperCase(), [, prefix, number] = issuedKey.exec(key) ?? [];
6876
7024
  return or(sql`${tickets.id}::text = ${identifier}`, eq(tickets.key, key), sql`${tickets.id} IN (SELECT ta.ticket_id FROM ${ticketAliases} ta WHERE ta.key = ${key})`, prefix && number ? and(eq(tickets.number, Number(number)), sql`${tickets.projectId} IN (SELECT pp.project_id FROM ${projectPrefixes} pp WHERE pp.prefix = ${prefix})`) : void 0);
6877
7025
  }
6878
- async function context(db, actor, scope, projectId, workspaceId, permission = scope === "read" ? null : scope === "admin" ? "project.manage" : "ticket.write") {
6879
- if (!hasScope(actor, scope)) fail(403, "insufficient_scope", `This credential requires ${scope} permission`);
7026
+ async function context(db, actor, scope, projectId, workspaceId, permission = scope === "read" ? null : scope === "admin" ? "project.manage" : "ticket.write", operation) {
7027
+ requireScope(actor, scope, operation);
6880
7028
  const ctx = baseContext(db, actor);
6881
7029
  if (workspaceId) {
6882
7030
  const access = await requireWorkspace(ctx, workspaceId, permission);
@@ -7052,11 +7200,26 @@ async function publish(ctx, type, ticketId, data) {
7052
7200
  data
7053
7201
  }]));
7054
7202
  }
7055
- const relationEvents = (type, relation) => [relation.sourceId, relation.targetId].map((ticketId) => ({
7203
+ const relationEvents = (type, relation, sides) => [relation.sourceId, relation.targetId].map((ticketId) => ({
7056
7204
  type,
7057
7205
  ticketId,
7058
- data: { relation }
7206
+ data: { relation },
7207
+ projectId: requireRow(sides.find((side) => side.id === ticketId)).projectId
7059
7208
  }));
7209
+ const deletedSide = alias(tickets, "deleted_side");
7210
+ const survivingSide = alias(tickets, "surviving_side");
7211
+ async function severedRelationEvents(ctx, ticketIds) {
7212
+ return (await ctx.db.select({
7213
+ relation: relations$1,
7214
+ ticketId: survivingSide.id,
7215
+ projectId: survivingSide.projectId
7216
+ }).from(relations$1).innerJoin(deletedSide, or(eq(deletedSide.id, relations$1.sourceId), eq(deletedSide.id, relations$1.targetId))).innerJoin(survivingSide, or(eq(survivingSide.id, relations$1.sourceId), eq(survivingSide.id, relations$1.targetId))).where(and(ticketIds ? inArray(deletedSide.id, [...ticketIds]) : eq(deletedSide.projectId, project(ctx).id), ne(survivingSide.projectId, project(ctx).id)))).map(({ relation, ticketId, projectId }) => ({
7217
+ type: "relation.removed",
7218
+ ticketId,
7219
+ projectId,
7220
+ data: { relation }
7221
+ }));
7222
+ }
7060
7223
  function ownsLiveClaim(ctx, claim) {
7061
7224
  return claim.ownerId === ctx.actor.ownerId && claim.expiresAt > /* @__PURE__ */ new Date() && hasScope(ctx.actor, "claim") && ctx.permissions.includes("ticket.write");
7062
7225
  }
@@ -7148,6 +7311,7 @@ function createDomain(database, services = {}) {
7148
7311
  const input = spec.input.parse(value);
7149
7312
  const projectId = "projectId" in input ? input.projectId : void 0;
7150
7313
  const workspaceId = "workspaceId" in input && !workspaceIsFilter.has(name) ? input.workspaceId : void 0;
7314
+ if (intakeOnly(actor)) requireScope(actor, spec.scope, name);
7151
7315
  if (actor.machine && sessionOnly.has(name)) fail(403, "session_required", "Use a signed-in account for this action");
7152
7316
  if (actor.projectIds && spec.method !== "GET" && !projectId) fail(403, "insufficient_scope", "Project-bound credentials can only change their configured projects");
7153
7317
  const outgoing = [];
@@ -7161,19 +7325,20 @@ function createDomain(database, services = {}) {
7161
7325
  return parseOutput(name, json(await handler(ctx, input)));
7162
7326
  };
7163
7327
  const permission = operationPermission(name);
7164
- if (spec.method === "GET" || remoteReads.has(name)) return invoke(await context(database.db, actor, spec.scope, projectId, workspaceId, permission));
7328
+ if (spec.method === "GET" || remoteReads.has(name)) return invoke(await context(database.db, actor, spec.scope, projectId, workspaceId, permission, name));
7165
7329
  const requestId = "requestId" in input ? input.requestId : void 0;
7166
7330
  const reader = sourceReads[name];
7167
- if (reader && !(requestId && (await database.db.select({ id: receipts.requestId }).from(receipts).where(and(eq(receipts.actorId, actor.id), eq(receipts.requestId, requestId))).limit(1)).length)) read = await reader.read(withServices(await context(database.db, actor, spec.scope, projectId, workspaceId, permission)), input);
7331
+ if (reader && !(requestId && (await database.db.select({ id: receipts.requestId }).from(receipts).where(and(eq(receipts.actorId, actor.id), eq(receipts.requestId, requestId))).limit(1)).length)) read = await reader.read(withServices(await context(database.db, actor, spec.scope, projectId, workspaceId, permission, name)), input);
7168
7332
  const committed = await locked(async (tx) => {
7169
7333
  const hash = createHash("sha256").update(JSON.stringify({
7170
7334
  name,
7171
- input: reader ? reader.receipt(input) : input
7335
+ input: reader ? reader.receipt(input) : input,
7336
+ ...intakeOnly(actor) && { intakeCredential: actor.ownerId }
7172
7337
  })).digest("hex");
7173
7338
  const receipt = requestId ? (await tx.select().from(receipts).where(and(eq(receipts.actorId, actor.id), eq(receipts.requestId, requestId))).limit(1))[0] : void 0;
7174
7339
  if (receipt && (receipt.hash !== hash || receipt.operation !== name)) fail(409, "idempotency_conflict", "This requestId was already used for another write");
7175
7340
  if (receipt && (name === "delete_project" || name === "delete_workspace")) return parseOutput(name, receipt.result);
7176
- const ctx = await context(tx, actor, spec.scope, projectId, workspaceId, permission);
7341
+ const ctx = await context(tx, actor, spec.scope, projectId, workspaceId, permission, name);
7177
7342
  if (receipt) {
7178
7343
  if ((name === "create_time_entry" || name === "update_time_entry" || name === "delete_time_entry") && "ticketId" in input) await getTicket(ctx, input.ticketId);
7179
7344
  const prior = parseOutput(name, receipt.result);
@@ -7670,6 +7835,8 @@ function createAuth(database, config) {
7670
7835
  schema: auth_schema_exports,
7671
7836
  transaction: true
7672
7837
  });
7838
+ const verification = new AsyncLocalStorage();
7839
+ const consumed = () => verification.getStore()?.row ?? null;
7673
7840
  const instance = createInstance(database, config);
7674
7841
  const credentials = socialCredentials(config);
7675
7842
  const mail = config.SMTP_URL && config.SMTP_FROM ? createMailSender(config.SMTP_URL, config.SMTP_FROM) : void 0;
@@ -7704,23 +7871,35 @@ function createAuth(database, config) {
7704
7871
  disableIdTokenSignIn: true
7705
7872
  };
7706
7873
  };
7707
- return betterAuth({
7874
+ const auth = betterAuth({
7708
7875
  appName: "Kanvil",
7709
7876
  baseURL: config.KANVIL_URL,
7710
7877
  secret: config.BETTER_AUTH_SECRET,
7711
7878
  database: (options) => {
7712
7879
  const base = adapter(options);
7713
- const update = (input) => {
7880
+ const update = async (input) => {
7714
7881
  const fields = Object.keys(input.update);
7715
- if (input.model === "apikey" && fields.length > 0 && fields.every((field) => field === "lastRequest" || field === "updatedAt")) return base.findOne({
7882
+ if (input.model === "apikey" && fields.length > 0 && fields.every((field) => field === "lastRequest" || field === "updatedAt")) return await base.findOne({
7716
7883
  model: input.model,
7717
7884
  where: input.where
7718
- });
7885
+ }) ?? consumed();
7719
7886
  return base.update(input);
7720
7887
  };
7888
+ const incrementOne = async (input) => {
7889
+ const row = await base.incrementOne(input);
7890
+ const store = input.model === "apikey" ? verification.getStore() : void 0;
7891
+ if (!store) return row;
7892
+ if (row) store.row = row;
7893
+ else if (store.row && !await base.findOne({
7894
+ model: input.model,
7895
+ where: input.where.filter(({ field }) => field === "id")
7896
+ })) return store.row;
7897
+ return row;
7898
+ };
7721
7899
  return {
7722
7900
  ...base,
7723
- update
7901
+ update,
7902
+ incrementOne
7724
7903
  };
7725
7904
  },
7726
7905
  trustedOrigins: config.trustedOrigins,
@@ -7871,6 +8050,14 @@ function createAuth(database, config) {
7871
8050
  }] : [] })
7872
8051
  ]
7873
8052
  });
8053
+ const verifyApiKey = Object.assign((input) => verification.run({}, () => auth.api.verifyApiKey(input)), auth.api.verifyApiKey);
8054
+ return {
8055
+ ...auth,
8056
+ api: {
8057
+ ...auth.api,
8058
+ verifyApiKey
8059
+ }
8060
+ };
7874
8061
  }
7875
8062
  //#endregion
7876
- export { storageBackends as $, readCalendarFeed as A, attachments as B, deliverBatch as C, createWriteLimiter as D, createAccess as E, imageResponse as F, instanceSettings as G, deliveryCheckpoints as H, stagedUploadCutoff as I, notifications as J, members as K, createObjectStore as L, createTicketIndex as M, createBlobStorage as N, requireInstanceAdmin as O, decodeImage as P, stagedUploads as Q, createPollingJob as R, defaultSenders as S, createHttpSender as T, eventClock as U, blobObjects as V, events as W, projects as X, profilePhotos as Y, pushDevices as Z, reachable as _, deliverySecrets as a, processImports as b, ownerBlocker as c, createDomain as d, subscriptions as et, errorEnvelope as f, one as g, notify as h, createInstance as i, matchesOIDCBinding as it, createTicketSearch as j, requireSession as k, appendEvents as l, json as m, emailCodesAvailable as n, workspaceMembers as nt, otherActiveAdministrator as o, fail as p, notificationPreferences as q, signInProviders as r, workspaces as rt, ownedWorkspaces as s, createAuth as t, tickets as tt, assertVersion as u, recipientColumns as v, DeliveryError as w, receiveRepositoryWebhook as x, requireRow as y, attachmentContent as z };
8063
+ export { stagedUploads as $, requireSession as A, attachmentContent as B, defaultSenders as C, createAccess as D, createHttpSender as E, decodeImage as F, events as G, blobObjects as H, imageResponse as I, notificationPreferences as J, instanceSettings as K, stagedUploadCutoff as L, createTicketSearch as M, createTicketIndex as N, createWriteLimiter as O, createBlobStorage as P, pushDevices as Q, createObjectStore as R, receiveRepositoryWebhook as S, DeliveryError as T, deliveryCheckpoints as U, attachments as V, eventClock as W, profilePhotos as X, notifications as Y, projects as Z, one as _, deliverySecrets as a, matchesOIDCBinding as at, requireRow as b, ownerBlocker as c, createDomain as d, storageBackends as et, errorEnvelope as f, notify as g, json as h, createInstance as i, workspaces as it, readCalendarFeed as j, requireInstanceAdmin as k, appendEvents as l, intakeOnly as m, emailCodesAvailable as n, tickets as nt, otherActiveAdministrator as o, fail as p, members as q, signInProviders as r, workspaceMembers as rt, ownedWorkspaces as s, createAuth as t, subscriptions as tt, assertVersion as u, reachable as v, deliverBatch as w, processImports as x, recipientColumns as y, createPollingJob as z };