TinkerPop Deviations

This page lists every place where Graphersal knowingly differs from Apache TinkerPop 3.8.2, and every known gap that is deliberately not fixed (yet). Pass/fail numbers are on TinkerPop Compliance; the backlog is section 4 ("TinkerPop backlog") of roadmap/first-public-release.md. Each row links to the page that explains the behaviour for users.

The scenarios of the rows select() of an undeclared label, order() of property elements, multi-properties and meta-properties, property(k, null), and asString() of a map, and the families excluded for good (match(), the extra number types, io(), the graph algorithms), are excluded from the compatibility numbers: see Deliberate incompatibilities.

Excluded for good

These TinkerPop features are not planned. Their scenarios are listed by id in INCOMPATIBILITIES (crates/graphersal/tests/tinkerpop/harness/scope.rs) and are outside the compatibility scope.

CodeFeatureWhy
match-stepmatch()declarative pattern matching will come through a Cypher/GQL front end on the same engine instead; match is also a reserved Rhai word
extra-number-typesGType.BYTE/SHORT/BIGINT/BIGDECIMAL/CHAR/BINARY, BigInteger literalsthe number model is int64/float64; see Number widths
io-stepio(), read(), write(), the IO tokensGraphSON and GraphML import and export is Graphersal's own API (GraphSource::from_graphson, from_graphml, import_graphson, import_graphml, the CLI's --graph), not a traversal step (GraphSON)
graph-algorithmspageRank(), shortestPath(), connectedComponent(), peerPressure() and their token classesGraphComputer vertex programs, not planned for the traversal language (the scenarios are @GraphComputerOnly as well)

Dates (asDate, dateAdd, dateDiff, DT, GType.DATETIME) are not in this list: they are planned for 0.2.0 and stay unsupported until then.

Deliberate differences

