Commit graph

10 commits

Author SHA1 Message Date
d74f32dc1c feat(p19): a cost code must have a FORM where the input offers forms
P18's round 2 ended with two VALIDATED proposals whose affected_item codes
were ordinary words from a road standard's prose -- impulsventilator (4 of
270 N500 documents) and bituminoest baerelag (4 of 1133 N200). Both are
grounded in P7's sense and neither is inert in P18/B1's sense; they are simply
not identifiers of a cost line, and the gate had no stage that could say so.
Known positive MEASURED, not asserted: replayed offline against the bases
those runs were given, both come back Rejection naming the denominator.

IDENTIFIER_FORMS moved from generate.py to validator.py: they now drive both
P8's report and this gate, and two copies of "what an identifier looks like"
would let the two disagree about one run's own input.

B1 -- two new forms, transcribed from measurement. R761's requirement numbers
are bare dotted numbers and all six refs in kontrakt-sorasen's fasit are of
that shape, which neither pre-P19 form matched: r761's whole offer was 3
identifiers over 6.5 MB, and is now 2332. The FIRST form was widened in the
same pass because B2 made these forms decide prose vs identifier, and this
repo's own ENERGI-TOTAL-EL matched none of them -- a gate may only be wrong in
the direction that admits too much.

Three things keep the gate from being a rule about shapes: the generality
guard (it fires only where the input offers forms), the baseline exemption
(stage 0 has already ruled that code real), and full-matching.

Honesty limit, measured and given its OWN arm: a decimal and an R761 process
number are typographically identical, so the form counts both. P8's existing
"bare numbers" arm is narrowed to bare INTEGERS accordingly.

Measured over all nine round-1+2 outboxes: 26 of 36 codes are prose.

Load-bearing measured (22 arms), four mutations all red against the whole
suite (16 / 10 / 1 / 2), green control 1734/5 and the golden byte-unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 02:11:29 +02:00
a790ce3ae1 fix(hosting): _run_kwargs reads _REFUSED_BY_NAME instead of a hardcoded literal
MAJOR-2 review, fokuspunkt 4: `_REFUSED_BY_NAME = ("proposal_review",)` was
documented as the tuple the door reads, but the `if "proposal_review" in
payload` refusal never consulted it — two copies of one fact, free to drift.

Måling FØR bygging: `grep -rn 'no terminal to answer' tests/` gave 0 treff,
men `grep -rn proposal_review tests/ | grep -i hosting` fant
`test_proposal_review_loop_loadbearing.py:1677` (T21) — so half of the fact
(refusing `proposal_review` itself, with the exact CLI-pointing message) WAS
already pinned. The unpinned half was the second name: any OTHER entry added
to `_REFUSED_BY_NAME` fell through to the generic `unknown field(s)` check
instead of being refused by name before it.

Fix: `_run_kwargs` now iterates `_REFUSED_BY_NAME`; `proposal_review` keeps
its verbatim message, any other name gets a message naming the field.

New test `test_the_named_refusal_reads_the_constant_not_a_literal`
(tests/test_hosting_loadbearing.py) is unit-level against `_run_kwargs`
directly: confirms an unlisted name is refused generically (control), then
monkeypatches `_REFUSED_BY_NAME` wider and confirms the SAME payload is now
refused by name, before the generic check.

Mutation check (done and reverted): setting `_REFUSED_BY_NAME = ()`
temporarily turns the EXISTING T21 test red — proving the constant is now
load-bearing. Verified against pre-fix code that the same mutation left T21
green (the old literal-based `if` never consulted the constant at all), so
the fix closes the exact drift the review flagged.

Suite: 1368 passed / 5 skipped (was 1367/5 on 17998af), 0 regressions.
ruff + mypy clean. Golden demo-transcript.stdout byte-unchanged
(shasum -a 1 = ea8c534773acdbe41ae68f2c55724d69aaf8be4f).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 07:21:04 +02:00
baae7507a9 feat(okf): den erklaerte bundle_id-en er identiteten - mountet er en filsystem-tilfeldighet
S7a-3 pkt. 1. Til i dag NEKTET reconcile_bundle_id en base som erklaerte en id
katalogen ikke bar. Maalt mot K2 - den foerste leverte basen som erklaerer sin
egen id (618 av 630 konseptfiler + rot-index, alle "k2-trinn1-20260903", levert
som "K2-bundle-20260903") - betydde det at basen ikke kunne aapnes slik den var
levert, og at eneste botemiddel var aa montere den paa nytt for haand, en gang
per leveranse. PM-beslutning: konsumenten slakker.

