@maudecode/kanvil 0.4.2 → 0.4.3

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.
@@ -1700,11 +1700,10 @@ async function rememberLabelColor(ctx, color) {
1700
1700
  async function changeLabelUsage(ctx, name, replacement) {
1701
1701
  const affected = await ctx.db.select().from(tickets).where(and(eq(tickets.projectId, project(ctx).id), sql`${name} = ANY(${tickets.labels})`));
1702
1702
  const pending = [];
1703
- for (const ticket of affected) pending.push((await updateTicketRow(ctx, {
1704
- projectId: project(ctx).id,
1703
+ for (const ticket of affected) pending.push((await updateTicketLabelsForDefinition(ctx, {
1705
1704
  ticketId: ticket.id,
1706
1705
  expectedVersion: ticket.version,
1707
- changes: { labels: [...new Set(replacement ? ticket.labels.map((tag) => tag === name ? replacement : tag) : ticket.labels.filter((tag) => tag !== name))] }
1706
+ labels: [...new Set(replacement ? ticket.labels.map((tag) => tag === name ? replacement : tag) : ticket.labels.filter((tag) => tag !== name))]
1708
1707
  })).event);
1709
1708
  await publishMany(ctx, pending);
1710
1709
  await rewriteViewFilters(ctx, "label", (tag) => tag !== name ? tag : replacement ?? null);
@@ -2277,15 +2276,24 @@ async function updateTicketRow(ctx, input) {
2277
2276
  const current = await getTicket(ctx, input.ticketId);
2278
2277
  assertVersion(current, input.expectedVersion, "ticket");
2279
2278
  await assertWorkerClaim(ctx, current, input.claimId);
2280
- await validateReferences(ctx, current.id, input.changes);
2281
- const state = await resolveState(ctx, input.changes, current);
2282
- const { startAt, dueAt, customFields, ...changes } = input.changes, dates = dateColumns({
2279
+ return persistTicketChanges(ctx, current, input.changes);
2280
+ }
2281
+ async function updateTicketLabelsForDefinition(ctx, input) {
2282
+ if (!can(ctx, "project.manage") && !can(ctx, "workspace.manage")) fail(403, "forbidden", "Label administration permission is required");
2283
+ const current = await getTicket(ctx, input.ticketId);
2284
+ assertVersion(current, input.expectedVersion, "ticket");
2285
+ return persistTicketChanges(ctx, current, { labels: input.labels });
2286
+ }
2287
+ async function persistTicketChanges(ctx, current, changes) {
2288
+ await validateReferences(ctx, current.id, changes);
2289
+ const state = await resolveState(ctx, changes, current);
2290
+ const { startAt, dueAt, customFields, ...values } = changes, dates = dateColumns({
2283
2291
  startAt,
2284
2292
  dueAt
2285
2293
  });
2286
2294
  assertSchedule(dates.startAt === void 0 ? current.startAt : dates.startAt, dates.dueAt === void 0 ? current.dueAt : dates.dueAt, current.key);
2287
2295
  const row = one(await ctx.db.update(tickets).set({
2288
- ...changes,
2296
+ ...values,
2289
2297
  ...state,
2290
2298
  ...dates,
2291
2299
  ...customFields && { customFields: applyCustomFieldValues(await projectCustomFields(ctx), current.customFields, customFields) },
@@ -2300,7 +2308,7 @@ async function updateTicketRow(ctx, input) {
2300
2308
  ticketId: row.id,
2301
2309
  data: {
2302
2310
  version: row.version,
2303
- changedFields: [.../* @__PURE__ */ new Set([...Object.keys(input.changes), ...state ? ["status", "stateId"] : []])]
2311
+ changedFields: [.../* @__PURE__ */ new Set([...Object.keys(changes), ...state ? ["status", "stateId"] : []])]
2304
2312
  }
2305
2313
  }
2306
2314
  };
@@ -1,7 +1,7 @@
1
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-DOT1SDQO.mjs";
2
2
  import { A as hasAdminRole, B as memberSchema, C as errorSchema, Ft as photoInputSchema, G as notificationReadInput, H as notificationIdsInput, I as isOperation, It as photoResultSchema, L as isPresetColor, Lt as s3SettingsSchema, M as importProviderDefinitions, Nt as brokerSettingsSchema, Pt as instanceUpdateSchema, R as isPublicOperation, U as notificationPreferencesInput, W as notificationPreferencesSchema, Y as operations, at as publicOperations, c as credentialInput, g as defaultWorkflow, ht as scopes, j as importLimits, jt as adminInstanceSchema, mt as roles, ot as pushDeviceInput, q as okSchema, s as closedStates, t as ApiError, wt as ticketExportFormat, xt as stateLabels, zt as searchSettingsSchema } from "./src-VxR5jZiK.mjs";
3
3
  import { t as migrateDatabase } from "./migrate-C9f4ozB4.mjs";
4
- import { $ as subscriptions, A as createTicketSearch, B as blobObjects, C as DeliveryError, D as requireInstanceAdmin, E as createWriteLimiter, F as stagedUploadCutoff, G as members, H as eventClock, I as createObjectStore, J as profilePhotos, K as notificationPreferences, L as createPollingJob, M as createBlobStorage, N as decodeImage, O as requireSession, P as imageResponse, Q as storageBackends, R as attachmentContent, S as deliverBatch, T as createAccess, U as events, V as deliveryCheckpoints, X as pushDevices, Y as projects, Z as stagedUploads, _ as recipientColumns, a as deliverySecrets, b as receiveRepositoryWebhook, c as ownerBlocker, d as createDomain, et as tickets, f as fail, g as reachable, h as one, i as createInstance, j as createTicketIndex, k as readCalendarFeed, l as appendEvents, m as notify, n as emailCodesAvailable, nt as workspaces, o as otherActiveAdministrator, p as json, q as notifications, r as signInProviders, rt as matchesOIDCBinding, s as ownedWorkspaces, t as createAuth, tt as workspaceMembers, u as assertVersion, v as requireRow, w as createHttpSender, x as defaultSenders, y as processImports, z as attachments } from "./auth-CLAZTAoL.mjs";
4
+ import { $ as subscriptions, A as createTicketSearch, B as blobObjects, C as DeliveryError, D as requireInstanceAdmin, E as createWriteLimiter, F as stagedUploadCutoff, G as members, H as eventClock, I as createObjectStore, J as profilePhotos, K as notificationPreferences, L as createPollingJob, M as createBlobStorage, N as decodeImage, O as requireSession, P as imageResponse, Q as storageBackends, R as attachmentContent, S as deliverBatch, T as createAccess, U as events, V as deliveryCheckpoints, X as pushDevices, Y as projects, Z as stagedUploads, _ as recipientColumns, a as deliverySecrets, b as receiveRepositoryWebhook, c as ownerBlocker, d as createDomain, et as tickets, f as fail, g as reachable, h as one, i as createInstance, j as createTicketIndex, k as readCalendarFeed, l as appendEvents, m as notify, n as emailCodesAvailable, nt as workspaces, o as otherActiveAdministrator, p as json, q as notifications, r as signInProviders, rt as matchesOIDCBinding, s as ownedWorkspaces, t as createAuth, tt as workspaceMembers, u as assertVersion, v as requireRow, w as createHttpSender, x as defaultSenders, y as processImports, z as attachments } from "./auth-CnAQzKyq.mjs";
5
5
  import { serve } from "@hono/node-server";
6
6
  import pg from "pg";
7
7
  import { drizzle } from "drizzle-orm/node-postgres";
@@ -325,7 +325,7 @@ function createAccountAdministration(database, auth, config) {
325
325
  }
326
326
  //#endregion
327
327
  //#region src/version.ts
328
- const applicationVersion = "0.4.2";
328
+ const applicationVersion = "0.4.3";
329
329
  //#endregion
330
330
  //#region src/realtime.ts
331
331
  async function createRealtime(config) {
@@ -1,6 +1,6 @@
1
1
  import { d as user, n as createDatabase, r as account, t as readConfig, u as session } from "./config-DOT1SDQO.mjs";
2
2
  import { A as hasAdminRole } from "./src-VxR5jZiK.mjs";
3
- import { W as instanceSettings, f as fail, t as createAuth } from "./auth-CLAZTAoL.mjs";
3
+ import { W as instanceSettings, f as fail, t as createAuth } from "./auth-CnAQzKyq.mjs";
4
4
  import { and, eq, isNull, sql } from "drizzle-orm";
5
5
  import { z } from "zod";
6
6
  import { parseArgs } from "node:util";
package/docs/api.md CHANGED
@@ -50,9 +50,9 @@ Use `list_tickets` with `parentId` set to a parent UUID or ticket key to page th
50
50
 
51
51
  `list_project_labels` (`GET /api/v1/projects/{projectId}/labels/catalog`) returns the labels the project's tickets can use, with stable IDs, names, colors, versions, and this project's usage counts including archived tickets. Each entry has a `scope`: `project` entries carry `projectId` and belong to the project; `workspace` entries carry `workspaceId` and are shared by every project in the workspace. Unused definitions remain available. Existing ticket label names are backfilled during upgrade as project labels; new freeform labels from ticket writes register automatically as project labels with gray as their color, unless the workspace already defines the name. Ticket payloads continue to use label-name arrays.
52
52
 
53
- Project administrators can `create_project_label` (`POST .../labels`), `update_project_label` (`PATCH .../labels/{labelId}`), and `delete_project_label` (`DELETE .../labels/{labelId}`) through HTTP or MCP. Creation takes `name` and optional `color`; updates take `name`, `color`, and `expectedVersion`; deletion takes `expectedVersion`. Renames update every affected ticket, including archived tickets, and saved-view label filters. Deletion removes the label from tickets and removes its filter from saved views, retaining other filters. These changes commit atomically, preserve ticket write/claim checks, increment affected ticket versions, and emit normal `ticket.updated` events plus `label.created`, `label.updated`, or `label.deleted`. Recoloring emits a label event without changing ticket versions. Realtime consumers can refresh the label catalog on label events.
53
+ Project administrators can `create_project_label` (`POST .../labels`), `update_project_label` (`PATCH .../labels/{labelId}`), and `delete_project_label` (`DELETE .../labels/{labelId}`) through HTTP or MCP. Creation takes `name` and optional `color`; updates take `name`, `color`, and `expectedVersion`; deletion takes `expectedVersion`. Renames update every affected ticket, including archived tickets, and saved-view label filters. Deletion removes the label from tickets and removes its filter from saved views, retaining other filters. These changes require label-administration permission and commit atomically, increment affected ticket versions, and emit normal `ticket.updated` events plus `label.created`, `label.updated`, or `label.deleted`. Recoloring emits a label event without changing ticket versions. Realtime consumers can refresh the label catalog on label events. Label-definition renames and deletions propagate only labels without requiring or changing affected tickets' worker claims, including completed tickets whose ownership requirement remains recorded. Ordinary ticket and bulk edits still enforce worker ownership; automation readiness and other ticket fields remain unchanged.
54
54
 
55
- Workspace administrators manage shared labels with `list_workspace_labels` (`GET /api/v1/workspaces/{workspaceId}/labels`), `create_workspace_label` (`POST .../labels`), `update_workspace_label` (`PATCH .../labels/{labelId}`), and `delete_workspace_label` (`DELETE .../labels/{labelId}`); any workspace member can list them. Inputs match the project operations. List counts include only tickets in projects the caller can read, so private projects never show up in them. A rename or deletion updates tickets, saved-view filters, and calendar feed filters in every project of the workspace in one transaction with the same version and claim checks: one claimed ticket or failed check rolls the whole change back. The label event goes to each project of the workspace with `scope: "workspace"` and `workspaceId`.
55
+ Workspace administrators manage shared labels with `list_workspace_labels` (`GET /api/v1/workspaces/{workspaceId}/labels`), `create_workspace_label` (`POST .../labels`), `update_workspace_label` (`PATCH .../labels/{labelId}`), and `delete_workspace_label` (`DELETE .../labels/{labelId}`); any workspace member can list them. Inputs match the project operations. List counts include only tickets in projects the caller can read, so private projects never show up in them. A rename or deletion updates tickets, saved-view filters, and calendar feed filters in every project of the workspace in one transaction with label-administration permission and version checks. Worker claims remain unchanged and do not block this label-only propagation; any failed permission or version check rolls the whole change back. The label event goes to each project of the workspace with `scope: "workspace"` and `workspaceId`.
56
56
 
57
57
  Within a workspace a name belongs to the workspace or to projects, never both, so a ticket label always resolves to one color. Creating or renaming a project label onto a workspace label's name fails with `409 duplicate_label`. Creating or renaming a workspace label onto a name that projects already define fails with `409 label_conflict`, whose `details.projects` lists their keys. Retry with `adopt: true` to replace those project definitions: their tickets keep the label and show the workspace color, and a ticket that carries both names after an adopting rename keeps one.
58
58
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maudecode/kanvil",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "Open-source, self-hosted ticketing for agents and people.",
5
5
  "license": "MIT",
6
6
  "engines": {