Roadmap

This page is what is still to come. Everything already shipped is on what has been delivered, and the design decisions page explains the reasoning behind the plan. The original pitch documents that started the project are in the repository history (first commits on main).

Delivered so far

Phase What it established
0 The algorithm, proved against five real historical fixtures
1 Deterministic single-finding remediation (release 0.1)
2 The advisory database: built locally, shared centrally
3 Ecosystem corpus, CI, Composer and PHP version matrices
4 Global planning: one command that fixes everything
5 Optional automation: --apply and a batched pull request
6 Integrations and prioritisation: SARIF, SBOM, EPSS and KEV, baselines

The detail of each, and the assurance work behind them, is on what has been delivered.

Phase 7 — goal-driven planning (candidate, the next large piece of work)

Motivating case: composer/composer#12777. A TYPO3 12 project requires typo3/cms-core and one extension; the extension requires typo3/cms-dashboard, which the project never lists. The documented major-upgrade command fails, Composer's error names the extension as "locked and not requested" but never the dashboard package, and adding it by hand makes the upgrade work. The planner's conflict-expansion step already reads those "locked" lines, widens the allow list and retries, and the report prints the resulting command with the solver's output as evidence. It does not run here only because nothing in that lock carries an advisory. The engine finds the smallest verified change that reaches a goal; a security fix is one goal, "this package at this constraint" is another.

  • a goal as input (package:constraint, one or several) in place of an advisory: candidate generation takes the target constraint where it now takes the fixed range above the advisory; acceptance becomes "the target is satisfied and no advisories are introduced"
  • a run with no advisory data at all is valid for a goal (today it exits 4), while the no-new-advisories rule still applies whenever data is available; the fail-closed paths get their own tests for this mode
  • goal-aware ranking: widening a root constraint is a last resort for a security fix and the point of a major upgrade; constraint drag is reported as the plan, not as a finding
  • the same reports, exit codes and global combination; several goals are planned jointly
  • fixtures: the TYPO3 case above built with bin/build-fixture.php, and two or three more from Composer's discussions board, with the human-chosen command recorded as for the security fixtures
  • --apply and the batched pull request from Phase 5 accept goals too

Boundary: the goal is a version constraint on a locked package. Interpreting release notes, judging breaking changes or choosing the target version is out of scope; the user names the goal and the planner finds the smallest verified command that reaches it.

Report additions from community feedback (three delivered in 0.9.1, in JSON since 0.10.1)

