// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0 // Project-scoped task and comment contracts. Authority comes from resolved scope. import { procedure } from "@conusai-kit/core" import { z } from "zod" const inProject = { org: z.string(), workspace: z.string(), project: z.string() } const id = z.string().uuid() const resourceSuffix = "[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}" export const activityId = z.string().regex(new RegExp(`^act_${resourceSuffix}$`)) const commentId = z.string().regex(new RegExp(`^cmt_${resourceSuffix}$`)) // Cursors belong to the service and are forwarded unchanged; they are not resource IDs. const cursor = z.string().min(1).max(4096) const revision = z.string().regex(/^[1-9][0-9]{0,18}$/) export const taskView = z.object({ taskId: activityId, subject: z.string(), body: z.string(), revision, status: z.enum(["open", "done", "cancelled"]), createdAt: z.string(), }) export type TaskView = z.output export const commentView = z.object({ commentId, body: z.string(), author: z.string().nullable(), erased: z.boolean(), }) export type CommentView = z.output export const commentReceipt = z.object({ commentId, revision }) export type CommentReceipt = z.output export const receipt = z.object({ taskId: activityId, revision }) export type TaskReceipt = z.output const write = { ...inProject, taskId: activityId, expectedVersion: revision, requestId: id } export const taskList = procedure("activities.task.list") .kind("read") .scope("project") .permission("activities.task.read") .input(z.object({ ...inProject, status: z.enum(["open", "done", "cancelled"]).optional(), after: cursor.optional() })) .output(z.object({ rows: z.array(taskView), after: cursor.optional() })) .contract() export const taskGet = procedure("activities.task.get") .kind("read") .scope("project") .permission("activities.task.read") .input(z.object({ ...inProject, taskId: activityId })) .output(taskView) .contract() export const taskCreate = procedure("activities.task.create") .kind("effect") .scope("project") .permission("activities.task.write") .input( z.object({ ...inProject, subject: z.string().trim().min(1).max(200), body: z.string().max(10000).optional(), requestId: id, }), ) .output(receipt) .contract() export const taskUpdate = procedure("activities.task.update") .kind("effect") .scope("project") .permission("activities.task.write") .input(z.object({ ...write, subject: z.string().trim().min(1).max(200), body: z.string().max(10000) })) .output(receipt) .contract() export const taskComplete = procedure("activities.task.complete") .kind("effect") .scope("project") .permission("activities.task.write") .input(z.object(write)) .output(receipt) .contract() export const taskReopen = procedure("activities.task.reopen") .kind("effect") .scope("project") .permission("activities.task.write") .input(z.object(write)) .output(receipt) .contract() export const commentList = procedure("activities.comment.list") .kind("read") .scope("project") .permission("activities.task.read") .input(z.object({ ...inProject, taskId: activityId, after: cursor.optional() })) .output(z.object({ rows: z.array(commentView), after: cursor.optional() })) .contract() export const commentAdd = procedure("activities.comment.add") .kind("effect") .scope("project") .permission("activities.comment.write") .input(z.object({ ...write, body: z.string().trim().min(1).max(10000) })) .output(commentReceipt) .contract() export const timeline = procedure("activities.timeline.list") .kind("read") .scope("project") .permission("activities.task.read") .input(z.object({ ...inProject, after: cursor.optional() })) .output(z.object({ rows: z.array(taskView), after: cursor.optional() })) .contract()