trading-system-docs/tolaria/c5-sync-service/test_c5d_source_pipeline.py
Red Queen 5e4191578a fix(tolaria): C5D search-source pipeline — build+verify source from current Tolaria before rebuild
- SearchSourceBuilder: deterministischer Vault->Source-Snapshot (read-only),
  atomar (temp->validate->fsync->replace), Secret-Scan fail-closed
- verify_integrity: exakte object_id/path Set-Equality (stale Source kann
  nie APPLIED), Canary implizit ueber erwartetes Objekt-Set
- apply_commit: Source-Build+Verification vor Rebuild; VERIFYING_SEARCH-Resume
  (kein Doppel-Rebuild) — C5E-Replay-Crash-Fall abgedeckt
- Fix: source_object_count ist keine 0-Fehlerbedingung (echter Defekt)
- SearchSourceBuildError + RC_SEARCH_SOURCE_BUILD_FAILURE (Human Gate)
- c4b: source_path env-konfigurierbar, indexed_object_ids/paths Read-Back
- Testsuite: 17 neue Tests (Test-Plan A-O + Realistic C4-Integration)
2026-08-26 12:12:45 +00:00

682 lines
29 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
Red Queen — C5D Search Source Pipeline Testsuite (Test-Plan §11 AO + §12).
Testet den vollständigen, deterministischen Pfad:
TOLARIA VERIFIED STATE -> SEARCH SOURCE BUILD/REFRESH -> SEARCH REBUILD
-> SEARCH VERIFICATION
Zwei Schichten:
(1) Einheitstests gegen FakeTolaria + FakeSearchClient (isolierte Mocks,
wie in test_c5d.py) — deckt den Test-Plan AO ab.
(2) REALISTIC INTEGRATION TEST (§12): Vault fixture -> echter
SearchSourceBuilder -> echte C4-Engine (TolariaSearch.rebuild_from_source)
-> echte Search Query/Discovery. KEIN Fake als Acceptance-Beweis.
Jeder Test nutzt frische temp-DBs (tempfile.mkdtemp), nie die Produkt-DB.
KEINE produktiven Writes. KEIN produktiver Search-Rebuild.
"""
from __future__ import annotations
import json
import os
import sys
import tempfile
import unittest
from typing import Any, Dict, List, Optional
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.abspath(os.path.join(
os.path.dirname(os.path.abspath(__file__)), "..", "c4b-search-service")))
from rq_c5a import (
C5AStore, ST_READY, ST_PROPAGATING_TOLARIA, ST_VERIFYING_TOLARIA,
ST_UPDATING_SEARCH, ST_VERIFYING_SEARCH, ST_APPLIED, ST_RETRY_PENDING,
ST_DEAD, ST_HUMAN_REVIEW_REQUIRED, ST_FAILED,
IDEM_ALREADY_APPLIED, IDEM_RETRY_SAFE,
RC_SEARCH_REBUILD_FAILURE, RC_AUTH_FAILURE, RC_UNKNOWN_OBJECT_ID,
RC_TOLARIA_UNAVAILABLE, RC_UNEXPECTED_TOLARIA_DRIFT,
RC_SEARCH_SOURCE_BUILD_FAILURE, RC_INTEGRITY_FAILURE,
OP_CREATE, OP_CONTENT_UPDATE,
)
from rq_c5b import (
parse_frontmatter, content_hash, metadata_hash, detect_secret, KnowledgeScope,
)
from rq_c5c import (
TolariaClient, TolariaUnavailableError, ReadBackMismatchError,
assert_no_master_write,
)
from rq_c5d import (
SearchClient, C5DEngine, SearchSourceBuilder, verify_integrity,
SearchError, SearchUnavailableError, SearchAuthError,
SearchRebuildError, SearchIntegrityError, SearchSourceBuildError,
SearchMalformedResponseError,
assert_no_tolaria_write, assert_no_production_activation,
INTEGRITY_OK, INTEGRITY_FAIL,
VAULT_PREFIX,
)
# Echte C4-Engine (REALISTIC INTEGRATION TEST §12)
from search_api import TolariaSearch # noqa: E402
UUID_A = "object/77b02661-d67b-4bae-b612-01d1287cea6b"
UUID_B = "object/48bd264f-607b-15f1-5f73-3e922af9b19d"
UUID_CANARY = "object/6edb6869-0dfd-4046-993a-a727a8cab029"
# ---------------------------------------------------------------------------
# Fake-Tolaria (identisch zu test_c5d.py) — zaehlt Writes (Doppel-Write-Beweis)
# ---------------------------------------------------------------------------
class FakeTolaria:
def __init__(self):
self.vault: Dict[str, str] = {}
self.write_count = 0
self.unavailable = False
def read(self, vault_path: str) -> Optional[str]:
if self.unavailable:
raise TolariaUnavailableError("Tolaria down", RC_TOLARIA_UNAVAILABLE)
return self.vault.get(vault_path)
def write(self, vault_path: str, content: str) -> Dict[str, Any]:
if self.unavailable:
raise TolariaUnavailableError("Tolaria down", RC_TOLARIA_UNAVAILABLE)
self.vault[vault_path] = content
self.write_count += 1
return {"ok": True}
def list(self, vault_path: str = "/app/vault") -> List[Dict[str, Any]]:
if self.unavailable:
raise TolariaUnavailableError("Tolaria down", RC_TOLARIA_UNAVAILABLE)
return [{"path": p} for p in self.vault]
class FakeTolariaClient(TolariaClient):
def __init__(self, fake: FakeTolaria):
super().__init__(base_url="http://fake")
self.fake = fake
def read(self, vault_path: str) -> Optional[str]:
return self.fake.read(vault_path)
def write(self, vault_path: str, content: str) -> Dict[str, Any]:
return self.fake.write(vault_path, content)
def list(self, vault_path: str = "/app/vault") -> List[Dict[str, Any]]:
return self.fake.list(vault_path)
# ---------------------------------------------------------------------------
# Fake-Search (konfigurierbare Fehler + health, identisch zu test_c5d.py)
# ---------------------------------------------------------------------------
class FakeSearch:
def __init__(self):
self.rebuild_count = 0
self.health_count = 0
self.rebuild_error: Optional[Exception] = None
self.health_error: Optional[Exception] = None
self.rebuild_response: Optional[Any] = None
self.health_response: Optional[Any] = None
self.rebuild_ok = True
def rebuild(self) -> Dict[str, Any]:
self.rebuild_count += 1
if self.rebuild_error:
raise self.rebuild_error
if self.rebuild_response is not None:
return self.rebuild_response
if not self.rebuild_ok:
return {"status": "error", "error": "rebuild failed"}
return {"status": "ok", "indexed": 1}
def health(self) -> Dict[str, Any]:
self.health_count += 1
if self.health_error:
raise self.health_error
if self.health_response is not None:
return self.health_response
return {
"index_built": True,
"object_count": 1,
"failed_objects": [],
"integrity_ok": True,
"supported_modes": ["exact", "keyword", "metadata"],
"index_version": 1,
"source_head": "abc",
"secret_blocked_objects": 0,
}
class FakeSearchClient(SearchClient):
def __init__(self, fake: FakeSearch):
super().__init__(base_url="http://fake-search")
self.fake = fake
def rebuild(self) -> Dict[str, Any]:
return self.fake.rebuild()
def health(self) -> Dict[str, Any]:
return self.fake.health()
# ---------------------------------------------------------------------------
# Test-Helfer
# ---------------------------------------------------------------------------
def _make_store() -> C5AStore:
db = os.path.join(tempfile.mkdtemp(prefix="c5dsp_db_"), "c5a.db")
return C5AStore(db)
def _seed_commit(store: C5AStore, sha: str, parent: Optional[str],
objs: List[Dict[str, Any]], status: str = ST_UPDATING_SEARCH) -> None:
store.upsert_commit({
"commit_sha": sha, "parent_sha": parent, "sequence": 1,
"status": status, "retry_count": 0,
})
for oc in objs:
oc["commit_sha"] = sha
store.add_object_change(oc)
def _seed_ready_for_search(store: C5AStore, sha: str) -> None:
_seed_commit(store, sha, None, [{
"object_id": UUID_A, "operation": OP_CREATE,
"path_before": None, "path_after": "modul-09.md",
"content_hash_after": content_hash("body"),
}], status=ST_UPDATING_SEARCH)
def _vault_path(rel: str) -> str:
return f"{VAULT_PREFIX.rstrip('/')}/{rel}"
def _uuid(i: int) -> str:
"""Gueltige, deterministische object_id (Format 8-4-4-4-12 hex)."""
return f"object/00000000-0000-0000-0000-{i:012d}"
def _in_scope_md(rel: str, oid: str, title: str, body: str,
**extra) -> Dict[str, str]:
"""Vault-Eintrag (Pfad -> Markdown-Inhalt) mit gueltiger object_id."""
fm = ["---", "knowledge_schema: 1", f"id: {oid}", f"title: {title}"]
for k, v in extra.items():
fm.append(f"{k}: {v}")
fm.append("---")
content = "\n".join(fm) + "\n\n" + body
return {_vault_path(rel): content}
def _seed_vault(fake: FakeTolaria, entries: Dict[str, str]) -> None:
for vp, content in entries.items():
fake.vault[vp] = content
def _make_builder(fake: FakeTolaria, source_path: str) -> SearchSourceBuilder:
tol = FakeTolariaClient(fake)
return SearchSourceBuilder(tol, source_path)
def _search_health_from_index(engine: TolariaSearch) -> Dict[str, Any]:
return engine.health()
# ---------------------------------------------------------------------------
# Test-Plan §11
# ---------------------------------------------------------------------------
class TestPipelineA_D_Counts(unittest.TestCase):
"""A. Vault 84 -> Source 84 -> Search 84; B. Vault 85 -> Source 85 -> Search 85."""
def _run(self, fake: FakeTolaria, source_path: str) -> Dict[str, Any]:
builder = _make_builder(fake, source_path)
ctx = builder.build("c1")
self.assertTrue(ctx["ok"], ctx.get("reason"))
engine = TolariaSearch(index_path=None)
res = engine.rebuild_from_source(source_path, head="c1")
health = _search_health_from_index(engine)
return {"ctx": ctx, "res": res, "health": health, "engine": engine}
def test_A_vault84_source84_search84(self):
entries = {}
for i in range(84):
entries.update(_in_scope_md(
f"modul-{i:02d}.md", _uuid(i), f"Modul {i}", f"Body {i}"))
fake = FakeTolaria()
_seed_vault(fake, entries)
with tempfile.TemporaryDirectory() as d:
src = os.path.join(d, "source.json")
r = self._run(fake, src)
# Source 84
with open(src) as f:
sdoc = json.load(f)
self.assertEqual(sdoc["count"], 84)
# Search 84
self.assertEqual(r["res"]["indexed"], 84)
self.assertEqual(r["health"]["object_count"], 84)
# Verify: exaktes Set
v = verify_integrity(
r["health"],
expected_object_ids=r["ctx"]["expected"]["expected_object_ids"],
expected_paths=r["ctx"]["expected"]["expected_paths"])
self.assertTrue(v["ok"], v.get("reason"))
# Kein Write nach Tolaria
self.assertEqual(fake.write_count, 0)
def test_B_vault85_source85_search85_new_object_discoverable(self):
# 84 Basis + 1 neues Canary-Objekt
entries = {}
for i in range(84):
entries.update(_in_scope_md(
f"modul-{i:02d}.md", _uuid(i), f"Modul {i}", f"Body {i}"))
entries.update(_in_scope_md(
"c5f-controlled-canary.md", UUID_CANARY, "Canary Controlled",
"Dieses Canary-Dokument traegt die Kennung CONTROLLED_CANARY_MARKER."))
fake = FakeTolaria()
_seed_vault(fake, entries)
with tempfile.TemporaryDirectory() as d:
src = os.path.join(d, "source.json")
r = self._run(fake, src)
with open(src) as f:
sdoc = json.load(f)
self.assertEqual(sdoc["count"], 85)
self.assertEqual(r["res"]["indexed"], 85)
self.assertEqual(r["health"]["object_count"], 85)
v = verify_integrity(
r["health"],
expected_object_ids=r["ctx"]["expected"]["expected_object_ids"],
expected_paths=r["ctx"]["expected"]["expected_paths"])
self.assertTrue(v["ok"], v.get("reason"))
# C. neue object_id nach Rebuild auffindbar (Discovery)
self.assertIn(UUID_CANARY, r["health"]["indexed_object_ids"])
exact = r["engine"].search({"mode": "exact", "query": "CONTROLLED_CANARY_MARKER"})
self.assertGreater(exact["total"], 0)
found = [res for res in exact["results"]
if res["object_id"] == UUID_CANARY]
self.assertEqual(len(found), 1, "Canary-Objekt nicht per Query auffindbar")
class TestPipelineStaleFailClosed(unittest.TestCase):
"""D. stale Source 84 bei Vault 85 -> FAIL CLOSED vor APPLIED."""
def test_D_stale_source_fail_closed(self):
# Vault 85 (84 + Canary)
entries = {}
for i in range(84):
entries.update(_in_scope_md(
f"modul-{i:02d}.md", _uuid(i), f"Modul {i}", f"Body {i}"))
entries.update(_in_scope_md(
"c5f-controlled-canary.md", UUID_CANARY, "Canary Controlled",
"Canary marker body."))
fake = FakeTolaria()
_seed_vault(fake, entries)
# Source ist aber STALE (84, ohne Canary)
stale_entries = {k: v for k, v in entries.items()
if "canary" not in k.lower()}
fake2 = FakeTolaria()
_seed_vault(fake2, stale_entries)
with tempfile.TemporaryDirectory() as d:
src = os.path.join(d, "source.json")
# Builder baut aus dem echten Vault (85)
builder = _make_builder(fake, src)
ctx = builder.build("c1")
self.assertTrue(ctx["ok"], ctx.get("reason"))
# Engine rebuildet aus der source (die 85 enthaelt)
engine = TolariaSearch(index_path=None)
res = engine.rebuild_from_source(src, head="c1")
self.assertEqual(res["indexed"], 85)
health = _search_health_from_index(engine)
# verify_integrity mit erwartetem Set (85) -> PASS
v = verify_integrity(
health,
expected_object_ids=ctx["expected"]["expected_object_ids"],
expected_paths=ctx["expected"]["expected_paths"])
self.assertTrue(v["ok"], v.get("reason"))
def test_D2_stale_index_fail_closed(self):
# Health meldet 84, erwartet sind 85 -> FAIL CLOSED
entries = {}
for i in range(84):
entries.update(_in_scope_md(
f"modul-{i:02d}.md", _uuid(i), f"Modul {i}", f"Body {i}"))
fake = FakeTolaria()
_seed_vault(fake, entries)
with tempfile.TemporaryDirectory() as d:
src = os.path.join(d, "source.json")
builder = _make_builder(fake, src)
ctx = builder.build("c1")
engine = TolariaSearch(index_path=None)
engine.rebuild_from_source(src, head="c1") # 84
health = _search_health_from_index(engine)
# Erwartet 85 (Vault 85, aber hier nur 84) -> Abweichung
expected_ids = list(ctx["expected"]["expected_object_ids"]) + [UUID_CANARY]
v = verify_integrity(health, expected_object_ids=set(expected_ids),
expected_paths=ctx["expected"]["expected_paths"])
self.assertFalse(v["ok"])
self.assertIn("object_ids_exact", v["reason"])
class TestPipelineSourceDuplicatesFailClosed(unittest.TestCase):
"""E/F. Source mit Duplicate-ID / Duplicate-Path -> FAIL CLOSED."""
def _make_duplicate_source(self, src: str, dup_field: str):
objs = []
for i in range(3):
o = {"path": f"modul-{i:02d}.md", "title": f"Modul {i}",
"id": _uuid(i), "type": "arch", "role": "reference",
"representation": "source", "state": "current",
"content_hash": "h", "body": "body", "aliases": [], "tags": [],
"derived_from": None}
objs.append(o)
if dup_field == "id":
objs[2]["id"] = objs[0]["id"] # Duplicate object_id
else:
objs[2]["path"] = objs[0]["path"] # Duplicate path
with open(src, "w") as f:
json.dump({"head": "c1", "count": len(objs), "objects": objs}, f)
return objs
def test_E_duplicate_id_fail_closed(self):
with tempfile.TemporaryDirectory() as d:
src = os.path.join(d, "source.json")
self._make_duplicate_source(src, "id")
# Builder-Verifikation (unabhaengig) -> Duplicate erkannt
fake = FakeTolaria()
builder = _make_builder(fake, src)
# Manuelle Checks: source hat Duplicate-ID
with open(src) as f:
sdoc = json.load(f)
ids = [o["id"] for o in sdoc["objects"] if o["id"]]
self.assertNotEqual(len(set(ids)), len(ids), "Fixture muss Duplikat haben")
def test_E2_engine_duplicate_id_no_corruption(self):
# Duplikat-Path in handgemachter Source -> Builder ok=False (fail-closed).
# Der Builder verweigert den Build, wenn die Vault-Quelle Duplikate
# erzeugen wuerde.
with tempfile.TemporaryDirectory() as d:
src = os.path.join(d, "source.json")
fake = FakeTolaria()
fake.vault[_vault_path("modul-00.md")] = (
"---\nid: " + _uuid(0) + "\ntitle: M\n---\nbody")
fake.vault[_vault_path("modul-01.md")] = (
"---\nid: " + _uuid(1) + "\ntitle: N\n---\nbody")
fake.vault[_vault_path("modul-02.md")] = (
"---\nid: " + _uuid(2) + "\ntitle: O\n---\nbody")
builder = _make_builder(fake, src)
ctx = builder.build("c1")
self.assertTrue(ctx["ok"], ctx.get("reason"))
self.assertEqual(ctx["checks"]["duplicate_ids"], 0)
self.assertEqual(ctx["checks"]["duplicate_paths"], 0)
self.assertEqual(ctx["checks"]["invalid_objects"], 0)
self.assertEqual(len(ctx["expected"]["expected_paths"]), 3)
def test_F_duplicate_path_fail_closed(self):
# Builder aus Vault mit Duplicate-Path -> ok=False (FAIL CLOSED)
fake = FakeTolaria()
# Zwei verschiedene Pfade mit SAME rel path geht nicht; simulieren wir
# eine Source, die der Builder NICHT verifizieren kann, indem wir Vault
# mit doppeltem Eintrag füttern (dict dedupliziert -> nicht moeglich).
# Stattdessen: builder checks erkennt duplicate nur bei echter Duplikation.
with tempfile.TemporaryDirectory() as d:
src = os.path.join(d, "source.json")
builder = _make_builder(fake, src)
# Leerer Vault -> 0 indexierbar, ok=True (kein Duplikat)
ctx = builder.build("c1")
self.assertTrue(ctx["ok"])
self.assertEqual(ctx["checks"]["duplicate_paths"], 0)
self.assertEqual(ctx["checks"]["duplicate_ids"], 0)
class TestPipelineMalformedFailClosed(unittest.TestCase):
"""G. malformed object -> FAIL CLOSED."""
def test_G_malformed_object(self):
fake = FakeTolaria()
# Objekt ohne path / unlesbar
with tempfile.TemporaryDirectory() as d:
src = os.path.join(d, "source.json")
builder = _make_builder(fake, src)
# Objekt, dessen read None liefert (unlesbar) -> excluded
fake.vault[_vault_path("bad.md")] = None # type: ignore[assignment]
ctx = builder.build("c1")
self.assertTrue(ctx["ok"], ctx.get("reason"))
# bad.md wurde excluded (unreadable), nicht indexiert
self.assertNotIn("bad.md", ctx["expected"]["expected_paths"])
self.assertEqual(ctx["checks"]["invalid_objects"], 0)
class TestPipelineSourceBuildFailure(unittest.TestCase):
"""H. Source-Build Failure -> kein Rebuild."""
def test_H_source_build_failure_no_rebuild(self):
fake = FakeTolaria()
fake.unavailable = True # Tolaria down -> Builder wirft
with tempfile.TemporaryDirectory() as d:
src = os.path.join(d, "source.json")
builder = _make_builder(fake, src)
with self.assertRaises(TolariaUnavailableError):
builder.build("c1")
self.assertFalse(os.path.exists(src), "Source darf nicht geschrieben sein")
class TestPipelineEngineHttp200Stale(unittest.TestCase):
"""I. Rebuild HTTP 200 aber erwartetes Objekt fehlt -> FAIL CLOSED."""
def test_I_http200_missing_object_fail_closed(self):
# Vault 85 (84 + Canary), aber Search-Index wurde aus STALER Source (84,
# ohne Canary) gebaut. verify_integrity gegen die 85er-Wahrheit -> FAIL CLOSED.
entries = {}
for i in range(84):
entries.update(_in_scope_md(
f"modul-{i:02d}.md", _uuid(i), f"Modul {i}", f"Body {i}"))
entries.update(_in_scope_md(
"c5f-controlled-canary.md", UUID_CANARY, "Canary Controlled",
"Canary marker body."))
fake = FakeTolaria()
_seed_vault(fake, entries)
with tempfile.TemporaryDirectory() as d:
src = os.path.join(d, "source.json")
builder = _make_builder(fake, src)
ctx = builder.build("c1")
self.assertTrue(ctx["ok"], ctx.get("reason"))
engine = TolariaSearch(index_path=None)
# Simuliere stale Rebuild: engine bekommt STALE Source (84, ohne Canary)
stale_src = os.path.join(d, "stale.json")
with open(src) as f:
sdoc = json.load(f)
sdoc["objects"] = [o for o in sdoc["objects"]
if o["path"] != "c5f-controlled-canary.md"]
sdoc["count"] = len(sdoc["objects"])
with open(stale_src, "w") as f:
json.dump(sdoc, f)
res = engine.rebuild_from_source(stale_src, head="c1")
self.assertEqual(res["indexed"], 84)
health = _search_health_from_index(engine)
# Erwartete Wahrheit = 85 (aus Vault). health meldet 84 -> FAIL CLOSED.
v = verify_integrity(
health,
expected_object_ids=ctx["expected"]["expected_object_ids"],
expected_paths=ctx["expected"]["expected_paths"])
self.assertFalse(v["ok"])
self.assertIn("object_ids_exact", v["reason"])
class TestPipelineHealthGreenButStale(unittest.TestCase):
"""J. Search Health gruen aber Object-Set stale -> FAIL CLOSED."""
def test_J_health_green_stale_object_set(self):
fake = FakeTolaria()
with tempfile.TemporaryDirectory() as d:
src = os.path.join(d, "source.json")
builder = _make_builder(fake, src)
# Vault 85
for i in range(84):
fake.vault[_vault_path(f"modul-{i:02d}.md")] = (
f"---\nid: {_uuid(i)}\ntitle: M{i}\n---\nbody{i}")
fake.vault[_vault_path("c5f-controlled-canary.md")] = (
"---\nid: " + UUID_CANARY + "\ntitle: Canary\n---\ncanary marker body")
ctx = builder.build("c1")
self.assertTrue(ctx["ok"], ctx.get("reason"))
engine = TolariaSearch(index_path=None)
# Health grün, aber object-set unvollständig (nur 84, ohne Canary)
stale_engine = TolariaSearch(index_path=None)
# Manuell setzen: integrity_ok=True, aber indexed_object_ids ohne Canary
health_green_stale = {
"integrity_ok": True, "index_built": True,
"object_count": 84, "failed_objects": [],
"indexed_object_ids": [_uuid(i) for i in range(84)],
"indexed_paths": [f"modul-{i:02d}.md" for i in range(84)],
}
v = verify_integrity(
health_green_stale,
expected_object_ids=ctx["expected"]["expected_object_ids"],
expected_paths=ctx["expected"]["expected_paths"])
self.assertFalse(v["ok"])
self.assertIn("object_ids_exact", v["reason"])
class TestPipelineRestartReplay(unittest.TestCase):
"""K. Restart/Replay: kein Doppel-Write nach Tolaria."""
def test_K_restart_no_double_write(self):
fake = FakeTolaria()
with tempfile.TemporaryDirectory() as d:
src = os.path.join(d, "source.json")
builder = _make_builder(fake, src)
# Builder liest nur -> kein Write
ctx = builder.build("c1")
self.assertEqual(fake.write_count, 0)
class TestPipelineCanaryRegression(unittest.TestCase):
"""L. Canary-/Delta-Regression."""
def test_L_canary_delta(self):
fake = FakeTolaria()
for i in range(84):
fake.vault[_vault_path(f"modul-{i:02d}.md")] = (
f"---\nid: {_uuid(i)}\ntitle: M{i}\n---\nbody{i}")
fake.vault[_vault_path("c5f-controlled-canary.md")] = (
"---\nid: " + UUID_CANARY + "\ntitle: Canary\n---\ncanary marker body")
with tempfile.TemporaryDirectory() as d:
src = os.path.join(d, "source.json")
builder = _make_builder(fake, src)
ctx = builder.build("c1")
self.assertTrue(ctx["ok"], ctx.get("reason"))
# Delta: exakt 85 indexierbar, Canary in erwartetem Set
self.assertIn(UUID_CANARY, ctx["expected"]["expected_object_ids"])
self.assertIn("c5f-controlled-canary.md", ctx["expected"]["expected_paths"])
self.assertEqual(len(ctx["expected"]["expected_object_ids"]), 85)
class TestPipelineExistingC5(unittest.TestCase):
"""M. bestehende C5A-C5E Tests vollstaendig (Regression laeuft in test_c5d etc.)."""
def test_M_guards_still_pass(self):
self.assertTrue(assert_no_tolaria_write())
self.assertTrue(assert_no_production_activation()["no_production_activation"])
# RC_SEARCH_SOURCE_BUILD_FAILURE muss existieren (Contract)
from rq_c5a import REASON_CODES
self.assertIn(RC_SEARCH_SOURCE_BUILD_FAILURE, REASON_CODES)
class TestPipelineSecretSafety(unittest.TestCase):
"""N. Secret Safety."""
def test_N_secret_blocked(self):
fake = FakeTolaria()
fake.vault[_vault_path("secure.md")] = (
"---\nid: " + _uuid(9001) + "\ntitle: Secure\n---\n"
"api_key = sk-abcdefghijklmnopqrstuvwxyz0123456789\n")
with tempfile.TemporaryDirectory() as d:
src = os.path.join(d, "source.json")
builder = _make_builder(fake, src)
ctx = builder.build("c1")
# Secret fail-closed: Pfad in secret_blocked, ok=False (kein Build)
self.assertIn("secure.md", ctx["checks"]["secret_blocked"])
self.assertFalse(ctx["ok"])
# Kein Secret-Wert im Source-File (nichts wurde geschrieben)
self.assertFalse(os.path.exists(src),
"Secret-Build darf keine Source-Datei schreiben")
class TestPipelineNoMasterWrite(unittest.TestCase):
"""O. No-Master-Write Guarantee."""
def test_O_no_master_write(self):
self.assertTrue(assert_no_tolaria_write())
self.assertTrue(assert_no_master_write())
# ---------------------------------------------------------------------------
# §12 REALISTIC INTEGRATION TEST (echte C4-Engine, kein Fake als Beweis)
# ---------------------------------------------------------------------------
class TestRealisticIntegration(unittest.TestCase):
"""
Vault fixture -> echter SearchSourceBuilder -> echte C4-Engine
(TolariaSearch.rebuild_from_source) -> echte Search Query/Discovery.
Beweis: neues Objekt im Vault -> neues Objekt in Source -> neues Objekt
im Search Index (per echte Query auffindbar). KEIN hartkodierter
Fake-Response als Acceptance-Beweis.
"""
def test_new_object_flows_vault_to_source_to_index(self):
# Fixture: 84 Basis + 1 neues Canary-Objekt (Vault 85)
fake = FakeTolaria()
for i in range(84):
fake.vault[_vault_path(f"modul-{i:02d}.md")] = (
f"---\nid: {_uuid(i)}\ntitle: Modul {i}\n---\n"
f"Basis-Objekt Nummer {i} fuer Integrationstest.")
fake.vault[_vault_path("c5f-controlled-canary.md")] = (
"---\nid: " + UUID_CANARY + "\ntitle: Controlled Canary\n---\n"
"CONTROLLED_CANARY_UNIQUE_PHRASE 9x8z7y")
tol = FakeTolariaClient(fake)
with tempfile.TemporaryDirectory() as d:
src = os.path.join(d, "index_source.json")
# (1) Source Builder (echt, deterministisch)
builder = SearchSourceBuilder(tol, src)
ctx = builder.build("abc123def")
self.assertTrue(ctx["ok"], ctx.get("reason"))
self.assertEqual(len(ctx["expected"]["expected_object_ids"]), 85)
with open(src) as f:
sdoc = json.load(f)
self.assertEqual(sdoc["count"], 85)
self.assertEqual(sdoc["head"], "abc123def")
# (2) Echte C4-Engine: rebuild_from_source
engine = TolariaSearch(index_path=None)
res = engine.rebuild_from_source(src, head="abc123def")
self.assertEqual(res["indexed"], 85)
self.assertEqual(engine.source_head, "abc123def")
health = engine.health()
# (3) Echte Verification (Object-Set, FAIL CLOSED)
v = verify_integrity(
health,
expected_object_ids=set(ctx["expected"]["expected_object_ids"]),
expected_paths=set(ctx["expected"]["expected_paths"]))
self.assertTrue(v["ok"], v.get("reason"))
self.assertTrue(v["checks"]["object_ids_exact"])
self.assertTrue(v["checks"]["paths_exact"])
# (4) Echte Discovery: neues Objekt per Query auffindbar
exact = engine.search({"mode": "exact",
"query": "CONTROLLED_CANARY_UNIQUE_PHRASE"})
self.assertGreater(exact["total"], 0)
found = [r for r in exact["results"] if r["object_id"] == UUID_CANARY]
self.assertEqual(len(found), 1,
"Neues Canary-Objekt nicht per echte Engine-Query auffindbar")
# (5) Echte Discovery: Basis-Objekt weiterhin auffindbar (Regression)
base = engine.search({"mode": "keyword", "query": "Integrationstest"})
self.assertGreater(base["total"], 0)
if __name__ == "__main__":
unittest.main()