{"name":"semantic-review","lang":"python","entry":"spec-semantic-review.py","script":"#!/usr/bin/env python3\n\"\"\"\nspec-semantic-review.py — proposition-fidelity review submission checker\n(MetaTask spec script, protocol v1.2.1 §spec contract). Used by triage-kind\nreview nodes (and by proof-kind nodes whose deliverable is a literature-aligned\nrestatement, e.g. T4 node `base`).\n\nNode shape it judges: \"review an on-chain deliverable against the INDEPENDENT\ncorrespondence artifact cited by the task's spec.validation.proposition_fidelity\nand report, item by item, whether the formalized statement / definitions /\nproof direction still match the original proposition\". This script closes the\nmachine-checkable part of that delivery: the artifact reference is a real pin\nreference (never the unresolved publish placeholder), the protocol's three\ncorrespondence items are all covered exactly once, each item carries a\nsubstantive note, a `divergent` item carries evidence, and the summary verdict\nagrees with the items.\n\nVerdict semantics: `pass` means the review report is complete and well formed —\nincluding a well-evidenced `divergent` report, which is a legitimate delivery\n(it tells the publisher the task's proposition may not match the bank's\nrecord). `fail` is reserved for malformed or incomplete reports. The substance\nof the review (is the alignment claim true?) stays with the reviewer's\nsemantic_check, which the protocol routes to this same artifact.\n\nInput (stdin, JSON):\n {\n \"task\": \"T1-JSP-000301\",\n \"artifactPin\": \"metafile://...i0\",\n \"items\": [\n { \"id\": \"statement\", \"assessment\": \"aligned\", \"note\": \"...\" },\n { \"id\": \"definitions\", \"assessment\": \"aligned\", \"note\": \"...\" },\n { \"id\": \"proof-direction\", \"assessment\": \"divergent\", \"note\": \"...\", \"evidence\": \"...\" }\n ],\n \"reviewerVerdict\": \"divergent\",\n \"semanticCheck\": \"...\"\n }\nOutput (stdout, JSON): { \"verdict\": \"pass\" | \"fail\" | \"invalid\", \"detail\": \"...\" }\nCriteria (all must hold for pass):\n 1) task is a non-empty string; artifactPin matches ^(pin://|metafile://)\\\\S+$\n and is NOT the publish-time placeholder PUBLISH_ARTIFACT_FIRST\n 2) items is a non-empty array; ids unique; the three protocol items\n statement / definitions / proof-direction are all present\n 3) every item: assessment in {aligned, divergent}; note a string of >= 20\n characters; a divergent item carries an evidence string of >= 10 characters\n 4) reviewerVerdict in {aligned, divergent} and consistent with the items:\n any divergent item -> \"divergent\", none -> \"aligned\"\n 5) semanticCheck is a string of >= 20 characters (the pointer the protocol\n tells reviewers to raise)\nnull/missing input, or a shape that cannot be judged -> verdict=invalid with the\noffending location (protocol null_tolerance criterion).\n\"\"\"\nimport json\nimport re\nimport sys\n\nREQUIRED_ITEMS = (\"statement\", \"definitions\", \"proof-direction\")\nARTIFACT_RE = re.compile(r\"^(pin://|metafile://)\\S+$\")\nPLACEHOLDER = \"PUBLISH_ARTIFACT_FIRST\"\nMIN_NOTE = 20\nMIN_EVIDENCE = 10\n\n\ndef emit(verdict, detail, **extra):\n payload = {\"verdict\": verdict, \"detail\": detail}\n payload.update(extra)\n print(json.dumps(payload, ensure_ascii=False))\n sys.exit(0)\n\n\ndef main():\n try:\n raw = json.load(sys.stdin)\n except Exception as err:\n emit(\"invalid\", \"input json: %s\" % err)\n if not isinstance(raw, dict):\n emit(\"invalid\", \"input must be an object\")\n\n task = raw.get(\"task\")\n if not isinstance(task, str) or not task.strip():\n emit(\"invalid\", \"task: missing or empty\")\n\n artifact_pin = raw.get(\"artifactPin\")\n if not isinstance(artifact_pin, str) or not artifact_pin.strip():\n emit(\"invalid\", \"artifactPin: missing or empty\")\n if artifact_pin.strip() == PLACEHOLDER:\n emit(\"fail\", \"artifactPin: unresolved publish placeholder %s — the correspondence \"\n \"artifact must be published and its pinId substituted before review\" % PLACEHOLDER)\n if not ARTIFACT_RE.match(artifact_pin.strip()):\n emit(\"invalid\", \"artifactPin: must be a pin:// or metafile:// reference\")\n\n items = raw.get(\"items\")\n if not isinstance(items, list) or not items:\n emit(\"invalid\", \"items: missing or empty array\")\n seen = set()\n assessments = {}\n for index, item in enumerate(items):\n where = \"items[%d]\" % index\n if not isinstance(item, dict):\n emit(\"invalid\", \"%s: not an object\" % where)\n item_id = item.get(\"id\")\n if not isinstance(item_id, str) or not item_id.strip():\n emit(\"invalid\", \"%s.id: missing or empty\" % where)\n if item_id in seen:\n emit(\"fail\", \"%s.id: duplicate item %s\" % (where, item_id))\n seen.add(item_id)\n assessment = item.get(\"assessment\")\n if assessment not in (\"aligned\", \"divergent\"):\n emit(\"invalid\", \"%s.assessment: must be aligned or divergent\" % where)\n note = item.get(\"note\")\n if not isinstance(note, str) or len(note.strip()) < MIN_NOTE:\n emit(\"fail\", \"%s.note: must be a substantive string (>= %d characters)\" % (where, MIN_NOTE))\n if assessment == \"divergent\":\n evidence = item.get(\"evidence\")\n if not isinstance(evidence, str) or len(evidence.strip()) < MIN_EVIDENCE:\n emit(\"fail\", \"%s: divergent item needs evidence (>= %d characters)\" % (where, MIN_EVIDENCE))\n assessments[item_id] = assessment\n\n missing = [item_id for item_id in REQUIRED_ITEMS if item_id not in assessments]\n if missing:\n emit(\"fail\", \"items: missing required correspondence item(s): %s\" % \", \".join(missing))\n\n reviewer_verdict = raw.get(\"reviewerVerdict\")\n if reviewer_verdict not in (\"aligned\", \"divergent\"):\n emit(\"invalid\", \"reviewerVerdict: must be aligned or divergent\")\n expected_verdict = \"divergent\" if \"divergent\" in assessments.values() else \"aligned\"\n if reviewer_verdict != expected_verdict:\n emit(\"fail\", \"reviewerVerdict %s disagrees with the item assessments (expected %s)\"\n % (reviewer_verdict, expected_verdict))\n\n semantic_check = raw.get(\"semanticCheck\")\n if not isinstance(semantic_check, str) or len(semantic_check.strip()) < MIN_NOTE:\n emit(\"fail\", \"semanticCheck: must be a substantive string (>= %d characters)\" % MIN_NOTE)\n\n print(json.dumps({\n \"verdict\": \"pass\",\n \"detail\": \"review report complete for %s against %s: %d item(s), reviewer verdict %s\"\n % (task, artifact_pin, len(items), reviewer_verdict),\n \"coveredItems\": sorted(assessments),\n \"expected_count\": len(REQUIRED_ITEMS),\n \"reviewerVerdict\": reviewer_verdict,\n }, ensure_ascii=False))\n\n\nif __name__ == \"__main__\":\n main()\n","input":"{\"nodeParams\": [\"task\", \"artifactKey\", \"requiredItems\"], \"stdin\": \"one JSON object (same contract as semantic-review-301)\"}","output":"{\"onPass\": \"coveredItems, expected_count (3) and the reviewer verdict\", \"stdout\": \"one JSON object\", \"verdict\": \"pass | fail | invalid\"}","validation":{"enumeration_closure":{"closure":"the three protocol correspondence items (statement, definitions, proof-direction) of the JSP-000870 artifact, each exactly once, including the index-base question that artifact raises","expected_count":3,"selfcheck":{"expected_count":3,"expected_verdict":"pass","vector":"complete report for the JSP-000870 artifact"}},"note":"Protocol v1.2.1 paths.spec.fields.validation (verbatim): H_ACT2 及以后发布的 spec 必填且三项齐备——null_tolerance(bool:任何分支把 null/缺失输入映射为 verdict=invalid 并在 detail 给出位置,未捕获异常判非合规);enumeration_closure(object:声明闭包并附至少一个具体自检向量,期望计数须为整数字段供机械对账,如 n=8→28);proposition_fidelity(object:必须引用独立 correspondence 件(pin://|metafile://)承载逐项对照表——定理陈述/定义/证明方向对齐原始命题;自报布尔判非合规;复核者经 semantic_check 指向该件)","null_tolerance":true,"proposition_fidelity":{"artifactKey":"correspondence-T3-JSP-000870","artifactPin":"metafile://028e1432d1059070fc76ceeed018c45429d1263ba5c2f0ad1c5e6e69c8dfb01ci0.json","correspondence":"metafile://028e1432d1059070fc76ceeed018c45429d1263ba5c2f0ad1c5e6e69c8dfb01ci0.json","coverage":["statement","definitions","proof-direction"],"note":"Independent correspondence artifact: wave1-correspondence-artifacts.json#correspondence-T3-JSP-000870. Publish it first and replace the placeholder with its pin://|metafile:// id in BOTH correspondence and artifactPin. A self-declared boolean is non-compliant (index base and irrationality scope are the items to recheck)."}}}