#!/usr/bin/env python3 """Compile one hash-bound source document into a pending-review KB proposal. The command is deliberately build-only. It validates a raw artifact, its UTF-8 text extraction, and an extraction manifest; then it reuses Leo's rich proposal normalizer and strict staging preflight to produce a schema-valid ``approve_claim`` child. It never connects to Postgres and never executes the generated staging SQL. """ from __future__ import annotations import argparse import hashlib import json import os import re import sys import tempfile import uuid from pathlib import Path from typing import Any from urllib.parse import urlsplit HERE = Path(__file__).resolve().parent sys.path.insert(0, str(HERE)) import apply_proposal as ap # noqa: E402 import stage_normalized_proposal as stage # noqa: E402 MANIFEST_SCHEMA = "livingip.kbSourceExtractionManifest.v1" MANIFEST_SCHEMA_V2 = "livingip.kbSourceExtractionManifest.v2" MANIFEST_SCHEMAS = {MANIFEST_SCHEMA, MANIFEST_SCHEMA_V2} BUNDLE_SCHEMA = "livingip.kbSourceProposalBundle.v1" STATUS_SCHEMA = "livingip.kbSourceCompilerStatus.v1" SHA256_RE = re.compile(r"^[0-9a-f]{64}$") KEY_RE = re.compile(r"^[a-z0-9][a-z0-9._:-]{0,127}$") URI_SCHEME_RE = re.compile(r"^[a-z][a-z0-9+.-]*:", re.IGNORECASE) UNSAFE_URI_SCHEMES = {"data", "javascript", "vbscript"} TOP_LEVEL_KEYS = { "schema", "artifact_sha256", "extracted_text_sha256", "extractor", "source", "claims", } TOP_LEVEL_KEYS_V2 = TOP_LEVEL_KEYS | {"dedupe"} EXTRACTOR_KEYS = {"name", "version"} SOURCE_KEYS = {"identity", "source_key", "source_type", "title", "locator"} CLAIM_KEYS = {"claim_key", "type", "text", "quote", "confidence", "tags", "evidence"} EVIDENCE_KEYS = {"quote", "role"} DEDUPE_KEYS = {"database_search_query", "candidate_claims", "duplicates"} DEDUPE_CANDIDATE_KEYS = {"id", "text", "score", "evidence_count", "edge_count"} DEDUPE_DUPLICATE_KEYS = {"claim_id", "source_quote", "reason"} class CompilerError(RuntimeError): """Raised when an input cannot safely become a reviewable proposal.""" def sha256_bytes(value: bytes) -> str: return hashlib.sha256(value).hexdigest() def canonical_json_bytes(value: Any) -> bytes: return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True).encode("utf-8") def canonical_sha256(value: Any) -> str: return sha256_bytes(canonical_json_bytes(value)) def stable_uuid(label: str) -> str: return str(uuid.uuid5(uuid.NAMESPACE_URL, f"livingip:kb-source-compiler:v1:{label}")) def _require_object(value: Any, label: str) -> dict[str, Any]: if not isinstance(value, dict): raise CompilerError(f"{label} must be an object") return value def _require_exact_keys(value: dict[str, Any], allowed: set[str], label: str) -> None: unknown = sorted(set(value) - allowed) if unknown: raise CompilerError(f"{label} contains unsupported keys: {', '.join(unknown)}") def _require_nonempty_string(value: Any, label: str) -> str: if not isinstance(value, str) or not value.strip(): raise CompilerError(f"{label} must be a non-empty string") if value != value.strip(): raise CompilerError(f"{label} must not have leading or trailing whitespace") return value def _require_sha256(value: Any, label: str) -> str: text = _require_nonempty_string(value, label) if not SHA256_RE.fullmatch(text): raise CompilerError(f"{label} must be a lowercase SHA-256 digest") return text def _require_key(value: Any, label: str) -> str: text = _require_nonempty_string(value, label) if not KEY_RE.fullmatch(text): raise CompilerError(f"{label} must match {KEY_RE.pattern}") return text def _require_stable_reference(value: Any, label: str) -> str: text = _require_nonempty_string(value, label) if any(character.isspace() for character in text) or not URI_SCHEME_RE.match(text): raise CompilerError(f"{label} must be a stable URI-like reference with a scheme") parsed = urlsplit(text) if not parsed.scheme or text.endswith(":"): raise CompilerError(f"{label} must identify a specific source, not only a scheme or chat label") scheme = parsed.scheme.lower() if scheme in UNSAFE_URI_SCHEMES: raise CompilerError(f"{label} uses an unsafe URI scheme: {scheme}") if scheme in {"http", "https"}: if not parsed.netloc: raise CompilerError(f"{label} HTTP reference must include a host") if parsed.username is not None or parsed.password is not None: raise CompilerError(f"{label} must not embed credentials") if scheme in {"telegram", "tg"} and not re.search(r"\d", f"{parsed.netloc}{parsed.path}"): raise CompilerError(f"{label} Telegram reference must include a stable chat or message identifier") return text def _read_required_file(path: Path, label: str) -> bytes: try: if not path.is_file(): raise CompilerError(f"{label} path is not a regular file: {path}") value = path.read_bytes() except OSError as exc: raise CompilerError(f"could not read {label}: {exc}") from exc if not value: raise CompilerError(f"{label} must not be empty") return value def _quote_binding(text: str, quote: Any, label: str) -> dict[str, Any]: exact = _require_nonempty_string(quote, label) start = text.find(exact) if start < 0: raise CompilerError(f"{label} is not an exact substring of the extracted text") return { "quote": exact, "quote_sha256": sha256_bytes(exact.encode("utf-8")), "first_start": start, "first_end": start + len(exact), "occurrence_count": text.count(exact), } def _require_nonnegative_integer(value: Any, label: str) -> int: if isinstance(value, bool) or not isinstance(value, int) or value < 0: raise CompilerError(f"{label} must be a non-negative integer") return value def _validate_dedupe(value: Any, extracted_text: str) -> dict[str, Any]: dedupe = _require_object(value, "manifest.dedupe") _require_exact_keys(dedupe, DEDUPE_KEYS, "manifest.dedupe") query = _require_nonempty_string(dedupe.get("database_search_query"), "manifest.dedupe.database_search_query") raw_candidates = dedupe.get("candidate_claims") if not isinstance(raw_candidates, list) or len(raw_candidates) > 20: raise CompilerError("manifest.dedupe.candidate_claims must be a list with at most 20 rows") candidates: list[dict[str, Any]] = [] candidate_ids: set[str] = set() for index, raw_candidate in enumerate(raw_candidates): label = f"manifest.dedupe.candidate_claims[{index}]" candidate = _require_object(raw_candidate, label) _require_exact_keys(candidate, DEDUPE_CANDIDATE_KEYS, label) try: claim_id = str(uuid.UUID(_require_nonempty_string(candidate.get("id"), f"{label}.id"))) except ValueError as exc: raise CompilerError(f"{label}.id must be a full UUID") from exc if claim_id in candidate_ids: raise CompilerError(f"{label}.id duplicates an earlier canonical candidate") candidate_ids.add(claim_id) candidates.append( { "id": claim_id, "text": _require_nonempty_string(candidate.get("text"), f"{label}.text"), "score": _require_nonnegative_integer(candidate.get("score"), f"{label}.score"), "evidence_count": _require_nonnegative_integer( candidate.get("evidence_count"), f"{label}.evidence_count" ), "edge_count": _require_nonnegative_integer(candidate.get("edge_count"), f"{label}.edge_count"), } ) raw_duplicates = dedupe.get("duplicates") if not isinstance(raw_duplicates, list) or len(raw_duplicates) > len(candidates): raise CompilerError("manifest.dedupe.duplicates must not exceed the retrieved candidate count") duplicates: list[dict[str, Any]] = [] duplicate_ids: set[str] = set() for index, raw_duplicate in enumerate(raw_duplicates): label = f"manifest.dedupe.duplicates[{index}]" duplicate = _require_object(raw_duplicate, label) _require_exact_keys(duplicate, DEDUPE_DUPLICATE_KEYS, label) try: claim_id = str(uuid.UUID(_require_nonempty_string(duplicate.get("claim_id"), f"{label}.claim_id"))) except ValueError as exc: raise CompilerError(f"{label}.claim_id must be a full UUID") from exc if claim_id not in candidate_ids: raise CompilerError(f"{label}.claim_id was not present in the canonical retrieval candidates") if claim_id in duplicate_ids: raise CompilerError(f"{label}.claim_id duplicates an earlier duplicate judgment") duplicate_ids.add(claim_id) source_quote = _quote_binding(extracted_text, duplicate.get("source_quote"), f"{label}.source_quote") duplicates.append( { "claim_id": claim_id, "source_quote": source_quote["quote"], "reason": _require_nonempty_string(duplicate.get("reason"), f"{label}.reason"), } ) return { "database_search_query": query, "candidate_claims": candidates, "duplicates": duplicates, } def _validate_manifest( manifest: dict[str, Any], artifact_sha256: str, text_sha256: str, extracted_text: str ) -> tuple[dict[str, Any], list[dict[str, Any]]]: manifest_schema = manifest.get("schema") if manifest_schema not in MANIFEST_SCHEMAS: raise CompilerError(f"manifest.schema must be one of {sorted(MANIFEST_SCHEMAS)}") _require_exact_keys( manifest, TOP_LEVEL_KEYS_V2 if manifest_schema == MANIFEST_SCHEMA_V2 else TOP_LEVEL_KEYS, "manifest", ) declared_artifact_sha256 = _require_sha256(manifest.get("artifact_sha256"), "manifest.artifact_sha256") declared_text_sha256 = _require_sha256(manifest.get("extracted_text_sha256"), "manifest.extracted_text_sha256") if declared_artifact_sha256 != artifact_sha256: raise CompilerError("artifact SHA-256 does not match manifest.artifact_sha256") if declared_text_sha256 != text_sha256: raise CompilerError("extracted text SHA-256 does not match manifest.extracted_text_sha256") extractor = _require_object(manifest.get("extractor"), "manifest.extractor") _require_exact_keys(extractor, EXTRACTOR_KEYS, "manifest.extractor") extractor_name = _require_nonempty_string(extractor.get("name"), "manifest.extractor.name") extractor_version = _require_nonempty_string(extractor.get("version"), "manifest.extractor.version") source = _require_object(manifest.get("source"), "manifest.source") _require_exact_keys(source, SOURCE_KEYS, "manifest.source") source_identity = _require_stable_reference(source.get("identity"), "manifest.source.identity") source_locator = _require_stable_reference(source.get("locator"), "manifest.source.locator") source_key = _require_key(source.get("source_key"), "manifest.source.source_key") source_title = _require_nonempty_string(source.get("title"), "manifest.source.title") source_type = _require_nonempty_string(source.get("source_type"), "manifest.source.source_type") if source_type not in ap.SOURCE_TYPES: raise CompilerError(f"manifest.source.source_type must be one of {sorted(ap.SOURCE_TYPES)}") claims = manifest.get("claims") if not isinstance(claims, list) or not claims: raise CompilerError("manifest.claims must be a non-empty list of objects") if len(claims) > ap.MAX_BUNDLE_ROWS["claims"]: raise CompilerError(f"manifest.claims exceeds {ap.MAX_BUNDLE_ROWS['claims']} rows") validated_claims: list[dict[str, Any]] = [] quote_bindings: list[dict[str, Any]] = [] seen_claim_keys: set[str] = set() derived_source_keys = {source_key} for claim_index, raw_claim in enumerate(claims): label = f"manifest.claims[{claim_index}]" claim = _require_object(raw_claim, label) _require_exact_keys(claim, CLAIM_KEYS, label) claim_key = _require_key(claim.get("claim_key"), f"{label}.claim_key") if claim_key in seen_claim_keys: raise CompilerError(f"duplicate claim_key: {claim_key}") seen_claim_keys.add(claim_key) body_source_key = f"{claim_key}__proposal_body" if body_source_key in derived_source_keys: raise CompilerError(f"duplicate source_key after normalization: {body_source_key}") derived_source_keys.add(body_source_key) claim_type = _require_nonempty_string(claim.get("type"), f"{label}.type") if claim_type not in ap.CLAIM_TYPES: raise CompilerError(f"{label}.type must be one of {sorted(ap.CLAIM_TYPES)}") claim_text = _require_nonempty_string(claim.get("text"), f"{label}.text") claim_quote = _quote_binding(extracted_text, claim.get("quote"), f"{label}.quote") quote_bindings.append({"claim_key": claim_key, "kind": "claim", **claim_quote}) confidence = claim.get("confidence") if confidence is not None and ( isinstance(confidence, bool) or not isinstance(confidence, (int, float)) or not 0 <= confidence <= 1 ): raise CompilerError(f"{label}.confidence must be a number from 0 to 1 or null") tags = claim.get("tags", []) if not isinstance(tags, list) or any( not isinstance(tag, str) or not tag.strip() or tag != tag.strip() for tag in tags ): raise CompilerError(f"{label}.tags must be a list of non-empty strings") if len(tags) != len(set(tags)): raise CompilerError(f"{label}.tags contains duplicates") evidence = claim.get("evidence") if not isinstance(evidence, list) or not evidence: raise CompilerError(f"{label}.evidence must be a non-empty list of exact-quote objects") validated_evidence: list[dict[str, Any]] = [] seen_evidence: set[tuple[str, str]] = set() for evidence_index, raw_evidence in enumerate(evidence): evidence_label = f"{label}.evidence[{evidence_index}]" evidence_row = _require_object(raw_evidence, evidence_label) _require_exact_keys(evidence_row, EVIDENCE_KEYS, evidence_label) role = _require_nonempty_string(evidence_row.get("role"), f"{evidence_label}.role") if role not in ap.EVIDENCE_ROLES: raise CompilerError(f"{evidence_label}.role must be one of {sorted(ap.EVIDENCE_ROLES)}") binding = _quote_binding(extracted_text, evidence_row.get("quote"), f"{evidence_label}.quote") evidence_key = (binding["quote"], role) if evidence_key in seen_evidence: raise CompilerError(f"{evidence_label} duplicates an earlier evidence quote and role") seen_evidence.add(evidence_key) quote_bindings.append( { "claim_key": claim_key, "kind": "evidence", "evidence_role": role, **binding, } ) validated_evidence.append({"quote": binding["quote"], "role": role}) validated_claims.append( { "claim_key": claim_key, "type": claim_type, "text": claim_text, "quote": claim_quote["quote"], "confidence": confidence, "tags": tags, "evidence": validated_evidence, } ) validated_manifest = { "schema": manifest_schema, "artifact_sha256": artifact_sha256, "extracted_text_sha256": text_sha256, "extractor": {"name": extractor_name, "version": extractor_version}, "source": { "identity": source_identity, "source_key": source_key, "source_type": source_type, "title": source_title, "locator": source_locator, }, "claims": validated_claims, } if manifest_schema == MANIFEST_SCHEMA_V2: validated_manifest["dedupe"] = _validate_dedupe(manifest.get("dedupe"), extracted_text) return validated_manifest, quote_bindings def _locator_fields(locator: str) -> tuple[str | None, str | None]: scheme = urlsplit(locator).scheme.lower() if scheme in {"http", "https"}: return locator, None return None, locator def build_parent_proposal( manifest: dict[str, Any], manifest_sha256: str, quote_bindings: list[dict[str, Any]] ) -> dict[str, Any]: source = manifest["source"] parent_seed = ":".join((manifest["artifact_sha256"], manifest["extracted_text_sha256"], manifest_sha256)) parent_id = stable_uuid(f"parent:{parent_seed}") url, storage_path = _locator_fields(source["locator"]) excerpts: list[dict[str, Any]] = [] seen_quotes: set[str] = set() for binding in quote_bindings: quote = binding["quote"] if quote not in seen_quotes: seen_quotes.add(quote) excerpts.append({"text": quote}) claim_candidates = [] for claim in manifest["claims"]: claim_candidates.append( { "claim_key": claim["claim_key"], "type": claim["type"], "headline": claim["text"], "text": claim["text"], "body": claim["quote"], "confidence": claim["confidence"], "tags": claim["tags"], "evidence_tier": "hash_bound_exact_quote", "needs_research": False, "evidence": [{"source_key": source["source_key"], "role": item["role"]} for item in claim["evidence"]], "edges": [], } ) provenance_summary = ( f"identity={source['identity']}; locator={source['locator']}; " f"artifact_sha256={manifest['artifact_sha256']}; " f"extracted_text_sha256={manifest['extracted_text_sha256']}; " f"extractor={manifest['extractor']['name']}@{manifest['extractor']['version']}" ) dedupe = manifest.get("dedupe") or {} return { "id": parent_id, "proposal_type": "approve_claim", "status": "pending_review", "proposed_by_handle": "leo", "proposed_by_agent_id": None, "channel": "source_document_compiler", "source_ref": ( f"source-compiler:{manifest['artifact_sha256'][:16]}:" f"{manifest['extracted_text_sha256'][:16]}:{manifest_sha256[:16]}" ), "rationale": "Compile one hash-bound source artifact into exact-quote claim candidates for review.", "payload": { "source_document": { "manifest_schema": manifest["schema"], "manifest_sha256": manifest_sha256, "artifact_sha256": manifest["artifact_sha256"], "extracted_text_sha256": manifest["extracted_text_sha256"], "source_identity": source["identity"], "source_locator": source["locator"], "extractor": manifest["extractor"], }, "claim_candidates": claim_candidates, "source_candidates": [ { "source_key": source["source_key"], "source_type": source["source_type"], "title": source["title"], "url": url, "storage_path": storage_path, "source_quality": provenance_summary, "usage_limits": "Only the exact bound quotes may ground claims; human review is required.", "key_excerpts": excerpts, "content_sha256": manifest["artifact_sha256"], } ], "dedupe_conflict_assessment": { "database_search_query": dedupe.get("database_search_query") or " ".join(claim["text"] for claim in manifest["claims"]), "canonical_candidates": dedupe.get("candidate_claims", []), "potential_existing_claim_ids": [item["claim_id"] for item in dedupe.get("duplicates", [])], "conflicts": dedupe.get("duplicates", []), "review_required": True, }, }, } def _validate_compiled_child( child: dict[str, Any], manifest: dict[str, Any], quote_bindings: list[dict[str, Any]] ) -> None: if child.get("status") != "pending_review" or child.get("proposal_type") != "approve_claim": raise CompilerError("normalization did not produce one pending_review approve_claim child") apply_payload = (child.get("payload") or {}).get("apply_payload") if not isinstance(apply_payload, dict) or apply_payload.get("contract_version") != 2: raise CompilerError("strict child has no schema-valid approve_claim v2 apply_payload") claims = apply_payload.get("claims") or [] sources = apply_payload.get("sources") or [] if len(claims) != len(manifest["claims"]): raise CompilerError("strict child claim count differs from the extraction manifest") if len({row.get("id") for row in claims}) != len(claims): raise CompilerError("strict child contains duplicate claim ids") if [row.get("text") for row in claims] != [row["text"] for row in manifest["claims"]]: raise CompilerError("strict child claim text differs from the extracted atomic propositions") if len({row.get("id") for row in sources}) != len(sources): raise CompilerError("strict child contains duplicate source ids") source = manifest["source"] artifact_sources = [ row for row in sources if row.get("hash") == manifest["artifact_sha256"] and (row.get("url") == source["locator"] or row.get("storage_path") == source["locator"]) ] if len(artifact_sources) != 1: raise CompilerError("strict child must contain exactly one hash-bound declared artifact source") try: excerpt_packet = json.loads(artifact_sources[0]["excerpt"]) except (TypeError, json.JSONDecodeError) as exc: raise CompilerError("declared source excerpt was truncated or is not reviewable JSON") from exc retained_excerpt = excerpt_packet.get("excerpt") if isinstance(excerpt_packet, dict) else None if not isinstance(retained_excerpt, str): raise CompilerError("declared source excerpt does not retain exact quote text") missing_quotes = sorted( {binding["quote"] for binding in quote_bindings if binding["quote"] not in retained_excerpt} ) if missing_quotes: raise CompilerError("one or more exact quotes exceed the current canonical source excerpt capacity") def compile_source_packet(artifact_path: Path, text_path: Path, manifest_path: Path) -> dict[str, Any]: artifact_bytes = _read_required_file(artifact_path, "artifact") text_bytes = _read_required_file(text_path, "extracted text") manifest_bytes = _read_required_file(manifest_path, "extraction manifest") try: extracted_text = text_bytes.decode("utf-8", errors="strict") except UnicodeDecodeError as exc: raise CompilerError(f"extracted text must be valid UTF-8: {exc}") from exc try: manifest_raw = json.loads(manifest_bytes.decode("utf-8", errors="strict")) except (UnicodeDecodeError, json.JSONDecodeError) as exc: raise CompilerError(f"extraction manifest must be valid UTF-8 JSON: {exc}") from exc manifest_object = _require_object(manifest_raw, "manifest") artifact_sha256 = sha256_bytes(artifact_bytes) text_sha256 = sha256_bytes(text_bytes) manifest, quote_bindings = _validate_manifest(manifest_object, artifact_sha256, text_sha256, extracted_text) manifest_canonical_sha256 = canonical_sha256(manifest) parent = build_parent_proposal(manifest, manifest_canonical_sha256, quote_bindings) try: child = stage.prepare_normalized_child(parent) except (ValueError, stage.bound.CheckpointError) as exc: raise CompilerError(f"strict child preflight rejected the source packet: {exc}") from exc _validate_compiled_child(child, manifest, quote_bindings) stage_sql = stage.build_stage_sql(child) stable_bundle = { "schema": BUNDLE_SCHEMA, "status": "pending_review", "build_only": True, "database_write_performed": False, "canonical_apply_performed": False, "inputs": { "artifact_bytes": len(artifact_bytes), "extracted_text_bytes": len(text_bytes), "manifest_bytes": len(manifest_bytes), }, "validated_manifest": manifest, "quote_bindings": quote_bindings, "parent_proposal": parent, "strict_child_proposal": child, "stage_preview": { "target_table": "kb_stage.kb_proposals", "executed": False, "stage_sql_sha256": sha256_bytes(stage_sql.encode("utf-8")), }, "compiler_contract": { "normalizer": "scripts/kb_proposal_normalize.py", "strict_preflight": "scripts/stage_normalized_proposal.py", "declared_artifact_source_count": 1, "source_from_chat_labels": False, }, "hashes": { "artifact_sha256": artifact_sha256, "extracted_text_sha256": text_sha256, "manifest_file_sha256": sha256_bytes(manifest_bytes), "manifest_canonical_sha256": manifest_canonical_sha256, "parent_packet_sha256": child["parent_packet_sha256"], "strict_child_payload_sha256": child["payload_sha256"], }, } stable_bundle["hashes"]["bundle_content_sha256"] = canonical_sha256(stable_bundle) return stable_bundle def parse_args(argv: list[str] | None = None) -> argparse.Namespace: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--artifact", required=True, type=Path, help="raw source artifact; any binary format") parser.add_argument("--text", required=True, type=Path, help="strict UTF-8 extraction of the artifact") parser.add_argument("--manifest", required=True, type=Path, help="extraction manifest JSON") parser.add_argument("--output", required=True, type=Path, help="private path for the compiled proposal bundle") return parser.parse_args(argv) def _write_private_output(path: Path, rendered: str) -> None: path.parent.mkdir(parents=True, exist_ok=True, mode=0o700) fd, raw_temp = tempfile.mkstemp(prefix=f".{path.name}.", suffix=".tmp", dir=path.parent) temp = Path(raw_temp) try: os.fchmod(fd, 0o600) with os.fdopen(fd, "w", encoding="utf-8") as handle: handle.write(rendered) handle.flush() os.fsync(handle.fileno()) os.replace(temp, path) os.chmod(path, 0o600) finally: if temp.exists(): temp.unlink() def _print_status(status: str, *, reason: str | None = None) -> None: payload: dict[str, Any] = { "schema": STATUS_SCHEMA, "status": status, "database_write_performed": False, "canonical_apply_performed": False, "private_bundle_written": status == "pending_review", } if reason is not None: payload["reason"] = reason print(json.dumps(payload, sort_keys=True)) def main(argv: list[str] | None = None) -> int: args = parse_args(argv) try: output_path = args.output.resolve() input_paths = {args.artifact.resolve(), args.text.resolve(), args.manifest.resolve()} if output_path in input_paths: raise CompilerError("--output must not overwrite an input file") bundle = compile_source_packet(args.artifact, args.text, args.manifest) except (CompilerError, OSError, ValueError): _print_status("rejected", reason="input_validation_failed") return 2 rendered = json.dumps(bundle, indent=2, sort_keys=True, ensure_ascii=True) + "\n" try: _write_private_output(args.output, rendered) except OSError: _print_status("rejected", reason="output_write_failed") return 2 _print_status("pending_review") return 0 if __name__ == "__main__": raise SystemExit(main())