METRICS = {"portable_backends": "backends", "renewals": "buyers",
           "eligible_buyers": "buyers", "age": "days"}
def valid_iso(value):
    parts = value.split("-") if type(value) is str else []
    return len(parts) == 3 and [len(part) for part in parts] == [4, 2, 2] and all(part.isdigit() for part in parts)
def decide(memo, maximum_age_days=90):
    errors = []
    if memo.get("scenario") not in {"base", "downside", "upside"} or memo.get("claim_kind") not in {"fact", "inference"}:
        errors.append("scenario_or_claim_kind")
    sources = memo.get("sources", {})
    if type(sources) is not dict or not sources:
        errors.append("sources")
    else:
        for source_id, source_date in sources.items():
            if not valid_iso(source_date):
                errors.append(source_id + "_date")
    for name, unit in METRICS.items():
        item = memo.get(name, {})
        if type(item.get("value")) is not int or item.get("unit") != unit:
            errors.append(name + "_schema")
    failures = []
    if not errors:
        if memo["portable_backends"]["value"] < 2:
            failures.append("portability")
        if memo["renewals"]["value"] < 3:
            failures.append("renewals")
        if memo["age"]["value"] > maximum_age_days:
            failures.append("freshness")
    return {"decision": "invalid" if errors else ("reject" if failures else "conditional-proceed"),
            "errors": sorted(set(errors)), "failed_gates": failures}
memo = {"scenario": "base", "claim_kind": "inference",
        "sources": {"scenario:renewal-ledger": "2026-08-01", "scenario:portability-run": "2026-08-10"},
        "portable_backends": {"value": 2, "unit": "backends"},
        "renewals": {"value": 3, "unit": "buyers"},
        "eligible_buyers": {"value": 4, "unit": "buyers"}, "age": {"value": 4, "unit": "days"}}
base_result = decide(memo)
bad_result = decide({**memo, "sources": {"scenario:renewal-ledger": ""}})
renewal_result = decide({**memo, "scenario": "downside", "renewals": {"value": 2, "unit": "buyers"}})
portability_result = decide({**memo, "scenario": "downside", "portable_backends": {"value": 1, "unit": "backends"}})
assert base_result["decision"] == "conditional-proceed"
assert bad_result["decision"] == "invalid" and "scenario:renewal-ledger_date" in bad_result["errors"]
assert renewal_result["failed_gates"] == ["renewals"] and portability_result["failed_gates"] == ["portability"]
print(f"PASS: 82 memo workbook base={base_result['decision']} invalid={bad_result['errors']} renewal={renewal_result['decision']} portability={portability_result['decision']}")
