portfolio-optimiser-claude/CHANGELOG.md
Kjell Tore Guttormsen f92b04bf62
fix(credential): a subscription paid for the run, and one print line decided it
ANTHROPIC_API_KEY is now the ONLY accepted credential. Through v0.1.0 the
preflight cleared on CLAUDE_CODE_OAUTH_TOKEN, and run_s10 went further: an
unset key printed "note: relying on the CLI's own credentials" and carried
on. That note was not a warning, it was a decision - made silently, on the
operator's behalf, about who pays. Both paths are gone; a run with no key
refuses with exit 2 before anything is opened.

Red first, both halves: _check_credentials refuses an OAuth-only env, and
the run entrance is driven as a real subprocess with a deliberately missing
bundle, so the credential refusal must win the race against the bundle
error. Detach it and the process reaches navigate_bundle instead - a
different exit code, no refusal line, the fallback back in the output. The
positive control (key set) gets past the gate and fails on the bundle, so
the gate is a gate and not a wall. 997 -> 1002, offline, no key in env.

The SDK exception is now stated where a reader meets it, not implied: this
framework runs on the Claude Agent SDK, which starts the Claude Code CLI it
bundles as a subprocess. That is the SDK's intended use WITH an API key,
and it is a deliberate, stated exception to the owner's rule that his own
code never starts Claude Code. Rewriting to direct HTTP calls was weighed
and declined - measuring what the Agent SDK offers is the point of D7. The
repo is closed as a worked example.

Two prose claims were corrected rather than left standing: run_s10.py is no
longer byte-frozen (it carries exactly one change, and runs/s10/ is still
the v0.1.0 run), and its two round() call sites moved 110->118, 130->138.

The credential paragraph is prose under an existing heading, not a new
section: test_readme_anchors_loadbearing.py pins 14 heading ids MEASURED on
the published page and forbids re-deriving them. This order forbids push, so
a new heading could not have been honestly re-measured.

Version 0.1.1: pyproject.toml, uv.lock self-entry, CHANGELOG - 3 of 3. No
version badge in README, no constant in src. v0.1.0 stands as released.

Order 20260920T131502Z-7496226791-from-.claude. The older D7 mirroring order
20260913T053840Z-9473220509 is retired unexecuted: po closes at v1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 15:24:55 +02:00

7.8 KiB
Raw Blame History

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[Unreleased]

[0.1.1] - 2026-09-20

Removed

  • A Claude Code subscription is no longer a way to pay for a run. Through 0.1.0 the preflight accepted CLAUDE_CODE_OAUTH_TOKEN — the bundled CLI's own login token — as a credential, and run_s10 went further: with no ANTHROPIC_API_KEY set it printed a note: and carried on, letting the CLI resolve whatever credentials it found. Both paths are gone. ANTHROPIC_API_KEY is now the only accepted credential; an OAuth token alone is a structured refusal, and a run entrance with no key exits non-zero before it opens anything. 0.1.0 stands as released — this is stated, not rewritten.

Added

  • The SDK exception is stated where a reader meets it. README and CONTRIBUTING now say plainly that this framework runs on the Claude Agent SDK, that the SDK starts the Claude Code CLI it bundles as a subprocess, that this is the SDK's intended use with an API key, and that the repo is closed as a worked example. Rewriting to direct HTTP API calls was considered and deliberately not done: measuring what the Agent SDK offers is the point of the D7 sibling.
  • Load-bearing proof for both refusals (tests/test_api_key_only_loadbearing.py): the run entrance is driven as a real subprocess with a deliberately missing bundle, so the credential refusal must win the race against the bundle error — detach it and the process reaches navigate_bundle instead. The positive control (a key set) gets past the gate and fails on the bundle, so the gate is a gate and not a wall. 997 → 1002 tests, still offline and still with no key in the environment.

