@stigmer/server 3.15.2 → 3.15.3-dev.20260914121148

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.
Files changed (217) hide show
  1. package/dist/boot/compose.d.ts.map +1 -1
  2. package/dist/boot/compose.js +103 -9
  3. package/dist/boot/compose.js.map +1 -1
  4. package/dist/domain/iampolicy/access-lists.d.ts +27 -0
  5. package/dist/domain/iampolicy/access-lists.d.ts.map +1 -0
  6. package/dist/domain/iampolicy/access-lists.js +124 -0
  7. package/dist/domain/iampolicy/access-lists.js.map +1 -0
  8. package/dist/domain/iampolicy/constants.d.ts +135 -0
  9. package/dist/domain/iampolicy/constants.d.ts.map +1 -0
  10. package/dist/domain/iampolicy/constants.js +203 -0
  11. package/dist/domain/iampolicy/constants.js.map +1 -0
  12. package/dist/domain/iampolicy/controller.d.ts +29 -0
  13. package/dist/domain/iampolicy/controller.d.ts.map +1 -0
  14. package/dist/domain/iampolicy/controller.js +451 -0
  15. package/dist/domain/iampolicy/controller.js.map +1 -0
  16. package/dist/domain/iampolicy/display-resolver.d.ts +6 -0
  17. package/dist/domain/iampolicy/display-resolver.d.ts.map +1 -0
  18. package/dist/domain/iampolicy/display-resolver.js +90 -0
  19. package/dist/domain/iampolicy/display-resolver.js.map +1 -0
  20. package/dist/domain/iampolicy/grant-path.d.ts +42 -0
  21. package/dist/domain/iampolicy/grant-path.d.ts.map +1 -0
  22. package/dist/domain/iampolicy/grant-path.js +183 -0
  23. package/dist/domain/iampolicy/grant-path.js.map +1 -0
  24. package/dist/domain/iampolicy/grant-scope.d.ts +4 -0
  25. package/dist/domain/iampolicy/grant-scope.d.ts.map +1 -0
  26. package/dist/domain/iampolicy/grant-scope.js +34 -0
  27. package/dist/domain/iampolicy/grant-scope.js.map +1 -0
  28. package/dist/domain/iampolicy/membership.d.ts +111 -0
  29. package/dist/domain/iampolicy/membership.d.ts.map +1 -0
  30. package/dist/domain/iampolicy/membership.js +152 -0
  31. package/dist/domain/iampolicy/membership.js.map +1 -0
  32. package/dist/domain/iampolicy/permissions.d.ts +19 -0
  33. package/dist/domain/iampolicy/permissions.d.ts.map +1 -0
  34. package/dist/domain/iampolicy/permissions.js +27 -0
  35. package/dist/domain/iampolicy/permissions.js.map +1 -0
  36. package/dist/domain/iampolicy/resource-store.d.ts +4 -0
  37. package/dist/domain/iampolicy/resource-store.d.ts.map +1 -0
  38. package/dist/domain/iampolicy/resource-store.js +135 -0
  39. package/dist/domain/iampolicy/resource-store.js.map +1 -0
  40. package/dist/domain/iampolicy/role-lifecycle.d.ts +9 -0
  41. package/dist/domain/iampolicy/role-lifecycle.d.ts.map +1 -0
  42. package/dist/domain/iampolicy/role-lifecycle.js +106 -0
  43. package/dist/domain/iampolicy/role-lifecycle.js.map +1 -0
  44. package/dist/domain/iampolicy/roles.d.ts +11 -0
  45. package/dist/domain/iampolicy/roles.d.ts.map +1 -0
  46. package/dist/domain/iampolicy/roles.js +78 -0
  47. package/dist/domain/iampolicy/roles.js.map +1 -0
  48. package/dist/domain/iampolicy/specs.d.ts +7 -0
  49. package/dist/domain/iampolicy/specs.d.ts.map +1 -0
  50. package/dist/domain/iampolicy/specs.js +45 -0
  51. package/dist/domain/iampolicy/specs.js.map +1 -0
  52. package/dist/domain/iampolicy/steps.d.ts +18 -0
  53. package/dist/domain/iampolicy/steps.d.ts.map +1 -0
  54. package/dist/domain/iampolicy/steps.js +117 -0
  55. package/dist/domain/iampolicy/steps.js.map +1 -0
  56. package/dist/domain/iampolicy/store-contract.d.ts +8 -0
  57. package/dist/domain/iampolicy/store-contract.d.ts.map +1 -0
  58. package/dist/domain/iampolicy/store-contract.js +258 -0
  59. package/dist/domain/iampolicy/store-contract.js.map +1 -0
  60. package/dist/domain/iampolicy/store.d.ts +78 -0
  61. package/dist/domain/iampolicy/store.d.ts.map +1 -0
  62. package/dist/domain/iampolicy/store.js +13 -0
  63. package/dist/domain/iampolicy/store.js.map +1 -0
  64. package/dist/domain/iampolicy/wire-refusals.d.ts +25 -0
  65. package/dist/domain/iampolicy/wire-refusals.d.ts.map +1 -0
  66. package/dist/domain/iampolicy/wire-refusals.js +76 -0
  67. package/dist/domain/iampolicy/wire-refusals.js.map +1 -0
  68. package/dist/domain/identityaccount/constants.d.ts +33 -0
  69. package/dist/domain/identityaccount/constants.d.ts.map +1 -1
  70. package/dist/domain/identityaccount/constants.js +2 -48
  71. package/dist/domain/identityaccount/constants.js.map +1 -1
  72. package/dist/domain/identityaccount/controller.d.ts +8 -1
  73. package/dist/domain/identityaccount/controller.d.ts.map +1 -1
  74. package/dist/domain/identityaccount/controller.js +54 -29
  75. package/dist/domain/identityaccount/controller.js.map +1 -1
  76. package/dist/domain/identityaccount/operator.d.ts.map +1 -1
  77. package/dist/domain/identityaccount/operator.js +2 -4
  78. package/dist/domain/identityaccount/operator.js.map +1 -1
  79. package/dist/domain/identityaccount/provisioning.d.ts +20 -1
  80. package/dist/domain/identityaccount/provisioning.d.ts.map +1 -1
  81. package/dist/domain/identityaccount/provisioning.js +17 -3
  82. package/dist/domain/identityaccount/provisioning.js.map +1 -1
  83. package/dist/domain/identityaccount/resolve.d.ts +23 -0
  84. package/dist/domain/identityaccount/resolve.d.ts.map +1 -1
  85. package/dist/domain/identityaccount/resolve.js +17 -0
  86. package/dist/domain/identityaccount/resolve.js.map +1 -1
  87. package/dist/domain/identityaccount/store-contract.d.ts +4 -18
  88. package/dist/domain/identityaccount/store-contract.d.ts.map +1 -1
  89. package/dist/domain/identityaccount/store-contract.js +12 -47
  90. package/dist/domain/identityaccount/store-contract.js.map +1 -1
  91. package/dist/extensions/authorization-queries.d.ts +81 -0
  92. package/dist/extensions/authorization-queries.d.ts.map +1 -0
  93. package/dist/extensions/authorization-queries.js +2 -0
  94. package/dist/extensions/authorization-queries.js.map +1 -0
  95. package/dist/extensions/drivers.d.ts +49 -2
  96. package/dist/extensions/drivers.d.ts.map +1 -1
  97. package/dist/extensions/identity.d.ts +14 -0
  98. package/dist/extensions/identity.d.ts.map +1 -1
  99. package/dist/extensions/identity.js +18 -1
  100. package/dist/extensions/identity.js.map +1 -1
  101. package/dist/extensions/list-read-scope.d.ts +41 -4
  102. package/dist/extensions/list-read-scope.d.ts.map +1 -1
  103. package/dist/extensions/list-read-scope.js +44 -7
  104. package/dist/extensions/list-read-scope.js.map +1 -1
  105. package/dist/extensions/policy-grant-scope.d.ts +50 -0
  106. package/dist/extensions/policy-grant-scope.d.ts.map +1 -0
  107. package/dist/extensions/policy-grant-scope.js +2 -0
  108. package/dist/extensions/policy-grant-scope.js.map +1 -0
  109. package/dist/extensions/registry.d.ts +23 -1
  110. package/dist/extensions/registry.d.ts.map +1 -1
  111. package/dist/extensions/registry.js +30 -0
  112. package/dist/extensions/registry.js.map +1 -1
  113. package/dist/extensions/resource-authorization.d.ts +63 -0
  114. package/dist/extensions/resource-authorization.d.ts.map +1 -1
  115. package/dist/index.d.ts +7 -1
  116. package/dist/index.d.ts.map +1 -1
  117. package/dist/index.js +2 -0
  118. package/dist/index.js.map +1 -1
  119. package/dist/pipeline/apiresource-meta.d.ts +77 -1
  120. package/dist/pipeline/apiresource-meta.d.ts.map +1 -1
  121. package/dist/pipeline/apiresource-meta.js +183 -1
  122. package/dist/pipeline/apiresource-meta.js.map +1 -1
  123. package/dist/pipeline/interceptors/auth.d.ts +12 -0
  124. package/dist/pipeline/interceptors/auth.d.ts.map +1 -1
  125. package/dist/pipeline/interceptors/auth.js +13 -1
  126. package/dist/pipeline/interceptors/auth.js.map +1 -1
  127. package/dist/pipeline/steps/authorization-tuples.d.ts.map +1 -1
  128. package/dist/pipeline/steps/authorization-tuples.js +7 -26
  129. package/dist/pipeline/steps/authorization-tuples.js.map +1 -1
  130. package/dist/pipeline/steps/authorize.d.ts +20 -5
  131. package/dist/pipeline/steps/authorize.d.ts.map +1 -1
  132. package/dist/pipeline/steps/authorize.js +37 -17
  133. package/dist/pipeline/steps/authorize.js.map +1 -1
  134. package/dist/pipeline/steps/defaults.d.ts +10 -0
  135. package/dist/pipeline/steps/defaults.d.ts.map +1 -1
  136. package/dist/pipeline/steps/defaults.js +39 -0
  137. package/dist/pipeline/steps/defaults.js.map +1 -1
  138. package/dist/pipeline/steps/shapes.d.ts +18 -0
  139. package/dist/pipeline/steps/shapes.d.ts.map +1 -1
  140. package/dist/pipeline/steps/shapes.js +29 -0
  141. package/dist/pipeline/steps/shapes.js.map +1 -1
  142. package/dist/store/port-contract.d.ts +61 -0
  143. package/dist/store/port-contract.d.ts.map +1 -0
  144. package/dist/store/port-contract.js +67 -0
  145. package/dist/store/port-contract.js.map +1 -0
  146. package/package.json +4 -4
  147. package/src/boot/compose.ts +107 -10
  148. package/src/domain/iampolicy/__tests__/access-lists.test.ts +238 -0
  149. package/src/domain/iampolicy/__tests__/constants.test.ts +388 -0
  150. package/src/domain/iampolicy/__tests__/controller.test.ts +960 -0
  151. package/src/domain/iampolicy/__tests__/display-resolver.test.ts +138 -0
  152. package/src/domain/iampolicy/__tests__/grant-path.test.ts +474 -0
  153. package/src/domain/iampolicy/__tests__/grant-scope.test.ts +61 -0
  154. package/src/domain/iampolicy/__tests__/iampolicy.test.ts +532 -0
  155. package/src/domain/iampolicy/__tests__/membership.test.ts +447 -0
  156. package/src/domain/iampolicy/__tests__/permissions.test.ts +41 -0
  157. package/src/domain/iampolicy/__tests__/resource-store.test.ts +165 -0
  158. package/src/domain/iampolicy/__tests__/role-lifecycle.test.ts +361 -0
  159. package/src/domain/iampolicy/__tests__/roles.test.ts +53 -0
  160. package/src/domain/iampolicy/__tests__/specs.test.ts +41 -0
  161. package/src/domain/iampolicy/__tests__/steps.test.ts +278 -0
  162. package/src/domain/iampolicy/__tests__/support.ts +195 -0
  163. package/src/domain/iampolicy/__tests__/wire-refusals.test.ts +117 -0
  164. package/src/domain/iampolicy/access-lists.ts +191 -0
  165. package/src/domain/iampolicy/constants.ts +243 -0
  166. package/src/domain/iampolicy/controller.ts +798 -0
  167. package/src/domain/iampolicy/display-resolver.ts +123 -0
  168. package/src/domain/iampolicy/grant-path.ts +270 -0
  169. package/src/domain/iampolicy/grant-scope.ts +36 -0
  170. package/src/domain/iampolicy/membership.ts +298 -0
  171. package/src/domain/iampolicy/permissions.ts +35 -0
  172. package/src/domain/iampolicy/resource-store.ts +186 -0
  173. package/src/domain/iampolicy/role-lifecycle.ts +132 -0
  174. package/src/domain/iampolicy/roles.ts +95 -0
  175. package/src/domain/iampolicy/specs.ts +53 -0
  176. package/src/domain/iampolicy/steps.ts +178 -0
  177. package/src/domain/iampolicy/store-contract.ts +450 -0
  178. package/src/domain/iampolicy/store.ts +103 -0
  179. package/src/domain/iampolicy/wire-refusals.ts +110 -0
  180. package/src/domain/identityaccount/__tests__/provisioning.test.ts +19 -7
  181. package/src/domain/identityaccount/__tests__/resolve.test.ts +113 -2
  182. package/src/domain/identityaccount/constants.ts +16 -31
  183. package/src/domain/identityaccount/controller.ts +65 -35
  184. package/src/domain/identityaccount/operator.ts +5 -5
  185. package/src/domain/identityaccount/provisioning.ts +43 -5
  186. package/src/domain/identityaccount/resolve.ts +43 -0
  187. package/src/domain/identityaccount/store-contract.ts +22 -70
  188. package/src/extensions/__tests__/composed-support.ts +87 -0
  189. package/src/extensions/__tests__/iam-policy-composed.test.ts +373 -0
  190. package/src/extensions/__tests__/iam-policy-points.test.ts +169 -0
  191. package/src/extensions/__tests__/identity-account-composed.test.ts +12 -78
  192. package/src/extensions/__tests__/identity.test.ts +62 -0
  193. package/src/extensions/__tests__/list-read-scope.test.ts +138 -0
  194. package/src/extensions/__tests__/tier-truthfulness.test.ts +8 -0
  195. package/src/extensions/authorization-queries.ts +97 -0
  196. package/src/extensions/drivers.ts +49 -2
  197. package/src/extensions/identity.ts +21 -0
  198. package/src/extensions/list-read-scope.ts +87 -9
  199. package/src/extensions/policy-grant-scope.ts +50 -0
  200. package/src/extensions/registry.ts +62 -1
  201. package/src/extensions/resource-authorization.ts +65 -0
  202. package/src/index.ts +20 -0
  203. package/src/pipeline/__tests__/grantable-roles-for.test.ts +87 -0
  204. package/src/pipeline/__tests__/inherited-authorization-parent.test.ts +51 -0
  205. package/src/pipeline/__tests__/kind-by-enum-name.test.ts +107 -0
  206. package/src/pipeline/__tests__/kind-served-by-edition.test.ts +107 -0
  207. package/src/pipeline/apiresource-meta.ts +218 -1
  208. package/src/pipeline/interceptors/auth.ts +14 -1
  209. package/src/pipeline/steps/__tests__/authorize-annotation-completeness.test.ts +73 -3
  210. package/src/pipeline/steps/__tests__/authorize.test.ts +134 -9
  211. package/src/pipeline/steps/__tests__/derived-id.test.ts +49 -0
  212. package/src/pipeline/steps/authorization-tuples.ts +7 -28
  213. package/src/pipeline/steps/authorize.ts +46 -21
  214. package/src/pipeline/steps/defaults.ts +42 -0
  215. package/src/pipeline/steps/shapes.ts +31 -0
  216. package/src/store/__tests__/port-contract.test.ts +123 -0
  217. package/src/store/port-contract.ts +102 -0
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Pins `inheritedAuthorizationParentOf` (pipeline/apiresource-meta.ts):
3
+ * the kind_meta question "is this kind's authorization its parent's
4
+ * whole?", answered by the PARENT-scope + INHERITED-owner pair and by
5
+ * nothing else (stigmer-cloud 20260913.04 T04, Q-LB-34).
6
+ *
7
+ * The table below is the WHOLE kind enum, on purpose. The list read scope
8
+ * carries `authorizationParent` for exactly the kinds this function names,
9
+ * and a composed driver asks its authorization backend about the parent
10
+ * in the child's place — so a second kind joining the set is a change in
11
+ * what that driver checks, and must arrive as a reviewed edit of this
12
+ * table, never as a silent consequence of a kind_meta edit. Kinds with
13
+ * additional parents whose inheritance is partial (workflow_execution's
14
+ * opt-in `execution_viewer from workflow_instance`, agent_instance's
15
+ * `viewer from default_of`) own themselves and are pinned as `undefined`.
16
+ */
17
+ import { describe, expect, it } from "vitest";
18
+
19
+ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
20
+
21
+ import { inheritedAuthorizationParentOf } from "../apiresource-meta.js";
22
+
23
+ describe("inheritedAuthorizationParentOf — the parent a kind's authorization is", () => {
24
+ it("agent_execution's authorization is its session's: relation `session`, spec field `session_id`", () => {
25
+ const parent = inheritedAuthorizationParentOf(
26
+ ApiResourceKind.agent_execution,
27
+ );
28
+ expect(parent).toBeDefined();
29
+ expect(parent?.kind).toBe("session");
30
+ expect(parent?.relation).toBe("session");
31
+ expect(parent?.specField).toBe("session_id");
32
+ });
33
+
34
+ it("every other kind's authorization is its own (the whole enum, pinned)", () => {
35
+ const inherited = Object.values(ApiResourceKind)
36
+ .filter((value): value is ApiResourceKind => typeof value === "number")
37
+ .filter((kind) => kind !== ApiResourceKind.api_resource_kind_unknown)
38
+ .filter((kind) => inheritedAuthorizationParentOf(kind) !== undefined)
39
+ .map((kind) => ApiResourceKind[kind]);
40
+ expect(inherited).toEqual(["agent_execution"]);
41
+ });
42
+
43
+ it.each([
44
+ ApiResourceKind.workflow_execution,
45
+ ApiResourceKind.agent_instance,
46
+ ApiResourceKind.workflow_instance,
47
+ ApiResourceKind.memory,
48
+ ])("a kind with an additional, partial parent owns itself: %s", (kind) => {
49
+ expect(inheritedAuthorizationParentOf(kind)).toBeUndefined();
50
+ });
51
+ });
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Pins `kindByEnumName` (pipeline/apiresource-meta.ts), the lookup for the
3
+ * SECOND kind vocabulary the contract carries. A resource's own `kind`
4
+ * field is the `kind_meta.name` ("McpServer"; `getKindEnum`, the #545
5
+ * lesson). An `ApiResourceRef.kind` — the IamPolicy spec's principal and
6
+ * resource, the FGA object type — is the enum MEMBER name ("mcp_server"),
7
+ * which is what every SDK call site sends and what the cloud's
8
+ * `kindFromSpecString` resolved before this entry (20260913.01, Q-OR-2).
9
+ *
10
+ * Two properties are load-bearing and pinned here rather than assumed:
11
+ *
12
+ * - EXACT match, never canonicalised. The IamPolicy id is derived from
13
+ * the spec's text (Q-OR-9), so a lenient matcher that admitted
14
+ * "Organization" beside "organization" would mint two rows for one
15
+ * grant, or force the domain to rewrite caller input before storing
16
+ * it. The two spellings `getKindEnum` accepts are refused here on
17
+ * purpose; the contrast arm keeps the two lookups from being merged.
18
+ * - NEVER a throw. The Authorize step resolves `resource_kind_path`
19
+ * through this function at position 1 of every chain, and resolution
20
+ * never throws by doctrine (authorize.ts header): an unknown name is
21
+ * the unknown kind, and the Authorizer owns the decision.
22
+ */
23
+ import { describe, expect, it } from "vitest";
24
+
25
+ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
26
+
27
+ import {
28
+ getKindEnum,
29
+ kindByEnumName,
30
+ kindEnumName,
31
+ } from "../apiresource-meta.js";
32
+
33
+ describe("kindByEnumName — an ApiResourceRef.kind is the enum member name", () => {
34
+ it.each([
35
+ ["organization", ApiResourceKind.organization],
36
+ ["identity_account", ApiResourceKind.identity_account],
37
+ ["mcp_server", ApiResourceKind.mcp_server],
38
+ ["agent", ApiResourceKind.agent],
39
+ ["iam_policy", ApiResourceKind.iam_policy],
40
+ ])("%s resolves to its kind", (name, kind) => {
41
+ expect(kindByEnumName(name)).toBe(kind);
42
+ });
43
+
44
+ it.each([
45
+ ["Organization", "the kind_meta spelling, not a member name"],
46
+ ["McpServer", "the kind_meta spelling, not a member name"],
47
+ ["ORGANIZATION", "case differs"],
48
+ ["organization ", "trailing whitespace"],
49
+ ["", "empty"],
50
+ ["spaceship", "not a kind"],
51
+ ["5", "a number spelled as text is not a name"],
52
+ ["constructor", "a prototype key is not a name"],
53
+ ])("%s is the unknown kind (%s) — never a throw", (value) => {
54
+ expect(kindByEnumName(value)).toBe(
55
+ ApiResourceKind.api_resource_kind_unknown,
56
+ );
57
+ });
58
+
59
+ it("the zero value's own name is the unknown kind too", () => {
60
+ expect(kindByEnumName("api_resource_kind_unknown")).toBe(
61
+ ApiResourceKind.api_resource_kind_unknown,
62
+ );
63
+ });
64
+
65
+ it("is exact where getKindEnum is lenient — the two vocabularies stay two lookups", () => {
66
+ // A resource's `kind` field: canonical kind_meta.name matching,
67
+ // case-insensitive, underscores ignored, and a throw on unknown.
68
+ expect(getKindEnum("McpServer")).toBe(ApiResourceKind.mcp_server);
69
+ expect(getKindEnum("mcp_server")).toBe(ApiResourceKind.mcp_server);
70
+ expect(() => getKindEnum("spaceship")).toThrow();
71
+ // An ApiResourceRef.kind: the member name and nothing else.
72
+ expect(kindByEnumName("McpServer")).toBe(
73
+ ApiResourceKind.api_resource_kind_unknown,
74
+ );
75
+ expect(kindByEnumName("mcp_server")).toBe(ApiResourceKind.mcp_server);
76
+ });
77
+
78
+ it("every member of the enum round-trips through its own name", () => {
79
+ for (const [name, value] of Object.entries(ApiResourceKind)) {
80
+ if (typeof value !== "number") continue;
81
+ expect(kindByEnumName(name), name).toBe(value);
82
+ }
83
+ });
84
+ });
85
+
86
+ describe("kindEnumName — the inverse: a kind spelled as an ApiResourceRef.kind", () => {
87
+ it.each([
88
+ [ApiResourceKind.organization, "organization"],
89
+ [ApiResourceKind.identity_account, "identity_account"],
90
+ [ApiResourceKind.mcp_server, "mcp_server"],
91
+ ])("%s spells as %s", (kind, name) => {
92
+ expect(kindEnumName(kind)).toBe(name);
93
+ });
94
+
95
+ it("the unknown kind spells itself — never a throw", () => {
96
+ expect(kindEnumName(ApiResourceKind.api_resource_kind_unknown)).toBe(
97
+ "api_resource_kind_unknown",
98
+ );
99
+ });
100
+
101
+ it("round-trips with kindByEnumName over every member — the two are inverses", () => {
102
+ for (const value of Object.values(ApiResourceKind)) {
103
+ if (typeof value !== "number") continue;
104
+ expect(kindByEnumName(kindEnumName(value)), String(value)).toBe(value);
105
+ }
106
+ });
107
+ });
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Pins `tierServedByEdition` and `kindServedByEdition`
3
+ * (pipeline/apiresource-meta.ts), the server's twin of the SDK's tier-rank
4
+ * functions (sdk/typescript/src/resource-availability.ts: editions
5
+ * oss < enterprise < cloud; a tier admits its own edition and every edition
6
+ * above it; the enum NUMBERS are wire identifiers, never ranks —
7
+ * `enterprise` sits at 3 in both enums and ranks second). T01_1_review.md
8
+ * Q-OR-8 and claim check C5: the two implementations must agree on the
9
+ * whole matrix, because the console reads the SDK's answer to decide what
10
+ * to SHOW and `checkMyPermission` reads the server's to decide what is
11
+ * HELD; a disagreement is a surface that appears and then fails, the
12
+ * Q-EC-2b class.
13
+ *
14
+ * The matrix is spelled out rather than computed, so a change on either
15
+ * side is a visible diff here and the SDK's own test remains the SDK's.
16
+ */
17
+ import { describe, expect, it } from "vitest";
18
+
19
+ import {
20
+ ApiResourceKind,
21
+ ResourceTier,
22
+ } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
23
+ import { ServerEdition } from "@stigmer/protos/ai/stigmer/platform/v1/server_info_pb";
24
+
25
+ import {
26
+ kindServedByEdition,
27
+ tierServedByEdition,
28
+ } from "../apiresource-meta.js";
29
+
30
+ const EDITIONS = [
31
+ ServerEdition.oss,
32
+ ServerEdition.enterprise,
33
+ ServerEdition.cloud,
34
+ ] as const;
35
+
36
+ /** tier → the editions that serve it, in the SDK's order. */
37
+ const MATRIX: ReadonlyArray<readonly [ResourceTier, readonly boolean[]]> = [
38
+ [ResourceTier.open_source, [true, true, true]],
39
+ [ResourceTier.enterprise, [false, true, true]],
40
+ [ResourceTier.cloud_only, [false, false, true]],
41
+ // A kind whose proto forgot the field is treated as core, as the SDK
42
+ // treats it: hiding a kind is the worse failure for a client, and holding
43
+ // a permission on a kind every edition serves is the truthful answer.
44
+ [ResourceTier.resource_tier_unspecified, [true, true, true]],
45
+ ];
46
+
47
+ describe("tierServedByEdition — the SDK's rank table, spelled out", () => {
48
+ for (const [tier, served] of MATRIX) {
49
+ it(`${ResourceTier[tier]} is served by [oss, enterprise, cloud] = [${served.join(", ")}]`, () => {
50
+ expect(
51
+ EDITIONS.map((edition) => tierServedByEdition(tier, edition)),
52
+ ).toEqual(served);
53
+ });
54
+ }
55
+
56
+ it("an edition the server does not know serves everything — an older server hides nothing, the SDK's own fallback", () => {
57
+ for (const [tier] of MATRIX) {
58
+ expect(
59
+ tierServedByEdition(tier, ServerEdition.server_edition_unspecified),
60
+ ).toBe(true);
61
+ }
62
+ });
63
+ });
64
+
65
+ describe("kindServedByEdition — the kind's tier through the same table", () => {
66
+ it("reads the tier from kind_meta: agent everywhere, invitation from enterprise up, api_resource_version on cloud only", () => {
67
+ expect(
68
+ EDITIONS.map((edition) =>
69
+ kindServedByEdition(ApiResourceKind.agent, edition),
70
+ ),
71
+ ).toEqual([true, true, true]);
72
+ expect(
73
+ EDITIONS.map((edition) =>
74
+ kindServedByEdition(ApiResourceKind.invitation, edition),
75
+ ),
76
+ ).toEqual([false, true, true]);
77
+ expect(
78
+ EDITIONS.map((edition) =>
79
+ kindServedByEdition(ApiResourceKind.api_resource_version, edition),
80
+ ),
81
+ ).toEqual([false, false, true]);
82
+ });
83
+
84
+ it("platform is enterprise-tiered, so the operator seat's permissions are never held on open source (the settings-navigation arm)", () => {
85
+ expect(
86
+ kindServedByEdition(ApiResourceKind.platform, ServerEdition.oss),
87
+ ).toBe(false);
88
+ expect(
89
+ kindServedByEdition(ApiResourceKind.platform, ServerEdition.enterprise),
90
+ ).toBe(true);
91
+ });
92
+
93
+ it("iam_policy is served by every edition once this entry flips it", () => {
94
+ expect(
95
+ kindServedByEdition(ApiResourceKind.iam_policy, ServerEdition.oss),
96
+ ).toBe(true);
97
+ });
98
+
99
+ it("the unknown kind is a programming error, not an answer", () => {
100
+ expect(() =>
101
+ kindServedByEdition(
102
+ ApiResourceKind.api_resource_kind_unknown,
103
+ ServerEdition.cloud,
104
+ ),
105
+ ).toThrow();
106
+ });
107
+ });
@@ -8,6 +8,39 @@
8
8
  * Both editions resolve the same strings from the same proto metadata; the
