Commit graph

672 commits

Author SHA1 Message Date
Andrew Altshuler
58defb4c51
Fence Optimize against late recovery intents (#347)
Some checks are pending
CI / Classify Changes (push) Waiting to run
CI / Check AGENTS.md Links (push) Waiting to run
CI / Container Entrypoint (push) Waiting to run
CI / Test Workspace (push) Blocked by required conditions
CI / Test omnigraph-server --features aws (push) Blocked by required conditions
CI / RustFS S3 Integration (cli) (push) Blocked by required conditions
CI / RustFS S3 Integration (cluster) (push) Blocked by required conditions
CI / RustFS S3 Integration (engine) (push) Blocked by required conditions
CI / RustFS S3 Integration (failpoints) (push) Blocked by required conditions
CI / RustFS S3 Integration (server) (push) Blocked by required conditions
Release Edge / Prepare edge release (push) Waiting to run
Release Edge / Build edge omnigraph-linux-arm64 (push) Blocked by required conditions
Release Edge / Build edge omnigraph-linux-x86_64 (push) Blocked by required conditions
Release Edge / Build edge omnigraph-macos-arm64 (push) Blocked by required conditions
Release Edge / Build edge omnigraph-windows-x86_64 (push) Blocked by required conditions
Release Edge / Smoke Windows installer (push) Blocked by required conditions
* Fence optimize against late recovery intents

* Preserve manifest compaction after table errors
2026-07-12 03:28:27 +03:00
Andrew Altshuler
905a27c4bd
Harden RFC-022 pre-arm recovery ownership (#346) 2026-07-11 23:52:53 +03:00
Andrew Altshuler
e0e145aa92
Enroll branch merge in RFC-022 write path (#345) 2026-07-11 19:02:20 +03:00
Andrew Altshuler
bd4c614e42
Harden branch recovery and cleanup retention (#344)
* Harden branch recovery and cleanup retention

* Fail closed on blocked partial rollback
2026-07-11 16:56:40 +03:00
Andrew Altshuler
f758ff0d17
Implement RFC-022 unified graph write protocol (#343)
* Implement unified graph write protocol

* Preserve recovery error wire compatibility
2026-07-11 14:02:54 +03:00
Andrew Altshuler
0c8d769501
feat(ci): publish omnigraph-server image to Docker Hub alongside GHCR (#341)
Some checks failed
CI / Classify Changes (push) Has been cancelled
CI / Check AGENTS.md Links (push) Has been cancelled
CI / Container Entrypoint (push) Has been cancelled
Release Edge / Prepare edge release (push) Has been cancelled
CI / Test Workspace (push) Has been cancelled
CI / Test omnigraph-server --features aws (push) Has been cancelled
CI / RustFS S3 Integration (cli) (push) Has been cancelled
CI / RustFS S3 Integration (cluster) (push) Has been cancelled
CI / RustFS S3 Integration (engine) (push) Has been cancelled
CI / RustFS S3 Integration (failpoints) (push) Has been cancelled
CI / RustFS S3 Integration (server) (push) Has been cancelled
Release Edge / Build edge omnigraph-linux-arm64 (push) Has been cancelled
Release Edge / Build edge omnigraph-linux-x86_64 (push) Has been cancelled
Release Edge / Build edge omnigraph-macos-arm64 (push) Has been cancelled
Release Edge / Build edge omnigraph-windows-x86_64 (push) Has been cancelled
Release Edge / Smoke Windows installer (push) Has been cancelled
* feat(ci): publish omnigraph-server image to Docker Hub alongside GHCR

Docker Hub (docker.io/modernrelay/omnigraph-server) becomes the primary
anonymous-pull distribution channel; the step no-ops when the
DOCKERHUB_* secrets are unset so forks and the GHCR-only path keep
working. Same latest-tag policy as GHCR (real tag pushes only, never
dispatch backfills). Docs updated in the same PR (ci.md, deployment.md).

* review fixes: preserve digest summary on Hub failure, tolerate unprefixed RepoDigests

- Docker Hub push is continue-on-error with an explicit fail-gate AFTER
  the digest report, so a Hub failure still fails the run but can no
  longer suppress the summary for the already-pushed GHCR image.
- RepoDigests may store Hub images without the docker.io/ prefix
  depending on Docker version; the digest grep now accepts both forms.
2026-07-08 20:28:28 +03:00
Andrew Altshuler
d2f9b5c5fb
feat(ci): publish omnigraph-server container image to GHCR on release (#340)
* feat(ci): publish omnigraph-server container image to GHCR on release

Build binaries inside a rust:1-bookworm container (matching the
Dockerfile's bookworm-slim runtime glibc) with the aws feature enabled,
then build and push ghcr.io/<owner>/omnigraph-server:<tag>. Separate
from release.yml so an image failure never blocks the binary release,
and a workflow_dispatch backfill of a past tag never re-runs the release
matrix or repoints :latest.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* review fixes: validate dispatch tag (existing v* only), document workflow + GHCR image

- Guard workflow_dispatch: the free-form tag input must name an EXISTING
  v* tag (gh api ref check) and checkout is pinned to refs/tags/<tag>,
  so a branch/SHA can never be published under a release-looking image
  tag (greptile P1).
- docs/dev/ci.md: add the publish-image.yml entry (same-PR docs rule).
- docs/user/deployment.md: document pulling the prebuilt GHCR image.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 02:23:15 +03:00
aaltshuler
b92be13bc6 docs(skill): review follow-ups — gotcha-table consistency, stale D2 note, example naming
Devin caught the gotchas table still advising the tighten-in-follow-up
recipe this PR corrects elsewhere; the D2 note claiming deletes
inline-commit (staged since 0.8.0 — the rule is a deliberate boundary,
not an implementation gap); and the undirected example describing
`Related` while traversing `<issueRelated>` (text now names
`IssueRelated` per the PascalCase->lowerCamelCase convention).
2026-07-07 01:49:03 +03:00
aaltshuler
7eb8a4b562 docs(skill): align the omnigraph skill with 0.8.1
- Undirected traversal documented (SKILL.md quick-rules + embedded
  grammar + references/queries.md worked example, marked >= 0.8.1).
- Enum widening documented as a supported metadata-only apply
  (references/schema.md), with the still-refused shapes listed.
- Two standing inaccuracies fixed: `{1,}` was documented as unbounded
  traversal but the typechecker rejects unbounded (T15); the
  required-property recipe's final "tighten to required" step is refused
  by the planner (OG-MF-106) — the recipe now says to keep the property
  optional until required-tightening ships.
- Skill metadata version -> 0.8.1; compatibility note marks the
  version-gated features.
2026-07-07 01:49:03 +03:00
aaltshuler
e415338acb release: v0.8.1
Version bump across all seven workspace crates + path-dep constraints,
Cargo.lock, openapi.json, and the AGENTS.md surveyed version (also
restores the RFC-process row in the topic index that was dropped during
branch juggling). Release notes: docs/releases/v0.8.1.md.

Channel note: crates.io publication is deferred for this release — the
substrate is a rev-pinned Lance pre-release (git dependency), which
published crates cannot reference. publish-crates.yml gains a
self-healing guard that skips cleanly while the pin is a git dep and
resumes automatically when the dependency returns to a registry version
(Lance 9.0.0 stable → v0.9.0); a workflow_dispatch catch-up can then
publish any skipped tag. Binaries, Homebrew, and the installer ship
normally via release.yml.
2026-07-06 22:24:10 +03:00
Ragnor Comerford
ca2ad974e1 docs: self-service upgrade message + gated cross-version test (on-demand)
The storage-format refusal now names the release line that wrote each
internal-schema stamp (e.g. v3 -> 0.6.2-0.7.2) plus the exact
export/init/load commands — fail-closed but self-service; the message is
engine-shared so server boot-quarantine and cluster status get it free.

Adds the OMNIGRAPH_OLD_BIN-gated cross-version test: mint a GENUINE v3
graph with a real omnigraph-cli 0.7.2 binary, assert the current binary
refuses it with the release-named message, and the documented rebuild
round-trips. Deliberately NOT wired into CI (the write_cost_s3
disposition): compiling 0.7.2 from source is a 15-25 min job and the
stamp seam changes a few times a year — testing.md documents the
on-demand invocation instead.

Rebased onto current main across the Lance 9.0.0-beta.15 migration;
validated locally against a genuine 0.7.2 binary — the v3 round-trip
passes on the Lance-9 main, which also makes it a live cross-substrate
read-compat check (a Lance-9 binary reading a Lance-7-written manifest
to find the stamp).
2026-07-06 12:48:25 +03:00
aaltshuler
50db10f65c docs(lance): 9.0.0-beta.15 alignment stanza; close the vendor-pin and blob-compaction gaps
Some checks failed
CI / Classify Changes (push) Has been cancelled
CI / Check AGENTS.md Links (push) Has been cancelled
CI / Container Entrypoint (push) Has been cancelled
Release Edge / Prepare edge release (push) Has been cancelled
CI / Test Workspace (push) Has been cancelled
CI / Test omnigraph-server --features aws (push) Has been cancelled
CI / RustFS S3 Integration (cli) (push) Has been cancelled
CI / RustFS S3 Integration (cluster) (push) Has been cancelled
CI / RustFS S3 Integration (engine) (push) Has been cancelled
CI / RustFS S3 Integration (failpoints) (push) Has been cancelled
CI / RustFS S3 Integration (server) (push) Has been cancelled
Release Edge / Build edge omnigraph-linux-arm64 (push) Has been cancelled
Release Edge / Build edge omnigraph-linux-x86_64 (push) Has been cancelled
Release Edge / Build edge omnigraph-macos-arm64 (push) Has been cancelled
Release Edge / Build edge omnigraph-windows-x86_64 (push) Has been cancelled
Release Edge / Smoke Windows installer (push) Has been cancelled
New dated audit stanza in lance.md (382 upstream commits across the 7->8
and 8->9-beta.15 legs; zero ecosystem churn, no format/reader-floor
movement, the FTS-v2 soft door, the merge_insert rewrite, the open
items #6666/#6914/#7508). invariants.md: the vendored-lance-table and
blob-column-compaction known gaps close per their documented removal
conditions. AGENTS.md: substrate line + compaction matrix row. testing.md:
the blob guard's new positive name.
2026-07-06 01:03:05 +03:00
aaltshuler
b97f9a6f6e chore(deps): drop the vendored lance-table sources (pin retired with the 9.0.0-beta.15 bump) 2026-07-06 01:03:05 +03:00
aaltshuler
3fe96f2239 feat(engine): blob tables compact — remove the LANCE_SUPPORTS_BLOB_COMPACTION gate
Lance 8.0.0 shipped full blob-v2 compaction (upstream #7017, hardened by
#7618 in 9.0.0-beta.15), executing this gate's documented removal plan:
the constant, the optimize skip branch, and
SkipReason::BlobColumnsUnsupportedByLance are deleted. The surface guard
inverts to a positive pin (compact_files_succeeds_on_blob_columns) and
the maintenance test now asserts a multi-fragment blob table compacts,
publishes, and keeps every row
(optimize_compacts_blob_table_alongside_plain_table).
2026-07-06 01:03:05 +03:00
aaltshuler
9361218a3f test(engine): realign surface guards + namespace tests to Lance 9 behavior
- filtered_scan_tolerates_merge_update_row_id_overlap passes on stock
  lance-table (vendor-pin retirement validated by its own guard).
- literal-coercion guard re-pinned: v8's coercion fixes (lance#6935 et
  al.) coerce width-mismatched literals to the column type BEFORE
  pushdown, so the widened case now USES the BTREE (was: index-defeating
  column cast). literal_to_typed_expr stays load-bearing.
- namespace decoupling guard: table_version_storage_enabled removed
  upstream (#7222); thesis unchanged and still pinned.
- lazy-fork namespace test: Lance 9 loudly rejects opening a main-owned
  manifest as another branch — the substrate now enforces the invariant
  entry-owner resolution protects; test pins both the error and the
  owner-branch open.
2026-07-06 01:03:05 +03:00
aaltshuler
5d755a9274 deps(lance): bump to 9.0.0-beta.15 (git rev-pin) — retire the lance-table vendor pin
The 9.x betas are git-tags only (crates.io carries <= 8.0.0), so all lance
crates become git dependencies rev-pinned to the v9.0.0-beta.15 tag commit
(f24e42c1). Carries lance#7480 (rowid overlap, closes #7444 — the
vendor/lance-table pin + [patch.crates-io] entry are retired per their
documented removal condition) and lance#7320 (BTREE segment merge, closes
#7230). Sole compile break in the workspace: RowAddrTreeMap moved to the
new lance-select crate.
2026-07-06 01:03:05 +03:00
aaltshuler
2faa8dfa3e test: lowering-boundary pins for Both + undirected under degraded coverage
Closes two coverage gaps from review of my own test surface: (1) the
lowering boundary — undirected lowers to Expand{direction: Both} both
top-level and inside not{} (the discarded-context-clone regression is
now pinned at the layer it lives at, not only end-to-end); (2) the
review-fix path — undirected both_modes equivalence with a
mutation-appended unindexed fragment degrading BTREE coverage, so
results stay correct and mode-equivalent whichever path the pessimistic
coverage pricing picks.
2026-07-06 00:59:09 +03:00
aaltshuler
a41d6be88f fix(engine): review follow-ups — Both coverage probes both columns; anti-join arm grouping
Devin: the cost model's index-coverage probe checked only the primary
endpoint column, so an undirected traversal with a degraded dst index
was priced as fully indexed; coverage is now the pessimistic combination
over every column endpoint_probes will scan. Greptile: the bulk
anti-join grouped Both with In for src_type_name but with Out for the
adjacency — safe only under T22's from==to guarantee; Both now groups
with Out in both arms so a future T22 relaxation cannot split them
silently.
2026-07-06 00:59:09 +03:00
aaltshuler
397d33f1b3 docs(queries): document undirected traversal — form, set semantics, T22 rule, execution cost 2026-07-06 00:59:09 +03:00
aaltshuler
9460f097d9 feat(engine): execute Direction::Both on both Expand arms
CSR arm: an undirected hop chains csr(edge).neighbors with
csc(edge).neighbors under the existing visited/seen_dst gates, so
both-direction pairs and self-loops dedup for free (set semantics),
including per-hop in variable-length BFS. Indexed arm: endpoint_probes
returns both (src,dst) and (dst,src) orientations for Both; the per-hop
scan runs once per orientation into one neighbor_map, deduped by the
existing per-source seen_dst sets. Bulk anti-join: "no edge in EITHER
direction" (csr || csc has_neighbors).

Also fixes a lowering gap the anti-join test caught red: negation
inners are typechecked into a discarded context clone, so the
ResolvedTraversal direction lookup silently fell back to Out inside
not{} — undirectedness now travels on the AST node itself (the syntax
is the source of truth), immune to context-propagation gaps.

Tests: traversal.rs (out ∪ in vs the directional miss, both-ways dedup,
var-length from an incoming-only node, undirected anti-join vs
directional), traversal_indexed.rs both_modes equivalence, and the
proptest arm-equivalence property widened with <knows>{1,3}.
2026-07-06 00:59:09 +03:00
aaltshuler
d4b21ce4eb feat(compiler): parse + typecheck undirected traversal — $a <edge> $b
iss-gq-undirected-traversal, the Expand-internal design (no plan-level
Union; unblocked from iss-744/579): the grammar gains an undirected_edge
alternative in the traversal rule (angle brackets are collision-free —
comparisons live in the structurally separate filter production), the
AST Traversal carries `undirected`, Direction gains `Both`, and
typecheck resolves undirected patterns to Both after the new T22 rule:
undirected requires a same-endpoint-type edge (an asymmetric edge is
well-typed in at most one orientation, so the form is rejected with
guidance to use the directional pattern). Lowering passes Both through;
the reverse-expand orientation flip is a no-op for a symmetric
traversal. Bounds ({min,max}) and not{} compose unchanged.

Direction has no serde derives and IR never crosses a wire — no
compatibility surface. Parser/typecheck tests cover the bare, bounded,
inside-not forms, Both resolution on Knows (Person->Person), and the
T22 rejection on WorksAt (Person->Company).
2026-07-06 00:59:09 +03:00
aaltshuler
db217e7db2 fix(bench): restore the cfg(unix) gate displaced by the results-log insertion
Both review bots caught it: the new fns were inserted between run_once's
cfg(unix) attribute and the fn itself, leaving append_result, git_sha,
and run_once ungated (non-unix name-resolution break, again). All three
gated; results_path keeps the displaced attribute.
2026-07-05 18:57:08 +03:00
aaltshuler
80db05f16e bench(engine): append every run to a results log (ts + git_sha)
The harness's JSON lines were vanishing into terminal scrollback — the
numbers that justify decisions (the 6.9GB merge crash, the prefilter
recall pair) had no system of record, and the v7 baselines are needed
for before/after comparison across the Lance 9 bump. Every run now
appends its record, stamped with a unix ts and the current git sha, to
--out <path> / OMNIGRAPH_BENCH_RESULTS / benches/results.jsonl
(gitignored — host-specific, self-describing via host+params).
2026-07-05 18:57:08 +03:00
aaltshuler
54dbf2f11c fix(bench): gate every unix item, not just main and the helpers include
Greptile follow-up on the previous cfg(unix) fix: ungated scenario fns
still referenced the unix-only helpers module, failing name resolution
on non-unix hosts (cfg_attr(allow) suppresses warnings, not resolution).
Every item is now cfg(unix)-gated; the non-unix build compiles to the
stub main alone.
2026-07-05 15:53:33 +03:00
aaltshuler
dd67adccd9 fix(bench): honor --baseline in nearest-prefilter
Greptile follow-up: the scenario ignored the flag and ran the full query
loop, so a baseline/delta pair measured ~0 instead of the queries'
memory cost. Baseline now exits after seeding + optimize, matching
merge-all-changed's shape.
2026-07-05 15:53:33 +03:00
aaltshuler
c6b4a6ce2b fix(bench): harden the harness — EINTR retry, loud setrlimit failure, cfg(unix) guard
Greptile review follow-ups: wait4 now retries on EINTR (a delivered
signal previously panicked the harness mid-run via the reaped-pid
assert); a failed setrlimit warns on inherited stderr instead of
silently running the child uncapped while the parent JSON records the
requested cap; and the Unix-only syscalls are cfg(unix)-gated with an
inert Windows stub main so cargo bench compiles everywhere.
2026-07-05 15:53:33 +03:00
aaltshuler
8195a3ed72 docs(testing): document the scenario benchmark harness in Examples & benches 2026-07-05 15:53:33 +03:00
aaltshuler
d426904ab6 bench(engine): scenario benchmark harness — cold subprocess runs, wait4 peak-RSS, JSON lines
The dedicated cost/perf instrument testing.md's write_cost_s3 note has been
promising: one cold, stateful macro-run per scenario in a fresh subprocess
(self-respawn via current_exe), reaped with libc::wait4 so ru_maxrss gives
kernel-exact peak RSS with no sampling; results are JSON lines and there are
deliberately no assertions — a decision instrument, never a CI gate.
Criterion is deliberately not used: statistics over warm in-process
iterations is the wrong model for multi-second stateful scenarios, it
measures no memory, and an OOM under --memory-cap-mb (setrlimit; enforced on
Linux, best-effort on macOS) is a data point that needs crash isolation.

Two scenarios ship with the skeleton:
- merge-all-changed: an embedding table whose branch changed EVERY row's
  vector, merged three-way into a diverged main — the changed-delta
  concat + hash-join cost of branch_merge. --baseline re-runs the identical
  workload minus the merge so the peak-RSS delta isolates it (smoke,
  20k rows x 256 dims: ~72 MB merge contribution on a 20.5 MB raw delta,
  ~3.5x).
- nearest-prefilter: selectivity-s filtered nearest() where matching rows sit
  far from the query point — quantifies the post-filter ANN recall deficit
  (smoke, 20k rows, s=0.05, k=10: 1000 matching rows exist, 0 returned);
  becomes the prefilter latency comparison unchanged once the fix lands.
2026-07-05 15:53:33 +03:00
aaltshuler
db96e7000d perf(engine): normalize with the already-computed norm in embedding validation
Greptile follow-up: the zero-norm guard computed the norm and then
normalize_vector recomputed it (a wasted pass at 1536+ dims), with the
inner zero guard unreachable by construction. Normalize in place with
the validated norm; normalize_vector stays for its other caller (the
mock provider).
2026-07-05 15:06:41 +03:00
aaltshuler
4834c6a0dc fix(engine): warn when OMNIGRAPH_EMBEDDINGS_MOCK overrides an explicit provider
The mock flag's precedence over OMNIGRAPH_EMBED_PROVIDER is deliberate
and stays (pinned by from_env_mock_flag_wins) — but the override was
silent, and mock vectors are indistinguishable from real ones (correct
dimension, unit norm, deterministic hash), so a leaked test env var
silently poisoned persisted embeds (CLI) and query-time nearest()
(server, for graphs without a bound cluster embedding profile). Emit a
tracing::warn naming the overridden provider; document the precedence
in the embeddings env-var table.

Closes iss-embeddings-mock-override-silent.
2026-07-05 15:06:41 +03:00
aaltshuler
fd32460eac fix(engine): loader rejects non-numeric vector elements (parity with mutation)
Replace the FixedSizeList arm's as_f64().unwrap_or(0.0) coercion with a
loud per-row OmniError::manifest in the loader's own style (property
name included, mutation path's "elements must be numeric" phrasing).
The red test turns green; the whole validators suite stays green.

Closes iss-loader-vector-element-coercion.
2026-07-05 15:06:41 +03:00
aaltshuler
abc92998bb test(engine): red — loader coerces non-numeric vector elements to 0.0
iss-loader-vector-element-coercion: the loader's FixedSizeList arm does
val.as_f64().unwrap_or(0.0) — a null element (what json! produces for a
non-finite float) or a string element silently zeroes the vector while
the mutation path rejects the same input loudly. No loader vector
validation had any test coverage; this also pins the existing dimension
check.

Fails as predicted against current code: the null-element load succeeds
(unwrap_err panics on Ok) instead of erroring.
2026-07-05 15:06:41 +03:00
aaltshuler
10835ca788 fix(engine): reject non-finite and zero-norm embeddings
Validate every component is_finite() (naming the offending index) and
reject zero-norm vectors before normalizing, via the existing
Err(String) channel — callers already map it through EmbedCallError
{retryable:false} into OmniError::manifest_internal, so no new error
plumbing. The red tests turn green; docs state the finite/non-zero
contract in embeddings.md.

Closes iss-embedding-nan-validation.
2026-07-05 15:06:41 +03:00
aaltshuler
3b00d81626 test(engine): red — NaN/Inf/zero embeddings pass validation
iss-embedding-nan-validation: validate_and_normalize_embedding checks
only dimension. The normalize guard (norm > f32::EPSILON) inverts on NaN
— normalization is skipped and the raw NaN vector returns Ok; an Inf
component normalizes to 0s + NaN; an all-zero vector (no direction,
undefined under cosine/ANN) passes. All were unpinned.

Fails as predicted against current code: unwrap_err() panics on the NaN
case — the poisoned vector is returned Ok.
2026-07-05 15:06:41 +03:00
aaltshuler
281525cf7a fix(engine): prefilter(true) for filtered vector/FTS search
Lance's scanner defaults to prefilter=false: a filter riding the same
scanner as nearest()/bm25() is applied AFTER the ANN/FTS top-k, so
`limit k` meant top-k of the whole table and a selective predicate
silently starved results (the deny-list's silent-partial-result shape;
measured by the nearest-prefilter bench scenario: 20k rows, s=0.05,
k=10 -> 1000 matching rows exist, 0 returned). Set prefilter(true)
whenever a structured filter is pushed to the scanner: one flag governs
both the vector and FTS sources, plain scans ignore it, and it
re-enables scalar-index acceleration for the predicate under nearest.

The red test turns green: filtered nearest now returns the top-k of
MATCHING rows. Docs state the filters-before-search contract explicitly
(docs/user/search/index.md).

Closes iss-nearest-postfilter-starves-results.
2026-07-05 15:06:41 +03:00
aaltshuler
31134db440 test(engine): red — filtered nearest starves results under post-filter ANN
iss-nearest-postfilter-starves-results: a scalar match predicate combined
with nearest returns the post-filtered remainder of the GLOBAL ANN top-k,
not the top-k of matching rows. Fixture: 3 matching docs sit far from the
query vector while 3 non-matching docs occupy the global top-3 — the query
returns 0 rows despite limit 3 and 3 matches existing.

Fails as predicted against current code:
  assertion left == right failed: left: 0, right: 3
2026-07-05 15:06:41 +03:00
aaltshuler
466af5bc95 test(compiler): pin the rename+widen interaction — rename first, ExtendEnum names the new property
Greptile review follow-up: a property renamed and widened in one
migration emits RenameProperty followed by ExtendEnum carrying the
post-rename name. That ordering/naming contract is now stated on the
ExtendEnum variant's doc and pinned by
plan_orders_rename_before_widening_and_names_the_new_property, so a
sequential step consumer (or a future apply-ordering change) can't
silently break it.
2026-07-05 01:32:11 +03:00
aaltshuler
f7ddabafae docs(schema): document enum widening — migration step list, OG-MF-106 scope, test map
Same-PR docs for the ExtendEnum step: the supported-steps list in the
schema migration section (with the narrowing/rename/String-conversion
carve-outs and the order-insensitivity note), the OG-MF-106 lint-table
row narrowed to what it still covers, and the schema_apply.rs test-map
row extended with the two new tests.
2026-07-05 01:32:11 +03:00
aaltshuler
cc6f8602a9 feat(engine): apply ExtendEnum as a metadata-only schema step
The widening is planner-verified (superset, same shape), so every
committed row is already valid under the wider set: the apply arm joins
the AddConstraint metadata-only family — accepted-catalog update, no
table work, no version bump — and the unified validator accepts the new
variants on all three write surfaces immediately.

Tests: enum_widening_apply_is_metadata_only_and_accepts_new_variant
(no table-version bump; new variant accepted, original still accepted,
out-of-set still rejected) and enum_narrowing_apply_is_refused
(OG-MF-106 surfaced, graph left writable).
2026-07-05 01:32:11 +03:00
aaltshuler
bc434fb577 feat(compiler): plan pure enum widening as a supported ExtendEnum step
Adding variants to an enum property previously planned as an unsupported
property-type change (OG-MF-106): enum values live inside PropType and
the planner compared whole types. Special-case the one safe shape — same
scalar/list/nullability and the desired value set a strict superset of
the accepted one — into a new ExtendEnum step carrying the added values.
Narrowing, variant renames, and enum<->String conversions still plan as
OG-MF-106; a pure reorder was already a no-op (the schema IR sorts +
dedups enum values).

Planner matrix pinned in in-source tests (widening, reorder-as-no-change,
narrowing, rename, widen+nullability-flip, enum<->String both ways); CLI
plan rendering gains the ExtendEnum arm.
2026-07-05 01:32:11 +03:00
aaltshuler
e211060119 fix(engine): classify StagedTableNamespace opens as data-table opens
Devin review follow-up on the opener unification: StagedTableNamespace's
open_head opens a DATA table (its table_uri is the per-table physical
path), so it belongs on the table_wrapper/data_open_count bucket, not
the manifest one. Test-only impact (the namespace module is #[cfg(test)]
since RFC-013 step 3a), but the classification should be right where the
cost vocabulary is defined.
2026-07-05 00:48:14 +03:00
aaltshuler
451585ee59 test(engine): shared test_session() helper for TableStore construction
Greptile review follow-up on the session-threading commit: the
Arc::new(Session::default()) boilerplate at every test TableStore
construction collapses into helpers::test_session() (and a local twin in
staged_writes.rs, which is primitive-level and deliberately does not
include the helpers module) — a future session-constructor change is a
single-point edit.
2026-07-05 00:48:14 +03:00
aaltshuler
d502603a52 perf(engine): attach the shared per-graph Session to every write/maintenance open
Thread the graph's one Lance Session (previously read-path-only via
ReadCaches) into TableStore, so open_dataset_head, the branch ops, and
open_at_entry all attach it through the unified opener — the read path's
handle cache and the write side now warm the same Lance metadata/index
caches. diff_snapshots takes the graph's &TableStore instead of
constructing its own session-less store from a root uri.

Measured effect (write_cost.rs): the local data-table SCAN term — the
merge-insert/RI scan that re-read O(depth) immutable fragment metadata
per write — collapses from growing-with-depth to flat (depth 10: 1 read,
depth 100: 1 read). The opener/scan-split gate is re-pinned to the new
behavior and renamed
(data_table_reads_split_into_flat_opener_and_scan_flat_with_session):
a red there now means a write-side open dropped the session. The
Snapshot-without-caches fallback stays session-less by design (a
detached snapshot has no graph to share a session with); the S3
acceptance of the opener term remains owed to write_cost_s3 per the
RFC-013 handoff.

testing.md's backend-split note updated in the same change (the "local
scan grows with depth" claim is now stale).
2026-07-05 00:48:14 +03:00
aaltshuler
61dc84caf2 refactor(engine): retire the open_dataset_head_for_write alias
The method was a delegating twin of open_dataset_head that discarded its
table_key argument ("retained for signature stability") — a dead
parameter every caller still had to supply, and a second name for the
same open policy on the sealed TableStorage surface. Remove it from the
trait, the TableStore impl, and all call sites, which also lets the two
needs_index_work_* helpers drop their now-unused table_key parameters.

Purely mechanical; no behavior change.
2026-07-05 00:48:14 +03:00
aaltshuler
9292ff42a9 refactor(engine): one dataset-open chokepoint — open_dataset(uri, VersionResolution, session, wrapper)
Unify the two instrumented openers (open_dataset_tracked latest-only,
open_table_dataset pinned-only) into a single
instrumentation::open_dataset whose version resolution (Latest | At(v))
is an explicit typed parameter, and route every production Dataset open
in the engine through it — including the previously-raw opens in the
branch ops (delete/list/force_delete_branch), manifest branch delete,
version-metadata resolution, recovery classification/restore, the
recovery-audit table, and schema-apply version GC (whose
forbidden-api-allow sentinel is now unnecessary and removed).

One chokepoint means three properties hold uniformly instead of
per-path: record_open feeds the cost probes on every open (the raw
sites were invisible to the gates), the per-query IO wrapper is
attached with the right class (manifest vs table) everywhere, and the
shared per-graph Session parameter exists on every path — threading it
into the write-side owners lands separately.

Behavior-preserving: every call site keeps its existing version
resolution, session argument, and wrapper class; in-source tests keep
raw opens (out of scope for the chokepoint by design).
2026-07-05 00:48:14 +03:00
Ragnor Comerford
3c0f7daca3
Invite users to join Omnigraph Slack community
Added a section inviting users to join the Omnigraph Slack community for questions and feedback.
2026-07-04 20:25:59 +01:00
aaltshuler
33d0134d2c docs(testing): match lineage row to the asserted invariant; fix stale write_cost_s3 module doc
Greptile review on #324: the lineage_projection row said the legacy
datasets 'hold ZERO commit rows' but the test asserts the directories are
never created — a strictly stronger invariant; say that. And
write_cost_s3.rs's module doc still claimed it runs in the rustfs CI job,
contradicting the (correct) new table row — align it with reality (run on
demand, cost gate).
2026-07-03 13:34:10 +03:00
aaltshuler
5c3125e7e1 docs(testing): sync the engine test map with reality — 36 files, 5 missing rows
The engine integration-test table drifted: failpoint_names_guard.rs,
lineage_projection.rs, merge_fast_forward.rs, scalar_indexes.rs, and
write_cost_s3.rs existed with no row (write_cost_s3 had prose mentions
only), and the file count said 28 where there are 36. testing.md is
@-imported into every agent session as the test-coverage map, so missing
rows defeat its "check what already covers it" contract. Row texts are
derived from each file's own doc comments; the write_cost_s3 row records
its run-on-demand (not every-merge CI) status per the existing
Cost-budget-tests note.
2026-07-03 13:34:10 +03:00
aaltshuler
7657c99c42 fix(cli-tests): surface stderr on the stalled-server panic too; hoist io imports
Greptile review on #323: the timeout path (server alive but never healthy)
panicked without the captured stderr — the harder failure mode was less
diagnosable than the port-race one. Read the log after kill+wait so the
final flush is captured. Hoist the io imports to the file's use block, and
reference the #327 design-fix follow-up (server-side :0 binding) at the
retry stop-gap.
2026-07-03 12:50:58 +03:00
aaltshuler
fbc1d3f4ed fix(cli-tests): retry lost port races in the server-spawn harness; surface server stderr
spawn_server_process picked a port by binding :0 and DROPPING the
listener before the spawned omnigraph-server rebound it — a TOCTOU
window in which any concurrently-spawning test steals the port. The
loser exits "address in use", but stderr was nulled, so the test
panicked with a bare "server exited before becoming healthy: exit
status: 1" on whichever parity/system test lost the race that run
(~1-in-2 parallel runs locally; 12/12 pass serially).

Fix, two halves:
- Retry the spawn on a fresh port (up to 5 attempts) when the child
  exits before passing the health check. A lost race becomes invisible;
  a deterministic startup failure still panics loudly after the retries.
- Capture stderr to a temp file (not a pipe, which a chatty healthy
  server would fill and block on; not null) and include it in the panic
  and per-retry messages, so genuine startup failures carry the server's
  own error text.

StdCommand isn't Clone and a reused command would trip clap's
duplicate --bind rejection, so retries respawn via clone_command
(program/args/envs/cwd).

Verified: parity_matrix 3x parallel clean (previously flaked ~every
other run), full omnigraph-cli suite green.
2026-07-03 12:50:58 +03:00