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
This commit is contained in:
parent
fc167deb59
commit
4cdd172522
1 changed files with 202 additions and 0 deletions
202
.claude/projects/2026-06-26-fase3-portfolio-fanout/brief.md
Normal file
202
.claude/projects/2026-06-26-fase3-portfolio-fanout/brief.md
Normal file
|
|
@ -0,0 +1,202 @@
|
||||||
|
---
|
||||||
|
type: trekbrief
|
||||||
|
brief_version: "2.2"
|
||||||
|
created: 2026-06-26
|
||||||
|
task: "Fase 3: orkestrator fan-out over N porteføljeprosjekter med state-isolasjon + delt læring"
|
||||||
|
slug: fase3-portfolio-fanout
|
||||||
|
project_dir: .claude/projects/2026-06-26-fase3-portfolio-fanout/
|
||||||
|
research_topics: 0
|
||||||
|
research_status: skipped
|
||||||
|
auto_research: false
|
||||||
|
interview_turns: 9
|
||||||
|
source: interview
|
||||||
|
framing: refine
|
||||||
|
phase_signals:
|
||||||
|
- phase: research
|
||||||
|
effort: standard
|
||||||
|
- phase: plan
|
||||||
|
effort: high
|
||||||
|
model: opus
|
||||||
|
- phase: execute
|
||||||
|
effort: high
|
||||||
|
model: opus
|
||||||
|
- phase: review
|
||||||
|
effort: high
|
||||||
|
model: 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_project`s 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:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 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
|
||||||
|
```
|
||||||
Loading…
Add table
Add a link
Reference in a new issue