portfolio-optimiser/src/portfolio_optimiser/provenance.py
Kjell Tore Guttormsen 156312c32e feat(visibility): en uforankret kjoering sier det - felt + linje (ORDRE 20260821T092039Z)
Maalt oekt 48: en bundle-kjoering uten cost-baseline.json gikk rc 0 uten et ord, og
`grep baseline provenance.py outbox.py` ga null treff - hverken stdout, stempelet eller
utboksen bar at validatorens steg 0 ble hoppet over.

To tenner, begge smaa, og begge fra kjoeringens ENE oppslag av baselinen (koe-(p)):

1. ProvenanceStamp.cost_baseline_anchored - PAAKREVD bool uten default. Begge defaults
   lyver: True lar en glemsom konstruktoer paastaa en ankring som ikke skjedde, False
   underrapporterer en ekte. Naar utboksen gratis (write_proposal dumper hele stempelet).
   DryRunReport baerer det samme - en dry-run stopper foer noe stempel finnes.
2. run.cost_baseline_notice(anchored) - ENESTE renderer, tar den alt opploeste booleanen,
   returnerer None naar kjoeringen ER forankret (omisjon, aldri en tom rad). Printes paa
   tre flater: --live-dry-run, full enkeltkjoering, og per prosjekt i portefoeljemodus.

IKKE foldet inn i mandate.announce, og det er en MAALING: den fyrer kun med --mandate, saa
nettopp de bare bundle-dry-runsene defekten ble maalt paa ville fortsatt sagt ingenting -
og den renderes foer run_project, altsaa foer noen har opploest baselinen.

Ankeringen forblir VALGFRI (en pre-amendment-base kjoerer uendret) - dette er synlighet,
ikke en ny nekt. Golden-transkriptet er byte-uendret: demoen kjoerer en base som HAR fila.

Load-bearing MAALT (tests/test_baseline_visibility_loadbearing.py, 11 tester), seks
mutasjoner alle roede mot HELE suiten + groenn kontroll 885/5: konstant stamp-wiring
(3 roede) - konstant dry-run-wiring (1) - detach dry-run-printen (1) - renderer returnerer
alltid linja (2, inkl. den forankrede kontrollen) - detach full-run-printen (1) - detach
portefoelje-printen (1). Portefoelje-armen er DEFENSIV og uttalt (ingen referanse-prosjekt
setter bundle_dir; budget_stop-presedensen, crafted PortfolioResult).

Det paakrevde feltet tvang fem eksisterende test-konstruktoerer til aa ta stilling.

Dokumentene som beskrev den gamle stillheten er rettet: kunnskapsbase-for-en-kjoring.md
S4.1 (tabellraden re-maalt live), S6 og S7; README «How it is set up»; CLAUDE.md S4.0-raden.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DA6HAF8HFQxGYC2h6ypRQe
2026-08-21 12:21:48 +02:00

94 lines
4.4 KiB
Python

"""First-class Pydantic provenance stamp.
Provenance is authoritative framework data — **independent** of MAF's ``Annotation`` type,
which silently drops on the Python streaming path (#4316, research 02 Dim 2). A
``ProvenanceStamp`` is a Pydantic model that must carry at least one ``Citation`` (a
``min_length=1`` constraint), the model + role that produced the proposal, the validator's
decision, and the token usage. ``to_annotations()`` maps to MAF ``Annotation`` dicts for
DISPLAY only — never the source of truth.
The locator type (``TextSpan``) is owned by ``retrieval.py`` (Step 5) and imported here, so
a citation's span is the same exact object the retriever produced.
"""
from __future__ import annotations
from typing import Literal
from agent_framework import Annotation, TextSpanRegion
from pydantic import BaseModel, Field
from portfolio_optimiser.retrieval import TextSpan
class Citation(BaseModel):
"""One exact-span citation into a source document (locator owned by retrieval.py)."""
file: str
locator: TextSpan
snippet: str
class ExternalCall(BaseModel):
"""One external service call a run actually made (Trekk B4).
**What this is evidence of, and what it is not.** It records that ``tool`` was invoked and which
configured ``server`` it belongs to. It is NOT evidence that the service's answer reached the
proposal, and it is not a verified rendering of what the service returned — the framework hands
the answer to the agent, and what the agent does with it is the agent's. Reading this as "the
figure came from the price register" would claim more than the record supports.
``server`` is ``""`` when the tool name cannot be attributed to exactly one configured server.
MEASURED against a real MCP stdio subprocess: MAF passes the BARE tool name to function
middleware, with no server prefix, so two servers exposing one tool name are indistinguishable
at this seam. Unattributed is the honest answer there; naming the first match would put a
service in the record that may never have been contacted.
"""
server: str
tool: str
class ProvenanceStamp(BaseModel):
"""Authoritative provenance for one proposal — at least one citation is mandatory."""
citations: list[Citation] = Field(min_length=1)
model: str
role: str
validator_decision: Literal["validated", "rejected"]
token_usage: int
#: Was the deterministic gate ANCHORED to the project's own cost lines? ``True`` means
#: ``validate_proposal`` ran its stage-0 reconciliation (every ``affected_item`` checked against
#: a real ``CostBaseline`` line, within tolerance, BEFORE the solver); ``False`` means the
#: bundle shipped no ``cost-baseline.json``, so that stage was SKIPPED and the gate reasoned
#: only about numbers the proposal itself supplied. Anchoring stays OPTIONAL (a pre-amendment
#: bundle is legitimately un-anchored) — this field does not gate anything, it makes the skip
#: legible. A STRUCTURED field rather than prose, for the reason ``BudgetExceeded`` carries
#: ``kind``/``limit``/``observed`` as fields (kø-(y)): "was the falsifier anchored" is an
#: operative question that must be readable by machine.
#:
#: REQUIRED, with no default, because both defaults lie: ``True`` would let a constructor that
#: forgot claim an anchoring that never happened, and ``False`` would under-claim a real one.
#: A binary fact about a falsifier has no honest default.
cost_baseline_anchored: bool
#: External service calls the run made (B4). EMPTY is a positive statement — "nothing outside
#: this process was contacted" — not an absent field, which is why it is always serialized.
external_calls: list[ExternalCall] = Field(default_factory=list)
def to_annotations(self) -> list[Annotation]:
"""Map to MAF ``Annotation`` dicts for display only (NOT the source of truth)."""
return [
Annotation(
type="citation",
snippet=c.snippet,
annotated_regions=[
TextSpanRegion(
type="text_span",
start_index=c.locator.start_index,
end_index=c.locator.end_index,
)
],
additional_properties={"file": c.file},
)
for c in self.citations
]