@toa.io/extensions.exposition 1.0.0-alpha.286 → 1.0.0-alpha.288

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 (219) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/components/identity.basic/tsconfig.tsbuildinfo +1 -1
  3. package/components/identity.clients/tsconfig.tsbuildinfo +1 -1
  4. package/components/identity.credentials/tsconfig.tsbuildinfo +1 -1
  5. package/components/identity.federation/tsconfig.tsbuildinfo +1 -1
  6. package/components/identity.grants/tsconfig.tsbuildinfo +1 -1
  7. package/components/identity.keys/tsconfig.tsbuildinfo +1 -1
  8. package/components/identity.otp/tsconfig.tsbuildinfo +1 -1
  9. package/components/identity.passkeys/manifest.toa.yaml +2 -1
  10. package/components/identity.passkeys/source/errors.test.ts +34 -0
  11. package/components/identity.passkeys/source/webauthn.test.ts +26 -0
  12. package/components/identity.passkeys/tsconfig.tsbuildinfo +1 -1
  13. package/components/identity.passkeys/types/toa.d.ts +1 -1
  14. package/components/identity.tokens/tsconfig.tsbuildinfo +1 -1
  15. package/documentation/access.md +22 -4
  16. package/documentation/discovery.md +81 -0
  17. package/documentation/help.md +109 -0
  18. package/documentation/introspection.md +34 -9
  19. package/documentation/mcp.md +13 -16
  20. package/features/cors.feature +31 -1
  21. package/features/dev.feature +2 -0
  22. package/features/discovery.feature +211 -0
  23. package/features/discovery.ui.feature +197 -0
  24. package/features/help.feature +466 -0
  25. package/features/introspection.feature +94 -8
  26. package/features/map.feature +1 -0
  27. package/features/mcp.feature +74 -12
  28. package/features/methods.feature +2 -1
  29. package/features/oauth.grants.feature +3 -1
  30. package/features/octets.download.feature +1 -0
  31. package/features/octets.meta.feature +1 -0
  32. package/features/site/_app/immutable/asset.js +1 -0
  33. package/features/site/favicon.ico +0 -0
  34. package/features/site/index.html +10 -0
  35. package/features/steps/Parameters.ts +12 -0
  36. package/package.json +6 -4
  37. package/readme.md +3 -0
  38. package/source/Directive.ts +4 -0
  39. package/source/Discovery/Explorer.ts +34 -0
  40. package/source/Discovery/Site.test.ts +147 -0
  41. package/source/Discovery/Site.ts +202 -0
  42. package/source/Discovery/index.ts +3 -0
  43. package/source/Discovery/tree.test.ts +175 -0
  44. package/source/Discovery/tree.ts +77 -0
  45. package/source/Discovery/trunk.ts +23 -0
  46. package/source/Endpoint.ts +4 -0
  47. package/source/Gateway.ts +34 -4
  48. package/source/HTTP/Context.ts +7 -0
  49. package/source/Introspection.ts +33 -10
  50. package/source/MCP/schema.ts +16 -5
  51. package/source/MCP/tools.ts +14 -32
  52. package/source/Mapping.ts +5 -0
  53. package/source/Query.ts +17 -2
  54. package/source/RTD/Directives.ts +10 -0
  55. package/source/RTD/Endpoint.ts +8 -1
  56. package/source/RTD/Node.ts +20 -6
  57. package/source/RTD/Tree.ts +19 -9
  58. package/source/RTD/factory.ts +37 -1
  59. package/source/RTD/segment.ts +21 -0
  60. package/source/const.ts +8 -0
  61. package/source/directives/auth/Anonymous.test.ts +31 -5
  62. package/source/directives/auth/Anonymous.ts +5 -3
  63. package/source/directives/auth/Anyone.ts +6 -0
  64. package/source/directives/auth/Assert.ts +5 -0
  65. package/source/directives/auth/Delegate.ts +5 -2
  66. package/source/directives/auth/Federation.ts +11 -0
  67. package/source/directives/auth/Id.ts +15 -0
  68. package/source/directives/auth/Input.ts +5 -0
  69. package/source/directives/auth/Role.ts +17 -0
  70. package/source/directives/cors/CORS.test.ts +75 -0
  71. package/source/directives/cors/CORS.ts +10 -3
  72. package/source/directives/help/Family.ts +118 -0
  73. package/source/directives/help/Help.test.ts +227 -0
  74. package/source/directives/help/Help.ts +26 -0
  75. package/source/directives/help/Parameters.ts +47 -0
  76. package/source/directives/help/described.ts +57 -0
  77. package/source/directives/help/index.ts +45 -0
  78. package/source/directives/index.ts +8 -1
  79. package/source/directives/map/Headers.ts +6 -6
  80. package/source/directives/mcp/MCP.ts +12 -21
  81. package/source/directives/mcp/Tool.test.ts +22 -68
  82. package/source/directives/mcp/Tool.ts +14 -41
  83. package/transpiled/Directive.d.ts +1 -0
  84. package/transpiled/Directive.js +3 -0
  85. package/transpiled/Directive.js.map +1 -1
  86. package/transpiled/Discovery/Explorer.d.ts +11 -0
  87. package/transpiled/Discovery/Explorer.js +29 -0
  88. package/transpiled/Discovery/Explorer.js.map +1 -0
  89. package/transpiled/Discovery/Site.d.ts +43 -0
  90. package/transpiled/Discovery/Site.js +171 -0
  91. package/transpiled/Discovery/Site.js.map +1 -0
  92. package/transpiled/Discovery/index.d.ts +3 -0
  93. package/transpiled/Discovery/index.js +4 -0
  94. package/transpiled/Discovery/index.js.map +1 -0
  95. package/transpiled/Discovery/tree.d.ts +26 -0
  96. package/transpiled/Discovery/tree.js +50 -0
  97. package/transpiled/Discovery/tree.js.map +1 -0
  98. package/transpiled/Discovery/trunk.d.ts +10 -0
  99. package/transpiled/Discovery/trunk.js +21 -0
  100. package/transpiled/Discovery/trunk.js.map +1 -0
  101. package/transpiled/Endpoint.d.ts +2 -1
  102. package/transpiled/Endpoint.js +3 -0
  103. package/transpiled/Endpoint.js.map +1 -1
  104. package/transpiled/Gateway.d.ts +4 -0
  105. package/transpiled/Gateway.js +29 -4
  106. package/transpiled/Gateway.js.map +1 -1
  107. package/transpiled/HTTP/Context.d.ts +6 -0
  108. package/transpiled/HTTP/Context.js +6 -0
  109. package/transpiled/HTTP/Context.js.map +1 -1
  110. package/transpiled/Introspection.d.ts +18 -7
  111. package/transpiled/Introspection.js +16 -1
  112. package/transpiled/Introspection.js.map +1 -1
  113. package/transpiled/MCP/schema.d.ts +2 -2
  114. package/transpiled/MCP/schema.js +11 -5
  115. package/transpiled/MCP/schema.js.map +1 -1
  116. package/transpiled/MCP/tools.d.ts +1 -1
  117. package/transpiled/MCP/tools.js +12 -28
  118. package/transpiled/MCP/tools.js.map +1 -1
  119. package/transpiled/Mapping.d.ts +2 -0
  120. package/transpiled/Mapping.js +4 -0
  121. package/transpiled/Mapping.js.map +1 -1
  122. package/transpiled/Query.d.ts +12 -0
  123. package/transpiled/Query.js +16 -2
  124. package/transpiled/Query.js.map +1 -1
  125. package/transpiled/RTD/Directives.d.ts +8 -0
  126. package/transpiled/RTD/Endpoint.d.ts +7 -1
  127. package/transpiled/RTD/Node.d.ts +11 -2
  128. package/transpiled/RTD/Node.js +12 -2
  129. package/transpiled/RTD/Node.js.map +1 -1
  130. package/transpiled/RTD/Tree.js +15 -8
  131. package/transpiled/RTD/Tree.js.map +1 -1
  132. package/transpiled/RTD/factory.js +27 -1
  133. package/transpiled/RTD/factory.js.map +1 -1
  134. package/transpiled/RTD/segment.d.ts +7 -0
  135. package/transpiled/RTD/segment.js +17 -0
  136. package/transpiled/RTD/segment.js.map +1 -1
  137. package/transpiled/const.d.ts +7 -0
  138. package/transpiled/const.js +7 -0
  139. package/transpiled/const.js.map +1 -1
  140. package/transpiled/directives/auth/Anonymous.d.ts +4 -2
  141. package/transpiled/directives/auth/Anonymous.js +5 -3
  142. package/transpiled/directives/auth/Anonymous.js.map +1 -1
  143. package/transpiled/directives/auth/Anyone.d.ts +3 -0
  144. package/transpiled/directives/auth/Anyone.js +4 -0
  145. package/transpiled/directives/auth/Anyone.js.map +1 -1
  146. package/transpiled/directives/auth/Assert.d.ts +2 -0
  147. package/transpiled/directives/auth/Assert.js +4 -0
  148. package/transpiled/directives/auth/Assert.js.map +1 -1
  149. package/transpiled/directives/auth/Delegate.d.ts +4 -1
  150. package/transpiled/directives/auth/Delegate.js +5 -2
  151. package/transpiled/directives/auth/Delegate.js.map +1 -1
  152. package/transpiled/directives/auth/Federation.d.ts +5 -0
  153. package/transpiled/directives/auth/Federation.js +8 -0
  154. package/transpiled/directives/auth/Federation.js.map +1 -1
  155. package/transpiled/directives/auth/Id.d.ts +9 -0
  156. package/transpiled/directives/auth/Id.js +12 -0
  157. package/transpiled/directives/auth/Id.js.map +1 -1
  158. package/transpiled/directives/auth/Input.d.ts +2 -0
  159. package/transpiled/directives/auth/Input.js +4 -0
  160. package/transpiled/directives/auth/Input.js.map +1 -1
  161. package/transpiled/directives/auth/Role.d.ts +7 -0
  162. package/transpiled/directives/auth/Role.js +11 -0
  163. package/transpiled/directives/auth/Role.js.map +1 -1
  164. package/transpiled/directives/cors/CORS.js +9 -2
  165. package/transpiled/directives/cors/CORS.js.map +1 -1
  166. package/transpiled/directives/help/Family.d.ts +28 -0
  167. package/transpiled/directives/help/Family.js +79 -0
  168. package/transpiled/directives/help/Family.js.map +1 -0
  169. package/transpiled/directives/help/Help.d.ts +16 -0
  170. package/transpiled/directives/help/Help.js +22 -0
  171. package/transpiled/directives/help/Help.js.map +1 -0
  172. package/transpiled/directives/help/Parameters.d.ts +18 -0
  173. package/transpiled/directives/help/Parameters.js +34 -0
  174. package/transpiled/directives/help/Parameters.js.map +1 -0
  175. package/transpiled/directives/help/described.d.ts +16 -0
  176. package/transpiled/directives/help/described.js +23 -0
  177. package/transpiled/directives/help/described.js.map +1 -0
  178. package/transpiled/directives/help/index.d.ts +22 -0
  179. package/transpiled/directives/help/index.js +34 -0
  180. package/transpiled/directives/help/index.js.map +1 -0
  181. package/transpiled/directives/index.d.ts +4 -0
  182. package/transpiled/directives/index.js +8 -1
  183. package/transpiled/directives/index.js.map +1 -1
  184. package/transpiled/directives/map/Headers.d.ts +5 -0
  185. package/transpiled/directives/map/Headers.js +7 -5
  186. package/transpiled/directives/map/Headers.js.map +1 -1
  187. package/transpiled/directives/mcp/MCP.d.ts +8 -5
  188. package/transpiled/directives/mcp/MCP.js +9 -13
  189. package/transpiled/directives/mcp/MCP.js.map +1 -1
  190. package/transpiled/directives/mcp/Tool.d.ts +6 -12
  191. package/transpiled/directives/mcp/Tool.js +10 -23
  192. package/transpiled/directives/mcp/Tool.js.map +1 -1
  193. package/tsconfig.tsbuildinfo +1 -1
  194. package/ui/dist/_app/env.js +1 -0
  195. package/ui/dist/_app/immutable/assets/0.BcIGtTfw.css +2 -0
  196. package/ui/dist/_app/immutable/assets/2.BC0-Jhqu.css +1 -0
  197. package/ui/dist/_app/immutable/assets/inter-cyrillic-ext-wght-normal.BOeWTOD4.woff2 +0 -0
  198. package/ui/dist/_app/immutable/assets/inter-cyrillic-wght-normal.DqGufNeO.woff2 +0 -0
  199. package/ui/dist/_app/immutable/assets/inter-greek-ext-wght-normal.DlzME5K_.woff2 +0 -0
  200. package/ui/dist/_app/immutable/assets/inter-greek-wght-normal.CkhJZR-_.woff2 +0 -0
  201. package/ui/dist/_app/immutable/assets/inter-latin-ext-wght-normal.DO1Apj_S.woff2 +0 -0
  202. package/ui/dist/_app/immutable/assets/inter-latin-wght-normal.Dx4kXJAl.woff2 +0 -0
  203. package/ui/dist/_app/immutable/assets/inter-vietnamese-wght-normal.CBcvBZtf.woff2 +0 -0
  204. package/ui/dist/_app/immutable/chunks/BgTrzN6u.js +1 -0
  205. package/ui/dist/_app/immutable/chunks/Bjy-W4x2.js +81 -0
  206. package/ui/dist/_app/immutable/chunks/CWJaG9mK.js +6 -0
  207. package/ui/dist/_app/immutable/chunks/DjIYL2zC.js +3 -0
  208. package/ui/dist/_app/immutable/chunks/xihTtKlq.js +1 -0
  209. package/ui/dist/_app/immutable/chunks/yDQhitF9.js +1 -0
  210. package/ui/dist/_app/immutable/entry/app.WZSMBcGf.js +2 -0
  211. package/ui/dist/_app/immutable/entry/start._LkoC6Lz.js +1 -0
  212. package/ui/dist/_app/immutable/nodes/0.Dl2DqINo.js +1 -0
  213. package/ui/dist/_app/immutable/nodes/1.CSSnGhVD.js +1 -0
  214. package/ui/dist/_app/immutable/nodes/2.DmolQyNG.js +14 -0
  215. package/ui/dist/_app/version.json +1 -0
  216. package/ui/dist/apple-touch-icon.png +0 -0
  217. package/ui/dist/favicon-96x96.png +0 -0
  218. package/ui/dist/favicon.ico +0 -0
  219. package/ui/dist/index.html +66 -0
