docs(discovery): publish provisional feature baseline

This commit is contained in:
Alexandre Teixeira
2026-07-26 12:44:34 +01:00
parent d8a2059df8
commit cb6c28113a
26 changed files with 9194 additions and 0 deletions
@@ -0,0 +1,224 @@
#!/usr/bin/env python3
"""Focused negative tests for validate_discovery_evidence.py."""
from __future__ import annotations
import importlib.util
import shutil
import sys
import tempfile
import unittest
from pathlib import Path
MODULE_PATH = Path(__file__).with_name("validate_discovery_evidence.py")
SPEC = importlib.util.spec_from_file_location("validate_discovery_evidence", MODULE_PATH)
assert SPEC and SPEC.loader
validator = importlib.util.module_from_spec(SPEC)
sys.modules[SPEC.name] = validator
SPEC.loader.exec_module(validator)
class FakeJavascriptParser:
supported = True
reason = "test parser"
def __init__(self, symbols: list[validator.Located] | None = None) -> None:
self.symbols = symbols or []
def parse(self, path: Path) -> list[validator.Located]:
return self.symbols
class UnsupportedJavascriptParser:
supported = False
reason = "no repository-local parser"
class EvidenceNegativeTests(unittest.TestCase):
def setUp(self) -> None:
self.temp = tempfile.TemporaryDirectory()
self.root = Path(self.temp.name)
(self.root / "sample.py").write_text(
"from fastapi import APIRouter\n"
"router = APIRouter(prefix='/api')\n"
"\n"
"class ChatHandler:\n"
" def preprocess_message(self):\n"
" return True\n"
"\n"
"@router.post('/chat')\n"
"def chat_stream():\n"
" return True\n",
encoding="utf-8",
)
(self.root / "sample.js").write_text(
"export const present = () => true;\n", encoding="utf-8"
)
(self.root / "sample.sh").write_text(
"#!/usr/bin/env bash\nreal_function() {\n return 0\n}\n",
encoding="utf-8",
)
self.backup = self.root / "copied-backups"
self.backup.mkdir()
for path in self.root.glob("sample.*"):
shutil.copy2(path, self.backup / path.name)
def tearDown(self) -> None:
for backup in self.backup.iterdir():
target = self.root / backup.name
shutil.copy2(backup, target)
self.assertEqual(target.read_bytes(), backup.read_bytes())
self.temp.cleanup()
def validate(
self,
evidence: dict[str, str],
javascript_parser: object | None = None,
) -> validator.Validation:
return validator.validate_evidence(
self.root,
"TEST-001",
0,
evidence,
javascript_parser or FakeJavascriptParser(),
)
def test_missing_python_symbol(self) -> None:
result = self.validate(
{
"path": "sample.py",
"kind": "python-function",
"locator": "fabricated",
"line_range": "L1-L1",
"explanation": "negative fixture",
}
)
self.assertEqual(result.result, "invalid-locator")
def test_incorrect_qualified_method(self) -> None:
result = self.validate(
{
"path": "sample.py",
"kind": "python-method",
"locator": "WrongHandler.preprocess_message",
"line_range": "L5-L6",
"explanation": "negative fixture",
}
)
self.assertEqual(result.result, "invalid-locator")
def test_symbol_outside_cited_range(self) -> None:
result = self.validate(
{
"path": "sample.py",
"kind": "python-method",
"locator": "ChatHandler.preprocess_message",
"line_range": "L1-L2",
"explanation": "negative fixture",
}
)
self.assertEqual(result.result, "locator-outside-range")
def test_fabricated_test_function(self) -> None:
result = self.validate(
{
"path": "sample.py",
"kind": "test-function",
"locator": "test_fabricated",
"line_range": "L1-L2",
"explanation": "negative fixture",
}
)
self.assertEqual(result.result, "invalid-locator")
def test_nonexistent_javascript_symbol_with_parser(self) -> None:
result = self.validate(
{
"path": "sample.js",
"kind": "javascript-function",
"locator": "missing",
"line_range": "L1-L1",
"explanation": "negative fixture",
},
FakeJavascriptParser(
[validator.Located("present", "javascript-export", 1, 1)]
),
)
self.assertEqual(result.result, "invalid-locator")
def test_unsupported_javascript_parser(self) -> None:
result = self.validate(
{
"path": "sample.js",
"kind": "javascript-function",
"locator": "present",
"line_range": "L1-L1",
"explanation": "negative fixture",
},
UnsupportedJavascriptParser(),
)
self.assertEqual(result.result, "unsupported")
def test_route_path_mismatch(self) -> None:
result = self.validate(
{
"path": "sample.py",
"kind": "python-route",
"locator": "POST /api/wrong -> chat_stream",
"line_range": "L9-L10",
"explanation": "negative fixture",
}
)
self.assertEqual(result.result, "invalid-locator")
self.assertIn("path", result.problem or "")
def test_http_method_mismatch(self) -> None:
result = self.validate(
{
"path": "sample.py",
"kind": "python-route",
"locator": "GET /api/chat -> chat_stream",
"line_range": "L9-L10",
"explanation": "negative fixture",
}
)
self.assertEqual(result.result, "invalid-locator")
self.assertIn("method", result.problem or "")
def test_shell_function_mismatch(self) -> None:
result = self.validate(
{
"path": "sample.sh",
"kind": "shell-function",
"locator": "fabricated",
"line_range": "L1-L4",
"explanation": "negative fixture",
}
)
self.assertEqual(result.result, "invalid-locator")
def test_invalid_file_level_evidence(self) -> None:
result = self.validate(
{
"path": "missing.file",
"kind": "file",
"explanation": "negative fixture",
}
)
self.assertEqual(result.result, "invalid-path")
def test_file_level_evidence_rejects_fake_symbol(self) -> None:
result = self.validate(
{
"path": "sample.sh",
"kind": "file",
"locator": "whole-script",
"explanation": "negative fixture",
}
)
self.assertEqual(result.result, "invalid-locator")
if __name__ == "__main__":
unittest.main()
+592
View File
@@ -0,0 +1,592 @@
#!/usr/bin/env python3
from __future__ import annotations
import json
import re
import sys
from collections import Counter, defaultdict
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
CATALOG_PATH = ROOT / "feature-catalog.json"
CATALOG_MD_PATH = ROOT / "feature-catalog.md"
DOMAINS_DIR = ROOT / "domains"
REVIEWS_DIR = ROOT / "reviews"
EXPECTED_COMMIT = "d8a2059df8e53bc7275c45339849d14c8651e73c"
EXPECTED_FEATURES = 79
EXPECTED_DOMAINS = 16
VALID_STATUSES = {
"verified",
"partial",
"disabled",
"experimental",
"legacy",
"dead-code-candidate",
"unverified",
}
VALID_MATURITY = {"E0", "E1", "E2", "E3", "E4"}
VALID_RUNTIME = {
"not-required",
"pending",
"blocked",
"completed",
}
REQUIRED_FIELDS = {
"id",
"domain",
"name",
"purpose",
"status",
"evidence_maturity",
"verified_at_commit",
"evidence",
"runtime_validation",
}
FEATURE_ID_RE = re.compile(r"^[A-Z][A-Z0-9]*-\d{3}$")
DOMAIN_HEADING_RE = re.compile(
r"^##\s+`?([A-Z][A-Z0-9]*-\d{3})`?"
r"\s+(?:—|-)\s+(.+?)\s*$"
)
DOMAIN_FIELD_RE = re.compile(
r"^-\s+\*\*"
r"(Domain|Status|Evidence Maturity|Commit Verified)"
r"\*\*:\s*(.*?)\s*$"
)
DOMAIN_RUNTIME_RE = re.compile(
r"^-\s+\*\*Runtime Validation\*\*:\s*(.*?)\s*$"
)
REVIEW_HEADING_RE = re.compile(
r"^###\s+(?:\d+\.\s+)?"
r"([A-Z][A-Z0-9]*-\d{3})"
r"\s+(?:—|-)\s+.+$"
)
REVIEW_RESULT_RE = re.compile(
r"^-\s+\*\*Resulting Status(?:\s+and|/)\s+Maturity\*\*:"
r"\s*`([^`]+)`\s*/\s*`([^`]+)`\s*$"
)
def clean(value: str) -> str:
value = value.strip()
if (
len(value) >= 2
and value.startswith("`")
and value.endswith("`")
):
return value[1:-1].strip()
return value
def load_catalog(errors: list[str]) -> list[dict[str, Any]]:
try:
data = json.loads(
CATALOG_PATH.read_text(encoding="utf-8")
)
except Exception as exc:
errors.append(f"Unable to read catalog: {exc}")
return []
if isinstance(data, list):
features = data
elif isinstance(data, dict) and isinstance(data.get("features"), list):
features = data["features"]
else:
errors.append(
"Catalog must be an array or contain a features array"
)
return []
if not all(isinstance(feature, dict) for feature in features):
errors.append("Every catalog feature must be an object")
return []
return features
def validate_catalog(
features: list[dict[str, Any]],
errors: list[str],
) -> None:
if len(features) != EXPECTED_FEATURES:
errors.append(
f"Expected {EXPECTED_FEATURES} features, "
f"found {len(features)}"
)
ids = [feature.get("id") for feature in features]
duplicates = sorted(
feature_id
for feature_id, count in Counter(ids).items()
if feature_id and count > 1
)
if duplicates:
errors.append(
"Duplicate feature IDs: " + ", ".join(duplicates)
)
for index, feature in enumerate(features):
feature_id = feature.get("id")
label = (
feature_id
if isinstance(feature_id, str)
else f"<index:{index}>"
)
missing = sorted(
field
for field in REQUIRED_FIELDS
if feature.get(field) in (None, "", [])
)
if missing:
errors.append(
f"{label}: missing fields: {', '.join(missing)}"
)
if (
not isinstance(feature_id, str)
or not FEATURE_ID_RE.fullmatch(feature_id)
):
errors.append(f"{label}: invalid feature ID")
if feature.get("status") not in VALID_STATUSES:
errors.append(
f"{label}: invalid status "
f"{feature.get('status')!r}"
)
if feature.get("evidence_maturity") not in VALID_MATURITY:
errors.append(
f"{label}: invalid maturity "
f"{feature.get('evidence_maturity')!r}"
)
if feature.get("verified_at_commit") != EXPECTED_COMMIT:
errors.append(
f"{label}: incorrect verified_at_commit"
)
runtime = feature.get("runtime_validation")
if not isinstance(runtime, dict):
errors.append(
f"{label}: runtime_validation must be an object"
)
continue
required = runtime.get("required")
runtime_status = runtime.get("status")
reason = runtime.get("reason")
if not isinstance(required, bool):
errors.append(
f"{label}: runtime required must be boolean"
)
if runtime_status not in VALID_RUNTIME:
errors.append(
f"{label}: invalid runtime status "
f"{runtime_status!r}"
)
if not isinstance(reason, str) or not reason.strip():
errors.append(
f"{label}: runtime reason is blank"
)
if required is False and runtime_status != "not-required":
errors.append(
f"{label}: required=false requires not-required"
)
if required is True and runtime_status == "not-required":
errors.append(
f"{label}: required=true cannot be not-required"
)
def parse_domain(
path: Path,
errors: list[str],
) -> dict[str, dict[str, str]]:
records: dict[str, dict[str, str]] = {}
current_id: str | None = None
for line_number, line in enumerate(
path.read_text(encoding="utf-8").splitlines(),
start=1,
):
heading = DOMAIN_HEADING_RE.match(line)
if heading:
current_id = heading.group(1)
if current_id in records:
errors.append(
f"{path.relative_to(ROOT)}:{line_number}: "
f"duplicate heading {current_id}"
)
records[current_id] = {
"Name": heading.group(2).strip(),
}
continue
field = DOMAIN_FIELD_RE.match(line)
if field and current_id:
value = clean(field.group(2))
if not value:
errors.append(
f"{path.relative_to(ROOT)}:{line_number}: "
f"blank {field.group(1)}"
)
records[current_id][field.group(1)] = value
continue
runtime = DOMAIN_RUNTIME_RE.match(line)
if runtime and current_id:
value = clean(runtime.group(1))
runtime_status = re.split(
r"\s+(?:—|-)\s+",
value,
maxsplit=1,
)[0]
records[current_id]["Runtime Validation"] = (
runtime_status.strip("` ")
)
return records
def validate_domains(
features: list[dict[str, Any]],
errors: list[str],
) -> None:
catalog = {
feature["id"]: feature
for feature in features
if feature.get("id")
}
expected_by_domain: defaultdict[str, set[str]] = defaultdict(set)
for feature in features:
expected_by_domain[feature["domain"]].add(feature["id"])
paths = sorted(DOMAINS_DIR.glob("*.md"))
if len(paths) != EXPECTED_DOMAINS:
errors.append(
f"Expected {EXPECTED_DOMAINS} domain files, "
f"found {len(paths)}"
)
all_found: set[str] = set()
for path in paths:
domain = path.stem
records = parse_domain(path, errors)
found = set(records)
expected = expected_by_domain.get(domain, set())
all_found.update(found)
if found != expected:
missing = sorted(expected - found)
extra = sorted(found - expected)
errors.append(
f"{domain}: missing={missing}, unexpected={extra}"
)
for feature_id, record in records.items():
feature = catalog.get(feature_id)
if feature is None:
continue
expected_values = {
"Name": feature["name"],
"Domain": feature["domain"],
"Status": feature["status"],
"Evidence Maturity": feature["evidence_maturity"],
"Commit Verified": feature["verified_at_commit"],
"Runtime Validation": (
feature["runtime_validation"]["status"]
),
}
for field, expected_value in expected_values.items():
actual = record.get(field)
if actual != expected_value:
errors.append(
f"{path.relative_to(ROOT)}: "
f"{feature_id} {field}: "
f"{actual!r} != {expected_value!r}"
)
if all_found != set(catalog):
errors.append(
"Domain Markdown IDs do not match catalog JSON"
)
def validate_catalog_markdown(
features: list[dict[str, Any]],
errors: list[str],
) -> None:
expected = {
feature["id"]: feature
for feature in features
}
found: dict[str, list[str]] = {}
for line in CATALOG_MD_PATH.read_text(
encoding="utf-8"
).splitlines():
if not line.startswith("|"):
continue
cells = [
cell.strip()
for cell in line.strip().strip("|").split("|")
]
if len(cells) < 6:
continue
feature_id = clean(cells[0])
if FEATURE_ID_RE.fullmatch(feature_id):
found[feature_id] = cells
if set(found) != set(expected):
errors.append(
"feature-catalog.md IDs do not match JSON"
)
for feature_id, cells in found.items():
feature = expected[feature_id]
runtime = feature["runtime_validation"]
runtime_display = (
runtime["status"]
if runtime["required"]
else "not required"
)
actual = {
"name": cells[1].replace("\\|", "|"),
"domain": clean(cells[2]),
"status": clean(cells[3]),
"maturity": clean(cells[4]),
"runtime": clean(cells[5]),
}
wanted = {
"name": feature["name"],
"domain": feature["domain"],
"status": feature["status"],
"maturity": feature["evidence_maturity"],
"runtime": runtime_display,
}
for field, expected_value in wanted.items():
if actual[field] != expected_value:
errors.append(
f"feature-catalog.md: {feature_id} "
f"{field}: {actual[field]!r} "
f"!= {expected_value!r}"
)
def validate_reviews(
features: list[dict[str, Any]],
errors: list[str],
) -> int:
catalog = {
feature["id"]: feature
for feature in features
}
checked = 0
for path in sorted(
REVIEWS_DIR.glob("evidence-sample-*.md")
):
current_id: str | None = None
results: set[str] = set()
for line_number, line in enumerate(
path.read_text(encoding="utf-8").splitlines(),
start=1,
):
heading = REVIEW_HEADING_RE.match(line)
if heading:
current_id = heading.group(1)
continue
result = REVIEW_RESULT_RE.match(line)
if not result or current_id is None:
continue
status, maturity = result.groups()
results.add(current_id)
checked += 1
feature = catalog.get(current_id)
if feature is None:
errors.append(
f"{path.relative_to(ROOT)}:{line_number}: "
f"unknown feature {current_id}"
)
continue
if feature["status"] != status:
errors.append(
f"{current_id}: review status {status!r} "
f"!= catalog {feature['status']!r}"
)
if feature["evidence_maturity"] != maturity:
errors.append(
f"{current_id}: review maturity {maturity!r} "
f"!= catalog "
f"{feature['evidence_maturity']!r}"
)
if path.name == "evidence-sample-01.md" and len(results) != 12:
errors.append(
f"{path.relative_to(ROOT)}: expected 12 "
f"review results, found {len(results)}"
)
return checked
def validate_whitespace(errors: list[str]) -> None:
for path in sorted(ROOT.rglob("*")):
if not path.is_file():
continue
if path.suffix not in {".md", ".json", ".py", ".txt"}:
continue
for line_number, line in enumerate(
path.read_text(
encoding="utf-8",
errors="replace",
).splitlines(),
start=1,
):
if line != line.rstrip(" \t"):
errors.append(
f"{path.relative_to(ROOT)}:{line_number}: "
"trailing whitespace"
)
if any(ROOT.rglob("*.pyc")):
errors.append("Generated .pyc files exist")
if any(
path.is_dir()
for path in ROOT.rglob("__pycache__")
):
errors.append("__pycache__ exists")
def main() -> int:
errors: list[str] = []
features = load_catalog(errors)
reviewed = 0
if features:
validate_catalog(features, errors)
validate_domains(features, errors)
validate_catalog_markdown(features, errors)
reviewed = validate_reviews(features, errors)
validate_whitespace(errors)
print(f"Catalog Features: {len(features)}")
print(
"Unique Feature IDs:",
len({feature.get("id") for feature in features}),
)
print(
"Domain Files:",
len(list(DOMAINS_DIR.glob("*.md"))),
)
if features:
print(
"Statuses:",
dict(
Counter(
feature.get("status")
for feature in features
)
),
)
print(
"Evidence Maturity:",
dict(
Counter(
feature.get("evidence_maturity")
for feature in features
)
),
)
print(
"Runtime Validation:",
dict(
Counter(
(
feature.get(
"runtime_validation",
{},
).get("required"),
feature.get(
"runtime_validation",
{},
).get("status"),
)
for feature in features
)
),
)
print(f"Review Results Checked: {reviewed}")
print(f"Consistency Errors: {len(errors)}")
for error in errors:
print(f"ERROR: {error}")
return 1 if errors else 0
if __name__ == "__main__":
sys.exit(main())
+172
View File
@@ -0,0 +1,172 @@
#!/usr/bin/env python3
"""Validate the Odysseus public discovery documentation package."""
from __future__ import annotations
import argparse
import json
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
REPO_ROOT = ROOT.parents[1]
CATALOG_PATH = ROOT / "feature-catalog.json"
DOMAINS_DIR = ROOT / "domains"
EXPECTED_COMMIT = "d8a2059df8e53bc7275c45339849d14c8651e73c"
ALLOWED_STATUSES = {
"verified", "partial", "disabled", "experimental", "legacy",
"dead-code-candidate", "unverified"
}
LINE_RANGE_RE = re.compile(r"^L([1-9]\d*)-L([1-9]\d*)$")
FEATURE_HEADING_RE = re.compile(
r"^##\s+`?([A-Z][A-Z0-9]*-\d{3})`?\s+(?:—|-)\s+.+$",
re.MULTILINE,
)
LINK_RE = re.compile(r"(?<!!)\[[^\]]*\]\(([^)]+)\)")
FORBIDDEN_TERMS_RE = re.compile(
r"(roadforge|kanban|matrix|owner link|editor link|github support|"
r"private maintainer|OD-AUD-|[A-Z]{2,10}-AUD-\d+|TASK-\d+)",
re.IGNORECASE,
)
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Validate Odysseus discovery docs.")
parser.add_argument(
"--repo-root",
type=Path,
default=REPO_ROOT,
help="Path to Odysseus repository root.",
)
return parser.parse_args()
def validate() -> int:
args = parse_args()
repo_root = args.repo_root.resolve()
errors: list[str] = []
# 1. Validate Catalog JSON existence and content
if not CATALOG_PATH.is_file():
errors.append(f"Missing catalog file: {CATALOG_PATH}")
print(f"Errors: {len(errors)}")
for e in errors:
print(f"ERROR: {e}")
return 1
try:
catalog = json.loads(CATALOG_PATH.read_text(encoding="utf-8"))
except Exception as e:
errors.append(f"Failed to parse catalog JSON: {e}")
print(f"Errors: {len(errors)}")
for err in errors:
print(f"ERROR: {err}")
return 1
if not isinstance(catalog, list):
errors.append("feature-catalog.json must be a JSON array")
return 1
feature_ids = [item.get("id") for item in catalog if isinstance(item, dict)]
if len(feature_ids) != 79:
errors.append(f"Expected 79 unique feature IDs, found {len(feature_ids)}")
if len(feature_ids) != len(set(feature_ids)):
errors.append("Duplicate feature IDs found in catalog JSON")
# Domain catalog counts
catalog_domain_counts: dict[str, int] = {}
for item in catalog:
if not isinstance(item, dict):
errors.append("Catalog item is not an object")
continue
fid = item.get("id", "<missing>")
status = item.get("status")
domain = item.get("domain", "").lower()
catalog_domain_counts[domain] = catalog_domain_counts.get(domain, 0) + 1
if status not in ALLOWED_STATUSES:
errors.append(f"{fid}: Invalid status '{status}'")
evidence_list = item.get("evidence")
if not isinstance(evidence_list, list) or not evidence_list:
errors.append(f"{fid}: Missing or empty evidence list")
continue
for ev in evidence_list:
if not isinstance(ev, dict):
errors.append(f"{fid}: Evidence item is not an object")
continue
path_str = ev.get("path")
lr_str = str(ev.get("line_range", ""))
if not path_str or Path(path_str).is_absolute() or ".." in Path(path_str).parts:
errors.append(f"{fid}: Unsafe or invalid path '{path_str}'")
continue
# Check path exists in repo
target_path = repo_root / path_str
if not target_path.is_file():
errors.append(f"{fid}: Referenced path '{path_str}' does not exist on disk")
continue
# Check line range format & bounds
m = LINE_RANGE_RE.fullmatch(lr_str)
if not m:
errors.append(f"{fid}: Invalid line range format '{lr_str}' for path '{path_str}'")
continue
start, end = int(m.group(1)), int(m.group(2))
lines_cnt = len(target_path.read_text(encoding="utf-8", errors="ignore").splitlines())
if start > end or end > lines_cnt or start < 1:
errors.append(
f"{fid}: Line range '{lr_str}' exceeds file length ({lines_cnt} lines) in '{path_str}'"
)
# 2. Check Domain Markdown files
md_feature_ids: list[str] = []
domain_files = sorted(DOMAINS_DIR.glob("*.md"))
for df in domain_files:
domain_name = df.stem.lower()
content = df.read_text(encoding="utf-8")
found_ids = FEATURE_HEADING_RE.findall(content)
md_feature_ids.extend(found_ids)
if len(found_ids) != catalog_domain_counts.get(domain_name, 0):
errors.append(
f"Domain '{domain_name}' count mismatch: catalog has {catalog_domain_counts.get(domain_name, 0)}, Markdown has {len(found_ids)}"
)
if sorted(md_feature_ids) != sorted(feature_ids):
errors.append("Markdown domain feature IDs do not match catalog JSON feature IDs")
# 3. Check for forbidden/private terms, sensitive credentials, and broken links across all docs
for md_file in ROOT.rglob("*.md"):
rel_md = md_file.relative_to(ROOT)
content = md_file.read_text(encoding="utf-8")
# Forbidden terms scan
forbidden_matches = FORBIDDEN_TERMS_RE.findall(content)
if forbidden_matches:
errors.append(
f"{rel_md}: Found forbidden/internal terms: {set(forbidden_matches)}"
)
# Broken local link check
for target in LINK_RE.findall(content):
target = target.strip().strip("<>")
if not target or target.startswith(("#", "http://", "https://", "mailto:")):
continue
target_path = target.split("#", 1)[0]
resolved = (md_file.parent / target_path).resolve()
if not resolved.exists():
errors.append(f"{rel_md}: Broken local link '{target}'")
print(f"Catalog Features: {len(catalog)}")
print(f"Domain Files: {len(domain_files)}")
print(f"Validation Errors: {len(errors)}")
for err in errors:
print(f"ERROR: {err}")
return 1 if errors else 0
if __name__ == "__main__":
sys.exit(validate())
File diff suppressed because it is too large Load Diff