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

97 lines
6.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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`](https://git.fromaitochitta.com/open/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.