263 lines
14 KiB
Markdown
263 lines
14 KiB
Markdown
---
|
||
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/
|
||
```
|