portfolio-optimiser/pyproject.toml
Kjell Tore Guttormsen a629902660 build(maf): lift the pin to core 1.18.0, and lift foundry/openai WITH it -- measured, not assumed
core 1.16.0 -> 1.18.0 turns the red guard green. The other three floors are
set to what `uv sync` actually installs, measured after the sync:

  core          1.16.0 -> 1.18.0   (latest, 2026-09-10)
  foundry        1.8.2 -> 1.13.0   (latest, 2026-09-10)
  openai         1.8.2 -> 1.14.3   (latest, 2026-09-10)
  orchestrations  1.1.1 -> 1.1.1   UNCHANGED -- 1.1.1 is still the latest release

TWO of the order's premises were felled by measurement, and the measurement
wins:

(1) `orchestrations` cannot be lifted -- there is nothing to lift it to
    (1.1.1, 2026-08-21, is still the head on PyPI). F15's comment "the two
    floors are lifted TOGETHER -- there is no partial bump" was true of F15's
    bump; after F16 it is measurably weaker, and the comment now says so.

(2) foundry/openai were NOT forced up. The order read their coupling off the
    LATEST releases (both require core>=1.17.0), but the PINNED 1.8.2 requires
    only core<2,>=1.9.0 -- so the resolver kept 1.8.2 against core 1.18.0 and
    resolved cleanly. They are lifted anyway, for a different and measured
    reason: two of the 1.17/1.18 BREAKING items name core AND foundry in the
    SAME bullet (#7918 sequence-only middleware inputs, #8127 SecretString), so
    a 1.9.0-era foundry against a 1.18.0 core is half of a coordinated change
    -- exactly the "still imports, semantics moved silently" class F15 measured.
    Config A (core 1.18.0 + foundry/openai 1.8.2) was measured green on the
    guard, the 19 async-plan-review tests and the golden transcript, and that
    is stated as the measured alternative rather than hidden.

Full suite 1577 passed / 5 skipped -- identical to the pre-bump baseline
measured on this HEAD, 0 tests lost or added. Both goldens BYTE-UNCHANGED
(stdout ea8c534773acdbe41ae68f2c55724d69aaf8be4f, stderr
ede3e2f685ce6a14ad9888e9de421d1a66f6c611); #8219's lazy Foundry/OpenAI loading
did NOT move stderr, which stays two lines through the UNCHANGED normaliser.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 17:06:47 +02:00

126 lines
9 KiB
TOML
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.

[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.18.0,<2", # kjerne (GA) — to-sidig pin (S2.5): major-bump krever re-verifisering av privat-API-premissene (test_maf_version_guard). F16 (12.09): løftet fra 1.16.0; 17 private/ugaranterte former, 16 sjekket mekanisk av proben, se docs/2026-09-12-f16-maf-1180.md. F15 (02.09) løftet 1.9.0→1.16.0.
# F16 (12.09): løftet 1.8.2 → 1.13.0 SAMMEN med `-openai` og `core`, og koblingen er MÅLT, ikke antatt.
# foundry 1.13.0 krever selv `core<2,>=1.17.0` OG `openai<2,>=1.14.2`, så de tre kan ikke løftes hver for
# seg. Resolveren ville BEHOLDT 1.8.2 (den krever bare `core<2,>=1.9.0`, målt) — men to av 1.17/1.18s
# BREAKING-endringer navngir core OG foundry i SAMME punkt (#7918 sequence-only middleware-inputer,
# #8127 `SecretString`), så et 1.9.0-æra foundry mot en 1.18.0 core er halvparten av en koordinert
# endring — nøyaktig «importerer fortsatt, semantikken har flyttet i stillhet»-klassen F15 § 4 målte.
"agent-framework-foundry>=1.13.0", # 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.14.3", # OpenAI + OpenAI-kompatible lokale endpoints (GA) → lokal profil; F16: løftet 1.8.2 → 1.14.3 (foundry 1.13.0 krever `openai>=1.14.2`; se raden over)
# Promotert dev→core i Fase 2 (MVP-runtime, ikke lenger spike-only):
# F16 (12.09): dette gulvet STÅR på 1.1.1, og det er en MÅLING, ikke en utelatelse: 1.1.1 (2026-08-21) er
# fortsatt siste utgivelse på PyPI, så det finnes ingenting å løfte det til. Kravet `core<2,>=1.15.0` er
# tilfredsstilt av core 1.18.0. F15s formulering «de to gulvene løftes SAMMEN — det finnes ingen delvis
# bump» var sann for F15s bump; etter F16 er den MÅLT SVAKERE: core kan løftes uten at orchestrations kan.
"agent-framework-orchestrations>=1.1.1", # GA orchestration builders (GroupChat/Concurrent/Magentic)
"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",
# U14 (økt 55, operatørbeslutning 23.08): the tracing seam's ONE new runtime dependency.
# `opentelemetry-api` was already here transitively via agent-framework-core, but the SDK is
# what `configure_otel_providers()` needs — without it MAF raises ModuleNotFoundError, so the
# seam cannot land as an opt-in without declaring it. The console exporter this framework uses
# (`opentelemetry.sdk.trace.export.ConsoleSpanExporter`) ships INSIDE this package, so console
# mode costs exactly one declaration. The OTLP exporter packages are deliberately NOT declared:
# they are egress, they drag grpc/protobuf into a published wheel, and MAF already raises a
# named ImportError telling the operator which one to install. Stated honesty limit, not an
# oversight — see tracing.py.
"opentelemetry-sdk>=1.42,<2",
# 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.