- Erklaert vinner (B1s rekkefoelge uroert), avviket REGISTRERES:
  ResolvedBundleId.mount + ProvenanceStamp.bundle_id_source +
  DryRunReport.bundle_id_source + run.bundle_id_notice (None ved enighet).
  Stempel-feltet er PAAKREVD uten default: None er en VERDI (veg-stien).
- Det som fortsatt nekter er den EKTE kollisjonen: to KONSEPTER i en base som
  erklaerer ULIKE id-er (okf.assert_declared_ids_agree, kalt ved hver doer som
  aapner en base). Rot-index er IKKE med i enighets-settet - konsept-slaar-index
  er en presedens-regel, saa en index i utakt er fallbacken som taper.
- KONSEKVENS, ikke scope-krype: explore._bundle_index loeser naa den erklaerte
  id-en. Den brukte Path(raw).name mens dispatcheren brukte reconcile...id; med
  erklaert-vinner ville explore() myntet approaches som navngir MOUNTET mens
  dispatcheren ruter paa ERKLAERINGEN - en utforskning med uruterbart mandat.

Load-bearing MAALT: 10 mutasjoner alle roede mot HELE suiten, groenn kontroll
1243 passed / 5 skipped og golden demo-transcript.stdout byte-uendret
(shasum -a 1 = ea8c534773acdbe41ae68f2c55724d69aaf8be4f).
M1 1 / M2 1 / M3 1 / M4 9 / M5 2 / M6 1 / M7 1 / M8 2 / M9 2 / M11 1.

Tre armer i test_bundle_id_reconciliation_loadbearing er SKREVET OM (ikke
slettet) - de pinnet nekten beslutningen fjernet. (j) ble skarpere enn den den
erstattet: erklaert id ruter, mountet nektes.

Kontrakt: docs/okf-konsum-kontrakter.md § 3.1. Invariantrad i CLAUDE.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 06:47:58 +02:00
56f4f6d084 feat(hitl): ekspertdommen kan ikke oppstaa av stillhet (F2, ORDRE 20260825T214801Z)
run_project KREVDE verdict_input og kjorte capture_verdict ubetinget; CLI-en
defaultet det til {"approved", "reviewed by expert"} og hosting listet det som
PAAKREVD. Netto: hver flaggloes kjoering myntet en ekspertgodkjenning ingen ga,
den gikk inn i den delte storen, og run_portfolio bar den inn i neste prosjekts
hypotese-prompt som en prior expert verdict -- paa flaten som ble overlevert
14.08. Non-goal 3, brutt i en soem.

RunResult.verdict er naa Verdict | None, og None er hva stillhet produserer:
ingenting myntes, ingenting lagres, ingenting varsles. Prinsippet sto allerede i
repoet -- RunFailure sin docstring: aa fylle et felt med en dummy legger
FABRIKKERT proveniens inn i aggregatet.

Traceability koster ingenting: RunResult.verdict_key (property, derivert fra
kandidaten) er verdicts.verdict_key sitt alt dokumenterte formaal -- identisk
med verdict.id naar en dom BLE gitt, og fortsatt meningsfull naar ingen ble det.
Det er den outboxen og den hostede responsen stempler.

Halv dom NEKTES paa begge doerer (FeedbackContract er eneste sted formen
valideres; CLI-en nekter ved navn FOER enhver mode-dispatch). Validering, aldri
reparasjon. De to mode-partisjonene fikk --decision/--rationale inn: kommentarene
sa ordrett at en aerlig nekt var uimplementerbar fordi de non-None
argparse-defaultene gjorde en eksplisitt verdi uskillbar fra defaulten -- med
defaultene borte er den implementerbar.

