feat: add issue privacy ACL foundation

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-08-01 22:54:01 +00:00
1 parent 0f12721ee9
commit cd86192ca2
24 files changed
+709 -21

No files matched your search

+4
View File
@@ -857,6 +857,10 @@ export type {
ExternalObjectSummaryItem,
CompactIssue,
Issue,
IssueVisibility,
IssueAccessGrant,
IssueAccessGrantSubjectType,
IssueAccessGrantSource,
IssueChangeReceiptEntry,
IssueChanges,
IssueAssigneeAdapterOverrides,
+4
View File
@@ -559,6 +559,10 @@ export type {
IssueSubtreeDiagnosticNode,
IssueSubtreeDiagnosticEdge,
IssueSubtreeDiagnosticsResponse,
IssueVisibility,
IssueAccessGrant,
IssueAccessGrantSubjectType,
IssueAccessGrantSource,
IssueBlockerAttention,
IssueBlockerAttentionReason,
IssueBlockerAttentionState,
+21
View File
@@ -40,6 +40,22 @@ import type {
export type { IssueWorkMode };
export type IssueVisibility = "open" | "private";
export type IssueAccessGrantSubjectType = "user" | "agent";
export type IssueAccessGrantSource = "explicit" | "assignment" | "project";
export interface IssueAccessGrant {
id: string;
issueId: string;
subjectType: IssueAccessGrantSubjectType;
subjectId: string;
source: IssueAccessGrantSource;
grantedByUserId: string | null;
grantedByAgentId: string | null;
createdAt: Date;
revokedAt: Date | null;
}
export interface IssueAncestorProject {
id: string;
name: string;
@@ -734,6 +750,9 @@ export interface Issue {
projectWorkspaceId: string | null;
goalId: string | null;
parentId: string | null;
/** Present on current API responses; optional for compatibility with older plugin payloads. */
visibility?: IssueVisibility;
privacyRootIssueId?: string | null;
ancestors?: IssueAncestor[];
title: string;
description: string | null;
@@ -818,6 +837,8 @@ export type CompactIssue = Pick<
| "projectWorkspaceId"
| "goalId"
| "parentId"
| "visibility"
| "privacyRootIssueId"
| "title"
| "description"
| "status"
@@ -13,6 +13,13 @@ import {
import { createAgentSchema } from "./agent.js";
describe("issue validators", () => {
it("accepts open/private visibility on create and update", () => {
expect(createIssueSchema.parse({ title: "Private", visibility: "private" }).visibility).toBe("private");
expect(createIssueSchema.parse({ title: "Open" }).visibility).toBe("open");
expect(updateIssueSchema.parse({}).visibility).toBeUndefined();
expect(updateIssueSchema.parse({ visibility: "private" }).visibility).toBe("private");
expect(updateIssueSchema.safeParse({ visibility: "secret" }).success).toBe(false);
});
it("passes real line breaks through unchanged", () => {
const parsed = createIssueSchema.parse({
title: "Follow up PR",
+3
View File
@@ -427,6 +427,7 @@ const createIssueBaseSchema = z.object({
projectWorkspaceId: z.string().uuid().optional().nullable(),
goalId: z.string().uuid().optional().nullable(),
parentId: z.string().uuid().optional().nullable(),
visibility: z.enum(["open", "private"]).optional().default("open"),
blockedByIssueIds: z.array(z.string().uuid()).optional(),
unblockDescriptor: z.object({
owner: z.union([
@@ -535,7 +536,9 @@ export const updateIssueSchema = createIssueBaseSchema.omit({
createdByUserId: true,
responsibleUserId: true,
watchdog: true,
visibility: true,
}).partial().extend({
visibility: z.enum(["open", "private"]).optional(),
requestDepth: issueRequestDepthInputSchema.optional(),
assigneeAgentId: z.string().trim().min(1).optional().nullable(),
comment: multilineTextSchema.pipe(z.string().min(1)).optional(),