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

@ -33,6 +33,9 @@ use crate::background_task::{
};
use crate::events::DomainEvent;
use crate::ids::{AgentId, NodeId, ProjectId, ScheduleId, SessionId, TaskId};
use crate::issue::{
Issue, IssueCarnet, IssueIndexEntry, IssueListFilter, IssueNumber, IssueRef, IssueVersion,
};
use crate::markdown::MarkdownDoc;
use crate::memory::{Memory, MemoryIndexEntry, MemoryLink, MemorySlug};
use crate::permission::ProjectPermissions;
@ -566,6 +569,27 @@ pub enum WakeError {
Task(String),
}
/// Errors from the issue store.
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum IssueStoreError {
/// The requested issue does not exist.
#[error("issue not found")]
NotFound,
/// Optimistic concurrency conflict.
#[error("issue version conflict: expected {expected}, actual {actual}")]
VersionConflict {
/// Expected version.
expected: IssueVersion,
/// Actual persisted version.
actual: IssueVersion,
},
/// The issue payload is invalid.
#[error("issue invalid: {0}")]
Invalid(String),
/// Store I/O or serialization failed.
#[error("issue store failed: {0}")]
Store(String),
}
// ---------------------------------------------------------------------------
// Git port support types
// ---------------------------------------------------------------------------
@ -1385,6 +1409,84 @@ pub trait AgentWakePort: Send + Sync {
) -> Result<(), WakeError>;
}
/// Allocates monotonic per-project issue numbers.
#[async_trait]
pub trait IssueNumberAllocator: Send + Sync {
/// Allocates the next number for `root`.
///
/// Implementations must never reuse an allocated number, even if a later
/// issue creation step fails.
///
/// # Errors
/// [`IssueStoreError`] on persistence/lock failure.
async fn allocate_next(&self, root: &ProjectPath) -> Result<IssueNumber, IssueStoreError>;
}
/// Persistence port for project-scoped issues.
#[async_trait]
pub trait IssueStore: Send + Sync {
/// Creates a new issue.
///
/// # Errors
/// [`IssueStoreError`] when the issue already exists or cannot be stored.
async fn create(&self, root: &ProjectPath, issue: &Issue) -> Result<(), IssueStoreError>;
/// Reads an issue by `#N` reference.
///
/// # Errors
/// [`IssueStoreError::NotFound`] when absent.
async fn get_by_ref(
&self,
root: &ProjectPath,
issue_ref: IssueRef,
) -> Result<Issue, IssueStoreError>;
/// Lists issue index entries matching `filter`.
///
/// # Errors
/// [`IssueStoreError`] on persistence failure.
async fn list(
&self,
root: &ProjectPath,
filter: IssueListFilter,
) -> Result<Vec<IssueIndexEntry>, IssueStoreError>;
/// Replaces an issue after checking its expected version.
///
/// # Errors
/// [`IssueStoreError::VersionConflict`] on optimistic-concurrency conflict.
async fn update(
&self,
root: &ProjectPath,
issue: &Issue,
expected_version: IssueVersion,
) -> Result<(), IssueStoreError>;
/// Reads only the issue carnet projection.
///
/// # Errors
/// [`IssueStoreError::NotFound`] when absent.
async fn read_carnet(
&self,
root: &ProjectPath,
issue_ref: IssueRef,
) -> Result<IssueCarnet, IssueStoreError>;
/// Replaces an issue carnet and increments the issue version.
///
/// # Errors
/// [`IssueStoreError::VersionConflict`] on optimistic-concurrency conflict.
async fn write_carnet(
&self,
root: &ProjectPath,
issue_ref: IssueRef,
carnet: MarkdownDoc,
actor: crate::issue::IssueActor,
now_ms: u64,
expected_version: IssueVersion,
) -> Result<IssueCarnet, IssueStoreError>;
}
/// Git operations for a project. Named `GitPort` to avoid clashing with the
/// [`crate::git::GitRepository`] *entity* (state image).
#[async_trait]