29 KiB
Runtime Flow — Dashboard 관리자 인증
목적과 범위
ASGI startup부터 login, token restoration, invalid-cookie cleanup, explicit logout까지의 실행 순서와 상태 계약을 정의한다.
- 포함 범위: config, HTTPS routes, cookie verification, Streamlit gate, cleanup/deletion
- 제외 범위: MCP request authentication, DB session/token registry, per-token revocation
- 시작 조건: trusted ASGI process receives request
- 성공 종료 상태: authenticated Dashboard page executes
- 실패 종료 상태: matching cookie deletion and unauthenticated login page
흐름
startup — ASGI startup
- 시작 이벤트: process startup
- 선행조건: deployment injects configuration without recording values
- 실행 방식: synchronous startup/request handling
- 트랜잭션·영속성 경계: no auth persistence; existing financial SQLite is untouched
| 단계 ID | 실행 주체 | 시작 조건·입력 | 처리 | 읽기·쓰기 상태 | 외부 호출 | 성공 시 다음 단계 | 실패 시 전이 |
|---|---|---|---|---|---|---|---|
startup-01 |
Uvicorn | process command | load dashboard.server:app |
process memory only | none | startup-02 |
startup-fail |
startup-02 |
ASGI app | app loaded | mount auth routes and Streamlit runtime | route table only | none | gate-01 |
startup-fail |
login — Credential login
- 시작 이벤트: same-origin HTTPS form POST
- 선행조건: valid trusted scheme, origin and bounded body
- 실행 방식: synchronous request
- 트랜잭션·영속성 경계: no DB transaction; cookie is browser state
| 단계 ID | 실행 주체 | 시작 조건·입력 | 처리 | 읽기·쓰기 상태 | 외부 호출 | 성공 시 다음 단계 | 실패 시 전이 |
|---|---|---|---|---|---|---|---|
login-01 |
auth server | POST /api/auth/login |
validate scheme/origin/body | request only | none | login-02 |
login-reject |
login-02 |
authentication | validated form | load three settings, fail closed | server memory | secret provider/environment | login-03 |
config-fail |
login-03 |
authentication | configured settings | constant-time compare ID/password | no persistent write | none | login-04 |
login-fail |
login-04 |
authentication | valid credential | issue exactly v, sub=dashboard-admin, iat, exp, jti; fix iat/exp at login success for default 24 hours or remember_me=on 30 days |
server memory | none | login-05 |
login-fail |
login-05 |
auth server | signed token | set hardened cookie with selected absolute expiry and redirect; later activity never reissues or extends it | browser cookie write | response | gate-01 |
login-fail |
restore — Existing cookie restoration
- 시작 이벤트: new request/session with browser cookie
- 선행조건: cookie present or absent; current configuration available
- 실행 방식: synchronous gate
- 트랜잭션·영속성 경계: no server-side session; financial DB begins only after gate success
| 단계 ID | 실행 주체 | 시작 조건·입력 | 처리 | 읽기·쓰기 상태 | 외부 호출 | 성공 시 다음 단계 | 실패 시 전이 |
|---|---|---|---|---|---|---|---|
gate-01 |
dashboard app | page startup | call authentication before DB path/page dispatch | no DB access | none | gate-02 |
unauthenticated |
gate-02 |
authentication | current config and cookie | verify signature, exact v/sub=dashboard-admin/iat/exp/jti claims, current admin-ID/password/signing-secret binding and absolute exp; never refresh or extend on restoration, rerun or activity |
no session or token write | none | dashboard-01 |
cleanup-01 |
dashboard-01 |
dashboard app | accepted token | resolve DB path and dispatch requested page | existing SQLite reads/writes | gateway/page code | authenticated |
dashboard-fail |
cleanup — Invalid cookie cleanup
- 시작 이벤트: gate rejects malformed, expired, tampered, claim-invalid, or config-failure cookie
- 선행조건: same-origin HTTPS cleanup request
- 실행 방식: synchronous request
- 트랜잭션·영속성 경계: no DB or token registry write
| 단계 ID | 실행 주체 | 시작 조건·입력 | 처리 | 읽기·쓰기 상태 | 외부 호출 | 성공 시 다음 단계 | 실패 시 전이 |
|---|---|---|---|---|---|---|---|
cleanup-01 |
browser/auth gate | invalid cookie | navigate to GET /api/auth/cleanup |
request only | auth route | cleanup-02 |
cleanup-reject |
cleanup-02 |
auth server | same-origin HTTPS Referer |
emit matching deletion (Max-Age=0) and root redirect |
browser cookie deletion | response | unauthenticated |
cleanup-reject |
logout — Explicit logout
- 시작 이벤트: user submits logout form
- 선행조건: same-origin HTTPS POST
- 실행 방식: synchronous request
- 트랜잭션·영속성 경계: no financial DB change and no per-token revocation
| 단계 ID | 실행 주체 | 시작 조건·입력 | 처리 | 읽기·쓰기 상태 | 외부 호출 | 성공 시 다음 단계 | 실패 시 전이 |
|---|---|---|---|---|---|---|---|
logout-01 |
browser | POST /api/auth/logout |
validate same-origin HTTPS | request only | none | logout-02 |
logout-reject |
logout-02 |
auth server | validated request | matching deletion and root redirect | browser cookie deletion | response | unauthenticated |
logout-reject |
실패와 복구
| 대상 단계 | 실패 조건 | 추가 쓰기 중단 | 재시도 계약 | 중복 방지 | 보상·수동 복구 | 최종 상태 |
|---|---|---|---|---|---|---|
login-02 |
missing/empty/read-error/short secret | yes | fix deployment config | no cookie | operator injects secret | config-fail |
login-03 |
wrong ID/password | yes | retry after generic error and 1s delay | no lock | external rate limit | login-fail |
gate-02 |
expired, malformed/tampered claims, or admin-ID/password/signing-secret rotation failure | yes | cleanup then login | matching deletion | user logs in again | unauthenticated |
cleanup-02 |
non-HTTPS/cross-origin | yes | retry same origin | deletion is idempotent | browser clears manually if needed | cleanup-reject |
logout-02 |
non-HTTPS/cross-origin | yes | retry same origin | deletion is idempotent | browser clears manually if needed | logout-reject |
동시성과 순서
- 동시 실행 허용 여부: independent requests allowed; no application-wide auth lock
- 잠금·직렬화 단위: none for authentication; existing SQLite rules remain for financial writes
- 순서 보장: config/cookie gate precedes DB path, gateway and page dispatch
- 충돌 검출·처리: token is stateless; password/signing-secret/admin-ID rotation invalidates old claims
- 만료 불변조건: login 성공 시 고정한
iat와exp는 복원·rerun·활동 동안 바뀌지 않으며 token을 재발급하거나 만료를 연장하지 않음 - rotation 불변조건: admin ID, password 또는 signing secret 변경은 이전 설정으로 발급한 모든 token을 무효화하고
cleanup-01로 전이
관찰 가능성
| 신호 종류 | 이름 | 발생 단계·조건 | 상관 식별자 | 진단 속성 | 민감정보 처리 |
|---|---|---|---|---|---|
| event log | authentication_required |
gate-01 rejects |
non-sensitive correlation ID | event class only | no cookie/token/reason |
| event log | login_succeeded |
login-05 |
non-sensitive correlation ID | TTL class only | no credential/token |
| event log | login_failed |
login-03 |
non-sensitive correlation ID | generic failure class | no ID/password |
| event log | cleanup |
cleanup-02 |
non-sensitive correlation ID | generic cleanup class | no cookie header |
| event log | logout |
logout-02 |
non-sensitive correlation ID | operation result | no token/cookie value |
검증
- 정상 흐름: auth server tests plus loopback HTTPS login and fresh session restoration
- 실패 흐름: configuration, wrong credential, malformed/tampered/expired cookie and origin rejection tests
- 경계 흐름: TTL boundary, rotation, oversized token, same-host different-port origin
- 동시성 흐름: independent failed submissions and no application lock; ingress rate-limit configuration review
- 운영 진입점과 기대 결과:
uvicorn dashboard.server:app --host 0.0.0.0 --port 8501; valid HTTPS reaches login or authenticated page, invalid cookie receives matching deletion before unauthenticated page
메뉴 전환 — REQ-002
인증 gate와 기존 dashboard-01 page dispatch의 선행 순서는 변하지 않는다. valid cookie를 통과한 뒤에만 menu transition flow가 실행되며, auth 실패는 기존 cleanup/unauthenticated 흐름으로 종료된다.
transition — authenticated body replacement
- 시작 이벤트: authenticated browser selects a value in
st.radio(key="dashboard_page") - 선행조건:
gate-02success; current page/query resolved - 실행 방식: Streamlit callback and rerun, no document/path navigation
- 트랜잭션·영속성 경계: no new persistence; existing financial SQLite rules unchanged
| 단계 ID | 실행 주체 | 시작 조건·입력 | 처리 | 읽기·쓰기 상태 | 외부 호출 | 성공 시 다음 단계 | 실패 시 전이 |
|---|---|---|---|---|---|---|---|
transition-01 |
dashboard app/common | authenticated selected menu | render_sidebar_nav reads exact 10 options and selected state |
session state dashboard_page |
Streamlit radio callback | transition-02 |
dashboard-fail |
transition-02 |
common callback | selected dashboard_page |
write query page only; preserve sibling query keys |
browser query state | Streamlit query params | transition-03 |
transition-03 |
transition-03 |
dashboard app | validated page | open stable st.empty body, show target loading, invoke target renderer |
body placeholder only; existing DB reads/writes | page/gateway code | transition-04 |
transition-error |
transition-04 |
dashboard app | renderer completed | remove target loading and leave target body; shell/background remain | body replacement only | none | authenticated |
dashboard-fail |
transition-error |
dashboard app | renderer exception | clear partial body and show generic error/retry locally | body error state only | none | retry reruns target | authenticated with shell intact |
transition invariants
- known direct
?page=selects that page; missing/unknown selects요약. - serialized rapid selections commit the latest selection only.
- menu transition never runs before auth and never changes cookie/logout/DB schema/MCP semantics.
- menu/query reruns and rendering do not initiate or replay financial writes or external calls; those remain behind explicit page user actions, with no new idempotency contract.
- history/back-forward and path URL behavior are explicitly outside this flow.
transition verification boundary
tests/test_dashboard_menu_transition.py provides exact AppTest/unit evidence for labels, query preservation, fallback/direct access, loading, local error, latest selection, and cache-clear. tests/test_dashboard_app.py::test_streamlit_apptest_core_pages_use_synthetic_fixture_db and tests/test_dashboard_authentication_app.py::test_valid_cookie_allows_dashboard_and_is_not_stored_in_session_state cover finance/auth regressions. AppTest is synthetic and cannot establish actual browser document continuity, paint timing, focus/scroll, or network behavior; scripts/streamlit_browser_evidence.py --mode transition의 authenticated HTTPS frame/DOM/document/shell evidence가 REQ-002.03-AC01에 필수다. tests/test_streamlit_browser_evidence.py는 이 manifest가 실제 teardown 이후에만 성공하고 document navigation, shell identity drift, 누락 관찰 또는 secret 포함 시 fail-closed하는지 검증한다.
REQ-003 런타임 흐름
valuation-register — register/select/correct
- 시작 이벤트: account form, MCP call, account/import write with dated evidence
- 선행조건: DB initialized; Dashboard path passed auth gate; account exists
- 실행 방식: synchronous SQLite transaction
- 트랜잭션·영속성 경계: one valuation insert or no write on validation/conflict
| 단계 ID | 실행 주체 | 시작 조건·입력 | 처리 | 읽기·쓰기 상태 | 외부 호출 | 성공 시 다음 단계 | 실패 시 전이 |
|---|---|---|---|---|---|---|---|
valuation-01 |
adapter | typed request | delegate shared service | read account | none | valuation-02 |
valuation-fail |
valuation-02 |
account valuation service | basis/amount/scope | validate exact integer, date, purpose, loan definition | no write | none | valuation-03 |
valuation-fail |
valuation-03 |
SQLite | valid payload | reuse idempotent row or insert append-only row | account_valuations write | SQLite | valuation-04 |
valuation-fail |
valuation-04 |
selector/report | requested basis | select verified leaf exact/as_of; warn candidates | read only | none | valuation-success |
valuation-fail |
snapshot-preview-close — preview/conditional close
- 시작 이벤트: assets page or MCP preview/close
- 선행조건: basis date; auth before Dashboard DB; explicit approval for conditional state
- 실행 방식: preview synchronous read; close synchronous atomic write
- 트랜잭션·영속성 경계: preview no write; close writes run and rows together
| 단계 ID | 실행 주체 | 시작 조건·입력 | 처리 | 읽기·쓰기 상태 | 외부 호출 | 성공 시 다음 단계 | 실패 시 전이 |
|---|---|---|---|---|---|---|---|
close-01 |
assets service | basis/policy | gather verified account/manual items and warnings | read accounts/valuations/manual_assets | none | close-02 |
close-fail |
close-02 |
Dashboard/MCP | preview request | return live authority and status | no write | none | close-end |
close-fail |
close-03 |
operator/service | explicit close + approval if needed | enforce conditions and correction reason | transaction begins | none | close-04 |
close-fail |
close-04 |
SQLite | approved payload | insert run and immutable snapshot rows | runs/snapshots write | SQLite | close-end |
close-fail |
import-candidate-reconciliation-monthly-ui — propagation
- import updates transactions/account balance then records only dated unverified candidate; reconciliation uses valuation-managed investment without fake transaction; monthly/UI reads shared reports and explicit form actions only.
| 단계 ID | 실행 주체 | 시작 조건·입력 | 처리 | 읽기·쓰기 상태 | 외부 호출 | 성공 시 다음 단계 | 실패 시 전이 |
|---|---|---|---|---|---|---|---|
prop-01 |
import/account write | real dated balance | _record_*_valuation_candidate |
account_valuations unverified insert | parser | prop-02 |
prop-fail |
prop-02 |
reconciliation service | account baseline | calculate signed ledger with provenance; investment verified => VALUATION_MANAGED | read only | none | prop-03 |
prop-fail |
prop-03 |
monthly/UI page | month/basis query | read snapshot/report and render | read only unless explicit button | Streamlit | prop-end |
prop-fail |
실패와 복구 추가
| 대상 단계 | 실패 조건 | 추가 쓰기 중단 | 재시도 계약 | 중복 방지 | 보상·수동 복구 | 최종 상태 |
|---|---|---|---|---|---|---|
valuation-03 |
idempotency/correction conflict | yes | same payload inspect/reuse; changed payload no blind retry | unique indexes | operator chooses new correction | valuation-fail |
close-03 |
missing/future/unverified/unknown or missing approval | yes | preview then explicit approval | close idempotency key | manual evidence review | close-fail |
close-04 |
SQLite exception/concurrency | transaction rollback | retry same key after lock resolution | unique run key | restore from backup if needed | close-fail |
prop-01 |
no real dated evidence | yes | no candidate write | source hash/date key | wait for evidence | prop-end without candidate |
동시성과 순서 추가
- 동시 실행 허용 여부: independent reads; SQLite writes serialized by transaction
- 잠금·직렬화 단위: account valuation/idempotency and snapshot close transaction
- 순서 보장: auth → DB → read/preview → explicit approval → close; latest run selected after correction
- 충돌 검출·처리: unique idempotency/single correction indexes and explicit error codes
관찰 가능성 추가
| 신호 종류 | 이름 | 발생 단계·조건 | 상관 식별자 | 진단 속성 | 민감정보 처리 |
|---|---|---|---|---|---|
| result/event | valuation_registered |
valuation-03 success |
request/idempotency key surrogate | basis/kind/status only | no source secret |
| result/event | snapshot_previewed |
close-02 |
request correlation | basis/status/warning codes | no account number |
| result/event | snapshot_closed |
close-04 |
snapshot_run_id | status/count/formula | evidence redacted |
| result/event | valuation_conflict |
validation/idempotency failure | correlation ID | error_code only | no payload secrets |
REQ-003 검증
- 정상 흐름:
tests/test_account_valuations.py,tests/test_asset_snapshots.py,tests/test_contract_regression.py - 실패 흐름: conflict, conditional approval, validation tests
- 경계 흐름: exact/as_of, stale/carry-forward, correction/re-close, empty fallback
- 동시성 흐름: SQLite unique/idempotency and transaction rollback review
- 운영 진입점과 기대 결과: Dashboard authenticated
자산/마감/계좌, MCP tools; preview leaves row count unchanged, explicit close returns run id
canonical transition and failure semantics
valuation-02 validates principal/interest/fee, source_ref account-scoped identity, parent scope, cyclic/self parent, one child, reason, and key/source mismatch before valuation-03; each failure is no-write with validation_error, idempotency_conflict, or correction_conflict. close-01 computes actual valuation_date, age, carry, verification, reconciliation completeness, investment unavailable, and loan purpose/definition/ambiguity. close-02 returns live authority, close_status, warnings, and exclusions without writing; close-03 records explicit run/approval/correction; close-04 atomically persists immutable rows. Drift, missing approval, unsafe evidence, and SQLite failure transition to close-fail with no partial write.
The close response exposes top-level run, approval, correction, warnings, excluded_items, and carried_items; its nested provenance exposes actual valuation date, age, carry, verification, approval/correction, warnings, and redacted exclusions. Live/closed/timeseries/monthly read models propagate the same meanings in their documented top-level and nested provenance fields. monthly_source_snapshot_id is a read-model alias of snapshot_run_id, not a persistence field. last_balance is external current comparison rather than opening evidence; missing balance_after/opening evidence transitions to INCOMPLETE_CALCULATION. No fake writes are made for unavailable investment or valuation-managed reconciliation.
C13 database lifecycle transitions
auth-before-DB is the first Dashboard boundary. write-first-use-01 explicitly calls init_db() and may migrate. db-read-01 calls read_db() in SQLite mode=ro; a missing database becomes db_not_initialized. db-read-02 succeeds without mkdir, migrate, or commit. An initialized database's schema or SQLite read failure becomes generic db_error, never db_not_initialized, and raw engine details are not exposed.
D06 runtime decision guards
- Current-cache no rollback: a late historical valuation may update historical authority but cannot roll back the newer current cache (
last_balance); current display and historical basis selection are separate decisions. - Reconciliation decision: return
reconciliation completeonly when required ledger/evidence inputs are complete. If required evidence is absent or unreadable, returnreconciliation unavailablewith provenance and do not infer a balance or create a transaction. - Preview drift revalidation: preview is read-only and records the authority/evidence fingerprint observed. Before close writes, revalidate current authority, approval policy, exclusions, carried items, and the preview fingerprint. Drift routes back to preview/review with a stale-preview or conflict decision and no partial write; only a revalidated preview reaches
close-04.
The fingerprint input is exactly basis date, selection policy, included and excluded evidence state hashes, warnings (excluding the informational existing-run warning), and candidate state hashes. The preview returns preview_fingerprint; close recomputes it before any insert. A changed authority returns authority_drift, and the transaction produces no partial write.
An idempotent close is replayed only for equal request fingerprints. Replay reconstructs the response from the persisted run/evidence (no replay-only reused payload), while a different payload returns idempotency_conflict. Recursive close metadata is allowlisted and redacted before it reaches Dashboard/MCP.
Dashboard missing-DB reads return stable unavailable db_not_initialized without create/migrate/commit. The missing DB stable unavailable contract is no-create. Initialized read failures return db_error; successful initialized reads are read-only. The initialized DB read failure is db_error and initialized read has no write. Reconciliation requires opening evidence and coverage through the requested basis; otherwise it reports INCOMPLETE_CALCULATION/reconciliation unavailable with provenance, no inferred balance, and no fake transaction.
settlement-draft-create — 월정산 초안 lifecycle (Issue #15)
create → edit → review → approve → finalize
| 단계 ID | 실행 주체 | 처리 / guard | 상태 경계 | 실패 전이 |
|---|---|---|---|---|
settlement-draft-create |
adapter/service | Strict month/date/actor/key validation; explicit correction prior run/reason; acquire BEGIN IMMEDIATE; idempotent equal replay; enforce one active draft/month. |
Persist draft and initial target items only; no snapshot run/rows/close. | validation/conflict; rollback |
settlement-draft-edit |
shared service | Expected version; targets change only through explicit refresh; item include/exclude and valuation selection audited; mutation clears approval. | Increment draft/item versions; retain history/reasons. | stale version or invalid scope, no write |
settlement-draft-review |
shared service | Read frozen source/valuation evidence, warning and correction-child drift; compute review fingerprint/totals/missing; no auto future selection. | Read-only; unverified candidate_amount with authority amount NULL. | drift/missing warnings; no authority mutation |
settlement-draft-approve |
authorized caller | Require matching current fingerprint/version and explicit actor/reason/reference/flags. | Persist approval binding and flags. | mismatch/unsafe unknown/rejected evidence rejected |
settlement-draft-finalize |
shared service/SQLite | Idempotency persisted-response lookup first; then active/version/approval/fingerprint/drift/correction-parent checks; insert run, rows and completed/link within one BEGIN IMMEDIATE. |
Atomic immutable close and draft completion. | conflict or injected DB failure rolls back all writes |
Exact requires same-date verified evidence; carried_before uses verified prior with reason; manual_substitute requires explicit ID and reason, with signed date_difference_days = valuation_date - basis_date. age_days keeps its existing direction/meaning. Corrected valuation children are never followed automatically: explicit reselection then fresh review/approval. Missing and substitution remain orthogonal; manual evidence drift requires explicit refresh. Existing row-only legacy close uses established latest row-group ordering and available run identity; never infer a registry row or fall back to live authority.
failure/recovery and read-only guards
Draft create/update/finalize use transaction-scoped writes; any DB failure rolls back run, rows and completion. Same finalize request returns persisted response, not a replay-only reused field; different request conflicts. Monthly summary and review do not mutate; transaction readiness remains independent. read_db() missing path is db_not_initialized, with no-create/no mkdir/no migrate/no commit. Dashboard gate precedes every protected DB access; existing auth cookie lifecycle and exact menu/query/shell/browser semantics remain unchanged.
Cancellation and reopening
- Entry: authenticated Dashboard explicit action or the existing MCP tool delegates through the shared service facade to
cancel_settlement_draft(draft_id, expected_version, cancelled_by, reason, db_path=None). This adds no MCP authentication claim and does not alter Dashboard authentication.
| Stage ID | Owner / input and guard | State, success and failure transition |
|---|---|---|
settlement-draft-cancel |
Shared _mutate requires an existing active draft, matching positive-integer expected_version, nonempty cancelled_by actor and nonempty reason. |
In BEGIN IMMEDIATE, persist status cancelled plus cancelled_by, cancellation_reason, cancelled_at; increment draft version and updated_at. Clear approval_fingerprint, approval_version, approver/time/reason/reference and flags. Success terminates in cancelled; state/version conflicts return conflict with no-write to the existing draft. Invalid input returns validation_error. |
settlement-draft-cancel-recovery |
Cancellation is a transaction-scoped metadata mutation, not evidence deletion or an asset close. | Existing draft item/evidence rows, account_valuations, asset_snapshot_runs, and asset_snapshots are retained. DB failures rollback both cancellation and approval invalidation and return db_error; correct input or reread current state/version before retry. Completed/already cancelled drafts cannot be cancelled again; stale expected versions never overwrite current state. |
settlement-draft-reopen |
Only completed or cancelled drafts release the active-month slot. Start a new explicit create request with a fresh create key; ordinary and correction drafts still share the one-active-month restriction. | The same create key with its original equal payload returns the previous draft, even after completion/cancellation; changed payload conflicts. A fresh key is not permission to overwrite history. No close exists: create without a correction parent. A close exists: explicitly link the latest prior run using correction_of_run_id and nonempty correction_reason, still at month-end. Continue at settlement-draft-create; missing/wrong parent or reason is rejected. |
Cancellation reason is stored in cancellation_reason, not memo; the cancellation actor and timestamp are persisted, not inferred from a later caller. Approval invalidation prevents reuse of the cancelled draft's approval. Reopening creates new draft/item identity and requires its own review/approval; finalization rechecks the correction parent and preserves prior immutable run/rows. Cancellation itself is not an idempotent finalization replay: retrying cancellation after its successful terminal transition returns conflict.
Coverage: tests/test_monthly_settlement_drafts.py checks cancellation/reopen, version conflicts and lifecycle persistence; tests/test_settlement_draft_authority.py checks cancellation approval invalidation; tests/test_settlement_draft_correction_contract.py checks latest-run linkage, reason, immutable history and persisted replay; tests/test_settlement_draft_docs.py checks these documented transitions. Existing auth/menu and read-only recovery flows above remain unchanged.
Issue #18 comprehensive settlement flow
Explicit policy setter validates expected draft version, saves draft.cashflow_policy, increments version, invalidates approval. Read-only review freezes [month start,next month start) active transactions, policy/version and same-month-end selected asset/liability evidence; generates grouped income/expense/excluded details and six totals. Savings only explicit category/subcategory pairs, debt_payment expense; transfer/voucher exclusion priority. Refund applies signed negative expense to original category in refund month. Unresolved unknown returns warning and blocks comprehensive approval regardless of asset conditional flags; confirmed exclusions do not block. KRW only; no FX. Review fingerprint binds source values/classification, policy, version, grouped details/totals and selected valuation evidence; any drift requires review/approval again.
Finalize under one BEGIN IMMEDIATE revalidates version/fingerprint/current sources and six equations, writes run header, asset/liability details, cashflow details/source evidence, completion link and persisted response atomically. Any mismatch/write failure rolls back all. Equal idempotent request replays stored result; changed request conflicts. Correction checks latest parent and single-child rule, appends new run/details, never edits history. Getter is readonly and resolves stored run/month separately from live report; asset direct-close remains asset-only. Dashboard auth precedes DB and missing DB reads never create/migrate. Responses: review/finalize root fields, active get nested review, completed get root final_response; groups named cashflow_kind. MCP hides db_path.