Skip to content

graphsense-lib 2.13.5

Choose a tag to compare

@github-actions github-actions released this 27 May 15:14
· 48 commits to master since this release

[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 (default false). When set, GraphsenseFallbackToLocalOneRetryPolicy downgrades a LOCAL_QUORUM read to LOCAL_ONE on the FIRST Unavailable / ReadTimeout if 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. New web/file_store.py (RedisFileStore, reusable FileStore protocol) holds files as TTL'd Redis hashes keyed by a 256-bit CSPRNG token; the route is a plain Starlette route, excluded from OpenAPI. New FileStoreConfig on GSRestConfig: enabled (default false), redis_url, download_path (/download), ttl_s (1800), max_file_bytes (5 MiB), base_url, key_prefix, embed_resource. URLs derive from X-Forwarded-*/Host with base_url override. Multi-worker safe; disabled by default.

  • Pathfinder verifier package (graphsenselib.pathfinder) and verify=true default on build_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 .gs files: 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 declared starting_point=true).
    • verify_against_backend(spec, *, default_network, backend) — async, takes a minimal GraphsenseBackend Protocol (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 original 9e44196... mediation failure mode). Concurrency-capped via semaphore (default 8) to avoid the 2026-05-04 pool-exhaustion shape.

    The shipped RestBackend adapter wires it to any graphsense REST deployment via httpx (in-process or out-of-process); python-client callers can write a ~15-line asyncio.to_thread adapter against the same Protocol. build_pathfinder_file runs both verifiers by default, merges findings into summary.warnings AND the TextContent block, and downgrades backend transport errors to a single "verifier unavailable" warning so a flaky backend never sinks a structurally valid build. Pass verify=false to skip backend checks while drafting (structural checks still run).

Changed

  • build_pathfinder_file: tidy-tree hierarchical layout, UI-matched spacing, and a download_url channel. The hierarchical layout (used whenever a node is flagged starting_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 (threshold y_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 from 3.0 to 2.5 to match the Pathfinder UI's own defaults (Config/Pathfinder.elm). When a file store is configured the tool stashes the .gs file and returns an additive download_url in structured_content (the channel weak MCP hosts can still use); the embedded resource is still sent unless file_store.embed_resource=false. The tool is now async. Backwards compatible — without file_store config the only change is download_url: null.
  • build_pathfinder_file MCP-side input pattern accepts underscore. _ID_PATTERN is 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. Affects build_pathfinder_file, lookup_tx_details, list_txs_for, list_tx_flows and the other consolidated MCP tools — previously they rejected _-bearing ids with Invalid 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 for verify=false to work around a format complaint.
  • build_pathfinder_file docstring guidance. _TxSpec.id no longer nudges callers toward the suffixed identifier unconditionally on account-model chains: bare tx_hash is the natural choice for the native transfer; <hash>_I<n> / <hash>_T<n> is for pointing at a specific sub-payment. label field descriptions tell the model not to restate attribution tags or transaction date/value (the UI already shows them) and to reserve label for 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_tokens instead of get_statistics and per-network-iterated empty token lists into "supports an extremely wide range of currencies". tools.yaml and instructions.md now disambiguate explicitly — get_statistics is THE source for supported networks; list_supported_tokens carries a negative cross-reference and notes that an empty list does NOT mean the network is unsupported. Description-only change.
  • Tagpack / actorpack !include now resolves against the repo root when called without an explicit header_dir. Previously gs tagpack validate <file> only worked when run from the tagpack repo root. A new helper find_pack_root walks up ≤ 3 ancestors looking for packs/ or a .git directory; the first match becomes the include base_dir. Explicit header_dir still 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.mcp logger tree (siblings of graphsenselib.web.app, so handler propagation wasn't reaching them); (2) a new ErrorLoggingMiddleware (mcp/error_logging.py) registered on the FastMCP instance calls logger.exception(...) on any unhandled tool/resource/prompt exception before re-raising; (3) backend HTTP 5xx responses (previously wrapped as ToolError by _get_json / _get_json_optional and silenced) now log at ERROR via a new _raise_backend_http_error helper before raising. Caller-side errors stay silent: ToolError, ResourceError, PromptError, fastmcp.exceptions.ValidationError and pydantic.ValidationError are 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_file robustness 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 .gs bytes (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).
    • PathfinderSpec declares an optional layout field so the common LLM mistake of nesting layout inside spec no longer fails with a Pydantic extra_forbidden validation error — when the top-level argument is the default ("auto") and spec.layout is set, the latter wins; explicit top-level layout still takes precedence.
    • The tool always appends a TextContent block carrying the download URL (or an "embedded in this response" line) so hosts that only render content and ignore structured_content still surface a usable response.
    • Verifier findings are folded into that same TextContent block under a Warnings — fix the spec and rebuild: section, so the original 9e44196... failure mode (warning fired in structured_content, host dropped it, agent shipped a broken .gs) cannot recur on content-only renderers.
  • Verifier RestBackend.tx_addresses requests include_io=true & include_nonstandard_io=true. UTXO inputs / outputs are declared Optional[...] = None on 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 warning tx involves {} on every tx-mediated edge — even for byte-identical files that had previously passed verification. Account-model bodies carry from_address / to_address unconditionally, 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 same response_model_exclude_none contract.

Web API + Python client (webapi-2.13.5)

Changed

  • graphsenselib.convert.gs_files hierarchical layout output has changed shape. Downstream consumers that call apply_hierarchical_layout directly or build .gs files via GsBuilder and 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_STEP 3.0 → 2.5, GsBuilder._ROW 3.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