Skip to content

v0.11.0

Choose a tag to compare

@github-actions github-actions released this 23 Sep 09:29
· 182 commits to main since this release

Added

  • S10: what the run could not check. ok meant two things — every check ran and found nothing,
    or a check never ran — and the difference was invisible on the finding. Anonymously the activity
    round asks only about packages already stale on release age, so a repository archived a week
    after its last release cannot be seen, and the only trace was a note counting how many packages
    were skipped without naming one. A package whose newest releases are dated by a commit their tags
    share has the same shape: S2 has nothing to measure, and the finding read as ok. S10 carries
    the missing check on the finding, with the reason and the signals it blocked (repository_activity
    → S3, S4; release_dates → S2, S8). It is informational, like S7 and S9: it never decides a
    verdict, and it is raised only where the missing check could have changed one — never on a package
    the repository already marks abandoned, nor on an allowlisted one. Credentials for every host take
    away the repository_activity reasons and only those — release_dates asks no forge — and a
    branch snapshot, which is on no release branch, is told about S2 alone.
    --fail-on=unchecked fails a run that could not check everything,
    which is how a pipeline catches the workflow that never passed GITHUB_TOKEN through. See
    What was not checked.

  • One number for how far behind the lock is: libyears. For each package, the years between
    the release installed and the package's newest stable release, summed over the analysed lock —
    171.3 on the wallabag fixture. Every finding carries its libyears in --format=json (null
    when not measured: a branch snapshot, no dated stable release, not from a Composer repository,
    metadata unavailable), and the document a libyears block that is the arithmetic over them —
    total, direct_requirements (the same sum over the direct requirements, the nearest number
    to php-libyear's, which reads composer.json), measured, unmeasured by reason and
    furthest_behind. total and direct_requirements are null where nothing could be measured, so
    that a reader adding the field up over several projects never counts an unmeasurable lock as a
    lock with nothing to fix; a measured lock with nothing behind reports 0. The table and markdown footers print one line (libyears: 171.3 behind across 195 of 200 packages · 111.7 from direct requirements · furthest behind smalot/pdfparser v1.1.0 at 4.7); the HTML page shows the total in its ledger, a sortable column, and the counts
    by reason on the Run tab; --explain prints the package's own value under its verdict, or the
    reason it was not measured. It is laid over the verdicts, not one of them: it counts every drift,
    healthy patches included, and enters no priority, --fail-on or baseline. The schemas gain the
    fields under the same number, optional so that older documents still validate. No new request
    is made: both dates were already in the data. A split package's installed version is dated by
    its monorepo parent's tag of the same version, not by the lock: the lock copies the date
    Packagist gave the split's tag, the commit its tags share, and illuminate/contracts v8.83.27
    would read as 4.65 libyears behind for 3.75. --explain prints that date as installed release
    and names the monorepo it came from. That holds for an installed tag on a shared commit
    under a branch whose newest tag has a commit of its own, as after a change to the split's
    directory: the parent is asked for the installed version, not only for the branch. --explain
    says whose date it is under
    installed_release and installed_release_dated_by, and so does the package card. Where no
    parent dates it, such a tag is not measured at all rather than measured from the commit's date:
    the installed tag's own entry in the repository decides that, not what dated the package's
    newest release.

  • left-behind suggests a branch the project can actually move to. S8 named the newest
    releasing branch and wrote the constraint that follows it, whatever PHP that branch requires:
    Matomo supports php >=7.2.5, locks monolog 1.27.1, and was told require ^3.12 — monolog
    3.x needs PHP 8.1. On the weekly watch, 99 of the 138 constraints suggested to projects that
    declare a require.php contradicted it. Every release branch now carries the php requirement of
    the release it names (read from the same repository data, no new request), and S8 holds the
    higher branches to two floors — the project's own require.php and the target PHP, the version
    Composer resolves against. The newest branch still proves the upstream moved on; the branch the
    evidence tells the project to follow is the newest releasing one within both floors, and when
    that is not the newest the line says what holds the newest back: 3.x released 3.12.0 (2026-09-09), needs php >=8.1 above the project's php >=7.2.5; 2.x released 2.11.1 (2026-09-02); require ^2.11 to follow. With no releasing branch within reach it says so and suggests nothing:
    the way forward is a PHP upgrade, not a composer.json line. --format=json gains newest_php,
    newest_within_reach, floor_php, floor_source and reachable_branch/_version/_release
    on the signal; --explain shows each branch's php requirement in the branch table and in the
    JSON branches rows. See Within reach.

  • abandoned says whether there is somewhere to go. Packagist's marker comes with a free-text
    replacement, and on the weekly watch 19 of 72 abandoned packages carried one — 17 naming a
    package, two naming Symfony and a sentence. The verdict stays one (it is what --fail-on, the
    baseline, the SARIF rule and every count key on); what changes is the reading. Each finding
    carries replacement in --format=json — the named package when it is a Composer package name,
    null otherwise, the free text staying in the evidence as text — the document carries
    "abandoned": {"total", "with_replacement"} next to counts, the summary line reads
    abandoned 7 (6 with a replacement) where that is not zero, and the HTML page tags the row with
    the replacement and links it on the card. Both fields are optional in the schemas. See
    Abandoned, and where to.

Changed

  • One reading of the installed version's date. The lock's time is a release date only
    sometimes — a branch snapshot carries its commit's, a subtree split's tag the date of a commit
    its tags share, a monorepo parent can date the version instead — and each surface re-derived
    that for itself, which is how one explanation came to call a date a release four lines above
    saying it was not one. InstalledRelease answers it once. No output changes.

  • old-promise reads the date against the target's major, not its minor. S5 held a release
    against the GA of the target PHP minor: anything older than 2024-11-21 with a >=7.x constraint
    was an old promise about PHP 8.4. That caught the wrong thing. A >=7.2 cut in 2022 was written
    with PHP 8.1 on every CI matrix and differs from a ^7.2 || ^8.0 of the same day in spelling
    alone, yet only the first was flagged — and the evidence blamed the style (has no upper bound),
    which is Symfony's own convention. The line is now the GA of the target's major (8.0,
    2020-11-26, for any 8.x target): the release predates the major it admits, and its constraint was
    written for an older one. On the weekly watch that is 22 of the 178 old-promise verdicts the
    minor line produced; the other 156 were releases of the PHP 8 era. The evidence reads released 2020-01-11 for PHP 5 (php ">=5.3.2"), before PHP 8 existed (8.0 GA 2020-11-26); admits 8.4 untested. In --format=json the signal's ga_date is now the major's, next to a new
    target_major, and written_for_php names the constraint's lower major. The verdict, its
    priority and --fail-on are unchanged; a project's count of old-promise will drop.

Fixed

  • --explain says what the lock's date is a date of. The composer.lock block called that
    date a release whenever the entry carried one, so a branch snapshot read released 2026-09-21 · branch snapshot and a subtree split read released 2023-06-05 four lines above the block saying
    that same date is a commit its tags share, not a release. It now reads dated … by its commit
    for a snapshot and dated … by a commit its tags share for a split, and released … only where
    the repository dated the version by a release. The JSON keeps its lock.released field, whose
    schema description now says which of the three it is carrying.
  • The report schema accepts the empty chain a run can really produce. chain was declared with
    minItems: 1, and two ordinary runs break that: a composer.lock with no composer.json beside
    it has no direct requirements at all, so nothing reaches any package, and a lock can hold a
    package only the skipped require-dev asks for. Every finding of such a run was valid JSON that
    its own published schema rejected. Documents themselves are unchanged.
  • migrate to names a package, or says nothing. The clause read the abandoned marker as the
    repository wrote it, so free text became an instruction — no fix expected; migrate to Symfony,
    migrate to EnglishInflector from the String component — and a repository that names the package
    itself sent the reader back to what they were leaving, while the new
    abandoned.with_replacement counted it. The clause, the JSON replacement and that count now
    all read the same validated successor: a Composer package name that is not this package's own.
    The repository's text is still shown in the evidence, as all free text is.