Compatibility and scope
What viv's byte-identical promise actually covers, what stays out of scope, and how that's checked before every release.
What viv promises
For the commands on the
Scope page, viv writes a
vendor/ directory — including vendor/composer/* — and, for update,
update-lock and require/remove, a composer.lock that are
byte-identical to what Composer 2.10 (the version pinned in
devbox.json) writes for the same composer.json, lock file and cache
state. This is the contract, not an implementation detail: if viv's output
differs from Composer's for a covered command, that's a bug.
Composer moves; viv tracks one version of it at a time. Each release states which Composer version it targets, and the compat sweep (see below) is run against that version before the release ships.
The package repository type is compat mode and is covered: viv update
against one produces Composer's lock byte for byte.
What is not covered
- Plugins. Only the native adapters viv ships (see
docs/plugin-strategy.md) are covered. Any other plugin is out of scope; viv either skips it or thecomposershim hands the invocation to the real Composer. - Research-chapter surface.
viv.lockandviv update --lock native,viv lock convert,viv lock merge,viv workspace listandviv isolate(with theextra.viv.isolatekey it writes) are outside this contract. They sit behind explicit opt-in, their behaviour is judged against their chapter indocs/research.md, and they may change between minor releases. - Commands Composer keeps.
search,config,global,self-update,licenses,dependsand the rest of the long tail stay with Composer and aren't part of viv's contract. - viv's own
init,newanddiagnose.viv initwrites acomposer.jsonfrom defaults, without Composer's interactive questions.viv new(alsocreate-project) starts a project in a new directory, andviv diagnoseprints viv's own report. Their output isn't promised to match Composer'sinit,create-projectordiagnose; thevendor/andcomposer.lockthey go on to write follow the same contract asviv install. - Progress wording on stdout during a run. Only the files viv writes and
the plain-text output of commands such as
show,whyandvalidateare contractual. Spinner text, progress counters and similar in-flight chatter can change between releases without notice. - Timing. The numbers in the README track viv's speed but aren't a promise; a release can get slower on a given machine without breaking the contract, though the project tries hard not to let that happen.
Works with
- Repositories: Packagist, Private Packagist and Satis, including a local
file://mirror,path,vcs, git, GitHub andpackage(inline declarations) sources; redirects are followed. - Packages: zip and tar dists, a git checkout when there is no dist,
preferred-install: source, sha1 checks, credentials fromauth.jsonandCOMPOSER_AUTH. - Autoload: PSR-4, PSR-0, classmap and files;
--optimize-autoloaderand--classmap-authoritative;platform_check.php;vendor/binproxies; lifecycle scripts. - Commands:
install, full and partialupdateincluding--minimal-changesand Composer's default blocking of versions with a security advisory (--no-blockingto allow them),add,rm,dump-autoload, and offline mode with--offlineorCOMPOSER_DISABLE_NETWORK.installrefuses before writing anything when the lock needs a PHP version, extension or library the detected platform lacks, honouringconfig.platform,--ignore-platform-reqsand--ignore-platform-req. - Plugins: the ones with native adapters, listed under Plugins.
Reasons not to use viv
- It stays 0.x. No 1.0 is planned. Minor releases can change behaviour
and flags; the release notes and
JOURNAL.mdcall those out. The output contract (vendor/andcomposer.lockidentical to Composer's) is the one thing that does not move.1 - Windows is not supported. Linux and macOS only.
- Maintenance is on demand. The compatible mode is complete and no new
plugin adapters or Composer commands are planned. A bug in it that a real
project hits gets fixed; open an issue with the project's
composer.jsonand lock. Releases continue as research chapters land, and the compat sweep and benchmark gates run on every change so the drop-in behaviour does not regress. - Some Composer plugins stop the install. symfony/flex and any plugin
without a native adapter make viv exit with an error naming the plugin.
--no-pluginsinstalls as Composer would without them, but the plugin's work is not done. Of the 10 pinned test projects, 1 is in this position:symfony/demo, for symfony/flex, which viv refuses by design. - The shim needs a real Composer for everything else. It maps
install,dump-autoload,normalize,create-project,update,requireandremoveto viv;searchand the rest go to the Composer on yourPATH, and with no real Composer installed they fail. vendor/files are read-only by default. viv hardlinks them from a shared store, so an edit insidevendor/fails instead of changing every project on the machine. If you patch vendor files by hand, install with--link-mode copy, or--link-mode clonefor writable files sharing the store's disk space where the filesystem supports it.- A
minimum-stabilitygap. A version filtered out byminimum-stabilityis still reported as not found rather than as filtered; Composer names the cause.
Is it safe to try
viv's contract is that its output matches Composer's byte for byte. Before
every release, a compatibility sweep installs a mix of pinned popular
projects and a random sample of Packagist packages with both Composer and
viv, then compares the results.2 The v0.21.0 sweep: all 20 install rows of the
pinned corpus are identical, 9 of the 10 pinned projects resolve the same
lock (craftcms/craft differs3), and viv lock export reproduces all 10
committed locks byte for byte. In the random sample, all 12 rows that
Composer could install are identical; the other 8 were skipped because
Composer itself could not resolve the project.4
One pinned project still needs --no-plugins, for a plugin viv refuses by
design rather than one it has yet to port.5 See Plugins
below.
How compatibility is checked
Every release runs the compatibility
sweep
against a mix of pinned popular projects and a random sample of Packagist
packages, comparing viv's vendor/ and composer.lock against Composer's
own. Per-release results live in
compat/results/;
ongoing sweeps of public projects outside that pinned set are tracked in
compat/hunted.md.
The Speed page turns the newest numbers into a table.
For the curious, here's exactly what a plain install writes, and how a path renders inside it — the detail the sweep checks byte for byte.
vendor/autoload.php, and in vendor/composer/:
| File | When |
|---|---|
autoload_namespaces.php, autoload_psr4.php, autoload_classmap.php, autoload_static.php, autoload_real.php |
always |
autoload_files.php |
any files entry, else deleted |
platform_check.php |
config.platform-check not false and at least one PHP or ext requirement, else deleted |
ClassLoader.php, InstalledVersions.php, LICENSE |
verbatim copies from Composer (MIT); vivace embeds them from src/autoload/templates/ |
installed.json, installed.php |
always |
vendor/bin/* |
packages with bin, per config.bin-compat (src/bin.rs) |
No .gitignore is written. Files are only rewritten when their bytes change.
Standard layout gives $vendorDir = dirname(__DIR__); $baseDir = dirname($vendorDir);.
A path under the vendor dir renders as $vendorDir . '/psr/log/src', anything
else relative to the project as $baseDir . '/src'. Paths are normalised
first: // collapsed, ./ and .. resolved, trailing slash stripped, so
"src/" and "src" both give /src. In autoload_static.php the same paths
render as __DIR__ . '/..' . '/psr/log/src' and __DIR__ . '/../..' . '/src'.
Versioning
viv stays in the 0.x series; no 1.0 is planned, because a 1.0 promises
maintenance that nobody has asked for yet (see docs/research.md). Minor
releases can make breaking changes, called out in JOURNAL.md. Within
that, a minor release may:
- add new commands or flags,
- make internals faster,
- change stdout progress wording or timing.
A minor release may not change the output bytes (vendor/, composer.lock,
or the contractual stdout of show/why/validate) that Composer's pinned
version would produce for a given input, unless it's fixing a bug in a
previous release's output.
When a release moves the pinned Composer version, its JOURNAL.md entry
says so, and the compat sweep results in compat/results/ are regenerated
against the new version as part of that release (see
AGENTS.md, "Before a release").
-
docs/stability.mdstates what a minor release may and may not change. ↩ -
See
compat/README.mdfor how the sweep works. ↩ -
The v0.18.0 sweep had one lock differ, #316: craftcms/craft's requirements are satisfied by
yii2-shell2.0.6and bydev-master, and the two solvers search in a different order, so the security-advisories feed, by changing which versions of other packages are available, decides whether Composer's search ends ondev-master. The v0.19.0 and v0.20.0 sweeps resolved the same lock on all 10, and the v0.21.0 sweep differs again, onsymfony/var-dumper(v7.4.18 in Composer's lock, v5.4.48 in viv's). The issue stays open because the feed can flip the result. ↩ -
The skips are packages Composer itself refuses to resolve, not something viv got wrong. In the v0.21.0 sweep, 3 of the 4 skipped projects have only
dev-or alpha versions, which the defaultminimum-stabilityexcludes; Composer could not find a dependency of the fourth. Earlier sweeps also skipped a project when security advisories blocked every matching version, or its platform requirements were unmet. Full results, including which projects and what was skipped, are incompat/results/v0.21.0.md. ↩ -
Of viv's 10 pinned compatibility projects, the one that needs
--no-pluginsissymfony/demo, forsymfony/flex. Flex does its work incomposer require, so installing from a committed lock loses nothing; seedocs/plugin-strategy.md. ↩