[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", # Door A ingest (§4–§6) — the shared implementation of shared/ingest-spec.md, MAF-free (D7). # NOT "zero runtime deps" since 0.4.0: llm-ingestion-guard is okf's one runtime dependency, # and okf >=0.8.5 declares `>=1.2,<2.0` for it. That is why the two lines below move together. "llm-ingestion-okf", "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.8.5" } # 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. # `tag =`, never `rev =`, and never a lower 1.x: okf >=0.8.5 pins this library itself via its own # [tool.uv.sources] `tag = "v1.4.0"`, and uv treats `rev = "v1.4.0"` as a DIFFERENT url from # `tag = "v1.4.0"` at the same value — both spellings were measured to fail resolution as # "conflicting URLs" (P13 § 2a). The declared floor okf asks for is >=1.2, but 1.2.0 is not # choosable for the same reason. llm-ingestion-guard = { git = "https://git.fromaitochitta.com/open/llm-ingestion-pipeline-security.git", tag = "v1.4.0" } # 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.