Skip to content

ci(monorepo): resolve the oldest allowed siblings, not just the newest - #201

Closed
levivannoort wants to merge 4 commits into
mainfrom
ci/catch-too-loose-sibling-constraints
Closed

ci(monorepo): resolve the oldest allowed siblings, not just the newest#201
levivannoort wants to merge 4 commits into
mainfrom
ci/catch-too-loose-sibling-constraints

Conversation

@levivannoort

@levivannoort levivannoort commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

The gap

bin/monorepo installs with composer update, which takes the newest version every range admits. A constraint that is too loose still pulls a sibling carrying the API the code calls, so nothing fails — the check that would catch it never runs.

Not hypothetical. queue/1.6.0 shipped with:

call constraint method exists from
Pool::maintain() pools: ^2.0 2.1.0
Connection::tick() nats: ^1.0.0 1.2.0

Both invisible in CI. Both uninstallable for a consumer holding an older sibling in its own lock — which is exactly cloud, on pools 2.0.2.

The check

--lowest on check and test resolves every constraint to its floor. Replayed against the pre-fix constraints it reproduces both failures; against the fixed ones it passes:

# pre-fix constraints
578  Call to an undefined method Utopia\NATS\Connection::tick().
127  Call to an undefined method Utopia\Pools\Pool::maintain().

# fixed constraints
all checks passed

A third instance, found while writing it

queue declares di: ^0.3 but calls Container::set() with two arguments. di 0.3.0 requires three (array $dependencies became optional in 0.3.1). queue/1.6.1 as published can be installed against di 0.3.0 and will fatal — so this wants a release once merged.

Why the CI job is informational

Seven packages carry the same defect today — cache, cli, http, platform, pools, servers, telemetry. Gating now would block unrelated work; continue-on-error: true surfaces it instead, and it becomes a gate once those are clean. lowest (telemetry) failing on this PR is that working as intended.

The matrix is pre-filtered in the changed job to registry-resolved entries only — --linked pins siblings to the checkout and never exercises the constraint, so a linked entry would boot a runner just to skip.

Note on the failing checks

  • lowest (telemetry) — informational, see above.
  • test (vcs, linked) — pre-existing and unrelated: vcs-gitlab-bootstrap-1 exited (1), the same e2e container failure that hit main on chore: stop committing package lock files #194. It surfaces here because touching bin/ puts every package in the changed set.

`bin/monorepo` installs with `composer update`, which takes the newest
version every range admits. A constraint that is too loose therefore still
pulls a sibling carrying the API the code calls, and nothing fails -- so the
check that would catch it never runs.

That is not hypothetical. queue 1.6.0 shipped calling Pool::maintain() under
`pools: ^2.0` (the method exists only from 2.1.0) and Connection::tick()
under `nats: ^1.0.0` (only from 1.2.0). Both were invisible here and both are
uninstallable for a consumer holding an older sibling in its own lock.

`--lowest` on `check` and `test` resolves every constraint to its floor,
which is the only combination that proves the declared ranges are honest.
Replaying it against the pre-fix constraints reproduces both failures; against
the fixed ones it passes.

It found a third instance while being written: queue declares `di: ^0.3` but
calls Container::set() with two arguments, and di 0.3.0 requires three. The
floor is 0.3.1. queue 1.6.1 as published can be installed against di 0.3.0
and will fatal, so this needs a release.

The CI job is informational for now -- 9 of 34 packages carry the same defect
(cache, cli, client, compression, http, platform, pools, servers, telemetry)
and gating on it today would block unrelated work. It becomes a gate once
those are fixed.
A linked entry booted a runner, installed PHP and then skipped its only
step, reporting a no-op pass. Filtering at the job level instead of the
step means the 22 linked entries never start.
The job-level `if` referencing `matrix.linked` is not valid — the matrix
context is not available there, and the whole workflow failed to parse in
0s. Emit a second, pre-filtered matrix from the changed job instead, so a
linked entry never becomes a job at all.
The first count came from a local sweep that conflated constraint failures
with missing local extensions. CI proved compression passes; client's two
errors were the same local noise. Seven packages carry the real defect.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant