Repository navigation
v0.14.1
GoGraph v0.14.1 — see CHANGELOG.md for the per-release entry.
0.14.1 — 2026-09-09
50 commits — 26 fixes, 7 documentation changes, 6 test changes, 3 features, 3
performance changes, 3 build changes, 1 chore and 1 merge. Counted at efd32fb9, the last
commit before release preparation; the release-preparation commits that carry this entry
are necessarily not in their own count. One sprint delivered this window — 357, clear
every open bug and chore, and ship GoGraph v0.14.1 — and 46 of its 47 tasks closed. The
forty-seventh, #2812 (an indexed-MERGE index probe), was carried to sprint 358 and is
not in this release. 184 files changed, 33 244 insertions, 1 679 deletions
(git diff --shortstat v0.14.0..efd32fb9).
Every one of the 46 closed tasks has exactly one commit in this window, and every commit
carrying a Refs line names one of them. That is a 1:1 mapping, established by comparing
the two sets rather than asserted: 46 task ids in the sprint, 46 distinct task ids across
the commit bodies, empty in both directions.
What the insertion count is, because 33 244 lines is not 33 244 lines of engine.
Bucketed by path from git diff --numstat v0.14.0..efd32fb9:
| bucket | files | insertions | deletions |
|---|---|---|---|
*_test.go |
93 | 20 921 | 374 |
*.go (non-test) |
71 | 9 343 | 745 |
docs/ (prose) |
9 | 1 980 | 341 |
root Markdown (README.md, knowledge-model.md) |
2 | 635 | 193 |
.rmp, cmd/kgverify/baseline.json |
2 | 112 | 1 |
| build, CI and release tooling | 5 | 106 | 2 |
release-notes/v0.14.0.md (the erratum) |
1 | 95 | 9 |
examples/ (one README) |
1 | 52 | 14 |
| total | 184 | 33 244 | 1 679 |
The engine change is 9 343 lines across 71 files, and the largest single bucket is tests —
which is the shape a bug-clearing sprint should have: every fix in this window carries a
regression test that fails on the old behaviour and passes on the new, by a sprint rule
carried over from sprint 350.
Three things define this release.
A MATCH on an indexed property could return a wrong answer, and did in the shipped
v0.14.0. The equality index seek was blind to writes the statement itself had made, so
it both lost rows the transaction had written and returned rows that no longer
matched — with no explicit transaction needed, on a single statement. #2814 declines the
index access path for the coordinates the transaction has dirtied, keyed per
(label, property), which turns a wrong answer into a slower correct one. This is the most
important item in the release and the reason it exists.
Four index-build paths could put a row in an index that the graph does not contain, or
leave one out. The backfill read uncommitted mutations, so a seek could return a
fabricated row (#2778); CREATE CONSTRAINT's backfill had the same defect (#2792);
FinishBuild's replay resolved against the live property bag, so a concurrent write could
fabricate an entry (#2793); and rewindConstraintDrop read live state, where the obvious
fix traded a fabricated entry for a lost one (#2799). Because a store written by an
affected build can hold fabricated payloads on disk, #2797 stamps an index-builder
epoch into the snapshot manifest and recovery now refuses to hydrate a payload written
before the fix, rebuilding the index once instead.
The durability path stopped calling three different failures clean. The checkpoint
self-sufficiency gate matched filenames and never verified the snapshot was readable, so a
WAL prefix could be discarded against a snapshot nothing could load (#2749); the readback
that fixed it covered the recovery reader but not its applier (#2780); ReplayWAL
reported corruption inside an already-committed frame as benign, contradicting its own
documented contract (#2794); and a producer op cap above the replay cap made committed data
unreplayable rather than merely rejected (#2530).
go.mod and go.sum are byte-identical to v0.14.0 — same pinned toolchain, same
dependency set at the same versions (git diff v0.14.0..efd32fb9 -- go.mod go.sum is
empty). There is nothing to report under a dependency heading and nothing is invented to
fill one.
No change is marked breaking — no commit in the window carries a BREAKING CHANGE
footer or a ! subject, verified over the full commit bodies rather than the subjects
alone. The exported surface did grow: 31 new exported declarations and 6 new exported
struct fields, with none removed. Under docs/semver.md a new exported
identifier is the MINOR trigger, so the delta is itemised in full under
Added below rather than summarised, and the two changes a caller can actually
feel — four exported default constants that changed value, and one exported function whose
declared return type changed — are set out under
Changed below, with the evidence for each.
This release is numbered 0.14.1 — a PATCH — by explicit decision, not by oversight.
Under docs/semver.md the 31 new exported identifiers are a MINOR trigger,
and PATCH is otherwise reserved for changes that preserve every previously-documented API
contract; four exported constants changing value does not preserve them. The number was
nonetheless kept at PATCH deliberately. So do not read 0.14.1 as a drop-in upgrade:
treat the Changed section below as the checklist a MINOR bump would have signalled. The
two items that will reach a caller silently are DefaultConnTimeout, DefaultTxTimeout,
DefaultMaxTxIdleTime and DefaultStatementTimeout, every one of which is now 0 —
disabled — where three of them were 30 s, 30 s and 5 s; and NewServer, which now
returns an error for a negative timeout it previously coerced to a default in silence.
The openCypher TCK gate is unchanged at 3 897/3 897. const tckExecutionBaseline = 3897
in cypher/tck/runner_test.go is untouched and no .feature file changed in this window
(git diff --name-only v0.14.0..efd32fb9 -- 'cypher/tck/features/**' is empty), so the
scenario population is the one v0.14.0 was measured against.
Read release-notes/v0.14.1.md before upgrading. There is
no data migration, but a store written by v0.14.0 or earlier rebuilds its secondary
indexes once on first open, and the Bolt server's four timeout defaults are now disabled.
Fixed
Cypher — the index access path returned wrong answers
- A
MATCHon an indexed property no longer loses rows the statement wrote, nor returns
rows that no longer match (#2814). The equality hash-index seek and the range seek both
read the committed index while ignoring the transaction's own pending mutations. Two
wrong answers followed from one cause: a row created or updated in the same statement was
missing from the result, and a row whose indexed property had been changed away from
the sought value was still returned. It needed no explicit transaction — a single
autocommit statement was enough. The fix tracks the(label, property)coordinates a
transaction has dirtied and declines the index access path for exactly those, falling back
to the label scan; correct and slower beats fast and wrong.EngineOptions.DisableIndexSeek
is the kill switch. Gated bycypher/index_seek_uncommitted_write_test.go, mutation-tested
from both sides: disabling the guard turns all four behavioural cases red, and a blanket
decline kills the precision cases, so "never seek on a write path" could not have passed
for it. - An index build no longer reads uncommitted mutations, so a seek cannot return a
fabricated row (#2778).CREATE INDEXbackfilled from the live property bag, which
includes writes no reader should see; a row that was never committed could therefore be
indexed, and a later seek would return it. The build now runs against a snapshot. CREATE CONSTRAINT's backfill had the same defect and is fixed the same way (#2792),
and its uniqueness seed is now taken from the same read its index is built from, so the
two cannot disagree.FinishBuild's replay answers with the graph it recorded, not the graph it finds
(#2793). Concurrent writes were reconciled against live state at the end of the build, so
a write that landed mid-build could fabricate an entry. The replay now resolves against
the recorded change log, which is bounded atMaxBuildLogChangesand reports overflow as
ErrIndexBuildOverflowrather than silently truncating.rewindConstraintDroprebuilds from committed state (#2799). Reading live state
fabricated an entry; the first fix considered traded that for a lost one, and both
failure directions are now covered by tests.CREATE CONSTRAINT … IS NOT NULLno longer succeeds over committed data that violates
it (#2798). Validation ran against live state only, so a violating row that was already
durable was not seen. It is now validated against committed state as well.- An explicit transaction now takes the schema gate, so its commit-time index fan-out
cannot escape a DDL barrier (#2738), and concurrent writes are reconciled into an index
built without excluding them.
Cypher — subqueries, projections and reported counts
COUNT { … }block form counts the rows its body returns (#2675).TranslateSubquery
dropped the body's trailingRETURN, so the count was taken over the wrong row set. The
same defect inexistsSubPlanis fixed separately (#2779), where the naive fix was
measured and refuted before the working one was found; theSemiApplyinner schema is
now isolated and the body'sRETURNtranslated.- Pattern predicates and
EXISTS/COUNTsubqueries now run inside a writing statement
(#2660). The write path never wired the subquery and pattern evaluators, so a writing
statement carrying either refused to run. FiveCOUNTblock-form placements that bypassed
evalRowentirely are fixed with it (#2781), including a property value, which is an
ordinary expression and may therefore be a subquery. - Eighteen Cypher write-op call sites no longer discard the error the WAL layer uses to
refuse (#2747). A refused write was reported as a success. - A relationship removal that took nothing out is no longer described as if it had
(#2734). On the directed engine,DELETE rover an undirected match pattern wrote twice
the WAL frames, half of them removing nothing. - A stale MCV equality estimate is demoted instead of being tagged exact (#2772). The
staleness veto was implemented for the histogram only, so a most-common-value hit was
reported asestExactfrom whatever snapshot was last published, however far it had
drifted — and sincev0.14.0that tag also decides how the figure renders, so a stale
per-value count was shown to a reader as ground truth (measured:Est.Rows = 1000against
10 rows). - A statistic now notices the rows that left the label, not just the writes (#2785). The
drift numerator isΔ + max(0, N0 − N_live):REMOVE p:Personmoved neither counter,
dropped the live count from 1 400 to 410, and was still taggedestExactat 100× wrong.
Persistence — the checkpoint, the WAL and the transaction commit
- The checkpoint verifies the snapshot is readable before discarding the WAL prefix
(#2749). The self-sufficiency gate matched filenames; it now reads the snapshot back. The
readback initially covered the recovery reader but not its applier, so the mapper is now
decoded, not merely parsed (#2780). - Recovery reports a discarded committed suffix instead of calling it clean (#2794).
Corruption inside an already-durable frame is surfaced asErrCommittedTxnCorruptOp,
which is what the function's own contract always promised. - Recovery refuses to hydrate an index payload written by an older builder (#2797). The
snapshot manifest carriesindex_builder_epoch, and a reader hydrates only on an exact
match withsnapshot.CurrentIndexBuilderEpoch; anything else — absent, older or newer —
rebuilds. This is what makes the #2778 and #2792 fixes reach a store that is already on
disk. - A producer op cap above the replay cap is clamped at the reopen handoff (#2530).
Committed data was made unreplayable rather than merely rejected;recovery.Resultnow
reportsMaxTxnOps. - A property value between 1 GiB and 4 GiB no longer commits durably only to block every
later checkpoint (#2750). Such a value is refused at commit, where the caller can act on
it, instead of at checkpoint time, where nobody can. - A nested property list is refused at commit instead of being silently dropped on replay
(#2783).encodeTxnListProphad noPropListcase, so a nested list survived the commit
and vanished on recovery; it is nowErrNestedPropertyList. - An edge-handle record count above the snapshot cap is refused at commit (#2784), for
the same reason: it committed durably and then blocked every checkpoint.
Instruments that could not fail on what they measured
Four test-layer defects, each of which made a green result meaningless rather than wrong.
- A DST regression assertion compared an unordered result against an ordered literal
(#2751), and a DSTcollectassertion compared against an ordered literal that its own
production checker sorts (#2786). Both now compare as the multiset the engine produces. TestSortDecorationArmFramespointed at functions that do not exist and a plan that
does not sort (#2782) — it could not fail on what it measured, and was red atHEAD.- The v1 CSR golden fixture was stale and its compatibility test could not notice
(#2752): it asserted the header and never the decoded payload. - The overload role mapped its only heavy-write family away, so nothing read the nodes it
created (#2736). - Three ordering claims in
graph/lpgthat carry the Isolation mandate now have tests
that defend them (#2775); they were asserted in godoc and gated by nothing.
Knowledge-graph fidelity gate
- The fidelity gate could not run at all, and the graph it guarded was half empty
(#2719).cmd/kgverifyshelled out tormp graph query, one of five subcommandsrmp
1.17.0 removed when it rebuiltrmp graphas a server/client pair, so every invocation
exited 3 — the harness could not conclude — and becauseci-kg-verifyis a member of
make ci, the sprint-close gate had been red for a reason unrelated to the graph. Repaired
and re-measured at0e7e982d: symbol coverage of the tree 43.1 % → 95.7 %
(12 518 → 27 826 declarations with a node),package-symbol-gap97 → 0 (modelled
packages at parity 8 → 105 of 105),symbol-absent327 → 0. Recorded in
docs/knowledge-graph-fidelity-2026-09-08.md
so the next cycle measures drift instead of rediscovering the gap. Those figures are
stamped to0e7e982dand are a measurement of that commit, not a standing property of the
branch — every later commit that adds or deletes a declaration moves them, which is why
the gate is a member ofmake cirather than a report anybody has to remember to read. - The
provenance-no-nodecheck no longer gatesci(#2796), because its population is
"declarations in files touched since the merge-base" and therefore grows with the branch:
the baseline was recorded against 1 837 declarations in 66 touched files, and twenty-two
commits intorelease/0.14.1it was 2 026 in 84, with the count risen from 1 082 to 1 187
without anyone adding un-noded code. It stays measured and printed on every run — at
the release tip it reads 257 of 3 137 declarations in 164 touched files against a
baseline of 160 — the 1 187 above being the figure of the day the exclusion was written,
not a current one. The debt it measures is not declared paid by the exclusion.
Examples
- The metrics example produces the q-error sample it asserts (#2795). #2772's staleness
demotion removed a metric the example asserted on, which turnedmake cired.
Added
The full exported-surface delta for this release. It was derived by diffing the two trees'
declarations — top-level declarations, struct fields, and a go doc cross-check — not read
off the commit log.
bolt/server
DefaultKeepAliveIdle(15 s),DefaultKeepAliveInterval(5 s) and
DefaultKeepAliveCount(3) — the TCP keep-alive budget now enabled on every accepted
connection, which is what replaces the read deadline as the liveness check (#2807).ErrNegativeTimeoutandNegativeTimeoutError(withFieldandValue,Errorand
Unwrap) — a negative timeout is now refused byNewServerrather than coerced to a
default.validateTimeoutsruns first and returnserrors.Joinof one error per
offending field, so a caller with two mistakes learns both at once (#2807).
cypher
EngineOptions.DisableIndexSeek(#2814) — turns off the equality index seek, so the
#2814 guard can be exercised and disabled from outside the package.EngineOptions.DisableExpandLabelPush(#2629) — turns off the far-endpoint label
admission, which is the control arm its benchmark needs.ExpandDstLabelPushCount(#2629) — how many times the far-endpoint label predicate
was admitted into the expansion. Safe for concurrent use.
cypher/exec
DstAdmitandExpand.WithDstAdmit(#2629) — the destination-label admission
predicate and the builder that installs it.ChunkOperator(#2629) — anOperatorthat can also be driven column-major. It
exists so a recogniser that ends up with no operator above the expansion — a
count(*)whose only predicate was pushed into the traversal — can return the wrapper as
the subtree's operator without a type assertion that cannot fail and therefore cannot be
tested.IndexBuffer.Pending(#2814) andCreateIndexOp.Catching(#2793) — the pending
index delta and the concurrent-change catch-up, exposed so the guards above are testable.
graph/index
Manager.BeginBuild,Manager.FinishBuild,Manager.AbandonBuild(#2793) — the
explicit index-build lifecycle that replaces building against live state.BuildLog,BuildLog.Len,BuildLog.Overflowed,BuildResolver,RegisterFunc,
ResolvedApplier,MaxBuildLogChanges(131 072) andErrIndexBuildOverflow(#2793) —
the bounded concurrent-change log the replay resolves against, and the typed error for the
bound being reached. The bound is explicit and surfaced, as the bounded-resource mandate
requires.btree.Index.ApplyResolvedandhash.Index.ApplyResolved(#2793) — apply an already
resolved change, so the resolution cannot happen twice against two different graphs.
store
snapshot.CurrentIndexBuilderEpoch(1),Manifest.IndexBuilderEpochand
snapshot.VerifyMapperDecodable(#2797, #2780) — the builder watermark a reader matches
before hydrating an index payload, and the decode check the checkpoint readback performs.
index_builder_epochis an additive manifest field, absent-means-never-hydrate, so
there is no manifest version step and no migration.recovery.ErrCommittedTxnCorruptOp(#2794) andrecovery.Result.MaxTxnOps
(#2530).txn.ErrNestedPropertyList(#2783) andtxn.CheckSchemaField— the refusals that
replace a silent loss.
Tooling
cmd/kgverify,make kg-verifyandmake ci-kg-verify(#2677, #2719) — the
knowledge-graph fidelity gate. Its oracle isgo/parser: the inventory of what exists is
built by parsing every.gofile, never by scanning text, which is the whole mechanism
by which a fabricated symbol node is caught.edgeTypeFilterFor, deleted from the code in
sprint 352, still appears in four Go comments and one Markdown document, so a text scan
reports it present while the declaration inventory correctly reports it absent. Twelve of
its twenty checks have a baseline of zero and failciimmediately.scripts/release_body.sh— composes the GitHub release body from this file's entry
for the tag, and exits non-zero when the tag has no matching entry. Untilv0.14.0every
release body was goreleaser's default — a one-line pointer plus an autogenerated commit
list — and ten of the seventeen published releases carried nothing else. The release
workflow now passes its output togoreleaser --release-notes.cmd/fmtfixture— a formatting fixture helper for the test layer.
Changed
-
The Bolt server's four timeout defaults are now
0, which means disabled, and a
negative value is refused (#2806, #2807).DefaultConnTimeout(was 30 s),
DefaultTxTimeout(was 30 s),DefaultStatementTimeout(was 30 s) and
DefaultMaxTxIdleTime(was 5 s) all ship at zero. The three-way contract is explicit:
zero disables the bound, a positive value sets it, and a negative one is an error. The
if opts.X <= 0 { opts.X = DefaultX }clauses are deleted rather than neutered,
because those clauses were precisely what swallowed a negative and substituted a number
the caller never asked for.The reason is not the size of the number. The server armed
SetReadDeadline(now + ConnTimeout)before every read of the post-handshake message
loop, and the reader sits in that read while the loop executes the client's own statement
— so the deadline ran against a busy server rather than an idle client. Measured under
#2806: withConnTimeoutat 100 ms, an autocommit statement of about 900 ms had its
connection closed at 110 ms. Both peers this module is measured against ship the
equivalent bounds disabled and use a different liveness check — PostgreSQL ships
statement_timeout,transaction_timeout,idle_in_transaction_session_timeoutand
idle_session_timeoutall at 0 and leans on TCP keep-alive; Neo4j ships
db.transaction.timeoutat 0 and uses protocol-level NOOP chunks. The bounds on count
are untouched —MaxConnections1 024,MaxOpenTxPerPrincipal2 048, horizon capacity
1 024 — so the bounded-resource mandate is still met by count, as PostgreSQL also meets
it. What disabling costs is under Security below, stated rather than
glossed. -
make cigainedci-kg-verify(#2677, #2796), which runscmd/kgverifywith exactly
two checks excluded —task-status-disagrees-with-rmp, whose count varies with time
becausermpis the authority, andprovenance-no-node, whose population varies with
branch length. Both remain measured and printed. This gate needs a running
rmp graph serve -r gograph: with nothing listening,kgverifycannot conclude and
make cifails for a reason unrelated to the change being gated. -
The GitHub release body is now composed from this file rather than from a
git log
excerpt (scripts/release_body.sh, wired in.github/workflows/release.yml). The
header:/footer:keys in.goreleaser.yamlapply only when goreleaser composes the
body itself and are now the fallback for a localgoreleaser releaserun. -
cypher/exec.NewColumnarExpandreturnsChunkOperatorinstead ofChunkProducer.
The declared return type changed, and it is not a source break:ChunkProducer
already embedsOperator, soChunkOperator(Operator+ChunkProducer) has an
identical method set and remains assignable to aChunkProducervariable. Verified by
compiling thev0.14.0caller shape —var p exec.ChunkProducer; p, ok = exec.NewColumnarExpand(e)— against this tree, unmodified, exit 0. No other exported
signature changed and no exported identifier was removed. -
The snapshot manifest carries a third additive field,
index_builder_epoch(#2797),
on the same terms as the twov0.12.0additions: absent means never hydrate, so no
version step and no migration.docs/semver.md's on-disk-format table records it.
Performance
Three changes in this window were made for performance, and several correctness fixes were
bought with a measured cost. Both directions are reported. The release-level campaign —
v0.14.1 against v0.14.0, three arms, interleaved, with its own noise floor measured
first — is docs/benchmarks/v0.14.1.md.
Measured at release level. Three arms — v0.14.0, the candidate, and a second
compilation of the candidate verified byte-identical by sha256 — interleaved with the arm
order rotated every round, n=6, load-gated, 192 invocations, 192 exiting 0, 0 malformed
result lines, over 150.9 minutes. Of 229 comparable rows, 25 reached p<0.05 and 11
survive adjudication against a same-code floor measured in the same rounds: 8
improvements and 3 regressions.
- The ORDER BY key hoist pays (#2662). Nine of ten
BoundedOrderrows move the same way;
four clear their band's bar:Topat −2.75 %, −2.56 % and −2.10 %,Sortat
−1.34 %. A whole family moving together is what a real change looks like. - The largest regression
v0.14.0published is given back.AllNodesScan_PerNodeAllocCost
goes 11.60 µs → 10.98 µs (−5.39 %,p=0.005);v0.14.0's own report named it as that
release's biggest cost at +7.11 %. Across six rounds the two candidate arms agree to 0.4 %
while thev0.14.0arm sits 5.4 % above both, every time. ExpandDir_InVsOut_Baseline/OUT_deg1_sources,v0.14.0's +3.46 % regression, is back to
parity at +0.10 %.- Three regressions, and they are not systemic.
ExpandOut_PerEdge_SingleSourceat
+6.54 % (78.10 → 83.21 µs) and +4.37 % (1.271 → 1.327 ms),
ExpandDir_InVsOut_Baseline/IN_deg1_sourcesat +5.81 %, andbolt's
MsgObserve_RealBackendat +2.13 % (68.17 → 69.63 ns). Eleven of the fourteen
Expand*rows are flat, so the cost is confined to the OUT single-source per-edge path and
the IN deg1-sources baseline rather than to the expansion machinery.cypher/exec/expand.go
changed in this window, which is the obvious candidate — attribution is a hypothesis, not
established, since no arm was built with the admission disabled. - The Cypher read path is unchanged at all five published concurrency levels (1, 8, 64,
256, 1024). Not one of the ten cells is significant, and the block's same-binary floor
geomean (−4.57 %) is larger than its effect geomean (+1.78 %). - The durable commit path is unchanged and still scales 119× from 1 to 256 writers. One
cell,goroutines=8at +3.56 %, is reported inconclusive rather than as a result. - The counting added in #2777 costs one allocation.
CountAllNodesgoes 27 → 28
allocs/op and 3 296 → 3 488 B/op with every sample equal in both arms, and the count
pushdown paths take +16 B/op.ReadTx_WriterLocktakes +2 allocs/op and +352 B/op
identically at every concurrency level — plausibly #2814's plan footprint, again a
hypothesis. No allocation count fell, and none rose by more than two. - The checkpoint readback is priced, and has no baseline because it is new. Reading the
snapshot back costs 14.73 ms against a 150.3 ms checkpoint — 9.8 % — and #2780's decode
pass adds 0.46 ms on top, 0.31 % of a checkpoint. That is what #2749 and #2780 cost to
stop a WAL prefix being discarded against a snapshot nothing can read. - The headline set is flat, which is what makes the rest credible: no file under
search/changed, andDijkstra_PostWarmup,Dijkstra_Large,Yen_K100and
Brandes_RandomGraphare all non-significant.BFSDirectionOpt_PowerLawat +2.92 %
(p=0.066) does not reach significance and is reported unresolved rather than flat. bolt/servercloses a gapv0.14.0named as its own: all 24 benchmarks were run,
including the elevenTxBookkeeping_*that report left unmeasured. None is significant.
cypher is sampled, not covered — 60 of 149 benchmark functions. The full sweep costs
454 s per arm against 204 s for the targeted set, which is disproportionate for a patch
release; graph/index{,/btree,/hash}, graph/lpg, store/snapshot, store/wal and six
further concurrency ladders were sized and dropped. Each omission, and its reason, is listed
in the report. Task-level figures measured while a change landed are labelled as such in
release-notes/v0.14.1.md and were not re-measured here.
Security
- Disabling
ConnTimeoutby default removes the only bound on an unauthenticated
client's connection slot (#2807), and this is a real exposure rather than a theoretical
one.DefaultHandshakeTimeoutbounds version negotiation only, so a client that completes
the handshake and then falls silent before LOGON now holds one of the 1 024 connection
slots and its two goroutines indefinitely. PostgreSQL bounds that window with a separate
authentication_timeout(1 minute by default); GoGraph has no such bound today. An
operator exposing this server to an untrusted network should setOptions.ConnTimeout
explicitly until it does. - A silent but live client is no longer reclaimed at all. TCP keep-alive reclaims a peer
that has vanished — a pulled cable, a dropped NAT binding, a host that went away without
a FIN — within roughly 15 s + 3 × 5 s. It does not reclaim a peer whose stack answers
probes while its process never sends another Bolt message. Where such a client left an
explicit transaction open, that transaction pins the MVCC reclamation horizon and one of
its 1 024 slots for as long as the connection lives. The remedy is the operator's, and it
is the one PostgreSQL offers for the same exposure: setOptions.MaxTxIdleTimeand
Options.ConnTimeout. - A refused write is no longer reported as a success (#2747), and corruption inside a
committed WAL frame is no longer reported as clean (#2794). Both were integrity failures
that presented as normal operation. go.modandgo.sumdid not move, so no dependency advisory is in scope for this
release.govulncheckruns insidemake ciand is part of the release gate.
Documentation
docs/persistence.md,docs/bolt.md,docs/cypher.mdanddocs/test-battery.mdwere
each re-read against the tree and corrected — 31, 19, 12 and 17 drifted claims
respectively (#2206, #2803, #2804, #2805). All four carry the
scripts/check_doc_freshness.shfooter and the gate reports zero errors.docs/knowledge-graph-fidelity-2026-09-08.mdis new: the measured state of the
knowledge graph, with the commands that produced every figure.docs/statistics-design.mdcarries two in-place corrections (#2772, #2785) rather
than silent rewrites, each stating what it did not close.docs/isolation-design.mdis corrected for the timeout changes: three claims that
derived from the old 5 sMaxTxIdleTimedefault no longer hold, and the abandoned-
transaction cost is restated for a default that reclaims neither the transaction nor the
connection.docs/explain-profile-honesty-audit-2026-09-05.mdgains the 2026-09-08 addendum
behind #2787, andrelease-notes/v0.14.0.mdcarries the matching erratum in place:
the columnar fusion chains do have a logical node and it does carry an estimate,
so two passages that blamed the estimator were wrong and are corrected where they were
read rather than quietly rewritten. The durable guard is
cypher/plan_estimate_columnar_gap_test.go.docs/wal'sWriterhealth contract is now stated (#2525):SyncBufferedreturns
nilon a poisoned writer, so a caller using it as a health probe is silently misled;
the godoc now names the method that does answer.
Compliance
- 100 % openCypher TCK-compliant at the execution level — 3 897 / 3 897 scenarios,
againstconst tckExecutionBaseline = 3897, which is untouched. No.featurefile
changed in this window, so the population is the onev0.14.0was measured against. This
release preserves the compliance; it does not extend it. - 100 % ACID-compliant, and this release is where several of those guarantees stopped
being merely asserted. #2749, #2780 and #2794 close paths on which a durability claim
could be made about state that was not durable or not readable; #2778, #2792, #2793 and
#2799 close paths on which an index could disagree with the graph, which is a Consistency
failure; #2814 closes a read that returned rows violating the statement's own writes,
which is an Isolation failure visible without any concurrency at all. Verified by the WAL
recovery tests instore/walandstore/recoveryand the deterministic crash-injection
battery ininternal/crashinject.
Notes
- Pre-1.0 stability. This is a
0.y.zrelease. The public Go API may change without a
major-version bump until1.0.0; pin the exact version you depend on. - Module path. The Go module path is
github.com/FlavioCFOliveira/GoGraphwith no/vN
suffix, which is Semantic-Import-Versioning-correct for a0.xline. - Adding a field to an exported struct breaks an unkeyed composite literal. Four
pre-existing exported structs gained a field —cypher.EngineOptions(two),
store/recovery.Resultandstore/snapshot.Manifest. A caller that writes
cypher.EngineOptions{…}with keys is unaffected; one that writes it positionally will not
compile. Keys are the idiomatic form and the one every example uses. - A store written before this release rebuilds its secondary indexes once on first open
(#2797), because its snapshot manifest carries noindex_builder_epoch. That is the
intended behaviour, not a fault: it is what removes any index payload the #2778 and #2792
defects could have fabricated. No data is lost and no migration step is required. - Examples are not part of the module.
examples/is an exercise harness; the module
neither imports nor depends on it. One example changed in this window
(examples/31_metrics_observability). - Gates not run for this release. The soak and nightly layers were not run, and this
release carries no production certification of its own — the most recent was taken at
thev0.11.0commit. Latency percentiles at the published concurrency levels remain
unmeasured.
Further reading
- Narrative release notes:
release-notes/v0.14.1.md - Measured benchmark report:
docs/benchmarks/v0.14.1.md - Changelog file at this tag:
CHANGELOG.md - Commits in this release: https://github.com/FlavioCFOliveira/GoGraph/commits/v0.14.1
Reproducibility: built from commit 9a6f756 with Go 1.26.