fix: preserve schema 15 compatibility for v0.1.8

This commit is contained in:
2026-10-17 18:27:53 +09:00
parent a97548c36b
commit 1134f058cc
61 changed files with 2650 additions and 644 deletions
+27 -28
View File
@@ -36,22 +36,6 @@ def _add_tx(day, typ, amount, category, subcategory, path):
return result["transaction"]["id"]
def _add_snapshot(name, amount, basis_date, bucket, path):
"""Create the schema-15 account snapshot evidence used by settlement review."""
account = _ok(service.create_ledger_account(name, type="bank", currency="KRW", liquidity_bucket=bucket, db_path=path))["account"]
with sqlite3.connect(path) as conn:
conn.execute(
"INSERT OR IGNORE INTO monthly_settlement "
"(id,month,basis_date,status,version,created_by,created_at,updated_by,updated_at) "
"VALUES (?,?,?,?,?,?,?,?,?)",
("monthly-2026-09", basis_date[:7], basis_date, "draft", 1, "test", "now", "test", "now"),
)
return _ok(service.register_account_valuation(
account["id"], basis_date, amount, currency="KRW", monthly_settlement_id="monthly-2026-09",
liquidity_bucket=bucket, db_path=path,
))
def test_explicit_savings_refund_and_movement_precedence(tmp_path):
path = str(tmp_path / "policy-precedence.sqlite3")
init_db(path)
@@ -120,11 +104,11 @@ def test_unknown_classification_blocks_and_explicit_exclusion_unblocks(tmp_path)
def test_policy_edit_persists_versions_and_invalidates_approval(tmp_path):
path = str(tmp_path / "policy-approval.sqlite3")
init_db(path)
_ok(_add_snapshot("Synthetic cash", 100, "2026-09-30", "cash", path))
_ok(service.create_manual_asset("Synthetic cash", 100, "2026-09-30", "cash", db_path=path))
draft = _draft(path)
reviewed = _ok(service.review_settlement_draft(draft["id"], db_path=path))
assert not reviewed["cashflow_blockers"]
_ok(service.approve_settlement_draft(draft["id"], draft["version"], reviewed["review_fingerprint"], "reviewer", "synthetic", "ref", allow_missing_exclusions=True, db_path=path))
_ok(service.approve_settlement_draft(draft["id"], draft["version"], reviewed["review_fingerprint"], "reviewer", "synthetic", "ref", db_path=path))
result = _ok(_capability("set_settlement_cashflow_policy")(draft["id"], draft["version"], "editor", _policy(), db_path=path))
saved = _ok(service.get_settlement_draft(draft["id"], db_path=path))["draft"]
assert saved["cashflow_policy"] == _policy()
@@ -148,14 +132,23 @@ def test_review_contract_and_stored_reader_never_recomputes_live_preview(tmp_pat
init_db(path)
reader = _capability("get_monthly_settlement_snapshot")
empty = reader(month="2026-09", db_path=path)
assert empty["success"] is False and empty["error_code"] == "unsupported"
_ok(_add_snapshot("Synthetic cash", 100, "2026-09-30", "cash", path))
assert empty["success"] is False and empty["error_code"] == "not_found"
_ok(service.create_manual_asset("Synthetic cash", 100, "2026-09-30", "cash", db_path=path))
draft = _draft(path)
reviewed = _ok(service.review_settlement_draft(draft["id"], db_path=path))
assert "review_fingerprint" in reviewed and "review" not in reviewed
approved = _ok(service.approve_settlement_draft(draft["id"], draft["version"], reviewed["review_fingerprint"], "reviewer", "synthetic", "ref", allow_missing_exclusions=True, db_path=path))
final = service.finalize_settlement_draft(draft["id"], approved["draft"]["version"], reviewed["review_fingerprint"], "close-key", db_path=path)
assert final == {"success": False, "error_code": "unsupported", "error": "run history unavailable"}
approved = _ok(service.approve_settlement_draft(draft["id"], draft["version"], reviewed["review_fingerprint"], "reviewer", "synthetic", "ref", db_path=path))
final = _ok(service.finalize_settlement_draft(draft["id"], approved["draft"]["version"], reviewed["review_fingerprint"], "close-key", db_path=path))
assert "final_response" not in final and "snapshot_run_id" in final
snapshot = _ok(reader(run_id=final["snapshot_run_id"], db_path=path))
assert snapshot["is_closed_snapshot"] is True
assert snapshot["snapshot_run_id"] == final["snapshot_run_id"]
assert snapshot["total_assets"] == 100
assert _ok(reader(month="2026-09", db_path=path))["snapshot_run_id"] == final["snapshot_run_id"]
_ok(service.create_manual_asset("Later live cash", 900, "2026-09-30", "cash", db_path=path))
persisted = _ok(reader(run_id=final["snapshot_run_id"], db_path=path))
assert persisted["total_assets"] == 100 and persisted["snapshot_run_id"] == final["snapshot_run_id"]
assert "live_authority" not in persisted.values()
def test_month_boundaries_refund_month_negative_total_and_fx_blocker(tmp_path):
@@ -199,7 +192,7 @@ def test_policy_capabilities_are_checked_at_call_time(tmp_path):
init_db(path)
assert callable(_capability("set_settlement_cashflow_policy"))
assert callable(_capability("get_monthly_settlement_snapshot"))
assert _capability("get_monthly_settlement_snapshot")(month="", run_id="", db_path=path)["error_code"] == "unsupported"
assert _capability("get_monthly_settlement_snapshot")(month="", run_id="", db_path=path)["error_code"] == "not_found"
def _private_cashflow_review(path, policy=None):
@@ -257,7 +250,7 @@ def test_inactive_category_uses_raw_fallback_through_public_close(tmp_path):
path = str(tmp_path / "inactive-category-fallback.sqlite3")
init_db(path)
# A close run requires a nonempty asset valuation; cashflow-only drafts cannot finalize.
_ok(_add_snapshot("Synthetic close cash", 100, "2026-09-30", "cash", path))
_ok(service.create_manual_asset("Synthetic close cash", 100, "2026-09-30", "cash", db_path=path))
cases = [
# SQL fallback is category-based: ordinary legacy categories become expense,
# while canonical legacy categories retain their established classifications.
@@ -290,9 +283,15 @@ def test_inactive_category_uses_raw_fallback_through_public_close(tmp_path):
assert row["category_cashflow_kind"] is None and row["category_active"] is None
assert reviewed["expense_total"] == 123 # 11 + 13 + 17 + 19 - 5 + 31 + 37
assert reviewed["income_total"] == 29
approved = _ok(service.approve_settlement_draft(draft["id"], draft["version"], reviewed["review_fingerprint"], "reviewer", "synthetic", "ref", allow_missing_exclusions=True, db_path=path))
closed = service.finalize_settlement_draft(draft["id"], approved["draft"]["version"], reviewed["review_fingerprint"], "inactive-category-close", db_path=path)
assert closed == {"success": False, "error_code": "unsupported", "error": "run history unavailable"}
approved = _ok(service.approve_settlement_draft(draft["id"], draft["version"], reviewed["review_fingerprint"], "reviewer", "synthetic", "ref", db_path=path))
closed = _ok(service.finalize_settlement_draft(draft["id"], approved["draft"]["version"], reviewed["review_fingerprint"], "inactive-category-close", db_path=path))
snapshot = _ok(service.get_monthly_settlement_snapshot(run_id=closed["snapshot_run_id"], db_path=path))
persisted = {row["id"]: row for row in snapshot["source_snapshot"]["transactions"]}
for txid in ids:
assert persisted[txid]["sourceprovenance"] == "fallback"
assert persisted[txid]["category_active"] is None
assert persisted[txid]["category_cashflow_kind"] is None
assert snapshot["expense_total"] == 123 and snapshot["income_total"] == 29
def test_explicit_decision_classifies_only_unresolved_inactive_fallback(tmp_path):