Commit graph

4 commits

Author SHA1 Message Date
44526254d0 fix(tests): the re-ingest fixture modelled two manifests, not one edited manifest
CORRECTION TO b33ea00. That commit message called the v0.4.0 behaviour a
regression that "breaks our §6 removal path". That claim is WRONG, and this
commit is the record of it — the history is not rewritten.

What v0.4.0 actually introduced is §10.2 per-manifest ownership.
`_is_ingest_owned` (materialize.py:133-160 at v0.5.0a2) compares the
`ingest_manifest` reference's STEM against the running manifest's stem, and
its own comment says why the stem rather than the whole stamp: an EDITED
manifest (new sha -> new stamp) must still reclaim the files its previous run
wrote, while a DIFFERENT manifest sharing the bundle keeps its own.

Our fixture named manifests `manifest-{len(extractions)}.json`, so "the same
manifest, edited" silently became `manifest-2.json` -> `manifest-1.json` —
two stems, i.e. two rival manifests. Refusing to overwrite was CORRECT
behaviour. The fixture was written when ownership was manifest-agnostic and
the naming was pure convenience; v0.4.0 made that convenience load-bearing.

`_write_project` now takes an optional `filename`, and the re-ingest case
pins one stem across both runs, so it means "an edited manifest" on every
version rather than by accident.

MEASURED both ways: green at the pinned v0.3.2 (the distinction is invisible
there), and the WHOLE suite green at v0.5.0a2 — 668 passed. So there is no
technical blocker to the version move at all. What remains is not technical:

  1. v0.4.0+ makes `llm-ingestion-guard>=0.2,<0.3` a hard runtime dependency
     (v0.3.1/v0.3.2: `dependencies = []`), which moves two invariants this
     repo publishes. Operator's call.
  2. v0.5.0a2 is an alpha its own CHANGELOG scopes to a named pilot set this
     repo is not in. llm-ingestion-okf's call; asked via coord.

The false report was corrected upstream the same hour it was sent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GyAbxJoyypnLLUDcMvnKh8
2026-08-05 12:30:08 +02:00
0a11af74a4 refactor(ingest): adopt shared llm-ingestion-okf v0.3.1 behind a thin adapter
Door A (manifest -> connector -> deterministic materialization -> index) is no
longer implemented here. src/portfolio_optimiser/ingest.py becomes a thin
consumer seam over the shared library, git-pinned to v0.3.1 on the same Forgejo
channel portfolio-optimiser-claude uses. Net -626/+385; ingest.py 599 -> 145 lines.

shared/ingest-spec.md remains the normative spec: the library implements it, it
does not replace it. Spec changes continue to go via commons.

Acceptance criterion met and proven: all three golden bundles (file/sql/http)
are byte-exact before and after, including the idempotence re-run. examples/ and
shared/ carry ZERO modifications -- the fasit was not adjusted to fit.

The rejection set was verified equivalent, not assumed: all 22 malformations the
repo's pydantic models refused are refused by the library, with typed codes
(okf_type_reserved, credential_embedded, extraction_id_duplicate, ...).

Test rebinding (invariants preserved, vehicle changed): the library has zero
runtime dependencies by design, so pydantic is unavailable to it.
ManifestV1.model_validate(dict) -> load_manifest_bytes(bytes); ValidationError ->
ManifestError; model_fields -> dataclasses.fields; PathSecurityError ->
SourceError(path_escape); ValueError -> MaterializationError(ingested_at_invalid).
Tests now also pin the refusal `code`, the library's documented stability
contract -- a sharper assertion than "some validation error was raised".

Two accepted behavioural deltas, recorded rather than silently dropped:
- Title whitespace is stored verbatim instead of collapsed at validation, so the
  frontmatter title and the index label are no longer guaranteed identical for
  irregular whitespace. Both behaviours are spec-conformant (the spec is SILENT;
  the old one was a repo-local pinned decision). Queued as a commons-amendment
  candidate so both stacks pin the same answer. Goldens unaffected.
- The section 8 audit log moves to logger llm_ingestion_okf.materialize. Nothing
  in the repo consumed the old channel.
Also: the `type` discriminator is no longer a dataclass field, so the spec
cross-check asserts it explicitly -- without that line the swap would have
silently narrowed the test.

New tests/test_ingest_library_seam.py pins the seam itself: the restated section 5
stamp formula against the stamp the library actually writes (the one place the
adapter does not purely delegate, since v0.3.1 exposes no stamp helper), the
local-only allow_network default, the list[Path] unwrapping, and a guard that the
adapter never regrows local Door A machinery. All four verified RED when detached,
as were both golden regressions under a byte-level render mutation.

Door A is UNGATED: it calls no guard before writing to disk. Gating untrusted
content remains the caller's responsibility (guard wiring still planned).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B4jNN186eVqfe1x5DnTU6r
2026-07-20 07:47:55 +02:00
e66a2b4015 style(ingest): ruff format (I2) 2026-07-03 18:40:40 +02:00
6236f2fb78 test(ingest): load-bearing seam quartet + MAF-free guard, detach-proven (I2) 2026-07-03 18:37:30 +02:00