feat(tracing): U14 - sporing er opt-in, og "av" betyr at MAF aldri kalles (ORDRE 20260823T165757Z)

PORTFOLIO_OTEL er eneste bryter, lest paa truthiness. Uten den kalles
configure_otel_providers ikke i det hele tatt: spans lages fortsatt
(ENABLE_INSTRUMENTATION defaulter True) og kastes, saa ingenting KAN forlate
prosessen. `console` skriver spans til stderr - demoens stdout er byte-identisk
med fasiten, maalt. `otlp` eksporterer over nett, og kun mot et endepunkt
operatoeren selv har navngitt.

To regler er MAALT, ikke valgt (observability.py:849 bygger exporter-lista i
fast rekkefoelge):

- enable_console_exporters sendes EKSPLISITT False i begge moduser. Overlatt til
  miljoeet faller den tilbake paa ENABLE_CONSOLE_EXPORTERS, hvis
  ConsoleSpanExporter skriver til STDOUT - nettopp det S6 maalte som oedeleggende
  for goldenen.
- `console` NEKTER naar en OTEL_EXPORTER_OTLP_*_ENDPOINT finnes: env-avledede
  exportere bygges UBETINGET og FOER vaare, saa ordet "console" ville vaert en
  usann paastand om hvor kjoeringens innhold tok veien. Validering, ALDRI
  reparasjon - vi fjerner ikke operatoerens variabel bak ryggen paa dem.

Tre kallsteder (run.main, simulation.main, hosting.main): demoen er et skriptet
bevis, ikke produktet, og en soem bare demoen naar ville latt de to inngangene en
virksomhet faktisk kjoerer vaere usporbare. tracing_notice er ENESTE renderer og
returnerer None naar sporing er av - omisjon, aldri tom rad.

IKKE bygget, med grunn: PLAN_CREATED/REPLANNED/PROGRESS_LEDGER_UPDATED hoerer til
sloeyfa U4 bygger; en emitter uten kallsted er en form gjettet i stedet for maalt.
OTLP-exporter-PAKKENE er bevisst ikke deklarert (egress + grpc/protobuf-vekt i et
publisert wheel); uttalt i README/DEPLOY/env.template.

Ny dep: opentelemetry-sdk>=1.42,<2 (operatoerbeslutning 2, 23.08). EN pakke, ikke
to - ConsoleSpanExporter bor inne i sdk-en. opentelemetry-api fulgte med
1.42.1 -> 1.44.0, maalt uskadelig.

Load-bearing MAALT (tests/test_tracing_loadbearing.py), ni mutasjoner alle roede
mot HELE suiten + groenn kontroll 943/5. Tre av de roede bor i tester som fantes
fra foer (golden-transkriptets fire-linjers stderr + portefoelje-CLI-ens stille
pass), altsaa er omisjons-regelen gatet av uavhengige vitner.

Golden ea8c534... uendret. mypy src + ruff rene.

Ordren tar ogsaa de fire operatoerbeslutningene inn i planens paragraf F.
Laasen paa orchestrations 1.0.1 er ENDELIG (operatoerbekreftelse 23.08), ikke
midlertidig: spike-ordrens "revert hvis E7 staar" er overstyrt av den senere
beslutningen, som betinget paa groenn suite - ikke paa E7.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-08-23 20:51:46 +02:00
commit 4a19d39e63
12 changed files with 928 additions and 16 deletions

View file

@ -90,7 +90,7 @@ You need, in Microsoft Foundry:
---
## 5. Configuration — the two variables that matter
## 5. Configuration — the two variables that matter, and one that is optional
**5.1 The project endpoint.** Resolved as the first non-empty of:
@ -128,7 +128,29 @@ export PORTFOLIO_MODEL_MAP=/path/to/model_map.json
If you skip this, the service starts and answers `/readiness` — and fails every invocation. That
is the single most likely first-deployment failure, which is why it has its own section.
**5.3 Verify before you pay for a model call:**
**5.3 Tracing — optional, and off until you ask.** `PORTFOLIO_OTEL` is the only switch:
```bash
export PORTFOLIO_OTEL=console # OpenTelemetry spans on STDERR — in a container, that is your log
```
Unset, no OpenTelemetry provider is configured at all: the spans are still created and then
discarded, so nothing can leave the process. `console` writes them to stderr and leaves stdout
byte-for-byte as it was, so you can trace a run without changing what the run prints. `otlp` sends
them to a collector, and only to an endpoint you name yourself:
```bash
export PORTFOLIO_OTEL=otlp
export OTEL_EXPORTER_OTLP_ENDPOINT=http://your-collector:4317
pip install opentelemetry-exporter-otlp-proto-grpc # not a declared dependency — egress is yours to opt into
```
Asking for `console` while an `OTEL_EXPORTER_OTLP_*_ENDPOINT` variable is set is **refused**, not
quietly honoured: those exporters are constructed unconditionally by the framework underneath, so
the run would have shipped its contents to the collector while calling itself console-only. The
service names its tracing destination on stderr at startup, before the first span exists.
**5.4 Verify before you pay for a model call:**
```bash
uv run python -m portfolio_optimiser.preflight --profile azure