graphsense-lib 2.13.5
[2.13.5] - 2026-05-27
Library (v2.13.5)
Added
-
Async Cassandra reader can downgrade LOCAL_QUORUM to LOCAL_ONE on transient unavailability. New opt-in
CassandraConfig.consistency_level_fallback(defaultfalse). When set,GraphsenseFallbackToLocalOneRetryPolicydowngrades a LOCAL_QUORUM read to LOCAL_ONE on the FIRSTUnavailable/ReadTimeoutif at least one replica is alive — letting the web tier survive a rolling restart on RF=2. Strictly scoped: one downgrade per query, only LOCAL_QUORUM is touched (QUORUM/ALL/EACH_QUORUM left alone), writes never downgraded. Trade-off: read-after-write consistency is dropped for those reads. -
Optional Redis-backed file store and
/download/{token}route. Newweb/file_store.py(RedisFileStore, reusableFileStoreprotocol) holds files as TTL'd Redis hashes keyed by a 256-bit CSPRNG token; the route is a plain Starlette route, excluded from OpenAPI. NewFileStoreConfigonGSRestConfig:enabled(defaultfalse),redis_url,download_path(/download),ttl_s(1800),max_file_bytes(5 MiB),base_url,key_prefix,embed_resource. URLs derive fromX-Forwarded-*/Hostwithbase_urloverride. Multi-worker safe; disabled by default. -
Pathfinder verifier package (
graphsenselib.pathfinder) andverify=truedefault onbuild_pathfinder_file. A new top-level package — independent of MCP, importable by CLI / scripts / the python client — exposes two verifiers callers can mix and match:verify_structural(spec)— sync, pure stdlib, no backend. Catches in-spec inconsistencies that produce visually broken.gsfiles: agg_edges without txs / tx_ids, references to ids not in their list, orphan txs, and stray addresses (listed but not used as any edge endpoint and not declaredstarting_point=true).verify_against_backend(spec, *, default_network, backend)— async, takes a minimalGraphsenseBackendProtocol (address_exists,tx_addresses). Cross-checks against on-chain reality: address exists, tx exists, AND both endpoints of every edge are in the tx's participant set (the original9e44196...mediation failure mode). Concurrency-capped via semaphore (default 8) to avoid the 2026-05-04 pool-exhaustion shape.
The shipped
RestBackendadapter wires it to any graphsense REST deployment via httpx (in-process or out-of-process); python-client callers can write a ~15-lineasyncio.to_threadadapter against the same Protocol.build_pathfinder_fileruns both verifiers by default, merges findings intosummary.warningsAND the TextContent block, and downgrades backend transport errors to a single "verifier unavailable" warning so a flaky backend never sinks a structurally valid build. Passverify=falseto skip backend checks while drafting (structural checks still run).
Changed
build_pathfinder_file: tidy-tree hierarchical layout, UI-matched spacing, and adownload_urlchannel. The hierarchical layout (used whenever a node is flaggedstarting_point=true) now builds a BFS spanning tree and walks it post-order, so every descendant stays on the same side as its ancestor and branches no longer cross; txs snap to the mean y of their endpoints, then a per-column single-linkage clustering pass (thresholdy_step / 2) spreads multi-tx piles along y so an N-tx edge no longer collapses onto a single visible point (observed: 73 of 83 txs invisible before the fix). Layout constants (_HIER_Y_STEP,GsBuilder._ROW) moved from3.0to2.5to match the Pathfinder UI's own defaults (Config/Pathfinder.elm). When a file store is configured the tool stashes the.gsfile and returns an additivedownload_urlinstructured_content(the channel weak MCP hosts can still use); the embedded resource is still sent unlessfile_store.embed_resource=false. The tool is now async. Backwards compatible — withoutfile_storeconfig the only change isdownload_url: null.build_pathfinder_fileMCP-side input pattern accepts underscore._ID_PATTERNis now^[a-zA-Z0-9_]{1,150}$so the documented account-model sub-payment identifier form<hash>_I<n>(internal trace) /<hash>_T<n>(token transfer) passes validation. Affectsbuild_pathfinder_file,lookup_tx_details,list_txs_for,list_tx_flowsand the other consolidated MCP tools — previously they rejected_-bearing ids withInvalid tx hash. The rejection message is also reworded to make clear it's a format check at the input boundary, not a verify finding ("…has an invalid format … This is a format check at the input boundary, NOT a verify finding"), so agents stop reaching forverify=falseto work around a format complaint.build_pathfinder_filedocstring guidance._TxSpec.idno longer nudges callers toward the suffixed identifier unconditionally on account-model chains: baretx_hashis the natural choice for the native transfer;<hash>_I<n>/<hash>_T<n>is for pointing at a specific sub-payment.labelfield descriptions tell the model not to restate attribution tags or transaction date/value (the UI already shows them) and to reservelabelfor case context. Description-only encoding-unchanged.
Fixed
- MCP tool selection for "what currencies are supported?". Weaker hosts (observed on Mistral Le Chat "Work" mode) picked
list_supported_tokensinstead ofget_statisticsand per-network-iterated empty token lists into "supports an extremely wide range of currencies".tools.yamlandinstructions.mdnow disambiguate explicitly —get_statisticsis THE source for supported networks;list_supported_tokenscarries a negative cross-reference and notes that an empty list does NOT mean the network is unsupported. Description-only change. - Tagpack / actorpack
!includenow resolves against the repo root when called without an explicitheader_dir. Previouslygs tagpack validate <file>only worked when run from the tagpack repo root. A new helperfind_pack_rootwalks up ≤ 3 ancestors looking forpacks/or a.gitdirectory; the first match becomes the includebase_dir. Explicitheader_dirstill wins; with no root found, the loader falls back to pyyaml-include's CWD-relative default. - Slack exception notifications now cover the MCP path. Three gaps closed: (1) the Slack handler is now attached to the
graphsenselib.mcplogger tree (siblings ofgraphsenselib.web.app, so handler propagation wasn't reaching them); (2) a newErrorLoggingMiddleware(mcp/error_logging.py) registered on the FastMCP instance callslogger.exception(...)on any unhandled tool/resource/prompt exception before re-raising; (3) backend HTTP 5xx responses (previously wrapped asToolErrorby_get_json/_get_json_optionaland silenced) now log atERRORvia a new_raise_backend_http_errorhelper before raising. Caller-side errors stay silent:ToolError,ResourceError,PromptError,fastmcp.exceptions.ValidationErrorandpydantic.ValidationErrorare all in_EXPECTED_MCP_ERRORS— they're contract, not incidents, and 4xx upstream responses are model-fixable inputs (tx not found,bad address), not ops issues. build_pathfinder_filerobustness on weak MCP hosts (Mistral Le Chat). Four behaviours that collectively kept Mistral-style hosts from rendering the tool's result correctly:- The docstring no longer claims the model receives the
.gsbytes (the bytes travel in a resource channel the model cannot read; the old wording made hosts that drop the embedded resource fabricate base64 /data:URLs). PathfinderSpecdeclares an optionallayoutfield so the common LLM mistake of nestinglayoutinsidespecno longer fails with a Pydanticextra_forbiddenvalidation error — when the top-level argument is the default ("auto") andspec.layoutis set, the latter wins; explicit top-levellayoutstill takes precedence.- The tool always appends a
TextContentblock carrying the download URL (or an "embedded in this response" line) so hosts that only rendercontentand ignorestructured_contentstill surface a usable response. - Verifier findings are folded into that same
TextContentblock under aWarnings — fix the spec and rebuild:section, so the original 9e44196... failure mode (warning fired instructured_content, host dropped it, agent shipped a broken.gs) cannot recur on content-only renderers.
- The docstring no longer claims the model receives the
- Verifier
RestBackend.tx_addressesrequestsinclude_io=true&include_nonstandard_io=true. UTXOinputs/outputsare declaredOptional[...] = Noneon the REST response model and excluded from the body when not requested (response_model_exclude_none=True). Without the params, the adapter returned an empty address set for every UTXO tx and the mediation check fired the misleading warningtx involves {}on every tx-mediated edge — even for byte-identical files that had previously passed verification. Account-model bodies carryfrom_address/to_addressunconditionally, so the flags are no-ops there. Locked in by both a wire-level regression test on the query string and an end-to-end test that drives the adapter through a real FastAPI route with the sameresponse_model_exclude_nonecontract.
Web API + Python client (webapi-2.13.5)
Changed
graphsenselib.convert.gs_fileshierarchical layout output has changed shape. Downstream consumers that callapply_hierarchical_layoutdirectly or build.gsfiles viaGsBuilderand depend on exact coordinates / byte-identical output will see drift: the layout now uses a BFS-tidy-tree (subtree-clustered), row pitch matches the Pathfinder UI defaults (_HIER_Y_STEP3.0 → 2.5,GsBuilder._ROW3.0 → 2.5), and multi-tx edges are de-overlapped along y instead of stacking on a single point. The decoded structure (addresses, txs, edges, ids, labels) is unchanged — only the(x, y)coordinates differ. See the Library section for design details.
Full Changelog: v2.13.4...v2.13.5