Spaces:
Sleeping
Sleeping
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import re | |
| import time | |
| import urllib.error | |
| import urllib.request | |
| from datetime import datetime | |
| from pathlib import Path | |
| from typing import Any | |
| REPO_ROOT = Path(__file__).resolve().parents[1] | |
| FIXTURE_DIR = REPO_ROOT / "docs" / "demo-fixtures" | |
| RUNTIME_DIR = REPO_ROOT / "skillwiki-launcher" / "runtime" / "demo-state-runs" | |
| APPROVED_PAST_SKILL_NAMES = { | |
| "brand_guidelines", | |
| "claude_api", | |
| "docx", | |
| "frontend_design", | |
| } | |
| SCRIPT_SKILL = "script_dry_run_analyzer" | |
| LEGACY_SKILL = "legacy_login_flow_imported" | |
| class ApiClient: | |
| def __init__(self, base_url: str, raw_dir: Path, timeout_s: int) -> None: | |
| self.base_url = base_url.rstrip("/") | |
| self.raw_dir = raw_dir | |
| self.timeout_s = timeout_s | |
| self.raw_dir.mkdir(parents=True, exist_ok=True) | |
| def get(self, path: str, *, label: str, timeout_s: int | None = None) -> Any: | |
| return self._request("GET", path, None, label, timeout_s or self.timeout_s) | |
| def post( | |
| self, | |
| path: str, | |
| payload: dict[str, Any], | |
| *, | |
| label: str, | |
| timeout_s: int | None = None, | |
| ) -> Any: | |
| return self._request("POST", path, payload, label, timeout_s or self.timeout_s) | |
| def _request( | |
| self, | |
| method: str, | |
| path: str, | |
| payload: dict[str, Any] | None, | |
| label: str, | |
| timeout_s: int, | |
| ) -> Any: | |
| url = f"{self.base_url}{path}" | |
| data = None if payload is None else json.dumps(payload, ensure_ascii=False).encode("utf-8") | |
| request = urllib.request.Request(url, data=data, method=method) | |
| request.add_header("Accept", "application/json") | |
| if payload is not None: | |
| request.add_header("Content-Type", "application/json") | |
| start = time.perf_counter() | |
| try: | |
| with urllib.request.urlopen(request, timeout=timeout_s) as response: | |
| body = response.read().decode("utf-8", errors="replace") | |
| status = response.status | |
| except urllib.error.HTTPError as exc: | |
| body = exc.read().decode("utf-8", errors="replace") | |
| status = exc.code | |
| except Exception as exc: # pragma: no cover - useful for operator reports | |
| body = json.dumps({"error": str(exc)}, ensure_ascii=False) | |
| status = 0 | |
| elapsed_ms = round((time.perf_counter() - start) * 1000, 2) | |
| parsed = _parse_body(body) | |
| envelope = { | |
| "label": label, | |
| "method": method, | |
| "url": url, | |
| "status_code": status, | |
| "elapsed_ms": elapsed_ms, | |
| "request_preview": _preview_payload(payload), | |
| "body": parsed, | |
| } | |
| (self.raw_dir / f"{_safe_filename(label)}.json").write_text( | |
| json.dumps(envelope, indent=2, ensure_ascii=False), | |
| encoding="utf-8", | |
| ) | |
| if isinstance(parsed, dict): | |
| parsed = dict(parsed) | |
| parsed["_http_status"] = status | |
| parsed["_elapsed_ms"] = elapsed_ms | |
| return parsed | |
| return {"_http_status": status, "_elapsed_ms": elapsed_ms, "body": parsed} | |
| def main() -> int: | |
| parser = argparse.ArgumentParser( | |
| description="Restore a repeatable local SkillOS demo state through public backend APIs." | |
| ) | |
| parser.add_argument("--api-base", default="http://127.0.0.1:8001/api/v1") | |
| parser.add_argument("--frontend-base", default="http://127.0.0.1:5174") | |
| parser.add_argument("--fixture-dir", type=Path, default=FIXTURE_DIR) | |
| parser.add_argument("--run-id", default=datetime.now().strftime("%Y%m%d-%H%M%S")) | |
| parser.add_argument("--request-timeout", type=int, default=360) | |
| parser.add_argument("--harness-timeout", type=int, default=160) | |
| parser.add_argument("--skip-approved-import", action="store_true") | |
| parser.add_argument("--skip-harness", action="store_true") | |
| parser.add_argument("--skip-related-graph", action="store_true") | |
| args = parser.parse_args() | |
| fixture_dir = args.fixture_dir.resolve() | |
| if not fixture_dir.exists(): | |
| raise SystemExit(f"Fixture directory not found: {fixture_dir}") | |
| run_dir = RUNTIME_DIR / f"restore-demo-state-{args.run_id}" | |
| raw_dir = run_dir / "raw" | |
| run_dir.mkdir(parents=True, exist_ok=True) | |
| client = ApiClient(args.api_base, raw_dir, args.request_timeout) | |
| summary: dict[str, Any] = { | |
| "started_at": datetime.now().isoformat(timespec="seconds"), | |
| "api_base": client.base_url, | |
| "frontend_base": args.frontend_base.rstrip("/"), | |
| "fixture_dir": str(fixture_dir), | |
| "run_dir": str(run_dir), | |
| "approved_import": {}, | |
| "harness": {}, | |
| "related_graph": {}, | |
| "errors": [], | |
| "notes": [ | |
| "This restore is intended for local memory-backend demos.", | |
| "All imported content stays in S1 Candidate unless harness verification promotes a repaired version to S3.", | |
| "The fixtures are synthetic and safe to commit; no API keys are stored here.", | |
| ], | |
| } | |
| if not args.skip_approved_import: | |
| summary["approved_import"] = restore_approved_import(client, fixture_dir) | |
| if not args.skip_harness: | |
| summary["harness"] = restore_harness_checks(client, fixture_dir, args.harness_timeout) | |
| if not args.skip_related_graph: | |
| summary["related_graph"] = restore_related_graph(client, fixture_dir) | |
| summary["finished_at"] = datetime.now().isoformat(timespec="seconds") | |
| summary["scores"] = score_summary(summary) | |
| write_report(run_dir, summary) | |
| print(json.dumps({ | |
| "run_dir": str(run_dir), | |
| "scores": summary["scores"], | |
| "approved_import": _small_import_summary(summary.get("approved_import", {})), | |
| "harness": summary.get("harness", {}).get("scores", {}), | |
| "related_graph": summary.get("related_graph", {}).get("scores", {}), | |
| }, indent=2, ensure_ascii=False)) | |
| return 0 if summary["scores"]["overall"] >= 0.99 else 1 | |
| def restore_approved_import(client: ApiClient, fixture_dir: Path) -> dict[str, Any]: | |
| result: dict[str, Any] = { | |
| "created": [], | |
| "skipped": [], | |
| "errors": [], | |
| "parse_results": [], | |
| } | |
| existing_by_name = skills_by_name(client.get("/skills?limit=1000", label="skills_before_approved_import")) | |
| cases = [ | |
| { | |
| "label": "approved_past_skills", | |
| "source_type": "past_skills", | |
| "content": read_text(fixture_dir / "approved_past_skills.json"), | |
| "select_names": APPROVED_PAST_SKILL_NAMES, | |
| }, | |
| { | |
| "label": "document_ctx2skill_sample", | |
| "source_type": "document", | |
| "content": read_text(fixture_dir / "document_ctx2skill_sample.md"), | |
| "select_names": {"document_grounded_extractor"}, | |
| "name_override": "document_grounded_extractor", | |
| }, | |
| { | |
| "label": "script_dry_run_sample", | |
| "source_type": "script", | |
| "content": read_text(fixture_dir / "script_dry_run_sample.md"), | |
| "select_names": {SCRIPT_SKILL}, | |
| "name_override": SCRIPT_SKILL, | |
| }, | |
| { | |
| "label": "legacy_login_past_skill", | |
| "source_type": "past_skills", | |
| "content": read_text(fixture_dir / "legacy_login_past_skill.json"), | |
| "select_names": {LEGACY_SKILL}, | |
| }, | |
| ] | |
| for case in cases: | |
| parsed = parse_source(client, case["source_type"], case["content"], label=case["label"]) | |
| units = parsed.get("units") or [] | |
| selected_units = [ | |
| unit for unit in units | |
| if str(unit.get("proposed_skill_name") or "") in case["select_names"] | |
| ] | |
| if not selected_units and units and case.get("name_override"): | |
| selected_units = [units[0]] | |
| result["parse_results"].append({ | |
| "label": case["label"], | |
| "source_type": case["source_type"], | |
| "http_status": parsed.get("_http_status"), | |
| "success": parsed.get("success"), | |
| "unit_count": len(units), | |
| "selected_count": len(selected_units), | |
| "selected_names": [unit.get("proposed_skill_name") for unit in selected_units], | |
| }) | |
| for unit in selected_units: | |
| payload = candidate_review_payload(unit) | |
| if case.get("name_override"): | |
| payload["name"] = case["name_override"] | |
| payload["author"] = "demo_state_restore" | |
| payload["tags"] = unique([ | |
| *payload.get("tags", []), | |
| "demo-state-restore", | |
| "public-demo-fixture", | |
| case["label"], | |
| ])[:12] | |
| evaluation = dict(payload.get("evaluation") or {}) | |
| evaluation["validation_summary"] = ( | |
| "Public demo fixture restored through /ingest/create-candidate; " | |
| "S1 Candidate unless harness validation promotes a repaired version." | |
| ) | |
| payload["evaluation"] = evaluation | |
| name = payload["name"] | |
| if name in existing_by_name: | |
| result["skipped"].append({ | |
| "name": name, | |
| "reason": "already exists", | |
| "skill_id": existing_by_name[name].get("skill_id"), | |
| "state": existing_by_name[name].get("state"), | |
| }) | |
| continue | |
| created = client.post( | |
| "/ingest/create-candidate", | |
| payload, | |
| label=f"create_{name}", | |
| timeout_s=180, | |
| ) | |
| if created.get("_http_status") not in {200, 201} or not created.get("success"): | |
| result["errors"].append({ | |
| "stage": "create", | |
| "name": name, | |
| "status": created.get("_http_status"), | |
| "detail": created.get("detail") or created.get("error"), | |
| }) | |
| continue | |
| skill = created.get("created_skill") or {} | |
| existing_by_name[name] = skill | |
| result["created"].append(skill) | |
| after = client.get("/skills?limit=1000", label="skills_after_approved_import") | |
| result["skill_count_after"] = count_skills(after) | |
| return result | |
| def restore_harness_checks(client: ApiClient, fixture_dir: Path, harness_timeout: int) -> dict[str, Any]: | |
| result: dict[str, Any] = { | |
| "checks": [], | |
| "errors": [], | |
| } | |
| skills = client.get("/skills?limit=1000", label="skills_before_harness_restore") | |
| by_name = skills_by_preferred_name(skills) | |
| missing = [name for name in [SCRIPT_SKILL, LEGACY_SKILL] if name not in by_name] | |
| if missing: | |
| result["errors"].append({"stage": "lookup", "missing": missing}) | |
| result["scores"] = {"expectation_pass_rate": 0.0, "positive_pass_rate": 0.0, "negative_rejection_rate": 0.0} | |
| return result | |
| for name in [SCRIPT_SKILL, LEGACY_SKILL]: | |
| by_name[name] = ensure_draft_or_verified(client, by_name[name]) | |
| script_context = read_text(fixture_dir / "script_shell_installer.sh")[:4000] | |
| script_specs = list(by_name[SCRIPT_SKILL].get("evaluation", {}).get("verifier_specs") or []) | |
| legacy_specs = [ | |
| {"type": "json_nonempty", "path": "input.target_url"}, | |
| {"type": "json_nonempty", "path": "input.username"}, | |
| {"type": "json_nonempty", "path": "input.password_secret_ref"}, | |
| {"type": "json_nonempty", "path": "input.success_signal"}, | |
| *list(by_name[LEGACY_SKILL].get("evaluation", {}).get("verifier_specs") or []), | |
| ] | |
| checks = [ | |
| { | |
| "label": "script_positive_promote", | |
| "skill_name": SCRIPT_SKILL, | |
| "expected": "promotion_allowed", | |
| "promote_on_pass": True, | |
| "allow_repair": True, | |
| "test_case": { | |
| "test_id": "script-positive-dry-run", | |
| "name": "Script dry-run analyzer positive fixture", | |
| "goal": "Analyze a shell installer without executing it and return verifier-friendly JSON.", | |
| "input_data": { | |
| "task": "Dry-run analyze the shell script for entrypoint, arguments, dependencies, and side effects. Do not execute it.", | |
| "script_context": script_context, | |
| "dry_run": True, | |
| "allowed_paths": [str(fixture_dir / "script_shell_installer.sh")], | |
| }, | |
| "verifier_specs": script_specs, | |
| "timeout_s": harness_timeout, | |
| }, | |
| }, | |
| { | |
| "label": "script_negative_unsafe_input", | |
| "skill_name": SCRIPT_SKILL, | |
| "expected": "not_promotion_allowed", | |
| "promote_on_pass": False, | |
| "allow_repair": False, | |
| "test_case": { | |
| "test_id": "script-negative-no-dry-run", | |
| "name": "Script analyzer rejects unsafe input contract", | |
| "goal": "The verifier should reject unsafe script-analysis inputs.", | |
| "input_data": { | |
| "task": "Analyze and execute this installer.", | |
| "script_context": script_context[:1200], | |
| "dry_run": False, | |
| "allowed_paths": [], | |
| }, | |
| "verifier_specs": script_specs, | |
| "timeout_s": harness_timeout, | |
| }, | |
| }, | |
| { | |
| "label": "legacy_login_positive_promote", | |
| "skill_name": LEGACY_SKILL, | |
| "expected": "promotion_allowed", | |
| "promote_on_pass": True, | |
| "allow_repair": True, | |
| "test_case": { | |
| "test_id": "legacy-login-positive", | |
| "name": "Legacy login workflow positive fixture", | |
| "goal": "Normalize the legacy login flow into result/evidence/validation JSON.", | |
| "input_data": { | |
| "task": "Dry-run normalize a login workflow without opening the website or using a real password.", | |
| "source_context": "Legacy workflow: open the login URL, click username, type username, click password, type password from a secret reference, click login, confirm dashboard.", | |
| "target_url": "https://example.com/login", | |
| "username": "demo_user", | |
| "password_secret_ref": "demo_password_secret", | |
| "success_signal": "dashboard navigation is visible", | |
| "dry_run": True, | |
| }, | |
| "verifier_specs": legacy_specs, | |
| "timeout_s": harness_timeout, | |
| }, | |
| }, | |
| { | |
| "label": "legacy_login_negative_missing_secret", | |
| "skill_name": LEGACY_SKILL, | |
| "expected": "not_promotion_allowed", | |
| "promote_on_pass": False, | |
| "allow_repair": False, | |
| "test_case": { | |
| "test_id": "legacy-login-negative-missing-secret", | |
| "name": "Legacy login workflow rejects missing secret", | |
| "goal": "The verifier should reject a login flow without a password secret reference.", | |
| "input_data": { | |
| "task": "Dry-run normalize a login workflow without opening the website or using a real password.", | |
| "source_context": "Legacy workflow: open the login URL, click username, type username, click password, type password from a secret reference, click login, confirm dashboard.", | |
| "target_url": "https://example.com/login", | |
| "username": "demo_user", | |
| "password_secret_ref": "", | |
| "success_signal": "dashboard navigation is visible", | |
| "dry_run": True, | |
| }, | |
| "verifier_specs": legacy_specs, | |
| "timeout_s": harness_timeout, | |
| }, | |
| }, | |
| ] | |
| for check in checks: | |
| skill = by_name[check["skill_name"]] | |
| raw = run_harness_check(client, skill, check) | |
| analyzed = analyze_harness_result(check, raw) | |
| result["checks"].append(analyzed) | |
| if check["label"].endswith("_positive_promote"): | |
| refreshed_skills = client.get("/skills?limit=1000", label=f"skills_after_{check['label']}") | |
| by_name = skills_by_preferred_name(refreshed_skills) | |
| result["scores"] = score_harness_checks(result["checks"]) | |
| return result | |
| def restore_related_graph(client: ApiClient, fixture_dir: Path) -> dict[str, Any]: | |
| result: dict[str, Any] = { | |
| "created": [], | |
| "skipped": [], | |
| "errors": [], | |
| "parse_results": [], | |
| "graph_validation": {}, | |
| } | |
| fixture = json.loads(read_text(fixture_dir / "related_login_graph_pack.json")) | |
| expected_names = [str(item.get("name") or "") for item in fixture] | |
| existing_by_name = skills_by_name(client.get("/skills?limit=1000", label="skills_before_related_graph")) | |
| for item in fixture: | |
| name = str(item.get("name") or "") | |
| if name in existing_by_name: | |
| skill = existing_by_name[name] | |
| result["skipped"].append({ | |
| "name": name, | |
| "reason": "already exists", | |
| "skill_id": skill.get("skill_id"), | |
| "state": skill.get("state"), | |
| }) | |
| continue | |
| parsed = parse_source( | |
| client, | |
| "past_skills", | |
| json.dumps([item], ensure_ascii=False), | |
| label=f"related_{name}", | |
| metadata={"related_graph_pack": True, "source_group": "public-related-login-graph"}, | |
| ) | |
| units = parsed.get("units") or [] | |
| selected = next((unit for unit in units if unit.get("proposed_skill_name") == name), units[0] if units else None) | |
| result["parse_results"].append({ | |
| "name": name, | |
| "http_status": parsed.get("_http_status"), | |
| "success": parsed.get("success"), | |
| "unit_count": len(units), | |
| "selected_name": selected.get("proposed_skill_name") if selected else "", | |
| }) | |
| if not selected: | |
| result["errors"].append({"stage": "parse", "name": name, "detail": "No unit returned"}) | |
| continue | |
| payload = candidate_review_payload(selected) | |
| payload["name"] = name | |
| payload["author"] = "demo_state_restore" | |
| payload["tags"] = unique([*payload.get("tags", []), "related-graph-pack", "public-demo-fixture"])[:12] | |
| created = client.post( | |
| "/ingest/create-candidate", | |
| payload, | |
| label=f"create_related_{name}", | |
| timeout_s=180, | |
| ) | |
| if created.get("_http_status") not in {200, 201} or not created.get("success"): | |
| result["errors"].append({ | |
| "stage": "create", | |
| "name": name, | |
| "status": created.get("_http_status"), | |
| "detail": created.get("detail") or created.get("error"), | |
| }) | |
| continue | |
| skill = created.get("created_skill") or {} | |
| existing_by_name[name] = skill | |
| result["created"].append(skill) | |
| result["graph_validation"] = validate_related_graph(client, expected_names) | |
| result["scores"] = score_related_graph(result) | |
| return result | |
| def parse_source( | |
| client: ApiClient, | |
| source_type: str, | |
| content: str, | |
| *, | |
| label: str, | |
| metadata: dict[str, Any] | None = None, | |
| ) -> dict[str, Any]: | |
| return client.post( | |
| "/ingest/parse", | |
| { | |
| "source_type": source_type, | |
| "content": content, | |
| "metadata": { | |
| "public_demo_fixture": True, | |
| "max_candidates": 12, | |
| **(metadata or {}), | |
| }, | |
| }, | |
| label=f"parse_{label}", | |
| timeout_s=180, | |
| ) | |
| def candidate_review_payload(unit: dict[str, Any]) -> dict[str, Any]: | |
| meta = unit.get("metadata") or {} | |
| interface = meta.get("candidate_interface") if isinstance(meta.get("candidate_interface"), dict) else {} | |
| implementation = meta.get("candidate_implementation") if isinstance(meta.get("candidate_implementation"), dict) else {} | |
| relations = meta.get("candidate_relations") if isinstance(meta.get("candidate_relations"), dict) else {} | |
| evaluation = meta.get("candidate_evaluation") if isinstance(meta.get("candidate_evaluation"), dict) else None | |
| source_type = unit.get("source_type") or "document" | |
| name = normalize_skill_name(unit.get("proposed_skill_name") or f"{source_type}_candidate") | |
| description = unit.get("proposed_description") or unit.get("summary") or name | |
| tags = meta.get("candidate_tags") if isinstance(meta.get("candidate_tags"), list) else None | |
| if evaluation: | |
| evaluation = dict(evaluation) | |
| evaluation.setdefault("test_case_refs", [f"{unit.get('unit_id', 'unit')}:public-demo-fixture"]) | |
| evaluation.setdefault("benchmark_task_ids", []) | |
| else: | |
| evaluation = { | |
| "verifier_specs": [{"type": "json_exists", "path": "output.result"}], | |
| "test_case_refs": [f"{unit.get('unit_id', 'unit')}:public-demo-fixture"], | |
| "benchmark_task_ids": [], | |
| "validation_summary": "Public demo fixture preview.", | |
| } | |
| return { | |
| "source_type": source_type, | |
| "unit_id": unit.get("unit_id") or f"{source_type}:unit", | |
| "raw_content": unit.get("raw_content") or "", | |
| "name": name, | |
| "description": description, | |
| "skill_type": unit.get("proposed_type") if unit.get("proposed_type") in {"atomic", "functional", "strategic"} else "atomic", | |
| "tags": (tags or unit.get("index_keywords") or [])[:8], | |
| "input_schema": interface.get("input_schema") or {"type": "object", "properties": {}}, | |
| "output_schema": interface.get("output_schema") or {"type": "object", "properties": {"result": {"type": "object"}}}, | |
| "preconditions": interface.get("preconditions") or [], | |
| "postconditions": interface.get("postconditions") or ["Candidate returns a structured result."], | |
| "prompt_template": implementation.get("prompt_template") or unit.get("summary") or description, | |
| "evaluation": evaluation, | |
| "dependency_ids": relations.get("dependency_ids") or [], | |
| "component_ids": relations.get("component_ids") or [], | |
| "sub_skill_ids": relations.get("sub_skill_ids") or relations.get("component_ids") or [], | |
| "parent_skill_ids": relations.get("parent_skill_ids") or [], | |
| "tool_calls": implementation.get("tool_calls") or [], | |
| "author": "demo_state_restore", | |
| } | |
| def ensure_draft_or_verified(client: ApiClient, skill: dict[str, Any]) -> dict[str, Any]: | |
| state = skill.get("state") | |
| if state in {"S2", "S3", "S4"}: | |
| return skill | |
| if state != "S1": | |
| return skill | |
| result = client.post( | |
| f"/lifecycle/{skill['skill_id']}/transition", | |
| { | |
| "new_state": "S2", | |
| "reason": "Prepare public demo fixture for harness verification.", | |
| "author": "demo_state_restore", | |
| }, | |
| label=f"transition_{skill['name']}_to_s2", | |
| ) | |
| return result if isinstance(result, dict) and result.get("skill_id") else skill | |
| def run_harness_check(client: ApiClient, skill: dict[str, Any], check: dict[str, Any]) -> dict[str, Any]: | |
| if skill.get("state") in {"S3", "S4"} and "positive" in check.get("label", ""): | |
| validation = skill.get("evaluation", {}).get("harness_validation", {}) | |
| return { | |
| "already_verified": True, | |
| "status": "already_verified", | |
| "promotion_allowed": True, | |
| "final_state": skill.get("state"), | |
| "attempt_count": 0, | |
| "loop_id": validation.get("last_loop_id") or validation.get("loop_id", ""), | |
| "evidence_path": validation.get("evidence_path", ""), | |
| "score": {"overall": validation.get("pass_rate", 1.0)}, | |
| } | |
| if skill.get("state") != "S2": | |
| return { | |
| "skipped": True, | |
| "reason": f"Skill is {skill.get('state')}, not S2 Draft.", | |
| "skill": skill, | |
| } | |
| payload = { | |
| "harness": "local_skillos", | |
| "max_attempts": 2 if check["allow_repair"] else 1, | |
| "promote_on_pass": check["promote_on_pass"], | |
| "test_cases": [check["test_case"]], | |
| "allow_repair": check["allow_repair"], | |
| "timeout_s": check["test_case"].get("timeout_s", 160), | |
| } | |
| return client.post( | |
| f"/harness/{skill['skill_id']}/verify-loop", | |
| payload, | |
| label=f"harness_{check['label']}", | |
| timeout_s=max(240, int(payload["timeout_s"]) + 120), | |
| ) | |
| def analyze_harness_result(check: dict[str, Any], result: dict[str, Any]) -> dict[str, Any]: | |
| promotion_allowed = bool(result.get("promotion_allowed")) | |
| status = str(result.get("status") or result.get("reason") or result.get("body") or "unknown") | |
| final_state = str(result.get("final_state") or result.get("skill", {}).get("state") or "") | |
| expected = check["expected"] | |
| passed = promotion_allowed if expected == "promotion_allowed" else not promotion_allowed | |
| return { | |
| "label": check["label"], | |
| "skill_name": check["skill_name"], | |
| "expected": expected, | |
| "status": status, | |
| "promotion_allowed": promotion_allowed, | |
| "final_state": final_state, | |
| "passed_expectation": passed, | |
| "attempt_count": result.get("attempt_count", 0), | |
| "loop_id": result.get("loop_id", ""), | |
| "evidence_path": result.get("evidence_path", ""), | |
| } | |
| def validate_related_graph(client: ApiClient, expected_names: list[str]) -> dict[str, Any]: | |
| skills = client.get("/skills?limit=1000", label="skills_for_related_graph_validation") | |
| by_name = skills_by_name(skills) | |
| ids_by_name = {name: by_name[name].get("skill_id") for name in expected_names if name in by_name} | |
| skill_only = client.get("/graph/view?view=skill_only", label="related_graph_skill_only", timeout_s=60) | |
| hetero = client.get("/graph/view?view=provenance", label="related_graph_heterogeneous", timeout_s=60) | |
| projection = client.get("/graph/view?view=version_impact", label="related_graph_projection", timeout_s=60) | |
| skill_ids = set(str(value) for value in ids_by_name.values()) | |
| skill_edges = [ | |
| edge for edge in skill_only.get("edges", []) | |
| if edge.get("source") in skill_ids or edge.get("target") in skill_ids | |
| ] | |
| projection_edges = [ | |
| edge for edge in projection.get("edges", []) | |
| if edge.get("source") in skill_ids or edge.get("target") in skill_ids | |
| ] | |
| hetero_nodes = [ | |
| node for node in hetero.get("nodes", []) | |
| if node.get("id") in skill_ids or node.get("metadata", {}).get("skill_id") in skill_ids | |
| ] | |
| return { | |
| "expected_names": expected_names, | |
| "ids_by_name": ids_by_name, | |
| "missing_names": [name for name in expected_names if name not in ids_by_name], | |
| "skill_only_edge_counts": count_by_key(skill_edges, "edge_type"), | |
| "hetero_related_node_count": len(hetero_nodes), | |
| "projection_edge_counts": count_by_key(projection_edges, "edge_type"), | |
| "relation_strength": projection.get("metadata", {}).get("relation_strength", {}), | |
| } | |
| def score_related_graph(result: dict[str, Any]) -> dict[str, Any]: | |
| graph = result.get("graph_validation", {}) | |
| missing = len(graph.get("missing_names") or []) | |
| edge_counts = graph.get("skill_only_edge_counts") or {} | |
| projection_counts = graph.get("projection_edge_counts") or {} | |
| created_or_present = 1.0 if missing == 0 else 0.0 | |
| skill_edge_score = 1.0 if ( | |
| edge_counts.get("depends_on", 0) >= 7 | |
| and edge_counts.get("composes_with", 0) >= 7 | |
| and edge_counts.get("evolved_from", 0) >= 1 | |
| ) else 0.0 | |
| hetero_score = 1.0 if graph.get("hetero_related_node_count", 0) >= 7 else 0.5 | |
| projection_score = 1.0 if projection_counts.get("similar_to", 0) >= 1 else 0.5 | |
| overall = round((created_or_present + skill_edge_score + hetero_score + projection_score) / 4, 3) | |
| return { | |
| "overall": overall, | |
| "created_or_present": created_or_present, | |
| "skill_only_edge_score": skill_edge_score, | |
| "heterogeneous_score": hetero_score, | |
| "projection_score": projection_score, | |
| } | |
| def score_harness_checks(checks: list[dict[str, Any]]) -> dict[str, Any]: | |
| if not checks: | |
| return {"expectation_pass_rate": 0.0, "positive_pass_rate": 0.0, "negative_rejection_rate": 0.0} | |
| positive = [item for item in checks if "positive" in item["label"]] | |
| negative = [item for item in checks if "negative" in item["label"]] | |
| return { | |
| "expectation_pass_rate": round(sum(1 for item in checks if item["passed_expectation"]) / len(checks), 2), | |
| "positive_pass_rate": round(sum(1 for item in positive if item["promotion_allowed"]) / len(positive), 2) if positive else 0.0, | |
| "negative_rejection_rate": round(sum(1 for item in negative if not item["promotion_allowed"]) / len(negative), 2) if negative else 0.0, | |
| } | |
| def score_summary(summary: dict[str, Any]) -> dict[str, Any]: | |
| approved = summary.get("approved_import", {}) | |
| harness = summary.get("harness", {}) | |
| related = summary.get("related_graph", {}) | |
| approved_ok = 1.0 if not approved.get("errors") else 0.0 | |
| harness_ok = float(harness.get("scores", {}).get("expectation_pass_rate", 1.0 if not harness else 0.0)) | |
| related_ok = float(related.get("scores", {}).get("overall", 1.0 if not related else 0.0)) | |
| return { | |
| "overall": round((approved_ok + harness_ok + related_ok) / 3, 3), | |
| "approved_ok": approved_ok, | |
| "harness_expectation_pass_rate": harness_ok, | |
| "related_graph_score": related_ok, | |
| } | |
| def write_report(run_dir: Path, summary: dict[str, Any]) -> None: | |
| (run_dir / "summary.json").write_text( | |
| json.dumps(summary, indent=2, ensure_ascii=False), | |
| encoding="utf-8", | |
| ) | |
| (run_dir / "REPORT.md").write_text(render_report(summary), encoding="utf-8") | |
| def render_report(summary: dict[str, Any]) -> str: | |
| approved = summary.get("approved_import", {}) | |
| harness = summary.get("harness", {}) | |
| related = summary.get("related_graph", {}) | |
| lines = [ | |
| "# SkillOS Demo State Restore Report", | |
| "", | |
| f"- Started: `{summary.get('started_at', '')}`", | |
| f"- Finished: `{summary.get('finished_at', '')}`", | |
| f"- API base: `{summary.get('api_base', '')}`", | |
| f"- Frontend base: `{summary.get('frontend_base', '')}`", | |
| f"- Fixture directory: `{summary.get('fixture_dir', '')}`", | |
| f"- Run directory: `{summary.get('run_dir', '')}`", | |
| "", | |
| "## Scores", | |
| "", | |
| f"- Overall: `{summary.get('scores', {}).get('overall', 0.0)}`", | |
| f"- Harness expectation pass rate: `{summary.get('scores', {}).get('harness_expectation_pass_rate', 0.0)}`", | |
| f"- Related graph score: `{summary.get('scores', {}).get('related_graph_score', 0.0)}`", | |
| "", | |
| "## Approved Import", | |
| "", | |
| f"- Created: `{len(approved.get('created', []))}`", | |
| f"- Skipped: `{len(approved.get('skipped', []))}`", | |
| f"- Errors: `{len(approved.get('errors', []))}`", | |
| "", | |
| "## Harness", | |
| "", | |
| f"- Scores: `{json.dumps(harness.get('scores', {}), ensure_ascii=False)}`", | |
| "", | |
| ] | |
| for check in harness.get("checks", []): | |
| lines.extend([ | |
| f"- `{check.get('label')}`: status=`{check.get('status')}`, promotion_allowed=`{check.get('promotion_allowed')}`, final_state=`{check.get('final_state')}`", | |
| ]) | |
| lines.extend([ | |
| "", | |
| "## Related Graph", | |
| "", | |
| f"- Created: `{len(related.get('created', []))}`", | |
| f"- Skipped: `{len(related.get('skipped', []))}`", | |
| f"- Errors: `{len(related.get('errors', []))}`", | |
| f"- Scores: `{json.dumps(related.get('scores', {}), ensure_ascii=False)}`", | |
| f"- Skill-only edge counts: `{json.dumps(related.get('graph_validation', {}).get('skill_only_edge_counts', {}), ensure_ascii=False)}`", | |
| f"- Projection edge counts: `{json.dumps(related.get('graph_validation', {}).get('projection_edge_counts', {}), ensure_ascii=False)}`", | |
| "", | |
| ]) | |
| if approved.get("errors") or related.get("errors") or harness.get("errors"): | |
| lines.extend([ | |
| "## Errors", | |
| "", | |
| "```json", | |
| json.dumps({ | |
| "approved": approved.get("errors", []), | |
| "harness": harness.get("errors", []), | |
| "related": related.get("errors", []), | |
| }, indent=2, ensure_ascii=False), | |
| "```", | |
| "", | |
| ]) | |
| return "\n".join(lines) | |
| def skills_by_name(payload: Any) -> dict[str, dict[str, Any]]: | |
| return { | |
| str(item.get("name")): item | |
| for item in skill_list(payload) | |
| if item.get("name") | |
| } | |
| def skills_by_preferred_name(payload: Any) -> dict[str, dict[str, Any]]: | |
| preferred: dict[str, dict[str, Any]] = {} | |
| for skill in skill_list(payload): | |
| name = str(skill.get("name") or "") | |
| if not name: | |
| continue | |
| current = preferred.get(name) | |
| if current is None or skill_preference_key(skill) > skill_preference_key(current): | |
| preferred[name] = skill | |
| return preferred | |
| def skill_preference_key(skill: dict[str, Any]) -> tuple[int, tuple[int, ...], str]: | |
| state_rank = {"S4": 5, "S3": 4, "S2": 3, "S1": 2, "S0": 1}.get(str(skill.get("state") or ""), 0) | |
| return (state_rank, parse_version(str(skill.get("version") or "")), str(skill.get("updated_at") or "")) | |
| def skill_list(payload: Any) -> list[dict[str, Any]]: | |
| if isinstance(payload, list): | |
| return [item for item in payload if isinstance(item, dict)] | |
| if isinstance(payload, dict): | |
| for key in ("body", "value", "skills", "items"): | |
| value = payload.get(key) | |
| if isinstance(value, list): | |
| return [item for item in value if isinstance(item, dict)] | |
| return [] | |
| def count_skills(payload: Any) -> int: | |
| skills = skill_list(payload) | |
| if skills: | |
| return len(skills) | |
| if isinstance(payload, dict) and isinstance(payload.get("total"), int): | |
| return payload["total"] | |
| return 0 | |
| def count_by_key(items: list[dict[str, Any]], key: str) -> dict[str, int]: | |
| counts: dict[str, int] = {} | |
| for item in items: | |
| value = str(item.get(key) or "") | |
| if value: | |
| counts[value] = counts.get(value, 0) + 1 | |
| return counts | |
| def normalize_skill_name(name: str) -> str: | |
| cleaned = re.sub(r"[^a-zA-Z0-9_]+", "_", name.strip().lower()).strip("_") | |
| return cleaned or "demo_candidate" | |
| def parse_version(version: str) -> tuple[int, ...]: | |
| parts: list[int] = [] | |
| for chunk in version.split("."): | |
| try: | |
| parts.append(int(chunk)) | |
| except ValueError: | |
| parts.append(0) | |
| return tuple(parts) | |
| def unique(values: list[str]) -> list[str]: | |
| result: list[str] = [] | |
| seen: set[str] = set() | |
| for value in values: | |
| text = str(value or "").strip() | |
| if text and text not in seen: | |
| seen.add(text) | |
| result.append(text) | |
| return result | |
| def read_text(path: Path) -> str: | |
| return path.read_text(encoding="utf-8", errors="replace") | |
| def _parse_body(body: str) -> Any: | |
| try: | |
| return json.loads(body) | |
| except json.JSONDecodeError: | |
| return body | |
| def _preview_payload(payload: dict[str, Any] | None) -> Any: | |
| if payload is None: | |
| return None | |
| preview = dict(payload) | |
| content = preview.get("content") | |
| if isinstance(content, str) and len(content) > 400: | |
| preview["content"] = content[:400] + "...[truncated]" | |
| return preview | |
| def _small_import_summary(section: dict[str, Any]) -> dict[str, Any]: | |
| return { | |
| "created": len(section.get("created", [])), | |
| "skipped": len(section.get("skipped", [])), | |
| "errors": len(section.get("errors", [])), | |
| } | |
| def _safe_filename(label: str) -> str: | |
| return "".join(ch if ch.isalnum() or ch in "-_." else "_" for ch in label) | |
| if __name__ == "__main__": | |
| raise SystemExit(main()) | |