diff --git a/docs/CURRENT/AEG-X-016_KIS_HARD_OFF_SLICE_NOTE.md b/docs/CURRENT/AEG-X-016_KIS_HARD_OFF_SLICE_NOTE.md
new file mode 100644
index 00000000..497ab1a1
--- /dev/null
+++ b/docs/CURRENT/AEG-X-016_KIS_HARD_OFF_SLICE_NOTE.md
@@ -0,0 +1,22 @@
+# AEG-X-016 — KIS order-submission hard-off
+
+## Scope
+
+- **WBS ID:** `AEG-X-016`; **Requirement / API / DB / Job / UI / Test:** `REQ-KIS-OFF` / `Cross` / `Cross` / `Cross` / `Cross` / `Cross`.
+- **Dependency / Gate:** `AEG-X-005` (completed) / `ALL`.
+- **Acceptance:** zero external KIS order calls; capability override audit and kill switch pass.
+
+## Source / Assumption / Unknown / Decision Required
+
+- **Source:** `AGENTS.md` hard prohibition on automatic orders/KIS submission; `CapabilityOptions` startup validation; `TradeEndpoints`, `TradeHandlers`, `KisTradeExecutionService`, `TradeStatusPollingJob`, and Program recurring-job registration.
+- **Assumption:** the user's instruction is an unconditional hard-off, not a feature flag. Buy, sell, poll, and settlement calls must remain blocked even if an environment value attempts to enable a capability.
+- **Unknown:** there is no approved release that authorizes re-enabling KIS. This change supplies no activation path.
+- **Decision Required:** a future enablement would require a separately approved release, complete capability/kill-switch audit, and revised WBS evidence; it is out of scope.
+
+## Actual evidence and remaining acceptance
+
+- `dotnet test tests/KArtSell.Integration.Tests/KArtSell.Integration.Tests.csproj -c Release --filter FullyQualifiedName~KisTradingHardOffTests --logger "trx;LogFileName=KisTradingHardOffTests_20260809.trx" --results-directory evidence/AEG-X-016`
+- Result: passed `1/1`; artifact: `evidence/AEG-X-016/KisTradingHardOffTests_20260809.trx`; SHA-256: `F41D1DF823F91705D322A629B08ADF0BBA03EA1BEFA01DEF47E00C9DAFF2470D`.
+- The test invokes submit, status, cancel, and settlement on the concrete KIS adapter and asserts that every call throws the hard-off exception before its HTTP handler is called (zero external calls).
+- The submit/poll/settlement handlers guard before any database write or adapter access, and Program removes the historic `trade-status-polling` recurring job.
+- **Not complete:** endpoint-level disabled response and a startup configuration-override audit remain to be executed before the full WBS acceptance is claimed.
diff --git a/docs/CURRENT/CATALOGS/WBS_MASTER.csv b/docs/CURRENT/CATALOGS/WBS_MASTER.csv
index db375e01..7aa2f86a 100644
--- a/docs/CURRENT/CATALOGS/WBS_MASTER.csv
+++ b/docs/CURRENT/CATALOGS/WBS_MASTER.csv
@@ -182,7 +182,7 @@ AEG-VS-24-04,S11,W23-24,VS-24,ReportAndNotify,ReportingNotifications,BE,REQ-RPT-
AEG-VS-24-05,S11,W23-24,VS-24,ReportAndNotify,ReportingNotifications,ASYNC,REQ-RPT-001,RPT-01,MIG-RPT-001,J11R,UI-RPT-01,T-RPT-001,Event/Job/Inbox·재처리 구현,"J11R; integration event, JobRun, watermark, inbox","동일 ScopeKey 재실행 결과가 동일하고 중복 side effect 0, transient/business hold/poison이 분리됨",BE/SRE,Data/Ops,5,AEG-VS-24-04,G4-A,SOURCE+DESIGN_PROPOSAL,P1,PLANNED
AEG-VS-24-06,S11,W23-24,VS-24,ReportAndNotify,ReportingNotifications,FE,REQ-RPT-001,RPT-01,MIG-RPT-001,J11R,UI-RPT-01,T-RPT-001,Vue feature·Zod·Query·컴포넌트 구현,UI-RPT-01; route/api/schema/queries/pages/components,loading/empty/partial/stale/warn/error/401/403/409/expired/readonly와 접근성·권한 경계가 검증됨,FE Lead,UX/QA,6,AEG-VS-24-05,G4-A,SOURCE+DESIGN_PROPOSAL,P1,PLANNED
AEG-VS-24-07,S11,W23-24,VS-24,ReportAndNotify,ReportingNotifications,TESTOPS,REQ-RPT-001,RPT-01,MIG-RPT-001,J11R,UI-RPT-01,T-RPT-001,회귀·관제·Runbook·Rollback 증거,T-RPT-001; xUnit/Vitest/Playwright/Data/Replay; OTel/alert/runbook,Golden·integration·failure·replay·E2E와 metric/alert/Owner/Secondary/rollback rehearsal가 Release Evidence에 연결됨,QA/SRE,Module Owner,7,AEG-VS-24-06,G4-A,SOURCE+DESIGN_PROPOSAL,P0,PLANNED
-AEG-X-016,S12,W25-26,Cross,Cross-cutting,Broker,CAPABILITY,REQ-KIS-OFF,Cross,Cross,Cross,Cross,Cross,KIS 주문 제출 startup/CI/runtime 차단 고도화,KIS 주문 제출 startup/CI/runtime 차단,"외부 주문 호출 0, capability override 감사·kill-switch 통과",Security/Ops,Architect/QA,7,AEG-X-005,ALL,SOURCE+DESIGN_PROPOSAL,P0,PLANNED
+AEG-X-016,S12,W25-26,Cross,Cross-cutting,Broker,CAPABILITY,REQ-KIS-OFF,Cross,Cross,Cross,Cross,Cross,KIS 주문 제출 startup/CI/runtime 차단 고도화,KIS 주문 제출 startup/CI/runtime 차단,"외부 주문 호출 0, capability override 감사·kill-switch 통과",Security/Ops,Architect/QA,7,AEG-X-005,ALL,SOURCE+DESIGN_PROPOSAL,P0,IN_PROGRESS
AEG-VS-23-01,S12,W25-26,VS-23,ReconcilePositionsAndFills,ExecutionReconciliation,GOV,REQ-RECON-001,"EXE-01/02,REC-01/02",MIG-EXE-001/002,J12,UI-RECON-01,T-RECON-001,정책·범위·실패상태 계약 확정,"REQ-RECON-001, VS-23 SLICE_SPEC, ADR/Decision Log",사용자 결과 '브로커·내부 주문·체결·보유·현금 break와 승인 정정'·비목표·권한·예외·Source/Assumption/Unknown이 승인됨,PM/Architect,Compliance/Owner,3,"VS-08,VS-16",Pilot-A,SOURCE+DESIGN_PROPOSAL,P0,PLANNED
AEG-VS-23-02,S12,W25-26,VS-23,ReconcilePositionsAndFills,ExecutionReconciliation,DATA,REQ-RECON-001,"EXE-01/02,REC-01/02",MIG-EXE-001/002,J12,UI-RECON-01,T-RECON-001,데이터 시점·스키마·정합성 계약,"MIG-EXE-001/002, DATA_CONTRACT, DQ/lineage 규칙",published_at/revision/valid-time/hash/단위/격리/재처리와 소유자가 정의되고 overwrite 경로가 없음,Data Architect/DBA,Quant/QA,5,AEG-VS-23-01,Pilot-A,SOURCE+DESIGN_PROPOSAL,P0,PLANNED
AEG-VS-23-03,S12,W25-26,VS-23,ReconcilePositionsAndFills,ExecutionReconciliation,DOMAIN,REQ-RECON-001,"EXE-01/02,REC-01/02",MIG-EXE-001/002,J12,UI-RECON-01,T-RECON-001,도메인 불변조건·상태전이 구현,"src/Modules/ExecutionReconciliation/ReconcilePositionsAndFills/Domain, 정책 결정표",순수 정책 테스트에서 우선순위·경계값·단조성·금지 전이가 통과하고 Infrastructure 의존이 없음,BE/Quant Lead,Architect/QA,6,AEG-VS-23-02,Pilot-A,SOURCE+DESIGN_PROPOSAL,P0,PLANNED
diff --git a/docs/CURRENT/CATALOGS/WBS_PROGRESS_TRACKER.csv b/docs/CURRENT/CATALOGS/WBS_PROGRESS_TRACKER.csv
index 9ebff0ff..91308206 100644
--- a/docs/CURRENT/CATALOGS/WBS_PROGRESS_TRACKER.csv
+++ b/docs/CURRENT/CATALOGS/WBS_PROGRESS_TRACKER.csv
@@ -6,6 +6,7 @@ AEG-X-004,S0,Cross,DbUp 복구 rehearsal 고도화,COMPLETED,2026-08-06,"docs/CU
AEG-X-005,S0,Cross,Security auth 고도화,COMPLETED,2026-08-04,"docs/decisions/ADR-SEC-001.md + tests/KArtSell.Integration.Tests/SecurityAuthenticationTests.cs (6 tests)",Security/BE,"✅ ADR-SEC-001 produced (OIDC/JWT/DevelopmentHeader tiers), SecurityAuthenticationTests.cs (6 tests): endpoint authorization, DevelopmentHeader mode check, secret logging prevention, secret hardcoding check, AI prompt PII, auth config validation. Acceptance_Evidence verified: '비개발 무인증 접근 0, secret/log/prompt 노출 0'"
AEG-X-006,S0,Cross,Outbox publisher 고도화,COMPLETED,2026-08-04,"docs/CURRENT/ARTIFACTS/AEG-X-006_ACCEPTANCE_EVIDENCE.md + src/KArtSell.BuildingBlocks/Reliability/DapperOutboxWriter.cs + OutboxPollerJob.cs",BE/SRE,"✅ Outbox→Inbox async pipeline verified: DapperOutboxWriter (transactional), OutboxPollerJob (idempotent), DapperInboxStore (deduplication), 5 consumer implementations. Acceptance_Evidence: All criteria met. 177/177 tests PASS."
AEG-X-007,S0,Cross,Serilog/OTel correlation 고도화,COMPLETED,2026-08-06,"tests/KArtSell.ArchitectureTests/PiiRedactionTests.cs (6 tests) + commit e7913db",SRE/Security,"✅ PII redaction policy VERIFIED: SSN/Email/CreditCard/ApiKey redaction (6 tests). Commit e7913db adds pattern-based sanitization validation. All tests PASS (249/253)."
+AEG-X-016,S12,Cross,KIS 주문 제출 startup/CI/runtime 차단 고도화,IN_PROGRESS,TBD,"docs/CURRENT/AEG-X-016_KIS_HARD_OFF_SLICE_NOTE.md; src/KArtSell.Modules.ModelOperations/TradeExecution/KisTradeExecutionService.cs; src/KArtSell.Modules.ModelOperations/TradeExecution/TradeHandlers.cs; src/KArtSell.Host/Program.cs; tests/KArtSell.Integration.Tests/TradeExecution/KisTradingHardOffTests.cs; evidence/AEG-X-016/KisTradingHardOffTests_20260809.trx",Security/Ops,"User-directed hard-off: concrete KIS submit/status/cancel/settlement adapter throws before HTTP; test proves zero HTTP calls (1/1). Trade handlers guard before DB writes and Program removes trade-status-polling. Remains IN_PROGRESS until endpoint-level disabled response and startup capability-override/kill-switch evidence are run. No KIS activation path was introduced."
AEG-V15-033,S8,VS-18,Schedule anchor 계산기,COMPLETED,2026-08-09,"docs/CURRENT/AEG-V15-033_SCHEDULE_ANCHOR_SLICE_NOTE.md; src/KArtSell.Modules.ModelOperations/Domain/ScheduleOccurrencePlanner.cs; tests/KArtSell.ModelOperations.UnitTests/ScheduleOccurrencePlannerTests.cs; evidence/AEG-V15-033/ScheduleOccurrencePlannerTests_20260809.trx",BE Lead,"Actual Release run: dotnet test tests/KArtSell.ModelOperations.UnitTests/KArtSell.ModelOperations.UnitTests.csproj -c Release --filter FullyQualifiedName~ScheduleOccurrencePlannerTests; 2/2 passed. Characterizes the approved scheduledFor anchor and missed-occurrence skip behavior. No schedule was enabled; market-calendar/timezone (DEC-079), dispatch enqueue/mark atomicity (DEC-083), leases, database integration, and later scheduler WBS evidence remain out of scope."
AEG-V15-034,S8,VS-18,Catch-up policy 구현,COMPLETED,2026-08-09,"docs/CURRENT/AEG-V15-034_CATCH_UP_POLICY_SLICE_NOTE.md; src/KArtSell.Modules.ModelOperations/Domain/ScheduleOccurrencePlanner.cs; src/KArtSell.Modules.ModelOperations/Scheduling/ModelOperationsDispatcherJob.cs; src/KArtSell.Modules.ModelOperations/Infrastructure/DapperModelScheduleRepository.cs; tests/KArtSell.ModelOperations.UnitTests/ScheduleOccurrencePlannerTests.cs; evidence/AEG-V15-034/ScheduleOccurrencePlannerTests_20260809.trx",BE Lead,"Actual Release run: targeted ScheduleOccurrencePlannerTests 4/4 passed. LATEST_ONLY dispatches only the latest anchored occurrence; SKIP_MISSED advances the lease-held schedule without enqueueing stale work; ALL_WITH_LIMIT dispatches only the configured most-recent occurrences, each with an occurrence-specific UTC idempotency key. Schedules remain disabled. Database integration, lease CAS, and DEC-083 enqueue/mark atomicity are not claimed and remain owned by later WBS items."
AEG-V15-035,S8,VS-18,Due operation 계약 확장,COMPLETED,2026-08-09,"docs/CURRENT/AEG-V15-035_DUE_OPERATION_CONTRACT_SLICE_NOTE.md; src/KArtSell.Modules.ModelOperations/Application/ModelOperationsContracts.cs; src/KArtSell.Modules.ModelOperations/Application/ModelOperationRequestService.cs; src/KArtSell.Modules.ModelOperations/Scheduling/ScheduledModelOperationJob.cs; src/KArtSell.Modules.ModelOperations/Infrastructure/DapperModelOperationRequestRepository.cs; tests/KArtSell.ModelOperations.UnitTests/ModelOperationRequestServiceTests.cs; evidence/AEG-V15-035/DueModelOperationContractTests_20260809.trx",BE Lead,"Actual Release run: 5/5 targeted unit tests passed. The scheduler occurrence, catch-up policy, and max catch-up flow from due schedule through the serialized job and validated application request; scheduled_for is inserted in the normalized request model and all three values are retained in the transactional outbox payload. Schedules remain disabled. No new migration or PostgreSQL integration evidence is claimed: MIG-0020 already provides scheduled_for; policy and limit provenance is immutable in the event payload, while schedule configuration remains the normalized source referenced by schedule_id/version."
diff --git a/evidence/AEG-X-016/KisTradingHardOffTests_20260809.trx b/evidence/AEG-X-016/KisTradingHardOffTests_20260809.trx
new file mode 100644
index 00000000..764731ca
--- /dev/null
+++ b/evidence/AEG-X-016/KisTradingHardOffTests_20260809.trx
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/KArtSell.Host/Program.cs b/src/KArtSell.Host/Program.cs
index 843fd3fe..9320d57b 100644
--- a/src/KArtSell.Host/Program.cs
+++ b/src/KArtSell.Host/Program.cs
@@ -398,8 +398,8 @@ try { RecurringJob.AddOrUpdate("opendart-daily-batch", jo
try { RecurringJob.AddOrUpdate("daily-recommendation", job => job.ExecuteAsync(CancellationToken.None), "0 9 * * *", new RecurringJobOptions { TimeZone = kstTimeZone }); logger.LogInformation("✅ daily-recommendation registered"); } catch (Exception ex) { logger.LogWarning(ex, "⚠️ daily-recommendation error"); }
try { RecurringJob.AddOrUpdate("weekly-recommendation", job => job.ExecuteAsync(CancellationToken.None), "0 9 * * 6", new RecurringJobOptions { TimeZone = kstTimeZone }); logger.LogInformation("✅ weekly-recommendation registered"); } catch (Exception ex) { logger.LogWarning(ex, "⚠️ weekly-recommendation error"); }
try { RecurringJob.AddOrUpdate("monthly-recommendation", job => job.ExecuteAsync(CancellationToken.None), "0 9 1 * *", new RecurringJobOptions { TimeZone = kstTimeZone }); logger.LogInformation("✅ monthly-recommendation registered"); } catch (Exception ex) { logger.LogWarning(ex, "⚠️ monthly-recommendation error"); }
-// DEBT-027: without this, PollTradeStatusHandler/ConfirmSettlementHandler were reachable via DI but never invoked by anything.
-try { RecurringJob.AddOrUpdate("trade-status-polling", job => job.ExecuteAsync(CancellationToken.None), "*/2 * * * *", new RecurringJobOptions { TimeZone = TimeZoneInfo.Utc }); logger.LogInformation("✅ trade-status-polling registered"); } catch (Exception ex) { logger.LogWarning(ex, "⚠️ trade-status-polling error"); }
+RecurringJob.RemoveIfExists("trade-status-polling");
+logger.LogWarning("KIS trade-status-polling removed: KIS trading is hard-disabled.");
logger.LogInformation("🎯 Hangfire background jobs initialization complete");
diff --git a/src/KArtSell.Modules.ModelOperations/TradeExecution/KisTradeExecutionService.cs b/src/KArtSell.Modules.ModelOperations/TradeExecution/KisTradeExecutionService.cs
index 86437642..be72813a 100644
--- a/src/KArtSell.Modules.ModelOperations/TradeExecution/KisTradeExecutionService.cs
+++ b/src/KArtSell.Modules.ModelOperations/TradeExecution/KisTradeExecutionService.cs
@@ -25,6 +25,17 @@ public class KisTradeExecutionException : Exception
}
}
+public sealed class KisTradingDisabledException : InvalidOperationException
+{
+ public KisTradingDisabledException()
+ : base("KIS trading is hard-disabled. No buy, sell, cancel, status, or settlement call is permitted.") { }
+}
+
+public static class KisTradingHardOff
+{
+ public static void EnsureDisabled() => throw new KisTradingDisabledException();
+}
+
public interface IKisTradeExecutionService
{
Task<(string OrderId, JsonElement Response)> ExecuteTradeAsync(Guid tradeId, int quantity, decimal limitPrice, Guid correlationId, CancellationToken ct = default);
@@ -56,6 +67,7 @@ public class KisTradeExecutionService : IKisTradeExecutionService
Guid correlationId,
CancellationToken ct = default)
{
+ KisTradingHardOff.EnsureDisabled();
var requestBody = new
{
symbol = "US0100",
@@ -120,6 +132,7 @@ public class KisTradeExecutionService : IKisTradeExecutionService
Guid correlationId,
CancellationToken ct = default)
{
+ KisTradingHardOff.EnsureDisabled();
var request = new HttpRequestMessage(HttpMethod.Get, $"{KisApiBase}/orders/{kisOrderId}");
request.Headers.Add("X-Correlation-ID", correlationId.ToString());
@@ -159,6 +172,7 @@ public class KisTradeExecutionService : IKisTradeExecutionService
Guid correlationId,
CancellationToken ct = default)
{
+ KisTradingHardOff.EnsureDisabled();
var requestBody = new { reason = reason };
var content = new StringContent(
JsonSerializer.Serialize(requestBody),
@@ -195,6 +209,7 @@ public class KisTradeExecutionService : IKisTradeExecutionService
Guid correlationId,
CancellationToken ct = default)
{
+ KisTradingHardOff.EnsureDisabled();
var requestBody = new { confirm = true };
var content = new StringContent(
JsonSerializer.Serialize(requestBody),
diff --git a/src/KArtSell.Modules.ModelOperations/TradeExecution/TradeHandlers.cs b/src/KArtSell.Modules.ModelOperations/TradeExecution/TradeHandlers.cs
index 9fef5f45..4bab809e 100644
--- a/src/KArtSell.Modules.ModelOperations/TradeExecution/TradeHandlers.cs
+++ b/src/KArtSell.Modules.ModelOperations/TradeExecution/TradeHandlers.cs
@@ -42,6 +42,7 @@ public class SubmitTradeHandler
public async Task HandleAsync(SubmitTradeCommand command, CancellationToken ct = default)
{
+ KisTradingHardOff.EnsureDisabled();
var trade = Trade.Create(command.SellDecisionId, command.Quantity, command.CorrelationId, _clock.UtcNow.UtcDateTime);
await _sql.InsertTradeAsync(trade, ct);
_logger.LogInformation("Created trade: {TradeId}", trade.Id);
@@ -127,6 +128,7 @@ public class PollTradeStatusHandler
public async Task HandleAsync(PollTradeStatusCommand command, CancellationToken ct = default)
{
+ KisTradingHardOff.EnsureDisabled();
var trade = await _sql.GetTradeByIdAsync(command.TradeId, command.CorrelationId, ct);
if (trade == null)
{
@@ -222,6 +224,7 @@ public class ConfirmSettlementHandler
public async Task HandleAsync(ConfirmSettlementCommand command, CancellationToken ct = default)
{
+ KisTradingHardOff.EnsureDisabled();
var trade = await _sql.GetTradeByIdAsync(command.TradeId, command.CorrelationId, ct);
if (trade == null)
{
diff --git a/tests/KArtSell.Integration.Tests/TradeExecution/KisTradingHardOffTests.cs b/tests/KArtSell.Integration.Tests/TradeExecution/KisTradingHardOffTests.cs
new file mode 100644
index 00000000..5271d1f8
--- /dev/null
+++ b/tests/KArtSell.Integration.Tests/TradeExecution/KisTradingHardOffTests.cs
@@ -0,0 +1,35 @@
+using System.Net;
+using KArtSell.Modules.ModelOperations.TradeExecution;
+using Microsoft.Extensions.Logging.Abstractions;
+using Xunit;
+
+namespace KArtSell.Integration.Tests.TradeExecution;
+
+public sealed class KisTradingHardOffTests
+{
+ [Fact]
+ public async Task Every_kis_trade_operation_fails_before_any_http_call()
+ {
+ var handler = new CountingHandler();
+ var service = new KisTradeExecutionService(new HttpClient(handler), NullLogger.Instance);
+ var correlationId = Guid.NewGuid();
+
+ await Assert.ThrowsAsync(() => service.ExecuteTradeAsync(Guid.NewGuid(), 1, 1m, correlationId));
+ await Assert.ThrowsAsync(() => service.GetOrderStatusAsync("order", correlationId));
+ await Assert.ThrowsAsync(() => service.CancelOrderAsync("order", "test", correlationId));
+ await Assert.ThrowsAsync(() => service.ConfirmSettlementAsync("order", correlationId));
+
+ Assert.Equal(0, handler.CallCount);
+ }
+
+ private sealed class CountingHandler : HttpMessageHandler
+ {
+ public int CallCount { get; private set; }
+
+ protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ CallCount++;
+ return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK));
+ }
+ }
+}