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:
@ -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]
|
||||
|
||||
Reference in New Issue
Block a user