@maudecode/kanvil 0.5.1 → 0.6.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 (131) hide show
  1. package/apps/server/dist/{auth-Bm6cIseE.mjs → auth-CFjetlB4.mjs} +49 -7
  2. package/apps/server/dist/client.mjs +1 -1
  3. package/apps/server/dist/{config-CrJuk5mY.mjs → config-BQw6mPzE.mjs} +1 -1
  4. package/apps/server/dist/index.mjs +10 -5
  5. package/apps/server/dist/migrate.mjs +1 -1
  6. package/apps/server/dist/recover.mjs +3 -3
  7. package/apps/server/dist/{src-D80LNZKP.mjs → src-BaTNDJB7.mjs} +88 -3
  8. package/apps/server/drizzle/0039_intake_credential.sql +2 -0
  9. package/apps/server/drizzle/meta/0039_snapshot.json +7020 -0
  10. package/apps/server/drizzle/meta/_journal.json +7 -0
  11. package/apps/web/dist/assets/{CompositeItem-Tueq2VKR.js → CompositeItem-Dotj8JZL.js} +1 -1
  12. package/apps/web/dist/assets/{Trans-BgU0eWAX.js → Trans-CE4EAAY1.js} +1 -1
  13. package/apps/web/dist/assets/admin-settings-CO7nnD9T.js +1 -0
  14. package/apps/web/dist/assets/{archive-restore-CSpoH6mS.js → archive-restore-BMtgjFUu.js} +1 -1
  15. package/apps/web/dist/assets/az-AZ-R8_HFWJr.js +1 -0
  16. package/apps/web/dist/assets/{az-DUcDY8Bp.js → az-B-DxMc__.js} +1 -1
  17. package/apps/web/dist/assets/configuration-Bnt72HRu.js +1 -0
  18. package/apps/web/dist/assets/{corner-left-up-C3OGlXIB.js → corner-left-up-O4OsTW-L.js} +1 -1
  19. package/apps/web/dist/assets/{de-89C1V8Ut.js → de-Btbkq-aX.js} +1 -1
  20. package/apps/web/dist/assets/de-DE-BHgMj19Y.js +1 -0
  21. package/apps/web/dist/assets/default-DqsN66dq.js +56 -0
  22. package/apps/web/dist/assets/delivery-settings-Cji0N0m0.js +1 -0
  23. package/apps/web/dist/assets/{el-D6CICkhi.js → el-DBVEX16k.js} +1 -1
  24. package/apps/web/dist/assets/el-GR-CQFGM5Sr.js +1 -0
  25. package/apps/web/dist/assets/{ellipsis-lJJU8zB7.js → ellipsis-CvEPDi9V.js} +1 -1
  26. package/apps/web/dist/assets/{es-B20Izib2.js → es-CMJoxZBk.js} +1 -1
  27. package/apps/web/dist/assets/es-ES-DZkcRVA4.js +1 -0
  28. package/apps/web/dist/assets/{forms--87R7VQW.js → forms-DbfnnPg_.js} +1 -1
  29. package/apps/web/dist/assets/fr-FR-CRPAezXU.js +1 -0
  30. package/apps/web/dist/assets/{fr-DVlGnGsu.js → fr-OyEmYoRn.js} +1 -1
  31. package/apps/web/dist/assets/{hi-D1F5Pi_I.js → hi-CIgXSseB.js} +1 -1
  32. package/apps/web/dist/assets/hi-IN-2YFE4UQk.js +1 -0
  33. package/apps/web/dist/assets/{highlight-ygDJPvRy.js → highlight-B4K3yEEh.js} +1 -1
  34. package/apps/web/dist/assets/id-ID-B-J0_QIW.js +1 -0
  35. package/apps/web/dist/assets/{id-LESXKW7G.js → id-WV2oSAPB.js} +1 -1
  36. package/apps/web/dist/assets/index-BAnwOrh1.css +2 -0
  37. package/apps/web/dist/assets/index-nltuyQtq.js +17 -0
  38. package/apps/web/dist/assets/invitations-BTB8f2Xl.js +1 -0
  39. package/apps/web/dist/assets/{it-CgRGAs6u.js → it-BWSrRqus.js} +1 -1
  40. package/apps/web/dist/assets/it-IT-CJ0HYggO.js +1 -0
  41. package/apps/web/dist/assets/item-HmFuRZet.js +1 -0
  42. package/apps/web/dist/assets/{ja-B7arhzgX.js → ja-BtOHWvUJ.js} +1 -1
  43. package/apps/web/dist/assets/ja-JP-DDQS7D7b.js +1 -0
  44. package/apps/web/dist/assets/{ko-Ro_UonyA.js → ko-DAryK0HN.js} +1 -1
  45. package/apps/web/dist/assets/ko-KR-CkpoewEa.js +1 -0
  46. package/apps/web/dist/assets/label-settings-ssfEX2uR.js +1 -0
  47. package/apps/web/dist/assets/{markdown-blocks-toSPlHSc.js → markdown-blocks-B1kO6iku.js} +1 -1
  48. package/apps/web/dist/assets/member-directory-BA4ZT1qU.js +1 -0
  49. package/apps/web/dist/assets/{mk-DR3kmrIp.js → mk-DBbiGUk5.js} +1 -1
  50. package/apps/web/dist/assets/mk-MK-C0RhS6XR.js +1 -0
  51. package/apps/web/dist/assets/{nl-D3uRJJLT.js → nl-D4V-8WdF.js} +1 -1
  52. package/apps/web/dist/assets/nl-NL-BSKqlzcL.js +1 -0
  53. package/apps/web/dist/assets/{pl-CtzDOLdV.js → pl-C--QrxO0.js} +1 -1
  54. package/apps/web/dist/assets/pl-PL-CHz38SiU.js +1 -0
  55. package/apps/web/dist/assets/project-lifecycle-COZb9FqM.js +1 -0
  56. package/apps/web/dist/assets/project-settings-DwmTm9KA.js +1 -0
  57. package/apps/web/dist/assets/pt-BR-BotWHRiO.js +1 -0
  58. package/apps/web/dist/assets/{pt-BR-FarXHHuf.js → pt-BR-DvFVKpmO.js} +1 -1
  59. package/apps/web/dist/assets/{public-project-DGf3nkg2.js → public-project-B3hEkeCF.js} +1 -1
  60. package/apps/web/dist/assets/{rotate-ccw-Byx7O3RH.js → rotate-ccw-CB36Zu3L.js} +1 -1
  61. package/apps/web/dist/assets/ru-RU-DtOwNna6.js +1 -0
  62. package/apps/web/dist/assets/{ru-DHMEVxvB.js → ru-UmbPUOka.js} +1 -1
  63. package/apps/web/dist/assets/search-Cvetr1OK.js +85 -0
  64. package/apps/web/dist/assets/{send-DlGV2XQS.js → send-CJDct4Oj.js} +1 -1
  65. package/apps/web/dist/assets/settings-3IXm9lJ-.js +1 -0
  66. package/apps/web/dist/assets/{sliders-horizontal-CEHGGft_.js → sliders-horizontal-dmqWngxy.js} +1 -1
  67. package/apps/web/dist/assets/switch-BtScTpP9.js +1 -0
  68. package/apps/web/dist/assets/{table-2AEfwytk.js → table-byFokzo-.js} +1 -1
  69. package/apps/web/dist/assets/ticket-detail-C3YA3Hzc.js +2 -0
  70. package/apps/web/dist/assets/ticket-editor-CZnZQMG1.js +2 -0
  71. package/apps/web/dist/assets/ticket-list-BXUorfmj.js +6 -0
  72. package/apps/web/dist/assets/ticket-list-controls-BxGKCZMo.js +1 -0
  73. package/apps/web/dist/assets/{tr-CjdSDu0z.js → tr-CNpgQ4ka.js} +1 -1
  74. package/apps/web/dist/assets/tr-TR-CdG2alU2.js +1 -0
  75. package/apps/web/dist/assets/{uk-C8od6u_H.js → uk-MQoTXdpk.js} +1 -1
  76. package/apps/web/dist/assets/uk-UA-Ceu5gorw.js +1 -0
  77. package/apps/web/dist/assets/{upload-BeTAGU_W.js → upload-D1x_ib3B.js} +1 -1
  78. package/apps/web/dist/assets/{useQueries-BtRgTciJ.js → useQueries-BnlNCoy4.js} +1 -1
  79. package/apps/web/dist/assets/{vi-BwdBtgfS.js → vi-CEw4gTWc.js} +1 -1
  80. package/apps/web/dist/assets/vi-VN-CxshgV4W.js +1 -0
  81. package/apps/web/dist/assets/visual-editor-CpdMRn3e.js +208 -0
  82. package/apps/web/dist/assets/workspace-settings-BickBtZx.js +1 -0
  83. package/apps/web/dist/assets/{zh-CN-DM-UoFmL.js → zh-CN-ChzoXkfr.js} +1 -1
  84. package/apps/web/dist/assets/zh-CN-D7Yrjhd3.js +1 -0
  85. package/apps/web/dist/assets/zh-TW-DyF38kqx.js +1 -0
  86. package/apps/web/dist/assets/{zh-TW-q2nrwFjt.js → zh-TW-bWBKARrK.js} +1 -1
  87. package/apps/web/dist/index.html +9 -9
  88. package/docs/api.md +2 -2
  89. package/docs/mobile.md +67 -4
  90. package/docs/releasing.md +6 -4
  91. package/package.json +1 -1
  92. package/apps/web/dist/assets/admin-settings-Ckis3XJG.js +0 -1
  93. package/apps/web/dist/assets/az-AZ-bFxpMMZN.js +0 -1
  94. package/apps/web/dist/assets/de-DE-BeXI30Ll.js +0 -1
  95. package/apps/web/dist/assets/default-Dnep8Yat.js +0 -56
  96. package/apps/web/dist/assets/delivery-settings-cMldZ9K7.js +0 -1
  97. package/apps/web/dist/assets/el-GR-SgRHlWhY.js +0 -1
  98. package/apps/web/dist/assets/es-ES-BF1J5vap.js +0 -1
  99. package/apps/web/dist/assets/fr-FR-D_sldwf6.js +0 -1
  100. package/apps/web/dist/assets/hi-IN-CfOw4TWf.js +0 -1
  101. package/apps/web/dist/assets/id-ID-CLKtHO7Q.js +0 -1
  102. package/apps/web/dist/assets/index-Bn-MADyj.css +0 -2
  103. package/apps/web/dist/assets/index-Ds3IHeIi.js +0 -17
  104. package/apps/web/dist/assets/invitations-C5WbdDUQ.js +0 -1
  105. package/apps/web/dist/assets/it-IT-eVZNW5qX.js +0 -1
  106. package/apps/web/dist/assets/item-BD1Poxfs.js +0 -1
  107. package/apps/web/dist/assets/ja-JP-D96njn-P.js +0 -1
  108. package/apps/web/dist/assets/ko-KR-Bash_ieA.js +0 -1
  109. package/apps/web/dist/assets/label-settings-BaikCfal.js +0 -1
  110. package/apps/web/dist/assets/member-directory-BjUy1KuK.js +0 -1
  111. package/apps/web/dist/assets/mk-MK-BWQWGIRx.js +0 -1
  112. package/apps/web/dist/assets/nl-NL-D1tvo0Gd.js +0 -1
  113. package/apps/web/dist/assets/pl-PL-DNgGUEcQ.js +0 -1
  114. package/apps/web/dist/assets/project-lifecycle-BBHcRi6p.js +0 -1
  115. package/apps/web/dist/assets/project-settings-BaYuxYjg.js +0 -1
  116. package/apps/web/dist/assets/pt-BR-CZsUZQ6P.js +0 -1
  117. package/apps/web/dist/assets/ru-RU-4ew92YE_.js +0 -1
  118. package/apps/web/dist/assets/search-DwsPEC4E.js +0 -85
  119. package/apps/web/dist/assets/settings-C_sN8Y1h.js +0 -1
  120. package/apps/web/dist/assets/switch-B_rsZYBf.js +0 -1
  121. package/apps/web/dist/assets/ticket-detail-C2l8nwF-.js +0 -2
  122. package/apps/web/dist/assets/ticket-editor-B0UB98HS.js +0 -2
  123. package/apps/web/dist/assets/ticket-list-D_MHudj1.js +0 -6
  124. package/apps/web/dist/assets/ticket-list-controls-jKg4i-NA.js +0 -1
  125. package/apps/web/dist/assets/tr-TR-CM381XCw.js +0 -1
  126. package/apps/web/dist/assets/uk-UA-AeOiWCVo.js +0 -1
  127. package/apps/web/dist/assets/vi-VN-td08xXv9.js +0 -1
  128. package/apps/web/dist/assets/visual-editor-CzB_qwvB.js +0 -208
  129. package/apps/web/dist/assets/workspace-settings-DOr9j8X1.js +0 -1
  130. package/apps/web/dist/assets/zh-CN-D3dhEYLS.js +0 -1
  131. package/apps/web/dist/assets/zh-TW-CfhOEFo8.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-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";
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-BQw6mPzE.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-BaTNDJB7.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";
@@ -218,6 +218,7 @@ const tickets = pgTable("tickets", {
218
218
  automationReady: boolean("automation_ready").notNull().default(false),
219
219
  claimRequired: boolean("claim_required").notNull().default(false),
220
220
  holdReason: text("hold_reason"),
221
+ intakeCredentialId: text("intake_credential_id"),
221
222
  startAt: timestamp("start_at", { withTimezone: true }),
222
223
  dueAt: timestamp("due_at", { withTimezone: true }),
223
224
  archived: boolean("archived").notNull().default(false),
@@ -233,6 +234,7 @@ const tickets = pgTable("tickets", {
233
234
  index("ticket_parent").on(table.parentId),
234
235
  index("ticket_project_position").on(table.projectId, table.position),
235
236
  index("ticket_state").on(table.stateId),
237
+ index("ticket_intake_credential").on(table.intakeCredentialId).where(sql`${table.intakeCredentialId} IS NOT NULL`),
236
238
  foreignKey({
237
239
  name: "ticket_workflow_state",
238
240
  columns: [
@@ -711,8 +713,7 @@ const createObjectStore = (configuration) => {
711
713
  if (!create) throw new Error("Unsupported object storage provider");
712
714
  return create(configuration.config);
713
715
  };
714
- //#endregion
715
- //#region src/blob-storage.ts
716
+ const intakeAttachmentLifetime = 2592e6;
716
717
  const stagedUploadCutoff = () => new Date(Date.now() - stagedUploadLifetime);
717
718
  const digest$1 = (bytes) => createHash("sha256").update(bytes).digest("hex");
718
719
  function decodeBlobData(value, limit) {
@@ -811,6 +812,31 @@ function createBlobStorage(database, instance, factory = createObjectStore, sche
811
812
  });
812
813
  }
813
814
  async function collectBatch() {
815
+ await database.db.transaction(async (tx) => {
816
+ await tx.execute(sql`SELECT pg_advisory_xact_lock(7361080)`);
817
+ const expired = await tx.select({
818
+ attachment: attachments,
819
+ key: tickets.key,
820
+ title: tickets.title
821
+ }).from(attachments).innerJoin(tickets, eq(tickets.id, attachments.ticketId)).where(and(isNotNull(tickets.intakeCredentialId), eq(tickets.holdReason, intakeHoldReason), lt(attachments.createdAt, /* @__PURE__ */ new Date(Date.now() - intakeAttachmentLifetime)))).orderBy(asc(attachments.createdAt), asc(attachments.id)).limit(100);
822
+ if (!expired.length) return;
823
+ await tx.delete(attachments).where(inArray(attachments.id, expired.map(({ attachment }) => attachment.id)));
824
+ await appendEvents(tx, expired.map(({ attachment, key, title }) => ({
825
+ projectId: attachment.projectId,
826
+ ticketId: attachment.ticketId,
827
+ actorId: null,
828
+ type: "attachment.deleted",
829
+ data: {
830
+ attachmentId: attachment.id,
831
+ key,
832
+ title,
833
+ source: {
834
+ kind: "system",
835
+ id: "blob-cleanup"
836
+ }
837
+ }
838
+ })));
839
+ });
814
840
  return database.db.transaction(async (tx) => {
815
841
  await tx.delete(stagedUploads).where(lt(stagedUploads.createdAt, stagedUploadCutoff()));
816
842
  const rows = await tx.select({ object: blobObjects }).from(blobObjects).leftJoin(attachments, eq(attachments.objectId, blobObjects.id)).leftJoin(profilePhotos, eq(profilePhotos.objectId, blobObjects.id)).leftJoin(stagedUploads, eq(stagedUploads.id, blobObjects.id)).leftJoin(projects, or(eq(projects.iconObjectId, blobObjects.id), eq(projects.backgroundObjectId, blobObjects.id))).where(and(isNull(attachments.id), isNull(profilePhotos.userId), isNull(stagedUploads.id), isNull(projects.id), lt(blobObjects.createdAt, /* @__PURE__ */ new Date(Date.now() - 864e5)))).limit(20).for("update", {
@@ -2453,7 +2479,8 @@ async function createTicketRow(ctx, input) {
2453
2479
  customFields: intakeOnly(ctx.actor) ? {} : applyCustomFieldValues(await projectCustomFields(ctx), null, customFields),
2454
2480
  ...await allocateTicket(ctx),
2455
2481
  id,
2456
- createdBy: ctx.actor.id
2482
+ createdBy: ctx.actor.id,
2483
+ intakeCredentialId: intakeOnly(ctx.actor) ? ctx.actor.ownerId : null
2457
2484
  }).returning());
2458
2485
  await subscribe(ctx, row.projectId, row.id, [ctx.actor.id, row.assigneeId]);
2459
2486
  return {
@@ -3495,6 +3522,14 @@ const collaborationHandlers = {
3495
3522
  async upload_attachment(ctx, input) {
3496
3523
  const ticket = await getTicket(ctx, input.ticketId);
3497
3524
  const bytes = decodeBlobData(input.data, attachmentLimit);
3525
+ if (intakeOnly(ctx.actor)) {
3526
+ const stored = one(await ctx.db.select({
3527
+ files: sql`count(*) filter (where ${attachments.ticketId} = ${ticket.id})`.mapWith(Number),
3528
+ bytes: sql`coalesce(sum(${attachments.size}), 0)`.mapWith(Number)
3529
+ }).from(attachments).innerJoin(tickets, eq(tickets.id, attachments.ticketId)).where(and(eq(tickets.intakeCredentialId, ctx.actor.ownerId), eq(tickets.holdReason, intakeHoldReason))));
3530
+ if (stored.files >= 10) fail(409, "attachment_limit", `An intake credential can attach at most 10 files to a ticket`);
3531
+ if (stored.bytes + bytes.length > 1e9) fail(409, "attachment_limit", "This intake credential's untriaged tickets hold too many attachment bytes");
3532
+ }
3498
3533
  const id = crypto.randomUUID();
3499
3534
  await ctx.blobs.put(ctx.db, id, bytes, input.contentType);
3500
3535
  const row = one(await ctx.db.insert(attachments).values({
@@ -6969,7 +7004,12 @@ const complete = (status) => closedStates.some((closed) => closed === status);
6969
7004
  const intakeHoldReason = "Untrusted intake: human triage required";
6970
7005
  const intakeQuarantined = (ticket) => ticket.holdReason === intakeHoldReason;
6971
7006
  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");
7007
+ const intakeOperations = /* @__PURE__ */ new Set([
7008
+ "create_ticket",
7009
+ "upload_attachment",
7010
+ "list_attachments"
7011
+ ]);
7012
+ const hasScope = (actor, scope, operation) => intakeOnly(actor) ? !!operation && intakeOperations.has(operation) : actor.scopes.includes("admin") || actor.scopes.includes(scope) || scope === "read" && actor.scopes.some((value) => value !== "intake");
6973
7013
  function requireScope(actor, scope, operation) {
6974
7014
  if (!hasScope(actor, scope, operation)) fail(403, "insufficient_scope", `This credential requires ${scope} permission`);
6975
7015
  }
@@ -7046,7 +7086,9 @@ async function context(db, actor, scope, projectId, workspaceId, permission = sc
7046
7086
  };
7047
7087
  }
7048
7088
  async function getTicket(ctx, identifier) {
7049
- return one(await ctx.db.select().from(tickets).where(and(eq(tickets.projectId, project(ctx).id), ticketMatch(identifier))).limit(1));
7089
+ const ticket = one(await ctx.db.select().from(tickets).where(and(eq(tickets.projectId, project(ctx).id), ticketMatch(identifier))).limit(1));
7090
+ if (intakeOnly(ctx.actor) && !(ticket.intakeCredentialId === ctx.actor.ownerId && intakeQuarantined(ticket))) fail(404, "not_found", "The requested record was not found");
7091
+ return ticket;
7050
7092
  }
7051
7093
  const insertBatch = 1e3;
7052
7094
  async function insertAll(rows, insert) {
@@ -1,4 +1,4 @@
1
- import { C as errorSchema, Ct as streamErrorSchema, E as eventSchema, G as notificationReadInput, H as notificationIdsInput, K as notificationsSchema, Q as parsePublicOutput, U as notificationPreferencesInput, W as notificationPreferencesSchema, Y as operations, Z as parseOutput, a as checkpointSchema, at as publicOperations, q as okSchema, t as ApiError, yt as snapshotSchema } from "./src-D80LNZKP.mjs";
1
+ import { C as errorSchema, Ct as streamErrorSchema, E as eventSchema, G as notificationReadInput, H as notificationIdsInput, K as notificationsSchema, Q as parsePublicOutput, U as notificationPreferencesInput, W as notificationPreferencesSchema, Y as operations, Z as parseOutput, a as checkpointSchema, at as publicOperations, q as okSchema, t as ApiError, yt as snapshotSchema } from "./src-BaTNDJB7.mjs";
2
2
  import { z } from "zod";
3
3
  import { parseArgs } from "node:util";
4
4
  import { constants } from "node:fs";
@@ -1,4 +1,4 @@
1
- import { Bt as searchSettingsSchema, Pt as brokerSettingsSchema, Rt as s3SettingsSchema, m as defaultClaimMaxLeaseSeconds, o as claimLeaseSecondsSchema } from "./src-D80LNZKP.mjs";
1
+ import { Bt as searchSettingsSchema, Pt as brokerSettingsSchema, Rt as s3SettingsSchema, m as defaultClaimMaxLeaseSeconds, o as claimLeaseSecondsSchema } from "./src-BaTNDJB7.mjs";
2
2
  import pg from "pg";
3
3
  import { drizzle } from "drizzle-orm/node-postgres";
4
4
  import { relations } from "drizzle-orm";
@@ -1,7 +1,7 @@
1
- import { a as auth_schema_exports, d as user$1, i as apikey, n as createDatabase, r as account, t as readConfig, u as session } from "./config-CrJuk5mY.mjs";
2
- import { A as hasAdminRole, B as memberSchema, Bt as searchSettingsSchema, C as errorSchema, Ft as instanceUpdateSchema, G as notificationReadInput, H as notificationIdsInput, I as isOperation, It as photoInputSchema, L as isPresetColor, Lt as photoResultSchema, M as importProviderDefinitions, Mt as adminInstanceSchema, Pt as brokerSettingsSchema, R as isPublicOperation, Rt as s3SettingsSchema, U as notificationPreferencesInput, W as notificationPreferencesSchema, Y as operations, at as publicOperations, c as credentialInput, g as defaultWorkflow, ht as scopes, j as importLimits, mt as roles, ot as pushDeviceInput, q as okSchema, s as closedStates, t as ApiError, wt as ticketExportFormat, xt as stateLabels } from "./src-D80LNZKP.mjs";
1
+ import { a as auth_schema_exports, d as user$1, i as apikey, n as createDatabase, r as account, t as readConfig, u as session } from "./config-BQw6mPzE.mjs";
2
+ import { A as hasAdminRole, B as memberSchema, Bt as searchSettingsSchema, C as errorSchema, Ft as instanceUpdateSchema, G as notificationReadInput, H as notificationIdsInput, I as isOperation, It as photoInputSchema, L as isPresetColor, Lt as photoResultSchema, M as importProviderDefinitions, Mt as adminInstanceSchema, Pt as brokerSettingsSchema, R as isPublicOperation, Rt as s3SettingsSchema, U as notificationPreferencesInput, W as notificationPreferencesSchema, Y as operations, at as publicOperations, c as credentialInput, g as defaultWorkflow, ht as scopes, j as importLimits, mt as roles, ot as pushDeviceInput, q as okSchema, s as closedStates, t as ApiError, wt as ticketExportFormat, xt as stateLabels } from "./src-BaTNDJB7.mjs";
3
3
  import { t as migrateDatabase } from "./migrate-C9f4ozB4.mjs";
4
- import { $ as stagedUploads, A as requireSession, B as attachmentContent, C as defaultSenders, D as createAccess, E as createHttpSender, F as decodeImage, G as events, H as blobObjects, I as imageResponse, J as notificationPreferences, L as stagedUploadCutoff, M as createTicketSearch, N as createTicketIndex, O as createWriteLimiter, P as createBlobStorage, Q as pushDevices, R as createObjectStore, S as receiveRepositoryWebhook, T as DeliveryError, U as deliveryCheckpoints, V as attachments, W as eventClock, X as profilePhotos, Y as notifications, Z as projects, _ as one, a as deliverySecrets, at as matchesOIDCBinding, b as requireRow, c as ownerBlocker, d as createDomain, et as storageBackends, f as errorEnvelope, g as notify, h as json, i as createInstance, it as workspaces, j as readCalendarFeed, k as requireInstanceAdmin, l as appendEvents, m as intakeOnly, n as emailCodesAvailable, nt as tickets, o as otherActiveAdministrator, p as fail, q as members, r as signInProviders, rt as workspaceMembers, s as ownedWorkspaces, t as createAuth, tt as subscriptions, u as assertVersion, v as reachable, w as deliverBatch, x as processImports, y as recipientColumns, z as createPollingJob } from "./auth-Bm6cIseE.mjs";
4
+ import { $ as stagedUploads, A as requireSession, B as attachmentContent, C as defaultSenders, D as createAccess, E as createHttpSender, F as decodeImage, G as events, H as blobObjects, I as imageResponse, J as notificationPreferences, L as stagedUploadCutoff, M as createTicketSearch, N as createTicketIndex, O as createWriteLimiter, P as createBlobStorage, Q as pushDevices, R as createObjectStore, S as receiveRepositoryWebhook, T as DeliveryError, U as deliveryCheckpoints, V as attachments, W as eventClock, X as profilePhotos, Y as notifications, Z as projects, _ as one, a as deliverySecrets, at as matchesOIDCBinding, b as requireRow, c as ownerBlocker, d as createDomain, et as storageBackends, f as errorEnvelope, g as notify, h as json, i as createInstance, it as workspaces, j as readCalendarFeed, k as requireInstanceAdmin, l as appendEvents, m as intakeOnly, n as emailCodesAvailable, nt as tickets, o as otherActiveAdministrator, p as fail, q as members, r as signInProviders, rt as workspaceMembers, s as ownedWorkspaces, t as createAuth, tt as subscriptions, u as assertVersion, v as reachable, w as deliverBatch, x as processImports, y as recipientColumns, z as createPollingJob } from "./auth-CFjetlB4.mjs";
5
5
  import { serve } from "@hono/node-server";
6
6
  import pg from "pg";
7
7
  import { drizzle } from "drizzle-orm/node-postgres";
@@ -359,7 +359,7 @@ function createCleanup() {
359
359
  }
360
360
  //#endregion
361
361
  //#region src/version.ts
362
- const applicationVersion = "0.5.1";
362
+ const applicationVersion = "0.6.1";
363
363
  //#endregion
364
364
  //#region src/realtime.ts
365
365
  async function createRealtime(config) {
@@ -3436,10 +3436,15 @@ async function initializeApplication(database, config, adapters, cleanup) {
3436
3436
  const result = await receiveRepositoryWebhook(domain, repositories, secrets, c.req.param("connectionId"), c.req.raw.headers, await c.req.text());
3437
3437
  return c.json(result, result.outcome === "duplicate" ? 200 : 202);
3438
3438
  });
3439
+ const intakeRoutes = [
3440
+ ["POST", /^\/api\/v1\/projects\/[^/]+\/tickets$/],
3441
+ ["POST", /^\/api\/v1\/projects\/[^/]+\/tickets\/[^/]+\/attachments$/],
3442
+ ["GET", /^\/api\/v1\/projects\/[^/]+\/tickets\/[^/]+\/attachments$/]
3443
+ ];
3439
3444
  app.use("/api/v1/*", async (c, next) => {
3440
3445
  access.checkOrigin(c.req.raw);
3441
3446
  const actor = await access.authenticate(c.req.raw);
3442
- if (intakeOnly(actor) && !(c.req.method === "POST" && /^\/api\/v1\/projects\/[^/]+\/tickets$/.test(c.req.path))) fail(403, "insufficient_scope", "Intake credentials can only create tickets");
3447
+ if (intakeOnly(actor) && !intakeRoutes.some(([method, path]) => c.req.method === method && path.test(c.req.path))) fail(403, "insufficient_scope", "Intake credentials can only create tickets and attach files to them");
3443
3448
  c.set("actor", actor);
3444
3449
  if (!actor.machine && ![
3445
3450
  "GET",
@@ -1,4 +1,4 @@
1
- import { n as createDatabase, t as readConfig } from "./config-CrJuk5mY.mjs";
1
+ import { n as createDatabase, t as readConfig } from "./config-BQw6mPzE.mjs";
2
2
  import { t as migrateDatabase } from "./migrate-C9f4ozB4.mjs";
3
3
  //#region src/cli/migrate.ts
4
4
  const database = createDatabase(readConfig());
@@ -1,6 +1,6 @@
1
- import { d as user, n as createDatabase, r as account, t as readConfig, u as session } from "./config-CrJuk5mY.mjs";
2
- import { A as hasAdminRole } from "./src-D80LNZKP.mjs";
3
- import { K as instanceSettings, p as fail, t as createAuth } from "./auth-Bm6cIseE.mjs";
1
+ import { d as user, n as createDatabase, r as account, t as readConfig, u as session } from "./config-BQw6mPzE.mjs";
2
+ import { A as hasAdminRole } from "./src-BaTNDJB7.mjs";
3
+ import { K as instanceSettings, p as fail, t as createAuth } from "./auth-CFjetlB4.mjs";
4
4
  import { and, eq, isNull, sql } from "drizzle-orm";
5
5
  import { z } from "zod";
6
6
  import { parseArgs } from "node:util";
@@ -1,5 +1,79 @@
1
1
  import { z } from "zod";
2
2
  import i18next from "i18next";
3
+ //#region ../../packages/contracts/src/icons.ts
4
+ const ticketSymbols = {
5
+ status: {
6
+ backlog: {
7
+ icon: "circle-dashed",
8
+ tone: "muted"
9
+ },
10
+ todo: {
11
+ icon: "circle",
12
+ tone: "muted"
13
+ },
14
+ in_progress: {
15
+ icon: "circle-dot",
16
+ tone: "accent"
17
+ },
18
+ in_review: {
19
+ icon: "clock",
20
+ tone: "warning"
21
+ },
22
+ done: {
23
+ icon: "circle-check",
24
+ tone: "success"
25
+ },
26
+ cancelled: {
27
+ icon: "circle-x",
28
+ tone: "muted"
29
+ }
30
+ },
31
+ priority: {
32
+ urgent: {
33
+ icon: "octagon-alert",
34
+ tone: "danger"
35
+ },
36
+ high: {
37
+ icon: "arrow-up",
38
+ tone: "warning"
39
+ },
40
+ medium: {
41
+ icon: "equal",
42
+ tone: "foreground"
43
+ },
44
+ low: {
45
+ icon: "arrow-down",
46
+ tone: "accent"
47
+ },
48
+ none: {
49
+ icon: "minus",
50
+ tone: "muted"
51
+ }
52
+ },
53
+ kind: {
54
+ feature: {
55
+ icon: "sparkles",
56
+ tone: "muted"
57
+ },
58
+ bug: {
59
+ icon: "bug",
60
+ tone: "muted"
61
+ },
62
+ chore: {
63
+ icon: "wrench",
64
+ tone: "muted"
65
+ },
66
+ research: {
67
+ icon: "flask-conical",
68
+ tone: "muted"
69
+ },
70
+ documentation: {
71
+ icon: "book-open",
72
+ tone: "muted"
73
+ }
74
+ }
75
+ };
76
+ //#endregion
3
77
  //#region ../../packages/contracts/src/instance.ts
4
78
  const endpoint$1 = z.url({ protocol: /^https?$/ }).refine((value) => !/^https?:\/\/[^/]*@/.test(value) && !/[?#]/.test(value), "Use an HTTP(S) endpoint without embedded credentials");
5
79
  const s3SettingsSchema = z.object({
@@ -930,6 +1004,7 @@ var en_US_default = {
930
1004
  },
931
1005
  ticket: {
932
1006
  list: {
1007
+ manageViews: "Manage views",
933
1008
  selectSection: "Select {{section}} tickets",
934
1009
  expandSection: "Expand {{section}} tickets",
935
1010
  collapseSection: "Collapse {{section}} tickets",
@@ -1058,6 +1133,7 @@ var en_US_default = {
1058
1133
  noDue: "No due date"
1059
1134
  },
1060
1135
  filter: {
1136
+ add: "Add filter",
1061
1137
  me: "Me",
1062
1138
  searchTickets: "Search tickets",
1063
1139
  searchTicketsPlaceholder: "Search tickets…",
@@ -1550,7 +1626,7 @@ var en_US_default = {
1550
1626
  permission: {
1551
1627
  intake: {
1552
1628
  name: "Submit reports",
1553
- description: "Create quarantined tickets only. Cannot read or edit tickets. Use alone."
1629
+ description: "Create quarantined tickets and attach files to them only. Cannot read or edit tickets. Use alone."
1554
1630
  },
1555
1631
  read: {
1556
1632
  name: "Read tickets",
@@ -2497,6 +2573,9 @@ var en_US_default = {
2497
2573
  }
2498
2574
  },
2499
2575
  native: {
2576
+ "navigation.projectButton": "{{project}} ⌄",
2577
+ "navigation.recentProjects": "Recent projects",
2578
+ "navigation.switchProject": "Switch workspace or project",
2500
2579
  labelValue: "{{label}}: {{value}}",
2501
2580
  ui: {
2502
2581
  dismiss: "Dismiss",
@@ -2545,6 +2624,10 @@ var en_US_default = {
2545
2624
  create: "Create project"
2546
2625
  },
2547
2626
  tickets: {
2627
+ options: "List options",
2628
+ expandAll: "Expand all sections",
2629
+ collapseAll: "Collapse all sections",
2630
+ select: "Select tickets",
2548
2631
  search: "Search tickets",
2549
2632
  searchPlaceholder: "Title, description, or key",
2550
2633
  allStatuses: "All statuses",
@@ -2671,6 +2754,7 @@ var en_US_default = {
2671
2754
  next: "Next activity"
2672
2755
  },
2673
2756
  inbox: {
2757
+ read: "Read",
2674
2758
  markAllRead: "Mark all read",
2675
2759
  empty: "No notifications"
2676
2760
  },
@@ -2855,6 +2939,7 @@ const attachmentLimit = 1e7;
2855
2939
  const stagedUploadLifetime = 864e5;
2856
2940
  const stateLabels = labelTable(states, (value) => t(`common.status.${value}`));
2857
2941
  const priorityLabels = labelTable(priorities, (value) => t(`common.priority.${value}`));
2942
+ ticketSymbols.status.backlog.tone, ticketSymbols.status.todo.tone, ticketSymbols.status.in_progress.tone, ticketSymbols.status.in_review.tone, ticketSymbols.status.done.tone, ticketSymbols.status.cancelled.tone;
2858
2943
  const id = z.string().min(1).max(80);
2859
2944
  const timestamp = z.iso.datetime();
2860
2945
  const version = z.number().int().positive();
@@ -4635,7 +4720,7 @@ const operations = {
4635
4720
  upload_attachment: op(ticketInput.extend({
4636
4721
  ...mutation,
4637
4722
  ...fileFields
4638
- }).strict(), attachmentSchema, "POST", "/projects/{projectId}/tickets/{ticketId}/attachments", "write", "Upload up to 10 MB of base64-encoded bytes. The original filename is metadata, never a filesystem path."),
4723
+ }).strict(), attachmentSchema, "POST", "/projects/{projectId}/tickets/{ticketId}/attachments", "write", "Upload up to 10 MB of base64-encoded bytes. The original filename is metadata, never a filesystem path. An intake credential can attach up to 10 files to a quarantined ticket it created, within 1 GB across its quarantined tickets."),
4639
4724
  stage_upload: op(projectInput.extend({
4640
4725
  ...mutation,
4641
4726
  ...fileFields
@@ -4644,7 +4729,7 @@ const operations = {
4644
4729
  ...mutation,
4645
4730
  uploadId: z.uuid()
4646
4731
  }).strict(), okSchema, "DELETE", "/projects/{projectId}/uploads/{uploadId}", "write", "Delete one of your unclaimed staged uploads and its bytes."),
4647
- list_attachments: op(ticketInput, z.array(attachmentSchema), "GET", "/projects/{projectId}/tickets/{ticketId}/attachments", "read", "List authorized attachment metadata."),
4732
+ list_attachments: op(ticketInput, z.array(attachmentSchema), "GET", "/projects/{projectId}/tickets/{ticketId}/attachments", "read", "List authorized attachment metadata. An intake credential can list them on a quarantined ticket it created."),
4648
4733
  delete_attachment: op(projectInput.extend({
4649
4734
  ...mutation,
4650
4735
  attachmentId: z.uuid()
@@ -0,0 +1,2 @@
1
+ ALTER TABLE "tickets" ADD COLUMN "intake_credential_id" text;--> statement-breakpoint
2
+ CREATE INDEX "ticket_intake_credential" ON "tickets" USING btree ("intake_credential_id") WHERE "tickets"."intake_credential_id" IS NOT NULL;