feat(ingest): I5 — SQL D7-speil, bygget fra commons-spec alene

Speiler MAF I4 fra shared/ingest-spec.md alene: manifest → SQL-konnektor →
materialisert OKF-bundle, byte-identisk med den delte golden-fasiten. Gaten
I4→I5 verifisert løst mot ground truth (MAF-commits d7e5f2f/4f45fe6/1b7612b)
før arbeidet startet. Spec byte-identisk delt, ingen spec-endring (I4).

- ingest.py: SqlSource (type: sql, id, connection_ref); ManifestContract.source
  er nå diskriminert union FileSource | SqlSource på type (http/ukjent tag →
  fail-fast). _render_sql_cell (§5 typed: NULL→"", int→decimal, float→korteste
  round-trip, str→verbatim m/ delt _escape_cell, annet→fail — aldri stille
  coercion). _resolve_connection_ref (env-oppslag §4/§8, usatt → fail-fast).
  _read_sql (read-only sqlite file:?mode=ro, ett SELECT, max_rows §8).
  _read_extraction dispatcher på source.type; materialisering/index/replacement
  uendret fra I3.
- examples/ingest-golden-sql/: repo-lokal golden (byte-frossen kopi av I4s fasit).
- Speiltester (I4s load-bearing-sett, gjennom SQL-konnektoren, detach-bevist røde):
  sql-golden byte-fasit + mutasjonskontroller · typed-cell/NULL (NY §I5-søm) ·
  provenance/navigability/verdict-reservasjon/re-ingest-safety · SqlSource-kontrakt/
  typed-rendering/connection_ref/max_rows/read-only · spec-integritet utvidet med
  connection_ref. Stale type:"sql"-avvisningscase erstattet (sql er gyldig post-I5).
- docs/2026-07-04-I5-brief.md: brief + premiss-verifisering.

Suite 265 passed uten nøkkel/nettverk (239 + 26 nye) · ruff + mypy --strict rene.

[skip-docs] README + docs/extending.md er bevisst utsatt til I7 per sesjonsplan
(programmet batcher ingest-doc der, avgrenset til det D7 faktisk har — CSV + SQL nå,
HTTP/MCP kun pekere). Dokumentert i docs/2026-07-04-I5-brief.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017MM6BWb1hWmJZuXFZ7rjxT
This commit is contained in:
Kjell Tore Guttormsen 2026-07-04 07:18:59 +02:00
commit 32640deffc
13 changed files with 615 additions and 9 deletions

View file

