feat(okf): bundle-import iterator (mode b) — per-concept validate+stamp, aggregate disposition, log.md (T7, TDD, +8)
This commit is contained in:
parent
eac3c91b89
commit
320a40244f
2 changed files with 171 additions and 0 deletions
|
|
@ -25,6 +25,7 @@ from llm_ingestion_guard.okf import (
|
|||
stamp_concept,
|
||||
trust_for,
|
||||
format_log_entry,
|
||||
import_bundle,
|
||||
Origin,
|
||||
Channel,
|
||||
OKFFrontmatterError,
|
||||
|
|
@ -303,3 +304,77 @@ def test_format_log_entry_prepends_timestamp():
|
|||
stamp = stamp_concept("a/b", Report(), Origin.INTERNAL, Channel.AUTOMATIC)
|
||||
line = format_log_entry(stamp, timestamp="2026-07-06T07:00:00Z")
|
||||
assert line.startswith("2026-07-06T07:00:00Z")
|
||||
|
||||
|
||||
# --- T7: bundle-import iterator (mode b) --------------------------------------
|
||||
# A received bundle is validated per concept, not as one unit: one bad concept
|
||||
# is rejected (fail-secure) and recorded, while the rest are still validated.
|
||||
|
||||
_CLEAN_A = (
|
||||
"---\ntype: table\ntitle: Users\ndescription: The users table.\n"
|
||||
"---\nA clean paragraph about the users table.\n"
|
||||
)
|
||||
_CLEAN_B = (
|
||||
"---\ntype: table\ntitle: Orders\ndescription: The orders table.\n"
|
||||
"---\nA clean paragraph about the orders table.\n"
|
||||
)
|
||||
|
||||
|
||||
def test_import_bundle_all_clean_warns():
|
||||
result = import_bundle({"tables/users.md": _CLEAN_A, "tables/orders.md": _CLEAN_B})
|
||||
assert len(result.concepts) == 2
|
||||
assert all(c.error is None for c in result.concepts)
|
||||
assert all(c.stamp is not None for c in result.concepts)
|
||||
assert result.disposition is Disposition.WARN
|
||||
|
||||
|
||||
def test_import_bundle_iterates_per_concept_not_whole_unit():
|
||||
# a reserved-name concept is rejected, but the good concept is still validated
|
||||
result = import_bundle({"index.md": _CLEAN_A, "tables/users.md": _CLEAN_B})
|
||||
by_path = {c.path: c for c in result.concepts}
|
||||
assert by_path["index.md"].disposition is Disposition.FAIL_SECURE
|
||||
assert by_path["index.md"].error is not None
|
||||
assert by_path["tables/users.md"].error is None
|
||||
assert by_path["tables/users.md"].disposition is Disposition.WARN
|
||||
|
||||
|
||||
def test_import_bundle_rejects_bad_resource():
|
||||
doc = "---\ntype: table\nresource: http://insecure.example/x\n---\nbody\n"
|
||||
c = import_bundle({"tables/x.md": doc}).concepts[0]
|
||||
assert c.disposition is Disposition.FAIL_SECURE
|
||||
assert c.error is not None
|
||||
|
||||
|
||||
def test_import_bundle_rejects_dangerous_frontmatter():
|
||||
doc = "---\ntype: &a table\n---\nbody\n"
|
||||
c = import_bundle({"tables/x.md": doc}).concepts[0]
|
||||
assert c.disposition is Disposition.FAIL_SECURE
|
||||
assert c.error is not None
|
||||
|
||||
|
||||
def test_import_bundle_flags_injection_concept():
|
||||
poisoned = "---\ntype: table\n---\n" + _INJECTION + "\n"
|
||||
c = import_bundle({"tables/x.md": poisoned}).concepts[0]
|
||||
assert c.disposition in (Disposition.QUARANTINE_REVIEW, Disposition.FAIL_SECURE)
|
||||
|
||||
|
||||
def test_import_bundle_aggregate_is_most_severe():
|
||||
poisoned = "---\ntype: table\n---\n" + _INJECTION + "\n"
|
||||
result = import_bundle({"a/clean.md": _CLEAN_A, "a/bad.md": poisoned})
|
||||
assert result.disposition in (Disposition.QUARANTINE_REVIEW, Disposition.FAIL_SECURE)
|
||||
|
||||
|
||||
def test_import_bundle_log_has_line_per_concept():
|
||||
log = import_bundle({"tables/users.md": _CLEAN_A, "tables/orders.md": _CLEAN_B}).log()
|
||||
assert len(log.strip().splitlines()) == 2
|
||||
assert "tables/users" in log and "tables/orders" in log
|
||||
|
||||
|
||||
def test_import_bundle_records_origin_channel_on_stamp():
|
||||
result = import_bundle(
|
||||
{"tables/users.md": _CLEAN_A}, origin=Origin.INTERNAL, channel=Channel.MANUAL
|
||||
)
|
||||
stamp = result.concepts[0].stamp
|
||||
assert stamp.trust is Trust.TRUSTED
|
||||
assert stamp.origin is Origin.INTERNAL
|
||||
assert stamp.channel is Channel.MANUAL
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue