Workstream H: Implement VS-03 Approval Workflow (Maker-Checker governance)

- 3 API endpoints: POST /approvals, GET /approvals, POST /approvals/{id}/approve
- State machine: DRAFT → PROPOSED → APPROVED → ACTIVE
- RBAC enforcement: Maker ≠ Checker separation of duties
- Evidence linkage: PBO/DSR/OOS artifact URLs stored
- Schema: Append-only events with correlation_id
- Tests: 5+ unit/integration scenarios
- Documentation: Full API contracts + compliance procedures
- AGENTS.md v16.0 13/13 compliance 

Closes workstream H (Phase 2 implementation).

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-08-07 16:38:14 +09:00
parent 2b2841671c
commit a2e742c78d
8 changed files with 1327 additions and 0 deletions
@@ -0,0 +1,109 @@
namespace KArtSell.Modules.ModelOperations.ApprovalWorkflow;
using System;
using System.Collections.Generic;
public class ApprovalProposal
{
public Guid Id { get; set; }
public Guid ModelId { get; set; }
public ApprovalStatus Status { get; set; }
public string CreatedBy { get; set; } = null!;
public DateTimeOffset CreatedAt { get; set; }
public string Justification { get; set; } = null!;
public DateOnly EffectiveAt { get; set; }
public DateTimeOffset? ProposedAt { get; set; }
public string? ApprovedBy { get; set; }
public DateTimeOffset? ApprovedAt { get; set; }
public string? ApprovalNotes { get; set; }
public string? ActivatedBy { get; set; }
public DateTimeOffset? ActivatedAt { get; set; }
public DateTimeOffset PublishedAt { get; set; }
public int Revision { get; set; }
public Guid CorrelationId { get; set; }
public List<ApprovalEvidence> Evidence { get; set; } = [];
public List<ApprovalEvent> Events { get; set; } = [];
public bool CanBeProposed => Status == ApprovalStatus.Draft && CreatedBy is not null;
public bool CanBeApproved => Status == ApprovalStatus.Proposed;
public bool CanBeActivated => Status == ApprovalStatus.Approved;
}
public enum ApprovalStatus
{
Draft,
Proposed,
Approved,
Active,
Rejected
}
public class ApprovalEvidence
{
public Guid Id { get; set; }
public Guid ApprovalProposalId { get; set; }
public string EvidenceType { get; set; } = null!;
public string EvidenceUrl { get; set; } = null!;
public string? ReviewerComment { get; set; }
public DateTimeOffset PublishedAt { get; set; }
public Guid CorrelationId { get; set; }
}
public class ApprovalEvent
{
public Guid Id { get; set; }
public Guid ApprovalProposalId { get; set; }
public string EventType { get; set; } = null!;
public string ActorEmail { get; set; } = null!;
public DateTimeOffset EventAt { get; set; }
public Dictionary<string, object>? Details { get; set; }
public DateTimeOffset PublishedAt { get; set; }
public Guid CorrelationId { get; set; }
}
public class CreateApprovalProposalRequest
{
public Guid ModelId { get; set; }
public DateOnly EffectiveAt { get; set; }
public string Justification { get; set; } = null!;
}
public class ApproveApprovalRequest
{
public string ApprovalNotes { get; set; } = null!;
public List<EvidenceItem> Evidence { get; set; } = [];
}
public class EvidenceItem
{
public string Type { get; set; } = null!;
public string Url { get; set; } = null!;
public string? Comment { get; set; }
}
public class ApprovalProposalResponse
{
public Guid Id { get; set; }
public Guid ModelId { get; set; }
public string Status { get; set; } = null!;
public string CreatedBy { get; set; } = null!;
public DateTimeOffset CreatedAt { get; set; }
public string Justification { get; set; } = null!;
public DateOnly EffectiveAt { get; set; }
public string? ApprovedBy { get; set; }
public DateTimeOffset? ApprovedAt { get; set; }
public string? ApprovalNotes { get; set; }
public List<ApprovalEvidenceResponse> Evidence { get; set; } = [];
}
public class ApprovalEvidenceResponse
{
public Guid Id { get; set; }
public string EvidenceType { get; set; } = null!;
public string EvidenceUrl { get; set; } = null!;
public string? ReviewerComment { get; set; }
}