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
13 changes: 9 additions & 4 deletions ci/gitlab/runtime-intelligence-artifacts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,18 @@ inferedge:deployment-risk-gate:
- job: inferedge:portfolio-report
artifacts: true
script:
- mkdir -p "$INFEREDGE_REPORT_DIR"
- poetry run inferedgelab portfolio-demo-check --format json > "$INFEREDGE_REPORT_DIR/deployment_risk_summary.json"
- poetry run python scripts/check_runtime_intelligence_ci_artifacts.py --report-dir "$INFEREDGE_REPORT_DIR" --summary-out "$INFEREDGE_REPORT_DIR/runtime_intelligence_ci_artifact_gate_summary.md"
- python -c "import json, pathlib, sys; data=json.loads(pathlib.Path('$INFEREDGE_REPORT_DIR/deployment_risk_summary.json').read_text()); sys.exit(0 if data.get('status') == 'pass' else 2)"
- bash scripts/smoke_runtime_intelligence_chain.sh --output-dir "$INFEREDGE_REPORT_DIR"
artifacts:
when: always
expire_in: 14 days
paths:
- "$INFEREDGE_REPORT_DIR/edgeenv_runtime_regression.md"
- "$INFEREDGE_REPORT_DIR/edgeenv_runtime_regression.html"
- "$INFEREDGE_REPORT_DIR/runtime_anomaly_summary.md"
- "$INFEREDGE_REPORT_DIR/runtime_anomaly_summary.html"
- "$INFEREDGE_REPORT_DIR/runtime_anomaly_gate_summary.md"
- "$INFEREDGE_REPORT_DIR/runtime_intelligence_bundle_manifest_gate_summary.md"
- "$INFEREDGE_REPORT_DIR/portfolio_demo_check.json"
- "$INFEREDGE_REPORT_DIR/portfolio_demo_check.md"
- "$INFEREDGE_REPORT_DIR/deployment_risk_summary.json"
- "$INFEREDGE_REPORT_DIR/runtime_intelligence_ci_artifact_gate_summary.md"
5 changes: 5 additions & 0 deletions docs/ci/runtime_intelligence_gitlab_artifacts.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ bash scripts/smoke_runtime_intelligence_chain.sh \
--output-dir reports/runtime_intelligence_chain
```

The optional template uses the same smoke script in the final
`deployment-risk` stage so the GitLab artifact gate and the local reproduction
path validate the same file bundle.

This maps to the ecosystem ownership model:

- Runtime evidence stays additive and Lab-compatible.
Expand Down Expand Up @@ -95,6 +99,7 @@ The initial gate is conservative:
- producer schema markers for EdgeEnv history, Orchestrator feed, and AIGuard
diagnosis evidence must stay aligned with the committed smoke artifacts
- portfolio demo check status must be `pass`
- deployment risk summary status must be `pass`
- the final deployment-risk job must re-check the collected manifest/report
gate summaries and Runtime Intelligence Risk Summary markers before passing
- the bundle manifest gate summary must include validated contract markers for
Expand Down
19 changes: 19 additions & 0 deletions scripts/check_runtime_intelligence_ci_artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
"runtime_intelligence_bundle_manifest_gate_summary.md",
"runtime_anomaly_gate_summary.md",
}
REQUIRED_JSON_ARTIFACTS = {
"portfolio_demo_check.json",
"deployment_risk_summary.json",
}
REQUIRED_BUNDLE_MANIFEST_SUMMARY_MARKERS = (
"## Validated Contract Markers",
"source_repositories: Runtime, EdgeEnv, Orchestrator, AIGuard, Lab",
Expand Down Expand Up @@ -65,6 +69,7 @@ def _validate_required_files(report_dir: Path, errors: list[str]) -> None:
REQUIRED_MARKDOWN_ARTIFACTS
| REQUIRED_HTML_ARTIFACTS
| REQUIRED_SUMMARY_ARTIFACTS
| REQUIRED_JSON_ARTIFACTS
):
_record((report_dir / name).is_file(), errors, f"missing artifact: {name}")

Expand Down Expand Up @@ -117,6 +122,16 @@ def _validate_portfolio_status(path: Path, errors: list[str]) -> None:
)


def _validate_deployment_risk_status(path: Path, errors: list[str]) -> None:
payload = _load_json(path, errors, "Deployment risk summary JSON")
if payload:
_record(
payload.get("status") == "pass",
errors,
"deployment_risk_summary.json status must be pass",
)


def _write_summary(path: Path, report_dir: Path, errors: list[str]) -> None:
lines = [
"# Runtime Intelligence CI Artifact Gate",
Expand Down Expand Up @@ -152,6 +167,10 @@ def main(report_dir: str, summary_out: str = "") -> int:
)
_validate_runtime_report(report_path / "runtime_anomaly_summary.md", errors)
_validate_portfolio_status(report_path / "portfolio_demo_check.json", errors)
_validate_deployment_risk_status(
report_path / "deployment_risk_summary.json",
errors,
)

if summary_out:
_write_summary(Path(summary_out), report_path, errors)
Expand Down
89 changes: 88 additions & 1 deletion tests/test_runtime_intelligence_ci_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,9 @@ def test_runtime_intelligence_gitlab_template_keeps_local_first_artifact_contrac
assert "--guard-analysis" in text
assert "check_runtime_intelligence_artifact_bundle.py" in text
assert "runtime_anomaly_gate_summary.md" in text
assert "check_runtime_intelligence_ci_artifacts.py" in text
assert "smoke_runtime_intelligence_chain.sh --output-dir" in text
assert "runtime_intelligence_ci_artifact_gate_summary.md" in text
assert "deployment_risk_summary.json" in text
assert "needs:" in text
assert "inferedge:deterministic-anomaly-summary" in text
assert "inferedge:portfolio-report" in text
Expand Down Expand Up @@ -126,6 +127,10 @@ def test_runtime_intelligence_ci_artifact_gate_passes_for_expected_outputs(tmp_p
'{"status": "pass"}',
encoding="utf-8",
)
(report_dir / "deployment_risk_summary.json").write_text(
'{"status": "pass"}',
encoding="utf-8",
)
summary_path = tmp_path / "ci_artifact_gate_summary.md"

result = ci_artifact_gate(
Expand Down Expand Up @@ -163,6 +168,10 @@ def test_runtime_intelligence_ci_artifact_gate_fails_for_missing_risk_summary(
'{"status": "pass"}',
encoding="utf-8",
)
(report_dir / "deployment_risk_summary.json").write_text(
'{"status": "pass"}',
encoding="utf-8",
)
summary_path = tmp_path / "ci_artifact_gate_summary.md"

result = ci_artifact_gate(
Expand Down Expand Up @@ -221,6 +230,10 @@ def test_runtime_intelligence_ci_artifact_gate_fails_for_missing_contract_marker
'{"status": "pass"}',
encoding="utf-8",
)
(report_dir / "deployment_risk_summary.json").write_text(
'{"status": "pass"}',
encoding="utf-8",
)
summary_path = tmp_path / "ci_artifact_gate_summary.md"

result = ci_artifact_gate(
Expand Down Expand Up @@ -272,6 +285,10 @@ def test_runtime_intelligence_ci_artifact_gate_fails_for_missing_coverage_gap_ma
'{"status": "pass"}',
encoding="utf-8",
)
(report_dir / "deployment_risk_summary.json").write_text(
'{"status": "pass"}',
encoding="utf-8",
)
summary_path = tmp_path / "ci_artifact_gate_summary.md"

result = ci_artifact_gate(
Expand All @@ -282,3 +299,73 @@ def test_runtime_intelligence_ci_artifact_gate_fails_for_missing_coverage_gap_ma
assert result == 2
summary = summary_path.read_text(encoding="utf-8")
assert "runtime report missing marker: runtime_telemetry_field_gap" in summary


def test_runtime_intelligence_ci_artifact_gate_fails_for_failed_deployment_risk(
tmp_path,
):
report_dir = tmp_path / "runtime_intelligence_ci"
report_dir.mkdir()
for name in (
"edgeenv_runtime_regression.md",
"edgeenv_runtime_regression.html",
"runtime_anomaly_summary.html",
"portfolio_demo_check.md",
):
(report_dir / name).write_text("placeholder\n", encoding="utf-8")
(report_dir / "runtime_anomaly_summary.md").write_text(
"\n".join(
[
"## Runtime Intelligence Risk Summary",
"Lab remains the final deployment decision owner.",
"AIGuard runtime operation anomalies",
"runtime_queue_overload, runtime_thermal_instability",
"Runtime telemetry coverage gaps",
"runtime_telemetry_field_gap",
"Inspect telemetry coverage missing fields",
"guard_warning_review",
"edgeenv_runtime_regression_review",
]
),
encoding="utf-8",
)
(report_dir / "runtime_intelligence_bundle_manifest_gate_summary.md").write_text(
"\n".join(
[
"- Status: passed",
"## Validated Contract Markers",
"- source_repositories: Runtime, EdgeEnv, Orchestrator, AIGuard, Lab",
"- producer_contracts: EdgeEnv history, Orchestrator feed, AIGuard diagnosis",
"- ownership: regression_owner=edgeenv, deployment_decision_owner=lab",
"- orchestrator_mapping_hint: coverage_summary_owner=edgeenv",
"- orchestrator_mapping_hint: operation_context_role=supplemental",
"- orchestrator_mapping_hint: aiguard_evidence_candidates=runtime_queue_overload,runtime_thermal_instability",
"- aiguard_raw_context: telemetry_coverage_source=history_telemetry_coverage",
"- aiguard_raw_context: orchestrator_mapping_hint preserved",
"- edgeenv_handoff: lab_bundle_alignment validated",
]
),
encoding="utf-8",
)
(report_dir / "runtime_anomaly_gate_summary.md").write_text(
"- Status: passed\n",
encoding="utf-8",
)
(report_dir / "portfolio_demo_check.json").write_text(
'{"status": "pass"}',
encoding="utf-8",
)
(report_dir / "deployment_risk_summary.json").write_text(
'{"status": "fail"}',
encoding="utf-8",
)
summary_path = tmp_path / "ci_artifact_gate_summary.md"

result = ci_artifact_gate(
report_dir=str(report_dir),
summary_out=str(summary_path),
)

assert result == 2
summary = summary_path.read_text(encoding="utf-8")
assert "deployment_risk_summary.json status must be pass" in summary
Loading