--- type: trekbrief brief_version: "2.2" created: 2026-07-03 task: "I2 — MAF reference implementation of the ingest layer: file catalogue/CSV (offline)" slug: i2-ingest-csv-maf project_dir: .claude/projects/2026-07-03-i2-ingest-csv-maf/ research_topics: 0 research_status: skipped auto_research: false interview_turns: 0 source: manual framing: preserve phase_signals: - phase: research effort: high model: fable - phase: plan effort: high model: fable - phase: execute effort: high model: fable - phase: review effort: high model: fable --- # Task: I2 — MAF reference implementation: ingest file catalogue/CSV (offline) > Generated by `/trekbrief` on 2026-07-03. > This brief is the contract between requirements and planning. `/trekplan` > reads it to produce the implementation plan. Every decision in the plan must > trace back to content in this brief. > > **Provenance note (framing defense):** this brief was derived autonomously from > two FROZEN, operator-approved sources — the I2 section of > `docs/plan/2026-07-03-sesjonsplan-ingest.md` and the normative > `shared/ingest-spec.md` — under the STATE.md operating model ("Les STATE.md og > følg instruksjonene" = go-signal for the NEXT step; I2 is not gated). No > interview questions were asked because every answer already exists in written, > approved form; re-asking would violate the operator's recorded > plan-over-menu preference. `framing: preserve` is documented, not guessed: > I2 continues the frozen plan arc unchanged (STATE.md: "I2 [NESTE]"). ## TL;DR Implement the ingest layer's first source type on the MAF side, exactly as frozen in `shared/ingest-spec.md`: fail-fast pydantic manifest contract, CSV/file-catalogue connector, deterministic materialization into an OKF bundle with provenance frontmatter + index generation — offline, zero model calls, TDD. Framing: **preserve** — the "I2 [NESTE]" step of the operator-approved, frozen ingest session plan. ## Intent The 8-step optimiser loop consumes hand-curated OKF bundles today; the frozen ingest målbilde closes the gap to real data sources while preserving the architecture-defining rule that **data reaches the model ONLY via OKF bundles** (no RAG, no query-time retrieval — method spec §3). I2 is the first implementation session of that program: it proves the frozen contract (`shared/ingest-spec.md`, authored and guarded in I1) is actually implementable from the spec alone, on the reference (MAF) side, using the offline `file` source type so the whole session stays at zero model calls and zero network (cost discipline D6). It also closes the plan's key assumption 2 — that an ingest manifest can be validated fail-fast without network — which is currently marked RISK with only pattern-level evidence (`contracts.py` is in-process). Finally, I2 turns the planning session's ephemeral ad-hoc proof (a synthetic generated bundle passing `navigate_bundle`/`bundle_context`) into a durable load-bearing test, so the seam can never silently detach. ## Goal `uv run pytest` green (without network or credentials) on a repo where: a pydantic manifest contract validates ingest manifests fail-fast per spec §4 (polymorphic on `source.type`, verdict-layer reservation enforced before any source call); a file-catalogue/CSV connector executes extractions per §4 (boundary-checked path resolution, `max_rows` fail-fast); materialization per §5–§7 writes `ingest-{id}.md` concept files with the exact 7-key provenance frontmatter and generates/updates `index.md` per §6 (idempotent by target, curated/promoted links preserved); and a golden extraction case at `examples/ingest-golden-file/` (spec §11 layout) reproduces byte for byte from fixture + fixed `ingested_at`. The existing loop consumes the generated bundle through UNCHANGED `okf.py` navigation code. Every seam in the I2 load-bearing set has a test proven RED at detach. ## Non-Goals - **SQL connector** — I4 (the pydantic manifest schema MAY validate the `sql`/`http` variants per §4, but no `sql` connector execution in I2; see Open Questions). - **HTTP/MCP connector and the network opt-in gate test** — I6 (gated: network capability). The `file` source type requires no network flag (§5, §8). - **Sibling repo (D7) work** — I3/I5 (gated: other repo). No files outside this repo. - **Commons/spec changes** — any needed spec change means STOP and a separate gated commons round (session-plan frame rule); never folded into I2. The golden case is authored LOCALLY in this repo in I2; sharing it via commons is a later gated round. - **Wiring ingest into `run_project`** — ingest is an explicit operator-run step BEFORE the loop (spec §2, §9); the run path is untouched. - **Extension points explicitly deferred by the spec:** per-row file splitting, multiple manifests per bundle, incremental re-ingest, approval registry / signed `approved_by` (spec §5, §9, §10). - **S12 release work** — independently gated (LICENSE is an operator-owned choice). Note on spec conformance: full §1 conformance requires both `file` and `sql` connectors — I2 is deliberately a partial-conformance milestone; §1 conformance completes at I4. Reviewers must not read §1 as an I2 failure. ## Constraints - **The spec is normative and frozen.** Implement from `shared/ingest-spec.md` alone; MUST/MUST NOT clauses are binding. On any discovered spec defect or needed change: stop, report — separate gated commons round. - **Offline, deterministic, zero model calls** (D6 + spec §1, §11): tests run without credentials and without network; no live sources anywhere. - **TDD Iron Law:** no production code without a failing test first. Load-bearing tests must be proven RED at detach (detach → observe red → reattach), the method spec §11 regime. - **`okf.py` navigation code stays UNCHANGED** — the navigability criterion is explicitly "consumable via UNCHANGED `okf.py`". Reusing its existing primitives (e.g. the index-linking primitive, whose idempotent-by-target semantics §6 references) is expected; modifying them is not in scope. - **Context-layer neutrality invariant:** `okf.py` and `shared/` remain pure stdlib, MAF-free (`test_okf_is_maf_free`). The new ingest module makes zero model calls and MUST NOT import `agent_framework`/`mcp` (it is a deterministic pre-loop step); pydantic is fine (already a core dependency, `contracts.py` pattern). - **Commons is PULL-ONLY**; nothing in I2 writes to commons or `shared/`. - **One session = one closed delivery:** green suite, STATE.md overwritten, commit + push to Forgejo; never a broken state across the session boundary. - **Surgical scope:** new ingest module + tests + golden example; no refactoring of adjacent code. ## Preferences - Follow the existing `contracts.py` pydantic style for the manifest contract (fail-fast, explicit models, type hints everywhere). - Mirror the existing load-bearing test idiom (e.g. `tests/test_step8_promotion_loadbearing.py`): each test names its seam and fails when the seam is detached. - Reuse `okf.py`'s existing index-linking primitive for §6 index updates rather than reimplementing link semantics. - Golden case directory per spec §11 convention: `examples/ingest-golden-file/` with `manifest.json`, `fixture/`, `ingested-at.txt`, `expected-bundle/`. - Small synthetic fixture data (cost discipline — no large test payloads). - Code/comments/docs in English; Conventional Commits. ## Non-Functional Requirements - **Bit-determinism:** same source content + manifest + `ingested_at` → byte-identical bundle; repeated runs idempotent (spec §10, §11). - **Zero new runtime dependencies** (pydantic and stdlib suffice; CSV via stdlib `csv`). - **Fail-fast, never silent:** malformed manifest, verdict-typed mapping, path escape, `max_rows` overflow, non-CSV-representable cell value, curated-file collision — all ERRORS, never warnings/truncation/coercion (spec §3, §4, §5, §8). - **File hygiene per spec §5:** LF-only, exactly one trailing newline, single-line frontmatter values, whitespace-collapsed `source_query`. - **Source calls are logged** (which source, when, row count — spec §8); test-visible via stdlib `logging`, no new dependency. - **Contract completeness:** the plan traces spec §12's cross-check table field-by-field (`manifest_version` … `generated`, golden-case entries) so no contract field is silently dropped — including `bundle_summary` (§4/§6) and the `ingest_manifest` stamp format `{stem}@{hash16}` (§5). ## Success Criteria All commands run from the repo root; "without network/credentials" is the standing test condition (spec §11). 1. **Golden extraction, bit-deterministic:** a test materializes `examples/ingest-golden-file/` from `manifest.json` + `fixture/` + `ingested-at.txt` and compares against `expected-bundle/` file by file, byte for byte — `uv run pytest tests/ -k ingest` exits 0. Running materialization twice over the same target produces identical bytes (idempotence, §10). 2. **Load-bearing: provenance detach** — with the §7 stamping detached (e.g. provenance keys not written), the suite goes RED. Proof recorded (detach → red → reattach) in the session log. 3. **Load-bearing: navigability via UNCHANGED `okf.py`** — the generated bundle is consumed by the existing navigation code (`navigate_bundle`/`bundle_context` seam): generated files reachable via index cross-links, unknown provenance fields preserved. Test goes RED if index generation/linking is detached. `git diff` for the delivery shows no modification to `okf.py`. 4. **Load-bearing: verdict reservation** — a manifest with `okf_type: verdict` (case-insensitive) or a generated-filename collision with `index.md`/the `promoted-verdict-*` namespace is REJECTED at validation, before any source call; test goes RED if the reservation is removed. 5. **Load-bearing: re-ingest layer safety** — re-materialization over a bundle containing a promoted verdict (created via the real `promote_verdict`) preserves the verdict file AND its index link; test goes RED if replacement semantics stop honouring the ingest-stamp ownership rule (§3, §6). 6. **Fail-fast manifest validation without network (closes key assumption 2):** malformed manifests (missing fields, bad `id` grammar, empty `extractions`, non-positive `max_rows`, unknown `source.type`) raise before any source access — asserted by tests that run with no network available. 7. **Full suite + gates green:** `uv run pytest` exits 0 — the pre-existing suite passes unchanged (baseline captured at session start before any I2 change) plus the new ingest tests; `uv run ruff check .` exits 0, `uv run ruff format --check .` clean, `uv run mypy src` exits 0. 8. **Honesty of artefacts:** no artefact delivered in I2 claims SQL/HTTP support, live-source integration, or commons sharing of the golden case. Concretely: case-insensitive grep for `sql`, `http`, `live`, `commons` over the new module's docstrings, new test files' docstrings, and any touched README/docs sections yields no support-claiming hits (mentions that explicitly DEFER — "I4", "I6", "extension point" — are fine); the plan fixes the exact grep invocation. ## Research Plan No external research needed — the codebase and this brief contain sufficient context for planning. Rationale (recorded in lieu of an interview): the task is fully specified by the frozen `shared/ingest-spec.md`; the implementation surface is stdlib + pydantic patterns already established in this repo (`contracts.py`, `okf.py`, `verdicts.py`); the session is offline by rule, so there is no external API/library/version decision to research. Key assumption 4 (D7 SDK hooks) belongs to I3, not I2. ## Open Questions / Assumptions - **[ASSUMPTION] Manifest schema breadth:** the pydantic contract is built polymorphic per spec §4 (validating `file`, `sql`, `http` source variants), while I2 implements ONLY the `file` connector execution path. This is the cheapest spec-complete reading; the plan may narrow it to `file`-only validation with justification (spec conformance for `sql` arrives in I4 either way). - **[ASSUMPTION] Invocation surface:** spec §9 requires ingest to be an explicit operator command. For I2 a public programmatic API (module function) satisfies the procedural gate for tests; whether to also add a `python -m` CLI entry in I2 is a plan decision (cheap, but not demanded by the I2 verification set). - **[ASSUMPTION] Module placement:** new module `src/portfolio_optimiser/ingest.py` (name/path is a plan decision; the constraint is only that `okf.py` stays unchanged and the module is MAF-free). - **[ASSUMPTION] Expected-bundle authoring:** `expected-bundle/` in the golden case is generated ONCE by the implementation after review of its content (then frozen in git), rather than hand-typed — acceptable because the golden test's job is regression pinning (spec §11), while correctness is carried by the unit/load-bearing tests. The plan should sequence this so the golden freeze happens LAST, after all behaviour tests are green. ## Prior Attempts None as implementation — this is the first ingest implementation session. Two prior results are load-bearing context, not failures: (1) key assumption 1 was proven ad hoc on 2026-07-03 (a synthetic bundle with provenance frontmatter passed `navigate_bundle`/`bundle_context`; unknown fields preserved, context rendered) — I2 makes that proof durable; (2) I1 delivered the frozen spec into `shared/` with the framework guard + structure test extended (`tests/test_method_spec_loadbearing.py`, commits `4df2140` + `7ba0fae`), so the contract I2 implements is already guarded in this repo. ## Metadata - **Created:** 2026-07-03 - **Interview turns:** 0 (brief derived from frozen, operator-approved plan + spec; see provenance note at top) - **Auto-research opted in:** no (0 topics) - **Source:** manual --- ## How to continue Manual (default): ```bash # No research topics — go straight to plan: /trekplan --project .claude/projects/2026-07-03-i2-ingest-csv-maf/ # Then execute: /trekexecute --project .claude/projects/2026-07-03-i2-ingest-csv-maf/ ```