Hosting er WIDENING, ikke bryting: verdict_input flyttet fra _REQUIRED_FIELDS
til _OPTIONAL_FIELDS. Ingen ekstern kaller brekker.

AERLIGHETS-GRENSE: referanse-fixturens SYNTETISKE verdict_input-rader staar
uroert -- de er merket SYNTETISK paa fire steder og er reviewens F5 (maaling av
misjonspaastanden), ikke F2. Project.verdict_input er naa valgfri.

Load-bearing MAALT (tests/test_ungiven_verdict_loadbearing.py, 15 armer), aatte
mutasjoner alle roede mot HELE suiten + gronn kontroll 1080/5 og golden
demo-transcript.stdout BYTE-UENDRET (ea8c534773acdbe41ae68f2c55724d69aaf8be4f).
En mutasjon falsifiserte testen foerst (vakuoes-gate-klassen, ellevte gang):
--report-armen brukte et bart --report, som nekter rc 1 uansett fordi --ledger
mangler.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 01:22:07 +02:00
234c8d138a feat(explore): U4 kallsted 2 - hostet explore_prompt, og whitelisten blir en TREDELING (ORDRE 20260823T204216Z) [skip-docs]
Kallsted (2) av oerkt 57s fire.

explore_prompt + explore_contract whitelistes paa den hostede flaten. De er ikke
run_project-parametre - utforskningen kjoerer FOERST og gir run_project et Mandate -
saa de KONSUMERES i stedet for aa videresendes, og whitelisten er dermed en
TREDELING (_REQUIRED / _OPTIONAL / _CONSUMED). Ett sted (_run_kwargs) avgjoer hvilke
felt som naar signaturen: et konsumert felt som blir liggende i kwargs er et argument
run_project ikke har, som containeren svarer 500 paa for det som egentlig er en
wiring-feil.

FASE 4e-REGELEN UTVIDET MED EN NEGATIV HALVDEL, og det var paakrevd: 4e-testen
sender HVERT whitelistet felt gjennom den EKTE run_project og asserterer dekning mot
_ALLOWED_FIELDS - en assert et konsumert felt ALDRI kan oppfylle. Den positive
halvdelen dekker naa _REQUIRED|_OPTIONAL (hvert felt maalt mot
inspect.signature(run_project)), og den negative at _CONSUMED er DISJUNKT fra samme
signatur. Uten den ville et felt som glir fra konsumert til videresendt vaere nettopp
driften 4e finnes for.

Fire nekter, alle ved navn og alle paa KALLERENS kanal (400):
explore_contract uten explore_prompt - explore_prompt uten explore_contract -
explore_prompt uten bundle_dir - enable_plan_review=true.

Den siste er nektet HER og ikke i explore(), som ogsaa nekter den: ExplorationError
er en RuntimeError, saa aa overlate den til sloeyfa ville svart en kallers
konfigurasjonsfeil paa KRASJ-kanalen (500) - samme sammenblanding BudgetExceeded
fikk sin egen 429 for aa avslutte. U13-doera er dessuten SYNKRON: den blokkerer
sloeyfa paa et menneske, og en HTTP-forespoersel har ingen - invocationen ville
hengt i stedet for aa svare. TracingConfigError er derimot en ValueError, saa 400-
armen dekket den alt (verifisert, ikke antatt).

Load-bearing MAALT (tests/test_explore_callsites_loadbearing.py, 6 nye tester), fire
mutasjoner alle roede mot HELE suiten + groenn kontroll 996/5: videresend de
konsumerte feltene igjen (2 roede) - detach mandate=-wiringen (1 roed) - detach
enable_plan_review-nekten (1) - detach bundle_dir-kravet (1).

Golden-transkriptet byte-uendret (ea8c534773acdbe41ae68f2c55724d69aaf8be4f).
mypy + ruff rene.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YRZhBJcxqTcqWyMW6hBttx
2026-08-25 09:25:01 +02:00
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
986fc19350 feat(1b): et tak som fyrer er ikke en krasj — BudgetExceeded får sin egen kanal [skip-docs]
Prosjektets første levende kjøring døde på `rounds limit=12 observed=13`, og den
hostede flaten svarte `500 {error_type, error}` — nøyaktig det samme den sier når
modell-endepunktet faller. Nå: 429 med trippelen som STRUKTUR.

