Skip to content

YTDB-154: Transaction-level query metrics listener#646

Open
lpld wants to merge 18 commits intodevelopfrom
ytdb-154-observability-query-listeners
Open

YTDB-154: Transaction-level query metrics listener#646
lpld wants to merge 18 commits intodevelopfrom
ytdb-154-observability-query-listeners

Conversation

@lpld
Copy link
Collaborator

@lpld lpld commented Feb 10, 2026

Motivation:

First part of the observability functionality in YouTrackDB, as described in https://youtrack.jetbrains.com/articles/YTDB-A-39/Observability-tools.
This change adds a low-overhead transaction-level QueryMetricsListener that gets notified about the query execution time. This mechanism is supposed to be a base for our further OpenTelemetry integration. It can operate in two modes: LIGHTWEIGHT and EXACT. Lightweight mode adds almost no overhead to the query, while providing approximate values. Exact mode operates with higher precision at a potential performance cost.

Changes:

  • A new QueryMetricsListener interface for query metrics that can be registered on an instance of YTDBTransaction.
  • YTDBTransaction now has a method for listener registering: withQueryListener.
  • It also has new methods for specifying query monitoring mode and transaction tracking ID: withQueryMonitoringMode and withTrackingId.
  • A new querySummary configuration parameter that can be used in Gremlin .with() step to provide a client string value that is gonna be associated with the query and passed to the query listener.
  • GranularTicker now has the ability to approximate current time millis. It does so by keeping the currentTimeMillis and nanoTime difference and refreshing it once in 10 seconds.
  • A new TinkerPop strategy YTDBQueryMetricsStrategy is now registered in YTDBGraphImplAbstract. It adds a new YTDBQueryMetricsStep that does the execution time calculation based on the configured query execution mode.
  • Tests for GranularTicker and YTDBQueryMetricsStrategy.
  • TinkerPop's GroovyTranslator with a custom TypeTranslator is used for translating GraphTraversal into a readable string representation with all the sensitive data anonymized.
  • JMH QueryMetricsOverheadBenchmark that measures the performance overhead of lightweight query monitoring on a minimal query g.inject(1).iterate(). It has 3 different metrics: no monitoring, monitoring with empty "do nothing listener" and monitoring with a reading listener (one that gets the query string representation from the QueryDetails). Here are the results of running the benchmark on my working laptop (Intel i9-13900H):
Benchmark                                                  Mode  Cnt     Score    Error  Units
QueryMetricsOverheadBenchmark.baseline_noMonitoring        avgt    5  3648.087 ± 88.042  ns/op
QueryMetricsOverheadBenchmark.lightweight_noopListener     avgt    5  4053.517 ± 50.063  ns/op
QueryMetricsOverheadBenchmark.lightweight_readingListener  avgt    5  4244.492 ± 53.020  ns/op

Shortcomings and differences from the original design

  • The new YTDBTransaction API is different from what has been described in the design document. During the implementation I have come to a conclusion that having separate withTrackingId and withQueryMonitoringMode is clearer for understanding in situations when clients have the ability to register several different listeners on a single transaction.
  • YTDBTransaction internally keeps one instance of the QueryMetricsListener interface: QueryMetricsListener. In the future we will probably need to support multiple listeners of each type, but at the moment I didn't want to focus on this part - so it will probably got changed in the next PRs.

@lpld lpld requested a review from andrii0lomakin February 10, 2026 21:31
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @lpld, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces foundational observability features to YouTrackDB by implementing transaction-level query metrics. It provides a mechanism for tracking query execution and transaction commit times with configurable precision, laying the groundwork for future OpenTelemetry integration. The changes enable developers to monitor database performance with minimal overhead, offering both approximate and exact timing measurements.

