@maudecode/kanvil 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/apps/server/dist/{auth-D43XyHdK.mjs → auth-CVnX2XIg.mjs} +3759 -3550
  2. package/apps/server/dist/client.mjs +1 -1
  3. package/apps/server/dist/{config-CQVdVhXv.mjs → config-DUIdB3c2.mjs} +1 -1
  4. package/apps/server/dist/index.mjs +12 -7
  5. package/apps/server/dist/migrate.mjs +1 -1
  6. package/apps/server/dist/recover.mjs +3 -3
  7. package/apps/server/dist/{src-D70GmZ5R.mjs → src-CC6lPIU_.mjs} +107 -9
  8. package/apps/server/drizzle/0042_public_boards.sql +37 -0
  9. package/apps/server/drizzle/0043_epics.sql +18 -0
  10. package/apps/server/drizzle/meta/0042_snapshot.json +7315 -0
  11. package/apps/server/drizzle/meta/0043_snapshot.json +7466 -0
  12. package/apps/server/drizzle/meta/_journal.json +14 -0
  13. package/apps/web/dist/assets/{CompositeItem-DLTRsada.js → CompositeItem-GgAhkI4m.js} +1 -1
  14. package/apps/web/dist/assets/{Trans-DqX-qhvQ.js → Trans-CT9xiBED.js} +1 -1
  15. package/apps/web/dist/assets/admin-settings-C7yxWm-w.js +1 -0
  16. package/apps/web/dist/assets/{archive-restore-BO1qng5Y.js → archive-restore-UgUTJyLs.js} +1 -1
  17. package/apps/web/dist/assets/az-AZ-IkRP1MKo.js +1 -0
  18. package/apps/web/dist/assets/{az-CEW7-a7J.js → az-CZ46yOUl.js} +1 -1
  19. package/apps/web/dist/assets/{configuration-D0zvwrXt.js → configuration-CU0QOaRw.js} +1 -1
  20. package/apps/web/dist/assets/{corner-left-up-Be7Z9Uhf.js → corner-left-up-BFY7AqdS.js} +1 -1
  21. package/apps/web/dist/assets/{de-B-oGVMYL.js → de-CseN-3gF.js} +1 -1
  22. package/apps/web/dist/assets/de-DE-DCGKtpLI.js +1 -0
  23. package/apps/web/dist/assets/default-CN9zcRIy.js +56 -0
  24. package/apps/web/dist/assets/{delivery-settings-BrwSrFIN.js → delivery-settings-xx9Bb6Ux.js} +1 -1
  25. package/apps/web/dist/assets/el-GR-C5w6p2-z.js +1 -0
  26. package/apps/web/dist/assets/{el-Cp69kezM.js → el-psH0HOR3.js} +1 -1
  27. package/apps/web/dist/assets/{ellipsis-B0AE8ax3.js → ellipsis-CcIJddV1.js} +1 -1
  28. package/apps/web/dist/assets/{es-CwOMVSTx.js → es-BVvGrD23.js} +1 -1
  29. package/apps/web/dist/assets/es-ES-PjJlJCgT.js +1 -0
  30. package/apps/web/dist/assets/{forms-CkVpTx29.js → forms-BUNl5uNV.js} +1 -1
  31. package/apps/web/dist/assets/{fr-Cy4co9ic.js → fr-C43nghuI.js} +1 -1
  32. package/apps/web/dist/assets/fr-FR-CGWH-Juq.js +1 -0
  33. package/apps/web/dist/assets/{hi-DbO9TKwg.js → hi-Ds3DGtT-.js} +1 -1
  34. package/apps/web/dist/assets/hi-IN-B3bq8hDJ.js +1 -0
  35. package/apps/web/dist/assets/{highlight-zMg1BK-r.js → highlight-BPZqoBaO.js} +1 -1
  36. package/apps/web/dist/assets/id-ID-98H240ET.js +1 -0
  37. package/apps/web/dist/assets/{id-B6NKsmCm.js → id-tg9awgd8.js} +1 -1
  38. package/apps/web/dist/assets/index-CvFGdLre.js +17 -0
  39. package/apps/web/dist/assets/{invitations-Dp40BfHj.js → invitations-fH1M9hYm.js} +1 -1
  40. package/apps/web/dist/assets/{it-ilGYCq2T.js → it-DRwaWQyE.js} +1 -1
  41. package/apps/web/dist/assets/it-IT-CNS9cL7I.js +1 -0
  42. package/apps/web/dist/assets/{item-DQR89bKm.js → item-BbaYy50i.js} +1 -1
  43. package/apps/web/dist/assets/ja-JP-BFTqtCdV.js +1 -0
  44. package/apps/web/dist/assets/{ja-D-hJRTgP.js → ja-jj4Ubayf.js} +1 -1
  45. package/apps/web/dist/assets/{ko-ajkkoJ_d.js → ko-Dn2pRrar.js} +1 -1
  46. package/apps/web/dist/assets/ko-KR-C58VNccr.js +1 -0
  47. package/apps/web/dist/assets/{label-settings-ChnKkA97.js → label-settings-CbBHSL0c.js} +1 -1
  48. package/apps/web/dist/assets/{markdown-blocks-CpzrwyQG.js → markdown-blocks-B6mE_S6n.js} +1 -1
  49. package/apps/web/dist/assets/{member-directory-XK0ZEGUq.js → member-directory-CoGg7WkW.js} +1 -1
  50. package/apps/web/dist/assets/{mk-C_sMyEHX.js → mk-DQUnXaPE.js} +1 -1
  51. package/apps/web/dist/assets/mk-MK-Bf5RDo6j.js +1 -0
  52. package/apps/web/dist/assets/{nl-D9-P4UPR.js → nl-C_Ijir3O.js} +1 -1
  53. package/apps/web/dist/assets/nl-NL-D1WFJyOY.js +1 -0
  54. package/apps/web/dist/assets/{pl-CiQM7fSB.js → pl-DgWXgYL-.js} +1 -1
  55. package/apps/web/dist/assets/pl-PL--jAcwyAK.js +1 -0
  56. package/apps/web/dist/assets/project-lifecycle-BHWCMq7o.js +1 -0
  57. package/apps/web/dist/assets/project-settings-CBG5CZX1.js +1 -0
  58. package/apps/web/dist/assets/pt-BR-CJQfuq9b.js +1 -0
  59. package/apps/web/dist/assets/{pt-BR-CP7dhuqF.js → pt-BR-N-Xvp9fC.js} +1 -1
  60. package/apps/web/dist/assets/public-project-DmTr9q4c.js +1 -0
  61. package/apps/web/dist/assets/{rotate-ccw-Cxh9ec2s.js → rotate-ccw-BFYpBtuj.js} +1 -1
  62. package/apps/web/dist/assets/{ru-DxJ6vF4E.js → ru-Ctq-5FYl.js} +1 -1
  63. package/apps/web/dist/assets/ru-RU-BtvJ8Glt.js +1 -0
  64. package/apps/web/dist/assets/{search-DfbsqO7M.js → search-C2QaoLwI.js} +3 -3
  65. package/apps/web/dist/assets/{send-DrJl0BKm.js → send-qRPqbT2m.js} +1 -1
  66. package/apps/web/dist/assets/{settings-BN8uIiBG.js → settings-BNPajW83.js} +1 -1
  67. package/apps/web/dist/assets/{sliders-horizontal-DM47MLe7.js → sliders-horizontal-S16oyCiS.js} +1 -1
  68. package/apps/web/dist/assets/{switch-CJTVGtE9.js → switch-C44Ouoqh.js} +1 -1
  69. package/apps/web/dist/assets/{table-BLLjFPWD.js → table-esnQpDtb.js} +1 -1
  70. package/apps/web/dist/assets/ticket-detail-BVyScTlg.js +2 -0
  71. package/apps/web/dist/assets/ticket-editor-BjuSeeiA.js +2 -0
  72. package/apps/web/dist/assets/ticket-list-BCvUvYju.js +6 -0
  73. package/apps/web/dist/assets/ticket-list-controls-BiC5CIwM.js +1 -0
  74. package/apps/web/dist/assets/{tr-9IkdRLIk.js → tr-CYT8BT69.js} +1 -1
  75. package/apps/web/dist/assets/tr-TR-Bahy-EKO.js +1 -0
  76. package/apps/web/dist/assets/uk-UA-DDVgxWqX.js +1 -0
  77. package/apps/web/dist/assets/{uk-Btampsdw.js → uk-y7QuyfrN.js} +1 -1
  78. package/apps/web/dist/assets/{upload-HARy4VC_.js → upload-e7CmLHhp.js} +1 -1
  79. package/apps/web/dist/assets/{useQueries-CTeEF--U.js → useQueries-U3MOyy3t.js} +1 -1
  80. package/apps/web/dist/assets/{vi-BVZzOn_n.js → vi-Bz2g5X1k.js} +1 -1
  81. package/apps/web/dist/assets/vi-VN-CuHCYxNQ.js +1 -0
  82. package/apps/web/dist/assets/{visual-editor-DKBPjqV-.js → visual-editor-UmVaDXWu.js} +1 -1
  83. package/apps/web/dist/assets/workspace-settings-BiWJqW0m.js +1 -0
  84. package/apps/web/dist/assets/zh-CN-C94sYOI_.js +1 -0
  85. package/apps/web/dist/assets/{zh-CN-BoUvF2do.js → zh-CN-y_sluDUm.js} +1 -1
  86. package/apps/web/dist/assets/zh-TW-BoRQfGQL.js +1 -0
  87. package/apps/web/dist/assets/{zh-TW-CEIDGTWe.js → zh-TW-DIEfy239.js} +1 -1
  88. package/apps/web/dist/index.html +7 -7
  89. package/docs/api.md +31 -3
  90. package/docs/mobile.md +1 -1
  91. package/docs/plan.md +1 -0
  92. package/docs/releasing.md +1 -1
  93. package/package.json +1 -1
  94. package/apps/web/dist/assets/admin-settings-BQzdZII5.js +0 -1
  95. package/apps/web/dist/assets/az-AZ-SXqDBPA2.js +0 -1
  96. package/apps/web/dist/assets/de-DE-CB7aQ6b-.js +0 -1
  97. package/apps/web/dist/assets/default-CCI1vZxa.js +0 -56
  98. package/apps/web/dist/assets/el-GR-D1ntYXIt.js +0 -1
  99. package/apps/web/dist/assets/es-ES-CllsaxcB.js +0 -1
  100. package/apps/web/dist/assets/fr-FR-DxTw6LgI.js +0 -1
  101. package/apps/web/dist/assets/hi-IN-CaQga-0l.js +0 -1
  102. package/apps/web/dist/assets/id-ID-DBfwAAqU.js +0 -1
  103. package/apps/web/dist/assets/index-CZuFd4LM.js +0 -17
  104. package/apps/web/dist/assets/it-IT-BckuH9Nl.js +0 -1
  105. package/apps/web/dist/assets/ja-JP-BAGgJKZ4.js +0 -1
  106. package/apps/web/dist/assets/ko-KR-srIEsTP5.js +0 -1
  107. package/apps/web/dist/assets/mk-MK-BTa1NMAF.js +0 -1
  108. package/apps/web/dist/assets/nl-NL-CQduhDqs.js +0 -1
  109. package/apps/web/dist/assets/pl-PL-BdZMf7Ni.js +0 -1
  110. package/apps/web/dist/assets/project-lifecycle-VYecJuiP.js +0 -1
  111. package/apps/web/dist/assets/project-settings-Bs6YEpo9.js +0 -1
  112. package/apps/web/dist/assets/pt-BR-DZEXJNHg.js +0 -1
  113. package/apps/web/dist/assets/public-project-DUfWDao9.js +0 -1
  114. package/apps/web/dist/assets/ru-RU-CZKEe7P8.js +0 -1
  115. package/apps/web/dist/assets/ticket-detail-BTy_3JeU.js +0 -2
  116. package/apps/web/dist/assets/ticket-editor-Bb_lyxnJ.js +0 -2
  117. package/apps/web/dist/assets/ticket-list-Botp5qf5.js +0 -6
  118. package/apps/web/dist/assets/ticket-list-controls-Dmkno4pm.js +0 -1
  119. package/apps/web/dist/assets/tr-TR-Y8wBLO8I.js +0 -1
  120. package/apps/web/dist/assets/uk-UA-CVpasGUS.js +0 -1
  121. package/apps/web/dist/assets/vi-VN-BMy-iJD-.js +0 -1
  122. package/apps/web/dist/assets/workspace-settings-DqzBNw60.js +0 -1
  123. package/apps/web/dist/assets/zh-CN-Cds_v0R8.js +0 -1
  124. package/apps/web/dist/assets/zh-TW-DCCYakXz.js +0 -1
