Repository navigation
v0.12.0
OmniGraph v0.12.0
Released 2026-10-05.
Upgrade actions
-
--allow-data-lossis removed fromomnigraph schema planand
omnigraph schema apply. In v0.11 a hard drop deleted the dropped table's
older data during apply; since table writes publish detached versions, no
drop reclaims anything at apply, and the flag only labelled drop steps as
hard. Older commits keep reading dropped data untilomnigraph cleanup
stops retaining them, and then it cannot be recovered. Theallow_data_loss
field ofPOST /graphs/{id}/schema/apply, themodeof drop steps in plan
and apply output, and the RustSchemaApplyOptionsandDropModetypes go
too.Scripts that pass the flag now fail with an unknown-argument error; remove
it. The server ignores anallow_data_lossfield still sent in a request
body. Rust callers useplan_schema,preview_schema_apply,
apply_schema_as(source, actor)and
apply_schema_as_with_catalog_check(source, actor, check)in place of the
*_with_optionsforms. To reclaim the space a drop frees, run
omnigraph cleanupwith a retention that excludes the commits before the
drop, for example--keep 1. See the
schema guide and
cleanup.
-
Clients that send
DateTimestrings with more than three fractional digits
must truncate them to milliseconds, unless the extra digits are zeros: such
a string used to load and match at its millisecond and now fails the
request. Python'sdatetime.isoformat()writes microseconds by default; use
isoformat(timespec="milliseconds"). A stored or cluster-declared query
whose source holds such adatetime(...)literal now fails type checking,
so its graph fails startup until the literal is trimmed. A Rust caller that
binds aDateTimeparameter throughParamMapmust bind a
Literal::DateTime(aLiteral::Listof them for[DateTime], or
Literal::Nullon a nullable parameter); aLiteral::Stringor any other
literal is refused, as forDate. A parameter named__nanograph_nowis
refused: the name is reserved for the clocknow()reads, and a value bound
under it used to replace that clock without any check. -
External Blob bases may no longer overlap OmniGraph storage. A base inside
or above a graph root or the cluster storage root let any authorized writer
copy another graph's tables, its own manifest, or the cluster ledger into a
Blob value readable throughGET /blob.cluster validate,planand
applynow refuse such a base, in either scope, with
external_blob_base_overlaps_storage_root. A server whose applied state
already carries an overlappingserver_safebase quarantines that graph and
serves its healthy siblings; startup fails with--require-all-graphs, and
withcluster_no_healthy_graphswhen every applied graph is quarantined.
Omnigraph::with_external_blob_policyrefuses a policy whose base overlaps
the handle's own graph root. A base is compared only with a storage root of
its own kind (s3://withs3://,file://with a local root); a
same-kind root spelled with an empty path component or a percent sign
cannot be compared and is refused with
external_blob_storage_root_uncomparableinstead. To upgrade, move the base to a sibling prefix
outside the storage root, runcluster apply, and restart. The new checks
do not rewrite values written earlier under an overlapping base.
Incremental writes copied those bytes into managed storage;
load --mode overwritekept the URI as an external reference, which still
reads through the process's storage credentials. If the base covered graph
or cluster data, audit both the copied managed values and the retained
external references that name a URI under it. -
Update HTTP integrations with the server: graph inventory now has one
graphslist with availability, and readiness uses ready/blocked counts instead of the removedquarantinedfields;served_graph_countcounts the whole registry. Graphs that fail startup remain visible to authorized callers with a sanitized failure category, and authorized requests to blocked graphs return 503 instead of 404. See deployment status; a 503 does not authorize replaying a write.
- A branch's
__manifestnow keeps a byte-bounded buffer of its latest
commits and releases older ones into immutable Lance files under
__history, so a publish no longer rewrites the branch's whole history.
Graph commit ids take the formhb1.<block>.<slot>.<nonce>. This is
storage format 14: a standalone format 8, 9 or 13 graph is converted by the
offlineomnigraph upgrade, and a graph at any other format is rebuilt by export,
initandload --mode overwrite, as described in the
upgrade guide. A session may lower the release
budget withset history_release_bytes = <bytes>;(1024..=262144, default
262144); the default is production's and cannot be raised.
-
F64values written from JSON before this release keep the value the older
parser gave them, which can differ in the last digits from the value the
same decimal names now. A JSON parameter with the same digits no longer
matches such a value in a filter,updateordelete, while a GQ literal
with those digits now does. To correct stored values, reload the affected
types from the original source withload --mode overwrite, or rewrite them
withupdate. Amergeload adds a second entity instead of updating the
old one when the type's@keyincludes anF64property, or when the type
has no@keyand the source rows carry noid, and reloading an export
taken before the upgrade writes the old value again. Upgrade the CLI
together with the server: the CLI parses--paramsitself, so an older CLI
still sends the old value. -
Rust callers:
OmniErrorhas a new variant,StoredExternalBlobDenied,
for an update that must carry a stored external Blob reference the graph's
policy refuses. An exhaustivematchonOmniErrormust add an arm for it. -
Schema apply and system-column upgrades now publish the schema source,
compiled schema and identity atomically with their table references in the
catalog. Ordinary open serves storage format 14 only and refuses every other
format. Convert a standalone format 8, 9 or 13 graph with the offline
omnigraph upgrade; rebuild a graph at any other format by export,initand
load --mode overwrite, as described in the
upgrade guide. Fresh graphs no
longer create separate root schema files.
- Cluster execution now uses ledger v2 only. Explicit
cluster upgrade-ledger --writers-stoppedpreserves existing data; legacyimport,refresh,approveand apply fallback are removed. Fresh apply creates graphs directly, andcluster apply --serverchanges schemas and stored queries or adds graphs without restarting the server. See cluster operations.
omnigraph upgradenow converts a standalone graph to storage format 14
in place, keeping its branches, commit ids and commit history; it no longer
only reports. It accepts two sources: formats 8 and 9, written by release
0.11.x, and format 13, written by main development builds from 2026-10-01
to 2026-10-04. The upgrade is offline: stop every process using the graph,
retain a verified backup of the whole root, run
omnigraph upgrade <graph> --check, thenomnigraph upgrade <graph>. An
interrupted run leaves the graph refused by normal open until the same
command is run again. Cluster-managed graphs and every other format are
still rebuilt by export,initandload --mode overwrite. Pre-upgrade
commits are kept under__history/legacy/: a 14 build older than this
change reports pre-upgrade commits as not found by id and refuses
full-history reads on an upgraded root, so upgrade every binary before the
graph. For a 0.11.x graph the schema is read from the three schema objects
at the graph root, which the upgrade leaves in place and which go stale at
the next schema apply; the objects must match the tables, so a stale copy
restored from an older backup is refused before any write; every
pre-upgrade commit is recorded under the one schema live at the upgrade; a
.stagingschema object or a recovery file left by 0.11.x is refused until
a 0.11.x read-write open (omnigraph snapshot) resolves it, while a
__schema_apply_lock__branch left alone by a killed 0.11.x schema apply is
handled by the upgrade and is not a live branch afterwards; and on a root
where 0.11.xcleanuporschema apply --allow-data-lossran, the first
cleanupafter the upgrade iscleanup --keep 1 --confirmalone. The JSON
report gains aworkobject, names its route per source
(history-lance-files-v8-to-v14,history-lance-files-v9-to-v14,
history-lance-files-v13-to-v14), nameshistory-lance-files-to-v14as
recovery.failed_handlerwhen it stops before any route is known (an
unreadable pending marker, or recovery files on a format no route
converts), and now reportscheck_passedandcompleted. See the
upgrade guide.
Features
-
Isolate read and write admission, retain request tracing across disconnect,
and keep proven pre-effect storage refusals nonfatal. Uncertain writes close
admission across the process and exit nonzero after other logical owners drain.
Remote discovery reports credential-safe transport causes; dedicated merge
commands canonicalize branch names before dispatch and receipt validation.
Malformed branch-create names return HTTP 400 and leave admission open. -
One v0.12 HTTP contract. Protected graph and registry requests require
Omnigraph-Http-Api: 0.12; missing or unsupported headers refuse before graph
access withapi_contract_mismatch. Upgrade CLI, server and HTTP integrations
together. The CLI probes public health before each request and validates
response headers before consuming JSON or streamed bodies, with redirects and
automatic retries disabled. A failed preflight sends no data request; an
incompatible response after dispatch leaves effects unknown. MCP and OAuth
retain their separate standard protocols. Server URLs must identify the service
root; replace graph-qualified URLs with the root and explicit graph selection. See
HTTP server guidance. -
Exact merge receipts. Branch merge HTTP responses, GQ merge statements and
CLI JSON now return the commit published by that merge, even when another
writer advances the target before delivery. Fast-forward returns its own
target commit; an already-up-to-date merge returnscommit: null. Optional
source deletion failure preserves the successful receipt and exit 0, with
branch_deleted: falseand structuredbranch_delete_error_detailsreplacing
the old string field. Embedded callers receiveMergeResultinstead of only
MergeOutcome. See merge outcomes. -
Typed edge selection (GQ 2.1).
(knows | likes)selects compatible named
edge types and*selects compatible types from the captured schema. Every
omitted bound means exactly{1,1}; explicit finite ranges combine shortest
distances across types. Bound edges retain concrete type and identity through
$e.@typeand$e.@id. Common properties must have compatible types on every
member. A shared finitetraversal_work_limitfails explicitly on exhaustion.
Historical wildcard targets and internal forced CSR selection are refused. Explain 4
records complete member lists, directions, version maps and typed sort ties;
serialized physical plans use a versioned envelope. Regenerate older saved
plans, including those without traversal nodes. Scan admission charges full
selected-table sizes, even for small neighborhoods. See
traversal and match patterns. -
Admitted writes outlive their HTTP callers. The server owns mutations,
loads and branch operations through completion, including optional merge-source
deletion. Disconnect no longer frees their admission capacity. Aggregate input
and operation limits bound retained server work; shutdown closes admission and
waits for owned writes, read bodies and server producers against one deadline.
An owner panic or indeterminate completion closes admission and stops the
process. This does not qualify every native storage-I/O lifetime. See
deployment. -
Whole-command write outcomes. CLI data-write errors include
command_outcomewith execution/effect evidence and a supported action,
preserving structured errors andRetry-After. Exit 75 permits a bounded caller
retry only for a verified typed admission refusal before the whole command's
effects. Conflicts and uncertain delivery remain exit 1; verified HTTP
conditional refusals retain exit 4. Embedded mismatches remain exit 1 because
writable open may complete earlier work. The CLI never automatically repeats a write. See
failure outcomes. -
cleanupis a tracing collector over table storage, and--keep
counts graph commits.--keep Nretains the newestNgraph commits on
every live branch and the table versions they pin;--older-thanalso
retains commits newer than its cutoff. Branch creation bases, selected
merge bases and exact snapshots protected by native tags are retained too.
Other published versions are collected; unpublished staging is collected only
after its recorded publication authority is provably gone. Missing or
malformed ownership evidence is retained. Native tree candidates are frozen
before graph capture; complete branch, head and tag views are revalidated
after all table inventories. A change refuses the plan before deletion.
Retirement archives exact branch-ref contents before unlinking active refs;
cleanup reclaims unneeded trees and archives after checking dependencies.
Branch creation does not scan accumulated retirement history. Accepted
merge inputs remain protected through cancellation or an in-doubt
publication until their target publication authority is provably gone.
Ordinary writes and merges may overlap cleanup; branch creation/deletion
and cleanup keep the existing single-writer-process control boundary. Dropped table lifetimes,
base and overlay data files, borrowed origins and Blob sidecars participate
in retention. Orphan objects and temporary manifests require their own
seven-day age. Nothing defers a table: thedeferredresult field is removed,
and each--jsonrow carriesmanifests_removed,unpublished_manifests,
unpublished_bytesandforeign_versions(old_versions_removedstays
for one release with the value ofmanifests_removed). Exit 0 means every
table was visited and no retained table version or file was removed; a table
whose trace did not finish is left whole and its row says why. -
Lost acknowledgements resolve against the attempted graph commit even when
another writer advances HEAD before readback. A publish that failed before
any object-store request, with the attempted__manifestversion absent and
the branch head below it, returns the original error. A storage error stays
in doubt, because its request may still land. The in-doubt error names the
graph_commit_id. -
omnigraph repairreports foreign drift instead of healing drift. A
graph table's Lance linear history ends at its creation version, recorded
on the registration asomnigraph.last_linear_version. Linear commits
above it came from outside OmniGraph and are resolved by no read or write;
repairclassifies such a tableforeign_drift, prints the last linear
version and the HEAD, takes no action and exits 0.--confirmand
--force --confirmpublish nothing on a v14 graph, andcleanupnever
deletes a foreign version. -
Inserting into a node type with a camelCase
@uniqueproperty no longer
fails withNo field named <lowercased name>: the committed-uniqueness probe
now reads the column by its exact name, formutate … insertand for
append/mergeloads. Fixes #765. -
A property named like a SQL prefix keyword (
interval,exists,trim)
can now be the column of adelete/update … wherepredicate and of an
indexedVector: the predicate and the index-training filter reach the
storage layer with the column quoted. -
Correlated blocks in
match:exists { … },count { … } > 2,
sum($d.size) { … } > 100(alsomin,max,avg). A block holds a
pattern matched once per outer row and keeps the row by a comparison on the
aggregate of its matches;not { … }is thecount = 0case of the same
operator. The rows are narrowed beforeorderandlimit, so a paged
listing filtered by a relationship count is exact. A binding declared inside
a block (not { $p worksAt $c; $c: Company { name: "Acme" } }) is now
reached through the block's traversal from the outer row instead of scanned
as a whole table and cross-joined with the outer rows. A single-hop,
filter-freecount { … }over a directed, unbound
edge is answered from the graph index's degree without reading the edge's
rows. The barecount($d) > 2inmatchstays refused (T7). Fixes #764
and the correlated-product half of #763. -
Writes no longer use recovery sidecars, and the recovery machinery is
gone (RFC 0067). Every writer stages its table effects as detached Lance
commits that nothing references until the one manifest commit publishes
them, so an interrupted write leaves the graph unchanged and there is
nothing to recover. The__recoverysidecars, the reopen-time recovery
sweep with its roll-forward and rollback, the write-entry recovery barrier
and the recovery audit table no longer exist. The failure classes they
caused go with them: a stale or mis-named sidecar can no longer make every
read-write open fail, a directoptimizebeside a live server no longer
leaves a barrier that refuses writes until a reopen, and a sidecar read can
no longer race its own cleanup into a transient error. Fixes #602; removes
the cause of #554, #601 and #330. -
Optional OIDC identity and MCP access.
omnigraph-server --oidc-identity-trust FILEverifies resource-bound human identities locally
against public keys and explicit principal admissions. Applied Cedar retains
all graph permissions. Public admission updates have bounded expiry and do
not restart serving. The option exposes standard protected-resource metadata
and MCP tools for minimal discovery and stored reads, using the maintained
protocol SDK and the existing authorization handlers. Direct deployments
without the option retain their existing routes. See
OIDC resource identities and MCP. -
Managed login uses public AuthKit authentication and cached credentials. Repeated
login --api
calls return fresh identity metadata without another browser flow while the
session remains usable. The WorkOS SDK obtains and rotates credentials directly,
without an embedded client secret or an API device/refresh broker, for
up to eight hours from sign-in; each access credential remains short-lived.
Metadata validation tolerates a bounded clock difference without extending
either expiry timestamp.
Credentials stay in the OS keychain, uncertain exchanges are never replayed,
and logout clears local custody while separately reporting provider revocation.
This profile requires an API supporting direct AuthKit access; old opaque
sessions must sign in again. That service issues version-2 identity credentials
only; retained--actionssyntax remains an explicit restricted request for
older compatible issuers and is never silently widened. Named-server login
is unchanged. See the
managed CLI reference. -
Managed bulk loading uses the native CLI. Implicit
load --graph
reuses the selected folder's data credential; applied policy supplies
change and branch-creation permissions. Loads use the existing NDJSON
endpoint with 32 MiB input, 8 MiB response and a 300-second deadline.
Redirects and automatic retries are disabled; a lost response requires
branch reconciliation. Explicit targets, positional storage URIs, profiles
and--directretain ordinary loading. See
managed bulk loading. -
Managed commit reads inspect native lineage.
commit listand
commit showuse the selected folder's identity and appliedread
permission to inspect graph history after a lost write response. They share
the 30-second request deadline and never replay the uncertain write. -
Managed queries and mutations allow 30 seconds to finish. The total
deadline includes connecting and reading the complete response, with at most
10 seconds to connect. Redirects and automatic retries remain disabled, and
responses remain limited to 8 MiB. A timed-out mutation may have committed;
inspect the graph before resubmitting it. See
managed data access. -
CLI graph errors retain their structured JSON. With
--jsonor a read
command's--format json, server refusals preserve their error code and
typed details on stdout. Policy denials and resource limits exit 1;
qualified HTTP conditional mutation mismatches retain exit 4. Data-write
diagnostics additionally report the whole-command outcome described above. -
Identity credentials leave permissions in applied policy. Graph commands
acquire and cache a cluster-bound identity credential with no graph or
action grants. Applied Cedar policy decides protected graph and schema
operations. Minimal graph discovery shows every effective graph ID/name
without exposing schema, roots or other operational metadata. Explicit
server addressing selects it withgraphs list --discovery. The older
restricted credential remains available only through explicit--actions
requests. See managed data access. -
Session settings. A
.gqfile may open withset <name> = <value>;
andreset <name>;lines, andshow <name>;orshow all;is a read
statement returningname,value,default,sourceandscope. A
value applies to that invocation or request and is never persisted. Seven
settings:engine,rrf_plan,merge_lineage,ann_nprobes,
stage_write_concurrency,traversal_work_limitandhistory_release_bytes.
history_release_bytesis the byte budget of a branch's buffer of unreleased
commits: a mutate, load or branch merge of the session that reaches it closes
a history block, and the next publish writes the block under__history;
production keeps the default, and a caller may only lower it. The CLI verbsquery,mutate,branch merge,
commit changes,changes poll,loadandingesttake a repeatable
--set NAME=VALUE;
POST /query,POST /mutate,POST /mutate/if-graph-commitand
POST /branches/mergetake an optionalsettingsobject;GET /changes
andGET /commits/{id}/changestake a repeatableset=<name>=<value>
parameter.omnigraph lintreports asetline's error with its position.
See Session settings. -
Every read query runs from a plan (engine v2). Engine v2 runs the plan as
one DataFusion physical plan under omnigraph's own memory pool:
DataFusion's operators for filter, join, projection, sort, limit and
aggregate, omnigraph's own for the scan, the traversal, the negation and
the rank fusion. Lance scans and graph hydration execute under the same
query context, and shared Arrow buffers are charged once across custom
operators. DataFusion operators that support spilling can use bounded
scratch space; custom graph breakers return a typed resource-limit error
when their memory reservation is refused. These query budgets do not bound
shared caches or total process memory. Ordinary v2 reads build only the
executable plan; explain JSON and structural hashes are generated only by
explicit explain requests.explaindescribes the v2 plan the query runs
and lists its lowered tree asdatafusion
rows when it can be built without running the query. The
planner answers an unfiltered bare-variable count from the pinned dataset's
live-row metadata throughaggregate_pushdown, without scanning IDs or
properties. Counts with filters, grouping, traversal or search retain their
scans. The columns each remaining scan reads are decided before execution:
the identity and key for a bare-variable count, the projected node
object's members forreturn { $var }, and each property an expression
names; aVectororBlobcolumn is read only when an expression names
it, so a plaincount($s)over a type with aVectorcolumn no longer
materializes every embedding (#704: 5.27 GiB peak RSS over 213,109 rows of
Vector(3072)on the executor). Engine v2 is the engine for every read at
every door: the embeddedSession, the CLI, the server, stored queries
and DST. The session settingenginekeeps the one valuev2, its
default, soset engine = v2;is accepted and changes nothing. Engine v1,
the executor, is retired from production: it is kept, frozen and
hash-pinned, as the test referenceomnigraph-reference-engine, a crate
that is never published and that only the GQ logic tests depend on, where
a query step's--- expect same as v1compares v2's rows with v1's.
Traversal destination
hydration also uses the planned projection before copying rows per edge,
avoiding unused text and vector payloads in grouped counts (#703). GQT
expect plansections assert destination columns on the dependent scan. -
Engine v2 decides the traversal mode in the planner. The cost model
that picks per-hop BTREE scans or the in-memory CSR for an unbound
traversal moves from the executor intoomnigraph-planner, decided at plan
time from a row-count estimate (the pinned snapshot's entity counts,
key-equality selectivity and the edge type's average fanout) and recorded on the
physicalExpandasmodeandfrontier_estimate;explainprints both
and lists the passexpand_mode, and a GQ logic test asserts the mode with
expand $s Knows $d: mode indexed_scan. The engine keeps two runtime
corrections and can reconsider a CSR choice for a smaller observed first
frontier. A degraded BTREE (or a CSR an earlier operator already built)
re-runs the model before an indexed start, and the mid-flight switch on the
observed frontier stays. -
Engine v2 reads a traversal's destination table once when the frontier
is dense. The planner chooses an access path for every traversal
destination: aHashJoinnode reads the destination table once, with its
pushed filters and projection, as the build side the traversal probes in
its own order; a dependent scan (accessid_lookup) keeps the per-batch
id IN (...)read in slices of at most 256 input rows. The rule checks the
table's row count against eight times the frontier estimate and requires
its estimated projected bytes to fit one quarter of the query pool;
explainprints theHashJoinnode with itsfallbackand lists the pass
access_path, and a GQ logic test asserts it withhash join $d. -
ann_nprobesis arequestsetting. A query sets it with
set ann_nprobes = N;or thesettingsfield ofPOST /query, andshow
prints its scope asrequest. -
The
profilerow carriesattempts[].drained. It istrueorfalse
for an omnigraph operator (its stream reached its end or not) andnull
for a DataFusion operator, whose end no counter observes. An attempt's
actual_rowsis complete only whendrainedistrue. -
Engine v2 streams a single-hop traversal. An unbound one-hop
Expand
no longer retains its frontier or its pairs: per input batch it walks the
CSR adjacency into dense arrays and emits slices of at most 256 pairs,
copying only those destination ids from the CSR dictionary; a
bound-edgeExpandruns its pair producer and sort per input batch.
Rows, multiplicity and order are unchanged;explainprints
streaming=trueon the operator. TheHashJoinnode's operator charges
its build and its output batches to the query pool (hash join output).
Multi-hop traversal keeps the BFS breaker. -
Engine v2 emits a multi-hop traversal's pairs a chunk at a time. An
unboundknows{1,2}-styleExpandstill drains its frontier before it
emits, but its pairs now leave in batches of at most 256, each charged to
the query pool on its own and released once hydrated, so the walk no longer
retains every pair and a trailinglimitends it once the limit has its
rows; a second multi-hopExpandbetween the two still drains the first
one whole before it walks. A 1,024 → hub → 1,024 fixture (1,050,624 one-or-two-hop pairs) under
limit 1refused withresource limit exceeded for query_memory_bytesand
now answers. Rows, multiplicity and order are unchanged;explainstill
printsstreaming=falseon the operator, and itsexpand_pairscounter
reports the pairs the walk handed on. Fixes
issue 782. -
Engine v2 bounds bound-edge traversal before grouped aggregation.
Matched edges pass through bounded producer queues, native spillable ordering
and incremental destination hydration. Counts and sums can consume expanded
rows without retaining the complete expansion. Source-row and edge-identity
ordering, parallel edges, destination filters and null amounts are preserved.
The source frontier is still retained and charged to the query pool;
unbound traversal keeps its existing execution path. Fixes the bound-edge
materialization in issue 723. -
One expression language for filters, mutation
where,return,order
and assignments (RFC 2026-09-24, shared expression model). Conditions
compose withand,or,not, parentheses andis null/is not null,
with three-valued null rules: a comparison with a null operand is null, a
filter or a mutationwherekeeps the rows whose expression is true. A
mutationwheretakes the same expression over the target's properties,
@id,@src,@dst, parameters andnow(), sodelete Knows where @src = "a" and @dst = "b"removes exactly the intersected edges (issue 660) and
$p.name = "Alice" or $p.name = "Bob"selects either (issue 651). A
comparison or Boolean expression inreturnis aBoolcolumn under its
alias. Anorderkey that is not a property or a system field binds to a
return alias or to an expression written inreturn, soorder { count($d) desc }sorts by the returned count (issue 566 part 3); a key that matches
nothing is refused withT42. Assignment values and inline binding matches
are constants over literals, parameters andnow(), evaluated once per
invocation.GQ_LANGUAGE_VERSIONis 2.1.LOGICAL_PLAN_VERSIONremains 2;
logicalFilterstoresconjuncts. Explain version 3 added
physicalContainsJoinandCrossJoinfilters;EXPLAIN_VERSIONis now 4.
PhysicalExpandrecords all selected members and versions, andSortand
RankFuserecord typed ties. Adding or removing a node kind bumps the explain
version.
- Synthetic benchmark cases can prepare deterministic reversible updates before branching, adding bounded real write history while preserving current graph contents. The versioned YAML recipe retains exact history assertions, fixture verification, and identical-state reset; see the benchmark guide.
- Blob delivery failures log their error class. A
GETorHEAD /blob500
logserror_kind="blob_pre_header_internal"with astage:targetis a
failure resolving a snapshot target for a policy-gated request,cellis
any failure of the engine's Blob read, including the branch or snapshot
resolution it does itself, andtransportis the server's own refusal
before headers. Thetargetandcellstages logerror_variant(for
exampleStorageorBlobIntegrity),storage_kindandmanifest_kind;
transportlogserror_variant="unclassified". A managed Blob body that
fails mid-stream logs the byte range with one of three kinds:
blob_payload_readwith the same error class,blob_payload_short_read
with the returned and expected byte counts, orblob_payload_permit_closed.
The error's message stays out of both the response and the log, because it
can hold object URIs or credentials; the response is unchanged. See
HTTP errors.
- Existing clusters can explicitly convert their ledger under stopped writers to durable offline schema and stored-query deployments without resetting graph data or history. Deployments capture immutable input, retain an original identity for root-only status and reconciliation, and allow corrective successors after a terminal partial result. Converted clusters share exclusive admission across servers and supported CLI operations, retaining their lock even on success until operator-established quiescence and exact-ID unlock; see the deployment workflow and limits.
- GQ has a list-membership predicate:
$x.number in $numbers, or
$x.number in ["A-1", "A-2"]with a literal list. On a matched binding's
property it filters that binding's scan.inis a reserved word as a
result. See the query guide.
- Servers retain per-graph request ownership while pausing and resuming the same serving bindings, including disconnected operations and streamed responses. Authorized graph inventory reports
transitioningandwait_for_transition; schema and stored-query deployments still require restart. See deployment status.
- Embedded callers can prepare schema changes before execution, receive the
operation's exact commit receipt, and reconcile retained publication evidence
without applying again. An unknown reconciliation outcome does not authorize
retry; see the graph write protocol.
- Server health, readiness and authorized graph inventory are available while graphs load. Ready graphs can serve immediately;
--require-all-graphskeeps graph admission closed until the entire startup succeeds. Readiness addsloading_graph_count, and inventory addsloadingwith actionwait_for_startup; startup attempts remain owned through shutdown. See deployment.
Behavior changes
-
Named traversal correctness. Disconnected traversal patterns without an
executable endpoint now fail type checking instead of disappearing from the
query. Named reverse traversals innot,existsand block aggregates now
use their checked direction and lexical expression scope. Later outer
declarations no longer capture earlier block-local variables. Missing edge
datasets on historical targets fail consistently when named, bound or
explicit alternative traversals open them. -
Writers stop serializing on the process-local schema gate (RFC
2026-09-18, shared schema gate). Mutations, loads, merges, index
maintenance, optimize, cleanup, repair, branch delete, the system-column
upgrade's--checkand read-view captures take a shared permit on the
schema gate; schema apply, the system-column upgrade, open, refresh,
reload,sync_branchand branch create take it exclusive. A
writer on one branch no longer waits for a writer on another, and a read
no longer waits behind another handle's publish hold (a read and a write
on the same handle and branch still wait for each other while it
publishes); same-branch writers still serialize on the branch gate, and
a schema apply still excludes every writer for its whole pass. No flag;
per-operation object-store request counts on the success path are
unchanged. -
A branch merge finds what changed without requiring either side to
descend from the merge base's table version. Withmerge_lineageat
on(the release default), candidate discovery compares the table
versions the merge base, the source and the target pin by file identity.
A merge whose side does not descend from the base's, such as merging
main back into a branch after a three-way merge, now discovers
candidates from version metadata where it fell back to the full
three-way scan. A merge that succeeds produces the same result in every
mode. -
A write on a branch other than the handle's bound one reads that
branch's__manifestonce less. Before its table effects the write
compares the branch's latest version and identifier with the ones it
captured; when both are unchanged it keeps the captured state and skips
the reopen and scan. A branch that moved, or was deleted and recreated,
is read again and the write is prepared again or refused as before. No
flag; writes on the bound branch are unchanged. -
Every table write is a detached Lance commit, published once and never
promoted (RFC 0067 and RFC "Detached-only tables"). Each table effect
is a detached commit of the pinned base, published as the pin
(published_dataset_version, staged_version, transaction_uuid)in the one
__manifestCAS, and that detached commit is the table's version for its
whole life: readers openstaged_version, nothing replays it onto the
table's linear history, and a graph table's linear HEAD stays at itsv1
create.published_dataset_versionkeeps its number,base + 1, as the
table's logical version and names no Lance version. Handle and topology
cache identities include the detached pin, including on backends without
e-tags. Persisted topology format v3 carries that pin; older artifacts are
cache misses and rebuild. A mutation or load
that fails before publication leaves the graph unchanged with nothing to
recover, and one that publishes is complete at the CAS; a
RecoveryRequiredfrom a mutation or load, the__recoverysidecar it
used to write, and the reopen-time recovery of that sidecar are gone. If
the manifest CAS lands durably but its
acknowledgement is lost, the write is already graph-visible, so the publisher
reads the commit back and returns success rather than an opaque failure a
non-idempotent retry could double-apply; only an outcome it genuinely cannot
confirm surfaces as an in-doubt error. A failed operation never wedges its
own live handle: once the fault source stops, the same handle's next write
succeeds without reopening. Writes to registered tables do not read their
linear HEAD, so a foreign linear commit does not block those writes;
omnigraph repairreports it asforeign_drift. Schema apply checks an
unregistered added-type path before reuse and requires its original empty
version 1.ensure_indicesand the explicit full-text rebuild
follow: each index batch is a detached commit published as a pin, a
rebuild interrupted after publication keeps serving search through the
staged batch, and noEnsureIndicessidecar is written. Branch merge
follows: a merge into a named branch chains every chunk of a merged table
as detached commits and publishes one pin per table, a merge onto main is
a pointer switch in which main's registration takes the source's pin with
no row copy, and a merge that fails before publication leaves the target
untouched; noBranchMergesidecar orRecoveryRequiredoutcome remains.
No per-table fork is created for a graph branch: a branch write stages
detached on the dataset its inherited registration names, two branches
writing one table produce two detached chains in one dataset, and forks
created by earlier builds stay valid and are reclaimed bycleanuponce
unreferenced. Schema apply follows: existing-table rewrites are detached
commits published as pins. An added type's original emptyv1Create can
be reused after a failed attempt; a different qualified original schema
uses a detached replacement, preserving the path and its versions. The
table delta and replacement schema-contract row publish together. No
SchemaApplysidecar or root-contract installation remains. A failure
before publication leaves the previous graph visible; a post-publication
error identifies a complete published outcome, including its contract.
Optimize follows: each table's compaction is one detached rewrite
of its pin, a scalar or vector index whose coverage lags is rebuilt whole
as a detached commit chained on it, the batch publishes once with an exact
check on every pin it planned from, and noOptimizesidecar is written;
a run that loses a table to a concurrent write fails with a read-set
conflict and the next run re-plans. Optimize no longer strips a stale
lance.auto_cleanupconfiguration from data tables: every engine commit
skips Lance's auto-cleanup, so the key is inert. Every detached commit
records the branch incarnation and graph head it was staged against in its
transaction properties (omnigraph.staged_against_branch_incarnation,
omnigraph.staged_against_graph_head), and a delete records the ids it
removed (omnigraph.deleted_ids, oromnigraph.deleted_ids_pathabove
64 KiB); the collector and the change feed read them. The optimization
admits at most 16 MiB of encoded JSON; larger records take exact discovery
without downloading an oversized spill. An external Lance
reader pointed at a table directory opens the table's last linear version
and sees stale rows;omnigraph exportis the supported external route.
Test seamsmutation.post_arm_pre_effect,mutation.post_sidecar_pre_fork,
mutation.sidecar_confirm_put,mutation.sidecar_post_publish_delete,
ensure_indices.post_sidecar_pre_fork,
ensure_indices.post_effects_pre_confirm,branch_merge.post_sidecar_pre_fork,
branch_merge.post_effects_pre_confirm,branch_merge.pre_error_recovery
andoptimize.post_compact_pre_reindexare gone, and so are the promotion
and fork windows that never shipped in a release
(mutation.post_publish_pre_promotion,
ensure_indices.post_publish_pre_promotion,
branch_merge.post_publish_pre_promotion,
schema_apply.post_publish_pre_promotion,
optimize.post_publish_pre_promotion,promotion.pre_replay,
fork.post_create_pre_open,cleanup.pre_reap,cleanup.reap_delete);
mutation.post_table_commit,publish.pre_merge,
branch_merge.post_table_effect,optimize.post_table_effect,
cleanup.collector_post_snapshot,cleanup.sweep_pre_manifest_delete,
cleanup.sweep_between_manifestsandcleanup.sweep_pre_file_deletename
the windows that exist, and the GQT cases that armed the retired seams are
retired or repointed with them. -
Graphs that still carry a recovery sidecar. No 0.12 build reads or
writes one. A read-write open refuses a graph
whose__recovery/directory holds a sidecar, naming it, until the 0.11.x
binary that wrote it has opened the graph read-write and finished its own
recovery; a read-only open is unaffected. Refresh validates the published
schema-contract row and updates the in-memory view; it does not install
root schema files. A schema or system-column operation can still report
RecoveryRequiredafter a proven publication, but the named commit already
contains its tables and contract.
Test seamsrecovery.*,optimize.post_recovery_check_pre_main_gate,
cleanup.post_recovery_check_pre_gatesand
system_column_upgrade.after_lock_reclaimare gone, and
branch_control.post_recovery_barrieris nowbranch_control.pre_gates.
The GQ logic test format loses its--- known_failuresection, which
existed only to pin known recovery-sidecar defects. -
Storage format v14; a standalone v8, v9 or v13 graph is converted in
place, any other format is rebuilt. Normal open serves v14 only and never
migrates. Fresh graphs of either system-column vintage start at v14. A
standalone graph at v8 or v9 (release 0.11.x) or at v13 is converted in
place by the offlineomnigraph upgrade, which keeps its branches, commit
ids, commit history and table data. A graph at any other format, and every
cluster-managed graph below v14, is refused, and the path is
omnigraph exportwith the binary of the release that wrote the graph,
thenomnigraph initandomnigraph load --mode overwrite. That rebuild
preserves data, vectors and blobs; commit history and branches are not
kept.omnigraph upgrade <graph>converts a standalone v8, v9 or v13 graph to
v14 offline: stop every process using the graph, retain a verified backup
of the whole root, runomnigraph upgrade <graph> --check, which writes
nothing, thenomnigraph upgrade <graph>. It reportscheck_passedor
completedfor a convertible graph,already_currentfor a v14 graph, the
findingunsupported_sourcefor any other format below 14,
newer_than_binaryabove 14, andunsupported_targetfor a--to-format
other than 14. A graph carrying a pending conversion marker reports
recovery_requiredand stays refused by ordinary open; run the same
command again with the executable that started the conversion.
Cluster-managed roots are refused. See the
upgrade guide.omnigraph schema upgrade-system-columnsseparately respells a served
graph without changing v14. Its detached table renames and replacement
contract publish together. An interruption before publication leaves the
graph unchanged; afterward both contract and table references are complete.
Test seamschema_apply.post_sidecar_pre_effectis now
schema_apply.post_lock_pre_effect, and
system_column_upgrade.after_stamp_advanceis gone. The
V5 ↔ V9 Format FenceCI job is nowV5 ↔ V10 Format Fence.
Historical queries retain current-contract semantics; older contract rows
do not add a schema history API. Export with this binary followed byinit
andloadwith an older one is a separate logical rebuild; it loses history
and branches. -
A published commit id is
hb1.<block>.<slot>.<nonce>. Every commit
after genesis is published under an id of that shape, so everycommit,
snapshotandchangesoutput and every/commits/{id}path carries it;
the genesis commit keeps its ULID, and on a graph converted by
omnigraph upgradethe commits from before the conversion keep the ids
they had. A prepared schema apply's commit is addressed by its published
id: the intent id no longer resolves through
GET /commits/{id}, andRecoveryRequired.operation_idnames the
published id. -
Engine v1 is retired from production; engine v2 answers every read.
The session settingenginehas the one valuev2, its default:
set engine = v1;,--set engine=v1,{"settings": {"engine": "v1"}}and
OMNIGRAPH_ENGINE=v1are refused as an unknown value
(unknown value `v1` for setting `engine`; expected one of v2), and the
process default refuses startup like any other invalid setting value.
Engine v1 survives only as the frozen test referenceomnigraph-reference-engine, which no production
door reaches. Three answers differ from v0.11.0, whose reads all ran on
engine v1:- A
bm25()ornearest()order on a traversal destination, a shape
engine v1 answered, is refused with a bad-request user error that names
the function and the binding, says engine v2 does not support it, and
suggests ordering on the traversal's source binding or matching the
destination withsearch(). - BM25 ties order by identity under a limit. Full-text candidates stay
uncapped until the final score and identity ordering, so an equal-score
entity outside an initial search window can still win, where engine v1
cut the candidates first. - Several text predicates (
search,fuzzy,match_text) on one scan
are conjoined, where engine v1 applied only the last one.
- A
-
Engine v2 no longer materializes the product of two unconnected bindings
under acontainsfilter (issue 775). A$p.text contains $m.number
between bindings no traversal connects runs on their join, below any
traversal, and, when the text binding's scan sits on the join's right
(written second, or moved there when both row counts are known and the
needles are no more than the texts), as a
ContainsJointhat sieves the text scan through one Aho-Corasick automaton
over the needles and pairs each text only with the needles it holds. Rows
are unchanged; a query that refused under the memory cap now answers.
explainshows theContainsJoinnode, the conjuncts aCrossJointests
(CrossJoin.filters) and the scan'sruntime_filter. -
Search predicates:
search,match_text, andfuzzyfilters accept
a standalone call or= true. Other comparisons now fail type checking
(T38) instead of being silently interpreted as a positive search. A
text filter on a BM25-ranked binding is preserved by applying its
membership set without changing the BM25 scoring query. -
A refused query carries its diagnostic. Every parse or type refusal
now reports a stable code (Q…,T…), where the failure is (line and
column, or the compiler stage), what was expected, and one fix. The HTTP
400body gains an additivediagnosticobject; the CLI prints the four
fields on stderr for human formats and the API's error body for--json
and--format jsonl, with no colour codes and no backtrace footer.
queries validate --jsonandcluster plan --jsoncarry the same detail
beside their messages. A declaration without its parameter list,query name {, is refused at the name's end with the fixquery name()where it
was reported at the file's first position; the one-lineerrortext of a
parse refusal no longer embeds the parser's multi-line rendering. See
Diagnostics. -
Setting environment variables are process defaults, and an invalid value
refuses startup.OMNIGRAPH_RRF_PLAN,
OMNIGRAPH_MERGE_LINEAGE,OMNIGRAPH_ANN_NPROBESand
OMNIGRAPH_LOAD_CONCURRENCYkeep their names and meaning as the default
of the setting each one seeds; they are read once, by the server at startup
and by the CLI per run, no longer by the engine. A value outside the
setting's row refuses the server start or the CLI invocation with the
setting's message instead of warning and running a default:
OMNIGRAPH_LOAD_CONCURRENCYmust be in1..=64(0and128are
refused),OMNIGRAPH_MERGE_LINEAGEmust beoff,onorverify.
OMNIGRAPH_TRAVERSAL_MODEis no longer read: the expand path is no session
setting, so nothing seeds it from the environment. Aprocesssetting
(rrf_plan,stage_write_concurrency) is refused in a request's
settingsfield,setparameter or text; a request may set
merge_lineage,ann_nprobes,traversal_work_limitand
history_release_bytes.traversal_work_limitdefaults
fromOMNIGRAPH_TRAVERSAL_WORK_LIMIT. The GQ logic-test runner refuses to run
while a settings-default variable or retiredOMNIGRAPH_TRAVERSAL_MODEis set. -
Malformed request JSON answers the API's own error body. A body that
does not parse onPOST /query,POST /mutate,
POST /mutate/if-graph-commit,POST /branches/mergeorPOST /changeis
refused with the JSONErrorOutputbody under the codebad_request,
where earlier releases answered axum's bare extractor rejection. -
Policy embedder source compatibility. Cluster configuration authorization
addsPolicyAction::ConfigManage,PolicyResourceKind::Cluster, and
PolicyEngineKind::Cluster. Exhaustive matches on these public enums need
the new variants. Existing static credentials, direct storage-holder APIs
and version 1 token structs keep their behavior and construction contracts.
An existing cluster needs an explicitly applied management policy before
activating identity-authorized configuration execution; proposed policies
cannot grant permission to install themselves. -
GQ: a new file-level statement,
explainbefore a read declaration
(explain query q() { … }), describes the v2 plan the query runs,
instead of returning query results. Its table
has one row per plan node (tree,depth,node,detail): the logical
and physical trees, then the available DataFusion tree, each in pre-order,
followed byplanrows for fired passes and document fields (route,
logical_hash, …). Served byomnigraph queryand
POST /queryunder the query's own target, parameters andreadpolicy
decision; refused atmutate, at the deprecated routes, and over a mutation
declaration. One statement per file, like the branch statements of RFC
0055. -
GQ logic tests: a query step may carry an
--- expect plansection
after its--- expect shape, asserting facts of the query's v2 plan.
Forms includescan <Type>[ as $var]: columns [..],
scan <Type>[ as $var]: not columns [..],scan <Type>[ as $var]: filter reads [..],scan <Type>[ as $var]: no filter,filter reads [..],
pass <name>,not pass <name>. The facts are matched against the
engine's explain document, never against rendered text. See
crates/omnigraph-gqt/README.md. -
GQ logic tests: a query step may end with
--- expect same as v1,
after its shape or plan section. The runner runs the query again on the
frozen engine v1 (omnigraph-reference-engine) and fails the step on a v1
error or a row difference; the step's own rows expect still applies. The
section is refused on mutate, error-expect,showandbranch list
steps and skipped under DST. Theengine-v2CI mode is gone, and
OMNIGRAPH_GQ_ENGINEaccepts onlyv2or empty. -
GQ logic tests: a
--- concurrentsection runs two to four labeled
GQ statements at the same time on the case's one handle, under the DST
runner, in the order itsorder:line names over store requests
(<label> [park] <verb> <key-suffix>), session starts and completions;
aparkentry holds a session inside whatever the engine holds at that
request. The block is one step, its bare--- expectnames each
session's outcome, a starved script fails with the entry it stopped at,
and under--measureeach session is its own cost row (RFC 0045
§Concurrent block). -
GQ logic tests: several
--- seamblocks may precede one mutate step
when they name distinct seams; each is armed and recorded on its own, and
the same seam twice before one step is refused. -
GQ logic tests: a fault injected at the object store is a
--- seam,
not a--- fault(RFC 0066, RFC 0045):atmay name a store place from
STORE_PLACESinomnigraph-dst(storage.putfirst) with asubject
glob over the object's root-relative name, or a decision seam that declares
a store effect; the site passes and the storage decoration acts on the next
put. The DST target installs that decoration on every run. The delivery
record carries the store's own hit (method,requested,stored). At most
one store action precedes one step. -
GQ logic tests: the
--- faultblock that named a code hook is replaced
by--- seam. Rename the section, keepat,occurrenceandscope, and
writeaction: fail(wasreturn_error) oraction: skip; a seam precedes
a mutate step and must name a seam in the engine catalog with a matching
effect. An old--- faultblock is refused with that instruction. Delivery
is proved by the runner's own decision (seam_delivered,seam_unobserved)
instead of the injected error text. Seecrates/omnigraph-gqt/README.md. -
Rust consumers of the engine's test seams: the feature-gated
omnigraph::failpointsmodule (registry,set_registry,
ScopedFailPoint,names) and thedst_clock/dst_ids/dst_gate
install and uninstall functions are removed. Seams are statics in
omnigraph::seams::catalogarmed through theomnigraph-seamscrate
(SEAM.fire_always(),.fire_once_at(n),.panic_at(),.hold(),
.observe(f);CLOCK.install(..)and friends for the thread-local seams),
each returning a guard that uninstalls on drop.fail-parallelis no longer
a dependency. -
Rust consumers of the engine workspace: two new crates hold code moved
out of theomnigraph-enginepackage with no behavior change:
omnigraph-core(OmniError, Lance dataset access, request
instrumentation, Lance native ref control (branch_control), dataset
addressing) andomnigraph-catalog(the__manifestcatalog: graph branch
registrations and lineage, the publish). Both are internal API with no
compatibility promise. The documented engine paths are unchanged:
omnigraph::db::{Snapshot, SnapshotDataset, SnapshotScanner}(engine-owned
types),omnigraph::db::manifest::*,omnigraph::db::commit_graph::*,
omnigraph::error::*,omnigraph::storage::*and
omnigraph::instrumentation::*. Code that was private or crate-private in
the engine and is now called across the crate boundary ispubin
omnigraph-coreandomnigraph-catalog(about 440 items, among them the
ManifestCoordinatorcommit and branch methods,CommitGraphin-memory
methods,OmniErrorconstructors andDatasetEntry/DatasetUpdate
fields). The engine does not re-exportManifestCoordinatoror the other
catalog writers, so their methods are reachable only by depending on the
internal crates directly. Items on types the engine does re-export are now
callable throughomnigraph: theOmniErrorconstructors, theCommitGraph
in-memory methods,QueryMemoryProbes::record_refusaland the
DatasetEntry/DatasetUpdatefields. None of them writes to the object
store. -
GQ reserves
and,or,not,is,nullandin; GQ language 2.0.
Before upgrading, an operator checks every schema and every stored query for
three spellings that no longer parse, since the server quarantines a graph
whose stored query fails to parse at boot: a property named one of the six
words written bare as an operand of a mutationwhere($p.andafter a dot
in a read, andand: 1in an assignment or binding match, stay legal), a
return alias spelled as one of them (as and), and an edge type named one of
them traversed bare ($a and $bis now a Boolean filter;notwas already
excluded). A property namedtrueorfalsecannot be named bare in a
mutationwhereeither: the parser reads the literal, and the type checker
refuses the ambiguous spelling withT46instead of selecting rows by a
constant. The fix for all four is a schema rename with@rename_from, which
keeps the data, plus a rewrite of the stored queries that name the old
spelling. Two grammar rules,match_valueandtext_search_clause, are
deprecated in this release and removed in the next
(crates/omnigraph-compiler/compat/gq_language.deprecated.txt).
-
An update no longer reads the Blob cells it assigns, and a stored external
reference the policy refuses has its own error. Replacing or clearing a
Blob never takes, probes or reads its old value, so it succeeds when that
value is an external reference whose source is gone or outside the graph's
external Blob policy. An update that must carry such a reference in a Blob
it does not assign fails with the new typed
OmniError::StoredExternalBlobDenied(HTTP 400), naming the type, id and
property, where it earlier failed with the misleadingExternalBlobPolicy
message about new URI ingress. -
Export refuses a ranged external Blob descriptor. Export writes an external
Blob as a bare URI, which reloads as the whole object, so a descriptor
naming a byte range, which only a writer outside OmniGraph can create, is
refused instead of silently widened. Change-feed images, the change-feed
baseline and entity reads by id instead describe it exactly as
{"uri", "offset", "length"}without reading the object, so the feed still
passes the commit that holds it and a baseline still succeeds; such a
baseline does not reload withload. -
A CLI data write refused because an allowed external Blob source could not
be read (HTTP 424 withexternal_blob_source) now reports
command_outcome.actionrefreshinstead ofreconcile. The source is
read during admission, before the write has any effect, so restore the
source or correct the reference and run the command again. See
failed data-write commands.
- Query success and errors wait for registered graph producers and blocking
workers; cancellation retains their resources until they stop. Multi-table
keyed writes, keyed parsed-payload estimates and removed-ID collections now
have separate 32 MiB operation-wide limits, with typed refusal before data
staging. Large deletes and overwrite loads that previously succeeded can now
be refused: the removed-ID limit holds 671,088 generated IDs, no setting
changes it, and an overwrite cannot be split. These limits do not bound total
memory or qualify native I/O settlement and online deployment. See
mutation limits.
-
Release notes are collected from individual changes and grouped by their
effect on users. Future release documents list upgrade actions first;
the existing v0.12.0 notes retain their original sections. -
An
updateevaluates its assignments before it opens or scans its table,
so a null assigned to a non-nullable property of any type,Blobincluded,
is refused even when the predicate matches no row. Such an update returned
0 affected rows before.
Fixes
- A
DateTimestring with a non-zero digit past the third fractional digit,
such as"2024-01-01T00:00:00.123456Z", is now refused with
invalid DateTime literalas a load value, a query or mutation parameter,
or adatetime(...)literal. Before, the extra digits were dropped without
an error, so an equality filter on such a value matched the row stored at
its millisecond. Trailing zeros, as in.123000, are still accepted, and
stored values do not change; see
JSON result spelling.
- Export and change-baseline streams retain transport capacity through the last
owner of each yielded buffer. Consumers holding all available chunks receive
a bounded stream failure without a usable baseline cursor; releasing chunks
lets streaming continue. See server resource limits.
- A JSON number for an
F64now parses to the nearestF64value, the one a
GQ literal with the same digits names, in load files and in query and
mutation parameters from the CLI or over HTTP, so equality filters find such
values and an exported value reloads unchanged. A graph whose schema has a
float@rangebound that parsed inexactly failed to reopen with a
schema_ir_hashmismatch; it opens again. Before, decimals with 16 or more
significant digits, or a magnitude above about 1e22 or below about 1e-22,
could land one or two units in the last place away, and CLIjsonl,csv,
kvandtableoutput printed those digits; see
JSON result spelling.
-
Loads now explicitly report
embedding_generation: "unsupported"when a
loaded node type declares@embed, andnullotherwise. CLI human output
explains how to supply vectors. This applies to HTTP JSON and NDJSON loads and
deprecated ingest paths; supplied vectors, nullable omissions, and
required-field validation retain their existing behavior. -
A malformed external Blob descriptor with an offset but no length fails as
a Blob integrity error instead of reading past the end of its object. -
A merge now reports
fast_forwardwhenever the target's schema and every
table version still equal the merge base's. Merging a branch again after an
earlier fast-forward from it, with no target write in between, reported
mergedbefore. The merged data is the same. Such a merge also skips
constraint validation when it only inserts nodes of types with no@unique,
@range,@checkor enum property, as a fast-forward onto an unmoved target
already did, so a repeat merge over the 32 MiB validation budget now
succeeds instead of being refused. Exception: when a fast-forward into
maincarried a table whose changes cancel out on the branch, or a table on
a fork left by a branch created before storage format v11,mainends on its
own version of that table, and a later merge from that branch can still
reportmerged. -
A null assigned to a nullable Blob clears it.
update … set { content: $content }with$content: Blob?bound to null now writes a null cell.
Earlier updates kept the old value and still published a commit. -
Read-only CLI graph commands no longer acquire the exclusive cluster writer lock. Completed preflight refusals for new deployments release admission, and recovery diagnostics preserve bounded engine causes. Every deployment checks the achieved schema identity; deliberately accepting drift requires an exact schema-correction acknowledgement. See cluster deployment.
omnigraph optimizebounds the Blob payload of each compaction batch.
Compaction reads every managed Blob value of a batch into memory, and a
batch could span a whole fragment. Optimize now sizes each compaction
task's batches from that task's largest row, summed over the row's managed
Blob values (external references are never read and count nothing), so one
batch holds at most 32 MiB of managed Blob payload. A task holding a row
over 16 MiB compacts one row per batch, and a row over 32 MiB is
materialized whole. This bounds the payload of a batch, not total memory.
Every task of a Blob table gets the derived size explicitly, 1 to 8192 rows,
so it takes precedence overLANCE_DEFAULT_BATCH_SIZEthere, including when
the variable is smaller. When a fragment being compacted holds a Blob
descriptor the engine's decoder rejects, sizing can refuse the compaction
with a Blob integrity error before that table is rewritten, and nothing is
committed; a table with nothing to compact is not scanned. See
Optimize.
-
Commit changes, the change feed, its baseline and export no longer fail with
internal error while reading changesor anordered_scan_input_batch_bytes
resource error on tables holding a very wide row or a large fragment of
multi-kilobyte rows. These walks now sort only row keys and read complete rows
in bounded chunks, as branch merge already did. -
Disconnected HTTP reads and cancelled or timed-out MCP reads retain their input and concurrency reservations until execution finishes. Shutdown waits for these reads, and query execution panics wait for registered graph workers before propagating. See server admission and shutdown.
- Schema apply now persists comment and formatting changes even when no table
migration is needed. Cluster apply no longer reports convergence while the graph
still exposes the previous schema source. See schema changes.
- Export and change-baseline streams resume after slow clients release backpressure instead of failing after 250 ms. Startup readiness stays unavailable until all graph opening attempts finish, while healthy graphs may serve direct requests. Expired or abandoned serving transitions keep the affected graph closed without blocking other graphs’ transitions. See deployment.
Performance
omnigraph snapshotandGET /graphs/{graph_id}/snapshotread the storage
format version from the snapshot they return instead of resolving the branch
again and opening its newest manifest version, so the reported version always
describes the same graph version as the tables beside it. Reading the version
itself now costs no storage request: a served snapshot ofmainwith no new
commit since the previous read does one version probe and opens no manifest
version. How many storage requests one probe makes depends on the object
store. A named branch also reads its branch reference, a handle that sees a
new commit refreshes its state first, and the CLI opens the graph on every
embedded command.