portfolio-optimiser-claude/CLAUDE.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

6.5 KiB
Raw Permalink Blame History

portfolio-optimiser-claude

Kontekst

Søsken-implementasjon (beslutning D7) av portfolio-optimiser-metoden på Claude Agent SDK: generisk, åpent Python-rammeverk som finner kostnadsbesparelser INNI hvert prosjekt i en portefølje. Bygges på metode-spec-en (shared/method-spec.md) som normativ kilde. Åpen konkurranse med MAF-søskenet (~/repos/portfolio-optimiser, operatørbeslutning 2026-08-09): alle tilgjengelige midler er lov for å nå den beste løsningen, inkludert å lese søskenets kode, og utvekslingen kan gå begge veier. Grensen er hensikten, ikke kilden: kopiér kun der det faktisk gir mening for løsningen — aldri som snarvei. De reelle begrensningene er hva MAF og Claude Agent SDK hver for seg tilbyr, og det er nettopp den forskjellen sammenligningen skal måle. Fram til 2026-08-09 ble repoet bygget fra speccen alene; det er datert i README og skal ikke påstås om arbeid etter den datoen. Program-planer (sesjonsplan S5S12, sammenligningsprotokoll, frossent målbilde) bor i hovedrepoets docs/plan/. Løpende state: STATE.md (local-only).

Stack

Python ≥3.10. Claude Agent SDK (claude-agent-sdk ≥0.2, kildeverifisert 0.2.139 per 2026-08-18 — pinnen >=0.2.111,<0.3 er en TILLATELSE, _VERIFIED_THROUGH i tests/test_sdk_version_guard.py er beviset og går rødt på en ulest nyere build. Per 2026-07-03: MIT-lisens, bundler Claude Code CLI — ingen separat installasjon; import og konstruksjon fungerer uten API-nøkkel, nøkkel trengs kun ved faktisk query()). Pakkehåndtering: uv. Pydantic for kontrakts-validering.

Konvensjoner

  • Type hints overalt; mypy --strict. ruff for lint+format. pytest for test.
  • Testsuiten kjører uten API-nøkkel og uten nettverk (invariant fra S5; håndhevet ved at all kontrakts-/kontekst-logikk er ren config/fil-lag).
  • Modell-valg som konfig: src/portfolio_optimiser_claude/data/model_map.json (rolle→Claude-modell-id per backend-profil, hver profil krever default; validert av contracts.py). Modell-id-er verifiseres mot offisiell doc før endring.
  • SDK-primitiver (verifisert 2026-07-03): query()/ClaudeSDKClient, ClaudeAgentOptions (max_turns, max_budget_usd — førsteklasses USD-tak), AgentDefinition (subagenter; sub-felt er camelCase), @tool + create_sdk_mcp_server (in-process MCP), hooks (PreToolUse med permissionDecision: "deny" for deterministisk gating). MERK: SessionStart/SessionEnd finnes IKKE som Python-callback-hooks.
  • shared/ er en git subtree av portfolio-optimiser-commons (source of truth; remote commons). Synk er PULL-ONLY: endringer committes i commons og hentes med git subtree pull --prefix=shared commons main --squash. ALDRI git subtree push fra konsument — re-split lekker hele konsument-historikken inn i commons (observert + opprydd i MAF-repoet 2026-07-03). Se shared/README.md.

Kommandoer

  • Sync: uv sync
  • Test: uv run pytest
  • Lint: uv run ruff check . + uv run ruff format .
  • Type: uv run mypy src scripts
  • Bevis en søm: uv run python scripts/mutation_harness.py --help (grønn-før/rød-etter)

Arbeidsflyt (invarianter)

  • Rent teknisk rammeverk: deployer eier DPIA/ROS/behandlingsformål — kun tekniske forutsetninger (lokal-only, provenance, ingen stille egress) + disclaimer.
  • Metode-spec-en er normativ (RFC 2119). Ærlighets-regelen (§1) er ufravikelig: ingen artefakt påstår mer enn implementasjonen gjør; skriptede stand-ins merkes som det.
  • Deterministisk validator obligatorisk + blokkerende (§3 steg 4); golden-suiten (shared/examples/bygg-energi-mikro/golden.json) er ENESTE fasit (§7).
  • Mutasjonsharnesset er verktøyet, ikke en huskeregel (beslutning 2026-08-25, etter ti håndbygg): scripts/mutation_harness.py kjører grønn-før/rød-etter mot det EKTE treet og håndhever de fire kontrollene som ellers svikter stille — unikt anker, node-id-en faktisk kjørt (pytest rc=4 er IKKE rødt), restaurering sha256-verifisert fra disk, og rødhet på en pinnet linje. Tre roller, og rekkefølgen følger av hva hver måler: --red grønn FØR (måleapparatets positivkontroll) og rød ETTER (selve målingen, ikke besvarbar før mutasjonen finnes), --green grønn i BEGGE (treffer den, landet mutasjonen for bredt). Verktøyet ligger bevisst UTENFOR tests/ — suiten kjører hver økt, og en fixture som skriver til disk gjør enhver avbrutt kjøring til et mutert tre. Mutasjon av en KOPI i minnet hører derimot hjemme i suiten og er der alt (test_guard_red_when_*).
  • Load-bearing tester (§11): hver søm bevises med en test som blir RØD når sømmen detaches — grønn-men-død er feilmoden regelen finnes for.
  • Stoppkriterier + budsjett-tak påkrevd ved oppstart (§8; contracts.py fail-fast). På kjørestien kommer USD-taket i tillegg via ClaudeAgentOptions.max_budget_usd.
  • Kostnadsdisiplin (D6): alt utvikles offline/deterministisk; den ENE ekte API-kjøringen i programmet er S10 (mikro-bundle, billigste egnede modell — per model_map Haiku 4.5 — harde tak, kost loggføres).
  • Kun egen API-nøkkel (v0.1.1): ANTHROPIC_API_KEY er ENESTE godtatte legitimasjon. Et abonnements-token (CLAUDE_CODE_OAUTH_TOKEN) AVVISES av preflight og av kjøre- inngangen, som ender i exit ≠ 0 — aldri et stille tilbakefall på CLI-ens innlogging.
  • Uttalt unntak (ikke skjult): repoet er bygget på Claude Agent SDK, som selv starter den medfølgende Claude Code-CLI-en som underprosess. Det er Anthropics tiltenkte bruk MED API-nøkkel, og står som et UTTALT unntak fra eierens regel om at egen kode ikke starter Claude Code. Omskriving til rene API-kall er vurdert og valgt bort — poenget med D7 er å måle hva Agent SDK-en tilbyr. Repoet er AVSLUTTET som eksempel: ingen videre utvikling er planlagt.
  • STATE.md er local-only (gitignored — repoet skal publiseres; STATE når aldri et offentlig speil).

Communication patterns

Linking to local files

When pointing to local files in responses, always use markdown link syntax with a descriptive name:

  • Use [Human-friendly name](file:///absolute/path) — never bare file:///... URLs or autolinks <file://...>.
  • Always use absolute paths. Never ~/ or relative paths.
  • For multiple files, render as a bullet list of named markdown links.

Why: bare file:// URLs only render the first as clickable across multiple lines. Named markdown links make each entry independently clickable and look cleaner.