Changed

  • The SDK pin now has a proof behind it, not only a permission. claude-agent-sdk moved 0.2.120 → 0.2.139, and the version guard gained _VERIFIED_THROUGH: the newest build whose source was actually read. The pin (>=0.2.111,<0.3, unchanged) says what uv may resolve; it never said anyone had looked. Those are different questions, and conflating them left 0.2.1110.2.999 admissible while the premises were source-verified only through 0.2.110 — uv sync --upgrade would have kept the suite green on an unexamined build. A build newer than the last one read now fails RED, naming the premises to re-check. A third question — "is something newer upstream?" — is deliberately NOT asked: it needs the network, and this suite stays offline.
  • The SDK premises are checked, not merely printed. They had existed only as a sentence the failure message recited; nothing verified them, so a premise that stopped being true would have surfaced on the one live paid run. They are now a table introspected against the installed package, with the operator-facing prose derived from that same table so the two cannot drift. The one premise introspection structurally cannot see — that query() yields an AssistantMessage then a closing ResultMessage — is named separately, and is the honest reason a human reading is still required.
  • Re-verified at 0.2.139 source level: the public query.py is byte-identical to 0.2.120, every premise field keeps its type and default, and the parser changes are additive (a new origin passthrough, a new ConversationResetMessage this client ignores). 0.2.139 also added a skills path that can default setting_sources to ["user", "project"]; it fires only on None, so the explicit [] that carries the S10 isolation fix is out of its reach.

[0.1.0] - 2026-08-17

Added

The method, implemented (D7). Sibling implementation of the portfolio-optimiser method on the Claude Agent SDK, built from the shared frozen spec + golden suite alone — never by reverse-engineering the MAF sibling.

  • Deterministic backbone — the typed cost-IR, the mandatory blocking validator (frozen by the shared golden suite, its only oracle), first-class provenance, and fail-fast startup contracts including the role → model map.
  • Agentic loop — bounded generation, makerchecker debate, the validator gate, and informed refinement; the budget meter admits no unbounded loop anywhere, and carries an optional pre-call run-total USD belt on top of the post-charge token/round caps.
  • Learning loop — the OKF context seam (navigation, never chunk-stuffing), the ExpeL-style experience fold, the async expert-verdict inbox, and the fail-closed promotion gate.
  • Ingest layer — deterministic CSV (file) and SQLite (sql) connectors in front of the loop, materializing OKF bundles the unchanged loop consumes; frozen by byte-identical golden extractions. http/MCP is an extension point this repo does not build, and a manifest naming it is rejected fail-fast.
  • Value layer — the fail-closed savings ledger (dimension-free sum, no double-counting), the hard/soft goal contract, the outbox output layer, the HITL pending/routing view, the pre-run cost simulation over schema-validated pricing config, the SDK/API preflight, opt-in notification sinks (webhook egress only behind an explicit per-run flag), and the per-run value report (modelled → expert-corrected → realized, goal progress, quantified learning effect, cost against value).
  • Operator CLI — one collecting entrance (run.py) for a single project (--bundle) or a portfolio (--portfolio, --verdict-dir), with --goals + --ledger stopping a run before any model call when the target is already met, and --value-report projecting what the run delivered. Standalone entrances for valuereport, hitl, costsim and preflight. Every flag the README documents is checked against the actual --help output by a load-bearing test.
  • Knowledge-base recipe — the documented team process for building the OKF bundles the framework reads (docs/oppskrift-kunnskapsbase.md), with an honest 12 week expectation.
  • Traceable run cost — the provenance stamp records which SDK build produced the run, read from the producing client rather than the environment, so the SDK's cost estimate can be traced to the price table that computed it. A run not produced by the SDK reports null instead of borrowing the installed version.
  • The programme's one live model run (S10) — executed and validated at a documented $0.127514, its four artifacts committed as fixed reference output under runs/s10/. That record is never edited after the fact: it predates the sdk_version field and is left without one rather than back-filled with a guess.
  • Load-bearing tests — every seam is proven by a test that goes red when the seam is detached; the whole suite runs offline, with no API key and no network.

Notes

  • Why 0.1.0 and not 1.0.0. Two gaps are documented and deliberately held open rather than papered over: the frozen spec's §7 generated shape (V1) has landed in the shared spec but is not yet adopted in the goldens — the golden is the library's own emission, so adoption is gated on the ingest-library pin swap, and test_ingest_stamp_conformance_loadbearing.py is the ratchet that goes red the moment a materialize() run reaches the new shape. And the SDK pin (>=0.2.111,<0.3) reaches further than the range whose premises are source-verified (through 0.2.110). A 1.0.0 would claim a settled surface this implementation does not yet have.
  • Honesty rule (method spec §1): no artifact in this repo claims more than the implementation does. Scripted stand-ins are labelled as such, unbuilt extension points are named as unbuilt, and a figure the data does not carry is reported unmarked rather than back-filled.
  • Licensed under the MIT License (see LICENSE).