{
    "$schema": "http://json-schema.org/draft-04/schema#",
    "id": "https://lockrot.dev/schema/report-1.json",
    "title": "lockrot report",
    "description": "The document `composer lockrot --format=json` prints. Objects are open: a field added later validates against this file unchanged; the number in the id changes only when a field is removed or renamed.",
    "type": "object",
    "required": ["lockrot", "generated_at", "activity_cache_oldest_at", "packages_checked", "include_dev", "not_from_composer_repository", "network_failures", "counts", "priorities", "exposure", "baseline", "notes", "findings"],
    "properties": {
        "$schema": { "type": "string" },
        "lockrot": { "$ref": "#/definitions/envelope" },
        "generated_at": { "$ref": "#/definitions/dateTime" },
        "activity_cache_oldest_at": {
            "description": "Fetch time of the oldest repository-activity answer served from lockrot's cache; null when every answer was fetched in this run.",
            "oneOf": [{ "$ref": "#/definitions/dateTime" }, { "type": "null" }]
        },
        "packages_checked": { "type": "integer", "minimum": 0 },
        "include_dev": { "type": "boolean" },
        "not_from_composer_repository": { "type": "integer", "minimum": 0 },
        "network_failures": { "type": "boolean" },
        "counts": {
            "description": "Findings per verdict, every verdict present.",
            "type": "object",
            "required": ["abandoned", "silent", "pinned", "left-behind", "old-promise", "stale", "unknown", "finished", "ok"],
            "properties": {
                "abandoned": { "type": "integer", "minimum": 0 },
                "silent": { "type": "integer", "minimum": 0 },
                "pinned": { "type": "integer", "minimum": 0 },
                "left-behind": { "type": "integer", "minimum": 0 },
                "old-promise": { "type": "integer", "minimum": 0 },
                "stale": { "type": "integer", "minimum": 0 },
                "unknown": { "type": "integer", "minimum": 0 },
                "finished": { "type": "integer", "minimum": 0 },
                "ok": { "type": "integer", "minimum": 0 }
            }
        },
        "priorities": {
            "description": "Findings per priority, every priority present.",
            "type": "object",
            "required": ["critical", "high", "medium", "low", "none"],
            "properties": {
                "critical": { "type": "integer", "minimum": 0 },
                "high": { "type": "integer", "minimum": 0 },
                "medium": { "type": "integer", "minimum": 0 },
                "low": { "type": "integer", "minimum": 0 },
                "none": { "type": "integer", "minimum": 0 }
            }
        },
        "exposure": {
            "description": "Direct requirements that pull in flagged transitive packages, most first.",
            "type": "array",
            "items": {
                "type": "object",
                "required": ["package", "flagged"],
                "properties": {
                    "package": { "$ref": "#/definitions/packageName" },
                    "flagged": { "type": "integer", "minimum": 1 }
                }
            }
        },
        "baseline": {
            "description": "How the run compared with the baseline file, null when none was used.",
            "oneOf": [{ "$ref": "#/definitions/baselineComparison" }, { "type": "null" }]
        },
        "notes": { "type": "array", "items": { "type": "string" } },
        "findings": {
            "description": "One per analysed package, in report order: priority, then verdict severity, direct before transitive, then name.",
            "type": "array",
            "items": { "$ref": "#/definitions/finding" }
        }
    },
    "definitions": {
        "envelope": {
            "type": "object",
            "required": ["version", "schema"],
            "properties": {
                "version": { "description": "The lockrot release that wrote the document.", "type": "string", "minLength": 1 },
                "schema": { "description": "The document schema number this file describes.", "type": "integer", "enum": [1] }
            }
        },
        "dateTime": { "type": "string", "format": "date-time" },
        "date": { "type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2}$" },
        "packageName": { "description": "A Composer package name, vendor/name.", "type": "string", "pattern": "^[^\\/]+\\/[^\\/]+$" },
        "verdict": { "type": "string", "enum": ["abandoned", "silent", "pinned", "left-behind", "old-promise", "stale", "unknown", "finished", "ok"] },
        "priority": { "type": "string", "enum": ["critical", "high", "medium", "low", "none"] },
        "level": { "type": "string", "enum": ["info", "warn", "high"] },
        "years": { "description": "Years elapsed, one decimal.", "type": "number", "minimum": 0 },
        "datedBy": { "description": "The monorepo the dates were read from through its `replace` list (a package split out of laravel/framework or symfony/symfony), null when they are the package's own.", "oneOf": [{ "$ref": "#/definitions/packageName" }, { "type": "null" }] },
        "baselineComparison": {
            "type": "object",
            "required": ["path", "known", "new", "worsened", "stale"],
            "properties": {
                "path": { "type": "string" },
                "known": { "type": "integer", "minimum": 0 },
                "new": { "type": "integer", "minimum": 0 },
                "worsened": { "type": "integer", "minimum": 0 },
                "stale": { "description": "Baselined package names absent from the analysed lock.", "type": "array", "items": { "$ref": "#/definitions/packageName" } }
            }
        },
        "finding": {
            "type": "object",
            "required": ["package", "version", "verdict", "priority", "direct", "dev", "signals", "chain", "direct_dependents", "evidence", "allowlist_reason", "note", "data_date"],
            "properties": {
                "package": { "$ref": "#/definitions/packageName" },
                "version": { "type": "string" },
                "verdict": { "$ref": "#/definitions/verdict" },
                "priority": { "$ref": "#/definitions/priority" },
                "direct": { "type": "boolean" },
                "dev": { "description": "Listed under packages-dev in the lock.", "type": "boolean" },
                "signals": { "type": "array", "items": { "$ref": "#/definitions/signal" } },
                "chain": { "description": "From the direct requirement down to this package; one element when it is direct.", "type": "array", "minItems": 1, "items": { "$ref": "#/definitions/packageName" } },
                "direct_dependents": { "type": "array", "items": { "$ref": "#/definitions/packageName" } },
                "evidence": { "type": "string" },
                "allowlist_reason": { "type": ["string", "null"] },
                "note": { "type": ["string", "null"] },
                "data_date": { "oneOf": [{ "$ref": "#/definitions/dateTime" }, { "type": "null" }] }
            }
        },
        "signal": {
            "type": "object",
            "required": ["id", "level", "summary", "data"],
            "properties": {
                "id": { "type": "string", "enum": ["S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9"] },
                "level": { "$ref": "#/definitions/level" },
                "summary": { "type": "string" },
                "data": { "type": "object" }
            },
            "anyOf": [
                { "properties": { "id": { "enum": ["S1"] }, "data": { "$ref": "#/definitions/s1" } } },
                { "properties": { "id": { "enum": ["S2"] }, "data": { "$ref": "#/definitions/s2" } } },
                { "properties": { "id": { "enum": ["S3"] }, "data": { "$ref": "#/definitions/s3" } } },
                { "properties": { "id": { "enum": ["S4"] }, "data": { "$ref": "#/definitions/s4" } } },
                { "properties": { "id": { "enum": ["S5"] }, "data": { "$ref": "#/definitions/s5" } } },
                { "properties": { "id": { "enum": ["S6"] }, "data": { "$ref": "#/definitions/s6" } } },
                { "properties": { "id": { "enum": ["S7"] }, "data": { "$ref": "#/definitions/s7" } } },
                { "properties": { "id": { "enum": ["S8"] }, "data": { "$ref": "#/definitions/s8" } } },
                { "properties": { "id": { "enum": ["S9"] }, "data": { "$ref": "#/definitions/s9" } } }
            ]
        },
        "s1": {
            "description": "Marked abandoned by its repository.",
            "type": "object",
            "required": ["replacement"],
            "properties": { "replacement": { "type": ["string", "null"] } }
        },
        "s2": {
            "description": "No release for years.",
            "type": "object",
            "required": ["last_release", "last_version", "years", "dated_by"],
            "properties": {
                "last_release": { "$ref": "#/definitions/dateTime" },
                "last_version": { "type": ["string", "null"] },
                "years": { "$ref": "#/definitions/years" },
                "dated_by": { "$ref": "#/definitions/datedBy" }
            }
        },
        "s3": {
            "description": "Repository archived.",
            "type": "object",
            "required": ["repo", "host"],
            "properties": { "repo": { "type": "string" }, "host": { "type": "string" } }
        },
        "s4": {
            "description": "No push for years.",
            "type": "object",
            "required": ["last_push", "repo", "host", "years"],
            "properties": {
                "last_push": { "$ref": "#/definitions/dateTime" },
                "repo": { "type": "string" },
                "host": { "type": "string" },
                "years": { "$ref": "#/definitions/years" }
            }
        },
        "s5": {
            "description": "Released before the target PHP's GA with an open-ended php constraint.",
            "type": "object",
            "required": ["target_php", "ga_date", "php_constraint", "released"],
            "properties": {
                "target_php": { "type": "string" },
                "ga_date": { "$ref": "#/definitions/date" },
                "php_constraint": { "type": "string" },
                "released": { "$ref": "#/definitions/dateTime" }
            }
        },
        "s6": {
            "description": "Pinned to a branch snapshot, or no tagged release.",
            "type": "object",
            "required": ["version"],
            "properties": { "version": { "type": "string" } }
        },
        "s7": {
            "description": "A direct requirement pulling in flagged transitive packages.",
            "type": "object",
            "required": ["flagged", "packages"],
            "properties": {
                "flagged": { "type": "integer", "minimum": 1 },
                "packages": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "required": ["package", "verdict", "chain"],
                        "properties": {
                            "package": { "$ref": "#/definitions/packageName" },
                            "verdict": { "$ref": "#/definitions/verdict" },
                            "chain": { "type": "array", "items": { "$ref": "#/definitions/packageName" } }
                        }
                    }
                }
            }
        },
        "s8": {
            "description": "The installed release branch stopped while a higher one kept releasing.",
            "type": "object",
            "required": ["branch", "branch_last_release", "branch_last_version", "years", "newest_branch", "newest_version", "newest_release", "suggested_constraint", "dated_by"],
            "properties": {
                "branch": { "type": "string" },
                "branch_last_release": { "$ref": "#/definitions/dateTime" },
                "branch_last_version": { "type": "string" },
                "years": { "$ref": "#/definitions/years" },
                "newest_branch": { "type": "string" },
                "newest_version": { "type": "string" },
                "newest_release": { "$ref": "#/definitions/dateTime" },
                "suggested_constraint": { "description": "A caret constraint that follows the newest releasing branch, null when Composer cannot suggest one.", "type": ["string", "null"] },
                "dated_by": { "$ref": "#/definitions/datedBy" }
            }
        },
        "s9": {
            "description": "Security advisories on the installed version.",
            "type": "object",
            "required": ["advisories"],
            "properties": {
                "advisories": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "required": ["id", "cve", "title", "link", "severity", "reported_at", "affected_versions", "fixed_by", "fixed_on_branch"],
                        "properties": {
                            "id": { "type": "string" },
                            "cve": { "type": ["string", "null"] },
                            "title": { "type": ["string", "null"] },
                            "link": { "type": ["string", "null"] },
                            "severity": { "type": ["string", "null"] },
                            "reported_at": { "oneOf": [{ "$ref": "#/definitions/dateTime" }, { "type": "null" }] },
                            "affected_versions": { "type": ["string", "null"] },
                            "fixed_by": { "description": "The lowest listed release the advisory does not affect, null when none.", "type": ["string", "null"] },
                            "fixed_on_branch": { "description": "Whether fixed_by is on the installed release branch.", "type": "boolean" }
                        }
                    }
                }
            }
        }
    }
}
