← Files note Workspace|ROGNALIAARCHIVED FILE

skills/note-workspace-setup/scripts/validate_role_task_bindings.py

2.53 KB · Oct 2, 2026 · 00:33 UTC

↓ Download file

#!/usr/bin/env python3
"""Validate role/task binding history and optional readiness completeness."""

from __future__ import annotations

import argparse
import json
import sys
from typing import Optional

from task_binding_common import (
    PRIMARY_ROLES_BY_MODE,
    TaskBindingError,
    active_bindings,
    load_events,
    resolve_workspace,
)


def _parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(description="Validate role/task bindings.")
    parser.add_argument("workspace", help="Absolute path to the user workspace")
    parser.add_argument(
        "--require-ready",
        action="store_true",
        help="Require every primary role selected by task_topology.mode",
    )
    return parser


def main(argv: Optional[list[str]] = None) -> int:
    args = _parser().parse_args(argv)
    try:
        workspace, manifest, settings = resolve_workspace(args.workspace)
        events = load_events(workspace, manifest, settings)
        mode = settings["task_topology"]["mode"]
        generation = settings["task_topology"]["binding_generation"]
        active = active_bindings(events, mode, generation)
        for (role, slot), event in active.items():
            if slot == "primary" and event["task_title"] != settings["task_names"][role]:
                raise TaskBindingError(
                    f"active binding title does not match operating settings: {role}:{slot}"
                )
        required = {(role, "primary") for role in PRIMARY_ROLES_BY_MODE[mode]}
        missing = sorted(required - set(active)) if args.require_ready else []
        if missing:
            labels = ", ".join(f"{role}:{slot}" for role, slot in missing)
            raise TaskBindingError(f"required ready bindings are missing: {labels}")
        print(
            json.dumps(
                {
                    "status": "pass",
                    "task_mode": mode,
                    "binding_generation": generation,
                    "event_count": len(events),
                    "active_binding_count": len(active),
                    "ready_complete": not missing and required.issubset(active),
                    "external_actions": [],
                },
                ensure_ascii=False,
                indent=2,
            )
        )
        return 0
    except (OSError, ValueError, json.JSONDecodeError) as exc:
        print(
            json.dumps({"status": "fail", "error": str(exc)}, ensure_ascii=False),
            file=sys.stderr,
        )
        return 1


if __name__ == "__main__":
    raise SystemExit(main())

SHA-256: 25e9cfa1a14220011bf54c9e0fc18c9afab30e95b97461aa068af92c954c7051