@@ -1,4 +1,4 @@
1
- import { Dt as streamErrorSchema, G as notificationIdsInput, J as notificationReadInput, K as notificationPreferencesInput, O as eventSchema, Q as operations, St as snapshotSchema, T as errorSchema, X as okSchema, Y as notificationsSchema, ct as publicOperations, et as parseOutput, o as checkpointSchema, q as notificationPreferencesSchema, t as ApiError, tt as parsePublicOutput } from "./src-D70GmZ5R.mjs";
1
+ import { Dt as streamErrorSchema, G as notificationIdsInput, J as notificationReadInput, K as notificationPreferencesInput, O as eventSchema, Q as operations, St as snapshotSchema, T as errorSchema, X as okSchema, Y as notificationsSchema, ct as publicOperations, et as parseOutput, o as checkpointSchema, q as notificationPreferencesSchema, t as ApiError, tt as parsePublicOutput } from "./src-CC6lPIU_.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 { Gt as searchSettingsSchema, Ut as s3SettingsSchema, g as defaultClaimMaxLeaseSeconds, s as claimLeaseSecondsSchema, zt as brokerSettingsSchema } from "./src-D70GmZ5R.mjs";
1
+ import { Gt as searchSettingsSchema, Ut as s3SettingsSchema, g as defaultClaimMaxLeaseSeconds, s as claimLeaseSecondsSchema, zt as brokerSettingsSchema } from "./src-CC6lPIU_.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-CQVdVhXv.mjs";
2
- import { B as isPublicOperation, Bt as instanceUpdateSchema, G as notificationIdsInput, Gt as searchSettingsSchema, H as memberSchema, Ht as photoResultSchema, J as notificationReadInput, K as notificationPreferencesInput, Lt as adminInstanceSchema, M as hasAdminRole, N as importLimits, Ot as ticketExportFormat, P as importProviderDefinitions, Q as operations, R as isOperation, T as errorSchema, Tt as stateLabels, Ut as s3SettingsSchema, Vt as photoInputSchema, X as okSchema, _t as roles, ct as publicOperations, l as closedStates, lt as pushDeviceInput, q as notificationPreferencesSchema, t as ApiError, u as credentialInput, v as defaultWorkflow, vt as scopes, z as isPresetColor, zt as brokerSettingsSchema } from "./src-D70GmZ5R.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-DUIdB3c2.mjs";
2
+ import { B as isPublicOperation, Bt as instanceUpdateSchema, G as notificationIdsInput, Gt as searchSettingsSchema, H as memberSchema, Ht as photoResultSchema, J as notificationReadInput, K as notificationPreferencesInput, Lt as adminInstanceSchema, M as hasAdminRole, N as importLimits, Ot as ticketExportFormat, P as importProviderDefinitions, Q as operations, R as isOperation, T as errorSchema, Tt as stateLabels, Ut as s3SettingsSchema, Vt as photoInputSchema, X as okSchema, _t as roles, ct as publicOperations, l as closedStates, lt as pushDeviceInput, q as notificationPreferencesSchema, t as ApiError, u as credentialInput, v as defaultWorkflow, vt as scopes, z as isPresetColor, zt as brokerSettingsSchema } from "./src-CC6lPIU_.mjs";
3
3
  import { t as migrateDatabase } from "./migrate-C9f4ozB4.mjs";
