portfolio-optimiser/.claude/projects/2026-06-26-fase3-portfolio-fanout/brief.md
Kjell Tore Guttormsen 4cdd172522 docs(fase3): /trekbrief — fan-out brief (PROCEED, gate 6/6) [skip-docs]
Fase 3 brief: sequential fan-out orchestrator over N portfolio projects,
reusing the single-project run_project seam. Four operator design decisions
locked via interview: isolate execution-state per project but SHARE one
VerdictStore (ExpeL accumulates across the portfolio); sequential (concurrency
= documented extension point); fixed config-list topology (resolves plan §12
open decision); list-of-RunResult + thin aggregate output. 0 research topics
(all internal; MAF concurrency already spiked in Fase 1 + de-scoped).

brief-reviewer (opus) → PROCEED on first iteration, all 6 dimensions pass
(completeness 5, consistency 4, testability 4, scope 5, research 5, memory 5).
Folded in its PROCEED-level refinements before finalize: per-project docs_dir
MUST be config-driven or SC1 breaks; SC7 given a concrete parametrised-test
assertion; SC5 baseline made relative (current+new, not a guessed 103); SC3
detach encodable as an automatic guard; SC4 fixture must yield overlapping
features; new synthetic project flagged AI-authored (user-not-domain-expert).
Validator: valid, 0 warnings. phase_signals: plan/execute/review = high/opus.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019any9zfGNNwWJPX5Zq2QRz
2026-06-26 11:01:01 +02:00

11 KiB

type brief_version created task slug project_dir research_topics research_status auto_research interview_turns source framing phase_signals
trekbrief 2.2 2026-06-26 Fase 3: orkestrator fan-out over N porteføljeprosjekter med state-isolasjon + delt læring fase3-portfolio-fanout .claude/projects/2026-06-26-fase3-portfolio-fanout/ 0 skipped false 9 interview refine
phase effort
research standard
phase effort model
plan high opus
phase effort model
execute high opus
phase effort model
review high opus

Task: Fase 3 — orkestrator fan-out over N porteføljeprosjekter

Generated by /trekbrief on 2026-06-26. This brief is the contract between requirements and planning. /trekplan reads it to produce the implementation plan. Every decision in the plan must trace back to content in this brief.

TL;DR

Refine av den låste planen (Fase 3, §52-57): bygg en sekvensiell fan-out-orkestrator som kjører N porteføljeprosjekter gjennom eksisterende run_project, isolerer eksekverings-state per prosjekt men deler én VerdictStore (ExpeL akkumulerer over porteføljen). Bevis at et nytt syntetisk prosjekt kjører ende-til-ende kun via konfig (null kjerne-endring), med load-bearing isolasjons-test + extension-dok.

Intent

Rammeverket beviser i dag metoden på ÉTT prosjekt (Fase 2 vertikal skive, run_project). Kjerneløftet — en generisk porteføljeoptimerer — er ubevist helt til rammeverket kjører over N uavhengige prosjekter uten kjerne-kodeendring per prosjekt. Fase 3 generaliserer: en orkestrator fan-out'er over en konfigurert portefølje, isolerer hvert prosjekts eksekverings-state (budsjett-meter, debatt, retrieval-kontekst) så det er null state-bleed, samtidig som den tråder én delt VerdictStore så læringsløkken (ExpeL) komponerer over porteføljen — en dom på ett prosjekt informerer like forslag på andre. Å bevise at et splitter nytt syntetisk prosjekt slippes inn kun via konfig er den konkrete falsifiseringen av genericitets-påstanden (D4/D5). Dette låser opp Fase 4 (open-source) og D7 Claude-SDK-søsken, som begge avhenger av en genuint generisk, konfig-drevet kjerne.

Goal

En run_portfolio-orkestrator (navn avgjøres i plan) eksisterer som tar en konfigurert liste prosjekt-id-er + én delt VerdictStore, kjører hvert gjennom run_project sekvensielt med isolert eksekverings-state, og returnerer en ordnet liste av per-prosjekt RunResult pluss et tynt aggregat (antall validert/avvist, sum claimed saving, sum token-forbruk). Et nytt syntetisk referanse-prosjekt lagt til kun i reference_projects.json (+ dets docs-fixture) kjører gjennom porteføljen uten endring i noen kjerne-modul. Referanse-domenet er pakket som dokumentert eksempel, med extension-point-dok for «legg til egen metode / egen datakilde / egen modell-map».

Non-Goals

  • Concurrent/parallell fan-out (asyncio.gather / ConcurrentBuilder) — kun sekvensiell; concurrency er et dokumentert extension point (90%-prinsipp).
  • Dynamisk/runtime prosjekt-topologi — kun fast konfig-liste.
  • Rik porteflje-rapport (HTML/markdown-dashboards) — kun tynt aggregat-struct.
  • Per-prosjekt isolert VerdictStore — vi deler bevisst én store (kryss-prosjekt-læring).
  • Live Azure/Foundry-kjøring som gate — offline syntetisk forblir default; ekte-profil-arm forblir gated/skipped (Standing item SC9, uendret).
  • Compliance-funksjoner (D3 — uendret invariant; deployer eier DPIA/ROS).

Constraints

  • Ingen kjerne-modul-kodeendring for å legge til et nytt prosjekt (det bærende genericitets-kriteriet). Å legge til et prosjekt = JSON-konfig + docs-fixture, intet annet.
  • Per-prosjekt docs_dir MÅ være konfig-drevet (et felt i prosjekt-konfigen, resolvet av loaderen) — IKKE en hardkodet id→sti-mapping i orkestrator-kode. Ellers brytes SC1 for det NESTE prosjektet. (Selve mekanismen å støtte per-prosjekt-docs_dir er en engangs kjerne-endring under Fase 3-bygget; det er den marginale kostnaden ved å legge til prosjekt N+1 etterpå SC1 måler.)
  • Kostnadsdisiplin (D6): utvikle/verifiser på lokal/offline syntetisk profil; ingen tunge live-kjøringer.
  • Deterministisk validator forblir obligatorisk + blokkerende (uendret).
  • MAF 1.9.0; uv; Python ≥3.10. Eksisterende run_project-signatur er per-prosjekt-sømmen (gjenbruk — IKKE fork/dupliser logikken).
  • STATE.md local-only (uendret).

Preferences

  • Gjenbruk run_project som-det-er (det eksponerer allerede store= og client_factory=-sømmene) — orkestratoren komponerer det, dupliserer ikke logikken.
  • Match eksisterende modul-stil: frozen dataclasses for resultat-typer, type hints overalt, ruff/mypy-rene.
  • Tynt aggregat som frozen dataclass (f.eks. PortfolioResult) som speiler RunResult-stilen.

Non-Functional Requirements

  • Suite forblir grønn offline (syntetisk), ingen ny live-avhengighet.
  • Isolasjons-testen MÅ være load-bearing: den FALLER hvis eksekverings-state bleed-er mellom prosjekter (f.eks. delt meter) — ikke green-but-dead (Fase 2-lærdommen, load-bearing-tests-quality).
  • uv run mypy src ren; uv run ruff check . ren.

Success Criteria

  • SC1 — Nytt prosjekt kun via konfig: et nytt syntetisk prosjekt lagt til KUN i reference_projects.json (+ docs-fixture, inkl. dets konfig-drevne docs_dir) kjører gjennom orkestratoren og produserer et RunResult for det. Bevis: git diff --name-only for tilføyelsen viser KUN endringer i *.json + docs-fixture + tester — null src/portfolio_optimiser/*.py-kjernemodul.
  • SC2 — Fan-out over ≥2 prosjekter: orkestratoren returnerer ett RunResult per prosjekt + ett aggregat. Falsifiserbart: uv run pytest tests/test_portfolio.py exit 0, asserterer len(result.runs) == N og at aggregatet summerer korrekt.
  • SC3 — State-isolasjon er load-bearing: en test beviser at per-prosjekt eksekverings-state (budsjett-meter) IKKE bleed-er; testen FALLER hvis orkestratoren endres til å dele én meter over alle prosjekter (detach-bevis). Foretrukket: enkod detach-en som en automatisk guard (inject en delt meter i orkestratoren, assert at isolasjons-testen da blir rød) framfor kun en manuell reviewer-revert — manuelle detach-bevis hoppes over under tidspress (Fase 2-feilmodus).
  • SC4 — Delt VerdictStore akkumulerer: etter fan-out inneholder den delte storen én verdict per prosjekt, og et senere prosjekts ExpeL-retrieval kan overflate en tidligere prosjekt-dom for et likt forslag. Assert: store.verdicts har lengde N, og retrieved er ikke-tom for en matchende feature i et senere prosjekt. (Retrieval keyer på _features_of(proposal) (run.py:185) — se Open Questions for fixture-kravet.)
  • SC5 — Full suite grønn offline, ingen regresjon: uv run pytest → alle nye tester passerer OG dagens grønne baseline-antall holder (current + new), de 3 eksisterende skip bevart, null nye failures; uv run mypy src ren; uv run ruff check . ren. (Baseline-tallet hentes fra faktisk suite ved plan/execute-start — IKKE hardkodet gjetning.)
  • SC6 — Extension-dok eksisterer: en doc-seksjon «legg til eget prosjekt / egen datakilde / egen modell-map» som peker på konfig-sømmene; verifiserbart ved at fila finnes og refererer reference_projects.json + modell-map.
  • SC7 — Begge profiler eksersert (offline): en parametrisert test kjører orkestratoren under profile=local OG profile=azure med den syntetiske client_factory, og asserterer at begge produserer et RunResult (kontrakt-stien faktisk kjørt, ikke bare backend instansiert). Ekte Azure-arm forblir @pytest.mark.skip (SC9 uendret).

Research Plan

No external research needed — the codebase and this brief contain sufficient context for planning. MAF-concurrency-mekanikk ble spiket i Fase 1 (Spike B) og concurrency er bevisst utenfor scope (sekvensiell valgt); run_project eksponerer allerede store= og client_factory=-sømmene planen trenger.

Open Questions / Assumptions

  • [ASSUMPTION] run_projects eksisterende store=-param tråder en delt VerdictStore korrekt over sekvensielle kall (den add-er per kall; retrieval leser tidligere). Verifiser i plan mot run.py:184-193.
  • [ASSUMPTION] Hvert run_project-kall bygger en fersk meter + fresh_workflow internt (run.py:151-161), så eksekverings-isolasjon er i stor grad iboende ved sekvensiell gjenbruk — orkestratoren må unngå å introdusere delt muterbar state. Verifiser at meteren konstrueres per kall (run.py:151).
  • [OPEN] docs_dir per prosjekt: run_project tar én docs_dir. En portefølje av N uavhengige prosjekter trenger naturlig hver sin docs-kilde. Løsningen er bundet av Constraints: per-prosjekt-docs_dir MÅ være et konfig-felt (resolvet av loaderen), ikke en kode-mapping — ellers brytes SC1. Plan ferdigstiller felt-navn + hvor det bor (i reference_projects.json per prosjekt vs en egen portefølje-konfig).
  • [OPEN] Aggregat-felter: eksakt sett (antall validert/avvist, sum claimed saving, sum tokens) ferdigstilles i plan.
  • [OPEN] SC4 fixture-design: for at et senere prosjekts retrieval skal overflate et tidligere prosjekts dom, må de to syntetiske prosjektene være laget så den syntetiske klienten gir OVERLAPPENDE features (retrieval keyer på _features_of: affected codes, measure_type, claimed saving). Plan må designe fixturene for et reelt feature-match, ikke oppdage gapet ved test-tid.
  • [ASSUMPTION/PROVENANCE] Nytt syntetisk prosjekt er AI-forfattet domene-innhold. SC1s nye prosjekt (kostnadskoder, enheter, enhetspriser) er AI-foreslått; operatøren er ikke domene-ekspert (user-not-domain-expert) og kan ikke fange domene-feil. Planen/fixturen markerer dataen som AI-forslag / «ikke verifisert», konsistent med eksisterende _note i reference_projects.json.

Prior Attempts

Ingen tidligere fan-out-forsøk. Fase 1 Spike B (tests/spikes/test_b_footguns.py, throwaway) testet ConcurrentBuilder state-isolasjon — informerer isolasjons-bekymringen, men brukte en ANNEN (concurrent) mekanisme enn det sekvensielle valget her. Lærdom fra Fase 2: load-bearing-tester må FALLE når sømmen detach-es (load-bearing-tests-quality).

Metadata

  • Created: 2026-06-26
  • Interview turns: 9
  • Auto-research opted in: no
  • Source: trekbrief interview

How to continue

Manual (default) — 0 research-tema, så rett til plan:

# Ingen /trekresearch (0 tema).
/trekplan --project .claude/projects/2026-06-26-fase3-portfolio-fanout

# Deretter:
/trekexecute --project .claude/projects/2026-06-26-fase3-portfolio-fanout