docs(ingest): the chain from ingest to run does not close by itself
Walked Door A from a fresh clone: materializing the file-family golden
manifest writes index.md plus one concept file per extraction, and pointing
the run at the result is refused —
run refused: IR projection not found in bundle: 'validator-input.json'
A clean fail-fast, but nothing adopter-facing said it was coming, while the
README actively invites it ("swap --bundle-dir for your own bundle"). The
run path needs the bundle's IR projection, which ingest does not and cannot
produce: ingest materializes source documents, the projection states the
candidate measure. Both docs now say so, with the shape reference named.
Also corrects a live-doc claim that was wrong in both halves: the MCP
timeout is `anyio.fail_after` nested inside both task groups, not
`asyncio.wait_for`, and `tests/test_ingest_golden_mcp.py` covers it
(verified — 2 passing timeout tests). And no bundled example ships a
`cost-baseline.json`, so the text no longer implies one does.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GyAbxJoyypnLLUDcMvnKh8
This commit is contained in:
parent
a109a703e2
commit
bd221e6349
2 changed files with 37 additions and 1 deletions
|
|
@ -79,6 +79,10 @@ uv run python -m portfolio_optimiser.run BYGG-KONTOR-NORD \
|
|||
|
||||
Ends in `ValidatedProposal`. Swap `--bundle-dir`/`--docs-dir` for your own bundle to run it over
|
||||
your own data — that is the point of this door, and the reason it is not the same thing as step 2.
|
||||
Your bundle needs one file the ingest layer does not write for you: `validator-input.json`, the
|
||||
candidate the deterministic validator judges (a bundle without it is refused, by design — see
|
||||
[`docs/extending.md`](docs/extending.md)). Copy the shape from
|
||||
`shared/examples/bygg-energi-mikro/`.
|
||||
|
||||
**4 — Watch it say no.** Raise `claimed_saving_nok` to `250000` in `replies.json` and run the same
|
||||
command again. The outcome becomes `Rejection`: the deterministic validator refuses a saving the
|
||||
|
|
|
|||
|
|
@ -59,6 +59,27 @@ seam from the retriever above: one JSON **manifest** per source coupling declare
|
|||
and a list of extractions; `materialize(...)` runs the connector for that type and writes an OKF
|
||||
bundle.
|
||||
|
||||
**An ingested bundle is not yet a runnable bundle — the chain does not close by itself.** Walked
|
||||
from a fresh clone (2026-08-05): materializing `examples/ingest-golden-file/manifest.json` writes
|
||||
exactly `index.md` plus one concept file per extraction, and pointing the run at it is refused:
|
||||
|
||||
```
|
||||
run refused: IR projection not found in bundle: 'validator-input.json'
|
||||
```
|
||||
|
||||
That is a clean fail-fast, not a crash — but nothing before this paragraph told you it was coming.
|
||||
The run path additionally needs the bundle's **IR projection**, `validator-input.json` (the
|
||||
candidate the deterministic validator judges — `shared/method-spec.md` §7.1), which the ingest
|
||||
layer does **not** produce: ingest materializes *source documents*, while the IR projection states
|
||||
the *candidate measure*, and no connector can infer one from the other. A bundle may also ship an
|
||||
optional `cost-baseline.json`; without it the validator still runs, but unanchored to the
|
||||
project's real cost lines. Both are hand-authored today. For the IR projection the shape reference
|
||||
is `shared/examples/bygg-energi-mikro/validator-input.json`; **no bundled example ships a
|
||||
`cost-baseline.json`** (checked), so its shape — `{code: {quantity, unit_cost}}` — comes from
|
||||
`ir.CostBaseline` and the README. Writing them from ingested content is
|
||||
unbuilt, and is not on the 90 %-principle side of the line: what candidate to propose is the
|
||||
agents' job, not the connector's.
|
||||
|
||||
> **Where to change it (2026-07-20):** `ingest.py` is a thin adapter — Door A is implemented by
|
||||
> the shared [`llm-ingestion-okf`](https://git.fromaitochitta.com/open/llm-ingestion-okf) library
|
||||
> (git-pinned to `v0.3.1`), so connectors, materialization and index generation improve in ONE
|
||||
|
|
@ -171,7 +192,18 @@ its `retrieve_cost_docs(query)` has a required parameter (verified — it return
|
|||
The two are separate seams by design: `build_mcp_server` serves the agents' retrieval path.
|
||||
|
||||
**Still true:** MCP remains **unwired in the optimiser run path** — the in-process `FunctionTool`
|
||||
seam stays the default there. The timeout path (`asyncio.wait_for`) is not covered by a test.
|
||||
seam stays the default there.
|
||||
|
||||
**The timeout path moved, and it is covered.** This paragraph used to say the deadline was
|
||||
`asyncio.wait_for` and that no test exercised it; both halves are now out of date. Measuring
|
||||
against a genuinely hanging server showed `asyncio.wait_for` never produced a `TimeoutError` at
|
||||
all — it cancels the call from *outside* the structure anyio owns (`stdio_client`, `ClientSession`),
|
||||
and the two cancellation mechanisms do not compose; the observed outcome was a
|
||||
`BrokenResourceError` inside an exception group. The deadline is therefore `anyio.fail_after`
|
||||
nested *inside* both task groups, and only the scope that hit its own deadline
|
||||
(`CancelScope.cancelled_caught`) earns the `mcp_timeout` code — any other `TimeoutError` is
|
||||
re-raised untouched, since the builtin is also `socket.timeout` and `asyncio.TimeoutError`.
|
||||
`tests/test_ingest_golden_mcp.py` pins both the deadline and that discriminator.
|
||||
|
||||
**Where the D7 sibling stands (målbilde §11 boundary).** The Claude Agent SDK sibling built the
|
||||
**file/CSV and SQL** connectors — mirroring I3/I5 — with bit-identical golden extractions. **HTTP
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue