V13-FE-006: consolidate approved UI and contract hardening
deploy / deploy (push) Successful in 1m52s
deploy / notify (push) Successful in 1s

This commit is contained in:
2026-08-13 02:41:00 +09:00
parent d79edae546
commit 3f293d8aa8
1278 changed files with 14384 additions and 1664 deletions
@@ -59,7 +59,7 @@ public class QueryAuditEventsEndpoint : Endpoint<QueryAuditEventsRequest, QueryA
public override void Configure()
{
Get("/audit/events");
AllowAnonymous(); // RBAC enforced at handler level (Compliance Officer role)
Roles("Compliance");
Summary(x =>
{
x.Summary = "Query Audit Events";
@@ -39,7 +39,7 @@ public class SubmitGdprRequestEndpoint : Endpoint<SubmitGdprRequestDto, GdprRequ
public override void Configure()
{
Post("/compliance/gdpr-request");
AllowAnonymous(); // RBAC enforced at handler level (Data Admin/Compliance Officer role)
Roles("DataAdmin", "Compliance");
Summary(x =>
{
x.Summary = "Submit GDPR Request";
@@ -22,7 +22,7 @@ public class CreateApprovalEndpoint : EndpointWithoutRequest<CreateApprovalRespo
public override void Configure()
{
Post("/approvals");
AllowAnonymous();
Roles("Maker");
}
public override async Task HandleAsync(CancellationToken ct)
@@ -51,7 +51,7 @@ public class GetApprovalsEndpoint : Endpoint<GetApprovalsRequest, GetApprovalsRe
public override void Configure()
{
Get("/approvals");
AllowAnonymous();
Roles("Maker", "Checker", "SRE");
}
public override async Task HandleAsync(GetApprovalsRequest req, CancellationToken ct)
@@ -76,7 +76,7 @@ public class ProposeForReviewEndpoint : EndpointWithoutRequest<ProposeForReviewR
public override void Configure()
{
Post("/approvals/{id}/propose");
AllowAnonymous();
Roles("Maker");
}
public override async Task HandleAsync(CancellationToken ct)
@@ -111,7 +111,7 @@ public class ActivateApprovalEndpoint : EndpointWithoutRequest<ActivateApprovalR
public override void Configure()
{
Post("/approvals/{id}/activate");
AllowAnonymous();
Roles("SRE");
}
public override async Task HandleAsync(CancellationToken ct)
@@ -140,7 +140,7 @@ public class GetApprovalByIdEndpoint : EndpointWithoutRequest<ApprovalDetailResp
public override void Configure()
{
Get("/approvals/{id}");
AllowAnonymous();
Roles("Maker", "Checker", "SRE");
}
public override async Task HandleAsync(CancellationToken ct)
@@ -183,7 +183,7 @@ public class ApproveApprovalEndpoint : Endpoint<ApproveApprovalRequest, ApproveA
public override void Configure()
{
Post("/approvals/{id}/approve");
AllowAnonymous();
Roles("Checker");
}
public override async Task HandleAsync(ApproveApprovalRequest req, CancellationToken ct)
@@ -6,10 +6,12 @@ using System.Linq;
using System.Threading.Tasks;
using FastEndpoints;
using KArtSell.BuildingBlocks.Time;
using Microsoft.AspNetCore.Http;
/// <summary>
/// GET /reconciliation/holdings - Returns current portfolio holdings
/// </summary>
[DontRegister]
public class GetHoldingsEndpoint : EndpointWithoutRequest<GetHoldingsResponse>
{
private readonly IReconciliationRepository _repository;
@@ -74,6 +76,7 @@ public class HoldingDto
/// <summary>
/// GET /reconciliation/mismatches - Returns flagged discrepancies
/// </summary>
[DontRegister]
public class GetMismatchesEndpoint : EndpointWithoutRequest<GetMismatchesResponse>
{
private readonly IReconciliationRepository _repository;
@@ -148,6 +151,7 @@ public class MismatchDto
/// <summary>
/// POST /reconciliation/reconcile-trade - Trigger trade reconciliation
/// </summary>
[DontRegister]
public class ReconcileTradeEndpoint : Endpoint<ReconcileTradeRequest>
{
private readonly ReconcileTradeHandler _handler;
@@ -165,6 +169,17 @@ public class ReconcileTradeEndpoint : Endpoint<ReconcileTradeRequest>
public override async Task HandleAsync(ReconcileTradeRequest request, CancellationToken ct)
{
if (!ReconcileTradeRequestContract.HasIdempotencyKey(request))
{
await Send.ResponseAsync(new
{
type = "https://httpstatuses.com/400",
title = "Invalid reconciliation request",
detail = "Idempotency-Key is required for replay-safe reconciliation."
}, StatusCodes.Status400BadRequest, ct);
return;
}
var command = new ReconcileTradeCommand
{
TradeId = request.TradeId,
@@ -186,6 +201,12 @@ public class ReconcileTradeEndpoint : Endpoint<ReconcileTradeRequest>
}
}
public static class ReconcileTradeRequestContract
{
public static bool HasIdempotencyKey(ReconcileTradeRequest request) =>
request is not null && !string.IsNullOrWhiteSpace(request.IdempotencyKey);
}
public class ReconcileTradeRequest
{
public Guid TradeId { get; set; }
@@ -204,6 +225,7 @@ public class ReconcileTradeRequest
/// <summary>
/// GET /reconciliation/report/daily - Returns daily reconciliation report
/// </summary>
[DontRegister]
public class GetDailyReportEndpoint : EndpointWithoutRequest<ReconciliationReportDto>
{
private readonly ReconciliationEngine _engine;
@@ -51,6 +51,10 @@ public class ReconcileTradeHandler
public async Task HandleAsync(ReconcileTradeCommand command)
{
ArgumentNullException.ThrowIfNull(command);
if (string.IsNullOrWhiteSpace(command.IdempotencyKey))
{
throw new ArgumentException("IdempotencyKey is required for replay-safe reconciliation.", nameof(command));
}
// DEBT-018: share one connection/transaction across the engine's holding/log writes and
// the outbox event(s) below, instead of the engine writing on its own connection and the
@@ -93,7 +97,7 @@ public class ReconcileTradeHandler
: null,
ReconciliationTimestamp = _clock.UtcNow.UtcDateTime,
CorrelationId = command.CorrelationId,
IdempotencyKey = command.IdempotencyKey ?? Guid.NewGuid().ToString()
IdempotencyKey = command.IdempotencyKey
};
await PublishAsync(transaction, "TradeReconciled", @event, command.CorrelationId, CancellationToken.None);
@@ -18,7 +18,6 @@ public class CreateSellDecisionEndpoint : Endpoint<CreateSellDecisionRequest, Cr
{
Post("/sell-decisions");
Roles("Maker");
AllowAnonymous();
}
public override async Task HandleAsync(CreateSellDecisionRequest req, CancellationToken ct)
@@ -45,7 +44,6 @@ public class ListSellDecisionsEndpoint : EndpointWithoutRequest<ListSellDecision
{
Get("/sell-decisions");
Roles("Quant", "Maker", "Checker");
AllowAnonymous();
}
public override async Task HandleAsync(CancellationToken ct)
@@ -112,7 +110,6 @@ public class ExecuteSellDecisionEndpoint : Endpoint<ExecuteSellDecisionRequest,
{
Post("/sell-decisions/{id}/execute");
Roles("Maker", "Checker");
AllowAnonymous();
}
public override async Task HandleAsync(ExecuteSellDecisionRequest req, CancellationToken ct)
@@ -4,6 +4,7 @@ using Microsoft.Extensions.Logging;
namespace KArtSell.Modules.ModelOperations.TradeExecution;
[DontRegister]
public class CreateTradeEndpoint : Endpoint<CreateTradeRequest, CreateTradeResponse>
{
private readonly SubmitTradeHandler _handler;
@@ -53,6 +54,7 @@ public class CreateTradeEndpoint : Endpoint<CreateTradeRequest, CreateTradeRespo
}
}
[DontRegister]
public class ListTradesEndpoint : Endpoint<EmptyRequest, ListTradesResponse>
{
private readonly ITradeSql _sql;