vortos / vortos-ops-kit
Vortos Ops Kit — the one uniform swappable-driver pattern (port + named drivers + DI-keyed registry + capability descriptor + conformance TCK + agnosticism lint + make:driver scaffolder). Shared primitives every deploy/ops concern reuses. Zero runtime reflection.
v1.0.0-alpha-152
2026-06-25 07:32 UTC
Requires
- php: >=8.2
- symfony/console: ^7.3
- symfony/dependency-injection: ^7.3
- vortos/vortos-foundation: ^1.0
Requires (Dev)
- nikic/php-parser: ^5.0
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^11.0
Suggests
- nikic/php-parser: Required for the AST-based agnosticism lint (AgnosticismScanner / PHPStan rule)
- phpstan/phpstan: Required to wire AgnosticismRule as a first-class PHPStan rule
This package is auto-updated.
Last update: 2026-06-25 18:05:12 UTC
README
The one uniform swappable-driver pattern every Vortos deploy/ops concern reuses (roadmap §10). Build it once here so concerns 2–26 never reinvent registry / compiler-pass / capability / TCK subtly differently — the guard against "a mess all over the place."
What's in the box
| Primitive | Purpose |
|---|---|
Attribute\AsDriver |
Declares a driver's stable string key (read once, at compile time). |
Driver\DriverInterface |
The universal contract: every driver reports its capabilities(). A concern's port extends this. |
Driver\Capability\CapabilityDescriptor |
What a driver declares it can do — versioned, canonically serializable, machine-diffable. The single source for config-time validation. |
Driver\Capability\RequiredCapabilities + CapabilityValidator |
What a selection requires, and the fail-closed assertion that runs at config-validation time (CapabilityMismatchException with an actionable message). |
Driver\TaggedDriverRegistry |
Lazy, key→driver registry backed by a compile-time-built ServiceLocator. Zero runtime reflection. |
Driver\DependencyInjection\CollectDriversCompilerPass |
Reusable, late-priority collecting pass. Validates keys (lower-kebab, unique) at build time. |
Testing\ConformanceTestCase |
The TCK base. Every port ships a concrete subclass; every driver extends it — including negative cases (unsupported ⇒ throws, malformed ⇒ rejected). |
Architecture\AgnosticismScanner + Testing\AgnosticismLintTestCase |
AST-based lint (via nikic/php-parser): no provider name outside a Driver\ namespace or the composition root. |
PHPStan\AgnosticismRule |
The same lint as a first-class PHPStan rule, sharing ProviderNameMatcher (wire it once a phpstan.neon exists). |
Console\MakeDriverCommand |
vortos:make:driver <concern> <name> — scaffolds impl + #[AsDriver] + conformance test (--split adds a full composer package). |
Adding a concern (the shape every concern follows)
// 1. A port that extends DriverInterface interface DeployTargetInterface extends DriverInterface { /* ... */ } // 2. A typed registry final class DeployTargetRegistry extends TaggedDriverRegistry { public function __construct(ContainerInterface $drivers) { parent::__construct('deploy', $drivers); } public function target(string $key): DeployTargetInterface { return $this->get($key); } } // 3. A collecting pass final class CollectDeployTargetsPass extends CollectDriversCompilerPass { public function __construct() { parent::__construct('vortos.deploy.target', 'vortos.deploy.target_locator', 'deploy'); } } // 4. In the concern's DI extension: // - register the ServiceLocator (tag container.service_locator) + the registry // - registerForAutoconfiguration(DeployTargetInterface::class)->addTag('vortos.deploy.target') // - in Package::build(): CollectDriversCompilerPass::register($container, new CollectDeployTargetsPass()) // 5. A conformance TCK base for the port (extends ConformanceTestCase), and an // agnosticism test (extends AgnosticismLintTestCase).
A driver is then just: implement the port + #[AsDriver(key: '…')]. vortos:make:driver
writes that for you.
Tests
docker compose exec backend ./vendor/bin/phpunit --testsuite OpsKit