@ -0,0 +1,74 @@
# I5 — D7-speil av ingest (SQL-kilde) — brief
> Forankret i hovedrepoets sesjonsplan
> `~/repos/portfolio-optimiser/docs/plan/2026-07-03-sesjonsplan-ingest.md` §I5 og dette
> repoets STATE. Bygget **fra `shared/ingest-spec.md` alene** — aldri ved reverse-engineering
> av MAF-koden. Golden-**bytene** (den delte fasiten fra MAF I4) er lest, siden spec §11 gjør
> dem til konformanskravet begge stacker reproduserer. Gaten I4→I5 ble verifisert løst mot
> ground truth (MAF-commits `d7e5f2f`/`4f45fe6`/`1b7612b`, «I4») før arbeidet startet.
## Mål (§I5)
Andre kildetype konsumert **uendret** fra samme delte kontrakt i søskenrepoet: D7s uavhengige
SQL-konnektor reproduserer MAF I4s golden **byte-identisk** fra spec alene. Scope = `sql`
(lokal SQL-database, sqlite-fixture, null nettverk). `file` var I3; `http` = ikke i D7
(målbildet: HTTP/MCP demonstreres MAF-only, I6).
## Premiss-verifisering (før impl)
- **Spec byte-identisk delt:** `diff -q shared/ingest-spec.md` mot MAF = IDENTISK. Spec dekker
`sql` fullt (§4 `connection_ref`, §5 typed-cell/NULL, read-only SELECT). I4 gjorde **ingen
spec-endring** (sesjonsplan §I4) → subtree-pull er no-op (bekreftet, ikke kjørt — ingen drift).
- **Golden-stamp:** SHA-256(manifest)[:16] = `a4b891aea55df97b` reprodusert lokalt → matcher
`ingest_manifest: manifest@a4b891aea55df97b` i forventet bundle.
- **Ingen nye operatør-beslutninger:** I3s to avgjørelser (golden repo-lokal kopi; okf relaksert
for frontmatterløs index) bærer uendret inn i I5. Én lukket økt, ingen commons-runde.
## Leveranse
- `examples/ingest-golden-sql/` — repo-lokal golden (byte-frossen kopi av MAF I4s; `diff -r`
tomt): `manifest.json`, `fixture/portefolje.sqlite`, `ingested-at.txt`, `expected-bundle/`.
- `src/portfolio_optimiser_claude/ingest.py` — SQL-utvidelse:
- `SqlSource`-modell (`type: "sql"`, `id`, `connection_ref`); `ManifestContract.source` er nå
en **diskriminert union** `FileSource | SqlSource``type` (ukjent/`http`-tag → fail-fast).
- `_render_sql_cell` — §5 typed rendering: `None`(NULL)→`""`, `int`→plain decimal, `float`
korteste round-trip (`repr`), `str`→verbatim (§5-escaping via delt `_escape_cell`), annet
(BLOB o.l.)→**fail** (aldri stille coercion).
- `_resolve_connection_ref` — env-var-oppslag (§4/§8: referanse ved navn, secret aldri i
manifest; usatt ref → fail-fast).
- `_read_sql` — read-only sqlite (`file:…?mode=ro`), ett SELECT (driver håndhever
én-statement), `max_rows` fail-fast (§8).
- `_read_extraction` — dispatch på `source.type` (`file`→CSV, `sql`→SQL); `materialize`
kaller den (materialisering/index/replacement uendret fra I3).
- Speiltester (I4s load-bearing-sett, gjennom SQL-konnektoren):
- `test_ingest_sql_golden.py` — byte-fasit + idempotens + 3 mutasjonskontroller (endret rad /
timestamp / manifest-bytes).
- `test_ingest_sql_loadbearing.py` — typed-cell/NULL (NY §I5-søm) · provenance · navigability ·
verdict-reservasjon (fail-fast før DB åpnes) · re-ingest-safety.
- `test_ingest_sql.py``SqlSource`-kontrakt, `_render_sql_cell`-enheter, connection_ref
fail-fast, `max_rows`-tak, read-only-håndhevelse.
- `test_ingest_spec_loadbearing.py``connection_ref` lagt til kontrakt-felt-guarden (D7s
sql-impl avhenger nå av at spec dokumenterer feltet).
- `test_ingest.py` — stale `type:"sql"`-avvisningscase erstattet (sql er gyldig post-I5) med
`http`/ukjent-diskriminator-avvisning (honesty-regelen).
## SDK-kroker (STATE steg 4 — søk-først)
I5 (SQL) introduserer **ingen SDK-flate**: ingest er deterministisk, null modellkall. Søk-først
SDK-krok-reverifisering (API-drift på `create_sdk_mcp_server`) er relevant for I6 (HTTP/MCP),
ikke I5 — utsatt dit kroken faktisk brukes. Eksisterende SDK-klient uendret og grønn i suiten.
## Verifisering (kjørt, grønn)
- Golden **byte-identisk** konsumert (5/5, inkl. idempotens + mutasjonskontroller).
- Speiltester **røde ved detach** — de nye sømmene bevist load-bearing (detach-bevis-skript):
typed-cell/NULL · connection_ref fail-fast · sql-dispatch · spec-integritet (`connection_ref`).
- Søskensuite **grønn uten nøkkel/nettverk**: 265 passed (239 + 26 nye).
- `ruff check` + `ruff format` rene · `mypy --strict src`: no issues (16 filer).
## Åpent (forslag, ikke utført — utenfor I5-scope)
- **Standard-evolusjon:** «typet index overalt» som bevisst OKF-endring forblir en egen
koordinert commons-RFC (begge specs/golden/okf). Dagens permissive okf er forover-kompatibel.
- **Åpent-speil-synk:** I3/I5-koden er ikke pushet til det offentlige `open/`-speilet
(outward-facing → egen operatør-handling).