def schedule(roadmap):
    errors = []
    nodes = roadmap.get("milestones", {})
    for name, node in nodes.items():
        duration, exit_gate = node.get("duration", {}), node.get("exit", {})
        if type(node.get("parents")) is not list or not node.get("source_id"):
            errors.append(name + "_parents_or_source")
        if type(duration.get("value")) is not int or duration.get("unit") != "review intervals":
            errors.append(name + "_duration")
        if type(exit_gate.get("threshold")) not in (int, float) or not exit_gate.get("unit"):
            errors.append(name + "_exit")
        if any(parent not in nodes for parent in node.get("parents", [])):
            errors.append(name + "_unknown_parent")
    finish = {}
    pending = set(nodes)
    while pending and not errors:
        ready = sorted(name for name in pending if all(parent in finish for parent in nodes[name]["parents"]))
        if not ready:
            errors.append("cycle")
            break
        for name in ready:
            finish[name] = max([finish[parent] for parent in nodes[name]["parents"]] or [0]) + nodes[name]["duration"]["value"]
        pending = pending - set(ready)
    target = roadmap.get("target")
    return {"decision": "invalid" if errors else f"conditional-interval-{finish[target]}",
            "errors": sorted(set(errors)), "finish": finish, "target_finish": finish.get(target)}
roadmap = {"scenario": "logical-qubit dependency roadmap", "target": "logical", "milestones": {
    "physical": {"parents": [], "duration": {"value": 1, "unit": "review intervals"},
                 "exit": {"threshold": 0.005, "unit": "error/cycle"}, "source_id": "scenario:device-plan"},
    "syndrome": {"parents": ["physical"], "duration": {"value": 1, "unit": "review intervals"},
                 "exit": {"threshold": 1.0, "unit": "MHz"}, "source_id": "scenario:control-plan"},
    "decoder": {"parents": ["syndrome"], "duration": {"value": 2, "unit": "review intervals"},
                "exit": {"threshold": 1.0, "unit": "Msyndromes/s"}, "source_id": "scenario:decoder-plan"},
    "logical": {"parents": ["syndrome", "decoder"], "duration": {"value": 1, "unit": "review intervals"},
                "exit": {"threshold": 0.001, "unit": "logical error/operation"}, "source_id": "scenario:system-plan"}}}
base_result = schedule(roadmap)
bad_decoder = {key: value for key, value in roadmap["milestones"]["decoder"].items() if key != "source_id"}
bad_result = schedule({**roadmap, "milestones": {**roadmap["milestones"], "decoder": bad_decoder}})
delayed_decoder = {**roadmap["milestones"]["decoder"], "duration": {"value": 3, "unit": "review intervals"}}
delayed_result = schedule({**roadmap, "milestones": {**roadmap["milestones"], "decoder": delayed_decoder}})
assert base_result["target_finish"] == 5 and base_result["finish"]["decoder"] == 4
assert bad_result["decision"] == "invalid" and "decoder_parents_or_source" in bad_result["errors"]
assert delayed_result["target_finish"] == 6 and delayed_result["decision"] == "conditional-interval-6"
print(f"PASS: 81 roadmap workbook base={base_result['target_finish']} intervals invalid={bad_result['errors']} decoder_delay={delayed_result['target_finish']}")
