aliehsaeedii commented on code in PR #22882:
URL: https://github.com/apache/kafka/pull/22882#discussion_r3757068653


##########
docs/streams/developer-guide/interactive-queries.md:
##########
@@ -257,6 +257,233 @@ After the application has started, you can get access to 
"CountsWindowStore" and
       System.out.println("Count of 'world' @ time " + windowTimestamp + " is " 
+ next.value);
     }
 
+## Header-aware stores and interactive queries 
{#header-aware-stores-interactive-queries}
+
+A [header-aware 
store](/{version}/streams/developer-guide/processor-api/#headers-in-state-stores)
 — built with a `*WithHeaders` supplier and its matching builder 
([KIP-1271](../../upgrade-guide/#kip-1271-headers-aware-stores)) — persists 
each record's 
[headers](</{version}/javadoc/org/apache/kafka/streams/processor/api/Record.html#headers()>)
 together with its value and timestamp. This section shows how to read those 
headers back interactively, through both the `store()` API and the IQv2 
`query()` API.

Review Comment:
   [content] "persists each record's headers together with its value and 
timestamp" is not true for session stores, which this section goes on to cover. 
`AggregationWithHeaders` has no timestamp at all — its only accessors are:
   
   ```java
   public AGG aggregation()   // AggregationWithHeaders.java:83
   public Headers headers()   // :87
   ```
   
   and `TimestampedWindowRangeWithHeadersQuery`'s javadoc spells out why: 
"Session aggregations carry no per-record event-time of their own, so 
`ReadOnlyRecord#timestamp()` is filled from the session window's (inclusive) 
end timestamp." Your own line 483 relies on that fact.
   
   Since this is the sentence that defines the concept for the whole section, 
suggest: "…persists each record's headers alongside its value (and, for 
key-value and window stores, its timestamp)."



##########
docs/streams/developer-guide/interactive-queries.md:
##########
@@ -257,6 +257,233 @@ After the application has started, you can get access to 
"CountsWindowStore" and
       System.out.println("Count of 'world' @ time " + windowTimestamp + " is " 
+ next.value);
     }
 
+## Header-aware stores and interactive queries 
{#header-aware-stores-interactive-queries}
+
+A [header-aware 
store](/{version}/streams/developer-guide/processor-api/#headers-in-state-stores)
 — built with a `*WithHeaders` supplier and its matching builder 
([KIP-1271](../../upgrade-guide/#kip-1271-headers-aware-stores)) — persists 
each record's 
[headers](</{version}/javadoc/org/apache/kafka/streams/processor/api/Record.html#headers()>)
 together with its value and timestamp. This section shows how to read those 
headers back interactively, through both the `store()` API and the IQv2 
`query()` API.
+
+### Reading headers with the `store()` API
+
+Look up the store with the `*WithHeaders()` entry from `QueryableStoreTypes` 
that matches your store type. The returned `ReadOnly*Store` surfaces the 
headers as part of its value type: 
[ValueTimestampHeaders](/{version}/javadoc/org/apache/kafka/streams/state/ValueTimestampHeaders.html)
 for key-value and window stores, and 
[AggregationWithHeaders](/{version}/javadoc/org/apache/kafka/streams/state/AggregationWithHeaders.html)
 for session stores. These examples assume a header-aware store built with a 
`*WithHeaders` supplier, as shown in [Headers in State 
Stores](/{version}/streams/developer-guide/processor-api/#headers-in-state-stores).
 There are only three such helpers — `timestampedKeyValueStoreWithHeaders()`, 
`timestampedWindowStoreWithHeaders()`, and `sessionStoreWithHeaders()`; there 
is no `*WithHeaders()` helper for a plain (non-timestamped) key-value or window 
store.
+    
+    
+    // Key-value store built with a *WithHeaders supplier
+    ReadOnlyKeyValueStore<String, ValueTimestampHeaders<Long>> keyValueStore =
+        streams.store(StoreQueryParameters.fromNameAndType(
+            "counts-store", 
QueryableStoreTypes.timestampedKeyValueStoreWithHeaders()));
+    
+    ValueTimestampHeaders<Long> vth = keyValueStore.get("hello");
+    if (vth != null) {
+      System.out.println("value:     " + vth.value());
+      System.out.println("timestamp: " + vth.timestamp());
+      System.out.println("headers:   " + vth.headers());
+    }
+    
+    // Window store built with a *WithHeaders supplier
+    ReadOnlyWindowStore<String, ValueTimestampHeaders<Long>> windowStore =
+        streams.store(StoreQueryParameters.fromNameAndType(
+            "counts-window-store", 
QueryableStoreTypes.timestampedWindowStoreWithHeaders()));
+    
+    // fetch returns a WindowStoreIterator whose values carry headers
+    try (WindowStoreIterator<ValueTimestampHeaders<Long>> it =
+             windowStore.fetch("hello", Instant.ofEpochMilli(0), 
Instant.now())) {
+      while (it.hasNext()) {
+        ValueTimestampHeaders<Long> wv = it.next().value;
+        System.out.println("value: " + wv.value() + " headers: " + 
wv.headers());
+      }
+    }
+
+Session stores return `AggregationWithHeaders<V>`, which exposes the 
aggregated value via `aggregation()` (not `value()`) and the headers via 
`headers()`.
+    
+    
+    // Session store built with a *WithHeaders supplier
+    ReadOnlySessionStore<String, AggregationWithHeaders<Long>> sessionStore =
+        streams.store(StoreQueryParameters.fromNameAndType(
+            "counts-session-store", 
QueryableStoreTypes.sessionStoreWithHeaders()));
+    
+    try (KeyValueIterator<Windowed<String>, AggregationWithHeaders<Long>> it =
+             sessionStore.fetch("hello")) {
+      while (it.hasNext()) {
+        AggregationWithHeaders<Long> awh = it.next().value;
+        System.out.println("aggregation: " + awh.aggregation());
+        System.out.println("headers:     " + awh.headers());
+      }
+    }
+
+### Reading headers with the IQv2 `query()` API
+
+Interactive Queries v2 (IQv2) is the query-based interactive-queries API: 
instead of accessing a store object directly, you build a `Query`, wrap it in a 
[StateQueryRequest](/{version}/javadoc/org/apache/kafka/streams/query/StateQueryRequest.html),
 and run it with `KafkaStreams#query(...)`. The call returns a 
[StateQueryResult](/{version}/javadoc/org/apache/kafka/streams/query/StateQueryResult.html)
 that holds a per-partition 
[QueryResult](/{version}/javadoc/org/apache/kafka/streams/query/QueryResult.html):
 use `getOnlyPartitionResult()` for a single-key lookup, or 
`getPartitionResults()` for the full `Map<Integer, QueryResult<R>>`. Each 
`QueryResult` exposes the query result via `getResult()` and its data-freshness 
`Position` via `getPosition()`.
+
+Before [KIP-1356](../../upgrade-guide/#kip-1356-iqv2-header-queries), no IQv2 
query type exposed record headers. 
[KIP-1356](../../upgrade-guide/#kip-1356-iqv2-header-queries) adds four 
`@Evolving` query types whose results carry headers. Each returns a 
[ReadOnlyRecord](/{version}/javadoc/org/apache/kafka/streams/processor/api/ReadOnlyRecord.html)
 — a read-only view exposing `key()`, `value()`, `timestamp()`, and `headers()` 
— or, for the range and window queries, a closeable 
[ReadOnlyRecordIterator](/{version}/javadoc/org/apache/kafka/streams/state/ReadOnlyRecordIterator.html)
 of such records. `headers()` is never null (an empty `Headers` when the record 
had none) and must be treated as read-only: records served as IQv2 results have 
their headers frozen, so adding or removing a header (for example `add(...)`) 
throws `IllegalStateException`. The freeze is shallow, though — the byte array 
behind an individual header value can still be mutated in place, so treat 
header values as 
 read-only too.
+
+`TimestampedKeyWithHeadersQuery` is a single-key lookup against a header-aware 
key-value store, parallel to `TimestampedKeyQuery`:
+    
+    
+    TimestampedKeyWithHeadersQuery<String, Long> query =
+        TimestampedKeyWithHeadersQuery.withKey("hello");
+    
+    StateQueryRequest<ReadOnlyRecord<String, Long>> request =
+        StateQueryRequest.inStore("counts-store").withQuery(query);
+    
+    StateQueryResult<ReadOnlyRecord<String, Long>> result = 
streams.query(request);
+    QueryResult<ReadOnlyRecord<String, Long>> partitionResult = 
result.getOnlyPartitionResult();
+    if (partitionResult != null && partitionResult.isSuccess()) {
+      ReadOnlyRecord<String, Long> record = partitionResult.getResult();
+      if (record != null) {
+        System.out.println("value:   " + record.value());
+        System.out.println("headers: " + record.headers());
+      }
+    }
+
+Call `skipCache()` on the query to bypass the record cache and read directly 
from the underlying store (of the four header-aware queries, only this 
single-key one offers `skipCache()`).
+
+`TimestampedRangeWithHeadersQuery` is a key-range scan, parallel to 
`TimestampedRangeQuery`. It returns a `ReadOnlyRecordIterator`, so close it 
when done (for example, with try-with-resources). A range can span several 
local partitions, so iterate `getPartitionResults()`:
+    
+    
+    TimestampedRangeWithHeadersQuery<String, Long> query =
+        TimestampedRangeWithHeadersQuery.withRange("a", "n");
+    
+    StateQueryRequest<ReadOnlyRecordIterator<String, Long>> request =
+        StateQueryRequest.inStore("counts-store").withQuery(query);
+    
+    StateQueryResult<ReadOnlyRecordIterator<String, Long>> result = 
streams.query(request);
+    for (QueryResult<ReadOnlyRecordIterator<String, Long>> partition : 
result.getPartitionResults().values()) {
+      if (partition.isFailure()) {
+        System.out.println("failed: " + partition.getFailureReason() + " - " + 
partition.getFailureMessage());
+        continue;
+      }
+      try (ReadOnlyRecordIterator<String, Long> iterator = 
partition.getResult()) {
+        while (iterator.hasNext()) {
+          ReadOnlyRecord<String, Long> record = iterator.next();
+          System.out.println(record.key() + " -> " + record.value() + " " + 
record.headers());
+        }
+      }
+    }
+
+Use `withLowerBound`, `withUpperBound`, or `withNoBounds` for open-ended or 
full scans. Results are unordered by default; call `withAscendingKeys()` or 
`withDescendingKeys()` to fix the order, which is defined over the serialized 
`byte[]` of the keys rather than their logical order.
+
+`TimestampedWindowKeyWithHeadersQuery` fetches all windows for a single key 
within a window-start range from a header-aware window store. It parallels 
`WindowKeyQuery`, but with a different result shape: `WindowKeyQuery` returns a 
`WindowStoreIterator<V>` keyed by the window-start `long`, whereas this query 
returns a `ReadOnlyRecordIterator<Windowed<K>, V>` whose records are keyed by 
`Windowed<K>` (the window lives in the key; `timestamp()` is the stored record 
event-time). Build and consume it as for the range query above, but note the 
`Windowed<String>` in the request and result types:
+    
+    
+    TimestampedWindowKeyWithHeadersQuery<String, Long> query =
+        TimestampedWindowKeyWithHeadersQuery.withKeyAndWindowStartRange(
+            "hello", Instant.ofEpochMilli(0), Instant.now());
+    
+    StateQueryRequest<ReadOnlyRecordIterator<Windowed<String>, Long>> request =
+        StateQueryRequest.inStore("counts-window-store").withQuery(query);
+    
+    StateQueryResult<ReadOnlyRecordIterator<Windowed<String>, Long>> result = 
streams.query(request);
+    // Iterate result.getPartitionResults() and close each 
ReadOnlyRecordIterator, as in the range example.
+
+`TimestampedWindowRangeWithHeadersQuery` is parallel to `WindowRangeQuery` and 
has two forms. Use `withWindowStartRange(timeFrom, timeTo)` against a 
header-aware window store to fetch every key across a window-start range, or 
`withKey(key)` against a header-aware session store to fetch all sessions for a 
key (for session results, `timestamp()` is the session-window end). As with 
`WindowRangeQuery`, each store accepts only its corresponding form; submitting 
the wrong form fails with an unknown-query-type error. Both forms are 
`Query<ReadOnlyRecordIterator<Windowed<K>, V>>` — including the session 
`withKey` form, whose records are keyed by the session's `Windowed<K>`.
+    
+    
+    // Window store: every key across a window-start range
+    TimestampedWindowRangeWithHeadersQuery<String, Long> byWindow =
+        TimestampedWindowRangeWithHeadersQuery.withWindowStartRange(
+            Instant.ofEpochMilli(0), Instant.now());
+    
+    // Session store: all sessions for one key
+    TimestampedWindowRangeWithHeadersQuery<String, Long> byKey =
+        TimestampedWindowRangeWithHeadersQuery.withKey("hello");
+    
+    // Both forms build and consume identically; the result element type is 
ReadOnlyRecord<Windowed<String>, Long>:
+    StateQueryRequest<ReadOnlyRecordIterator<Windowed<String>, Long>> request =
+        StateQueryRequest.inStore("counts-window-store").withQuery(byWindow);

Review Comment:
   [content] "Both forms build and consume identically" — but the snippet then 
hardcodes the **window** store, and the store is precisely the thing that is 
*not* identical. `byKey` is the session form; submitting it to 
`"counts-window-store"` is exactly the mispairing the prose four lines up warns 
about:
   
   > As with `WindowRangeQuery`, each store accepts only its corresponding 
form; submitting the wrong form fails with an unknown-query-type error.
   
   Confirmed in the class javadoc: "Submitting the `withWindowStartRange` form 
to a session store, or the `withKey` form to a window store, fails with 
`FailureReason#UNKNOWN_QUERY_TYPE`" 
(`TimestampedWindowRangeWithHeadersQuery.java`, class javadoc).
   
   A reader who takes "identically" at face value and swaps `byWindow` for 
`byKey` in the shown request gets `UNKNOWN_QUERY_TYPE`. Suggest showing both 
requests, since the store names are already defined above:
   
   ```java
   StateQueryRequest<ReadOnlyRecordIterator<Windowed<String>, Long>> 
windowRequest =
       StateQueryRequest.inStore("counts-window-store").withQuery(byWindow);
   
   StateQueryRequest<ReadOnlyRecordIterator<Windowed<String>, Long>> 
sessionRequest =
       StateQueryRequest.inStore("counts-session-store").withQuery(byKey);
   ```
   
   and narrowing the comment to "Both forms have the same result type; each 
must target its own store type."



##########
docs/streams/developer-guide/interactive-queries.md:
##########
@@ -257,6 +257,233 @@ After the application has started, you can get access to 
"CountsWindowStore" and
       System.out.println("Count of 'world' @ time " + windowTimestamp + " is " 
+ next.value);
     }
 
+## Header-aware stores and interactive queries 
{#header-aware-stores-interactive-queries}
+
+A [header-aware 
store](/{version}/streams/developer-guide/processor-api/#headers-in-state-stores)
 — built with a `*WithHeaders` supplier and its matching builder 
([KIP-1271](../../upgrade-guide/#kip-1271-headers-aware-stores)) — persists 
each record's 
[headers](</{version}/javadoc/org/apache/kafka/streams/processor/api/Record.html#headers()>)
 together with its value and timestamp. This section shows how to read those 
headers back interactively, through both the `store()` API and the IQv2 
`query()` API.
+
+### Reading headers with the `store()` API
+
+Look up the store with the `*WithHeaders()` entry from `QueryableStoreTypes` 
that matches your store type. The returned `ReadOnly*Store` surfaces the 
headers as part of its value type: 
[ValueTimestampHeaders](/{version}/javadoc/org/apache/kafka/streams/state/ValueTimestampHeaders.html)
 for key-value and window stores, and 
[AggregationWithHeaders](/{version}/javadoc/org/apache/kafka/streams/state/AggregationWithHeaders.html)
 for session stores. These examples assume a header-aware store built with a 
`*WithHeaders` supplier, as shown in [Headers in State 
Stores](/{version}/streams/developer-guide/processor-api/#headers-in-state-stores).
 There are only three such helpers — `timestampedKeyValueStoreWithHeaders()`, 
`timestampedWindowStoreWithHeaders()`, and `sessionStoreWithHeaders()`; there 
is no `*WithHeaders()` helper for a plain (non-timestamped) key-value or window 
store.
+    
+    
+    // Key-value store built with a *WithHeaders supplier
+    ReadOnlyKeyValueStore<String, ValueTimestampHeaders<Long>> keyValueStore =
+        streams.store(StoreQueryParameters.fromNameAndType(
+            "counts-store", 
QueryableStoreTypes.timestampedKeyValueStoreWithHeaders()));
+    
+    ValueTimestampHeaders<Long> vth = keyValueStore.get("hello");
+    if (vth != null) {
+      System.out.println("value:     " + vth.value());
+      System.out.println("timestamp: " + vth.timestamp());
+      System.out.println("headers:   " + vth.headers());
+    }
+    
+    // Window store built with a *WithHeaders supplier
+    ReadOnlyWindowStore<String, ValueTimestampHeaders<Long>> windowStore =
+        streams.store(StoreQueryParameters.fromNameAndType(
+            "counts-window-store", 
QueryableStoreTypes.timestampedWindowStoreWithHeaders()));
+    
+    // fetch returns a WindowStoreIterator whose values carry headers
+    try (WindowStoreIterator<ValueTimestampHeaders<Long>> it =
+             windowStore.fetch("hello", Instant.ofEpochMilli(0), 
Instant.now())) {
+      while (it.hasNext()) {
+        ValueTimestampHeaders<Long> wv = it.next().value;
+        System.out.println("value: " + wv.value() + " headers: " + 
wv.headers());
+      }
+    }
+
+Session stores return `AggregationWithHeaders<V>`, which exposes the 
aggregated value via `aggregation()` (not `value()`) and the headers via 
`headers()`.
+    
+    
+    // Session store built with a *WithHeaders supplier
+    ReadOnlySessionStore<String, AggregationWithHeaders<Long>> sessionStore =
+        streams.store(StoreQueryParameters.fromNameAndType(
+            "counts-session-store", 
QueryableStoreTypes.sessionStoreWithHeaders()));
+    
+    try (KeyValueIterator<Windowed<String>, AggregationWithHeaders<Long>> it =
+             sessionStore.fetch("hello")) {
+      while (it.hasNext()) {
+        AggregationWithHeaders<Long> awh = it.next().value;
+        System.out.println("aggregation: " + awh.aggregation());
+        System.out.println("headers:     " + awh.headers());
+      }
+    }
+
+### Reading headers with the IQv2 `query()` API
+
+Interactive Queries v2 (IQv2) is the query-based interactive-queries API: 
instead of accessing a store object directly, you build a `Query`, wrap it in a 
[StateQueryRequest](/{version}/javadoc/org/apache/kafka/streams/query/StateQueryRequest.html),
 and run it with `KafkaStreams#query(...)`. The call returns a 
[StateQueryResult](/{version}/javadoc/org/apache/kafka/streams/query/StateQueryResult.html)
 that holds a per-partition 
[QueryResult](/{version}/javadoc/org/apache/kafka/streams/query/QueryResult.html):
 use `getOnlyPartitionResult()` for a single-key lookup, or 
`getPartitionResults()` for the full `Map<Integer, QueryResult<R>>`. Each 
`QueryResult` exposes the query result via `getResult()` and its data-freshness 
`Position` via `getPosition()`.
+
+Before [KIP-1356](../../upgrade-guide/#kip-1356-iqv2-header-queries), no IQv2 
query type exposed record headers. 
[KIP-1356](../../upgrade-guide/#kip-1356-iqv2-header-queries) adds four 
`@Evolving` query types whose results carry headers. Each returns a 
[ReadOnlyRecord](/{version}/javadoc/org/apache/kafka/streams/processor/api/ReadOnlyRecord.html)
 — a read-only view exposing `key()`, `value()`, `timestamp()`, and `headers()` 
— or, for the range and window queries, a closeable 
[ReadOnlyRecordIterator](/{version}/javadoc/org/apache/kafka/streams/state/ReadOnlyRecordIterator.html)
 of such records. `headers()` is never null (an empty `Headers` when the record 
had none) and must be treated as read-only: records served as IQv2 results have 
their headers frozen, so adding or removing a header (for example `add(...)`) 
throws `IllegalStateException`. The freeze is shallow, though — the byte array 
behind an individual header value can still be mutated in place, so treat 
header values as 
 read-only too.
+
+`TimestampedKeyWithHeadersQuery` is a single-key lookup against a header-aware 
key-value store, parallel to `TimestampedKeyQuery`:
+    
+    
+    TimestampedKeyWithHeadersQuery<String, Long> query =
+        TimestampedKeyWithHeadersQuery.withKey("hello");
+    
+    StateQueryRequest<ReadOnlyRecord<String, Long>> request =
+        StateQueryRequest.inStore("counts-store").withQuery(query);
+    
+    StateQueryResult<ReadOnlyRecord<String, Long>> result = 
streams.query(request);
+    QueryResult<ReadOnlyRecord<String, Long>> partitionResult = 
result.getOnlyPartitionResult();
+    if (partitionResult != null && partitionResult.isSuccess()) {
+      ReadOnlyRecord<String, Long> record = partitionResult.getResult();
+      if (record != null) {
+        System.out.println("value:   " + record.value());
+        System.out.println("headers: " + record.headers());
+      }
+    }
+
+Call `skipCache()` on the query to bypass the record cache and read directly 
from the underlying store (of the four header-aware queries, only this 
single-key one offers `skipCache()`).

Review Comment:
   [content] "Call `skipCache()` on the query" reads as an in-place mutation, 
but the query types are immutable — `skipCache()` returns a **new** instance 
and discards nothing into the receiver:
   
   ```java
   public TimestampedKeyWithHeadersQuery<K, V> skipCache() {
       return new TimestampedKeyWithHeadersQuery<>(key, true);
   }
   ```
   (`TimestampedKeyWithHeadersQuery.java:81-83`)
   
   The example directly above binds the query to a variable and reuses it, so 
the natural reading of this sentence produces a silent no-op:
   
   ```java
   TimestampedKeyWithHeadersQuery<String, Long> query = 
TimestampedKeyWithHeadersQuery.withKey("hello");
   query.skipCache();   // return value dropped -- still cache-served
   ```
   
   Suggest showing the chained form instead: "Chain `skipCache()` when building 
the query — `TimestampedKeyWithHeadersQuery.withKey(\"hello\").skipCache()` — 
to bypass the record cache and read directly from the underlying store (of the 
four header-aware queries, only this single-key one offers `skipCache()`)."
   
   The scoping claim in the parenthetical is correct as written.



##########
docs/streams/developer-guide/interactive-queries.md:
##########
@@ -257,6 +257,233 @@ After the application has started, you can get access to 
"CountsWindowStore" and
       System.out.println("Count of 'world' @ time " + windowTimestamp + " is " 
+ next.value);
     }
 
+## Header-aware stores and interactive queries 
{#header-aware-stores-interactive-queries}
+
+A [header-aware 
store](/{version}/streams/developer-guide/processor-api/#headers-in-state-stores)
 — built with a `*WithHeaders` supplier and its matching builder 
([KIP-1271](../../upgrade-guide/#kip-1271-headers-aware-stores)) — persists 
each record's 
[headers](</{version}/javadoc/org/apache/kafka/streams/processor/api/Record.html#headers()>)
 together with its value and timestamp. This section shows how to read those 
headers back interactively, through both the `store()` API and the IQv2 
`query()` API.
+
+### Reading headers with the `store()` API
+
+Look up the store with the `*WithHeaders()` entry from `QueryableStoreTypes` 
that matches your store type. The returned `ReadOnly*Store` surfaces the 
headers as part of its value type: 
[ValueTimestampHeaders](/{version}/javadoc/org/apache/kafka/streams/state/ValueTimestampHeaders.html)
 for key-value and window stores, and 
[AggregationWithHeaders](/{version}/javadoc/org/apache/kafka/streams/state/AggregationWithHeaders.html)
 for session stores. These examples assume a header-aware store built with a 
`*WithHeaders` supplier, as shown in [Headers in State 
Stores](/{version}/streams/developer-guide/processor-api/#headers-in-state-stores).
 There are only three such helpers — `timestampedKeyValueStoreWithHeaders()`, 
`timestampedWindowStoreWithHeaders()`, and `sessionStoreWithHeaders()`; there 
is no `*WithHeaders()` helper for a plain (non-timestamped) key-value or window 
store.

Review Comment:
   [completeness] This subsection gives the reader no way to know that what 
`store()` returns also depends on how the store was built. The table below 
covers exactly that, but it sits under `### Reading headers with the IQv2 
query() API` and is written in IQv2 vocabulary ("Query outcome", 
"unknown-query-type", "store-exception"), so a reader who only needs the 
`store()` API never sees it — and the two adapter paths degrade *silently* 
here, unlike IQv2:
   
   - **Timestamped adapter** — `headers()` comes back empty, same as the 
table's timestamped row. The examples on this page print `vth.headers()` with 
no hint it can be empty.
   - **Plain adapter** — worse: `ValueTimestampHeaders` performs no timestamp 
validation (`ValueTimestampHeaders.java:40-99` — the constructor and both 
factories only null-check the value), so `vth.timestamp()` returns the injected 
`-1` with no error. The negative-timestamp guard that turns this into a 
`STORE_EXCEPTION` exists *only* on the IQv2 path 
(`MeteredTimestampedKeyValueStoreWithHeaders.java:421-429`); `get(K)` at `:135` 
goes straight through `getInternal` with no such check.
   
   So the identical store that makes `TimestampedKeyWithHeadersQuery` fail 
loudly makes `store().get()` return a plausible-looking `ValueTimestampHeaders` 
with a bogus timestamp. Suggest one sentence here pointing forward, e.g.: "What 
the store returns also depends on the supplier the `*WithHeaders` builder wraps 
— see [the table below](#header-aware-stores-interactive-queries); note that on 
the adapter paths the `store()` API degrades silently (empty headers, or a `-1` 
timestamp) rather than failing."



##########
docs/streams/developer-guide/interactive-queries.md:
##########
@@ -257,6 +257,233 @@ After the application has started, you can get access to 
"CountsWindowStore" and
       System.out.println("Count of 'world' @ time " + windowTimestamp + " is " 
+ next.value);
     }
 
+## Header-aware stores and interactive queries 
{#header-aware-stores-interactive-queries}
+
+A [header-aware 
store](/{version}/streams/developer-guide/processor-api/#headers-in-state-stores)
 — built with a `*WithHeaders` supplier and its matching builder 
([KIP-1271](../../upgrade-guide/#kip-1271-headers-aware-stores)) — persists 
each record's 
[headers](</{version}/javadoc/org/apache/kafka/streams/processor/api/Record.html#headers()>)
 together with its value and timestamp. This section shows how to read those 
headers back interactively, through both the `store()` API and the IQv2 
`query()` API.
+
+### Reading headers with the `store()` API
+
+Look up the store with the `*WithHeaders()` entry from `QueryableStoreTypes` 
that matches your store type. The returned `ReadOnly*Store` surfaces the 
headers as part of its value type: 
[ValueTimestampHeaders](/{version}/javadoc/org/apache/kafka/streams/state/ValueTimestampHeaders.html)
 for key-value and window stores, and 
[AggregationWithHeaders](/{version}/javadoc/org/apache/kafka/streams/state/AggregationWithHeaders.html)
 for session stores. These examples assume a header-aware store built with a 
`*WithHeaders` supplier, as shown in [Headers in State 
Stores](/{version}/streams/developer-guide/processor-api/#headers-in-state-stores).
 There are only three such helpers — `timestampedKeyValueStoreWithHeaders()`, 
`timestampedWindowStoreWithHeaders()`, and `sessionStoreWithHeaders()`; there 
is no `*WithHeaders()` helper for a plain (non-timestamped) key-value or window 
store.
+    
+    
+    // Key-value store built with a *WithHeaders supplier
+    ReadOnlyKeyValueStore<String, ValueTimestampHeaders<Long>> keyValueStore =
+        streams.store(StoreQueryParameters.fromNameAndType(
+            "counts-store", 
QueryableStoreTypes.timestampedKeyValueStoreWithHeaders()));
+    
+    ValueTimestampHeaders<Long> vth = keyValueStore.get("hello");
+    if (vth != null) {
+      System.out.println("value:     " + vth.value());
+      System.out.println("timestamp: " + vth.timestamp());
+      System.out.println("headers:   " + vth.headers());
+    }
+    
+    // Window store built with a *WithHeaders supplier
+    ReadOnlyWindowStore<String, ValueTimestampHeaders<Long>> windowStore =
+        streams.store(StoreQueryParameters.fromNameAndType(
+            "counts-window-store", 
QueryableStoreTypes.timestampedWindowStoreWithHeaders()));
+    
+    // fetch returns a WindowStoreIterator whose values carry headers
+    try (WindowStoreIterator<ValueTimestampHeaders<Long>> it =
+             windowStore.fetch("hello", Instant.ofEpochMilli(0), 
Instant.now())) {
+      while (it.hasNext()) {
+        ValueTimestampHeaders<Long> wv = it.next().value;
+        System.out.println("value: " + wv.value() + " headers: " + 
wv.headers());
+      }
+    }
+
+Session stores return `AggregationWithHeaders<V>`, which exposes the 
aggregated value via `aggregation()` (not `value()`) and the headers via 
`headers()`.
+    
+    
+    // Session store built with a *WithHeaders supplier
+    ReadOnlySessionStore<String, AggregationWithHeaders<Long>> sessionStore =
+        streams.store(StoreQueryParameters.fromNameAndType(
+            "counts-session-store", 
QueryableStoreTypes.sessionStoreWithHeaders()));
+    
+    try (KeyValueIterator<Windowed<String>, AggregationWithHeaders<Long>> it =
+             sessionStore.fetch("hello")) {
+      while (it.hasNext()) {
+        AggregationWithHeaders<Long> awh = it.next().value;
+        System.out.println("aggregation: " + awh.aggregation());
+        System.out.println("headers:     " + awh.headers());
+      }
+    }
+
+### Reading headers with the IQv2 `query()` API
+
+Interactive Queries v2 (IQv2) is the query-based interactive-queries API: 
instead of accessing a store object directly, you build a `Query`, wrap it in a 
[StateQueryRequest](/{version}/javadoc/org/apache/kafka/streams/query/StateQueryRequest.html),
 and run it with `KafkaStreams#query(...)`. The call returns a 
[StateQueryResult](/{version}/javadoc/org/apache/kafka/streams/query/StateQueryResult.html)
 that holds a per-partition 
[QueryResult](/{version}/javadoc/org/apache/kafka/streams/query/QueryResult.html):
 use `getOnlyPartitionResult()` for a single-key lookup, or 
`getPartitionResults()` for the full `Map<Integer, QueryResult<R>>`. Each 
`QueryResult` exposes the query result via `getResult()` and its data-freshness 
`Position` via `getPosition()`.

Review Comment:
   [completeness] This paragraph introduces `Position` on the read side 
(`getPosition()`) but never the write side, yet the third behavior bullet at 
line 399 depends on it: "with a position bound, fails with a not-up-to-bound 
error." Nothing on the page says how a reader would set one, so "position 
bound" arrives as an undefined term at the point where it matters most.
   
   It is a one-clause fix here, where `StateQueryRequest` is already being 
described: mention `StateQueryRequest#withPositionBound(...)` alongside 
`getPosition()`. That also makes the bullet's failure mode actionable — right 
now a reader can observe `NOT_UP_TO_BOUND` but has no pointer to what produced 
it.



##########
docs/streams/developer-guide/interactive-queries.md:
##########
@@ -135,7 +135,7 @@ Every application instance can directly query any of its 
local state stores.
 
 The _name_ of a state store is defined when you create the store. You can 
create the store explicitly by using the Processor API or implicitly by using 
stateful operations in the DSL.
 
-The _type_ of a state store is defined by `QueryableStoreType`. Pass a 
built-in implementation from 
[`QueryableStoreTypes`](/{version}/javadoc/org/apache/kafka/streams/state/QueryableStoreTypes.html)
 as the second argument to `KafkaStreams#store(...)`. The available built-in 
helpers are:
+The _type_ of a state store is defined by `QueryableStoreType`. Pass a 
built-in implementation from 
[`QueryableStoreTypes`](/{version}/javadoc/org/apache/kafka/streams/state/QueryableStoreTypes.html)
 to `StoreQueryParameters.fromNameAndType(...)`, then hand that to 
`KafkaStreams#store(...)`. The available built-in helpers are:

Review Comment:
   [completeness] This PR makes `StoreQueryParameters` load-bearing on this 
page — it now appears in all four `store()` examples — but leaves it as a bare 
code span, while `QueryableStoreTypes` in this same sentence gets a javadoc 
link. Suggest linking it too: 
`/{version}/javadoc/org/apache/kafka/streams/StoreQueryParameters.html` (note 
the package is `org.apache.kafka.streams`, not `...streams.state`).
   
   Separately — genuinely optional, and fair to defer as outside this KIP's 
scope — `StoreQueryParameters` has two builder methods this page never mentions:
   
   ```java
   public StoreQueryParameters<T> withPartition(final Integer partition)  // 
StoreQueryParameters.java:54
   public StoreQueryParameters<T> enableStaleStores()                     // :63
   ```
   
   `grep -niE 'stale|withPartition'` over this file returns **zero** hits, yet 
the page's entire subject is querying local state stores — and 
`enableStaleStores()` is precisely what a reader needs in order to query during 
restore or a rebalance, which is one of the most common IQ questions. Worth a 
sentence here, or a follow-up PR.
   
   (Disclosure: the current wording of this line is what I suggested in an 
earlier round, so the missing link is my omission, not yours.)



##########
docs/streams/developer-guide/interactive-queries.md:
##########
@@ -257,6 +257,233 @@ After the application has started, you can get access to 
"CountsWindowStore" and
       System.out.println("Count of 'world' @ time " + windowTimestamp + " is " 
+ next.value);
     }
 
+## Header-aware stores and interactive queries 
{#header-aware-stores-interactive-queries}
+
+A [header-aware 
store](/{version}/streams/developer-guide/processor-api/#headers-in-state-stores)
 — built with a `*WithHeaders` supplier and its matching builder 
([KIP-1271](../../upgrade-guide/#kip-1271-headers-aware-stores)) — persists 
each record's 
[headers](</{version}/javadoc/org/apache/kafka/streams/processor/api/Record.html#headers()>)
 together with its value and timestamp. This section shows how to read those 
headers back interactively, through both the `store()` API and the IQv2 
`query()` API.
+
+### Reading headers with the `store()` API
+
+Look up the store with the `*WithHeaders()` entry from `QueryableStoreTypes` 
that matches your store type. The returned `ReadOnly*Store` surfaces the 
headers as part of its value type: 
[ValueTimestampHeaders](/{version}/javadoc/org/apache/kafka/streams/state/ValueTimestampHeaders.html)
 for key-value and window stores, and 
[AggregationWithHeaders](/{version}/javadoc/org/apache/kafka/streams/state/AggregationWithHeaders.html)
 for session stores. These examples assume a header-aware store built with a 
`*WithHeaders` supplier, as shown in [Headers in State 
Stores](/{version}/streams/developer-guide/processor-api/#headers-in-state-stores).
 There are only three such helpers — `timestampedKeyValueStoreWithHeaders()`, 
`timestampedWindowStoreWithHeaders()`, and `sessionStoreWithHeaders()`; there 
is no `*WithHeaders()` helper for a plain (non-timestamped) key-value or window 
store.
+    
+    
+    // Key-value store built with a *WithHeaders supplier
+    ReadOnlyKeyValueStore<String, ValueTimestampHeaders<Long>> keyValueStore =
+        streams.store(StoreQueryParameters.fromNameAndType(
+            "counts-store", 
QueryableStoreTypes.timestampedKeyValueStoreWithHeaders()));
+    
+    ValueTimestampHeaders<Long> vth = keyValueStore.get("hello");
+    if (vth != null) {
+      System.out.println("value:     " + vth.value());
+      System.out.println("timestamp: " + vth.timestamp());
+      System.out.println("headers:   " + vth.headers());
+    }
+    
+    // Window store built with a *WithHeaders supplier
+    ReadOnlyWindowStore<String, ValueTimestampHeaders<Long>> windowStore =
+        streams.store(StoreQueryParameters.fromNameAndType(
+            "counts-window-store", 
QueryableStoreTypes.timestampedWindowStoreWithHeaders()));
+    
+    // fetch returns a WindowStoreIterator whose values carry headers
+    try (WindowStoreIterator<ValueTimestampHeaders<Long>> it =
+             windowStore.fetch("hello", Instant.ofEpochMilli(0), 
Instant.now())) {
+      while (it.hasNext()) {
+        ValueTimestampHeaders<Long> wv = it.next().value;
+        System.out.println("value: " + wv.value() + " headers: " + 
wv.headers());
+      }
+    }
+
+Session stores return `AggregationWithHeaders<V>`, which exposes the 
aggregated value via `aggregation()` (not `value()`) and the headers via 
`headers()`.
+    
+    
+    // Session store built with a *WithHeaders supplier
+    ReadOnlySessionStore<String, AggregationWithHeaders<Long>> sessionStore =
+        streams.store(StoreQueryParameters.fromNameAndType(
+            "counts-session-store", 
QueryableStoreTypes.sessionStoreWithHeaders()));
+    
+    try (KeyValueIterator<Windowed<String>, AggregationWithHeaders<Long>> it =
+             sessionStore.fetch("hello")) {
+      while (it.hasNext()) {
+        AggregationWithHeaders<Long> awh = it.next().value;
+        System.out.println("aggregation: " + awh.aggregation());
+        System.out.println("headers:     " + awh.headers());
+      }
+    }
+
+### Reading headers with the IQv2 `query()` API
+
+Interactive Queries v2 (IQv2) is the query-based interactive-queries API: 
instead of accessing a store object directly, you build a `Query`, wrap it in a 
[StateQueryRequest](/{version}/javadoc/org/apache/kafka/streams/query/StateQueryRequest.html),
 and run it with `KafkaStreams#query(...)`. The call returns a 
[StateQueryResult](/{version}/javadoc/org/apache/kafka/streams/query/StateQueryResult.html)
 that holds a per-partition 
[QueryResult](/{version}/javadoc/org/apache/kafka/streams/query/QueryResult.html):
 use `getOnlyPartitionResult()` for a single-key lookup, or 
`getPartitionResults()` for the full `Map<Integer, QueryResult<R>>`. Each 
`QueryResult` exposes the query result via `getResult()` and its data-freshness 
`Position` via `getPosition()`.
+
+Before [KIP-1356](../../upgrade-guide/#kip-1356-iqv2-header-queries), no IQv2 
query type exposed record headers. 
[KIP-1356](../../upgrade-guide/#kip-1356-iqv2-header-queries) adds four 
`@Evolving` query types whose results carry headers. Each returns a 
[ReadOnlyRecord](/{version}/javadoc/org/apache/kafka/streams/processor/api/ReadOnlyRecord.html)
 — a read-only view exposing `key()`, `value()`, `timestamp()`, and `headers()` 
— or, for the range and window queries, a closeable 
[ReadOnlyRecordIterator](/{version}/javadoc/org/apache/kafka/streams/state/ReadOnlyRecordIterator.html)
 of such records. `headers()` is never null (an empty `Headers` when the record 
had none) and must be treated as read-only: records served as IQv2 results have 
their headers frozen, so adding or removing a header (for example `add(...)`) 
throws `IllegalStateException`. The freeze is shallow, though — the byte array 
behind an individual header value can still be mutated in place, so treat 
header values as 
 read-only too.
+
+`TimestampedKeyWithHeadersQuery` is a single-key lookup against a header-aware 
key-value store, parallel to `TimestampedKeyQuery`:
+    
+    
+    TimestampedKeyWithHeadersQuery<String, Long> query =
+        TimestampedKeyWithHeadersQuery.withKey("hello");
+    
+    StateQueryRequest<ReadOnlyRecord<String, Long>> request =
+        StateQueryRequest.inStore("counts-store").withQuery(query);
+    
+    StateQueryResult<ReadOnlyRecord<String, Long>> result = 
streams.query(request);
+    QueryResult<ReadOnlyRecord<String, Long>> partitionResult = 
result.getOnlyPartitionResult();
+    if (partitionResult != null && partitionResult.isSuccess()) {
+      ReadOnlyRecord<String, Long> record = partitionResult.getResult();
+      if (record != null) {
+        System.out.println("value:   " + record.value());
+        System.out.println("headers: " + record.headers());
+      }
+    }
+
+Call `skipCache()` on the query to bypass the record cache and read directly 
from the underlying store (of the four header-aware queries, only this 
single-key one offers `skipCache()`).
+
+`TimestampedRangeWithHeadersQuery` is a key-range scan, parallel to 
`TimestampedRangeQuery`. It returns a `ReadOnlyRecordIterator`, so close it 
when done (for example, with try-with-resources). A range can span several 
local partitions, so iterate `getPartitionResults()`:
+    
+    
+    TimestampedRangeWithHeadersQuery<String, Long> query =
+        TimestampedRangeWithHeadersQuery.withRange("a", "n");
+    
+    StateQueryRequest<ReadOnlyRecordIterator<String, Long>> request =
+        StateQueryRequest.inStore("counts-store").withQuery(query);
+    
+    StateQueryResult<ReadOnlyRecordIterator<String, Long>> result = 
streams.query(request);
+    for (QueryResult<ReadOnlyRecordIterator<String, Long>> partition : 
result.getPartitionResults().values()) {
+      if (partition.isFailure()) {
+        System.out.println("failed: " + partition.getFailureReason() + " - " + 
partition.getFailureMessage());
+        continue;
+      }
+      try (ReadOnlyRecordIterator<String, Long> iterator = 
partition.getResult()) {
+        while (iterator.hasNext()) {
+          ReadOnlyRecord<String, Long> record = iterator.next();
+          System.out.println(record.key() + " -> " + record.value() + " " + 
record.headers());
+        }
+      }
+    }
+
+Use `withLowerBound`, `withUpperBound`, or `withNoBounds` for open-ended or 
full scans. Results are unordered by default; call `withAscendingKeys()` or 
`withDescendingKeys()` to fix the order, which is defined over the serialized 
`byte[]` of the keys rather than their logical order.
+
+`TimestampedWindowKeyWithHeadersQuery` fetches all windows for a single key 
within a window-start range from a header-aware window store. It parallels 
`WindowKeyQuery`, but with a different result shape: `WindowKeyQuery` returns a 
`WindowStoreIterator<V>` keyed by the window-start `long`, whereas this query 
returns a `ReadOnlyRecordIterator<Windowed<K>, V>` whose records are keyed by 
`Windowed<K>` (the window lives in the key; `timestamp()` is the stored record 
event-time). Build and consume it as for the range query above, but note the 
`Windowed<String>` in the request and result types:
+    
+    
+    TimestampedWindowKeyWithHeadersQuery<String, Long> query =
+        TimestampedWindowKeyWithHeadersQuery.withKeyAndWindowStartRange(
+            "hello", Instant.ofEpochMilli(0), Instant.now());
+    
+    StateQueryRequest<ReadOnlyRecordIterator<Windowed<String>, Long>> request =
+        StateQueryRequest.inStore("counts-window-store").withQuery(query);
+    
+    StateQueryResult<ReadOnlyRecordIterator<Windowed<String>, Long>> result = 
streams.query(request);
+    // Iterate result.getPartitionResults() and close each 
ReadOnlyRecordIterator, as in the range example.
+
+`TimestampedWindowRangeWithHeadersQuery` is parallel to `WindowRangeQuery` and 
has two forms. Use `withWindowStartRange(timeFrom, timeTo)` against a 
header-aware window store to fetch every key across a window-start range, or 
`withKey(key)` against a header-aware session store to fetch all sessions for a 
key (for session results, `timestamp()` is the session-window end). As with 
`WindowRangeQuery`, each store accepts only its corresponding form; submitting 
the wrong form fails with an unknown-query-type error. Both forms are 
`Query<ReadOnlyRecordIterator<Windowed<K>, V>>` — including the session 
`withKey` form, whose records are keyed by the session's `Windowed<K>`.
+    
+    
+    // Window store: every key across a window-start range
+    TimestampedWindowRangeWithHeadersQuery<String, Long> byWindow =
+        TimestampedWindowRangeWithHeadersQuery.withWindowStartRange(
+            Instant.ofEpochMilli(0), Instant.now());
+    
+    // Session store: all sessions for one key
+    TimestampedWindowRangeWithHeadersQuery<String, Long> byKey =
+        TimestampedWindowRangeWithHeadersQuery.withKey("hello");
+    
+    // Both forms build and consume identically; the result element type is 
ReadOnlyRecord<Windowed<String>, Long>:
+    StateQueryRequest<ReadOnlyRecordIterator<Windowed<String>, Long>> request =
+        StateQueryRequest.inStore("counts-window-store").withQuery(byWindow);
+    StateQueryResult<ReadOnlyRecordIterator<Windowed<String>, Long>> result = 
streams.query(request);
+    // Iterate result.getPartitionResults() and close each 
ReadOnlyRecordIterator, as in the range example.
+
+**Behavior notes**
+
+  * **Window start range is required.** As with the existing window queries, 
`TimestampedWindowKeyWithHeadersQuery` and the `withWindowStartRange` form of 
`TimestampedWindowRangeWithHeadersQuery` require a closed window-start range — 
both `timeFrom` and `timeTo` must be present, and both bounds are inclusive.
+  * **Close iterators exactly once.** The range and window queries return a 
`ReadOnlyRecordIterator`; close it when you are done — always, even if a 
`next()` call throws partway through — or the underlying store iterator (and 
the store's `num-open-iterators` metric) leaks. A try-with-resources block does 
this correctly. The iterator does not support `remove()`.
+  * **Read-your-writes applies only to the single-key query.** Only 
`TimestampedKeyWithHeadersQuery` reads through the record cache, so it sees a 
write that has not yet been flushed to the store — unless you call 
`skipCache()`, or the entry has already been flushed. The range, window, and 
session queries bypass the cache entirely, so a not-yet-flushed write is 
invisible to them and, with a position bound, fails with a not-up-to-bound 
error.
+
+**How the store was built determines what the queries return.** For key-value 
and window stores, the outcome depends on the supplier the `*WithHeaders` 
builder wraps:
+
+<table>
+<tr>
+<th>
+
+`*WithHeaders` store built over…
+</th>
+<th>
+
+Headers
+</th>
+<th>
+
+Query outcome
+</th> </tr>
+<tr>
+<td>
+
+Native (RocksDB) header supplier
+</td>
+<td>
+
+Returned
+</td>
+<td>
+
+All succeed
+</td> </tr>
+<tr>
+<td>
+
+In-memory non-header supplier
+</td>
+<td>
+
+Returned (a marker keeps the header-format bytes verbatim)
+</td>
+<td>
+
+All succeed
+</td> </tr>
+<tr>
+<td>
+
+Persistent *timestamped* non-header supplier
+</td>
+<td>
+
+Empty
+</td>
+<td>
+
+All succeed
+</td> </tr>
+<tr>
+<td>
+
+Persistent *plain* non-header supplier
+</td>
+<td>
+
+Returned while cache-served; otherwise —
+</td>
+<td>
+
+Store-served point query fails with a store-exception error (a cache-served 
read still succeeds, with real value, timestamp, and headers, until the cache 
is flushed or `skipCache()` is used); the range, window-key, and 
`withWindowStartRange` window-range iterators throw a `StreamsException` 
mid-iteration. (The `withKey` form of the window-range query targets session 
stores, covered in the note below.)
+</td> </tr>
+<tr>
+<td>
+
+*(no `*WithHeaders` builder at all)*
+</td>
+<td>
+
+—
+</td>
+<td>
+
+Unknown-query-type
+</td> </tr> </table>
+
+Session stores have no plain/timestamped split: a `*WithHeaders` session store 
built over a non-header supplier uses a single adapter and behaves like the 
*timestamped* row above. The `withKey` form of 
`TimestampedWindowRangeWithHeadersQuery` (the session-store form) never throws 
— a session window always carries a valid end timestamp — so it returns empty 
`headers()` and surfaces a `null` `value()` only where the stored value itself 
is null.

Review Comment:
   [content] "uses a single adapter and behaves like the *timestamped* row 
above" holds only for a **persistent** non-header supplier. The session builder 
branches the same two ways the key-value/window builders do:
   
   ```java
   if (!(sessionStore instanceof HeadersBytesStore)) {
       if (sessionStore.persistent()) {
           sessionStore = new SessionToHeadersStoreAdapter(sessionStore);
       } else {
           sessionStore = new 
InMemorySessionStoreWithHeadersMarker(sessionStore);
       }
   }
   ```
   (`SessionStoreWithHeadersBuilder.java:62-69`)
   
   `InMemorySessionStoreWithHeadersMarker` (`:97-99`) is a marker — it 
`implements HeadersBytesStore` and wraps without converting, so the 
header-format bytes round-trip verbatim. An **in-memory** session store built 
over a non-header supplier therefore behaves like the *in-memory* row of the 
table (headers returned), not the *timestamped* row (headers empty).
   
   This is the same distinction you correctly added to the table for key-value 
and window stores; the session footnote just didn't get it. Suggest: "…a 
`*WithHeaders` session store built over a non-header **persistent** supplier 
uses a single adapter and behaves like the *timestamped* row above; over an 
in-memory supplier it uses a marker and behaves like the *in-memory* row."
   
   The rest of the sentence — no plain/timestamped split, and the `withKey` 
form never throwing — is correct.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to