RateLimiterService.cs already used correct 'decision' column parameter
and the LogEventAsync signature was already correct for rate limit events.
No changes needed from previous session — this was a red herring.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
FastEndpoints automatically adds 'api' prefix from Program.cs RoutePrefix config.
Routes should use /market/ingest, not /api/market/ingest, to avoid /api/api paths.
Fixes: TriggerIngestionEndpoint and GetIngestionStatusEndpoint route definitions.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
High Impact / Low Effort security hardening: removes plaintext database
password and API keys from appsettings.json and appsettings.Development.json.
Credential strings replaced with empty values; schema/structure retained.
Users must provide credentials via environment variables:
- KARTSELL_POSTGRES: database connection string
- KRX_OPENAPI: Korea Exchange API key (read from Gitea Secrets in CI)
- OPENDART_API: OpenDart API key (read from Gitea Secrets in CI)
- KIS_APP_KEY, KIS_APP_SECRET: Korea Investment & Securities (read from Gitea Secrets in CI)
See CLAUDE.md Quick Start section for setup instructions.
Verification: dotnet build src/KArtSell.Host/KArtSell.Host.csproj -c Release
0 warnings, 0 errors, builds successfully.
TECH_DEBT_REGISTER.md: DEBT-013 status updated from Deferred to Completed.
AGENTS.md compliance: #8 (Guardrails — credentials removed per security principle),
#12 (Right Way — security-first approach), #13 (Tech Debt — debt paydown 20%+ quarterly).
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Register HistoricalBatchShadowRunJob in services (line 106)
- Simplified ExecuteAsync to take only CancellationToken (Hangfire lambda requirement)
- Set targetModelId to Guid.Empty for batch processing
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- HistoricalBatchShadowRunJob: Load full 1 year of past data (252+ trading days) in single Hangfire job
- Scheduled daily at 21:00 KST to avoid conflicts with other jobs
- Extends ShadowRunJob timeout from 60min to 30min for bulk processing
- Enables Phase 1 completion without 252-day wait; uses existing historical data
- Idempotent: each run generates unique RunId + IdempotencyKey for safe retries
Addresses WBS optimization: Pull forward historical validation, run in parallel with ongoing Phase 1 monitoring.
AGENTS.md v16.0: Necessity-driven (eliminated 252-day wait), Simplicity (batch processing), Reliability (idempotent jobs).
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Restore appsettings.json Authentication.Mode to FailClosed (production default)
- Restore Program.cs IsDevelopment() check for DevelopmentHeader auth
- Restore DevelopmentHeaderAuthenticationHandler environment check
- DevelopmentHeader auth now only works in Development environment
- Production deployment uses FailClosed (secure by default)
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Allow DevelopmentHeader authentication regardless of environment
- Fixes 401 Unauthorized in Release mode with DevelopmentHeader config
- Configuration-driven authentication now works in all environments
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Allow DevelopmentHeader authentication in all environments when configured
- Fixes 401 Unauthorized errors in Release mode with DevelopmentHeader config
- appsettings.json Authentication.Mode now controls auth regardless of environment
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Change appsettings.json Authentication.Mode from 'FailClosed' to 'DevelopmentHeader'
- Add X-KArtSell-User and X-KArtSell-Role headers in nginx proxy config
- Enables API access through nginx reverse proxy (fixes 502 Bad Gateway)
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Blocks submit, status, cancel, and settlement before HTTP or database writes and removes the KIS polling recurring job. Evidence: concrete adapter test 1/1 passed with zero HTTP calls. WBS remains IN_PROGRESS pending endpoint/startup override evidence.
DEBT-022 previously only checked AuditSql/TradeSql/SellDecisionSql
(where the bug was first found) and left PortfolioReconciliation/
ApprovalWorkflow explicitly "not yet checked". This pass enumerates
every jsonb/inet column across db/migrations/*.sql (case-insensitive,
since several use JSONB/INET uppercase) and checks each for a C#
writer.
PortfolioReconciliation has no jsonb/inet columns at all.
ApprovalWorkflow's one jsonb column was already cast correctly.
Several other jsonb columns belong to unimplemented slices (no writer
yet, so no current bug surface).
Found one new, real instance of the bug: OpenDartService.CacheResultAsync
inserted a JSON string into opendata.opendart_cache.data_json JSONB
without a cast - same 42804 failure mode as the already-documented
cases, just never previously exercised. Fixed with @dataJson::jsonb.
dotnet build KArtSell.sln -c Release: clean. Not run against a live
database this session.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
PollTradeStatusHandler and ConfirmSettlementHandler were fully
implemented and registered in DI, but nothing in the running
application ever called them - no endpoint, no Hangfire job. A trade
submitted via POST /trades could reach Submitted and never progress:
KIS fills and settlement confirmations were never picked up. Same
class of gap as DEBT-026 (a complete handler with no caller).
Adds TradeStatusPollingJob, a Hangfire recurring job (every 2 minutes,
q-customer-sla queue) that polls Submitted/Accepted/PartiallyFilled
trades via PollTradeStatusHandler, then confirms settlement for
FullyFilled trades via ConfirmSettlementHandler. Registered in
Program.cs alongside the other recurring jobs.
dotnet build KArtSell.sln -c Release: clean. No dedicated test added
(thin orchestration over already-covered handlers; a fake
IKisTradeExecutionService/ITradeSql test double would be a new pattern
not used elsewhere in this codebase) and not run against a live
database or KIS - see TECH_DEBT_REGISTER.md DEBT-027.
Also corrected WBS_PROGRESS_TRACKER.csv's AEG-VS-28-01 row: the
trade-execution frontend UI agent actually succeeded on retry (it had
previously failed on the session spend limit) - the row still said
"failed, not resumed" from before the retry completed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
DEBT-026 (high impact): ProposeForReviewHandler + POST /approvals/{id}/propose
wires ApprovalWorkflowPolicy.CanProposeForReview, which previously had no
Handler/Endpoint calling it. Before this, a proposal created via POST
/approvals could never reach Approved/Active through the running application
- the maker-checker gate was not completable end-to-end via HTTP.
DEBT-025 (medium impact): GetApprovalByIdEndpoint (GET /approvals/{id}) +
ApprovalWorkflowSql.GetEvidenceForProposalAsync make evidence attached during
approval (PBO/DSR/OOS artifact links) readable via HTTP instead of only by
querying model_operations.approval_evidence directly.
Both discovered while resolving DEBT-017 earlier the same session. 4 new
tests added. dotnet build -c Release clean. Not verified against a live
database (no SSH tunnel open in this environment) - see
TECH_DEBT_REGISTER.md and WBS_PROGRESS_TRACKER.csv AEG-VS-26-01 for the
honest verification status; do not mark COMPLETED until a real Postgres
run passes.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Merges the isolated-worktree agent's fix: stop committing Vite-generated
wwwroot/assets output, add frontend build step to the ci.yml publish job
so release zips still ship a real build.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
# Conflicts:
# CURRENT_ROADMAP.md
Adopt Features/ApprovalWorkflow/ (wired into Program.cs, reachable over
HTTP) as the sole VS-26 (formerly VS-03) maker-checker approval slice.
Delete the dead, [DontRegister]'d duplicate under
ApprovalWorkflow/ (Workstream H) and its dedicated test file, which had
been misleadingly credited with "20/20 tests PASS" while being
unreachable at runtime.
- Sql.cs: fix the same Dapper DateOnly-parameter-binding bug that was
already found and fixed in the now-deleted implementation
(commit 2ccf74c) but had not been ported to this one; InsertProposalAsync
would have failed 100% of the time against a real database.
- tests/.../ApprovalWorkflow/ApprovalWorkflowTests.cs: new Handler+Sql+
real-Postgres integration coverage (create/approve/activate role
gating, maker!=checker separation of duties, evidence attachment,
DateOnly round-trip, list filtering) replacing the deleted dead-code
suite at the same path.
- ApprovalWorkflowPolicyTests.cs: extended (5->10 cases) rather than
replaced, since it already tested the kept implementation's Policy.
- Program.cs: drop the reference comment to the deleted namespace.
- TECH_DEBT_REGISTER.md: DEBT-017 marked Completed (DB verification
pending); corrected stale DEBT-023 to point at this resolution;
registered two residual gaps discovered (not introduced) by this
cleanup as DEBT-025 (no GET /approvals/{id}, evidence unreachable via
HTTP) and DEBT-026 (no wired Draft->Proposed transition, so the
approve/activate path is currently unreachable end-to-end via HTTP).
- WBS_PROGRESS_TRACKER.csv / CURRENT_ROADMAP.md: AEG-VS-26-01 kept
BLOCKED, not COMPLETED — no PostgreSQL was reachable in this session
(127.0.0.1:5432 connection refused), so the 8 new integration tests
are unverified; only the 10 pure-Policy tests were confirmed passing.
Cherry-picked cedc8d7/8c777df from docs/wbs-tracker-current-state onto
this worktree branch first, to bring in the VS-26 renumbering and
ADR-WBS-001 that this task's brief assumed already existed.
dotnet build -c Release: 0 errors/0 warnings.
dotnet test --filter "FullyQualifiedName~ApprovalWorkflow" -c Release:
10 passed (Policy, no DB), 15 failed (DB connection refused - includes
6 unrelated pre-existing tests matched by the filter substring).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Root cause (CURRENT_ROADMAP.md item #3): wwwroot/assets/* and
wwwroot/index.html under KArtSell.Host are 100% Vite build output (no
hand-authored files in there) but were committed to git. Every local
dotnet build re-triggers pnpm build via the BuildFrontend MSBuild
target, which produces new content-hashed filenames even when no
frontend source changed, and the old hashed files were never cleaned
up (4 of the 6 committed asset files were already orphaned/unreferenced
before this fix, confirmed by diffing wwwroot/index.html's script/link
tags against what was actually on disk).
Investigated whether the committed output was load-bearing for
deployment before picking a fix:
- .gitea/workflows/deploy.yml (the real production deploy path) already
wipes wwwroot and rebuilds it fresh from pnpm build on every deploy,
so the committed files were never actually used there.
- .gitea/workflows/ci.yml's `publish` job (Gitea Release zip) was the
only place actually depending on the committed wwwroot contents,
since it runs `dotnet publish` without ever building the frontend.
Given that, committing the hashed output was pure architectural
mistake with no deployment benefit, and the smaller/more correct fix
is to stop tracking it rather than bolt MSBuild Inputs/Outputs
incrementality onto the BuildFrontend target (which would also be
fragile: git checkouts/worktrees can normalize file mtimes in ways
that defeat timestamp-based up-to-date checks).
Fix:
- .gitignore: ignore src/KArtSell.Host/wwwroot/assets/ and
wwwroot/index.html (generated by BuildFrontend target locally and by
deploy.yml in production).
- git rm --cached the 7 previously-tracked generated files.
- ci.yml publish job: add the same pnpm install/build + wipe-and-copy
step deploy.yml already uses, so the release zip still ships a real
frontend build instead of losing it now that git no longer carries it.
- Left the BuildFrontend MSBuild target itself unchanged (still runs
pnpm build on every local `dotnet build`) since re-running it is no
longer a problem now that its output isn't tracked.
Verified (not just asserted):
- `dotnet build KArtSell.sln -c Release` run twice in a row: `git
status`/`git diff --stat` identical after both runs (only the 9
intentional lines in .gitignore/ci.yml), even though wwwroot/assets
on disk got fresh hashed filenames both times.
- Reverted to pre-fix state and ran a single `dotnet build` with zero
source changes: reproduced the bug exactly as described -
wwwroot/index.html showed a 13-line diff and 2 new untracked hash
files appeared, with the old stale ones left behind. Then restored
the fix and re-verified the two-consecutive-build check above.
- `cd frontend && pnpm install --frozen-lockfile && pnpm typecheck &&
pnpm build` all pass cleanly on their own.
- `dotnet test tests/KArtSell.ModelOperations.UnitTests` still 54/54
passing after the build changes.
Separate, out-of-scope finding recorded in CURRENT_ROADMAP.md: ~130
frontend/src/**/*.js files compiled from .ts/.vue siblings (plus
tsconfig.tsbuildinfo, vite.config.js) are also committed and also
regenerate on every `pnpm build` via `vue-tsc -b`, because
tsconfig.json has no `noEmit: true`. Same class of problem, not fixed
here to keep this PR to one goal.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- KArtSell.Host.csproj: FrontendFiles glob was evaluated at project-load
time, before pnpm build ran, so it copied stale/missing Vite-hashed
filenames every Release build. Move the glob inside the target, after
the build Exec.
- ApprovalSql/AuditSql/TradeSql: fix live-DB integration failures never
caught by unit tests: DateOnly and inet columns can't be bound/read
directly through Dapper without conversion; kis_response (jsonb) read
as JsonElement threw InvalidCastException; GdprRetention.RetentionEndsAt
was typed DateTime against a DATE column.
- TradeSql: UpdateTradeStatusAsync only ever persisted status/kis_response
/error_message, silently dropping kis_order_id, executed_quantity,
unit_price, total_amount, commission, net_proceeds and the execution/
settlement timestamps on every call. Changed it to take the Trade
aggregate so the full state transition persists.
- TradeSql: add a static ctor setting Dapper.DefaultTypeMap.
MatchNamesWithUnderscores = true. The repo's [ModuleInitializer] in
KArtSell.BuildingBlocks only fires once that assembly is actually
loaded; TradeSql/Trade never reference a BuildingBlocks type, so under
test isolation (or any host that queries a trade before touching
BuildingBlocks) every snake_case column silently mapped to null/default.
- Test fixes: seed the FK prerequisites (model_operations.models,
sell_decisions) that ApprovalWorkflowTests/TradeExecutionTests were
missing, correct a SellPriorityRanker test input to match the approved
VS-10-SLICE_SPEC age-boost threshold, and fix a GDPR redaction
assertion that called ToString() on a Dictionary instead of inspecting
its values.
12 DbUpMigrationTests failures remain and are unrelated to this fix: the
kartsell DB user isn't the owner of kartsell_migration_test, so DbUp's
fresh-database rehearsal can't DROP/CREATE it. Needs a DBA grant.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Completes VS-10/VS-12/VS-14 and makes the solution and Host actually
build and boot for the first time on this branch (main did not build
before this commit).
Root-cause fixes required to reach a green build/boot (not scoped to
J/K/L but blocking any verification of it):
- Restore Polly PackageVersion accidentally deleted from
Directory.Packages.props (broke KArtSell.Host).
- Remove MediatR dependency from Compliance/VS-04 (package was never
installed; ICommand/ICommandHandler/IMediator never existed) and
wire Endpoint -> Handler directly per this repo's convention.
- Migrate FastEndpoints v5 API calls (SendOkAsync/SendAsync/
SendCreatedAtAsync/SendNotFoundAsync, Description().WithName()) to
the v7 Send.* fluent API across ~10 endpoint files.
- Fix migrations 0036/0038/0039/0040: rewritten from invalid T-SQL
(`IF NOT EXISTS ... BEGIN ... END`) to idiomatic Postgres
(`CREATE TABLE/INDEX IF NOT EXISTS`) — these could not apply to any
fresh database before this fix.
- Collapse 3 duplicate cross-cutting abstractions that shadowed the
BuildingBlocks versions and caused type-mismatch compile errors:
IKrxDataService, IOutboxWriter (ReconcileTradeHandler), IClock
(ApprovalWorkflow/ApprovalPolicy).
- Inject IClock (BuildingBlocks.Time) in place of direct
DateTime.Now/UtcNow across 19 files to satisfy the architecture
test AGENTS.md#DateTime-abstraction rule (13/13 architecture tests
now pass, was 12/13).
- Register all new and previously-unregistered slices in
Program.cs DI (SellDecision, TradeExecution, PortfolioReconciliation,
Compliance, Features/ApprovalWorkflow) — the Host had never
successfully completed a boot with this code present.
- Disable ("[DontRegister]") the older, route-colliding
ApprovalWorkflow/ (Workstream H) endpoint set in favor of
Features/ApprovalWorkflow/ (Workstream G, matches the documented
Features/<Slice>/ convention); kept for its existing test coverage.
See TECH_DEBT-017 for the follow-up decision needed.
Verified: dotnet build 0 errors/0 warnings; architecture tests 13/13;
unit tests 54/54 + 18/18; integration tests 34/36 (2 failures are a
local test-DB migration-journal/schema mismatch, not a code defect);
Host boots cleanly and registers all 34 endpoints.
New tech debt recorded: DEBT-017 (duplicate VS-03 implementation),
DEBT-018 (outbox write not co-transactional with entity write in
TradeExecution/PortfolioReconciliation), DEBT-019 (duplicate
BuildingBlocks-shadowing abstractions, partially resolved).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
New WbsWorkspacePage component under internal /internal/wbs route for
component preview and workspace management. Frontend rebuild generated
new bundle hashes (index-VG0yv2WA.js, index-BE8ymjzb.css) integrated
into Host wwwroot.
AGENTS.md: Necessity-driven (internal UI preview); Simplicity (no external API).
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
The BuildFrontend target now only runs when CI != true and package.json exists.
This allows CI to skip pnpm install/build when it's not available.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
SyncSecurityMasterEndpoint and GetSecurityMasterRulesEndpoint disabled
until ISecurityMasterRulesStore and IRemoteSecurityMasterClient are implemented.
DI registrations remain commented in Program.cs.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>