Skip to content

fix(validate,schema)!: enforce @card minimums for every written source node - #1212

Merged
ragnorc merged 7 commits into
ModernRelay:mainfrom
ragnorc:fix/card-minimum-written-sources
Nov 1, 2026
Merged

ragnorc merged 7 commits into
ModernRelay:mainfrom
ragnorc:fix/card-minimum-written-sources

Conversation

@ragnorc

@ragnorc ragnorc commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

A non-zero @card minimum was enforced only for sources whose edges a write added, moved or removed. A node written without any edge of the type was never counted, so under edge WorksAt: Person -> Company @card(1..) a Person with no WorksAt loaded, alone or beside people loaded with theirs, while deleting a person's last edge was refused: the same graph state was valid or not depending on how it was reached. Two more doors did the same. An edge-table overwrite never recounted the sources it emptied, because the replaced table's committed view is empty. Schema apply could add an edge type with a minimum over existing source nodes, all of which then sat below it. The user docs said cardinality is checked on every write path; the skill docs described the gap and told agents to keep orphan queries.

The unified evaluator (mutation, JSONL load, merge, Blob cell writes) now counts every source node row a write inserts or updates when the minimum is non-zero, the same rule uniqueness and endpoint checks apply to every row a write touches, so a node and its required edges arrive in one mutation query or one load. Under an overwrite, every source of the replaced image holds only its delta edges: one bounded scan of the replaced table's source column keeps the smallest source the delta leaves without an edge, so memory stays with the delta and no key list grows with the input. A load csv plan declares that scan as the cardinality check's scan access, and the overwrite judge refuses a plan with a minimum that lacks it instead of counting nothing; a node plan carries a new typed source_cardinality check. The schema planner refuses an added edge type with a minimum from an existing node type as OG-MF-107, data-independently, as it refuses a required property (OG-MF-103). Committed edges are counted per source in sorted filtered scans of at most 8,192 sources each, streamed and kept as one count per source, because a node-table overwrite and a merge can write any number of sources; the load csv judge calls the evaluator's evaluate_cardinality, so the rule has one edit site; and a fast-forward of pure node inserts keeps the evaluator when the type is the source of an edge with a minimum, since only a branch written before this change can hold such nodes.

The regression tests fail on the code before this change at the predicted step and pass after it: in validators.rs, a JSONL load of an edgeless person beside one loaded with an edge (append and merge) and edge overwrites that remove or move a person's only edge; two .gqt cases covering mutations (one with two inserted people and one edge), a generated node-only load, every load csv mode, the CSV edge overwrite and the plan's source_cardinality line; the planner tests for OG-MF-107 and for the declared accesses; a validators.rs overwrite of 9,000 people that crosses the 8,192-source chunk boundary; a unit test of the fast-forward rule; and schema_apply.rs, where the refused apply publishes nothing and a new type with its required edge applies and is enforced from its first write. Two existing tests whose seeds relied on the gap now seed valid graphs. Locally the engine, planner, compiler and gqt-core suites pass; the GQT corpus passed except for wall-clock budget timeouts on unrelated cases while other builds saturated the machine, and workspace Clippy was not rerun after merging main because the disk was full, so CI's run is the check for that.

For review: the decision to challenge is that an update counts, not only an insert. A node an earlier version wrote below the minimum is refused the next time a write updates it, a property update or a Blob cell write included, until its edges are added, and a rebuild by export and load refuses it too; counting only new nodes would need an existence probe and leave such nodes writable forever. A writer that loads nodes before their edges breaks, so the note is breaking. OG-MF-107 also refuses an empty source type, because a data-dependent check at apply time would let schema plan and schema apply disagree. Explain JSON gains the source_cardinality check kind and scan access for an overwrite's cardinality under a minimum, additive under the same explain_version; the store format is unchanged, and no DST or cost test declares a @card minimum, so no golden line moves. There is no backing issue; the gap was found while reviewing the schema docs.

