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);
}