All notable changes to Orion (the orion-search/* packages and univeros/orion) are documented in this
file. The format follows Keep a Changelog, and the project adheres
to Semantic Versioning. Every package shares one version line.
[Unreleased]
Nothing yet.
[1.0.0] - 2026-10-27
The first release: every work package of the specification (docs/spec/revision.md §7), proven by the engine
conformance, tenancy isolation, determinism, fault-injection and query-equivalence suites on Elasticsearch 9.5 and
8.19, SolrCloud 10 and 9.11, OpenSearch 3.9 and 2.19, and Meilisearch 1.56 and 1.13, and by the Univeros demo
walkthrough, in CI.
Added
-
WP0: the monorepo, root QA (
composer qa),bin/check-importswith tests, theorion-search/coreandorion-search/testingskeletons, the specification and the decisions log, CI. -
WP1: the query AST (
Query::on(), lexical clauses, filters, k-NN vector, fusion, sort, facets, highlight, pagination) with its declarative form;ResultSet,Hit,FusionTraceand deterministic reciprocal rank fusion in core; theIndexSchemaAST withorgKey()and per-enginenative()settings,LiveSchemaandMigrationPlanwith total, fail-closed change classification;Document,BulkResult,WriteOutcome; theSearchAdapter,SchemaAdapter,WriteAdaptercontracts,CapabilityandUnsupportedClause; theStorageport with Orion’s tables as data;InMemoryStorageandStorageConformanceTestCase(same-connection test included) inorion-search/testing;Orion::create(new Config(...))with explicit tenancy. -
WP2: adapter contracts with version-stamped deletes (
Tombstone),SchemaAdapter::drop()and a scopableSearchAdapter::raw();EngineConformanceTestCaseand the devInMemoryengine;orion-search/pdo, storage on the host’s PDO connection that joins the host’s transaction, with the table installer (SQLite, PostgreSQL, MySQL);orion-search/elasticsearchfor Elasticsearch 8.19 and 9.x;compose.yamland.env.testingfor the local service-backed suites. -
WP3: the schema lifecycle.
MigrationPlan::between()diffs the org key and per-engine native settings against the registered schema, failing closed;IndexSchema::hash(); the index registry (orion_indexper versioned physical index,orion_aliasper alias,IndexRegistration,IndexRegistry);SchemaAdapter::alias()with the Elasticsearch implementation and an alias conformance test;AliasManager,IndexNamer,Planner(plan, create version 1 behind the alias, additive apply in place,reindex_requiredfor breaking plans and refused additive ones),OperationandOutcome;Graph::registry(),aliases(),planner();LifecycleDeterminismTestCaseandFixedClockinorion-search/testing. -
WP4:
Storage::increment()andStorage\Counters, so outbox offsets per index are dense and committed in order; the compacted transactional outbox (Outbox::index(),delete(),Entry); the drain (Drain,DrainResult,Delivery) with dead letters and retry; theDispatcher(SyncDispatcherby default) andMetricsports; receipts (Receipt,ReceiptSigner,SodiumSigner,ReceiptStore,Verifier), issued by the planner and the reindexer; theReindexer(replay, leased lock inorion_reindex, resume,abort(),prunable(),prune());Graph::outbox(),drain(),deadLetters(),dispatcher(),metrics(),receipts(),verifier(),reindexer();orion-search/cliwithbin/orion;FaultyEngine,FaultyStorage,SettableClockandFaultInjectionTestCaseinorion-search/testing; the determinism harness covers reindex, prune and receipts;ReceiptVerifierandSodiumVerifier(verification from public keys),orion verify --headfor an anchored head;Storage\DuplicateKey;AliasingEngineinorion-search/testing. -
WP5:
univeros/orion, the Univeros module (Module,Bootstrap\OrionConfig,Database\CycleExecutor,CycleStorage,CycleInstaller, theInstallOrionmigration,Console\Commands, sevenDoctorchecks,Messaging\BusDispatcherandDrainIndexHandler,Observability\MeterMetrics,AgentSpec\ManifestSection);Orion\Pdo\SqlStorage,ExecutorandPdoExecutor;Orion\Manifest\Loader,Manifest,DeclarationandGraph::manifest();SchemaAdapter::resolve();ElasticsearchEngine::connect(); the harnesses’createStorage()hook;examples/univeroswithbin/setupandbin/walkthrough.sh, and a CI job running it. -
WP6: tenancy on a shared index.
Orion\Query\Searcher(Graph::searcher()), the one way Orion queries an engine, with the tenant’s filter injected beneath every query and the escape hatch;Tenancy\ScopeInjector,IndexScopeandNoTenant;TenantScope::qualify()andunqualify();TenancyIsolationTestCaseandSettableResolverinorion-search/testing, run on the in-memory engine and Elasticsearch 9 and 8.19;orion-search/polariswithActiveOrganization; inuniveros/orion, tenancy from univeros/polaris throughHttp\RequestToken, the boundSearcher, andORION_ENGINE=elasticsearchwithORION_ELASTICSEARCH_URL. -
WP9: the agent surface in core (
Orion\Tools: theToolcontract,Tools::for(),ListIndices,DescribeIndex,Capabilities,CompileQuery,Search,Plan, and the gatedApplyandReindex, which preview untilconfirm;QuerySchema, the JSON schema of a query’s declarative form);Searcher::compile()andQuery\Compilation(the requestsearch()would send, bound and scoped, with the two legs and the fusion when core fuses, never sent); theAdapter\QueryCompilingport, implemented by the in-memory, Elasticsearch and Solr engines and checked by the conformance case;ResultSet::toArray(),Hit::toArray(),FusionTrace::toArray(); inuniveros/orion, theorion__*MCP tools (Mcp\OrionTooland one adapter per core tool with its#[McpTool]attribute and schema file,Mcp\McpTools) registered on the framework’sToolRegistrysobin/altair mcp:serveoffers them, a confirmedapplyorreindexneeding--allow-writes; the tools in the agent manifest section;FaultyEngine::claim(); the demo walkthrough callingorion__compile_queryover stdio. -
WP8:
orion-search/psr15, thesearch:endpoints as a PSR-15 middleware (SearchMiddleware,SearchHandler,SearchRequest,Envelope, RFC 9457Problems, theAuthorizerport,RequestTenant); the manifest’ssearch(Manifest\Endpoint) andsource(Manifest\Source) keys and theProjection\Projectorcontract;Manifest::endpoints(); amatchover several weighted fields (Query::match()with a list or weights by field,Clause::$fields,weighted(),fieldNames();fieldsandboostin the declarative form);Query\Binder, the query bound to the registered schema (fields, flags, value types, query-time boosts) by the searcher; core reciprocal-rank fusion of hybrid queries in theSearcher(lexicalLeg(),vectorLeg(),withClauses(),Searcher::FUSION_WINDOW);Page::WINDOW,Page::depth()andUnsupportedClause::deepPaging();IndexSchema::fieldAt(); inuniveros/orion,Http\SearchEndpoints,Http\GateAuthorizer, one route per endpoint and the endpoints in the agent manifest section; inorion-search/testing, the endpoint harness and fixtures (Endpoint\EndpointFixtureTestCase,Harness,Fixture,fixtures/manifests,fixtures/endpoints) and the property-based query equivalence case (Equivalence\QueryEquivalenceTestCase,QueryGenerator,Seeded);FaultyEnginekills searches too; the demo serves its search from the manifest’s endpoint. Endpoint responses carryCache-Control: private, no-store; aqis at most 64 words and a sort names each field once. -
WP7:
orion-search/solr, the SolrCloud 9.x and 10.x adapter (SolrEngine,QueryCompiler,NativeScope,Schema,Configsetwith Orion’s configset);ORION_ENGINE=solrwithORION_SOLR_URLinuniveros/orion; Solr 10 and 9.11 incompose.yamland CI. -
WP10:
orion-search/opensearch, the engine adapter for OpenSearch 2.19 and 3.x onopensearch-project/opensearch-php(OpenSearchEngine,OpenSearchEngine::connect()over Guzzle,QueryCompiler,Mapping,BulkResponse): strict mappings, externally versioned writes and deletes, the neutral query compiled to the Query DSL, vectors asknn_vectorfields on the Lucene engine queried through the k-NN plugin’sknnquery with the filters applied while the neighbours are gathered, hybrid fusion left to core; a vector joining an index created without one isRequiresReindex. Inuniveros/orion,ORION_ENGINE=opensearchwithORION_OPENSEARCH_URL. The endpoint fixtures replay on both OpenSearch lines;compose.yaml,.env.testingand CI carry the two services. -
WP11:
orion-search/meilisearch, the engine adapter for Meilisearch 1.13 and later onmeilisearch/meilisearch-phpover Guzzle (MeilisearchEngine,MeilisearchEngine::connect(),QueryCompiler,Settings,Documents): the neutral schema as index settings plus a declaration document Orion keeps in the index, documents validated against it before they are sent (an undeclared field or a wrong type fails alone), every write a task thatBulkResult::await()waits for, versions kept by Orion (a stale write or delete found by reading the stored version, a delete leaving a tombstone every search excludes), aliases emulated by core, lexical clauses as filters on hex-encoded shadow attributes with the portable semantics (neverq, which stays for the escape hatch), vectors as user-provided embeddings ranked by cosine similarity, facets counted within the k nearest, highlights as the conformance case defines them, hybrid fusion left to core. Inuniveros/orion,ORION_ENGINE=meilisearchwithORION_MEILISEARCH_URLand an optionalORION_MEILISEARCH_KEY. The endpoint fixtures replay on both lines;compose.yaml,.env.testingand CI carry the two services.
Changed
- WP11:
QueryEquivalenceTestCaselets a fused query’s hit count differ from the oracle’s by the slack, because an approximate neighbour may coincide with a lexical match where the exact one did not; the slack is a protected constant of the leaf. - WP10: the Elasticsearch scoped escape hatch ANDs the scope into every
knnquery at any depth of the native body (theknnquery clause of 8.12 and later, inpost_filter,rescore, a highlight query or another entry’s filter), not only the top-level entries, each k-NN filter written as oneboolfilter; the body is taken as the JSON it would be sent as. Unscoped, whether a tenant’s document came back told where the foreign vectors lay. - WP9:
Orion\Psr15\Envelopecomposes the response overResultSet::toArray()(same bytes). The searcher’ssearch()andcompile()share the binding, window, scope and leg logic, so whatcompile_queryshows is whatsearch()sends. The engine conformance case requires every engine to be aQueryCompiling. The root’sautoload-devmapsAltair\Mcp\to verbatim copies of the univeros/mcp v2.6.0 contracts underpackages/univeros/tests/Stubs(univeros/mcp cannot be installed beside the Elasticsearch 9 client); a host with the real package never reads them. - WP8: no engine returns vector fields in a hit’s source (the in-memory engine strips them, Solr leaves them out of
its stored source, the Elasticsearch mapping excludes them from
_sourceso 8.19 answers as 9 does); Solr compiles the neutral_scoresort to itsscorefield; the conformance case pins both and amatchover several fields. The in-memory engine no longer applies the schema’s boost itself: the searcher’s binding does, for every engine. A query naming an undeclared field, a field not flagged for the use, a value of the wrong type, or a page past 10 000 results is refused by the searcher before any engine sees it. The isolation case gains hybrid queries. - WP7: the engine conformance case also requires that a deleted document is found by no query, that missing sort
values come last either way, that only a matching field is highlighted, that
matchon a keyword finds the whole value, that term filters read values as their fields declare, and thatexistsworks on an object. - WP6: under a tenant resolver,
Outbox::index()anddelete()record as the current tenant (qualified id, org key stamped) andPlanner::apply()andReindexer::reindex()refuse a schema whose org key does not fit the tenancy or that changes the org key of an index holding documents;Outbox,PlannerandReindexertake aScopeInjector. Elasticsearch’s scoped escape hatch refuses document references andwrapper, and native mappings cannot touch the org key.bin/check-importsmatches root namespaces only, reads every source underpackages/andexamples/, matchesusestatements in any case and sees group use. - WP5:
PdoStoragedelegates toSqlStorage; on MySQL,update()counts matches with a locking read, so a compare-and-set another transaction won no longer counts as a match. The CLI reads schemas and engines from thesearch:manifest when--schemais not given, and--schemaaccepts a manifest file;Cli\Contexttakes a lazy resolver andCli\Application::commands()lists the commands for a host’s console. - WP4:
orion_outboxis keyed on (index_name,outbox_offset) with one row per document;orion_receiptis keyed onsequenceand gainsengine;orion_counter,orion_dead_letterandorion_reindexare new.Plannerissues receipts, refuses an apply while a reindex is pending, and reports a query-time-only change asapplied.Lifecycle\Operationgainsreindexedanddropped.bin/check-importsconfines the Symfony console to the CLI and host packages. - WP3:
orion_indexandorion_aliascolumns (provisional until now) take their final shape;Change::$beforeand$aftermay hold a declared setting value as well as a field.