Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 29 additions & 6 deletions artifacts/evidence_index.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,29 @@
"evidence_bearing": true,
"visualization_only": false
},
{
"path": "artifacts/mcp_trace_corruption_manifest.json",
"format": "json",
"generator": "scripts/generate_mcp_trace_corruptions.py",
"evidence_category": "corruption_manifest",
"evidence_role": "deterministic MCP trace corruption manifest evidence",
"fixture_families": [
"mcp_trace_replay"
],
"top_level_keys": [
"allowed_operators",
"corruptions",
"manifest_id",
"summary",
"version"
],
"deterministic_evaluation": true,
"llm_judges": "none",
"external_apis": "none",
"manifest_aligned": false,
"evidence_bearing": true,
"visualization_only": false
},
{
"path": "artifacts/mcp_trace_replay_results.json",
"format": "json",
Expand Down Expand Up @@ -188,13 +211,13 @@
}
],
"global_summary": {
"artifact_count": 7,
"json_artifact_count": 6,
"artifact_count": 8,
"json_artifact_count": 7,
"svg_artifact_count": 1,
"evidence_bearing_count": 6,
"evidence_bearing_count": 7,
"visualization_only_count": 1,
"deterministic_artifact_count": 7,
"llm_free_artifact_count": 7,
"external_api_free_artifact_count": 7
"deterministic_artifact_count": 8,
"llm_free_artifact_count": 8,
"external_api_free_artifact_count": 8
}
}
27 changes: 27 additions & 0 deletions scripts/generate_evidence_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from __future__ import annotations

import json
import re
from pathlib import Path
from typing import Any

Expand All @@ -29,6 +30,15 @@
"evidence_bearing": True,
"visualization_only": False,
},
{
"path": "artifacts/mcp_trace_corruption_manifest.json",
"format": "json",
"generator": "scripts/generate_mcp_trace_corruptions.py",
"evidence_category": "corruption_manifest",
"evidence_role": "deterministic MCP trace corruption manifest evidence",
"evidence_bearing": True,
"visualization_only": False,
},
{
"path": "artifacts/mcp_trace_replay_results.json",
"format": "json",
Expand Down Expand Up @@ -85,6 +95,11 @@ def _manifest_families() -> set[str]:
manifest = _load_json(MANIFEST_PATH)
return {str(fixture["family"]) for fixture in manifest["fixtures"]}

def _family_from_fixture_slug(slug: str) -> str | None:
match = re.match(r"^(?P<family>.+)_[^_]+_v\d+$", slug)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The regex r"^(?P<family>.+)_[^_]+_v\d+$" is incorrect for fixture slugs that do not include a degradation level (e.g., baseline fixtures like mcp_trace_replay_v1). In such cases, it would incorrectly extract mcp_trace as the family instead of mcp_trace_replay. Additionally, it is fragile if a family name contains multiple underscores. Using a non-greedy match for the family and explicitly handling the optional degradation levels is more robust.

Suggested change
match = re.match(r"^(?P<family>.+)_[^_]+_v\d+$", slug)
match = re.match(r"^(?P<family>.+?)(?:_(?:mild|moderate|degraded))?_v\d+$", slug)

if not match:
return None
return match.group("family")

def _extract_fixture_families(payload: dict[str, Any]) -> list[str]:
families: set[str] = set()
Expand All @@ -94,6 +109,18 @@ def _extract_fixture_families(payload: dict[str, Any]) -> list[str]:
families.add(family["family"])
if isinstance(payload.get("family"), str):
families.add(payload["family"])

if isinstance(payload.get("corruptions"), list):
for corruption in payload["corruptions"]:
if not isinstance(corruption, dict):
continue
source_fixture = corruption.get("source_fixture")
if not isinstance(source_fixture, str):
continue
fixture_slug = source_fixture.rsplit("/", 1)[-1]
family = _family_from_fixture_slug(fixture_slug)
if family:
families.add(family)
Comment on lines +113 to +123

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This block violates the general rule regarding JSON list processing. It should treat null as an empty list and raise a RuntimeError for other non-list types to maintain strictness. Additionally, use type guards like isinstance(item, dict) before accessing fields within list items to prevent crashes on malformed data.

Suggested change
if isinstance(payload.get("corruptions"), list):
for corruption in payload["corruptions"]:
if not isinstance(corruption, dict):
continue
source_fixture = corruption.get("source_fixture")
if not isinstance(source_fixture, str):
continue
fixture_slug = source_fixture.rsplit("/", 1)[-1]
family = _family_from_fixture_slug(fixture_slug)
if family:
families.add(family)
corruptions = payload.get("corruptions")
if corruptions is not None:
if not isinstance(corruptions, list):
raise RuntimeError("Field 'corruptions' must be a list if present")
for corruption in corruptions:
if not isinstance(corruption, dict):
continue
source_fixture = corruption.get("source_fixture")
if not isinstance(source_fixture, str):
continue
fixture_slug = source_fixture.rsplit("/", 1)[-1]
family = _family_from_fixture_slug(fixture_slug)
if family:
families.add(family)
References
  1. When processing JSON data, treat null values for expected list fields as empty lists, but raise a RuntimeError for other non-list types to maintain strictness. Use type guards like isinstance(item, dict) before accessing fields within list items to prevent crashes on malformed data.

return sorted(families)


Expand Down
Loading