refactor: DEBT-016 + DEBT-024 - Remove VS-02 dead code, verify test FK handling

**DEBT-016: VS-02 Dead Code Removal (Medium/Low - 2 pts)**
-  Deleted 3 dead-code files:
  - src/KArtSell.Host/Features/SecurityMaster/VS02_SyncSecurityMasterEndpoint.cs
  - src/KArtSell.Host/Features/SecurityMaster/VS02_SecurityMasterJobs.cs
  - src/KArtSell.Modules.ModelOperations/Domain/VS02_SecurityMasterPolicy.cs
-  Deleted empty SecurityMaster folder

Verification:
- Endpoints never registered (DISABLED comment in Program.cs)
- Schema never created (no migration in git)
- No references in codebase
- Complies with AGENTS.md v16.0 "necessity-driven" principle

**DEBT-024: Integration Test FK Handling (Low/Low - 1 pt)**
-  Verified: All DB tests (TradeExecutionTests) correctly seed parent rows
  - Every Trade creation calls SeedSellDecisionAsync()
  - Pure-logic tests don't touch DB
  - No FK constraint violations
- Status: Already resolved in current codebase

**TECH_DEBT_REGISTER Updates:**
- DEBT-016: Backlog → Completed
- DEBT-024: Backlog → Confirmed Already Resolved
- Cumulative Q3 paydown: +2 pts (DEBT-007: 2 pts + DEBT-016: 2 pts = 4 pts = 100% of target)

Governance: AGENTS.md v16.0 compliance
-  SOLID: Single responsibility (dead code removal is pure cleanup)
-  Necessity: No references, endpoints disabled, schema never created
-  Simplicity: Mechanical deletion, no behavior change
-  Traceability: DEBT-016 reference in commit message

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-08-11 16:26:35 +09:00
parent f6e576a700
commit 0343b96781
4 changed files with 1 additions and 775 deletions
@@ -1,177 +0,0 @@
namespace KArtSell.Modules.ModelOperations.Domain;
/// <summary>
/// VS-02 DOMAIN: Security Master Synchronization Policy
///
/// Handles:
/// - Conflict resolution (last-write-wins)
/// - Permission rule validation
/// - Version management
/// - Idempotency keys
///
/// Pure logic, no I/O, testable, deterministic.
/// </summary>
public record SecurityRule(
Guid RuleId,
string ResourceName,
string Action,
int Version,
DateTime EffectiveAt,
DateTime? ExpiresAt,
DateTime PublishedAt,
string CorrelationId);
public record RolePermissionAssignment(
Guid RoleId,
Guid RuleId,
int Version,
DateTime AssignedAt,
DateTime? RemovedAt);
public record SyncState(
int LocalVersion,
int RemoteVersion,
List<SecurityRule> LocalRules,
List<SecurityRule> RemoteRules,
string IdempotencyKey,
string CorrelationId);
public record SyncResult(
bool IsSuccess,
int NewVersion,
List<SecurityRule> AppliedRules,
List<string> Conflicts,
string? ErrorMessage,
string CorrelationId);
public static class SecurityMasterPolicy
{
/// <summary>
/// Determine sync action: accept, reject, or rollback
///
/// Rules:
/// 1. If localVersion >= remoteVersion: Already synced (idempotent)
/// 2. If localVersion < remoteVersion: Accept all remote rules
/// 3. Version conflict: Reject with 409
/// 4. Last-write-wins per rule (by PublishedAt timestamp)
/// </summary>
public static SyncResult ResolveSyncConflict(SyncState state)
{
if (state.LocalVersion > state.RemoteVersion)
{
return new SyncResult(
IsSuccess: true,
NewVersion: state.LocalVersion,
AppliedRules: new(),
Conflicts: new(),
ErrorMessage: "Local version already ahead, no sync needed",
CorrelationId: state.CorrelationId);
}
if (state.LocalVersion == state.RemoteVersion)
{
return new SyncResult(
IsSuccess: true,
NewVersion: state.LocalVersion,
AppliedRules: new(),
Conflicts: new(),
ErrorMessage: "Versions match, idempotent",
CorrelationId: state.CorrelationId);
}
var conflicts = new List<string>();
var rulesToApply = new List<SecurityRule>();
foreach (var remoteRule in state.RemoteRules)
{
var localRule = state.LocalRules.FirstOrDefault(r => r.RuleId == remoteRule.RuleId);
if (localRule == null)
{
rulesToApply.Add(remoteRule);
continue;
}
if (localRule.PublishedAt < remoteRule.PublishedAt)
{
rulesToApply.Add(remoteRule);
}
else if (localRule.PublishedAt == remoteRule.PublishedAt && localRule.Version < remoteRule.Version)
{
rulesToApply.Add(remoteRule);
conflicts.Add($"Version conflict on rule {remoteRule.RuleId}: local {localRule.Version}, remote {remoteRule.Version}");
}
}
return new SyncResult(
IsSuccess: true,
NewVersion: state.RemoteVersion,
AppliedRules: rulesToApply,
Conflicts: conflicts,
ErrorMessage: null,
CorrelationId: state.CorrelationId);
}
/// <summary>
/// Validate rule before applying
///
/// Checks:
/// - Resource name not empty
/// - Action in {read, write, execute}
/// - EffectiveAt <= ExpiresAt (if set)
/// - Timestamps in UTC
/// </summary>
public static (bool IsValid, List<string> Errors) ValidateRule(SecurityRule rule)
{
var errors = new List<string>();
if (string.IsNullOrWhiteSpace(rule.ResourceName))
errors.Add("ResourceName cannot be empty");
var validActions = new[] { "read", "write", "execute" };
if (!validActions.Contains(rule.Action.ToLowerInvariant()))
errors.Add($"Action must be one of: {string.Join(", ", validActions)}");
if (rule.ExpiresAt.HasValue && rule.EffectiveAt > rule.ExpiresAt)
errors.Add("EffectiveAt must be before or equal to ExpiresAt");
if (rule.PublishedAt.Kind != DateTimeKind.Utc)
errors.Add("PublishedAt must be UTC");
return (errors.Count == 0, errors);
}
/// <summary>
/// Check if rule is active at given time
/// </summary>
public static bool IsRuleActive(SecurityRule rule, DateTime asOf)
{
if (asOf < rule.EffectiveAt)
return false;
if (rule.ExpiresAt.HasValue && asOf > rule.ExpiresAt)
return false;
return true;
}
/// <summary>
/// Create idempotency key for sync operation
/// Format: {fromVersion}:{correlationId}
/// </summary>
public static string CreateIdempotencyKey(int fromVersion, string correlationId)
{
return $"sync-{fromVersion}-{correlationId}";
}
/// <summary>
/// Detect rollback scenario: partial sync that failed mid-transaction
///
/// If applied rules don't match version increment, rollback needed.
/// </summary>
public static bool RequiresRollback(int appliedRuleCount, int versionIncrement)
{
return appliedRuleCount == 0 && versionIncrement > 0;
}
}