REQUIRED = {"name": str, "algorithm": str, "owner": str, "exposure": dict,
            "replacement_test": str, "dependency": str, "track": str, "source_id": str}
def triage(inventory, urgent_years):
    errors = []
    migration = []
    for asset in inventory:
        missing = [name for name, kind in REQUIRED.items()
                   if name not in asset or type(asset[name]) is not kind or not asset[name]]
        exposure = asset.get("exposure", {})
        if type(exposure.get("value")) not in (int, float) or exposure.get("unit") != "years":
            missing.append("exposure_value_or_unit")
        if asset.get("track") == "QKD-evaluation" and not asset.get("physical_link"):
            missing.append("physical_link")
        if missing:
            errors.append(asset.get("name", "asset") + ":" + ",".join(sorted(set(missing))))
        elif asset["track"] == "PQC-migration":
            migration.append(asset)
    ordered = [a["name"] for a in sorted(migration,
               key=lambda a: (-a["exposure"]["value"], a["name"]))]
    urgent = [a["name"] for a in migration if a["exposure"]["value"] >= urgent_years]
    return {"decision": "invalid" if errors else "migrate", "errors": errors,
            "priority": ordered, "urgent": sorted(urgent)}
assets = [
    {"name": "archive", "algorithm": "RSA-2048", "owner": "release",
     "exposure": {"value": 15, "unit": "years"}, "replacement_test": "ML-KEM restore test",
     "dependency": "release signer", "track": "PQC-migration", "source_id": "nist-pqc"},
    {"name": "external-kex", "algorithm": "ECDH P-256", "owner": "network",
     "exposure": {"value": 8, "unit": "years"}, "replacement_test": "hybrid handshake test",
     "dependency": "gateway", "track": "PQC-migration", "source_id": "nist-pqc"},
    {"name": "metro-link", "algorithm": "authenticated QKD", "owner": "network",
     "exposure": {"value": 1, "unit": "years"}, "replacement_test": "key-service failover",
     "dependency": "fiber plus authentication", "track": "QKD-evaluation",
     "source_id": "etsi-qkd", "physical_link": "leased fiber"}]
base_result = triage(assets, 10)
bad_assets = [{key: value for key, value in assets[0].items() if key != "owner"}, *assets[1:]]
bad_result = triage(bad_assets, 10)
short_archive = [{**assets[0], "exposure": {"value": 0.5, "unit": "years"}}, *assets[1:]]
sensitive_result = triage(short_archive, 10)
assert base_result["priority"][:2] == ["archive", "external-kex"] and base_result["urgent"] == ["archive"]
assert bad_result["decision"] == "invalid" and "owner" in bad_result["errors"][0]
assert sensitive_result["priority"][0] == "external-kex" and sensitive_result["urgent"] == []
print(f"PASS: 68 crypto dossier priority={base_result['priority']} invalid={bad_result['errors']} sensitivity={sensitive_result['priority'][0]}")
