portfolio-optimiser/src/portfolio_optimiser
Kjell Tore Guttormsen 7dab2dfb78 fix(s54): reject non-array/non-object ledger JSON in SavingsLedger.load
S5.4 review MINOR (SC5 fail-fast hole, run.py:740). A valid-JSON but
wrong-shape savings ledger escaped the --report fail-fast refusal:
  - top-level {} iterated zero keys -> entries=[] -> rc 0 "0,00 kr"
    (a malformed file masquerading as a real zero-savings result)
  - a bare scalar / object-with-keys / list-of-non-objects raised an
    uncaught TypeError -> traceback (violates SC5 "rc 1, no traceback")

Fix at the fail-fast boundary, not the run.py except tuple: the review's
first option (add TypeError to run.py:740) leaves the {} masquerade
because {} is an empty iteration, not a TypeError. SavingsLedger.load now
raises ValueError for a non-array top-level and a non-object row, caught
by run.py:740's existing ValueError arm. Hardens both callers
(run.py:740 report + run.py:785 portfolio).

RED-first: 6 unit cases (test_ledger) + 2 CLI rc-1 cases (test_run_cli).
452 passed; ruff + mypy clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KNNiJRk1sSwxgVLS5AobT1
2026-07-24 19:52:52 +02:00
..
data feat(s36): PricingContract + fail-fast loader + placeholder path + quality guidance 2026-07-15 09:57:48 +02:00
__init__.py feat(s53): load_dimension fail-fast loader (MAF-free, mirrors load_goal_config) 2026-07-23 21:31:32 +02:00
backends.py fix(s41): pass required credential in AzureFoundryBackend.create_chat_client 2026-07-15 11:18:40 +02:00
budget.py chore(format): ruff format the 4 drifted files (no behavior change) 2026-07-03 00:32:49 +02:00
contracts.py feat(fase1): GoalContract + GoalConfig standalone fail-fast loader (F1) 2026-07-07 08:01:53 +02:00
costsim.py docs(s54): --report CLI surface + truthful costsim kost_mot_verdi seam note 2026-07-24 01:38:10 +02:00
datasource.py feat(fase2b): OKF-navigated bundle context replaces chunk-stuffing 2026-06-30 06:42:19 +02:00
dimension.py feat(s53): load_dimension fail-fast loader (MAF-free, mirrors load_goal_config) 2026-07-23 21:31:32 +02:00
generate.py feat(fase4): route the validator falsification into the next hypothesis (informed refinement) 2026-06-30 09:08:02 +02:00
hitl.py fix(s51): close 2 review MINORs — non-UTF-8 skip + affected_codes parity 2026-07-15 20:59:01 +02:00
ingest.py refactor(ingest): adopt shared llm-ingestion-okf v0.3.1 behind a thin adapter 2026-07-20 07:47:55 +02:00
ir.py feat(fase2): promote blocking validator + IR to src 2026-06-24 13:24:03 +02:00
ledger.py fix(s54): reject non-array/non-object ledger JSON in SavingsLedger.load 2026-07-24 19:52:52 +02:00
notify.py fix(s52): resolve ConsoleNotifier default stream at call time 2026-07-17 03:18:00 +02:00
okf.py feat(fase1): dimension filter on bundle_context, default unchanged (F1) 2026-07-07 07:44:33 +02:00
outbox.py feat(s42): byte-deterministic write_run_config outbox writer 2026-07-15 12:39:42 +02:00
persona.py feat(shared-root): S3 — configurable shared-root resolver with load-bearing override test 2026-07-03 01:10:05 +02:00
preflight.py fix(s41): close review WARN — OSError refusal + scheme/profile branch coverage 2026-07-15 11:58:10 +02:00
provenance.py feat(fase2): first-class Pydantic provenance stamp 2026-06-24 13:26:55 +02:00
reference_domain.py feat(fase2a): optional Project.bundle_dir + loader (backward-compat, S2.0) 2026-07-15 07:03:15 +02:00
retrieval.py fix(retrieval): fail-closed on uncanonicalisable paths (embedded null byte) 2026-07-21 08:00:11 +02:00
run.py feat(s54): --report/--json CLI mode in run.py over value_report 2026-07-24 01:35:35 +02:00
shared_root.py test(okf): lock link_in_index success-path byte/order preservation + honest shared_root claim 2026-07-21 09:38:56 +02:00
simulation.py feat(s42): live_dry_run cut in run_project + DryRunReport 2026-07-15 18:05:16 +02:00
validator.py feat(fase1): assessment-method encoding — persisted dimension-scoped example + validator rule (F1) 2026-07-07 08:15:44 +02:00
value_report.py feat(s54): value_report text + deterministic JSON formatters 2026-07-24 01:29:31 +02:00
verdicts.py fix(s51): close 2 review MINORs — non-UTF-8 skip + affected_codes parity 2026-07-15 20:59:01 +02:00
workflow.py feat(fase3): make the maker-checker checker actually gate the reasoning 2026-06-30 07:24:30 +02:00