Highlights

  • Query and Transaction Metrics Listeners: Introduced QueryMetricsListener and TransactionMetricsListener interfaces to allow external components to receive notifications about query execution times and transaction commit times, respectively. YTDBTransaction now includes methods (withQueryListener, withTransactionListener) to register these listeners.
  • Query Monitoring Modes and Tracking: Added QueryMonitoringMode (LIGHTWEIGHT and EXACT) to control the precision and overhead of query monitoring. YTDBTransaction can now be configured with a monitoring mode (withQueryMonitoringMode) and a client-provided tracking ID (withTrackingId) for better traceability.
  • Enhanced GranularTicker for Time Approximation: The GranularTicker has been updated to approximate currentTimeMillis by periodically adjusting the difference between currentTimeMillis and nanoTime. A new configuration parameter, PROFILER_TICKER_ADJUSTMENT_RATE, controls how often this adjustment occurs, balancing precision and performance.
  • Gremlin Integration for Query Metrics: A new TinkerPop strategy, YTDBQueryMetricsStrategy, was registered in YTDBGraphImplAbstract. This strategy injects a YTDBQueryMetricsStep into Gremlin traversals to capture query execution times based on the configured monitoring mode and associates them with an optional querySummary provided via YTDBQueryConfigParam.
  • New Query Configuration Parameter: Added querySummary to YTDBQueryConfigParam, allowing clients to provide a descriptive string for their queries, which is then passed to the QueryMetricsListener for better context in monitoring.
  • Test Coverage for New Functionality: New unit tests were added for GranularTicker's time approximation capabilities and for the YTDBQueryMetricsStrategy to ensure the new monitoring features function as expected in both lightweight and exact modes.