AreaTinkerPopGraphersalDetails
negate() / P.not(p)Compare.negate() flips the operatorplain complement of the resultPredicates
Element idsnumeric ids order numericallyids are strings, "10" sorts before "9"; hasId(P.gt(3)) (an order predicate on a number) is an error whose help shows hasId(P.gt("3")); equality predicates (eq, neq, within, without) take numbers and compare their textPredicates
hasId(id, ids...)a later list stays one idthe same; a list flattens in first position only, null is an id that matches nothingId Arguments
length()counts UTF-16 code unitscounts Unicode scalar values; an emoji is 1, TinkerPop gives 2Type Conversion
asNumber(GType.INT|LONG) of a floatNaN, infinity or out of range wraps or narrows to the target widthtruncates toward zero; NaN, infinity and out of int64 range are errors (one int64 width)Type Conversion
asBool() of a stringtrue/false in any letter casethe same; no other text converts ("1", "yes" are errors)Type Conversion
trim()/lTrim()/rTrim()Java String.trim() strips characters up to U+0020strips Unicode White_Space (U+00A0, U+3000 too)Type Conversion
choose()/branch() with Pick.anyruns for a produced valuealso runs when the selector yields nothingOption Keys
Branch children of union/choose/branchone traverser at a timestateful children run once over the whole stream; output is branch-majorBranch Children
where(traversal) labelsstart/end label rules of configureStartAndEndStepsthe same rules; an unresolved label filters outwhere() Labels
filter(traversal)TraversalFilterStep: a plain existence test, as() in the child is an ordinary labelthe same (no deviation); it is not an alias of where(traversal), which reads a leading/trailing as() as a scope label and accepts a predicatefilter() and where()
Second by()accepted by some stepsInvalidModulator for aggregate, store, groupCount, valueMap, dedupThe by() Modulator
select() of an undeclared labelselect("a") and select(Pop.x, "a") where no as("a") or side-effect key exists silently filter the traverseran error before execution that names the label and shows how to declare it (a label declared but only sometimes set still filters); a silent filter hides typos (a map-producing step upstream, such as valueMap() or inject(), disables the check, so valueMap().select(Pop.first, "a") filters as in TinkerPop)Select with Pop, Deliberate incompatibilities, help() of the error
order() of properties() elementsProperty elements have their own total order (key, then value) and property idsordered by value only; no property idsProperty Elements
by(T.id) / by(T.key) / by(T.value) over properties() elementsT.id is the opaque property id; T.key/T.value are Property tokensby(T.label) orders by key (as in TinkerPop); by(T.id) is an error (no property ids); by(T.key)/by(T.value) read the key and the value as in TinkerPop (both also read a map entry, a Graphersal extension; any other value is a cast error, as in TinkerPop)Property Elements
Paths::g_V_shortestpaththe expected 24 rows depend on PathRetractionStrategy dropping the repeat-loop v labels in the second iteration, which filters every distance-3 candidatethe full shortest-path set, 30 rows: the six extra rows are the true distance-3 shortest paths (vadas-peter, vadas-ripple, ripple-peter, both directions). Verified on TinkerPop: the scenario's query returns 24, the same query with a no-op filter(__.path()) at the start of the repeat body (which disables the strategy) returns 30. An upstream TinkerPop issue is being preparedDeliberate incompatibilities
Property null@AllowNullPropertyValues / @DisallowNullPropertyValues flavoursnull is a real stored value; writing it never removes a property; use remove_property() to removeUpserts, Removing Properties
Multi-properties, meta-propertiesCardinality.list/set, properties on propertiesone value per key; list/set writes fail with UnsupportedCardinalityUpserts, Property Elements
has(key, ..) / hasNot(key) on a property elementtests the property's meta-properties (an edge property has none)tests the key inside the property's object value (properties("meta").has("a", P.eq(1)) reads meta.a, never the owning element's a); a non-object value is a cast errorProperty Elements
asString() of a mapJava Map.toString ({name=[marko]})the JSON-like physical textProperty Elements
Set side effectsa real Set value typeno set value type: cap() returns an array in first-seen order, members compare by valueSet Side Effects
tree("a")progressive tree, bulk countsthe tree grows with the stream, bulk is ignored (see the gaps below for local())tree() Side Effect
repeat.order = dfsdoes not exista Graphersal addition; a limit() in the body then counts per traverserRecursive Traversals
Map keys 0.0 / -0.0different keys (Double.equals)the same key (-0.0 is canonicalized when a number is built)Maps With Non-String Keys
Map with a non-string key as a property valueTinkerGraph stores any valuenever storable (InvalidPropertyValue); a string-keyed map is storableMaps With Non-String Keys
group()/groupCount()/tree() vertex or edge keythe live element, compared by ida snapshot (element map) materialized once per result; equal within one resultMaps With Non-String Keys
sideEffect(traversal) and bulkthe child runs once per traverserthe child runs once per unit of a bulk-n traverser (the engine-wide bulk rule); the stream passes through unchangedBulk and Barriers
profile() metricsTraversalMetrics with camelCase keys (traverserCount, elementCount, percentDur, dur) and internal step idssnake_case keys (traverser_count, element_count, percent_duration, duration_ns) plus our fields (calls, count_in, optional timing/loops/memory), ids are static plan positions <step>.<child>.<step> shared with error locations; step names are the canonical DSL rendering of the step (has_label("person"), v(labels: ["person"]).count(); g.with("render.spelling", "camel") gives hasLabel("person")), not the step class names (HasStep([~label.eq(person)])); a failing profile() is an error, execute() (ours) returns results, error and metrics of one runProfiling and execute()
limit()/count() in a repeat() bodycounts across the whole loopcounts per iteration (BFS) or per traverser (DFS); only dedup() has loop-wide stateRecursive Traversals
cap() of subgraph()a graph valuea graph value (a TraversalGraph snapshot, empty when nothing was collected): selected edges, both endpoint vertices, ids, vertex label sets and all properties at full depth; the stored schema is copied. In the Rust API next()/to_list() list it as {"vertices", "edges"} (use to_graph() for the graph)Set Side Effects
to_graph()not in TinkerPopa Graphersal terminal: subgraph("sg").cap("sg") without a label; a stream of anything but edges is an errorSet Side Effects
hasLabel() without argumentsnot in TinkerPop (hasLabel takes at least one label or a predicate)a Graphersal extension: keeps the elements that have a label (a vertex with a non-empty label set, an edge with a label); not(__.has_label()) selects the unlabeled ones; hasLabel(null) and an empty list still match nothingLabel Steps
subgraph() over non-edges, subgraph() without a labelnot specified here (vanilla fails with a cast error on a vertex; unverified, no network)an explicit error with a help naming outE()/inE()/bothE(), and for the missing label subgraph("sg")/to_graph()Set Side Effects
withSideEffect("sg", graph) as the subgraph() targetany grapha graph value of this engine, a TraversalGraph (a host graph of another storage fails with Unsupported; not the traversed graph: it fails instead of deadlocking); an existing vertex id is reused, an existing edge id skipped, the target's own schema keptSet Side Effects
Script expression depththe JVM stack decidesa Rhai expression-depth limit (128 expressions, 64 in a function); a chain of about 124 steps is rejected cleanlyQuery Limits
Number widthsbyte, short, int, long, BigInteger, BigDecimalint64, float64 onlybelow
conjoin() of a list holding a list or a mapwrites Java's toString ([a, b], {k=v}) for the elementa cast error: Graphersal writes no Java formats as data text (the rule of asString() of a map); conjoin() or unfold the inner list first; no suite scenario covers itconjoin
format() of a list or map valuewrites Java's toString for the valuea cast error, as for conjoin(); no suite scenario covers itformat
TextP.regex()/notRegex() dialectjava.util.regexthe Rust regex crate's own dialect (syntax); the individual differences are not listedPredicates
Random draws (coin(), sample(), Order.shuffle)SeedStrategy seeds Java's Randomg.with("random.seed", n) seeds the execution's own generator (Rust rand): reproducible per Graphersal version, but not the same draws as TinkerPop for the same seed; the SeedStrategy scenarios stay out of scope with withStrategies()sample, Execution Options
sample(n).by(weight)weights any number; with only zero weights left the sampling loop does not endthe weight must be a number of at least 0 (ValueError::SampleWeight otherwise); a zero-weight traverser is never drawn, so fewer than n can come back; each draw is exactly proportional to the remaining weightsample
P.typeOf(name)any type name registered in TinkerPop's type cache (custom types included)a fixed list of names: String, Integer, Long, Double, Float, Boolean, UUID, Number, List, Map, Vertex, Edge, Path, Graph (every Java simple class name TinkerPop registers for a GType Graphersal has); Integer is int64 and Float is float64 (one width each)Predicates
min()/max() over incomparable valuesa ClassCastException for mixed kinds; vertices and edges order by ida mix of kinds (number and string, boolean and number) fails with a cast-style error naming both kinds; elements, paths and collections are not ordered by min/max and fail the same wayLocal Scope
int + float in sum/min/maxNumberHelper widens to doublethe same: the result is a float even when the extremum came from an integer; integers beyond 2^53 lose precision when widened; integer-only streams stay exact and overflow raisesLocal Scope
path()/simplePath()/cyclicPath() from()/to()Path.subPath(from, to): last carrier of each labelthe same; an unknown label or a to() before the from() is an error (TinkerPop raises too, text differs); a label list is an InvalidModulatorPath Windows
by() of path()/simplePath()/cyclicPath() yielding nothingdrops the traverser (without ProductiveByStrategy)the same, for all three stepsPath Windows
where(P.gt("a")) with by(); ring order of a composite predicatelabel operands take ring entries in the order TinkerPop's connective handling visits them (not verified)textual order of the predicate, independent of short-circuiting; an unproductive by() drops the traverser, also under not()where() Start and End Labels
group("a")/groupCount("a") on a key that holds another side effectthe map is merged into whatever a holds (withSideEffect("a", [:]) seeds it)an error naming the key: only group steps that reduce a key the same way may share a side-effect key; a reducing value traversal is re-run over all members of a grown key at the end of each step execution, so readers in the same traversal see reduced valuesgroup
dedup("a", "b") with a label that is not on the pathgetSafeScopeValue raises an errorthe traverser is filtered out, like an unproductive by()dedup
addE() endpoint that is no path label and no side-effect keythe select(key) lookup failsthe name is looked up as a vertex id (to("2"))addE() Endpoints
addE().to(["2", "3"])one endpoint per from()/to()one edge per nameaddE() Endpoints
addE() endpoint idsvertex id of the graph's id typecompared as strings (2 is "2")addE() Endpoints
id()/label() of an element removed earlier in the same query (as("a"), aggregate(), a path, a lazy values()/id() read after the drop)TinkerGraph still returns the id and label it keeps on the removed element object; properties and edges are goneproperties and edges are gone too, but id(), label(), materialization and writes fail with GraphError::ElementRemoved: a generational handle no longer has any data (the freed slot may hold another element), and it never resolves to that other elementDropping Elements
A traversal that fails after it changed the graph (a failing step, a schema violation, a resource limit, evaluationTimeout)TinkerGraph without transactions keeps the changes made before the errorevery traversal is one unit (implicit auto-commit): the failure rolls back all its changes, so nothing of it is applied; in a script the unit is one traversal (a host may make the whole script one unit, and the Rust API has explicit transactions and dry runs)Transactions
outV()/inV()/bothV()/otherV() of an edge removed earlier in the same queryTinkerGraph's removed edge object still references its endpoint verticesempty, like every other adjacency of a removed element (the generational handle has no data left)Dropping Elements
Explicit transactionsg.tx() with commit()/rollback() on a transactional graph (TinkerGraph itself is not transactional)no tx() in the DSL (0.1.0); the Rust API has Transactional::transaction(|g| ..) for every storage (a closure: Ok commits, Err rolls back, every traversal inside is a savepoint), transaction_with (commit metadata) and dry_run (always rolled back, returns the ChangeSet); a host can make a whole Rhai script one unit (eval_value_atomic); automatic ids of rolled-back elements are never handed out againTransactions
Mutation eventsEventStrategy with MutationListeners, called per mutation after it happenedper committed unit: CommitHook::before_commit (may veto, the unit is rolled back), after_commit, after_rollback, each with the whole ChangeSet (before and after values); no per-mutation eventsTransactions
id() of a property element (properties().id())an opaque property idan error, Graphersal has no property ids (key() and element().id() work); Orderability::g_V_properties_order_id stays failingProperty Elements
label()/hasLabel() of a propertya vertex property's label is its key; an edge Property is no elementthe same for properties() elements of vertices; edge properties and values() handles are cast errorsProperty Elements
valueMap(true, ..) token keysthe T.id/T.label tokensthe strings id and label; a property of that name makes a second entry with the same keyProperty Elements
remove_property(keys...)not a TinkerPop step (removal is properties(k).drop())Graphersal extension: removes keys (none = all) of vertices/edges and emits the same element; rejected for a property in its label's required list in open/closed schemasRemoving Properties
add_label(..), drop_label(..), set_label(..)not TinkerPop steps (labels are immutable)Graphersal extensions: change the label set of a vertex or the label of an edge and emit the same element; checked by an open/closed schemaadd_label, drop_label, set_label
valueMap(null)ambiguous single-null varargsa null key is dropped, so a lone null means all keysProperty Elements
Operator integer overflowsum/minus/mult promote int to long and long to BigIntegerint64 only: overflow is an OperatorFailed errorSack and Operators
Operator.sumLong overflowwraps around (two's complement)an OperatorFailed errorSack and Operators
Operator.addAll of a list and a scalar3.7.2 throws, 3.8.2 appendsappends the scalar (the 3.8.2 feature files decide)Sack and Operators
Operator null operandNumberHelper: sum(null, x) is null, sum(x, null) is xthe same (kept deliberately); min/max/and/or take the other operandSack and Operators
Sack value is a vertex or an edgeany object is a valid sacka cast error (help names by(T.id)); use an id or a propertySack and Operators
Sack split and Supplier initial valueswithSack(Supplier) clones per traverser, a UnaryOperator splitsnot supported; a container sack is shared by handle and never modified in placeSack and Operators
Merging traversers that carry a sacka sacked traverser never merges without a merge operatorequal-sack traversers merge (invisible after bulk expansion); different sacks never doSack and Operators
sack(Operator) arithmetic wideninga sack of 127b plus 1b becomes a long, Long.MAX_VALUE + 1 a BigIntegerint64/float64 only: the result is an int64, and an overflow is an OperatorFailed error naming sackSack and Operators
sack(Operator) with a vertex or edge operandany objectOperatorFailed or a cast error (a sack never holds an element); by(T.id) gives the idSack and Operators
sack(BiFunction), barrier(Consumer) lambdasacceptednot supported: sack(Operator.sum) and barrier(Barrier.normSack) onlySack and Operators
by() of sack(Operator)the first result of the child (TraversalUtil.produce)the same; one by() only (a second is InvalidModulator)Sack and Operators
normSack numeratorsack * bulk / totalwith a merge operator the same; without one sack / total with total = sum(sack * bulk): equal sacks merge invisibly here, TinkerPop's bulk is 1 without a merge operator, so the answers agreeSack and Operators
Merge points with a sack merge operator or withBulk(false)LazyBarrierStrategy inserts barriers that merge sacks, so the result depends on where they landonly an explicit barrier() merges; the repeat() frontier and lazy_barrier() pass through (context.auto_merge); write repeat(__.out().barrier())Sack and Operators
bulk.merge=false with a merge operatornot an optiondisables every merge including the explicit barrier, so the sack result changes (diagnostic switch)Sack and Operators
withBulk(false)requirement ONE_BULKthe option bulk.one; same effect at an explicit barrier; additionally turns the automatic merge points offSack and Operators
Containers and Full paths under a merge operatorarrays and maps with equal content merge; path equality is by contentnever merge (containers have no key, paths compare by handle), so the operator does not combine thereSack and Operators
withSack(init, UnaryOperator), three-argument withSacksplit operatorsnot supported (error naming the merge Operator)Sack and Operators
aggregate()/store() into withSideEffect(key, init, Operator)3.7.2 gives the reducer one BulkSet per step execution (sum(1, BulkSet) throws); 3.8.2 behaviour is only visible in the feature filesone uniform rule per step execution: assign/addAll receive the collected values as one list, every other operator folds them one by one; inferred from the 24 sideEffect/Aggregate Operator scenarios (all pass)Sack and Operators
fold(seed, Operator), withSideEffect(key, init, Operator) lambdasfold(seed, BiFunction), withSideEffect(key, init, BinaryOperator) acceptedonly an Operator token (an error names the form); a bulk-n traverser applies the operator n times, no shortcutSack and Operators
select(Pop.x, ..) path recordingthe path keeps every labelled positionevery pop except Pop.last, and every select(.., traversal) (the key is only known at run time), records full paths and so skips bulk merging upstream (a cost difference only; results are the same)Select with Pop
select(Pop.mixed, "a"), oldest occurrence is a listPath.get(label) appends the later occurrences to the stored list in placethe same result on a copy; the stored value never changesSelect with Pop
math() of a trigonometric functionJava Math.sinthe platform libm; the last digit of a result can differ (sin(4.0) is -0.7568024953079283 here, ...282 in Java: map/Math::g_withSackX1X_injectX1X_repeatXsackXsumX_byXconstantX1XXX_timesX5X_emit_mathXsin__X_byXsackX passes in the harness because float results compare within 1 ulp, see 1 ulp float tolerance)Math

GraphSON

GraphSON 3.0 import and export is Graphersal's own API, not the io() step (which stays excluded). Where it differs from TinkerPop's GraphSONReader/GraphSONWriter (details and the full type table on GraphSON):

AreaTinkerPopGraphersal
Element idsany type (g:Int32 1)strings: an imported g:Int32 1 is "1"; export writes string ids, so TinkerGraph answers g.V("1"); with its LONG id managers also g.V(1) (recipe)
Multi- and meta-propertieslist cardinality, properties on propertiesseveral entries become one array, meta-properties the reserved _meta object property; export restores them from _meta only
Multi-label verticesno label setswritten as "A::B", one opaque label in TinkerGraph
Unlabeled vertex/edgealways labeledexported with the default labels vertex/edge, read back with them
Dates, BigDecimal, Class, enum tokens, vendor typesread into Java valuesskipped and listed in the import report, never converted (dates come in 0.2.0)
Edge to a vertex that is not in the filereadGraph failsskipped and reported (or connected to the target graph's vertex of that id)
Duplicate vertex idan errorthe first vertex wins, the second is skipped and reported
Vertex-property idskeptnot kept; export writes a file-wide g:Int64 sequence
g:Int32, g:Floatkept as suchint64/float64; export writes g:Int64/g:Double

jpath(..) keys wherever a property name is taken

Extension, not a deviation of an existing TinkerPop behaviour. values, properties, has (every form with a key), hasNot, valueMap, elementMap, every property by(), property and remove_property also accept jpath("a.b[1]"), a singular RFC 9535 path into a nested property value (Path Keys). A plain string is always a literal property name, exactly as in TinkerPop, and is never parsed as a path. A missing path is an absent property, not an error. In maps (valueMap, elementMap) the key of a path entry is the canonical path text ($.a.b[1]). json_path("a.b[1]") stays the standalone string-argument step with the same grammar. A side effect of the key conversion: has(null, v) reads the null key as the name "" and matches nothing, which is what the TinkerPop scenario g_V_hasXnull_testnullkeyX expects.

properties(jpath(..)) yields a path property element

Extension, not a deviation of an existing TinkerPop behaviour: properties("name") is exactly as in TinkerPop. properties(jpath("a.b[1]")) (a Graphersal path key, see Path Keys) yields TraverserValue::PathProperty, a materialized property element: key() is the canonical path text ($.a.b[1]), value() the leaf, label()/hasKey() see the path text, id() is the same deliberate error as for any property element, and a terminal materializes the leaf. Reason: a path has no stored property a lazy handle could point at. It is only produced by a path key, never by a plain string.

property(jpath(..), value) writes below a stored property

TinkerPop has no nested property write. Graphersal's property(jpath("a.b[1]"), v) (Path Keys) sets a value inside the stored property a: missing object keys are created, [n] replaces, [len] appends, anything else out of range or of the wrong type is an error (GraphError::InvalidPropertyPath) and never an overwrite. property(Cardinality, jpath(..), v) is an explicit error in v1. With a schema, the written leaf is coerced at its declared nested type and the resulting property is validated, Closed rejecting undeclared nested keys (Schemas and nested paths). A plain string key behaves exactly as in TinkerPop. A traversal as the key (property(select("a"), v)) is still not implemented.

remove_property(jpath(..)) removes below a stored property

TinkerPop removes a property with properties(k).drop(); Graphersal's remove_property(..) is an extension that keeps the element in the stream, and it now also takes a jpath(..) key (user's guide page "Path keys (jpath)"): an object key is removed, an array element is removed with shift, and a missing path or a type mismatch on the way is a no-op, never an error (more lenient than the write side, where a mismatch is an error). properties(jpath(..)).drop() is an error, because a path property element holds no reference to its owner; use remove_property(jpath(..)).

Known gaps, not fixed yet

Counts are in-scope scenarios (missing unless noted; the current numbers are in target/tinkerpop-report.md after a suite run); a scenario can wait on more than one gap.

GapEffectScenarios
repeat.order = dfs body limit()counts per traverser, not per iteration (a Graphersal addition, see above)none
local(tree("a")) needs a streaming executora step after the local() child sees the finished tree, not the progressive onenone known to fail
literal types the harness cannot express (datetime, set)translate; a {..} set literal is not mapped to Set.of(..) because Graphersal has no set value (only a set side-effect seed), so GType.SET would have nothing to matchabout 30 (byte/short/bigint/bigdecimal are excluded for good)
GType.SET, GType.TREE, GType.VPROPERTYmissing; no set, tree or vertex-property value type7
call() service steptranslate (call is a Rhai built-in function, so a future step needs a DSL name of its own); no service model (registry, tinkerpop.dc, list, search) yet; may come with the server20
@DisallowNullPropertyValues, multi-/meta-properties, valueMap().asString()deliberate, outside the compatibility scopesee Deliberate incompatibilities
subgraph() capnone: cap() is a graph value, the harness compares its {"vertices", "edges"} listing with the scenario's edge and vertex tables, and GType.GRAPH exists. The 4 Subgraph and 3 TypeOfGraph scenarios passnone

Why @DisallowNullPropertyValues stays unfixed: the 3 scenarios expect a write of null to remove the property, the 4 @AllowNullPropertyValues scenarios that pass expect it to be stored. Both cannot hold in one mode; the answer is a graph-level null mode (roadmap/first-public-release.md, section 4).

Number-width literals in the test surface

The harness maps 1b/1s/1n to an integer and 1m to a float, so these scenarios pass by value equality only. They do not show byte, short, BigInteger or BigDecimal support:

  • map/AsNumber.feature: g_injectX5bX_asNumber, g_injectX5sX_asNumber, g_injectX5nX_asNumber.
  • map/Sum.feature (12): g_V_injectX127b_1bX_sumXX, g_V_injectX_128b__1bX_sumXX, g_V_injectX32767s_1sX_sumXX, g_V_injectX_32768s__1sX_sumXX, g_V_age_injectX1000nX_sum, g_injectX1b_2b_3bX_sum, g_injectX1b_2b_3sX_sum, g_injectX1b_26b_3iX_sum, g_V_age_injectX1000nX_fold_sumXlocalX, g_injectX1b_2b_3bX_fold_sumXlocalX, g_injectX1b_2b_3sX_fold_sumXlocalX, g_injectX1b_26b_3iX_fold_sumXlocalX.
  • sideEffect/Inject.feature::g_injectXbigintBoundaryValuesX.

That is 16 scenarios. The 8 semantics/Equality.feature Primitives_Number_* scenarios pass the same way (byte, short, bigint and bigdecimal included); Sum of byte values that overflow a byte is the case where TinkerPop and Graphersal would differ without the collapse.

1 ulp float tolerance in the test surface

The harness compares two float64 result values as equal when they are equal or at most 1 ulp apart (NaN equals NaN, infinities exact, +0 equals -0, other signs must agree). The cause is that Rust's libm and Java's Math differ in the last digit of some results (sin(4.0)); Graphersal's arithmetic does not deviate from TinkerPop. The tolerance applies only to float vs. float result values, never to integers, ids, orderings or error cases. It lets the one map/Math sin scenario pass, which is a pass by tolerance, not proof of bit-identical trigonometry.

Which scenarios could expose negate()

Compare.negate() and a plain complement differ only for incomparable operands (NaN, a string against a number). The P.not/negate() scenarios of the suite (filter/TypeOf, filter/Where, filter/HasLabel, integrated/Recommendation) negate typeOf, within or comparisons of numbers, where both agree. The NaN scenarios of semantics/Comparability.feature (not(is(P.gt(NaN)))) use the not() traversal step, which negates the filter result, not the predicate. No vendored scenario exposes the difference.