9
9
  * visibility helpers mirror Cloud's VisibilityConfigResolver so the
10
10
  * cross-edition error contract is identical by construction.
11
+ *
12
+ * The contract carries TWO kind vocabularies, and this module holds one
13
+ * lookup for each so they are never merged:
14
+ *
15
+ * - A resource's own `kind` field is the `kind_meta.name` ("McpServer").
16
+ * `getKindEnum` reads it by canonical name (case-insensitive,
17
+ * underscores ignored) and throws on an unknown value.
18
+ * - An `ApiResourceRef.kind` — the IamPolicy spec's principal and
19
+ * resource, the FGA object type — is the enum MEMBER name
20
+ * ("mcp_server"). `kindByEnumName` reads it by the descriptor's exact
21
+ * proto name and never throws (20260913.01, Q-OR-2). Exact, because the
22
+ * IamPolicy id is derived from the spec's text (Q-OR-9): a lenient
23
+ * match would let "Organization" and "organization" mint two rows for
24
+ * one grant. Reading the descriptor's name is not deriving a spelling;
25
+ * the #545 rule is about recovering kind_meta.name from an enum, which
26
+ * neither lookup does. A reader asked about a kind that arrived
27
+ * through this vocabulary follows its doctrine: `grantableRolesFor`
28
+ * answers the empty list for the unknown kind instead of throwing,
29
+ * because the kind it is asked about may have come off the wire.
30
+ * `kindEnumName` is the inverse — how server code SPELLS a kind when
31
+ * it builds an `ApiResourceRef` itself (the built-in role lifecycle's
32
+ * cleanup refs; the organization-role specs) — so no caller reaches
33
+ * for the enum's reverse index by hand.
34
+ *
35
+ * A kind's TIER meets the served EDITION in exactly one place on the
36
+ * server, `tierServedByEdition` / `kindServedByEdition` (20260913.01
37
+ * Q-OR-8; claim check C5) — the twin of the SDK's rank functions
38
+ * (sdk/typescript/src/resource-availability.ts), pinned equal on the whole
39
+ * matrix by __tests__/kind-served-by-edition.test.ts. The console reads
40
+ * the SDK's answer to decide what to SHOW; `checkMyPermission` reads the
41
+ * server's to decide what is HELD; a disagreement is a surface that
42
+ * appears and then fails. The enum NUMBERS are wire identifiers, never
43
+ * ranks (`enterprise` sits at 3 in both enums and ranks second).
11
44
  */
