From 6a31bc3737d45d7e0e2faf0217229d8682e02b2a Mon Sep 17 00:00:00 2001 From: kjh2064 Date: Sun, 2 Aug 2026 05:59:01 +0900 Subject: [PATCH] PR 4b: Apply CA1822 static method modifiers + Gitea Actions secrets guidance Completed DEBT-001 paydown (1pt) by making three pure-function methods static: - ScheduleOccurrencePlanner.GetNextDueAt (no instance state accessed) - PromotionGateEvaluator.Evaluate (evidence gate only, no mutations) - EvaluationWindowPlanner.Plan (deterministic date calculation) Changes: - Added `static` modifier to three domain methods - Updated call sites: ModelOperationsDispatcherJob, tests - Removed unnecessary DI registrations (ModelOperationsModule) - Eliminated instance creation overhead in tests Test Results: 40/40 PASS (17 ModelOps + 18 SignalEngine + 5 Architecture) Documentation: - Updated TECH_DEBT_REGISTER.md: DEBT-001 Completed (PR 4b) - Added Gitea Actions Secrets section to CLAUDE.md documenting: - KRX_API_KEY, OPENDART_API_KEY, KIS_API_KEY storage location - CI/CD usage pattern - Local dev guidance Per AGENTS.md v16.0: Code changes are performance improvements, not suppressions. Quarterly paydown: +1pt (target 4pts for 20% Q3 2026) Co-Authored-By: Claude Haiku 4.5 --- CLAUDE.md | 25 ++++++++++++++++++- Directory.Build.props | 2 +- TECH_DEBT_REGISTER.md | 14 +++++------ .../Domain/EvaluationWindowPlanner.cs | 2 +- .../Domain/PromotionGateEvaluator.cs | 2 +- .../Domain/ScheduleOccurrencePlanner.cs | 2 +- .../ModelOperationsModule.cs | 3 --- .../ModelOperationsDispatcherJob.cs | 3 +-- .../EvaluationWindowPlannerTests.cs | 3 +-- .../PromotionGateEvaluatorTests.cs | 4 +-- .../ScheduleOccurrencePlannerTests.cs | 5 ++-- 11 files changed, 41 insertions(+), 24 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index b8d63260..ac6c5489 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -452,7 +452,30 @@ Before writing code, verify: - **Non-value-loss sell:** Requires ReentryWatch, new CycleId/Lot, step intervals, expiry, dedup. - **Activation gating:** Requires ModelCard, OOS/PBO/DSR evidence, maker-checker approval, effective_at, rollback justification. -## Gitea API Automation (Optional but Recommended) +## Gitea API Automation & Actions Secrets + +### Gitea Actions Secrets + +**External API keys are stored in Gitea Actions Secrets (not in .env or code).** + +**Location:** `https://gitea.taxbaik.com/kjh2064/KArtSell.Aegis/settings/actions/secrets` + +**Available secrets:** +- `KRX_API_KEY` — Korea Exchange data feed (market calendar, trading sessions) +- `OPENDART_API_KEY` — OpenDart financial disclosure API +- `KIS_API_KEY` — Korea Investment & Securities trading API + +**Usage in CI/CD (`.gitea/workflows/*.yml`):** +```yaml +env: + KRX_API_KEY: ${{ secrets.KRX_API_KEY }} + OPENDART_API_KEY: ${{ secrets.OPENDART_API_KEY }} + KIS_API_KEY: ${{ secrets.KIS_API_KEY }} +``` + +**For local development:** Ask team lead for local sandbox keys or use mock fixtures in tests. + +### Gitea API Automation (Optional but Recommended) ### Environment Setup diff --git a/Directory.Build.props b/Directory.Build.props index c0607132..5ca5caad 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -6,7 +6,7 @@ enable true latest-recommended - $(NoWarn);CA1822;CA1873;CA1305;CA1707;CA1861;xUnit2031 + $(NoWarn);CA1873;CA1305;CA1707;CA1861;xUnit2031 true true diff --git a/TECH_DEBT_REGISTER.md b/TECH_DEBT_REGISTER.md index ba0f871f..42f49782 100644 --- a/TECH_DEBT_REGISTER.md +++ b/TECH_DEBT_REGISTER.md @@ -8,9 +8,9 @@ | Status | Count | Total Impact | |--------|-------|--------------| -| Backlog | 8 | 20 pts | +| Backlog | 7 | 19 pts | | In Progress | 0 | 0 pts | -| Completed | 0 | 0 pts | +| Completed | 1 | 1 pt | --- @@ -20,7 +20,7 @@ | ID | Category | Impact | Effort | Status | Notes | Owner | ADR | |----|----------|--------|--------|--------|-------|-------|-----| -| DEBT-001 | CA1822 (static hints) | Low (1) | Low (1) | Backlog | Analyzer suggestions for instance methods that could be static. Not performance-critical; batch during refactors. | Team | - | +| DEBT-001 | CA1822 (static hints) | Low (1) | Low (1) | Completed | Applied `static` to GetNextDueAt, Evaluate, Plan methods; removed DI registrations. | @claude | PR 4b | | DEBT-002 | CA1873 (array logging) | Low (1) | Low (1) | Backlog | Conditional array evaluation in logging. Low runtime impact; defer until performance profiling. | Team | - | | DEBT-003 | CA1305 (culture) | Low (1) | Low (1) | Backlog | Locale-specific formatting. Accept as-is for Serilog; breaking change if fixed. | Team | - | | DEBT-004 | CA1707 (test naming) | Low (1) | Low (1) | Backlog | xUnit underscores in test names. Convention; no fix needed. | Team | - | @@ -47,12 +47,12 @@ Low Impact QUICK WINS MONITOR (DEBT-001/002) (DEBT-003/004/005/006/008) ``` -### Quick Wins — Q3 2026 (To Resolve) +### Quick Wins — Q3 2026 (In Progress) **Rationale (per AGENTS.md v16.0 "Paydown Target: 20% quarterly"):** -- DEBT-001 (CA1822): static method hints — True performance benefit. Easy to fix with `static` modifier. **Target: PR 4c** -- DEBT-002 (CA1873): array logging — Avoid unnecessary array allocation in conditional log. Easy fix with guard check. **Target: PR 4d** -- Result: +2 pts resolved (4pts total for Q3 target) ✅ +- ✅ DEBT-001 (CA1822): static method hints — Completed in PR 4b. Applied `static` to ScheduleOccurrencePlanner.GetNextDueAt, PromotionGateEvaluator.Evaluate, EvaluationWindowPlanner.Plan; removed unnecessary DI registrations (+1 pt). +- DEBT-002 (CA1873): array logging — Avoid unnecessary array allocation in conditional log. Easy fix with guard check. **Target: PR 4c** +- Current: +1 pt resolved; Target: +2 pts total (4pts for 20% Q3 target) ✅ ### Batch During Feature Work - ~~DEBT-001~~, ~~DEBT-002~~ — Moving to Quick Wins (PR 4 priority) diff --git a/src/KArtSell.Modules.ModelOperations/Domain/EvaluationWindowPlanner.cs b/src/KArtSell.Modules.ModelOperations/Domain/EvaluationWindowPlanner.cs index 3acdb003..c07d37ab 100644 --- a/src/KArtSell.Modules.ModelOperations/Domain/EvaluationWindowPlanner.cs +++ b/src/KArtSell.Modules.ModelOperations/Domain/EvaluationWindowPlanner.cs @@ -11,7 +11,7 @@ public sealed class EvaluationWindowPlanner { public static readonly int[] ApprovedWindows = [1, 5, 20, 63, 126, 252]; - public IReadOnlyList Plan( + public static IReadOnlyList Plan( string calendarId, DateOnly predictionSession, ITradingSessionCalendar calendar) diff --git a/src/KArtSell.Modules.ModelOperations/Domain/PromotionGateEvaluator.cs b/src/KArtSell.Modules.ModelOperations/Domain/PromotionGateEvaluator.cs index b5d57ac7..850283b1 100644 --- a/src/KArtSell.Modules.ModelOperations/Domain/PromotionGateEvaluator.cs +++ b/src/KArtSell.Modules.ModelOperations/Domain/PromotionGateEvaluator.cs @@ -5,7 +5,7 @@ namespace KArtSell.Modules.ModelOperations.Domain; /// public sealed class PromotionGateEvaluator { - public PromotionGateResult Evaluate(ModelEvaluationSnapshot snapshot, PromotionGateThresholds thresholds) + public static PromotionGateResult Evaluate(ModelEvaluationSnapshot snapshot, PromotionGateThresholds thresholds) { var blockers = new List(); var warnings = new List(); diff --git a/src/KArtSell.Modules.ModelOperations/Domain/ScheduleOccurrencePlanner.cs b/src/KArtSell.Modules.ModelOperations/Domain/ScheduleOccurrencePlanner.cs index 9d769c92..c3f383d8 100644 --- a/src/KArtSell.Modules.ModelOperations/Domain/ScheduleOccurrencePlanner.cs +++ b/src/KArtSell.Modules.ModelOperations/Domain/ScheduleOccurrencePlanner.cs @@ -2,7 +2,7 @@ namespace KArtSell.Modules.ModelOperations.Domain; public sealed class ScheduleOccurrencePlanner { - public DateTimeOffset GetNextDueAt( + public static DateTimeOffset GetNextDueAt( DateTimeOffset scheduledFor, string cadence, string catchUpPolicy, diff --git a/src/KArtSell.Modules.ModelOperations/ModelOperationsModule.cs b/src/KArtSell.Modules.ModelOperations/ModelOperationsModule.cs index 1c5212be..a3e686a4 100644 --- a/src/KArtSell.Modules.ModelOperations/ModelOperationsModule.cs +++ b/src/KArtSell.Modules.ModelOperations/ModelOperationsModule.cs @@ -9,9 +9,6 @@ public static class ModelOperationsModule { public static IServiceCollection AddModelOperationsModule(this IServiceCollection services) { - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/src/KArtSell.Modules.ModelOperations/Scheduling/ModelOperationsDispatcherJob.cs b/src/KArtSell.Modules.ModelOperations/Scheduling/ModelOperationsDispatcherJob.cs index 0a78c9ca..57950a1c 100644 --- a/src/KArtSell.Modules.ModelOperations/Scheduling/ModelOperationsDispatcherJob.cs +++ b/src/KArtSell.Modules.ModelOperations/Scheduling/ModelOperationsDispatcherJob.cs @@ -12,7 +12,6 @@ public sealed class ModelOperationsDispatcherJob( IModelScheduleRepository schedules, IBackgroundJobClient jobs, IClock clock, - ScheduleOccurrencePlanner occurrencePlanner, ILogger logger) { private static readonly Action LogDispatchFailed = @@ -42,7 +41,7 @@ public sealed class ModelOperationsDispatcherJob( item.IdempotencyKey)); var backgroundJobId = jobs.Create(job, new EnqueuedState(item.Queue)); - var nextDueAt = occurrencePlanner.GetNextDueAt(item.ScheduledFor, item.Cadence, item.CatchUpPolicy, item.MaxCatchUp, now); + var nextDueAt = ScheduleOccurrencePlanner.GetNextDueAt(item.ScheduledFor, item.Cadence, item.CatchUpPolicy, item.MaxCatchUp, now); await schedules.MarkDispatchedAsync( item.ScheduleId, leaseOwner, diff --git a/tests/KArtSell.ModelOperations.UnitTests/EvaluationWindowPlannerTests.cs b/tests/KArtSell.ModelOperations.UnitTests/EvaluationWindowPlannerTests.cs index 8bd483d8..920ed257 100644 --- a/tests/KArtSell.ModelOperations.UnitTests/EvaluationWindowPlannerTests.cs +++ b/tests/KArtSell.ModelOperations.UnitTests/EvaluationWindowPlannerTests.cs @@ -4,8 +4,7 @@ public sealed class EvaluationWindowPlannerTests { [Fact] public void Uses_trading_sessions_not_calendar_days() { - var planner = new EvaluationWindowPlanner(); - var due = planner.Plan("KRX", new DateOnly(2026, 8, 3), new WeekdayCalendar()); + var due = EvaluationWindowPlanner.Plan("KRX", new DateOnly(2026, 8, 3), new WeekdayCalendar()); Assert.Equal(new[] { 1, 5, 20, 63, 126, 252 }, due.Select(x => x.WindowTradingDays)); Assert.All(due, x => Assert.DoesNotContain(x.DueSession.DayOfWeek, new[] { DayOfWeek.Saturday, DayOfWeek.Sunday })); } diff --git a/tests/KArtSell.ModelOperations.UnitTests/PromotionGateEvaluatorTests.cs b/tests/KArtSell.ModelOperations.UnitTests/PromotionGateEvaluatorTests.cs index 3bc4b840..892a11ac 100644 --- a/tests/KArtSell.ModelOperations.UnitTests/PromotionGateEvaluatorTests.cs +++ b/tests/KArtSell.ModelOperations.UnitTests/PromotionGateEvaluatorTests.cs @@ -22,7 +22,7 @@ public sealed class PromotionGateEvaluatorTests true, DateTimeOffset.UtcNow); - var result = new PromotionGateEvaluator().Evaluate(snapshot, PromotionGateThresholds.ResearchBaseline); + var result = PromotionGateEvaluator.Evaluate(snapshot, PromotionGateThresholds.ResearchBaseline); Assert.Equal(GateDecision.Pass, result.Decision); Assert.Empty(result.BlockingReasons); @@ -49,7 +49,7 @@ public sealed class PromotionGateEvaluatorTests true, DateTimeOffset.UtcNow); - var result = new PromotionGateEvaluator().Evaluate(snapshot, PromotionGateThresholds.ResearchBaseline); + var result = PromotionGateEvaluator.Evaluate(snapshot, PromotionGateThresholds.ResearchBaseline); Assert.Equal(GateDecision.Hold, result.Decision); Assert.Contains(result.BlockingReasons, reason => reason.Contains("Operational integrity", StringComparison.Ordinal)); diff --git a/tests/KArtSell.ModelOperations.UnitTests/ScheduleOccurrencePlannerTests.cs b/tests/KArtSell.ModelOperations.UnitTests/ScheduleOccurrencePlannerTests.cs index 9c7a6937..50e2946d 100644 --- a/tests/KArtSell.ModelOperations.UnitTests/ScheduleOccurrencePlannerTests.cs +++ b/tests/KArtSell.ModelOperations.UnitTests/ScheduleOccurrencePlannerTests.cs @@ -2,17 +2,16 @@ using KArtSell.Modules.ModelOperations.Domain; namespace KArtSell.ModelOperations.UnitTests; public sealed class ScheduleOccurrencePlannerTests { - private readonly ScheduleOccurrencePlanner planner = new(); [Fact] public void Daily_anchor_does_not_drift_to_dispatch_time() { var scheduled = new DateTimeOffset(2026, 8, 1, 1, 0, 0, TimeSpan.Zero); - var next = planner.GetNextDueAt(scheduled, "DAILY", "LATEST_ONLY", 1, scheduled.AddHours(10)); + var next = ScheduleOccurrencePlanner.GetNextDueAt(scheduled, "DAILY", "LATEST_ONLY", 1, scheduled.AddHours(10)); Assert.Equal(scheduled.AddDays(1), next); } [Fact] public void Missed_occurrences_are_skipped_without_dispatch_storm() { var scheduled = new DateTimeOffset(2026, 7, 1, 1, 0, 0, TimeSpan.Zero); - var next = planner.GetNextDueAt(scheduled, "DAILY", "LATEST_ONLY", 1, new DateTimeOffset(2026, 8, 1, 4, 0, 0, TimeSpan.Zero)); + var next = ScheduleOccurrencePlanner.GetNextDueAt(scheduled, "DAILY", "LATEST_ONLY", 1, new DateTimeOffset(2026, 8, 1, 4, 0, 0, TimeSpan.Zero)); Assert.True(next > new DateTimeOffset(2026, 8, 1, 4, 0, 0, TimeSpan.Zero)); Assert.Equal(1, next.Hour); }