A non-zero @card minimum was counted only for sources whose edges a write
added, moved or removed, so a node written without its edges published
below the minimum, and an edge-table overwrite never recounted the
sources it emptied (the overwritten table's committed view is empty).

The unified evaluator now also counts every source node row a mutation,
JSONL load or merge writes, and reads the old sources of removed or moved
edges from the pre-write image. A `load csv` node plan carries the new
typed `source_cardinality` check, and the overwrite judge recounts the
sources an edge overwrite removes.

Schema apply refuses a new edge type with a non-zero minimum whose source
node type already exists (OG-MF-107): its existing nodes have none of the
new edges.
…a-c2c6f3

# Conflicts:
#	crates/omnigraph-gqt-core/src/load_plan.rs
#	crates/omnigraph-gqt/README.md
#	crates/omnigraph/src/validate/load.rs
#	docs/user/queries/explain.md
#	docs/user/schema/index.md
#	skills/omnigraph/references/schema.md
Keep the mutations page within its 350-line cap, spell the product
Omnigraph, fix a stale evaluate_cardinality doc line, and trim the
mutation case to two environments and one result query.

ragnorc commented Oct 31, 2026

Copy link
Copy Markdown
Contributor Author

Why

A non-zero @card minimum must hold for every source node a write publishes. Before this change, the check counted a source only when the write added, moved, or removed an edge of that type. A node written with no edge of the type could publish below the minimum. An edge-table overwrite has an empty committed view, so a source the overwrite empties was not counted. Schema apply could add an edge type with a minimum on an existing source type.

What

The change counts every source node a write inserts or updates when the minimum is greater than 0. An edge overwrite reads the replaced source column once, in bounded batches, and keeps the smallest source the delta leaves with no edge. Schema apply refuses a new edge with a minimum when that source type is already accepted (OG-MF-107). The change is breaking. A node with a non-zero minimum must arrive in the same mutation query or JSONL load as its edges. load csv publishes one table per statement, so it cannot add such a node.

How

Mutation, JSONL load, merge validation, and Blob writes share evaluate_cardinality. That function adds the written source ids to the affected set and probes committed edges. CSV node loads use a separate SourceCardinality arm. That arm calls card_violations with an empty plan. Both paths call committed_edges. That function builds one in_list of every key.

Impact

A writer that loads nodes before edges now fails closed. A writer that overwrites an edge table and leaves a source empty also fails closed. Rows written before this change stay in place until a later write inserts or updates the node or changes its edges. Schema plan and schema apply stay data-independent. A required property already uses that rule (OG-MF-103).

Recommendation

Revise. One blocking finding. Two optional findings.

Blocking

committed_edges builds one in_list of every key and keeps every matching (id, src). Uniqueness already chunks at UNIQUE_PROBE_CHUNK_KEYS (8192). The comment on that constant says a merge delta is not row-capped. KEYED_WRITE_MAX_ROWS is 8192 rows per batch. A load and a merge can contain many batches. A CSV sweep unions the id set of every partition in Extracted::prepare, then probes once.

This change puts every inserted or updated source id on that probe. evaluate_cardinality passes written into card_affected. The CSV arm passes extracted.ids. A large node reload or a merge, when the minimum is greater than 0, plans one filter of every id. The edge-overwrite path has the right shape. It keeps one bounded scan and one source id.

Admission for SourceCardinality charges one (id, src) pair per input row and one expression per row. The charge matches the minimum case of one pair per node. It leaves out the edges the probe returns. It leaves out one expression list that holds every key at once. Merge validation bounds the retained changeset at MERGE_VALIDATION_MAX_BYTES (32 MiB). That bound does not cover this probe result.

Chunk committed_edges the same way as the unique probe. On the node-only path the plan is empty, so a count per source is enough. That path can drop the edge ids.

Optional

  1. proven_fast_forward_needs_no_validation in crates/omnigraph/src/exec/merge.rs still treats a node-only pure insert as free of cross-table checks. The predicate does not read edge @card minimums. A write from this binary already passed evaluate_cardinality, so a same-binary fast-forward cannot create a new violation. A branch written before the upgrade can still place old source nodes on the target with no count. This change leaves old rows in place until a write touches them. Document that skip next to the predicate, or return false when the node type is the source of an edge whose minimum is greater than 0.

  2. The CSV SourceCardinality arm does not call evaluate_cardinality. The two sites share card_violations, card_affected, and evaluate_replaced_cardinality. The rule for which source rows count lives in both places. Five similar constraints would add five arms beside the evaluator. Have the CSV arm call the same helper.

Tradeoffs and liability

The change removes a path-dependent contract. The same nodes were valid or invalid according to whether the writer touched an edge. It adds a probe sized to the written node set, a second node-count site in the CSV judge, and a documented break for writers that load nodes before edges.

Efficiency of a large node reload is the quality this change serves less. Correctness of the published minimum stays first. Agreement between schema plan and schema apply stays with it. That order matches this contract. The overwrite path shows a bound is possible. The node probe should use a bound of the same kind.

These choices are sound.

  • load csv and a generated node-only load cannot add a new source under a non-zero minimum. One statement publishes one table. JSONL and a mutation can carry the node and its edges together. The changelog and the user docs say this.
  • OG-MF-107 refuses the edge when the existing source type has zero rows. A data-dependent check would let plan and apply disagree.
  • Rows from before this change stay below the minimum until a later write inserts or updates the node or changes its edges. A Blob put and a property update use the same changeset, so they recount. GQT cannot seed an orphan on the new engine. The insert path and card_affected cover the count.
  • A max-only @card(0..N) does not recount a node update. When the minimum is greater than 0, card_violations also checks the maximum. A node update can then fail an existing maximum violation.
  • The explain version stays 7. The new check kind is additive. The store format stays the same.
  • No DST scenario declares @card(. cost_table.txt stays the same. That is correct.
  • Open pull requests do not move this cardinality owner. Schema apply stays on main with one live branch. The live-branch apply RFC (rfc: schema apply with live branches #963) is still open. Polymorphic edges (rfc: polymorphic types with interfaces, unions and polymorphic edges #884) would later stress the single from_type assumption. This change does not need to take that on.

Validation

Head 15f35897ce0f847a0d45138db475c7be24994611. Merge base with origin/main is 1a3e7ed090710ce070b8dd06e385c4575f56c75b.

Head GQT, CARGO_TARGET_DIR=/workspace/target, checkout /tmp/og-1212-head:

cargo test -p omnigraph-gqt --locked --test gq_logic_tests card_minimum -- --test-threads=2 --nocapture

Result: 2 passed, 427 filtered out. card_minimum_counts_every_written_source and load_csv_card_minimum_counts_written_and_vacated_sources passed on the filesystem engine and on DST. DST replay matched.

Merge base, fresh target /tmp/og-target-base, checkout /tmp/og-1212-base. A first run with the shared /workspace/target reused the head binary. That run is discarded.

Same GQT command. card_minimum_counts_every_written_source.gqt failed at step 1. The mutation succeeded on the filesystem engine and on DST replay 0 and replay 1. The case expected @card violation on edge WorksAt: source 'bob' has 0 edges (min 1).

The full CSV case on the merge base failed as invalid_case. The base parser has no source_cardinality plan form. I removed the two --- expect plan sections in that isolated checkout only, then ran:

cargo test -p omnigraph-gqt --locked --test gq_logic_tests load_csv_card_minimum -- --test-threads=1 --nocapture

Result: failed at step 1. load csv of carol succeeded on the filesystem engine and on DST replay 0 and replay 1. The case expected the minimum-1 error for source carol. I deleted those temporary files. I did not push.

Head Rust, same target and checkout:

  • cargo test -p omnigraph-engine --locked --test integration jsonl_ -- --test-threads=2 --nocapture — 12 passed, 641 filtered out. Includes validators::jsonl_load_refuses_a_source_node_without_its_required_edge and validators::jsonl_edge_overwrite_recounts_the_sources_it_empties.
  • cargo test -p omnigraph-engine --locked --test schema_apply apply_schema_rejects_a_card_minimum_edge_from_an_existing_node_type -- --exact --nocapture — 1 passed.
  • cargo test -p omnigraph-engine --locked --test writes mutation_insert_edge_enforces_min_cardinality -- --exact --nocapture — 1 passed.
  • cargo test -p omnigraph-planner --locked --test integration load_plan::card_minimum_declares_the_replaced_source_scan_and_the_source_count -- --exact --nocapture — 1 passed.

CI on this head is green. GQ Logic Tests passed ordinary, served, dst, and dst-clippy. CI passed Test Workspace, clippy, rustfmt, S3, and Azure. DST passed the pinned suite. I did not rerun clippy locally.

Lance 13.0.0 scanner default include_deleted_rows is false. first_replaced_source uses a bounded scan and does not set that flag. Deleted edge rows stay out of the overwrite count. I did not run a deletion-vector fixture.

Limitations

I did not measure a large load. The blocking finding is from the code. Uniqueness chunks for this reason, and this change widens the key set to every written source. I did not execute schema apply on the merge base. The merge-base plan_edges has no minimum check. I did not run a deletion-vector fixture.

Inline comments

crates/omnigraph/src/validate.rs line 1445, side RIGHT:

written is every source node this write inserts or updates when min > 0. card_affected then sends that set to one committed_edges call on src. That call sits just below this hunk. committed_edges builds one in_list of every key and keeps every matching (id, src).

Uniqueness chunks at UNIQUE_PROBE_CHUNK_KEYS (8192) because a merge delta is not row-capped. The comment is on that constant in this file. KEYED_WRITE_MAX_ROWS is 8192 rows per batch. A reload or a merge can be many batches, so this plans one filter of every id.

Chunk committed_edges the same way as the unique probe. On the node-only path the plan is empty, so a count per source is enough. The overwrite branch above keeps one bounded scan and one source id. This probe needs a bound of the same kind.

crates/omnigraph/src/validate/load.rs line 309, side RIGHT:

This arm sends every sweep id through the same unchunked committed_edges. Extracted::prepare unions the id set of every partition first. A CSV node reload under a non-zero minimum plans one filter of every written id.

Admission in crates/omnigraph/src/engine/operators/validate.rs charges one (id, src) pair per input row. The charge leaves out the edges the probe returns. It also leaves out the single expression list of every key.

Optional: this arm repeats the node rule that evaluate_cardinality owns (written ids, empty plan, card_violations). Call that helper so the next change to which rows count has one edit site.

…ery route

The committed `@card` lookup built one `in_list` of every key and kept every
matching `(id, src)`, and the written-source rule widened its keys to every
node a write inserts or updates, so a node-table overwrite or a merge planned
one filter of every id. Sources are now counted in sorted filtered scans of at
most 8,192 keys each, streamed and kept as one count per source; by-id lookups
of removed and moved edges chunk the same way.

The `load csv` judge's cardinality and source-cardinality arms call the
evaluator's `evaluate_cardinality`, so which rows count has one edit site.

A fast-forward's pure node inserts skip validation only when no edge type
with a non-zero `@card` minimum starts at the type: such inserts are below
the minimum, which only a branch written before inserted nodes were counted
can hold.
@ragnorc

ragnorc commented Oct 31, 2026

Copy link
Copy Markdown
Contributor Author

Addressed in 805aec1.

Blocking, unchunked probe. Agreed. The written-source rule widened committed_edges to every node a write inserts or updates, and the key list was one unchunked in_list. Sources are now counted by committed_source_counts: it sorts the keys and probes them in filtered scans of at most 8,192 keys each (the uniqueness constant, renamed PROBE_CHUNK_KEYS). It streams the edges batch by batch and keeps one count per source. An edge the change removes or re-places is excluded while counting, so card_violations takes those counts and no longer receives the edge pairs. The by-id lookups of removed and moved edges (committed_edges_by_id) chunk the same way, and an id names at most one edge. The source_cardinality admission now charges one count per written node and no edge pairs. One correction to the scenario: a keyed load (append or merge) caps a table at 8,192 rows, so only a node-table overwrite and a branch merge reach a larger written set. The new test, validators::card_minimum_counts_written_sources_across_probe_chunks, overwrites 9,000 people. With one more edgeless person, who sorts into the second chunk, the overwrite is refused for that person. Without that person it passes, which needs the second chunk counted.

Optional 1, fast-forward skip. Taken as the code change rather than a note. proven_fast_forward_needs_no_validation now delegates to node_inserts_need_no_validation, which returns false when an edge type with a non-zero @card minimum starts at the node type. The predicate holds only when every candidate is a node table taking pure inserts, so such inserts are new sources with no edges. They are always a violation, and only a branch written before this change can hold them, so the check costs nothing on same-binary merges. A unit test covers each case: plain, @range, a minimum source, a minimum target and a max-only source. docs/dev/merge.md states the rule.

Optional 2, second rule site. Both CSV arms now call evaluate_cardinality, which takes the plan and the written and deleted source sets. The edge arm still checks the plan's scan access before delegating.

Local runs pass for the engine integration suite (validators, merge fast-forward), schema apply, writes, the engine and compiler lib tests and the planner load-plan tests. The two @card GQT cases passed every assertion. Their final attempt ran over the 10 s budget only because other builds held the machine at a load near 40. CI reruns them, along with clippy.

ragnorc commented Oct 31, 2026

Copy link
Copy Markdown
Contributor Author

Why

The previous review found one blocking problem. The written-source rule sent every inserted or updated source id through one committed_edges call. That call built one in_list and kept every matching (id, src) pair. A node-table overwrite and a branch merge can hold more source ids than one keyed batch of 8,192 rows. The retained set then grew with the edges of those sources, not with the source count.

What

Commit 805aec13a379b5acf82347dd4751aff557407ef2 counts those sources in bounded probes. It keeps one count per source. The load csv judge uses the same evaluator. A fast-forward skips validation only when a pure node insert cannot break a non-zero @card minimum.

This delta is one commit. Its parent is 15f35897ce0f847a0d45138db475c7be24994611. It does not merge main.

How

committed_source_counts sorts the affected source ids. It probes them in filtered scans of at most PROBE_CHUNK_KEYS (8,192) ids. That is the old uniqueness chunk size, now shared. Each scan streams batches. An edge whose id the change removes or re-places is left out of the count. card_violations adds the coalesced delta at the last source. It no longer receives edge pairs.

committed_edges_by_id chunks the same way. An id names at most one edge, so that result stays within the changed id list. The dataset is opened once per call. Every chunk of that call reads the same snapshot.

evaluate_cardinality is the one rule. Mutation, JSONL load, and merge already call it. Both load csv arms call it now. An edge overwrite still takes evaluate_replaced_cardinality when min > 0 and the table is in overwritten. That path keeps one bounded scan of the replaced source column and one source id. The CSV judge sets overwritten to the table it loads, so the edge overwrite still reaches that path.

Schema apply does not probe written rows. OG-MF-107 still refuses a new edge with a minimum when the source type is already accepted. This delta does not change that door.

proven_fast_forward_needs_no_validation now calls node_inserts_need_no_validation. The skip is false when any edge type with min > 0 starts at the node type. A pure insert of that node is under the minimum. Only a branch written before inserted nodes were counted can hold one. When the skip is false, merge validation builds the changeset from the pure inserts and runs the same evaluator.

Status of the earlier findings

Blocking, unchunked probe: fixed. The fix is in the committed lookup that owns the probe, not in one caller. The affected class is every write whose source set can exceed one chunk: a node overwrite (JSONL or CSV), and a branch merge. A keyed append or merge refuses a table above 8,192 rows, so that path stays in one chunk. The code still chunks it. An edge overwrite does not use this probe. Its committed view is empty, and the replaced-image scan stays bounded. Schema apply does not use it.

The count is correct across a chunk boundary. Sources are sorted, and the chunks do not overlap. A source with no returned edge is absent from the map, and the check treats that as zero. The reload of 9,000 sources that keep an edge is the proof that the second chunk is counted. If that chunk were skipped, those sources would look empty and the reload would be refused.

Optional 1, fast-forward skip: taken in code. The predicate returns false for a minimum source. It stays true for a plain node, a minimum target, and a max-only source. docs/dev/merge.md states the rule. The unit test covers each case.

Optional 2, second rule site: taken. Both CSV arms call evaluate_cardinality. The edge arm still requires a scan access before an overwrite with min > 0.

Findings

No new blocking finding. No new optional finding.

Tradeoffs and liability

The change removes the uncapped filter and the retained edge pairs on the written-source path. A source with many edges now adds one to a counter. Peak retained state is one count per affected source, plus one chunk of literals. That matches invariant 11: hot-path memory follows the working set. Invariant 8 still holds. A source under the minimum fails with the same @card error.

The by-id lookup still keeps one pair per changed edge id. That set is the delta the validator already holds. It is not the edge table.

The new cost is one filtered scan per 8,192 sources. Edge tables carry a BTREE on the source column (table_ops builds BTree on edge id, src, and dst). A fragment the index does not cover is still scanned. Missing index coverage can change cost. It does not change the count. Lance 13 Scanner defaults include_deleted_rows to false (scanner.rs around the scanner constructor). This scan does not set the flag, so a deleted edge stays out of the count.

The fast-forward change adds a catalog check and, when it hits, a full validation of pure node inserts. Same-binary merges cannot hold the bad rows. The cost appears for a schema that already has a minimum, and only when every candidate is a pure node insert. A pre-change branch that fast-forwards such inserts is now refused. That is the intended break, applied at the merge door that used to skip the check.

evaluate_cardinality is the single owner of which rows count. PROBE_CHUNK_KEYS is the shared chunk bound. The three probe loops still differ: unique tuples are matched in Rust, by-id keeps pairs, and source counts stream. A fourth wrapper around those loops would not remove a drift risk. The drift that mattered was which rows count, and that now has one function.

Five more checks of this shape should add a caller of evaluate_cardinality, not a new probe. Open pull requests do not move this owner. Schema apply on live branches (#963) and polymorphic edges (#884) are still open. This delta still assumes one from_type. It does not need to take that on.

Efficiency of a very large source set is the quality served less here. Each chunk is a separate scan. Correctness of the published minimum stays first. That order matches this contract.

Validation

Head 805aec13a379b5acf82347dd4751aff557407ef2. Merge base with origin/main is 1a3e7ed090710ce070b8dd06e385c4575f56c75b.

Local head, checkout /tmp/og-1212-head, CARGO_TARGET_DIR=/workspace/target:

  • cargo test -p omnigraph-engine --locked --test integration validators::card_minimum_counts_written_sources_across_probe_chunks -- --exact --nocapture --test-threads=1 — 1 passed in 0.46s. The overwrite of 9,000 people who keep a WorksAt edge is accepted. The same image plus p99999, who has no edge and sorts into the second chunk, is refused with @card violation on edge WorksAt: source 'p99999' has 0 edges (min 1).
  • The same integration binary, exact tests: validators::jsonl_load_refuses_a_source_node_without_its_required_edge, validators::jsonl_edge_overwrite_recounts_the_sources_it_empties, validators::cardinality_rejected_on_mutation_insert_edge, validators::merge_load_edge_src_move_rechecks_vacated_src_cardinality, validators::merge_load_duplicate_edge_id_counts_once_per_card — each 1 passed.
  • exec::merge::fast_forward_validation_tests::node_inserts_skip_validation_only_when_they_can_break_nothing — 1 passed.
  • schema_apply apply_schema_rejects_a_card_minimum_edge_from_an_existing_node_type — 1 passed.
  • writes mutation_insert_edge_enforces_min_cardinality — 1 passed.
  • planner load_plan::card_minimum_declares_the_replaced_source_scan_and_the_source_count — 1 passed.
  • cargo test -p omnigraph-gqt --locked --test gq_logic_tests card_minimum -- --test-threads=1 --nocapture — 2 passed, 427 filtered out. card_minimum_counts_every_written_source and load_csv_card_minimum_counts_written_and_vacated_sources passed on the filesystem engine and on DST. DST replay matched.

Local merge base, checkout /tmp/og-1212-base, CARGO_TARGET_DIR=/tmp/og-target-base. A first run reused copied head artifacts. That run is discarded. After cargo clean of the local crates, the same commands compiled /tmp/og-1212-base sources.

  • The chunk test does not exist on the base. I added it in that checkout only. It failed. The overwrite that adds p99999 was published: nodes_loaded: {"Person": 9001}. The test expected the minimum-1 error. I restored validators.rs. I did not push.
  • The two GQT cases are not on the base. I copied them into that checkout. I removed the two --- expect plan sections from the CSV case so the base plan grammar could run it. Both failed at step 1 on the filesystem engine and on DST replay 0 and replay 1. The mutation of bob succeeded. load csv of carol succeeded. Each case expected the minimum-1 error. I deleted those files. I did not push.

CI on this head is green. GQ Logic Tests, CI, and DST passed. I did not rerun clippy, S3, or Azure locally.

Limitations

I did not measure a source with millions of edges. The bound is from the code: the scan streams, and the map stores one count. I did not run a deletion-vector fixture. The scanner default cited above is the basis for that claim. I did not execute schema apply on the merge base. The head test still refuses the new edge, and this delta does not edit that path.

Recommendation

Ready. The blocking finding is fixed at the probe that owns it. Both optional findings are taken. This delta adds no new defect that I can show.

…a-c2c6f3

# Conflicts:
#	skills/omnigraph/SKILL.md
#	skills/omnigraph/references/schema-design.md
@ragnorc
ragnorc enabled auto-merge October 31, 2026 22:48
@ragnorc
ragnorc added this pull request to the merge queue Oct 31, 2026
Merged via the queue into ModernRelay:main with commit d88f977 Nov 1, 2026
32 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant