portfolio-optimiser/pyproject.toml
Kjell Tore Guttormsen 28a420ab97 feat(3): flaten sier sant om seg selv, og to gater måler at den fortsetter å gjøre det
Fase 3 (AAA+ på publisert flate). Tre av planens premisser falt på måling og er
rettet FØR handling, ikke etterpå:

* GOVERNANCE-raden hadde feil tiltak. Planen sa «skriv den»; org-ops D11 sier én
  kanonisk fil som hvert repo LENKER, og filen er nå publisert (målt: HTTP 200 på
  open/repo-standard). Å skrive vår egen ville gjort oss til kopi nr. 12 av en
  fil D11-bølgen holder på å rydde vekk. README lenker den, i samme form som
  repo-mailbox bruker, og bus-faktor 1 står uttalt i den kanoniske teksten.
* Release-objektet for v1.0.0 FINNES allerede på open/ (id 155, CHANGELOG-kropp,
  siden rendrer) — det som mangler er vedlegg, ikke objektet.
* WARN RELEASE-STALE fyrer ikke, og kan ikke: regelen sammenligner utgivelse mot
  tagg og er strukturelt blind for repo med null utgivelser (org-ops hovedbok
  #18). Gaten var OK/20 sjekker FØR arbeidet startet, så den kan ikke tjene som
  verifikasjon for denne fasen. Bevisene er Forgejo-APIet, filinnholdet og
  ren-klon-kjøringen.

A5-defekten rettet: env.template:21 sa at credential resolves via
DefaultAzureCredential. Den har aldri gjort det — backends.py:149 konstruerer
ManagedIdentityCredential eller AzureCliCredential, og Learns MAF-veiledning
navngir den spesifikke credentialen NETTOPP for å unngå probing. En operatør som
kopierte templaten ble fortalt at feil identitet ville bli brukt.

To load-bearing gater (Iron Law: begge røde før fiksen, 2 failed / 7 passed):

1. env.template navngir de credentials backends.py faktisk konstruerer, og ingen
   linje utgir DefaultAzureCredential for å være mekanismen. LINJEFORANKRET, ikke
   delstreng: backends.py NAVNGIR klassen fire ganger i kommentarene som
   begrunner hvorfor den ikke brukes, så en fil-bred substring-gate ville vært
   rød på nøyaktig den prosaen den beskytter (repoets 08-09-klasse, fjerde gang).
2. README-ens wheel-filnavn bærer versjonen bygget stempler på fila. Uten den
   ville en versjonsbump stille etterlatt en publisert install-kommando som peker
   på en fil som ikke finnes.

Hver positiv assert er paret med en KONTROLL på at det søkes etter noe som
finnes — en ekstraktor som stille finner null lager en gate som bare kan bli
grønn.

MUTASJONER MÅLT MOT HELE SUITEN, begge røde på riktig test og på INGEN annen:
gjeninnfør den usanne credential-påstanden (2 røde, 844 grønne) · la
wheel-filnavnet drifte til 1.0.0 (1 rød, 845 grønne). Restaurert fra scratchpad
+ shasum -c mellom hver. Bumpen selv var den andre mutasjonen: pyproject 1.0.0 →
1.1.0 gjorde README-gaten rød alene, før README ble rettet.

SECURITY.md: varslingsfrist (minst én minor-release og aldri under 30 dager
mellom kunngjøring og fjerning, med sikkerhetskritisk fjerning som uttalt
unntak). Støttetabellen er bevisst VERSJONSFRI — et release-nummer skrevet der
ville drevet ved neste tagg, altså samme defektklasse som gate 2 fanger.

CLAUDE.md beholdt på flaten med en engelsk innramming øverst (operatørvalg): den
sier hva fila er for en fremmed. Innholdet er repoets sterkeste bevis på at hver
beslutning er målt; å fjerne det ville fjernet bevis, ikke friksjon.

Versjon 1.1.0 — synket i pyproject, __init__, test_smoke og README-kommandoen.
1.0.0-treet kan ikke produsere en kjørbar wheel (force-include kom etter taggen,
målt: git show v1.0.0:pyproject.toml har den ikke), så en wheel hengt på den
utgivelsen ville vært nøyaktig den usanne påstanden denne fasen finnes for å
fjerne. Operatøren valgte bumpen framfor et vedlegg som ikke virker.

846 passed / 4 skipped (fra 837). ruff + format + mypy rene.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ckyg3Pc6k7FRuR6fDGQLJ
2026-08-14 06:57:25 +02:00

105 lines
6.8 KiB
TOML
Raw Permalink 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.

[project]
name = "portfolio-optimiser"
version = "1.1.0"
description = "Generic framework on Microsoft Agent Framework for per-project cost-savings optimization"
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
# MAF — GA-pakker, IKKE meta-pakken `agent-framework`. Metaen drar `agent-framework-core[all]`,
# og `[all]` trekker inn de fortsatt-beta integrasjonene (azure-ai-search/cosmos/ollama/…) som
# tvinger pre-releases og drar med en ALPHA pydantic. Offisiell guide: installer kun det du trenger.
# Beta-integrasjoner legges til per-fase (med snevert pre-release-scope) når de faktisk trengs.
"agent-framework-core>=1.9.0,<2", # kjerne (GA) — to-sidig pin (S2.5): major-bump krever re-verifisering av privat-API-premissene (test_maf_version_guard)
"agent-framework-foundry>=1.8.2", # Azure/Foundry-profil: FoundryChatClient (GA)
"azure-identity>=1.25", # S4.1: AzureFoundryBackend passes an explicit AzureCliCredential (Foundry requires it); already transitive via foundry — promoted to a declared direct dep (zero new install weight)
"agent-framework-openai>=1.8.2", # OpenAI + OpenAI-kompatible lokale endpoints (GA) → lokal profil
# Promotert dev→core i Fase 2 (MVP-runtime, ikke lenger spike-only):
"agent-framework-orchestrations>=1.0.0", # GA orchestration builders (GroupChat/Concurrent/Magentic); resolves med core 1.9.0
"pulp>=2.8", # deterministisk validator-solver; PuLP bundler CBC i wheelen (R2). Installert 3.3.2.
# PuLP 4.0 vil kreve `pip install pulp[cbc]` + COIN_CMD (Fase-migrasjonsnotat).
"mcp>=1.28.0", # tynn lokal-mappe MCP-server (Step 7) — GA (resolverte 1.28.0) per Step 1-beslutning
"anyio>=4.14", # kø-(z): ingest_mcp.py bruker anyio.fail_after direkte (MCP-timeout-stien); allerede transitiv via mcp — promotert til deklarert direkte dep (zero new install weight, resolverte 4.14.0)
"pydantic>=2.11,<3", # IR/validering (B1) — eksplisitt pin til STABIL 2.x, aldri alpha
# S3.1: brute-force cosine for the hybrid verdict retriever — MAF-free, offline (D-C).
# Upper bound is <2.3, NOT <3, and it is load-bearing twice over: numpy 2.3+ requires
# Python >=3.11 (2.4+ requires >=3.12), so a wider range silently contradicts this project's
# `requires-python = ">=3.10"`; and numpy 2.3+ stubs use PEP 695 `type` statements, which
# mypy refuses to parse under `python_version = 3.10` (python/mypy#18701) — breaking
# `uv run mypy src` outright. Raise both floors together or not at all.
"numpy>=2.0,<2.3",
"llm-ingestion-okf", # Door A ingest (§4§6) — the shared implementation of shared/ingest-spec.md; zero runtime deps, MAF-free (D7)
"llm-ingestion-guard", # Door A content gate — scans materialized concepts before they reach the bundle; zero runtime deps, MAF-free (D7)
]
# Console entry points — the install surface a fresh clone gets from `uv sync` (P4 pkt. 5).
# Deliberately TWO, not five: `run` is the framework CLI (three documented modes) and `simulation`
# is the offline end-to-end proof the README points a newcomer at. `costsim` / `hitl` / `preflight`
# keep the `python -m` form — they are operator utilities, not the product's front door, and every
# name here is a name the freeze has to carry. Pinned by tests/test_console_entry_points.py.
[project.scripts]
portfolio-optimiser = "portfolio_optimiser.run:main"
portfolio-optimiser-demo = "portfolio_optimiser.simulation:main"
# Distribution channel for the shared ingest library (mirrors portfolio-optimiser-claude,
# verified in consumer CI): git pin against the public Forgejo repo — reproducible for every
# consumer, uv.lock pins the exact commit behind the tag. Bump the rev on a new library tag.
[tool.uv.sources]
llm-ingestion-okf = { git = "https://git.fromaitochitta.com/open/llm-ingestion-okf.git", rev = "v0.3.2" }
# Pinned to a TAG, never a floating ref — a security component whose version can move under a
# `uv sync` is a gate that can stop gating without a local diff (the okf `generated`-literal
# lesson, measured 2026-08-09). Bumping this is a decision, not maintenance.
llm-ingestion-guard = { git = "https://git.fromaitochitta.com/open/llm-ingestion-pipeline-security.git", rev = "v0.3.4" }
# Dev tooling as a PEP 735 dependency-group (uv includes it by default in `uv sync`/`uv run`),
# so the documented bare `uv sync` + `uv run pytest` workflow installs it without `--extra`.
#
# INTENTIONAL, RECORDED DEVIATION (Fase 2 review F4): the original plan Step 1 authorized only
# moving `agent-framework-orchestrations` + `pulp` from the dev extra into core. Migrating the
# dev group from `[project.optional-dependencies]` to this PEP 735 `[dependency-groups]` block
# went beyond that stated scope, but is KEPT (not reverted) because it is what makes the brief's
# Success Criterion SC1 ("bare `uv sync` + `uv run pytest`") install dev tooling without
# `--extra` — reverting would break SC1. Logged here so the deviation is no longer silent.
[dependency-groups]
dev = [
"pytest>=8",
"ruff>=0.6",
"mypy>=1.11",
"pytest-asyncio>=0.24", # MAF orchestrations are async (await workflow.run(...)) — test-only
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["src/portfolio_optimiser"]
# Fase 4a: the wheel CARRIES shared/ (spec, persona skill, example bundles) as packaged data —
# a byte-identical mirror under portfolio_optimiser/_shared/, resolved by shared_root() only when
# no working tree is present (checkout wins; PORTFOLIO_SHARED_ROOT wins over both). This is what
# makes an installed wheel — and a container — work without a clone, while shared/ itself stays a
# pull-only subtree at the repo root. Pinned by tests/test_shared_packaged_data_loadbearing.py.
[tool.hatch.build.targets.wheel.force-include]
"shared" = "portfolio_optimiser/_shared"
[tool.ruff]
line-length = 100
src = ["src", "tests", "spikes"]
[tool.pytest.ini_options]
pythonpath = ["src", "."]
testpaths = ["tests"]
asyncio_mode = "auto"
[tool.mypy]
python_version = "3.10"
# Third-party libs without bundled type stubs (py.typed) — analysed as untyped, not errors.
[[tool.mypy.overrides]]
module = ["pulp.*", "agent_framework_foundry.*", "llm_ingestion_guard.*"]
ignore_missing_imports = true
# NOTE on `llm_ingestion_guard`: the override alone would make the seam type-BLIND, not
# type-safe — every symbol arrives as `Any`, so a field rename upstream would type-check
# happily and fail at runtime. `ingest.materialize_gated` therefore coerces each value it
# reads off the guard's result objects to a concrete type at the boundary (the okf precedent),
# and the seam's behaviour is pinned by tests/test_ingest_content_gate_loadbearing.py.