Skip to content

Release harness ​

A harness tests released packages straight from the registry, with no checkout at all. The manifest names the packages; composer installs them into each cell with full dependency resolution, exactly as a customer would; the source gates run on what was installed. Nothing is downloaded anywhere else: every package reaches the tests only through a real composer require into a cell.

The manifest ​

Omit modules and the manifest is a harness. install must then be composer from packagist and name the packages.

json
{
  "furnace": 1,
  "name": "qoliber/advanced-faq",
  "install": {
    "method": "composer",
    "source": "packagist",
    "repositories": ["https://qoliber.repo.packagist.com/qoliber-shop"],
    "packages": [
      "qoliber/advanced-faq",
      "qoliber/advanced-faq-ai",
      "qoliber/advanced-faq-graphql",
      "qoliber/advanced-faq-hyva",
      "qoliber/advanced-faq-rule",
      "qoliber/advanced-faq-search",
      "qoliber/advanced-faq-webapi",
      "qoliber/ai"
    ],
    "version": "^1.0"
  },
  "layers": ["unit", "integration"],
  "requires": { "capabilities": [] },
  "matrix": {
    "magento": ["2.4.6", "2.4.7", "2.4.8", "2.4.9"],
    "mageos": ["1.3.1", "2.3.0", "3.4.0"],
    "php": ["8.1", "8.2", "8.3", "8.4", "8.5"],
    "themes": ["luma", "hyva"]
  }
}

name is the extension under test. install.repositories is a list of URLs (composer repositories) or {"type": "composer"|"vcs"|"git"|"github"|"gitlab"|"bitbucket", "url": ...} entries; they are added to the cell for the run and removed by the golden restore. version applies to every package; with a range such as ^1.0 each cell may legitimately hold a different release, and the report says which.

Publish the harness as a package of its own next to the extension it tests, with the manifest, and later the seed and specs, inside. Ours lives at ~/qoliber-extensions/furnace-advanced-faq as qoliber/furnace-advanced-faq (composer type furnace-manifest); it is not on the registry yet.

Running it ​

bash
# from the harness directory
cd ~/qoliber-extensions/furnace-advanced-faq
furnace run --gate=all --release=1.0.0 -c m248       # one cell, a pinned release
furnace run --gate=all -p 3                          # the matrix, newest ^1.0 per cell

# from anywhere: composer fetches the harness itself first
furnace run --gate=all --package=qoliber/furnace-advanced-faq \
    --repository=https://qoliber.repo.packagist.com/qoliber-shop
furnace run --gate=all --package=qoliber/furnace-advanced-faq:dev-main \
    --repository=~/qoliber-extensions/furnace-advanced-faq        # a local checkout

--package is the one composer require that happens outside a cell: the harness itself, into a small work project under ~/.furnace/work/harness/, because its manifest decides which cells run. --repository says where to find it: a composer repository URL, or a local directory used as a path repository (repeatable). Composer's own credentials apply.

A harness always runs --gate=all; --install=copy, --source=path and --source=archive need module code and are refused.

What runs where ​

GateSource
composer requirethe packages, from install.repositories and the repositories the cell already knows
rector, syntax floor, static analysis, uniton each cell after the install, against the module directories inside the cell's vendor, with the cell's PHP, PHPUnit, PHPStan and PHPCS and Magento's unit bootstrap; they land in that cell's row
setup:upgrade, modules enabled, di:compile, cronthe installed packages that carry a registration.php
integrationTest/Integration inside the installed packages
seed, e2ethe harness directory, when it ships e2e.seed and dev/tests/specs

Two cells, m248 and m249, ship no vendor/bin/phpstan or vendor/bin/phpcs, so static analysis is reported n/a there; the other seven cells have all three tools.

What this requires of the packages ​

The tests must ship in the dist. With no checkout there is no fallback: a package that export-ignores its Test/ directory contributes no unit or integration tests, and the gate records a skip, which blocks certification. The m2-faqs modules have no .gitattributes, so their 1.0.0 dists carry 145 unit tests and 82 integration tests. Most other Qoliber modules export-ignore /Test; for those, certify from the main repository with --release, where the checkout's tests run against the installed package. The decision is one per vendor: ship the tests, or always certify from a checkout.

A harness judges the release by its own tests. On 2026-10-26 the 1.0.0 release of m2-faqs failed its shipped FaqsTest on m248: the test constructs a block with six arguments and the block takes seven. The current checkout has the fixed test; the release does not. That is a release finding, and the correct verdict.

Verified ​

Both on m248, 2026-10-26:

  • From the harness directory with --release=1.0.0: eight packages installed from the private registry in 6 s, two setup:upgrade passes, eight modules enabled, di:compile, cleanup left the cell clean.
  • With --package=qoliber/furnace-advanced-faq:dev-main --repository=<local dir>: the harness fetched by composer into the work project, install, rector 2 s, syntax floor, static analysis n/a (no tools on m248), unit 145 tests with the 15 release errors above, cleanup clean, doctor fully green afterwards.

Two traps surfaced and were fixed on the way: Rector's global */vendor/* skip silently excluded modules that live in the cell's vendor, so skips are now per module path; and a cell's composer platform_check.php refuses furnace's own PHP (m248 requires 8.4), so the installed packages carry the cell's PHP binary into every tool that runs on them.

Furnace 1.0.0-alpha · internal tooling · qoliber.com