@dashevo/dapi-grpc 4.1.0 → 4.2.0-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dashevo/dapi-grpc",
3
- "version": "4.1.0",
3
+ "version": "4.2.0-dev.1",
4
4
  "description": "DAPI GRPC definition file and generated clients",
5
5
  "browser": "browser.js",
6
6
  "main": "node.js",
@@ -45,7 +45,7 @@
45
45
  },
46
46
  "homepage": "https://github.com/dashevo/dapi-grpc#readme",
47
47
  "dependencies": {
48
- "@dashevo/grpc-common": "4.1.0",
48
+ "@dashevo/grpc-common": "4.2.0-dev.1",
49
49
  "@dashevo/protobufjs": "6.10.5",
50
50
  "@grpc/grpc-js": "^1.14.3",
51
51
  "@improbable-eng/grpc-web": "^0.15.0",
@@ -661,9 +661,13 @@ message GetDocumentsRequest {
661
661
  }
662
662
 
663
663
  // Per-group aggregate operand for the left side of a
664
- // `HavingClause`. Only the per-group aggregates live here:
665
- // `MIN` / `MAX` / `TOP` / `BOTTOM` are **cross-group** ranking
666
- // primitives and appear on the right side via `HavingRanking`.
664
+ // `HavingClause`, and the aggregate-function target of an
665
+ // `OrderClause`. Only the per-group aggregates live here
666
+ // `HAVING` is a boolean predicate over one group's own aggregate,
667
+ // and nothing on this message reaches across groups. Cross-group
668
+ // ranking ("which groups score highest?") is expressed with SQL's
669
+ // ordering surface instead: `ORDER BY <the selected aggregate>
670
+ // DESC LIMIT n [OFFSET m]`. See `GetDocumentsRequestV1.order_by`.
667
671
  //
668
672
  // **Field semantics by function**:
669
673
  // - `COUNT`: empty `field` means `COUNT(*)` (group cardinality);
@@ -682,58 +686,42 @@ message GetDocumentsRequest {
682
686
  string field = 2;
683
687
  }
684
688
 
685
- // Cross-group ranking primitive on the right side of a
686
- // `HavingClause`. The ranking is computed over the set of
687
- // group-aggregate results (one per `GROUP BY` row), so
688
- // `HAVING COUNT(*) EQ MAX` selects groups whose count equals
689
- // the maximum count across all groups, and
690
- // `HAVING COUNT(*) IN TOP(5)` selects groups whose count is
691
- // among the five largest. Concise way to express top-N /
692
- // bottom-N selection without window functions or
693
- // `ORDER BY` + `LIMIT`.
694
- //
695
- // **Operator compatibility**:
696
- // - Scalar operators (`=`, `!=`, `<`, `<=`, `>`, `>=`) work
697
- // with `MIN` / `MAX`. `TOP` / `BOTTOM` with scalar operators
698
- // only make sense when `n=1` (the single largest / smallest);
699
- // evaluation rejects other combinations as ambiguous.
700
- // - `IN` works with `TOP(n)` / `BOTTOM(n)` for set membership.
701
- // - `BETWEEN*` doesn't compose meaningfully with rankings and
702
- // is rejected at evaluation time.
703
- message HavingRanking {
704
- enum Kind {
705
- MIN = 0;
706
- MAX = 1;
707
- TOP = 2;
708
- BOTTOM = 3;
709
- }
710
- Kind kind = 1;
711
- // N-th rank for `TOP` / `BOTTOM` (1-indexed: `n=1` is the
712
- // single largest / smallest). Required for those two kinds;
713
- // must be unset for `MIN` / `MAX`. The wire allows setting
714
- // it on `MIN` / `MAX` for forward compatibility, but
715
- // evaluation rejects it as a malformed ranking.
716
- optional uint64 n = 2 [jstype = JS_STRING];
717
- }
718
-
719
- // Single `HAVING <aggregate> <op> <right>` clause. Multiple
689
+ // Single `HAVING <aggregate> <op> <value>` clause. Multiple
720
690
  // entries in `GetDocumentsRequestV1.having` combine with
721
691
  // implicit AND — same semantics as multiple `where_clauses`
722
692
  // entries. `HAVING COUNT(*) > 5 AND SUM(amount) > 100` is two
723
693
  // `HavingClause` rows, not a tree.
724
694
  //
695
+ // **`HAVING` is a boolean per-group predicate and nothing else.**
696
+ // Its right operand is always a literal `DocumentFieldValue`; it
697
+ // never names another group or the set of groups. Cross-group
698
+ // ranking — "the 5 highest-scoring groups" — is `ORDER BY <the
699
+ // selected aggregate> DESC LIMIT 5`, exactly as in SQL, and is
700
+ // served by the ranked executor (protocol v14+). An earlier draft
701
+ // of this surface carried the ranking on the right of a `HAVING`
702
+ // (`HAVING AVG(grade) IN TOP(5)`); that spelling was removed
703
+ // before release rather than deprecated, because it invented
704
+ // non-SQL grammar for something SQL already expresses.
705
+ //
706
+ // **`HAVING` cannot yet combine with an aggregate `ORDER BY`.**
707
+ // The ranked executor reads a pre-sorted per-axis secondary and
708
+ // has no way to drop groups from the middle of that walk, so a
709
+ // request carrying both a non-empty `having` and a ranking
710
+ // `order_by` is rejected with `Unsupported` rather than served
711
+ // with one of the two silently ignored.
712
+ //
725
713
  // The operator set mirrors `WhereOperator` minus `STARTS_WITH`
726
714
  // (prefix matching has no natural meaning against a scalar
727
715
  // aggregate result, even a string-typed one). `BETWEEN*` and
728
716
  // `IN` operand semantics match `WhereOperator`: `BETWEEN*`
729
717
  // expects a 2-element `DocumentFieldValue.list` carrying
730
718
  // `[lower, upper]`, and `IN` expects a `list` of candidate
731
- // values (or a ranking set via `right.ranking`).
719
+ // values.
732
720
  //
733
- // The `right` oneof carries either a concrete
734
- // `DocumentFieldValue` (literal comparison target) or a
735
- // `HavingRanking` (cross-group reference). Exactly one is set;
736
- // the wire rejects an unset `right`.
721
+ // The `right` oneof exists (rather than a bare
722
+ // `DocumentFieldValue` field) so the "unset right operand" case
723
+ // stays distinguishable from "the literal null value"; the wire
724
+ // rejects an unset `right`.
737
725
  message HavingClause {
738
726
  enum Operator {
739
727
  EQUAL = 0;
@@ -752,33 +740,65 @@ message GetDocumentsRequest {
752
740
  Operator operator = 2;
753
741
  oneof right {
754
742
  DocumentFieldValue value = 3;
755
- HavingRanking ranking = 4;
756
743
  }
757
744
  }
758
745
 
759
- // Single `ORDER BY field <direction>` clause. Multi-field
746
+ // Single `ORDER BY <target> <direction>` clause. Multi-field
760
747
  // ordering is expressed by repeating this message at the
761
748
  // request level (`repeated OrderClause order_by = 4`), matching
762
749
  // SQL's `ORDER BY a ASC, b DESC` shape.
763
- // Single ORDER BY entry. Multi-entry ordering is expressed by
764
- // repeating this message at the request level.
765
750
  //
766
751
  // The `target` oneof carries either a plain field name
767
752
  // (`ORDER BY field`) or an aggregate function applied to a
768
- // field (`ORDER BY COUNT(*)`, `ORDER BY SUM(amount)`) — the
769
- // latter sorts per-group result rows produced by `GROUP BY`,
770
- // useful with `LIMIT` for top-N / bottom-N selection at the
771
- // routing layer (overlapping `HavingRanking::Top` / `Bottom`
772
- // but more general because the ranking field can be any
773
- // aggregate, not just count).
753
+ // field (`ORDER BY COUNT(*)`, `ORDER BY SUM(amount)`).
754
+ //
755
+ // **Two distinct roles ride the `field` target.**
756
+ //
757
+ // 1. *Row ordering* `select = DOCUMENTS`. `field` names a
758
+ // document property and the matched rows come back in that
759
+ // order. This is v0's behaviour, unchanged.
760
+ //
761
+ // 2. *Aggregate ordering* — the **ranked** surface (protocol
762
+ // v14+). With a `GROUP BY` and a single aggregate `select`,
763
+ // exactly one `order_by` clause naming that select's
764
+ // aggregate orders the *groups* by their aggregate value and
765
+ // routes the request to the ranked executor:
774
766
  //
775
- // **Aggregate target currently rejected** with
776
- // `Unsupported("ORDER BY on aggregate is not yet implemented")`.
777
- // The wire surface is shipped now so callers can encode the
778
- // shape ahead of server support landing.
767
+ // ```text
768
+ // SELECT AVG(grade) GROUP BY restaurantId
769
+ // ORDER BY grade DESC LIMIT 3 -- 3 best restaurants
770
+ // SELECT COUNT(*) GROUP BY restaurantId
771
+ // ORDER BY $count DESC LIMIT 10 OFFSET 10 -- busiest, page 2
772
+ // ```
773
+ //
774
+ // `SUM(f)` / `AVG(f)` are named by `f` — the same property the
775
+ // projection aggregates, which is how `ORDER BY avg(grade)`
776
+ // reads once `SELECT` has already fixed the function.
777
+ // `COUNT(*)` aggregates no property, so it is named by the
778
+ // reserved sentinel **`$count`**. The `$` prefix is what keeps
779
+ // the sentinel from colliding with a real document property:
780
+ // document properties cannot start with `$` (that namespace is
781
+ // the system fields' `$id` / `$ownerId` / …), so `$count` can
782
+ // never be mistaken for a column. `DESC` is the "top n"
783
+ // reading, `ASC` the "bottom n" reading.
784
+ //
785
+ // An `order_by` naming anything other than the selected
786
+ // aggregate — a second clause, the `GROUP BY` property, an
787
+ // unrelated field — is rejected rather than normalized: it
788
+ // asks for an ordering the ranked secondary cannot produce.
789
+ //
790
+ // **Aggregate target still rejected** with
791
+ // `Unsupported("ORDER BY on aggregate keys is not yet
792
+ // implemented")`. It is the *explicit* spelling of role 2
793
+ // (`ORDER BY AVG(grade)` rather than `ORDER BY grade` under a
794
+ // `SELECT AVG(grade)`) and is wire-stable so it can start being
795
+ // evaluated without another version bump; today the field-target
796
+ // spelling above is the one the ranked executor reads.
779
797
  message OrderClause {
780
798
  oneof target {
781
- // Plain field name. Today's evaluated form.
799
+ // Plain field name. Today's evaluated form — a document
800
+ // property for row ordering, or the selected aggregate's
801
+ // property (`$count` for `COUNT(*)`) for aggregate ordering.
782
802
  string field = 1;
783
803
  // Aggregate function applied to a field, sorted by the
784
804
  // per-group result. `function = DOCUMENTS` is invalid
@@ -823,12 +843,18 @@ message GetDocumentsRequest {
823
843
  // other shapes return `Unsupported` (see supported-shape table
824
844
  // below).
825
845
  //
826
- // `having` is wire-reserved for a future server capability. Any
827
- // non-empty `having` list currently returns
828
- // `Unsupported("HAVING clause is not yet implemented")`
829
- // regardless of `select` / `group_by`. The wire shape is
830
- // `repeated WhereClause` so when execution lands the surface is
831
- // already typed end-to-end and callers don't need to re-encode.
846
+ // **Ranked mode** is served from protocol v14 and is selected by
847
+ // `group_by` + a single `order_by` naming the selected aggregate —
848
+ // SQL's own top-n spelling, `ORDER BY <agg> DESC LIMIT n OFFSET m`.
849
+ // It returns `ResultData.ranked`. See `order_by` and the
850
+ // supported-shape table below.
851
+ //
852
+ // `having` is a boolean per-group predicate and is **still**
853
+ // `Unsupported` at every protocol version, ranked mode or not
854
+ // (`"HAVING clause is not yet implemented"`). It carries no ranking
855
+ // spelling: an earlier draft put cross-group ranking on the right of
856
+ // a `HAVING` (`HAVING AVG(grade) IN TOP(5)`) and that grammar was
857
+ // removed before release in favour of `ORDER BY` + `LIMIT`.
832
858
  //
833
859
  // **Supported shapes** (everything else rejects with a typed
834
860
  // `QuerySyntaxError::Unsupported` so callers can detect un-wired
@@ -853,8 +879,13 @@ message GetDocumentsRequest {
853
879
  // `select=COUNT, group_by=[a, b]`:
854
880
  // - a is the In field AND b is the range field, in that order → existing compound distinct shape; entries carry both `in_key` (= a's value) and `key` (= b's value).
855
881
  //
882
+ // `select=<COUNT(*)|SUM(f)|AVG(f)>, group_by=[p], order_by=[<the selected aggregate>]` (protocol v14+) — **ranked mode**:
883
+ // - exactly one `group_by` property, exactly one `order_by` clause naming the select's aggregate (`f` for `SUM(f)` / `AVG(f)`, the `$count` sentinel for `COUNT(*)`), a `limit` in `1 ..= 100`, an optional `offset`, and no `where` / `having` / `start_at`, on an index declaring the matching `rankedCountable` / `rankedSummable` / `rankedAverageable` axis → ranked executor, answered in `ResultData.ranked`.
884
+ // - `DESC` is the "top n" reading (walk the axis from the largest aggregate down), `ASC` the "bottom n" reading. Worked example: `SELECT AVG(grade) GROUP BY restaurantId ORDER BY grade DESC LIMIT 1 OFFSET 4` is the 5th-best restaurant.
885
+ //
856
886
  // **Rejected shapes** (return `Unsupported`):
857
- // - any non-empty `having` (always pending future server capability).
887
+ // - any non-empty `having`, at every protocol version.
888
+ // - at v14+: a ranked-shaped request carrying a `where` clause, a `start_at` / `start_after` cursor, more than one `order_by`, or an `order_by` naming anything but the selected aggregate.
858
889
  // - `select=DOCUMENTS` with non-empty `group_by`.
859
890
  // - `select=COUNT` with `group_by` on a field that is not constrained by an `In` or range where clause.
860
891
  // - `select=COUNT` with `group_by.len() > 2`.
@@ -927,11 +958,11 @@ message GetDocumentsRequest {
927
958
  AVG = 3;
928
959
  // Per-group MIN / MAX — `SELECT MIN(field) GROUP BY
929
960
  // category` returns the smallest `field` value in each
930
- // category. Semantically distinct from
931
- // `HavingRanking::Min` / `Max` (which are cross-group
932
- // meta-aggregates over group results). MIN/MAX here
933
- // operate over the row values within each group, the
934
- // same way `SUM` and `AVG` do.
961
+ // category. These operate over the row values *within*
962
+ // each group, the same way `SUM` and `AVG` do; they are
963
+ // not a cross-group ranking. To pick out the extreme
964
+ // *group*, order by the aggregate instead:
965
+ // `ORDER BY <agg> ASC|DESC LIMIT 1` (see `order_by`).
935
966
  MIN = 4;
936
967
  MAX = 5;
937
968
  }
@@ -1044,33 +1075,54 @@ message GetDocumentsRequest {
1044
1075
  // message-level docstring for the supported-shape table.
1045
1076
  repeated string group_by = 10;
1046
1077
 
1047
- // SQL `HAVING` clauses — aggregate filters that apply to the
1048
- // grouped rows produced by `select=COUNT, group_by=[…]`. The
1049
- // wire shape is `HavingClause`, not `WhereClause`, because
1050
- // HAVING evaluates against per-group aggregates
1051
- // (`COUNT`/`SUM`/`AVG`/`MIN`/`MAX`/`TOP`/`BOTTOM`) rather than
1052
- // row field values. Multiple entries combine with implicit
1053
- // AND. See `HavingClause` / `HavingAggregate` for the
1054
- // operator and aggregate-function catalogs.
1078
+ // SQL `HAVING` clauses — **boolean** aggregate filters that
1079
+ // apply to the grouped rows produced by `select=<COUNT|SUM|AVG>,
1080
+ // group_by=[…]`. The wire shape is `HavingClause`, not
1081
+ // `WhereClause`, because HAVING evaluates against per-group
1082
+ // aggregates (`COUNT` / `SUM` / `AVG`) rather than row field
1083
+ // values. Multiple entries combine with implicit AND. See
1084
+ // `HavingClause` / `HavingAggregate` for the operator and
1085
+ // aggregate-function catalogs.
1055
1086
  //
1056
- // **Always rejected when non-empty** today with
1057
- // `Unsupported("HAVING clause is not yet implemented")`. The
1058
- // wire shape is shipped now so the future server capability
1059
- // can land without another version bump and so callers can
1060
- // construct full `HAVING COUNT(*) > 5 AND SUM(amount) > 100`
1061
- // requests in their builders even before the server evaluates
1062
- // them.
1087
+ // **Every non-empty `having` is rejected**, at every protocol
1088
+ // version, with `Unsupported("HAVING clause is not yet
1089
+ // implemented")`. The wire shape ships ahead of evaluation so
1090
+ // callers can construct full `HAVING COUNT(*) > 5 AND
1091
+ // SUM(amount) > 100` requests in their builders, and so the
1092
+ // capability can land without another version bump.
1093
+ //
1094
+ // **`having` does not express ranking.** "The n highest-scoring
1095
+ // groups" is `ORDER BY <the selected aggregate> DESC LIMIT n`
1096
+ // (see `order_by`), which *is* served, from protocol v14. An
1097
+ // earlier draft of this surface spelled it
1098
+ // `HAVING AVG(grade) IN TOP(5)`; that grammar was removed
1099
+ // before release rather than deprecated.
1063
1100
  repeated HavingClause having = 11;
1064
1101
 
1065
1102
  // Row-based pagination offset, on top of the cursor-based
1066
1103
  // `start_after` / `start_at` pagination. `OFFSET N` skips the
1067
- // first `N` matching rows before applying `limit`. Currently
1068
- // **always rejected when non-`None`** with
1069
- // `Unsupported("OFFSET pagination is not yet implemented")`
1070
- // the wire surface is shipped now so callers can encode it
1071
- // ahead of server support landing without another version
1072
- // bump. Cursor pagination via `start_after` / `start_at`
1073
- // remains the supported way to page through results.
1104
+ // first `N` result rows before applying `limit`.
1105
+ //
1106
+ // **Consumed in ranked mode** (protocol v14+): on a request that
1107
+ // routes to the ranked executor (`group_by` + a single `order_by`
1108
+ // naming the selected aggregate), `offset` skips that many ranks
1109
+ // before the returned page, so `ORDER BY avg(grade) DESC LIMIT 1
1110
+ // OFFSET 4` is the 5th-best group. The skip is **count-attested**,
1111
+ // not walked: grovedb proves it from the counted subtree
1112
+ // commitments, so the proof stays `O(log n + k)` at any offset and
1113
+ // the response echoes the attested number in
1114
+ // `RankedEntries.skipped`. There is deliberately no ceiling — an
1115
+ // offset of 4 and an offset of four billion cost the same, so
1116
+ // there is no denial-of-service lever a cap would close. An offset
1117
+ // past the end of the ranking is a provable answer rather than an
1118
+ // error: `entries` comes back empty and `skipped` is the ranking's
1119
+ // whole population.
1120
+ //
1121
+ // **Rejected everywhere else** with
1122
+ // `Unsupported("OFFSET pagination is not yet implemented")` —
1123
+ // including on every path at protocol v13 and earlier, which has
1124
+ // no ranked executor. Cursor pagination via `start_after` /
1125
+ // `start_at` remains the supported way to page through documents.
1074
1126
  optional uint32 offset = 12;
1075
1127
  }
1076
1128
 
@@ -1258,9 +1310,115 @@ message GetDocumentsResponse {
1258
1310
  }
1259
1311
  }
1260
1312
 
1313
+ // One group in a ranked (`GROUP BY … ORDER BY <agg> LIMIT n`)
1314
+ // result: the group's index key plus the aggregate it was ranked
1315
+ // by.
1316
+ //
1317
+ // `key` is the raw index-key bytes of the GROUP BY property's
1318
+ // value — the same bytes that name the group's value tree under
1319
+ // the index (for a `string` property, its UTF-8 bytes). Clients
1320
+ // that want the typed value decode it with the document type's
1321
+ // key deserialization; the wire carries bytes so prover and
1322
+ // verifier agree without a schema round-trip.
1323
+ //
1324
+ // Exactly one `value` variant is set, determined by the
1325
+ // request's SELECT function:
1326
+ // * `count` — `SELECT COUNT(*)`, ranked on the index's
1327
+ // `rankedCountable` axis.
1328
+ // * `sum` — `SELECT SUM(field)`, `rankedSummable` axis.
1329
+ // Signed for the same reason `SumEntry.sum` is.
1330
+ // * `avg` — `SELECT AVG(field)`,
1331
+ // `rankedAverageable` axis.
1332
+ message RankedEntry {
1333
+ bytes key = 1;
1334
+ oneof value {
1335
+ // `jstype = JS_STRING` so JS/Web clients receive a string
1336
+ // and don't round counts > 2^53−1 to the nearest
1337
+ // representable Number — same choice as `CountEntry.count`.
1338
+ uint64 count = 2 [jstype = JS_STRING];
1339
+ // `jstype = JS_STRING` for the same precision reason as
1340
+ // `SumEntry.sum`.
1341
+ sint64 sum = 3 [jstype = JS_STRING];
1342
+ // The group's average, as a **`double` approximation** of the
1343
+ // exact value the Avg axis is ordered by.
1344
+ //
1345
+ // What grovedb actually commits to and sorts by is an `i128`
1346
+ // fixed-point integer: `floor(sum * SCALE / count)` with
1347
+ // euclidean (toward −∞) division, where SCALE is grovedb's
1348
+ // `AVG_FIXED_POINT_SCALE` (currently 10^19). This field is
1349
+ // that integer divided by SCALE in `f64`, i.e.
1350
+ // `fixed_point as f64 / SCALE as f64`.
1351
+ //
1352
+ // A `double` is honest here because `RankedEntry` is only ever
1353
+ // populated on the **no-proof ("quick answer") path**, where
1354
+ // the client has already chosen to trust the server's reply.
1355
+ // A proof-verifying client never reads this field: it
1356
+ // reconstructs each entry from the grovedb proof itself, where
1357
+ // the exact fixed-point `i128` lives, so nothing about proof
1358
+ // verification depends on this number's precision.
1359
+ //
1360
+ // Precision bound: `f64` carries ~15–16 significant decimal
1361
+ // digits, so two groups whose exact fixed-point averages differ
1362
+ // only beyond that can compare equal here. Do not use this
1363
+ // value for equality checks, tie-breaking, or any
1364
+ // reconstruction of the committed integer — request the proof
1365
+ // and read the fixed point from it instead. Entry *order* is
1366
+ // still exact: the server ranks on the i128 before converting.
1367
+ //
1368
+ // SCALE is a grovedb constant, not a wire constant: it moved
1369
+ // from 10^15 to 10^19 before release. Clients that need it
1370
+ // (e.g. to go back to fixed point on the proof path) should
1371
+ // read it from the SDK's re-export (`RANKED_AVG_SCALE`) rather
1372
+ // than hardcoding the literal.
1373
+ double avg = 4;
1374
+ }
1375
+ }
1376
+
1377
+ // Ranked result entries. **Entry order IS the ranking order** —
1378
+ // best-first for `ORDER BY <agg> DESC`, worst-first for `ASC`.
1379
+ // Clients must not re-sort; ties (equal aggregates) come back in
1380
+ // group-key order in the direction of the walk, which is
1381
+ // descending group-key order for `DESC`.
1382
+ //
1383
+ // Fewer than `limit` entries is normal — the index simply has
1384
+ // fewer groups than requested — and is not an error.
1385
+ message RankedEntries {
1386
+ repeated RankedEntry entries = 1;
1387
+
1388
+ // How many groups were skipped before the first entry — the
1389
+ // page's **starting rank base**. Entry `i` of `entries` is the
1390
+ // group at rank `skipped + i` (0-based).
1391
+ //
1392
+ // `0` for an `OFFSET 0` (or offset-less) query, which is what
1393
+ // makes this field additive: a caller that never paginates sees
1394
+ // the value it would have assumed. For `OFFSET m` it is
1395
+ // normally `m`, and it is what turns a page back into a
1396
+ // *ranking* — without it, a caller who asked for
1397
+ // `ORDER BY avg(grade) DESC LIMIT 1 OFFSET 4` receives one
1398
+ // entry with no way to tell that it really is the 5th-best
1399
+ // group rather than the best.
1400
+ //
1401
+ // **When a requested offset exceeds the population**, `entries`
1402
+ // is empty and `skipped` is the ranking's attested *total*
1403
+ // population — a positive, useful answer ("there are only 12
1404
+ // groups") rather than a bare empty list.
1405
+ //
1406
+ // On the proved path the number is grovedb's cryptographically
1407
+ // attested count, re-derived by the verifier from the counted
1408
+ // subtree commitments in the proof bytes rather than trusted
1409
+ // from this field; a proving client should use the verified
1410
+ // value. On the unproven read there is nothing to attest and
1411
+ // grovedb's read API does not report a short walk, so the server
1412
+ // echoes the requested offset. The two therefore disagree in
1413
+ // exactly one case — an offset past the end, where the unproven
1414
+ // read reports the request and the proved one reports the truth.
1415
+ // Callers who need the population must prove.
1416
+ optional uint64 skipped = 2 [jstype = JS_STRING];
1417
+ }
1418
+
1261
1419
  // Non-proof result wrapper. The outer `oneof result` switches
1262
1420
  // between this and `proof`; this inner oneof switches between
1263
- // the four non-proof shapes the v1 surface can return.
1421
+ // the non-proof shapes the v1 surface can return.
1264
1422
  message ResultData {
1265
1423
  oneof variant {
1266
1424
  Documents documents = 1;
@@ -1280,6 +1438,18 @@ message GetDocumentsResponse {
1280
1438
  // `book/src/drive/average-index-examples.md` for the design
1281
1439
  // and the grades-contract worked example.
1282
1440
  AverageResults averages = 4;
1441
+ // Ranked-aggregate result. Routed when the request pairs a
1442
+ // single-property `group_by` with a single `order_by` clause
1443
+ // naming the single aggregate `select` (the `$count` sentinel
1444
+ // for `COUNT(*)`) and a `limit` — SQL's `ORDER BY <agg>
1445
+ // DESC LIMIT n [OFFSET m]`. Answered from the per-axis
1446
+ // secondary of an indexed tree, so the index must declare the
1447
+ // matching `rankedCountable` / `rankedSummable` /
1448
+ // `rankedAverageable` keyword (meta-schema v3, protocol
1449
+ // version 14+). Entry order is the ranking order, and
1450
+ // `skipped` carries the page's starting rank — see
1451
+ // `RankedEntries`.
1452
+ RankedEntries ranked = 5;
1283
1453
  }
1284
1454
  }
1285
1455