Suggested in reactions to the project; each is a line in the report, not an engine change. Three were delivered in 0.9.1 for the text and HTML reports, and reached the JSON report in 0.10.1, which increased schema_version to 2 (see compatibility).

  • on a constraint-drag finding, say that no fixed release exists within the current major on the branch in use, and that a maintained fork or a backport published under another name, if one exists, is the alternative to the widening the report recommends (the planner already tries the same-branch backport first: advisory ranges are per branch, so a 2.x project gets the 2.x fix before any major bump is considered; what it cannot see is a fix under a different package name)
  • under every recommendation that relies on a --with constraint, the persistent form of the same instruction: the conflict entry for composer.json ("twig/twig": "<3.14.0", derived from the fixed range already computed) that keeps future updates from falling back below the fix and makes the resolver's error name the reason; Composer has no subcommand for conflict, so it stays a follow-up line rather than part of the verified command
  • what the upgrade changes about what a package can do, as a note on the recommendation: the package type becoming composer-plugin, autoload.files (which runs on every request) or binaries appearing where there were none, and the source or dist host changing between the locked and the recommended version. All of it comes from metadata already diffed, so nothing is downloaded or unpacked; it is shown rather than folded into the ranking, because the count of changed packages measures review burden and this measures something else
  • declared consequence hints: --exposed <package> says that a package handles untrusted input in this application, and its findings are listed first. There was a suppressive side already (--ignore, the baseline) and no escalating one, and a reader's account of triaging four Dompdf findings ahead of one high-severity Guzzle advisory was the case for it. Declared, never inferred; applied to the finished plan, so it reorders the report and cannot change what is recommended or the exit code. In the JSON report as schema_version 3
  • the three above in the JSON report (0.10.1): schema_version 2, report-v2.schema.json published alongside the now-frozen report-v1.schema.json, summary.packages_running_new_code for a gate to key on, and CI recipes for the conflict entries and the capability changes
  • deliberately not adopted: a root replace to pin a transitive dependency (it tells Composer the root provides the package, which then stops being installed), distro backports (they patch PHP and system packages, not a project's vendor directory), and reachability analysis, which decides whether an application can actually reach a vulnerable function: a wrong "not reachable" is a silent miss, and PHP's dynamic dispatch, container wiring and string callables make that verdict too unreliable to put in front of a gate

Before a stable 1.0

1.0 is a promise about stability rather than a feature count, so what stands in the way is not on the lists above. Done so far: the suite runs on Windows and macOS, a compatibility policy says which surfaces a version number covers, and the advisory database this project publishes is watched rather than assumed to be working.

  • soak time on --apply, which is the only thing that writes to a project and is the feature a 1.0 invites people to run unattended on a schedule; this is a reason to wait rather than something to build
  • the sixth adversarial review, taken as the first review of the advisory database's own supply chain and of the parallel planning code. Seven findings, all answered: a feed answering with no data built a database missing that source and nothing noticed, a dead worker discarded the whole run, the count of disagreeing sources was computed and never shown, the published database's provenance was never checked by anything an adopter ran, the publish job attested bytes it had not verified, and the compatibility page promised a schema URL per version that one unversioned file could not honour. One reported finding was a false positive and is recorded as such below
  • a seventh adversarial review, which found that advisory data readable only in part became complete coverage: a lock could be reported clean, with no gaps and no warnings, against data read in part. Six more answered with it, including verification that bound a scan to a pathname rather than to the bytes it had checked. All seven came with runnable reproductions
  • an eighth adversarial review (0.10.0), which took the contract suite as a claim to attack. Ten findings, nine fixed, every one reproduced and none a false positive; three rechecks of the answers each found the previous round's repairs incomplete — four, then three, then one, one of which was a defect introduced by the repair before it. The repairs that held were the ones made structural, so the defect cannot be written again; the ones that came back were the ones that checked a thing at a moment. The advisory database took four attempts and was settled only by removing the need to check at all: a run reads a private copy of the verified bytes. The tenth finding is below
  • a ninth review, of what 0.10.0 changed and of the contract suite it left behind
  • deliberately not in 1.0: Phase 7 below. It is a new capability, not a hole in this one

Assurance (ongoing, alongside the phases)

Work that does not add features but decides whether the recommendations can be trusted. What has already been done is on what has been delivered; each item there has a regression test behind it, and the changelog has the details. Still open:

  • SonarQube itself in addition, for the complexity and duplication view a per-pull-request reviewer does not give (asked for, awaiting an answer)
  • remaining long methods (InProcessSolver::solve, DbBuildCommand::execute, HtmlRenderer::finding, the planner's per-finding search); the PackageChange kind constants became the ChangeKind enum, the last string-constant set in the engine
  • the dependency-path search is bounded in its output, not in its work (the eighth review's tenth finding, the one left open). MAX_PATHS and MAX_DEPTH are applied after Composer has expanded the entire recursive ancestor tree, so a layered graph costs far more than the result suggests: 35 packages took six seconds and 144 MiB to return the same 200 paths that 17 packages returned in 0.007 and no extra memory. Two attempts were reverted because both changed which command the planner recommends on the islandora fixture — walking a level at a time returns more than the 200-path cap, and the truncation drops drupal/core-recommended from the front of the nearest root requirements; pruning each package once across the traversal collapses 123 paths to 34, with the first branch explored claiming every shared ancestor. getDependents() is not a plain walk: it grows its needle list through replaces, which for Drupal produces most of those 123 paths. A fix means reimplementing that faithfully, with the current output as a golden file across the fixture corpus. No safety consequence — it is a cost, and it needs an adversarial graph to bite
  • the reviews already done are on what has been delivered: the sixth took the advisory database's supply chain and the parallel planning code, the seventh the completeness of advisory coverage, the eighth the boundaries between components