{"record":{"id":"936b354b417d136f","repo":"mvanhorn/last30days-skill","slug":"label-capitalize-file-file-path-must-be-a-to","errorCode":null,"errorMessage":"{label.capitalize()} file {file_path} must be a top-level JSON object, got {type(payload).__name__}.","messagePattern":"(.+?) file (.+?) must be a top-level JSON object, got (.+?)\\.","errorType":"exception","errorClass":"HandoffContractError","httpStatus":null,"severity":"error","filePath":"skills/last30days/scripts/lib/discovery_handoff.py","lineNumber":631,"sourceCode":"\n\ndef _load_host_file(path: str | Path, label: str) -> dict[str, Any]:\n    \"\"\"Load a host-authored handoff file with strict top-level checks.\"\"\"\n    file_path = Path(path).expanduser()\n    try:\n        raw = file_path.read_text(encoding=\"utf-8\")\n    except OSError as exc:\n        raise HandoffContractError(\n            f\"Could not read {label} file {file_path}: {exc}\"\n        ) from exc\n    try:\n        payload = json.loads(raw)\n    except json.JSONDecodeError as exc:\n        raise HandoffContractError(\n            f\"{label.capitalize()} file {file_path} is not valid JSON: {exc}\"\n        ) from exc\n    if not isinstance(payload, dict):\n        raise HandoffContractError(\n            f\"{label.capitalize()} file {file_path} must be a top-level JSON \"\n            f\"object, got {type(payload).__name__}.\"\n        )\n    return payload\n\n\ndef _require_bundle_binding(\n    payload: dict[str, Any],\n    bundle: NominationsBundle | PendingReport,\n    *,\n    label: str,\n    save_dir: str | Path | None,\n    config_dir: Path | None,\n) -> None:\n    \"\"\"Enforce bundle-id binding between a host file and the current bundle\n    (or, on the finalize leg, the pending report that inherited its id).\n    The mismatch message names the file actually validated against - the\n    pending report on the finalize leg - so a host's retry is not misdirected","sourceCodeStart":613,"sourceCodeEnd":649,"githubUrl":"https://github.com/mvanhorn/last30days-skill/blob/c7460f6114449ddfe6ea3fc2f23c3d910c0e740c/skills/last30days/scripts/lib/discovery_handoff.py#L613-L649","documentation":"Raised by _load_host_file in discovery_handoff.py when a host-authored file (judgments or angles) parses as valid JSON but its top-level value is not a JSON object (dict) — e.g. the file is a JSON array or a bare string/number. The discovery handoff protocol requires every host file to be a top-level object so fields like bundle_id and the payload list can be looked up by key. It is thrown as HandoffContractError, the dedicated exception for host-file contract violations.","triggerScenarios":"Calling load_judgments/save flow with a judgments file whose content is a bare JSON array like '[{\"nomination_id\": ...}]' instead of '{\"bundle_id\": ..., \"judgments\": [...]}'; same for the angles file passed to the angles loader. Any json.loads result that is a list, str, int, float, bool, or null triggers it after the JSONDecodeError guard passes.","commonSituations":"The host agent writes judgments as a top-level list because the schema mentions 'a list of rows' and it skips the wrapper object; hand-editing the file and dropping the outer braces; a template or example that shows only the array portion gets copied verbatim.","solutions":["Wrap the array in an object with the required keys: {\"bundle_id\": \"<id>\", \"judgments\": [...]} (or \"angles\": [...] for angles files).","Re-run the same discovery leg with the corrected file path; no engine-side change is needed.","Validate the shape before handing the file to the engine: python -c \"import json;d=json.load(open(f));assert isinstance(d,dict)\"."],"exampleFix":"// before (judgments.json)\n[\n  {\"nomination_id\": \"n1\", \"junk\": false}\n]\n\n// after\n{\n  \"bundle_id\": \"<bundle_id from the bundle file>\",\n  \"judgments\": [\n    {\"nomination_id\": \"n1\", \"junk\": false}\n  ]\n}","handlingStrategy":"validation","validationCode":"import json\n\ndef valid_host_file(path: str) -> bool:\n    try:\n        payload = json.loads(open(path, encoding=\"utf-8\").read())\n    except (OSError, json.JSONDecodeError):\n        return False\n    return isinstance(payload, dict)","typeGuard":null,"tryCatchPattern":"try:\n    load_judgments(path, bundle, ...)\nexcept HandoffContractError as e:\n    # message names the file and the offending type; fix the file, not the call\n    raise SystemExit(f\"host file rejected: {e}\")","preventionTips":["Always emit host files with json.dump on a dict, never on a list.","Keep a canonical example judgments/angles file and copy it as the starting point.","Run a one-line shape check (isinstance(json.load(...), dict)) before invoking the engine."],"tags":["json","contract","discovery-handoff","validation"],"backgroundTag":null,"analyzedSha":"c7460f6114449ddfe6ea3fc2f23c3d910c0e740c","analyzedAt":"2026-08-15T03:34:49.540Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}