@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,197 @@
1
+ Feature: The discovery page
2
+
3
+ The page that reads the tree, served from the same origin the tree is. The scenarios are
4
+ about the server rather than the page: it is pointed at a fixture, so none of them needs
5
+ a UI build.
6
+
7
+ Scenario: The page
8
+ When the following request is received:
9
+ """
10
+ GET /.discovery/ HTTP/1.1
11
+ host: nex.toa.io
12
+ """
13
+ Then the following reply is sent:
14
+ """
15
+ 200 OK
16
+ cache-control: no-cache
17
+ content-type: text/html; charset=utf-8
18
+
19
+ the page
20
+ """
21
+
22
+ Scenario: The bare path is the directory it is
23
+ When the following request is received:
24
+ """
25
+ GET /.discovery HTTP/1.1
26
+ host: nex.toa.io
27
+ """
28
+ Then the following reply is sent:
29
+ """
30
+ 302 Moved Temporarily
31
+ location: /.discovery/
32
+ """
33
+
34
+ Scenario: An asset carries the build it was named for
35
+ When the following request is received:
36
+ """
37
+ GET /.discovery/_app/immutable/asset.js HTTP/1.1
38
+ host: nex.toa.io
39
+ """
40
+ Then the following reply is sent:
41
+ """
42
+ 200 OK
43
+ cache-control: public, max-age=31536000, immutable
44
+ content-type: text/javascript; charset=utf-8
45
+ """
46
+
47
+ Scenario: Anything that could be a route is the page
48
+ The client router knows what routes there are, and this server does not.
49
+
50
+ When the following request is received:
51
+ """
52
+ GET /.discovery/pots/:id HTTP/1.1
53
+ host: nex.toa.io
54
+ """
55
+ Then the following reply is sent:
56
+ """
57
+ 200 OK
58
+ content-type: text/html; charset=utf-8
59
+
60
+ the page
61
+ """
62
+
63
+ Scenario: An asset that is not there is not there
64
+ When the following request is received:
65
+ """
66
+ GET /.discovery/_app/immutable/gone.js HTTP/1.1
67
+ host: nex.toa.io
68
+ """
69
+ Then the following reply is sent:
70
+ """
71
+ 404 Not Found
72
+ """
73
+
74
+ Scenario: A path that merely starts like this one is not this one
75
+ When the following request is received:
76
+ """
77
+ GET /.discoveryable/ HTTP/1.1
78
+ host: nex.toa.io
79
+ """
80
+ Then the following reply is sent:
81
+ """
82
+ 404 Not Found
83
+ """
84
+
85
+ Scenario: A path that climbs out of the page is refused
86
+ An encoded separator is what reaches here: a dot segment is collapsed while the URL is
87
+ parsed, and `%2f` survives that to decode into one afterwards.
88
+
89
+ When the following request is received:
90
+ """
91
+ GET /.discovery/%2f%2e%2e%2f%2e%2e%2fpackage.json HTTP/1.1
92
+ host: nex.toa.io
93
+ """
94
+ Then the following reply is sent:
95
+ """
96
+ 404 Not Found
97
+ """
98
+
99
+ Scenario: The page is served before a credential is read
100
+ A page is public, and one served after `auth` would refuse the client holding a stale
101
+ token — who is the client most likely to have opened it.
102
+
103
+ Given the annotation:
104
+ """yaml
105
+ /:
106
+ GET:
107
+ dev:stub: hello
108
+ """
109
+ When the following request is received:
110
+ """
111
+ GET / HTTP/1.1
112
+ host: nex.toa.io
113
+ """
114
+ Then the following reply is sent:
115
+ """
116
+ 401 Unauthorized
117
+ """
118
+ When the following request is received:
119
+ """
120
+ GET /.discovery/ HTTP/1.1
121
+ host: nex.toa.io
122
+ """
123
+ Then the following reply is sent:
124
+ """
125
+ 200 OK
126
+
127
+ the page
128
+ """
129
+
130
+ Scenario: What else the path answers
131
+ When the following request is received:
132
+ """
133
+ POST /.discovery/ HTTP/1.1
134
+ host: nex.toa.io
135
+ """
136
+ Then the following reply is sent:
137
+ """
138
+ 405 Method Not Allowed
139
+ allow: GET, HEAD, OPTIONS
140
+ """
141
+
142
+ Scenario: A browser at the trunk is sent to the page
143
+ An application serves what it declares, and `/` is usually not one of those. Someone
144
+ who typed the address into a browser is looking for something to look at, and the page
145
+ is the only thing here that is one.
146
+
147
+ When the following request is received:
148
+ """
149
+ GET / HTTP/1.1
150
+ host: nex.toa.io
151
+ accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
152
+ """
153
+ Then the following reply is sent:
154
+ """
155
+ 302 Moved Temporarily
156
+ location: /.discovery/
157
+ """
158
+
159
+ Scenario: A client that takes anything is not
160
+ `accept` has to prefer a page over what the gateway answers with, which is what a
161
+ browser sends and an API client does not.
162
+
163
+ When the following request is received:
164
+ """
165
+ GET / HTTP/1.1
166
+ host: nex.toa.io
167
+ accept: */*
168
+ """
169
+ Then the following reply is sent:
170
+ """
171
+ 405 Method Not Allowed
172
+ """
173
+
174
+ Scenario: What the application serves there answers instead
175
+ A route declared at the trunk is what answers it, browser or not.
176
+
177
+ Given the annotation:
178
+ """yaml
179
+ /:
180
+ anonymous: true
181
+ GET:
182
+ dev:stub: hello
183
+ """
184
+ When the following request is received:
185
+ """
186
+ GET / HTTP/1.1
187
+ host: nex.toa.io
188
+ accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
189
+ """
190
+ Then the following reply is sent:
191
+ """
192
+ 200 OK
193
+ """
194
+ And the reply does not contain:
195
+ """
196
+ /.discovery/
197
+ """
@@ -0,0 +1,466 @@
1
+ Feature: 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`, discovery, and the
5
+ tools MCP publishes.
6
+
7
+ Scenario: What a method is
8
+ A bare value is the title, which is the short thing. A sentence is written beside it.
9
+
10
+ Given the `pots` is running with the following manifest:
11
+ """yaml
12
+ exposition:
13
+ /:
14
+ io:output: [id]
15
+ GET:
16
+ help:method: Every pot
17
+ endpoint: enumerate
18
+ POST:
19
+ help:method:
20
+ title: Put a pot on
21
+ description: Start a new pot brewing.
22
+ endpoint: create
23
+ """
24
+ When the following request is received:
25
+ """
26
+ OPTIONS /pots/ HTTP/1.1
27
+ host: nex.toa.io
28
+ accept: application/yaml
29
+ """
30
+ Then the following reply is sent:
31
+ """
32
+ 200 OK
33
+ Allow: GET, POST
34
+
35
+ GET:
36
+ title: Every pot
37
+ POST:
38
+ title: Put a pot on
39
+ description: Start a new pot brewing.
40
+ """
41
+
42
+ Scenario: What a resource is
43
+ A resource is described beside the methods it serves, and a verb is upper case — so
44
+ neither key can be mistaken for the other.
45
+
46
+ Given the `pots` is running with the following manifest:
47
+ """yaml
48
+ exposition:
49
+ /:
50
+ help:node: Pots
51
+ io:output: [id]
52
+ GET: enumerate
53
+ """
54
+ When the following request is received:
55
+ """
56
+ OPTIONS /pots/ HTTP/1.1
57
+ host: nex.toa.io
58
+ accept: application/yaml
59
+ """
60
+ Then the following reply is sent:
61
+ """
62
+ 200 OK
63
+ Allow: GET
64
+
65
+ title: Pots
66
+ GET:
67
+ """
68
+
69
+ Scenario: A resource says what a method does, in the same words
70
+ Both take the same value: a bare one is the title, a sentence is written beside it.
71
+
72
+ Given the `pots` is running with the following manifest:
73
+ """yaml
74
+ exposition:
75
+ /:
76
+ help:node:
77
+ title: Pots
78
+ description: What is brewing, and what is in it.
79
+ io:output: [id]
80
+ GET: enumerate
81
+ """
82
+ When the following request is received:
83
+ """
84
+ OPTIONS /pots/ HTTP/1.1
85
+ host: nex.toa.io
86
+ accept: application/yaml
87
+ """
88
+ Then the following reply is sent:
89
+ """
90
+ 200 OK
91
+ Allow: GET
92
+
93
+ title: Pots
94
+ description: What is brewing, and what is in it.
95
+ GET:
96
+ """
97
+
98
+ Scenario: What a resource is, is not said of what is under it
99
+ Every other directive is inherited. This one is not: a description carried downward
100
+ would say of every resource below that it is the one it was written for.
101
+
102
+ Given the `pots` is running with the following manifest:
103
+ """yaml
104
+ exposition:
105
+ /:
106
+ help:node: Pots
107
+ io:output: [id]
108
+ GET: enumerate
109
+ /:id:
110
+ GET: observe
111
+ """
112
+ When the following request is received:
113
+ """
114
+ OPTIONS /pots/:id/ HTTP/1.1
115
+ host: nex.toa.io
116
+ accept: application/yaml
117
+ """
118
+ Then the following reply is sent:
119
+ """
120
+ 200 OK
121
+ Allow: GET
122
+ """
123
+ And the reply does not contain:
124
+ """
125
+ Pots
126
+ """
127
+
128
+ Scenario: A resource that answers in another's place is described by it
129
+ An intermediate node is never what a path matches — its `/` answers there instead — so
130
+ the two are one resource, and what describes it carries.
131
+
132
+ Given the `pots` is running with the following manifest:
133
+ """yaml
134
+ exposition:
135
+ /:
136
+ help:node: Pots
137
+ /:
138
+ io:output: [id]
139
+ GET: enumerate
140
+ """
141
+ When the following request is received:
142
+ """
143
+ OPTIONS /pots/ HTTP/1.1
144
+ host: nex.toa.io
145
+ accept: application/yaml
146
+ """
147
+ Then the following reply is sent:
148
+ """
149
+ 200 OK
150
+ Allow: GET
151
+
152
+ title: Pots
153
+ """
154
+
155
+ Scenario: The whole tree carries it
156
+ Given the `pots` is running with the following manifest:
157
+ """yaml
158
+ exposition:
159
+ /:
160
+ help:node: Pots
161
+ io:output: [id]
162
+ GET:
163
+ help:method: Every pot
164
+ endpoint: enumerate
165
+ """
166
+ When the following request is received:
167
+ """
168
+ OPTIONS /.discovery HTTP/1.1
169
+ host: nex.toa.io
170
+ accept: application/yaml
171
+ """
172
+ Then the following reply is sent:
173
+ """
174
+ 200 OK
175
+
176
+ routes:
177
+ /pots:
178
+ title: Pots
179
+ GET:
180
+ title: Every pot
181
+ """
182
+
183
+ Scenario: What the parameters are
184
+ A schema says `title` and `description` of what it describes, which is where these go.
185
+
186
+ Given the `echo` is running with the following manifest:
187
+ """yaml
188
+ exposition:
189
+ /:first:
190
+ io:output: true
191
+ PATCH:
192
+ query:
193
+ parameters: [a]
194
+ help:route:
195
+ first: Which one
196
+ help:query:
197
+ a:
198
+ title: The one to greet
199
+ description: A name, or nothing to greet the world.
200
+ endpoint: parameters
201
+ """
202
+ When the following request is received:
203
+ """
204
+ OPTIONS /echo/:first/ HTTP/1.1
205
+ host: nex.toa.io
206
+ accept: application/yaml
207
+ """
208
+ Then the following reply is sent:
209
+ """
210
+ 200 OK
211
+ Allow: PATCH
212
+
213
+ PATCH:
214
+ route:
215
+ first:
216
+ title: Which one
217
+ query:
218
+ a:
219
+ type: string
220
+ title: The one to greet
221
+ description: A name, or nothing to greet the world.
222
+ """
223
+
224
+ Scenario: A segment a mapping renamed
225
+ `map:segments` answers a variable under the property it fills, so that is the name it
226
+ is described by — the template's is nowhere in the answer.
227
+
228
+ Given the `echo` is running with the following manifest:
229
+ """yaml
230
+ exposition:
231
+ /:first:
232
+ io:output: true
233
+ PATCH:
234
+ map:segments:
235
+ a: first
236
+ help:route:
237
+ a: Which one
238
+ endpoint: parameters
239
+ """
240
+ When the following request is received:
241
+ """
242
+ OPTIONS /echo/:first/ HTTP/1.1
243
+ host: nex.toa.io
244
+ accept: application/yaml
245
+ """
246
+ Then the following reply is sent:
247
+ """
248
+ 200 OK
249
+ Allow: PATCH
250
+
251
+ PATCH:
252
+ route:
253
+ a:
254
+ type: string
255
+ title: Which one
256
+ """
257
+ And the reply does not contain:
258
+ """
259
+ first:
260
+ """
261
+
262
+ Scenario: A parameter nothing answers
263
+ A name that is nobody's is not answered. The template is not what says them all, so
264
+ there is nothing to check it against where it is written.
265
+
266
+ Given the `pots` is running with the following manifest:
267
+ """yaml
268
+ exposition:
269
+ /:id:
270
+ io:output: [id]
271
+ GET:
272
+ help:route:
273
+ pot: Which pot
274
+ help:query:
275
+ nothing: Nobody
276
+ endpoint: observe
277
+ """
278
+ When the following request is received:
279
+ """
280
+ OPTIONS /pots/:id/ HTTP/1.1
281
+ host: nex.toa.io
282
+ accept: application/yaml
283
+ """
284
+ Then the following reply is sent:
285
+ """
286
+ 200 OK
287
+ Allow: GET
288
+ """
289
+ And the reply does not contain:
290
+ """
291
+ Which pot
292
+ """
293
+
294
+ Scenario: What guards a resource
295
+ `auth` says what reaching one takes, so that a reader is not offered what is not theirs
296
+ and can tell the machinery from what the application serves.
297
+
298
+ Given the annotation:
299
+ """yaml
300
+ /:
301
+ anonymous: true
302
+ /pots:
303
+ GET:
304
+ auth:anyone: true
305
+ dev:stub: []
306
+ /accounts/:id:
307
+ GET:
308
+ auth:id: id
309
+ dev:stub: []
310
+ /admin:
311
+ GET:
312
+ auth:role: operator
313
+ dev:stub: []
314
+ /introspection:
315
+ GET:
316
+ auth:role: system:introspection
317
+ dev:stub: []
318
+ """
319
+ When the following request is received:
320
+ """
321
+ OPTIONS /.discovery HTTP/1.1
322
+ host: nex.toa.io
323
+ accept: application/yaml
324
+ """
325
+ Then the following reply is sent:
326
+ """
327
+ 200 OK
328
+
329
+ routes:
330
+ /accounts/:id:
331
+ private: true
332
+ GET:
333
+ private: true
334
+ /admin:
335
+ protected: true
336
+ GET:
337
+ protected: true
338
+ /introspection:
339
+ protected: true
340
+ system: true
341
+ GET:
342
+ protected: true
343
+ system: true
344
+ /pots:
345
+ authenticated: true
346
+ GET:
347
+ authenticated: true
348
+ """
349
+
350
+ Scenario: A rule says what each of its own says
351
+ `auth:rule` is the directives it composes, and describing one is describing them.
352
+
353
+ Given the annotation:
354
+ """yaml
355
+ /:
356
+ anonymous: true
357
+ /papers/:id:
358
+ GET:
359
+ auth:rule:
360
+ id: id
361
+ role: system:archive
362
+ dev:stub: []
363
+ """
364
+ When the following request is received:
365
+ """
366
+ OPTIONS /papers/:id/ HTTP/1.1
367
+ host: nex.toa.io
368
+ accept: application/yaml
369
+ """
370
+ Then the following reply is sent:
371
+ """
372
+ 200 OK
373
+ Allow: GET
374
+
375
+ private: true
376
+ protected: true
377
+ system: true
378
+ GET:
379
+ private: true
380
+ protected: true
381
+ system: true
382
+ """
383
+
384
+ Scenario: A tool is called what the method is called
385
+ `mcp:tool` says only whether the method is published; what it is, is `help:method`.
386
+
387
+ Given the annotation:
388
+ """yaml
389
+ mcp:
390
+ name: Pots
391
+ anonymous: true
392
+ /:
393
+ anonymous: true
394
+ /pots:
395
+ GET:
396
+ mcp:tool: true
397
+ help:method:
398
+ title: Hot pots
399
+ description: The pots that are too hot to pour.
400
+ dev:stub: []
401
+ """
402
+ When the following request is received:
403
+ """
404
+ POST /.mcp HTTP/1.1
405
+ host: nex.toa.io
406
+ content-type: application/json
407
+ accept: application/yaml
408
+ mcp-protocol-version: 2026-07-28
409
+ mcp-method: tools/list
410
+
411
+ {"jsonrpc": "2.0", "id": 1, "method": "tools/list",
412
+ "params": {"_meta": {"io.modelcontextprotocol/protocolVersion": "2026-07-28",
413
+ "io.modelcontextprotocol/clientCapabilities": {}}}}
414
+ """
415
+ Then the following reply is sent:
416
+ """
417
+ 200 OK
418
+
419
+ - name: pots.GET
420
+ title: Hot pots
421
+ description: The pots that are too hot to pour.
422
+ """
423
+ # and the resource says of itself that it publishes one
424
+ When the following request is received:
425
+ """
426
+ OPTIONS /pots/ HTTP/1.1
427
+ host: nex.toa.io
428
+ accept: application/yaml
429
+ """
430
+ Then the following reply is sent:
431
+ """
432
+ 200 OK
433
+ Allow: GET
434
+
435
+ GET:
436
+ title: Hot pots
437
+ description: The pots that are too hot to pour.
438
+ mcp: true
439
+ """
440
+
441
+ Scenario: A resource that serves nothing cannot be described
442
+ Nothing would carry what it says, so the declaration is refused where it is written
443
+ rather than found missing from the answer.
444
+
445
+ Given the `pots` is running with the following manifest:
446
+ """yaml
447
+ exposition:
448
+ /:
449
+ io:output: [id]
450
+ GET: enumerate
451
+ /brewing:
452
+ help:node: Brewing
453
+ /:id:
454
+ GET: observe
455
+ """
456
+ # a route only this declaration serves: a refused branch leaves the one before it
457
+ When the following request is received:
458
+ """
459
+ OPTIONS /pots/brewing/:id/ HTTP/1.1
460
+ host: nex.toa.io
461
+ accept: application/yaml
462
+ """
463
+ Then the following reply is sent:
464
+ """
465
+ 404 Not Found
466
+ """