Merge branch 'feature/issue-ticket-system' into feature/background-tasks-first-class

Intègre le système de tickets/issues V1 (validé QA vert de bout en bout,
mémoire tickets-v1-e2e-validated-qa) dans la branche des tâches de fond.
Conflits résolus : app-tauri/state.rs, domain/events.rs, domain/lib.rs, domain/ports.rs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-03 15:15:43 +02:00
38 changed files with 6539 additions and 45 deletions

View File

@ -41,6 +41,12 @@ import type {
SkillScope,
Template,
TerminalSession,
Ticket,
TicketCarnet,
TicketLinkKind,
TicketList,
TicketPriority,
TicketStatus,
TurnPage,
Unsubscribe,
} from "@/domain";
@ -698,6 +704,98 @@ export interface ConversationGateway {
): Promise<TurnPage>;
}
/** Filter/search criteria for {@link TicketGateway.list}. */
export interface TicketListQuery {
status?: TicketStatus;
priority?: TicketPriority;
assignedAgentId?: string;
/** Free-text match over title/description. */
text?: string;
limit?: number;
cursor?: string;
}
/** Input for {@link TicketGateway.create}. */
export interface CreateTicketInput {
title: string;
description?: string;
priority?: TicketPriority;
status?: TicketStatus;
assignedAgentIds?: string[];
}
/**
* Fields to change on {@link TicketGateway.update}. Only the provided keys are
* touched; `expectedVersion` is mandatory (optimistic concurrency — a stale
* value surfaces a `versionConflict` {@link GatewayError}).
*/
export interface UpdateTicketInput {
title?: string;
description?: string;
status?: TicketStatus;
priority?: TicketPriority;
assignedAgentIds?: string[];
expectedVersion: number;
}
/**
* Ticket gateway (public wire name for the backend `Issue` model). The only
* frontend place that knows the `ticket_*` command names; features consume this
* port through DI. Every mutation carries an `expectedVersion` and rejects with
* a `versionConflict` {@link GatewayError} when the ticket moved underneath.
*/
export interface TicketGateway {
/** Creates a ticket and returns it. */
create(projectId: string, input: CreateTicketInput): Promise<Ticket>;
/** Reads one ticket, optionally including its carnet body. */
read(
projectId: string,
ref: string,
includeCarnet?: boolean,
): Promise<Ticket>;
/** Lists tickets matching the (optional) filter/search query. */
list(projectId: string, query?: TicketListQuery): Promise<TicketList>;
/** Applies partial edits (title/description/status/priority/assignees). */
update(
projectId: string,
ref: string,
input: UpdateTicketInput,
): Promise<Ticket>;
/** Reads the ticket-scoped Markdown carnet. */
readCarnet(projectId: string, ref: string): Promise<TicketCarnet>;
/** Replaces (not appends) the ticket-scoped carnet body. */
updateCarnet(
projectId: string,
ref: string,
carnet: string,
expectedVersion: number,
): Promise<Ticket>;
/** Links this ticket to another `#id` with the given relationship kind. */
link(
projectId: string,
ref: string,
targetRef: string,
kind: TicketLinkKind,
expectedVersion: number,
): Promise<Ticket>;
/** Removes a link (optionally scoped to a single kind). */
unlink(
projectId: string,
ref: string,
targetRef: string,
expectedVersion: number,
kind?: TicketLinkKind,
): Promise<Ticket>;
/** Assigns/unassigns an agent to the ticket. */
assign(
projectId: string,
ref: string,
agentId: string,
assigned: boolean,
expectedVersion: number,
): Promise<Ticket>;
}
/**
* The full set of gateways the app depends on, injected via the DI provider.
* The composition (real vs mock) is chosen in `app/`.
@ -719,4 +817,5 @@ export interface Gateways {
permission: PermissionGateway;
workState: WorkStateGateway;
conversation: ConversationGateway;
ticket: TicketGateway;
}