Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
5606dcc
feat(codebase): prepare detached Hatch project environments
djm81 Oct 24, 2026
82221c4
chore(modules): ci sign changed modules
github-actions[bot] Oct 24, 2026
c7ff03f
fix(codebase): bind prepared Hatch target to selected Python base
djm81 Oct 24, 2026
e96e1f0
chore(modules): ci sign changed modules
github-actions[bot] Oct 24, 2026
9d15359
fix(codebase): isolate Python startup and honor numeric selection
djm81 Oct 24, 2026
1ec6ca2
chore(modules): ci sign changed modules
github-actions[bot] Oct 24, 2026
5ad4537
fix(tests): reject incomplete native Hatch acceptance cells
djm81 Oct 24, 2026
5da104b
refactor(tests): flatten native acceptance case dispatch
djm81 Oct 24, 2026
1e4a2d8
refactor(codebase): extract approved preparation guards
djm81 Oct 24, 2026
e658e35
chore(modules): ci sign changed modules
github-actions[bot] Oct 24, 2026
4184c1e
refactor(codebase): complete approved preparation decomposition
djm81 Oct 24, 2026
d2b41bb
fix(codebase): keep preparation outside existing targets
djm81 Oct 24, 2026
41f1560
chore(modules): ci sign changed modules
github-actions[bot] Oct 24, 2026
32d0a50
fix(codebase): fingerprint supported pytest configuration
djm81 Oct 24, 2026
20eb763
chore(modules): ci sign changed modules
github-actions[bot] Oct 24, 2026
a8ba731
fix(codebase): reject incomplete directory fingerprints
djm81 Oct 24, 2026
59611f3
chore(modules): ci sign changed modules
github-actions[bot] Oct 24, 2026
e2b6487
fix(ci): expose bounded analyzer failure provenance
djm81 Oct 24, 2026
23967ee
fix(tests): use supported diagnostic fixture execution
djm81 Oct 24, 2026
6b86578
chore(review): enforce native migration scope (#513)
djm81 Oct 25, 2026
8171f49
ci(codebase): require native Hatch acceptance (#513)
djm81 Oct 25, 2026
b658ac5
fix(codebase): preserve native project and migration identities (#513)
djm81 Oct 25, 2026
ccc5aa5
chore(modules): ci sign changed modules
github-actions[bot] Oct 25, 2026
79a2e84
ci: verify canonically signed native review fixes (#513)
djm81 Oct 25, 2026
415abf6
ci: replace capsule PR prerequisite with signed native review (#513)
djm81 Oct 25, 2026
b223f55
fix(ci): isolate pinned Semgrep dependencies from signed core
djm81 Oct 25, 2026
955fe48
fix(ci): retain trusted Semgrep transitive constraints
djm81 Oct 25, 2026
c7b9ce6
fix(ci): authenticate paired-core runtime smoke fixture
djm81 Oct 25, 2026
5b937ad
fix(review): fingerprint coverage and discover existing package tests
djm81 Oct 25, 2026
060922b
chore(modules): ci sign changed modules
github-actions[bot] Oct 25, 2026
84652ff
ci: verify signed native fixes after canonical module signing
djm81 Oct 25, 2026
670d403
fix(ci): select tests across configured coverage roots
djm81 Oct 25, 2026
20819d9
ci: rebuild exact head after analyzer timeout
djm81 Oct 25, 2026
5baeacc
fix(ci): project type analysis onto reviewed sources
djm81 Oct 25, 2026
c5418e0
fix(ci): distinguish released and paired core verification
djm81 Nov 3, 2026
dc896c7
fix(ci): map nonmirrored smart-test coverage checks
djm81 Nov 3, 2026
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
73 changes: 64 additions & 9 deletions .github/workflows/capsule-customer-execution.yml
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,13 @@ jobs:
detail = test_failure_details.get((nodeid, outcome, phase), "")
exception = re.search(r"^E\s+(?:subprocess\.)?(AssertionError|FileNotFoundError|PermissionError|CalledProcessError|RuntimeError|ImportError|ModuleNotFoundError|TypeError|ValueError)(?=:|\s|$)", detail, re.MULTILINE)
failure = {"test_failure_class": exception.group(1)} if exception else {}
native_skips = {
"Set SPECFACT_HATCH_PROOF_WORKSPACE with native Hatch and Python 3.11 prerequisites for the acceptance cell",
"Native Hatch and project Python are required for customer storage acceptance",
"Native Hatch and project Python are required for absolute declaration acceptance",
}
if outcome == "skipped" and detail in native_skips:
failure["test_diagnostic"] = "native_prerequisites_not_supplied"
for option in ("-I", "-E", "-S"):
if "project_python_option_unsupported:" + option + ";" in detail:
failure.update(test_diagnostic="project_python_option_unsupported", python_option=option)
Expand Down Expand Up @@ -382,6 +389,8 @@ jobs:
):
location = {"file": file, "line": line, "severity": severity}
public_values = {
"differential_state": {"introduced", "fixed", "unchanged", "unknown"},
"status": {"open", "fixed", "waived-by-reference"},
"category": {"clean_code", "security", "type_safety", "contracts", "testing", "style", "architecture", "tool_error", "naming", "kiss", "yagni", "dry", "solid", "ai_bloat"},
"tool": {"ruff", "radon", "radon-kiss", "semgrep", "ast", "basedpyright", "pylint", "contract_runner", "crosshair", "pytest"},
"rule": {"tool_error", "TEST_OUTCOME_NOT_PASS", "TEST_COVERAGE_POLICY_FAILED", "MISSING_ICONTRACT", "CROSSHAIR_COUNTEREXAMPLE", "CROSSHAIR_INCOMPLETE_EVIDENCE", "kiss.loc.error", "kiss.loc.warning", "kiss.nesting.error", "kiss.nesting.warning", "kiss.parameter-count.error", "kiss.parameter-count.warning", *(f"CC{number}" for number in range(1, 1001))},
Expand All @@ -390,6 +399,8 @@ jobs:
value = finding.get(key)
if isinstance(value, str) and value in allowed:
location[key] = value
if type(finding.get("blocking")) is bool:
location["blocking"] = finding["blocking"]
if location.get("tool") == "pytest" and location.get("rule") == "TEST_OUTCOME_NOT_PASS":
location.update(_public_test_observation(finding, file))
if location.get("category") == "tool_error":
Expand All @@ -401,7 +412,21 @@ jobs:
("semgrep returned empty stdout", "empty_output"),
("unrecognized crosshair output", "unrecognized_output"),
)
if location.get("tool") == "pytest":
location["diagnostic_code"] = "unclassified"
if isinstance(message, str) and location.get("tool") == "pytest":
diagnostic_message = message
producer_prefixes = {
"Unable to execute targeted tests: ": "pytest_execute_error",
"Unable to read targeted pytest evidence: ": "pytest_evidence_read_error",
}
for prefix, failure_class in producer_prefixes.items():
if message.startswith(prefix):
location["failure_class"] = failure_class
diagnostic_message = message[len(prefix):]
break
if message == "Targeted pytest observer, JUnit, and process outcomes could not be reconciled.":
location["failure_class"] = "pytest_outcome_reconciliation_failed"
pytest_codes = {
"project_pytest_request_invalid", "project_pytest_configuration_or_collection_failed",
"project_pytest_collection_error", "project_pytest_root_missing_or_invalid",
Expand All @@ -418,14 +443,14 @@ jobs:
"project_pytest_installed_coverage_unavailable", "project_pytest_installed_coverage",
"project_pytest_failure_without_observed_test", "project_pytest_internal_error",
}
prefix = message.split(":", 1)[0].split(";", 1)[0]
prefix = diagnostic_message.split(":", 1)[0].split(";", 1)[0]
if prefix in pytest_codes:
location["diagnostic_code"] = prefix
elif message.startswith("[Errno 2] "):
elif diagnostic_message.startswith("[Errno 2] "):
location["diagnostic_code"] = "file_missing"
elif message.startswith("[Errno 13] "):
elif diagnostic_message.startswith("[Errno 13] "):
location["diagnostic_code"] = "permission_denied"
if isinstance(message, str):
if isinstance(message, str) and "failure_class" not in location:
for marker, classification in classes:
if marker in message.lower():
location["failure_class"] = classification
Expand Down Expand Up @@ -475,6 +500,8 @@ jobs:
continue
incomplete_sides = [side for side in ("base", "head")
if isinstance(row.get(side), dict) and (row[side].get("execution_state") == "error" or row[side].get("evidence_outcome") == "UNKNOWN")]
if not incomplete_sides and (row.get("execution_state") == "error" or row.get("evidence_outcome") == "UNKNOWN"):
incomplete_sides = ["unavailable"]
if incomplete_sides:
print(json.dumps({"analyzer": row["id"], "incomplete_snapshot_sides": incomplete_sides}))
tokens = str(row.get("diagnostic", "")).split(":")[:3]
Expand Down Expand Up @@ -894,6 +921,13 @@ jobs:
detail = test_failure_details.get((nodeid, outcome, phase), "")
exception = re.search(r"^E\s+(?:subprocess\.)?(AssertionError|FileNotFoundError|PermissionError|CalledProcessError|RuntimeError|ImportError|ModuleNotFoundError|TypeError|ValueError)(?=:|\s|$)", detail, re.MULTILINE)
failure = {"test_failure_class": exception.group(1)} if exception else {}
native_skips = {
"Set SPECFACT_HATCH_PROOF_WORKSPACE with native Hatch and Python 3.11 prerequisites for the acceptance cell",
"Native Hatch and project Python are required for customer storage acceptance",
"Native Hatch and project Python are required for absolute declaration acceptance",
}
if outcome == "skipped" and detail in native_skips:
failure["test_diagnostic"] = "native_prerequisites_not_supplied"
for option in ("-I", "-E", "-S"):
if "project_python_option_unsupported:" + option + ";" in detail:
failure.update(test_diagnostic="project_python_option_unsupported", python_option=option)
Expand Down Expand Up @@ -946,6 +980,8 @@ jobs:
):
location = {"file": file, "line": line, "severity": severity}
public_values = {
"differential_state": {"introduced", "fixed", "unchanged", "unknown"},
"status": {"open", "fixed", "waived-by-reference"},
"category": {"clean_code", "security", "type_safety", "contracts", "testing", "style", "architecture", "tool_error", "naming", "kiss", "yagni", "dry", "solid", "ai_bloat"},
"tool": {"ruff", "radon", "radon-kiss", "semgrep", "ast", "basedpyright", "pylint", "contract_runner", "crosshair", "pytest"},
"rule": {"tool_error", "TEST_OUTCOME_NOT_PASS", "TEST_COVERAGE_POLICY_FAILED", "MISSING_ICONTRACT", "CROSSHAIR_COUNTEREXAMPLE", "CROSSHAIR_INCOMPLETE_EVIDENCE", "kiss.loc.error", "kiss.loc.warning", "kiss.nesting.error", "kiss.nesting.warning", "kiss.parameter-count.error", "kiss.parameter-count.warning", *(f"CC{number}" for number in range(1, 1001))},
Expand All @@ -954,6 +990,8 @@ jobs:
value = finding.get(key)
if isinstance(value, str) and value in allowed:
location[key] = value
if type(finding.get("blocking")) is bool:
location["blocking"] = finding["blocking"]
if location.get("tool") == "pytest" and location.get("rule") == "TEST_OUTCOME_NOT_PASS":
location.update(_public_test_observation(finding, file))
if location.get("category") == "tool_error":
Expand All @@ -965,7 +1003,21 @@ jobs:
("semgrep returned empty stdout", "empty_output"),
("unrecognized crosshair output", "unrecognized_output"),
)
if location.get("tool") == "pytest":
location["diagnostic_code"] = "unclassified"
if isinstance(message, str) and location.get("tool") == "pytest":
diagnostic_message = message
producer_prefixes = {
"Unable to execute targeted tests: ": "pytest_execute_error",
"Unable to read targeted pytest evidence: ": "pytest_evidence_read_error",
}
for prefix, failure_class in producer_prefixes.items():
if message.startswith(prefix):
location["failure_class"] = failure_class
diagnostic_message = message[len(prefix):]
break
if message == "Targeted pytest observer, JUnit, and process outcomes could not be reconciled.":
location["failure_class"] = "pytest_outcome_reconciliation_failed"
pytest_codes = {
"project_pytest_request_invalid", "project_pytest_configuration_or_collection_failed",
"project_pytest_collection_error", "project_pytest_root_missing_or_invalid",
Expand All @@ -982,14 +1034,14 @@ jobs:
"project_pytest_installed_coverage_unavailable", "project_pytest_installed_coverage",
"project_pytest_failure_without_observed_test", "project_pytest_internal_error",
}
prefix = message.split(":", 1)[0].split(";", 1)[0]
prefix = diagnostic_message.split(":", 1)[0].split(";", 1)[0]
if prefix in pytest_codes:
location["diagnostic_code"] = prefix
elif message.startswith("[Errno 2] "):
elif diagnostic_message.startswith("[Errno 2] "):
location["diagnostic_code"] = "file_missing"
elif message.startswith("[Errno 13] "):
elif diagnostic_message.startswith("[Errno 13] "):
location["diagnostic_code"] = "permission_denied"
if isinstance(message, str):
if isinstance(message, str) and "failure_class" not in location:
for marker, classification in classes:
if marker in message.lower():
location["failure_class"] = classification
Expand Down Expand Up @@ -1062,6 +1114,8 @@ jobs:
node = row.get(side)
if isinstance(node, dict) and (node.get("execution_state") == "error" or node.get("evidence_outcome") == "UNKNOWN"):
observed.setdefault(name, set()).add(side)
if name not in observed and (row.get("execution_state") == "error" or row.get("evidence_outcome") == "UNKNOWN"):
observed[name] = {"unavailable"}
return {name: sorted(sides) for name, sides in sorted(observed.items())}

report, header = {}, {}
Expand Down Expand Up @@ -1123,7 +1177,8 @@ jobs:
"diagnostic_observations": codes or ["unclassified"]}
if outcome == "oversized_report":
projected["report_header_observed"] = bool(header)
if outcome in ("structured_report", "oversized_report"):
projected["error_analyzer_sides"] = incomplete_sides(members)
projected["crosshair_failure_observations"] = crosshair_failure_observations(header.get("findings", []))
projected["crosshair_failure_observations"] = crosshair_failure_observations(evidence_source.get("findings", []))
print(json.dumps(projected))
PY_REVIEW_FAILURE
113 changes: 113 additions & 0 deletions .github/workflows/native-hatch-acceptance.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: native-hatch-acceptance

on:
workflow_call:

permissions:
contents: read

jobs:
native-hatch:
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- name: Checkout exact head
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
fetch-depth: 0
persist-credentials: false
- name: Set up project Python
id: project-python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
with:
python-version: "3.11.15"
- name: Set up controller Python
id: controller-python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
with:
python-version: "3.12.13"
- name: Install pinned native Hatch
id: hatch
shell: bash
run: |
set -euo pipefail
python -m pip install hatch==1.18.1
echo "executable=$(command -v hatch)" >> "$GITHUB_OUTPUT"
- name: Bootstrap repository Hatch environment serially
shell: bash
run: |
set -euo pipefail
hatch run python -m pip --version
hatch run dev-deps
- name: Run mapped native Hatch acceptance
env:
SPECFACT_HATCH_PROJECT_PYTHON: ${{ steps.project-python.outputs.python-path }}
SPECFACT_HATCH_EXECUTABLE: ${{ steps.hatch.outputs.executable }}
SPECFACT_HATCH_PROOF_WORKSPACE: ${{ runner.temp }}/native-hatch-proof
NATIVE_HATCH_JUNIT: ${{ runner.temp }}/native-hatch-acceptance/junit.xml
run: |
hatch run python - <<'PY'
import os
import subprocess
import sys
import xml.etree.ElementTree as ET
from pathlib import Path

import yaml

mapping = Path("openspec/changes/code-review-17-project-runtime-bootstrap/requirements-evidence.yaml")
if not mapping.is_file():
archived = list(Path("openspec/changes/archive").glob(
"*-code-review-17-project-runtime-bootstrap/requirements-evidence.yaml"
))
if len(archived) != 1:
raise SystemExit("Native acceptance mapping needs exactly one active or archived source")
mapping = archived[0]
requirements = yaml.safe_load(mapping.read_text())["requirements"]
selectors = [
case["selector"]["node_id"]
for requirement in requirements.values()
for case in requirement.get("verification_cases", [])
if case.get("method") == "test" and case.get("selector", {}).get("runner") == "pytest"
]
if not selectors or any(not isinstance(item, str) or not item for item in selectors):
raise SystemExit("Native acceptance mapping must contain nonempty pytest selectors")
if len(set(selectors)) != len(selectors):
raise SystemExit("Native acceptance mapping contains duplicate selectors")
report = Path(os.environ["NATIVE_HATCH_JUNIT"])
report.parent.mkdir(parents=True, exist_ok=True)
if report.exists():
raise SystemExit("Native acceptance report path must be fresh")
completed = subprocess.run(
[sys.executable, "-m", "pytest", *selectors,
"-o", "addopts=-ra -q --import-mode=importlib",
"-o", "junit_family=xunit1", "--junitxml", str(report)],
check=False,
)
if completed.returncode != 0 or not report.is_file():
raise SystemExit("Native acceptance pytest failed or produced no report")
result = ET.parse(report).getroot()
if result.tag not in {"testsuite", "testsuites"}:
raise SystemExit("Native acceptance report has an invalid root")
if any(element.tag in {"skipped", "failure", "error"} for element in result.iter()):
raise SystemExit("Native acceptance report contains an unsuccessful outcome")
observed = []
for case in result.iter("testcase"):
properties = case.findall("./properties/property[@name='specfact.selector']")
if len(properties) != 1 or not properties[0].get("value"):
raise SystemExit("Each testcase must report exactly one selector property")
observed.append(properties[0].get("value"))
if len(observed) != len(selectors) or set(observed) != set(selectors):
raise SystemExit("Native acceptance report does not match the exact mapped selectors")
PY
- name: Upload native Hatch acceptance evidence
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: native-hatch-acceptance
path: |
${{ runner.temp }}/native-hatch-acceptance/junit.xml
${{ runner.temp }}/native-hatch-proof/receipt.json
if-no-files-found: error
Loading
Loading