Changelog
  • core/src/main/java/com/jetbrains/youtrackdb/api/config/GlobalConfiguration.java
    • Added PROFILER_TICKER_ADJUSTMENT_RATE configuration parameter to control ticker adjustment frequency.
  • core/src/main/java/com/jetbrains/youtrackdb/api/gremlin/tokens/YTDBQueryConfigParam.java
    • Added querySummary enum entry for client-provided query descriptions.
    • Implemented getValue method to retrieve configuration parameters from a traversal.
  • core/src/main/java/com/jetbrains/youtrackdb/internal/common/profiler/GranularTicker.java
    • Modified constructor to accept granularityNanos and timestampRefreshRate.
    • Updated start method to schedule periodic updates for nanoTime and nanoTimeDifference.
    • Renamed lastNanoTime to approximateNanoTime.
    • Added approximateCurrentTimeMillis method for approximate current time in milliseconds.
    • Updated getTick to use nanoTime.
    • Changed close method signature to remove throws Exception.
  • core/src/main/java/com/jetbrains/youtrackdb/internal/common/profiler/Profiler.java
    • Updated GranularTicker instantiation to use the new constructor with PROFILER_TICKER_ADJUSTMENT_RATE.
    • Added getTicker method to expose the ticker instance.
  • core/src/main/java/com/jetbrains/youtrackdb/internal/common/profiler/Ticker.java
    • Renamed lastNanoTime to approximateNanoTime.
    • Added approximateCurrentTimeMillis method.
    • Updated Javadoc for new methods and overall interface.
  • core/src/main/java/com/jetbrains/youtrackdb/internal/common/profiler/metrics/Stopwatch.java
    • Updated currentApproxNanoTime to call ticker.approximateNanoTime().
  • core/src/main/java/com/jetbrains/youtrackdb/internal/common/profiler/metrics/TimeInterval.java
    • Added toMillis method to convert time interval to milliseconds.
  • core/src/main/java/com/jetbrains/youtrackdb/internal/common/profiler/monitoring/QueryMetricsListener.java
    • Added new interface QueryMetricsListener for transaction-level query monitoring.
  • core/src/main/java/com/jetbrains/youtrackdb/internal/common/profiler/monitoring/QueryMonitoringMode.java
    • Added new enum QueryMonitoringMode to define lightweight and exact monitoring.
  • core/src/main/java/com/jetbrains/youtrackdb/internal/common/profiler/monitoring/TransactionMetricsListener.java
    • Added new interface TransactionMetricsListener for transaction commit monitoring.
  • core/src/main/java/com/jetbrains/youtrackdb/internal/common/profiler/monitoring/YTDBQueryMetricsStep.java
    • Added new class YTDBQueryMetricsStep to capture query execution metrics within a traversal.
  • core/src/main/java/com/jetbrains/youtrackdb/internal/common/profiler/monitoring/YTDBQueryMetricsStrategy.java
    • Added new class YTDBQueryMetricsStrategy to inject YTDBQueryMetricsStep into Gremlin traversals.
  • core/src/main/java/com/jetbrains/youtrackdb/internal/core/YouTrackDBEnginesManager.java
    • Imported Ticker class.
    • Added getTicker method to retrieve the profiler's ticker instance.
  • core/src/main/java/com/jetbrains/youtrackdb/internal/core/gremlin/YTDBGraphImplAbstract.java
    • Imported YTDBQueryMetricsStrategy.
    • Registered YTDBQueryMetricsStrategy as an optimization strategy.
  • core/src/main/java/com/jetbrains/youtrackdb/internal/core/gremlin/YTDBTransaction.java
    • Imported monitoring-related classes.
    • Added fields for queryMonitoringMode, trackingId, queryMetricsListener, and transactionMetricsListener.
    • Reset queryMetricsListener and transactionMetricsListener to NO_OP on commit and rollback.
    • Added withTrackingId method to set a transaction tracking ID.
    • Added withQueryMonitoringMode method to configure query monitoring precision.
    • Added withQueryListener method to register a query metrics listener.
    • Added withTransactionListener method to register a transaction metrics listener.
    • Added isMonitoringEnabled method to check if any monitoring is active.
    • Added getter methods for queryMonitoringMode, trackingId, queryMetricsListener, and transactionMetricsListener.
  • core/src/main/java/com/jetbrains/youtrackdb/internal/core/gremlin/traversal/strategy/optimization/YTDBStrategyUtil.java
    • Updated isPolymorphic method to use YTDBQueryConfigParam.getValue for retrieving the polymorphic query setting.
  • core/src/test/java/com/jetbrains/youtrackdb/internal/common/profiler/GranularTickerTest.java
    • Added new test file GranularTickerTest to verify time approximation.
  • core/src/test/java/com/jetbrains/youtrackdb/internal/common/profiler/metrics/RatioTest.java
    • Updated GranularTicker instantiation to include timestampRefreshRate.
  • core/src/test/java/com/jetbrains/youtrackdb/internal/common/profiler/metrics/StubTicker.java
    • Added approximateCurrentTimeMillis method.
    • Renamed lastNanoTime to approximateNanoTime.
  • core/src/test/java/com/jetbrains/youtrackdb/internal/common/profiler/metrics/TimeRateTest.java
    • Updated GranularTicker instantiation to include timestampRefreshRate.
  • core/src/test/java/com/jetbrains/youtrackdb/internal/core/MeterBenchmark.java
    • Updated GranularTicker instantiation to include timestampRefreshRate.
  • core/src/test/java/com/jetbrains/youtrackdb/internal/core/gremlin/gremlintest/scenarios/YTDBQueryMetricsStrategyTest.java
    • Added new test file YTDBQueryMetricsStrategyTest to validate query monitoring functionality.
  • core/src/test/java/com/jetbrains/youtrackdb/internal/core/gremlin/gremlintest/suites/YTDBGremlinProcessTests.java
    • Added YTDBQueryMetricsStrategyTest to the list of embedded-only tests.
Activity
  • No specific activity (comments, reviews, progress updates) was provided in the context for this pull request.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@github-actions
Copy link

github-actions bot commented Feb 10, 2026

Qodana for JVM

20 new problems were found

Inspection name Severity Problems
Spelling ◽️ Notice 19
Grammar ◽️ Notice 1