@@ -0,0 +1,81 @@
1
+ # Resource discovery
2
+
3
+ Everything an application serves, at one path, and a page to read it with.
4
+
5
+ ```http
6
+ OPTIONS /.discovery HTTP/1.1
7
+ accept: application/yaml
8
+ ```
9
+
10
+ ```http
11
+ 200 OK
12
+ Allow: GET, HEAD, OPTIONS
13
+ cache-control: private, max-age=1800
14
+
15
+ routes:
16
+ /pots:
17
+ title: Pots
18
+ GET:
19
+ title: Every pot
20
+ description: Every pot there is, newest first.
21
+ output:
22
+ type: array
23
+ items:
24
+ type: object
25
+ properties:
26
+ title: { type: string, maxLength: 64 }
27
+ POST:
28
+ input:
29
+ type: object
30
+ properties:
31
+ title: { type: string, maxLength: 64 }
32
+ required: [title]
33
+ errors:
34
+ - NO_WAY
35
+ /pots/:id:
36
+ protected: true
37
+ GET:
38
+ protected: true
39
+ route:
40
+ id: { title: Which pot }
41
+ ```
42
+
43
+ `routes` is keyed by route template, and an object so that what is said of the whole tree has
44
+ somewhere to go. What a key maps to is what [`OPTIONS`](introspection.md) on that path
45
+ answers — what the resource is, from [`help:node`](help.md), beside every method of it — so a key
46
+ can be read from here and sent straight back as a request. A method this identity may not reach is
47
+ not in either, and a resource they may reach no method of is not here at all — so what a reader
48
+ sees is what they may call, and signing in is what adds to it.
49
+
50
+ A key carries no trailing slash, and a request needs one: `OPTIONS /pots/:id` and
51
+ `OPTIONS /pots/:id/` both answer, but `GET /pots/:id` is `404` and `GET /pots/:id/` is the call.
52
+ Append it.
53
+
54
+ Held for half an hour, and `private`: what is in it is what the identity that asked may reach.
55
+
56
+ ## The page
57
+
58
+ `GET /.discovery/` is a page that reads the tree, served from the same origin the API is. It
59
+ answers before a credential is read, so it opens for a client whose token has expired — which is
60
+ the client most likely to be looking.
61
+
62
+ A method is called from it: the verb is a button, what the method takes is a form, and what came
63
+ back is shown where the form was. The call is made as whoever is reading — the same request they
64
+ would send themselves, and with the same effect.
65
+
66
+ A `GET /` whose `accept` prefers a page is sent here, so the address of the application opens it
67
+ in a browser. A route declared at `/` answers `/` as it always did; this is only what stands where
68
+ an application serves nothing there.
69
+
70
+ ## What it is not
71
+
72
+ Neither is annotated, and both answer wherever a gateway does. What each entry says is what
73
+ `OPTIONS` on that path already says to the same caller, but the enumeration is not otherwise
74
+ available: an application that does not publish its routes to everyone refuses `/.discovery` at
75
+ the ingress.
76
+
77
+ ## References
78
+
79
+ - [Introspection](introspection.md), which is what an entry is
80
+ - [Help](help.md), which is what names one
81
+ - [Features](../features/discovery.feature)
@@ -0,0 +1,109 @@
1
+ # Help
2
+
3
+ What a resource and its methods are, in the words an application chooses for them. One
4
+ declaration, read by everything that describes a resource: [`OPTIONS`](introspection.md),
5
+ [discovery](discovery.md), and the tools [MCP](mcp.md) publishes.
6
+
7
+ ```yaml
8
+ /pots:
9
+ help:node: Pots
10
+ GET:
11
+ help:method: Every pot
12
+ help:query:
13
+ criteria:
14
+ title: What to match
15
+ description: An RSQL expression over a pot's fields.
16
+ endpoint: enumerate
17
+ /:id:
18
+ GET:
19
+ help:method:
20
+ title: One pot
21
+ description: The pot by its id, and what is in it.
22
+ help:route:
23
+ id: Which pot
24
+ endpoint: observe
25
+ ```
26
+
27
+ <dl>
28
+ <dt><code>help:node</code></dt>
29
+ <dd>What the resource is.</dd>
30
+ <dt><code>help:method</code></dt>
31
+ <dd>What one of its methods is.</dd>
32
+ <dt><code>help:route</code></dt>
33
+ <dd>What a route variable is, by the name the template gives it.</dd>
34
+ <dt><code>help:query</code></dt>
35
+ <dd>What a querystring parameter is, by name.</dd>
36
+ </dl>
37
+
38
+ All four take the same value. A bare one is the <code>title</code> — the short thing, and what a
39
+ client has somewhere to put. A sentence is written as a <code>description</code> beside it, and
40
+ either may stand alone. The parameters name each one they describe.
41
+
42
+ A title is not a name. A name is an address — `apps._identity._id.repos.POST` — and reads as one;
43
+ this is what a person is shown instead.
44
+
45
+ An operation [states what it is](/documentation/component/declaration.md) as well, and that is not
46
+ this: it is written without knowledge of any route, and a method is an operation and a route
47
+ together. The same operation mounted twice is two methods, and one sentence is not true of both.
48
+ The operation's own is the Introspection's to read, and the gateway does not use it.
49
+
50
+ ## Where it is answered
51
+
52
+ `help:node` is answered beside the methods, and `help:method` inside the method it is on. A verb
53
+ is upper case, so neither key can be mistaken for the other. A parameter's goes in its schema,
54
+ which is where a schema says these anyway:
55
+
56
+ ```yaml
57
+ title: Pots
58
+ GET:
59
+ title: Every pot
60
+ query:
61
+ criteria:
62
+ type: string
63
+ title: What to match
64
+ description: An RSQL expression over a pot's fields.
65
+ ```
66
+
67
+ Describe a parameter by the name the answer carries it under. `map:segments` answers a variable
68
+ under the property it fills, so that is its name here and the template's is nowhere in the answer.
69
+
70
+ A route variable the template names is answered whether or not the operation declares it — an `:id`
71
+ an observation takes through the query is otherwise not in the answer at all, and describing it is
72
+ what puts it there. Anything else is described only where it is already answered. A name that is
73
+ nobody's is not answered, and nothing here checks one: the template does not say them all.
74
+
75
+ `help:query` describes the [parameters a resource declares](query.md#parameters). What selects
76
+ records — `criteria`, `sort` and the rest — is the same of every queryable resource and says so
77
+ itself, so it is neither answered here nor described.
78
+
79
+ ## What it is not inherited by
80
+
81
+ Every other directive applies to everything below the node it is on. This one does not: carried
82
+ downward, it would say of every resource below that it is the one it was written for.
83
+
84
+ ```yaml
85
+ /pots:
86
+ help:node: Pots # `/pots`, and not `/pots/:id`
87
+ GET: enumerate
88
+ /:id:
89
+ GET: observe
90
+ ```
91
+
92
+ A node whose `/` answers in its place is the exception, because the two are one resource:
93
+
94
+ ```yaml
95
+ /pots:
96
+ help:node: Pots # `/pots/` is what answers, and it is described
97
+ /:
98
+ GET: enumerate
99
+ ```
100
+
101
+ A resource is described beside what it serves, so a node that serves nothing cannot be described.
102
+ Declaring it there is refused where it is written, rather than found missing from the answer.
103
+
104
+ ## References
105
+
106
+ - [Resource introspection](introspection.md), which is where a method's own is answered
107
+ - [Resource discovery](discovery.md), which answers every resource at once
108
+ - [MCP](mcp.md), where a published method is a tool called this
109
+ - [Features](../features/help.feature)
@@ -1,21 +1,46 @@
1
1
  # Resource introspection
2
2
 
3
3
  Any resource can be introspected by sending an `OPTIONS` request to the resource's path.
4
+ [Discovery](discovery.md) answers the same of every resource at once.
4
5
 
5
6
  What it answers is what the route's directives leave of what the operation declared, for each
6
- method the request may reach. A method it may not is not there, nor in `Allow`; a resource whose
7
- every method it may not reach is `403`.
7
+ method this identity may reach. A method they may not is not there, nor in `Allow`; a resource
8
+ whose every method they may not reach is `403`.
9
+
10
+ A credential does not narrow it. What refuses one at an [`anonymous`](access.md#anonymous) route
11
+ is about a reply a cache would hold, and a description is not one — so a route that is public is
12
+ described to whoever asks, as it is at `/.rpc` and `/.mcp`.
13
+
14
+ What the resource itself is, from [`help:node`](help.md), is answered beside the methods rather
15
+ than inside one. A verb is upper case, so neither key can be mistaken for the other. A resource
16
+ carries `private`, `protected` and `system` where any of its methods does.
8
17
 
9
18
  Introspection properties:
10
19
 
11
- - `description` what the route states this method is, from [`mcp:tool`](mcp.md#what-a-tool-is).
12
- The operation states what it is too, and that is not this: it is written without knowledge of
13
- any route, and the same operation mounted twice is two methods
14
- - `route` route parameters, including what `map:segments` names differently
15
- - `query` query parameters
16
- - `headers` properties `map:headers` reads from a request header, and which header
20
+ - `title` and `description` what the route states this method is, from
21
+ [`help:method`](help.md). The operation states what it is too, and that is not this: it is
22
+ written without knowledge of any route, and the same operation mounted twice is two methods
23
+ - `authenticated` reaching it takes being someone, whoever — [`auth:anyone`](access.md#anyone),
24
+ `auth:delegate` and `auth:claims`
25
+ - `private` reaching it is being the identity it is about — [`auth:id`](access.md#id)
26
+ - `protected` reaching it takes a role — [`auth:role`](access.md#role)
27
+ - `system` and that role is one of the `system` scope, which guards what an application runs
28
+ on rather than what it serves
29
+ - `route` route parameters, including what `map:segments` names differently, each as
30
+ [`help:route`](help.md) describes it
31
+ - `query` the [query parameters](query.md#parameters) this resource declares, each as
32
+ [`help:query`](help.md) describes it. What selects records — `criteria`, `sort`, `limit`,
33
+ `omit`, `search` — is not among them: it is the same of every queryable resource, and so
34
+ not something one says about itself. A [procedure](rpc.md) carries it, because there it is
35
+ something the caller sends
17
36
  - `input` input schema, restricted by `io:input`, without what the gateway fills itself
18
- - `output` output schema, restricted by `io:output`; absent where the reply is not sent at all
37
+ - `output` output schema, restricted by `io:output`; absent where the reply is not sent at all.
38
+ An operation that declares none is described by what its type answers: a transition, an
39
+ observation and an assignment work on the Entity Object, so that is what they are said to
40
+ answer — a set of them where the scope is a set. A computation neither uses the scope nor
41
+ produces one, and an effect answers whatever it computed, so neither is guessed at. It is a
42
+ description and nothing is held to it — an operation answering a projection says so by
43
+ declaring its own `output`
19
44
  - `errors` error codes
20
45
 
21
46
  ```http
@@ -32,12 +32,14 @@ A tool is an RTD method that says it is one, named as the [procedure](rpc.md#the
32
32
  ```yaml
33
33
  /pots:
34
34
  GET:
35
- mcp:tool: Every pot there is, newest first.
35
+ mcp:tool: true
36
+ help:method: Every pot there is, newest first.
36
37
  endpoint: enumerate
37
38
  /hot:
38
39
  GET:
39
40
  query: { criteria: temperature=gt=80 }
40
- mcp:tool:
41
+ mcp:tool: true
42
+ help:method:
41
43
  title: Hot pots
42
44
  description: The pots that are too hot to pour.
43
45
  endpoint: enumerate
@@ -47,21 +49,12 @@ A default denies, and a tree holds everything an application serves — its iden
47
49
  uploads, the machinery of the authorization flow the model already came through. Publishing all of
48
50
  it would spend a model's context on what it has no business calling.
49
51
 
50
- The value is what the tool is, and stating it is what publishes it — there is no way to publish one
51
- that says nothing, because a tool a model cannot read the purpose of is one it cannot choose.
52
+ `mcp:tool` says only whether the method is published. What the tool is called and what it is for is
53
+ [`help:method`](help.md), which is what describes the method everywhere else as well — a tool a
54
+ model cannot read the purpose of is one it cannot choose, so declare one beside the other.
52
55
 
53
- A `title` is what a person is shown where a client lists what it may call. Without one a client has
54
- only the name, which is an address — `apps._identity._id.repos.POST` — and reads as one. A
55
- description alone is written as the value; a title beside it is written as a mapping.
56
-
57
- An operation [states what it is](/documentation/component/declaration.md) as well, and that is not
58
- this: it is written without knowledge of any route, and a tool is an operation and a route together.
59
- The two routes above are one operation and two tools, and one sentence is not true of both. The
60
- operation's own is the Introspection's to read, and the gateway does not use it.
61
-
62
- A declaration is inherited by everything below it, as every directive is, and the nearer one wins.
63
- Since what it carries is what one method is, it belongs on a method: a node stating one would say
64
- the same thing of everything under it.
56
+ A declaration is inherited by everything below it, as every directive is, and the nearer one wins —
57
+ so a node publishes a subtree, and `mcp:tool: false` withdraws one method of it.
65
58
 
66
59
  A route whose name a client [cannot spell](rpc.md#what-has-a-name) is refused where the directive
67
60
  is built, rather than served as a tool that is quietly never listed.
@@ -81,6 +74,10 @@ application did not publish is not reachable by guessing what it would have been
81
74
  querystring under `query`, and what is left is the body. `io:input` restricts it, and a property
82
75
  `map` fills is not there — a call carries no headers of its own.
83
76
 
77
+ `query` carries what selects records — `criteria`, `sort`, `limit`, `omit`, `search` — beside the
78
+ parameters the resource declares. `OPTIONS` states only the latter, because the former is the same
79
+ of every queryable resource; here it is stated, because here it is something the caller sends.
80
+
84
81
  ```yaml
85
82
  type: object
86
83
  properties:
@@ -13,6 +13,7 @@ Feature: CORS Support
13
13
  When the following request is received:
14
14
  """
15
15
  OPTIONS / HTTP/1.1
16
+ access-control-request-method: GET
16
17
  host: nex.toa.io
17
18
  origin: https://hello.world
18
19
  """
@@ -21,7 +22,7 @@ Feature: CORS Support
21
22
  204 No Content
22
23
  access-control-allow-credentials: true
23
24
  access-control-allow-headers: accept, authorization, content-type, if-match, if-none-match
24
- access-control-allow-methods: GET, POST, PUT, PATCH, DELETE, LOCK, UNLOCK
25
+ access-control-allow-methods: GET, POST, PUT, PATCH, DELETE, LOCK, UNLOCK, OPTIONS
25
26
  access-control-allow-origin: https://hello.world
26
27
  access-control-max-age: 3600
27
28
  cache-control: max-age=3600
@@ -42,6 +43,35 @@ Feature: CORS Support
42
43
  vary: origin
43
44
  """
44
45
 
46
+ Scenario: An OPTIONS that is not a preflight
47
+ A preflight is `OPTIONS` carrying `Access-Control-Request-Method`, and only that. A
48
+ browser puts `Origin` on every request whose method is not `GET` or `HEAD`, its own
49
+ `OPTIONS` included — so one answered as a preflight is a resource no page can introspect.
50
+
51
+ Given the annotation:
52
+ """yaml
53
+ /:
54
+ anonymous: true
55
+ /foo:
56
+ io:output: true
57
+ GET:
58
+ dev:stub: Hello
59
+ """
60
+ When the following request is received:
61
+ """
62
+ OPTIONS /foo/ HTTP/1.1
63
+ host: nex.toa.io
64
+ origin: https://hello.world
65
+ accept: application/yaml
66
+ """
67
+ Then the following reply is sent:
68
+ """
69
+ 200 OK
70
+ access-control-allow-origin: https://hello.world
71
+ Allow: GET
72
+ vary: origin
73
+ """
74
+
45
75
  Scenario: Always vary CORS
46
76
  Given the annotation:
47
77
  """yaml
@@ -18,6 +18,7 @@ Feature: Dev
18
18
  When the following request is received:
19
19
  """
20
20
  OPTIONS / HTTP/1.1
21
+ access-control-request-method: GET
21
22
  origin: http://example.com
22
23
  """
23
24
  Then the following reply is sent:
@@ -70,6 +71,7 @@ Feature: Dev
70
71
  When the following request is received:
71
72
  """
72
73
  OPTIONS / HTTP/1.1
74
+ access-control-request-method: GET
73
75
  origin: http://example.com
74
76
  """
75
77
  Then the following reply is sent:
@@ -0,0 +1,211 @@
1
+ Feature: Resource discovery
2
+
3
+ Every route an application serves, at one path, described as `OPTIONS` on each of them
4
+ describes it.
5
+
6
+ Scenario: What an application serves
7
+ Given the `pots` is running with the following manifest:
8
+ """yaml
9
+ exposition:
10
+ /:
11
+ io:output: [id, title]
12
+ GET: enumerate
13
+ POST: create
14
+ /:id:
15
+ GET: observe
16
+ """
17
+ When the following request is received:
18
+ """
19
+ OPTIONS /.discovery HTTP/1.1
20
+ host: nex.toa.io
21
+ accept: application/yaml
22
+ """
23
+ Then the following reply is sent:
24
+ """
25
+ 200 OK
26
+ Allow: GET, HEAD, OPTIONS
27
+ cache-control: private, max-age=1800
28
+
29
+ routes:
30
+ /pots:
31
+ GET:
32
+ output:
33
+ type: array
34
+ items:
35
+ properties:
36
+ id:
37
+ type: string
38
+ title:
39
+ type: string
40
+ POST:
41
+ input:
42
+ properties:
43
+ title:
44
+ type: string
45
+ /pots/:id:
46
+ GET:
47
+ output:
48
+ properties:
49
+ id:
50
+ type: string
51
+ """
52
+
53
+ Scenario: A key is a request that can be made
54
+ What each entry says is what `OPTIONS` on that key says, so a client reads the tree
55
+ once and addresses any of it by the key it was given.
56
+
57
+ Given the `echo` is running with the following manifest:
58
+ """yaml
59
+ exposition:
60
+ /:a:
61
+ io:output: true
62
+ PATCH: parameters
63
+ """
64
+ When the following request is received:
65
+ """
66
+ OPTIONS /.discovery HTTP/1.1
67
+ host: nex.toa.io
68
+ accept: application/yaml
69
+ """
70
+ Then the following reply is sent:
71
+ """
72
+ 200 OK
73
+
74
+ routes:
75
+ /echo/:a:
76
+ PATCH:
77
+ route:
78
+ a:
79
+ type: string
80
+ input:
81
+ type: object
82
+ properties:
83
+ b:
84
+ type: string
85
+ """
86
+ When the following request is received:
87
+ """
88
+ OPTIONS /echo/:a/ HTTP/1.1
89
+ host: nex.toa.io
90
+ accept: application/yaml
91
+ """
92
+ Then the following reply is sent:
93
+ """
94
+ 200 OK
95
+ Allow: PATCH
96
+
97
+ PATCH:
98
+ route:
99
+ a:
100
+ type: string
101
+ input:
102
+ type: object
103
+ properties:
104
+ b:
105
+ type: string
106
+ """
107
+
108
+ Scenario: A resource this caller may reach no method of is not there
109
+ `OPTIONS` on one answers `403`, and a tree cannot refuse a single entry — so it is
110
+ omitted, rather than named as something that exists and is not for them.
111
+
112
+ Given the annotation:
113
+ """yaml
114
+ /:
115
+ /open:
116
+ anonymous: true
117
+ GET:
118
+ dev:stub: hello
119
+ /closed:
120
+ auth:role: admin
121
+ GET:
122
+ dev:stub: secret
123
+ """
124
+ When the following request is received:
125
+ """
126
+ OPTIONS /.discovery HTTP/1.1
127
+ host: nex.toa.io
128
+ accept: application/yaml
129
+ """
130
+ Then the following reply is sent:
131
+ """
132
+ 200 OK
133
+
134
+ routes:
135
+ /open:
136
+ GET: {}
137
+ """
138
+ And the reply does not contain:
139
+ """
140
+ /closed:
141
+ """
142
+
143
+ Scenario: Only the verbs this caller may reach
144
+ A resource is described by what is left of it, not dropped for the part that is not
145
+ theirs.
146
+
147
+ Given the `pots` is running with the following manifest:
148
+ """yaml
149
+ exposition:
150
+ /:
151
+ isolated: true
152
+ io:output: [id, title]
153
+ GET:
154
+ anonymous: true
155
+ endpoint: enumerate
156
+ POST:
157
+ auth:role: admin
158
+ endpoint: create
159
+ """
160
+ When the following request is received:
161
+ """
162
+ OPTIONS /.discovery HTTP/1.1
163
+ host: nex.toa.io
164
+ accept: application/yaml
165
+ """
166
+ Then the following reply is sent:
167
+ """
168
+ 200 OK
169
+
170
+ routes:
171
+ /pots:
172
+ GET:
173
+ """
174
+ And the reply does not contain:
175
+ """
176
+ temperature
177
+ """
178
+
179
+ Scenario: A credential does not hide what is anonymous
180
+ A credential refuses an `anonymous` route because it would make the reply uncacheable,
181
+ and a description is not that reply. Without this an identity would be shown less of
182
+ the tree than someone presenting nothing at all.
183
+
184
+ Given the `identity.basic` database contains:
185
+ # developer:secret
186
+ | _id | authority | username | password |
187
+ | efe3a65ebbee47ed95a73edd911ea328 | nex | developer | $2b$10$ZRSKkgZoGnrcTNA5w5eCcu3pxDzdTduhteVYXcp56AaNcilNkwJ.O |
188
+ And the `identity.bans` database is empty
189
+ And the annotation:
190
+ """yaml
191
+ /:
192
+ /pots:
193
+ anonymous: true
194
+ GET:
195
+ dev:stub: hello
196
+ """
197
+ When the following request is received:
198
+ """
199
+ OPTIONS /.discovery HTTP/1.1
200
+ host: nex.toa.io
201
+ authorization: Basic ZGV2ZWxvcGVyOnNlY3JldA==
202
+ accept: application/yaml
203
+ """
204
+ Then the following reply is sent:
205
+ """
206
+ 200 OK
207
+
208
+ routes:
209
+ /pots:
210
+ GET: {}
211
+ """