12
45
  import { getOption, hasOption } from "@bufbuild/protobuf";
13
46
  import type { DescEnumValue } from "@bufbuild/protobuf";
@@ -16,9 +49,17 @@ import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apires
16
49
  import {
17
50
  ApiResourceKind,
18
51
  ApiResourceKindSchema,
52
+ ResourceTier,
19
53
  kind_meta,
20
54
  } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
21
55
  import type { ApiResourceKindMeta } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
56
+ import {
57
+ AuthorizationScopeType,
58
+ OwnerAttributionType,
59
+ } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/authorization_config_pb";
60
+ import type { ParentRelationConfig } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/authorization_config_pb";
61
+ import type { IamRole } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
62
+ import { ServerEdition } from "@stigmer/protos/ai/stigmer/platform/v1/server_info_pb";
22
63
 
23
64
  /** Go GetKindMeta: the kind_meta extension of the enum value. */
24
65
  export function getKindMeta(kind: ApiResourceKind): ApiResourceKindMeta {
@@ -29,6 +70,38 @@ export function getKindMeta(kind: ApiResourceKind): ApiResourceKindMeta {
29
70
  return getOption(valueDesc, kind_meta);
30
71
  }
31
72
 
73
+ /**
74
+ * The parent a kind's authorization IS — the `kind_meta` declaration that
75
+ * a kind's permissions are its parent's whole, not its own: PARENT scope
76
+ * (the one structural link is to that parent) AND INHERITED owner (no
77
+ * owner tuple of its own; `owner from <parent>` in the FGA model).
78
+ * Undefined for every other kind, including kinds with additional parents
79
+ * whose inheritance is partial (a workflow_execution's opt-in
80
+ * `execution_viewer from workflow_instance` sits beside its own owner).
81
+ *
82
+ * Today exactly one kind answers: agent_execution → session (its
83
+ * `can_view` is `viewer or can_view from session`, and `viewer` is the
84
+ * session's). The list read scope reads this to carry the parent on every
85
+ * candidate (`ListEntryMeta.authorizationParent`, 20260913.04 T04), so a
86
+ * composed driver may ask its authorization backend about the parent —
87
+ * direct tuples — instead of the child, whose resolution walks the
88
+ * parent's whole set. A second kind declaring the pair gets the behavior
89
+ * from this declaration alone; the table pin in __tests__ makes that a
90
+ * reviewed change.
91
+ */
92
+ export function inheritedAuthorizationParentOf(
93
+ kind: ApiResourceKind,
94
+ ): ParentRelationConfig | undefined {
95
+ const config = getKindMeta(kind).authorization;
96
+ if (
97
+ config?.scopeType !== AuthorizationScopeType.PARENT ||
98
+ config.ownerType !== OwnerAttributionType.INHERITED
99
+ ) {
100
+ return undefined;
101
+ }
102
+ return config.parent;
103
+ }
104
+
32
105
  /** Go GetIdPrefix: e.g. agent → "agt". */
33
106
  export function getIdPrefix(kind: ApiResourceKind): string {
34
107
  return getKindMeta(kind).idPrefix;
@@ -55,19 +128,143 @@ export function getKindEnum(kindValue: string): ApiResourceKind {
55
128
  return kind;
56
129
  }
57
130
 
131
+ /**
132
+ * Resolves an `ApiResourceRef.kind` — a kind's enum MEMBER name, exactly
133
+ * as the proto spells it ("organization", "identity_account") — to the
134
+ * enum. Anything else, including the kind_meta spelling, a different case,
135
+ * the empty string and the zero value's own name, is
136
+ * `api_resource_kind_unknown`; never a throw, because the Authorize step
137
+ * resolves `resource_kind_path` through this at position 1 of every chain
138
+ * and resolution never fails a request (authorize.ts header). See the
139
+ * module header for why the match is exact.
140
+ */
141
+ export function kindByEnumName(value: string): ApiResourceKind {
142
+ return (
143
+ kindsByEnumName().get(value) ?? ApiResourceKind.api_resource_kind_unknown
144
+ );
145
+ }
146
+
147
+ /**
148
+ * The inverse of `kindByEnumName`: a kind spelled as an `ApiResourceRef.kind`
149
+ * — the descriptor's exact proto name ("organization", "identity_account").
150
+ * Total over the enum: the unknown kind spells itself, so a ref built from
151
+ * it round-trips to the unknown kind and the gate that reads it refuses
152
+ * the way it refuses a wire caller's garbage; never a throw.
153
+ */
154
+ export function kindEnumName(kind: ApiResourceKind): string {
155
+ // A number that is no member (a cast from outside the enum) spells as
156
+ // the unknown kind, which every descriptor carries as its zero value.
157
+ const value =
158
+ kindValueDescriptor(kind) ??
159
+ kindValueDescriptor(ApiResourceKind.api_resource_kind_unknown);
160
+ return value?.name ?? "";
161
+ }
162
+
58
163
  /**
59
164
  * Go DefaultVisibilityFor: blueprint kinds flagged defaults_to_org_visibility
60
165
  * get visibility_org (blueprints are shared org assets — a private default
61
166
  * would silently hide new blueprints from teammates); all others get
62
167
  * visibility_private.
63
168
  */
64
- export function defaultVisibilityFor(kind: ApiResourceKind): ApiResourceVisibility {
169
+ export function defaultVisibilityFor(
170
+ kind: ApiResourceKind,
171
+ ): ApiResourceVisibility {
65
172
  const config = getKindMeta(kind).authorization?.visibility;
66
173
  return config?.defaultsToOrgVisibility === true
67
174
  ? ApiResourceVisibility.visibility_org
68
175
  : ApiResourceVisibility.visibility_private;
69
176
  }
70
177
 
178
+ /**
179
+ * The roles a user may be granted on a resource of `kind` according to the
180
+ * CONTRACT — `kind_meta.authorization.grantable_roles`, the one source of
181
+ * "what can be granted at all" (Java AuthorizationConfigResolver
182
+ * .getGrantableRoles; the cloud's iam/policy/roles.ts grantableRolesFor,
183
+ * which this replaces at the 20260913.01 re-point). A kind that lists none
184
+ * is system-managed in every edition (identity_account, api_key,
185
+ * iam_policy, invitation, agent_execution); an edition's PolicyGrantScope
186
+ * (extensions/policy-grant-scope.ts) only narrows this list.
187
+ *
188
+ * Total, never a throw: the kind is an `ApiResourceRef.kind` resolved by
189
+ * `kindByEnumName`, so the unknown kind — the one member without
190
+ * `kind_meta` — is a legitimate argument and answers the empty list
191
+ * (module header, the second vocabulary).
192
+ */
193
+ export function grantableRolesFor(
194
+ kind: ApiResourceKind,
195
+ ): ReadonlyArray<IamRole> {
196
+ const valueDesc = kindValueDescriptor(kind);
197
+ if (valueDesc === undefined || !hasOption(valueDesc, kind_meta)) {
198
+ return [];
199
+ }
200
+ return getOption(valueDesc, kind_meta).authorization?.grantableRoles ?? [];
201
+ }
202
+
203
+ /**
204
+ * Whether an edition serves a tier: the tier names the MINIMUM edition,
205
+ * the editions are ordered oss < enterprise < cloud (each composes the
206
+ * previous one's units), so a tier admits its own edition and every one
207
+ * above. An edition the server does not know (`server_edition_unspecified`)
208
+ * serves everything — the SDK's own fallback: an older client against a
209
+ * newer server hides nothing. A tier the proto forgot is treated as core
210
+ * for the same reason the SDK does: hiding a served kind is the worse
211
+ * failure.
212
+ */
213
+ export function tierServedByEdition(
214
+ tier: ResourceTier,
215
+ edition: ServerEdition,
216
+ ): boolean {
217
+ if (edition === ServerEdition.server_edition_unspecified) {
218
+ return true;
219
+ }
220
+ return editionRank(edition) >= tierRank(tier);
221
+ }
222
+
223
+ /**
224
+ * Whether this edition serves a kind: its `kind_meta.tier` through the
225
+ * table above. Throws for a kind without `kind_meta` (the unknown kind):
226
+ * a caller that reaches this with wire input has skipped the kind
227
+ * refusals, and "served" for it would hide that bug.
228
+ */
229
+ export function kindServedByEdition(
230
+ kind: ApiResourceKind,
231
+ edition: ServerEdition,
232
+ ): boolean {
233
+ return tierServedByEdition(getKindMeta(kind).tier, edition);
234
+ }
235
+
236
+ function editionRank(edition: ServerEdition): number {
237
+ switch (edition) {
238
+ case ServerEdition.oss:
239
+ return 1;
240
+ case ServerEdition.enterprise:
241
+ return 2;
242
+ case ServerEdition.cloud:
243
+ case ServerEdition.server_edition_unspecified:
244
+ return 3;
245
+ default: {
246
+ const exhaustive: never = edition;
247
+ throw new Error(`unknown server edition: ${String(exhaustive)}`);
248
+ }
249
+ }
250
+ }
251
+
252
+ function tierRank(tier: ResourceTier): number {
253
+ switch (tier) {
254
+ case ResourceTier.open_source:
255
+ case ResourceTier.resource_tier_unspecified:
256
+ return 1;
257
+ case ResourceTier.enterprise:
258
+ return 2;
259
+ case ResourceTier.cloud_only:
260
+ return 3;
261
+ default: {
262
+ const exhaustive: never = tier;
263
+ throw new Error(`unknown resource tier: ${String(exhaustive)}`);
264
+ }
265
+ }
266
+ }
267
+
71
268
  /**
72
269
  * Go SupportsVisibility: PRIVATE and UNSPECIFIED are always supported (no
73
270
  * visibility grant); every other level requires the matching supports_*
@@ -134,3 +331,23 @@ function kindsByCanonicalName(): Map<string, ApiResourceKind> {
134
331
  }
135
332
  return canonicalNameCache;
136
333
  }
334
+
335
+ let enumNameCache: Map<string, ApiResourceKind> | undefined;
336
+
337
+ /**
338
+ * Every member by its descriptor name, the zero value included (it maps to
339
+ * itself, which is the unknown kind either way). A Map rather than the TS
340
+ * enum's reverse mapping, so a prototype key or a number spelled as text
341
+ * cannot resolve to anything.
342
+ */
343
+ function kindsByEnumName(): Map<string, ApiResourceKind> {
344
+ if (enumNameCache === undefined) {
345
+ enumNameCache = new Map(
346
+ ApiResourceKindSchema.values.map((value) => [
347
+ value.name,
348
+ value.number as ApiResourceKind,
349
+ ]),
350
+ );
351
+ }
352
+ return enumNameCache;
353
+ }
@@ -175,6 +175,19 @@ export function trustedLocalIdentity(): CallerIdentity {
175
175
  return trustedLocalIdentityFor(operatorIdentitySnapshot());
176
176
  }
177
177
 
178
+ /**
179
+ * The identityId of the UNCONFIGURED laptop's operator — no
180
+ * STIGMER_OPERATOR_EMAIL, so the trust domain's one principal has no
181
+ * email to be named by. It is what the interceptor stamps and therefore
182
+ * what every audit actor on such a laptop carries as `created_by.id`; the
183
+ * operator account derives its subject from it (`local|system`) and the
184
+ * membership rules read it as "not a person" when they classify creator
185
+ * stamps (domain/iampolicy/membership.ts). One home (20260913.01 slice 4,
186
+ * Q-S4-6): a second spelling anywhere would make a stamp and its reader
187
+ * disagree silently.
188
+ */
189
+ export const SYSTEM_OPERATOR_IDENTITY_ID = "system";
190
+
178
191
  /**
179
192
  * The trusted-local identity for a given operator — the one construction
180
193
  * of that principal, shared by the per-request stamp above and by the
@@ -187,7 +200,7 @@ export function trustedLocalIdentityFor(operator: {
187
200
  }): CallerIdentity {
188
201
  if (operator.email === "") {
189
202
  return {
190
- identityId: "system",
203
+ identityId: SYSTEM_OPERATOR_IDENTITY_ID,
191
204
  callerClass: "user",
192
205
  issuer: "",
193
206
  rawToken: "",
@@ -20,9 +20,32 @@
20
20
  * never reach the annotation's resolution (the skip lanes resolve their
21
21
  * target in the handler through `authorizeDirect`'s override). A config
22
22
  * that names NO kind (static or by path) is outside this invariant: the
23
- * check is then about the kind, not the id, and today no OSS-served method
24
- * is shaped that way (the IamPolicy RPCs that are belong to
25
- * `sp.oss-organization-roles`).
23
+ * check is then about the kind, not the id.
24
+ *
25
+ * The IamPolicy contract is the one place that shape exists, and it is
26
+ * pinned here by descriptor, not by served route, so the arm turns green
27
+ * when the ANNOTATIONS land (sp.oss-organization-roles slice 1) and not
28
+ * when the service is first served (slice 5). Of its nine `rpc.config`
29
+ * methods: six name their target — `create`, `delete`,
30
+ * `listResourceAccessByPrincipal`, `getPrincipalResourceRoles` by
31
+ * `resource_kind_path` (the spec's `resource.kind`, a STRING the resolver
32
+ * reads by enum name) + `field_path`; `revokeOrgAccess` and
33
+ * `getPrincipalsCount` statically. `get(IamPolicyId)` names no kind BY
34
+ * DESIGN: its target is inside the row it loads, so it is the one
35
+ * load-then-authorize lane (`authorizeDirect` with the override's
36
+ * resourceKind + resourceId, the getByEmail precedent). The two
37
+ * `listAuthorized*Ids` were the open question of S1 (finding 7): an
38
+ * annotation with a permission and no kind resolves to an EMPTY target,
39
+ * which the open-source permissive Authorizer allows and the cloud's
40
+ * OpenFGA Authorizer denies for every caller — the #1073 class this
41
+ * invariant cannot see. Ruled at slice 1 (2026-09-13, T01_1_review.md
42
+ * Q-OR-2): `listAuthorizedPrincipalIds` HAS a resource and names it (the
43
+ * same `can_view_access` target `listResourceAccessByPrincipal` enforces
44
+ * for the same question); `listAuthorizedResourceIds` has a principal and
45
+ * no resource, so it is an `is_skip_authorization` lane whose handler
46
+ * enforces `checkAuthorization`'s principal-trust rule (a user only for
47
+ * themselves; machine and internal for anyone) — a dead `can_view_access`
48
+ * config would have been exactly the kind-less shape this file guards.
26
49
  *
27
50
  * The mutation arms prove the check bites over the same function.
28
51
  */
@@ -41,6 +64,8 @@ import {
41
64
  is_public,
42
65
  is_skip_authorization,
43
66
  } from "@stigmer/protos/ai/stigmer/commons/rpc/method_options_pb";
67
+ import { IamPolicyCommandController } from "@stigmer/protos/ai/stigmer/iam/iampolicy/v1/command_pb";
68
+ import { IamPolicyQueryController } from "@stigmer/protos/ai/stigmer/iam/iampolicy/v1/query_pb";
44
69
 
45
70
  import { loadConfig } from "../../../boot/config.js";
46
71
  import { composeServer } from "../../../boot/compose.js";
@@ -151,6 +176,51 @@ describe("authorization annotations name where the resource id comes from", () =
151
176
  });
152
177
  });
153
178
 
179
+ describe("the IamPolicy contract names its targets (sp.oss-organization-roles slice 1, read by descriptor)", () => {
180
+ const targets = annotationTargets([
181
+ IamPolicyCommandController,
182
+ IamPolicyQueryController,
183
+ ]);
184
+ const byName = new Map(
185
+ targets.map((t) => [t.method.split("/")[1] ?? t.method, t]),
186
+ );
187
+
188
+ it.each([
189
+ "create",
190
+ "delete",
191
+ "revokeOrgAccess",
192
+ "listResourceAccessByPrincipal",
193
+ "getPrincipalResourceRoles",
194
+ "getPrincipalsCount",
195
+ "listAuthorizedPrincipalIds",
196
+ ])("%s names a resource kind AND where its id comes from", (method) => {
197
+ const target = byName.get(method);
198
+ expect(target, `${method} carries rpc.config`).toBeDefined();
199
+ expect(target?.namesKind, `${method} names a kind`).toBe(true);
200
+ expect(target?.namesId, `${method} names its id`).toBe(true);
201
+ });
202
+
203
+ it("get names no kind — the one load-then-authorize lane, its target inside the row", () => {
204
+ const get = byName.get("get");
205
+ expect(get, "get carries rpc.config").toBeDefined();
206
+ expect(get?.namesKind).toBe(false);
207
+ expect(get?.skipsResolution).toBe(false);
208
+ });
209
+
210
+ it("listAuthorizedResourceIds carries NO rpc.config and skips — its trust rule is the handler's (finding 7)", () => {
211
+ // Read the descriptor directly: `annotationTargets` lists only
212
+ // config-carrying methods, and this one must have left that set.
213
+ const method = IamPolicyQueryController.method.listAuthorizedResourceIds;
214
+ expect(byName.has("listAuthorizedResourceIds")).toBe(false);
215
+ expect(hasOption(method, rpcAuthorizationConfig)).toBe(false);
216
+ expect(getOption(method, is_skip_authorization)).toBe(true);
217
+ });
218
+
219
+ it("the invariant reports none of them once slice 1 lands", () => {
220
+ expect(unresolvableTargets(targets)).toEqual([]);
221
+ });
222
+ });
223
+
154
224
  describe("the check bites (mutation proofs over the same function)", () => {
155
225
  const shaped = (overrides: Partial<AnnotationTarget>): AnnotationTarget => ({
156
226
  method: "x.v1.Query/probe",