Beslutningen er S3.4-invarianten anvendt på transporten: `budget_stop` ble holdt
utenfor `stop_reason` fordi de to stoppene betyr motsatte ting, og å svare
ressurs-utmattelse på krasj-kanalen gjør «det gikk ikke» uleselig på samme måte.

IKKE 200, og det er dét som skiller den fra `Rejection`: en `Rejection` er en
kjøring som KONKLUDERTE og hører i payloaden, mens et uttømt budsjett produserte
ingen proposal — en 2xx ville latt en automatisk kaller bokføre «analysert» for en
kjøring som analyserte ingenting. 429 fordi betingelsen oppstår av en TILDELING
(`max_rounds`/`max_tokens` er whitelistede request-felt), aldri av en serverfeil.

`kind`/`limit`/`observed` legges ut som felt, aldri `str(exc)` (kø-(y));
`error_type` holdes ute — den nøkkelen tilhører feilkanalen. `budget_exhausted` er
ikke foldet inn i `outcome_type` og kunne ikke vært det: `outbox.outcome_payload`
er den ene kopien av den forgreningen og tar `ValidatedProposal | Rejection`.
Ærlighets-grense: ingen `Retry-After` — å vente endrer ingenting.

Iron Law: begge nye tester RØDE før armen fantes. Fem mutasjoner mot HELE suiten,
alle røde med hver sin signatur, grønn kontroll 867/4: detach armen (2 røde) ·
flat streng i stedet for struktur (1 rød — struktur-testen alene) · ekko `limit`
som `observed` (1 rød) · utvid armen til `Exception` (6 røde) · stemple
`error_type` på budsjett-kroppen (1 rød).

500-armens vitne ble BYTTET, ikke slettet: den eksisterende testen brukte
`BudgetExceeded` som sin 500-prøve, så en ny arm alene ville etterlatt
krasj-kanalen uten vitne. Den bærer nå en ekte ikke-budsjett-`RuntimeError`, og er
dét som holder den nye armen smal.

Kjørt, ikke bare testet: `python main.py` startet, `/readiness` 200, ukjent felt →
400 med navnet, 404, SIGTERM → exit 0.

865 → 867 passed / 4 skipped; ruff + format + mypy rene. DEPLOY.md §6 dokumenterer
429 for mottakeren.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W1bsX79aDS7fJ5udWGWAEN
2026-08-14 17:06:11 +02:00
605c02aa62 feat(5): overleveringspakka er KUN kjørbar Python — container-innpakningen fjernet
Operatørdirektiv 14.08 etter ekstern test: mottakeren skal få kjørbar Python, ikke en
Docker-innpakking. `Dockerfile` og `azure.yaml` er SLETTET fra treet.

Sømmen er valgt av den eksisterende invarianten, ikke av smak: pakka ER `git archive HEAD`,
så å ekskludere filene fra arkivet ville krevd en kurerings-mekanisme — den andre kopien av
«hva mottakeren får», fri til å drifte fra HEAD (kø-(p)). Fjerning holder arkivet ukurert og
gjør fraværet til en egenskap ved HEAD, som er det eneste en gate kan måle.

De to gatene som pinnet flaten er håndtert bevisst:
- 4e-rå-tekst-gaten (`--platform linux/amd64` + ÉN kopi av startkommandoen) er SLETTET, med
  et notat der den sto. En gate som pinner en fjernet flate kan bare bli grønn.
- handover-gatens `_REQUIRED_MEMBERS` er ikke bare fratatt de to navnene, men erstattet av en
  POSITIV fraværs-assert pluss en dokument-gate. Å kun slutte å KREVE dem ville gitt en gate
  som ikke kan skille «fjernet» fra «shippes fortsatt».

Startkommandoen har nå ÉN kopi igjen: DEPLOY.md-ens `python main.py`, som navngir inngangen
subprosess-testen faktisk kjører.
2026-08-14 14:09:35 +02:00
18dbd7d4d6 test(4e): whitelisten mot EKTE run_project + rå-tekst-gate på deploy-artefaktene
De to gjenstående 4e-radene, begge målt mot hele suiten (837 passed / 4 skipped).

