fix(okf): fail-closed ingest-stempel-sjekk paa YAML-1.1-sannhetsformer [skip-docs]
_carries_complete_ingest_stamp sammenlignet generated kun mot literalen "true" og
feilet AAPENT: en pinnet ingest-writer som skrev "yes"/"on" ville sluppet det
komplette eierskaps-stempelet forbi write_concept_file uten en eneste lokal diff.
_YAML_TRUE_LITERALS ({"true","yes","on"}, case-insensitivt) er naa ENESTE vokabular,
malt mot PyYAML sin safe_load-resolver; "1"/bare "y"/"n" er bevisst utelatt siden en
YAML-leser aldri leser dem som bool. Halv-stempel forblir lovlig.
RED-foerst (tests/test_ingest_stamp_fail_closed_loadbearing.py), fire mutasjoner
alle roede mot hele suiten (904 passed/5 skipped): revert til literalen "true"
(2 roede) - over-widen til aa inkludere 1/y (1 roed) - and->or paa halv-stempel
(4 roede) - detach gaten helt (4 roede).
Ordre: 20260821T152153Z-432035430-from-portfolio-optimiser
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhxZ7XNjAFHFenWX5j9pev
This commit is contained in:
parent
56c48f6f65
commit
ed696d2650
3 changed files with 109 additions and 8 deletions
11
CLAUDE.md
11
CLAUDE.md
|
|
@ -93,6 +93,17 @@ Python ≥3.10. MAF (`agent-framework-core` 1.9.0). Pakkehåndtering: `uv`. To b
|
||||||
`IngestStampError` — mens hver halvdel alene er lovlig (kuratert innhold kan bære ett
|
`IngestStampError` — mens hver halvdel alene er lovlig (kuratert innhold kan bære ett
|
||||||
provenance-felt). Validering, ALDRI reparasjon: ingenting skrives. Uten dette kunne en kuratert fil
|
provenance-felt). Validering, ALDRI reparasjon: ingenting skrives. Uten dette kunne en kuratert fil
|
||||||
bli stille slettet av en senere re-materialisering, som fjerner nøyaktig det som bærer stempelet.
|
bli stille slettet av en senere re-materialisering, som fjerner nøyaktig det som bærer stempelet.
|
||||||
|
**`generated`-verdien er FAIL-CLOSED på YAML-1.1-sannhetsformer, ikke bare literalen `"true"`**
|
||||||
|
(funn 21.08, økt 52): `_YAML_TRUE_LITERALS` (`{"true", "yes", "on"}`, case-insensitivt) er
|
||||||
|
ENESTE vokabular, målt mot PyYAML sin `safe_load`-resolver — bare `1`/bare `y`/`n` er BEVISST
|
||||||
|
UTELATT (resolves til int/streng, aldri bool, så en YAML-leser ville uansett ikke lest dem som
|
||||||
|
stempelet). Uten dette var sjekken inert kun i kraft av at pinnet `llm-ingestion-okf v0.3.2`
|
||||||
|
skriver strengen `"true"` — en fremtidig `uv sync` mot en skrivemåte som `yes`/`on` ville latt
|
||||||
|
vakten slutte å vokte uten én lokal diff. Load-bearing MÅLT
|
||||||
|
(`tests/test_ingest_stamp_fail_closed_loadbearing.py`), fire mutasjoner alle røde mot HELE
|
||||||
|
suiten: revert til literalen `"true"` (2 røde — de nye sannhetsformene alene) · over-widen til å
|
||||||
|
inkludere `1`/`y` (1 rød) · `and`→`or` (4 røde, halv-stempel-lovligheten brutt) · detach gaten
|
||||||
|
helt (4 røde).
|
||||||
- **`IngestError` må overleve anyio-task-gruppene (kø-(x), 2026-08-03):** `stdio_client` og
|
- **`IngestError` må overleve anyio-task-gruppene (kø-(x), 2026-08-03):** `stdio_client` og
|
||||||
`ClientSession` er hver sin task group, og anyio pakker ALT som forlater en av dem i en
|
`ClientSession` er hver sin task group, og anyio pakker ALT som forlater en av dem i en
|
||||||
`BaseExceptionGroup`. Derfor nådde `stdio_call_tool`s egne feil (`mcp_tool_error`,
|
`BaseExceptionGroup`. Derfor nådde `stdio_call_tool`s egne feil (`mcp_tool_error`,
|
||||||
|
|
|
||||||
|
|
@ -326,17 +326,30 @@ class IngestStampError(ValueError):
|
||||||
forged it could be silently deleted by a later ingest run."""
|
forged it could be silently deleted by a later ingest run."""
|
||||||
|
|
||||||
|
|
||||||
def _carries_complete_ingest_stamp(frontmatter: dict[str, str]) -> bool:
|
_YAML_TRUE_LITERALS = frozenset({"true", "yes", "on"})
|
||||||
"""Whether ``frontmatter`` carries BOTH halves of the ingest ownership stamp: ``generated:
|
"""Every scalar a real YAML reader parses to boolean ``True`` (measured with PyYAML's ``safe_load``
|
||||||
true`` together with a non-empty ``ingest_manifest`` reference (ingest-spec §7).
|
core-schema resolver: ``true``/``yes``/``on``, any case, are bool; the same resolver reads bare
|
||||||
|
``y``/``n`` and ``1``/``0`` as string/int, never bool — so those are deliberately EXCLUDED here.
|
||||||
|
Widening past what a YAML reader actually resolves would over-block curated content no ingest
|
||||||
|
pipeline ever produces, on a form nothing downstream would honour as the stamp either."""
|
||||||
|
|
||||||
The test is on the COMPLETE stamp, never on the individual field names — curated content may
|
|
||||||
legitimately carry a single provenance field, and a verbatim round-trip of one half must keep
|
def _carries_complete_ingest_stamp(frontmatter: dict[str, str]) -> bool:
|
||||||
working. Values are compared the way ``parse_frontmatter`` yields them (line-oriented strings,
|
"""Whether ``frontmatter`` carries BOTH halves of the ingest ownership stamp: a ``generated``
|
||||||
quotes retained), so surrounding quotes and case are normalised away here."""
|
value a YAML reader would read as boolean ``True`` (``_YAML_TRUE_LITERALS``) together with a
|
||||||
|
non-empty ``ingest_manifest`` reference (ingest-spec §7).
|
||||||
|
|
||||||
|
FAIL-CLOSED on the value literal: the field previously matched only the exact string ``"true"``,
|
||||||
|
so a pinned ingest writer emitting any other YAML-1.1 truthy form (``yes``, ``on``) would have
|
||||||
|
slipped the stamp past this gate undetected — inert only by the accident of the pinned writer's
|
||||||
|
current output, per the CLAUDE.md ingest-stamp invariant. The test is on the COMPLETE stamp,
|
||||||
|
never on the individual field names — curated content may legitimately carry a single provenance
|
||||||
|
field, and a verbatim round-trip of one half must keep working. Values are compared the way
|
||||||
|
``parse_frontmatter`` yields them (line-oriented strings, quotes retained), so surrounding quotes
|
||||||
|
and case are normalised away here."""
|
||||||
generated = str(frontmatter.get("generated", "")).strip().strip('"').lower()
|
generated = str(frontmatter.get("generated", "")).strip().strip('"').lower()
|
||||||
manifest = str(frontmatter.get("ingest_manifest", "")).strip().strip('"')
|
manifest = str(frontmatter.get("ingest_manifest", "")).strip().strip('"')
|
||||||
return generated == "true" and bool(manifest)
|
return generated in _YAML_TRUE_LITERALS and bool(manifest)
|
||||||
|
|
||||||
|
|
||||||
def write_concept_file(bundle_dir: str, name: str, frontmatter: dict[str, str], body: str) -> Path:
|
def write_concept_file(bundle_dir: str, name: str, frontmatter: dict[str, str], body: str) -> Path:
|
||||||
|
|
|
||||||
77
tests/test_ingest_stamp_fail_closed_loadbearing.py
Normal file
77
tests/test_ingest_stamp_fail_closed_loadbearing.py
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
"""RED-FIRST: the ingest ownership stamp check must be FAIL-CLOSED on the *value*, not just on
|
||||||
|
field presence. Before this fix ``_carries_complete_ingest_stamp`` compared ``generated`` against
|
||||||
|
the single literal string ``"true"`` — a YAML-1.1 truthy form such as ``yes``/``on`` slipped the
|
||||||
|
gate open even though a real YAML reader reads it as boolean ``True`` (measured with PyYAML's
|
||||||
|
``safe_load`` core-schema resolver, the same resolver family ``ruamel``/most YAML tooling ships:
|
||||||
|
``yes``/``no``/``true``/``false``/``on``/``off``, any case, are read as bool; bare ``y``/``n`` and
|
||||||
|
``1``/``0`` are NOT — they resolve to string/int). ``okf.py`` stays a hand-rolled, PyYAML-free
|
||||||
|
line parser (D7 constraint), so the vocabulary is a plain literal set, not a real parser call."""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from portfolio_optimiser import okf
|
||||||
|
|
||||||
|
|
||||||
|
def test_yaml_truthy_form_yes_is_recognized_as_the_stamp(tmp_path) -> None:
|
||||||
|
"""``generated: yes`` is read as boolean True by a real YAML reader, so it is the SAME stamp
|
||||||
|
as ``generated: true`` and must be refused identically."""
|
||||||
|
fm = {"type": "reference", "generated": "yes", "ingest_manifest": "bygg@0123456789abcdef"}
|
||||||
|
with pytest.raises(okf.IngestStampError):
|
||||||
|
okf.write_concept_file(str(tmp_path), "forged.md", fm, "body\n")
|
||||||
|
assert not (tmp_path / "forged.md").exists() # refused, not silently repaired
|
||||||
|
|
||||||
|
|
||||||
|
def test_yaml_truthy_form_on_mixed_case_is_recognized_as_the_stamp(tmp_path) -> None:
|
||||||
|
"""Case-insensitivity must hold for every truthy form, not only ``true``/``True``/``TRUE``."""
|
||||||
|
fm = {"type": "reference", "generated": "On", "ingest_manifest": "bygg@0123456789abcdef"}
|
||||||
|
with pytest.raises(okf.IngestStampError):
|
||||||
|
okf.write_concept_file(str(tmp_path), "forged.md", fm, "body\n")
|
||||||
|
assert not (tmp_path / "forged.md").exists()
|
||||||
|
|
||||||
|
|
||||||
|
def test_literal_true_still_refused_control(tmp_path) -> None:
|
||||||
|
"""Control: the literal the gate has always recognized must still raise — otherwise the two
|
||||||
|
assertions above could pass against a gate that raises unconditionally, proving nothing."""
|
||||||
|
fm = {"type": "reference", "generated": "true", "ingest_manifest": "bygg@0123456789abcdef"}
|
||||||
|
with pytest.raises(okf.IngestStampError):
|
||||||
|
okf.write_concept_file(str(tmp_path), "forged.md", fm, "body\n")
|
||||||
|
assert not (tmp_path / "forged.md").exists()
|
||||||
|
|
||||||
|
|
||||||
|
def test_non_yaml_boolean_forms_are_not_treated_as_the_stamp(tmp_path) -> None:
|
||||||
|
"""``1`` and bare ``y`` are NOT read as boolean True by a real YAML reader (measured: PyYAML
|
||||||
|
resolves them to int ``1`` and string ``"y"``, never bool) — widening the true-vocabulary to
|
||||||
|
include them would over-block curated content no ingest pipeline ever produces, and no YAML
|
||||||
|
reader would read as the stamp either. Both write through unchanged, same as any other
|
||||||
|
unrecognized ``generated`` value paired with a manifest."""
|
||||||
|
for value in ("1", "y", "maybe"):
|
||||||
|
name = f"curated-{value}.md"
|
||||||
|
okf.write_concept_file(
|
||||||
|
str(tmp_path),
|
||||||
|
name,
|
||||||
|
{"type": "reference", "generated": value, "ingest_manifest": "bygg@0123456789abcdef"},
|
||||||
|
"body\n",
|
||||||
|
)
|
||||||
|
assert (tmp_path / name).exists()
|
||||||
|
|
||||||
|
|
||||||
|
def test_half_stamp_remains_legal_generated_alone(tmp_path) -> None:
|
||||||
|
"""CLAUDE.md's ingest-stamp invariant: either half of the stamp alone is still legal curated
|
||||||
|
content — even when that half is written in a non-``true`` truthy form."""
|
||||||
|
okf.write_concept_file(str(tmp_path), "a.md", {"type": "reference", "generated": "yes"}, "b\n")
|
||||||
|
assert (tmp_path / "a.md").exists()
|
||||||
|
|
||||||
|
|
||||||
|
def test_half_stamp_remains_legal_manifest_alone(tmp_path) -> None:
|
||||||
|
okf.write_concept_file(
|
||||||
|
str(tmp_path),
|
||||||
|
"b.md",
|
||||||
|
{"type": "reference", "ingest_manifest": "bygg@0123456789abcdef"},
|
||||||
|
"c\n",
|
||||||
|
)
|
||||||
|
assert (tmp_path / "b.md").exists()
|
||||||
|
|
||||||
|
|
||||||
|
def test_unstamped_file_written_as_before(tmp_path) -> None:
|
||||||
|
okf.write_concept_file(str(tmp_path), "c.md", {"type": "reference"}, "d\n")
|
||||||
|
assert (tmp_path / "c.md").exists()
|
||||||
Loading…
Add table
Add a link
Reference in a new issue