4
- import { $ as pushDevices, A as requireInstanceAdmin, B as createPollingJob, C as defaultSenders, D as receiveUpload, E as createHttpSender, F as createBlobStorage, G as eventClock, H as attachments, I as decodeImage, J as members, K as events, L as imageResponse, M as readCalendarFeed, N as createTicketSearch, O as createAccess, P as createTicketIndex, Q as projects, R as stagedUploadCutoff, S as receiveRepositoryWebhook, T as DeliveryError, U as blobObjects, V as attachmentContent, W as deliveryCheckpoints, X as notifications, Y as notificationPreferences, Z as profilePhotos, _ as one, a as deliverySecrets, at as workspaces, b as requireRow, c as ownerBlocker, d as createDomain, et as stagedUploads, f as errorEnvelope, g as notify, h as json, i as createInstance, it as workspaceMembers, j as requireSession, k as createWriteLimiter, l as appendEvents, m as intakeOnly, n as emailCodesAvailable, nt as subscriptions, o as otherActiveAdministrator, ot as matchesOIDCBinding, p as fail, r as signInProviders, rt as tickets, s as ownedWorkspaces, t as createAuth, tt as storageBackends, u as assertVersion, v as reachable, w as deliverBatch, x as processImports, y as recipientColumns, z as createObjectStore } from "./auth-D43XyHdK.mjs";
4
+ import { $ as pushDevices, A as createWriteLimiter, B as createPollingJob, C as receiveRepositoryWebhook, D as createHttpSender, E as DeliveryError, F as createBlobStorage, G as eventClock, H as attachments, I as decodeImage, J as members, K as events, L as imageResponse, M as requireSession, N as createTicketSearch, O as receiveUpload, P as createTicketIndex, Q as projects, R as stagedUploadCutoff, S as readCalendarFeed, T as deliverBatch, U as blobObjects, V as attachmentContent, W as deliveryCheckpoints, X as notifications, Y as notificationPreferences, Z as profilePhotos, _ as one, a as deliverySecrets, at as workspaces, b as requireRow, c as ownerBlocker, d as createDomain, et as stagedUploads, f as errorEnvelope, g as notify, h as json, i as createInstance, it as workspaceMembers, j as requireInstanceAdmin, k as createAccess, l as appendEvents, m as intakeOnly, n as emailCodesAvailable, nt as subscriptions, o as otherActiveAdministrator, ot as matchesOIDCBinding, p as fail, r as signInProviders, rt as tickets, s as ownedWorkspaces, t as createAuth, tt as storageBackends, u as assertVersion, v as reachable, w as defaultSenders, x as processImports, y as recipientColumns, z as createObjectStore } from "./auth-CVnX2XIg.mjs";
5
5
  import { serve } from "@hono/node-server";
6
6
  import pg from "pg";
7
7
  import { drizzle } from "drizzle-orm/node-postgres";
@@ -9,8 +9,8 @@ import { and, asc, count, desc, eq, gt, ilike, inArray, isNotNull, isNull, notIn
9
9
  import { z } from "zod";
10
10
  import { fileURLToPath } from "node:url";
11
11
  import { createHash, createHmac, timingSafeEqual } from "node:crypto";
12
- import { isDeepStrictEqual } from "node:util";
13
12
  import { Expo } from "expo-server-sdk";
13
+ import { isDeepStrictEqual } from "node:util";
14
14
  import { EventEmitter } from "node:events";
15
15
  import { connect } from "amqplib";
16
16
  import { OpenAPIHono, createRoute, z as z$1 } from "@hono/zod-openapi";
@@ -359,7 +359,7 @@ function createCleanup() {
359
359
  }
360
360
  //#endregion
361
361
  //#region src/version.ts
362
- const applicationVersion = "0.7.0";
362
+ const applicationVersion = "0.8.0";
363
363
  //#endregion
364
364
  //#region src/realtime.ts
365
365
  async function createRealtime(config) {
@@ -3145,6 +3145,11 @@ const known = (name) => {
3145
3145
  if (!isOperation(name)) throw new ApiError(404, "not_found", `No operation is named ${name}`);
3146
3146
  return name;
3147
3147
  };
3148
+ const refuseUnknownKeys = (name, input) => {
3149
+ const { shape } = toolInput(name);
3150
+ const unknown = Object.keys(input).filter((key) => !Object.hasOwn(shape, key));
3151
+ if (unknown.length > 0) throw new ApiError(400, "invalid_request", `${name} does not take ${unknown.join(", ")}. Valid keys: ${Object.keys(shape).join(", ") || "none"}.`);
3152
+ };
3148
3153
  const summary = (name) => ({
3149
3154
  name,
3150
3155
  description: operations[name].description,
@@ -3189,6 +3194,7 @@ const runnerInput = {
3189
3194
  function createMcp(auth, domain, access, config, limitWrite) {
3190
3195
  const serve = (request, identity, limited = false) => {
3191
3196
  const execute = async (name, input) => {
3197
+ refuseUnknownKeys(name, input);
3192
3198
  if (limited && operations[name].method !== "GET") limitWrite(identity.ownerId);
3193
3199
  const value = "view" in operations[name].input.shape ? {
3194
3200
  view: defaultView,
@@ -3208,10 +3214,9 @@ function createMcp(auth, domain, access, config, limitWrite) {
3208
3214
  }, { instructions });
3209
3215
  for (const name of coreNames) {
3210
3216
  const spec = operations[name];
3211
- const inputSchema = toolInput(name).shape;
3212
3217
  server.registerTool(name, {
3213
3218
  description: spec.description,
3214
- inputSchema,
3219
+ inputSchema: toolInput(name).loose(),
3215
3220
  annotations: {
3216
3221
  readOnlyHint: spec.method === "GET",
3217
3222
  destructiveHint: false,
@@ -1,4 +1,4 @@
1
- import { n as createDatabase, t as readConfig } from "./config-CQVdVhXv.mjs";
1
+ import { n as createDatabase, t as readConfig } from "./config-DUIdB3c2.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-CQVdVhXv.mjs";
2
- import { M as hasAdminRole } from "./src-D70GmZ5R.mjs";
3
- import { p as fail, q as instanceSettings, t as createAuth } from "./auth-D43XyHdK.mjs";
1
+ import { d as user, n as createDatabase, r as account, t as readConfig, u as session } from "./config-DUIdB3c2.mjs";
2
+ import { M as hasAdminRole } from "./src-CC6lPIU_.mjs";
3
+ import { p as fail, q as instanceSettings, t as createAuth } from "./auth-CVnX2XIg.mjs";
4
4
  import { and, eq, isNull, sql } from "drizzle-orm";
5
5
  import { z } from "zod";
6
6
  import { parseArgs } from "node:util";
@@ -441,6 +441,10 @@ var en_US_default = {
441
441
  updated: "Time entry updated",
442
442
  deleted: "Time entry deleted"
443
443
  },
444
+ epic: {
445
+ created: "Epic created",
446
+ updated: "Epic updated"
447
+ },
444
448
  label: {
445
449
  created: "Label created",
446
450
  updated: "Label updated",
@@ -494,6 +498,7 @@ var en_US_default = {
494
498
  created: "Calendar feed created",
495
499
  revoked: "Calendar feed revoked"
496
500
  },
501
+ public_board: { updated: "Public board updated" },
497
502
  project: {
498
503
  created: "Project created",
499
504
  updated: "Project updated",
@@ -1079,6 +1084,8 @@ var en_US_default = {
1079
1084
  kind: "Kind",
1080
1085
  difficulty: "Difficulty",
1081
1086
  assignee: "Assignee",
1087
+ epic: "Epic",
1088
+ noEpic: "No epic",
1082
1089
  you: "{{name}} (you)",
1083
1090
  assignTo: "Assign to…",
1084
1091
  set: "Set {{label, lowercase}}…",
@@ -1834,6 +1841,8 @@ var en_US_default = {
1834
1841
  kept: "Kept",
1835
1842
  projects: "Projects",
1836
1843
  members: "Members",
1844
+ epics: "Epics",
1845
+ epicTickets: "Tickets leaving an epic",
1837
1846
  sharedLabels: "Shared labels",
1838
1847
  signIn: "Sign-in",
1839
1848
  sessions: "Sessions",
@@ -3237,6 +3246,7 @@ const ticketFields = z.object({
3237
3246
  scopes: tags,
3238
3247
  assigneeId: z.string().nullable(),
3239
3248
  parentId: z.uuid().nullable(),
3249
+ epicId: z.uuid().nullable(),
3240
3250
  automationReady: z.boolean(),
3241
3251
  holdReason: z.string().trim().max(2e3).nullable(),
3242
3252
  startAt: dueDate.nullable(),
@@ -3345,7 +3355,8 @@ const projectImpactSchema = z.object({
3345
3355
  copiedLabels: z.array(z.string()),
3346
3356
  mergedLabels: z.array(z.string()),
3347
3357
  subscriptions: count,
3348
- invitations: count
3358
+ invitations: count,
3359
+ epicTickets: count.default(0)
3349
3360
  }).nullable()
3350
3361
  });
3351
3362
  const workspaceImpactSchema = projectImpactSchema.omit({ transfer: true }).extend({
@@ -3356,7 +3367,8 @@ const workspaceImpactSchema = projectImpactSchema.omit({ transfer: true }).exten
3356
3367
  })),
3357
3368
  members: count,
3358
3369
  invitations: count,
3359
- labels: count
3370
+ labels: count,
3371
+ epics: count.default(0)
3360
3372
  });
3361
3373
  const deletedAccountName = "Deleted account";
3362
3374
  const accountImpactSchema = z.object({
@@ -3378,6 +3390,7 @@ const accountImpactSchema = z.object({
3378
3390
  });
3379
3391
  const ticketSchema = z.object({
3380
3392
  ...ticketFields.shape,
3393
+ epicId: z.uuid().nullable().default(null),
3381
3394
  startAt: dueDate.nullable().default(null),
3382
3395
  stateId: z.uuid().nullable().default(null),
3383
3396
  customFields: z.record(z.string(), customFieldValue).default({}),
@@ -3406,11 +3419,27 @@ const ticketSummarySchema = ticketSchema.pick({
3406
3419
  scopes: true,
3407
3420
  archived: true,
3408
3421
  automationReady: true,
3422
+ epicId: true,
3409
3423
  updatedAt: true,
3410
3424
  version: true
3411
3425
  });
3412
3426
  const ticketView = { view: z.enum(["summary", "full"]).default("full") };
3413
3427
  const ticketRow = z.union([ticketSchema, ticketSummarySchema]);
3428
+ const epicSchema = z.object({
3429
+ id: z.uuid(),
3430
+ workspaceId: z.uuid(),
3431
+ title: text.max(200),
3432
+ description: z.string(),
3433
+ archived: z.boolean(),
3434
+ version,
3435
+ createdBy: z.string(),
3436
+ createdAt: timestamp,
3437
+ updatedAt: timestamp
3438
+ });
3439
+ const epicRollupSchema = epicSchema.extend({
3440
+ openTickets: count,
3441
+ closedTickets: count
3442
+ });
3414
3443
  const snapshotSchema = z.object({
3415
3444
  project: projectSchema,
3416
3445
  tickets: z.array(ticketSchema),
@@ -3585,6 +3614,25 @@ const calendarFeedSchema = z.object({
3585
3614
  createdAt: timestamp,
3586
3615
  active: z.boolean()
3587
3616
  });
3617
+ const publicBoardSchema = z.object({
3618
+ projectId: z.uuid(),
3619
+ enabled: z.boolean(),
3620
+ title: text.max(80),
3621
+ labels: tags,
3622
+ excludedLabels: tags,
3623
+ version,
3624
+ createdAt: timestamp,
3625
+ updatedAt: timestamp
3626
+ });
3627
+ const publicBoardTicketSchema = z.object({
3628
+ projectId: z.uuid(),
3629
+ key: z.string(),
3630
+ title: z.string(),
3631
+ kind: z.enum(kinds),
3632
+ status: z.enum(states),
3633
+ createdAt: timestamp,
3634
+ updatedAt: timestamp
3635
+ });
3588
3636
  const eventSchema = z.object({
3589
3637
  id: z.string().regex(/^\d+$/),
3590
3638
  projectId: z.uuid(),
@@ -3968,6 +4016,8 @@ const eventLabels = labelTable([
3968
4016
  "label.created",
3969
4017
  "label.updated",
3970
4018
  "label.deleted",
4019
+ "epic.created",
4020
+ "epic.updated",
3971
4021
  "workflow.created",
3972
4022
  "workflow.updated",
3973
4023
  "workflow.deleted",
@@ -4001,6 +4051,7 @@ const eventLabels = labelTable([
4001
4051
  "resource.removed",
4002
4052
  "calendar_feed.created",
4003
4053
  "calendar_feed.revoked",
4054
+ "public_board.updated",
4004
4055
  "project.created",
4005
4056
  "project.updated",
4006
4057
  "project.archived",
@@ -4340,6 +4391,37 @@ const operations = {
4340
4391
  userId: z.string().min(1),
4341
4392
  confirmEmail: z.string().trim().max(320)
4342
4393
  }).strict(), okSchema, "DELETE", "/accounts/{userId}", "write", "Delete an account: remove its sign-in methods, sessions, API keys, OAuth grants, devices, notification settings, memberships, and photo, release its claims, cancel its imports, and replace its name and email with a `Deleted account` tombstone. Tickets, comments, evidence, and activity keep the account ID. Only the account itself or an instance administrator, signed in within the last 15 minutes, can delete; `confirmEmail` must repeat the account's email. Fails with `workspace_owner` while it owns a workspace and `last_administrator` for the last active instance or workspace administrator."),
4394
+ list_epics: op(z.object({
4395
+ workspaceId: z.uuid(),
4396
+ archived: ticketFilter.shape.archived,
4397
+ ...pagination
4398
+ }), page(epicRollupSchema), "GET", "/workspaces/{workspaceId}/epics", "read", "List the workspace's Epics by title, each with open and closed ticket counts from the projects you can read, leaving out archived tickets. Archived Epics are left out unless `archived` is `include` or `only`."),
4399
+ get_epic: op(z.object({
4400
+ workspaceId: z.uuid(),
4401
+ epicId: z.uuid()
4402
+ }), epicRollupSchema, "GET", "/workspaces/{workspaceId}/epics/{epicId}", "read", "Read one Epic with the same ticket counts as list_epics."),
4403
+ create_epic: op(z.object({
4404
+ ...mutation,
4405
+ workspaceId: z.uuid(),
4406
+ title: epicSchema.shape.title,
4407
+ description: z.string().max(1e5).default("")
4408
+ }).strict(), epicSchema, "POST", "/workspaces/{workspaceId}/epics", "write", "Create an Epic that groups tickets from any project in the workspace. Add a ticket by setting `epicId` through create_ticket, update_ticket, or bulk_update_tickets. Membership has no effect on readiness, eligibility, or claims. Reuse requestId when retrying this write."),
4409
+ update_epic: op(z.object({
4410
+ ...mutation,
4411
+ workspaceId: z.uuid(),
4412
+ epicId: z.uuid(),
4413
+ expectedVersion: version,
4414
+ title: epicSchema.shape.title.optional(),
4415
+ description: z.string().max(1e5).optional(),
4416
+ archived: z.boolean().optional()
4417
+ }).strict().refine(({ title, description, archived }) => title !== void 0 || description !== void 0 || archived !== void 0, "Provide at least one change"), epicSchema, "PATCH", "/workspaces/{workspaceId}/epics/{epicId}", "write", "Change an Epic's title or description, archive it, or restore it with `archived: false`. An archived Epic keeps its members and refuses new ones. There is no deletion. Reuse requestId when retrying this write."),
4418
+ list_epic_tickets: op(z.object({
4419
+ workspaceId: z.uuid(),
4420
+ epicId: z.uuid(),
4421
+ archived: ticketFilter.shape.archived,
4422
+ ...pagination,
4423
+ ...ticketView
4424
+ }), page(ticketRow).extend({ restrictedCount: count }), "GET", "/workspaces/{workspaceId}/epics/{epicId}/tickets", "read", "List an Epic's tickets across projects, oldest first. Only tickets of projects you can read appear; `restrictedCount` counts the rest without naming them. With `view: \"summary\"`, rows omit the description and acceptance criteria."),
4343
4425
  list_workspace_labels: op(z.object({ workspaceId: z.uuid() }), z.array(workspaceCatalogLabel), "GET", "/workspaces/{workspaceId}/labels", "read", "List the workspace's shared labels with ticket counts from the projects you can read, including archived tickets."),
4344
4426
  create_workspace_label: op(z.object({
4345
4427
  ...mutation,
@@ -4362,7 +4444,7 @@ const operations = {
4362
4444
  workspaceId: z.uuid(),
4363
4445
  labelId: z.uuid(),
4364
4446
  expectedVersion: version
4365
- }).strict(), okSchema, "DELETE", "/workspaces/{workspaceId}/labels/{labelId}", "admin", "Delete a workspace label and remove it from tickets and saved-view filters in every project of the workspace atomically."),
4447
+ }).strict(), okSchema, "DELETE", "/workspaces/{workspaceId}/labels/{labelId}", "admin", "Delete a workspace label and remove it from tickets and saved-view filters in every project of the workspace atomically. Fails with `label_in_use` while an enabled public board in the workspace excludes it."),
4366
4448
  list_invitations: op(z.object({ workspaceId: z.uuid() }), z.array(invitationSchema), "GET", "/workspaces/{workspaceId}/invitations", "admin", "List the workspace's pending and expired invitations."),
4367
4449
  create_invitation: op(z.object({
4368
4450
  workspaceId: z.uuid(),
@@ -4477,18 +4559,18 @@ const operations = {
4477
4559
  expectedVersion: version,
4478
4560
  archived: z.boolean()
4479
4561
  }).strict(), projectSchema, "POST", "/projects/{projectId}/archive", "admin", "Archive a project, or restore it with `archived: false`. An archived project keeps its tickets and history but leaves default project lists, search, My work, calendar feeds, public links, reminders, and agent queues, and its tickets and settings are read only. Fails with `claim_conflict` while any ticket is claimed."),
4480
- get_project_impact: op(projectInput.extend({ workspaceId: z.uuid().optional() }), projectImpactSchema, "GET", "/projects/{projectId}/impact", "admin", "Preview what deleting the project removes, or with `workspaceId`, what moving it to that workspace changes: who loses or gains access, labels copied or merged, follows and invitations dropped, and blockers such as claimed tickets."),
4562
+ get_project_impact: op(projectInput.extend({ workspaceId: z.uuid().optional() }), projectImpactSchema, "GET", "/projects/{projectId}/impact", "admin", "Preview what deleting the project removes, or with `workspaceId`, what moving it to that workspace changes: who loses or gains access, labels copied or merged, follows and invitations dropped, tickets leaving their Epic, and blockers such as claimed tickets."),
4481
4563
  transfer_project: op(projectInput.extend({
4482
4564
  ...mutation,
4483
4565
  expectedVersion: version,
4484
4566
  workspaceId: z.uuid()
4485
- }).strict(), projectSchema, "POST", "/projects/{projectId}/transfer", "admin", "Move a project with its tickets, keys, and links to another workspace. Requires administration of both workspaces and applies the reconciliation `get_project_impact` previews in one transaction; blockers fail with their code."),
4567
+ }).strict(), projectSchema, "POST", "/projects/{projectId}/transfer", "admin", "Move a project with its tickets, keys, and links to another workspace. Requires administration of both workspaces and applies the reconciliation `get_project_impact` previews in one transaction, including clearing each ticket's `epicId`; blockers fail with their code."),
4486
4568
  delete_project: op(projectInput.extend({
4487
4569
  ...mutation,
4488
4570
  expectedVersion: version,
4489
4571
  confirmKey: z.string().max(20)
4490
- }).strict(), okSchema, "DELETE", "/projects/{projectId}", "admin", "Permanently delete a project's tickets, comments, attachments, labels, feeds, and destinations. `confirmKey` must repeat the project prefix. Fails with `claim_conflict` while any ticket is claimed. The prefix stays reserved and the activity history and deletion events are kept."),
4491
- export_project: op(projectInput, ticketExportSchema, "GET", "/projects/{projectId}/export", "read", "Export the project's tickets, archived ones included, as a versioned `kanvil.tickets` bundle: workflow states, custom fields, labels, relationships, comments, evidence, and links, with the people they name. Records reference each other by source ID. `completeness.omitted` counts what the bundle leaves out, such as attachments and activity history."),
4572
+ }).strict(), okSchema, "DELETE", "/projects/{projectId}", "admin", "Permanently delete a project's tickets, comments, attachments, labels, feeds, public board, and destinations. `confirmKey` must repeat the project prefix. Fails with `claim_conflict` while any ticket is claimed. The prefix stays reserved and the activity history and deletion events are kept."),
4573
+ export_project: op(projectInput, ticketExportSchema, "GET", "/projects/{projectId}/export", "read", "Export the project's tickets, archived ones included, as a versioned `kanvil.tickets` bundle: workflow states, custom fields, labels, relationships, comments, evidence, and links, with the people they name. Records reference each other by source ID. `completeness.omitted` counts what the bundle leaves out, such as attachments, Epics, and activity history."),
4492
4574
  preview_import: op(z.object({
4493
4575
  ...mutation,
4494
4576
  workspaceId: z.uuid(),
@@ -4534,6 +4616,7 @@ const operations = {
4534
4616
  ...pagination,
4535
4617
  relatedTo: id.optional(),
4536
4618
  stateId: z.uuid().optional(),
4619
+ epicId: z.uuid().optional(),
4537
4620
  scheduledFrom: timestamp.optional(),
4538
4621
  scheduledTo: timestamp.optional(),
4539
4622
  ...ticketView
@@ -4559,7 +4642,7 @@ const operations = {
4559
4642
  ...mutation,
4560
4643
  labelId: z.uuid(),
4561
4644
  expectedVersion: version
4562
- }).strict(), okSchema, "DELETE", "/projects/{projectId}/labels/{labelId}", "admin", "Delete a project label and remove it from tickets and saved-view filters atomically."),
4645
+ }).strict(), okSchema, "DELETE", "/projects/{projectId}/labels/{labelId}", "admin", "Delete a project label and remove it from tickets and saved-view filters atomically. Fails with `label_in_use` while the project's enabled public board excludes it."),
4563
4646
  list_label_colors: op(z.object({}), z.array(z.object({
4564
4647
  value: hexColor,
4565
4648
  createdAt: timestamp
@@ -4648,13 +4731,14 @@ const operations = {
4648
4731
  scopes: tags.default([]),
4649
4732
  assigneeId: z.string().nullable().default(null),
4650
4733
  parentId: z.uuid().nullable().default(null),
4734
+ epicId: z.uuid().nullable().default(null),
4651
4735
  automationReady: z.boolean().default(false),
4652
4736
  holdReason: z.string().trim().max(2e3).nullable().default(null),
4653
4737
  archived: z.boolean().default(false),
4654
4738
  uploads: z.array(z.uuid()).max(50).default([]),
4655
4739
  customFields: customFieldValues.default({})
4656
4740
  }).strict(), ticketSchema, "POST", "/projects/{projectId}/tickets", "write", "Create a ticket. `customFields` maps field IDs from list_custom_fields to values; omitted fields take their default, and a required field must end with a value. `uploads` lists your staged inline uploads in this project; they become its attachments in the same write, or nothing is created. Reuse requestId when retrying this write.", { mcp: "core" }),
4657
- duplicate_ticket: op(ticketInput.extend(mutation).strict(), ticketSchema, "POST", "/projects/{projectId}/tickets/{ticketId}/duplicate", "write", "Copy a ticket into a new To do ticket in the same project, linked to the original by a clones relationship. Copies requirements, properties, labels, scopes, and parent; resets agent readiness and never copies claims, comments, evidence, attachments, or children. Reuse requestId when retrying this write."),
4741
+ duplicate_ticket: op(ticketInput.extend(mutation).strict(), ticketSchema, "POST", "/projects/{projectId}/tickets/{ticketId}/duplicate", "write", "Copy a ticket into a new To do ticket in the same project, linked to the original by a clones relationship. Copies requirements, properties, labels, scopes, parent, and an Epic the caller can still add to; resets agent readiness and never copies claims, comments, evidence, attachments, or children. Reuse requestId when retrying this write."),
4658
4742
  update_ticket: op(ticketInput.extend({
4659
4743
  ...mutation,
4660
4744
  expectedVersion: version,
@@ -4886,6 +4970,18 @@ const operations = {
4886
4970
  ...mutation,
4887
4971
  feedId: z.uuid()
4888
4972
  }).strict(), okSchema, "DELETE", "/projects/{projectId}/calendar-feeds/{feedId}", "admin", "Revoke a calendar feed. Its link stops working immediately."),
4973
+ get_project_board: op(projectInput, z.object({
4974
+ board: publicBoardSchema.nullable(),
4975
+ tickets: z.array(publicBoardTicketSchema)
4976
+ }), "GET", "/projects/{projectId}/public-board", "admin", "Read the project's public board, or null, and the tickets it selects exactly as it publishes them, even while it is disabled."),
4977
+ update_project_board: op(projectInput.extend({
4978
+ ...mutation,
4979
+ expectedVersion: version.nullable(),
4980
+ enabled: z.boolean(),
4981
+ title: text.max(80),
4982
+ labels: tags,
4983
+ excludedLabels: tags
4984
+ }).strict(), publicBoardSchema, "PATCH", "/projects/{projectId}/public-board", "admin", "Create or replace the project's public board. It publishes the key, title, kind, status, and dates of unarchived, unheld tickets that carry any of `labels` and none of `excludedLabels`. Enabling requires at least one label."),
4889
4985
  list_views: op(projectInput, z.array(z.object({
4890
4986
  id: z.uuid(),
4891
4987
  name: z.string(),
@@ -5101,6 +5197,8 @@ const permissionOverrides = {
5101
5197
  list_calendar_feeds: "sharing.manage",
5102
5198
  create_calendar_feed: "sharing.manage",
5103
5199
  revoke_calendar_feed: "sharing.manage",
5200
+ get_project_board: "sharing.manage",
5201
+ update_project_board: "sharing.manage",
5104
5202
  update_project: null
5105
5203
  };
5106
5204
  function operationPermission(name) {
@@ -0,0 +1,37 @@
1
+ CREATE TABLE "public_boards" (
2
+ "project_id" uuid PRIMARY KEY NOT NULL,
3
+ "enabled" boolean DEFAULT false NOT NULL,
4
+ "title" text NOT NULL,
5
+ "labels" text[] DEFAULT '{}' NOT NULL,
6
+ "excluded_labels" text[] DEFAULT '{}' NOT NULL,
7
+ "version" integer DEFAULT 1 NOT NULL,
8
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
9
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL
10
+ );
11
+ --> statement-breakpoint
12
+ ALTER TABLE "public_boards" ADD CONSTRAINT "public_boards_project_id_projects_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
13
+ CREATE VIEW "public_board_candidates" WITH (security_barrier) AS
14
+ SELECT t."project_id", t."key", t."title", t."kind", t."status", t."created_at", t."updated_at"
15
+ FROM "tickets" t
16
+ JOIN "public_boards" b ON b."project_id" = t."project_id"
17
+ JOIN "projects" p ON p."id" = b."project_id"
18
+ WHERE NOT t."archived"
19
+ AND t."hold_reason" IS NULL
20
+ AND t."labels" && b."labels"
21
+ AND NOT t."labels" && b."excluded_labels"
22
+ AND p."archived_at" IS NULL
23
+ AND p."deleted_at" IS NULL;
24
+ --> statement-breakpoint
25
+ CREATE VIEW "public_board_tickets" WITH (security_barrier) AS
26
+ SELECT c."project_id", c."key", c."title", c."kind", c."status", c."created_at", c."updated_at"
27
+ FROM "public_board_candidates" c
28
+ JOIN "public_boards" b ON b."project_id" = c."project_id"
29
+ WHERE b."enabled";
30
+ --> statement-breakpoint
31
+ CREATE VIEW "public_board_summaries" WITH (security_barrier) AS
32
+ SELECT b."project_id", b."title"
33
+ FROM "public_boards" b
34
+ JOIN "projects" p ON p."id" = b."project_id"
35
+ WHERE b."enabled"
36
+ AND p."archived_at" IS NULL
37
+ AND p."deleted_at" IS NULL;
@@ -0,0 +1,18 @@
1
+ CREATE TABLE "epics" (
2
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
3
+ "workspace_id" uuid NOT NULL,
4
+ "title" text NOT NULL,
5
+ "description" text DEFAULT '' NOT NULL,
6
+ "archived" boolean DEFAULT false NOT NULL,
7
+ "created_by" text NOT NULL,
8
+ "version" integer DEFAULT 1 NOT NULL,
9
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
10
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL
11
+ );
12
+ --> statement-breakpoint
13
+ ALTER TABLE "tickets" ADD COLUMN "epic_id" uuid;--> statement-breakpoint
14
+ ALTER TABLE "epics" ADD CONSTRAINT "epics_workspace_id_workspaces_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspaces"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
15
+ ALTER TABLE "epics" ADD CONSTRAINT "epics_created_by_user_id_fk" FOREIGN KEY ("created_by") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
16
+ CREATE INDEX "epic_workspace" ON "epics" USING btree ("workspace_id");--> statement-breakpoint
17
+ ALTER TABLE "tickets" ADD CONSTRAINT "tickets_epic_id_epics_id_fk" FOREIGN KEY ("epic_id") REFERENCES "public"."epics"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
18
+ CREATE INDEX "ticket_epic" ON "tickets" USING btree ("epic_id") WHERE "tickets"."epic_id" IS NOT NULL;