{"name":"s2a-python-engine","lang":"bash","entry":"spec-s2a-python.sh","script":"#!/usr/bin/env bash\n# spec-s2a-python.sh — S2a (Python replay engine) verifier.\n#\n# MetaTask v1.3 pilot spec script (protocol draft §4.3 CI-style verifier contract).\n# Template family: spec-s2-engine (S2a python / S2b go / S2c ts share the\n# skeleton: env contract → toolchain evidence → artifact fetch → bundle clone →\n# base-ancestry → language build gate → vector runs ×2 → determinism).\n#\n# Environment (§4.3):\n# METATASK_ARTIFACT_URI metafile:// (file:// / local\n# path accepted for offline runs)\n# METATASK_COMMIT commit under test (40-hex)\n# METATASK_BASE_COMMIT declared base commit; empty/null = greenfield\n# (ancestry check skipped WITH EVIDENCE, §4.4)\n# METATASK_NODE / METATASK_TASKID\n# METATASK_DOWNLOAD_BASE metafile resolution base: fetch\n# /file/ (pilot-local\n# convention — acceptance-sheet.md \"Open items\")\n# METATASK_VECTOR_SET_URI overrides the embedded vector-set pin (offline/test\n# use; production runs use the backfilled constant)\n#\n# The embedded VECTOR_SET_URI below is a PUBLISH-TIME PLACEHOLDER: until the\n# pilot vector-set metafile is uploaded and its URI backfilled, resolution\n# fails and this script reports invalid (exit 2) — never a fail verdict.\n#\n# Exit codes: 0 pass (evidence on stdout) · 1 fail (reason on stderr) ·\n# 2 invalid (null/empty/unresolvable input). The script keeps a check counter\n# and asserts the enumeration closure (EXPECTED_CHECKS) on the pass path.\nset -uo pipefail\n\nreadonly VECTOR_SET_URI=\"metafile://71f09271a6be857a0f40200498381decab99d9d7f7049f32f69f2f0df11bc58ei0.gz\" # PLACEHOLDER — backfilled with metafile://… at publish\nEXPECTED_CHECKS=15\n\nCHECKS=0\n\njson_line() { python3 -c 'import json,sys; print(json.dumps({\"verdict\":sys.argv[1],\"detail\":sys.argv[2],\"checks\":int(sys.argv[3])}))' \"$1\" \"$2\" \"$CHECKS\"; }\nnote() { printf '%s\\n' \"$1\"; }\nfail() { json_line fail \"$1\"; printf 'FAIL: %s\\n' \"$1\" >&2; exit 1; }\ninvalid() { json_line invalid \"$1\"; exit 2; }\ncheck() { # check [detail]\n CHECKS=$((CHECKS+1))\n if [ \"$2\" = \"0\" ]; then note \"[check $CHECKS] $1: ok${3:+ — $3}\"; else note \"[check $CHECKS] $1: FAIL${3:+ — $3}\"; fail \"$1${3:+ — $3}\"; fi\n}\ngate() { # gate [detail] — false outcome = invalid (null_tolerance)\n CHECKS=$((CHECKS+1))\n if [ \"$2\" = \"0\" ]; then note \"[check $CHECKS] $1: ok${3:+ — $3}\"; else note \"[check $CHECKS] $1: INVALID${3:+ — $3}\"; invalid \"$1${3:+ — $3}\"; fi\n}\ntool() { # tool — record version evidence; return 1 if missing\n if command -v \"$1\" >/dev/null 2>&1; then\n note \"[tool] $1: $(\"$@\" 2>&1 | head -1)\"\n return 0\n fi\n note \"[tool] $1: MISSING\"\n return 1\n}\n\n# fetch_artifact — metafile:// via METATASK_DOWNLOAD_BASE,\n# file:// / local path direct. Empty dest on failure.\nfetch_artifact() {\n local uri=\"$1\" dest=\"$2\"\n case \"$uri\" in\n file://*) cp \"${uri#file://}\" \"$dest\" 2>/dev/null && return 0 ;;\n metafile://*|pin://*)\n local base=\"${METATASK_DOWNLOAD_BASE:-}\"\n if [ -z \"$base\" ]; then note \"[fetch] $uri requires METATASK_DOWNLOAD_BASE (not set)\"; return 1; fi\n if FETCH_URI=\"$uri\" FETCH_BASE=\"$base\" FETCH_DEST=\"$dest\" python3 - <<'PYEOF'\nimport hashlib, os, sys\nfrom urllib.parse import quote\nfrom urllib.request import urlopen\nuri, base, dest = os.environ[\"FETCH_URI\"], os.environ[\"FETCH_BASE\"].rstrip(\"/\"), os.environ[\"FETCH_DEST\"]\nurl = \"%s/file/%s\" % (base, quote(uri, safe=\"\"))\ntry:\n with urlopen(url, timeout=120) as response:\n data = response.read()\n with open(dest, \"wb\") as handle:\n handle.write(data)\n print(\"[fetch] %s → %s (%d bytes)\" % (uri, url, len(data)))\nexcept Exception as err:\n print(\"[fetch] %s via %s failed: %s\" % (uri, url, err))\n sys.exit(1)\nPYEOF\n then return 0; else return 1; fi ;;\n *) [ -f \"$uri\" ] && cp \"$uri\" \"$dest\" && return 0 ;;\n esac\n return 1\n}\n\nARTIFACT_URI=\"${METATASK_ARTIFACT_URI:-}\"\nCOMMIT=\"${METATASK_COMMIT:-}\"\nBASE_COMMIT=\"${METATASK_BASE_COMMIT:-}\"\nNODE=\"${METATASK_NODE:-}\"\nTASKID=\"${METATASK_TASKID:-}\"\n\nnote \"[env] node=${NODE:-} taskid=${TASKID:-}\"\nnote \"[env] artifact=${ARTIFACT_URI:-} commit=${COMMIT:-} base=${BASE_COMMIT:-}\"\n\nenv_bad=1\n[ -n \"$ARTIFACT_URI\" ] && [ -n \"$COMMIT\" ] && [ -n \"$NODE\" ] && [ -n \"$TASKID\" ] && env_bad=0\ngate \"env contract present (ARTIFACT_URI/COMMIT/NODE/TASKID)\" \"$env_bad\"\ncommit_bad=1\nprintf '%s' \"$COMMIT\" | grep -qE '^[0-9a-fA-F]{40}$' && commit_bad=0\ngate \"commit under test is 40-hex\" \"$commit_bad\" \"$COMMIT\"\n\ntools_bad=1\ntool git git --version && tool python3 python3 --version && tools_bad=0\ngate \"toolchain: git + python3\" \"$tools_bad\" \"spec-s2a requires git and python3\"\n\nVECTOR_URI=\"${METATASK_VECTOR_SET_URI:-$VECTOR_SET_URI}\"\nvec_bad=0\ncase \"$VECTOR_URI\" in *VECTOR_SET_URI*|\"\") vec_bad=1 ;; esac\ngate \"vector set reference resolved\" \"$vec_bad\" \"VECTOR_SET_URI placeholder not backfilled and no METATASK_VECTOR_SET_URI override — publish the vector-set metafile first\"\n\nWORK=\"$(mktemp -d \"${TMPDIR:-/tmp}/metatask-s2a.XXXXXX\")\"\ntrap 'rm -rf \"$WORK\"' EXIT\n\nfetch_artifact \"$ARTIFACT_URI\" \"$WORK/submission.bundle\"\ngate \"artifact fetched\" \"$([ -s \"$WORK/submission.bundle\" ] && echo 0 || echo 1)\" \"$ARTIFACT_URI\"\n\ngit bundle verify \"$WORK/submission.bundle\" >/dev/null 2>&1 && git clone -q \"$WORK/submission.bundle\" \"$WORK/repo\" 2>/dev/null\ncheck \"git bundle verifies and clones\" \"$([ -d \"$WORK/repo/.git\" ] && echo 0 || echo 1)\" \"$ARTIFACT_URI\"\n\ngit -C \"$WORK/repo\" checkout -q \"$COMMIT\" 2>/dev/null\ncheck \"checkout METATASK_COMMIT\" \"$([ \"$(git -C \"$WORK/repo\" rev-parse HEAD 2>/dev/null)\" = \"$(printf '%s' \"$COMMIT\" | tr 'A-F' 'a-f')\" ] && echo 0 || echo 1)\" \"$COMMIT\"\n\nif [ -z \"$BASE_COMMIT\" ] || [ \"$BASE_COMMIT\" = \"null\" ]; then\n CHECKS=$((CHECKS+1)); note \"[check $CHECKS] base-commit ancestry: ok — greenfield node (baseCommit null), ancestry skipped per draft §4.4\"\nelse\n git -C \"$WORK/repo\" merge-base --is-ancestor \"$BASE_COMMIT\" \"$COMMIT\" 2>/dev/null\n check \"declared baseCommit is an ancestor of commit\" \"$([ $? -eq 0 ] && echo 0 || echo 1)\" \"base=$BASE_COMMIT\"\nfi\n\n# Language gate: stdlib-only. Scan every *.py for top-level imports; each must\n# be the standard library or a repo-local module. Dependency manifests with\n# real requirements are refused.\ndep_scan=\"$WORK/dep-scan.py\"\ncat > \"$dep_scan\" <<'PYEOF'\nimport ast, os, sys\nroot = sys.argv[1]\ntry:\n STDLIB = set(sys.stdlib_module_names)\nexcept AttributeError: # python 3.9 fallback\n STDLIB = set(\"\"\"argparse json sys os re hashlib math itertools functools collections typing dataclasses enum\npathlib subprocess tempfile shutil io base64 binascii struct time datetime copy textwrap string secrets random unittest\nlogging traceback contextlib abc numbers fractions statistics heapq bisect array queue threading multiprocessing socket ssl\nurllib http email csv configparser sqlite3 gzip zipfile tarfile lzma bz2 glob fnmatch linecache tokenize keyword platform\ngetpass pprint reprlib types weakref gc inspect importlib warnings signal errno stat locale gettext ast dis code codeop\npickle marshal dbm shelve xml html ipaddress uuid hmac os.path site sysconfig builtins __future__ cmath decimal atexit\ncodecs encodings mmap select selectors pty fcntl timeit trace tracemalloc asyncio concurrent ctypes curses distutils\ndoctest fileinput filecmp optparse parser pdb plistlib posix shlex sched sndhdr tabnanny token unicodedata venv\nwebbrowser wsgiref zipapp zipimport zlib ftplib smtplib imaplib poplib nntplib xmlrpc mimetypes quopri uu binhex\ncgi cgitb chunk colorsys imghdr mailbox crypt aifc audioop sunau wave xdrlib telnetlib msilib nis ossaudiodev spwd\nsyslog termios tty resource readline rlcompleter difflib\"\"\".split())\nlocal_modules = set()\npyfiles = []\nfor dirpath, dirnames, filenames in os.walk(root):\n dirnames[:] = [d for d in dirnames if d not in (\".git\", \"node_modules\", \"__pycache__\", \".venv\", \"venv\")]\n for name in filenames:\n if name.endswith(\".py\"):\n pyfiles.append(os.path.join(dirpath, name))\n rel = os.path.relpath(os.path.join(dirpath, name), root)\n parts = rel.split(os.sep)\n if len(parts) == 1:\n local_modules.add(name[:-3])\n if name == \"__init__.py\":\n rel = os.path.relpath(dirpath, root)\n if os.sep not in rel:\n local_modules.add(os.path.basename(dirpath))\nforeign = set()\nfor path in pyfiles:\n with open(path, \"r\", encoding=\"utf-8\") as handle:\n try:\n tree = ast.parse(handle.read())\n except SyntaxError as err:\n print(\"SYNTAX %s: %s\" % (os.path.relpath(path, root), err))\n continue\n for node in ast.walk(tree):\n if isinstance(node, ast.Import):\n for alias in node.names:\n foreign.add((os.path.relpath(path, root), alias.name.split(\".\")[0]))\n elif isinstance(node, ast.ImportFrom) and node.level == 0 and node.module:\n foreign.add((os.path.relpath(path, root), node.module.split(\".\")[0]))\nbad = sorted({\"%s imports %s\" % (f, m) for f, m in foreign if m not in STDLIB and m not in local_modules})\nfor line in bad:\n print(\"FOREIGN\", line)\nprint(\"SCANNED %d python files, %d import roots\" % (len(pyfiles), len({m for _, m in foreign})))\nsys.exit(2 if bad else 0)\nPYEOF\nscan_out=\"$(python3 \"$dep_scan\" \"$WORK/repo\")\"\nscan_rc=$?\nwhile IFS= read -r line; do note \"[deps] $line\"; done <<< \"$scan_out\"\nmanifest_bad=0\nfor marker in requirements.txt requirements-dev.txt Pipfile setup.py; do\n [ -f \"$WORK/repo/$marker\" ] && { note \"[deps] forbidden dependency manifest present: $marker\"; manifest_bad=1; }\ndone\nif [ -f \"$WORK/repo/pyproject.toml\" ] && grep -E '^\\s*dependencies\\s*=\\s*\\[\\s*[^]]' \"$WORK/repo/pyproject.toml\" >/dev/null 2>&1; then\n note \"[deps] pyproject.toml declares dependencies\"; manifest_bad=1\nfi\ncheck \"stdlib-only (zero third-party deps)\" \"$([ \"$scan_rc\" = \"0\" ] && [ \"$manifest_bad\" = \"0\" ] && echo 0 || echo 1)\" \"import scan + manifest scan\"\n\n[ -f \"$WORK/repo/metatask_replay.py\" ]\ncheck \"CLI entry metatask_replay.py present\" \"$?\" \"contract: README.md\"\n[ -x \"$WORK/repo/run-vectors.sh\" ] || [ -f \"$WORK/repo/run-vectors.sh\" ]\ncheck \"run-vectors.sh present\" \"$?\" \"contract: README.md vector runner\"\n\nfetch_artifact \"$VECTOR_URI\" \"$WORK/vector-set.tar.gz\"\n[ -s \"$WORK/vector-set.tar.gz\" ] && mkdir -p \"$WORK/vectors\" && tar -xzf \"$WORK/vector-set.tar.gz\" -C \"$WORK/vectors\" 2>/dev/null\ngate \"vector set fetched and extracted\" \"$([ -d \"$WORK/vectors\" ] && ls \"$WORK/vectors\"/*.json >/dev/null 2>&1 && echo 0 || echo 1)\" \"$VECTOR_URI\"\n\n( cd \"$WORK/repo\" && bash ./run-vectors.sh \"$WORK/vectors\" ) > \"$WORK/run1.log\" 2>&1\nrun1_rc=$?\nnote \"[run 1] exit=$run1_rc\"; sed 's/^/[run 1] /' \"$WORK/run1.log\" | tail -6\ncheck \"vector run 1 green\" \"$run1_rc\" \"run-vectors.sh exit code\"\n\n( cd \"$WORK/repo\" && bash ./run-vectors.sh \"$WORK/vectors\" ) > \"$WORK/run2.log\" 2>&1\nrun2_rc=$?\nnote \"[run 2] exit=$run2_rc\"; sed 's/^/[run 2] /' \"$WORK/run2.log\" | tail -3\ncheck \"vector run 2 green\" \"$run2_rc\" \"run-vectors.sh exit code\"\n\nd1=\"$(grep -E '^CANONICAL_SHA256 [0-9a-f]{64}$' \"$WORK/run1.log\" | tail -1 | awk '{print $2}')\"\nd2=\"$(grep -E '^CANONICAL_SHA256 [0-9a-f]{64}$' \"$WORK/run2.log\" | tail -1 | awk '{print $2}')\"\ncheck \"determinism: two runs byte-identical canonical digest\" \"$([ -n \"$d1\" ] && [ \"$d1\" = \"$d2\" ] && echo 0 || echo 1)\" \"run1=${d1:-} run2=${d2:-}\"\n\nif [ \"$CHECKS\" != \"$EXPECTED_CHECKS\" ]; then\n invalid \"check-counter mismatch: ran $CHECKS, spec declares $EXPECTED_CHECKS (script bug)\"\nfi\njson_line pass \"S2a green: clone+build+stdlib gates ok; vector set $VECTOR_URI passed twice; canonical digest ${d1}\"\nexit 0\n","input":{"env":{"METATASK_ARTIFACT_URI":"metafile://","METATASK_COMMIT":"submission tip commit (40-hex)","METATASK_BASE_COMMIT":"b1c8d01561dc96b80211b0b171a8b5a59689318e (pinned base tip)","METATASK_NODE":"S2a","METATASK_TASKID":"","METATASK_VECTOR_SET_URI":"offline override; production runs use the backfilled VECTOR_SET_URI constant","METATASK_DOWNLOAD_BASE":"optional; metafile resolution base"}},"output":{"stdout":"evidence log + final JSON line {\"verdict\",\"detail\",\"checks\"}","exitCode":"0 pass | 1 fail | 2 invalid"},"workspace":{"type":"git","baseRef":"metafile://f25716f86b18d50671b89dbd824fdad11ecc5899da12b52fb795d5db614e0bb6i0.bundle","baseCommit":"b1c8d01561dc96b80211b0b171a8b5a59689318e","notes":"greenfield-adjacent skeleton: README contract + argparse shell + MIT LICENSE"},"validation":{"null_tolerance":true,"enumeration_closure":{"closure":"every machine-checkable S2a acceptance item is one numbered check; the pass path asserts the check counter equals the declared expected_count","selfcheck":{"expected_count":15}},"proposition_fidelity":{"correspondence":"metafile://d7f836544764bf4ca68bd7903b7a6c7a978ff16e34a828e9b31c928cbec05ccci0.md","artifactPin":"metafile://d7f836544764bf4ca68bd7903b7a6c7a978ff16e34a828e9b31c928cbec05ccci0.md","artifactKey":"acceptance-sheet","coverage":["statement","definitions","proof-direction"],"note":"statement = S2a deliverable (CLI contract in the base README); definitions = git-bundle result contract + runner contract; proof-direction = the acceptance-sheet S2a table."}}}