💡 Qodana analysis was run in the pull request mode: only the changed files were checked
☁️ View the detailed Qodana report

Contact Qodana team

Contact us at qodana-support@jetbrains.com

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a transaction-level query metrics listener for observability. The changes are comprehensive, including new interfaces for listeners, updates to YTDBTransaction to support them, a new TinkerPop strategy to inject metrics collection, and modifications to GranularTicker for low-overhead time approximation. The code is well-structured and includes new tests for the added functionality. I've found a couple of issues that should be addressed, for which I've left comments.

() -> time = System.nanoTime(),
0, granularity, TimeUnit.NANOSECONDS
() -> nanoTimeDifference = System.currentTimeMillis() - System.nanoTime() / 1_000_000,
timestampRefreshRate, timestampRefreshRate, TimeUnit.MILLISECONDS
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The timestampRefreshRate parameter is provided in nanoseconds (from GlobalConfiguration and in tests), but it's being used with TimeUnit.MILLISECONDS here. This will cause the adjustment task to run much less frequently than intended. For example, with the default value of 10 seconds (10_000_000_000 ns), this schedules the task to run approximately every 115 days instead of every 10 seconds.

To fix this, you should use TimeUnit.NANOSECONDS.

Suggested change
timestampRefreshRate, timestampRefreshRate, TimeUnit.MILLISECONDS
timestampRefreshRate, timestampRefreshRate, TimeUnit.NANOSECONDS


