-
Notifications
You must be signed in to change notification settings - Fork 13
/
Copy pathindex.bs
460 lines (379 loc) · 24.4 KB
/
index.bs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
<pre class="metadata">
Title: Payment Method Manifest
Group: web-payments
Shortname: payment-method-manifest
Repository: w3c/payment-method-manifest
Status: ED
ED: https://w3c.github.io/payment-method-manifest/
TR: https://www.w3.org/TR/payment-method-manifest/
Level: none
Editor: Dapeng Liu, Alibaba
Editor: Rouslan Solomakhin, Google https://www.google.com/, rouslan@google.com
Editor: Stephen McGruer, Google https://www.google.com/, smcgruer@chromium.org
Former Editor: Domenic Denicola, w3cid 52873, Google https://www.google.com/, d@domenic.me, https://domenic.me/
Former Editor: Zach Koch, w3cid 76588, Google https://www.google.com/
Abstract: This specification defines the machine-readable manifest file, known as a
Abstract: <dfn export>payment method manifest</dfn>, describing how a [=payment method=]
Abstract: participates in the Web Payments ecosystem, and how such files are to be used.
Default Ref Status: current
Complain About: accidental-2119 true, missing-example-ids true
Assume Explicit For: true
Inline GitHub Issues: title
Boilerplate: omit issues-index
Indent: 2
Prepare For TR: True
</pre>
<pre class="anchors">
urlPrefix: https://w3c.github.io/payment-request/; spec: PAYMENT-REQUEST
type: dfn
text: payment method; url: #dfn-payment-method
text: [[serializedMethodData]]; url: #dfn-serializedmethoddata
text: PaymentRequest(methodData, details, options); type: constructor; for: PaymentRequest; url: #dfn-paymentrequest-paymentrequest
text: PaymentRequest; type: interface; url: #dom-paymentrequest
urlPrefix: https://w3c.github.io/manifest/; spec: APPMANIFEST; type: dfn
text: web app manifest; url: #dfn-manifest
text: URL; for: web app manifest; url: #dfn-manifest-url
text: steps for processing a web app manifest; url: #dfn-processing-a-manifest
text: processed web app manifest; url: #dfn-processed-manifest
urlPrefix: https://w3c.github.io/payment-method-id/; spec: PAYMENT-METHOD-ID; type: dfn
text: payment method identifier; url: #dfn-pmi
text: standardized payment method identifier; url: #dfn-standardized-payment-method-identifier
text: URL-based payment method identifier; url: #dfn-url-based-payment-method-identifier
text: validate a URL-based payment method identifier; url: #dfn-validate-a-url-based-payment-method-identifier
</pre>
<div class="non-normative">
<h2 id="introduction">Introduction</h2>
<em>This section and its sub-sections are non-normative.</em>
<h3 id="use-cases">Use cases</h3>
This specification intends to address the following use cases:
* The owner of a [=payment method=] wishes to authorize only certain parties to distribute
payment apps that are capable of implementing the payment method. In this use case, the
browser helps to ensure that for that payment method, the user can only invoke payment apps from
parties authorized by the owner of the payment method.
* In addition, the owner of a [=payment method=] wishes to confirm the authenticity of a
particular payment app (e.g., via a digital signature for that app).
* When the user has not yet installed a payment app for a [=payment method=], the user agent
can provide an improved user experience for procuring one.
This is accomplished via the requirement that every [=payment method=] whose
[=payment method identifier|identifier=] is [=URL-based payment method identifier|URL-based=] will
provide a [=payment method manifest=] file in JSON format containing two key pieces of information:
* any default payment apps that are associated with this [=payment method=], referenced as
[=valid URL strings=] giving the [=web app manifest/URL=] of their [=web app manifests=]; and
* any other [=origins=] that are permitted to return payment credentials for this
[=payment method=].
<h3 id="accessing">Accessing the manifest</h3>
The machine-readable [=payment method manifest=] might be found either directly at the
[=payment method identifier=] URL, or in a location indicated indirectly by following a HTTP
`<code>Link</code>` header from that URL. [[RFC8288]]
This potential indirection allows the use of generic, human-readable URLs (such as
"<code>https://alicepay.com/</code>") to serve as [=payment method identifiers=], while still
locating the actual [=payment method manifest=] at a different URL.
For an example [=payment method=] AlicePay, with [=payment method identifier=]
"<code>https://alicepay.com/</code>", a user agent would issue a GET request to that
[=payment method identifier=] URL as follows:
<pre>
GET / HTTP/2
Host: alicepay.com
User-Agent: Mellblomenator/9000
</pre>
The server could then either respond with
<pre>
HTTP/2 204
Link: </pay/payment-manifest.json>; rel="payment-method-manifest"
</pre>
to redirect the user agent to "<code>https://alicepay.com/pay/payment-manifest.json</code>", or
could respond with the JSON contents of the payment method manifest directly in a
<code>200</code>-status response.
<h3 id="manifest-example">Example manifest file</h3>
Continuing our example from [[#accessing]], the AlicePay [=payment method=] could provide the
following [=payment method manifest=] file at
<code>https://alicepay.com/pay/payment-manifest.json</code>:
<pre highlight="json">
{
"default_applications": ["app/webappmanifest.json"],
"supported_origins": [
"https://bobpay.xyz",
"https://alicepay.friendsofalice.example"
]
}
</pre>
This indicates that, if the user agent does not have a payment app for AlicePay installed, it
can locate one by consulting the [=web app manifest=] at
"<code>https://alicepay.com/pay/app/webappmanifest.json</code>".
It also indicates that, apart from this default payment app, AlicePay also allows payment apps
hosted at the two indicated [=origins=] to be used for AlicePay. This means that if the user agent
ever encounters payment apps hosted at those origins claiming support for AlicePay, it can allow
them to act as payment apps for the AlicePay [=payment method=].
The manifest file could also omit the "<code>supported_origins</code>" key, if no third-party
payment apps are supported for the [=payment method=] in question.
</div>
<h2 id="format">Manifest format</h2>
A <dfn export local-lt="valid|validity">valid payment method manifest</dfn> file is a [=UTF-8=]
encoded file containing contents parseable as a JSON object. The resulting JSON object must contain
at most two items, with the possible keys "<code>default_applications</code>" and
"<code>supported_origins</code>".
The value of the <code>default_applications</code> key, if present, must be a non-empty JSON array.
Each item in the array must be a [=valid URL string=] such that the resulting [=URL=]'s
[=url/scheme=], when parsed against the URL of the payment method manifest, is "<code>https</code>".
The value of the <code>supported_origins</code> key, if present, must be a non-empty JSON array.
Each item in the array must be an
[=absolute-URL string=] that represents an HTTPS [=origin=]. Formally, the string must be equal to
the [=serialization of an origin|serialization=] of the resulting parsed [=URL=]'s [=url/origin=].
Web developers must ensure that all of their [=payment method manifests=] are [=valid=].
<p class="note">As with all conformance requirements on the contents of files, these are
web-developer facing, and not implementer-facing. The exact processing model (given in
[[#processing-model]]) governs how implementers process all [=payment method manifest=] files,
including invalid ones.</p>
<div class="example" id="example-invalid-manifest">
The following [=payment method manifest=] is not [=valid=], but the currently-specified processing
model algorithms will still accept it:
<pre highlight="json">
{
"default_applications": ["app/webappmanifest.json"],
"created_by": "Alice",
"created_in": "Wonderland"
}
</pre>
This could change in the future, for example if the processing model later expands to define a
meaning for a new standard "<code>created_by</code>" key that requires it to be an object instead
of a string. To avoid situations like this, web developers are best served by ensuring
[=validity=] of their [=payment method manifests=], and thus avoiding any unpleasant surprises.
</div>
<h2 id="processing-model">Processing model</h2>
<h3 id="monkeypatch">Modifications to <cite>Payment Request API</cite></h3>
This specification integrates with the rest of the Payment Request ecosystem by modifying the
{{PaymentRequest/PaymentRequest(methodData, details, options)}} constructor. It adds the following
steps, before the algorithm completes and returns the newly-constructed {{PaymentRequest}} object.
In what follows, let |request| be the {{PaymentRequest}} instance being constructed.
[[!PAYMENT-REQUEST]]
1. Let |identifiers| be a list consisting of the first [=tuple/item=] of each pair in
|request|.[=[[serializedMethodData]]=].
1. Let |client| be the [=current settings object=].
1. [=Ingest payment method manifests=] given |identifiers| and |client|.
These steps are what kicks off the whole process. The rest of [[#processing-model]] is concerned
with defining how this process eventually leads to new payment apps being available for the user.
Issue: As this specification gains multi-implementer interest, we anticipate moving this section
into the <cite>Payment Request API</cite> specification itself, instead of maintaining a monkeypatch
here.
<h3 id="ingest">Ingesting payment method manifests</h3>
Given a list of [=payment method identifiers=] |identifiers|, as well as an
[=environment settings object=] |client|, the user agent may run the following steps, to
<dfn export>ingest payment method manifests</dfn>:
1. [=Fetch payment method manifests=], given |identifiers| and |client|, and wait for this to
asynchronously complete with |manifestsMap|. If the result is failure, return.
1. [=list/For each=] |identifierURL| → (|responseURL|, |manifest|) of |manifestsMap|:
1. Let |parsed| be the result of
[=validate and parse the payment method manifest|validating and parsing=] |manifest| given
|responseURL|. If this returns failure, [=iteration/continue=].
1. [=set/For each=] |webAppManifestURL| in |parsed|'s [=parsed payment method manifest/default
applications=]:
1. [=fetch the web app manifest for a default payment app|Fetch the web app manifest=] at
|webAppManifestURL| given |identifierURL| and |client|, and wait for it to asynchronously
complete with |webAppManifestString|. If the result is failure, [=iteration/continue=].
1. Let |webAppManifest| be the result of running the [=steps for processing a web app manifest=]
given |webAppManifestString|.
<p class="note">The [=steps for processing a web app manifest=] are very forgiving and will
return empty objects or objects missing crucial fields instead of failing. User agents will
need to separately validate the [=processed web app manifest=] to ensure it contains enough
data for their purposes in the next step.</p>
1. In a user-agent-specific way, use the resulting [=processed web app manifest=]
|webAppManifest| to install any applicable payment apps for the [=payment method=]
identified by |identifier|.
<p class="note">In the future, the plan is for there to be a user-agent-independent way to
use the resulting [=processed web app manifest=], by consulting its
<code>serviceworker</code> field and using that to install a web-based payment app
conforming to the <cite>Payment Handler API</cite> specification. [[PAYMENT-HANDLER]]</p>
1. Associate the [=parsed payment method manifest/supported origins=] to |identifier| so that the
user agent can use it in the future to determine what third-party payment apps can be
displayed for the [=payment method=] identified by |identifier|.
<h3 id="fetch-pmm">Fetching payment method manifests</h3>
To <dfn export>fetch payment method manifests</dfn>, given a [=list=] of [=JavaScript strings=]
|supportedMethods| and an [=environment settings object=] |client|, perform the following steps.
This algorithm will asynchronously complete with a [=map=] (possibly empty) from [=URLs=] to
([=URL=], [=byte sequence=]) [=tuples=], mapping [=payment method identifiers=] to the [=URL=] and
contents of the corresponding manifest.
1. Let |identifierURLs| be an empty [=list=].
1. [=list/For each=] |string| of |supportedMethods|:
1. Let |identifierURL| be the result of [=basic URL parser|basic URL parsing=] |string|. If the
result is failure, [=iteration/continue=].
<p class="note">The result will be failure for any [=payment method identifier=] that is not a
[=URL-based payment method identifier=], i.e. for a
[=standardized payment method identifier=].</p>
1. If [=validate a URL-based payment method identifier|validating=] |identifierURL| returns false,
[=iteration/continue=].
1. Optionally, [=iteration/continue=].
<p class="note">This step allows implementations to skip any of the provided [=payment method
identifiers=] for user-agent-specific reasons. [[#security]] discusses some reasons why user
agents might prefer to only ingest certain identifiers.</p>
1. [=list/Append=] |identifierURL| to |identifierURLs|.
1. Let |manifestsMap| be an empty [=map=].
1. [=list/For each=] |identifierURL| of |identifierURLs|:
1. Let |manifestURLString| be null.
1. Let |identifierRequest| be a new [=request=] whose [=request/method=] is `<code>GET</code>`,
[=request/url=] is |identifierURL|, [=request/client=] is |client|, [=request/mode=] is
"<code>cors</code>", [=request/credentials mode=] is "<code>omit</code>",
[=request/redirect mode=] is "<code>error</code>", and [=request/referrer policy=] is
"<code>strict-origin-when-cross-origin</code>".
1. Let |identifierFetchController| be the result of [=fetching=] |identifierRequest|, with
<i>[=fetch/processResponse=]</i> set to the following steps given [=response=]
|identifierResponse|:
1. If |identifierResponse| is a [=network error=] or |identifierResponse|'s [=response/status=]
is not an [=ok status=], abort these steps.
1. Let |linkHeaders| be the result of
[=extract header list values|extracting header list values=] given `<code>Link</code>` and
|identifierResponse|'s [=response/header list=].
1. [=list/For each=] |linkHeader| of |linkHeaders|:
1. Parse |linkHeader| according to the <code>link-value</code> production. If it cannot be
parsed, [=iteration/continue=]. [[!RFC8288]]
1. If the parsed header contains a parameter whose name is an [=ASCII case-insensitive=] match
for the string "<code>rel</code>" and whose value is an [=ASCII case-insensitive=] match
for the string "<code>payment-method-manifest</code>", then set |manifestURLString| to the
string given by the <code>URI-Reference</code> production in the parsed header, and
[=iteration/break=].
1. If |manifestURLString| is not null, then:
1. [=fetch controller/Terminate=] |identifierFetchController| (since the
[=response/body=] will not be needed).
1. Let |manifestURL| be the result of [=basic URL parser|basic URL parsing=]
|manifestURLString| with base URL given by |identifierResponse|'s [=response/URL=]. If the
result is failure, then abort these steps.
1. If |manifestURL|'s [=url/scheme=] is not "<code>https</code>", [=iteration/continue=].
1. Let |manifestRequest| be a new [=request=] whose [=request/URL=] is |manifestURL|,
[=request/client=] is |client|, [=request/referrer=] is |identifierResponse|'s
[=response/URL=], [=request/mode=] is "<code>cors</code>", [=request/credentials mode=] is
"<code>omit</code>", and [=request/redirect mode=] is "<code>error</code>".
1. [=Fetch=] |manifestRequest|, with <i>[=fetch/processResponseConsumeBody=]</i> set to the
following steps given |manifestResponse| and |manifestBody|:
1. If |manifestResponse|'s [=response/status=] is not an [=ok status=], then abort these
steps.
1. If |manifestBody| is null or failure, then abort these steps.
1. [=map/Set=] |manifestsMap|[|identifierURL|] to (|response|'s [=response/URL=],
|manifestBody|).
and with <i>[=fetch/processResponseConsumeBody=]</i> set to the following steps given
|identifierResponse| and |identifierBody|:
1. If |manifestURLString| is not null, then abort these steps.
1. If |identifierResponse|'s [=response/status=] is not an [=ok status=], then abort these
steps.
1. If |identifierBody| is null or failure, then abort these steps.
1. [=map/Set=] |manifestsMap|[|identifierURL|] to (|identifierResponse|'s [=response/URL=],
|identifierBody|).
1. Once all ongoing [=fetch=] algorithms initiated by the above steps are complete, including the
specified <i>[=fetch/processResponse=]</i> and <i>[=fetch/processResponseConsumeBody=]</i> steps,
asynchronously complete this algorithm with |manifestsMap|.
<h3 id="validate-and-parse">Validating and parsing payment method manifests</h3>
A <dfn export>parsed payment method manifest</dfn> is a [=struct=] containing two fields:
: <dfn export for="parsed payment method manifest">default applications</dfn>
:: An [=ordered set=] of [=URLs=], possibly empty
: <dfn export for="parsed payment method manifest">supported origins</dfn>
:: An [=ordered set=] of [=origins=]
To <dfn export lt="validate and parse the payment method manifest">validate and parse</dfn> a
[=byte sequence=] |bytes| purporting to contain a payment method manifest, given a [=URL=] |url|,
perform the following steps. The result will either be a [=parsed payment method manifest=], or
failure.
1. Let |string| be the result of [=UTF-8 decode|UTF-8 decoding=] |bytes|.
1. Let |parsed| be the result of [=parse JSON into Infra values|parsing JSON into Infra values=]
given |string|. If this throws an exception, return failure.
1. If |parsed| is not an [=ordered map=], return failure.
1. Let |defaultApps| be an empty [=ordered set=].
1. If |parsed|["<code>default_applications</code>"] [=map/exists=]:
1. Let |defaultAppsValue| be |parsed|["<code>default_applications</code>"].
1. If |defaultAppsValue| is not a [=list=], return failure.
1. If the [=list/size=] of |defaultAppsValue| is 0, return failure.
1. [=list/For each=] |defaultApp| in |defaultAppsValue|:
1. If |defaultApp| is not a string, return failure.
1. Let |defaultAppURL| be the result of [=basic URL parser|basic URL parsing=]
|defaultApp|, given the base URL |url|. If the result is failure, return failure.
1. If |defaultAppURL|'s [=url/scheme=] is not "<code>https</code>", return failure.
1. [=set/Append=] |defaultAppURL| to |defaultApps|.
1. Let |supportedOrigins| be an empty [=ordered set=].
1. If |parsed|["<code>supported_origins</code>"] [=map/exists=]:
1. Let |supportedOriginsValue| be |parsed|["<code>supported_origins</code>"].
1. If |supportedOriginsValue| is not a [=list=], return failure.
1. If the [=list/size=] of |supportedOriginsValue| is 0, return failure.
1. [=list/For each=] |supportedOrigin| in |supportedOriginsValue|:
1. If |supportedOrigin| is not a string, return failure.
1. Let |supportedOriginURL| be the result of [=basic URL parser|basic URL parsing=]
|supportedOrigin|. If the result is failure, return failure.
1. If |supportedOriginURL|'s [=url/scheme=] is not "<code>https</code>", return failure.
1. If |supportedOriginURL|'s [=url/username=] or [=url/password=] are not the empty string,
return failure.
1. If |supportedOriginURL|'s [=url/path=]'s [=list/size=] is not 0, return failure.
1. If |supportedOriginURL|'s [=url/query=] or [=url/fragment=] are not null, return failure.
1. [=set/Append=] |supportedOriginURL|'s [=url/origin=] to |supportedOrigins|.
1. Return a new [=parsed payment method manifest=] with
[=parsed payment method manifest/default applications=] given by |defaultApps| and
[=parsed payment method manifest/supported origins=] given by |supportedOrigins|.
<div class="note">
Empty arrays for "<code>default_applications</code>" or "<code>supported_origins</code>"
will cause parsing to fail. That is, this is not a [=valid payment method manifest=], and will
be rejected by the above algorithm:
<pre highlight="json">
{
"default_applications": ["https://alicepay.com/pay/app/webappmanifest.json"],
"supported_origins": []
}
</pre>
</div>
<h3 id="fetch-wam">Fetching web app manifests</h3>
Because the determination of payment apps happens independent of any embedding HTML document,
the procedure for obtaining a [=web app manifest=] that gives information about a default payment
app is different from the usual steps for <code>link rel="manifest"</code>.
To <dfn export>fetch the web app manifest for a default payment app</dfn>, given a [=URL=] |url|, a
URL |referrer|, and an [=environment settings object=] |client|, perform the following steps. This
algorithm will asynchronously complete with either a [=scalar value string=] or failure.
1. Let |request| be a new [=request=] whose [=request/url=] is |url|, [=request/client=] is
|client|, [=request/referrer=] is |referrer|, [=request/mode=] is "<code>cors</code>",
[=request/credentials mode=] is "<code>omit</code>", and [=request/redirect mode=] is
"<code>error</code>".
1. [=Fetch=] |request|, with <i>[=fetch/processResponseConsumeBody=]</i> set the following steps
given |response| and |body|:
1. If |response| [=response/status=] is not an [=ok status=], asynchronously complete this
algorithm with failure.
1. If |body| is null or failure, asynchronously complete this algorithm with failure.
1. Asynchronously complete this algorithm with the result of [=UTF-8 decode|UTF-8 decoding=]
|body|.
<h2 id="security">Security and privacy considerations</h2>
<h3 id="revealing-to-providers">Revealing user activity to payment providers</h3>
[=ingest payment method manifests|Ingesting payment method manifests=] might reveal information
to a payment service about the activity of an end user. For example, a [=payment method=] that is
only supported on one website might allow that payment provider to discover the IP addresses of
users who visit that website.
One way to mitigate this is to [=fetch payment method manifests|fetch the manifests=] only for the
payment apps that the user has installed or has explicitly expressed interest in. This confines
the risk to only sharing the user's IP address with those parties.
Another important mitigation is already built in to the spec, by setting the appropriate
[=request/referrer policy=] and [=request/referrer=] on the various fetches performed here. These
requirements ensure that only the server that controls the [=URL-based payment method
identifier|identifier URL=] will see the user's activity, and even then will only see the [=origin=]
of the web page that is creating the {{PaymentRequest}} object (instead of seeing the full URL).
<h3 id="development-environments">Considerations for development environments</h3>
In development situations, a user agent might lower the security requirements of this specification
to ease testing. For example, the requirements in the [=fetch payment method manifests=] and
[=validate and parse the payment method manifest=] algorithms requiring a "<code>https</code>"
[=url/scheme=] might be dropped in a development environment. In such situations, the user agent is
intentionally not complying with this specification, and should notify the user of the reduced
security.
<h3 id="matching-payment-apps">Matching payment apps</h3>
Issue(11):
<h2 id="iana">IANA considerations</h2>
<h3 id="payment-method-manifest-link">The <code>payment-method-manifest</code> link relation</h3>
This registration is for community review and will be submitted to the IESG for review, approval,
and registration with IANA.
: Relation name
:: payment-method-manifest
: Description
:: Links to a payment method manifest, which describes a specific [=payment method=] within the Web
Payments ecosystem.
: Reference
:: <a href="https://w3c.github.io/payment-method-manifest/">https://w3c.github.io/payment-method-manifest/</a>
: Notes
:: See [[#fetch-pmm]] for the specific manner in which such links are expected to be fetched, and
[[#ingest]] for the larger context in which they are used.
<h2 id="acknowledgments" class="no-num">Acknowledgments</h2>
[[#processing-model]] is based heavily on algorithms originally outlined by Rouslan Solomakhin.
Thanks to
Andrew Betts,
Anne van Kesteren,
Ian Jacobs,
L. David Baron,
Marcos Cáceres, and
Rouslan Solomakhin for their contributions to making this specification awesome!