(1) /invocations svarer gyldig mot en SKRIPTET backend gjennom EKTE run_project.
Alle 4d-testene ga invoke en stand-in som sluker **kwargs, så whitelisten kunne
navngi et felt run_project ikke tar — eller sende samme argument to ganger — uten
at én test merket det, mens en levende container svarte 500. Sømmen er
run._default_factory, ikke payloaden: client_factory nektes av whitelisten med
vilje, så factory-defaulten er eneste injeksjonspunkt flaten etterlater.
Payloaden sender HVERT whitelistet felt, med en dekningsassert mot
_ALLOWED_FIELDS. Profilen er LOCAL fordi AZURE-armen slår opp et
Foundry-deployment-navn i modell-mappet FØR noen klient bygges (målt).

(2) Rå-tekst-gate: Dockerfile + azure.yaml kjøres av ingen test (docker build og
azd deploy er operatør-gatet). Gaten pinner --platform linux/amd64 (målt påkrevd)
og ÉN kopi av startkommandoen (imagets CMD; azure.yaml har ingen startupCommand).
Nøkkel-sjekkene er linjeforankret, ikke delstreng — azure.yaml sin egen kommentar
navngir begge nøklene for å begrunne fraværet.

Fem mutasjoner, alle røde på riktig test og på INGEN annen (836 øvrige grønne
hver gang): send project_id to ganger · whitelist et felt run_project ikke tar ·
fjern bundle_dir fra whitelisten · fjern --platform linux/amd64 · gi azure.yaml en
startupCommand-nøkkel. Kontroll: pristine tre 837/4.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018GfbDLY7YVLKVqpUHnbwVW
2026-08-14 00:03:20 +02:00
426ccb0ad6 feat(4d): hostet inngang — main.py wrapper rundt run_project på én asyncio-løkke
To målinger avgjorde formen FØR koden: (1) hosting-pakkas InvocationsHostServer
finnes kun i bygg som krever agent-framework-core>=1.13.0 (treet låser 1.9.0;
eneste 1.9-kompatible bygg er en forlatt alfa som importerer mcp udeklarert),
(2) et gjenbrukt bygget workflow er single-use på 1.9.0 (kall-serie [2,0,0] —
rundetaket persisterer; ferskt objekt per kall er ren kontroll). Derfor spikens
§5-fallback: hosting.py serverer kontrakten (8088/PORT, /readiness,
/invocations, SIGTERM→0) selv, stdlib asyncio på ÉN løkke — aldri as_agent()
(gatene ligger utenfor grafen), aldri tråder (NG1-guarden fanget første utkast
med ThreadingHTTPServer; asyncio-formen består den by construction).

Payload whitelistes på run_projects signatur — ukjente felt nektes ved navn
(400), aldri stille droppet; profile defaulter til azure kun her. ValueError →
400, alt annet → 500 {error_type, error}; Rejection er vellykket kjøring → 200.
outbox.outcome_payload ekstrahert som den ENE kopien av validated/rejected-
forgreningen (kø-(p)-regelen). azure.yaml validert GRØNN mot begge autoritative
skjemaer (jsonschema, hentet ferskt); ingen env:, ingen startupCommand (imagets
CMD er den ene kopien). Dockerfile: 3.12-slim-bookworm + git + uv==0.9.8 +
uv sync --frozen --no-dev; git archive <indeks-tre> | docker build
--platform linux/amd64 grønn på nøyaktig de stagede bytene.

Iron Law fulgt: testfila rød ved collection FØR modulen fantes. 835 passed /
4 skipped (fra 821), ruff+format+mypy rene. Seks mutasjoner mot HELE suiten,
alle røde på riktig test: detach felt-mappingen · dropp ukjente felt stille ·
flipp 400/500 · detach azure-defaulten · detach SIGTERM-handleren · detach
main.py-shimen (de to siste kun fanget av subprosess-testen, P4-presedensen).
Deploy IKKE utført — azd-steget er operatørens.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PEiiSGRShizKc771ZBa1iq
2026-08-13 23:31:02 +02:00