fix: address settlement display and documentation review findings

Rework Issue 18 / PR 19, source review 76: C11-F01 and D02-F02/D03-F03/D04-F04. Preserve the approved contract and add named display, schema, policy, ownership and signature regressions.
This commit is contained in:
2026-10-13 13:09:16 +00:00
parent 616bb7a744
commit ae4383ca2c
7 changed files with 296 additions and 3 deletions
+41
View File
@@ -471,3 +471,44 @@ Shared service setter: `set_settlement_cashflow_policy(draft_id,expected_version
The stored reader recognizes `snapshot_schema_version=1` only with `aggregation_version=comprehensive-v1`. Any non-NULL version metadata requires both recognized versions; a partial pair, unsupported snapshot schema, or absent/unknown aggregation version returns the existing `success=false`, `error_code=db_error`, `error` envelope without authoritative totals/details or DB writes. Legacy NULL schema headers remain legacy reads with unrecorded totals NULL; this does not reinterpret unknown comprehensive versions as legacy.
Getter distinguishes immutable stored run/evidence (exact run ID, schema/aggregation version, legacy/partial/missing/excluded indicators) from live current-ledger report. Existing asset-only API inputs/responses/errors/aliases and direct-close meaning remain unchanged and are not comprehensive close. Reads/render are readonly/no-create; authenticated Dashboard gate precedes DB. Existing envelope, stale approval/drift rejection, idempotent persisted replay, conflict, atomic rollback, append-only correction, and privacy contracts remain.
### Comprehensive cashflow policy input
`set_settlement_cashflow_policy(draft_id, expected_version, updated_by, cashflow_policy, db_path=None)` accepts this normalized input (omitted lists normalize to empty lists):
```json
{"savings_categories":[{"category":"Savings","subcategory":"Deposit"}],"category_decisions":[{"category":"Food","subcategory":"Dining","cashflow_kind":"expense","reason":"Reviewed classification"}]}
```
Only `savings_categories` and `category_decisions` are allowed top-level keys; each is a list. Savings entries have exactly `category` and `subcategory`; decision entries have exactly those keys plus `cashflow_kind` and `reason`. Category fields are strings and blank strings are valid. Kinds are `income`, `expense`, or `excluded`; reason must be a nonempty string. Within each list, duplicate category/subcategory pairs are rejected. Normalization sorts each list and supplies omitted lists as empty.
The setter requires matching `expected_version`; successful mutation increments version and clears prior approval/review fingerprint. Review computes a new fingerprint; approval applies only to the reviewed version/fingerprint. Changed policy or transactions require re-review and re-approval. `aggregation_version` is `comprehensive-v1`; policy editing increments the draft version, clears approval fields and invalidates the prior `review_fingerprint` for approval.
### Comprehensive settlement response contract
The six totals are integer root fields in comprehensive review and saved-run reader responses. Legacy readers return NULL for totals they never stored.
| Field | Type | Presence | Legacy |
|---|---|---|---|
| `income_total` | integer | required | null |
| `expense_total` | integer | required | null |
| `net_cashflow` | integer | required | null |
| `total_assets` | integer | required | null |
| `total_liabilities` | integer | required | null |
| `net_worth` | integer | required | null |
A review response has root-level `review_fingerprint`, `cashflow_policy`, `cashflow_items`, `cashflow_blockers`, and `source_snapshot`. The active draft getter nests its current review under `review`. For a completed draft, `get_settlement_draft` exposes the persisted close response under `final_response`; `finalize_settlement_draft` returns that close response at root, including on idempotent replay. A saved-run reader instead returns the stored snapshot at root with `source: settlement_snapshot`, `run_id`, and six totals. `cashflow_items` is a required list in comprehensive review, close and saved-reader results (empty is valid): each entry has `cashflow_kind` (income/expense/excluded string), `category` and `subcategory` (strings, including blanks), signed integer `amount`, positive integer `transaction_count`, and string `currency`. `cashflow_blockers` is a required review/close list with string `transaction_id` and `reason` entries; successful close requires it to be empty. The saved reader does not synthesize a blocker list from old records. `cashflow_policy` is the normalized policy object for comprehensive responses, but null for the legacy saved reader. `source_snapshot` is the preserved transaction/asset input object for comprehensive responses and an empty object for legacy reads. Version metadata is preserved, not inferred from today's ledger. The following JSON examples show selected fields rather than exhaustive response payloads.
Successful root review example, including a negative signed expense (refund):
```json
{"success":true,"income_total":120000,"expense_total":-5000,"net_cashflow":125000,"total_assets":900000,"total_liabilities":200000,"net_worth":700000,"review_fingerprint":"<fingerprint>","cashflow_policy":{"savings_categories":[],"category_decisions":[]},"cashflow_items":[{"cashflow_kind":"expense","category":"Food","subcategory":"Dining","amount":-5000,"transaction_count":1,"currency":"KRW"}],"cashflow_blockers":[{"transaction_id":"txn-example","reason":"unknown cashflow classification requires an explicit decision"}],"source_snapshot":{"transactions":[],"assets":[]}}
```
Saved reader root example:
```json
{"success":true,"source":"settlement_snapshot","run_id":"run-example","income_total":120000,"expense_total":-5000,"net_cashflow":125000,"total_assets":900000,"total_liabilities":200000,"net_worth":700000,"cashflow_policy":{"savings_categories":[],"category_decisions":[]},"source_snapshot":{"transactions":[],"assets":[]}}
```
A `validation_error` results when cashflow review blockers must be resolved before approval/finalize. Remediate policy or transaction classification, then re-review and re-approve. Examples use synthetic identifiers, not raw secrets.