diff --git a/Makefile b/Makefile index ceddcdeb..05e3ad04 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: help demo test lint ci clean bench-leakage bench-multi-negatives bench-baseline bench-hidden-active bench-cluster-split bench-expert-ablation bench-expert-ablation-500 bench-selectivity bench-feature-decomp bench-gate bench-easy-baseline bench-charge-matched bench-order-dependent bench-precision-at-k bench-per-family bench-simulation-ablation bench-simulation-ablation-within-amp simulation-gate bench-strategy-compare regenerate-all generate phase3 pilot validate-scoring validate-scoring-phase3 validate-scoring-strict external-pilot pilot-confident presynth-qc gold-standard diversity synthesis-order novelty-broad external-consensus questionnaire gate-check ip-report benchmark-card wave0-5-gate-check wave0-5-novelty-audit wave0-5-novelty-audit-v2 wave0-5-panel wave0-5-evidence wave0-5-fill-external wave0-5b-generate wave0-5b-filter recalibration-engine recalibration-engine-dry-run validate-policy-version generate-synthetic-lab-results bump-policy-version generate-review-packet failed-candidate-report safe-publication-filter classify-negative-informativeness validate-rejection-events negative-result-dashboard calibration-audit calibration-audit-example calibration-overfit-check result-quality-filter synthetic-result-policy-check calibration-decision-checklist calibration-rollback-plan simulation-registry validate-simulation-result-schema simulation-baseline-check adapter-gate-check simulation-provenance simulation-ensemble-check simulation-ci-report simulation-deprecation-check simulation-scope-check simulation-evidence-packet artifact-version candidate-manifest artifact-changelog integration-check adapter-author-check license-check artifact-compat-check contribution-check adoption-scorecard release-gate-check decision-log release-request-check coi-check rotation-plan-check security-report-check citation-check +.PHONY: help demo test lint ci clean bench-leakage bench-multi-negatives bench-baseline bench-hidden-active bench-cluster-split bench-expert-ablation bench-expert-ablation-500 bench-selectivity bench-feature-decomp bench-gate bench-easy-baseline bench-charge-matched bench-order-dependent bench-precision-at-k bench-per-family bench-simulation-ablation bench-simulation-ablation-within-amp simulation-gate bench-strategy-compare regenerate-all generate phase3 pilot validate-scoring validate-scoring-phase3 validate-scoring-strict external-pilot pilot-confident presynth-qc gold-standard diversity synthesis-order novelty-broad external-consensus questionnaire gate-check ip-report benchmark-card wave0-5-gate-check wave0-5-novelty-audit wave0-5-novelty-audit-v2 wave0-5-panel wave0-5-evidence wave0-5-fill-external wave0-5b-generate wave0-5b-filter recalibration-engine recalibration-engine-dry-run validate-policy-version generate-synthetic-lab-results bump-policy-version generate-review-packet failed-candidate-report safe-publication-filter classify-negative-informativeness validate-rejection-events negative-result-dashboard calibration-audit calibration-audit-example calibration-overfit-check result-quality-filter synthetic-result-policy-check calibration-decision-checklist calibration-rollback-plan simulation-registry validate-simulation-result-schema simulation-baseline-check adapter-gate-check simulation-provenance simulation-ensemble-check simulation-ci-report simulation-deprecation-check simulation-scope-check simulation-evidence-packet artifact-version candidate-manifest artifact-changelog integration-check adapter-author-check license-check artifact-compat-check contribution-check adoption-scorecard release-gate-check decision-log release-request-check coi-check rotation-plan-check security-report-check citation-check roadmap-sync-check PYTHON := $(shell [ -f .venv/bin/python ] && echo .venv/bin/python || echo python3) PYTEST := $(shell [ -f .venv/bin/pytest ] && echo .venv/bin/pytest || echo pytest) @@ -870,3 +870,8 @@ citation-check: .venv/bin/openamp-foundry citation-check \ --citation-json '{"artifact_id":"OAMP-v0.7.5","citation_type":"software","title":"OpenAMP Foundry","version":"v0.7.5","authors":["Open-Problem-Lab"],"year":"2026","license_identifier":"MIT","reuse_class":"open","url":"https://github.com/Open-Problem-Lab/OpenAMP-Foundry","bibtex_key":"openamp_foundry_2026"}' @echo "Citation check complete." + +roadmap-sync-check: + .venv/bin/openamp-foundry roadmap-sync-check \ + --entry-json '{"item_id":"J8","phase":"J","description":"Add roadmap-to-issue sync checklist","priority":"B","sync_status":"synced","issue_number":807}' + @echo "Roadmap sync check complete." diff --git a/docs/evidence/METRICS_CURRENT.md b/docs/evidence/METRICS_CURRENT.md index 725e60b2..e5fe6f06 100644 --- a/docs/evidence/METRICS_CURRENT.md +++ b/docs/evidence/METRICS_CURRENT.md @@ -5,7 +5,9 @@ Machine-readable snapshot: `outputs/metrics_snapshot.json` regenerated with `mak > **Purpose:** One authoritative table of current pipeline metrics. If any doc disagrees > with this file, this file wins. Updated whenever benchmark/benchmark config changes. > -> **Last updated:** 2026-07-09 (Phase J J7 — citation and reuse guide — v0.7.5) +> **Last updated:** 2026-07-09 (Phase J J8 — roadmap-to-issue sync checklist — v0.7.6) +> **New in v0.7.6:** Roadmap-to-issue sync checklist (J8 — Loop 116) — docs/governance/ROADMAP_ISSUE_SYNC_CHECKLIST.md with 5-section sync checklist (roadmap items → issues, issues → roadmap, completed items, priority alignment, version consistency). src/openamp_foundry/governance/roadmap_sync.py with RoadmapSyncEntry dataclass (10 fields: item_id, phase, description, priority, sync_status, issue_number, pr_number, completed, completion_date, dry_lab_only), RoadmapSyncResult dataclass (5 fields: item_id, phase, passed, errors, warnings, dry_lab_only=True), VALID_SYNC_STATUSES (5: synced, missing_issue, orphaned_issue, stale, completed), VALID_PRIORITY_LEVELS (4: A, B, C, D), VALID_PHASES (7: E, F, G, H, I, J, K), validate_roadmap_sync_entry() (8 checks: non-empty item_id, valid phase, non-empty description, valid priority, valid sync_status, dry_lab_only must be True, completed must have completion_date, completion_date must be YYYY-MM-DD; priority A + missing_issue warns, stale warns, orphaned_issue warns, no issue_number warns), validate_roadmap_sync_dict() (5 required fields guard). CLI (openamp-foundry roadmap-sync-check) with --entry-json, --format text|json. make roadmap-sync-check target. 24 tests. Keeps strategy actionable: roadmap sync entries are now machine-validated. +> > **New in v0.7.5:** Citation and reuse guide (J7 — Loop 115) — docs/governance/CITATION_AND_REUSE_GUIDE.md with citation formats (inline, BibTeX), reuse table (4 artifact types, open/attribution_required/contact_required/restricted classes), attribution requirements, honest-use boundary. src/openamp_foundry/governance/citation_policy.py with CitationEntry (11 fields: artifact_id, citation_type, title, version, authors, year, license_identifier, reuse_class, url, bibtex_key, dry_lab_only), CitationValidationResult (6 fields, dry_lab_only=True), VALID_CITATION_TYPES (4: dataset, method, schema, software), VALID_REUSE_CLASSES (4: attribution_required, contact_required, open, restricted), VALID_LICENSE_IDENTIFIERS (5: Apache-2.0, CC-BY-4.0, CC-BY-NC-4.0, MIT, Proprietary), validate_citation_entry() (9 checks: non-empty artifact_id, valid citation_type, non-empty title, non-empty version, non-empty authors, 4-digit year, valid license_identifier, valid reuse_class, dry_lab_only must be True; restricted warns, contact_required+no-url warns, empty bibtex_key warns), validate_citation_dict() (8 required fields guard). CLI (openamp-foundry citation-check) with --citation-json, --format text|json. make citation-check target. 24 tests. Ecosystem clarity: citation entries are now machine-validated. > > **New in v0.7.4:** Security policy (J6 — Loop 114) — `docs/governance/SECURITY_POLICY.md` with private vulnerability reporting process, response timeline (48h acknowledgment, 30d patch), severity classification (critical/high/medium/low), 5 vulnerability categories (code_vulnerability, secret_leakage, dependency_vulnerability, safety_guardrail_bypass, dual_use_risk), out-of-scope items, disclosure process. `src/openamp_foundry/governance/security_policy.py` with `VulnerabilityReport` dataclass (9 fields: report_id, severity, category, description, affected_version, reporter_handle, report_date, status, dry_lab_only), `SecurityReportValidationResult` dataclass (6 fields, dry_lab_only=True), `VALID_SEVERITY_LEVELS` (4: critical, high, medium, low), `VALID_VULNERABILITY_CATEGORIES` (5: code_vulnerability, secret_leakage, dependency_vulnerability, safety_guardrail_bypass, dual_use_risk), `VALID_REPORT_STATUSES` (6: received, acknowledged, under_review, patched, disclosed, not_applicable), `validate_vulnerability_report()` (9 checks: report_id SEC- prefix, valid severity, valid category, non-empty description, non-empty affected_version, non-empty reporter_handle, YYYY-MM-DD date, valid status, dry_lab_only must be True; critical+received warning, safety_guardrail_bypass warning), `validate_report_dict()` (dict input with 8 required fields guard). CLI (`openamp-foundry security-report-check`) with `--report-json`, `--format text|json`. `make security-report-check` target. 18 tests. Dry-lab only. Private vulnerability reporting now has a validated structure and documented process. @@ -104,7 +106,7 @@ Machine-readable snapshot: `outputs/metrics_snapshot.json` regenerated with `mak > **New in v0.5.31:** Added dipeptide-order features for sequence-order awareness. `dipeptide_order_score` achieves AUROC 0.7861 on AMP-vs-scrambled discrimination — the strongest order-dependent feature in the pipeline. Only 7/31 features survive scrambling (amphipathicity/helix-wheel + dipeptide). All composition features are purely position-independent (exactly 0.5000 AUROC on scrambled test). > **New in v0.5.30:** Easy baseline benchmark added — charge density alone (AUROC 0.8166) outperforms the full pipeline ensemble (0.7792) on AMP-vs-Swiss-Prot-decoy discrimination. Honest finding documented: expected because pipeline optimizes for safety, not raw discrimination. > **New in v0.5.29:** Expanded benchmark to 500 AMPs + 500 composition-matched decoys (n=1000). AUROC 0.7792 (CI₉₅: 0.7505–0.8065) confirms signal generalizes. Cluster-aware CI: 0.746–0.8102. Representative AUROC: 0.778. Standard benchmark (n=191) retained for backward comparison. -> **Pipeline version:** v0.7.5 (J7 — citation and reuse guide) +> **Pipeline version:** v0.7.6 (J8 — roadmap-to-issue sync checklist) > **Branch:** main --- diff --git a/docs/governance/ROADMAP_ISSUE_SYNC_CHECKLIST.md b/docs/governance/ROADMAP_ISSUE_SYNC_CHECKLIST.md new file mode 100644 index 00000000..c305281a --- /dev/null +++ b/docs/governance/ROADMAP_ISSUE_SYNC_CHECKLIST.md @@ -0,0 +1,78 @@ +# Roadmap-to-Issue Sync Checklist + +This checklist ensures that items in `docs/research/ROADMAP.md` and +`docs/research/NEXT_100_PR_MAP.md` remain actionable by keeping them linked +to tracked GitHub issues or pull requests. + +## Purpose + +The roadmap drives research direction. Without linking roadmap items to GitHub +issues, work can stall, duplicate, or drift from the stated plan. This +checklist is run by maintainers before each release and can be validated +by the CLI tool `openamp-foundry roadmap-sync-check`. + +## Sync Checklist (run before each release) + +### 1. Roadmap items → Issues + +For each open item in ROADMAP.md and NEXT_100_PR_MAP.md: + +- [ ] Item has a corresponding open GitHub issue or PR number +- [ ] Issue title matches the roadmap item description closely +- [ ] Issue is assigned to a milestone matching the roadmap version +- [ ] Issue labels include `roadmap` and the relevant phase (e.g. `phase-j`) +- [ ] Issue body references the roadmap item by ID (e.g. `J8`) + +### 2. Issues → Roadmap + +For each open issue labeled `roadmap`: + +- [ ] Issue maps to a specific item in ROADMAP.md or NEXT_100_PR_MAP.md +- [ ] No orphaned issues labeled `roadmap` without a matching roadmap item +- [ ] Closed issues are reflected in the roadmap as `✓` completed items + +### 3. Completed items + +For each completed (✓) item in ROADMAP.md: + +- [ ] Corresponding GitHub issue or PR is closed +- [ ] PR number is referenced in the roadmap entry +- [ ] METRICS_CURRENT.md is updated with the version bump +- [ ] ROADMAP.md entry includes the completion date + +### 4. Priority alignment + +- [ ] NEXT_100_PR_MAP.md priority column (A/B/C/D) reflects current bottlenecks +- [ ] Top-priority items (A) have open issues already filed +- [ ] No item has been at priority A for more than 2 release cycles without progress + +### 5. Version consistency + +- [ ] ROADMAP.md versions match METRICS_CURRENT.md pipeline version +- [ ] Each version bump in ROADMAP.md has a corresponding git tag or PR +- [ ] No version appears in ROADMAP.md without a corresponding METRICS_CURRENT.md entry + +## Machine Validation + +Roadmap sync entries can be validated with: + +```bash +openamp-foundry roadmap-sync-check --entry-json '{\"item_id\": \"J8\", ...}' +``` + +See `src/openamp_foundry/governance/roadmap_sync.py` for the RoadmapSyncEntry schema. + +## When to Run + +- Before every minor version release +- When adding new items to NEXT_100_PR_MAP.md +- When closing issues labeled `roadmap` +- Monthly, as part of the governance review cycle + +## Linked Policies + +- [docs/research/ROADMAP.md](../research/ROADMAP.md) — live roadmap +- [docs/research/NEXT_100_PR_MAP.md](../research/NEXT_100_PR_MAP.md) — backlog +- [docs/evidence/METRICS_CURRENT.md](../evidence/METRICS_CURRENT.md) — version history +- [GOVERNANCE.md](../../GOVERNANCE.md) — governance structure +- [docs/governance/MAINTAINER_ROTATION_PLAN.md](MAINTAINER_ROTATION_PLAN.md) — maintainer responsibilities diff --git a/docs/research/ROADMAP.md b/docs/research/ROADMAP.md index ed349b48..cfbafa41 100644 --- a/docs/research/ROADMAP.md +++ b/docs/research/ROADMAP.md @@ -1,5 +1,29 @@ # Roadmap +## v0.7.6 — Loop 116: Phase J J8 — Roadmap-to-Issue Sync Checklist ✓ (2026-07-09) + +`docs/governance/ROADMAP_ISSUE_SYNC_CHECKLIST.md` with 5-section checklist: +roadmap items → issues (5 checks), issues → roadmap (3 checks), completed +items (4 checks), priority alignment (3 checks), version consistency (3 checks). + +`src/openamp_foundry/governance/roadmap_sync.py` with `RoadmapSyncEntry` +dataclass (10 fields: item_id, phase, description, priority, sync_status, +issue_number, pr_number, completed, completion_date, dry_lab_only), +`RoadmapSyncResult` dataclass (5 fields, dry_lab_only=True), +`VALID_SYNC_STATUSES` (5: synced, missing_issue, orphaned_issue, stale, +completed), `VALID_PRIORITY_LEVELS` (4: A, B, C, D), `VALID_PHASES` (7: +E, F, G, H, I, J, K), `validate_roadmap_sync_entry()` (8 checks + 4 warning +conditions), `validate_roadmap_sync_dict()` (5 required fields guard). + +CLI (`openamp-foundry roadmap-sync-check`) with `--entry-json` (required), +`--format text|json`. Handler `_run_roadmap_sync_check` in reports.py. + +`make roadmap-sync-check` target. 24 tests. **3624 total.** + +Keeps strategy actionable: roadmap sync entries are machine-validated, +priority A items without issues get an immediate warning, and stale or +orphaned items are flagged for cleanup. + ## v0.7.5 — Loop 115: Phase J J7 — Citation and Reuse Guide ✓ (2026-07-09) `docs/governance/CITATION_AND_REUSE_GUIDE.md` with citation formats (inline, diff --git a/src/openamp_foundry/cli/commands/reports.py b/src/openamp_foundry/cli/commands/reports.py index 30f41d39..7097a39c 100644 --- a/src/openamp_foundry/cli/commands/reports.py +++ b/src/openamp_foundry/cli/commands/reports.py @@ -2715,3 +2715,37 @@ def _run_citation_check(args: argparse.Namespace) -> int: print(" Citation entry validated. Ready for publication reference.") return 0 if result.passed else 3 + + +def _run_roadmap_sync_check(args: argparse.Namespace) -> int: + """Validate a roadmap sync entry.""" + import json as _json + from openamp_foundry.governance.roadmap_sync import validate_roadmap_sync_dict + + try: + d = _json.loads(args.entry_json) + except _json.JSONDecodeError as exc: + print(_json.dumps({"status": "error", "error": f"Invalid JSON: {exc}"})) + return 2 + + if not isinstance(d, dict): + print(_json.dumps({"status": "error", "error": "--entry-json must be a JSON object"})) + return 2 + + result = validate_roadmap_sync_dict(d) + output_format = getattr(args, "format", "text") + + if output_format == "json": + import dataclasses + print(_json.dumps(dataclasses.asdict(result), indent=2)) + else: + status = "PASS" if result.passed else "FAIL" + print(f"Roadmap sync entry {result.item_id} (phase {result.phase}): {status}") + for e in result.errors: + print(f" ERROR: {e}") + for w in result.warnings: + print(f" WARN: {w}") + if result.passed: + print(" Roadmap sync entry validated. Strategy is actionable.") + + return 0 if result.passed else 3 diff --git a/src/openamp_foundry/cli/main.py b/src/openamp_foundry/cli/main.py index 305dafba..a8e97a61 100644 --- a/src/openamp_foundry/cli/main.py +++ b/src/openamp_foundry/cli/main.py @@ -14,6 +14,7 @@ from openamp_foundry.cli.commands.reports import _run_rotation_plan_check from openamp_foundry.cli.commands.reports import _run_security_report_check from openamp_foundry.cli.commands.reports import _run_citation_check +from openamp_foundry.cli.commands.reports import _run_roadmap_sync_check import argparse import json @@ -1962,6 +1963,24 @@ def build_parser() -> argparse.ArgumentParser: help="Output format (default: text).", ) + # ── Roadmap sync check (Phase J J8) ───────────────────────────── + rsc = sub.add_parser( + "roadmap-sync-check", + help=( + "Validate a roadmap-to-issue sync entry. Ensures roadmap items " + "are tracked and actionable. Dry-lab only." + ), + ) + rsc.add_argument( + "--entry-json", type=str, required=True, + help="JSON string of a RoadmapSyncEntry dict (required).", + ) + rsc.add_argument( + "--format", type=str, default="text", + choices=["text", "json"], + help="Output format (default: text).", + ) + # ── Simulation-result confidence interval report (H7) ──────────── sicr = sub.add_parser( "simulation-ci-report", @@ -2221,6 +2240,9 @@ def main(argv: list[str] | None = None) -> int: if args.command == "citation-check": return _run_citation_check(args) + if args.command == "roadmap-sync-check": + return _run_roadmap_sync_check(args) + parser.error("unknown command") return 2 diff --git a/src/openamp_foundry/governance/roadmap_sync.py b/src/openamp_foundry/governance/roadmap_sync.py new file mode 100644 index 00000000..c225356b --- /dev/null +++ b/src/openamp_foundry/governance/roadmap_sync.py @@ -0,0 +1,162 @@ +"""Roadmap-to-issue sync validation for OpenAMP Foundry.""" +from __future__ import annotations + +import re +from dataclasses import dataclass, field +from typing import List, Optional + +VALID_SYNC_STATUSES: set[str] = { + "synced", + "missing_issue", + "orphaned_issue", + "stale", + "completed", +} +VALID_PRIORITY_LEVELS: set[str] = {"A", "B", "C", "D"} +VALID_PHASES: set[str] = { + "E", + "F", + "G", + "H", + "I", + "J", + "K", +} + + +@dataclass +class RoadmapSyncEntry: + """A single roadmap item with its sync status.""" + + item_id: str + phase: str + description: str + priority: str + sync_status: str + issue_number: Optional[int] = None + pr_number: Optional[int] = None + completed: bool = False + completion_date: str = "" + dry_lab_only: bool = True + + +@dataclass +class RoadmapSyncResult: + """Result of validating a RoadmapSyncEntry.""" + + item_id: str + phase: str + passed: bool + errors: List[str] = field(default_factory=list) + warnings: List[str] = field(default_factory=list) + dry_lab_only: bool = True + + +def validate_roadmap_sync_entry(entry: RoadmapSyncEntry) -> RoadmapSyncResult: + """Validate a RoadmapSyncEntry against policy rules.""" + errors: list[str] = [] + warnings: list[str] = [] + + if not entry.item_id or not entry.item_id.strip(): + errors.append("item_id must be non-empty") + + if entry.phase not in VALID_PHASES: + errors.append( + f"phase must be one of {sorted(VALID_PHASES)}, got: {entry.phase!r}" + ) + + if not entry.description or not entry.description.strip(): + errors.append("description must be non-empty") + + if entry.priority not in VALID_PRIORITY_LEVELS: + errors.append( + f"priority must be one of {sorted(VALID_PRIORITY_LEVELS)}, " + f"got: {entry.priority!r}" + ) + + if entry.sync_status not in VALID_SYNC_STATUSES: + errors.append( + f"sync_status must be one of {sorted(VALID_SYNC_STATUSES)}, " + f"got: {entry.sync_status!r}" + ) + + if not entry.dry_lab_only: + errors.append("dry_lab_only must be True for roadmap sync entries") + + # Completed items must have a completion_date + if entry.completed and not entry.completion_date: + errors.append( + "completed items must have a non-empty completion_date" + ) + + # completion_date must be YYYY-MM-DD if provided + if entry.completion_date and not re.match(r"^\d{4}-\d{2}-\d{2}$", entry.completion_date): + errors.append( + f"completion_date must be YYYY-MM-DD, got: {entry.completion_date!r}" + ) + + # Warnings + if entry.sync_status == "missing_issue" and entry.priority == "A": + warnings.append( + "priority A item has sync_status 'missing_issue': " + "file a GitHub issue immediately" + ) + + if entry.sync_status == "stale": + warnings.append( + "sync_status is 'stale': this item may have drifted from the " + "roadmap — review and update" + ) + + if entry.sync_status == "orphaned_issue": + warnings.append( + "sync_status is 'orphaned_issue': a GitHub issue exists with " + "no matching roadmap item — add to roadmap or close the issue" + ) + + if entry.issue_number is None and not entry.completed: + warnings.append( + "no issue_number: open a GitHub issue to track this roadmap item" + ) + + return RoadmapSyncResult( + item_id=entry.item_id, + phase=entry.phase, + passed=len(errors) == 0, + errors=errors, + warnings=warnings, + dry_lab_only=True, + ) + + +def validate_roadmap_sync_dict(d: dict) -> RoadmapSyncResult: + """Validate a dict representation of a RoadmapSyncEntry.""" + required = [ + "item_id", + "phase", + "description", + "priority", + "sync_status", + ] + missing = [f for f in required if f not in d] + if missing: + return RoadmapSyncResult( + item_id=d.get("item_id", ""), + phase=d.get("phase", ""), + passed=False, + errors=[f"Missing required fields: {missing}"], + dry_lab_only=True, + ) + entry = RoadmapSyncEntry( + item_id=d["item_id"], + phase=d["phase"], + description=d["description"], + priority=d["priority"], + sync_status=d["sync_status"], + issue_number=d.get("issue_number"), + pr_number=d.get("pr_number"), + completed=d.get("completed", False), + completion_date=d.get("completion_date", ""), + dry_lab_only=d.get("dry_lab_only", True), + ) + return validate_roadmap_sync_entry(entry) diff --git a/tests/governance/test_roadmap_sync.py b/tests/governance/test_roadmap_sync.py new file mode 100644 index 00000000..b5203536 --- /dev/null +++ b/tests/governance/test_roadmap_sync.py @@ -0,0 +1,172 @@ +"""Tests for roadmap-to-issue sync validation.""" +import pytest +from openamp_foundry.governance.roadmap_sync import ( + VALID_PHASES, + VALID_PRIORITY_LEVELS, + VALID_SYNC_STATUSES, + RoadmapSyncEntry, + RoadmapSyncResult, + validate_roadmap_sync_dict, + validate_roadmap_sync_entry, +) + + +def _valid_entry(**kwargs) -> RoadmapSyncEntry: + defaults = dict( + item_id="J8", + phase="J", + description="Add roadmap-to-issue sync checklist", + priority="B", + sync_status="synced", + issue_number=807, + pr_number=807, + completed=False, + completion_date="", + dry_lab_only=True, + ) + defaults.update(kwargs) + return RoadmapSyncEntry(**defaults) + + +class TestValidSyncEntry: + def test_valid_entry_passes(self): + result = validate_roadmap_sync_entry(_valid_entry()) + assert result.passed is True + assert result.errors == [] + + def test_result_has_correct_item_id(self): + result = validate_roadmap_sync_entry(_valid_entry()) + assert result.item_id == "J8" + + def test_result_has_correct_phase(self): + result = validate_roadmap_sync_entry(_valid_entry()) + assert result.phase == "J" + + def test_all_phases_accepted(self): + for phase in VALID_PHASES: + result = validate_roadmap_sync_entry(_valid_entry(phase=phase)) + assert result.passed is True, f"Expected phase {phase} to pass" + + def test_all_priorities_accepted(self): + for priority in VALID_PRIORITY_LEVELS: + result = validate_roadmap_sync_entry(_valid_entry(priority=priority)) + assert result.passed is True, f"Expected priority {priority} to pass" + + def test_all_sync_statuses_accepted(self): + for status in VALID_SYNC_STATUSES: + result = validate_roadmap_sync_entry(_valid_entry(sync_status=status)) + assert result.passed is True, f"Expected status {status} to pass" + + def test_completed_entry_with_date_passes(self): + result = validate_roadmap_sync_entry( + _valid_entry(completed=True, completion_date="2026-07-09") + ) + assert result.passed is True + + +class TestInvalidSyncEntry: + def test_empty_item_id_fails(self): + result = validate_roadmap_sync_entry(_valid_entry(item_id="")) + assert result.passed is False + assert any("item_id" in e for e in result.errors) + + def test_invalid_phase_fails(self): + result = validate_roadmap_sync_entry(_valid_entry(phase="Z")) + assert result.passed is False + assert any("phase" in e for e in result.errors) + + def test_empty_description_fails(self): + result = validate_roadmap_sync_entry(_valid_entry(description="")) + assert result.passed is False + assert any("description" in e for e in result.errors) + + def test_invalid_priority_fails(self): + result = validate_roadmap_sync_entry(_valid_entry(priority="E")) + assert result.passed is False + assert any("priority" in e for e in result.errors) + + def test_invalid_sync_status_fails(self): + result = validate_roadmap_sync_entry(_valid_entry(sync_status="unknown")) + assert result.passed is False + assert any("sync_status" in e for e in result.errors) + + def test_dry_lab_only_false_fails(self): + result = validate_roadmap_sync_entry(_valid_entry(dry_lab_only=False)) + assert result.passed is False + assert any("dry_lab_only" in e for e in result.errors) + + def test_completed_without_date_fails(self): + result = validate_roadmap_sync_entry( + _valid_entry(completed=True, completion_date="") + ) + assert result.passed is False + assert any("completion_date" in e for e in result.errors) + + def test_invalid_completion_date_format_fails(self): + result = validate_roadmap_sync_entry( + _valid_entry(completed=True, completion_date="09-07-2026") + ) + assert result.passed is False + assert any("completion_date" in e for e in result.errors) + + +class TestSyncWarnings: + def test_priority_a_missing_issue_warns(self): + result = validate_roadmap_sync_entry( + _valid_entry(priority="A", sync_status="missing_issue") + ) + assert result.passed is True + assert any("missing_issue" in w for w in result.warnings) + + def test_stale_status_warns(self): + result = validate_roadmap_sync_entry(_valid_entry(sync_status="stale")) + assert result.passed is True + assert any("stale" in w for w in result.warnings) + + def test_orphaned_issue_warns(self): + result = validate_roadmap_sync_entry( + _valid_entry(sync_status="orphaned_issue") + ) + assert result.passed is True + assert any("orphaned_issue" in w for w in result.warnings) + + def test_no_issue_number_warns(self): + result = validate_roadmap_sync_entry( + _valid_entry(issue_number=None, sync_status="missing_issue") + ) + assert result.passed is True + assert any("issue_number" in w for w in result.warnings) + + +class TestValidateRoadmapSyncDict: + def test_valid_dict_passes(self): + d = { + "item_id": "J8", + "phase": "J", + "description": "Add roadmap-to-issue sync checklist", + "priority": "B", + "sync_status": "synced", + "issue_number": 807, + } + result = validate_roadmap_sync_dict(d) + assert result.passed is True + + def test_missing_fields_fails(self): + result = validate_roadmap_sync_dict({"item_id": "J8"}) + assert result.passed is False + assert any("Missing" in e for e in result.errors) + + +class TestConstants: + def test_valid_sync_statuses_has_5(self): + assert len(VALID_SYNC_STATUSES) == 5 + + def test_valid_priority_levels_has_4(self): + assert len(VALID_PRIORITY_LEVELS) == 4 + + def test_valid_phases_has_7(self): + assert len(VALID_PHASES) == 7 + + def test_all_results_dry_lab_only_true(self): + result = validate_roadmap_sync_entry(_valid_entry()) + assert result.dry_lab_only is True diff --git a/tests/test_test_count_regression.py b/tests/test_test_count_regression.py index e5fc127a..b41cee02 100644 --- a/tests/test_test_count_regression.py +++ b/tests/test_test_count_regression.py @@ -2,11 +2,11 @@ import subprocess import sys -# Baseline: 3599 tests as of Loop 115 (2026-07-09) -- Phase J J7 (citation and reuse guide) +# Baseline: 3624 tests as of Loop 116 (2026-07-09) -- Phase J J8 (roadmap-to-issue sync checklist) # Note: 3 pre-existing collection errors in test_benchmark_calibration.py, # test_benchmark_charge_distribution.py, test_benchmark_cheap_enemies.py. # Raise threshold if legitimate tests are removed; lower threshold is okay. -BASELINE = 3599 +BASELINE = 3624 TOLERANCE = 0.05 # allow 5% fluctuation