univeros / orion
Orion as a Univeros module: one line in config/modules.php gives the host Orion on its Cycle connection, the migration, the orion:* commands and the doctor checks
Requires
- php: >=8.3
- cycle/database: ^2.10
- cycle/migrations: ^4.2
- orion-search/cli: ^1.0
- orion-search/core: ^1.0
- orion-search/pdo: ^1.0
- orion-search/psr15: ^1.0
- univeros/configuration: ^2.6
- univeros/container: ^2.6
- univeros/module: ^2.6
- univeros/persistence: ^2.6
Requires (Dev)
- orion-search/elasticsearch: ^1.0
- orion-search/meilisearch: ^1.0
- orion-search/opensearch: ^1.0
- orion-search/polaris: ^1.0
- orion-search/solr: ^1.0
- orion-search/testing: ^1.0
- univeros/doctor: ^2.6
- univeros/messaging: ^2.6
- univeros/observability: ^2.6
Suggests
- laminas/laminas-diactoros: PSR-17 factories for the search: endpoints when the container binds none (the skeleton ships it)
- orion-search/elasticsearch: ORION_ENGINE=elasticsearch with ORION_ELASTICSEARCH_URL
- orion-search/meilisearch: ORION_ENGINE=meilisearch with ORION_MEILISEARCH_URL and ORION_MEILISEARCH_KEY
- orion-search/opensearch: ORION_ENGINE=opensearch with ORION_OPENSEARCH_URL
- orion-search/polaris: With univeros/polaris, each request's tenant is its token's active organization
- orion-search/solr: ORION_ENGINE=solr with ORION_SOLR_URL
- univeros/doctor: The seven Orion checks join bin/altair doctor, with --fix where it is safe
- univeros/mcp: The orion__* tools (compile_query, search, list_indices, describe_index, capabilities, plan; apply and reindex gated) join bin/altair mcp:serve
- univeros/messaging: The drain runs on the bus worker after each commit instead of in the request
- univeros/observability: Orion's counters and the outbox lag gauge go to the framework Meter
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-27 19:15:58 UTC
README
Orion as a Univeros module. One line in config/modules.php:
return [ new App\AppModule(), new Univeros\Orion\Module(), ];
What that line gives the application:
- Orion on the application's own Cycle connection. The outbox entry commits or rolls back with the entity in the same transaction, which is what makes reindex by replay lossless (docs/spec/revision.md ยง4).
- The migration.
bin/altair db:migrateinstalls Orion's tables. - The console. The
orion:*commands: plan, apply, reindex, prune, drift, status, flush, verify, capabilities, outbox:drain, outbox:retry and receipts:keygen. The application'sbin/altairadds them withforeach (Univeros\Orion\Console\Commands::all($container) as $command) { $application->add($command); }. - The doctor checks. Seven checks join
bin/altair doctor, with--fixwhere it is safe. - The bus. With
univeros/messagingconfigured, the drain runs on the bus worker after each commit. - The Meter. With
univeros/observability, Orion's counters and the outbox lag gauge go to the Meter. - The AI-readable manifest. Its section in
bin/altair manifest:generate.
Nothing is built until something resolves it, so db:migrate runs before Orion is configured.
Configuration
Anything the container binds wins over the environment.
| Setting | Binding | Environment |
|---|---|---|
| Storage | Orion\Storage\Storage |
the Cycle connection CycleOrmConfiguration configures (DB_*) |
| Engines | Orion\Registry\EngineRegistry, for several engines or a client you build yourself |
ORION_ENGINE=elasticsearch with ORION_ELASTICSEARCH_URL (comma-separated hosts; needs orion-search/elasticsearch), ORION_ENGINE=opensearch with ORION_OPENSEARCH_URL (a node; needs orion-search/opensearch), ORION_ENGINE=solr with ORION_SOLR_URL (a SolrCloud node; needs orion-search/solr), ORION_ENGINE=meilisearch with ORION_MEILISEARCH_URL and an optional ORION_MEILISEARCH_KEY (needs orion-search/meilisearch), or ORION_ENGINE=memory (development only) |
| Tenancy | Orion\Tenancy\Tenancy |
ORION_TENANCY=single; there is no default |
| Receipt signing | Orion\Receipt\ReceiptSigner |
ORION_RECEIPT_KID, ORION_RECEIPT_SECRET_KEY (base64), ORION_RECEIPT_PREVIOUS_KEYS (kid:base64,...) |
| Receipt verification | Orion\Receipt\ReceiptVerifier |
ORION_RECEIPT_PUBLIC_KEYS (kid:base64,...), so an auditor needs no secret |
| Manifests | ORION_MANIFESTS (comma-separated directories; api by default) |
|
| Endpoint prefix | ORION_ENDPOINT_PREFIX: where the search: endpoints are mounted |
|
| Clock, logger, events, cache | the PSR interfaces | |
| Doctor lag threshold | ORION_DOCTOR_MAX_LAG (1000 by default) |
bin/altair orion:receipts:keygen --kid=prod-1 prints a key pair for the secret store.
Indices
Declare each index in a search: manifest, api/<name>/search.yaml:
index: products engine: elasticsearch schema: title: { type: text, language: en } sku: { type: keyword, filterable: true } org_key: org_id # optional
bin/altair orion:apply products builds it, and orion:drift --check reports when the declaration and the index
part ways.
Doctor
| Check | Status when failing | --fix |
|---|---|---|
orion_engine_reachable |
error | none; reports the connection setting |
orion_registry_current |
warn | builds declared indices; clears what a crashed create left, only when it holds no documents |
orion_schema_drift |
error when breaking, warn when additive | applies the additive changes; breaking drift reports bin/altair orion:reindex <index> |
orion_alias_resolves |
error | none; the agent action resumes the reindex |
orion_outbox_lag |
warn above ORION_DOCTOR_MAX_LAG |
none; reports the drain command |
orion_dead_letters |
error when any | none; reports the retry command |
orion_receipts_verify |
error on a break, warn when receipts are off | none |
Writing to the outbox
Record into the outbox in the transaction that writes the entity, as its last statement. Dispatch once that transaction has committed:
$entry = $databases->database()->transaction(function ($database) use ($product, $outbox) { $database->insert('products')->values($product)->run(); return $outbox->index(IndexName::of('products'), $product['id'], ['title' => $product['title'], 'sku' => $product['sku']]); }); $dispatcher->dispatch(IndexName::of('products'));
Open the host's transactions through Cycle ($database->transaction(), begin(), the ORM's unit of work), not on
the raw PDO handle: Cycle counts the nesting, and Orion joins it as a savepoint.
examples/univeros is a running host built this way, with a walkthrough.
The module in context, with the PSR-15 endpoints it serves: the Frameworks guide.