public @Nonnull String getTrackingId() {
return trackingId != null ? trackingId :
" " + activeSession.getActiveTransaction().getId();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The default tracking ID is being generated with a leading space. This seems unintentional. If it's not a typo, a comment explaining the purpose of the space would be helpful. Otherwise, it should be removed to avoid potential issues for consumers of this ID, such as needing to trim it.

Suggested change
" " + activeSession.getActiveTransaction().getId();
String.valueOf(activeSession.getActiveTransaction().getId());

() -> nanoTime = System.nanoTime(),
granularity, granularity, TimeUnit.NANOSECONDS
);
executor.scheduleAtFixedRate(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with Gemini.

public void close() throws Exception {

final var duration = isLightweight ? ticker.approximateNanoTime() - nano : nano;
ytdbTx.getQueryMetricsListener().queryFinished(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a traversal is created and closed without calling hasNext()/next(), close() still fires the listener with startMillis = 0 and a garbage duration.

}

public @Nonnull String getTrackingId() {
return trackingId != null ? trackingId :
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with gemini


public @Nonnull String getTrackingId() {
return trackingId != null ? trackingId :
" " + activeSession.getActiveTransaction().getId();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NPE if activeSesison is null, unlikely possible but good for defensive programming

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think that throwing IllegalStateException here is okay? I am replacing activeSession with getDatabaseSession() call that already checks if the session is not null

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, agree


final var ticker = YouTrackDBEnginesManager.instance().getTicker();
final String querySummary = YTDBQueryConfigParam.querySummary.getValue(traversal);
final var metricsStep = new YTDBQueryMetricsStep<>(traversal, ytdbTx, querySummary, ticker);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Step is added even if there is no real query listener.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, there is a "return" above:

if (!ytdbTx.isMonitoringEnabled()) {
  return;
}

but I am still gonna change this part a bit, because there is no need to add this step when only transaction listener is registered

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but check it does, or so presence of the TX listener is enough.

package com.jetbrains.youtrackdb.internal.common.profiler.monitoring;

/// Controls the precision of query monitoring.
public enum QueryMonitoringMode {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let us add @see tag here

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pointing to where?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To listeners


public GranularTicker(long granularity) {
public GranularTicker(long granularityNanos, long timestampRefreshRate) {
this.executor = ThreadPoolExecutors.newSingleThreadScheduledPool("GranularTicker");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to monitor all usages of thread pools and aggeregate them in one single manager to avoid thread context switching overhead. Please create PR.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean a YouTrack issue?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you already noticed

private volatile long time;
private final long timestampRefreshRate;
private volatile long nanoTime;
private volatile long nanoTimeDifference;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be good to have comment why we need this.


private void testQuery(QueryMonitoringMode mode, RememberingListener listener) throws Exception {
final var rand = RandomUtils.insecure();
((YTDBTransaction) g().tx())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would not it better if we override metho in GTS ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, could you elaborate?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tx() returns transaction that users always need to case, let us override it in our graph traversal

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually thought about this, but the problem is that YTDBGraphTraversalSource#tx() can actually return DriverRemoteTransaction in remote mode and we can't simply change the type here.

@Override
public void close() throws Exception {

final var duration = isLightweight ? ticker.approximateNanoTime() - nano : nano;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, that is incorrect estimation for lightweight mode, we also check time from the granular ticker as our execution time is till start to the last hasNext/next not till the close, which may happen long time after.

Copy link
Collaborator

@andrii0lomakin andrii0lomakin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also add JMH benchmark to check performance overhead of lightweight mode on dumb querhy like g.inject(1).terate() I am looking forward to seeing results.

@lpld lpld force-pushed the ytdb-154-observability-query-listeners branch from 9e0e565 to e016cb7 Compare February 11, 2026 20:00
@lpld lpld changed the base branch from ytdb-154-observability to develop February 12, 2026 15:58
@lpld lpld force-pushed the ytdb-154-observability-query-listeners branch from 2284d2a to 607e5d5 Compare February 13, 2026 08:23
lpld and others added 12 commits February 13, 2026 14:48
…lose

In lightweight mode, duration now covers first-to-last hasNext/next call
instead of first call to close. Also skip listener notification if the
traversal was never iterated.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reset trackingId and queryMonitoringMode on commit and rollback.
Also rename isMonitoringEnabled to isQueryMetricsEnabled, add @nonnull
annotations with runtime checks, and remove unused imports.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move getValue (renamed to getConfigValue) out of the public API enum
into YTDBStrategyUtil. Also move YTDBStrategyUtil from
strategy.optimization to strategy package.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Translate query bytecode to a readable Gremlin string using a custom
ValueAnonymizingTypeTranslator that preserves labels, property names,
step labels, and side-effect keys while parameterizing actual values.
Comprehensive test coverage for ~100 Gremlin step variants.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The ticker-measured duration window sits inside the System.nanoTime
window, so assert [0, duration + lag] instead of approximate equality.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add QueryMetricsOverheadBenchmark measuring the cost of lightweight
monitoring on g.inject(1).iterate(). Enable JMH annotation processor
for test compilation in core module. Also add @see tags to
QueryMonitoringMode and null-check in isQueryMetricsEnabled.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@lpld lpld force-pushed the ytdb-154-observability-query-listeners branch from 7c2b992 to d52d364 Compare February 13, 2026 13:48
lpld and others added 6 commits February 13, 2026 17:25
…nslator

Split has() and property() into separate branches: property key (and
optional Cardinality) are structural, values and meta-properties are
parameterized. Add test coverage for all property() variants including
Map, Cardinality+Map, CardinalityValueTraversal, and meta-properties.
Also update class/method docs to accurately describe four argument
categories.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace g() casts with the base g field where the YTDB-specific type
is not needed. Keep g() only for executeInTx and with(YTDBQueryConfigParam).
Also apply linter fixes: var inference, unused import removal, line wrapping.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add trackingId test coverage with randomized withTrackingId assertions.
Fix Qodana inspections: suppress unused warning on benchmark, add
@SuppressWarnings("resource") in YTDBStrategyUtil, remove unused
TimeInterval.toMillis(). Fix javadoc references and minor comment tweaks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…csStep

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move TransactionMetricsListener interface and its usages to a separate
branch (ytdb-539-observability-tx-listeners) for future implementation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@lpld lpld requested a review from andrii0lomakin February 14, 2026 00:22
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.

2 participants